ipv4: Implement IP_UNICAST_IF socket option.
authorErich E. Hoover <ehoover@mines.edu>
Wed, 8 Feb 2012 09:11:07 +0000 (09:11 +0000)
committerDavid S. Miller <davem@davemloft.net>
Wed, 8 Feb 2012 20:52:45 +0000 (15:52 -0500)
The IP_UNICAST_IF feature is needed by the Wine project.  This patch
implements the feature by setting the outgoing interface in a similar
fashion to that of IP_MULTICAST_IF.  A separate option is needed to
handle this feature since the existing options do not provide all of
the characteristics required by IP_UNICAST_IF, a summary is provided
below.

SO_BINDTODEVICE:
* SO_BINDTODEVICE requires administrative privileges, IP_UNICAST_IF
does not.  From reading some old mailing list articles my
understanding is that SO_BINDTODEVICE requires administrative
privileges because it can override the administrator's routing
settings.
* The SO_BINDTODEVICE option restricts both outbound and inbound
traffic, IP_UNICAST_IF only impacts outbound traffic.

IP_PKTINFO:
* Since IP_PKTINFO and IP_UNICAST_IF are independent options,
implementing IP_UNICAST_IF with IP_PKTINFO will likely break some
applications.
* Implementing IP_UNICAST_IF on top of IP_PKTINFO significantly
complicates the Wine codebase and reduces the socket performance
(doing this requires a lot of extra communication between the
"server" and "user" layers).

bind():
* bind() does not work on broadcast packets, IP_UNICAST_IF is
specifically intended to work with broadcast packets.
* Like SO_BINDTODEVICE, bind() restricts both outbound and inbound
traffic.

Signed-off-by: Erich E. Hoover <ehoover@mines.edu>
Signed-off-by: Eric Dumazet <eric.dumazet@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/linux/in.h
include/net/inet_sock.h
net/ipv4/ip_sockglue.c
net/ipv4/ping.c
net/ipv4/raw.c
net/ipv4/udp.c

index 01129c0ea87cb385c558adaf1df526555fbec1a0..e0337f11d92eb54e0c78db71a9d1b6fcb3f42d80 100644 (file)
@@ -111,6 +111,7 @@ struct in_addr {
 #define MCAST_LEAVE_SOURCE_GROUP       47
 #define MCAST_MSFILTER                 48
 #define IP_MULTICAST_ALL               49
+#define IP_UNICAST_IF                  50
 
 #define MCAST_EXCLUDE  0
 #define MCAST_INCLUDE  1
index e3e405106afea5ac34eb5d84e0d92f192e810c31..022f772c0ebee8df9a7126720b044a1209e4a3d2 100644 (file)
@@ -132,6 +132,7 @@ struct rtable;
  * @tos - TOS
  * @mc_ttl - Multicasting TTL
  * @is_icsk - is this an inet_connection_sock?
+ * @uc_index - Unicast outgoing device index
  * @mc_index - Multicast device index
  * @mc_list - Group array
  * @cork - info to build ip hdr on each ip frag while socket is corked
@@ -167,6 +168,7 @@ struct inet_sock {
                                transparent:1,
                                mc_all:1,
                                nodefrag:1;
+       int                     uc_index;
        int                     mc_index;
        __be32                  mc_addr;
        struct ip_mc_socklist __rcu     *mc_list;
index 8aa87c19fa008f00adbcf8e0fa1b4411d119288b..9125529dab9593964b0529d31a96727e07d1a470 100644 (file)
@@ -469,6 +469,7 @@ static int do_ip_setsockopt(struct sock *sk, int level,
                             (1<<IP_ROUTER_ALERT) | (1<<IP_FREEBIND) |
                             (1<<IP_PASSSEC) | (1<<IP_TRANSPARENT) |
                             (1<<IP_MINTTL) | (1<<IP_NODEFRAG))) ||
+           optname == IP_UNICAST_IF ||
            optname == IP_MULTICAST_TTL ||
            optname == IP_MULTICAST_ALL ||
            optname == IP_MULTICAST_LOOP ||
@@ -628,6 +629,35 @@ static int do_ip_setsockopt(struct sock *sk, int level,
                        goto e_inval;
                inet->mc_loop = !!val;
                break;
+       case IP_UNICAST_IF:
+       {
+               struct net_device *dev = NULL;
+               int ifindex;
+
+               if (optlen != sizeof(int))
+                       goto e_inval;
+
+               ifindex = (__force int)ntohl((__force __be32)val);
+               if (ifindex == 0) {
+                       inet->uc_index = 0;
+                       err = 0;
+                       break;
+               }
+
+               dev = dev_get_by_index(sock_net(sk), ifindex);
+               err = -EADDRNOTAVAIL;
+               if (!dev)
+                       break;
+               dev_put(dev);
+
+               err = -EINVAL;
+               if (sk->sk_bound_dev_if)
+                       break;
+
+               inet->uc_index = ifindex;
+               err = 0;
+               break;
+       }
        case IP_MULTICAST_IF:
        {
                struct ip_mreqn mreq;
@@ -1178,6 +1208,9 @@ static int do_ip_getsockopt(struct sock *sk, int level, int optname,
        case IP_MULTICAST_LOOP:
                val = inet->mc_loop;
                break;
+       case IP_UNICAST_IF:
+               val = (__force int)htonl((__u32) inet->uc_index);
+               break;
        case IP_MULTICAST_IF:
        {
                struct in_addr addr;
index aea5a199c37a341be3fab08500f1c35a34cbf215..cfc82cf339f6e387bc0abcb66b4f23f6411c7464 100644 (file)
@@ -556,7 +556,8 @@ static int ping_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
                        ipc.oif = inet->mc_index;
                if (!saddr)
                        saddr = inet->mc_addr;
-       }
+       } else if (!ipc.oif)
+               ipc.oif = inet->uc_index;
 
        flowi4_init_output(&fl4, ipc.oif, sk->sk_mark, tos,
                           RT_SCOPE_UNIVERSE, sk->sk_protocol,
index 3ccda5ae8a27b24855c86cf0f3e858c648fc5295..ab466305b629bae03bbc44767156d3fb467684cf 100644 (file)
@@ -563,7 +563,8 @@ static int raw_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
                        ipc.oif = inet->mc_index;
                if (!saddr)
                        saddr = inet->mc_addr;
-       }
+       } else if (!ipc.oif)
+               ipc.oif = inet->uc_index;
 
        flowi4_init_output(&fl4, ipc.oif, sk->sk_mark, tos,
                           RT_SCOPE_UNIVERSE,
index 5d075b5f70fcd61dabe0c38ac6864771b4022093..cd99f1a0f59f5f5ccb68cfa9f41da9f7231f0247 100644 (file)
@@ -917,7 +917,8 @@ int udp_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
                if (!saddr)
                        saddr = inet->mc_addr;
                connected = 0;
-       }
+       } else if (!ipc.oif)
+               ipc.oif = inet->uc_index;
 
        if (connected)
                rt = (struct rtable *)sk_dst_check(sk, 0);