diff mbox

[ovs-dev,RFC,v2,3/3] ovn: add SLAAC support for IPv6

Message ID 1470104218-32200-1-git-send-email-zealokii@gmail.com
State Superseded
Headers show

Commit Message

Zong Kai LI Aug. 2, 2016, 2:16 a.m. UTC
This patch tries to implement Router Advertisement (RA) responder for SLAAC
on ovn-northd side.

It tries to build lflows per each Logical Router Port, who have IPv6 networks
and set their 'slaac' column to true.

The lflows will look like:
 match=(inport == "lrp-32a71e0b-8b19-4c52-8cde-058325e4df5d" &&
        ip6.dst == ff02::2 && nd_rs)
 action=(nd_ra{slaac(fd80:a123:b345::/64,1450,fa:16:3e:62:f1:e6);
         outport = inport; flags.loopback = 1; output;};)
while:
 - nd_rs is a new symbol stands for
   "icmp6.type == 133 && icmp6.code == 0 && ttl == 255"
 - slaac is a new action which accepts ordered parameter list:
     - one or more IPv6 prefixes: such as fd80:a123:b345::/64.
     - MTU: logical switch MTU, such as 1450.
     - MAC address: router port mac address, such as fa:16:3e:62:f1:e6.
 - nd_ra is a new action which stands for RA responder, it will compose a RA
   packet per parameters in slaac, and eth.src and ip6.src from packet being
   processed.

Logical_Router_Port.slaac column will only tell whether ovn should reply a RA
packet for Router solicitation packet received from the lrp port. To respond
a RA packet for other scenario will be a future work.
---
 ovn/northd/ovn-northd.c |  94 ++++++++++++++++++++++++++++++++++++----
 ovn/ovn-nb.ovsschema    |   6 ++-
 ovn/ovn-nb.xml          |  11 +++++
 tests/ovn.at            | 111 ++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 213 insertions(+), 9 deletions(-)

Comments

Dustin Lundquist Aug. 9, 2016, 9:05 p.m. UTC | #1
On Mon, Aug 1, 2016 at 7:16 PM, Zong Kai LI <zealokii@gmail.com> wrote:

> This patch tries to implement Router Advertisement (RA) responder for SLAAC
> on ovn-northd side.
>
> It tries to build lflows per each Logical Router Port, who have IPv6
> networks
> and set their 'slaac' column to true.
>
> The lflows will look like:
>  match=(inport == "lrp-32a71e0b-8b19-4c52-8cde-058325e4df5d" &&
>         ip6.dst == ff02::2 && nd_rs)
>  action=(nd_ra{slaac(fd80:a123:b345::/64,1450,fa:16:3e:62:f1:e6);
>          outport = inport; flags.loopback = 1; output;};)
> while:
>  - nd_rs is a new symbol stands for
>    "icmp6.type == 133 && icmp6.code == 0 && ttl == 255"
>  - slaac is a new action which accepts ordered parameter list:
>      - one or more IPv6 prefixes: such as fd80:a123:b345::/64.
>      - MTU: logical switch MTU, such as 1450.
>      - MAC address: router port mac address, such as fa:16:3e:62:f1:e6.
>  - nd_ra is a new action which stands for RA responder, it will compose a
> RA
>    packet per parameters in slaac, and eth.src and ip6.src from packet
> being
>    processed.
>
This would be a router solicitation responder, since it responds to router
solicitation messages by sending router advertisement messages.

>
> Logical_Router_Port.slaac column will only tell whether ovn should reply a
> RA
> packet for Router solicitation packet received from the lrp port. To
> respond
> a RA packet for other scenario will be a future work.
> ---
>  ovn/northd/ovn-northd.c |  94 ++++++++++++++++++++++++++++++++++++----
>  ovn/ovn-nb.ovsschema    |   6 ++-
>  ovn/ovn-nb.xml          |  11 +++++
>  tests/ovn.at            | 111 ++++++++++++++++++++++++++++++
> ++++++++++++++++++
>  4 files changed, 213 insertions(+), 9 deletions(-)
>
> diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c
> index d6c14cf..98db819 100644
> --- a/ovn/northd/ovn-northd.c
> +++ b/ovn/northd/ovn-northd.c
> @@ -126,9 +126,10 @@ enum ovn_stage {
>      PIPELINE_STAGE(ROUTER, IN,  IP_INPUT,    1, "lr_in_ip_input")     \
>      PIPELINE_STAGE(ROUTER, IN,  UNSNAT,      2, "lr_in_unsnat")       \
>      PIPELINE_STAGE(ROUTER, IN,  DNAT,        3, "lr_in_dnat")         \
> -    PIPELINE_STAGE(ROUTER, IN,  IP_ROUTING,  4, "lr_in_ip_routing")   \
> -    PIPELINE_STAGE(ROUTER, IN,  ARP_RESOLVE, 5, "lr_in_arp_resolve")  \
> -    PIPELINE_STAGE(ROUTER, IN,  ARP_REQUEST, 6, "lr_in_arp_request")  \
> +    PIPELINE_STAGE(ROUTER, IN,  RA_RSP,      4, "lr_in_ra_rsp")      \

Since this is responding to router solicitation messages, should this be
RS_RSP?

> +    PIPELINE_STAGE(ROUTER, IN,  IP_ROUTING,  5, "lr_in_ip_routing")   \
> +    PIPELINE_STAGE(ROUTER, IN,  ARP_RESOLVE, 6, "lr_in_arp_resolve")  \
> +    PIPELINE_STAGE(ROUTER, IN,  ARP_REQUEST, 7, "lr_in_arp_request")  \
>                                                                        \
>      /* Logical router egress stages. */                               \
>      PIPELINE_STAGE(ROUTER, OUT, SNAT,      0, "lr_out_snat")          \
> @@ -3409,7 +3443,51 @@ build_lrouter_flows(struct hmap *datapaths, struct
> hmap *ports,
>                        "ip", "flags.loopback = 1; ct_dnat;");
>      }
>
> -    /* Logical router ingress table 4: IP Routing.
> +    /* Logical router ingress table 5: RA responder, by default goto next.
>
Isn't this table 4 now?

> +     * (priority 0)*/
> +    HMAP_FOR_EACH (od, key_node, datapaths) {
> +        if (!od->nbr) {
> +            continue;
> +        }
> +
> +        ovn_lflow_add(lflows, od, S_ROUTER_IN_RA_RSP, 0, "1", "next;");
> +    }
> +
> +    /* Logical router ingress table 5: RA responder, reply for 'slaac'
> enabled
> +     * router port. (priority 50)*/
> +    HMAP_FOR_EACH (op, key_node, ports) {
> +        if (!op->nbrp || op->nbrp->peer
> +            || !op->peer
> +            || !op->nbrp->slaac
> +            || !*op->nbrp->slaac) {
> +            continue;
> +        }
> +
> +        ds_clear(&match);
> +        ds_put_format(&match, "inport == %s", op->json_key);
> +        ds_put_cstr(&match,  " && ip6.dst == ff02::2 && nd_rs");
> +        ds_clear(&actions);
> +        ds_put_format(&actions, "nd_ra{slaac(");
> +        size_t actions_len = actions.length;
> +        for (size_t i = 0; i != op->lrp_networks.n_ipv6_addrs; i++) {
> +            if (in6_is_lla(&op->lrp_networks.ipv6_addrs[i].network)) {
> +                continue;
> +            }
> +            ds_put_format(&actions, "%s/%u,",
> +                          op->lrp_networks.ipv6_addrs[i].network_s,
> +                          op->lrp_networks.ipv6_addrs[i].plen);
> +        }
> +        if (actions.length != actions_len) {
> +            ds_put_format(&actions, "%ld,", op->peer->od->nbs->mtu);
> +            ds_put_cstr(&actions, op->lrp_networks.ea_s);
> +            ds_put_cstr(&actions, "); outport = inport; flags.loopback =
> 1;"
> +                                  " output;};");
> +            ovn_lflow_add(lflows, op->od, S_ROUTER_IN_RA_RSP, 50,
> +                          ds_cstr(&match), ds_cstr(&actions));
> +        }
> +    }
> +
> +    /* Logical router ingress table 5: IP Routing.
>       *
>       * A packet that arrives at this table is an IP packet that should be
>       * routed to the address in 'ip[46].dst'. This table sets outport to
> diff --git a/ovn/ovn-nb.ovsschema b/ovn/ovn-nb.ovsschema
> index 660db76..f4d57a1 100644
> --- a/ovn/ovn-nb.ovsschema
> +++ b/ovn/ovn-nb.ovsschema
> @@ -34,6 +34,9 @@
>                  "other_config": {
>                      "type": {"key": "string", "value": "string",
>                               "min": 0, "max": "unlimited"}},
> +                "mtu": {"type": {"key": {"type": "integer",
> +                                         "minInteger": 0,
> +                                         "maxInteger": 65535}}},
>
IPv6 specifies a minimum MTU of 1280, unless 0 is a sentinel value to not
include MTU options in router advertisements (which I didn't see code to
implement), we should not permit invalid MTUs in the database schema.

>                  "external_ids": {
>                      "type": {"key": "string", "value": "string",
>                               "min": 0, "max": "unlimited"}}},
>
Ben Pfaff Aug. 10, 2016, 4:52 p.m. UTC | #2
Why do all of these patches have the same title?  It makes it difficult
to distinguish them.  Please give them unique titles.
diff mbox

Patch

diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c
index d6c14cf..98db819 100644
--- a/ovn/northd/ovn-northd.c
+++ b/ovn/northd/ovn-northd.c
@@ -126,9 +126,10 @@  enum ovn_stage {
     PIPELINE_STAGE(ROUTER, IN,  IP_INPUT,    1, "lr_in_ip_input")     \
     PIPELINE_STAGE(ROUTER, IN,  UNSNAT,      2, "lr_in_unsnat")       \
     PIPELINE_STAGE(ROUTER, IN,  DNAT,        3, "lr_in_dnat")         \
-    PIPELINE_STAGE(ROUTER, IN,  IP_ROUTING,  4, "lr_in_ip_routing")   \
-    PIPELINE_STAGE(ROUTER, IN,  ARP_RESOLVE, 5, "lr_in_arp_resolve")  \
-    PIPELINE_STAGE(ROUTER, IN,  ARP_REQUEST, 6, "lr_in_arp_request")  \
+    PIPELINE_STAGE(ROUTER, IN,  RA_RSP,      4, "lr_in_ra_rsp")      \
+    PIPELINE_STAGE(ROUTER, IN,  IP_ROUTING,  5, "lr_in_ip_routing")   \
+    PIPELINE_STAGE(ROUTER, IN,  ARP_RESOLVE, 6, "lr_in_arp_resolve")  \
+    PIPELINE_STAGE(ROUTER, IN,  ARP_REQUEST, 7, "lr_in_arp_request")  \
                                                                       \
     /* Logical router egress stages. */                               \
     PIPELINE_STAGE(ROUTER, OUT, SNAT,      0, "lr_out_snat")          \
@@ -2413,13 +2414,20 @@  build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
         }
     }
 
-    /* Ingress table 1 and 2: Port security - IP and ND, by default goto next.
-     * (priority 0)*/
+    /* Ingress table 1 and 2: Port security - IP and ND. */
     HMAP_FOR_EACH (od, key_node, datapaths) {
         if (!od->nbs) {
             continue;
         }
 
+        /* Allow Router Solicitation and Router Advertisement messages pass.
+         * (priority 90)*/
+        ovn_lflow_add(lflows, od, S_SWITCH_IN_PORT_SEC_ND, 90, "nd_rs",
+                      "next;");
+        ovn_lflow_add(lflows, od, S_SWITCH_IN_PORT_SEC_ND, 90, "nd_ra",
+                      "next;");
+
+        /* By default goto next. (priority 0)*/
         ovn_lflow_add(lflows, od, S_SWITCH_IN_PORT_SEC_ND, 0, "1", "next;");
         ovn_lflow_add(lflows, od, S_SWITCH_IN_PORT_SEC_IP, 0, "1", "next;");
     }
@@ -2582,6 +2590,32 @@  build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
         ovn_lflow_add(lflows, od, S_SWITCH_IN_DHCP_RESPONSE, 0, "1", "next;");
     }
 
+    /* Ingress table 12: Destination lookup, router solicitation handling
+     * (priority 110). */
+    HMAP_FOR_EACH (od, key_node, datapaths) {
+        if (!od->nbs) {
+            continue;
+        }
+
+        if (!od->n_router_ports) {
+            continue;
+        }
+
+        ds_clear(&actions);
+        for (size_t i = 0; i != od->n_router_ports; i++) {
+            op = od->router_ports[i];
+            if (!op->lsp_addrs || !op->lsp_addrs->n_ipv6_addrs) {
+                continue;
+            }
+            ds_put_format(&actions, "outport = %s; output; ", op->json_key);
+        }
+        if (actions.length != 0) {
+            ds_chomp(&actions, ' ');
+            ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 110, "nd_rs",
+                          ds_cstr(&actions));
+        }
+    }
+
     /* Ingress table 12: Destination lookup, broadcast and multicast handling
      * (priority 100). */
     HMAP_FOR_EACH (op, key_node, ports) {
@@ -3409,7 +3443,51 @@  build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
                       "ip", "flags.loopback = 1; ct_dnat;");
     }
 
-    /* Logical router ingress table 4: IP Routing.
+    /* Logical router ingress table 5: RA responder, by default goto next.
+     * (priority 0)*/
+    HMAP_FOR_EACH (od, key_node, datapaths) {
+        if (!od->nbr) {
+            continue;
+        }
+
+        ovn_lflow_add(lflows, od, S_ROUTER_IN_RA_RSP, 0, "1", "next;");
+    }
+
+    /* Logical router ingress table 5: RA responder, reply for 'slaac' enabled
+     * router port. (priority 50)*/
+    HMAP_FOR_EACH (op, key_node, ports) {
+        if (!op->nbrp || op->nbrp->peer
+            || !op->peer
+            || !op->nbrp->slaac
+            || !*op->nbrp->slaac) {
+            continue;
+        }
+
+        ds_clear(&match);
+        ds_put_format(&match, "inport == %s", op->json_key);
+        ds_put_cstr(&match,  " && ip6.dst == ff02::2 && nd_rs");
+        ds_clear(&actions);
+        ds_put_format(&actions, "nd_ra{slaac(");
+        size_t actions_len = actions.length;
+        for (size_t i = 0; i != op->lrp_networks.n_ipv6_addrs; i++) {
+            if (in6_is_lla(&op->lrp_networks.ipv6_addrs[i].network)) {
+                continue;
+            }
+            ds_put_format(&actions, "%s/%u,",
+                          op->lrp_networks.ipv6_addrs[i].network_s,
+                          op->lrp_networks.ipv6_addrs[i].plen);
+        }
+        if (actions.length != actions_len) {
+            ds_put_format(&actions, "%ld,", op->peer->od->nbs->mtu);
+            ds_put_cstr(&actions, op->lrp_networks.ea_s);
+            ds_put_cstr(&actions, "); outport = inport; flags.loopback = 1;"
+                                  " output;};");
+            ovn_lflow_add(lflows, op->od, S_ROUTER_IN_RA_RSP, 50,
+                          ds_cstr(&match), ds_cstr(&actions));
+        }
+    }
+
+    /* Logical router ingress table 5: IP Routing.
      *
      * A packet that arrives at this table is an IP packet that should be
      * routed to the address in 'ip[46].dst'. This table sets outport to
@@ -3451,7 +3529,7 @@  build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
 
     /* XXX destination unreachable */
 
-    /* Local router ingress table 5: ARP Resolution.
+    /* Local router ingress table 6: ARP Resolution.
      *
      * Any packet that reaches this table is an IP packet whose next-hop IP
      * address is in reg0. (ip4.dst is the final destination.) This table
@@ -3648,7 +3726,7 @@  build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
                       "get_nd(outport, xxreg0); next;");
     }
 
-    /* Local router ingress table 6: ARP request.
+    /* Local router ingress table 7: ARP request.
      *
      * In the common case where the Ethernet destination has been resolved,
      * this table outputs the packet (priority 0).  Otherwise, it composes
diff --git a/ovn/ovn-nb.ovsschema b/ovn/ovn-nb.ovsschema
index 660db76..f4d57a1 100644
--- a/ovn/ovn-nb.ovsschema
+++ b/ovn/ovn-nb.ovsschema
@@ -1,7 +1,7 @@ 
 {
     "name": "OVN_Northbound",
     "version": "5.3.0",
-    "cksum": "1305504870 9051",
+    "cksum": "2595190831 9309",
     "tables": {
         "NB_Global": {
             "columns": {
@@ -34,6 +34,9 @@ 
                 "other_config": {
                     "type": {"key": "string", "value": "string",
                              "min": 0, "max": "unlimited"}},
+                "mtu": {"type": {"key": {"type": "integer",
+                                         "minInteger": 0,
+                                         "maxInteger": 65535}}},
                 "external_ids": {
                     "type": {"key": "string", "value": "string",
                              "min": 0, "max": "unlimited"}}},
@@ -150,6 +153,7 @@ 
                 "mac": {"type": "string"},
                 "peer": {"type": {"key": "string", "min": 0, "max": 1}},
                 "enabled": {"type": {"key": "boolean", "min": 0, "max": 1}},
+                "slaac": {"type": {"key": "boolean", "min": 0, "max": 1}},
                 "external_ids": {
                     "type": {"key": "string", "value": "string",
                              "min": 0, "max": "unlimited"}}},
diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml
index 4ce295a..18895a0 100644
--- a/ovn/ovn-nb.xml
+++ b/ovn/ovn-nb.xml
@@ -134,6 +134,10 @@ 
       </column>
     </group>
 
+    <column name="mtu">
+      Logical Switch MTU.
+    </column>
+
     <group title="Common Columns">
       <column name="external_ids">
         See <em>External IDs</em> at the beginning of this document.
@@ -904,6 +908,13 @@ 
       port has all ingress and egress traffic dropped.
     </column>
 
+    <column name="slaac">
+      Setting <code>true</code> specifies the logical switch subnets behind
+      this router port IPv6 networks are going to use SLAAC as IPv6 address
+      configuration and RA mode. Otherwise set this column to
+      <code>false</code>, or this router port has no IPv6 networks.
+    </column>
+
     <group title="Attachment">
       <p>
         A given router port serves one of two purposes:
diff --git a/tests/ovn.at b/tests/ovn.at
index 454cb24..ffca833 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -4029,3 +4029,114 @@  AT_CHECK([cat received2.packets], [0], [expout])
 OVN_CLEANUP([hv1])
 
 AT_CLEANUP
+
+AT_SETUP([ovn -- ipv6 stateless address autoconfiguration])
+AT_KEYWORDS([ovn-slaac])
+AT_SKIP_IF([test $HAVE_PYTHON = no])
+ovn_start
+
+# In this test case we create 1 lswitch with 2 VIF ports attached
+# with. Router solicitation packet we test, sent from VIF port, will be replied
+# by local ovn-controller.
+
+# Create hypervisors and logical switch lsw0, logical router lr0, attach lsw0
+# onto lr0, set Logical_Router_Port.slaac column to 'true' to allow lrp0 send
+# RA with SLAAC flags.
+ovn-nbctl ls-add lsw0
+ovn-nbctl set logical-switch lsw0 mtu=1450
+ovn-nbctl lr-add lr0
+ovn-nbctl lrp-add lr0 lrp0 fa:16:3e:32:3c:e0 fdad:a0f9:a012::1/64
+ovn-nbctl set Logical_Router_Port lrp0 slaac="true"
+ovn-nbctl \
+    -- lsp-add lsw0 lsp0 \
+    -- set Logical_Switch_Port lsp0 type=router \
+                     options:router-port=lrp0 \
+                     addresses='"fa:16:3e:32:3c:e0 fdad:a0f9:a012::1"'
+net_add n1
+sim_add hv1
+as hv1
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.2
+
+# Add vif1 to hv1 and lsw0, turn on l2 port security on vif1.
+ovs-vsctl add-port br-int vif1 -- set Interface vif1 external-ids:iface-id=lp1 options:tx_pcap=hv1/vif1-tx.pcap options:rxq_pcap=hv1/vif1-rx.pcap ofport-request=1
+ovn-nbctl lsp-add lsw0 lp1
+ovn-nbctl lsp-set-addresses lp1 "fa:16:3e:6e:a1:42 10.0.0.12 fdad:a0f9:a012:0:f816:3eff:fe6e:a142"
+ovn-nbctl lsp-set-port-security lp1 "fa:16:3e:6e:a1:42 10.0.0.12 fdad:a0f9:a012:0:f816:3eff:fe6e:a142"
+
+# Add vif2 to hv1 and lsw0, turn on l2 port security on vif2.
+ovs-vsctl add-port br-int vif2 -- set Interface vif2 external-ids:iface-id=lp2 options:tx_pcap=hv1/vif2-tx.pcap options:rxq_pcap=hv1/vif2-rx.pcap ofport-request=2
+ovn-nbctl lsp-add lsw0 lp2
+ovn-nbctl lsp-set-addresses lp2 "fa:16:3e:6e:b2:55 10.0.0.13 fdad:a0f9:a012:0:f816:3eff:fe6e:b255"
+ovn-nbctl lsp-set-port-security lp2 "fa:16:3e:6e:b2:55 10.0.0.13 fdad:a0f9:a012:0:f816:3eff:fe6e:b255"
+
+# Add ACL rule for ICMPv6 on lsw0
+ovn-nbctl acl-add lsw0 from-lport 1002 'ip6 && icmp6'  allow-related
+ovn-nbctl acl-add lsw0 to-lport 1002 'outport == "lp1" && ip6 && icmp6'  allow-related
+ovn-nbctl acl-add lsw0 to-lport 1002 'outport == "lp2" && ip6 && icmp6'  allow-related
+
+# Allow some time for ovn-northd and ovn-controller to catch up.
+# XXX This should be more systematic.
+sleep 1
+
+# Given the name of a logical port, prints the name of the hypervisor
+# on which it is located.
+vif_to_hv() {
+    echo hv1${1%?}
+}
+trim_zeros() {
+    sed 's/\(00\)\{1,\}$//'
+}
+for i in 1 2; do
+    : > $i.expected
+done
+
+# Complete Router Solicitation packet and Router Advertisement packet.
+rs_packet=333300000002fa163e6ea14286dd6000000000103afffe80000000000000f8163efffe6ea142ff0200000000000000000000000000028500cb9e000000000101fa163e6ea142
+packet_l2=fa163e6ea142fa163e323ce086dd
+packet_l3=6000000000403afffe80000000000000f8163efffe323ce0fe80000000000000f8163efffe6ea142
+packet_l4_hd_no_csum=860040002a300000000000000000
+prefix_opt=030440c000002a3000002a3000000000fdada0f9a01200000000000000000000
+mtu_opt=05010000000005aa
+sll_opt=0101fa163e323ce0
+ra_packet=$packet_l2$packet_l3$packet_l4_hd_no_csum$prefix_opt$mtu_opt$sll_opt
+
+as hv1 ovs-appctl netdev-dummy/receive vif1 $rs_packet
+echo $ra_packet | trim_zeros >> 1.expected
+
+sleep 1
+
+# Extend lrp0 networks, update lp2 addresses and l2 port security.
+ovn-nbctl add Logical_Router_Port lrp0 networks \"fdad:b123:d789::1/64\"
+ovn-nbctl set Logical_Switch_Port lp2 addresses \"fa:16:3e:6e:b2:55 10.0.0.13 fdad:a0f9:a012:0:f816:3eff:fe6e:b255 fdad:b123:d789:0:f816:3eff:fe6e:b255\"
+ovn-nbctl set Logical_Switch_Port lp2 port-security \"fa:16:3e:6e:b2:55 10.0.0.13 fdad:a0f9:a012:0:f816:3eff:fe6e:b255 fdad:b123:d789:0:f816:3eff:fe6e:b255\"
+# Update Router solicitation packet for lp2.
+rs_packet=333300000002fa163e6eb25586dd6000000000103afffe80000000000000f8163efffe6eb255ff0200000000000000000000000000028500cb9e000000000101fa163e6eb255
+# Router Advertisement packet should have a new prefix information option.
+packet_l2=fa163e6eb255fa163e323ce086dd
+packet_l3=6000000000603afffe80000000000000f8163efffe323ce0fe80000000000000f8163efffe6eb255
+new_prefix_opt=030440c000002a3000002a3000000000fdadb123d78900000000000000000000
+ra_packet=$packet_l2$packet_l3$packet_l4_hd_no_csum$prefix_opt$new_prefix_opt$mtu_opt$sll_opt
+
+as hv1 ovs-appctl netdev-dummy/receive vif2 $rs_packet
+echo $ra_packet | trim_zeros >> 2.expected
+
+sleep 1
+
+echo "------ hv1 dump ------"
+as hv1 ovs-vsctl show
+as hv1 ovs-ofctl -O OpenFlow13 show br-int
+as hv1 ovs-ofctl -O OpenFlow13 dump-flows br-int
+
+for i in 1 2; do
+    file=hv1/vif$i-tx.pcap
+    echo $file
+    # Remove checksum to compare.
+    $PYTHON "$top_srcdir/utilities/ovs-pcap.in" $file | trim_zeros | cut -b 1-112,117- > $i.packets
+    cat $i.expected > expout
+    AT_CHECK([cat $i.packets], [0], [expout])
+done
+
+OVN_CLEANUP([hv1])
+
+AT_CLEANUP