TODO | 1 - doc/miredo.conf.5 | 47 +++++++- libteredo/Makefile.am | 4 +- libteredo/discovery.c | 290 ++++++++++++++++++++++++++++++++++++++++++++++++ libteredo/discovery.h | 97 ++++++++++++++++ libteredo/iothread.c | 94 ++++++++++++++++ libteredo/iothread.h | 57 ++++++++++ libteredo/libteredo.sym | 1 + libteredo/packets.c | 41 ++++--- libteredo/packets.h | 16 +++ libteredo/peerlist.h | 5 +- libteredo/relay.c | 253 +++++++++++++++++++++++++++++++++++------- libteredo/tunnel.h | 45 +++++++- src/relayd.c | 98 +++++++++++++++- 14 files changed, 981 insertions(+), 68 deletions(-) diff --git a/TODO b/TODO index fddc96e..8e93119 100644 --- a/TODO +++ b/TODO @@ -15,7 +15,6 @@ Important features & fixes: ---------------------------- (*) do something in case of DoS against the peers list ( ) fixed TODOs and FIXMEs in source code -(*) local Teredo discovery Not so important features: --------------------------- diff --git a/doc/miredo.conf.5 b/doc/miredo.conf.5 index e864253..ed5ccdf 100644 --- a/doc/miredo.conf.5 +++ b/doc/miredo.conf.5 @@ -1,5 +1,5 @@ .\" *********************************************************************** -.\" * Copyright © 2004-2006 Rémi Denis-Courmont. * +.\" * Copyright © 2004-2009 Rémi Denis-Courmont and contributors. * .\" * This program is free software; you can redistribute and/or modify * .\" * it under the terms of the GNU General Public License as published * .\" * by the Free Software Foundation; version 2 of the license. * @@ -116,6 +116,51 @@ Miredo assumes that the secondary Teredo server address equals the primary server address plus one. If that is not the case, this directive must be used. +.TP +.BI "LocalDiscovery " "mode" +Determines whether the local client discovery procedure is performed. +Local discovery works by sending multicast packets +on the local network interfaces at regular intervals, +announcing a client's Teredo address. + +.RI "If " "mode" " is " "\fBenabled" " (the default)," +all the multicast-capable network interfaces with a local IPv4 address +will be considered for the exchange of local discovery packets. +.RI "If " "mode" " is " "\fBforced" , +interfaces with a global address will be considered as well. +.RB "A value of " "disabled" " will turn off local discovery completely." + +.TP +.BI "DiscoveryInterfaces " "regex" +Restricts the network interfaces considered for local discovery +to those whose names match the given regular expression. +.IR "regex" " is a POSIX extended regular expression;" +it is not anchored so be careful to include +.BR "^" " and " "$" " if this is what you mean." + +.RI "For instance, on Linux, a " "regex" " of " "\fB^eth[0-9]" +will restrict local discovery to operate +on the regular ethernet interfaces only. +You may specify a list of interfaces using the branch operator, +.RB "as in " "^(eth0|eth1|ppp0)$" . +See regex(7) for more information about regular expressions. + +.TP +.BI "DiscoveryNetmask " "netmask" +Specifies which Teredo addresses are accepted from local peers. +An IPv6 Teredo address embeds a client's external IPv4 address. +When receiving announcements from the local networks, +Miredo compares this external address with its own. +.RI "The " "netmask" " parameter, written in dotted-quad IPv4 notation," +specifies which bits of the address should match. + +.RB "A value of " "255.255.255.255" " (the default)" +will restrict the accepted local peers to those with the +exact same external IPv4 address as ours, +and silently ignore announcements from other clients. +.RB "On the other hand, if " "\fInetmask" " is " "0.0.0.0" , +any local client will be accepted, regardless of its external address. + .SH RELAY OPTIONS .RI "The following directives are only available in " "relay" " mode." .RI "They are not available in " "(auto)client" " mode." diff --git a/libteredo/Makefile.am b/libteredo/Makefile.am index 45679e0..6c3bc1a 100644 --- a/libteredo/Makefile.am +++ b/libteredo/Makefile.am @@ -42,9 +42,9 @@ libteredo_common_la_LDFLAGS = -no-undefined # libteredo.la libteredo_la_SOURCES = init.c relay.c security.c security.h md5.c md5.h \ packets.c packets.h peerlist.c peerlist.h \ - clock.c clock.h stub.c + clock.c clock.h iothread.c iothread.h stub.c if TEREDO_CLIENT -libteredo_la_SOURCES += maintain.c maintain.h +libteredo_la_SOURCES += maintain.c maintain.h discovery.c discovery.h endif libteredo_la_DEPENDENCIES = libteredo.sym $(LIBADD) libteredo_la_LIBADD = @LIBJUDY@ @LIBRT@ $(LTLIBINTL) $(LIBADD) diff --git a/libteredo/discovery.c b/libteredo/discovery.c new file mode 100644 index 0000000..a736c52 --- /dev/null +++ b/libteredo/discovery.c @@ -0,0 +1,290 @@ +/* + * discovery.c - Teredo local client discovery procedure + * + * See "Teredo: Tunneling IPv6 over UDP through NATs" + * for more information + */ + +/*********************************************************************** + * Copyright © 2009 Jérémie Koenig. * + * This program is free software; you can redistribute and/or modify * + * it under the terms of the GNU General Public License as published * + * by the Free Software Foundation; version 2 of the license, or (at * + * your option) any later version. * + * * + * This program 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 this program; if not, you can get it from: * + * http://www.gnu.org/copyleft/gpl.html * + ***********************************************************************/ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include // malloc() +#include // mem???() + +#include // struct in6_addr +#include // struct ip6_hdr (for packets.h) +#include // inet_ntop() +#include +#include // getifaddrs() +#include // IFF_MULTICAST + +#include "teredo.h" +#include "teredo-udp.h" +#include "packets.h" +#include "v4global.h" +#include "security.h" +#include "clock.h" +#include "debug.h" +#include "iothread.h" +#include "tunnel.h" +#include "discovery.h" + + +struct teredo_discovery +{ + int refcnt; + struct teredo_discovery_interface + { + uint32_t addr; + uint32_t mask; + } *ifaces; + struct in6_addr src; + teredo_iothread *recv; + teredo_iothread *send; +}; + + +static const struct in6_addr in6addr_allnodes = +{ { { 0xff, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x1 } } }; + + +bool is_ipv4_discovered (teredo_discovery *d, uint32_t ip) +{ + int i; + + for (i = 0; d->ifaces[i].addr; i++) + if (((ip ^ d->ifaces[i].addr) & d->ifaces[i].mask) == 0) + return true; + + return false; +} + + +void SendDiscoveryBubble (teredo_discovery *d, int fd) +{ + struct ip_mreqn mreq; + int i, r; + + for (i = 0; d->ifaces[i].addr; i++) + { + memset (&mreq, 0, sizeof mreq); + mreq.imr_multiaddr.s_addr = htonl (TEREDO_DISCOVERY_IPV4); + mreq.imr_address.s_addr = d->ifaces[i].addr; + r = setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, + &mreq, sizeof mreq); + if (r < 0) + { + debug ("Could not set multicast interface"); + continue; + } + + teredo_send_bubble_anyway (fd, htonl (TEREDO_DISCOVERY_IPV4), + htons (IPPORT_TEREDO), + &d->src, &in6addr_allnodes); + } + + debug ("discovery bubble sent"); +} + + +bool IsDiscoveryBubble (const teredo_packet *restrict packet) +{ + return IsBubble(packet->ip6) + && packet->dest_ipv4 == htonl (TEREDO_DISCOVERY_IPV4) + && memcmp(&packet->ip6->ip6_dst, &in6addr_allnodes, 16) == 0; +} + + +// 5.2.8 Optional Local Client Discovery Procedure +static LIBTEREDO_NORETURN void *teredo_sendmcast_thread (void *opaque, int fd) +{ + teredo_discovery *d = (teredo_discovery *)opaque; + + for (;;) + { + SendDiscoveryBubble (d, fd); + + int interval = 200 + teredo_get_flbits (teredo_clock ()) % 100; + struct timespec delay = { .tv_sec = interval }; + while (clock_nanosleep (CLOCK_REALTIME, 0, &delay, &delay)); + } +} + + +/* Join the Teredo local discovery multicast group on a given interface */ +static void teredo_discovery_joinmcast(int sk, uint32_t ifaddr) +{ + struct ip_mreqn mreq; + int r; + char addr[20]; + + memset (&mreq, 0, sizeof mreq); + mreq.imr_address.s_addr = ifaddr; + mreq.imr_multiaddr.s_addr = htonl (TEREDO_DISCOVERY_IPV4); + r = setsockopt (sk, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof mreq); + + debug (r < 0 ? "Could not join the Teredo local discovery " + "multicast group on interface %.20s" + : "Listening for Teredo local discovery bubbles " + "on interface %.20s", + inet_ntop(AF_INET, &ifaddr, addr, sizeof addr)); +} + + +teredo_discovery * +teredo_discovery_start (const teredo_discovery_params *params, + int fd, const struct in6_addr *src, + teredo_iothread_proc proc, void *opaque) +{ + struct ifaddrs *ifaddrs, *ifa; + int r, ifno; + + teredo_discovery *d = malloc (sizeof (teredo_discovery)); + if (d == NULL) + { + return NULL; + } + + d->refcnt = 1; + + /* Get a list of the suitable interfaces */ + + r = getifaddrs(&ifaddrs); + if (r < 0) + { + debug ("Could not enumerate interfaces for local discovery"); + free (d); + return NULL; + } + + d->ifaces = NULL; + ifno = 0; + + for (ifa = ifaddrs; ifa; ifa = ifa->ifa_next) + { + struct teredo_discovery_interface *list = d->ifaces; + struct sockaddr_in *sa = (struct sockaddr_in *) ifa->ifa_addr; + struct sockaddr_in *ma = (struct sockaddr_in *) ifa->ifa_netmask; + + if (!ifa->ifa_addr || ifa->ifa_addr->sa_family != AF_INET) + continue; + if (!(ifa->ifa_flags & IFF_MULTICAST)) + continue; + + if (!params->forced + && is_ipv4_global_unicast (sa->sin_addr.s_addr)) + continue; + if (params->ifname_re + && regexec(params->ifname_re, ifa->ifa_name, 0, NULL, 0) != 0) + continue; + + list = realloc (list, (ifno + 2) * sizeof (*d->ifaces)); + if(list == NULL) + { + debug ("Out of memory."); + break; // memory error + } + + d->ifaces = list; + d->ifaces[ifno].addr = sa->sin_addr.s_addr; + d->ifaces[ifno].mask = ma->sin_addr.s_addr; + ifno++; + } + + freeifaddrs(ifaddrs); + + if (d->ifaces == NULL) + { + debug ("No suitable interfaces found for local discovery"); + free (d); + return NULL; + } + d->ifaces[ifno].addr = 0; + + /* Setup the multicast-receiving socket */ + + int sk = teredo_socket (0, htons (IPPORT_TEREDO)); + if (sk < 0) + { + debug ("Could not create the local discovery socket"); + free (d->ifaces); + free (d); + return NULL; + } + + for (ifno = 0; d->ifaces[ifno].addr; ifno++) + teredo_discovery_joinmcast (sk, d->ifaces[ifno].addr); + + d->recv = teredo_iothread_start (proc, opaque, sk); + + /* Start the discovery procedure thread */ + + memcpy (&d->src, src, sizeof d->src); + setsockopt (fd, IPPROTO_IP, IP_MULTICAST_LOOP, &(int){0}, sizeof (int)); + + d->send = teredo_iothread_start (teredo_sendmcast_thread, d, fd); + + return d; +} + + +struct teredo_discovery *teredo_discovery_grab (teredo_discovery *d) +{ + assert (d->refcnt); + + d->refcnt++; + return d; +} + + +void teredo_discovery_release (teredo_discovery *d) +{ + assert (d->refcnt); + + if (--d->refcnt) + return; + + assert (d->send == NULL); // ie. teredo_discovery_stop() has been called + assert (d->recv == NULL); + + free (d->ifaces); + free (d); +} + + +void teredo_discovery_stop (teredo_discovery *d) +{ + if (d->send) + { + teredo_iothread_stop (d->send, false); + d->send = NULL; + } + if (d->recv) + { + teredo_iothread_stop (d->recv, true); + d->recv = NULL; + } + + teredo_discovery_release(d); +} + diff --git a/libteredo/discovery.h b/libteredo/discovery.h new file mode 100644 index 0000000..e957097 --- /dev/null +++ b/libteredo/discovery.h @@ -0,0 +1,97 @@ +/* + * @file discovery.h + * @brief Local client discovery procedure + */ + +/*********************************************************************** + * Copyright © 2009 Jérémie Koenig. * + * This program is free software; you can redistribute and/or modify * + * it under the terms of the GNU General Public License as published * + * by the Free Software Foundation; version 2 of the license, or (at * + * your option) any later version. * + * * + * This program 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 this program; if not, you can get it from: * + * http://www.gnu.org/copyleft/gpl.html * + ***********************************************************************/ + +#ifndef LIBTEREDO_TEREDO_DISCOVERY_H +# define LIBTEREDO_TEREDO_DISCOVERY_H + +/** + * Teredo local client discovery procedure internal state. + */ +typedef struct teredo_discovery teredo_discovery; + +# ifdef __cplusplus +extern "C" { +# endif + +/** + * Tests whether a given IPv4 address belongs to one of the local networks + * a given discovery object operates on. + * + * @param ip IPv4 address to test. + */ +bool is_ipv4_discovered (teredo_discovery *d, uint32_t ip); + +/** + * Sends a discovery bubble. + * + * @param fd socket to send the bubble from. + */ +void SendDiscoveryBubble (teredo_discovery *d, int fd); + +/** + * Returns true if the given @p packet looks like a discovery bubble. + */ +bool IsDiscoveryBubble (const teredo_packet *restrict packet); + +/** + * Creates and starts threads for the Teredo local client discovery procedure. + * A list of interfaces suitable for the exchange of multicast local discovery + * bubbles will be assembled for later use by SendDiscoveryBubble(). + * + * @param params local discovery configuration parameters. + * @param fd socket used for sending the discovery bubbles. + * @param src source Teredo IPv6 address for the discovery bubbles. + * @param proc IO procedure to use for receiving multicast traffic + * @param opaque pointer passed to @p proc + */ +teredo_discovery * +teredo_discovery_start (const teredo_discovery_params *params, + int fd, const struct in6_addr *src, + teredo_iothread_proc proc, void *opaque); + +/** + * Protects a @c teredo_discovery object from destruction until + * teredo_discovery_release() is called. + * + * @param d the discovery object to grab + * @return @c d + */ +struct teredo_discovery *teredo_discovery_grab (teredo_discovery *d); + +/** + * Release a previously grabbed @c teredo_discovery object. + * A reference counter is used: this function must be called exactly as many + * times as teredo_discovery_grab() has been used. + */ +void teredo_discovery_release (teredo_discovery *d); + +/** + * Stops and destroys discovery threads created by teredo_discovery_start(). + * + * @param d non-NULL pointer from teredo_discovery_start(). + */ +void teredo_discovery_stop (teredo_discovery *d); + +# ifdef __cplusplus +} +# endif +#endif /* ifndef LIBTEREDO_TEREDO_DISCOVERY_H */ diff --git a/libteredo/iothread.c b/libteredo/iothread.c new file mode 100644 index 0000000..5ceb302 --- /dev/null +++ b/libteredo/iothread.c @@ -0,0 +1,94 @@ +/* + * iothread.c - IO thread management for Teredo tunnels + */ + +/*********************************************************************** + * Copyright © 2009 Jérémie Koenig * + * This program is free software; you can redistribute and/or modify * + * it under the terms of the GNU General Public License as published * + * by the Free Software Foundation; version 2 of the license, or (at * + * your option) any later version. * + * * + * This program 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 this program; if not, you can get it from: * + * http://www.gnu.org/copyleft/gpl.html * + ***********************************************************************/ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include // malloc() +#include +#include +#include + +#include "iothread.h" +#include "teredo-udp.h" // teredo_close() +#include "debug.h" + + +struct teredo_iothread +{ + pthread_t thread; + teredo_iothread_proc proc; + void *opaque; + int fd; +}; + + +static void *teredo_iothread_run (void *data) +{ + teredo_iothread *io = (teredo_iothread *)data; + return io->proc (io->opaque, io->fd); +} + + +teredo_iothread *teredo_iothread_start (teredo_iothread_proc proc, + void *opaque, int fd) +{ + teredo_iothread *io = malloc (sizeof *io); + if (io == NULL) + return NULL; + + io->proc = proc; + io->opaque = opaque; + io->fd = fd; + + if (pthread_create (&io->thread, NULL, teredo_iothread_run, io)) + { + debug ("Could not create IO thread for fd %d.", fd); + free (io); + return NULL; + } + +#ifndef NDEBUG + debug ("IO thread started (%p, %p, %p, %d)", io, proc, opaque, fd); +#endif + + return io; +} + + +void teredo_iothread_stop (teredo_iothread *io, bool close) +{ + pthread_cancel (io->thread); + pthread_join (io->thread, NULL); + + if (close) + teredo_close (io->fd); + +#ifndef NDEBUG + debug ("IO thread stopped (%p)", io); +#endif + + free (io); +} + + diff --git a/libteredo/iothread.h b/libteredo/iothread.h new file mode 100644 index 0000000..404b28c --- /dev/null +++ b/libteredo/iothread.h @@ -0,0 +1,57 @@ +/** + * @file iothread.h + * @brief IO thread management + */ + +/*********************************************************************** + * Copyright © 2009 Jérémie Koenig. * + * This program is free software; you can redistribute and/or modify * + * it under the terms of the GNU General Public License as published * + * by the Free Software Foundation; version 2 of the license, or (at * + * your option) any later version. * + * * + * This program 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 this program; if not, you can get it from: * + * http://www.gnu.org/copyleft/gpl.html * + ***********************************************************************/ + +#ifndef LIBTEREDO_IOTHREAD_H +# define LIBTEREDO_IOTHREAD_H + +# ifdef __cplusplus +extern "C" { +# endif + +typedef struct teredo_iothread teredo_iothread; +typedef void *(*teredo_iothread_proc) (void *opaque, int fd); + +/** + * Start a new IO thread. + * + * @param proc callback function to be run as the new thread. + * @param opaque opaque pointer passed to @p proc. + * @param fd file descriptor for this thread. + * + * @return the new IO thread on success, NULL on error. + */ +teredo_iothread *teredo_iothread_start (teredo_iothread_proc proc, + void *opaque, int fd); + +/** + * Stop an IO thread and destroy the teredo_iothread object. + * + * @param io the IO thread to stop. + * @param close whether to close the file descriptor associted with it. + */ +void teredo_iothread_stop (teredo_iothread *io, bool close); + +# ifdef __cplusplus +} +# endif /* ifdef __cplusplus */ +#endif /* ifndef LIBTEREDO_IOTHREAD_H */ + diff --git a/libteredo/libteredo.sym b/libteredo/libteredo.sym index 82832e3..053adc5 100644 --- a/libteredo/libteredo.sym +++ b/libteredo/libteredo.sym @@ -4,6 +4,7 @@ teredo_create teredo_destroy teredo_get_privdata teredo_set_client_mode +teredo_set_discovery_params teredo_set_relay_mode teredo_set_cone_flag teredo_set_icmpv6_callback diff --git a/libteredo/packets.c b/libteredo/packets.c index c323f92..52442c8 100644 --- a/libteredo/packets.c +++ b/libteredo/packets.c @@ -51,27 +51,34 @@ int -teredo_send_bubble (int fd, uint32_t ip, uint16_t port, - const struct in6_addr *src, const struct in6_addr *dst) +teredo_send_bubble_anyway (int fd, uint32_t ip, uint16_t port, + const struct in6_addr *src, + const struct in6_addr *dst) { - if (is_ipv4_global_unicast (ip)) + static const uint8_t head[] = + "\x60\x00\x00\x00" /* flow */ + "\x00\x00" /* plen = 0 */ + "\x3b" /* nxt = IPPROTO_NONE */ + "\x00" /* hlim = 0 */; + struct iovec iov[3] = { - static const uint8_t head[] = - "\x60\x00\x00\x00" /* flow */ - "\x00\x00" /* plen = 0 */ - "\x3b" /* nxt = IPPROTO_NONE */ - "\x00" /* hlim = 0 */; - struct iovec iov[3] = - { - { (void *)head, 8 }, - { (void *)src, 16 }, - { (void *)dst, 16 } - }; + { (void *)head, 8 }, + { (void *)src, 16 }, + { (void *)dst, 16 } + }; - return teredo_sendv (fd, iov, 3, ip, port) == 40 ? 0 : -1; - } + return teredo_sendv (fd, iov, 3, ip, port) == 40 ? 0 : -1; +} - return 0; + +int +teredo_send_bubble (int fd, uint32_t ip, uint16_t port, + const struct in6_addr *src, const struct in6_addr *dst) +{ + if (is_ipv4_global_unicast (ip)) + return teredo_send_bubble_anyway(fd, ip, port, src, dst); + else + return 0; /* FIXME: really ? */ } diff --git a/libteredo/packets.h b/libteredo/packets.h index 1e90f73..63794ba 100644 --- a/libteredo/packets.h +++ b/libteredo/packets.h @@ -76,6 +76,22 @@ int teredo_send_bubble (int fd, uint32_t ip, uint16_t port, const struct in6_addr *src, const struct in6_addr *dst); +/** + * Sends a Teredo bubble. + * + * This version does not check that @p ip is a global unicast address. + * + * @param ip destination IPv4 + * @param port destination UDP port + * @param src pointer to source IPv6 address + * @param dst pointer to destination IPv6 address + * + * @return 0 on success, -1 on error. + */ +int teredo_send_bubble_anyway (int fd, uint32_t ip, uint16_t port, + const struct in6_addr *src, + const struct in6_addr *dst); + static inline int teredo_reply_bubble (int fd, uint32_t ip, uint16_t port, const struct ip6_hdr *req) { diff --git a/libteredo/peerlist.h b/libteredo/peerlist.h index 33ce29b..8fbdf00 100644 --- a/libteredo/peerlist.h +++ b/libteredo/peerlist.h @@ -34,12 +34,13 @@ typedef struct teredo_peer size_t queue_left; teredo_clock_t last_rx; teredo_clock_t last_tx; + teredo_clock_t last_ping; uint32_t mapped_addr; uint16_t mapped_port; unsigned trusted:1; + unsigned local:1; unsigned bubbles:3; unsigned pings:3; - unsigned last_ping:9; } teredo_peer; @@ -82,7 +83,7 @@ static inline void TouchTransmit (teredo_peer *peer, teredo_clock_t now) static inline bool IsValid (const teredo_peer *peer, teredo_clock_t now) { - return (now - peer->last_rx) <= 30; + return (now - peer->last_rx) <= (peer->local ? 600 : 30); } diff --git a/libteredo/relay.c b/libteredo/relay.c index 410c423..62ee5c8 100644 --- a/libteredo/relay.c +++ b/libteredo/relay.c @@ -6,7 +6,7 @@ */ /*********************************************************************** - * Copyright © 2004-2007 Rémi Denis-Courmont. * + * Copyright © 2004-2009 Rémi Denis-Courmont and contributors. * * This program is free software; you can redistribute and/or modify * * it under the terms of the GNU General Public License as published * * by the Free Software Foundation; version 2 of the license, or (at * @@ -51,8 +51,10 @@ #include "maintain.h" #include "clock.h" #include "peerlist.h" +#include "iothread.h" #ifdef MIREDO_TEREDO_CLIENT # include "security.h" +# include "discovery.h" #endif #include "debug.h" #ifndef NDEBUG @@ -65,9 +67,11 @@ struct teredo_tunnel void *opaque; #ifdef MIREDO_TEREDO_CLIENT struct teredo_maintenance *maintenance; + struct teredo_discovery *discovery; teredo_state_up_cb up_cb; teredo_state_down_cb down_cb; + const teredo_discovery_params *disc_params; #endif teredo_recv_cb recv_cb; teredo_icmpv6_cb icmpv6_cb; @@ -84,11 +88,7 @@ struct teredo_tunnel } ratelimit; // Asynchronous packet reception - struct - { - pthread_t thread; - bool running; - } recv; + teredo_iothread *recv; int fd; }; @@ -174,6 +174,8 @@ TeredoRelay::EmitICMPv6Error (const void *packet, size_t length, #ifdef MIREDO_TEREDO_CLIENT +static LIBTEREDO_NORETURN void *teredo_recv_thread (void *opaque, int fd); + static void teredo_state_change (const teredo_state *state, void *self) { @@ -185,6 +187,12 @@ teredo_state_change (const teredo_state *state, void *self) if (tunnel->state.up) { + if (tunnel->discovery) + { + teredo_discovery_stop (tunnel->discovery); + tunnel->discovery = NULL; + } + /* * NOTE: we get an hold on both state and peer list locks here. * As such, in any case, attempting to acquire the state lock while @@ -200,9 +208,20 @@ teredo_state_change (const teredo_state *state, void *self) debug ("Internal IPv4 address: %s", inet_ntop (AF_INET, &tunnel->state.ipv4, b, sizeof (b))); #endif + + if (tunnel->disc_params) + { + teredo_discovery *d; + d = teredo_discovery_start (tunnel->disc_params, + tunnel->fd, + &tunnel->state.addr.ip6, + teredo_recv_thread, tunnel); + tunnel->discovery = d; + } } else if (previously_up) + /* FIXME: stop discovery? */ tunnel->down_cb (tunnel->opaque); /* @@ -228,7 +247,7 @@ static int CountPing (teredo_peer *peer, teredo_clock_t now) res = -1; // test must be separated by at least 2 seconds else - if (((now - peer->last_ping) & 0x1ff) <= 2) + if (now - peer->last_ping <= 2) res = 1; else res = 0; // can test again! @@ -430,12 +449,13 @@ int teredo_transmit (teredo_tunnel *restrict tunnel, } else { - p->trusted = p->bubbles = p->pings = 0; + p->trusted = p->local = p->bubbles = p->pings = 0; } - debug ("Connecting %s: %strusted, %svalid, %u pings, %u bubbles", + debug ("Connecting %s: %s%strusted, %svalid, %u pings, %u bubbles", created ? "" : inet_ntop(AF_INET, &p->mapped_addr, b, sizeof (b)), + !p->local ? "" : "LOCAL, ", p->trusted ? "" : "NOT ", IsValid (p, now) ? "" : "NOT ", p->pings, p->bubbles); @@ -474,12 +494,46 @@ int teredo_transmit (teredo_tunnel *restrict tunnel, inet_ntop (AF_INET6, &dst->ip6, b, sizeof (b)), res); return 0; } + + /* Client case 3: untrusted local peer */ + if (p->local && IsValid (p, now)) + { + teredo_enqueue_out (p, packet, length); + + int res = CountBubble (p, now); + uint32_t addr = p->mapped_addr; + uint16_t port = p->mapped_port; + + teredo_list_release (list); + + if (res == 0) + { + teredo_send_bubble_anyway (tunnel->fd, addr, port, + &s.addr.ip6, &dst->ip6); + + pthread_rwlock_rdlock (&tunnel->state_lock); + teredo_discovery *d = NULL; + if (tunnel->discovery) + d = teredo_discovery_grab (tunnel->discovery); + pthread_rwlock_unlock (&tunnel->state_lock); + + if (d != NULL) + SendDiscoveryBubble (d, tunnel->fd); + + teredo_discovery_release (d); + } + + if (res == -1) + // TODO: blacklist as a local peer ? + teredo_send_unreach (tunnel, ICMP6_DST_UNREACH_ADDR, + packet, length); + + return 0; + } #endif // Untrusted Teredo client - /* Client case 3: TODO: implement local discovery */ - if (created) /* Unknown Teredo clients */ SetMapping (p, IN6_TEREDO_IPV4 (dst), IN6_TEREDO_PORT (dst)); @@ -524,6 +578,34 @@ int teredo_transmit (teredo_tunnel *restrict tunnel, } +#ifdef MIREDO_TEREDO_CLIENT +/** + * Checks whether a given packet qualifies as a local one. + * Must be called with the state lock held for reading. + */ +static bool +teredo_islocal (teredo_tunnel *restrict tunnel, + const struct teredo_packet *restrict packet) +{ + if (!tunnel->disc_params || !tunnel->discovery) + return false; // local discovery disabled + + union teredo_addr our = tunnel->state.addr; + if (IN6_TEREDO_PREFIX (&packet->ip6->ip6_src) != our.teredo.prefix) + return false; // not a teredo address + + uint32_t client_ip = IN6_TEREDO_IPV4 (&packet->ip6->ip6_src); + if ((client_ip ^ ~our.teredo.client_ip) & tunnel->disc_params->netmask) + return false; // non-matching mapped IPv4 + + if (!is_ipv4_discovered (tunnel->discovery, packet->source_ipv4)) + return false; // non-matching source IPv4 + + return true; +} +#endif + + static void teredo_predecap (teredo_tunnel *restrict tunnel, teredo_peer *restrict peer, teredo_clock_t now) @@ -576,9 +658,11 @@ teredo_run_inner (teredo_tunnel *restrict tunnel, return; // malformatted IPv6 packet } - teredo_state s; pthread_rwlock_rdlock (&tunnel->state_lock); - s = tunnel->state; + teredo_state s = tunnel->state; +#ifdef MIREDO_TEREDO_CLIENT + bool islocal = teredo_islocal (tunnel, packet); +#endif /* * We can afford to use a slightly outdated state, but we cannot afford to * use an inconsistent state, hence this lock. Also, we cannot call @@ -670,6 +754,62 @@ teredo_run_inner (teredo_tunnel *restrict tunnel, return; } + /* Actual packet reception, either as a relay or a client */ + + teredo_clock_t now = teredo_clock (); + + // Checks source IPv6 address / looks up peer in the list: + struct teredo_peerlist *list = tunnel->list; + teredo_peer *p = teredo_list_lookup (list, &ip6->ip6_src, NULL); + +#ifdef MIREDO_TEREDO_CLIENT + /* + * Client case 4 (local discovery bubble) + * + * NOTE: In addition to their announcement role, local discovery + * bubbles are used in a way similar to indirect bubbles: when + * transmitting to an untrusted local peer, a client sends both a + * direct unicast bubble and a local discovery bubble, then waits for + * the unicast reply we send below. (client tx case 3 and rx case 5) + * + * So we must check discovery bubbles right now, before case 1 gets a + * chance to discard them, otherwise a trusted local peer will never + * get a chance to trust us as well. + */ + if (islocal && IsDiscoveryBubble (packet)) + { + if (p == NULL) + { + p = teredo_list_lookup (list, &ip6->ip6_src, &(bool){ false }); + if (p == NULL) { + debug ("Out of memory."); + return; // memory error + } + p->trusted = 0; + p->local = 0; + } + + /* reset the number of bubbles when a peer becomes local */ + if (!p->local) + p->bubbles = 0; + + SetMappingFromPacket (p, packet); + p->local = 1; + TouchReceive (p, now); + teredo_list_release (list); + + if (CountBubble (p, now) != 0) + return; + + debug ("Replying to discovery bubble"); + teredo_send_bubble_anyway (tunnel->fd, + packet->source_ipv4, + packet->source_port, + &s.addr.ip6, &ip6->ip6_src); + return; + } +#endif + /* * NOTE: * Clients are supposed to check that the destination is their Teredo IPv6 @@ -680,8 +820,9 @@ teredo_run_inner (teredo_tunnel *restrict tunnel, * is served by them (i.e. egress filtering from Teredo to native IPv6). * The IPv6 stack firewall should be used to that end. * - * Multicast destinations are not supposed to occur, not even for hole - * punching. We drop them as a precautionary measure. + * With the exception of local client discovery bubbles, multicast + * destinations are not supposed to occur, not even for hole punching. + * We drop them as a precautionary measure. * * We purposedly don't drop packets on the basis of link-local destination * as it breaks hole punching: we send Teredo bubbles with a link-local @@ -691,20 +832,14 @@ teredo_run_inner (teredo_tunnel *restrict tunnel, * */ if (ip6->ip6_dst.s6_addr[0] == 0xff) - { + { + if (p != NULL) + teredo_list_release (list); debug ("Multicast destination %s not supported.", inet_ntop (AF_INET6, &ip6->ip6_dst.s6_addr, b, sizeof b)); return; } - /* Actual packet reception, either as a relay or a client */ - - teredo_clock_t now = teredo_clock (); - - // Checks source IPv6 address / looks up peer in the list: - struct teredo_peerlist *list = tunnel->list; - teredo_peer *p = teredo_list_lookup (list, &ip6->ip6_src, NULL); - if (p != NULL) { @@ -744,12 +879,27 @@ teredo_run_inner (teredo_tunnel *restrict tunnel, // Client case 3 (unknown or untrusted matching Teredo client): if (IN6_MATCHES_TEREDO_CLIENT (&ip6->ip6_src, packet->source_ipv4, packet->source_port) +#ifdef MIREDO_TEREDO_CLIENT + // Client case 5 (untrusted local peer) + || (p != NULL && p->local + && (packet->source_ipv4 == p->mapped_addr) + && (packet->source_port == p->mapped_port)) + // Extension: packet from unknown local peer (faster discovery) + || (p == NULL && islocal) +#endif // Extension: allow mismatch (i.e. clients behind symmetric NATs) || (IsBubble (ip6) && (CheckBubble (packet) == 0))) { #ifdef MIREDO_TEREDO_CLIENT if (IsClient (tunnel) && (p == NULL)) + { p = teredo_list_lookup (list, &ip6->ip6_src, &(bool){ false }); + if (p == NULL) { + debug ("Out of memory."); + return; // memory error + } + p->local = islocal; + } #endif /* * Relays are explicitly allowed to drop packets from @@ -774,8 +924,6 @@ teredo_run_inner (teredo_tunnel *restrict tunnel, tunnel->recv_cb (tunnel->opaque, ip6, length); return; } - - // TODO: local Teredo } #ifdef MIREDO_TEREDO_CLIENT else @@ -783,8 +931,6 @@ teredo_run_inner (teredo_tunnel *restrict tunnel, assert (IN6_TEREDO_PREFIX (&ip6->ip6_src) != s.addr.teredo.prefix); assert (IsClient (tunnel)); - // TODO: implement client cases 4 & 5 for local Teredo - /* * Default: Client case 6: * (unknown non-Teredo node or Tereco client with incorrect mapping): @@ -815,7 +961,8 @@ teredo_run_inner (teredo_tunnel *restrict tunnel, { p->mapped_port = 0; p->mapped_addr = 0; - p->trusted = p->bubbles = p->pings = 0; + p->trusted = p->local = 0; + p->bubbles = p->pings = 0; } } @@ -933,13 +1080,13 @@ void teredo_destroy (teredo_tunnel *t) * we need not lock anyting in teredo_destroy(). */ if (t->maintenance != NULL) teredo_maintenance_stop (t->maintenance); + + if (t->discovery != NULL) + teredo_discovery_stop (t->discovery); #endif - if (t->recv.running) - { - pthread_cancel (t->recv.thread); - pthread_join (t->recv.thread, NULL); - } + if (t->recv != NULL) + teredo_iothread_stop (t->recv, false); teredo_list_destroy (t->list); pthread_rwlock_destroy (&t->state_lock); @@ -949,7 +1096,7 @@ void teredo_destroy (teredo_tunnel *t) } -static LIBTEREDO_NORETURN void *teredo_recv_thread (void *t) +static LIBTEREDO_NORETURN void *teredo_recv_thread (void *t, int fd) { teredo_tunnel *tunnel = (teredo_tunnel *)t; @@ -957,7 +1104,7 @@ static LIBTEREDO_NORETURN void *teredo_recv_thread (void *t) { struct teredo_packet packet; - if (teredo_wait_recv (tunnel->fd, &packet) == 0) + if (teredo_wait_recv (fd, &packet) == 0) { pthread_setcancelstate (PTHREAD_CANCEL_DISABLE, NULL); teredo_run_inner (tunnel, &packet); @@ -972,13 +1119,13 @@ int teredo_run_async (teredo_tunnel *t) assert (t != NULL); /* already running */ - if (t->recv.running) + if (t->recv) return -1; - if (pthread_create (&t->recv.thread, NULL, teredo_recv_thread, t)) + t->recv = teredo_iothread_start (teredo_recv_thread, t, t->fd); + if (t->recv == NULL) return -1; - t->recv.running = true; return 0; } @@ -1072,6 +1219,17 @@ int teredo_set_client_mode (teredo_tunnel *restrict t, return -1; } + /* expand the list's expiration time to handle local peers */ + teredo_peerlist *newlist = teredo_list_create (MAX_PEERS, 600); + if (newlist == NULL) + { + debug ("Could not create new list for client mode."); + pthread_rwlock_unlock (&t->state_lock); + return -1; + } + teredo_list_destroy (t->list); + t->list = newlist; + struct teredo_maintenance *m; m = teredo_maintenance_start (t->fd, teredo_state_change, t, s, s2, 0, 0, 0, 0); @@ -1089,6 +1247,23 @@ int teredo_set_client_mode (teredo_tunnel *restrict t, } +int teredo_set_discovery_params (teredo_tunnel *restrict t, + const teredo_discovery_params *p) +{ +#ifdef MIREDO_TEREDO_CLIENT + pthread_rwlock_wrlock (&t->state_lock); + assert (t != NULL && IsClient(t)); + t->disc_params = p; + pthread_rwlock_unlock (&t->state_lock); + return 0; +#else + (void)t; + (void)p; + return -1; +#endif +} + + void *teredo_set_privdata (teredo_tunnel *t, void *opaque) { assert (t != NULL); diff --git a/libteredo/tunnel.h b/libteredo/tunnel.h index 3de29f8..4f79088 100644 --- a/libteredo/tunnel.h +++ b/libteredo/tunnel.h @@ -12,7 +12,7 @@ */ /*********************************************************************** - * Copyright © 2004-2006 Rémi Denis-Courmont. * + * Copyright © 2004-2009 Rémi Denis-Courmont and contributors. * * This program is free software; you can redistribute and/or modify * * it under the terms of the GNU General Public License as published * * by the Free Software Foundation; version 2 of the license, or (at * @@ -213,6 +213,49 @@ int teredo_set_client_mode (teredo_tunnel *restrict t, const char *s1, const char *s2); /** + * Parameters for the local client discovery procedure. + */ +typedef struct teredo_discovery_params +{ + /** + * Specifies whether the network interfaces with a global address + * should be considered for local discovery. + */ + bool forced; + + /** + * Regular expression for network interface filtering. + * If this is a non-@c NULL pointer to a @c regex_t object (initialized + * with regcomp()), only the network interfaces whose names match this + * regex will be considered for local discovery. + */ + regex_t *ifname_re; + + /** + * Netmask (in network byte order) for comparing the mapped external + * IPv4 address of the local clients with our own. + */ + uint32_t netmask; + +} teredo_discovery_params; + +/** + * Enables the Teredo local client discovery procedure. + * teredo_set_client_mode() must have been called for the given tunnel prior to + * invoking this function. + * + * @param t Tereo tunnel instance + * @param p local discovery parameters + * + * Thread-safety: This function is thread-safe. + * + * @return 0 on success, -1 in case of error. + * In case of error, the teredo_tunnel instance is not modifed. + */ +int teredo_set_discovery_params (teredo_tunnel *restrict t, + const teredo_discovery_params *p); + +/** * Sets the private data pointer of a Teredo tunnel instance. * This value is passed to callbacks. * diff --git a/src/relayd.c b/src/relayd.c index e9b51c3..ce6ad62 100644 --- a/src/relayd.c +++ b/src/relayd.c @@ -3,7 +3,7 @@ */ /*********************************************************************** - * Copyright © 2004-2007 Rémi Denis-Courmont. * + * Copyright © 2004-2009 Rémi Denis-Courmont and contributors. * * This program is free software; you can redistribute and/or modify * * it under the terms of the GNU General Public License as published * * by the Free Software Foundation; version 2 of the license, or (at * @@ -199,6 +199,69 @@ ParseRelayType (miredo_conf *conf, const char *name, int *type) } +static bool +ParseLocalDiscovery (miredo_conf *conf, const char *name, + teredo_discovery_params **disc_params) +{ + unsigned line; + char *val = miredo_conf_get (conf, name, &line); + + if (val == NULL) + return true; + + if (strcasecmp (val, "enabled") == 0) + (*disc_params)->forced = false; + else + if (strcasecmp (val, "forced") == 0) + (*disc_params)->forced = false; + else + if (strcasecmp (val, "disabled") == 0) + *disc_params = NULL; + else + { + syslog (LOG_ERR, _("Invalid discovery mode \"%s\" at line %u"), + val, line); + free (val); + return false; + } + free (val); + return true; +} + + +static bool +ParseDiscoveryInterfaces (miredo_conf *conf, const char *name, regex_t **preg) +{ + unsigned line; + char *val = miredo_conf_get (conf, name, &line); + + if (val == NULL) + { + *preg = NULL; + return true; + } + + int r = regcomp (*preg, val, REG_EXTENDED); + if (r != 0) + { + char err[100]; + regerror (r, *preg, err, sizeof err); + + /* TRANSLATORS: the second %s is an error message for the + * failed regex compilation */ + syslog (LOG_ERR, _("Invalid regex \"%s\" at line %u: %s"), + val, line, err); + + free (val); + regfree (*preg); + return false; + } + + free(val); + return true; +} + + #ifdef MIREDO_TEREDO_CLIENT static tun6 * create_dynamic_tunnel (const char *ifname, int *pfd) @@ -311,15 +374,23 @@ miredo_down_callback (void *data) static int -setup_client (teredo_tunnel *client, const char *server, const char *server2) +setup_client (teredo_tunnel *client, const char *server, const char *server2, + teredo_discovery_params *disc_params) { + int r; + teredo_set_state_cb (client, miredo_up_callback, miredo_down_callback); - return teredo_set_client_mode (client, server, server2); + r = teredo_set_client_mode (client, server, server2); + + if (disc_params && r == 0) + r = teredo_set_discovery_params (client, disc_params); + + return r; } #else # define create_dynamic_tunnel( a, b ) NULL # define destroy_dynamic_tunnel( a, b ) (void)0 -# define setup_client( a, b, c ) (-1) +# define setup_client( a, b, c, d ) (-1) #endif @@ -454,6 +525,14 @@ relay_run (miredo_conf *conf, const char *server_name) #ifdef MIREDO_TEREDO_CLIENT const char *server_name2 = NULL; char namebuf[NI_MAXHOST], namebuf2[NI_MAXHOST]; + + regex_t preg; + teredo_discovery_params ldp = + { + .forced = false, + .netmask = 0xffffffff, + .ifname_re = &preg, + }, *disc_params = &ldp; #endif uint16_t mtu = 1280; bool cone = false; @@ -482,6 +561,14 @@ relay_run (miredo_conf *conf, const char *server_name) server_name2 = namebuf2; } } + + if (!ParseLocalDiscovery (conf, "LocalDiscovery", &disc_params) + || !ParseDiscoveryInterfaces (conf, "DiscoveryInterfaces", &ldp.ifname_re) + || !miredo_conf_parse_IPv4 (conf, "DiscoveryNetmask", &ldp.netmask)) + { + syslog (LOG_ALERT, _("Fatal configuration error")); + return -2; + } #else syslog (LOG_ALERT, _("Unsupported Teredo client mode")); syslog (LOG_ALERT, _("Fatal configuration error")); @@ -570,7 +657,8 @@ relay_run (miredo_conf *conf, const char *server_name) teredo_set_icmpv6_callback (relay, miredo_icmp6_callback); retval = (mode & TEREDO_CLIENT) - ? setup_client (relay, server_name, server_name2) + ? setup_client (relay, server_name, server_name2, + disc_params) : setup_relay (relay, prefix.teredo.prefix, cone); /*