diff mbox

[ovs-dev,v2,2/7] tunneling: Handle multiple ip address for given device.

Message ID 1458061053-108802-2-git-send-email-pshelar@ovn.org
State Superseded
Headers show

Commit Message

Pravin Shelar March 15, 2016, 4:57 p.m. UTC
From: Pravin B Shelar <pshelar@nicira.com>

Device can have multiple IP address but netdev_get_in4/6()
returns only one configured IPv6 address. Following
patch fixes it.
OVS router is also updated to return source ip address for
given destination, This is required when interface has multiple
IP address configured.

Signed-off-by: Pravin B Shelar <pshelar@ovn.org>
---
 lib/netdev-bsd.c             |  40 ++++----------
 lib/netdev-dpdk.c            |   2 +-
 lib/netdev-dummy.c           |  64 ++++++++++++++++++----
 lib/netdev-linux.c           |  55 +++----------------
 lib/netdev-provider.h        |  12 ++--
 lib/netdev-vport.c           |   4 +-
 lib/netdev.c                 | 127 +++++++++++++++++++++++++++++++++++++++----
 lib/netdev.h                 |  10 +++-
 lib/ovs-router.c             |  79 ++++++++++++++++++++++++---
 lib/ovs-router.h             |   2 +-
 lib/route-table.c            |   2 -
 lib/tnl-ports.c              | 124 +++++++++++++++++++++++-------------------
 ofproto/ofproto-dpif-xlate.c |  20 ++-----
 tests/ofproto-dpif.at        |  12 ++--
 vswitchd/bridge.c            |   3 +-
 15 files changed, 361 insertions(+), 195 deletions(-)

Comments

Ben Pfaff March 23, 2016, 3:27 p.m. UTC | #1
On Tue, Mar 15, 2016 at 09:57:28AM -0700, Pravin B Shelar wrote:
> From: Pravin B Shelar <pshelar@nicira.com>

You might want to update the author email.

> Device can have multiple IP address but netdev_get_in4/6()
> returns only one configured IPv6 address. Following
> patch fixes it.
> OVS router is also updated to return source ip address for
> given destination, This is required when interface has multiple
> IP address configured.
> 
> Signed-off-by: Pravin B Shelar <pshelar@ovn.org>

Acked-by: Ben Pfaff <blp@ovn.org>
diff mbox

Patch

diff --git a/lib/netdev-bsd.c b/lib/netdev-bsd.c
index edf04bf..d7d73af 100644
--- a/lib/netdev-bsd.c
+++ b/lib/netdev-bsd.c
@@ -92,7 +92,6 @@  struct netdev_bsd {
     struct eth_addr etheraddr;
     struct in_addr in4;
     struct in_addr netmask;
-    struct in6_addr in6;
     int mtu;
     int carrier;
 
@@ -1244,37 +1243,20 @@  netdev_bsd_set_in4(struct netdev *netdev_, struct in_addr addr,
 }
 
 static int
-netdev_bsd_get_in6(const struct netdev *netdev_, struct in6_addr *in6)
+netdev_bsd_get_addr_list(const struct netdev *netdev_,
+                         struct in6_addr **addr, struct in6_addr **mask, int *n_cnt)
 {
     struct netdev_bsd *netdev = netdev_bsd_cast(netdev_);
-    if (!(netdev->cache_valid & VALID_IN6)) {
-        struct ifaddrs *ifa, *head;
-        struct sockaddr_in6 *sin6;
-        const char *netdev_name = netdev_get_name(netdev_);
-
-        if (getifaddrs(&head) != 0) {
-            VLOG_ERR("getifaddrs on %s device failed: %s", netdev_name,
-                    ovs_strerror(errno));
-            return errno;
-        }
+    int error;
 
-        for (ifa = head; ifa; ifa = ifa->ifa_next) {
-            if (ifa->ifa_addr->sa_family == AF_INET6 &&
-                    !strcmp(ifa->ifa_name, netdev_name)) {
-                sin6 = ALIGNED_CAST(struct sockaddr_in6 *, ifa->ifa_addr);
-                if (sin6) {
-                    memcpy(&netdev->in6, &sin6->sin6_addr, sin6->sin6_len);
-                    netdev->cache_valid |= VALID_IN6;
-                    *in6 = netdev->in6;
-                    freeifaddrs(head);
-                    return 0;
-                }
-            }
-        }
-        return EADDRNOTAVAIL;
+    if (!(netdev->cache_valid & VALID_IN6)) {
+        netdev_get_addrs_list_flush();
     }
-    *in6 = netdev->in6;
-    return 0;
+    error = netdev_get_addrs(netdev_get_name(netdev_), addr, mask, n_cnt);
+    if (!error) {
+        netdev->cache_valid |= VALID_IN6;
+    }
+    return error;
 }
 
 #if defined(__NetBSD__)
@@ -1595,7 +1577,7 @@  netdev_bsd_update_flags(struct netdev *netdev_, enum netdev_flags off,
                                                      \
     netdev_bsd_get_in4,                              \
     netdev_bsd_set_in4,                              \
-    netdev_bsd_get_in6,                              \
+    netdev_bsd_get_addr_list,                        \
     NULL, /* add_router */                           \
     netdev_bsd_get_next_hop,                         \
     NULL, /* get_status */                           \
diff --git a/lib/netdev-dpdk.c b/lib/netdev-dpdk.c
index f402354..78e8f0b 100644
--- a/lib/netdev-dpdk.c
+++ b/lib/netdev-dpdk.c
@@ -2689,7 +2689,7 @@  static const struct dpdk_qos_ops egress_policer_ops = {
                                                               \
     NULL,                       /* get_in4 */                 \
     NULL,                       /* set_in4 */                 \
-    NULL,                       /* get_in6 */                 \
+    NULL,                       /* get_addr_list */           \
     NULL,                       /* add_router */              \
     NULL,                       /* get_next_hop */            \
     GET_STATUS,                                               \
diff --git a/lib/netdev-dummy.c b/lib/netdev-dummy.c
index ccd4a0a..9f8bed4 100644
--- a/lib/netdev-dummy.c
+++ b/lib/netdev-dummy.c
@@ -116,7 +116,7 @@  struct netdev_dummy {
     FILE *tx_pcap, *rxq_pcap OVS_GUARDED;
 
     struct in_addr address, netmask;
-    struct in6_addr ipv6;
+    struct in6_addr ipv6, ipv6_mask;
     struct ovs_list rxes OVS_GUARDED; /* List of child "netdev_rxq_dummy"s. */
 };
 
@@ -731,15 +731,50 @@  netdev_dummy_get_in4(const struct netdev *netdev_,
 }
 
 static int
-netdev_dummy_get_in6(const struct netdev *netdev_, struct in6_addr *in6)
+netdev_dummy_get_addr_list(const struct netdev *netdev_, struct in6_addr **paddr,
+                           struct in6_addr **pmask, int *n_addr)
 {
     struct netdev_dummy *netdev = netdev_dummy_cast(netdev_);
+    int cnt = 0, i = 0, err = 0;
+    struct in6_addr *addr, *mask;
 
     ovs_mutex_lock(&netdev->mutex);
-    *in6 = netdev->ipv6;
+    if (netdev->address.s_addr != INADDR_ANY) {
+        cnt++;
+    }
+
+    if (ipv6_addr_is_set(&netdev->ipv6)) {
+        cnt++;
+    }
+    if (!cnt) {
+        err = EADDRNOTAVAIL;
+        goto out;
+    }
+    addr = xmalloc(sizeof *addr * cnt);
+    mask = xmalloc(sizeof *mask * cnt);
+    if (netdev->address.s_addr != INADDR_ANY) {
+        in6_addr_set_mapped_ipv4(&addr[i], netdev->address.s_addr);
+        in6_addr_set_mapped_ipv4(&mask[i], netdev->netmask.s_addr);
+        i++;
+    }
+
+    if (ipv6_addr_is_set(&netdev->ipv6)) {
+        memcpy(&addr[i], &netdev->ipv6, sizeof *addr);
+        memcpy(&mask[i], &netdev->ipv6_mask, sizeof *mask);
+        i++;
+    }
+    if (paddr) {
+        *paddr = addr;
+        *pmask = mask;
+        *n_addr = cnt;
+    } else {
+        free(addr);
+        free(mask);
+    }
+out:
     ovs_mutex_unlock(&netdev->mutex);
 
-    return ipv6_addr_is_set(in6) ? 0 : EADDRNOTAVAIL;
+    return err;
 }
 
 static int
@@ -757,12 +792,14 @@  netdev_dummy_set_in4(struct netdev *netdev_, struct in_addr address,
 }
 
 static int
-netdev_dummy_set_in6(struct netdev *netdev_, struct in6_addr *in6)
+netdev_dummy_set_in6(struct netdev *netdev_, struct in6_addr *in6,
+                     struct in6_addr *mask)
 {
     struct netdev_dummy *netdev = netdev_dummy_cast(netdev_);
 
     ovs_mutex_lock(&netdev->mutex);
     netdev->ipv6 = *in6;
+    netdev->ipv6_mask = *mask;
     ovs_mutex_unlock(&netdev->mutex);
 
     return 0;
@@ -1244,7 +1281,7 @@  static const struct netdev_class dummy_class = {
 
     netdev_dummy_get_in4,       /* get_in4 */
     NULL,                       /* set_in4 */
-    netdev_dummy_get_in6,       /* get_in6 */
+    netdev_dummy_get_addr_list,
     NULL,                       /* add_router */
     NULL,                       /* get_next_hop */
     NULL,                       /* get_status */
@@ -1537,15 +1574,20 @@  netdev_dummy_ip6addr(struct unixctl_conn *conn, int argc OVS_UNUSED,
     struct netdev *netdev = netdev_from_name(argv[1]);
 
     if (netdev && is_dummy_class(netdev->netdev_class)) {
-        char ip6_s[IPV6_SCAN_LEN + 1];
         struct in6_addr ip6;
+        char *error;
+        uint32_t plen;
 
-        if (ovs_scan(argv[2], IPV6_SCAN_FMT, ip6_s) &&
-            inet_pton(AF_INET6, ip6_s, &ip6) == 1) {
-            netdev_dummy_set_in6(netdev, &ip6);
+        error = ipv6_parse_cidr(argv[2], &ip6, &plen);
+        if (!error) {
+            struct in6_addr mask;
+
+            mask = ipv6_create_mask(plen);
+            netdev_dummy_set_in6(netdev, &ip6, &mask);
             unixctl_command_reply(conn, "OK");
         } else {
-            unixctl_command_reply_error(conn, "Invalid parameters");
+            unixctl_command_reply_error(conn, error);
+            free(error);
         }
         netdev_close(netdev);
     } else {
diff --git a/lib/netdev-linux.c b/lib/netdev-linux.c
index 570677e..7068493 100644
--- a/lib/netdev-linux.c
+++ b/lib/netdev-linux.c
@@ -483,7 +483,6 @@  struct netdev_linux {
     int ifindex;
     struct eth_addr etheraddr;
     struct in_addr address, netmask;
-    struct in6_addr in6;
     int mtu;
     unsigned int ifi_flags;
     long long int carrier_resets;
@@ -727,6 +726,9 @@  netdev_linux_changed(struct netdev_linux *dev,
     dev->ifi_flags = ifi_flags;
 
     dev->cache_valid &= mask;
+    if (!(mask & (VALID_IN4 | VALID_IN6))) {
+        netdev_get_addrs_list_flush();
+    }
 }
 
 static void
@@ -2535,61 +2537,18 @@  netdev_linux_set_in4(struct netdev *netdev_, struct in_addr address,
     return error;
 }
 
-static bool
-parse_if_inet6_line(const char *line,
-                    struct in6_addr *in6, char ifname[16 + 1])
-{
-    uint8_t *s6 = in6->s6_addr;
-#define X8 "%2"SCNx8
-    return ovs_scan(line,
-                    " "X8 X8 X8 X8 X8 X8 X8 X8 X8 X8 X8 X8 X8 X8 X8 X8
-                    "%*x %*x %*x %*x %16s\n",
-                    &s6[0], &s6[1], &s6[2], &s6[3],
-                    &s6[4], &s6[5], &s6[6], &s6[7],
-                    &s6[8], &s6[9], &s6[10], &s6[11],
-                    &s6[12], &s6[13], &s6[14], &s6[15],
-                    ifname);
-}
-
 /* If 'netdev' has an assigned IPv6 address, sets '*in6' to that address.
  * Otherwise, sets '*in6' to 'in6addr_any' and returns the corresponding
  * error. */
 static int
-netdev_linux_get_in6(const struct netdev *netdev_, struct in6_addr *in6)
+netdev_linux_get_addr_list(const struct netdev *netdev_,
+                          struct in6_addr **addr, struct in6_addr **mask, int *n_cnt)
 {
     struct netdev_linux *netdev = netdev_linux_cast(netdev_);
     int error;
 
     ovs_mutex_lock(&netdev->mutex);
-    if (!(netdev->cache_valid & VALID_IN6)) {
-        FILE *file;
-        char line[128];
-
-        netdev->in6 = in6addr_any;
-        netdev->in6_error = EADDRNOTAVAIL;
-
-        file = fopen("/proc/net/if_inet6", "r");
-        if (file != NULL) {
-            const char *name = netdev_get_name(netdev_);
-            while (fgets(line, sizeof line, file)) {
-                struct in6_addr in6_tmp;
-                char ifname[16 + 1];
-                if (parse_if_inet6_line(line, &in6_tmp, ifname)
-                    && !strcmp(name, ifname))
-                {
-                    netdev->in6 = in6_tmp;
-                    netdev->in6_error = 0;
-                    break;
-                }
-            }
-            fclose(file);
-        } else {
-            netdev->in6_error = EOPNOTSUPP;
-        }
-        netdev->cache_valid |= VALID_IN6;
-    }
-    *in6 = netdev->in6;
-    error = netdev->in6_error;
+    error = netdev_get_addrs(netdev_get_name(netdev_), addr, mask, n_cnt);
     ovs_mutex_unlock(&netdev->mutex);
 
     return error;
@@ -2890,7 +2849,7 @@  netdev_linux_update_flags(struct netdev *netdev_, enum netdev_flags off,
                                                                 \
     netdev_linux_get_in4,                                       \
     netdev_linux_set_in4,                                       \
-    netdev_linux_get_in6,                                       \
+    netdev_linux_get_addr_list,                                 \
     netdev_linux_add_router,                                    \
     netdev_linux_get_next_hop,                                  \
     GET_STATUS,                                                 \
diff --git a/lib/netdev-provider.h b/lib/netdev-provider.h
index 1952a02..99d7f5d 100644
--- a/lib/netdev-provider.h
+++ b/lib/netdev-provider.h
@@ -636,7 +636,11 @@  struct netdev_class {
     int (*set_in4)(struct netdev *netdev, struct in_addr addr,
                    struct in_addr mask);
 
-    /* If 'netdev' has an assigned IPv6 address, sets '*in6' to that address.
+    /* Returns all assigned IP address to  'netdev' and returns 0.
+     * API allocates array of address and masks and set it to
+     * '*addr' and '*mask'.
+     * Otherwise, returns a positive errno value and sets '*addr', '*mask
+     * and '*n_addr' to NULL.
      *
      * The following error values have well-defined meanings:
      *
@@ -644,9 +648,9 @@  struct netdev_class {
      *
      *   - EOPNOTSUPP: No IPv6 network stack attached to 'netdev'.
      *
-     * This function may be set to null if it would always return EOPNOTSUPP
-     * anyhow. */
-    int (*get_in6)(const struct netdev *netdev, struct in6_addr *in6);
+     * 'addr' may be null, in which case the address itself is not reported. */
+    int (*get_addr_list)(const struct netdev *netdev, struct in6_addr **in,
+                         struct in6_addr **mask, int *n_in6);
 
     /* Adds 'router' as a default IP gateway for the TCP/IP stack that
      * corresponds to 'netdev'.
diff --git a/lib/netdev-vport.c b/lib/netdev-vport.c
index df6d8cf..0d1b899 100644
--- a/lib/netdev-vport.c
+++ b/lib/netdev-vport.c
@@ -319,7 +319,7 @@  tunnel_check_status_change__(struct netdev_vport *netdev)
 
     iface[0] = '\0';
     route = &netdev->tnl_cfg.ipv6_dst;
-    if (ovs_router_lookup(route, iface, &gw)) {
+    if (ovs_router_lookup(route, iface, NULL, &gw)) {
         struct netdev *egress_netdev;
 
         if (!netdev_open(iface, "system", &egress_netdev)) {
@@ -1530,7 +1530,7 @@  netdev_vport_range(struct unixctl_conn *conn, int argc,
                                                             \
     NULL,                       /* get_in4 */               \
     NULL,                       /* set_in4 */               \
-    NULL,                       /* get_in6 */               \
+    NULL,                       /* get_addr_list */         \
     NULL,                       /* add_router */            \
     NULL,                       /* get_next_hop */          \
     GET_STATUS,                                             \
diff --git a/lib/netdev.c b/lib/netdev.c
index 150f8d8..c6fadb4 100644
--- a/lib/netdev.c
+++ b/lib/netdev.c
@@ -24,6 +24,13 @@ 
 #include <string.h>
 #include <unistd.h>
 
+#ifndef _WIN32
+#include <ifaddrs.h>
+#include <net/if.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#endif
+
 #include "coverage.h"
 #include "dpif.h"
 #include "dp-packet.h"
@@ -45,6 +52,7 @@ 
 #include "svec.h"
 #include "openvswitch/vlog.h"
 #include "flow.h"
+#include "util.h"
 
 VLOG_DEFINE_THIS_MODULE(netdev);
 
@@ -1128,9 +1136,11 @@  netdev_get_status(const struct netdev *netdev, struct smap *smap)
             : EOPNOTSUPP);
 }
 
-/* If 'netdev' has an assigned IPv6 address, sets '*in6' to that address and
- * returns 0.  Otherwise, returns a positive errno value and sets '*in6' to
- * all-zero-bits (in6addr_any).
+/* Returns all assigned IP address to  'netdev' and returns 0.
+ * API allocates array of address and masks and set it to
+ * '*addr' and '*mask'.
+ * Otherwise, returns a positive errno value and sets '*addr', '*mask
+ * and '*n_addr' to NULL.
  *
  * The following error values have well-defined meanings:
  *
@@ -1138,20 +1148,21 @@  netdev_get_status(const struct netdev *netdev, struct smap *smap)
  *
  *   - EOPNOTSUPP: No IPv6 network stack attached to 'netdev'.
  *
- * 'in6' may be null, in which case the address itself is not reported. */
+ * 'addr' may be null, in which case the address itself is not reported. */
 int
-netdev_get_in6(const struct netdev *netdev, struct in6_addr *in6)
+netdev_get_addr_list(const struct netdev *netdev, struct in6_addr **addr,
+                     struct in6_addr **mask, int *n_addr)
 {
-    struct in6_addr dummy;
     int error;
 
-    error = (netdev->netdev_class->get_in6
-             ? netdev->netdev_class->get_in6(netdev,
-                    in6 ? in6 : &dummy)
-             : EOPNOTSUPP);
-    if (error && in6) {
-        memset(in6, 0, sizeof *in6);
+    error = (netdev->netdev_class->get_addr_list
+             ? netdev->netdev_class->get_addr_list(netdev, addr, mask, n_addr): EOPNOTSUPP);
+    if (error && addr) {
+        *addr = NULL;
+        *mask = NULL;
+        *n_addr = 0;
     }
+
     return error;
 }
 
@@ -1854,3 +1865,95 @@  netdev_get_change_seq(const struct netdev *netdev)
 {
     return netdev->change_seq;
 }
+
+#ifndef _WIN32
+/* This implementation is shared by Linux and BSD. */
+
+static struct ifaddrs *if_addr_list;
+static struct ovs_mutex if_addr_list_lock = OVS_MUTEX_INITIALIZER;
+
+void
+netdev_get_addrs_list_flush(void)
+{
+    ovs_mutex_lock(&if_addr_list_lock);
+    if (if_addr_list) {
+        freeifaddrs(if_addr_list);
+        if_addr_list = NULL;
+    }
+    ovs_mutex_unlock(&if_addr_list_lock);
+}
+
+int
+netdev_get_addrs(const char dev[], struct in6_addr **paddr,
+                 struct in6_addr **pmask, int *n_in)
+{
+    struct in6_addr *addr_array, *mask_array;
+    const struct ifaddrs *ifa;
+    int cnt = 0, i = 0;
+
+    ovs_mutex_lock(&if_addr_list_lock);
+    if (!if_addr_list) {
+        int err;
+
+        err = getifaddrs(&if_addr_list);
+        if (err) {
+            ovs_mutex_unlock(&if_addr_list_lock);
+            return -err;
+        }
+    }
+
+    for (ifa = if_addr_list; ifa; ifa = ifa->ifa_next) {
+        int family;
+
+        family = ifa->ifa_addr->sa_family;
+        if (family == AF_INET || family == AF_INET6) {
+            if (!strncmp(ifa->ifa_name, dev, IFNAMSIZ)) {
+                cnt++;
+            }
+        }
+    }
+
+    if (!cnt) {
+        ovs_mutex_unlock(&if_addr_list_lock);
+        return EADDRNOTAVAIL;
+    }
+    addr_array = xzalloc(sizeof *addr_array * cnt);
+    mask_array = xzalloc(sizeof *mask_array * cnt);
+    for (ifa = if_addr_list; ifa; ifa = ifa->ifa_next) {
+        int family;
+
+        if (strncmp(ifa->ifa_name, dev, IFNAMSIZ)) {
+            continue;
+        }
+
+        family = ifa->ifa_addr->sa_family;
+        if (family == AF_INET) {
+            const struct sockaddr_in *sin;
+
+            sin = ALIGNED_CAST(const struct sockaddr_in *, ifa->ifa_addr);
+            in6_addr_set_mapped_ipv4(&addr_array[i], sin->sin_addr.s_addr);
+            sin = (struct sockaddr_in *) &ifa->ifa_netmask;
+            in6_addr_set_mapped_ipv4(&mask_array[i], sin->sin_addr.s_addr);
+            i++;
+        } else if (family == AF_INET6) {
+            const struct sockaddr_in6 *sin6;
+
+            sin6 = ALIGNED_CAST(const struct sockaddr_in6 *, ifa->ifa_addr);
+            memcpy(&addr_array[i], &sin6->sin6_addr, sizeof *addr_array);
+            sin6 = (struct sockaddr_in6 *) &ifa->ifa_netmask;
+            memcpy(&mask_array[i], &sin6->sin6_addr, sizeof *mask_array);
+            i++;
+        }
+    }
+    ovs_mutex_unlock(&if_addr_list_lock);
+    if (paddr) {
+        *n_in = cnt;
+        *paddr = addr_array;
+        *pmask = mask_array;
+    } else {
+        free(addr_array);
+        free(mask_array);
+    }
+    return 0;
+}
+#endif
diff --git a/lib/netdev.h b/lib/netdev.h
index a81989e..1d2725d 100644
--- a/lib/netdev.h
+++ b/lib/netdev.h
@@ -256,7 +256,9 @@  int netdev_get_in4(const struct netdev *, struct in_addr *address,
                    struct in_addr *netmask);
 int netdev_set_in4(struct netdev *, struct in_addr addr, struct in_addr mask);
 int netdev_get_in4_by_name(const char *device_name, struct in_addr *in4);
-int netdev_get_in6(const struct netdev *, struct in6_addr *);
+int netdev_get_addr_list(const struct netdev *netdev, struct in6_addr **addr,
+                         struct in6_addr **mask, int *n_in6);
+
 int netdev_add_router(struct netdev *, struct in_addr router);
 int netdev_get_next_hop(const struct netdev *, const struct in_addr *host,
                         struct in_addr *next_hop, char **);
@@ -343,6 +345,12 @@  int netdev_dump_queue_stats(const struct netdev *,
 enum { NETDEV_MAX_BURST = 32 }; /* Maximum number packets in a batch. */
 extern struct seq *tnl_conf_seq;
 
+#ifndef _WIN32
+void netdev_get_addrs_list_flush(void);
+int netdev_get_addrs(const char dev[], struct in6_addr **paddr,
+                     struct in6_addr **pmask, int *n_in6);
+#endif
+
 #ifdef  __cplusplus
 }
 #endif
diff --git a/lib/ovs-router.c b/lib/ovs-router.c
index d416bdb..9eaa128 100644
--- a/lib/ovs-router.c
+++ b/lib/ovs-router.c
@@ -42,6 +42,9 @@ 
 #include "tnl-ports.h"
 #include "unixctl.h"
 #include "util.h"
+#include "unaligned.h"
+#include "unixctl.h"
+#include "util.h"
 
 static struct ovs_mutex mutex = OVS_MUTEX_INITIALIZER;
 static struct classifier cls;
@@ -51,6 +54,7 @@  struct ovs_router_entry {
     char output_bridge[IFNAMSIZ];
     struct in6_addr gw;
     struct in6_addr nw_addr;
+    struct in6_addr src_addr;
     uint8_t plen;
     uint8_t priority;
 };
@@ -67,7 +71,7 @@  ovs_router_entry_cast(const struct cls_rule *cr)
 
 bool
 ovs_router_lookup(const struct in6_addr *ip6_dst, char output_bridge[],
-                  struct in6_addr *gw)
+                  struct in6_addr *src, struct in6_addr *gw)
 {
     const struct cls_rule *cr;
     struct flow flow = {.ipv6_dst = *ip6_dst};
@@ -78,6 +82,9 @@  ovs_router_lookup(const struct in6_addr *ip6_dst, char output_bridge[],
 
         ovs_strlcpy(output_bridge, p->output_bridge, IFNAMSIZ);
         *gw = p->gw;
+        if (src) {
+            *src = p->src_addr;
+        }
         return true;
     }
     return false;
@@ -89,7 +96,7 @@  ovs_router_lookup4(ovs_be32 ip_dst, char output_bridge[], ovs_be32 *gw)
     struct in6_addr ip6_dst = in6_addr_mapped_ipv4(ip_dst);
     struct in6_addr gw6;
 
-    if (ovs_router_lookup(&ip6_dst, output_bridge, &gw6)) {
+    if (ovs_router_lookup(&ip6_dst, output_bridge, NULL, &gw6)) {
         *gw = in6_addr_get_mapped_ipv4(&gw6);
         return true;
     }
@@ -117,7 +124,48 @@  static void rt_init_match(struct match *match, const struct in6_addr *ip6_dst,
     match->wc.masks.ipv6_dst = mask;
 }
 
-static void
+static int
+get_src_addr(const struct in6_addr *ip6_dst,
+             const char output_bridge[], struct in6_addr *psrc)
+{
+    struct in6_addr *mask, *addr6;
+    int err, n_in6, i, max_plen = -1;
+    struct netdev *dev;
+
+    err = netdev_open(output_bridge, NULL, &dev);
+    if (err) {
+        return err;
+    }
+
+    err = netdev_get_addr_list(dev, &addr6, &mask, &n_in6);
+    if (err) {
+        goto out;
+    }
+
+    for (i = 0; i < n_in6; i++) {
+        struct in6_addr a1, a2;
+        int mask_bits;
+
+        a1 = ipv6_addr_bitand(ip6_dst, &mask[i]);
+        a2 = ipv6_addr_bitand(&addr6[i], &mask[i]);
+        mask_bits = bitmap_count1(ALIGNED_CAST(const unsigned long *, &mask[i]), 128);
+
+        if (!memcmp(&a1, &a2, sizeof (a1)) && mask_bits > max_plen) {
+            *psrc = addr6[i];
+            max_plen = mask_bits;
+        }
+    }
+    if (max_plen == -1) {
+        err = ENOENT;
+    }
+out:
+    free(addr6);
+    free(mask);
+    netdev_close(dev);
+    return err;
+}
+
+static int
 ovs_router_insert__(uint8_t priority, const struct in6_addr *ip6_dst,
                     uint8_t plen, const char output_bridge[],
                     const struct in6_addr *gw)
@@ -125,6 +173,7 @@  ovs_router_insert__(uint8_t priority, const struct in6_addr *ip6_dst,
     const struct cls_rule *cr;
     struct ovs_router_entry *p;
     struct match match;
+    int err;
 
     rt_init_match(&match, ip6_dst, plen);
 
@@ -136,6 +185,10 @@  ovs_router_insert__(uint8_t priority, const struct in6_addr *ip6_dst,
     p->nw_addr = match.flow.ipv6_dst;
     p->plen = plen;
     p->priority = priority;
+    err = get_src_addr(ip6_dst, output_bridge, &p->src_addr);
+    if (err) {
+        return err;
+    }
     /* Longest prefix matches first. */
     cls_rule_init(&p->cr, &match, priority);
 
@@ -149,6 +202,7 @@  ovs_router_insert__(uint8_t priority, const struct in6_addr *ip6_dst,
     }
     tnl_port_map_insert_ipdev(output_bridge);
     seq_change(tnl_conf_seq);
+    return 0;
 }
 
 void
@@ -226,6 +280,7 @@  ovs_router_add(struct unixctl_conn *conn, int argc,
     unsigned int plen;
     struct in6_addr ip6;
     struct in6_addr gw6;
+    int err;
 
     if (scan_ipv4_route(argv[1], &ip, &plen)) {
         ovs_be32 gw = 0;
@@ -246,8 +301,12 @@  ovs_router_add(struct unixctl_conn *conn, int argc,
         unixctl_command_reply_error(conn, "Invalid parameters");
         return;
     }
-    ovs_router_insert__(plen + 32, &ip6, plen, argv[2], &gw6);
-    unixctl_command_reply(conn, "OK");
+    err = ovs_router_insert__(plen + 32, &ip6, plen, argv[2], &gw6);
+    if (err) {
+        unixctl_command_reply(conn, "Error while inserting route.");
+    } else {
+        unixctl_command_reply(conn, "OK");
+    }
 }
 
 static void
@@ -298,6 +357,8 @@  ovs_router_show(struct unixctl_conn *conn, int argc OVS_UNUSED,
             ds_put_format(&ds, " GW ");
             ipv6_format_mapped(&rt->gw, &ds);
         }
+        ds_put_format(&ds, " SRC ");
+        ipv6_format_mapped(&rt->src_addr, &ds);
         ds_put_format(&ds, "\n");
     }
     unixctl_command_reply(conn, ds_cstr(&ds));
@@ -312,7 +373,7 @@  ovs_router_lookup_cmd(struct unixctl_conn *conn, int argc OVS_UNUSED,
     struct in6_addr ip6;
     unsigned int plen;
     char iface[IFNAMSIZ];
-    struct in6_addr gw;
+    struct in6_addr gw, src;
 
     if (scan_ipv4_route(argv[1], &ip, &plen) && plen == 32) {
         in6_addr_set_mapped_ipv4(&ip6, ip);
@@ -321,10 +382,12 @@  ovs_router_lookup_cmd(struct unixctl_conn *conn, int argc OVS_UNUSED,
         return;
     }
 
-    if (ovs_router_lookup(&ip6, iface, &gw)) {
+    if (ovs_router_lookup(&ip6, iface, &src, &gw)) {
         struct ds ds = DS_EMPTY_INITIALIZER;
+        ds_put_format(&ds, "src ");
+        ipv6_format_mapped(&src, &ds);
         ds_put_format(&ds, "gateway ");
-        ipv6_format_mapped(&ip6, &ds);
+        ipv6_format_mapped(&gw, &ds);
         ds_put_format(&ds, "\ndev %s\n", iface);
         unixctl_command_reply(conn, ds_cstr(&ds));
         ds_destroy(&ds);
diff --git a/lib/ovs-router.h b/lib/ovs-router.h
index c23d554..9ac61ce 100644
--- a/lib/ovs-router.h
+++ b/lib/ovs-router.h
@@ -26,7 +26,7 @@  extern "C" {
 #endif
 
 bool ovs_router_lookup(const struct in6_addr *ip_dst, char out_dev[],
-                        struct in6_addr *gw);
+                       struct in6_addr *src, struct in6_addr *gw);
 bool ovs_router_lookup4(ovs_be32 ip_dst, char out_dev[], ovs_be32 *gw);
 void ovs_router_init(void);
 void ovs_router_insert(const struct in6_addr *ip_dst, uint8_t plen,
diff --git a/lib/route-table.c b/lib/route-table.c
index 9dc2038..649b656 100644
--- a/lib/route-table.c
+++ b/lib/route-table.c
@@ -276,8 +276,6 @@  route_table_parse(struct ofpbuf *buf, struct route_table_msg *change)
                 change->rd.rta_gw = nl_attr_get_in6_addr(attrs[RTA_GATEWAY]);
             }
         }
-
-
     } else {
         VLOG_DBG_RL(&rl, "received unparseable rtnetlink route message");
     }
diff --git a/lib/tnl-ports.c b/lib/tnl-ports.c
index e7f2066..4f9ae9e 100644
--- a/lib/tnl-ports.c
+++ b/lib/tnl-ports.c
@@ -40,8 +40,8 @@  static struct classifier cls;   /* Tunnel ports. */
 struct ip_device {
     struct netdev *dev;
     struct eth_addr mac;
-    ovs_be32 addr4;
-    struct in6_addr addr6;
+    struct in6_addr *addr;
+    int n_addr;
     uint64_t change_seq;
     struct ovs_list node;
     char dev_name[IFNAMSIZ];
@@ -150,6 +150,20 @@  map_insert(odp_port_t port, struct eth_addr mac, struct in6_addr *addr,
     }
 }
 
+static void
+map_insert_ipdev__(struct ip_device *ip_dev, char dev_name[],
+                   odp_port_t port, ovs_be16 udp_port)
+{
+    if (ip_dev->n_addr) {
+        int i;
+
+        for (i = 0; i < ip_dev->n_addr; i++) {
+            map_insert(port, ip_dev->mac, &ip_dev->addr[i],
+                       udp_port, dev_name);
+        }
+    }
+}
+
 void
 tnl_port_map_insert(odp_port_t port,
                     ovs_be16 udp_port, const char dev_name[])
@@ -171,15 +185,7 @@  tnl_port_map_insert(odp_port_t port,
     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);
-        }
+        map_insert_ipdev__(ip_dev, p->dev_name, p->port, p->udp_port);
     }
 
 out:
@@ -210,6 +216,18 @@  map_delete(struct eth_addr mac, struct in6_addr *addr, ovs_be16 udp_port)
     tnl_port_unref(cr);
 }
 
+static void
+ipdev_map_delete(struct ip_device *ip_dev, ovs_be16 udp_port)
+{
+    if (ip_dev->n_addr) {
+        int i;
+
+        for (i = 0; i < ip_dev->n_addr; i++) {
+            map_delete(ip_dev->mac, &ip_dev->addr[i], udp_port);
+        }
+    }
+}
+
 void
 tnl_port_map_delete(ovs_be16 udp_port)
 {
@@ -230,13 +248,7 @@  tnl_port_map_delete(ovs_be16 udp_port)
         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);
-        }
+        ipdev_map_delete(ip_dev, p->udp_port);
     }
 
     free(p);
@@ -331,56 +343,64 @@  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);
-        }
+        map_insert_ipdev__(ip_dev, p->dev_name, p->port, p->udp_port);
     }
 }
 
 static void
-insert_ipdev(const char dev_name[])
+insert_ipdev__(struct netdev *dev,
+               struct in6_addr *addr, int n_addr)
 {
     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;
+        goto err;
     }
 
     ip_dev = xzalloc(sizeof *ip_dev);
-    ip_dev->dev = dev;
+    ip_dev->dev = netdev_ref(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;
+        goto err_free_ipdev;
     }
+    ip_dev->addr = addr;
+    ip_dev->n_addr = n_addr;
     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);
+    return;
+
+err_free_ipdev:
+    netdev_close(ip_dev->dev);
+    free(ip_dev);
+err:
+    free(addr);
+}
+
+static void
+insert_ipdev(const char dev_name[])
+{
+    struct in6_addr *addr, *mask;
+    struct netdev *dev;
+    int error, n_in6;
+
+    error = netdev_open(dev_name, NULL, &dev);
+    if (error) {
+        return;
+    }
+
+    error = netdev_get_addr_list(dev, &addr, &mask, &n_in6);
+    if (error) {
+        netdev_close(dev);
+        return;
+    }
+    free(mask);
+    insert_ipdev__(dev, addr, n_in6);
+    netdev_close(dev);
 }
 
 static void
@@ -389,17 +409,12 @@  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);
-        }
+        ipdev_map_delete(ip_dev, p->udp_port);
     }
 
     list_remove(&ip_dev->node);
     netdev_close(ip_dev->dev);
+    free(ip_dev->addr);
     free(ip_dev);
 }
 
@@ -417,7 +432,6 @@  tnl_port_map_insert_ipdev(const char dev_name[])
             }
             /* Address changed. */
             delete_ipdev(ip_dev);
-            break;
         }
     }
     insert_ipdev(dev_name);
diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
index e4692b3..903cd1a 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -2750,7 +2750,8 @@  process_special(struct xlate_ctx *ctx, const struct xport *xport)
 
 static int
 tnl_route_lookup_flow(const struct flow *oflow,
-                      struct in6_addr *ip, struct xport **out_port)
+                      struct in6_addr *ip, struct in6_addr *src,
+                      struct xport **out_port)
 {
     char out_dev[IFNAMSIZ];
     struct xbridge *xbridge;
@@ -2759,7 +2760,7 @@  tnl_route_lookup_flow(const struct flow *oflow,
     struct in6_addr dst;
 
     dst = flow_tnl_dst(&oflow->tunnel);
-    if (!ovs_router_lookup(&dst, out_dev, &gw)) {
+    if (!ovs_router_lookup(&dst, out_dev, src, &gw)) {
         return -ENOENT;
     }
 
@@ -2850,7 +2851,7 @@  build_tunnel_send(struct xlate_ctx *ctx, const struct xport *xport,
     char buf_sip6[INET6_ADDRSTRLEN];
     char buf_dip6[INET6_ADDRSTRLEN];
 
-    err = tnl_route_lookup_flow(flow, &d_ip6, &out_dev);
+    err = tnl_route_lookup_flow(flow, &d_ip6, &s_ip6, &out_dev);
     if (err) {
         xlate_report(ctx, "native tunnel routing failed");
         return err;
@@ -2869,18 +2870,7 @@  build_tunnel_send(struct xlate_ctx *ctx, const struct xport *xport,
 
     d_ip = in6_addr_get_mapped_ipv4(&d_ip6);
     if (d_ip) {
-        err = netdev_get_in4(out_dev->netdev, (struct in_addr *) &s_ip, NULL);
-        if (err) {
-            xlate_report(ctx, "tunnel output device lacks IPv4 address");
-            return err;
-        }
-        in6_addr_set_mapped_ipv4(&s_ip6, s_ip);
-    } else {
-        err = netdev_get_in6(out_dev->netdev, &s_ip6);
-        if (err) {
-            xlate_report(ctx, "tunnel output device lacks IPv6 address");
-            return err;
-        }
+        s_ip = in6_addr_get_mapped_ipv4(&s_ip6);
     }
 
     err = tnl_neigh_lookup(out_dev->xbridge->name, &d_ip6, &dmac);
diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
index bf6661d..2d81711 100644
--- a/tests/ofproto-dpif.at
+++ b/tests/ofproto-dpif.at
@@ -5476,12 +5476,10 @@  dummy@ovs-dummy: hit:0 missed:0
 ])
 
 dnl set up route to 1.1.2.92 via br0 and action=normal
-AT_CHECK([ovs-appctl ovs/route/add 1.1.2.92/24 br0], [0], [OK
-])
-AT_CHECK([ovs-appctl ovs/route/add 192.168.0.0/16 br0], [0], [OK
-])
 AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 1.1.2.88/24], [0], [OK
 ])
+AT_CHECK([ovs-appctl ovs/route/add 1.1.2.92/24 br0], [0], [OK
+])
 AT_CHECK([ovs-ofctl add-flow br0 action=normal])
 
 dnl Prime ARP Cache for 1.1.2.92
@@ -5493,6 +5491,12 @@  ovs-vsctl \
    --id=@sf create sflow targets=\"127.0.0.1:$SFLOW_PORT\" \
      header=128 sampling=1 polling=0
 
+dnl set up route to 192.168.1.2 via br0
+AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 192.168.1.1/16], [0], [OK
+])
+AT_CHECK([ovs-appctl ovs/route/add 192.168.0.0/16 br0], [0], [OK
+])
+
 dnl add rule for int-br to force packet onto tunnel. There is no ifindex
 dnl for this port so the sFlow output will just report that it went to
 dnl 1 output (out_format=2, out_ifindex=1)
diff --git a/vswitchd/bridge.c b/vswitchd/bridge.c
index 25a0663..7113d3a 100644
--- a/vswitchd/bridge.c
+++ b/vswitchd/bridge.c
@@ -4916,8 +4916,7 @@  collect_splinter_vlans(const struct ovsrec_open_vswitch *ovs_cfg)
                 struct netdev *netdev;
 
                 if (!netdev_open(vlan_dev->name, "system", &netdev)) {
-                    if (!netdev_get_in4(netdev, NULL, NULL) ||
-                        !netdev_get_in6(netdev, NULL)) {
+                    if (!netdev_get_addr_list(netdev, NULL, NULL, NULL)) {
                         /* It has an IP address configured, so we don't own
                          * it.  Don't delete it. */
                     } else {