Sisyphus repository
Last update: 1 october 2023 | SRPMs: 18631 | Visits: 37863716
en ru br
ALT Linux repos
S:1.2.6-alt3.gite5f565

Group :: System/Servers
RPM: miredo

 Main   Changelog   Spec   Patches   Sources   Download   Gear   Bugs and FR  Repocop 

Patch: miredo-1.2.6-alt1.patch
Download


 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 <config.h>
+#endif
+
+#include <stdbool.h>
+#include <inttypes.h>
+#include <stdlib.h> // malloc()
+#include <string.h> // mem???()
+
+#include <netinet/in.h> // struct in6_addr
+#include <netinet/ip6.h> // struct ip6_hdr (for packets.h)
+#include <arpa/inet.h> // inet_ntop()
+#include <pthread.h>
+#include <ifaddrs.h> // getifaddrs()
+#include <net/if.h> // 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 <config.h>
+#endif
+
+#include <stdbool.h>
+#include <stdlib.h> // malloc()
+#include <assert.h>
+#include <inttypes.h>
+#include <pthread.h>
+
+#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 ? "<unknown>" : 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);
 	
 				/*
 
design & coding: Vladimir Lettiev aka crux © 2004-2005, Andrew Avramenko aka liks © 2007-2008
current maintainer: Michael Shigorin