Skip to content

Commit

Permalink
ipv4/raw: support binding to nonlocal addresses
Browse files Browse the repository at this point in the history
Add support to inet v4 raw sockets for binding to nonlocal addresses
through the IP_FREEBIND and IP_TRANSPARENT socket options, as well as
the ipv4.ip_nonlocal_bind kernel parameter.

Add helper function to inet_sock.h to check for bind address validity on
the base of the address type and whether nonlocal address are enabled
for the socket via any of the sockopts/sysctl, deduplicating checks in
ipv4/ping.c, ipv4/af_inet.c, ipv6/af_inet6.c (for mapped v4->v6
addresses), and ipv4/raw.c.

Add test cases with IP[V6]_FREEBIND verifying that both v4 and v6 raw
sockets support binding to nonlocal addresses after the change. Add
necessary support for the test cases to nettest.

Signed-off-by: Riccardo Paolo Bestetti <pbl@bestov.io>
Reviewed-by: David Ahern <dsahern@kernel.org>
Link: https://lore.kernel.org/r/20211117090010.125393-1-pbl@bestov.io
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
  • Loading branch information
Riccardo Paolo Bestetti authored and Jakub Kicinski committed Nov 18, 2021
1 parent 75082e7 commit 8ff978b
Showing 7 changed files with 100 additions and 26 deletions.
12 changes: 12 additions & 0 deletions include/net/inet_sock.h
Original file line number Diff line number Diff line change
@@ -372,4 +372,16 @@ static inline bool inet_can_nonlocal_bind(struct net *net,
inet->freebind || inet->transparent;
}

static inline bool inet_addr_valid_or_nonlocal(struct net *net,
struct inet_sock *inet,
__be32 addr,
int addr_type)
{
return inet_can_nonlocal_bind(net, inet) ||
addr == htonl(INADDR_ANY) ||
addr_type == RTN_LOCAL ||
addr_type == RTN_MULTICAST ||
addr_type == RTN_BROADCAST;
}

#endif /* _INET_SOCK_H */
7 changes: 2 additions & 5 deletions net/ipv4/af_inet.c
Original file line number Diff line number Diff line change
@@ -489,11 +489,8 @@ int __inet_bind(struct sock *sk, struct sockaddr *uaddr, int addr_len,
* is temporarily down)
*/
err = -EADDRNOTAVAIL;
if (!inet_can_nonlocal_bind(net, inet) &&
addr->sin_addr.s_addr != htonl(INADDR_ANY) &&
chk_addr_ret != RTN_LOCAL &&
chk_addr_ret != RTN_MULTICAST &&
chk_addr_ret != RTN_BROADCAST)
if (!inet_addr_valid_or_nonlocal(net, inet, addr->sin_addr.s_addr,
chk_addr_ret))
goto out;

snum = ntohs(addr->sin_port);
14 changes: 5 additions & 9 deletions net/ipv4/ping.c
Original file line number Diff line number Diff line change
@@ -311,15 +311,11 @@ static int ping_check_bind_addr(struct sock *sk, struct inet_sock *isk,
pr_debug("ping_check_bind_addr(sk=%p,addr=%pI4,port=%d)\n",
sk, &addr->sin_addr.s_addr, ntohs(addr->sin_port));

if (addr->sin_addr.s_addr == htonl(INADDR_ANY))
chk_addr_ret = RTN_LOCAL;
else
chk_addr_ret = inet_addr_type(net, addr->sin_addr.s_addr);

if ((!inet_can_nonlocal_bind(net, isk) &&
chk_addr_ret != RTN_LOCAL) ||
chk_addr_ret == RTN_MULTICAST ||
chk_addr_ret == RTN_BROADCAST)
chk_addr_ret = inet_addr_type(net, addr->sin_addr.s_addr);

if (!inet_addr_valid_or_nonlocal(net, inet_sk(sk),
addr->sin_addr.s_addr,
chk_addr_ret))
return -EADDRNOTAVAIL;

#if IS_ENABLED(CONFIG_IPV6)
13 changes: 7 additions & 6 deletions net/ipv4/raw.c
Original file line number Diff line number Diff line change
@@ -717,6 +717,7 @@ static int raw_bind(struct sock *sk, struct sockaddr *uaddr, int addr_len)
{
struct inet_sock *inet = inet_sk(sk);
struct sockaddr_in *addr = (struct sockaddr_in *) uaddr;
struct net *net = sock_net(sk);
u32 tb_id = RT_TABLE_LOCAL;
int ret = -EINVAL;
int chk_addr_ret;
@@ -725,16 +726,16 @@ static int raw_bind(struct sock *sk, struct sockaddr *uaddr, int addr_len)
goto out;

if (sk->sk_bound_dev_if)
tb_id = l3mdev_fib_table_by_index(sock_net(sk),
sk->sk_bound_dev_if) ? : tb_id;
tb_id = l3mdev_fib_table_by_index(net,
sk->sk_bound_dev_if) ? : tb_id;

chk_addr_ret = inet_addr_type_table(sock_net(sk), addr->sin_addr.s_addr,
tb_id);
chk_addr_ret = inet_addr_type_table(net, addr->sin_addr.s_addr, tb_id);

ret = -EADDRNOTAVAIL;
if (addr->sin_addr.s_addr && chk_addr_ret != RTN_LOCAL &&
chk_addr_ret != RTN_MULTICAST && chk_addr_ret != RTN_BROADCAST)
if (!inet_addr_valid_or_nonlocal(net, inet, addr->sin_addr.s_addr,
chk_addr_ret))
goto out;

inet->inet_rcv_saddr = inet->inet_saddr = addr->sin_addr.s_addr;
if (chk_addr_ret == RTN_MULTICAST || chk_addr_ret == RTN_BROADCAST)
inet->inet_saddr = 0; /* Use device */
7 changes: 2 additions & 5 deletions net/ipv6/af_inet6.c
Original file line number Diff line number Diff line change
@@ -337,11 +337,8 @@ static int __inet6_bind(struct sock *sk, struct sockaddr *uaddr, int addr_len,
chk_addr_ret = inet_addr_type_dev_table(net, dev, v4addr);
rcu_read_unlock();

if (!inet_can_nonlocal_bind(net, inet) &&
v4addr != htonl(INADDR_ANY) &&
chk_addr_ret != RTN_LOCAL &&
chk_addr_ret != RTN_MULTICAST &&
chk_addr_ret != RTN_BROADCAST) {
if (!inet_addr_valid_or_nonlocal(net, inet, v4addr,
chk_addr_ret)) {
err = -EADDRNOTAVAIL;
goto out;
}
40 changes: 40 additions & 0 deletions tools/testing/selftests/net/fcnal-test.sh
Original file line number Diff line number Diff line change
@@ -66,6 +66,10 @@ NSB_LO_IP=172.16.2.2
NSA_LO_IP6=2001:db8:2::1
NSB_LO_IP6=2001:db8:2::2

# non-local addresses for freebind tests
NL_IP=172.17.1.1
NL_IP6=2001:db8:4::1

MD5_PW=abc123
MD5_WRONG_PW=abc1234

@@ -316,6 +320,9 @@ addr2str()
${NSB_LO_IP6}) echo "ns-B loopback IPv6";;
${NSB_LINKIP6}|${NSB_LINKIP6}%*) echo "ns-B IPv6 LLA";;

${NL_IP}) echo "nonlocal IP";;
${NL_IP6}) echo "nonlocal IPv6";;

${VRF_IP}) echo "VRF IP";;
${VRF_IP6}) echo "VRF IPv6";;

@@ -1767,6 +1774,14 @@ ipv4_addr_bind_novrf()
log_test_addr ${a} $? 0 "Raw socket bind to local address after device bind"
done

#
# raw socket with nonlocal bind
#
a=${NL_IP}
log_start
run_cmd nettest -s -R -P icmp -f -l ${a} -I ${NSA_DEV} -b
log_test_addr ${a} $? 0 "Raw socket bind to nonlocal address after device bind"

#
# tcp sockets
#
@@ -1815,6 +1830,14 @@ ipv4_addr_bind_vrf()
run_cmd nettest -s -R -P icmp -l ${a} -I ${VRF} -b
log_test_addr ${a} $? 1 "Raw socket bind to out of scope address after VRF bind"

#
# raw socket with nonlocal bind
#
a=${NL_IP}
log_start
run_cmd nettest -s -R -P icmp -f -l ${a} -I ${VRF} -b
log_test_addr ${a} $? 0 "Raw socket bind to nonlocal address after VRF bind"

#
# tcp sockets
#
@@ -1965,6 +1988,7 @@ ipv4_rt()

a=${NSA_IP}
log_start

run_cmd nettest ${varg} -s &
sleep 1
run_cmd nettest ${varg} -d ${NSA_DEV} -r ${a} &
@@ -3402,6 +3426,14 @@ ipv6_addr_bind_novrf()
log_test_addr ${a} $? 0 "Raw socket bind to local address after device bind"
done

#
# raw socket with nonlocal bind
#
a=${NL_IP6}
log_start
run_cmd nettest -6 -s -R -P icmp -f -l ${a} -I ${NSA_DEV} -b
log_test_addr ${a} $? 0 "Raw socket bind to nonlocal address"

#
# tcp sockets
#
@@ -3443,6 +3475,14 @@ ipv6_addr_bind_vrf()
run_cmd nettest -6 -s -R -P ipv6-icmp -l ${a} -I ${VRF} -b
log_test_addr ${a} $? 1 "Raw socket bind to invalid local address after vrf bind"

#
# raw socket with nonlocal bind
#
a=${NL_IP6}
log_start
run_cmd nettest -6 -s -R -P icmp -f -l ${a} -I ${VRF} -b
log_test_addr ${a} $? 0 "Raw socket bind to nonlocal address after VRF bind"

#
# tcp sockets
#
33 changes: 32 additions & 1 deletion tools/testing/selftests/net/nettest.c
Original file line number Diff line number Diff line change
@@ -85,6 +85,7 @@ struct sock_args {
int version; /* AF_INET/AF_INET6 */

int use_setsockopt;
int use_freebind;
int use_cmsg;
const char *dev;
const char *server_dev;
@@ -514,6 +515,29 @@ static int set_membership(int sd, uint32_t grp, uint32_t addr, int ifindex)
return 0;
}

static int set_freebind(int sd, int version)
{
unsigned int one = 1;
int rc = 0;

switch (version) {
case AF_INET:
if (setsockopt(sd, SOL_IP, IP_FREEBIND, &one, sizeof(one))) {
log_err_errno("setsockopt(IP_FREEBIND)");
rc = -1;
}
break;
case AF_INET6:
if (setsockopt(sd, SOL_IPV6, IPV6_FREEBIND, &one, sizeof(one))) {
log_err_errno("setsockopt(IPV6_FREEBIND");
rc = -1;
}
break;
}

return rc;
}

static int set_broadcast(int sd)
{
unsigned int one = 1;
@@ -1419,6 +1443,9 @@ static int lsock_init(struct sock_args *args)
set_unicast_if(sd, args->ifindex, args->version))
goto err;

if (args->use_freebind && set_freebind(sd, args->version))
goto err;

if (bind_socket(sd, args))
goto err;

@@ -1827,7 +1854,7 @@ static int ipc_parent(int cpid, int fd, struct sock_args *args)
return client_status;
}

#define GETOPT_STR "sr:l:c:p:t:g:P:DRn:M:X:m:d:I:BN:O:SCi6xL:0:1:2:3:Fbq"
#define GETOPT_STR "sr:l:c:p:t:g:P:DRn:M:X:m:d:I:BN:O:SCi6xL:0:1:2:3:Fbqf"
#define OPT_FORCE_BIND_KEY_IFINDEX 1001
#define OPT_NO_BIND_KEY_IFINDEX 1002

@@ -1864,6 +1891,7 @@ static void print_usage(char *prog)
" -I dev bind socket to given device name - server mode\n"
" -S use setsockopt (IP_UNICAST_IF or IP_MULTICAST_IF)\n"
" to set device binding\n"
" -f bind socket with the IP[V6]_FREEBIND option\n"
" -C use cmsg and IP_PKTINFO to specify device binding\n"
"\n"
" -L len send random message of given length\n"
@@ -1999,6 +2027,9 @@ int main(int argc, char *argv[])
case 'S':
args.use_setsockopt = 1;
break;
case 'f':
args.use_freebind = 1;
break;
case 'C':
args.use_cmsg = 1;
break;

0 comments on commit 8ff978b

Please sign in to comment.