You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
383 lines
10 KiB
383 lines
10 KiB
/* $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 |
|
|
|
|