Skip to content

Commit c5feb70

Browse files
author
Francesco Lavra
committed
network sockets: convert outgoing "any" (0.0.0.0, [::]) address to loopback
Even though the "any" IP address (i.e. 0.0.0.0 for IPv4, [::] for IPv6) is not a valid routable destination address according to RFC standards (is only valid for binding a server to listen on all available interfaces), on Unix-like OSes outgoing network packets destined to this address are redirected to the loopback address. And a few user programs rely on this behavior.
1 parent 1cd220d commit c5feb70

File tree

2 files changed

+136
-4
lines changed

2 files changed

+136
-4
lines changed

src/net/netsyscall.c

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -309,22 +309,37 @@ static inline void ip6addr_to_sockaddr(ip_addr_t *ip_addr,
309309
sizeof(addr->sin6_addr.s6_addr));
310310
}
311311

312+
static inline boolean ip6addr_is_any(struct sockaddr_in6 *addr)
313+
{
314+
u32 *addr_p = (u32 *)&addr->sin6_addr;
315+
return !addr_p[0] && !addr_p[1] && !addr_p[2] && !addr_p[3];
316+
}
317+
312318
static sysreturn sockaddr_to_addrport(netsock s, struct sockaddr *addr,
313319
socklen_t addrlen,
320+
boolean tx,
314321
ip_addr_t *ip_addr, u16 *port)
315322
{
316323
*ip_addr = (ip_addr_t){};
317324
if (s->sock.domain == AF_INET) {
318325
if (addrlen < sizeof(struct sockaddr_in))
319326
return -EINVAL;
320327
struct sockaddr_in *sin = (struct sockaddr_in *)addr;
321-
ip_addr_set_ip4_u32(ip_addr, sin->address);
328+
if (tx && (sin->address == PP_NTOHL(IPADDR_ANY)))
329+
ip4_addr_set_loopback(ip_2_ip4(ip_addr));
330+
else
331+
ip_addr_set_ip4_u32(ip_addr, sin->address);
322332
*port = ntohs(sin->port);
323333
} else {
324334
if (addrlen < sizeof(struct sockaddr_in6))
325335
return -EINVAL;
326336
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)addr;
327-
sockaddr_to_ip6addr(sin6, ip_addr);
337+
if (tx && ip6addr_is_any(sin6)) {
338+
IP_SET_TYPE_VAL(*ip_addr, IPADDR_TYPE_V6);
339+
ip6_addr_set_loopback(ip_2_ip6(ip_addr));
340+
} else {
341+
sockaddr_to_ip6addr(sin6, ip_addr);
342+
}
328343
*port = ntohs(sin6->port);
329344
}
330345
/* If this is an an IPv4 mapped address then this socket
@@ -829,6 +844,7 @@ static sysreturn socket_write_udp(netsock s, void *source, struct iovec *iov, u6
829844
if (context_set_err(ctx))
830845
return -EFAULT;
831846
sysreturn ret = sockaddr_to_addrport(s, dest_addr, addrlen,
847+
true,
832848
&ipaddr, &port);
833849
context_clear_err(ctx);
834850
if (ret)
@@ -1516,7 +1532,7 @@ static sysreturn netsock_bind(struct sock *sock, struct sockaddr *addr,
15161532
sysreturn ret;
15171533
context ctx = get_current_context(current_cpu());
15181534
if (!context_set_err(ctx)) {
1519-
ret = sockaddr_to_addrport(s, addr, addrlen, &ipaddr, &port);
1535+
ret = sockaddr_to_addrport(s, addr, addrlen, false, &ipaddr, &port);
15201536
context_clear_err(ctx);
15211537
} else {
15221538
ret = -EFAULT;
@@ -1717,7 +1733,7 @@ static sysreturn netsock_connect(struct sock *sock, struct sockaddr *addr,
17171733
sysreturn ret;
17181734
context ctx = get_current_context(current_cpu());
17191735
if (!context_set_err(ctx)) {
1720-
ret = sockaddr_to_addrport(s, addr, addrlen, &ipaddr, &port);
1736+
ret = sockaddr_to_addrport(s, addr, addrlen, true, &ipaddr, &port);
17211737
context_clear_err(ctx);
17221738
} else {
17231739
ret = -EFAULT;

test/runtime/netsock.c

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@
2525

2626
#define NETSOCK_TEST_PEEK_COUNT 8
2727

28+
struct netsock_thread_params {
29+
int sock_type;
30+
boolean ipv6;
31+
struct sockaddr *addr;
32+
};
33+
2834
static inline void timespec_sub(struct timespec *a, struct timespec *b, struct timespec *r)
2935
{
3036
r->tv_sec = a->tv_sec - b->tv_sec;
@@ -35,6 +41,28 @@ static inline void timespec_sub(struct timespec *a, struct timespec *b, struct t
3541
}
3642
}
3743

44+
/* Finds an available port to bind to, starting from 1024. */
45+
static void netsock_bind(int fd, boolean ipv6, struct sockaddr *addr)
46+
{
47+
int port = 1024;
48+
49+
while (1) {
50+
int ret;
51+
52+
if (ipv6) {
53+
((struct sockaddr_in6 *)addr)->sin6_port = htons(port);
54+
ret = bind(fd, addr, sizeof(struct sockaddr_in6));
55+
} else {
56+
((struct sockaddr_in *)addr)->sin_port = htons(port);
57+
ret = bind(fd, addr, sizeof(struct sockaddr_in));
58+
}
59+
if (ret == 0)
60+
break;
61+
test_assert((ret == -1) && (errno == EADDRINUSE));
62+
port++;
63+
}
64+
}
65+
3866
static void *netsock_test_basic_thread(void *arg)
3967
{
4068
int sock_type = (long)arg;
@@ -947,6 +975,90 @@ static void netsock_test_timeout(void)
947975
close(listen_fd);
948976
}
949977

978+
static void *netsock_test_txrx_thread(void *arg)
979+
{
980+
struct netsock_thread_params *params = arg;
981+
int fd;
982+
socklen_t addrlen = params->ipv6 ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in);
983+
uint8_t buf[KB];
984+
985+
fd = socket(params->ipv6 ? AF_INET6 : AF_INET, params->sock_type, 0);
986+
test_assert(fd > 0);
987+
if (params->sock_type == SOCK_STREAM) {
988+
test_assert(connect(fd, params->addr, addrlen) == 0);
989+
test_assert(send(fd, buf, sizeof(buf), 0) > 0);
990+
test_assert(recv(fd, buf, sizeof(buf), 0) > 0);
991+
} else {
992+
test_assert(sendto(fd, buf, sizeof(buf), 0, params->addr, addrlen) > 0);
993+
test_assert(recvfrom(fd, buf, sizeof(buf), 0, params->addr, &addrlen) > 0);
994+
}
995+
close(fd);
996+
return NULL;
997+
}
998+
999+
/* Tests connection to the "any" address (0.0.0.0 for IPv4, [::] for IPv6), which should be remapped
1000+
* to the loopback address. */
1001+
static void netsock_test_addr_any(int sock_type, boolean ipv6)
1002+
{
1003+
int fd;
1004+
struct sockaddr_in addr4;
1005+
struct sockaddr_in6 addr6;
1006+
struct sockaddr *addr;
1007+
struct netsock_thread_params params;
1008+
pthread_t thread;
1009+
uint8_t buf[KB];
1010+
int ret;
1011+
1012+
fd = socket(ipv6 ? AF_INET6 : AF_INET, sock_type, 0);
1013+
test_assert(fd > 0);
1014+
if (ipv6) {
1015+
memset(&addr6, 0, sizeof(addr6));
1016+
addr6.sin6_family = AF_INET6;
1017+
addr6.sin6_addr = in6addr_any;
1018+
addr = (struct sockaddr *)&addr6;
1019+
} else {
1020+
addr4.sin_family = AF_INET;
1021+
addr4.sin_addr.s_addr = htonl(INADDR_ANY);
1022+
addr = (struct sockaddr *)&addr4;
1023+
}
1024+
netsock_bind(fd, ipv6, addr);
1025+
params.sock_type = sock_type;
1026+
params.ipv6 = ipv6;
1027+
params.addr = addr;
1028+
if (sock_type == SOCK_STREAM) {
1029+
int conn_fd;
1030+
1031+
test_assert(listen(fd, 1) == 0);
1032+
ret = pthread_create(&thread, NULL, netsock_test_txrx_thread, &params);
1033+
test_assert(ret == 0);
1034+
conn_fd = accept(fd, NULL, NULL);
1035+
test_assert(conn_fd > 0);
1036+
test_assert(recv(conn_fd, buf, sizeof(buf), 0) > 0);
1037+
test_assert(send(conn_fd, buf, sizeof(buf), 0) > 0);
1038+
close(conn_fd);
1039+
} else {
1040+
struct sockaddr_in peer_addr4;
1041+
struct sockaddr_in6 peer_addr6;
1042+
socklen_t addrlen;
1043+
1044+
ret = pthread_create(&thread, NULL, netsock_test_txrx_thread, &params);
1045+
test_assert(ret == 0);
1046+
if (ipv6) {
1047+
addr = (struct sockaddr *)&peer_addr6;
1048+
addrlen = sizeof(peer_addr6);
1049+
} else {
1050+
addr = (struct sockaddr *)&peer_addr4;
1051+
addrlen = sizeof(peer_addr4);
1052+
}
1053+
ret = recvfrom(fd, buf, sizeof(buf), 0, addr, &addrlen);
1054+
test_assert(ret > 0);
1055+
ret = sendto(fd, buf, sizeof(buf), 0, addr, addrlen);
1056+
test_assert(ret > 0);
1057+
}
1058+
test_assert(pthread_join(thread, NULL) == 0);
1059+
close(fd);
1060+
}
1061+
9501062
int main(int argc, char **argv)
9511063
{
9521064
netsock_test_basic(SOCK_STREAM);
@@ -962,6 +1074,10 @@ int main(int argc, char **argv)
9621074
netsock_test_msg(SOCK_DGRAM);
9631075
netsock_test_fault();
9641076
netsock_test_timeout();
1077+
netsock_test_addr_any(SOCK_STREAM, false);
1078+
netsock_test_addr_any(SOCK_STREAM, true);
1079+
netsock_test_addr_any(SOCK_DGRAM, false);
1080+
netsock_test_addr_any(SOCK_DGRAM, true);
9651081
printf("Network socket tests OK\n");
9661082
return EXIT_SUCCESS;
9671083
}

0 commit comments

Comments
 (0)