diff --git a/changes/ticket14710 b/changes/ticket14710
new file mode 100644
index 0000000000..2b6cb2060f
--- /dev/null
+++ b/changes/ticket14710
@@ -0,0 +1,10 @@
+ o Code simplification and refactoring:
+ - Move the hacky fallback code out of get_interface_address6()
+ into separate function and get it covered with unit-tests. Resolves
+ ticket 14710.
+
+ o Minor bugfixes:
+ - When attempting to use fallback technique for network interface
+ lookup, disregard loopback and multicast addresses since they are
+ unsuitable for public communications.
+
diff --git a/src/common/address.c b/src/common/address.c
index 42a116a91e..6bd107889a 100644
--- a/src/common/address.c
+++ b/src/common/address.c
@@ -1504,6 +1504,80 @@ tor_addr_is_multicast(const tor_addr_t *a)
return 0;
}
+/** Attempt to retrieve IP address of current host by utilizing some
+ * UDP socket trickery. Only look for address of given family.
+ * Set result to *addr. Return 0 on success, -1 on failure.
+ */
+STATIC int
+get_interface_address6_via_udp_socket_hack(int severity,
+ sa_family_t family,
+ tor_addr_t *addr)
+{
+ struct sockaddr_storage my_addr, target_addr;
+ int sock=-1, r=-1;
+ socklen_t addr_len;
+
+ memset(addr, 0, sizeof(tor_addr_t));
+ memset(&target_addr, 0, sizeof(target_addr));
+
+ /* Don't worry: no packets are sent. We just need to use a real address
+ * on the actual Internet. */
+ if (family == AF_INET6) {
+ struct sockaddr_in6 *sin6 = (struct sockaddr_in6*)&target_addr;
+ /* Use the "discard" service port */
+ sin6->sin6_port = htons(9);
+ sock = tor_open_socket(PF_INET6,SOCK_DGRAM,IPPROTO_UDP);
+ addr_len = (socklen_t)sizeof(struct sockaddr_in6);
+ sin6->sin6_family = AF_INET6;
+ S6_ADDR16(sin6->sin6_addr)[0] = htons(0x2002); /* 2002:: */
+ } else if (family == AF_INET) {
+ struct sockaddr_in *sin = (struct sockaddr_in*)&target_addr;
+ /* Use the "discard" service port */
+ sin->sin_port = htons(9);
+ sock = tor_open_socket(PF_INET,SOCK_DGRAM,IPPROTO_UDP);
+ addr_len = (socklen_t)sizeof(struct sockaddr_in);
+ sin->sin_family = AF_INET;
+ sin->sin_addr.s_addr = htonl(0x12000001); /* 18.0.0.1 */
+ } else {
+ return -1;
+ }
+
+ if (sock < 0) {
+ int e = tor_socket_errno(-1);
+ log_fn(severity, LD_NET, "unable to create socket: %s",
+ tor_socket_strerror(e));
+ goto err;
+ }
+
+ if (tor_connect_socket(sock,(struct sockaddr *)&target_addr,
+ addr_len) < 0) {
+ int e = tor_socket_errno(sock);
+ log_fn(severity, LD_NET, "connect() failed: %s", tor_socket_strerror(e));
+ goto err;
+ }
+
+ if (tor_getsockname(sock,(struct sockaddr*)&my_addr, &addr_len)) {
+ int e = tor_socket_errno(sock);
+ log_fn(severity, LD_NET, "getsockname() to determine interface failed: %s",
+ tor_socket_strerror(e));
+ goto err;
+ }
+
+ if (tor_addr_from_sockaddr(addr, (struct sockaddr*)&my_addr, NULL) == 0) {
+ if (tor_addr_is_loopback(addr) || tor_addr_is_multicast(addr)) {
+ log_fn(severity, LD_NET, "Address that we determined via UDP socket"
+ " magic is unsuitable for public comms.");
+ } else {
+ r=0;
+ }
+ }
+
+ err:
+ if (sock >= 0)
+ tor_close_socket(sock);
+ return r;
+}
+
/** Set *addr to the IP address (if any) of whatever interface
* connects to the Internet. This address should only be used in checking
* whether our address has changed. Return 0 on success, -1 on failure.
@@ -1513,9 +1587,6 @@ get_interface_address6,(int severity, sa_family_t family, tor_addr_t *addr))
{
/* XXX really, this function should yield a smartlist of addresses. */
smartlist_t *addrs;
- int sock=-1, r=-1;
- struct sockaddr_storage my_addr, target_addr;
- socklen_t addr_len;
tor_assert(addr);
/* Try to do this the smart way if possible. */
@@ -1543,55 +1614,7 @@ get_interface_address6,(int severity, sa_family_t family, tor_addr_t *addr))
}
/* Okay, the smart way is out. */
- memset(addr, 0, sizeof(tor_addr_t));
- memset(&target_addr, 0, sizeof(target_addr));
- /* Don't worry: no packets are sent. We just need to use a real address
- * on the actual Internet. */
- if (family == AF_INET6) {
- struct sockaddr_in6 *sin6 = (struct sockaddr_in6*)&target_addr;
- /* Use the "discard" service port */
- sin6->sin6_port = htons(9);
- sock = tor_open_socket(PF_INET6,SOCK_DGRAM,IPPROTO_UDP);
- addr_len = (socklen_t)sizeof(struct sockaddr_in6);
- sin6->sin6_family = AF_INET6;
- S6_ADDR16(sin6->sin6_addr)[0] = htons(0x2002); /* 2002:: */
- } else if (family == AF_INET) {
- struct sockaddr_in *sin = (struct sockaddr_in*)&target_addr;
- /* Use the "discard" service port */
- sin->sin_port = htons(9);
- sock = tor_open_socket(PF_INET,SOCK_DGRAM,IPPROTO_UDP);
- addr_len = (socklen_t)sizeof(struct sockaddr_in);
- sin->sin_family = AF_INET;
- sin->sin_addr.s_addr = htonl(0x12000001); /* 18.0.0.1 */
- } else {
- return -1;
- }
- if (sock < 0) {
- int e = tor_socket_errno(-1);
- log_fn(severity, LD_NET, "unable to create socket: %s",
- tor_socket_strerror(e));
- goto err;
- }
-
- if (connect(sock,(struct sockaddr *)&target_addr, addr_len) < 0) {
- int e = tor_socket_errno(sock);
- log_fn(severity, LD_NET, "connect() failed: %s", tor_socket_strerror(e));
- goto err;
- }
-
- if (getsockname(sock,(struct sockaddr*)&my_addr, &addr_len)) {
- int e = tor_socket_errno(sock);
- log_fn(severity, LD_NET, "getsockname() to determine interface failed: %s",
- tor_socket_strerror(e));
- goto err;
- }
-
- tor_addr_from_sockaddr(addr, (struct sockaddr*)&my_addr, NULL);
- r=0;
- err:
- if (sock >= 0)
- tor_close_socket(sock);
- return r;
+ return get_interface_address6_via_udp_socket_hack(severity,family,addr);
}
/* ======
diff --git a/src/common/address.h b/src/common/address.h
index df835e917a..cd80615f93 100644
--- a/src/common/address.h
+++ b/src/common/address.h
@@ -274,6 +274,9 @@ tor_addr_port_t *tor_addr_port_new(const tor_addr_t *addr, uint16_t port);
#ifdef ADDRESS_PRIVATE
STATIC smartlist_t *get_interface_addresses_raw(int severity);
+STATIC int get_interface_address6_via_udp_socket_hack(int severity,
+ sa_family_t family,
+ tor_addr_t *addr);
#ifdef HAVE_IFADDRS_TO_SMARTLIST
STATIC smartlist_t *ifaddrs_to_smartlist(const struct ifaddrs *ifa);
diff --git a/src/common/compat.c b/src/common/compat.c
index 1788e32ee3..8da7ef3f69 100644
--- a/src/common/compat.c
+++ b/src/common/compat.c
@@ -1156,12 +1156,20 @@ mark_socket_open(tor_socket_t s)
/** @} */
/** As socket(), but counts the number of open sockets. */
-tor_socket_t
-tor_open_socket(int domain, int type, int protocol)
+MOCK_IMPL(tor_socket_t,
+tor_open_socket,(int domain, int type, int protocol))
{
return tor_open_socket_with_extensions(domain, type, protocol, 1, 0);
}
+/** Mockable wrapper for connect(). */
+MOCK_IMPL(tor_socket_t,
+tor_connect_socket,(tor_socket_t socket,const struct sockaddr *address,
+ socklen_t address_len))
+{
+ return connect(socket,address,address_len);
+}
+
/** As socket(), but creates a nonblocking socket and
* counts the number of open sockets. */
tor_socket_t
@@ -1308,6 +1316,14 @@ get_n_open_sockets(void)
return n;
}
+/** Mockable wrapper for getsockname(). */
+MOCK_IMPL(int,
+tor_getsockname,(tor_socket_t socket, struct sockaddr *address,
+ socklen_t *address_len))
+{
+ return getsockname(socket, address, address_len);
+}
+
/** Turn socket into a nonblocking socket. Return 0 on success, -1
* on failure.
*/
diff --git a/src/common/compat.h b/src/common/compat.h
index 11b41cded9..5189b7e056 100644
--- a/src/common/compat.h
+++ b/src/common/compat.h
@@ -463,7 +463,8 @@ int tor_close_socket(tor_socket_t s);
tor_socket_t tor_open_socket_with_extensions(
int domain, int type, int protocol,
int cloexec, int nonblock);
-tor_socket_t tor_open_socket(int domain, int type, int protocol);
+MOCK_DECL(tor_socket_t,
+tor_open_socket,(int domain, int type, int protocol));
tor_socket_t tor_open_socket_nonblocking(int domain, int type, int protocol);
tor_socket_t tor_accept_socket(tor_socket_t sockfd, struct sockaddr *addr,
socklen_t *len);
@@ -474,8 +475,15 @@ tor_socket_t tor_accept_socket_with_extensions(tor_socket_t sockfd,
struct sockaddr *addr,
socklen_t *len,
int cloexec, int nonblock);
+MOCK_DECL(tor_socket_t,
+tor_connect_socket,(tor_socket_t socket,const struct sockaddr *address,
+ socklen_t address_len));
int get_n_open_sockets(void);
+MOCK_DECL(int,
+tor_getsockname,(tor_socket_t socket, struct sockaddr *address,
+ socklen_t *address_len));
+
#define tor_socket_send(s, buf, len, flags) send(s, buf, len, flags)
#define tor_socket_recv(s, buf, len, flags) recv(s, buf, len, flags)
diff --git a/src/test/test_address.c b/src/test/test_address.c
index 424a6352b0..317b370912 100644
--- a/src/test/test_address.c
+++ b/src/test/test_address.c
@@ -130,8 +130,8 @@ test_address_ifaddrs_to_smartlist(void *arg)
ipv6_sockaddr = tor_malloc(sizeof(struct sockaddr_in6));
ipv6_sockaddr->sin6_family = AF_INET6;
ipv6_sockaddr->sin6_port = 0;
- inet_pton(AF_INET6, "2001:db8:8714:3a90::12",
- &(ipv6_sockaddr->sin6_addr));
+ tor_inet_pton(AF_INET6, "2001:db8:8714:3a90::12",
+ &(ipv6_sockaddr->sin6_addr));
ifa = tor_malloc(sizeof(struct ifaddrs));
ifa_ipv4 = tor_malloc(sizeof(struct ifaddrs));
@@ -452,10 +452,181 @@ test_address_get_if_addrs_ioctl(void *arg)
#endif
+#define FAKE_SOCKET_FD (42)
+
+tor_socket_t
+fake_open_socket(int domain, int type, int protocol)
+{
+ (void)domain;
+ (void)type;
+ (void)protocol;
+
+ return FAKE_SOCKET_FD;
+}
+
+static int last_connected_socket_fd = 0;
+
+static int connect_retval = 0;
+
+tor_socket_t
+pretend_to_connect(tor_socket_t socket, const struct sockaddr *address,
+ socklen_t address_len)
+{
+ (void)address;
+ (void)address_len;
+
+ last_connected_socket_fd = socket;
+
+ return connect_retval;
+}
+
+static struct sockaddr *mock_addr = NULL;
+
+int
+fake_getsockname(tor_socket_t socket, struct sockaddr *address,
+ socklen_t *address_len)
+{
+ socklen_t bytes_to_copy = 0;
+
+ if (!mock_addr)
+ return -1;
+
+ if (mock_addr->sa_family == AF_INET) {
+ bytes_to_copy = sizeof(struct sockaddr_in);
+ } else if (mock_addr->sa_family == AF_INET6) {
+ bytes_to_copy = sizeof(struct sockaddr_in6);
+ } else {
+ return -1;
+ }
+
+ if (*address_len < bytes_to_copy) {
+ return -1;
+ }
+
+ memcpy(address,mock_addr,bytes_to_copy);
+ *address_len = bytes_to_copy;
+
+ return 0;
+}
+
+static void
+test_address_udp_socket_trick_whitebox(void *arg)
+{
+ int hack_retval;
+ tor_addr_t *addr_from_hack = tor_malloc_zero(sizeof(tor_addr_t));
+ struct sockaddr_in6 *mock_addr6;
+ struct sockaddr_in6 *ipv6_to_check =
+ tor_malloc_zero(sizeof(struct sockaddr_in6));
+
+ (void)arg;
+
+ MOCK(tor_open_socket,fake_open_socket);
+ MOCK(tor_connect_socket,pretend_to_connect);
+ MOCK(tor_getsockname,fake_getsockname);
+
+ mock_addr = tor_malloc_zero(sizeof(struct sockaddr_storage));
+ sockaddr_in_from_string("23.32.246.118",(struct sockaddr_in *)mock_addr);
+
+ hack_retval =
+ get_interface_address6_via_udp_socket_hack(LOG_DEBUG,
+ AF_INET, addr_from_hack);
+
+ tt_int_op(hack_retval,==,0);
+ tt_assert(tor_addr_eq_ipv4h(addr_from_hack, 0x1720f676));
+
+ /* Now, lets do an IPv6 case. */
+ memset(mock_addr,0,sizeof(struct sockaddr_storage));
+
+ mock_addr6 = (struct sockaddr_in6 *)mock_addr;
+ mock_addr6->sin6_family = AF_INET6;
+ mock_addr6->sin6_port = 0;
+ tor_inet_pton(AF_INET6,"2001:cdba::3257:9652",&(mock_addr6->sin6_addr));
+
+ hack_retval =
+ get_interface_address6_via_udp_socket_hack(LOG_DEBUG,
+ AF_INET6, addr_from_hack);
+
+ tt_int_op(hack_retval,==,0);
+
+ tor_addr_to_sockaddr(addr_from_hack,0,(struct sockaddr *)ipv6_to_check,
+ sizeof(struct sockaddr_in6));
+
+ tt_assert(sockaddr_in6_are_equal(mock_addr6,ipv6_to_check));
+
+ UNMOCK(tor_open_socket);
+ UNMOCK(tor_connect_socket);
+ UNMOCK(tor_getsockname);
+
+ done:
+ tor_free(ipv6_to_check);
+ tor_free(mock_addr);
+ tor_free(addr_from_hack);
+ return;
+}
+
+static void
+test_address_udp_socket_trick_blackbox(void *arg)
+{
+ /* We want get_interface_address6_via_udp_socket_hack() to yield
+ * the same valid address that get_interface_address6() returns.
+ * If the latter is unable to find a valid address, we want
+ * _hack() to fail and return-1.
+ *
+ * Furthermore, we want _hack() never to crash, even if
+ * get_interface_addresses_raw() is returning NULL.
+ */
+
+ tor_addr_t addr4;
+ tor_addr_t addr4_to_check;
+ tor_addr_t addr6;
+ tor_addr_t addr6_to_check;
+ int retval, retval_reference;
+
+ (void)arg;
+
+ retval_reference = get_interface_address6(LOG_DEBUG,AF_INET,&addr4);
+ retval = get_interface_address6_via_udp_socket_hack(LOG_DEBUG,
+ AF_INET,
+ &addr4_to_check);
+
+ tt_int_op(retval,==,retval_reference);
+ tt_assert( (retval == -1 && retval_reference == -1) ||
+ (tor_addr_compare(&addr4,&addr4_to_check,CMP_EXACT) == 0) );
+
+ //[XXX: Skipping the AF_INET6 case because bug #12377 makes it fail.]
+ (void)addr6_to_check;
+ (void)addr6;
+#if 0
+ retval_reference = get_interface_address6(LOG_DEBUG,AF_INET6,&addr6);
+ retval = get_interface_address6_via_udp_socket_hack(LOG_DEBUG,
+ AF_INET6,
+ &addr6_to_check);
+
+ tt_int_op(retval,==,retval_reference);
+ tt_assert( (retval == -1 && retval_reference == -1) ||
+ (tor_addr_compare(&addr6,&addr6_to_check,CMP_EXACT) == 0) );
+
+#endif
+
+ /* When family is neither AF_INET nor AF_INET6, we want _hack to
+ * fail and return -1.
+ */
+
+ retval = get_interface_address6_via_udp_socket_hack(LOG_DEBUG,
+ AF_CCITT,&addr4);
+
+ tt_assert(retval == -1);
+
+ done:
+ return;
+}
+
#define ADDRESS_TEST(name, flags) \
{ #name, test_address_ ## name, flags, NULL, NULL }
struct testcase_t address_tests[] = {
+ ADDRESS_TEST(udp_socket_trick_whitebox, TT_FORK),
+ ADDRESS_TEST(udp_socket_trick_blackbox, TT_FORK),
#ifdef HAVE_IFADDRS_TO_SMARTLIST
ADDRESS_TEST(get_if_addrs_ifaddrs, TT_FORK),
ADDRESS_TEST(ifaddrs_to_smartlist, 0),