lib: Simplify rtnetlink routing functionality.
[sliver-openvswitch.git] / lib / netdev-vport.c
index 9ae21d1..2d0d984 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010 Nicira Networks.
+ * Copyright (c) 2010, 2011 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 
 #include <errno.h>
 #include <fcntl.h>
+#include <sys/socket.h>
+#include <linux/rtnetlink.h>
 #include <net/if.h>
 #include <sys/ioctl.h>
 
 #include "byte-order.h"
+#include "hash.h"
+#include "hmap.h"
 #include "list.h"
 #include "netdev-provider.h"
+#include "netlink.h"
+#include "netlink-socket.h"
+#include "ofpbuf.h"
 #include "openvswitch/datapath-protocol.h"
 #include "openvswitch/tunnel.h"
 #include "packets.h"
+#include "rtnetlink.h"
+#include "route-table.h"
+#include "rtnetlink-link.h"
 #include "shash.h"
 #include "socket-util.h"
 #include "vlog.h"
 
 VLOG_DEFINE_THIS_MODULE(netdev_vport);
 
+static struct hmap name_map;
+static struct rtnetlink_notifier netdev_vport_link_notifier;
+
+struct name_node {
+    struct hmap_node node; /* Node in name_map. */
+    uint32_t ifi_index;    /* Kernel interface index. */
+
+    char ifname[IFNAMSIZ]; /* Interface name. */
+};
+
 struct netdev_vport_notifier {
     struct netdev_notifier notifier;
     struct list list_node;
@@ -66,6 +86,11 @@ static int netdev_vport_create(const struct netdev_class *, const char *,
                                const struct shash *, struct netdev_dev **);
 static void netdev_vport_poll_notify(const struct netdev *);
 
+static void netdev_vport_tnl_iface_init(void);
+static void netdev_vport_link_change(const struct rtnetlink_link_change *,
+                                     void *);
+static const char *netdev_vport_get_tnl_iface(const struct netdev *netdev);
+
 static bool
 is_vport_class(const struct netdev_class *class)
 {
@@ -107,6 +132,14 @@ netdev_vport_get_config(const struct netdev *netdev, void *config)
     }
 }
 
+static int
+netdev_vport_init(void)
+{
+    netdev_vport_tnl_iface_init();
+    route_table_register();
+    return 0;
+}
+
 static int
 netdev_vport_create(const struct netdev_class *netdev_class, const char *name,
                     const struct shash *args,
@@ -134,6 +167,7 @@ netdev_vport_destroy(struct netdev_dev *netdev_dev_)
 {
     struct netdev_dev_vport *netdev_dev = netdev_dev_vport_cast(netdev_dev_);
 
+    route_table_unregister();
     free(netdev_dev);
 }
 
@@ -322,6 +356,18 @@ netdev_vport_set_stats(struct netdev *netdev, const struct netdev_stats *stats)
     return err;
 }
 
+static int
+netdev_vport_get_status(const struct netdev *netdev, struct shash *sh)
+{
+    const char *iface = netdev_vport_get_tnl_iface(netdev);
+
+    if (iface) {
+        shash_add(sh, "tunnel_egress_iface", xstrdup(iface));
+    }
+
+    return 0;
+}
+
 static int
 netdev_vport_update_flags(struct netdev *netdev OVS_UNUSED,
                         enum netdev_flags off, enum netdev_flags on OVS_UNUSED,
@@ -387,6 +433,158 @@ netdev_vport_poll_remove(struct netdev_notifier *notifier_)
 
     free(notifier);
 }
+
+static void
+netdev_vport_run(void)
+{
+    rtnetlink_link_notifier_run();
+    route_table_run();
+}
+
+static void
+netdev_vport_wait(void)
+{
+    rtnetlink_link_notifier_wait();
+    route_table_wait();
+}
+\f
+/* get_tnl_iface() implementation. */
+
+static struct name_node *
+name_node_lookup(int ifi_index)
+{
+    struct name_node *nn;
+
+    HMAP_FOR_EACH_WITH_HASH(nn, node, hash_int(ifi_index, 0), &name_map) {
+        if (nn->ifi_index == ifi_index) {
+            return nn;
+        }
+    }
+
+    return NULL;
+}
+
+/* Queries the kernel for fresh data to populate the name map with. */
+static int
+netdev_vport_reset_names(void)
+{
+    int error;
+    struct nl_dump dump;
+    struct rtgenmsg *rtmsg;
+    struct ofpbuf request, reply;
+    static struct nl_sock *rtnl_sock;
+    struct name_node *nn, *nn_next;
+
+    HMAP_FOR_EACH_SAFE(nn, nn_next, node, &name_map) {
+        hmap_remove(&name_map, &nn->node);
+        free(nn);
+    }
+
+    error = nl_sock_create(NETLINK_ROUTE, 0, 0, 0, &rtnl_sock);
+    if (error) {
+        VLOG_WARN_RL(&rl, "Failed to create NETLINK_ROUTE socket");
+        return error;
+    }
+
+    ofpbuf_init(&request, 0);
+
+    nl_msg_put_nlmsghdr(&request, sizeof *rtmsg, RTM_GETLINK, NLM_F_REQUEST);
+
+    rtmsg = ofpbuf_put_zeros(&request, sizeof *rtmsg);
+    rtmsg->rtgen_family = AF_INET;
+
+    nl_dump_start(&dump, rtnl_sock, &request);
+
+    while (nl_dump_next(&dump, &reply)) {
+        struct rtnetlink_link_change change;
+
+        if (rtnetlink_link_parse(&reply, &change)) {
+            netdev_vport_link_change(&change, NULL);
+        }
+    }
+
+    error = nl_dump_done(&dump);
+    nl_sock_destroy(rtnl_sock);
+
+    return error;
+}
+
+static void
+netdev_vport_link_change(const struct rtnetlink_link_change *change,
+                         void *aux OVS_UNUSED)
+{
+
+    if (!change) {
+        netdev_vport_reset_names();
+    } else if (change->nlmsg_type == RTM_NEWLINK) {
+        struct name_node *nn;
+
+        if (name_node_lookup(change->ifi_index)) {
+            return;
+        }
+
+        nn            = xzalloc(sizeof *nn);
+        nn->ifi_index = change->ifi_index;
+
+        strncpy(nn->ifname, change->ifname, IFNAMSIZ);
+        nn->ifname[IFNAMSIZ - 1] = '\0';
+
+        hmap_insert(&name_map, &nn->node, hash_int(nn->ifi_index, 0));
+    } else if (change->nlmsg_type == RTM_DELLINK) {
+        struct name_node *nn;
+
+        nn = name_node_lookup(change->ifi_index);
+
+        if (nn) {
+            hmap_remove(&name_map, &nn->node);
+            free(nn);
+        }
+
+    } else {
+        VLOG_WARN_RL(&rl, "Received unexpected rtnetlink message type %d",
+                     change->nlmsg_type);
+    }
+}
+
+static void
+netdev_vport_tnl_iface_init(void)
+{
+    static bool tnl_iface_is_init = false;
+
+    if (!tnl_iface_is_init) {
+        hmap_init(&name_map);
+
+        rtnetlink_link_notifier_register(&netdev_vport_link_notifier,
+                                         netdev_vport_link_change, NULL);
+
+        netdev_vport_reset_names();
+        tnl_iface_is_init = true;
+    }
+}
+
+static const char *
+netdev_vport_get_tnl_iface(const struct netdev *netdev)
+{
+    int ifindex;
+    uint32_t route;
+    struct netdev_dev_vport *ndv;
+    struct tnl_port_config *config;
+
+    ndv = netdev_dev_vport_cast(netdev_get_dev(netdev));
+    config = (struct tnl_port_config *) ndv->config;
+    route = config->daddr;
+
+    if (route_table_get_ifindex(route, &ifindex)) {
+        struct name_node *nn;
+        HMAP_FOR_EACH_WITH_HASH(nn, node, hash_int(ifindex, 0), &name_map) {
+            if (nn->ifi_index == ifindex) {
+                return nn->ifname;
+            }
+        }
+    }
+
+    return NULL;
+}
 \f
 /* Helper functions. */
 
@@ -518,15 +716,28 @@ parse_tunnel_config(const struct netdev_dev *dev, const struct shash *args,
             if (shash_find(args, "certificate")) {
                 ipsec_mech_set = true;
             } else {
-                VLOG_WARN("%s: 'peer_cert' requires 'certificate' argument",
-                          name);
-                return EINVAL;
+                const char *use_ssl_cert;
+
+                /* If the "use_ssl_cert" is true, then "certificate" and
+                 * "private_key" will be pulled from the SSL table.  The
+                 * use of this option is strongly discouraged, since it
+                 * will like be removed when multiple SSL configurations
+                 * are supported by OVS.
+                 */
+                use_ssl_cert = shash_find_data(args, "use_ssl_cert");
+                if (!use_ssl_cert || strcmp(use_ssl_cert, "true")) {
+                    VLOG_WARN("%s: 'peer_cert' requires 'certificate' argument",
+                              name);
+                    return EINVAL;
+                }
+                ipsec_mech_set = true;
             }
         } else if (!strcmp(node->name, "psk") && is_ipsec) {
             ipsec_mech_set = true;
-        } else if (is_ipsec 
+        } else if (is_ipsec
                 && (!strcmp(node->name, "certificate")
-                    || !strcmp(node->name, "private_key"))) {
+                    || !strcmp(node->name, "private_key")
+                    || !strcmp(node->name, "use_ssl_cert"))) {
             /* Ignore options not used by the netdev. */
         } else {
             VLOG_WARN("%s: unknown %s argument '%s'",
@@ -591,10 +802,10 @@ parse_patch_config(const struct netdev_dev *dev, const struct shash *args,
     return 0;
 }
 \f
-#define VPORT_FUNCTIONS                                     \
-    NULL,                       /* init */                  \
-    NULL,                       /* run */                   \
-    NULL,                       /* wait */                  \
+#define VPORT_FUNCTIONS(GET_STATUS)                         \
+    netdev_vport_init,                                      \
+    netdev_vport_run,                                       \
+    netdev_vport_wait,                                      \
                                                             \
     netdev_vport_create,                                    \
     netdev_vport_destroy,                                   \
@@ -617,6 +828,7 @@ parse_patch_config(const struct netdev_dev *dev, const struct shash *args,
     netdev_vport_get_mtu,                                   \
     NULL,                       /* get_ifindex */           \
     NULL,                       /* get_carrier */           \
+    NULL,                       /* get_miimon */            \
     netdev_vport_get_stats,                                 \
     netdev_vport_set_stats,                                 \
                                                             \
@@ -641,6 +853,7 @@ parse_patch_config(const struct netdev_dev *dev, const struct shash *args,
     NULL,                       /* get_in6 */               \
     NULL,                       /* add_router */            \
     NULL,                       /* get_next_hop */          \
+    GET_STATUS,                                             \
     NULL,                       /* arp_lookup */            \
                                                             \
     netdev_vport_update_flags,                              \
@@ -652,10 +865,13 @@ void
 netdev_vport_register(void)
 {
     static const struct vport_class vport_classes[] = {
-        { { "gre", VPORT_FUNCTIONS }, parse_tunnel_config },
-        { { "ipsec_gre", VPORT_FUNCTIONS }, parse_tunnel_config },
-        { { "capwap", VPORT_FUNCTIONS }, parse_tunnel_config },
-        { { "patch", VPORT_FUNCTIONS }, parse_patch_config }
+        { { "gre", VPORT_FUNCTIONS(netdev_vport_get_status) },
+            parse_tunnel_config },
+        { { "ipsec_gre", VPORT_FUNCTIONS(netdev_vport_get_status) },
+            parse_tunnel_config },
+        { { "capwap", VPORT_FUNCTIONS(netdev_vport_get_status) },
+            parse_tunnel_config },
+        { { "patch", VPORT_FUNCTIONS(NULL) }, parse_patch_config }
     };
 
     int i;