diff mbox

[ovs-dev,ovn-ipv6,14/26] ovn: Support multiple addresses on a single logical router port.

Message ID 1468306616-125783-15-git-send-email-jpettit@ovn.org
State Accepted
Headers show

Commit Message

Justin Pettit July 12, 2016, 6:56 a.m. UTC
Supporting multiple addresses is only mildly interesting for IPv4.
However, it is a requirement for IPv6, which will arrive in a future
commit.

This commit introduces the extract_lrp_networks() function to ovn-util.[ch].

Signed-off-by: Justin Pettit <jpettit@ovn.org>
---
 ovn/lib/ovn-util.c            | 152 +++++++++++++++------
 ovn/lib/ovn-util.h            |   4 +
 ovn/northd/ovn-northd.8.xml   |   9 +-
 ovn/northd/ovn-northd.c       | 303 +++++++++++++++++++++++++-----------------
 ovn/ovn-nb.ovsschema          |   8 +-
 ovn/ovn-nb.xml                |  12 +-
 ovn/utilities/ovn-nbctl.8.xml |  21 ++-
 ovn/utilities/ovn-nbctl.c     |  83 +++++++++---
 tests/ovn-nbctl.at            |  16 ++-
 tests/ovn.at                  | 160 +++++++++++++++++++---
 10 files changed, 543 insertions(+), 225 deletions(-)

Comments

Zong Kai LI July 12, 2016, 12:55 p.m. UTC | #1
> diff --git a/ovn/ovn-nb.ovsschema b/ovn/ovn-nb.ovsschema
> index 5102948..460d5bd 100644
> --- a/ovn/ovn-nb.ovsschema
> +++ b/ovn/ovn-nb.ovsschema
> @@ -1,7 +1,7 @@
>  {
>      "name": "OVN_Northbound",
> -    "version": "4.0.0",
> -    "cksum": "2156178478 7460",
> +    "version": "5.0.0",
> +    "cksum": "849073644 7576",
>      "tables": {
>          "Logical_Switch": {
>              "columns": {
> @@ -123,7 +123,9 @@
>          "Logical_Router_Port": {
>              "columns": {
>                  "name": {"type": "string"},
> -                "network": {"type": "string"},
> +                "networks": {"type": {"key": "string",
> +                                      "min": 1,
> +                                      "max": "unlimited"}},
>                  "mac": {"type": "string"},
>                  "peer": {"type": {"key": "string", "min": 0, "max": 1}},
>                  "enabled": {"type": {"key": "boolean", "min": 0, "max": 1}},
> diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml
> index 1eeec22..e571eeb 100644
> --- a/ovn/ovn-nb.xml
> +++ b/ovn/ovn-nb.xml
> @@ -794,11 +794,13 @@
>        </p>
>      </column>
>
> -    <column name="network">
> -      The IP address of the router and the netmask.  For example,
> -      <code>192.168.0.1/24</code> indicates that the router's IP address is
> -      192.168.0.1 and that packets destined to 192.168.0.<var>x</var> should be
> -      routed to this port.
> +    <column name="networks">
> +      <p>
> +        The IP addresses and netmasks of the router.  For example,
> +        <code>192.168.0.1/24</code> indicates that the router's IP
> +        address is 192.168.0.1 and that packets destined to
> +        192.168.0.<var>x</var> should be routed to this port.
> +      </p>
>      </column>
>

I just considered about this patch again in my afternoon. Let me share
my new idea about this.
My idea is, maybe we can directly parse a lsp.addresses for its
patched lrp when that lsp.type is "router".
This should be the simplest way to align lrp.network and lrp.mac to
lsp.addresses when lsp.type is "router".
We even don't need to change current OVN NB DB schema, and
extract_lsp_addresses can work for this.

A router could connect to either a switch or another router, and a
lsp.peer should either be empty or be set with another lsp.name.
So when a lrp has its nbr->peer been set, we should parse current
network and mac column for it. For peer router port still these to
build static route.
And when a lrp has empty nbr->peer, we can let lsp processing branch
in join_logical_ports to extract addresses for it.
And even, for op is lrp and op->peer->nbs is NULL case, we can
directly use ip, network, mask via op->peer, without extracting
addresses for lrp at first.

I can't figure why a patched lsp and lrp could have different
addresses, networks, masks, macs. So I think this should make sense.
:)

Thanks.
Zong Kai, LI
Justin Pettit July 13, 2016, 6:39 a.m. UTC | #2
> On Jul 12, 2016, at 8:44 PM, Ben Pfaff <blp@ovn.org> wrote:
> 
> On Mon, Jul 11, 2016 at 11:56:44PM -0700, Justin Pettit wrote:
>> Supporting multiple addresses is only mildly interesting for IPv4.
>> However, it is a requirement for IPv6, which will arrive in a future
>> commit.
>> 
>> This commit introduces the extract_lrp_networks() function to ovn-util.[ch].
>> 
>> Signed-off-by: Justin Pettit <jpettit@ovn.org>
> 
> "sparse" complains.  Here is the fix:
> 
> diff --git a/ovn/lib/ovn-util.c b/ovn/lib/ovn-util.c
> index 3e0c138..c3cb8e4 100644
> --- a/ovn/lib/ovn-util.c
> +++ b/ovn/lib/ovn-util.c
> @@ -20,7 +20,7 @@
> VLOG_DEFINE_THIS_MODULE(ovn_util);
> 
> static void
> -add_ipv4_netaddr(struct lport_addresses *laddrs, uint32_t addr,
> +add_ipv4_netaddr(struct lport_addresses *laddrs, ovs_be32 addr,
>                  unsigned int plen)
> {
>     laddrs->n_ipv4_addrs++;

Thanks for catching that.

> The documentation for the lrp-add command is confusing for a few
> reasons.  First, it talks about an optional argument "peer" that isn't
> in the synopsis.  I guess that this is a special case of the ability to
> set arbitrary key/value pairs, but this may not be obvious to the casual
> reader.  It's also somewhat weird that --may-exist verifies that this
> doesn't change but not other key-value pairs.  Second, it's probably not
> easy to a casual reader to tell how the command distinguishes "network"
> parameters from key-values.  An example might help.

That's fair.  I didn't really like any of the syntax that I came up for supporting multiple addresses.  I've changed the documentation to this:

              [--may-exist] lrp-add router port mac network... [peer=peer]
                     Creates  on  router  a new logical router port named port
                     with Ethernet address mac and one or more IP address/net‐
                     mask for each network.

                     The  optional  argument  peer identifies a logical router
                     port that connects to this one.   The  following  example
                     adds  a  router  port  with an IPv4 and IPv6 address with
                     peer lr1:

                     lrp-add   lr0   lrp0   00:11:22:33:44:55   192.168.0.1/24
                     2001:db8::1/64 peer=lr1

                     It  is  an  error  if  a  logical  router port named port
                     already exists, unless --may-exist is specified.  Regard‐
                     less  of  --may-exist,  it  is  an  error if the existing
                     router port is in some logical router other than router.

Let me know if you have a better suggestion.

> The usage message for ovn-nbctl mentions the peer=PEER special case but
> not the general case.  If that's intentional, I guess it's OK--a usage
> message cannot be complete, otherwise it would be a manpage.

Yes, it's intentional, since space is limited.  In general, I find the whole peer thing a little ugly, but I don't have a better suggestion at the moment.

> In ovn-nbctl, I think that the minimum valid number of arguments is
> really 4 here (since 3 will always yield an error):
> +    { "lrp-add", 3, INT_MAX,
> +      "ROUTER PORT MAC NETWORK... [COLUMN[:KEY]=VALUE]...",

Thanks for catching that.

> In add_ipv4_netaddr() and add_ipv6_netaddr(), usually we write "sizeof
> *object" instead of "sizeof(type)"

Done.

> In add_ipv6_netaddr(), it seems odd to bother xmallocing() a fixed size
> instead of just including an equivalent fixed-sized buffer in struct
> ipv6_netaddr.

As you suggested earlier, I've rewritten that functionality to not dynamically allocate memory for any of these address strings.

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

Thanks,

--Justin
diff mbox

Patch

diff --git a/ovn/lib/ovn-util.c b/ovn/lib/ovn-util.c
index f732b85..3e0c138 100644
--- a/ovn/lib/ovn-util.c
+++ b/ovn/lib/ovn-util.c
@@ -15,20 +15,59 @@ 
 #include <config.h>
 #include "ovn-util.h"
 #include "openvswitch/vlog.h"
-#include "ovn/lib/ovn-sb-idl.h"
+#include "ovn/lib/ovn-nb-idl.h"
 
 VLOG_DEFINE_THIS_MODULE(ovn_util);
 
-/*
- * Extracts the mac, ipv4 and ipv6 addresses from the input param 'address'
- * which should be of the format 'MAC [IP1 IP2 ..]" where IPn should be
- * a valid IPv4 or IPv6 address and stores them in the 'ipv4_addrs' and
- * 'ipv6_addrs' fields of input param 'laddrs'.
+static void
+add_ipv4_netaddr(struct lport_addresses *laddrs, uint32_t addr,
+                 unsigned int plen)
+{
+    laddrs->n_ipv4_addrs++;
+    laddrs->ipv4_addrs = xrealloc(laddrs->ipv4_addrs,
+        sizeof (struct ipv4_netaddr) * laddrs->n_ipv4_addrs);
+
+    struct ipv4_netaddr *na = &laddrs->ipv4_addrs[laddrs->n_ipv4_addrs - 1];
+
+    na->addr = addr;
+    na->mask = be32_prefix_mask(plen);
+    na->network = addr & na->mask;
+    na->plen = plen;
+
+    na->addr_s = xasprintf(IP_FMT, IP_ARGS(addr));
+    na->network_s = xasprintf(IP_FMT, IP_ARGS(na->network));
+    na->bcast_s = xasprintf(IP_FMT, IP_ARGS(addr | ~na->mask));
+}
+
+static void
+add_ipv6_netaddr(struct lport_addresses *laddrs, struct in6_addr addr,
+                 unsigned int plen)
+{
+    laddrs->n_ipv6_addrs++;
+    laddrs->ipv6_addrs = xrealloc(laddrs->ipv6_addrs,
+        sizeof(struct ipv6_netaddr) * laddrs->n_ipv6_addrs);
+
+    struct ipv6_netaddr *na = &laddrs->ipv6_addrs[laddrs->n_ipv6_addrs - 1];
+
+    memcpy(&na->addr, &addr, sizeof(struct in6_addr));
+    na->mask = ipv6_create_mask(plen);
+    na->network = ipv6_addr_bitand(&addr, &na->mask);
+    na->plen = plen;
+
+    na->addr_s = xmalloc(INET6_ADDRSTRLEN);
+    inet_ntop(AF_INET6, &addr, na->addr_s, INET6_ADDRSTRLEN);
+    na->network_s = xmalloc(INET6_ADDRSTRLEN);
+    inet_ntop(AF_INET6, &na->network, na->network_s, INET6_ADDRSTRLEN);
+}
+
+/* Extracts the mac, IPv4 and IPv6 addresses from * 'address' which
+ * should be of the format 'MAC [IP1 IP2 ..]" where IPn should be a
+ * valid IPv4 or IPv6 address and stores them in the 'ipv4_addrs' and
+ * 'ipv6_addrs' fields of 'laddrs'.
  *
  * Return true if at least 'MAC' is found in 'address', false otherwise.
  *
- * The caller must call destroy_lport_addresses().
- */
+ * The caller must call destroy_lport_addresses(). */
 bool
 extract_lsp_addresses(char *address, struct lport_addresses *laddrs)
 {
@@ -58,48 +97,15 @@  extract_lsp_addresses(char *address, struct lport_addresses *laddrs)
         buf_index = 0;
         error = ip_parse_cidr_len(buf, &buf_index, &ip4, &plen);
         if (!error) {
-            laddrs->n_ipv4_addrs++;
-            laddrs->ipv4_addrs = xrealloc(laddrs->ipv4_addrs,
-                sizeof (struct ipv4_netaddr) * laddrs->n_ipv4_addrs);
-
-            struct ipv4_netaddr *na
-                = &laddrs->ipv4_addrs[laddrs->n_ipv4_addrs - 1];
-
-            na->addr = ip4;
-            na->mask = be32_prefix_mask(plen);
-            na->network = ip4 & na->mask;
-            na->plen = plen;
-
-            na->addr_s = xasprintf(IP_FMT, IP_ARGS(ip4));
-            na->network_s = xasprintf(IP_FMT, IP_ARGS(na->network));
-            na->bcast_s = xasprintf(IP_FMT, IP_ARGS(ip4 | ~na->mask));
-
+            add_ipv4_netaddr(laddrs, ip4, plen);
             buf += buf_index;
             continue;
         }
         free(error);
         error = ipv6_parse_cidr_len(buf, &buf_index, &ip6, &plen);
         if (!error) {
-            laddrs->n_ipv6_addrs++;
-            laddrs->ipv6_addrs = xrealloc(
-                laddrs->ipv6_addrs,
-                sizeof(struct ipv6_netaddr) * laddrs->n_ipv6_addrs);
-
-            struct ipv6_netaddr *na
-                = &laddrs->ipv6_addrs[laddrs->n_ipv6_addrs - 1];
-
-            memcpy(&na->addr, &ip6, sizeof(struct in6_addr));
-            na->mask = ipv6_create_mask(plen);
-            na->network = ipv6_addr_bitand(&ip6, &na->mask);
-            na->plen = plen;
-
-            na->addr_s = xmalloc(INET6_ADDRSTRLEN);
-            inet_ntop(AF_INET6, &ip6, na->addr_s, INET6_ADDRSTRLEN);
-            na->network_s = xmalloc(INET6_ADDRSTRLEN);
-            inet_ntop(AF_INET6, &na->network, na->network_s, INET6_ADDRSTRLEN);
-        }
-
-        if (error) {
+            add_ipv6_netaddr(laddrs, ip6, plen);
+        } else {
             static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
             VLOG_INFO_RL(&rl, "invalid syntax '%s' in address", address);
             free(error);
@@ -111,6 +117,64 @@  extract_lsp_addresses(char *address, struct lport_addresses *laddrs)
     return true;
 }
 
+/* Extracts the mac, IPv4 and IPv6 addresses from the
+ * "nbrec_logical_router_port" parameter 'lrp'.  Stores the IPv4 and
+ * IPv6 addresses in the 'ipv4_addrs' and 'ipv6_addrs' fields of
+ * 'laddrs', respectively.
+ *
+ * Return true if at least 'MAC' is found in 'lrp', false otherwise.
+ *
+ * The caller must call destroy_lport_addresses(). */
+bool
+extract_lrp_networks(const struct nbrec_logical_router_port *lrp,
+                     struct lport_addresses *laddrs)
+{
+    memset(laddrs, 0, sizeof *laddrs);
+
+    if (!eth_addr_from_string(lrp->mac, &laddrs->ea)) {
+        laddrs->ea = eth_addr_zero;
+        return false;
+    }
+    laddrs->ea_s = xasprintf(ETH_ADDR_FMT, ETH_ADDR_ARGS(laddrs->ea));
+
+    for (int i = 0; i < lrp->n_networks; i++) {
+        ovs_be32 ip4;
+        struct in6_addr ip6;
+        unsigned int plen;
+        char *error;
+
+        error = ip_parse_cidr(lrp->networks[i], &ip4, &plen);
+        if (!error) {
+            if (!ip4 || plen == 32) {
+                static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
+                VLOG_WARN_RL(&rl, "bad 'networks' %s", lrp->networks[i]);
+                continue;
+            }
+
+            add_ipv4_netaddr(laddrs, ip4, plen);
+            continue;
+        }
+        free(error);
+
+        error = ipv6_parse_cidr(lrp->networks[i], &ip6, &plen);
+        if (!error) {
+            if (plen == 128) {
+                static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
+                VLOG_WARN_RL(&rl, "bad 'networks' %s", lrp->networks[i]);
+                continue;
+            }
+            add_ipv6_netaddr(laddrs, ip6, plen);
+        } else {
+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+            VLOG_INFO_RL(&rl, "invalid syntax '%s' in networks",
+                         lrp->networks[i]);
+            free(error);
+        }
+    }
+
+    return true;
+}
+
 void
 destroy_lport_addresses(struct lport_addresses *laddrs)
 {
diff --git a/ovn/lib/ovn-util.h b/ovn/lib/ovn-util.h
index 83bfbb3..b550ece 100644
--- a/ovn/lib/ovn-util.h
+++ b/ovn/lib/ovn-util.h
@@ -18,6 +18,8 @@ 
 
 #include "lib/packets.h"
 
+struct nbrec_logical_router_port;
+
 struct ipv4_netaddr {
     ovs_be32 addr;            /* 192.168.10.123 */
     ovs_be32 mask;            /* 255.255.255.0 */
@@ -50,6 +52,8 @@  struct lport_addresses {
 
 
 bool extract_lsp_addresses(char *address, struct lport_addresses *);
+bool extract_lrp_networks(const struct nbrec_logical_router_port *,
+                          struct lport_addresses *);
 void destroy_lport_addresses(struct lport_addresses *);
 
 char *alloc_nat_zone_key(const char *key, const char *type);
diff --git a/ovn/northd/ovn-northd.8.xml b/ovn/northd/ovn-northd.8.xml
index 6bc83ea..178a1a9 100644
--- a/ovn/northd/ovn-northd.8.xml
+++ b/ovn/northd/ovn-northd.8.xml
@@ -608,13 +608,11 @@  output;
           request).  The port of the router that receives the echo request
           does not matter. Also, the ip.ttl of the echo request packet is not
           checked, so it complies with RFC 1812, section 4.2.2.9. These flows
-          use the following actions where <var>S</var> is the router's IP
-          address:
+          use the following actions:
         </p>
 
         <pre>
-ip4.dst = ip4.src;
-ip4.src = <var>S</var>;
+ip4.dst &lt;-&gt; ip4.src;
 ip.ttl = 255;
 icmp4.type = 0;
 inport = ""; /* Allow sending out inport. */
@@ -891,6 +889,7 @@  reg0 = <var>G</var>;
 reg1 = <var>A</var>;
 eth.src = <var>E</var>;
 outport = <var>P</var>;
+inport = ""; /* Allow sending out inport. */
 next;
         </pre>
 
@@ -956,7 +955,7 @@  icmp4 {
           from the <code>addresses</code> column in the
           <code>Logical_Switch_Port</code> table.  For router ports
           connected to other logical routers, MAC bindings can be known
-          statically from the <code>mac</code> and <code>network</code>
+          statically from the <code>mac</code> and <code>networks</code>
           column in the <code>Logical_Router_Port</code> table.
         </p>
 
diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c
index 23e3532..b262e86 100644
--- a/ovn/northd/ovn-northd.c
+++ b/ovn/northd/ovn-northd.c
@@ -494,16 +494,8 @@  struct ovn_port {
     /* Logical router port data. */
     const struct nbrec_logical_router_port *nbr; /* May be NULL. */
 
-    char *ip_s;                 /* "192.168.10.123" */
-    char *network_s;            /* "192.168.10.0" */
-    char *bcast_s;              /* "192.168.10.255" */
-    int plen;                   /* CIDR prefix: 24 */
+    struct lport_addresses lrp_networks;
 
-    ovs_be32 ip;                /* 192.168.10.123 */
-    ovs_be32 mask;              /* 255.255.255.0 */
-    ovs_be32 network;           /* 192.168.10.255 */
-
-    struct eth_addr mac;
     struct ovn_port *peer;
 
     struct ovn_datapath *od;
@@ -550,9 +542,7 @@  ovn_port_destroy(struct hmap *ports, struct ovn_port *port)
         }
         free(port->ps_addrs);
 
-        free(port->bcast_s);
-        free(port->network_s);
-        free(port->ip_s);
+        destroy_lport_addresses(&port->lrp_networks);
         free(port->json_key);
         free(port->key);
         free(port);
@@ -661,24 +651,17 @@  join_logical_ports(struct northd_context *ctx,
             }
         } else {
             for (size_t i = 0; i < od->nbr->n_ports; i++) {
-                const struct nbrec_logical_router_port *nbr
-                    = od->nbr->ports[i];
+                const struct nbrec_logical_router_port *nbr = od->nbr->ports[i];
 
-                struct eth_addr mac;
-                if (!eth_addr_from_string(nbr->mac, &mac)) {
+                struct lport_addresses lrp_networks;
+                if (!extract_lrp_networks(nbr, &lrp_networks)) {
                     static struct vlog_rate_limit rl
                         = VLOG_RATE_LIMIT_INIT(5, 1);
                     VLOG_WARN_RL(&rl, "bad 'mac' %s", nbr->mac);
                     continue;
                 }
 
-                ovs_be32 ip, mask;
-                char *error = ip_parse_masked(nbr->network, &ip, &mask);
-                if (error || mask == OVS_BE32_MAX || !ip_is_cidr(mask)) {
-                    static struct vlog_rate_limit rl
-                        = VLOG_RATE_LIMIT_INIT(5, 1);
-                    VLOG_WARN_RL(&rl, "bad 'network' %s", nbr->network);
-                    free(error);
+                if (!lrp_networks.n_ipv4_addrs && !lrp_networks.n_ipv6_addrs) {
                     continue;
                 }
 
@@ -694,21 +677,17 @@  join_logical_ports(struct northd_context *ctx,
                     op->nbr = nbr;
                     ovs_list_remove(&op->list);
                     ovs_list_push_back(both, &op->list);
+
+                    /* This port exists but should not have been
+                     * initialized fully. */
+                    ovs_assert(!op->lrp_networks.n_ipv4_addrs
+                               && !op->lrp_networks.n_ipv6_addrs);
                 } else {
                     op = ovn_port_create(ports, nbr->name, NULL, nbr, NULL);
                     ovs_list_push_back(nb_only, &op->list);
                 }
 
-                op->ip = ip;
-                op->mask = mask;
-                op->network = ip & mask;
-                op->plen = ip_count_cidr_bits(mask);
-
-                op->ip_s = xasprintf(IP_FMT, IP_ARGS(ip));
-                op->network_s = xasprintf(IP_FMT, IP_ARGS(op->network));
-                op->bcast_s = xasprintf(IP_FMT, IP_ARGS(ip | ~mask));
-
-                op->mac = mac;
+                op->lrp_networks = lrp_networks;
                 op->od = od;
             }
         }
@@ -2051,9 +2030,40 @@  lrport_is_enabled(const struct nbrec_logical_router_port *lrport)
     return !lrport->enabled || *lrport->enabled;
 }
 
+/* Returns a string of the IP address of the router port 'op' that
+ * overlaps with 'ip_s".  If one is not found, returns NULL.
+ *
+ * The caller must not free the returned string. */
+static const char *
+find_lrp_member_ip(const struct ovn_port *op, const char *ip_s)
+{
+    uint32_t ip;
+
+    if (!ip_parse(ip_s, &ip)) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
+        VLOG_WARN_RL(&rl, "bad ip address %s", ip_s);
+        return NULL;
+    }
+
+    for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
+        const struct ipv4_netaddr *na = &op->lrp_networks.ipv4_addrs[i];
+
+        if (!((na->network ^ ip) & na->mask)) {
+            /* There should be only 1 interface that matches the
+             * next hop.  Otherwise, it's a configuration error,
+             * because subnets of router's interfaces should NOT
+             * overlap. */
+            return na->addr_s;
+        }
+    }
+
+    return NULL;
+}
+
 static void
 add_route(struct hmap *lflows, const struct ovn_port *op,
-          const char *network_s, int plen, const char *gateway)
+          const char *lrp_addr_s, const char *network_s, int plen,
+          const char *gateway)
 {
     char *match = xasprintf("ip4.dst == %s/%d", network_s, plen);
 
@@ -2064,13 +2074,15 @@  add_route(struct hmap *lflows, const struct ovn_port *op,
     } else {
         ds_put_cstr(&actions, "ip4.dst");
     }
-    ds_put_format(&actions,
-                  "; "
+    ds_put_format(&actions, "; "
                   "reg1 = %s; "
-                  "eth.src = "ETH_ADDR_FMT"; "
+                  "eth.src = %s; "
                   "outport = %s; "
+                  "inport = \"\"; /* Allow sending out inport. */ "
                   "next;",
-                  op->ip_s, ETH_ADDR_ARGS(op->mac), op->json_key);
+                  lrp_addr_s,
+                  op->lrp_networks.ea_s,
+                  op->json_key);
 
     /* The priority here is calculated to implement longest-prefix-match
      * routing. */
@@ -2085,10 +2097,11 @@  build_static_route_flow(struct hmap *lflows, struct ovn_datapath *od,
                         struct hmap *ports,
                         const struct nbrec_logical_router_static_route *route)
 {
-    ovs_be32 prefix, next_hop, mask;
+    ovs_be32 prefix, nexthop, mask;
+    const char *lrp_addr_s;
 
     /* Verify that next hop is an IP address with 32 bits mask. */
-    char *error = ip_parse_masked(route->nexthop, &next_hop, &mask);
+    char *error = ip_parse_masked(route->nexthop, &nexthop, &mask);
     if (error || mask != OVS_BE32_MAX) {
         static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
         VLOG_WARN_RL(&rl, "bad next hop ip address %s", route->nexthop);
@@ -2116,6 +2129,7 @@  build_static_route_flow(struct hmap *lflows, struct ovn_datapath *od,
                          route->output_port, route->ip_prefix);
             return;
         }
+        lrp_addr_s = find_lrp_member_ip(out_port, route->nexthop);
     } else {
         /* output_port is not specified, find the
          * router port matching the next hop. */
@@ -2128,30 +2142,48 @@  build_static_route_flow(struct hmap *lflows, struct ovn_datapath *od,
                 continue;
             }
 
-            if (out_port->network
-                && !((out_port->network ^ next_hop) & out_port->mask)) {
-                /* There should be only 1 interface that matches the next hop.
-                 * Otherwise, it's a configuration error, because subnets of
-                 * router's interfaces should NOT overlap. */
+            lrp_addr_s = find_lrp_member_ip(out_port, route->nexthop);
+            if (lrp_addr_s) {
                 break;
             }
         }
-        if (i == od->nbr->n_ports) {
-            /* There is no matched out port. */
-            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-            VLOG_WARN_RL(&rl, "No path for static route %s; next hop %s",
-                         route->ip_prefix, route->nexthop);
-            return;
-        }
     }
 
-    char *prefix_s = xasprintf(IP_FMT, IP_ARGS(prefix));
-    add_route(lflows, out_port, prefix_s, ip_count_cidr_bits(mask),
-              route->nexthop);
+     if (!lrp_addr_s) {
+        /* There is no matched out port. */
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
+        VLOG_WARN_RL(&rl, "No path for static route %s; next hop %s",
+                     route->ip_prefix, route->nexthop);
+        return;
+    }
+
+    char *prefix_s = xasprintf(IP_FMT, IP_ARGS(prefix & mask));
+    add_route(lflows, out_port, lrp_addr_s, prefix_s,
+              ip_count_cidr_bits(mask), route->nexthop);
     free(prefix_s);
 }
 
 static void
+op_put_networks(struct ds *ds, const struct ovn_port *op, bool add_bcast)
+{
+    if (!add_bcast && op->lrp_networks.n_ipv4_addrs == 1) {
+        ds_put_format(ds, "%s", op->lrp_networks.ipv4_addrs[0].addr_s);
+        return;
+    }
+
+    ds_put_cstr(ds, "{");
+    for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
+        ds_put_format(ds, "%s, ", op->lrp_networks.ipv4_addrs[i].addr_s);
+        if (add_bcast) {
+            ds_put_format(ds, "%s, ", op->lrp_networks.ipv4_addrs[i].bcast_s);
+        }
+    }
+    ds_chomp(ds, ' ');
+    ds_chomp(ds, ',');
+    ds_put_cstr(ds, "}");
+}
+
+static void
 build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
                     struct hmap *lflows)
 {
@@ -2188,9 +2220,8 @@  build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
         }
 
         ds_clear(&match);
-        ds_put_format(&match,
-            "(eth.mcast || eth.dst == "ETH_ADDR_FMT") && inport == %s",
-            ETH_ADDR_ARGS(op->mac), op->json_key);
+        ds_put_format(&match, "(eth.mcast || eth.dst == %s) && inport == %s",
+                      op->lrp_networks.ea_s, op->json_key);
         ovn_lflow_add(lflows, op->od, S_ROUTER_IN_ADMISSION, 50,
                       ds_cstr(&match), "next;");
     }
@@ -2249,7 +2280,8 @@  build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
          * owned by the router or a broadcast address known to the router
          * (priority 100). */
         ds_clear(&match);
-        ds_put_format(&match, "ip4.src == {%s, %s}", op->ip_s, op->bcast_s);
+        ds_put_cstr(&match, "ip4.src == ");
+        op_put_networks(&match, op, true);
         ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 100,
                       ds_cstr(&match), "drop;");
 
@@ -2259,44 +2291,47 @@  build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
          * (i.e. the incoming locally attached net) does not matter.
          * The ip.ttl also does not matter (RFC1812 section 4.2.2.9) */
         ds_clear(&match);
-        ds_put_format(&match,
-                      "ip4.dst == %s && icmp4.type == 8 && icmp4.code == 0",
-                      op->ip_s);
+        ds_put_cstr(&match, "ip4.dst == ");
+        op_put_networks(&match, op, false);
+        ds_put_cstr(&match, " && icmp4.type == 8 && icmp4.code == 0");
+
         ds_clear(&actions);
         ds_put_format(&actions,
-            "ip4.dst = ip4.src; "
-            "ip4.src = %s; "
+            "ip4.dst <-> ip4.src; "
             "ip.ttl = 255; "
             "icmp4.type = 0; "
             "inport = \"\"; /* Allow sending out inport. */ "
-            "next; ",
-            op->ip_s);
+            "next; ");
         ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90,
                       ds_cstr(&match), ds_cstr(&actions));
 
         /* ARP reply.  These flows reply to ARP requests for the router's own
          * IP address. */
-        ds_clear(&match);
-        ds_put_format(&match, "inport == %s && arp.tpa == %s && arp.op == 1",
-                      op->json_key, op->ip_s);
-        ds_clear(&actions);
-        ds_put_format(&actions,
-            "eth.dst = eth.src; "
-            "eth.src = "ETH_ADDR_FMT"; "
-            "arp.op = 2; /* ARP reply */ "
-            "arp.tha = arp.sha; "
-            "arp.sha = "ETH_ADDR_FMT"; "
-            "arp.tpa = arp.spa; "
-            "arp.spa = %s; "
-            "outport = %s; "
-            "inport = \"\"; /* Allow sending out inport. */ "
-            "output;",
-            ETH_ADDR_ARGS(op->mac),
-            ETH_ADDR_ARGS(op->mac),
-            op->ip_s,
-            op->json_key);
-        ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90,
-                      ds_cstr(&match), ds_cstr(&actions));
+        for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
+            ds_clear(&match);
+            ds_put_format(&match,
+                          "inport == %s && arp.tpa == %s && arp.op == 1",
+                          op->json_key, op->lrp_networks.ipv4_addrs[i].addr_s);
+
+            ds_clear(&actions);
+            ds_put_format(&actions,
+                "eth.dst = eth.src; "
+                "eth.src = %s; "
+                "arp.op = 2; /* ARP reply */ "
+                "arp.tha = arp.sha; "
+                "arp.sha = %s; "
+                "arp.tpa = arp.spa; "
+                "arp.spa = %s; "
+                "outport = %s; "
+                "inport = \"\"; /* Allow sending out inport. */ "
+                "output;",
+                op->lrp_networks.ea_s,
+                op->lrp_networks.ea_s,
+                op->lrp_networks.ipv4_addrs[i].addr_s,
+                op->json_key);
+            ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90,
+                          ds_cstr(&match), ds_cstr(&actions));
+        }
 
         /* ARP handling for external IP addresses.
          *
@@ -2323,20 +2358,21 @@  build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
             ds_put_format(&match,
                           "inport == %s && arp.tpa == "IP_FMT" && arp.op == 1",
                           op->json_key, IP_ARGS(ip));
+
             ds_clear(&actions);
             ds_put_format(&actions,
                 "eth.dst = eth.src; "
-                "eth.src = "ETH_ADDR_FMT"; "
+                "eth.src = %s; "
                 "arp.op = 2; /* ARP reply */ "
                 "arp.tha = arp.sha; "
-                "arp.sha = "ETH_ADDR_FMT"; "
+                "arp.sha = %s; "
                 "arp.tpa = arp.spa; "
                 "arp.spa = "IP_FMT"; "
                 "outport = %s; "
                 "inport = \"\"; /* Allow sending out inport. */ "
                 "output;",
-                ETH_ADDR_ARGS(op->mac),
-                ETH_ADDR_ARGS(op->mac),
+                op->lrp_networks.ea_s,
+                op->lrp_networks.ea_s,
                 IP_ARGS(ip),
                 op->json_key);
             ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90,
@@ -2345,7 +2381,8 @@  build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
 
         /* Drop IP traffic to this router, unless the router ip is used as
          * SNAT ip. */
-        bool snat_ip_is_router_ip = false;
+        ovs_be32 *nat_ips = xmalloc(sizeof *nat_ips * op->od->nbr->n_nat);
+        size_t n_nat_ips = 0;
         for (int i = 0; i < op->od->nbr->n_nat; i++) {
             const struct nbrec_nat *nat;
             ovs_be32 ip;
@@ -2362,18 +2399,33 @@  build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
                 continue;
             }
 
-            if (ip == op->ip) {
-                snat_ip_is_router_ip = true;
-                break;
+            nat_ips[n_nat_ips++] = ip;
+        }
+
+        ds_clear(&match);
+        ds_put_cstr(&match, "ip4.dst == {");
+        bool has_drop_ips = false;
+        for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
+            for (int j = 0; j < n_nat_ips; j++) {
+                if (op->lrp_networks.ipv4_addrs[i].addr == nat_ips[j]) {
+                    continue;
+                }
             }
+            ds_put_format(&match, "%s, ",
+                          op->lrp_networks.ipv4_addrs[i].addr_s);
+            has_drop_ips = true;
         }
+        ds_chomp(&match, ' ');
+        ds_chomp(&match, ',');
+        ds_put_cstr(&match, "}");
 
-        if (!snat_ip_is_router_ip) {
-            ds_clear(&match);
-            ds_put_format(&match, "ip4.dst == %s", op->ip_s);
+        if (has_drop_ips) {
+            /* Drop IP traffic to this router. */
             ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 60,
                           ds_cstr(&match), "drop;");
         }
+
+        free(nat_ips);
     }
 
     /* NAT in Gateway routers. */
@@ -2515,8 +2567,13 @@  build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
             continue;
         }
 
-        add_route(lflows, op, op->network_s, op->plen, NULL);
+        for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
+            add_route(lflows, op, op->lrp_networks.ipv4_addrs[i].addr_s,
+                      op->lrp_networks.ipv4_addrs[i].network_s,
+                      op->lrp_networks.ipv4_addrs[i].plen, NULL);
+        }
     }
+
     HMAP_FOR_EACH (od, key_node, datapaths) {
         if (!od->nbr) {
             continue;
@@ -2553,15 +2610,14 @@  build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
                     continue;
                 }
 
-                if (!peer->ip || !op->ip) {
-                    continue;
-                }
                 ds_clear(&match);
-                ds_put_format(&match, "outport == %s && reg0 == %s",
-                              peer->json_key, op->ip_s);
+                ds_put_format(&match, "outport == %s && reg0 == ",
+                              peer->json_key);
+                op_put_networks(&match, op, false);
+
                 ds_clear(&actions);
-                ds_put_format(&actions, "eth.dst = "ETH_ADDR_FMT"; next;",
-                              ETH_ADDR_ARGS(op->mac));
+                ds_put_format(&actions, "eth.dst = %s; next;",
+                              op->lrp_networks.ea_s);
                 ovn_lflow_add(lflows, peer->od, S_ROUTER_IN_ARP_RESOLVE,
                               100, ds_cstr(&match), ds_cstr(&actions));
             }
@@ -2573,8 +2629,9 @@  build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
              * router port, add an ARP entry in that router's pipeline. */
 
             for (size_t i = 0; i < op->n_lsp_addrs; i++) {
+                const char *ea_s = op->lsp_addrs[i].ea_s;
                 for (size_t j = 0; j < op->lsp_addrs[i].n_ipv4_addrs; j++) {
-                    ovs_be32 ip = op->lsp_addrs[i].ipv4_addrs[j].addr;
+                    const char *ip_s = op->lsp_addrs[i].ipv4_addrs[j].addr_s;
                     for (size_t k = 0; k < op->od->n_router_ports; k++) {
                         /* Get the Logical_Router_Port that the
                          * Logical_Switch_Port is connected to, as
@@ -2591,22 +2648,19 @@  build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
                             continue;
                         }
 
-                        /* Make sure that 'ip' is in 'peer''s network. */
-                        if ((ip ^ peer->network) & peer->mask) {
+                        if (!find_lrp_member_ip(peer, ip_s)) {
                             continue;
                         }
 
                         ds_clear(&match);
                         ds_put_format(&match, "outport == %s && reg0 == %s",
-                                      peer->json_key,
-                                      op->lsp_addrs[i].ipv4_addrs[j].addr_s);
+                                      peer->json_key, ip_s);
+
                         ds_clear(&actions);
-                        ds_put_format(&actions, "eth.dst = %s; next;",
-                                      op->lsp_addrs[i].ea_s);
+                        ds_put_format(&actions, "eth.dst = %s; next;", ea_s);
                         ovn_lflow_add(lflows, peer->od,
                                       S_ROUTER_IN_ARP_RESOLVE, 100,
                                       ds_cstr(&match), ds_cstr(&actions));
-                        break;
                     }
                 }
             }
@@ -2625,17 +2679,17 @@  build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
             }
 
             struct ovn_port *peer = ovn_port_find(ports, peer_name);
-            if (!peer || !peer->nbr || !peer->ip) {
+            if (!peer || !peer->nbr) {
                 continue;
             }
 
-            for (size_t j = 0; j < op->od->n_router_ports; j++) {
+            for (size_t i = 0; i < op->od->n_router_ports; i++) {
                 const char *router_port_name = smap_get(
-                                    &op->od->router_ports[j]->nbs->options,
+                                    &op->od->router_ports[i]->nbs->options,
                                     "router-port");
                 struct ovn_port *router_port = ovn_port_find(ports,
                                                              router_port_name);
-                if (!router_port || !router_port->nbr || !router_port->ip) {
+                if (!router_port || !router_port->nbr) {
                     continue;
                 }
 
@@ -2644,15 +2698,14 @@  build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
                    continue;
                 }
 
-                if (!router_port->ip) {
-                    continue;
-                }
                 ds_clear(&match);
-                ds_put_format(&match, "outport == %s && reg0 == %s",
-                              peer->json_key, router_port->ip_s);
+                ds_put_format(&match, "outport == %s && reg0 == ",
+                              peer->json_key);
+                op_put_networks(&match, router_port, false);
+
                 ds_clear(&actions);
-                ds_put_format(&actions, "eth.dst = "ETH_ADDR_FMT"; next;",
-                              ETH_ADDR_ARGS(router_port->mac));
+                ds_put_format(&actions, "eth.dst = %s; next;",
+                              router_port->lrp_networks.ea_s);
                 ovn_lflow_add(lflows, peer->od, S_ROUTER_IN_ARP_RESOLVE,
                               100, ds_cstr(&match), ds_cstr(&actions));
             }
diff --git a/ovn/ovn-nb.ovsschema b/ovn/ovn-nb.ovsschema
index 5102948..460d5bd 100644
--- a/ovn/ovn-nb.ovsschema
+++ b/ovn/ovn-nb.ovsschema
@@ -1,7 +1,7 @@ 
 {
     "name": "OVN_Northbound",
-    "version": "4.0.0",
-    "cksum": "2156178478 7460",
+    "version": "5.0.0",
+    "cksum": "849073644 7576",
     "tables": {
         "Logical_Switch": {
             "columns": {
@@ -123,7 +123,9 @@ 
         "Logical_Router_Port": {
             "columns": {
                 "name": {"type": "string"},
-                "network": {"type": "string"},
+                "networks": {"type": {"key": "string",
+                                      "min": 1,
+                                      "max": "unlimited"}},
                 "mac": {"type": "string"},
                 "peer": {"type": {"key": "string", "min": 0, "max": 1}},
                 "enabled": {"type": {"key": "boolean", "min": 0, "max": 1}},
diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml
index 1eeec22..e571eeb 100644
--- a/ovn/ovn-nb.xml
+++ b/ovn/ovn-nb.xml
@@ -794,11 +794,13 @@ 
       </p>
     </column>
 
-    <column name="network">
-      The IP address of the router and the netmask.  For example,
-      <code>192.168.0.1/24</code> indicates that the router's IP address is
-      192.168.0.1 and that packets destined to 192.168.0.<var>x</var> should be
-      routed to this port.
+    <column name="networks">
+      <p>
+        The IP addresses and netmasks of the router.  For example,
+        <code>192.168.0.1/24</code> indicates that the router's IP
+        address is 192.168.0.1 and that packets destined to
+        192.168.0.<var>x</var> should be routed to this port.
+      </p>
     </column>
 
     <column name="mac">
diff --git a/ovn/utilities/ovn-nbctl.8.xml b/ovn/utilities/ovn-nbctl.8.xml
index b4b8501..1eed7fa 100644
--- a/ovn/utilities/ovn-nbctl.8.xml
+++ b/ovn/utilities/ovn-nbctl.8.xml
@@ -290,14 +290,25 @@ 
     <h1>Logical Router Port Commands</h1>
 
     <dl>
-      <dt>[<code>--may-exist</code>] <code>lrp-add</code> <var>router</var> <var>port</var> <var>mac</var> <var>network</var> [<var>peer</var>]</dt>
+      <dt>[<code>--may-exist</code>] <code>lrp-add</code> <var>router</var> <var>port</var> <var>mac</var> <var>network</var>... [<var>column</var>[<code>:</code><var>key</var>]<code>=</code><var>value</var>]...</dt>
       <dd>
         <p>
           Creates on <var>router</var> a new logical router port named
-          <var>port</var> with Ethernet address <var>mac</var> and IP
-          address/netmask <var>network</var>.  If <var>peer</var> is
-          specified, it identifies a logical router port that connects
-          to this one.
+          <var>port</var> with Ethernet address <var>mac</var> and one
+          or more IP address/netmask for each <var>network</var>.
+        </p>
+
+        <p>
+          Optional arguments set values of column in the <ref
+          table="Logical_Router_Port"/> record created by the command.
+          The syntax is the same as that for the <code>set</code>
+          command (see <code>Database Commands</code> below).
+        </p>
+
+        <p>
+          The optional argument <code>peer</code> identifies a logical router
+          port that connects to this one.  For example, <code>peer=lr1</code>
+          would set the peer router port to be <code>lr1</code>.
         </p>
 
         <p>
diff --git a/ovn/utilities/ovn-nbctl.c b/ovn/utilities/ovn-nbctl.c
index ad70a05..3115e80 100644
--- a/ovn/utilities/ovn-nbctl.c
+++ b/ovn/utilities/ovn-nbctl.c
@@ -29,6 +29,7 @@ 
 #include "poll-loop.h"
 #include "process.h"
 #include "smap.h"
+#include "sset.h"
 #include "stream.h"
 #include "stream-ssl.h"
 #include "svec.h"
@@ -340,7 +341,7 @@  Logical router commands:\n\
   lr-list                   print the names of all logical routers\n\
 \n\
 Logical router port commands:\n\
-  lrp-add ROUTER PORT MAC NETWORK [PEER]\n\
+  lrp-add ROUTER PORT MAC NETWORK... [peer=PEER]\n\
                             add logical port PORT on ROUTER\n\
   lrp-del PORT              delete PORT from its attached router\n\
   lrp-list ROUTER           print the names of all ports on ROUTER\n\
@@ -1527,8 +1528,22 @@  nbctl_lrp_add(struct ctl_context *ctx)
 
     const char *lrp_name = ctx->argv[2];
     const char *mac = ctx->argv[3];
-    const char *network = ctx->argv[4];
-    const char *peer = (ctx->argc == 6) ? ctx->argv[5] : NULL;
+    const char **networks = (const char **) &ctx->argv[4];
+
+    int n_networks = ctx->argc - 4;
+    for (int i = 4; i < ctx->argc; i++) {
+        if (strchr(ctx->argv[i], '=')) {
+            n_networks = i - 4;
+            break;
+        }
+    }
+
+    if (!n_networks) {
+        ctl_fatal("%s: router port requires specifying a network", lrp_name);
+    }
+
+    char **settings = (char **) &ctx->argv[n_networks + 4];
+    int n_settings = ctx->argc - 4 - n_networks;
 
     const struct nbrec_logical_router_port *lrp;
     lrp = lrp_by_name_or_uuid(ctx, lrp_name, false);
@@ -1551,9 +1566,29 @@  nbctl_lrp_add(struct ctl_context *ctx)
                       lrp->mac);
         }
 
-        if (strcmp(network, lrp->network)) {
-            ctl_fatal("%s: port already exists with network %s", lrp_name,
-                      lrp->network);
+        struct sset new_networks = SSET_INITIALIZER(&new_networks);
+        for (int i = 0; i < n_networks; i++) {
+            sset_add(&new_networks, networks[i]);
+        }
+
+        struct sset orig_networks = SSET_INITIALIZER(&orig_networks);
+        sset_add_array(&orig_networks, lrp->networks, lrp->n_networks);
+
+        if (!sset_equals(&orig_networks, &new_networks)) {
+            ctl_fatal("%s: port already exists with different network",
+                      lrp_name);
+        }
+
+        sset_destroy(&orig_networks);
+        sset_destroy(&new_networks);
+
+        /* Special-case sanity-check of peer ports. */
+        const char *peer = NULL;
+        for (int i = 0; i < n_settings; i++) {
+            if (!strncmp(settings[i], "peer=", 5)) {
+                peer = settings[i] + 5;
+                break;
+            }
         }
 
         if ((!peer != !lrp->peer) ||
@@ -1567,19 +1602,22 @@  nbctl_lrp_add(struct ctl_context *ctx)
 
     struct eth_addr ea;
     if (!ovs_scan(mac, ETH_ADDR_SCAN_FMT, ETH_ADDR_SCAN_ARGS(ea))) {
-        ctl_fatal("%s: invalid mac address.", mac);
+        ctl_fatal("%s: invalid mac address %s", lrp_name, mac);
     }
 
-    ovs_be32 ipv4;
-    unsigned int plen;
-    char *error = ip_parse_cidr(network, &ipv4, &plen);
-    if (error) {
-        free(error);
-        struct in6_addr ipv6;
-        error = ipv6_parse_cidr(network, &ipv6, &plen);
+    for (int i = 0; i < n_networks; i++) {
+        ovs_be32 ipv4;
+        unsigned int plen;
+        char *error = ip_parse_cidr(networks[i], &ipv4, &plen);
         if (error) {
             free(error);
-            ctl_fatal("%s: invalid network address.", network);
+            struct in6_addr ipv6;
+            error = ipv6_parse_cidr(networks[i], &ipv6, &plen);
+            if (error) {
+                free(error);
+                ctl_fatal("%s: invalid network address: %s", lrp_name,
+                          networks[i]);
+            }
         }
     }
 
@@ -1587,9 +1625,11 @@  nbctl_lrp_add(struct ctl_context *ctx)
     lrp = nbrec_logical_router_port_insert(ctx->txn);
     nbrec_logical_router_port_set_name(lrp, lrp_name);
     nbrec_logical_router_port_set_mac(lrp, mac);
-    nbrec_logical_router_port_set_network(lrp, network);
-    if (peer) {
-        nbrec_logical_router_port_set_peer(lrp, peer);
+    nbrec_logical_router_port_set_networks(lrp, networks, n_networks);
+
+    for (int i = 0; i < n_settings; i++) {
+        ctl_set_column("Logical_Router_Port", &lrp->header_, settings[i],
+                       ctx->symtab);
     }
 
     /* Insert the logical port into the logical router. */
@@ -2133,9 +2173,10 @@  static const struct ctl_command_syntax nbctl_commands[] = {
     { "lr-list", 0, 0, "", NULL, nbctl_lr_list, NULL, "", RO },
 
     /* logical router port commands. */
-    { "lrp-add", 4, 5, "ROUTER PORT MAC NETWORK [PEER]", NULL, nbctl_lrp_add,
-      NULL, "--may-exist", RW },
-    { "lrp-del", 1, 1, "LPORT", NULL, nbctl_lrp_del, NULL, "--if-exists", RW },
+    { "lrp-add", 3, INT_MAX,
+      "ROUTER PORT MAC NETWORK... [COLUMN[:KEY]=VALUE]...",
+      NULL, nbctl_lrp_add, NULL, "--may-exist", RW },
+    { "lrp-del", 1, 1, "PORT", NULL, nbctl_lrp_del, NULL, "--if-exists", RW },
     { "lrp-list", 1, 1, "ROUTER", NULL, nbctl_lrp_list, NULL, "", RO },
     { "lrp-set-enabled", 2, 2, "PORT STATE", NULL, nbctl_lrp_set_enabled,
       NULL, "", RW },
diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at
index 0c756ed..c666022 100644
--- a/tests/ovn-nbctl.at
+++ b/tests/ovn-nbctl.at
@@ -305,7 +305,7 @@  AT_CHECK([ovn-nbctl lrp-list lr0 | ${PERL} $srcdir/uuidfilt.pl], [0], [dnl
 <0> (lrp0)
 ])
 
-AT_CHECK([ovn-nbctl lrp-add lr0 lrp1 00:00:00:01:02:03 192.168.1.1/24 lrp1-peer])
+AT_CHECK([ovn-nbctl lrp-add lr0 lrp1 00:00:00:01:02:03 192.168.1.1/24 peer=lrp1-peer])
 AT_CHECK([ovn-nbctl lrp-list lr0 | ${PERL} $srcdir/uuidfilt.pl], [0], [dnl
 <0> (lrp0)
 <1> (lrp1)
@@ -328,13 +328,25 @@  AT_CHECK([ovn-nbctl --may-exist lrp-add lr0 lrp1 00:00:00:01:02:03 192.168.1.1/2
   [ovn-nbctl: lrp1: port already exists with mismatching peer
 ])
 
-AT_CHECK([ovn-nbctl --may-exist lrp-add lr0 lrp1 00:00:00:01:02:03 192.168.1.1/24 lrp1-peer])
+AT_CHECK([ovn-nbctl --may-exist lrp-add lr0 lrp1 00:00:00:01:02:03 10.0.0.1/24 peer=lrp1-peer], [1], [],
+  [ovn-nbctl: lrp1: port already exists with different network
+])
+
+AT_CHECK([ovn-nbctl --may-exist lrp-add lr0 lrp1 00:00:00:01:02:03 192.168.1.1/24 peer=lrp1-peer])
 
 AT_CHECK([ovn-nbctl lrp-del lrp1])
 AT_CHECK([ovn-nbctl lrp-list lr0 | ${PERL} $srcdir/uuidfilt.pl], [0], [dnl
 <0> (lrp0)
 ])
 
+AT_CHECK([ovn-nbctl --may-exist lrp-add lr0 lrp1 00:00:00:01:02:03 192.168.1.1/24 10.0.0.1/24 peer=lrp1-peer])
+
+AT_CHECK([ovn-nbctl --may-exist lrp-add lr0 lrp1 00:00:00:01:02:03 192.168.1.1/24 172.16.0.1/24 peer=lrp1-peer], [1], [],
+  [ovn-nbctl: lrp1: port already exists with different network
+])
+
+AT_CHECK([ovn-nbctl --may-exist lrp-add lr0 lrp1 00:00:00:01:02:03 10.0.0.1/24 192.168.1.1/24 peer=lrp1-peer])
+
 OVN_NBCTL_TEST_STOP
 AT_CLEANUP
 
diff --git a/tests/ovn.at b/tests/ovn.at
index fdac600..12de125 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -1508,8 +1508,8 @@  done
 ovn-nbctl lr-add lr0
 for i in 1 2 3; do
     for j in 1 2 3; do
-        ovn-nbctl lrp-add lr0 lrp$i$j 00:00:00:00:ff:$i$j \
-            192.168.$i$j.254/24 lrp$i$j-attachment
+        ovn-nbctl lrp-add lr0 lrp$i$j 00:00:00:00:ff:$i$j 192.168.$i$j.254/24 \
+            peer=lrp$i$j-attachment
         ovn-nbctl \
             -- lsp-add ls$i lrp$i$j-attachment \
             -- set Logical_Switch_Port lrp$i$j-attachment type=router \
@@ -2305,20 +2305,20 @@  ovn-nbctl ls-add ls1
 ovn-nbctl ls-add ls2
 
 # Connect ls1 to R1
-ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:03 192.168.1.1/24 rp-ls1
+ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:03 192.168.1.1/24 peer=rp-ls1
 
 ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 type=router \
   options:router-port=ls1 addresses=\"00:00:00:01:02:03\"
 
 # Connect ls2 to R2
-ovn-nbctl lrp-add R2 ls2 00:00:00:01:02:04 172.16.1.1/24 rp-ls2
+ovn-nbctl lrp-add R2 ls2 00:00:00:01:02:04 172.16.1.1/24 peer=rp-ls2
 
 ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 type=router \
   options:router-port=ls2 addresses=\"00:00:00:01:02:04\"
 
 # Connect R1 to R2
-ovn-nbctl lrp-add R1 R1_R2 00:00:00:02:03:04 20.0.0.1/24 R2_R1
-ovn-nbctl lrp-add R2 R2_R1 00:00:00:02:03:05 20.0.0.2/24 R1_R2
+ovn-nbctl lrp-add R1 R1_R2 00:00:00:02:03:04 20.0.0.1/24 peer=R2_R1
+ovn-nbctl lrp-add R2 R2_R1 00:00:00:02:03:05 20.0.0.2/24 peer=R1_R2
 
 ovn-nbctl lr-route-add R1 "0.0.0.0/0" 20.0.0.2
 ovn-nbctl lr-route-add R2 "0.0.0.0/0" 20.0.0.1
@@ -2416,7 +2416,138 @@  OVN_CLEANUP([hv1],[hv2])
 AT_CLEANUP
 
 
-AT_SETUP([ovn -- 1 HVs, 2 LSs, 1 lport/LS, 1 LR])
+AT_SETUP([ovn -- 1 HV, 1 LS, 2 lport/LS, 1 LR])
+AT_KEYWORDS([router-admin-state])
+AT_SKIP_IF([test $HAVE_PYTHON = no])
+ovn_start
+
+# Logical network:
+# One LR - R1 has switch ls1 with two subnets attached to it (191.168.1.0/24
+# and 172.16.1.0/24) connected to it.
+
+ovn-nbctl lr-add R1
+
+ovn-nbctl ls-add ls1
+
+# Connect ls1 to R1
+ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:03 192.168.1.1/24 172.16.1.1/24 \
+          peer=rp-ls1
+ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 type=router \
+          options:router-port=ls1 addresses=\"00:00:00:01:02:03\"
+
+# Create logical port ls1-lp1 in ls1
+ovn-nbctl lsp-add ls1 ls1-lp1 \
+          -- lsp-set-addresses ls1-lp1 "f0:00:00:01:02:03 192.168.1.2"
+
+# Create logical port ls1-lp2 in ls1
+ovn-nbctl lsp-add ls1 ls1-lp2 \
+          -- lsp-set-addresses ls1-lp2 "f0:00:00:01:02:04 172.16.1.2"
+
+# Create one hypervisor and create OVS ports corresponding to logical ports.
+net_add n1
+
+sim_add hv1
+as hv1
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.1
+ovs-vsctl -- add-port br-int vif1 -- \
+    set interface vif1 external-ids:iface-id=ls1-lp1 \
+    options:tx_pcap=hv1/vif1-tx.pcap \
+    options:rxq_pcap=hv1/vif1-rx.pcap \
+    ofport-request=1
+
+ovs-vsctl -- add-port br-int vif2 -- \
+    set interface vif2 external-ids:iface-id=ls1-lp2 \
+    options:tx_pcap=hv1/vif2-tx.pcap \
+    options:rxq_pcap=hv1/vif2-rx.pcap \
+    ofport-request=1
+
+
+# Allow some time for ovn-northd and ovn-controller to catch up.
+# XXX This should be more systematic.
+sleep 1
+
+# Send ip packets between the two ports.
+ip_to_hex() {
+    printf "%02x%02x%02x%02x" "$@"
+}
+trim_zeros() {
+    sed 's/\(00\)\{1,\}$//'
+}
+
+# Packet to send.
+src_mac="f00000010203"
+dst_mac="000000010203"
+src_ip=`ip_to_hex 192 168 1 2`
+dst_ip=`ip_to_hex 172 16 1 2`
+packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
+as hv1 ovs-appctl netdev-dummy/receive vif1 $packet
+
+
+echo "---------NB dump-----"
+ovn-nbctl show
+echo "---------------------"
+ovn-nbctl list logical_router
+echo "---------------------"
+ovn-nbctl list logical_router_port
+echo "---------------------"
+
+echo "---------SB dump-----"
+ovn-sbctl list datapath_binding
+echo "---------------------"
+ovn-sbctl list logical_flow
+echo "---------------------"
+
+echo "------ hv1 dump ----------"
+as hv1 ovs-ofctl dump-flows br-int
+
+
+#Disable router R1
+ovn-nbctl set Logical_Router R1 enabled=false
+
+echo "---------SB dump-----"
+ovn-sbctl list datapath_binding
+echo "---------------------"
+ovn-sbctl list logical_flow
+echo "---------------------"
+
+echo "------ hv1 dump ----------"
+as hv1 ovs-ofctl dump-flows br-int
+
+as hv1 ovs-appctl netdev-dummy/receive vif1 $packet
+
+# Packet to Expect
+expect_src_mac="000000010203"
+expect_dst_mac="f00000010204"
+expected=${expect_dst_mac}${expect_src_mac}08004500001c000000003f110100${src_ip}${dst_ip}0035111100080000
+
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap | trim_zeros > received.packets
+echo $expected | trim_zeros > expout
+AT_CHECK([cat received.packets], [0], [expout])
+
+
+as hv1
+OVS_APP_EXIT_AND_WAIT([ovn-controller])
+OVS_APP_EXIT_AND_WAIT([ovs-vswitchd])
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as ovn-sb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as ovn-nb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as northd
+OVS_APP_EXIT_AND_WAIT([ovn-northd])
+
+as main
+OVS_APP_EXIT_AND_WAIT([ovs-vswitchd])
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+AT_CLEANUP
+
+
+AT_SETUP([ovn -- 1 HV, 2 LSs, 1 lport/LS, 1 LR])
 AT_KEYWORDS([router-admin-state])
 AT_SKIP_IF([test $HAVE_PYTHON = no])
 ovn_start
@@ -2431,12 +2562,12 @@  ovn-nbctl ls-add ls1
 ovn-nbctl ls-add ls2
 
 # Connect ls1 to R1
-ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:03 192.168.1.1/24 rp-ls1
+ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:03 192.168.1.1/24 peer=rp-ls1
 ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 type=router \
           options:router-port=ls1 addresses=\"00:00:00:01:02:03\"
 
 # Connect ls2 to R1
-ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:04 172.16.1.1/24 rp-ls2
+ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:04 172.16.1.1/24 peer=rp-ls2
 ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 type=router \
           options:router-port=ls2 addresses=\"00:00:00:01:02:04\"
 
@@ -2506,7 +2637,6 @@  echo "---------------------"
 echo "------ hv1 dump ----------"
 as hv1 ovs-ofctl dump-flows br-int
 
-
 #Disable router R1
 ovn-nbctl set Logical_Router R1 enabled=false
 
@@ -2558,23 +2688,23 @@  ovn-nbctl ls-add alice
 ovn-nbctl ls-add bob
 
 # Connect foo to R1
-ovn-nbctl lrp-add R1 foo 00:00:00:01:02:03 192.168.1.1/24 rp-foo
+ovn-nbctl lrp-add R1 foo 00:00:00:01:02:03 192.168.1.1/24 peer=rp-foo
 ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo type=router \
           options:router-port=foo addresses=\"00:00:00:01:02:03\"
 
 # Connect alice to R2
-ovn-nbctl lrp-add R2 alice 00:00:00:01:02:04 172.16.1.1/24 rp-alice
+ovn-nbctl lrp-add R2 alice 00:00:00:01:02:04 172.16.1.1/24 peer=rp-alice
 ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \
           type=router options:router-port=alice addresses=\"00:00:00:01:02:04\"
 
 # Connect bob to R2
-ovn-nbctl lrp-add R2 bob 00:00:00:01:02:05 172.16.2.1/24 rp-bob
+ovn-nbctl lrp-add R2 bob 00:00:00:01:02:05 172.16.2.1/24 peer=rp-bob
 ovn-nbctl lsp-add bob rp-bob -- set Logical_Switch_Port rp-bob type=router \
           options:router-port=bob addresses=\"00:00:00:01:02:05\"
 
 # Connect R1 to R2
-ovn-nbctl lrp-add R1 R1_R2 00:00:00:02:03:04 20.0.0.1/24 R2_R1
-ovn-nbctl lrp-add R2 R2_R1 00:00:00:02:03:05 20.0.0.2/24 R1_R2
+ovn-nbctl lrp-add R1 R1_R2 00:00:00:02:03:04 20.0.0.1/24 peer=R2_R1
+ovn-nbctl lrp-add R2 R2_R1 00:00:00:02:03:05 20.0.0.2/24 peer=R1_R2
 
 #install static routes
 ovn-nbctl lr-route-add R1 172.16.1.0/24 20.0.0.2