netdev: Add 'netdev' parameter to netdev_add_router().
[openvswitch] / lib / netdev.c
index 17e8fe5d52f81517cf6d8bce6fc437150564c6a8..701bb53e5e43db47b5bb1ef499c0a913fe6a2237 100644 (file)
@@ -1,17 +1,17 @@
 /*
  * Copyright (c) 2008, 2009 Nicira Networks.
  *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
  *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 #include <config.h>
 #include "dynamic-string.h"
 #include "fatal-signal.h"
 #include "list.h"
+#include "netdev-linux.h"
 #include "netlink.h"
 #include "ofpbuf.h"
 #include "openflow/openflow.h"
 #include "packets.h"
 #include "poll-loop.h"
+#include "shash.h"
 #include "socket-util.h"
 #include "svec.h"
 
@@ -333,59 +335,49 @@ do_get_features(struct netdev *netdev,
 int
 netdev_open(const char *name, int ethertype, struct netdev **netdevp) 
 {
-    if (!strncmp(name, "tap:", 4)) {
-        return netdev_open_tap(name + 4, netdevp);
-    } else {
+    if (strncmp(name, "tap:", 4)) {
         return do_open_netdev(name, ethertype, -1, netdevp); 
-    }
-}
+    } else {
+        static const char tap_dev[] = "/dev/net/tun";
+        struct ifreq ifr;
+        int error;
+        int tap_fd;
 
-/* Opens a TAP virtual network device.  If 'name' is a nonnull, non-empty
- * string, attempts to assign that name to the TAP device (failing if the name
- * is already in use); otherwise, a name is automatically assigned.  Returns
- * zero if successful, otherwise a positive errno value.  On success, sets
- * '*netdevp' to the new network device, otherwise to null.  */
-int
-netdev_open_tap(const char *name, struct netdev **netdevp)
-{
-    static const char tap_dev[] = "/dev/net/tun";
-    struct ifreq ifr;
-    int error;
-    int tap_fd;
+        tap_fd = open(tap_dev, O_RDWR);
+        if (tap_fd < 0) {
+            ovs_error(errno, "opening \"%s\" failed", tap_dev);
+            return errno;
+        }
 
-    tap_fd = open(tap_dev, O_RDWR);
-    if (tap_fd < 0) {
-        ovs_error(errno, "opening \"%s\" failed", tap_dev);
-        return errno;
-    }
+        memset(&ifr, 0, sizeof ifr);
+        ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
+        if (name) {
+            strncpy(ifr.ifr_name, name, sizeof ifr.ifr_name);
+        }
+        if (ioctl(tap_fd, TUNSETIFF, &ifr) < 0) {
+            int error = errno;
+            ovs_error(error, "ioctl(TUNSETIFF) on \"%s\" failed", tap_dev);
+            close(tap_fd);
+            return error;
+        }
 
-    memset(&ifr, 0, sizeof ifr);
-    ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
-    if (name) {
-        strncpy(ifr.ifr_name, name, sizeof ifr.ifr_name);
-    }
-    if (ioctl(tap_fd, TUNSETIFF, &ifr) < 0) {
-        int error = errno;
-        ovs_error(error, "ioctl(TUNSETIFF) on \"%s\" failed", tap_dev);
-        close(tap_fd);
-        return error;
-    }
+        error = set_nonblocking(tap_fd);
+        if (error) {
+            ovs_error(error, "set_nonblocking on \"%s\" failed", tap_dev);
+            close(tap_fd);
+            return error;
+        }
 
-    error = set_nonblocking(tap_fd);
-    if (error) {
-        ovs_error(error, "set_nonblocking on \"%s\" failed", tap_dev);
-        close(tap_fd);
+        error = do_open_netdev(ifr.ifr_name, NETDEV_ETH_TYPE_NONE, tap_fd,
+                               netdevp);
+        if (error) {
+            close(tap_fd);
+        }
         return error;
     }
-
-    error = do_open_netdev(ifr.ifr_name, NETDEV_ETH_TYPE_NONE, tap_fd,
-                           netdevp);
-    if (error) {
-        close(tap_fd);
-    }
-    return error;
 }
 
+
 static int
 do_open_netdev(const char *name, int ethertype, int tap_fd,
                struct netdev **netdev_)
@@ -536,6 +528,21 @@ netdev_close(struct netdev *netdev)
     }
 }
 
+/* Checks whether a network device named 'name' exists and returns true if so,
+ * false otherwise. */
+bool
+netdev_exists(const char *name)
+{
+    struct stat s;
+    char *filename;
+    int error;
+
+    filename = xasprintf("/sys/class/net/%s", name);
+    error = stat(filename, &s);
+    free(filename);
+    return !error;
+}
+
 /* Pads 'buffer' out with zero-bytes to the minimum valid length of an
  * Ethernet packet, if necessary.  */
 static void
@@ -686,12 +693,14 @@ netdev_nodev_set_etheraddr(const char *name, const uint8_t mac[ETH_ADDR_LEN])
     return set_etheraddr(name, ARPHRD_ETHER, mac);
 }
 
-/* Returns a pointer to 'netdev''s MAC address.  The caller must not modify or
- * free the returned buffer. */
-const uint8_t *
-netdev_get_etheraddr(const struct netdev *netdev)
+/* Retrieves 'netdev''s MAC address.  If successful, returns 0 and copies the
+ * the MAC address into 'mac'.  On failure, returns a positive errno value and
+ * clears 'mac' to all-zeros. */
+int
+netdev_get_etheraddr(const struct netdev *netdev, uint8_t mac[ETH_ADDR_LEN])
 {
-    return netdev->etheraddr;
+    memcpy(mac, netdev->etheraddr, ETH_ADDR_LEN);
+    return 0;
 }
 
 /* Returns the name of the network device that 'netdev' represents,
@@ -702,13 +711,18 @@ netdev_get_name(const struct netdev *netdev)
     return netdev->name;
 }
 
-/* Returns the maximum size of transmitted (and received) packets on 'netdev',
- * in bytes, not including the hardware header; thus, this is typically 1500
- * bytes for Ethernet devices. */
+/* Retrieves the MTU of 'netdev'.  The MTU is the maximum size of transmitted
+ * (and received) packets, in bytes, not including the hardware header; thus,
+ * this is typically 1500 bytes for Ethernet devices.
+ *
+ * If successful, returns 0 and stores the MTU size in '*mtup'.  On failure,
+ * returns a positive errno value and stores ETH_PAYLOAD_MAX (1500) in
+ * '*mtup'. */
 int
-netdev_get_mtu(const struct netdev *netdev
+netdev_get_mtu(const struct netdev *netdev, int *mtup)
 {
-    return netdev->mtu;
+    *mtup = netdev->mtu;
+    return 0;
 }
 
 /* Stores the features supported by 'netdev' into each of '*current',
@@ -729,6 +743,7 @@ netdev_get_features(struct netdev *netdev,
                            peer ? peer : &dummy[3]);
 }
 
+/* Set the features advertised by 'netdev' to 'advertise'. */
 int
 netdev_set_advertisements(struct netdev *netdev, uint32_t advertise)
 {
@@ -782,27 +797,39 @@ netdev_set_advertisements(struct netdev *netdev, uint32_t advertise)
 }
 
 /* If 'netdev' has an assigned IPv4 address, sets '*in4' to that address (if
- * 'in4' is non-null) and returns true.  Otherwise, returns false. */
-bool
-netdev_get_in4(const struct netdev *netdev, struct in_addr *in4)
+ * 'in4' is non-null) and returns 0.  Otherwise, returns a positive errno value
+ * and sets '*in4' to INADDR_ANY (0). */
+int
+netdev_nodev_get_in4(const char *netdev_name, struct in_addr *in4)
 {
     struct ifreq ifr;
     struct in_addr ip = { INADDR_ANY };
+    int error;
 
-    strncpy(ifr.ifr_name, netdev->name, sizeof ifr.ifr_name);
+    init_netdev();
+
+    strncpy(ifr.ifr_name, netdev_name, sizeof ifr.ifr_name);
     ifr.ifr_addr.sa_family = AF_INET;
     COVERAGE_INC(netdev_get_in4);
     if (ioctl(af_inet_sock, SIOCGIFADDR, &ifr) == 0) {
         struct sockaddr_in *sin = (struct sockaddr_in *) &ifr.ifr_addr;
         ip = sin->sin_addr;
+        error = ip.s_addr != INADDR_ANY ? 0 : EADDRNOTAVAIL;
     } else {
         VLOG_DBG_RL(&rl, "%s: ioctl(SIOCGIFADDR) failed: %s",
-                    netdev->name, strerror(errno));
+                    netdev_name, strerror(errno));
+        error = errno;
     }
     if (in4) {
         *in4 = ip;
     }
-    return ip.s_addr != INADDR_ANY;
+    return error;
+}
+
+int
+netdev_get_in4(const struct netdev *netdev, struct in_addr *in4)
+{
+    return netdev_nodev_get_in4(netdev->name, in4);
 }
 
 static void
@@ -852,9 +879,10 @@ netdev_set_in4(struct netdev *netdev, struct in_addr addr, struct in_addr mask)
     return error;
 }
 
-/* Adds 'router' as a default IP gateway. */
+/* Adds 'router' as a default IP gateway for the TCP/IP stack that corresponds
+ * to 'netdev'. */
 int
-netdev_add_router(struct in_addr router)
+netdev_add_router(struct netdev *netdev UNUSED, struct in_addr router)
 {
     struct in_addr any = { INADDR_ANY };
     struct rtentry rt;
@@ -970,13 +998,15 @@ netdev_turn_flags_off(struct netdev *netdev, enum netdev_flags flags,
  * returns 0.  Otherwise, it returns a positive errno value; in particular,
  * ENXIO indicates that there is not ARP table entry for 'ip' on 'netdev'. */
 int
-netdev_arp_lookup(const struct netdev *netdev,
-                  uint32_t ip, uint8_t mac[ETH_ADDR_LEN]) 
+netdev_nodev_arp_lookup(const char *netdev_name, uint32_t ip, 
+                        uint8_t mac[ETH_ADDR_LEN]) 
 {
     struct arpreq r;
     struct sockaddr_in *pa;
     int retval;
 
+    init_netdev();
+
     memset(&r, 0, sizeof r);
     pa = (struct sockaddr_in *) &r.arp_pa;
     pa->sin_family = AF_INET;
@@ -984,18 +1014,25 @@ netdev_arp_lookup(const struct netdev *netdev,
     pa->sin_port = 0;
     r.arp_ha.sa_family = ARPHRD_ETHER;
     r.arp_flags = 0;
-    strncpy(r.arp_dev, netdev->name, sizeof r.arp_dev);
+    strncpy(r.arp_dev, netdev_name, sizeof r.arp_dev);
     COVERAGE_INC(netdev_arp_lookup);
     retval = ioctl(af_inet_sock, SIOCGARP, &r) < 0 ? errno : 0;
     if (!retval) {
         memcpy(mac, r.arp_ha.sa_data, ETH_ADDR_LEN);
     } else if (retval != ENXIO) {
         VLOG_WARN_RL(&rl, "%s: could not look up ARP entry for "IP_FMT": %s",
-                     netdev->name, IP_ARGS(&ip), strerror(retval));
+                     netdev_name, IP_ARGS(&ip), strerror(retval));
     }
     return retval;
 }
 
+int
+netdev_arp_lookup(const struct netdev *netdev, uint32_t ip, 
+                  uint8_t mac[ETH_ADDR_LEN]) 
+{
+    return netdev_nodev_arp_lookup(netdev->name, ip, mac);
+}
+
 static int
 get_stats_via_netlink(int ifindex, struct netdev_stats *stats)
 {
@@ -1113,6 +1150,8 @@ get_stats_via_proc(const char *netdev_name, struct netdev_stats *stats)
     return ENODEV;
 }
 
+/* Sets 'carrier' to true if carrier is active (link light is on) on 
+ * 'netdev'. */
 int
 netdev_get_carrier(const struct netdev *netdev, bool *carrier)
 {
@@ -1169,6 +1208,7 @@ exit:
     return error;
 }
 
+/* Retrieves current device stats for 'netdev'. */
 int
 netdev_get_stats(const struct netdev *netdev, struct netdev_stats *stats)
 {
@@ -1275,6 +1315,45 @@ netdev_enumerate(struct svec *svec)
     }
 }
 
+/* Attempts to locate a device based on its IPv4 address.  The caller
+ * may provide a hint as to the device by setting 'netdev_name' to a
+ * likely device name.  This string must be malloc'd, since if it is 
+ * not correct then it will be freed.  If there is no hint, then
+ * 'netdev_name' must be the NULL pointer.
+ *
+ * If the device is found, the return value will be true and 'netdev_name' 
+ * contains the device's name as a string, which the caller is responsible 
+ * for freeing.  If the device is not found, the return value is false. */
+bool
+netdev_find_dev_by_in4(const struct in_addr *in4, char **netdev_name)
+{
+    int i;
+    struct in_addr dev_in4;
+    struct svec dev_list;
+
+    /* Check the hint first. */
+    if (*netdev_name && !netdev_nodev_get_in4(*netdev_name, &dev_in4)
+            && (dev_in4.s_addr == in4->s_addr)) {
+        return true;
+    }
+
+    free(*netdev_name);
+    *netdev_name = NULL;
+    netdev_enumerate(&dev_list);
+
+    for (i=0; i<dev_list.n; i++) {
+        if (!netdev_nodev_get_in4(dev_list.names[i], &dev_in4)
+                && (dev_in4.s_addr == in4->s_addr)) {
+            *netdev_name = xstrdup(dev_list.names[i]);
+            svec_destroy(&dev_list);
+            return true;
+        }
+    }
+
+    svec_destroy(&dev_list);
+    return false;
+}
+
 /* Obtains the current flags for the network device named 'netdev_name' and
  * stores them into '*flagsp'.  Returns 0 if successful, otherwise a positive
  * errno value.  On error, stores 0 into '*flagsp'.
@@ -1364,6 +1443,106 @@ done:
     return error;
 }
 \f
+struct netdev_monitor {
+    struct linux_netdev_notifier notifier;
+    struct shash polled_netdevs;
+    struct shash changed_netdevs;
+};
+
+static void netdev_monitor_change(const struct linux_netdev_change *change,
+                                  void *monitor);
+
+int
+netdev_monitor_create(struct netdev_monitor **monitorp)
+{
+    struct netdev_monitor *monitor;
+    int error;
+
+    monitor = xmalloc(sizeof *monitor);
+    error = linux_netdev_notifier_register(&monitor->notifier,
+                                           netdev_monitor_change, monitor);
+    if (error) {
+        free(monitor);
+        return error;
+    }
+    shash_init(&monitor->polled_netdevs);
+    shash_init(&monitor->changed_netdevs);
+    *monitorp = monitor;
+    return 0;
+}
+
+void
+netdev_monitor_destroy(struct netdev_monitor *monitor)
+{
+    if (monitor) {
+        linux_netdev_notifier_unregister(&monitor->notifier);
+        shash_destroy(&monitor->polled_netdevs);
+        free(monitor);
+    }
+}
+
+void
+netdev_monitor_add(struct netdev_monitor *monitor, struct netdev *netdev)
+{
+    if (!shash_find(&monitor->polled_netdevs, netdev_get_name(netdev))) {
+        shash_add(&monitor->polled_netdevs, netdev_get_name(netdev), NULL);
+    }
+}
+
+void
+netdev_monitor_remove(struct netdev_monitor *monitor, struct netdev *netdev)
+{
+    struct shash_node *node;
+
+    node = shash_find(&monitor->polled_netdevs, netdev_get_name(netdev));
+    if (node) {
+        shash_delete(&monitor->polled_netdevs, node);
+        node = shash_find(&monitor->changed_netdevs, netdev_get_name(netdev));
+        if (node) {
+            shash_delete(&monitor->changed_netdevs, node);
+        }
+    }
+}
+
+int
+netdev_monitor_poll(struct netdev_monitor *monitor, char **devnamep)
+{
+    int error = linux_netdev_notifier_get_error(&monitor->notifier);
+    *devnamep = NULL;
+    if (!error) {
+        struct shash_node *node = shash_first(&monitor->changed_netdevs);
+        if (!node) {
+            return EAGAIN;
+        }
+        *devnamep = xstrdup(node->name);
+        shash_delete(&monitor->changed_netdevs, node);
+    } else {
+        shash_clear(&monitor->changed_netdevs);
+    }
+    return error;
+}
+
+void
+netdev_monitor_poll_wait(const struct netdev_monitor *monitor)
+{
+    if (!shash_is_empty(&monitor->changed_netdevs)
+        || linux_netdev_notifier_peek_error(&monitor->notifier)) {
+        poll_immediate_wake();
+    } else {
+        linux_netdev_notifier_wait();
+    }
+}
+
+static void
+netdev_monitor_change(const struct linux_netdev_change *change, void *monitor_)
+{
+    struct netdev_monitor *monitor = monitor_;
+    if (shash_find(&monitor->polled_netdevs, change->ifname)
+        && !shash_find(&monitor->changed_netdevs, change->ifname)) {
+        shash_add(&monitor->changed_netdevs, change->ifname, NULL);
+    }
+}
+\f
 static void restore_all_flags(void *aux);
 
 /* Set up a signal hook to restore network device flags on program