Skip to content

Commit

Permalink
ovn-northd: Add logical flows to support DHCPv6
Browse files Browse the repository at this point in the history
OVN implements native DHCPv6. DHCPv6 options are stored
in the 'DHCP_Options' NB table and logical ports refer to this
table to configure the DHCPv6 options.

For each logical port configured with DHCPv6 Options following flows
are added
 - A logical flow which copies the DHCPv6 options to the DHCPv6
   request packets using the 'put_dhcpv6_opts' action and advances the
   packet to the next stage.

 - A logical flow which implements the DHCPv6 reponder by sending
   the DHCPv6 reply back to the inport once the 'put_dhcpv6_opts' action
   is applied.

Signed-off-by: Numan Siddique <[email protected]>
Signed-off-by: Ben Pfaff <[email protected]>
numansiddique authored and blp committed Aug 14, 2016
1 parent 01cfdb2 commit 33ac3c8
Showing 6 changed files with 550 additions and 21 deletions.
1 change: 1 addition & 0 deletions lib/packets.h
Original file line number Diff line number Diff line change
@@ -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);
}
72 changes: 66 additions & 6 deletions ovn/northd/ovn-northd.8.xml
Original file line number Diff line number Diff line change
@@ -488,7 +488,8 @@ nd_na {

<p>
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.
</p>

<ul>
@@ -501,20 +502,43 @@ nd_na {
</p>

<pre>
reg0[3] = put_dhcp_opts(offer_ip = <var>O</var>, <i>options</i>...);
reg0[3] = put_dhcp_opts(offer_ip = <var>ip</var>, <var>options</var>...);
next;
</pre>

<p>
For DHCPDISCOVER and DHCPREQUEST, this transforms the packet into a
DHCP reply, adds the DHCP offer IP <var>O</var> and options to the
DHCP reply, adds the DHCP offer IP <var>ip</var> 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.
</p>

</li>

<li>
<p>
A priority-100 logical flow is added for these logical ports
which matches the IPv6 packet with <code>udp.src</code> = 546 and
<code>udp.dst</code> = 547 and applies the action
<code>put_dhcpv6_opts</code> and advances the packet to the next
table.
</p>

<pre>
reg0[3] = put_dhcpv6_opts(ia_addr = <var>ip</var>, <var>options</var>...);
next;
</pre>

<p>
For DHCPv6 Solicit/Request/Confirm packets, this transforms the
packet into a DHCPv6 Advertise/Reply, adds the DHCPv6 offer IP
<var>ip</var> 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.
</p>
</li>

<li>
A priority-0 flow that matches all packets to advances to table 11.
</li>
@@ -541,7 +565,7 @@ next;
<pre>
eth.dst = eth.src;
eth.src = <var>E</var>;
ip4.dst = <var>O</var>;
ip4.dst = <var>A</var>;
ip4.src = <var>S</var>;
udp.src = 67;
udp.dst = 68;
@@ -552,7 +576,7 @@ output;

<p>
where <var>E</var> is the server MAC address and <var>S</var> is the
server IPv4 address defined in the DHCPv4 options and <var>O</var> is
server IPv4 address defined in the DHCPv4 options and <var>A</var> is
the IPv4 address defined in the logical port's addresses column.
</p>

@@ -562,6 +586,41 @@ output;
</p>
</li>

<li>
<p>
A priority 100 logical flow is added for the logical ports configured
with DHCPv6 options which matches IPv6 packets with <code>udp.src == 546
&amp;&amp; udp.dst == 547 &amp;&amp; reg0[3] == 1</code> and
responds back to the <code>inport</code> after applying these
actions. If <code>reg0[3]</code> is set to 1, it means that the
action <code>put_dhcpv6_opts</code> was successful.
</p>

<pre>
eth.dst = eth.src;
eth.src = <var>E</var>;
ip6.dst = <var>A</var>;
ip6.src = <var>S</var>;
udp.src = 547;
udp.dst = 546;
outport = <var>P</var>;
flags.loopback = 1;
output;
</pre>

<p>
where <var>E</var> is the server MAC address and <var>S</var> is the
server IPv6 LLA address generated from the <code>server_id</code>
defined in the DHCPv6 options and <var>A</var> is
the IPv6 address defined in the logical port's addresses column.
</p>

<p>
(This terminates packet processing; the packet does not go on the
next ingress table.)
</p>
</li>

<li>
A priority-0 flow that matches all packets to advances to table 12.
</li>
@@ -643,7 +702,8 @@ output;

<p>
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
<code>Ingress Table 11: DHCP responses</code>.
</p>

171 changes: 168 additions & 3 deletions ovn/northd/ovn-northd.c
Original file line number Diff line number Diff line change
@@ -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);
9 changes: 7 additions & 2 deletions ovn/ovn-nb.ovsschema
Original file line number Diff line number Diff line change
@@ -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"}}},
84 changes: 78 additions & 6 deletions ovn/ovn-nb.xml
Original file line number Diff line number Diff line change
@@ -593,6 +593,12 @@
Please see the <ref table="DHCP_Options"/> table.
</column>

<column name="dhcpv6_options">
This column defines the DHCPv6 Options to be included by the
<code>ovn-controller</code> when it replies to the DHCPv6 requests.
Please see the <ref table="DHCP_Options"/> table.
</column>

<column name="external_ids">
See <em>External IDs</em> at the beginning of this document.
</column>
@@ -1018,25 +1024,30 @@

<table name="DHCP_Options" title="DHCP options">
<p>
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 <code>ovn-controller</code>.
</p>

<p>
OVN also implements native DHCPv6 support which provides stateless
replies to DHCPv6 requests.
</p>

<column name="cidr">
<p>
The DHCPv4 options will be included if the logical port has the IPv4
address in this <ref column="cidr"/>.
The DHCPv4/DHCPv6 options will be included if the logical port has its
IP address in this <ref column="cidr"/>.
</p>
</column>

<group title="DHCPv4 options">
<p>
CMS should define the set of DHCPv4 options as key/value pairs in the
<ref column="options"/> column of this table. For
The CMS should define the set of DHCPv4 options as key/value pairs
in the <ref column="options"/> column of this table. For
<code>ovn-controller</code> to include these DHCPv4 options, the
<ref column="dhcpv4_options"/> of <ref table="Logical_Switch_Port"/>
should refer to an entry in this table.
@@ -1236,6 +1247,67 @@
</group>
</group>

<group title="DHCPv6 options">
<p>
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 <ref column="cidr"/>.
</p>

<group title="Mandatory DHCPv6 options">
<p>
The following options must be defined.
</p>

<column name="options" key="server_id">
<p>
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.
<code>ovn-controller</code> defines DUID based on
Link-layer Address [DUID-LL].
</p>
</column>
</group>

<group title="IPv6 DHCPv6 options">
<p>
Below are the supported DHCPv6 options whose values are an IPv6
address, e.g. <code>aef0::4</code>. Some options accept multiple
IPv6 addresses enclosed within curly braces, e.g. <code>{aef0::4,
aef0::5}</code>. Please refer to RFC 3315 for more details on
DHCPv6 options and their codes.
</p>

<column name="options" key="dns_server">
<p>
The DHCPv6 option code for this option is 23. This option specifies
the DNS servers that the VM should use.
</p>
</column>
</group>

<group title="String DHCPv6 options">
<p>
These options accept string values.
</p>

<column name="options" key="domain_search">
<p>
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.
</p>

<p>
Example: <code>"ovn.org"</code>.
</p>
</column>
</group>
</group>

<group title="Common Columns">
<column name="external_ids">
See <em>External IDs</em> at the beginning of this document.
234 changes: 230 additions & 4 deletions tests/ovn.at
Original file line number Diff line number Diff line change
@@ -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])

0 comments on commit 33ac3c8

Please sign in to comment.