diff --git a/lib/packets.h b/lib/packets.h
index 9b98b29cd75..dcfcd04b22e 100644
--- a/lib/packets.h
+++ b/lib/packets.h
@@ -920,6 +920,7 @@ static inline bool ipv6_addr_equals(const struct in6_addr *a,
#endif
}
+/* Checks the IPv6 address in 'mask' for all zeroes. */
static inline bool ipv6_mask_is_any(const struct in6_addr *mask) {
return ipv6_addr_equals(mask, &in6addr_any);
}
diff --git a/ovn/northd/ovn-northd.8.xml b/ovn/northd/ovn-northd.8.xml
index 77974172f7a..1cf6b6e4117 100644
--- a/ovn/northd/ovn-northd.8.xml
+++ b/ovn/northd/ovn-northd.8.xml
@@ -488,7 +488,8 @@ nd_na {
This table adds the DHCPv4 options to a DHCPv4 packet from the
- logical ports configured with IPv4 address(es) and DHCPv4 options.
+ logical ports configured with IPv4 address(es) and DHCPv4 options,
+ and similarly for DHCPv6 options.
@@ -501,13 +502,13 @@ nd_na {
-reg0[3] = put_dhcp_opts(offer_ip = O, options...);
+reg0[3] = put_dhcp_opts(offer_ip = ip, options...);
next;
For DHCPDISCOVER and DHCPREQUEST, this transforms the packet into a
- DHCP reply, adds the DHCP offer IP O and options to the
+ DHCP reply, adds the DHCP offer IP ip and options to the
packet, and stores 1 into reg0[3]. For other kinds of packets, it
just stores 0 into reg0[3]. Either way, it continues to the next
table.
@@ -515,6 +516,29 @@ next;
+
+
+ A priority-100 logical flow is added for these logical ports
+ which matches the IPv6 packet with udp.src
= 546 and
+ udp.dst
= 547 and applies the action
+ put_dhcpv6_opts
and advances the packet to the next
+ table.
+
+
+
+reg0[3] = put_dhcpv6_opts(ia_addr = ip, options...);
+next;
+
+
+
+ For DHCPv6 Solicit/Request/Confirm packets, this transforms the
+ packet into a DHCPv6 Advertise/Reply, adds the DHCPv6 offer IP
+ ip and options to the packet, and stores 1 into reg0[3].
+ For other kinds of packets, it just stores 0 into reg0[3]. Either
+ way, it continues to the next table.
+
+
+
A priority-0 flow that matches all packets to advances to table 11.
@@ -541,7 +565,7 @@ next;
eth.dst = eth.src;
eth.src = E;
-ip4.dst = O;
+ip4.dst = A;
ip4.src = S;
udp.src = 67;
udp.dst = 68;
@@ -552,7 +576,7 @@ output;
where E is the server MAC address and S is the
- server IPv4 address defined in the DHCPv4 options and O is
+ server IPv4 address defined in the DHCPv4 options and A is
the IPv4 address defined in the logical port's addresses column.
@@ -562,6 +586,41 @@ output;
+
+
+ A priority 100 logical flow is added for the logical ports configured
+ with DHCPv6 options which matches IPv6 packets with udp.src == 546
+ && udp.dst == 547 && reg0[3] == 1
and
+ responds back to the inport
after applying these
+ actions. If reg0[3]
is set to 1, it means that the
+ action put_dhcpv6_opts
was successful.
+
+
+
+eth.dst = eth.src;
+eth.src = E;
+ip6.dst = A;
+ip6.src = S;
+udp.src = 547;
+udp.dst = 546;
+outport = P;
+flags.loopback = 1;
+output;
+
+
+
+ where E is the server MAC address and S is the
+ server IPv6 LLA address generated from the server_id
+ defined in the DHCPv6 options and A is
+ the IPv6 address defined in the logical port's addresses column.
+
+
+
+ (This terminates packet processing; the packet does not go on the
+ next ingress table.)
+
+
+
A priority-0 flow that matches all packets to advances to table 12.
@@ -643,7 +702,8 @@ output;
Also a priority 34000 logical flow is added for each logical port which
- has DHCPv4 options defined to allow the DHCPv4 reply packet from the
+ has DHCPv4 options defined to allow the DHCPv4 reply packet and which has
+ DHCPv6 options defined to allow the DHCPv6 reply packet from the
Ingress Table 11: DHCP responses
.
diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c
index 8c7e80ca066..8f995338e72 100644
--- a/ovn/northd/ovn-northd.c
+++ b/ovn/northd/ovn-northd.c
@@ -1807,6 +1807,72 @@ build_dhcpv4_action(struct ovn_port *op, ovs_be32 offer_ip,
return true;
}
+static bool
+build_dhcpv6_action(struct ovn_port *op, struct in6_addr *offer_ip,
+ struct ds *options_action, struct ds *response_action)
+{
+ if (!op->nbsp->dhcpv6_options) {
+ /* CMS has disabled native DHCPv6 for this lport. */
+ return false;
+ }
+
+ struct in6_addr host_ip, mask;
+
+ char *error = ipv6_parse_masked(op->nbsp->dhcpv6_options->cidr, &host_ip,
+ &mask);
+ if (error) {
+ free(error);
+ return false;
+ }
+ struct in6_addr ip6_mask = ipv6_addr_bitxor(offer_ip, &host_ip);
+ ip6_mask = ipv6_addr_bitand(&ip6_mask, &mask);
+ if (!ipv6_mask_is_any(&ip6_mask)) {
+ /* offer_ip doesn't belongs to the cidr defined in lport's DHCPv6
+ * options.*/
+ return false;
+ }
+
+ /* "server_id" should be the MAC address. */
+ const char *server_mac = smap_get(&op->nbsp->dhcpv6_options->options,
+ "server_id");
+ struct eth_addr ea;
+ if (!server_mac || !eth_addr_from_string(server_mac, &ea)) {
+ /* "server_id" should be present in the dhcpv6_options. */
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
+ VLOG_WARN_RL(&rl, "server_id not present in the DHCPv6 options"
+ " for lport %s", op->json_key);
+ return false;
+ }
+
+ /* Get the link local IP of the DHCPv6 server from the server MAC. */
+ struct in6_addr lla;
+ in6_generate_lla(ea, &lla);
+
+ char server_ip[INET6_ADDRSTRLEN + 1];
+ ipv6_string_mapped(server_ip, &lla);
+
+ char ia_addr[INET6_ADDRSTRLEN + 1];
+ ipv6_string_mapped(ia_addr, offer_ip);
+
+ ds_put_format(options_action,
+ REGBIT_DHCP_OPTS_RESULT" = put_dhcpv6_opts(ia_addr = %s, ",
+ ia_addr);
+ struct smap_node *node;
+ SMAP_FOR_EACH (node, &op->nbsp->dhcpv6_options->options) {
+ ds_put_format(options_action, "%s = %s, ", node->key, node->value);
+ }
+ ds_chomp(options_action, ' ');
+ ds_chomp(options_action, ',');
+ ds_put_cstr(options_action, "); next;");
+
+ ds_put_format(response_action, "eth.dst = eth.src; eth.src = %s; "
+ "ip6.dst = ip6.src; ip6.src = %s; udp.src = 547; "
+ "udp.dst = 546; outport = inport; flags.loopback = 1; "
+ "output;",
+ server_mac, server_ip);
+ return true;
+}
+
static bool
has_stateful_acl(struct ovn_datapath *od)
{
@@ -2238,6 +2304,32 @@ build_acls(struct ovn_datapath *od, struct hmap *lflows)
actions);
}
}
+
+ if (od->nbs->ports[i]->dhcpv6_options) {
+ const char *server_mac = smap_get(
+ &od->nbs->ports[i]->dhcpv6_options->options, "server_id");
+ struct eth_addr ea;
+ if (server_mac && eth_addr_from_string(server_mac, &ea)) {
+ /* Get the link local IP of the DHCPv6 server from the
+ * server MAC. */
+ struct in6_addr lla;
+ in6_generate_lla(ea, &lla);
+
+ char server_ip[INET6_ADDRSTRLEN + 1];
+ ipv6_string_mapped(server_ip, &lla);
+
+ struct ds match = DS_EMPTY_INITIALIZER;
+ const char *actions = has_stateful ? "ct_commit; next;" :
+ "next;";
+ ds_put_format(&match, "outport == \"%s\" && eth.src == %s "
+ "&& ip6.src == %s && udp && udp.src == 547 "
+ "&& udp.dst == 546", od->nbs->ports[i]->name,
+ server_mac, server_ip);
+ ovn_lflow_add(
+ lflows, od, S_SWITCH_OUT_ACL, 34000, ds_cstr(&match),
+ actions);
+ }
+ }
}
}
}
@@ -2530,8 +2622,9 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
continue;
}
- if (!op->nbsp->dhcpv4_options) {
- /* CMS has disabled native DHCPv4 for this lport. */
+ if (!op->nbsp->dhcpv4_options && !op->nbsp->dhcpv6_options) {
+ /* CMS has disabled both native DHCPv4 and DHCPv6 for this lport.
+ */
continue;
}
@@ -2564,6 +2657,34 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
break;
}
}
+
+ for (size_t j = 0; j < op->lsp_addrs[i].n_ipv6_addrs; j++) {
+ struct ds options_action = DS_EMPTY_INITIALIZER;
+ struct ds response_action = DS_EMPTY_INITIALIZER;
+ if (build_dhcpv6_action(
+ op, &op->lsp_addrs[i].ipv6_addrs[j].addr,
+ &options_action, &response_action)) {
+ struct ds match = DS_EMPTY_INITIALIZER;
+ ds_put_format(
+ &match, "inport == %s && eth.src == %s"
+ " && ip6.dst == ff02::1:2 && udp.src == 546 &&"
+ " udp.dst == 547", op->json_key,
+ op->lsp_addrs[i].ea_s);
+
+ ovn_lflow_add(lflows, op->od, S_SWITCH_IN_DHCP_OPTIONS, 100,
+ ds_cstr(&match), ds_cstr(&options_action));
+
+ /* If REGBIT_DHCP_OPTS_RESULT is set to 1, it means the
+ * put_dhcpv6_opts action is successful */
+ ds_put_cstr(&match, " && "REGBIT_DHCP_OPTS_RESULT);
+ ovn_lflow_add(lflows, op->od, S_SWITCH_IN_DHCP_RESPONSE, 100,
+ ds_cstr(&match), ds_cstr(&response_action));
+ ds_destroy(&match);
+ ds_destroy(&options_action);
+ ds_destroy(&response_action);
+ break;
+ }
+ }
}
}
@@ -3937,6 +4058,13 @@ static struct dhcp_opts_map supported_dhcp_opts[] = {
DHCP_OPT_T2
};
+static struct dhcp_opts_map supported_dhcpv6_opts[] = {
+ DHCPV6_OPT_IA_ADDR,
+ DHCPV6_OPT_SERVER_ID,
+ DHCPV6_OPT_DOMAIN_SEARCH,
+ DHCPV6_OPT_DNS_SERVER
+};
+
static void
check_and_add_supported_dhcp_opts_to_sb_db(struct northd_context *ctx)
{
@@ -3970,6 +4098,39 @@ check_and_add_supported_dhcp_opts_to_sb_db(struct northd_context *ctx)
hmap_destroy(&dhcp_opts_to_add);
}
+static void
+check_and_add_supported_dhcpv6_opts_to_sb_db(struct northd_context *ctx)
+{
+ struct hmap dhcpv6_opts_to_add = HMAP_INITIALIZER(&dhcpv6_opts_to_add);
+ for (size_t i = 0; (i < sizeof(supported_dhcpv6_opts) /
+ sizeof(supported_dhcpv6_opts[0])); i++) {
+ hmap_insert(&dhcpv6_opts_to_add, &supported_dhcpv6_opts[i].hmap_node,
+ dhcp_opt_hash(supported_dhcpv6_opts[i].name));
+ }
+
+ const struct sbrec_dhcpv6_options *opt_row, *opt_row_next;
+ SBREC_DHCPV6_OPTIONS_FOR_EACH_SAFE(opt_row, opt_row_next, ctx->ovnsb_idl) {
+ struct dhcp_opts_map *dhcp_opt =
+ dhcp_opts_find(&dhcpv6_opts_to_add, opt_row->name);
+ if (dhcp_opt) {
+ hmap_remove(&dhcpv6_opts_to_add, &dhcp_opt->hmap_node);
+ } else {
+ sbrec_dhcpv6_options_delete(opt_row);
+ }
+ }
+
+ struct dhcp_opts_map *opt;
+ HMAP_FOR_EACH(opt, hmap_node, &dhcpv6_opts_to_add) {
+ struct sbrec_dhcpv6_options *sbrec_dhcpv6_option =
+ sbrec_dhcpv6_options_insert(ctx->ovnsb_txn);
+ sbrec_dhcpv6_options_set_name(sbrec_dhcpv6_option, opt->name);
+ sbrec_dhcpv6_options_set_code(sbrec_dhcpv6_option, opt->code);
+ sbrec_dhcpv6_options_set_type(sbrec_dhcpv6_option, opt->type);
+ }
+
+ hmap_destroy(&dhcpv6_opts_to_add);
+}
+
/* Updates the sb_cfg and hv_cfg columns in the northbound NB_Global table. */
static void
update_northbound_cfg(struct northd_context *ctx,
@@ -4190,7 +4351,10 @@ main(int argc, char *argv[])
add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dhcp_options_col_code);
add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dhcp_options_col_type);
add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dhcp_options_col_name);
-
+ ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_dhcpv6_options);
+ add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dhcpv6_options_col_code);
+ add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dhcpv6_options_col_type);
+ add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dhcpv6_options_col_name);
ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_address_set);
add_column_noalert(ovnsb_idl_loop.idl, &sbrec_address_set_col_name);
add_column_noalert(ovnsb_idl_loop.idl, &sbrec_address_set_col_addresses);
@@ -4212,6 +4376,7 @@ main(int argc, char *argv[])
ovnsb_db_run(&ctx, &ovnsb_idl_loop);
if (ctx.ovnsb_txn) {
check_and_add_supported_dhcp_opts_to_sb_db(&ctx);
+ check_and_add_supported_dhcpv6_opts_to_sb_db(&ctx);
}
unixctl_server_run(unixctl);
diff --git a/ovn/ovn-nb.ovsschema b/ovn/ovn-nb.ovsschema
index 660db7650a6..6e33135e961 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",
+ "version": "5.3.1",
+ "cksum": "3864094809 9343",
"tables": {
"NB_Global": {
"columns": {
@@ -69,6 +69,11 @@
"refType": "weak"},
"min": 0,
"max": 1}},
+ "dhcpv6_options": {"type": {"key": {"type": "uuid",
+ "refTable": "DHCP_Options",
+ "refType": "weak"},
+ "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 4ce295a2935..8fb52449294 100644
--- a/ovn/ovn-nb.xml
+++ b/ovn/ovn-nb.xml
@@ -593,6 +593,12 @@
Please see the table.
+
+ This column defines the DHCPv6 Options to be included by the
+ ovn-controller
when it replies to the DHCPv6 requests.
+ Please see the table.
+
+
See External IDs at the beginning of this document.
@@ -1018,25 +1024,30 @@
- OVN implements a native DHCPv4 support which caters to the common
+ OVN implements native DHCPv4 support which caters to the common
use case of providing an IPv4 address to a booting instance by
providing stateless replies to DHCPv4 requests based on statically
configured address mappings. To do this it allows a short list of
DHCPv4 options to be configured and applied at each compute host
- running ovn-controller.
+ running ovn-controller
.
+
+
+
+ OVN also implements native DHCPv6 support which provides stateless
+ replies to DHCPv6 requests.
- The DHCPv4 options will be included if the logical port has the IPv4
- address in this .
+ The DHCPv4/DHCPv6 options will be included if the logical port has its
+ IP address in this .
- CMS should define the set of DHCPv4 options as key/value pairs in the
- column of this table. For
+ The CMS should define the set of DHCPv4 options as key/value pairs
+ in the column of this table. For
ovn-controller
to include these DHCPv4 options, the
of
should refer to an entry in this table.
@@ -1236,6 +1247,67 @@
+
+
+ OVN also implements native DHCPv6 support. The CMS should define
+ the set of DHCPv6 options as key/value pairs. The define DHCPv6
+ options will be included in the DHCPv6 response to the DHCPv6
+ Solicit/Request/Confirm packet from the logical ports having the
+ IPv6 addresses in the .
+
+
+
+
+ The following options must be defined.
+
+
+
+
+ The Ethernet address for the DHCP server to use. This is also
+ included in the DHCPv6 reply as option 2, ``Server Identifier''
+ to carry a DUID identifying a server between a client and a server.
+ ovn-controller
defines DUID based on
+ Link-layer Address [DUID-LL].
+
+
+
+
+
+
+ Below are the supported DHCPv6 options whose values are an IPv6
+ address, e.g. aef0::4
. Some options accept multiple
+ IPv6 addresses enclosed within curly braces, e.g. {aef0::4,
+ aef0::5}
. Please refer to RFC 3315 for more details on
+ DHCPv6 options and their codes.
+
+
+
+
+ The DHCPv6 option code for this option is 23. This option specifies
+ the DNS servers that the VM should use.
+
+
+
+
+
+
+ These options accept string values.
+
+
+
+
+ The DHCPv6 option code for this option is 24. This option specifies
+ the domain search list the client should use to resolve hostnames
+ with DNS.
+
+
+
+ Example: "ovn.org"
.
+
+
+
+
+
See External IDs at the beginning of this document.
diff --git a/tests/ovn.at b/tests/ovn.at
index 829199fe3ad..cf198e3c399 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -748,10 +748,10 @@ get_nd(xxreg0, ip6.dst); => Cannot use numeric field xxreg0 where string field i
put_nd(inport, nd.target, nd.sll); => actions=push:NXM_NX_XXREG0[],push:NXM_OF_ETH_SRC[],push:NXM_NX_ND_SLL[],push:NXM_NX_ND_TARGET[],pop:NXM_NX_XXREG0[],pop:NXM_OF_ETH_SRC[],controller(userdata=00.00.00.04.00.00.00.00),pop:NXM_OF_ETH_SRC[],pop:NXM_NX_XXREG0[], prereqs=((icmp6.type == 0x87 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd)) || (icmp6.type == 0x88 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd))) && icmp6.code == 0 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd) && ip.ttl == 0xff && (eth.type == 0x800 || eth.type == 0x86dd) && icmp6.type == 0x87 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd) && icmp6.code == 0 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd) && ip.ttl == 0xff && (eth.type == 0x800 || eth.type == 0x86dd)
# put_dhcpv6_opts
-reg1[0] = put_dhcpv6_opts(ia_addr = ae70::4, server_id = 00:00:00:00:10:02); => actions=controller(userdata=00.00.00.05.00.00.00.00.80.01.00.08.00.00.00.00.05.00.10.00.ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.04.02.00.06.00.00.00.00.00.10.02,pause), prereqs=1
-reg1[0] = put_dhcpv6_opts(); => actions=controller(userdata=00.00.00.05.00.00.00.00.80.01.00.08.00.00.00.00,pause), prereqs=1
-reg1[0] = put_dhcpv6_opts(dns_server={ae70::1,ae70::2}); => actions=controller(userdata=00.00.00.05.00.00.00.00.80.01.00.08.00.00.00.00.17.00.20.00.ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.01.ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.02,pause), prereqs=1
-reg1[0] = put_dhcpv6_opts(domain_search="ovn.org"); => actions=controller(userdata=00.00.00.05.00.00.00.00.80.01.00.08.00.00.00.00.18.00.07.00.6f.76.6e.2e.6f.72.67,pause), prereqs=1
+reg1[0] = put_dhcpv6_opts(ia_addr = ae70::4, server_id = 00:00:00:00:10:02); => actions=controller(userdata=00.00.00.05.00.00.00.00.00.01.de.10.00.00.00.40.05.00.10.00.ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.04.02.00.06.00.00.00.00.00.10.02,pause), prereqs=1
+reg1[0] = put_dhcpv6_opts(); => actions=controller(userdata=00.00.00.05.00.00.00.00.00.01.de.10.00.00.00.40,pause), prereqs=1
+reg1[0] = put_dhcpv6_opts(dns_server={ae70::1,ae70::2}); => actions=controller(userdata=00.00.00.05.00.00.00.00.00.01.de.10.00.00.00.40.17.00.20.00.ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.01.ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.02,pause), prereqs=1
+reg1[0] = put_dhcpv6_opts(domain_search="ovn.org"); => actions=controller(userdata=00.00.00.05.00.00.00.00.00.01.de.10.00.00.00.40.18.00.07.00.6f.76.6e.2e.6f.72.67,pause), prereqs=1
reg1[0] = put_dhcpv6_opts(x = 1.2.3.4); => Syntax error at `x' expecting DHCPv6 option name.
reg1[0] = put_dhcpv6_opts(ia_addr=ae70::4, "hi"); => Syntax error at `"hi"'.
reg1[0] = put_dhcpv6_opts(ia_addr=ae70::4, xyzzy); => Syntax error at `xyzzy' expecting DHCPv6 option name.
@@ -3343,6 +3343,232 @@ OVS_APP_EXIT_AND_WAIT([ovsdb-server])
AT_CLEANUP
+AT_SETUP([ovn -- dhcpv6 : 1 HV, 2 LS, 2 LSPs/LS])
+AT_KEYWORDS([dhcpv6])
+AT_SKIP_IF([test $HAVE_PYTHON = no])
+ovn_start
+
+ovn-nbctl ls-add ls1
+ovn-nbctl lsp-add ls1 ls1-lp1 \
+-- lsp-set-addresses ls1-lp1 "f0:00:00:00:00:01 10.0.0.4 ae70::4"
+
+ovn-nbctl lsp-set-port-security ls1-lp1 "f0:00:00:00:00:01 10.0.0.4 ae70::4"
+
+ovn-nbctl lsp-add ls1 ls1-lp2 \
+-- lsp-set-addresses ls1-lp2 "f0:00:00:00:00:02 ae70::5"
+
+ovn-nbctl lsp-set-port-security ls1-lp2 "f0:00:00:00:00:02 ae70::5"
+
+ovn-nbctl -- --id=@d1 create DHCP_Options cidr="ae70\:\:/64" \
+options="\"server_id\"=\"00:00:00:10:00:01\"" \
+-- add Logical_Switch_Port ls1-lp1 dhcpv6_options @d1 \
+-- add Logical_Switch_Port ls1-lp2 dhcpv6_options @d1
+
+ovn-nbctl ls-add ls2
+ovn-nbctl lsp-add ls2 ls2-lp1 \
+-- lsp-set-addresses ls2-lp1 "f0:00:00:00:00:03 be70::3"
+ovn-nbctl lsp-set-port-security ls2-lp1 "f0:00:00:00:00:03 be70::3"
+ovn-nbctl lsp-add ls2 ls2-lp2 \
+-- lsp-set-addresses ls2-lp2 "f0:00:00:00:00:04 be70::4"
+ovn-nbctl lsp-set-port-security ls2-lp2 "f0:00:00:00:00:04 be70::4"
+
+net_add n1
+sim_add hv1
+
+as hv1
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.1
+ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=ls1-lp1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+ options:rxq_pcap=hv1/vif1-rx.pcap \
+ ofport-request=1
+
+ovs-vsctl -- add-port br-int hv1-vif2 -- \
+ set interface hv1-vif2 external-ids:iface-id=ls1-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=ls2-lp1 \
+ options:tx_pcap=hv1/vif3-tx.pcap \
+ options:rxq_pcap=hv1/vif3-rx.pcap \
+ ofport-request=3
+
+ovs-vsctl -- add-port br-int hv1-vif4 -- \
+ set interface hv1-vif4 external-ids:iface-id=ls2-lp2 \
+ options:tx_pcap=hv1/vif4-tx.pcap \
+ options:rxq_pcap=hv1/vif4-rx.pcap \
+ ofport-request=4
+
+ovn_populate_arp
+
+sleep 2
+
+trim_zeros() {
+ sed 's/\(00\)\{1,\}$//'
+}
+
+# This shell function sends a DHCPv6 request packet
+# test_dhcp INPORT SRC_MAC DHCPv6_MSG_TYPE OUTPORT...
+# The OUTPORTs (zero or more) list the VIFs on which the original DHCP
+# packet should be received twice (one from ovn-controller and the other
+# from the "ovs-ofctl monitor br-int resume"
+test_dhcpv6() {
+ local inport=$1 src_mac=$2 src_lla=$3 msg_code=$4 offer_ip=$5
+ local request=ffffffffffff${src_mac}86dd00000000002a1101${src_lla}
+ # dst ip ff02::1:2
+ request+=ff020000000000000000000000010002
+ # udp header and dhcpv6 header
+ request+=02220223002affff${msg_code}010203
+ # Client identifier
+ request+=0001000a00030001${src_mac}
+ # IA-NA (Identity Association for Non Temporary Address)
+ request+=0003000c0102030400000e1000001518
+ shift; shift; shift; shift; shift;
+ if test $offer_ip != 0; then
+ local server_mac=000000100001
+ local server_lla=fe80000000000000020000fffe100001
+ local reply_code=07
+ if test $msg_code = 01; then
+ reply_code=02
+ fi
+ local reply=${src_mac}${server_mac}86dd0000000000541101${server_lla}${src_lla}
+ # udp header and dhcpv6 header
+ reply+=022302220054ffff${reply_code}010203
+ # Client identifier
+ reply+=0001000a00030001${src_mac}
+ # IA-NA
+ reply+=0003002801020304ffffffffffffffff00050018${offer_ip}ffffffffffffffff
+ # Server identifier
+ reply+=0002000a00030001${server_mac}
+ echo $reply | trim_zeros >> $inport.expected
+ else
+ for outport; do
+ echo $request | trim_zeros >> $outport.expected
+ done
+ fi
+
+ as hv1 ovs-appctl netdev-dummy/receive hv1-vif$inport $request
+}
+
+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
+}
+
+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
+
+echo "---------NB dump-----"
+ovn-nbctl show
+echo "---------------------"
+echo "---------SB dump-----"
+ovn-sbctl list datapath_binding
+echo "---------------------"
+ovn-sbctl list logical_flow
+echo "---------------------"
+
+echo "---------------------"
+ovn-sbctl dump-flows
+echo "---------------------"
+
+echo "------ hv1 dump ----------"
+as hv1 ovs-ofctl dump-flows br-int
+
+src_mac=f00000000001
+src_lla=fe80000000000000f20000fffe000001
+offer_ip=ae700000000000000000000000000004
+test_dhcpv6 1 $src_mac $src_lla 01 $offer_ip
+
+# NXT_RESUMEs 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 | trim_zeros > 1.packets
+# cat 1.expected | trim_zeros > expout
+cat 1.expected | cut -c -120 > expout
+AT_CHECK([cat 1.packets | cut -c -120], [0], [expout])
+# Skipping the UDP checksum
+cat 1.expected | cut -c 125- > expout
+AT_CHECK([cat 1.packets | cut -c 125-], [0], [expout])
+
+rm 1.expected
+
+# Send invalid packet on ls1-lp2. ovn-controller should resume the packet
+# without any modifications and the packet should be received by ls1-lp1.
+# ls1-lp1 will receive the packet twice, one from the ovn-controller after the
+# resume and the other from ovs-ofctl monitor resume.
+
+reset_pcap_file hv1-vif1 hv1/vif1
+reset_pcap_file hv1-vif2 hv1/vif2
+
+src_mac=f00000000002
+src_lla=fe80000000000000f20000fffe000002
+offer_ip=ae700000000000000000000000000005
+# Set invalid msg_type
+
+test_dhcpv6 2 $src_mac $src_lla 10 0 1 1
+
+# NXT_RESUMEs should be 2.
+OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
+
+# vif2-tx.pcap should not have received the DHCPv6 reply packet
+rm 2.packets
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap | trim_zeros > 2.packets
+AT_CHECK([cat 2.packets], [0], [])
+
+# vif1-tx.pcap should have received the DHCPv6 (invalid) request packet
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap | trim_zeros > 1.packets
+cat 1.expected > expout
+AT_CHECK([cat 1.packets], [0], [expout])
+
+# Send DHCPv6 packet on ls2-lp1. native DHCPv6 is disabled on this port.
+# There should be no DHCPv6 reply from ovn-controller and the request packet
+# should be received by ls2-lp2.
+
+src_mac=f00000000003
+src_lla=fe80000000000000f20000fffe000003
+test_dhcpv6 3 $src_mac $src_lla 01 0 4
+
+# NXT_RESUMEs should be 2 only.
+OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
+
+# vif3-tx.pcap should not have received the DHCPv6 reply packet
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif3-tx.pcap | trim_zeros > 3.packets
+AT_CHECK([cat 3.packets], [0], [])
+
+# vif4-tx.pcap should have received the DHCPv6 request packet
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif4-tx.pcap | trim_zeros > 4.packets
+cat 4.expected > expout
+AT_CHECK([cat 4.packets], [0], [expout])
+
+as hv1
+OVS_APP_EXIT_AND_WAIT([ovn-controller])
+OVS_APP_EXIT_AND_WAIT([ovs-vswitchd])
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as ovn-sb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as ovn-nb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as northd
+OVS_APP_EXIT_AND_WAIT([ovn-northd])
+
+as main
+OVS_APP_EXIT_AND_WAIT([ovs-vswitchd])
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+AT_CLEANUP
+
AT_SETUP([ovn -- 2 HVs, 2 LRs connected via LS, gateway router])
AT_KEYWORDS([ovngatewayrouter])
AT_SKIP_IF([test $HAVE_PYTHON = no])