netdev-dpdk: fix mbuf leaks
[cascardo/ovs.git] / lib / tnl-ports.c
index 2602db5..e7f2066 100644 (file)
  */
 
 #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;
@@ -56,33 +81,39 @@ 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);
 
-    ovs_mutex_lock(&mutex);
     do {
         cr = classifier_lookup(&cls, CLS_MAX_VERSION, &match.flow, NULL);
         p = tnl_port_cast(cr);
@@ -95,16 +126,63 @@ tnl_port_map_insert(odp_port_t port, ovs_be32 ip_dst, ovs_be16 udp_port,
 
         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;
+         /* 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, CLS_MIN_VERSION); /* Priority == 0. */
+        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, NULL, 0);
+        classifier_insert(&cls, &p->cr, CLS_MIN_VERSION, NULL, 0);
     }
+}
+
+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;
+        }
+    }
+
+    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);
+
+    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);
+        }
+    }
+
+out:
     ovs_mutex_unlock(&mutex);
 }
 
@@ -114,26 +192,58 @@ tnl_port_unref(const struct cls_rule *cr)
     struct tnl_port_in *p = tnl_port_cast(cr);
 
     if (cr && ovs_refcount_unref_relaxed(&p->ref_cnt) == 1) {
-        ovs_mutex_lock(&mutex);
         if (classifier_remove(&cls, cr)) {
             ovsrcu_postpone(tnl_port_free, p);
         }
-        ovs_mutex_unlock(&mutex);
     }
 }
 
-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, 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
@@ -146,13 +256,10 @@ tnl_port_map_lookup(struct flow *flow, struct flow_wildcards *wc)
 }
 
 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;
@@ -161,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);
+        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);
+        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_u64s);
-    unixctl_command_register("tnl/ports/show", "", 0, 0, tnl_port_show, NULL);
+    list_init(&addr_list);
+    list_init(&port_list);
+    unixctl_command_register("tnl/ports/show", "-v", 0, 1, tnl_port_show, NULL);
 }