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 General Public License as published
- by the Free Software Foundation; either version 3, or (at your
- option) any later version.
+ 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
- General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with GNUnet; see the file COPYING. If not, write to the
- Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- Boston, MA 02110-1301, USA.
+ 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 <http://www.gnu.org/licenses/>.
*/
/**
* @brief network address translation traversal service
* @author Christian Grothoff
*
- * The purpose of this service is to enable transports to
+ * 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:
- * - test manual hole punching support
- * - adapt existing transports to use new NAT logic
- * - abandon legacy NAT code
- *
- * - implement "more" autoconfig:
- * + consider moving autoconfig-logic into separate service!
- * + re-work gnunet-nat-server & integrate!
- * + test manually punched NAT (how?)
+ * - 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.
/**
- * Information we track per client address.
+ * Information we track per client address.
*/
struct ClientAddress
{
* pending.
*/
struct GNUNET_NAT_MiniHandle *mh;
-
+
};
* 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).
* 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
* What type of address is this?
*/
enum GNUNET_NAT_AddressClass ac;
-
+
};
* Kept in a DLL.
*/
struct ClientHandle *next;
-
+
/**
* Kept in a DLL.
*/
/**
* Underlying handle for this client with the service.
- */
+ */
struct GNUNET_SERVICE_Client *client;
/**
/**
* 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
+ * 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).
*/
/**
* 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.
* Handle for monitoring external IP changes.
*/
struct GN_ExternalIPMonitor *external_monitor;
-
+
/**
* DLL of external IP addresses as given in @e hole_external.
*/
/**
* Port number we found in @e hole_external.
- */
+ */
uint16_t ext_dns_port;
-
+
/**
* What does this client care about?
*/
* 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.
*/
{
/**
* Kept in a DLL.
- */
+ */
struct StunExternalIP *next;
/**
* Kept in a DLL.
- */
+ */
struct StunExternalIP *prev;
/**
struct GNUNET_SCHEDULER_Task *timeout_task;
/**
- * Our external IP address as reported by the
+ * Our external IP address as reported by the
* STUN server.
*/
struct sockaddr_in external_addr;
/**
- * Address of the reporting STUN server. Used to
+ * Address of the reporting STUN server. Used to
* detect when a STUN server changes its opinion
* to more quickly remove stale results.
*/
* Head of client DLL.
*/
static struct ClientHandle *ch_head;
-
+
/**
* Tail of client DLL.
*/
/**
* Kept in a DLL.
- */
+ */
static struct StunExternalIP *se_head;
/**
* Kept in a DLL.
- */
+ */
static struct StunExternalIP *se_tail;
/**
/**
* Free the DLL starting at #lal_head.
- */
+ */
static void
destroy_lal ()
{
#endif
default:
GNUNET_break (0);
- return GNUNET_SYSERR;
+ return GNUNET_SYSERR;
}
if (alen > left)
{
GNUNET_break (0);
- return GNUNET_SYSERR;
+ return GNUNET_SYSERR;
}
off += alen;
left -= alen;
if (left != ntohs (message->str_len))
{
GNUNET_break (0);
- return GNUNET_SYSERR;
+ return GNUNET_SYSERR;
}
- return GNUNET_OK;
+ return GNUNET_OK;
}
struct in6_addr net;
struct in6_addr mask;
unsigned int off;
-
+
if (0 == bits)
return GNUNET_YES;
GNUNET_assert (1 == inet_pton (AF_INET6,
struct IfcProcContext
{
- /**
+ /**
* Head of DLL of local addresses.
*/
struct LocalAddressList *lal_head;
{
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);
addr_len);
GNUNET_MQ_send (ch->mq,
env);
-}
+}
/**
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:
GNUNET_memcpy (&v4,
&delta->addr,
alen);
-
+
/* Check for client notifications */
for (unsigned int i=0;i<ch->num_caddrs;i++)
{
(! 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) ) &&
- (0 != c4->sin_addr.s_addr) &&
match_ipv4 ("127.0.0.1", &v4.sin_addr, 8) )
continue; /* bound to non-loopback, but this is loopback */
- if ( (0 != (ch->flags & 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 != (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 != memcmp (&v4.sin_addr,
- &c4->sin_addr,
- sizeof (struct in_addr))) &&
- (0 != c4->sin_addr.s_addr) &&
- ( (! is_nat_v4 (&c4->sin_addr)) ||
- (0 == (ch->flags & GNUNET_NAT_AC_EXTERN))) )
+ &c4->sin_addr,
+ sizeof (struct in_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 */
- v4.sin_port = c4->sin_port;
+ if (0 == htons (v4.sin_port))
+ v4.sin_port = c4->sin_port;
notify_client (delta->ac,
ch,
add,
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;
(! match_ipv6 ("::1", &v6.sin6_addr, 128)) )
continue; /* bound to loopback, but this is not loopback */
if ( (! match_ipv6 ("::1", &c6->sin6_addr, 128) ) &&
- (0 != memcmp (&c6->sin6_addr,
- &in6addr_any,
- sizeof (struct in6_addr))) &&
match_ipv6 ("::1", &v6.sin6_addr, 128) )
continue; /* bound to non-loopback, but this is loopback */
- if ( (0 != (ch->flags & GNUNET_NAT_AC_EXTERN)) &&
- (0 != memcmp (&c6->sin6_addr,
+ if ( (0 != (delta->ac & GNUNET_NAT_AC_EXTERN)) &&
+ (0 != memcmp (&c6->sin6_addr,
&in6addr_any,
sizeof (struct in6_addr))) &&
(! is_nat_v6 (&v6.sin6_addr)) )
does not match and is not an external IP */
/* OK, IP seems relevant, notify client */
- v6.sin6_port = c6->sin6_port;
+ if (0 == htons (v6.sin6_port))
+ v6.sin6_port = c6->sin6_port;
notify_client (delta->ac,
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
{
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;
- if (0 == (GNUNET_NAT_RF_ADDRESSES & ch->flags))
- return;
have_v4 = GNUNET_NO;
for (unsigned int i=0;i<ch->num_caddrs;i++)
{
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_LAN
: GNUNET_NAT_AC_EXTERN | GNUNET_NAT_AC_GLOBAL,
ch,
add,
* 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
+ * @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,
for (struct ClientHandle *ch = ch_head;
NULL != ch;
ch = ch->next)
- {
+ {
struct GNUNET_NAT_ConnectionReversalRequestedMessage *crrm;
struct GNUNET_MQ_Envelope *env;
int match;
{
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;
* Task we run periodically to scan for network interfaces.
*
* @param cls NULL
- */
+ */
static void
run_scan (void *cls)
{
int found;
int have_nat;
struct LocalAddressList *lnext;
-
+
scan_task = GNUNET_SCHEDULER_add_delayed (SCAN_FREQ,
&run_scan,
NULL);
{
const struct sockaddr_in *s4
= (const struct sockaddr_in *) &pos->addr;
-
- GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Found NATed local address %s, starting NAT server\n",
- GNUNET_a2s ((void *) &pos->addr, sizeof (*s4)));
+ GNUNET_a2s ((const struct sockaddr *) &pos->addr,
+ sizeof (*s4)));
pos->hc = GN_start_gnunet_nat_server_ (&s4->sin_addr,
&reversal_callback,
pos);
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");
/**
- * Resolve the `hole_external` name to figure out our
+ * 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
+ * port number has already been parsed, this task is
* responsible for periodically doing a DNS lookup.
*
* @param ch client handle to act upon
if (NULL == addr)
{
struct LocalAddressList *laln;
-
+
ch->ext_dns = NULL;
- ch->ext_dns_task
+ ch->ext_dns_task
= GNUNET_SCHEDULER_add_delayed (dyndns_frequency,
&dyndns_lookup,
ch);
GNUNET_a2s (addr,
addrlen),
ch->hole_external);
-
+
/* build sockaddr storage with port number */
- memset (&ss, 0, sizeof (ss));
- memcpy (&ss, addr, addrlen);
+ memset (&ss,
+ 0,
+ sizeof (ss));
+ GNUNET_memcpy (&ss,
+ addr,
+ addrlen);
switch (addr->sa_family)
{
case AF_INET:
/**
- * Resolve the `hole_external` name to figure out our
+ * 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
+ * port number has already been parsed, this task is
* responsible for periodically doing a DNS lookup.
*
* @param ch client handle to act upon
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;
AF_UNSPEC,
GNUNET_TIME_UNIT_MINUTES,
&process_external_ip,
- ch);
+ ch);
}
/**
- * Resolve the `hole_external` name to figure out our
+ * 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
+ * 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.
*
unsigned int pnum;
struct sockaddr_in *s4;
struct LocalAddressList *lal;
-
+
port = strrchr (ch->hole_external, ':');
if (NULL == port)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
_("Invalid port number in punched hole specification `%s' (lacks port)\n"),
port + 1);
- return;
+ return;
}
ch->ext_dns_port = (uint16_t) pnum;
*port = '\0';
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_free (lal);
ch->ext_dns_task
= GNUNET_SCHEDULER_add_now (&dyndns_lookup,
- ch);
+ ch);
}
GNUNET_SERVICE_client_drop (ch->client);
return;
}
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Received REGISTER message from client\n");
ch->flags = message->flags;
ch->proto = message->proto;
ch->num_caddrs = ntohs (message->num_addrs);
off = (const char *) &message[1];
for (unsigned int i=0;i<ch->num_caddrs;i++)
{
- size_t alen;
const struct sockaddr *sa = (const struct sockaddr *) off;
+ size_t alen;
uint16_t port;
int is_nat;
{
case AF_INET:
{
- const struct sockaddr_in *s4 = (const struct sockaddr_in *) sa;
-
+ struct sockaddr_in s4;
+
+ GNUNET_memcpy (&s4,
+ off,
+ sizeof (struct sockaddr_in));
alen = sizeof (struct sockaddr_in);
- if (is_nat_v4 (&s4->sin_addr))
+ if (is_nat_v4 (&s4.sin_addr))
is_nat = GNUNET_YES;
- port = ntohs (s4->sin_port);
+ port = ntohs (s4.sin_port);
}
break;
case AF_INET6:
{
- const struct sockaddr_in6 *s6 = (const struct sockaddr_in6 *) sa;
-
+ struct sockaddr_in6 s6;
+
+ GNUNET_memcpy (&s6,
+ off,
+ sizeof (struct sockaddr_in6));
alen = sizeof (struct sockaddr_in6);
- if (is_nat_v6 (&s6->sin6_addr))
+ if (is_nat_v6 (&s6.sin6_addr))
is_nat = GNUNET_YES;
- port = ntohs (s6->sin6_port);
+ port = ntohs (s6.sin6_port);
}
break;
#if AF_UNIX
default:
GNUNET_break (0);
GNUNET_SERVICE_client_drop (ch->client);
- return;
+ return;
}
/* store address */
GNUNET_assert (alen <= left);
GNUNET_assert (alen <= sizeof (struct sockaddr_storage));
GNUNET_memcpy (&ch->caddrs[i].ss,
- sa,
- alen);
+ off,
+ alen);
/* If applicable, try UPNPC NAT punching */
if ( (is_nat) &&
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;
{
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);
struct sockaddr_in v4;
struct GNUNET_NAT_AddressChangeNotificationMessage *msg;
struct GNUNET_MQ_Envelope *env;
-
+
if (! ch->natted_address)
continue;
v4 = *ip;
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 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
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))
&r4.sin_addr);
if (GNUNET_OK != ret)
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- _("Connection reversal request failed\n"));
+ _("Connection reversal request failed\n"));
GNUNET_SERVICE_client_continue (ch->client);
}
stun_stale_timeout = GNUNET_TIME_UNIT_HOURS;
/* Check for UPnP */
- enable_upnp
+ enable_upnp
= GNUNET_CONFIGURATION_get_value_yesno (cfg,
"NAT",
"ENABLE_UPNP");
"DYNDNS_FREQUENCY",
&dyndns_frequency))
dyndns_frequency = DYNDNS_FREQUENCY;
-
+
GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
NULL);
stats = GNUNET_STATISTICS_create ("nat",
ch->ext_dns = NULL;
}
GNUNET_free_non_null (ch->hole_external);
- GNUNET_free (ch->section_name);
+ GNUNET_free_non_null (ch->section_name);
GNUNET_free (ch);
}