netdev-linux: Fix netdev_send() to tap device.
authorBen Pfaff <blp@nicira.com>
Fri, 8 Apr 2011 23:34:17 +0000 (16:34 -0700)
committerBen Pfaff <blp@nicira.com>
Mon, 11 Apr 2011 16:36:49 +0000 (09:36 -0700)
Commit 76c308b50d3 "netdev-linux: Support 'send' for netdevs opened with
NETDEV_ETH_TYPE_NONE" broke sending packets to tap devices.  Sending a
packet to a tap device with an AF_PACKET socket causes that packet to be
looped back to be received on the tap device again, which obviously isn't
useful.

lib/netdev-linux.c

index 50bec0887f38cc88b0ee0143585cdb82a2c8e289..e2af6594fcd665c9f91b11e4a55a315a1a5ba426 100644 (file)
@@ -829,36 +829,49 @@ netdev_linux_drain(struct netdev *netdev_)
 static int
 netdev_linux_send(struct netdev *netdev_, const void *data, size_t size)
 {
-    struct sockaddr_ll sll;
-    struct msghdr msg;
-    struct iovec iov;
-    int ifindex;
-    int error;
+    struct netdev_linux *netdev = netdev_linux_cast(netdev_);
+    for (;;) {
+        ssize_t retval;
 
-    error = get_ifindex(netdev_, &ifindex);
-    if (error) {
-        return error;
-    }
+        if (netdev->fd < 0) {
+            /* Use our AF_PACKET socket to send to this device. */
+            struct sockaddr_ll sll;
+            struct msghdr msg;
+            struct iovec iov;
+            int ifindex;
+            int error;
 
-    /* We don't bother setting most fields in sockaddr_ll because the kernel
-     * ignores them for SOCK_RAW. */
-    memset(&sll, 0, sizeof sll);
-    sll.sll_family = AF_PACKET;
-    sll.sll_ifindex = ifindex;
+            error = get_ifindex(netdev_, &ifindex);
+            if (error) {
+                return error;
+            }
 
-    iov.iov_base = (void *) data;
-    iov.iov_len = size;
+            /* We don't bother setting most fields in sockaddr_ll because the
+             * kernel ignores them for SOCK_RAW. */
+            memset(&sll, 0, sizeof sll);
+            sll.sll_family = AF_PACKET;
+            sll.sll_ifindex = ifindex;
 
-    msg.msg_name = &sll;
-    msg.msg_namelen = sizeof sll;
-    msg.msg_iov = &iov;
-    msg.msg_iovlen = 1;
-    msg.msg_control = NULL;
-    msg.msg_controllen = 0;
-    msg.msg_flags = 0;
+            iov.iov_base = (void *) data;
+            iov.iov_len = size;
+
+            msg.msg_name = &sll;
+            msg.msg_namelen = sizeof sll;
+            msg.msg_iov = &iov;
+            msg.msg_iovlen = 1;
+            msg.msg_control = NULL;
+            msg.msg_controllen = 0;
+            msg.msg_flags = 0;
+
+            retval = sendmsg(af_packet_sock, &msg, 0);
+        } else {
+            /* Use the netdev's own fd to send to this device.  This is
+             * essential for tap devices, because packets sent to a tap device
+             * with an AF_PACKET socket will loop back to be *received* again
+             * on the tap device. */
+            retval = write(netdev->fd, data, size);
+        }
 
-    for (;;) {
-        ssize_t retval = sendmsg(af_packet_sock, &msg, 0);
         if (retval < 0) {
             /* The Linux AF_PACKET implementation never blocks waiting for room
              * for packets, instead returning ENOBUFS.  Translate this into