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>
 
                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 */
 
         *  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);
 
                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)
 
 {
        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;
                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 */
 
                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;
                }
 
 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
 
        ${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";;
 
                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
        #
        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
        #
 
        a=${NSA_IP}
        log_start
+
        run_cmd nettest ${varg} -s &
        sleep 1
        run_cmd nettest ${varg} -d ${NSA_DEV} -r ${a} &
                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
        #
        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
        #
 
        int version;   /* AF_INET/AF_INET6 */
 
        int use_setsockopt;
+       int use_freebind;
        int use_cmsg;
        const char *dev;
        const char *server_dev;
        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;
                 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;
 
        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
 
        "    -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"
                case 'S':
                        args.use_setsockopt = 1;
                        break;
+               case 'f':
+                       args.use_freebind = 1;
+                       break;
                case 'C':
                        args.use_cmsg = 1;
                        break;