/*
This file is part of GNUnet.
Copyright (C) 2016, 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 .
SPDX-License-Identifier: AGPL3.0-or-later
*/
/**
* @file nat/gnunet-service-nat.c
* @brief network address translation traversal service
* @author Christian Grothoff
*
* The purpose of this service is to enable transports to
* traverse NAT routers, by providing traversal options and
* knowledge about the local network topology.
*
* TODO:
* - migrate test cases to new NAT service
* - add new traceroute-based logic for external IP detection
*
* - implement & test STUN processing to classify NAT;
* basically, open port & try different methods.
*/
#include "platform.h"
#include
#include "gnunet_util_lib.h"
#include "gnunet_protocols.h"
#include "gnunet_signatures.h"
#include "gnunet_statistics_service.h"
#include "gnunet_resolver_service.h"
#include "gnunet_nat_service.h"
#include "gnunet-service-nat.h"
#include "gnunet-service-nat_externalip.h"
#include "gnunet-service-nat_stun.h"
#include "gnunet-service-nat_mini.h"
#include "gnunet-service-nat_helper.h"
#include "nat.h"
#include
/**
* How often should we ask the OS about a list of active
* network interfaces?
*/
#define SCAN_FREQ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 15)
/**
* How long do we wait until we forcefully terminate autoconfiguration?
*/
#define AUTOCONFIG_TIMEOUT GNUNET_TIME_relative_multiply ( \
GNUNET_TIME_UNIT_SECONDS, 5)
/**
* How often do we scan for changes in how our external (dyndns) hostname resolves?
*/
#define DYNDNS_FREQUENCY GNUNET_TIME_relative_multiply ( \
GNUNET_TIME_UNIT_MINUTES, 7)
/**
* Information we track per client address.
*/
struct ClientAddress
{
/**
* Network address used by the client.
*/
struct sockaddr_storage ss;
/**
* Handle to active UPnP request where we asked upnpc to open
* a port at the NAT. NULL if we do not have such a request
* pending.
*/
struct GNUNET_NAT_MiniHandle *mh;
};
/**
* List of local addresses this system has.
*/
struct LocalAddressList
{
/**
* This is a linked list.
*/
struct LocalAddressList *next;
/**
* Previous entry.
*/
struct LocalAddressList *prev;
/**
* Context for a gnunet-helper-nat-server used to listen
* for ICMP messages to this client for connection reversal.
*/
struct HelperContext *hc;
/**
* The address itself (i.e. `struct sockaddr_in` or `struct
* sockaddr_in6`, in the respective byte order).
*/
struct sockaddr_storage addr;
/**
* Address family. (FIXME: redundant, addr.ss_family! Remove!?)
*/
int af;
/**
* #GNUNET_YES if we saw this one in the previous iteration,
* but not in the current iteration and thus might need to
* remove it at the end.
*/
int old;
/**
* What type of address is this?
*/
enum GNUNET_NAT_AddressClass ac;
};
/**
* Internal data structure we track for each of our clients.
*/
struct ClientHandle
{
/**
* Kept in a DLL.
*/
struct ClientHandle *next;
/**
* Kept in a DLL.
*/
struct ClientHandle *prev;
/**
* Underlying handle for this client with the service.
*/
struct GNUNET_SERVICE_Client *client;
/**
* Message queue for communicating with the client.
*/
struct GNUNET_MQ_Handle *mq;
/**
* Array of addresses used by the service.
*/
struct ClientAddress *caddrs;
/**
* External DNS name and port given by user due to manual
* hole punching. Special DNS name 'AUTO' is used to indicate
* desire for automatic determination of the external IP
* (instead of DNS or manual configuration, i.e. to be used
* if the IP keeps changing and we have no DynDNS, but we do
* have a hole punched).
*/
char *hole_external;
/**
* Name of the configuration section this client cares about.
*/
char *section_name;
/**
* Task for periodically re-running the @e ext_dns DNS lookup.
*/
struct GNUNET_SCHEDULER_Task *ext_dns_task;
/**
* Handle for (DYN)DNS lookup of our external IP as given in
* @e hole_external.
*/
struct GNUNET_RESOLVER_RequestHandle *ext_dns;
/**
* Handle for monitoring external IP changes.
*/
struct GN_ExternalIPMonitor *external_monitor;
/**
* DLL of external IP addresses as given in @e hole_external.
*/
struct LocalAddressList *ext_addr_head;
/**
* DLL of external IP addresses as given in @e hole_external.
*/
struct LocalAddressList *ext_addr_tail;
/**
* Port number we found in @e hole_external.
*/
uint16_t ext_dns_port;
/**
* What does this client care about?
*/
enum GNUNET_NAT_RegisterFlags flags;
/**
* Is any of the @e caddrs in a reserved subnet for NAT?
*/
int natted_address;
/**
* Number of addresses that this service is bound to.
* Length of the @e caddrs array.
*/
uint16_t num_caddrs;
/**
* Client's IPPROTO, e.g. IPPROTO_UDP or IPPROTO_TCP.
*/
uint8_t proto;
};
/**
* External IP address as given to us via some STUN server.
*/
struct StunExternalIP
{
/**
* Kept in a DLL.
*/
struct StunExternalIP *next;
/**
* Kept in a DLL.
*/
struct StunExternalIP *prev;
/**
* Task we run to remove this entry when it is stale.
*/
struct GNUNET_SCHEDULER_Task *timeout_task;
/**
* Our external IP address as reported by the
* STUN server.
*/
struct sockaddr_in external_addr;
/**
* Address of the reporting STUN server. Used to
* detect when a STUN server changes its opinion
* to more quickly remove stale results.
*/
struct sockaddr_storage stun_server_addr;
/**
* Number of bytes used in @e stun_server_addr.
*/
size_t stun_server_addr_len;
};
/**
* Timeout to use when STUN data is considered stale.
*/
static struct GNUNET_TIME_Relative stun_stale_timeout;
/**
* How often do we scan for changes in how our external (dyndns) hostname resolves?
*/
static struct GNUNET_TIME_Relative dyndns_frequency;
/**
* Handle to our current configuration.
*/
static const struct GNUNET_CONFIGURATION_Handle *cfg;
/**
* Handle to the statistics service.
*/
static struct GNUNET_STATISTICS_Handle *stats;
/**
* Task scheduled to periodically scan our network interfaces.
*/
static struct GNUNET_SCHEDULER_Task *scan_task;
/**
* Head of client DLL.
*/
static struct ClientHandle *ch_head;
/**
* Tail of client DLL.
*/
static struct ClientHandle *ch_tail;
/**
* Head of DLL of local addresses.
*/
static struct LocalAddressList *lal_head;
/**
* Tail of DLL of local addresses.
*/
static struct LocalAddressList *lal_tail;
/**
* Kept in a DLL.
*/
static struct StunExternalIP *se_head;
/**
* Kept in a DLL.
*/
static struct StunExternalIP *se_tail;
/**
* Is UPnP enabled? #GNUNET_YES if enabled, #GNUNET_NO if disabled,
* #GNUNET_SYSERR if configuration enabled but binary is unavailable.
*/
int enable_upnp;
/**
* Is IP Scanning enabled? #GNUNET_YES if enabled, #GNUNET_NO if disabled,
* without, only explicitly specified IPs will be handled (HOLE_EXTERNAL)
*/
int enable_ipscan;
/**
* Remove and free an entry from the #lal_head DLL.
*
* @param lal entry to free
*/
static void
free_lal (struct LocalAddressList *lal)
{
GNUNET_CONTAINER_DLL_remove (lal_head,
lal_tail,
lal);
if (NULL != lal->hc)
{
GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
"Lost NATed local address %s, stopping NAT server\n",
GNUNET_a2s ((const struct sockaddr *) &lal->addr,
sizeof(struct sockaddr_in)));
GN_stop_gnunet_nat_server_ (lal->hc);
lal->hc = NULL;
}
GNUNET_free (lal);
}
/**
* Free the DLL starting at #lal_head.
*/
static void
destroy_lal ()
{
struct LocalAddressList *lal;
while (NULL != (lal = lal_head))
free_lal (lal);
}
/**
* Check validity of #GNUNET_MESSAGE_TYPE_NAT_REGISTER message from
* client.
*
* @param cls client who sent the message
* @param message the message received
* @return #GNUNET_OK if message is well-formed
*/
static int
check_register (void *cls,
const struct GNUNET_NAT_RegisterMessage *message)
{
uint16_t num_addrs = ntohs (message->num_addrs);
const char *off = (const char *) &message[1];
size_t left = ntohs (message->header.size) - sizeof(*message);
for (unsigned int i = 0; i < num_addrs; i++)
{
size_t alen;
const struct sockaddr *sa = (const struct sockaddr *) off;
if (sizeof(sa_family_t) > left)
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
switch (sa->sa_family)
{
case AF_INET:
alen = sizeof(struct sockaddr_in);
break;
case AF_INET6:
alen = sizeof(struct sockaddr_in6);
break;
#if AF_UNIX
case AF_UNIX:
alen = sizeof(struct sockaddr_un);
break;
#endif
default:
GNUNET_break (0);
return GNUNET_SYSERR;
}
if (alen > left)
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
off += alen;
left -= alen;
}
if (left != ntohs (message->str_len))
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
return GNUNET_OK;
}
/**
* Check if @a ip is in @a network with @a bits netmask.
*
* @param network to test
* @param ip IP address to test
* @param bits bitmask for the network
* @return #GNUNET_YES if @a ip is in @a network
*/
static int
match_ipv4 (const char *network,
const struct in_addr *ip,
uint8_t bits)
{
struct in_addr net;
if (0 == ip->s_addr)
return GNUNET_YES;
if (0 == bits)
return GNUNET_YES;
GNUNET_assert (1 == inet_pton (AF_INET,
network,
&net));
return ! ((ip->s_addr ^ net.s_addr) & htonl (0xFFFFFFFFu << (32 - bits)));
}
/**
* Check if @a ip is in @a network with @a bits netmask.
*
* @param network to test
* @param ip IP address to test
* @param bits bitmask for the network
* @return #GNUNET_YES if @a ip is in @a network
*/
static int
match_ipv6 (const char *network,
const struct in6_addr *ip,
uint8_t bits)
{
struct in6_addr net;
struct in6_addr mask;
unsigned int off;
if (0 == bits)
return GNUNET_YES;
GNUNET_assert (1 == inet_pton (AF_INET6,
network,
&net));
memset (&mask, 0, sizeof(mask));
if (0 == GNUNET_memcmp (&mask,
ip))
return GNUNET_YES;
off = 0;
while (bits > 8)
{
mask.s6_addr[off++] = 0xFF;
bits -= 8;
}
while (bits > 0)
{
mask.s6_addr[off] = (mask.s6_addr[off] >> 1) + 0x80;
bits--;
}
for (unsigned j = 0; j < sizeof(struct in6_addr) / sizeof(uint32_t); j++)
if (((((uint32_t *) ip)[j] & ((uint32_t *) &mask)[j])) !=
(((uint32_t *) &net)[j] & ((int *) &mask)[j]))
return GNUNET_NO;
return GNUNET_YES;
}
/**
* Test if the given IPv4 address is in a known range
* for private networks.
*
* @param ip address to test
* @return #GNUNET_YES if @a ip is in a NAT range
*/
static int
is_nat_v4 (const struct in_addr *ip)
{
return
match_ipv4 ("10.0.0.0", ip, 8) || /* RFC 1918 */
match_ipv4 ("100.64.0.0", ip, 10) || /* CG-NAT, RFC 6598 */
match_ipv4 ("192.168.0.0", ip, 12) || /* RFC 1918 */
match_ipv4 ("169.254.0.0", ip, 16) || /* AUTO, RFC 3927 */
match_ipv4 ("172.16.0.0", ip, 16); /* RFC 1918 */
}
/**
* Test if the given IPv6 address is in a known range
* for private networks.
*
* @param ip address to test
* @return #GNUNET_YES if @a ip is in a NAT range
*/
static int
is_nat_v6 (const struct in6_addr *ip)
{
return
match_ipv6 ("fc00::", ip, 7) || /* RFC 4193 */
match_ipv6 ("fec0::", ip, 10) || /* RFC 3879 */
match_ipv6 ("fe80::", ip, 10); /* RFC 4291, link-local */
}
/**
* Closure for #ifc_proc.
*/
struct IfcProcContext
{
/**
* Head of DLL of local addresses.
*/
struct LocalAddressList *lal_head;
/**
* Tail of DLL of local addresses.
*/
struct LocalAddressList *lal_tail;
};
/**
* Callback function invoked for each interface found. Adds them
* to our new address list.
*
* @param cls a `struct IfcProcContext *`
* @param name name of the interface (can be NULL for unknown)
* @param isDefault is this presumably the default interface
* @param addr address of this interface (can be NULL for unknown or unassigned)
* @param broadcast_addr the broadcast address (can be NULL for unknown or unassigned)
* @param netmask the network mask (can be NULL for unknown or unassigned)
* @param addrlen length of the address
* @return #GNUNET_OK to continue iteration, #GNUNET_SYSERR to abort
*/
static int
ifc_proc (void *cls,
const char *name,
int isDefault,
const struct sockaddr *addr,
const struct sockaddr *broadcast_addr,
const struct sockaddr *netmask,
socklen_t addrlen)
{
struct IfcProcContext *ifc_ctx = cls;
struct LocalAddressList *lal;
size_t alen;
const struct in_addr *ip4;
const struct in6_addr *ip6;
enum GNUNET_NAT_AddressClass ac;
switch (addr->sa_family)
{
case AF_INET:
alen = sizeof(struct sockaddr_in);
ip4 = &((const struct sockaddr_in *) addr)->sin_addr;
if (match_ipv4 ("127.0.0.0", ip4, 8))
ac = GNUNET_NAT_AC_LOOPBACK;
else if (is_nat_v4 (ip4))
ac = GNUNET_NAT_AC_LAN;
else
ac = GNUNET_NAT_AC_GLOBAL;
break;
case AF_INET6:
alen = sizeof(struct sockaddr_in6);
ip6 = &((const struct sockaddr_in6 *) addr)->sin6_addr;
if (match_ipv6 ("::1", ip6, 128))
ac = GNUNET_NAT_AC_LOOPBACK;
else if (is_nat_v6 (ip6))
ac = GNUNET_NAT_AC_LAN;
else
ac = GNUNET_NAT_AC_GLOBAL;
if ((ip6->s6_addr[11] == 0xFF) &&
(ip6->s6_addr[12] == 0xFE))
{
/* contains a MAC, be extra careful! */
ac |= GNUNET_NAT_AC_PRIVATE;
}
break;
#if AF_UNIX
case AF_UNIX:
GNUNET_break (0);
return GNUNET_OK;
#endif
default:
GNUNET_break (0);
return GNUNET_OK;
}
lal = GNUNET_malloc (sizeof(*lal));
lal->af = addr->sa_family;
lal->ac = ac;
GNUNET_memcpy (&lal->addr,
addr,
alen);
GNUNET_CONTAINER_DLL_insert (ifc_ctx->lal_head,
ifc_ctx->lal_tail,
lal);
return GNUNET_OK;
}
/**
* Notify client about a change in the list of addresses this peer
* has.
*
* @param ac address class of the entry in the list that changed
* @param ch client to contact
* @param add #GNUNET_YES to add, #GNUNET_NO to remove
* @param addr the address that changed
* @param addr_len number of bytes in @a addr
*/
static void
notify_client (enum GNUNET_NAT_AddressClass ac,
struct ClientHandle *ch,
int add,
const void *addr,
size_t addr_len)
{
struct GNUNET_MQ_Envelope *env;
struct GNUNET_NAT_AddressChangeNotificationMessage *msg;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Notifying client about %s of IP %s\n",
add ? "addition" : "removal",
GNUNET_a2s (addr,
addr_len));
env = GNUNET_MQ_msg_extra (msg,
addr_len,
GNUNET_MESSAGE_TYPE_NAT_ADDRESS_CHANGE);
msg->add_remove = htonl (add);
msg->addr_class = htonl (ac);
GNUNET_memcpy (&msg[1],
addr,
addr_len);
GNUNET_MQ_send (ch->mq,
env);
}
/**
* Check if we should bother to notify this client about this
* address change, and if so, do it.
*
* @param delta the entry in the list that changed
* @param ch client to check
* @param add #GNUNET_YES to add, #GNUNET_NO to remove
*/
static void
check_notify_client (struct LocalAddressList *delta,
struct ClientHandle *ch,
int add)
{
size_t alen;
struct sockaddr_in v4;
struct sockaddr_in6 v6;
if (0 == (ch->flags & GNUNET_NAT_RF_ADDRESSES))
{
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Not notifying client as it does not care about addresses\n");
return;
}
switch (delta->af)
{
case AF_INET:
alen = sizeof(struct sockaddr_in);
GNUNET_memcpy (&v4,
&delta->addr,
alen);
/* Check for client notifications */
for (unsigned int i = 0; i < ch->num_caddrs; i++)
{
const struct sockaddr_in *c4;
if (AF_INET != ch->caddrs[i].ss.ss_family)
continue; /* IPv4 not relevant */
c4 = (const struct sockaddr_in *) &ch->caddrs[i].ss;
if (match_ipv4 ("127.0.0.1", &c4->sin_addr, 8) &&
(0 != c4->sin_addr.s_addr) &&
(! match_ipv4 ("127.0.0.1", &v4.sin_addr, 8)))
continue; /* bound to loopback, but this is not loopback */
if ((! match_ipv4 ("127.0.0.1", &c4->sin_addr, 8)) &&
match_ipv4 ("127.0.0.1", &v4.sin_addr, 8))
continue; /* bound to non-loopback, but this is loopback */
if ((0 != (delta->ac & GNUNET_NAT_AC_EXTERN)) &&
(0 != c4->sin_addr.s_addr) &&
(! is_nat_v4 (&v4.sin_addr)))
continue; /* based on external-IP, but this IP is not
from private address range. */
if ((0 != GNUNET_memcmp (&v4.sin_addr,
&c4->sin_addr)) &&
(0 != c4->sin_addr.s_addr) &&
(! is_nat_v4 (&c4->sin_addr)))
continue; /* this IP is not from private address range,
and IP does not match. */
/* OK, IP seems relevant, notify client */
if (0 == htons (v4.sin_port))
v4.sin_port = c4->sin_port;
notify_client (delta->ac,
ch,
add,
&v4,
alen);
}
break;
case AF_INET6:
alen = sizeof(struct sockaddr_in6);
GNUNET_memcpy (&v6,
&delta->addr,
alen);
for (unsigned int i = 0; i < ch->num_caddrs; i++)
{
const struct sockaddr_in6 *c6;
if (AF_INET6 != ch->caddrs[i].ss.ss_family)
continue; /* IPv4 not relevant */
c6 = (const struct sockaddr_in6 *) &ch->caddrs[i].ss;
if (match_ipv6 ("::1", &c6->sin6_addr, 128) &&
(0 != GNUNET_memcmp (&c6->sin6_addr,
&in6addr_any)) &&
(! match_ipv6 ("::1", &v6.sin6_addr, 128)))
continue; /* bound to loopback, but this is not loopback */
if ((! match_ipv6 ("::1", &c6->sin6_addr, 128)) &&
match_ipv6 ("::1", &v6.sin6_addr, 128))
continue; /* bound to non-loopback, but this is loopback */
if ((0 != (delta->ac & GNUNET_NAT_AC_EXTERN)) &&
(0 != GNUNET_memcmp (&c6->sin6_addr,
&in6addr_any)) &&
(! is_nat_v6 (&v6.sin6_addr)))
continue; /* based on external-IP, but this IP is not
from private address range. */
if ((0 != GNUNET_memcmp (&v6.sin6_addr,
&c6->sin6_addr)) &&
(0 != GNUNET_memcmp (&c6->sin6_addr,
&in6addr_any)) &&
(! is_nat_v6 (&c6->sin6_addr)))
continue; /* this IP is not from private address range,
and IP does not match. */
if ((match_ipv6 ("fe80::", &c6->sin6_addr, 10)) &&
(0 != GNUNET_memcmp (&c6->sin6_addr,
&in6addr_any)) &&
(0 != GNUNET_memcmp (&v6.sin6_addr,
&c6->sin6_addr)) &&
(0 == (delta->ac & GNUNET_NAT_AC_EXTERN)))
continue; /* client bound to link-local, and the other address
does not match and is not an external IP */
/* OK, IP seems relevant, notify client */
if (0 == htons (v6.sin6_port))
v6.sin6_port = c6->sin6_port;
notify_client (delta->ac,
ch,
add,
&v6,
alen);
}
break;
default:
GNUNET_break (0);
return;
}
}
/**
* Notify all clients about a change in the list
* of addresses this peer has.
*
* @param delta the entry in the list that changed
* @param add #GNUNET_YES to add, #GNUNET_NO to remove
*/
static void
notify_clients (struct LocalAddressList *delta,
int add)
{
for (struct ClientHandle *ch = ch_head;
NULL != ch;
ch = ch->next)
check_notify_client (delta,
ch,
add);
}
/**
* Tell relevant client about a change in our external
* IPv4 address.
*
* @param cls client to check if it cares and possibly notify
* @param v4 the external address that changed
* @param add #GNUNET_YES to add, #GNUNET_NO to remove
*/
static void
notify_client_external_ipv4_change (void *cls,
const struct in_addr *v4,
int add)
{
struct ClientHandle *ch = cls;
struct sockaddr_in sa;
int have_v4;
/* (0) check if this impacts 'hole_external' */
if ((NULL != ch->hole_external) &&
(0 == strcasecmp (ch->hole_external,
"AUTO")))
{
struct LocalAddressList lal;
struct sockaddr_in *s4;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Detected eternal IP, can now back-fill AUTO:%u in hole punching specification of `%s'\n",
(unsigned int) ch->ext_dns_port,
ch->section_name);
memset (&lal, 0, sizeof(lal));
s4 = (struct sockaddr_in *) &lal.addr;
s4->sin_family = AF_INET;
s4->sin_port = htons (ch->ext_dns_port);
s4->sin_addr = *v4;
lal.af = AF_INET;
lal.ac = GNUNET_NAT_AC_GLOBAL | GNUNET_NAT_AC_MANUAL;
check_notify_client (&lal,
ch,
add);
}
/* (1) check if client cares. */
if (! ch->natted_address)
return;
have_v4 = GNUNET_NO;
for (unsigned int i = 0; i < ch->num_caddrs; i++)
{
const struct sockaddr_storage *ss = &ch->caddrs[i].ss;
if (AF_INET != ss->ss_family)
continue;
have_v4 = GNUNET_YES;
break;
}
if (GNUNET_NO == have_v4)
return; /* IPv6-only */
/* (2) build address info */
memset (&sa,
0,
sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_addr = *v4;
sa.sin_port = htons (0);
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Detected eternal IP %s, notifying client of external IP (without port)\n",
GNUNET_a2s ((const struct sockaddr *) &sa,
sizeof(sa)));
/* (3) notify client of change */
notify_client (is_nat_v4 (v4)
? GNUNET_NAT_AC_EXTERN | GNUNET_NAT_AC_LAN
: GNUNET_NAT_AC_EXTERN | GNUNET_NAT_AC_GLOBAL,
ch,
add,
&sa,
sizeof(sa));
}
/**
* We got a connection reversal request from another peer.
* Notify applicable clients.
*
* @param cls closure with the `struct LocalAddressList`
* @param ra IP address of the peer who wants us to connect to it
*/
static void
reversal_callback (void *cls,
const struct sockaddr_in *ra)
{
struct LocalAddressList *lal = cls;
const struct sockaddr_in *l4;
GNUNET_assert (AF_INET == lal->af);
l4 = (const struct sockaddr_in *) &lal->addr;
for (struct ClientHandle *ch = ch_head;
NULL != ch;
ch = ch->next)
{
struct GNUNET_NAT_ConnectionReversalRequestedMessage *crrm;
struct GNUNET_MQ_Envelope *env;
int match;
/* Check if client is in applicable range for ICMP NAT traversal
for this local address */
if (! ch->natted_address)
continue;
match = GNUNET_NO;
for (unsigned int i = 0; i < ch->num_caddrs; i++)
{
struct ClientAddress *ca = &ch->caddrs[i];
const struct sockaddr_in *c4;
if (AF_INET != ca->ss.ss_family)
continue;
c4 = (const struct sockaddr_in *) &ca->ss;
if ((0 != c4->sin_addr.s_addr) &&
(l4->sin_addr.s_addr != c4->sin_addr.s_addr))
continue;
match = GNUNET_YES;
break;
}
if (! match)
continue;
/* Notify applicable client about connection reversal request */
env = GNUNET_MQ_msg_extra (crrm,
sizeof(struct sockaddr_in),
GNUNET_MESSAGE_TYPE_NAT_CONNECTION_REVERSAL_REQUESTED);
GNUNET_memcpy (&crrm[1],
ra,
sizeof(struct sockaddr_in));
GNUNET_MQ_send (ch->mq,
env);
}
}
/**
* Task we run periodically to scan for network interfaces.
*
* @param cls NULL
*/
static void
run_scan (void *cls)
{
struct IfcProcContext ifc_ctx;
int found;
int have_nat;
struct LocalAddressList *lnext;
scan_task = GNUNET_SCHEDULER_add_delayed (SCAN_FREQ,
&run_scan,
NULL);
memset (&ifc_ctx,
0,
sizeof(ifc_ctx));
GNUNET_OS_network_interfaces_list (&ifc_proc,
&ifc_ctx);
/* remove addresses that disappeared */
for (struct LocalAddressList *lal = lal_head;
NULL != lal;
lal = lnext)
{
lnext = lal->next;
found = GNUNET_NO;
for (struct LocalAddressList *pos = ifc_ctx.lal_head;
NULL != pos;
pos = pos->next)
{
if ((pos->af == lal->af) &&
(0 == memcmp (&lal->addr,
&pos->addr,
(AF_INET == lal->af)
? sizeof(struct sockaddr_in)
: sizeof(struct sockaddr_in6))))
{
found = GNUNET_YES;
}
}
if (GNUNET_NO == found)
{
notify_clients (lal,
GNUNET_NO);
free_lal (lal);
}
}
/* add addresses that appeared */
have_nat = GNUNET_NO;
for (struct LocalAddressList *pos = ifc_ctx.lal_head;
NULL != pos;
pos = ifc_ctx.lal_head)
{
found = GNUNET_NO;
if (GNUNET_NAT_AC_LAN == (GNUNET_NAT_AC_LAN & pos->ac))
have_nat = GNUNET_YES;
for (struct LocalAddressList *lal = lal_head;
NULL != lal;
lal = lal->next)
{
if ((pos->af == lal->af) &&
(0 == memcmp (&lal->addr,
&pos->addr,
(AF_INET == lal->af)
? sizeof(struct sockaddr_in)
: sizeof(struct sockaddr_in6))))
found = GNUNET_YES;
}
GNUNET_CONTAINER_DLL_remove (ifc_ctx.lal_head,
ifc_ctx.lal_tail,
pos);
if (GNUNET_YES == found)
{
GNUNET_free (pos);
}
else
{
notify_clients (pos,
GNUNET_YES);
GNUNET_CONTAINER_DLL_insert (lal_head,
lal_tail,
pos);
if ((AF_INET == pos->af) &&
(NULL == pos->hc) &&
(0 != (GNUNET_NAT_AC_LAN & pos->ac)))
{
const struct sockaddr_in *s4
= (const struct sockaddr_in *) &pos->addr;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Found NATed local address %s, starting NAT server\n",
GNUNET_a2s ((const struct sockaddr *) &pos->addr,
sizeof(*s4)));
pos->hc = GN_start_gnunet_nat_server_ (&s4->sin_addr,
&reversal_callback,
pos,
cfg);
}
}
}
GN_nat_status_changed (have_nat);
}
/**
* Function called whenever our set of external addresses
* as created by `upnpc` changes.
*
* @param cls closure with our `struct ClientHandle *`
* @param add_remove #GNUNET_YES to mean the new public IP address, #GNUNET_NO to mean
* the previous (now invalid) one, #GNUNET_SYSERR indicates an error
* @param addr either the previous or the new public IP address
* @param addrlen actual length of the @a addr
* @param result #GNUNET_NAT_ERROR_SUCCESS on success, otherwise the specific error code
*/
static void
upnp_addr_change_cb (void *cls,
int add_remove,
const struct sockaddr *addr,
socklen_t addrlen,
enum GNUNET_NAT_StatusCode result)
{
struct ClientHandle *ch = cls;
enum GNUNET_NAT_AddressClass ac;
switch (result)
{
case GNUNET_NAT_ERROR_SUCCESS:
GNUNET_assert (NULL != addr);
break;
case GNUNET_NAT_ERROR_UPNPC_FAILED:
case GNUNET_NAT_ERROR_UPNPC_TIMEOUT:
case GNUNET_NAT_ERROR_IPC_FAILURE:
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Running upnpc failed: %d\n",
result);
return;
case GNUNET_NAT_ERROR_EXTERNAL_IP_UTILITY_NOT_FOUND:
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"external-ip binary not found\n");
return;
case GNUNET_NAT_ERROR_UPNPC_NOT_FOUND:
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"upnpc binary not found\n");
return;
case GNUNET_NAT_ERROR_EXTERNAL_IP_UTILITY_FAILED:
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"external-ip binary could not be run\n");
return;
case GNUNET_NAT_ERROR_UPNPC_PORTMAP_FAILED:
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"upnpc failed to create port mapping\n");
return;
case GNUNET_NAT_ERROR_EXTERNAL_IP_UTILITY_OUTPUT_INVALID:
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Invalid output from upnpc\n");
return;
case GNUNET_NAT_ERROR_EXTERNAL_IP_ADDRESS_INVALID:
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Invalid address returned by upnpc\n");
return;
default:
GNUNET_break (0); /* should not be possible */
return;
}
switch (addr->sa_family)
{
case AF_INET:
ac = is_nat_v4 (&((const struct sockaddr_in *) addr)->sin_addr)
? GNUNET_NAT_AC_LAN
: GNUNET_NAT_AC_EXTERN;
break;
case AF_INET6:
ac = is_nat_v6 (&((const struct sockaddr_in6 *) addr)->sin6_addr)
? GNUNET_NAT_AC_LAN
: GNUNET_NAT_AC_EXTERN;
break;
default:
GNUNET_break (0);
return;
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"upnp external address %s: %s\n",
add_remove ? "added" : "removed",
GNUNET_a2s (addr,
addrlen));
notify_client (ac,
ch,
add_remove,
addr,
addrlen);
}
/**
* Resolve the `hole_external` name to figure out our
* external address from a manually punched hole. The
* port number has already been parsed, this task is
* responsible for periodically doing a DNS lookup.
*
* @param ch client handle to act upon
*/
static void
dyndns_lookup (void *cls);
/**
* Our (external) hostname was resolved. Update lists of
* current external IPs (note that DNS may return multiple
* addresses!) and notify client accordingly.
*
* @param cls the `struct ClientHandle`
* @param addr NULL on error, otherwise result of DNS lookup
* @param addrlen number of bytes in @a addr
*/
static void
process_external_ip (void *cls,
const struct sockaddr *addr,
socklen_t addrlen)
{
struct ClientHandle *ch = cls;
struct LocalAddressList *lal;
struct sockaddr_storage ss;
struct sockaddr_in *v4;
struct sockaddr_in6 *v6;
if (NULL == addr)
{
struct LocalAddressList *laln;
ch->ext_dns = NULL;
ch->ext_dns_task
= GNUNET_SCHEDULER_add_delayed (dyndns_frequency,
&dyndns_lookup,
ch);
/* Current iteration is over, remove 'old' IPs now */
for (lal = ch->ext_addr_head; NULL != lal; lal = laln)
{
laln = lal->next;
if (GNUNET_YES == lal->old)
{
GNUNET_CONTAINER_DLL_remove (ch->ext_addr_head,
ch->ext_addr_tail,
lal);
check_notify_client (lal,
ch,
GNUNET_NO);
GNUNET_free (lal);
}
}
return;
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Got IP `%s' for external address `%s'\n",
GNUNET_a2s (addr,
addrlen),
ch->hole_external);
/* build sockaddr storage with port number */
memset (&ss,
0,
sizeof(ss));
GNUNET_memcpy (&ss,
addr,
addrlen);
switch (addr->sa_family)
{
case AF_INET:
v4 = (struct sockaddr_in *) &ss;
v4->sin_port = htons (ch->ext_dns_port);
break;
case AF_INET6:
v6 = (struct sockaddr_in6 *) &ss;
v6->sin6_port = htons (ch->ext_dns_port);
break;
default:
GNUNET_break (0);
return;
}
/* See if 'ss' matches any of our known addresses */
for (lal = ch->ext_addr_head; NULL != lal; lal = lal->next)
{
if (GNUNET_NO == lal->old)
continue; /* already processed, skip */
if ((addr->sa_family == lal->addr.ss_family) &&
(0 == memcmp (&ss,
&lal->addr,
addrlen)))
{
/* Address unchanged, remember so we do not remove */
lal->old = GNUNET_NO;
return; /* done here */
}
}
/* notify client, and remember IP for later removal! */
lal = GNUNET_new (struct LocalAddressList);
lal->addr = ss;
lal->af = ss.ss_family;
lal->ac = GNUNET_NAT_AC_GLOBAL | GNUNET_NAT_AC_MANUAL;
GNUNET_CONTAINER_DLL_insert (ch->ext_addr_head,
ch->ext_addr_tail,
lal);
check_notify_client (lal,
ch,
GNUNET_YES);
}
/**
* Resolve the `hole_external` name to figure out our
* external address from a manually punched hole. The
* port number has already been parsed, this task is
* responsible for periodically doing a DNS lookup.
*
* @param ch client handle to act upon
*/
static void
dyndns_lookup (void *cls)
{
struct ClientHandle *ch = cls;
struct LocalAddressList *lal;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Performing DNS lookup for punched hole given for `%s' as `%s:%u'\n",
ch->section_name,
ch->hole_external,
(unsigned int) ch->ext_dns_port);
for (lal = ch->ext_addr_head; NULL != lal; lal = lal->next)
lal->old = GNUNET_YES;
ch->ext_dns_task = NULL;
ch->ext_dns = GNUNET_RESOLVER_ip_get (ch->hole_external,
AF_UNSPEC,
GNUNET_TIME_UNIT_MINUTES,
&process_external_ip,
ch);
}
/**
* Resolve the `hole_external` name to figure out our
* external address from a manually punched hole. The
* given name may be "AUTO" in which case we should use
* the IP address(es) we have from upnpc or other methods.
* The name can also be an IP address, in which case we
* do not need to do DNS resolution. Finally, we also
* need to parse the port number.
*
* @param ch client handle to act upon
*/
static void
lookup_hole_external (struct ClientHandle *ch)
{
char *port;
unsigned int pnum;
struct sockaddr_in *s4;
struct LocalAddressList *lal;
port = strrchr (ch->hole_external, ':');
if (NULL == port)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
_ ("Malformed punched hole specification `%s' (lacks port)\n"),
ch->hole_external);
return;
}
if ((1 != sscanf (port + 1,
"%u",
&pnum)) ||
(pnum > 65535))
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
_ (
"Invalid port number in punched hole specification `%s' (lacks port)\n"),
port + 1);
return;
}
ch->ext_dns_port = (uint16_t) pnum;
*port = '\0';
lal = GNUNET_new (struct LocalAddressList);
if ('[' == *ch->hole_external)
{
struct sockaddr_in6 *s6 = (struct sockaddr_in6 *) &lal->addr;
s6->sin6_family = AF_INET6;
if (']' != (ch->hole_external[strlen (ch->hole_external) - 1]))
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
_ ("Malformed punched hole specification `%s' (lacks `]')\n"),
ch->hole_external);
GNUNET_free (lal);
return;
}
ch->hole_external[strlen (ch->hole_external) - 1] = '\0';
if (1 != inet_pton (AF_INET6,
ch->hole_external + 1,
&s6->sin6_addr))
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
_ (
"Malformed punched hole specification `%s' (IPv6 address invalid)"),
ch->hole_external + 1);
GNUNET_free (lal);
return;
}
s6->sin6_port = htons (ch->ext_dns_port);
lal->af = AF_INET6;
lal->ac = GNUNET_NAT_AC_GLOBAL | GNUNET_NAT_AC_MANUAL;
GNUNET_CONTAINER_DLL_insert (ch->ext_addr_head,
ch->ext_addr_tail,
lal);
check_notify_client (lal,
ch,
GNUNET_YES);
return;
}
s4 = (struct sockaddr_in *) &lal->addr;
s4->sin_family = AF_INET;
if (1 == inet_pton (AF_INET,
ch->hole_external,
&s4->sin_addr))
{
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"IPv4 punched hole given for `%s' via `%s:%u'\n",
ch->section_name,
ch->hole_external,
(unsigned int) ch->ext_dns_port);
s4->sin_port = htons (ch->ext_dns_port);
lal->af = AF_INET;
lal->ac = GNUNET_NAT_AC_GLOBAL | GNUNET_NAT_AC_MANUAL;
GNUNET_CONTAINER_DLL_insert (ch->ext_addr_head,
ch->ext_addr_tail,
lal);
check_notify_client (lal,
ch,
GNUNET_YES);
return;
}
if (0 == strcasecmp (ch->hole_external,
"AUTO"))
{
/* handled in #notify_client_external_ipv4_change() */
GNUNET_free (lal);
return;
}
/* got a DNS name, trigger lookup! */
GNUNET_free (lal);
ch->ext_dns_task
= GNUNET_SCHEDULER_add_now (&dyndns_lookup,
ch);
}
/**
* Handler for #GNUNET_MESSAGE_TYPE_NAT_REGISTER message from client.
* We remember the client for updates upon future NAT events.
*
* @param cls client who sent the message
* @param message the message received
*/
static void
handle_register (void *cls,
const struct GNUNET_NAT_RegisterMessage *message)
{
struct ClientHandle *ch = cls;
const char *off;
size_t left;
if ((0 != ch->proto) ||
(NULL != ch->caddrs))
{
/* double registration not allowed */
GNUNET_break (0);
GNUNET_SERVICE_client_drop (ch->client);
return;
}
ch->flags = message->flags;
ch->proto = message->proto;
ch->num_caddrs = ntohs (message->num_addrs);
ch->caddrs = GNUNET_new_array (ch->num_caddrs,
struct ClientAddress);
left = ntohs (message->header.size) - sizeof(*message);
off = (const char *) &message[1];
for (unsigned int i = 0; i < ch->num_caddrs; i++)
{
const struct sockaddr *sa = (const struct sockaddr *) off;
size_t alen;
uint16_t port;
int is_nat;
if (sizeof(sa_family_t) > left)
{
GNUNET_break (0);
GNUNET_SERVICE_client_drop (ch->client);
return;
}
is_nat = GNUNET_NO;
switch (sa->sa_family)
{
case AF_INET:
{
struct sockaddr_in s4;
GNUNET_memcpy (&s4,
off,
sizeof(struct sockaddr_in));
alen = sizeof(struct sockaddr_in);
if (is_nat_v4 (&s4.sin_addr))
is_nat = GNUNET_YES;
port = ntohs (s4.sin_port);
}
break;
case AF_INET6:
{
struct sockaddr_in6 s6;
GNUNET_memcpy (&s6,
off,
sizeof(struct sockaddr_in6));
alen = sizeof(struct sockaddr_in6);
if (is_nat_v6 (&s6.sin6_addr))
is_nat = GNUNET_YES;
port = ntohs (s6.sin6_port);
}
break;
#if AF_UNIX
case AF_UNIX:
alen = sizeof(struct sockaddr_un);
port = 0;
break;
#endif
default:
GNUNET_break (0);
GNUNET_SERVICE_client_drop (ch->client);
return;
}
/* store address */
GNUNET_assert (alen <= left);
GNUNET_assert (alen <= sizeof(struct sockaddr_storage));
GNUNET_memcpy (&ch->caddrs[i].ss,
off,
alen);
/* If applicable, try UPNPC NAT punching */
if ((is_nat) &&
(enable_upnp) &&
((IPPROTO_TCP == ch->proto) ||
(IPPROTO_UDP == ch->proto)))
{
ch->natted_address = GNUNET_YES;
ch->caddrs[i].mh
= GNUNET_NAT_mini_map_start (port,
IPPROTO_TCP == ch->proto,
&upnp_addr_change_cb,
ch);
}
off += alen;
}
ch->section_name
= GNUNET_strndup (off,
ntohs (message->str_len));
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Received REGISTER message from client for subsystem `%s'\n",
ch->section_name);
if (GNUNET_OK ==
GNUNET_CONFIGURATION_get_value_string (cfg,
ch->section_name,
"HOLE_EXTERNAL",
&ch->hole_external))
lookup_hole_external (ch);
/* Actually send IP address list to client */
for (struct LocalAddressList *lal = lal_head;
NULL != lal;
lal = lal->next)
{
check_notify_client (lal,
ch,
GNUNET_YES);
}
/* Also consider IPv4 determined by `external-ip` */
ch->external_monitor
= GN_external_ipv4_monitor_start (¬ify_client_external_ipv4_change,
ch);
GNUNET_SERVICE_client_continue (ch->client);
}
/**
* Check validity of #GNUNET_MESSAGE_TYPE_NAT_HANDLE_STUN message from
* client.
*
* @param cls client who sent the message
* @param message the message received
* @return #GNUNET_OK if message is well-formed
*/
static int
check_stun (void *cls,
const struct GNUNET_NAT_HandleStunMessage *message)
{
size_t sa_len = ntohs (message->sender_addr_size);
size_t expect = sa_len + ntohs (message->payload_size);
if (ntohs (message->header.size) - sizeof(*message) != expect)
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
if (sa_len < sizeof(sa_family_t))
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
return GNUNET_OK;
}
/**
* Notify all clients about our external IP address
* as reported by the STUN server.
*
* @param ip the external IP
* @param add #GNUNET_YES to add, #GNUNET_NO to remove
*/
static void
notify_clients_stun_change (const struct sockaddr_in *ip,
int add)
{
for (struct ClientHandle *ch = ch_head;
NULL != ch;
ch = ch->next)
{
struct sockaddr_in v4;
struct GNUNET_NAT_AddressChangeNotificationMessage *msg;
struct GNUNET_MQ_Envelope *env;
if (! ch->natted_address)
continue;
v4 = *ip;
v4.sin_port = htons (0);
env = GNUNET_MQ_msg_extra (msg,
sizeof(v4),
GNUNET_MESSAGE_TYPE_NAT_ADDRESS_CHANGE);
msg->add_remove = htonl ((int32_t) add);
msg->addr_class = htonl (GNUNET_NAT_AC_EXTERN
| GNUNET_NAT_AC_GLOBAL);
GNUNET_memcpy (&msg[1],
&v4,
sizeof(v4));
GNUNET_MQ_send (ch->mq,
env);
}
}
/**
* Function to be called when we decide that an
* external IP address as told to us by a STUN
* server has gone stale.
*
* @param cls the `struct StunExternalIP` to drop
*/
static void
stun_ip_timeout (void *cls)
{
struct StunExternalIP *se = cls;
se->timeout_task = NULL;
notify_clients_stun_change (&se->external_addr,
GNUNET_NO);
GNUNET_CONTAINER_DLL_remove (se_head,
se_tail,
se);
GNUNET_free (se);
}
/**
* Handler for #GNUNET_MESSAGE_TYPE_NAT_HANDLE_STUN message from
* client.
*
* @param cls client who sent the message
* @param message the message received
*/
static void
handle_stun (void *cls,
const struct GNUNET_NAT_HandleStunMessage *message)
{
struct ClientHandle *ch = cls;
const char *buf = (const char *) &message[1];
const struct sockaddr *sa;
const void *payload;
size_t sa_len;
size_t payload_size;
struct sockaddr_in external_addr;
sa_len = ntohs (message->sender_addr_size);
payload_size = ntohs (message->payload_size);
sa = (const struct sockaddr *) &buf[0];
payload = (const struct sockaddr *) &buf[sa_len];
switch (sa->sa_family)
{
case AF_INET:
if (sa_len != sizeof(struct sockaddr_in))
{
GNUNET_break (0);
GNUNET_SERVICE_client_drop (ch->client);
return;
}
break;
case AF_INET6:
if (sa_len != sizeof(struct sockaddr_in6))
{
GNUNET_break (0);
GNUNET_SERVICE_client_drop (ch->client);
return;
}
break;
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Received HANDLE_STUN message from client\n");
if (GNUNET_OK ==
GNUNET_NAT_stun_handle_packet_ (payload,
payload_size,
&external_addr))
{
/* We now know that a server at "sa" claims that
we are visible at IP "external_addr".
We should (for some fixed period of time) tell
all of our clients that listen to a NAT'ed address
that they might want to consider the given 'external_ip'
as their public IP address (this includes TCP and UDP
clients, even if only UDP sends STUN requests).
If we do not get a renewal, the "external_addr" should be
removed again. The timeout frequency should be configurable
(with a sane default), so that the UDP plugin can tell how
often to re-request STUN.
*/struct StunExternalIP *se;
/* Check if we had a prior response from this STUN server */
for (se = se_head; NULL != se; se = se->next)
{
if ((se->stun_server_addr_len != sa_len) ||
(0 != memcmp (sa,
&se->stun_server_addr,
sa_len)))
continue; /* different STUN server */
if (0 != GNUNET_memcmp (&external_addr,
&se->external_addr))
{
/* external IP changed, update! */
notify_clients_stun_change (&se->external_addr,
GNUNET_NO);
se->external_addr = external_addr;
notify_clients_stun_change (&se->external_addr,
GNUNET_YES);
}
/* update timeout */
GNUNET_SCHEDULER_cancel (se->timeout_task);
se->timeout_task
= GNUNET_SCHEDULER_add_delayed (stun_stale_timeout,
&stun_ip_timeout,
se);
return;
}
/* STUN server is completely new, create fresh entry */
se = GNUNET_new (struct StunExternalIP);
se->external_addr = external_addr;
GNUNET_memcpy (&se->stun_server_addr,
sa,
sa_len);
se->stun_server_addr_len = sa_len;
se->timeout_task = GNUNET_SCHEDULER_add_delayed (stun_stale_timeout,
&stun_ip_timeout,
se);
GNUNET_CONTAINER_DLL_insert (se_head,
se_tail,
se);
notify_clients_stun_change (&se->external_addr,
GNUNET_NO);
}
GNUNET_SERVICE_client_continue (ch->client);
}
/**
* Check validity of
* #GNUNET_MESSAGE_TYPE_NAT_REQUEST_CONNECTION_REVERSAL message from
* client.
*
* @param cls client who sent the message
* @param message the message received
* @return #GNUNET_OK if message is well-formed
*/
static int
check_request_connection_reversal (void *cls,
const struct
GNUNET_NAT_RequestConnectionReversalMessage *
message)
{
size_t expect;
expect = ntohs (message->local_addr_size)
+ ntohs (message->remote_addr_size);
if (ntohs (message->header.size) - sizeof(*message) != expect)
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
return GNUNET_OK;
}
/**
* Handler for #GNUNET_MESSAGE_TYPE_NAT_REQUEST_CONNECTION_REVERSAL
* message from client.
*
* @param cls client who sent the message
* @param message the message received
*/
static void
handle_request_connection_reversal (void *cls,
const struct
GNUNET_NAT_RequestConnectionReversalMessage
*message)
{
struct ClientHandle *ch = cls;
const char *buf = (const char *) &message[1];
size_t local_sa_len = ntohs (message->local_addr_size);
size_t remote_sa_len = ntohs (message->remote_addr_size);
struct sockaddr_in l4;
struct sockaddr_in r4;
int ret;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Received REQUEST CONNECTION REVERSAL message from client\n");
if (local_sa_len != sizeof(struct sockaddr_in))
{
GNUNET_break_op (0);
GNUNET_SERVICE_client_drop (ch->client);
return;
}
if (remote_sa_len != sizeof(struct sockaddr_in))
{
GNUNET_break_op (0);
GNUNET_SERVICE_client_drop (ch->client);
return;
}
GNUNET_memcpy (&l4,
buf,
sizeof(struct sockaddr_in));
GNUNET_break_op (AF_INET == l4.sin_family);
buf += sizeof(struct sockaddr_in);
GNUNET_memcpy (&r4,
buf,
sizeof(struct sockaddr_in));
GNUNET_break_op (AF_INET == r4.sin_family);
ret = GN_request_connection_reversal (&l4.sin_addr,
ntohs (l4.sin_port),
&r4.sin_addr,
cfg);
if (GNUNET_OK != ret)
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
_ ("Connection reversal request failed\n"));
GNUNET_SERVICE_client_continue (ch->client);
}
/**
* Task run during shutdown.
*
* @param cls unused
*/
static void
shutdown_task (void *cls)
{
struct StunExternalIP *se;
while (NULL != (se = se_head))
{
GNUNET_CONTAINER_DLL_remove (se_head,
se_tail,
se);
GNUNET_SCHEDULER_cancel (se->timeout_task);
GNUNET_free (se);
}
GN_nat_status_changed (GNUNET_NO);
if (NULL != scan_task)
{
GNUNET_SCHEDULER_cancel (scan_task);
scan_task = NULL;
}
if (NULL != stats)
{
GNUNET_STATISTICS_destroy (stats,
GNUNET_NO);
stats = NULL;
}
destroy_lal ();
}
/**
* Setup NAT service.
*
* @param cls closure
* @param c configuration to use
* @param service the initialized service
*/
static void
run (void *cls,
const struct GNUNET_CONFIGURATION_Handle *c,
struct GNUNET_SERVICE_Handle *service)
{
cfg = c;
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_time (cfg,
"NAT",
"STUN_STALE",
&stun_stale_timeout))
stun_stale_timeout = GNUNET_TIME_UNIT_HOURS;
/* Check for UPnP */
enable_upnp
= GNUNET_CONFIGURATION_get_value_yesno (cfg,
"NAT",
"ENABLE_UPNP");
if (GNUNET_YES == enable_upnp)
{
/* check if it works */
if (GNUNET_SYSERR ==
GNUNET_OS_check_helper_binary ("upnpc",
GNUNET_NO,
NULL))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
_ (
"UPnP enabled in configuration, but UPnP client `upnpc` command not found, disabling UPnP\n"));
enable_upnp = GNUNET_SYSERR;
}
}
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_time (cfg,
"nat",
"DYNDNS_FREQUENCY",
&dyndns_frequency))
dyndns_frequency = DYNDNS_FREQUENCY;
enable_ipscan
= GNUNET_CONFIGURATION_get_value_yesno (cfg,
"NAT",
"ENABLE_IPSCAN");
GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
NULL);
stats = GNUNET_STATISTICS_create ("nat",
cfg);
if (GNUNET_YES == enable_ipscan)
scan_task = GNUNET_SCHEDULER_add_now (&run_scan,
NULL);
}
/**
* Callback called when a client connects to the service.
*
* @param cls closure for the service
* @param c the new client that connected to the service
* @param mq the message queue used to send messages to the client
* @return a `struct ClientHandle`
*/
static void *
client_connect_cb (void *cls,
struct GNUNET_SERVICE_Client *c,
struct GNUNET_MQ_Handle *mq)
{
struct ClientHandle *ch;
ch = GNUNET_new (struct ClientHandle);
ch->mq = mq;
ch->client = c;
GNUNET_CONTAINER_DLL_insert (ch_head,
ch_tail,
ch);
return ch;
}
/**
* Callback called when a client disconnected from the service
*
* @param cls closure for the service
* @param c the client that disconnected
* @param internal_cls a `struct ClientHandle *`
*/
static void
client_disconnect_cb (void *cls,
struct GNUNET_SERVICE_Client *c,
void *internal_cls)
{
struct ClientHandle *ch = internal_cls;
struct LocalAddressList *lal;
GNUNET_CONTAINER_DLL_remove (ch_head,
ch_tail,
ch);
for (unsigned int i = 0; i < ch->num_caddrs; i++)
{
if (NULL != ch->caddrs[i].mh)
{
GNUNET_NAT_mini_map_stop (ch->caddrs[i].mh);
ch->caddrs[i].mh = NULL;
}
}
GNUNET_free_non_null (ch->caddrs);
while (NULL != (lal = ch->ext_addr_head))
{
GNUNET_CONTAINER_DLL_remove (ch->ext_addr_head,
ch->ext_addr_tail,
lal);
GNUNET_free (lal);
}
if (NULL != ch->ext_dns_task)
{
GNUNET_SCHEDULER_cancel (ch->ext_dns_task);
ch->ext_dns_task = NULL;
}
if (NULL != ch->external_monitor)
{
GN_external_ipv4_monitor_stop (ch->external_monitor);
ch->external_monitor = NULL;
}
if (NULL != ch->ext_dns)
{
GNUNET_RESOLVER_request_cancel (ch->ext_dns);
ch->ext_dns = NULL;
}
GNUNET_free_non_null (ch->hole_external);
GNUNET_free_non_null (ch->section_name);
GNUNET_free (ch);
}
/**
* Define "main" method using service macro.
*/
GNUNET_SERVICE_MAIN
("nat",
GNUNET_SERVICE_OPTION_NONE,
&run,
&client_connect_cb,
&client_disconnect_cb,
NULL,
GNUNET_MQ_hd_var_size (register,
GNUNET_MESSAGE_TYPE_NAT_REGISTER,
struct GNUNET_NAT_RegisterMessage,
NULL),
GNUNET_MQ_hd_var_size (stun,
GNUNET_MESSAGE_TYPE_NAT_HANDLE_STUN,
struct GNUNET_NAT_HandleStunMessage,
NULL),
GNUNET_MQ_hd_var_size (request_connection_reversal,
GNUNET_MESSAGE_TYPE_NAT_REQUEST_CONNECTION_REVERSAL,
struct GNUNET_NAT_RequestConnectionReversalMessage,
NULL),
GNUNET_MQ_handler_end ());
#if defined(__linux__) && defined(__GLIBC__)
#include
/**
* MINIMIZE heap size (way below 128k) since this process doesn't need much.
*/
void __attribute__ ((constructor))
GNUNET_ARM_memory_init ()
{
mallopt (M_TRIM_THRESHOLD, 4 * 1024);
mallopt (M_TOP_PAD, 1 * 1024);
malloc_trim (0);
}
#endif
/* end of gnunet-service-nat.c */