mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2024-12-03 19:00:50 -08:00
384 lines
10 KiB
C
384 lines
10 KiB
C
/* $Id: natpmp.c,v 1.20 2015/05/27 12:43:15 nanard Exp $ */
|
|
/* libnatpmp
|
|
Copyright (c) 2007-2015, Thomas BERNARD
|
|
All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions are met:
|
|
|
|
* Redistributions of source code must retain the above copyright notice,
|
|
this list of conditions and the following disclaimer.
|
|
* Redistributions in binary form must reproduce the above copyright notice,
|
|
this list of conditions and the following disclaimer in the documentation
|
|
and/or other materials provided with the distribution.
|
|
* The name of the author may not be used to endorse or promote products
|
|
derived from this software without specific prior written permission.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
#ifdef __linux__
|
|
#define _BSD_SOURCE 1
|
|
#endif
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#if !defined(_MSC_VER)
|
|
#include <sys/time.h>
|
|
#endif
|
|
#ifdef WIN32
|
|
#include <errno.h>
|
|
#include <winsock2.h>
|
|
#include <ws2tcpip.h>
|
|
#include <io.h>
|
|
#ifndef EWOULDBLOCK
|
|
#define EWOULDBLOCK WSAEWOULDBLOCK
|
|
#endif
|
|
#ifndef ECONNREFUSED
|
|
#define ECONNREFUSED WSAECONNREFUSED
|
|
#endif
|
|
#include "wingettimeofday.h"
|
|
#define gettimeofday natpmp_gettimeofday
|
|
#else
|
|
#include <errno.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#define closesocket close
|
|
#endif
|
|
#include "natpmp.h"
|
|
#include "getgateway.h"
|
|
#include <stdio.h>
|
|
|
|
LIBSPEC int initnatpmp(natpmp_t * p, int forcegw, in_addr_t forcedgw)
|
|
{
|
|
#ifdef WIN32
|
|
u_long ioctlArg = 1;
|
|
#else
|
|
int flags;
|
|
#endif
|
|
struct sockaddr_in addr;
|
|
if(!p)
|
|
return NATPMP_ERR_INVALIDARGS;
|
|
memset(p, 0, sizeof(natpmp_t));
|
|
p->s = socket(PF_INET, SOCK_DGRAM, 0);
|
|
if(p->s < 0)
|
|
return NATPMP_ERR_SOCKETERROR;
|
|
#ifdef WIN32
|
|
if(ioctlsocket(p->s, FIONBIO, &ioctlArg) == SOCKET_ERROR)
|
|
return NATPMP_ERR_FCNTLERROR;
|
|
#else
|
|
if((flags = fcntl(p->s, F_GETFL, 0)) < 0)
|
|
return NATPMP_ERR_FCNTLERROR;
|
|
if(fcntl(p->s, F_SETFL, flags | O_NONBLOCK) < 0)
|
|
return NATPMP_ERR_FCNTLERROR;
|
|
#endif
|
|
|
|
if(forcegw) {
|
|
p->gateway = forcedgw;
|
|
} else {
|
|
if(getdefaultgateway(&(p->gateway)) < 0)
|
|
return NATPMP_ERR_CANNOTGETGATEWAY;
|
|
}
|
|
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_port = htons(NATPMP_PORT);
|
|
addr.sin_addr.s_addr = p->gateway;
|
|
if(connect(p->s, (struct sockaddr *)&addr, sizeof(addr)) < 0)
|
|
return NATPMP_ERR_CONNECTERR;
|
|
return 0;
|
|
}
|
|
|
|
LIBSPEC int closenatpmp(natpmp_t * p)
|
|
{
|
|
if(!p)
|
|
return NATPMP_ERR_INVALIDARGS;
|
|
if(closesocket(p->s) < 0)
|
|
return NATPMP_ERR_CLOSEERR;
|
|
return 0;
|
|
}
|
|
|
|
int sendpendingrequest(natpmp_t * p)
|
|
{
|
|
int r;
|
|
/* struct sockaddr_in addr;*/
|
|
if(!p)
|
|
return NATPMP_ERR_INVALIDARGS;
|
|
/* memset(&addr, 0, sizeof(addr));
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_port = htons(NATPMP_PORT);
|
|
addr.sin_addr.s_addr = p->gateway;
|
|
r = (int)sendto(p->s, p->pending_request, p->pending_request_len, 0,
|
|
(struct sockaddr *)&addr, sizeof(addr));*/
|
|
r = (int)send(p->s, (const char *)p->pending_request, p->pending_request_len, 0);
|
|
return (r<0) ? NATPMP_ERR_SENDERR : r;
|
|
}
|
|
|
|
int sendnatpmprequest(natpmp_t * p)
|
|
{
|
|
int n;
|
|
if(!p)
|
|
return NATPMP_ERR_INVALIDARGS;
|
|
/* TODO : check if no request is already pending */
|
|
p->has_pending_request = 1;
|
|
p->try_number = 1;
|
|
n = sendpendingrequest(p);
|
|
gettimeofday(&p->retry_time, NULL); // check errors !
|
|
p->retry_time.tv_usec += 250000; /* add 250ms */
|
|
if(p->retry_time.tv_usec >= 1000000) {
|
|
p->retry_time.tv_usec -= 1000000;
|
|
p->retry_time.tv_sec++;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
LIBSPEC int getnatpmprequesttimeout(natpmp_t * p, struct timeval * timeout)
|
|
{
|
|
struct timeval now;
|
|
if(!p || !timeout)
|
|
return NATPMP_ERR_INVALIDARGS;
|
|
if(!p->has_pending_request)
|
|
return NATPMP_ERR_NOPENDINGREQ;
|
|
if(gettimeofday(&now, NULL) < 0)
|
|
return NATPMP_ERR_GETTIMEOFDAYERR;
|
|
timeout->tv_sec = p->retry_time.tv_sec - now.tv_sec;
|
|
timeout->tv_usec = p->retry_time.tv_usec - now.tv_usec;
|
|
if(timeout->tv_usec < 0) {
|
|
timeout->tv_usec += 1000000;
|
|
timeout->tv_sec--;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
LIBSPEC int sendpublicaddressrequest(natpmp_t * p)
|
|
{
|
|
if(!p)
|
|
return NATPMP_ERR_INVALIDARGS;
|
|
//static const unsigned char request[] = { 0, 0 };
|
|
p->pending_request[0] = 0;
|
|
p->pending_request[1] = 0;
|
|
p->pending_request_len = 2;
|
|
// TODO: return 0 instead of sizeof(request) ??
|
|
return sendnatpmprequest(p);
|
|
}
|
|
|
|
LIBSPEC int sendnewportmappingrequest(natpmp_t * p, int protocol,
|
|
uint16_t privateport, uint16_t publicport,
|
|
uint32_t lifetime)
|
|
{
|
|
if(!p || (protocol!=NATPMP_PROTOCOL_TCP && protocol!=NATPMP_PROTOCOL_UDP))
|
|
return NATPMP_ERR_INVALIDARGS;
|
|
p->pending_request[0] = 0;
|
|
p->pending_request[1] = protocol;
|
|
p->pending_request[2] = 0;
|
|
p->pending_request[3] = 0;
|
|
/* break strict-aliasing rules :
|
|
*((uint16_t *)(p->pending_request + 4)) = htons(privateport); */
|
|
p->pending_request[4] = (privateport >> 8) & 0xff;
|
|
p->pending_request[5] = privateport & 0xff;
|
|
/* break stric-aliasing rules :
|
|
*((uint16_t *)(p->pending_request + 6)) = htons(publicport); */
|
|
p->pending_request[6] = (publicport >> 8) & 0xff;
|
|
p->pending_request[7] = publicport & 0xff;
|
|
/* break stric-aliasing rules :
|
|
*((uint32_t *)(p->pending_request + 8)) = htonl(lifetime); */
|
|
p->pending_request[8] = (lifetime >> 24) & 0xff;
|
|
p->pending_request[9] = (lifetime >> 16) & 0xff;
|
|
p->pending_request[10] = (lifetime >> 8) & 0xff;
|
|
p->pending_request[11] = lifetime & 0xff;
|
|
p->pending_request_len = 12;
|
|
return sendnatpmprequest(p);
|
|
}
|
|
|
|
LIBSPEC int readnatpmpresponse(natpmp_t * p, natpmpresp_t * response)
|
|
{
|
|
unsigned char buf[16];
|
|
struct sockaddr_in addr;
|
|
socklen_t addrlen = sizeof(addr);
|
|
int n;
|
|
if(!p)
|
|
return NATPMP_ERR_INVALIDARGS;
|
|
n = recvfrom(p->s, (char *)buf, sizeof(buf), 0,
|
|
(struct sockaddr *)&addr, &addrlen);
|
|
if(n<0)
|
|
#ifdef WIN32
|
|
switch(WSAGetLastError()) {
|
|
#else
|
|
switch(errno) {
|
|
#endif
|
|
/*case EAGAIN:*/
|
|
case EWOULDBLOCK:
|
|
n = NATPMP_TRYAGAIN;
|
|
break;
|
|
case ECONNREFUSED:
|
|
n = NATPMP_ERR_NOGATEWAYSUPPORT;
|
|
break;
|
|
default:
|
|
n = NATPMP_ERR_RECVFROM;
|
|
}
|
|
/* check that addr is correct (= gateway) */
|
|
else if(addr.sin_addr.s_addr != p->gateway)
|
|
n = NATPMP_ERR_WRONGPACKETSOURCE;
|
|
else {
|
|
response->resultcode = ntohs(*((uint16_t *)(buf + 2)));
|
|
response->epoch = ntohl(*((uint32_t *)(buf + 4)));
|
|
if(buf[0] != 0)
|
|
n = NATPMP_ERR_UNSUPPORTEDVERSION;
|
|
else if(buf[1] < 128 || buf[1] > 130)
|
|
n = NATPMP_ERR_UNSUPPORTEDOPCODE;
|
|
else if(response->resultcode != 0) {
|
|
switch(response->resultcode) {
|
|
case 1:
|
|
n = NATPMP_ERR_UNSUPPORTEDVERSION;
|
|
break;
|
|
case 2:
|
|
n = NATPMP_ERR_NOTAUTHORIZED;
|
|
break;
|
|
case 3:
|
|
n = NATPMP_ERR_NETWORKFAILURE;
|
|
break;
|
|
case 4:
|
|
n = NATPMP_ERR_OUTOFRESOURCES;
|
|
break;
|
|
case 5:
|
|
n = NATPMP_ERR_UNSUPPORTEDOPCODE;
|
|
break;
|
|
default:
|
|
n = NATPMP_ERR_UNDEFINEDERROR;
|
|
}
|
|
} else {
|
|
response->type = buf[1] & 0x7f;
|
|
if(buf[1] == 128)
|
|
//response->publicaddress.addr = *((uint32_t *)(buf + 8));
|
|
response->pnu.publicaddress.addr.s_addr = *((uint32_t *)(buf + 8));
|
|
else {
|
|
response->pnu.newportmapping.privateport = ntohs(*((uint16_t *)(buf + 8)));
|
|
response->pnu.newportmapping.mappedpublicport = ntohs(*((uint16_t *)(buf + 10)));
|
|
response->pnu.newportmapping.lifetime = ntohl(*((uint32_t *)(buf + 12)));
|
|
}
|
|
n = 0;
|
|
}
|
|
}
|
|
return n;
|
|
}
|
|
|
|
int readnatpmpresponseorretry(natpmp_t * p, natpmpresp_t * response)
|
|
{
|
|
int n;
|
|
if(!p || !response)
|
|
return NATPMP_ERR_INVALIDARGS;
|
|
if(!p->has_pending_request)
|
|
return NATPMP_ERR_NOPENDINGREQ;
|
|
n = readnatpmpresponse(p, response);
|
|
if(n<0) {
|
|
if(n==NATPMP_TRYAGAIN) {
|
|
struct timeval now;
|
|
gettimeofday(&now, NULL); // check errors !
|
|
if(timercmp(&now, &p->retry_time, >=)) {
|
|
int delay, r;
|
|
if(p->try_number >= 9) {
|
|
return NATPMP_ERR_NOGATEWAYSUPPORT;
|
|
}
|
|
/*printf("retry! %d\n", p->try_number);*/
|
|
delay = 250 * (1<<p->try_number); // ms
|
|
/*for(i=0; i<p->try_number; i++)
|
|
delay += delay;*/
|
|
p->retry_time.tv_sec += (delay / 1000);
|
|
p->retry_time.tv_usec += (delay % 1000) * 1000;
|
|
if(p->retry_time.tv_usec >= 1000000) {
|
|
p->retry_time.tv_usec -= 1000000;
|
|
p->retry_time.tv_sec++;
|
|
}
|
|
p->try_number++;
|
|
r = sendpendingrequest(p);
|
|
if(r<0)
|
|
return r;
|
|
}
|
|
}
|
|
} else {
|
|
p->has_pending_request = 0;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
#ifdef ENABLE_STRNATPMPERR
|
|
LIBSPEC const char * strnatpmperr(int r)
|
|
{
|
|
const char * s;
|
|
switch(r) {
|
|
case NATPMP_ERR_INVALIDARGS:
|
|
s = "invalid arguments";
|
|
break;
|
|
case NATPMP_ERR_SOCKETERROR:
|
|
s = "socket() failed";
|
|
break;
|
|
case NATPMP_ERR_CANNOTGETGATEWAY:
|
|
s = "cannot get default gateway ip address";
|
|
break;
|
|
case NATPMP_ERR_CLOSEERR:
|
|
#ifdef WIN32
|
|
s = "closesocket() failed";
|
|
#else
|
|
s = "close() failed";
|
|
#endif
|
|
break;
|
|
case NATPMP_ERR_RECVFROM:
|
|
s = "recvfrom() failed";
|
|
break;
|
|
case NATPMP_ERR_NOPENDINGREQ:
|
|
s = "no pending request";
|
|
break;
|
|
case NATPMP_ERR_NOGATEWAYSUPPORT:
|
|
s = "the gateway does not support nat-pmp";
|
|
break;
|
|
case NATPMP_ERR_CONNECTERR:
|
|
s = "connect() failed";
|
|
break;
|
|
case NATPMP_ERR_WRONGPACKETSOURCE:
|
|
s = "packet not received from the default gateway";
|
|
break;
|
|
case NATPMP_ERR_SENDERR:
|
|
s = "send() failed";
|
|
break;
|
|
case NATPMP_ERR_FCNTLERROR:
|
|
s = "fcntl() failed";
|
|
break;
|
|
case NATPMP_ERR_GETTIMEOFDAYERR:
|
|
s = "gettimeofday() failed";
|
|
break;
|
|
case NATPMP_ERR_UNSUPPORTEDVERSION:
|
|
s = "unsupported nat-pmp version error from server";
|
|
break;
|
|
case NATPMP_ERR_UNSUPPORTEDOPCODE:
|
|
s = "unsupported nat-pmp opcode error from server";
|
|
break;
|
|
case NATPMP_ERR_UNDEFINEDERROR:
|
|
s = "undefined nat-pmp server error";
|
|
break;
|
|
case NATPMP_ERR_NOTAUTHORIZED:
|
|
s = "not authorized";
|
|
break;
|
|
case NATPMP_ERR_NETWORKFAILURE:
|
|
s = "network failure";
|
|
break;
|
|
case NATPMP_ERR_OUTOFRESOURCES:
|
|
s = "nat-pmp server out of resources";
|
|
break;
|
|
default:
|
|
s = "Unknown libnatpmp error";
|
|
}
|
|
return s;
|
|
}
|
|
#endif
|
|
|