@@ -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_<ENUM> 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. */
@@ -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
@@ -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")) {
@@ -1923,15 +1923,15 @@ next;
<p>
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:
</p>
<ul>
<li>
<p>
- Unknown MAC address. A priority-100 flow with match <code>eth.dst ==
- 00:00:00:00:00:00</code> has the following actions:
+ Unknown MAC address. A priority-100 flow for IPv4 packets with match
+ <code>eth.dst == 00:00:00:00:00:00</code> has the following actions:
</p>
<pre>
@@ -1945,13 +1945,25 @@ arp {
</pre>
<p>
+ Unknown MAC address. A priority-100 flow for IPv6 packets with match
+ <code>eth.dst == 00:00:00:00:00:00</code> has the following actions:
+ </p>
+
+ <pre>
+nd_ns {
+ nd.target = xxreg0;
+ output;
+};
+ </pre>
+
+ <p>
(Ingress table <code>IP Routing</code> initialized <code>reg1</code>
with the IP address owned by <code>outport</code> and
- <code>reg0</code> with the next-hop IP address)
+ <code>(xx)reg0</code> with the next-hop IP address)
</p>
<p>
- The IP packet that triggers the ARP request is dropped.
+ The IP packet that triggers the ARP/IPv6 NS request is dropped.
</p>
</li>
@@ -5783,7 +5783,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;
@@ -5798,6 +5798,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;");
}
@@ -1258,6 +1258,43 @@
<p><b>Example:</b> <code>put_arp(inport, arp.spa, arp.sha);</code></p>
</dd>
+ <dt><code>nd_ns { <var>action</var>; </code>...<code> };</code></dt>
+ <dd>
+ <p>
+ Temporarily replaces the IPv6 packet being processed by an IPv6
+ Neighbor Solicitation packet and executes each nested
+ <var>action</var> on the IPv6 NS packet. Actions following the
+ <var>nd_ns</var> action, if any, apply to the original, unmodified
+ packet.
+ </p>
+
+ <p>
+ 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:
+ </p>
+
+ <ul>
+ <li><code>eth.src</code> unchanged</li>
+ <li><code>eth.dst</code> set to IPv6 multicast MAC address</li>
+ <li><code>eth.type = 0x86dd</code></li>
+ <li><code>ip6.src</code> copied from <code>ip6.src</code></li>
+ <li>
+ <code>ip6.dst</code> set to IPv6 Solicited-Node multicast address
+ </li>
+ <li><code>icmp6.type = 135</code> (Neighbor Solicitation)</li>
+ <li><code>nd.target</code> copied from <code>ip6.dst</code></li>
+ </ul>
+
+ <p>
+ The IPv6 NS packet has the same VLAN header, if any, as the IP
+ packet it replaces.
+ </p>
+
+ <p><b>Prerequisite:</b> <code>ip6</code></p>
+ </dd>
+
<dt>
<code>nd_na { <var>action</var>; </code>...<code> };</code>
</dt>
@@ -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;
@@ -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; };
@@ -8816,6 +8826,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