ovn-northd: Allow lport 'addresses' to store multiple ips in each set
authorNuman Siddique <nusiddiq@redhat.com>
Mon, 22 Feb 2016 10:29:37 +0000 (15:59 +0530)
committerBen Pfaff <blp@ovn.org>
Thu, 25 Feb 2016 22:22:11 +0000 (14:22 -0800)
If a logical port has two ipv4 addresses and one ipv6 address
it will be stored as ["MAC IPv41 IPv42 IPv61"] instead of
["MAC IPv41", "MAC IPv42", "MAC IPv61"].

Signed-off-by: Numan Siddique <nusiddiq@redhat.com>
[blp@ovn.org made changes to comments and ovn.at]
Signed-off-by: Ben Pfaff <blp@ovn.org>
lib/packets.c
lib/packets.h
ovn/northd/ovn-northd.c
ovn/ovn-nb.xml
tests/ovn.at

index d82341d..daca1b3 100644 (file)
@@ -425,29 +425,29 @@ ip_parse(const char *s, ovs_be32 *ip)
 }
 
 /* Parses string 's', which must be an IP address with an optional netmask or
- * CIDR prefix length.  Stores the IP address into '*ip' and the netmask into
- * '*mask'.  (If 's' does not contain a netmask, 255.255.255.255 is
- * assumed.)
+ * CIDR prefix length.  Stores the IP address into '*ip', netmask into '*mask',
+ * (255.255.255.255, if 's' lacks a netmask), and number of scanned characters
+ * into '*n'.
  *
  * Returns NULL if successful, otherwise an error message that the caller must
  * free(). */
 char * OVS_WARN_UNUSED_RESULT
-ip_parse_masked(const char *s, ovs_be32 *ip, ovs_be32 *mask)
+ip_parse_masked_len(const char *s, int *n, ovs_be32 *ip,
+                    ovs_be32 *mask)
 {
     int prefix;
-    int n;
 
-    if (ovs_scan(s, IP_SCAN_FMT"/"IP_SCAN_FMT"%n",
-                 IP_SCAN_ARGS(ip), IP_SCAN_ARGS(mask), &n) && !s[n]) {
+    if (ovs_scan_len(s, n, IP_SCAN_FMT"/"IP_SCAN_FMT,
+                 IP_SCAN_ARGS(ip), IP_SCAN_ARGS(mask))) {
         /* OK. */
-    } else if (ovs_scan(s, IP_SCAN_FMT"/%d%n", IP_SCAN_ARGS(ip), &prefix, &n)
-               && !s[n]) {
+    } else if (ovs_scan_len(s, n, IP_SCAN_FMT"/%d",
+                            IP_SCAN_ARGS(ip), &prefix)) {
         if (prefix <= 0 || prefix > 32) {
             return xasprintf("%s: network prefix bits not between 0 and "
                              "32", s);
         }
         *mask = be32_prefix_mask(prefix);
-    } else if (ip_parse(s, ip)) {
+    } else if (ovs_scan_len(s, n, IP_SCAN_FMT, IP_SCAN_ARGS(ip))) {
         *mask = OVS_BE32_MAX;
     } else {
         return xasprintf("%s: invalid IP address", s);
@@ -455,15 +455,33 @@ ip_parse_masked(const char *s, ovs_be32 *ip, ovs_be32 *mask)
     return NULL;
 }
 
-/* Similar to ip_parse_masked(), but the mask, if present, must be a CIDR mask
- * and is returned as a prefix length in '*plen'. */
+/* This function is similar to ip_parse_masked_len(), but doesn't return the
+ * number of scanned characters and expects 's' to end after the ip/(optional)
+ * mask.
+ *
+ * Returns NULL if successful, otherwise an error message that the caller must
+ * free(). */
 char * OVS_WARN_UNUSED_RESULT
-ip_parse_cidr(const char *s, ovs_be32 *ip, unsigned int *plen)
+ip_parse_masked(const char *s, ovs_be32 *ip, ovs_be32 *mask)
+{
+    int n = 0;
+
+    char *error = ip_parse_masked_len(s, &n, ip, mask);
+    if (!error && s[n]) {
+        return xasprintf("%s: invalid IP address", s);
+    }
+    return error;
+}
+
+/* Similar to ip_parse_masked_len(), but the mask, if present, must be a CIDR
+ * mask and is returned as a prefix len in '*plen'. */
+char * OVS_WARN_UNUSED_RESULT
+ip_parse_cidr_len(const char *s, int *n, ovs_be32 *ip, unsigned int *plen)
 {
     ovs_be32 mask;
     char *error;
 
-    error = ip_parse_masked(s, ip, &mask);
+    error = ip_parse_masked_len(s, n, ip, &mask);
     if (error) {
         return error;
     }
@@ -475,6 +493,21 @@ ip_parse_cidr(const char *s, ovs_be32 *ip, unsigned int *plen)
     return NULL;
 }
 
+/* Similar to ip_parse_cidr_len(), but doesn't return the number of scanned
+ * characters and expects 's' to be NULL terminated at the end of the
+ * ip/(optional) cidr. */
+char * OVS_WARN_UNUSED_RESULT
+ip_parse_cidr(const char *s, ovs_be32 *ip, unsigned int *plen)
+{
+    int n = 0;
+
+    char *error = ip_parse_cidr_len(s, &n, ip, plen);
+    if (!error && s[n]) {
+        return xasprintf("%s: invalid IP address", s);
+    }
+    return error;
+}
+
 /* Parses string 's', which must be an IPv6 address.  Stores the IPv6 address
  * into '*ip'.  Returns true if successful, otherwise false. */
 bool
@@ -485,49 +518,65 @@ ipv6_parse(const char *s, struct in6_addr *ip)
 
 /* Parses string 's', which must be an IPv6 address with an optional netmask or
  * CIDR prefix length.  Stores the IPv6 address into '*ip' and the netmask into
- * '*mask'.  (If 's' does not contain a netmask, all-one-bits is assumed.)
+ * '*mask' (if 's' does not contain a netmask, all-one-bits is assumed), and
+ * number of scanned characters into '*n'.
  *
  * Returns NULL if successful, otherwise an error message that the caller must
  * free(). */
 char * OVS_WARN_UNUSED_RESULT
-ipv6_parse_masked(const char *s, struct in6_addr *ip, struct in6_addr *mask)
+ipv6_parse_masked_len(const char *s, int *n, struct in6_addr *ip,
+                      struct in6_addr *mask)
 {
     char ipv6_s[IPV6_SCAN_LEN + 1];
     int prefix;
-    int n;
 
-    if (ovs_scan(s, IPV6_SCAN_FMT"%n", ipv6_s, &n) && ipv6_parse(ipv6_s, ip)) {
-        s += n;
-        if (!*s) {
-            *mask = in6addr_exact;
-        } else if (ovs_scan(s, "/%d%n", &prefix, &n) && !s[n]) {
+    if (ovs_scan_len(s, n, " "IPV6_SCAN_FMT, ipv6_s)
+        && ipv6_parse(ipv6_s, ip)) {
+        if (ovs_scan_len(s, n, "/%d", &prefix)) {
             if (prefix <= 0 || prefix > 128) {
                 return xasprintf("%s: IPv6 network prefix bits not between 0 "
                                  "and 128", s);
             }
             *mask = ipv6_create_mask(prefix);
-        } else if (ovs_scan(s, "/"IPV6_SCAN_FMT"%n", ipv6_s, &n)
-                   && !s[n]
-                   && ipv6_parse(ipv6_s, mask)) {
+        } else if (ovs_scan_len(s, n, "/"IPV6_SCAN_FMT, ipv6_s)) {
+             if (!ipv6_parse(ipv6_s, mask)) {
+                 return xasprintf("%s: Invalid IPv6 mask", s);
+             }
             /* OK. */
         } else {
-            return xasprintf("%s: syntax error expecting IPv6 prefix length "
-                             "or mask", s);
+            /* OK. No mask. */
+            *mask = in6addr_exact;
         }
         return NULL;
     }
     return xasprintf("%s: invalid IPv6 address", s);
 }
 
-/* Similar to ipv6_parse_masked(), but the mask, if present, must be a CIDR
+/* This function is similar to ipv6_parse_masked_len(), but doesn't return the
+ * number of scanned characters and expects 's' to end following the
+ * ipv6/(optional) mask. */
+char * OVS_WARN_UNUSED_RESULT
+ipv6_parse_masked(const char *s, struct in6_addr *ip, struct in6_addr *mask)
+{
+    int n = 0;
+
+    char *error = ipv6_parse_masked_len(s, &n, ip, mask);
+    if (!error && s[n]) {
+        return xasprintf("%s: invalid IPv6 address", s);
+    }
+    return error;
+}
+
+/* Similar to ipv6_parse_masked_len(), but the mask, if present, must be a CIDR
  * mask and is returned as a prefix length in '*plen'. */
 char * OVS_WARN_UNUSED_RESULT
-ipv6_parse_cidr(const char *s, struct in6_addr *ip, unsigned int *plen)
+ipv6_parse_cidr_len(const char *s, int *n, struct in6_addr *ip,
+                    unsigned int *plen)
 {
     struct in6_addr mask;
     char *error;
 
-    error = ipv6_parse_masked(s, ip, &mask);
+    error = ipv6_parse_masked_len(s, n, ip, &mask);
     if (error) {
         return error;
     }
@@ -539,6 +588,20 @@ ipv6_parse_cidr(const char *s, struct in6_addr *ip, unsigned int *plen)
     return NULL;
 }
 
+/* Similar to ipv6_parse_cidr_len(), but doesn't return the number of scanned
+ * characters and expects 's' to end after the ipv6/(optional) cidr. */
+char * OVS_WARN_UNUSED_RESULT
+ipv6_parse_cidr(const char *s, struct in6_addr *ip, unsigned int *plen)
+{
+    int n = 0;
+
+    char *error = ipv6_parse_cidr_len(s, &n, ip, plen);
+    if (!error && s[n]) {
+        return xasprintf("%s: invalid IPv6 address", s);
+    }
+    return error;
+}
+
 /* Stores the string representation of the IPv6 address 'addr' into the
  * character array 'addr_str', which must be at least INET6_ADDRSTRLEN
  * bytes long. */
index f1445de..bf12937 100644 (file)
@@ -594,6 +594,11 @@ char *ip_parse_masked(const char *s, ovs_be32 *ip, ovs_be32 *mask)
     OVS_WARN_UNUSED_RESULT;
 char *ip_parse_cidr(const char *s, ovs_be32 *ip, unsigned int *plen)
     OVS_WARN_UNUSED_RESULT;
+char *ip_parse_masked_len(const char *s, int *n, ovs_be32 *ip, ovs_be32 *mask)
+    OVS_WARN_UNUSED_RESULT;
+char *ip_parse_cidr_len(const char *s, int *n, ovs_be32 *ip,
+                        unsigned int *plen)
+    OVS_WARN_UNUSED_RESULT;
 
 #define IP_VER(ip_ihl_ver) ((ip_ihl_ver) >> 4)
 #define IP_IHL(ip_ihl_ver) ((ip_ihl_ver) & 15)
@@ -1041,6 +1046,11 @@ char *ipv6_parse_masked(const char *s, struct in6_addr *ipv6,
                         struct in6_addr *mask);
 char *ipv6_parse_cidr(const char *s, struct in6_addr *ip, unsigned int *plen)
     OVS_WARN_UNUSED_RESULT;
+char *ipv6_parse_masked_len(const char *s, int *n, struct in6_addr *ipv6,
+                            struct in6_addr *mask);
+char *ipv6_parse_cidr_len(const char *s, int *n, struct in6_addr *ip,
+                          unsigned int *plen)
+    OVS_WARN_UNUSED_RESULT;
 
 void *eth_compose(struct dp_packet *, const struct eth_addr eth_dst,
                   const struct eth_addr eth_src, uint16_t eth_type,
index e6271cf..b2b1a45 100644 (file)
@@ -914,6 +914,112 @@ ovn_lflow_destroy(struct hmap *lflows, struct ovn_lflow *lflow)
     }
 }
 
+struct ipv4_netaddr {
+    ovs_be32 addr;
+    unsigned int plen;
+};
+
+struct ipv6_netaddr {
+    struct in6_addr addr;
+    unsigned int plen;
+};
+
+struct lport_addresses {
+    struct eth_addr ea;
+    size_t n_ipv4_addrs;
+    struct ipv4_netaddr *ipv4_addrs;
+    size_t n_ipv6_addrs;
+    struct ipv6_netaddr *ipv6_addrs;
+};
+
+/*
+ * 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'.
+ * The caller has to free the 'ipv4_addrs' and 'ipv6_addrs' fields.
+ * If input param 'store_ipv6' is true only then extracted ipv6 addresses
+ * are stored in 'ipv6_addrs' fields.
+ * Return true if at least 'MAC' is found in 'address', false otherwise.
+ * Eg 1.
+ * If 'address' = '00:00:00:00:00:01 10.0.0.4 fe80::ea2a:eaff:fe28:3390/64
+ *                 30.0.0.3/23' and 'store_ipv6' = true
+ * then returns true with laddrs->n_ipv4_addrs = 2, naddrs->n_ipv6_addrs = 1.
+ *
+ * Eg. 2
+ * If 'address' = '00:00:00:00:00:01 10.0.0.4 fe80::ea2a:eaff:fe28:3390/64
+ *                 30.0.0.3/23' and 'store_ipv6' = false
+ * then returns true with laddrs->n_ipv4_addrs = 2, naddrs->n_ipv6_addrs = 0.
+ *
+ * Eg 3. If 'address' = '00:00:00:00:00:01 10.0.0.4 addr 30.0.0.4', then
+ * returns true with laddrs->n_ipv4_addrs = 1 and laddrs->n_ipv6_addrs = 0.
+ */
+static bool
+extract_lport_addresses(char *address, struct lport_addresses *laddrs,
+                        bool store_ipv6)
+{
+    char *buf = address;
+    int buf_index = 0;
+    char *buf_end = buf + strlen(address);
+    if (!ovs_scan_len(buf, &buf_index, ETH_ADDR_SCAN_FMT,
+                      ETH_ADDR_SCAN_ARGS(laddrs->ea))) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+        VLOG_INFO_RL(&rl, "invalid syntax '%s' in address. No MAC address"
+                     " found", address);
+        return false;
+    }
+
+    ovs_be32 ip4;
+    struct in6_addr ip6;
+    unsigned int plen;
+    char *error;
+
+    laddrs->n_ipv4_addrs = 0;
+    laddrs->n_ipv6_addrs = 0;
+    laddrs->ipv4_addrs = NULL;
+    laddrs->ipv6_addrs = NULL;
+
+    /* Loop through the buffer and extract the IPv4/IPv6 addresses
+     * and store in the 'laddrs'. Break the loop if invalid data is found.
+     */
+    buf += buf_index;
+    while (buf < buf_end) {
+        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);
+            laddrs->ipv4_addrs[laddrs->n_ipv4_addrs - 1].addr = ip4;
+            laddrs->ipv4_addrs[laddrs->n_ipv4_addrs - 1].plen = plen;
+            buf += buf_index;
+            continue;
+        }
+        free(error);
+        error = ipv6_parse_cidr_len(buf, &buf_index, &ip6, &plen);
+        if (!error && store_ipv6) {
+            laddrs->n_ipv6_addrs++;
+            laddrs->ipv6_addrs = xrealloc(
+                laddrs->ipv6_addrs,
+                sizeof(struct ipv6_netaddr) * laddrs->n_ipv6_addrs);
+            memcpy(&laddrs->ipv6_addrs[laddrs->n_ipv6_addrs - 1].addr, &ip6,
+                   sizeof(struct in6_addr));
+            laddrs->ipv6_addrs[laddrs->n_ipv6_addrs - 1].plen = plen;
+        }
+
+        if (error) {
+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+            VLOG_INFO_RL(&rl, "invalid syntax '%s' in address", address);
+            free(error);
+            break;
+        }
+        buf += buf_index;
+    }
+
+    return true;
+}
+
 /* Appends port security constraints on L2 address field 'eth_addr_field'
  * (e.g. "eth.src" or "eth.dst") to 'match'.  'port_security', with
  * 'n_port_security' elements, is the collection of port_security constraints
@@ -1194,14 +1300,15 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
         }
 
         for (size_t i = 0; i < op->nbs->n_addresses; i++) {
-            struct eth_addr ea;
-            ovs_be32 ip;
-
-            if (ovs_scan(op->nbs->addresses[i],
-                         ETH_ADDR_SCAN_FMT" "IP_SCAN_FMT,
-                         ETH_ADDR_SCAN_ARGS(ea), IP_SCAN_ARGS(&ip))) {
+            struct lport_addresses laddrs;
+            if (!extract_lport_addresses(op->nbs->addresses[i], &laddrs,
+                                         false)) {
+                continue;
+            }
+            for (size_t j = 0; j < laddrs.n_ipv4_addrs; j++) {
                 char *match = xasprintf(
-                    "arp.tpa == "IP_FMT" && arp.op == 1", IP_ARGS(ip));
+                    "arp.tpa == "IP_FMT" && arp.op == 1",
+                    IP_ARGS(laddrs.ipv4_addrs[j].addr));
                 char *actions = xasprintf(
                     "eth.dst = eth.src; "
                     "eth.src = "ETH_ADDR_FMT"; "
@@ -1213,14 +1320,16 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
                     "outport = inport; "
                     "inport = \"\"; /* Allow sending out inport. */ "
                     "output;",
-                    ETH_ADDR_ARGS(ea),
-                    ETH_ADDR_ARGS(ea),
-                    IP_ARGS(ip));
+                    ETH_ADDR_ARGS(laddrs.ea),
+                    ETH_ADDR_ARGS(laddrs.ea),
+                    IP_ARGS(laddrs.ipv4_addrs[j].addr));
                 ovn_lflow_add(lflows, op->od, S_SWITCH_IN_L2_LKUP, 150,
                               match, actions);
                 free(match);
                 free(actions);
             }
+
+            free(laddrs.ipv4_addrs);
         }
     }
 
@@ -1541,12 +1650,14 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
             /* XXX ARP for neighboring router */
         } else if (op->od->n_router_ports) {
             for (size_t i = 0; i < op->nbs->n_addresses; i++) {
-                struct eth_addr ea;
-                ovs_be32 ip;
+                struct lport_addresses laddrs;
+                if (!extract_lport_addresses(op->nbs->addresses[i], &laddrs,
+                                             false)) {
+                    continue;
+                }
 
-                if (ovs_scan(op->nbs->addresses[i],
-                             ETH_ADDR_SCAN_FMT" "IP_SCAN_FMT,
-                             ETH_ADDR_SCAN_ARGS(ea), IP_SCAN_ARGS(&ip))) {
+                for (size_t k = 0; k < laddrs.n_ipv4_addrs; k++) {
+                    ovs_be32 ip = laddrs.ipv4_addrs[k].addr;
                     for (size_t j = 0; j < op->od->n_router_ports; j++) {
                         /* Get the Logical_Router_Port that the Logical_Port is
                          * connected to, as 'peer'. */
@@ -1574,7 +1685,7 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
                                                   "outport = %s; "
                                                   "output;",
                                                   ETH_ADDR_ARGS(peer->mac),
-                                                  ETH_ADDR_ARGS(ea),
+                                                  ETH_ADDR_ARGS(laddrs.ea),
                                                   peer->json_key);
                         ovn_lflow_add(lflows, peer->od,
                                       S_ROUTER_IN_ARP, 200, match, actions);
@@ -1583,6 +1694,8 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
                         break;
                     }
                 }
+
+                free(laddrs.ipv4_addrs);
             }
         }
     }
index c3b4934..dbd7bbb 100644 (file)
         </p>
 
         <dl>
-          <dt><code><var>xx</var>:<var>xx</var>:<var>xx</var>:<var>xx</var>:<var>xx</var>:<var>xx</var></code></dt>
+          <dt><code>Ethernet address followed by zero or more IPv4 or IPv6 addresses (or both)</code></dt>
           <dd>
             <p>
-              An Ethernet address owned by the logical port.  Like a physical
-              Ethernet NIC, a logical port ordinarily has a single fixed
-              Ethernet address.
+              An Ethernet address defined is owned by the logical port.
+              Like a physical Ethernet NIC, a logical port ordinarily has
+              a single fixed Ethernet address.
             </p>
 
             <p>
               if a MAC learning process had learned that MAC address on the
               port.
             </p>
-          </dd>
 
-          <dt><code><var>xx</var>:<var>xx</var>:<var>xx</var>:<var>xx</var>:<var>xx</var>:<var>xx</var> <var>a</var>.<var>b</var>.<var>c</var>.<var>d</var></code></dt>
-          <dd>
             <p>
-              This form has all the effects of the previous form.  It also
-              indicates that the logical port owns the given IPv4 address.
+              If IPv4 or IPv6 address(es) (or both) are defined, it indicates
+              that the logical port owns the given IP addresses.
             </p>
 
             <p>
-              The OVN logical switch uses this information to synthesize
-              responses to ARP requests without traversing the physical
-              network.  The OVN logical router connected to the logical switch,
-              if any, uses this information to avoid issuing ARP requests for
-              logical switch ports.
+              If IPv4 address(es) are defined, the OVN logical switch uses this
+              information to synthesize responses to ARP requests without
+              traversing the physical network. The OVN logical router connected
+              to the logical switch, if any, uses this information to avoid
+              issuing ARP requests for logical switch ports.
             </p>
 
             <p>
               Note that the order here is important. The Ethernet address must
-              be listed before the IP address.
+              be listed before the IP address(es) if defined.
+            </p>
+
+            <p>
+              Examples:
             </p>
+
+            <dl>
+              <dt><code>80:fa:5b:06:72:b7</code></dt>
+              <dd>
+                This indicates that the logical port owns the above mac address.
+              </dd>
+
+              <dt><code>80:fa:5b:06:72:b7 10.0.0.4 20.0.0.4</code></dt>
+              <dd>
+                This indicates that the logical port owns the mac address and two
+                IPv4 addresses.
+              </dd>
+
+              <dt><code>80:fa:5b:06:72:b7 fdaa:15f2:72cf:0:f816:3eff:fe20:3f41</code></dt>
+              <dd>
+                This indicates that the logical port owns the mac address and
+                1 IPv6 address.
+              </dd>
+
+              <dt><code>80:fa:5b:06:72:b7 10.0.0.4 fdaa:15f2:72cf:0:f816:3eff:fe20:3f41</code></dt>
+              <dd>
+                This indicates that the logical port owns the mac address and
+                1 IPv4 address and 1 IPv6 address.
+              </dd>
+            </dl>
           </dd>
 
           <dt><code>unknown</code></dt>
index d0200e4..0e71732 100644 (file)
@@ -551,7 +551,12 @@ for i in 1 2 3; do
         if test $j = 1; then
             ovn-nbctl lport-set-addresses lp$i$j "f0:00:00:00:00:$i$j 192.168.0.$i$j" unknown
         else
-            ovn-nbctl lport-set-addresses lp$i$j "f0:00:00:00:00:$i$j 192.168.0.$i$j"
+            if test $j = 3; then
+                ip_addrs="192.168.0.$i$j fe80::ea2a:eaff:fe28:$i$j/64 192.169.0.$i$j"
+            else
+                ip_addrs="192.168.0.$i$j"
+            fi
+            ovn-nbctl lport-set-addresses lp$i$j "f0:00:00:00:00:$i$j $ip_addrs"
             ovn-nbctl lport-set-port-security lp$i$j f0:00:00:00:00:$i$j
         fi
     done
@@ -719,6 +724,12 @@ for is in 1 2 3; do
                 tip_unknown=`ip_to_hex 11 11 11 11`
                 test_arp $s f000000000$s $sip $tip f000000000$d            #9
                 test_arp $s f000000000$s $sip $tip_unknown                 #10
+
+                if test $jd = 3; then
+                    # lport[123]3 has an additional ip 192.169.0.[123]3.
+                    tip=`ip_to_hex 192 169 0 $id$jd`
+                    test_arp $s f000000000$s $sip $tip f000000000$d        #9
+                fi
             done
         done
 
@@ -743,6 +754,17 @@ for is in 1 2 3; do
     done
 done
 
+# set address for lp13 with invalid characters.
+# lp13 should be configured with only 192.168.0.13.
+ovn-nbctl lport-set-addresses lp13 "f0:00:00:00:00:13 192.168.0.13 invalid 192.169.0.13"
+sip=`ip_to_hex 192 168 0 11`
+tip=`ip_to_hex 192 168 0 13`
+test_arp 11 f00000000011  $sip $tip f00000000013
+
+tip=`ip_to_hex 192 169 0 13`
+#arp request for 192.169.0.13 should be flooded
+test_arp 11 f00000000011  $sip $tip
+
 # Allow some time for packet forwarding.
 # XXX This can be improved.
 sleep 1