---
ovn/lib/logical-fields.c | 4 +
ovn/northd/ovn-northd.8.xml | 83 ++++++++++++++-
ovn/northd/ovn-northd.c | 137 +++++++++++++++++++++---
ovn/ovn-nb.ovsschema | 7 +-
ovn/ovn-nb.xml | 39 +++++++
ovn/ovn-sb.xml | 4 +
tests/ovn.at | 249 ++++++++++++++++++++++++++++++++++++++++++++
7 files changed, 500 insertions(+), 23 deletions(-)
diff --git a/ovn/lib/logical-fields.c b/ovn/lib/logical-fields.c
index 26e336f5a..a8b5e3c51 100644
--- a/ovn/lib/logical-fields.c
+++ b/ovn/lib/logical-fields.c
@@ -183,6 +183,10 @@ ovn_init_symtab(struct shash *symtab)
"icmp6.type == 135 && icmp6.code == 0 && ip.ttl == 255");
expr_symtab_add_predicate(symtab, "nd_na",
"icmp6.type == 136 && icmp6.code == 0 && ip.ttl == 255");
+ expr_symtab_add_predicate(symtab, "nd_rs",
+ "icmp6.type == 133 && icmp6.code == 0 && ip.ttl == 255");
+ expr_symtab_add_predicate(symtab, "nd_ra",
+ "icmp6.type == 134 && icmp6.code == 0 && ip.ttl == 255");
expr_symtab_add_field(symtab, "nd.target", MFF_ND_TARGET, "nd", false);
expr_symtab_add_field(symtab, "nd.sll", MFF_ND_SLL, "nd_ns", false);
expr_symtab_add_field(symtab, "nd.tll", MFF_ND_TLL, "nd_na", false);
diff --git a/ovn/northd/ovn-northd.8.xml b/ovn/northd/ovn-northd.8.xml
index 0d85ec0d2..a994abf78 100644
--- a/ovn/northd/ovn-northd.8.xml
+++ b/ovn/northd/ovn-northd.8.xml
@@ -1584,7 +1584,82 @@ icmp4 {
- Ingress Table 5: IP Routing
+ Ingress Table 5: IPv6 ND RA option processing
+
+
+ -
+
+ A priority-50 logical flow is added for each logical router port
+ configured with IPv6 ND RA options which matches IPv6 ND Router
+ Solicitation packet and applies the action
+ put_nd_ra_opts
and advances the packet to the next
+ table.
+
+
+
+reg0[5] = put_nd_ra_opts(options);next;
+
+
+
+ For a valid IPv6 ND RS packet, this transforms the packet into an
+ IPv6 ND RA reply and sets the RA options to the packet and stores 1
+ into reg0[5]. For other kinds of packets, it just stores 0 into
+ reg0[5]. Either way, it continues to the next table.
+
+
+
+ -
+ A priority-0 logical flow with match
1
has actions
+ next;
.
+
+
+
+ Ingress Table 6: IPv6 ND RA responder
+
+
+ This table implements IPv6 ND RA responder for the IPv6 ND RA replies
+ generated by the previous table.
+
+
+
+ -
+
+ A priority-50 logical flow is added for each logical router port
+ configured with IPv6 ND RA options which matches IPv6 ND RA
+ packets and reg0[5] == 1
and responds back to the
+ inport
after applying these actions.
+ If reg0[5]
is set to 1, it means that the action
+ put_nd_ra_opts
was successful.
+
+
+
+eth.dst = eth.src;
+eth.src = E;
+ip6.dst = ip6.src;
+ip6.src = I;
+outport = P;
+flags.loopback = 1;
+output;
+
+
+
+ where E is the MAC address and I is the IPv6
+ link local address of the logical router port.
+
+
+
+ (This terminates packet processing in ingress pipeline; the packet
+ does not go to the next ingress table.)
+
+
+
+ -
+ A priority-0 logical flow with match
1
has actions
+ next;
.
+
+
+
+ Ingress Table 7: IP Routing
A packet that arrives at this table is an IP packet that should be
@@ -1686,7 +1761,7 @@ next;
-
Ingress Table 6: ARP/ND Resolution
+ Ingress Table 8: ARP/ND Resolution
Any packet that reaches this table is an IP packet whose next-hop
@@ -1779,7 +1854,7 @@ next;
-
Ingress Table 7: Gateway Redirect
+ Ingress Table 9: Gateway Redirect
For distributed logical routers where one of the logical router
@@ -1836,7 +1911,7 @@ next;
-
Ingress Table 8: ARP Request
+ Ingress Table 10: ARP Request
In the common case where the Ethernet destination has been resolved, this
diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c
index 75f2c6607..3da20d25b 100644
--- a/ovn/northd/ovn-northd.c
+++ b/ovn/northd/ovn-northd.c
@@ -129,15 +129,17 @@ enum ovn_stage {
PIPELINE_STAGE(SWITCH, OUT, PORT_SEC_L2, 8, "ls_out_port_sec_l2") \
\
/* Logical router ingress stages. */ \
- PIPELINE_STAGE(ROUTER, IN, ADMISSION, 0, "lr_in_admission") \
- PIPELINE_STAGE(ROUTER, IN, IP_INPUT, 1, "lr_in_ip_input") \
- PIPELINE_STAGE(ROUTER, IN, DEFRAG, 2, "lr_in_defrag") \
- PIPELINE_STAGE(ROUTER, IN, UNSNAT, 3, "lr_in_unsnat") \
- PIPELINE_STAGE(ROUTER, IN, DNAT, 4, "lr_in_dnat") \
- 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, GW_REDIRECT, 7, "lr_in_gw_redirect") \
- PIPELINE_STAGE(ROUTER, IN, ARP_REQUEST, 8, "lr_in_arp_request") \
+ PIPELINE_STAGE(ROUTER, IN, ADMISSION, 0, "lr_in_admission") \
+ PIPELINE_STAGE(ROUTER, IN, IP_INPUT, 1, "lr_in_ip_input") \
+ PIPELINE_STAGE(ROUTER, IN, DEFRAG, 2, "lr_in_defrag") \
+ PIPELINE_STAGE(ROUTER, IN, UNSNAT, 3, "lr_in_unsnat") \
+ PIPELINE_STAGE(ROUTER, IN, DNAT, 4, "lr_in_dnat") \
+ PIPELINE_STAGE(ROUTER, IN, ND_RA_OPTIONS, 5, "lr_in_nd_ra_options") \
+ PIPELINE_STAGE(ROUTER, IN, ND_RA_RESPONSE, 6, "lr_in_nd_ra_response") \
+ PIPELINE_STAGE(ROUTER, IN, IP_ROUTING, 7, "lr_in_ip_routing") \
+ PIPELINE_STAGE(ROUTER, IN, ARP_RESOLVE, 8, "lr_in_arp_resolve") \
+ PIPELINE_STAGE(ROUTER, IN, GW_REDIRECT, 9, "lr_in_gw_redirect") \
+ PIPELINE_STAGE(ROUTER, IN, ARP_REQUEST, 10, "lr_in_arp_request") \
\
/* Logical router egress stages. */ \
PIPELINE_STAGE(ROUTER, OUT, UNDNAT, 0, "lr_out_undnat") \
@@ -159,11 +161,12 @@ enum ovn_stage {
#define OVN_ACL_PRI_OFFSET 1000
/* Register definitions specific to switches. */
-#define REGBIT_CONNTRACK_DEFRAG "reg0[0]"
-#define REGBIT_CONNTRACK_COMMIT "reg0[1]"
-#define REGBIT_CONNTRACK_NAT "reg0[2]"
-#define REGBIT_DHCP_OPTS_RESULT "reg0[3]"
+#define REGBIT_CONNTRACK_DEFRAG "reg0[0]"
+#define REGBIT_CONNTRACK_COMMIT "reg0[1]"
+#define REGBIT_CONNTRACK_NAT "reg0[2]"
+#define REGBIT_DHCP_OPTS_RESULT "reg0[3]"
#define REGBIT_DNS_LOOKUP_RESULT "reg0[4]"
+#define REGBIT_ND_RA_OPTS_RESULT "reg0[5]"
/* Register definitions for switches and routers. */
#define REGBIT_NAT_REDIRECT "reg9[0]"
@@ -2868,7 +2871,11 @@ build_pre_acls(struct ovn_datapath *od, struct hmap *lflows)
*
* Not to do conntrack on ND packets. */
ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_ACL, 110, "nd", "next;");
+ ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_ACL, 110, "(nd_rs || nd_ra)",
+ "next;");
ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 110, "nd", "next;");
+ ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 110,
+ "(nd_rs || nd_ra)", "next;");
/* Ingress and Egress Pre-ACL Table (Priority 100).
*
@@ -5319,7 +5326,103 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
sset_destroy(&all_ips);
}
- /* Logical router ingress table 5: IP Routing.
+ /* Logical router ingress table 5 and 6: IPv6 Router Adv (RA) options and
+ * response. */
+ HMAP_FOR_EACH (op, key_node, ports) {
+ if (!op->nbrp || op->nbrp->peer || !op->peer) {
+ continue;
+ }
+
+ if (!op->lrp_networks.n_ipv6_addrs) {
+ continue;
+ }
+
+ const char *address_mode = smap_get(
+ &op->nbrp->ipv6_ra_configs, "address_mode");
+
+ if (!address_mode) {
+ continue;
+ }
+ if (strcmp(address_mode, "slaac") &&
+ strcmp(address_mode, "dhcpv6_stateful") &&
+ strcmp(address_mode, "dhcpv6_stateless")) {
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+ VLOG_WARN_RL(&rl, "Invalid address mode [%s] defined",
+ address_mode);
+ continue;
+ }
+
+ ds_clear(&match);
+ ds_put_format(&match, "inport == %s && ip6.dst == ff02::2 && nd_rs",
+ op->json_key);
+ ds_clear(&actions);
+
+ const char *mtu_s = smap_get(
+ &op->nbrp->ipv6_ra_configs, "mtu");
+
+ /* As per RFC 2460, 1280 is minimum IPv6 MTU. */
+ uint32_t mtu = (mtu_s && atoi(mtu_s) >= 1280) ? atoi(mtu_s) : 0;
+
+ ds_put_format(&actions, REGBIT_ND_RA_OPTS_RESULT" = put_nd_ra_opts("
+ "addr_mode = \"%s\", slla = %s",
+ address_mode, op->lrp_networks.ea_s);
+ if (mtu > 0) {
+ ds_put_format(&actions, ", mtu = %u", mtu);
+ }
+
+ bool add_rs_response_flow = false;
+
+ 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;
+ }
+
+ /* Add the prefix option if the address mode is slaac or
+ * dhcpv6_stateless. */
+ if (strcmp(address_mode, "dhcpv6_stateful")) {
+ ds_put_format(&actions, ", prefix = %s/%u",
+ op->lrp_networks.ipv6_addrs[i].network_s,
+ op->lrp_networks.ipv6_addrs[i].plen);
+ }
+ add_rs_response_flow = true;
+ }
+
+ if (add_rs_response_flow) {
+ ds_put_cstr(&actions, "); next;");
+ ovn_lflow_add(lflows, op->od, S_ROUTER_IN_ND_RA_OPTIONS, 50,
+ ds_cstr(&match), ds_cstr(&actions));
+ ds_clear(&actions);
+ ds_clear(&match);
+ ds_put_format(&match, "inport == %s && ip6.dst == ff02::2 && "
+ "nd_ra && "REGBIT_ND_RA_OPTS_RESULT, op->json_key);
+
+ char ip6_str[INET6_ADDRSTRLEN + 1];
+ struct in6_addr lla;
+ in6_generate_lla(op->lrp_networks.ea, &lla);
+ memset(ip6_str, 0, sizeof(ip6_str));
+ ipv6_string_mapped(ip6_str, &lla);
+ ds_put_format(&actions, "eth.dst = eth.src; eth.src = %s; "
+ "ip6.dst = ip6.src; ip6.src = %s; "
+ "outport = inport; flags.loopback = 1; "
+ "output;",
+ op->lrp_networks.ea_s, ip6_str);
+ ovn_lflow_add(lflows, op->od, S_ROUTER_IN_ND_RA_RESPONSE, 50,
+ ds_cstr(&match), ds_cstr(&actions));
+ }
+ }
+
+ /* Logical router ingress table 5, 6: RS 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_ND_RA_OPTIONS, 0, "1", "next;");
+ ovn_lflow_add(lflows, od, S_ROUTER_IN_ND_RA_RESPONSE, 0, "1", "next;");
+ }
+
+ /* Logical router ingress table 7: 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
@@ -5361,7 +5464,7 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
/* XXX destination unreachable */
- /* Local router ingress table 6: ARP Resolution.
+ /* Local router ingress table 8: 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
@@ -5556,7 +5659,7 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
"get_nd(outport, xxreg0); next;");
}
- /* Logical router ingress table 7: Gateway redirect.
+ /* Logical router ingress table 9: Gateway redirect.
*
* For traffic with outport equal to the l3dgw_port
* on a distributed router, this table redirects a subset
@@ -5596,7 +5699,7 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
ovn_lflow_add(lflows, od, S_ROUTER_IN_GW_REDIRECT, 0, "1", "next;");
}
- /* Local router ingress table 8: ARP request.
+ /* Local router ingress table 10: 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 a077bfb81..fcd878cf2 100644
--- a/ovn/ovn-nb.ovsschema
+++ b/ovn/ovn-nb.ovsschema
@@ -1,7 +1,7 @@
{
"name": "OVN_Northbound",
- "version": "5.8.0",
- "cksum": "2812300190 16766",
+ "version": "5.8.1",
+ "cksum": "607160660 16929",
"tables": {
"NB_Global": {
"columns": {
@@ -222,6 +222,9 @@
"mac": {"type": "string"},
"peer": {"type": {"key": "string", "min": 0, "max": 1}},
"enabled": {"type": {"key": "boolean", "min": 0, "max": 1}},
+ "ipv6_ra_configs": {
+ "type": {"key": "string", "value": "string",
+ "min": 0, "max": "unlimited"}},
"external_ids": {
"type": {"key": "string", "value": "string",
"min": 0, "max": "unlimited"}}},
diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml
index 9869d7ed7..8ad53cd7d 100644
--- a/ovn/ovn-nb.xml
+++ b/ovn/ovn-nb.xml
@@ -1332,6 +1332,45 @@
port has all ingress and egress traffic dropped.
+
+
+ This column defines the IPv6 ND RA address mode and ND MTU Option to be
+ included by ovn-controller
when it replies to the IPv6
+ Router solicitation requests.
+
+
+
+ The address mode to be used for IPv6 address configuration.
+ The supported values are:
+
+ -
+
slaac
: Address configuration using Router
+ Advertisement (RA) packet. The IPv6 prefixes defined in the
+ table's
+ column will
+ be included in the RA's ICMPv6 option - Prefix information.
+
+
+ -
+
dhcpv6_stateful
: Address configuration using DHCPv6.
+
+
+ -
+
dhcpv6_stateless
: Address configuration using Router
+ Advertisement (RA) packet. Other IPv6 options are provided by
+ DHCPv6.
+
+
+
+
+
+ The recommended MTU for the link. Default is 0, which means no MTU
+ Option will be included in RA packet replied by ovn-controller.
+ Per RFC 2460, the mtu value is recommended no less than 1280, so
+ any mtu value less than 1280 will be considered as no MTU Option.
+
+
+
Additional options for the logical router port.
diff --git a/ovn/ovn-sb.xml b/ovn/ovn-sb.xml
index fab3f9de6..2e4f28b96 100644
--- a/ovn/ovn-sb.xml
+++ b/ovn/ovn-sb.xml
@@ -911,6 +911,10 @@
nd
expands to icmp6.type == {135, 136} && icmp6.code == 0 && ip.ttl == 255
nd_ns
expands to icmp6.type == 135 && icmp6.code == 0 && ip.ttl == 255
nd_na
expands to icmp6.type == 136 && icmp6.code == 0 && ip.ttl == 255
+ nd_rs
expands to icmp6.type == 133 &&
+ icmp6.code == 0 && ip.ttl == 255
+ nd_ra
expands to icmp6.type == 134 &&
+ icmp6.code == 0 && ip.ttl == 255
tcp
expands to ip.proto == 6
udp
expands to ip.proto == 17
sctp
expands to ip.proto == 132
diff --git a/tests/ovn.at b/tests/ovn.at
index e56dc6232..3aa4e5e22 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -7847,6 +7847,255 @@ OVN_CLEANUP([hv1],[hv2],[hv3])
AT_CLEANUP
+AT_SETUP([ovn -- IPv6 ND Router Solicitation responder])
+AT_KEYWORDS([ovn-nd_ra])
+AT_SKIP_IF([test $HAVE_PYTHON = no])
+ovn_start
+
+# In this test case we create 1 lswitch with 3 VIF ports attached,
+# and a lrouter connected to the lswitch.
+# We generate the Router solicitation packet and verify the Router Advertisement
+# reply packet from the ovn-controller.
+
+# Create hypervisor and logical switch lsw0, logical router lr0, attach lsw0
+# onto lr0, set Logical_Router_Port.ipv6_ra_configs:address_mode column to
+# 'slaac' to allow lrp0 send RA for SLAAC mode.
+ovn-nbctl ls-add lsw0
+ovn-nbctl lr-add lr0
+ovn-nbctl lrp-add lr0 lrp0 fa:16:3e:00:00:01 fdad:1234:5678::1/64
+ovn-nbctl set Logical_Router_Port lrp0 ipv6_ra_configs:address_mode="slaac"
+ovn-nbctl \
+ -- lsp-add lsw0 lsp0 \
+ -- set Logical_Switch_Port lsp0 type=router \
+ options:router-port=lrp0 \
+ addresses='"fa:16:3e:00:00:01 fdad:1234:5678::1"'
+net_add n1
+sim_add hv1
+as hv1
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.2
+
+ovn-nbctl lsp-add lsw0 lp1
+ovn-nbctl lsp-set-addresses lp1 "fa:16:3e:00:00:02 10.0.0.12 fdad:1234:5678:0:f816:3eff:fe:2"
+ovn-nbctl lsp-set-port-security lp1 "fa:16:3e:00:00:02 10.0.0.12 fdad:1234:5678:0:f816:3eff:fe:2"
+
+ovn-nbctl lsp-add lsw0 lp2
+ovn-nbctl lsp-set-addresses lp2 "fa:16:3e:00:00:03 10.0.0.13 fdad:1234:5678:0:f816:3eff:fe:3"
+ovn-nbctl lsp-set-port-security lp2 "fa:16:3e:00:00:03 10.0.0.13 fdad:1234:5678:0:f816:3eff:fe:3"
+
+ovn-nbctl lsp-add lsw0 lp3
+ovn-nbctl lsp-set-addresses lp3 "fa:16:3e:00:00:04 10.0.0.14 fdad:1234:5678:0:f816:3eff:fe:4"
+ovn-nbctl lsp-set-port-security lp3 "fa:16:3e:00:00:04 10.0.0.14 fdad:1234:5678:0:f816:3eff:fe:4"
+
+# 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
+ovn-nbctl acl-add lsw0 to-lport 1002 'outport == "lp3" && ip6 && icmp6' allow-related
+
+ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=lp1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+ options:rxq_pcap=hv1/vif1-rx.pcap \
+ ofport-request=1
+
+ovs-vsctl -- add-port br-int hv1-vif2 -- \
+ set interface hv1-vif2 external-ids:iface-id=lp2 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+ options:rxq_pcap=hv1/vif2-rx.pcap \
+ ofport-request=2
+
+ovs-vsctl -- add-port br-int hv1-vif3 -- \
+ set interface hv1-vif3 external-ids:iface-id=lp3 \
+ options:tx_pcap=hv1/vif3-tx.pcap \
+ options:rxq_pcap=hv1/vif3-rx.pcap \
+ ofport-request=3
+
+# Allow some time for ovn-northd and ovn-controller to catch up.
+# XXX This should be more systematic.
+sleep 1
+
+reset_pcap_file() {
+ local iface=$1
+ local pcap_file=$2
+ ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \
+options:rxq_pcap=dummy-rx.pcap
+ rm -f ${pcap_file}*.pcap
+ ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \
+options:rxq_pcap=${pcap_file}-rx.pcap
+}
+
+# Make sure that ovn-controller has installed the corresponding OF Flow.
+OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`])
+
+# This shell function sends a Router Solicitation packet.
+# test_ipv6_ra INPORT SRC_MAC SRC_LLA ADDR_MODE MTU RA_PREFIX_OPT
+test_ipv6_ra() {
+ local inport=$1 src_mac=$2 src_lla=$3 addr_mode=$4 mtu=$5 prefix_opt=$6
+ local request=333300000002${src_mac}86dd6000000000103aff${src_lla}ff02000000000000000000000000000285000efc000000000101${src_mac}
+
+ local len=24
+ local mtu_opt=""
+ if test $mtu != 0; then
+ len=`expr $len + 8`
+ mtu_opt=05010000${mtu}
+ fi
+
+ if test ${#prefix_opt} != 0; then
+ prefix_opt=${prefix_opt}fdad1234567800000000000000000000
+ len=`expr $len + ${#prefix_opt} / 2`
+ fi
+
+ len=$(printf "%x" $len)
+ local lrp_mac=fa163e000001
+ local lrp_lla=fe80000000000000f8163efffe000001
+ local reply=${src_mac}${lrp_mac}86dd6000000000${len}3aff${lrp_lla}${src_lla}8600XXXXff${addr_mode}ffff00000000000000000101${lrp_mac}${mtu_opt}${prefix_opt}
+ echo $reply >> $inport.expected
+
+ as hv1 ovs-appctl netdev-dummy/receive hv1-vif${inport} $request
+}
+
+AT_CAPTURE_FILE([ofctl_monitor0.log])
+as hv1 ovs-ofctl monitor br-int resume --detach --no-chdir \
+--pidfile=ovs-ofctl0.pid 2> ofctl_monitor0.log
+
+# MTU is not set and the address mode is set to slaac
+addr_mode=00
+default_prefix_option_config=030440c0ffffffffffffffff00000000
+src_mac=fa163e000002
+src_lla=fe80000000000000f8163efffe000002
+test_ipv6_ra 1 $src_mac $src_lla $addr_mode 0 $default_prefix_option_config
+
+# NXT_RESUME should be 1.
+OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
+
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets
+
+cat 1.expected | cut -c -112 > expout
+AT_CHECK([cat 1.packets | cut -c -112], [0], [expout])
+
+# Skipping the ICMPv6 checksum.
+cat 1.expected | cut -c 117- > expout
+AT_CHECK([cat 1.packets | cut -c 117-], [0], [expout])
+
+rm -f *.expected
+reset_pcap_file hv1-vif1 hv1/vif1
+reset_pcap_file hv1-vif2 hv1/vif2
+reset_pcap_file hv1-vif3 hv1/vif3
+
+# Set the MTU to 1500
+ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:mtu=1500
+
+# Make sure that ovn-controller has installed the corresponding OF Flow.
+OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`])
+
+addr_mode=00
+default_prefix_option_config=030440c0ffffffffffffffff00000000
+src_mac=fa163e000003
+src_lla=fe80000000000000f8163efffe000003
+mtu=000005dc
+
+test_ipv6_ra 2 $src_mac $src_lla $addr_mode $mtu $default_prefix_option_config
+
+# NXT_RESUME should be 2.
+OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
+
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
+
+cat 2.expected | cut -c -112 > expout
+AT_CHECK([cat 2.packets | cut -c -112], [0], [expout])
+
+# Skipping the ICMPv6 checksum.
+cat 2.expected | cut -c 117- > expout
+AT_CHECK([cat 2.packets | cut -c 117-], [0], [expout])
+
+rm -f *.expected
+reset_pcap_file hv1-vif1 hv1/vif1
+reset_pcap_file hv1-vif2 hv1/vif2
+reset_pcap_file hv1-vif3 hv1/vif3
+
+# Set the address mode to dhcpv6_stateful
+ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:address_mode=dhcpv6_stateful
+# Make sure that ovn-controller has installed the corresponding OF Flow.
+OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`])
+
+addr_mode=80
+default_prefix_option_config=""
+src_mac=fa163e000004
+src_lla=fe80000000000000f8163efffe000004
+mtu=000005dc
+
+test_ipv6_ra 3 $src_mac $src_lla $addr_mode $mtu $default_prefix_option_config
+
+# NXT_RESUME should be 3.
+OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
+
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif3-tx.pcap > 3.packets
+
+cat 3.expected | cut -c -112 > expout
+AT_CHECK([cat 3.packets | cut -c -112], [0], [expout])
+
+# Skipping the ICMPv6 checksum.
+cat 3.expected | cut -c 117- > expout
+AT_CHECK([cat 3.packets | cut -c 117-], [0], [expout])
+
+rm -f *.expected
+reset_pcap_file hv1-vif1 hv1/vif1
+reset_pcap_file hv1-vif2 hv1/vif2
+reset_pcap_file hv1-vif3 hv1/vif3
+
+# Set the address mode to dhcpv6_stateless
+ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:address_mode=dhcpv6_stateless
+# Make sure that ovn-controller has installed the corresponding OF Flow.
+OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`])
+
+addr_mode=40
+default_prefix_option_config=030440c0ffffffffffffffff00000000
+src_mac=fa163e000002
+src_lla=fe80000000000000f8163efffe000002
+mtu=000005dc
+
+test_ipv6_ra 1 $src_mac $src_lla $addr_mode $mtu $default_prefix_option_config
+
+# NXT_RESUME should be 4.
+OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
+
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets
+
+cat 1.expected | cut -c -112 > expout
+AT_CHECK([cat 1.packets | cut -c -112], [0], [expout])
+
+# Skipping the ICMPv6 checksum.
+cat 1.expected | cut -c 117- > expout
+AT_CHECK([cat 1.packets | cut -c 117-], [0], [expout])
+
+rm -f *.expected
+reset_pcap_file hv1-vif1 hv1/vif1
+reset_pcap_file hv1-vif2 hv1/vif2
+reset_pcap_file hv1-vif3 hv1/vif3
+
+# Set the address mode to invalid.
+ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:address_mode=invalid
+# Make sure that ovn-controller has not installed any OF Flow for IPv6 ND RA.
+OVS_WAIT_UNTIL([test 0 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`])
+
+addr_mode=40
+default_prefix_option_config=""
+src_mac=fa163e000002
+src_lla=fe80000000000000f8163efffe000002
+mtu=000005dc
+
+test_ipv6_ra 1 $src_mac $src_lla $addr_mode $mtu $default_prefix_option_config
+
+# NXT_RESUME should be 4 only.
+OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
+
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets
+AT_CHECK([cat 1.packets], [0], [])
+
+OVN_CLEANUP([hv1])
+AT_CLEANUP
+
AT_SETUP([ovn -- /32 router IP address])
AT_SKIP_IF([test $HAVE_PYTHON = no])
ovn_start
From patchwork Mon Oct 2 16:25:50 2017
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
X-Patchwork-Submitter: Numan Siddique
X-Patchwork-Id: 820622
Return-Path:
X-Original-To: incoming@patchwork.ozlabs.org
Delivered-To: patchwork-incoming@bilbo.ozlabs.org
Authentication-Results: ozlabs.org;
spf=pass (mailfrom) smtp.mailfrom=openvswitch.org
(client-ip=140.211.169.12; helo=mail.linuxfoundation.org;
envelope-from=ovs-dev-bounces@openvswitch.org;
receiver=)
Received: from mail.linuxfoundation.org (mail.linuxfoundation.org
[140.211.169.12])
(using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256
bits)) (No client certificate requested)
by ozlabs.org (Postfix) with ESMTPS id 3y5SJt1KrXz9s7g
for ;
Tue, 3 Oct 2017 03:28:30 +1100 (AEDT)
Received: from mail.linux-foundation.org (localhost [127.0.0.1])
by mail.linuxfoundation.org (Postfix) with ESMTP id 275C1B73;
Mon, 2 Oct 2017 16:25:58 +0000 (UTC)
X-Original-To: dev@openvswitch.org
Delivered-To: ovs-dev@mail.linuxfoundation.org
Received: from smtp1.linuxfoundation.org (smtp1.linux-foundation.org
[172.17.192.35])
by mail.linuxfoundation.org (Postfix) with ESMTPS id AB86BB65
for ; Mon, 2 Oct 2017 16:25:56 +0000 (UTC)
X-Greylist: domain auto-whitelisted by SQLgrey-1.7.6
Received: from mx1.redhat.com (mx1.redhat.com [209.132.183.28])
by smtp1.linuxfoundation.org (Postfix) with ESMTPS id 893851A9
for ; Mon, 2 Oct 2017 16:25:55 +0000 (UTC)
Received: from smtp.corp.redhat.com
(int-mx06.intmail.prod.int.phx2.redhat.com [10.5.11.16])
(using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits))
(No client certificate requested)
by mx1.redhat.com (Postfix) with ESMTPS id 13A85806B3
for ; Mon, 2 Oct 2017 16:25:55 +0000 (UTC)
DMARC-Filter: OpenDMARC Filter v1.3.2 mx1.redhat.com 13A85806B3
Authentication-Results: ext-mx02.extmail.prod.ext.phx2.redhat.com;
dmarc=none (p=none dis=none) header.from=redhat.com
Authentication-Results: ext-mx02.extmail.prod.ext.phx2.redhat.com;
spf=fail smtp.mailfrom=nusiddiq@redhat.com
Received: from numans.blr.redhat.com (dhcp-0-126.blr.redhat.com
[10.70.1.126])
by smtp.corp.redhat.com (Postfix) with ESMTP id B22194D72A;
Mon, 2 Oct 2017 16:25:53 +0000 (UTC)
From: nusiddiq@redhat.com
To: dev@openvswitch.org
Date: Mon, 2 Oct 2017 21:55:50 +0530
Message-Id: <20171002162550.1923-1-nusiddiq@redhat.com>
In-Reply-To: <20171002161109.956-1-nusiddiq@redhat.com>
References: <20171002161109.956-1-nusiddiq@redhat.com>
X-Scanned-By: MIMEDefang 2.79 on 10.5.11.16
X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16
(mx1.redhat.com [10.5.110.26]);
Mon, 02 Oct 2017 16:25:55 +0000 (UTC)
X-Spam-Status: No, score=-5.0 required=5.0 tests=RCVD_IN_DNSWL_HI,
RP_MATCHES_RCVD autolearn=disabled version=3.3.1
X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on
smtp1.linux-foundation.org
Subject: [ovs-dev] [PATCH v9 4/4] ovn: Generate Neighbor Solicitation packet
for unknown MAC IPv6 packets
X-BeenThere: ovs-dev@openvswitch.org
X-Mailman-Version: 2.1.12
Precedence: list
List-Id:
List-Unsubscribe: ,
List-Archive:
List-Post:
List-Help:
List-Subscribe: ,
MIME-Version: 1.0
Sender: ovs-dev-bounces@openvswitch.org
Errors-To: ovs-dev-bounces@openvswitch.org
From: Numan Siddique
In the router ingress pipeline, if the destination mac is unresolved
by the time the packet reaches the ARP_REQUEST stage, OVN should generate an
IPv6 Neighbor Solicitation packet to learn the MAC address. This feature is
presently missing. This patch adds this feature. A new action "nd_ns" is
added which replaces an IPv6 packet being processed with an IPv6 Neighbor
Solicitation packet. ovn-northd adds a flow in the ARP_REQUEST router ingress
pipeline stage if the eth.dst is zero which applies this action. This action is
similar to the IPv4 counterpart "arp" action.
OVN already has the support to learn the MAC from the IPv6 Neighbor Advertisement
packets and storing in the south bound MAC_Binding table.
Signed-off-by: Numan Siddique
Acked-by: Mark Michelson
---
include/ovn/actions.h | 9 +++-
ovn/controller/pinctrl.c | 122 +++++++++++++++++++++++---------------------
ovn/lib/actions.c | 22 ++++++++
ovn/northd/ovn-northd.8.xml | 24 ++++++---
ovn/northd/ovn-northd.c | 8 ++-
ovn/ovn-sb.xml | 37 ++++++++++++++
ovn/utilities/ovn-trace.c | 30 +++++++++++
tests/ovn.at | 116 +++++++++++++++++++++++++++++++++++++++++
8 files changed, 302 insertions(+), 66 deletions(-)
diff --git a/include/ovn/actions.h b/include/ovn/actions.h
index 15cee478d..8c7208ffc 100644
--- a/include/ovn/actions.h
+++ b/include/ovn/actions.h
@@ -73,7 +73,8 @@ struct simap;
OVNACT(SET_QUEUE, ovnact_set_queue) \
OVNACT(DNS_LOOKUP, ovnact_dns_lookup) \
OVNACT(LOG, ovnact_log) \
- OVNACT(PUT_ND_RA_OPTS, ovnact_put_opts)
+ OVNACT(PUT_ND_RA_OPTS, ovnact_put_opts) \
+ OVNACT(ND_NS, ovnact_nest)
/* enum ovnact_type, with a member OVNACT_ for each action. */
enum OVS_PACKED_ENUM ovnact_type {
@@ -427,6 +428,12 @@ enum action_opcode {
* - Any number of ICMPv6 options.
*/
ACTION_OPCODE_PUT_ND_RA_OPTS,
+
+ /* "nd_ns { ...actions... }".
+ *
+ * The actions, in OpenFlow 1.3 format, follow the action_header.
+ */
+ ACTION_OPCODE_ND_NS,
};
/* Header. */
diff --git a/ovn/controller/pinctrl.c b/ovn/controller/pinctrl.c
index 3a1348937..5aedf7d0d 100644
--- a/ovn/controller/pinctrl.c
+++ b/ovn/controller/pinctrl.c
@@ -85,6 +85,9 @@ static void pinctrl_handle_put_nd_ra_opts(
const struct flow *ip_flow, struct dp_packet *pkt_in,
struct ofputil_packet_in *pin, struct ofpbuf *userdata,
struct ofpbuf *continuation);
+static void pinctrl_handle_nd_ns(const struct flow *ip_flow,
+ const struct match *md,
+ struct ofpbuf *userdata);
COVERAGE_DEFINE(pinctrl_drop_put_mac_binding);
@@ -132,6 +135,43 @@ set_switch_config(struct rconn *swconn,
}
static void
+set_actions_and_enqueue_msg(const struct dp_packet *packet,
+ const struct match *md,
+ struct ofpbuf *userdata)
+{
+ /* Copy metadata from 'md' into the packet-out via "set_field"
+ * actions, then add actions from 'userdata'.
+ */
+ uint64_t ofpacts_stub[4096 / 8];
+ struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub);
+ enum ofp_version version = rconn_get_version(swconn);
+
+ reload_metadata(&ofpacts, md);
+ enum ofperr error = ofpacts_pull_openflow_actions(userdata, userdata->size,
+ version, NULL, NULL,
+ &ofpacts);
+ if (error) {
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+ VLOG_WARN_RL(&rl, "failed to parse actions from userdata (%s)",
+ ofperr_to_string(error));
+ ofpbuf_uninit(&ofpacts);
+ return;
+ }
+
+ struct ofputil_packet_out po = {
+ .packet = dp_packet_data(packet),
+ .packet_len = dp_packet_size(packet),
+ .buffer_id = UINT32_MAX,
+ .ofpacts = ofpacts.data,
+ .ofpacts_len = ofpacts.size,
+ };
+ match_set_in_port(&po.flow_metadata, OFPP_CONTROLLER);
+ enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version);
+ queue_msg(ofputil_encode_packet_out(&po, proto));
+ ofpbuf_uninit(&ofpacts);
+}
+
+static void
pinctrl_handle_arp(const struct flow *ip_flow, const struct match *md,
struct ofpbuf *userdata)
{
@@ -166,40 +206,8 @@ pinctrl_handle_arp(const struct flow *ip_flow, const struct match *md,
ip_flow->vlans[0].tci);
}
- /* Compose actions.
- *
- * First, copy metadata from 'md' into the packet-out via "set_field"
- * actions, then add actions from 'userdata'.
- */
- uint64_t ofpacts_stub[4096 / 8];
- struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub);
- enum ofp_version version = rconn_get_version(swconn);
-
- reload_metadata(&ofpacts, md);
- enum ofperr error = ofpacts_pull_openflow_actions(userdata, userdata->size,
- version, NULL, NULL,
- &ofpacts);
- if (error) {
- static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
- VLOG_WARN_RL(&rl, "failed to parse arp actions (%s)",
- ofperr_to_string(error));
- goto exit;
- }
-
- struct ofputil_packet_out po = {
- .packet = dp_packet_data(&packet),
- .packet_len = dp_packet_size(&packet),
- .buffer_id = UINT32_MAX,
- .ofpacts = ofpacts.data,
- .ofpacts_len = ofpacts.size,
- };
- match_set_in_port(&po.flow_metadata, OFPP_CONTROLLER);
- enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version);
- queue_msg(ofputil_encode_packet_out(&po, proto));
-
-exit:
+ set_actions_and_enqueue_msg(&packet, md, userdata);
dp_packet_uninit(&packet);
- ofpbuf_uninit(&ofpacts);
}
static void
@@ -994,6 +1002,10 @@ process_packet_in(const struct ofp_header *msg, struct controller_ctx *ctx)
&continuation);
break;
+ case ACTION_OPCODE_ND_NS:
+ pinctrl_handle_nd_ns(&headers, &pin.flow_metadata, &userdata);
+ break;
+
default:
VLOG_WARN_RL(&rl, "unrecognized packet-in opcode %"PRIu32,
ntohl(ah->opcode));
@@ -1812,9 +1824,6 @@ pinctrl_handle_nd_na(const struct flow *ip_flow, const struct match *md,
return;
}
- enum ofp_version version = rconn_get_version(swconn);
- enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version);
-
uint64_t packet_stub[128 / 8];
struct dp_packet packet;
dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub);
@@ -1827,35 +1836,32 @@ pinctrl_handle_nd_na(const struct flow *ip_flow, const struct match *md,
&ip_flow->nd_target, &ip_flow->ipv6_src,
htonl(ND_RSO_SOLICITED | ND_RSO_OVERRIDE));
- /* Reload previous packet metadata. */
- uint64_t ofpacts_stub[4096 / 8];
- struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub);
- reload_metadata(&ofpacts, md);
+ /* Reload previous packet metadata and set actions from userdata. */
+ set_actions_and_enqueue_msg(&packet, md, userdata);
+ dp_packet_uninit(&packet);
+}
- enum ofperr error = ofpacts_pull_openflow_actions(userdata, userdata->size,
- version, NULL, NULL,
- &ofpacts);
- if (error) {
+static void
+pinctrl_handle_nd_ns(const struct flow *ip_flow, const struct match *md,
+ struct ofpbuf *userdata)
+{
+ /* This action only works for IPv6 packets. */
+ if (get_dl_type(ip_flow) != htons(ETH_TYPE_IPV6)) {
static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
- VLOG_WARN_RL(&rl, "failed to parse actions for 'na' (%s)",
- ofperr_to_string(error));
- goto exit;
+ VLOG_WARN_RL(&rl, "NS action on non-IPv6 packet");
+ return;
}
- struct ofputil_packet_out po = {
- .packet = dp_packet_data(&packet),
- .packet_len = dp_packet_size(&packet),
- .buffer_id = UINT32_MAX,
- .ofpacts = ofpacts.data,
- .ofpacts_len = ofpacts.size,
- };
- match_set_in_port(&po.flow_metadata, OFPP_CONTROLLER);
+ uint64_t packet_stub[128 / 8];
+ struct dp_packet packet;
+ dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub);
- queue_msg(ofputil_encode_packet_out(&po, proto));
+ compose_nd_ns(&packet, ip_flow->dl_src, &ip_flow->ipv6_src,
+ &ip_flow->ipv6_dst);
-exit:
+ /* Reload previous packet metadata and set actions from userdata. */
+ set_actions_and_enqueue_msg(&packet, md, userdata);
dp_packet_uninit(&packet);
- ofpbuf_uninit(&ofpacts);
}
static void
diff --git a/ovn/lib/actions.c b/ovn/lib/actions.c
index 8d5863c0a..d0a4d7753 100644
--- a/ovn/lib/actions.c
+++ b/ovn/lib/actions.c
@@ -1134,6 +1134,12 @@ parse_ND_NA(struct action_context *ctx)
}
static void
+parse_ND_NS(struct action_context *ctx)
+{
+ parse_nested_action(ctx, OVNACT_ND_NS, "ip6");
+}
+
+static void
parse_CLONE(struct action_context *ctx)
{
parse_nested_action(ctx, OVNACT_CLONE, NULL);
@@ -1161,6 +1167,12 @@ format_ND_NA(const struct ovnact_nest *nest, struct ds *s)
}
static void
+format_ND_NS(const struct ovnact_nest *nest, struct ds *s)
+{
+ format_nested_action(nest, "nd_ns", s);
+}
+
+static void
format_CLONE(const struct ovnact_nest *nest, struct ds *s)
{
format_nested_action(nest, "clone", s);
@@ -1207,6 +1219,14 @@ encode_ND_NA(const struct ovnact_nest *on,
}
static void
+encode_ND_NS(const struct ovnact_nest *on,
+ const struct ovnact_encode_params *ep,
+ struct ofpbuf *ofpacts)
+{
+ encode_nested_neighbor_actions(on, ep, ACTION_OPCODE_ND_NS, ofpacts);
+}
+
+static void
encode_CLONE(const struct ovnact_nest *on,
const struct ovnact_encode_params *ep,
struct ofpbuf *ofpacts)
@@ -2146,6 +2166,8 @@ parse_action(struct action_context *ctx)
parse_ARP(ctx);
} else if (lexer_match_id(ctx->lexer, "nd_na")) {
parse_ND_NA(ctx);
+ } else if (lexer_match_id(ctx->lexer, "nd_ns")) {
+ parse_ND_NS(ctx);
} else if (lexer_match_id(ctx->lexer, "get_arp")) {
parse_get_mac_bind(ctx, 32, ovnact_put_GET_ARP(ctx->ovnacts));
} else if (lexer_match_id(ctx->lexer, "put_arp")) {
diff --git a/ovn/northd/ovn-northd.8.xml b/ovn/northd/ovn-northd.8.xml
index a994abf78..17123c690 100644
--- a/ovn/northd/ovn-northd.8.xml
+++ b/ovn/northd/ovn-northd.8.xml
@@ -1915,15 +1915,15 @@ next;
In the common case where the Ethernet destination has been resolved, this
- table outputs the packet. Otherwise, it composes and sends an ARP
- request. It holds the following flows:
+ table outputs the packet. Otherwise, it composes and sends an ARP or
+ IPv6 Neighbor Solicitation request. It holds the following flows:
-
- Unknown MAC address. A priority-100 flow with match eth.dst ==
- 00:00:00:00:00:00
has the following actions:
+ Unknown MAC address. A priority-100 flow for IPv4 packets with match
+ eth.dst == 00:00:00:00:00:00
has the following actions:
@@ -1937,13 +1937,25 @@ arp {
+ Unknown MAC address. A priority-100 flow for IPv6 packets with match
+ eth.dst == 00:00:00:00:00:00
has the following actions:
+
+
+
+nd_ns {
+ nd.target = xxreg0;
+ output;
+};
+
+
+
(Ingress table IP Routing
initialized reg1
with the IP address owned by outport
and
- reg0
with the next-hop IP address)
+ (xx)reg0
with the next-hop IP address)
- The IP packet that triggers the ARP request is dropped.
+ The IP packet that triggers the ARP/IPv6 NS request is dropped.
diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c
index 3da20d25b..b4ea34bc6 100644
--- a/ovn/northd/ovn-northd.c
+++ b/ovn/northd/ovn-northd.c
@@ -5703,7 +5703,7 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
*
* In the common case where the Ethernet destination has been resolved,
* this table outputs the packet (priority 0). Otherwise, it composes
- * and sends an ARP request (priority 100). */
+ * and sends an ARP/IPv6 NA request (priority 100). */
HMAP_FOR_EACH (od, key_node, datapaths) {
if (!od->nbr) {
continue;
@@ -5718,6 +5718,12 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
"arp.op = 1; " /* ARP request */
"output; "
"};");
+ ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_REQUEST, 100,
+ "eth.dst == 00:00:00:00:00:00",
+ "nd_ns { "
+ "nd.target = xxreg0; "
+ "output; "
+ "};");
ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_REQUEST, 0, "1", "output;");
}
diff --git a/ovn/ovn-sb.xml b/ovn/ovn-sb.xml
index 2e4f28b96..ca8cbecdd 100644
--- a/ovn/ovn-sb.xml
+++ b/ovn/ovn-sb.xml
@@ -1258,6 +1258,43 @@
Example: put_arp(inport, arp.spa, arp.sha);
+ nd_ns { action;
... };
+ -
+
+ Temporarily replaces the IPv6 packet being processed by an IPv6
+ Neighbor Solicitation packet and executes each nested
+ action on the IPv6 NS packet. Actions following the
+ nd_ns action, if any, apply to the original, unmodified
+ packet.
+
+
+
+ The IPv6 NS packet that this action operates on is initialized
+ based on the IPv6 packet being processed, as follows. These are
+ default values that the nested actions will probably want to
+ change:
+
+
+
+ eth.src
unchanged
+ eth.dst
set to IPv6 multicast MAC address
+ eth.type = 0x86dd
+ ip6.src
copied from ip6.src
+ -
+
ip6.dst
set to IPv6 Solicited-Node multicast address
+
+ icmp6.type = 135
(Neighbor Solicitation)
+ nd.target
copied from ip6.dst
+
+
+
+ The IPv6 NS packet has the same VLAN header, if any, as the IP
+ packet it replaces.
+
+
+ Prerequisite: ip6
+
+
-
nd_na { action;
... };
diff --git a/ovn/utilities/ovn-trace.c b/ovn/utilities/ovn-trace.c
index 211148b8b..e457284fc 100644
--- a/ovn/utilities/ovn-trace.c
+++ b/ovn/utilities/ovn-trace.c
@@ -1510,6 +1510,31 @@ execute_nd_na(const struct ovnact_nest *on, const struct ovntrace_datapath *dp,
}
static void
+execute_nd_ns(const struct ovnact_nest *on, const struct ovntrace_datapath *dp,
+ const struct flow *uflow, uint8_t table_id,
+ enum ovnact_pipeline pipeline, struct ovs_list *super)
+{
+ struct flow na_flow = *uflow;
+
+ /* Update fields for NA. */
+ na_flow.dl_src = uflow->dl_src;
+ na_flow.ipv6_src = uflow->ipv6_src;
+ na_flow.ipv6_dst = uflow->ipv6_dst;
+ struct in6_addr sn_addr;
+ in6_addr_solicited_node(&sn_addr, &uflow->ipv6_dst);
+ ipv6_multicast_to_ethernet(&na_flow.dl_dst, &sn_addr);
+ na_flow.tp_src = htons(135);
+ na_flow.arp_sha = eth_addr_zero;
+ na_flow.arp_tha = uflow->dl_dst;
+
+ struct ovntrace_node *node = ovntrace_node_append(
+ super, OVNTRACE_NODE_TRANSFORMATION, "nd_ns");
+
+ trace_actions(on->nested, on->nested_len, dp, &na_flow,
+ table_id, pipeline, &node->subs);
+}
+
+static void
execute_get_mac_bind(const struct ovnact_get_mac_bind *bind,
const struct ovntrace_datapath *dp,
struct flow *uflow, struct ovs_list *super)
@@ -1811,6 +1836,11 @@ trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len,
super);
break;
+ case OVNACT_ND_NS:
+ execute_nd_ns(ovnact_get_ND_NS(a), dp, uflow, table_id, pipeline,
+ super);
+ break;
+
case OVNACT_GET_ARP:
execute_get_mac_bind(ovnact_get_GET_ARP(a), dp, uflow, super);
break;
diff --git a/tests/ovn.at b/tests/ovn.at
index 3aa4e5e22..13cdc1679 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -993,6 +993,16 @@ reg1[0] = put_dhcp_opts(offerip="xyzzy");
reg1[0] = put_dhcp_opts(offerip=1.2.3.4, domain=1.2.3.4);
DHCPv4 option domain requires string value.
+# nd_ns
+nd_ns { nd.target = xxreg0; output; };
+ encodes as controller(userdata=00.00.00.09.00.00.00.00.ff.ff.00.18.00.00.23.20.00.06.00.80.00.00.00.00.00.01.de.10.00.01.2e.10.ff.ff.00.10.00.00.23.20.00.0e.ff.f8.40.00.00.00)
+ has prereqs ip6
+
+nd_ns { };
+ formats as nd_ns { drop; };
+ encodes as controller(userdata=00.00.00.09.00.00.00.00)
+ has prereqs ip6
+
# nd_na
nd_na { eth.src = 12:34:56:78:9a:bc; nd.tll = 12:34:56:78:9a:bc; outport = inport; inport = ""; /* Allow sending out inport. */ output; };
formats as nd_na { eth.src = 12:34:56:78:9a:bc; nd.tll = 12:34:56:78:9a:bc; outport = inport; inport = ""; output; };
@@ -8795,6 +8805,112 @@ OVN_CLEANUP([gw1],[gw2],[hv1])
AT_CLEANUP
+AT_SETUP([ovn -- IPv6 Neighbor Solicitation for unknown MAC])
+AT_KEYWORDS([ovn-nd_ns for unknown mac])
+AT_SKIP_IF([test $HAVE_PYTHON = no])
+ovn_start
+
+ovn-nbctl ls-add sw0_ip6
+ovn-nbctl lsp-add sw0_ip6 sw0_ip6-port1
+ovn-nbctl lsp-set-addresses sw0_ip6-port1 \
+"50:64:00:00:00:02 aef0::5264:00ff:fe00:0002"
+
+ovn-nbctl lsp-set-port-security sw0_ip6-port1 \
+"50:64:00:00:00:02 aef0::5264:00ff:fe00:0002"
+
+ovn-nbctl lr-add lr0_ip6
+ovn-nbctl lrp-add lr0_ip6 lrp0_ip6 00:00:00:00:af:01 aef0::/64
+ovn-nbctl lsp-add sw0_ip6 lrp0_ip6-attachment
+ovn-nbctl lsp-set-type lrp0_ip6-attachment router
+ovn-nbctl lsp-set-addresses lrp0_ip6-attachment 00:00:00:00:af:01
+ovn-nbctl lsp-set-options lrp0_ip6-attachment router-port=lrp0_ip6
+ovn-nbctl set logical_router_port lrp0_ip6 ipv6_ra_configs:address_mode=slaac
+
+ovn-nbctl ls-add public
+ovn-nbctl lsp-add public ln-public
+ovn-nbctl lsp-set-addresses ln-public unknown
+ovn-nbctl lsp-set-type ln-public localnet
+ovn-nbctl lsp-set-options ln-public network_name=phys
+
+ovn-nbctl lrp-add lr0_ip6 ip6_public 00:00:02:01:02:04 \
+2001:db8:1:0:200:02ff:fe01:0204/64 \
+-- set Logical_Router_port ip6_public options:redirect-chassis="hv1"
+
+
+ovn-nbctl lsp-add public rp-ip6_public -- set Logical_Switch_Port \
+rp-ip6_public type=router options:router-port=ip6_public \
+-- lsp-set-addresses rp-ip6_public router
+
+net_add n1
+sim_add hv1
+as hv1
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.2
+
+ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=sw0_ip6-port1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+ options:rxq_pcap=hv1/vif1-rx.pcap \
+ ofport-request=1
+ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
+
+# Allow some time for ovn-northd and ovn-controller to catch up.
+# XXX This should be more systematic.
+sleep 1
+
+trim_zeros() {
+ sed 's/\(00\)\{1,\}$//'
+}
+
+# Test the IPv6 Neighbor Solicitation (NS) - nd_ns action for unknown MAC
+# addresses. ovn-controller should generate an IPv6 NS request for IPv6
+# packets whose MAC is unknown (in the ARP_REQUEST router pipeline stage.
+# test_ipv6 INPORT SRC_MAC DST_MAC SRC_IP DST_IP OUTPORT...
+# This function sends ipv6 packet
+test_ipv6() {
+ local inport=$1 src_mac=$2 dst_mac=$3 src_ip=$4
+ dst_ip=20010db800010000020002fffe010205
+
+ local packet=${dst_mac}${src_mac}86dd6000000000083aff${src_ip}${dst_ip}
+ packet=${packet}8000000000000000
+ shift; shift; shift; shift
+
+ dst_mac=3333ff010205
+ src_mac=000002010204
+ mcast_node_ip=ff0200000000000000000001ff010205
+ expected_packet=${dst_mac}${src_mac}86dd6000000000203aff${src_ip}
+ expected_packet=${expected_packet}${mcast_node_ip}8700XXXX00000000${dst_ip}
+ expected_packet=${expected_packet}0101${src_mac}
+
+ as hv1 ovs-appctl netdev-dummy/receive hv1-vif${inport} $packet
+ echo $expected_packet >> ipv6_ns.expected
+}
+
+src_mac=506400000002
+dst_mac=00000000af01
+src_ip=aef0000000000000526400fffe000002
+# Send an IPv6 packet. Generated IPv6 Neighbor solicitation packet
+# should be received by the ports attached to br-phys.
+test_ipv6 1 $src_mac $dst_mac $src_ip 2
+
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/br-phys_n1-tx.pcap | \
+trim_zeros > 1.packets
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/br-phys-tx.pcap | \
+trim_zeros > 2.packets
+
+cat ipv6_ns.expected | cut -c -112 > expout
+AT_CHECK([cat 1.packets | cut -c -112], [0], [expout])
+AT_CHECK([cat 2.packets | cut -c -112], [0], [expout])
+
+# Skipping the ICMPv6 checksum
+cat ipv6_ns.expected | cut -c 117- > expout
+AT_CHECK([cat 1.packets | cut -c 117-], [0], [expout])
+AT_CHECK([cat 2.packets | cut -c 117-], [0], [expout])
+
+OVN_CLEANUP([hv1])
+
+AT_CLEANUP
+
AT_SETUP([ovn -- options:requested-chassis for logical port])
ovn_start