Skip to content

Commit 0e4d354

Browse files
Richard Gobertkuba-moo
authored andcommitted
net-next: Fix IP_UNICAST_IF option behavior for connected sockets
The IP_UNICAST_IF socket option is used to set the outgoing interface for outbound packets. The IP_UNICAST_IF socket option was added as it was needed by the Wine project, since no other existing option (SO_BINDTODEVICE socket option, IP_PKTINFO socket option or the bind function) provided the needed characteristics needed by the IP_UNICAST_IF socket option. [1] The IP_UNICAST_IF socket option works well for unconnected sockets, that is, the interface specified by the IP_UNICAST_IF socket option is taken into consideration in the route lookup process when a packet is being sent. However, for connected sockets, the outbound interface is chosen when connecting the socket, and in the route lookup process which is done when a packet is being sent, the interface specified by the IP_UNICAST_IF socket option is being ignored. This inconsistent behavior was reported and discussed in an issue opened on systemd's GitHub project [2]. Also, a bug report was submitted in the kernel's bugzilla [3]. To understand the problem in more detail, we can look at what happens for UDP packets over IPv4 (The same analysis was done separately in the referenced systemd issue). When a UDP packet is sent the udp_sendmsg function gets called and the following happens: 1. The oif member of the struct ipcm_cookie ipc (which stores the output interface of the packet) is initialized by the ipcm_init_sk function to inet->sk.sk_bound_dev_if (the device set by the SO_BINDTODEVICE socket option). 2. If the IP_PKTINFO socket option was set, the oif member gets overridden by the call to the ip_cmsg_send function. 3. If no output interface was selected yet, the interface specified by the IP_UNICAST_IF socket option is used. 4. If the socket is connected and no destination address is specified in the send function, the struct ipcm_cookie ipc is not taken into consideration and the cached route, that was calculated in the connect function is being used. Thus, for a connected socket, the IP_UNICAST_IF sockopt isn't taken into consideration. This patch corrects the behavior of the IP_UNICAST_IF socket option for connect()ed sockets by taking into consideration the IP_UNICAST_IF sockopt when connecting the socket. In order to avoid reconnecting the socket, this option is still ignored when applied on an already connected socket until connect() is called again by the Richard Gobert. Change the __ip4_datagram_connect function, which is called during socket connection, to take into consideration the interface set by the IP_UNICAST_IF socket option, in a similar way to what is done in the udp_sendmsg function. [1] https://lore.kernel.org/netdev/1328685717.4736.4.camel@edumazet-laptop/T/ [2] systemd/systemd#11935 (comment) [3] https://bugzilla.kernel.org/show_bug.cgi?id=210255 Signed-off-by: Richard Gobert <richardbgobert@gmail.com> Reviewed-by: David Ahern <dsahern@kernel.org> Link: https://lore.kernel.org/r/20220829111554.GA1771@debian Signed-off-by: Jakub Kicinski <kuba@kernel.org>
1 parent 744ccd5 commit 0e4d354

File tree

3 files changed

+46
-2
lines changed

3 files changed

+46
-2
lines changed

net/ipv4/datagram.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ int __ip4_datagram_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len
4242
oif = inet->mc_index;
4343
if (!saddr)
4444
saddr = inet->mc_addr;
45+
} else if (!oif) {
46+
oif = inet->uc_index;
4547
}
4648
fl4 = &inet->cork.fl.u.ip4;
4749
rt = ip_route_connect(fl4, usin->sin_addr.s_addr, saddr, oif,

tools/testing/selftests/net/fcnal-test.sh

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1466,6 +1466,13 @@ ipv4_udp_novrf()
14661466
run_cmd nettest -D -r ${a} -d ${NSA_DEV} -S -0 ${NSA_IP}
14671467
log_test_addr ${a} $? 0 "Client, device bind via IP_UNICAST_IF"
14681468

1469+
log_start
1470+
run_cmd_nsb nettest -D -s &
1471+
sleep 1
1472+
run_cmd nettest -D -r ${a} -d ${NSA_DEV} -S -0 ${NSA_IP} -U
1473+
log_test_addr ${a} $? 0 "Client, device bind via IP_UNICAST_IF, with connect()"
1474+
1475+
14691476
log_start
14701477
show_hint "Should fail 'Connection refused'"
14711478
run_cmd nettest -D -r ${a}
@@ -1525,6 +1532,13 @@ ipv4_udp_novrf()
15251532
run_cmd nettest -D -d ${NSA_DEV} -S -r ${a}
15261533
log_test_addr ${a} $? 0 "Global server, device client via IP_UNICAST_IF, local connection"
15271534

1535+
log_start
1536+
run_cmd nettest -s -D &
1537+
sleep 1
1538+
run_cmd nettest -D -d ${NSA_DEV} -S -r ${a} -U
1539+
log_test_addr ${a} $? 0 "Global server, device client via IP_UNICAST_IF, local connection, with connect()"
1540+
1541+
15281542
# IPv4 with device bind has really weird behavior - it overrides the
15291543
# fib lookup, generates an rtable and tries to send the packet. This
15301544
# causes failures for local traffic at different places
@@ -1550,6 +1564,15 @@ ipv4_udp_novrf()
15501564
sleep 1
15511565
run_cmd nettest -D -r ${a} -d ${NSA_DEV} -S
15521566
log_test_addr ${a} $? 1 "Global server, device client via IP_UNICAST_IF, local connection"
1567+
1568+
log_start
1569+
show_hint "Should fail since addresses on loopback are out of device scope"
1570+
run_cmd nettest -D -s &
1571+
sleep 1
1572+
run_cmd nettest -D -r ${a} -d ${NSA_DEV} -S -U
1573+
log_test_addr ${a} $? 1 "Global server, device client via IP_UNICAST_IF, local connection, with connect()"
1574+
1575+
15531576
done
15541577

15551578
a=${NSA_IP}
@@ -3157,6 +3180,13 @@ ipv6_udp_novrf()
31573180
sleep 1
31583181
run_cmd nettest -6 -D -r ${a} -d ${NSA_DEV} -S
31593182
log_test_addr ${a} $? 1 "Global server, device client via IP_UNICAST_IF, local connection"
3183+
3184+
log_start
3185+
show_hint "Should fail 'No route to host' since addresses on loopback are out of device scope"
3186+
run_cmd nettest -6 -D -s &
3187+
sleep 1
3188+
run_cmd nettest -6 -D -r ${a} -d ${NSA_DEV} -S -U
3189+
log_test_addr ${a} $? 1 "Global server, device client via IP_UNICAST_IF, local connection, with connect()"
31603190
done
31613191

31623192
a=${NSA_IP6}

tools/testing/selftests/net/nettest.c

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,9 @@ struct sock_args {
127127

128128
/* ESP in UDP encap test */
129129
int use_xfrm;
130+
131+
/* use send() and connect() instead of sendto */
132+
int datagram_connect;
130133
};
131134

132135
static int server_mode;
@@ -979,6 +982,11 @@ static int send_msg(int sd, void *addr, socklen_t alen, struct sock_args *args)
979982
log_err_errno("write failed sending msg to peer");
980983
return 1;
981984
}
985+
} else if (args->datagram_connect) {
986+
if (send(sd, msg, msglen, 0) < 0) {
987+
log_err_errno("send failed sending msg to peer");
988+
return 1;
989+
}
982990
} else if (args->ifindex && args->use_cmsg) {
983991
if (send_msg_cmsg(sd, addr, alen, args->ifindex, args->version))
984992
return 1;
@@ -1659,7 +1667,7 @@ static int connectsock(void *addr, socklen_t alen, struct sock_args *args)
16591667
if (args->has_local_ip && bind_socket(sd, args))
16601668
goto err;
16611669

1662-
if (args->type != SOCK_STREAM)
1670+
if (args->type != SOCK_STREAM && !args->datagram_connect)
16631671
goto out;
16641672

16651673
if (args->password && tcp_md5sig(sd, addr, alen, args))
@@ -1854,7 +1862,7 @@ static int ipc_parent(int cpid, int fd, struct sock_args *args)
18541862
return client_status;
18551863
}
18561864

1857-
#define GETOPT_STR "sr:l:c:p:t:g:P:DRn:M:X:m:d:I:BN:O:SCi6xL:0:1:2:3:Fbqf"
1865+
#define GETOPT_STR "sr:l:c:p:t:g:P:DRn:M:X:m:d:I:BN:O:SUCi6xL:0:1:2:3:Fbqf"
18581866
#define OPT_FORCE_BIND_KEY_IFINDEX 1001
18591867
#define OPT_NO_BIND_KEY_IFINDEX 1002
18601868

@@ -1891,6 +1899,7 @@ static void print_usage(char *prog)
18911899
" -I dev bind socket to given device name - server mode\n"
18921900
" -S use setsockopt (IP_UNICAST_IF or IP_MULTICAST_IF)\n"
18931901
" to set device binding\n"
1902+
" -U Use connect() and send() for datagram sockets\n"
18941903
" -f bind socket with the IP[V6]_FREEBIND option\n"
18951904
" -C use cmsg and IP_PKTINFO to specify device binding\n"
18961905
"\n"
@@ -2074,6 +2083,9 @@ int main(int argc, char *argv[])
20742083
case 'x':
20752084
args.use_xfrm = 1;
20762085
break;
2086+
case 'U':
2087+
args.datagram_connect = 1;
2088+
break;
20772089
default:
20782090
print_usage(argv[0]);
20792091
return 1;

0 commit comments

Comments
 (0)