netdev-dpdk: fix mbuf leaks
[cascardo/ovs.git] / lib / tnl-ports.c
index bcf055b..e7f2066 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014 Nicira, Inc.
+ * Copyright (c) 2014, 2015 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  */
 
 #include <config.h>
+
+#include "tnl-ports.h"
+
 #include <stddef.h>
 #include <stdint.h>
+#include <string.h>
 
 #include "classifier.h"
 #include "dynamic-string.h"
 #include "hash.h"
+#include "list.h"
+#include "netdev.h"
 #include "ofpbuf.h"
 #include "ovs-thread.h"
 #include "odp-util.h"
-#include "tnl-arp-cache.h"
-#include "tnl-ports.h"
+#include "ovs-thread.h"
 #include "unixctl.h"
 #include "util.h"
 
+static struct ovs_mutex mutex = OVS_MUTEX_INITIALIZER;
 static struct classifier cls;   /* Tunnel ports. */
 
+struct ip_device {
+    struct netdev *dev;
+    struct eth_addr mac;
+    ovs_be32 addr4;
+    struct in6_addr addr6;
+    uint64_t change_seq;
+    struct ovs_list node;
+    char dev_name[IFNAMSIZ];
+};
+
+static struct ovs_list addr_list;
+
+struct tnl_port {
+    odp_port_t port;
+    ovs_be16 udp_port;
+    char dev_name[IFNAMSIZ];
+    struct ovs_list node;
+};
+
+static struct ovs_list port_list;
+
 struct tnl_port_in {
     struct cls_rule cr;
     odp_port_t portno;
@@ -54,56 +81,109 @@ tnl_port_free(struct tnl_port_in *p)
 }
 
 static void
-tnl_port_init_flow(struct flow *flow, ovs_be32 ip_dst, ovs_be16 udp_port)
+tnl_port_init_flow(struct flow *flow, struct eth_addr mac,
+                   struct in6_addr *addr, ovs_be16 udp_port)
 {
     memset(flow, 0, sizeof *flow);
-    flow->dl_type = htons(ETH_TYPE_IP);
+
+    flow->dl_dst = mac;
+    if (IN6_IS_ADDR_V4MAPPED(addr)) {
+        flow->dl_type = htons(ETH_TYPE_IP);
+        flow->nw_dst = in6_addr_get_mapped_ipv4(addr);
+    } else {
+        flow->dl_type = htons(ETH_TYPE_IPV6);
+        flow->ipv6_dst = *addr;
+    }
+
     if (udp_port) {
         flow->nw_proto = IPPROTO_UDP;
     } else {
         flow->nw_proto = IPPROTO_GRE;
     }
     flow->tp_dst = udp_port;
-    /* When matching on incoming flow from remove tnl end point,
-     * our dst ip address is source ip for them. */
-    flow->nw_src = ip_dst;
 }
 
-void
-tnl_port_map_insert(odp_port_t port, ovs_be32 ip_dst, ovs_be16 udp_port,
-                    const char dev_name[])
+static void
+map_insert(odp_port_t port, struct eth_addr mac, struct in6_addr *addr,
+           ovs_be16 udp_port, const char dev_name[])
 {
     const struct cls_rule *cr;
     struct tnl_port_in *p;
     struct match match;
 
     memset(&match, 0, sizeof match);
-    tnl_port_init_flow(&match.flow, ip_dst, udp_port);
+    tnl_port_init_flow(&match.flow, mac, addr, udp_port);
 
     do {
-        cr = classifier_lookup(&cls, &match.flow, NULL);
+        cr = classifier_lookup(&cls, CLS_MAX_VERSION, &match.flow, NULL);
         p = tnl_port_cast(cr);
         /* Try again if the rule was released before we get the reference. */
     } while (p && !ovs_refcount_try_ref_rcu(&p->ref_cnt));
 
-    if (p) {
-        return; /* Added refcount of an existing port. */
+    if (!p) {
+        p = xzalloc(sizeof *p);
+        p->portno = port;
+
+        match.wc.masks.dl_type = OVS_BE16_MAX;
+        match.wc.masks.nw_proto = 0xff;
+         /* XXX: No fragments support. */
+        match.wc.masks.nw_frag = FLOW_NW_FRAG_MASK;
+
+        /* 'udp_port' is zero for non-UDP tunnels (e.g. GRE). In this case it
+         * doesn't make sense to match on UDP port numbers. */
+        if (udp_port) {
+            match.wc.masks.tp_dst = OVS_BE16_MAX;
+        }
+        if (IN6_IS_ADDR_V4MAPPED(addr)) {
+            match.wc.masks.nw_dst = OVS_BE32_MAX;
+        } else {
+            match.wc.masks.ipv6_dst = in6addr_exact;
+        }
+        match.wc.masks.vlan_tci = OVS_BE16_MAX;
+        memset(&match.wc.masks.dl_dst, 0xff, sizeof (struct eth_addr));
+
+        cls_rule_init(&p->cr, &match, 0); /* Priority == 0. */
+        ovs_refcount_init(&p->ref_cnt);
+        ovs_strlcpy(p->dev_name, dev_name, sizeof p->dev_name);
+
+        classifier_insert(&cls, &p->cr, CLS_MIN_VERSION, NULL, 0);
     }
+}
 
-    p = xzalloc(sizeof *p);
-    p->portno = port;
+void
+tnl_port_map_insert(odp_port_t port,
+                    ovs_be16 udp_port, const char dev_name[])
+{
+    struct tnl_port *p;
+    struct ip_device *ip_dev;
+
+    ovs_mutex_lock(&mutex);
+    LIST_FOR_EACH(p, node, &port_list) {
+        if (udp_port == p->udp_port) {
+             goto out;
+        }
+    }
 
-    match.wc.masks.dl_type = OVS_BE16_MAX;
-    match.wc.masks.nw_proto = 0xff;
-    match.wc.masks.nw_frag = 0xff;      /* XXX: No fragments support. */
-    match.wc.masks.tp_dst = OVS_BE16_MAX;
-    match.wc.masks.nw_src = OVS_BE32_MAX;
+    p = xzalloc(sizeof *p);
+    p->port = port;
+    p->udp_port = udp_port;
+    ovs_strlcpy(p->dev_name, dev_name, sizeof p->dev_name);
+    list_insert(&port_list, &p->node);
 
-    cls_rule_init(&p->cr, &match, 0);   /* Priority == 0. */
-    ovs_refcount_init(&p->ref_cnt);
-    strncpy(p->dev_name, dev_name, IFNAMSIZ);
+    LIST_FOR_EACH(ip_dev, node, &addr_list) {
+        if (ip_dev->addr4 != INADDR_ANY) {
+            struct in6_addr addr4 = in6_addr_mapped_ipv4(ip_dev->addr4);
+            map_insert(p->port, ip_dev->mac, &addr4,
+                       p->udp_port, p->dev_name);
+        }
+        if (ipv6_addr_is_set(&ip_dev->addr6)) {
+            map_insert(p->port, ip_dev->mac, &ip_dev->addr6,
+                       p->udp_port, p->dev_name);
+        }
+    }
 
-    classifier_insert(&cls, &p->cr);
+out:
+    ovs_mutex_unlock(&mutex);
 }
 
 static void
@@ -118,34 +198,68 @@ tnl_port_unref(const struct cls_rule *cr)
     }
 }
 
-void
-tnl_port_map_delete(ovs_be32 ip_dst, ovs_be16 udp_port)
+static void
+map_delete(struct eth_addr mac, struct in6_addr *addr, ovs_be16 udp_port)
 {
     const struct cls_rule *cr;
     struct flow flow;
 
-    tnl_port_init_flow(&flow, ip_dst, udp_port);
+    tnl_port_init_flow(&flow, mac, addr, udp_port);
 
-    cr = classifier_lookup(&cls, &flow, NULL);
+    cr = classifier_lookup(&cls, CLS_MAX_VERSION, &flow, NULL);
     tnl_port_unref(cr);
 }
 
+void
+tnl_port_map_delete(ovs_be16 udp_port)
+{
+    struct tnl_port *p, *next;
+    struct ip_device *ip_dev;
+    bool found = false;
+
+    ovs_mutex_lock(&mutex);
+    LIST_FOR_EACH_SAFE(p, next, node, &port_list) {
+        if (p->udp_port == udp_port) {
+            list_remove(&p->node);
+            found = true;
+            break;
+        }
+    }
+
+    if (!found) {
+        goto out;
+    }
+    LIST_FOR_EACH(ip_dev, node, &addr_list) {
+        if (ip_dev->addr4 != INADDR_ANY) {
+            struct in6_addr addr4 = in6_addr_mapped_ipv4(ip_dev->addr4);
+            map_delete(ip_dev->mac, &addr4, udp_port);
+        }
+        if (ipv6_addr_is_set(&ip_dev->addr6)) {
+            map_delete(ip_dev->mac, &ip_dev->addr6, udp_port);
+        }
+    }
+
+    free(p);
+out:
+    ovs_mutex_unlock(&mutex);
+}
+
+/* 'flow' is non-const to allow for temporary modifications during the lookup.
+ * Any changes are restored before returning. */
 odp_port_t
-tnl_port_map_lookup(const struct flow *flow, struct flow_wildcards *wc)
+tnl_port_map_lookup(struct flow *flow, struct flow_wildcards *wc)
 {
-    const struct cls_rule *cr = classifier_lookup(&cls, flow, wc);
+    const struct cls_rule *cr = classifier_lookup(&cls, CLS_MAX_VERSION, flow,
+                                                  wc);
 
     return (cr) ? tnl_port_cast(cr)->portno : ODPP_NONE;
 }
 
 static void
-tnl_port_show(struct unixctl_conn *conn, int argc OVS_UNUSED,
-              const char *argv[] OVS_UNUSED, void *aux OVS_UNUSED)
+tnl_port_show_v(struct ds *ds)
 {
-    struct ds ds = DS_EMPTY_INITIALIZER;
     const struct tnl_port_in *p;
 
-    ds_put_format(&ds, "Listening ports:\n");
     CLS_FOR_EACH(p, cr, &cls) {
         struct odputil_keybuf keybuf;
         struct odputil_keybuf maskbuf;
@@ -154,36 +268,204 @@ tnl_port_show(struct unixctl_conn *conn, int argc OVS_UNUSED,
         size_t key_len, mask_len;
         struct flow_wildcards wc;
         struct ofpbuf buf;
+        struct odp_flow_key_parms odp_parms = {
+            .flow = &flow,
+            .mask = &wc.masks,
+        };
 
-        ds_put_format(&ds, "%s (%"PRIu32") : ", p->dev_name, p->portno);
-        minimask_expand(&p->cr.match.mask, &wc);
-        miniflow_expand(&p->cr.match.flow, &flow);
+        ds_put_format(ds, "%s (%"PRIu32") : ", p->dev_name, p->portno);
+        minimask_expand(p->cr.match.mask, &wc);
+        miniflow_expand(p->cr.match.flow, &flow);
 
         /* Key. */
+        odp_parms.odp_in_port = flow.in_port.odp_port;
+        odp_parms.support.recirc = true;
         ofpbuf_use_stack(&buf, &keybuf, sizeof keybuf);
-        odp_flow_key_from_flow(&buf, &flow, &wc.masks,
-                               flow.in_port.odp_port, true);
-        key = ofpbuf_data(&buf);
-        key_len = ofpbuf_size(&buf);
+        odp_flow_key_from_flow(&odp_parms, &buf);
+        key = buf.data;
+        key_len = buf.size;
+
         /* mask*/
+        odp_parms.odp_in_port = wc.masks.in_port.odp_port;
+        odp_parms.support.recirc = false;
         ofpbuf_use_stack(&buf, &maskbuf, sizeof maskbuf);
-        odp_flow_key_from_mask(&buf, &wc.masks, &flow,
-                               odp_to_u32(wc.masks.in_port.odp_port),
-                               SIZE_MAX, false);
-        mask = ofpbuf_data(&buf);
-        mask_len = ofpbuf_size(&buf);
+        odp_flow_key_from_mask(&odp_parms, &buf);
+        mask = buf.data;
+        mask_len = buf.size;
 
         /* build string. */
-        odp_flow_format(key, key_len, mask, mask_len, NULL, &ds, false);
-        ds_put_format(&ds, "\n");
+        odp_flow_format(key, key_len, mask, mask_len, NULL, ds, false);
+        ds_put_format(ds, "\n");
     }
+}
+
+static void
+tnl_port_show(struct unixctl_conn *conn, int argc OVS_UNUSED,
+               const char *argv[] OVS_UNUSED, void *aux OVS_UNUSED)
+{
+    struct ds ds = DS_EMPTY_INITIALIZER;
+    struct tnl_port *p;
+
+    ds_put_format(&ds, "Listening ports:\n");
+    ovs_mutex_lock(&mutex);
+    if (argc > 1) {
+        if (!strcasecmp(argv[1], "-v")) {
+            tnl_port_show_v(&ds);
+            goto out;
+        }
+    }
+
+    LIST_FOR_EACH(p, node, &port_list) {
+        ds_put_format(&ds, "%s (%"PRIu32")\n", p->dev_name, p->port);
+    }
+
+out:
+    ovs_mutex_unlock(&mutex);
     unixctl_command_reply(conn, ds_cstr(&ds));
     ds_destroy(&ds);
 }
 
+static void
+map_insert_ipdev(struct ip_device *ip_dev)
+{
+    struct tnl_port *p;
+
+    LIST_FOR_EACH(p, node, &port_list) {
+        if (ip_dev->addr4 != INADDR_ANY) {
+            struct in6_addr addr4 = in6_addr_mapped_ipv4(ip_dev->addr4);
+            map_insert(p->port, ip_dev->mac, &addr4,
+                       p->udp_port, p->dev_name);
+        }
+        if (ipv6_addr_is_set(&ip_dev->addr6)) {
+            map_insert(p->port, ip_dev->mac, &ip_dev->addr6,
+                       p->udp_port, p->dev_name);
+        }
+    }
+}
+
+static void
+insert_ipdev(const char dev_name[])
+{
+    struct ip_device *ip_dev;
+    enum netdev_flags flags;
+    struct netdev *dev;
+    int error;
+    int error4, error6;
+
+    error = netdev_open(dev_name, NULL, &dev);
+    if (error) {
+        return;
+    }
+
+    error = netdev_get_flags(dev, &flags);
+    if (error || (flags & NETDEV_LOOPBACK)) {
+        netdev_close(dev);
+        return;
+    }
+
+    ip_dev = xzalloc(sizeof *ip_dev);
+    ip_dev->dev = dev;
+    ip_dev->change_seq = netdev_get_change_seq(dev);
+    error = netdev_get_etheraddr(ip_dev->dev, &ip_dev->mac);
+    if (error) {
+        free(ip_dev);
+        return;
+    }
+    error4 = netdev_get_in4(ip_dev->dev, (struct in_addr *)&ip_dev->addr4, NULL);
+    error6 = netdev_get_in6(ip_dev->dev, &ip_dev->addr6);
+    if (error4 && error6) {
+        free(ip_dev);
+        return;
+    }
+    ovs_strlcpy(ip_dev->dev_name, netdev_get_name(dev), sizeof ip_dev->dev_name);
+
+    list_insert(&addr_list, &ip_dev->node);
+    map_insert_ipdev(ip_dev);
+}
+
+static void
+delete_ipdev(struct ip_device *ip_dev)
+{
+    struct tnl_port *p;
+
+    LIST_FOR_EACH(p, node, &port_list) {
+        if (ip_dev->addr4 != INADDR_ANY) {
+            struct in6_addr addr4 = in6_addr_mapped_ipv4(ip_dev->addr4);
+            map_delete(ip_dev->mac, &addr4, p->udp_port);
+        }
+        if (ipv6_addr_is_set(&ip_dev->addr6)) {
+            map_delete(ip_dev->mac, &ip_dev->addr6, p->udp_port);
+        }
+    }
+
+    list_remove(&ip_dev->node);
+    netdev_close(ip_dev->dev);
+    free(ip_dev);
+}
+
+void
+tnl_port_map_insert_ipdev(const char dev_name[])
+{
+    struct ip_device *ip_dev, *next;
+
+    ovs_mutex_lock(&mutex);
+
+    LIST_FOR_EACH_SAFE(ip_dev, next, node, &addr_list) {
+        if (!strcmp(netdev_get_name(ip_dev->dev), dev_name)) {
+            if (ip_dev->change_seq == netdev_get_change_seq(ip_dev->dev)) {
+                goto out;
+            }
+            /* Address changed. */
+            delete_ipdev(ip_dev);
+            break;
+        }
+    }
+    insert_ipdev(dev_name);
+
+out:
+    ovs_mutex_unlock(&mutex);
+}
+
+void
+tnl_port_map_delete_ipdev(const char dev_name[])
+{
+    struct ip_device *ip_dev, *next;
+
+    ovs_mutex_lock(&mutex);
+    LIST_FOR_EACH_SAFE(ip_dev, next, node, &addr_list) {
+        if (!strcmp(netdev_get_name(ip_dev->dev), dev_name)) {
+            delete_ipdev(ip_dev);
+        }
+    }
+    ovs_mutex_unlock(&mutex);
+}
+
+void
+tnl_port_map_run(void)
+{
+    struct ip_device *ip_dev, *next;
+
+    ovs_mutex_lock(&mutex);
+    LIST_FOR_EACH_SAFE(ip_dev, next, node, &addr_list) {
+        char dev_name[IFNAMSIZ];
+
+        if (ip_dev->change_seq == netdev_get_change_seq(ip_dev->dev)) {
+            continue;
+        }
+
+        /* Address changed. */
+        ovs_strlcpy(dev_name, ip_dev->dev_name, sizeof dev_name);
+        delete_ipdev(ip_dev);
+        insert_ipdev(dev_name);
+    }
+    ovs_mutex_unlock(&mutex);
+}
+
 void
 tnl_port_map_init(void)
 {
-    classifier_init(&cls, flow_segment_u32s);
-    unixctl_command_register("tnl/ports/show", "", 0, 0, tnl_port_show, NULL);
+    classifier_init(&cls, flow_segment_u64s);
+    list_init(&addr_list);
+    list_init(&port_list);
+    unixctl_command_register("tnl/ports/show", "-v", 0, 1, tnl_port_show, NULL);
 }