/*
This file is part of GNUnet.
Copyright (C) 2007-2017 GNUnet e.V.
GNUnet is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License,
or (at your option) any later version.
GNUnet is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
/**
* @author Christian Grothoff
* @author Milan Bouchet-Valat
*
* @file nat/nat_api.c
* Service for handling UPnP and NAT-PMP port forwarding
* and external IP address retrieval
*/
#include "platform.h"
#include "gnunet_nat_service.h"
#include "nat.h"
#include "nat_stun.h"
/**
* Entry in DLL of addresses of this peer.
*/
struct AddrEntry
{
/**
* DLL.
*/
struct AddrEntry *next;
/**
* DLL.
*/
struct AddrEntry *prev;
/**
* Address class of the address.
*/
enum GNUNET_NAT_AddressClass ac;
/**
* Number of bytes that follow.
*/
socklen_t addrlen;
};
/**
* Handle for active NAT registrations.
*/
struct GNUNET_NAT_Handle
{
/**
* Configuration we use.
*/
const struct GNUNET_CONFIGURATION_Handle *cfg;
/**
* Message queue for communicating with the NAT service.
*/
struct GNUNET_MQ_Handle *mq;
/**
* Our registration message.
*/
struct GNUNET_MessageHeader *reg;
/**
* Head of address DLL.
*/
struct AddrEntry *ae_head;
/**
* Tail of address DLL.
*/
struct AddrEntry *ae_tail;
/**
* Function to call when our addresses change.
*/
GNUNET_NAT_AddressCallback address_callback;
/**
* Function to call when another peer requests connection reversal.
*/
GNUNET_NAT_ReversalCallback reversal_callback;
/**
* Closure for the various callbacks.
*/
void *callback_cls;
/**
* Task scheduled to reconnect to the service.
*/
struct GNUNET_SCHEDULER_Task *reconnect_task;
/**
* How long to wait until we reconnect.
*/
struct GNUNET_TIME_Relative reconnect_delay;
};
/**
* Task to connect to the NAT service.
*
* @param cls our `struct GNUNET_NAT_Handle *`
*/
static void
do_connect (void *cls);
/**
* Task to connect to the NAT service.
*
* @param nh handle to reconnect
*/
static void
reconnect (struct GNUNET_NAT_Handle *nh)
{
struct AddrEntry *ae;
if (NULL != nh->mq)
{
GNUNET_MQ_destroy (nh->mq);
nh->mq = NULL;
}
while (NULL != (ae = nh->ae_head))
{
GNUNET_CONTAINER_DLL_remove (nh->ae_head,
nh->ae_tail,
ae);
nh->address_callback (nh->callback_cls,
GNUNET_NO,
ae->ac,
(const struct sockaddr *) &ae[1],
ae->addrlen);
GNUNET_free (ae);
}
nh->reconnect_delay
= GNUNET_TIME_STD_BACKOFF (nh->reconnect_delay);
nh->reconnect_task
= GNUNET_SCHEDULER_add_delayed (nh->reconnect_delay,
&do_connect,
nh);
}
/**
* Check connection reversal request.
*
* @param cls our `struct GNUNET_NAT_Handle`
* @param crm the message
* @return #GNUNET_OK if @a crm is well-formed
*/
static int
check_connection_reversal_request (void *cls,
const struct GNUNET_NAT_ConnectionReversalRequestedMessage *crm)
{
if (ntohs (crm->header.size) !=
sizeof (*crm) +
sizeof (struct sockaddr_in) )
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
return GNUNET_OK;
}
/**
* Handle connection reversal request.
*
* @param cls our `struct GNUNET_NAT_Handle`
* @param crm the message
*/
static void
handle_connection_reversal_request (void *cls,
const struct GNUNET_NAT_ConnectionReversalRequestedMessage *crm)
{
struct GNUNET_NAT_Handle *nh = cls;
nh->reversal_callback (nh->callback_cls,
(const struct sockaddr *) &crm[1],
sizeof (struct sockaddr_in));
}
/**
* Check address change notification.
*
* @param cls our `struct GNUNET_NAT_Handle`
* @param acn the message
* @return #GNUNET_OK if @a crm is well-formed
*/
static int
check_address_change_notification (void *cls,
const struct GNUNET_NAT_AddressChangeNotificationMessage *acn)
{
size_t alen = ntohs (acn->header.size) - sizeof (*acn);
switch (alen)
{
case sizeof (struct sockaddr_in):
{
const struct sockaddr_in *s4
= (const struct sockaddr_in *) &acn[1];
if (AF_INET != s4->sin_family)
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
}
break;
case sizeof (struct sockaddr_in6):
{
const struct sockaddr_in6 *s6
= (const struct sockaddr_in6 *) &acn[1];
if (AF_INET6 != s6->sin6_family)
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
}
break;
default:
GNUNET_break (0);
return GNUNET_SYSERR;
}
return GNUNET_OK;
}
/**
* Handle connection reversal request.
*
* @param cls our `struct GNUNET_NAT_Handle`
* @param acn the message
*/
static void
handle_address_change_notification (void *cls,
const struct GNUNET_NAT_AddressChangeNotificationMessage *acn)
{
struct GNUNET_NAT_Handle *nh = cls;
size_t alen = ntohs (acn->header.size) - sizeof (*acn);
const struct sockaddr *sa = (const struct sockaddr *) &acn[1];
enum GNUNET_NAT_AddressClass ac;
struct AddrEntry *ae;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Received address change notification\n");
ac = (enum GNUNET_NAT_AddressClass) ntohl (acn->addr_class);
if (GNUNET_YES == ntohl (acn->add_remove))
{
ae = GNUNET_malloc (sizeof (*ae) + alen);
ae->ac = ac;
ae->addrlen = alen;
GNUNET_memcpy (&ae[1],
sa,
alen);
GNUNET_CONTAINER_DLL_insert (nh->ae_head,
nh->ae_tail,
ae);
}
else
{
for (ae = nh->ae_head; NULL != ae; ae = ae->next)
if ( (ae->addrlen == alen) &&
(0 == memcmp (&ae[1],
sa,
alen)) )
break;
if (NULL == ae)
{
GNUNET_break (0);
reconnect (nh);
return;
}
GNUNET_CONTAINER_DLL_remove (nh->ae_head,
nh->ae_tail,
ae);
GNUNET_free (ae);
}
nh->address_callback (nh->callback_cls,
ntohl (acn->add_remove),
ac,
sa,
alen);
}
/**
* Handle queue errors by reconnecting to NAT.
*
* @param cls the `struct GNUNET_NAT_Handle *`
* @param error details about the error
*/
static void
mq_error_handler (void *cls,
enum GNUNET_MQ_Error error)
{
struct GNUNET_NAT_Handle *nh = cls;
reconnect (nh);
}
/**
* Task to connect to the NAT service.
*
* @param cls our `struct GNUNET_NAT_Handle *`
*/
static void
do_connect (void *cls)
{
struct GNUNET_NAT_Handle *nh = cls;
struct GNUNET_MQ_MessageHandler handlers[] = {
GNUNET_MQ_hd_var_size (connection_reversal_request,
GNUNET_MESSAGE_TYPE_NAT_CONNECTION_REVERSAL_REQUESTED,
struct GNUNET_NAT_ConnectionReversalRequestedMessage,
nh),
GNUNET_MQ_hd_var_size (address_change_notification,
GNUNET_MESSAGE_TYPE_NAT_ADDRESS_CHANGE,
struct GNUNET_NAT_AddressChangeNotificationMessage,
nh),
GNUNET_MQ_handler_end ()
};
struct GNUNET_MQ_Envelope *env;
nh->reconnect_task = NULL;
nh->mq = GNUNET_CLIENT_connect (nh->cfg,
"nat",
handlers,
&mq_error_handler,
nh);
if (NULL == nh->mq)
{
reconnect (nh);
return;
}
env = GNUNET_MQ_msg_copy (nh->reg);
GNUNET_MQ_send (nh->mq,
env);
}
/**
* Attempt to enable port redirection and detect public IP address
* contacting UPnP or NAT-PMP routers on the local network. Use @a
* addr to specify to which of the local host's addresses should the
* external port be mapped. The port is taken from the corresponding
* sockaddr_in[6] field. The NAT module should call the given @a
* address_callback for any 'plausible' external address.
*
* @param cfg configuration to use
* @param config_section name of the configuration section for optionsx
* @param proto protocol this is about, IPPROTO_TCP or IPPROTO_UDP
* @param num_addrs number of addresses in @a addrs
* @param addrs list of local addresses packets should be redirected to
* @param addrlens actual lengths of the addresses in @a addrs
* @param address_callback function to call everytime the public IP address changes
* @param reversal_callback function to call if someone wants connection reversal from us,
* NULL if connection reversal is not supported
* @param callback_cls closure for callbacks
* @return NULL on error, otherwise handle that can be used to unregister
*/
struct GNUNET_NAT_Handle *
GNUNET_NAT_register (const struct GNUNET_CONFIGURATION_Handle *cfg,
const char *config_section,
uint8_t proto,
unsigned int num_addrs,
const struct sockaddr **addrs,
const socklen_t *addrlens,
GNUNET_NAT_AddressCallback address_callback,
GNUNET_NAT_ReversalCallback reversal_callback,
void *callback_cls)
{
struct GNUNET_NAT_Handle *nh;
struct GNUNET_NAT_RegisterMessage *rm;
size_t len;
size_t str_len;
char *off;
len = 0;
for (unsigned int i=0;i GNUNET_MAX_MESSAGE_SIZE - sizeof (*rm)) ||
(num_addrs > UINT16_MAX) )
{
GNUNET_break (0);
return NULL;
}
rm = GNUNET_malloc (sizeof (*rm) + len);
rm->header.size = htons (sizeof (*rm) + len);
rm->header.type = htons (GNUNET_MESSAGE_TYPE_NAT_REGISTER);
rm->flags = GNUNET_NAT_RF_NONE;
if (NULL != address_callback)
rm->flags |= GNUNET_NAT_RF_ADDRESSES;
if (NULL != reversal_callback)
rm->flags |= GNUNET_NAT_RF_REVERSAL;
rm->proto = proto;
rm->str_len = htons (str_len);
rm->num_addrs = htons ((uint16_t) num_addrs);
off = (char *) &rm[1];
for (unsigned int i=0;isa_family)
{
case AF_INET:
if (sizeof (struct sockaddr_in) != addrlens[i])
{
GNUNET_break (0);
GNUNET_free (rm);
return NULL;
}
break;
case AF_INET6:
if (sizeof (struct sockaddr_in6) != addrlens[i])
{
GNUNET_break (0);
GNUNET_free (rm);
return NULL;
}
break;
#if AF_UNIX
case AF_UNIX:
if (sizeof (struct sockaddr_un) != addrlens[i])
{
GNUNET_break (0);
GNUNET_free (rm);
return NULL;
}
break;
#endif
default:
GNUNET_break (0);
GNUNET_free (rm);
return NULL;
}
GNUNET_memcpy (off,
addrs[i],
addrlens[i]);
off += addrlens[i];
}
GNUNET_memcpy (off,
config_section,
str_len);
nh = GNUNET_new (struct GNUNET_NAT_Handle);
nh->reg = &rm->header;
nh->cfg = cfg;
nh->address_callback = address_callback;
nh->reversal_callback = reversal_callback;
nh->callback_cls = callback_cls;
do_connect (nh);
return nh;
}
/**
* Check if an incoming message is a STUN message.
*
* @param data the packet
* @param len the length of the packet in @a data
* @return #GNUNET_YES if @a data is a STUN packet,
* #GNUNET_NO if the packet is invalid (not a stun packet)
*/
static int
test_stun_packet (const void *data,
size_t len)
{
const struct stun_header *hdr;
const struct stun_attr *attr;
uint32_t advertised_message_size;
uint32_t message_magic_cookie;
/* On entry, 'len' is the length of the UDP payload. After the
* initial checks it becomes the size of unprocessed options,
* while 'data' is advanced accordingly.
*/
if (len < sizeof(struct stun_header))
{
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"STUN packet too short (only %d, wanting at least %d)\n",
(int) len,
(int) sizeof (struct stun_header));
return GNUNET_NO;
}
hdr = (const struct stun_header *) data;
/* Skip header as it is already in hdr */
len -= sizeof (struct stun_header);
data += sizeof (struct stun_header);
/* len as advertised in the message */
advertised_message_size = ntohs (hdr->msglen);
message_magic_cookie = ntohl (hdr->magic);
/* Compare if the cookie match */
if (STUN_MAGIC_COOKIE != message_magic_cookie)
{
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Invalid magic cookie for STUN\n");
return GNUNET_NO;
}
if (advertised_message_size > len)
{
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Scrambled STUN packet length (got %d, expecting %d)\n",
advertised_message_size,
(int)len);
return GNUNET_NO;
}
len = advertised_message_size;
while (len > 0)
{
if (len < sizeof (struct stun_attr))
{
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Attribute too short in STUN packet (got %d, expecting %d)\n",
(int) len,
(int) sizeof(struct stun_attr));
return GNUNET_NO;
}
attr = (const struct stun_attr *) data;
/* compute total attribute length */
advertised_message_size = ntohs (attr->len) + sizeof(struct stun_attr);
/* Check if we still have space in our buffer */
if (advertised_message_size > len)
{
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Inconsistent Attribute (length %d exceeds remaining msg len %d)\n",
advertised_message_size,
(int) len);
return GNUNET_NO;
}
data += advertised_message_size;
len -= advertised_message_size;
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"STUN Packet, msg %04x, length: %d\n",
ntohs (hdr->msgtype),
advertised_message_size);
return GNUNET_OK;
}
/**
* Handle an incoming STUN message. This function is useful as
* some GNUnet service may be listening on a UDP port and might
* thus receive STUN messages while trying to receive other data.
* In this case, this function can be used to process replies
* to STUN requests.
*
* The function does some basic sanity checks on packet size and
* content, try to extract a bit of information.
*
* At the moment this only processes BIND requests, and returns the
* externally visible address of the request to the rest of the
* NAT logic.
*
* @param nh handle to the NAT service
* @param sender_addr address from which we got @a data
* @param sender_addr_len number of bytes in @a sender_addr
* @param data the packet
* @param data_size number of bytes in @a data
* @return #GNUNET_OK on success
* #GNUNET_NO if the packet is not a STUN packet
* #GNUNET_SYSERR on internal error handling the packet
*/
int
GNUNET_NAT_stun_handle_packet (struct GNUNET_NAT_Handle *nh,
const struct sockaddr *sender_addr,
size_t sender_addr_len,
const void *data,
size_t data_size)
{
struct GNUNET_MQ_Envelope *env;
struct GNUNET_NAT_HandleStunMessage *hsn;
char *buf;
if (GNUNET_YES !=
test_stun_packet (data,
data_size))
return GNUNET_NO;
if (NULL == nh->mq)
return GNUNET_SYSERR;
env = GNUNET_MQ_msg_extra (hsn,
data_size + sender_addr_len,
GNUNET_MESSAGE_TYPE_NAT_HANDLE_STUN);
hsn->sender_addr_size = htons ((uint16_t) sender_addr_len);
hsn->payload_size = htons ((uint16_t) data_size);
buf = (char *) &hsn[1];
GNUNET_memcpy (buf,
sender_addr,
sender_addr_len);
buf += sender_addr_len;
GNUNET_memcpy (buf,
data,
data_size);
GNUNET_MQ_send (nh->mq,
env);
return GNUNET_OK;
}
/**
* Test if the given address is (currently) a plausible IP address for
* this peer. Mostly a convenience function so that clients do not
* have to explicitly track all IPs that the #GNUNET_NAT_AddressCallback
* has returned so far.
*
* @param nh the handle returned by register
* @param addr IP address to test (IPv4 or IPv6)
* @param addrlen number of bytes in @a addr
* @return #GNUNET_YES if the address is plausible,
* #GNUNET_NO if the address is not plausible,
* #GNUNET_SYSERR if the address is malformed
*/
int
GNUNET_NAT_test_address (struct GNUNET_NAT_Handle *nh,
const void *addr,
socklen_t addrlen)
{
struct AddrEntry *ae;
if ( (addrlen != sizeof (struct sockaddr_in)) &&
(addrlen != sizeof (struct sockaddr_in6)) )
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
for (ae = nh->ae_head; NULL != ae; ae = ae->next)
if ( (addrlen == ae->addrlen) &&
(0 == memcmp (addr,
&ae[1],
addrlen)) )
return GNUNET_YES;
return GNUNET_NO;
}
/**
* We learned about a peer (possibly behind NAT) so run the
* gnunet-nat-client to send dummy ICMP responses to cause
* that peer to connect to us (connection reversal).
*
* @param nh handle (used for configuration)
* @param local_sa our local address of the peer (IPv4-only)
* @param remote_sa the remote address of the peer (IPv4-only)
* @return #GNUNET_SYSERR on error,
* #GNUNET_NO if connection reversal is unavailable,
* #GNUNET_OK otherwise (presumably in progress)
*/
int
GNUNET_NAT_request_reversal (struct GNUNET_NAT_Handle *nh,
const struct sockaddr_in *local_sa,
const struct sockaddr_in *remote_sa)
{
struct GNUNET_MQ_Envelope *env;
struct GNUNET_NAT_RequestConnectionReversalMessage *req;
char *buf;
if (NULL == nh->mq)
return GNUNET_SYSERR;
GNUNET_break (AF_INET == local_sa->sin_family);
GNUNET_break (AF_INET == remote_sa->sin_family);
env = GNUNET_MQ_msg_extra (req,
2 * sizeof (struct sockaddr_in),
GNUNET_MESSAGE_TYPE_NAT_REQUEST_CONNECTION_REVERSAL);
req->local_addr_size = htons (sizeof (struct sockaddr_in));
req->remote_addr_size = htons (sizeof (struct sockaddr_in));
buf = (char *) &req[1];
GNUNET_memcpy (buf,
local_sa,
sizeof (struct sockaddr_in));
buf += sizeof (struct sockaddr_in);
GNUNET_memcpy (buf,
remote_sa,
sizeof (struct sockaddr_in));
GNUNET_MQ_send (nh->mq,
env);
return GNUNET_OK;
}
/**
* Stop port redirection and public IP address detection for the given
* handle. This frees the handle, after having sent the needed
* commands to close open ports.
*
* @param nh the handle to stop
*/
void
GNUNET_NAT_unregister (struct GNUNET_NAT_Handle *nh)
{
if (NULL != nh->mq)
{
GNUNET_MQ_destroy (nh->mq);
nh->mq = NULL;
}
if (NULL != nh->reconnect_task)
{
GNUNET_SCHEDULER_cancel (nh->reconnect_task);
nh->reconnect_task = NULL;
}
GNUNET_free (nh->reg);
GNUNET_free (nh);
}
/* end of nat_api.c */