summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJustin Pettit <jpettit@ovn.org>2016-06-28 02:57:27 -0700
committerJustin Pettit <jpettit@ovn.org>2016-07-29 02:54:57 -0700
commitc34a87b6c5700225733c6fce7f3a678f3ef5ac71 (patch)
tree80b8751f7ba7e5df26f3bc23ecfe66be3d960322
parenta63f7235e431415c950734138c62e107caaa2aec (diff)
downloadopenvswitch-c34a87b6c5700225733c6fce7f3a678f3ef5ac71.tar.gz
ovn: Add support for IPv6 dynamic bindings.
This commit also introduces "get_nd" and "put_nd" logical actions. Signed-off-by: Justin Pettit <jpettit@ovn.org> Acked-by: Ben Pfaff <blp@ovn.org>
-rw-r--r--include/ovn/actions.h13
-rw-r--r--ovn/controller/lflow.c27
-rw-r--r--ovn/controller/pinctrl.c169
-rw-r--r--ovn/lib/actions.c60
-rw-r--r--ovn/northd/ovn-northd.8.xml56
-rw-r--r--ovn/northd/ovn-northd.c17
-rw-r--r--ovn/ovn-architecture.7.xml2
-rw-r--r--ovn/ovn-sb.xml70
-rw-r--r--tests/ovn.at15
-rw-r--r--tests/test-ovn.c2
10 files changed, 310 insertions, 121 deletions
diff --git a/include/ovn/actions.h b/include/ovn/actions.h
index 2ae9cd216..a395ce9e3 100644
--- a/include/ovn/actions.h
+++ b/include/ovn/actions.h
@@ -78,6 +78,16 @@ enum action_opcode {
* The actions, in OpenFlow 1.3 format, follow the action_header.
*/
ACTION_OPCODE_ND_NA,
+
+ /* "put_nd(port, ip6, mac)"
+ *
+ * Arguments are passed through the packet metadata and data, as follows:
+ *
+ * MFF_XXREG0 = ip6
+ * MFF_LOG_INPORT = port
+ * MFF_ETH_SRC = mac
+ */
+ ACTION_OPCODE_PUT_ND,
};
/* Header. */
@@ -128,7 +138,8 @@ struct action_params {
uint8_t first_ptable; /* First OpenFlow table. */
uint8_t cur_ltable; /* 0 <= cur_ltable < n_tables. */
uint8_t output_ptable; /* OpenFlow table for 'output' to resubmit. */
- uint8_t arp_ptable; /* OpenFlow table for 'get_arp' to resubmit. */
+ uint8_t mac_bind_ptable; /* OpenFlow table for 'get_arp'/'get_nd' to
+ resubmit. */
};
char *actions_parse(struct lexer *, const struct action_params *,
diff --git a/ovn/controller/lflow.c b/ovn/controller/lflow.c
index 674b75620..fda10ebac 100644
--- a/ovn/controller/lflow.c
+++ b/ovn/controller/lflow.c
@@ -528,7 +528,7 @@ consider_logical_flow(const struct lport_index *lports,
.first_ptable = first_ptable,
.cur_ltable = lflow->table_id,
.output_ptable = output_ptable,
- .arp_ptable = OFTABLE_MAC_BINDING,
+ .mac_bind_ptable = OFTABLE_MAC_BINDING,
};
error = actions_parse_string(lflow->actions, &ap, &ofpacts, &prereqs);
if (error) {
@@ -638,16 +638,29 @@ consider_neighbor_flow(const struct lport_index *lports,
return;
}
- ovs_be32 ip;
- if (!ip_parse(b->ip, &ip)) {
- static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
- VLOG_WARN_RL(&rl, "bad 'ip' %s", b->ip);
- return;
+
+ if (strchr(b->ip, '.')) {
+ ovs_be32 ip;
+ if (!ip_parse(b->ip, &ip)) {
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
+ VLOG_WARN_RL(&rl, "bad 'ip' %s", b->ip);
+ return;
+ }
+ match_set_reg(match_p, 0, ntohl(ip));
+ } else {
+ struct in6_addr ip6;
+ if (!ipv6_parse(b->ip, &ip6)) {
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
+ VLOG_WARN_RL(&rl, "bad 'ip' %s", b->ip);
+ return;
+ }
+ ovs_be128 value;
+ memcpy(&value, &ip6, sizeof(value));
+ match_set_xxreg(match_p, 0, ntoh128(value));
}
match_set_metadata(match_p, htonll(pb->datapath->tunnel_key));
match_set_reg(match_p, MFF_LOG_OUTPORT - MFF_REG0, pb->tunnel_key);
- match_set_reg(match_p, 0, ntohl(ip));
ofpbuf_clear(ofpacts_p);
put_load(mac.ea, sizeof mac.ea, MFF_ETH_DST, 0, 48, ofpacts_p);
diff --git a/ovn/controller/pinctrl.c b/ovn/controller/pinctrl.c
index c03e2cfc0..bd685fe41 100644
--- a/ovn/controller/pinctrl.c
+++ b/ovn/controller/pinctrl.c
@@ -53,14 +53,15 @@ static struct rconn *swconn;
* rconn_get_connection_seqno(rconn), 'swconn' has reconnected. */
static unsigned int conn_seq_no;
-static void pinctrl_handle_put_arp(const struct flow *md,
- const struct flow *headers);
-static void init_put_arps(void);
-static void destroy_put_arps(void);
-static void run_put_arps(struct controller_ctx *,
- const struct lport_index *lports);
-static void wait_put_arps(struct controller_ctx *);
-static void flush_put_arps(void);
+static void pinctrl_handle_put_mac_binding(const struct flow *md,
+ const struct flow *headers,
+ bool is_arp);
+static void init_put_mac_bindings(void);
+static void destroy_put_mac_bindings(void);
+static void run_put_mac_bindings(struct controller_ctx *,
+ const struct lport_index *lports);
+static void wait_put_mac_bindings(struct controller_ctx *);
+static void flush_put_mac_bindings(void);
static void init_send_garps(void);
static void destroy_send_garps(void);
@@ -75,14 +76,14 @@ static void pinctrl_handle_nd_na(const struct flow *ip_flow,
static void reload_metadata(struct ofpbuf *ofpacts,
const struct match *md);
-COVERAGE_DEFINE(pinctrl_drop_put_arp);
+COVERAGE_DEFINE(pinctrl_drop_put_mac_binding);
void
pinctrl_init(void)
{
swconn = rconn_create(5, 0, DSCP_DEFAULT, 1 << OFP13_VERSION);
conn_seq_no = 0;
- init_put_arps();
+ init_put_mac_bindings();
init_send_garps();
}
@@ -403,7 +404,8 @@ process_packet_in(const struct ofp_header *msg)
break;
case ACTION_OPCODE_PUT_ARP:
- pinctrl_handle_put_arp(&pin.flow_metadata.flow, &headers);
+ pinctrl_handle_put_mac_binding(&pin.flow_metadata.flow, &headers,
+ true);
break;
case ACTION_OPCODE_PUT_DHCP_OPTS:
@@ -414,6 +416,11 @@ process_packet_in(const struct ofp_header *msg)
pinctrl_handle_nd_na(&headers, &pin.flow_metadata, &userdata);
break;
+ case ACTION_OPCODE_PUT_ND:
+ pinctrl_handle_put_mac_binding(&pin.flow_metadata.flow, &headers,
+ false);
+ break;
+
default:
VLOG_WARN_RL(&rl, "unrecognized packet-in opcode %"PRIu32,
ntohl(ah->opcode));
@@ -473,7 +480,7 @@ pinctrl_run(struct controller_ctx *ctx, const struct lport_index *lports,
if (conn_seq_no != rconn_get_connection_seqno(swconn)) {
pinctrl_setup(swconn);
conn_seq_no = rconn_get_connection_seqno(swconn);
- flush_put_arps();
+ flush_put_mac_bindings();
}
/* Process a limited number of messages per call. */
@@ -492,14 +499,14 @@ pinctrl_run(struct controller_ctx *ctx, const struct lport_index *lports,
}
}
- run_put_arps(ctx, lports);
+ run_put_mac_bindings(ctx, lports);
send_garp_run(br_int, chassis_id, lports, local_datapaths);
}
void
pinctrl_wait(struct controller_ctx *ctx)
{
- wait_put_arps(ctx);
+ wait_put_mac_bindings(ctx);
rconn_run_wait(swconn);
rconn_recv_wait(swconn);
send_garp_wait();
@@ -509,60 +516,61 @@ void
pinctrl_destroy(void)
{
rconn_destroy(swconn);
- destroy_put_arps();
+ destroy_put_mac_bindings();
destroy_send_garps();
}
-/* Implementation of the "put_arp" OVN action. This action sends a packet to
- * ovn-controller, using the flow as an API (see actions.h for details). This
- * code implements the action by updating the MAC_Binding table in the
- * southbound database.
+/* Implementation of the "put_arp" and "put_nd" OVN actions. These
+ * actions send a packet to ovn-controller, using the flow as an API
+ * (see actions.h for details). This code implements the actions by
+ * updating the MAC_Binding table in the southbound database.
*
* This code could be a lot simpler if the database could always be updated,
* but in fact we can only update it when ctx->ovnsb_idl_txn is nonnull. Thus,
- * we buffer up a few put_arps (but we don't keep them longer than 1 second)
- * and apply them whenever a database transaction is available. */
+ * we buffer up a few put_mac_bindings (but we don't keep them longer
+ * than 1 second) and apply them whenever a database transaction is
+ * available. */
-/* Buffered "put_arp" operation. */
-struct put_arp {
- struct hmap_node hmap_node; /* In 'put_arps'. */
+/* Buffered "put_mac_binding" operation. */
+struct put_mac_binding {
+ struct hmap_node hmap_node; /* In 'put_mac_bindings'. */
long long int timestamp; /* In milliseconds. */
/* Key. */
uint32_t dp_key;
uint32_t port_key;
- ovs_be32 ip;
+ char ip_s[INET6_ADDRSTRLEN + 1];
/* Value. */
struct eth_addr mac;
};
-/* Contains "struct put_arp"s. */
-static struct hmap put_arps;
+/* Contains "struct put_mac_binding"s. */
+static struct hmap put_mac_bindings;
static void
-init_put_arps(void)
+init_put_mac_bindings(void)
{
- hmap_init(&put_arps);
+ hmap_init(&put_mac_bindings);
}
static void
-destroy_put_arps(void)
+destroy_put_mac_bindings(void)
{
- flush_put_arps();
- hmap_destroy(&put_arps);
+ flush_put_mac_bindings();
+ hmap_destroy(&put_mac_bindings);
}
-static struct put_arp *
-pinctrl_find_put_arp(uint32_t dp_key, uint32_t port_key, ovs_be32 ip,
- uint32_t hash)
+static struct put_mac_binding *
+pinctrl_find_put_mac_binding(uint32_t dp_key, uint32_t port_key,
+ const char *ip_s, uint32_t hash)
{
- struct put_arp *pa;
- HMAP_FOR_EACH_WITH_HASH (pa, hmap_node, hash, &put_arps) {
+ struct put_mac_binding *pa;
+ HMAP_FOR_EACH_WITH_HASH (pa, hmap_node, hash, &put_mac_bindings) {
if (pa->dp_key == dp_key
&& pa->port_key == port_key
- && pa->ip == ip) {
+ && !strcmp(pa->ip_s, ip_s)) {
return pa;
}
}
@@ -570,64 +578,72 @@ pinctrl_find_put_arp(uint32_t dp_key, uint32_t port_key, ovs_be32 ip,
}
static void
-pinctrl_handle_put_arp(const struct flow *md, const struct flow *headers)
+pinctrl_handle_put_mac_binding(const struct flow *md,
+ const struct flow *headers, bool is_arp)
{
uint32_t dp_key = ntohll(md->metadata);
uint32_t port_key = md->regs[MFF_LOG_INPORT - MFF_REG0];
- ovs_be32 ip = htonl(md->regs[0]);
- uint32_t hash = hash_3words(dp_key, port_key, (OVS_FORCE uint32_t) ip);
- struct put_arp *pa = pinctrl_find_put_arp(dp_key, port_key, ip, hash);
- if (!pa) {
- if (hmap_count(&put_arps) >= 1000) {
- COVERAGE_INC(pinctrl_drop_put_arp);
+ char ip_s[INET6_ADDRSTRLEN];
+
+ if (is_arp) {
+ ovs_be32 ip = htonl(md->regs[0]);
+ inet_ntop(AF_INET, &ip, ip_s, sizeof(ip_s));
+ } else {
+ ovs_be128 ip6 = hton128(flow_get_xxreg(md, 0));
+ inet_ntop(AF_INET6, &ip6, ip_s, sizeof(ip_s));
+ }
+ uint32_t hash = hash_string(ip_s, hash_2words(dp_key, port_key));
+ struct put_mac_binding *pmb
+ = pinctrl_find_put_mac_binding(dp_key, port_key, ip_s, hash);
+ if (!pmb) {
+ if (hmap_count(&put_mac_bindings) >= 1000) {
+ COVERAGE_INC(pinctrl_drop_put_mac_binding);
return;
}
- pa = xmalloc(sizeof *pa);
- hmap_insert(&put_arps, &pa->hmap_node, hash);
- pa->dp_key = dp_key;
- pa->port_key = port_key;
- pa->ip = ip;
+ pmb = xmalloc(sizeof *pmb);
+ hmap_insert(&put_mac_bindings, &pmb->hmap_node, hash);
+ pmb->dp_key = dp_key;
+ pmb->port_key = port_key;
+ ovs_strlcpy(pmb->ip_s, ip_s, sizeof pmb->ip_s);
}
- pa->timestamp = time_msec();
- pa->mac = headers->dl_src;
+ pmb->timestamp = time_msec();
+ pmb->mac = headers->dl_src;
}
static void
-run_put_arp(struct controller_ctx *ctx, const struct lport_index *lports,
- const struct put_arp *pa)
+run_put_mac_binding(struct controller_ctx *ctx,
+ const struct lport_index *lports,
+ const struct put_mac_binding *pmb)
{
- if (time_msec() > pa->timestamp + 1000) {
+ if (time_msec() > pmb->timestamp + 1000) {
return;
}
/* Convert logical datapath and logical port key into lport. */
const struct sbrec_port_binding *pb
- = lport_lookup_by_key(lports, pa->dp_key, pa->port_key);
+ = lport_lookup_by_key(lports, pmb->dp_key, pmb->port_key);
if (!pb) {
static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
VLOG_WARN_RL(&rl, "unknown logical port with datapath %"PRIu32" "
- "and port %"PRIu32, pa->dp_key, pa->port_key);
+ "and port %"PRIu32, pmb->dp_key, pmb->port_key);
return;
}
- /* Convert arguments to string form for database. */
- char ip_string[INET_ADDRSTRLEN + 1];
- snprintf(ip_string, sizeof ip_string, IP_FMT, IP_ARGS(pa->ip));
-
+ /* Convert ethernet argument to string form for database. */
char mac_string[ETH_ADDR_STRLEN + 1];
snprintf(mac_string, sizeof mac_string,
- ETH_ADDR_FMT, ETH_ADDR_ARGS(pa->mac));
+ ETH_ADDR_FMT, ETH_ADDR_ARGS(pmb->mac));
- /* Check for and update an existing IP-MAC binding for this logical
+ /* Check for an update an existing IP-MAC binding for this logical
* port.
*
* XXX This is not very efficient. */
const struct sbrec_mac_binding *b;
SBREC_MAC_BINDING_FOR_EACH (b, ctx->ovnsb_idl) {
if (!strcmp(b->logical_port, pb->logical_port)
- && !strcmp(b->ip, ip_string)) {
+ && !strcmp(b->ip, pmb->ip_s)) {
if (strcmp(b->mac, mac_string)) {
sbrec_mac_binding_set_mac(b, mac_string);
}
@@ -638,39 +654,40 @@ run_put_arp(struct controller_ctx *ctx, const struct lport_index *lports,
/* Add new IP-MAC binding for this logical port. */
b = sbrec_mac_binding_insert(ctx->ovnsb_idl_txn);
sbrec_mac_binding_set_logical_port(b, pb->logical_port);
- sbrec_mac_binding_set_ip(b, ip_string);
+ sbrec_mac_binding_set_ip(b, pmb->ip_s);
sbrec_mac_binding_set_mac(b, mac_string);
sbrec_mac_binding_set_datapath(b, pb->datapath);
}
static void
-run_put_arps(struct controller_ctx *ctx, const struct lport_index *lports)
+run_put_mac_bindings(struct controller_ctx *ctx,
+ const struct lport_index *lports)
{
if (!ctx->ovnsb_idl_txn) {
return;
}
- const struct put_arp *pa;
- HMAP_FOR_EACH (pa, hmap_node, &put_arps) {
- run_put_arp(ctx, lports, pa);
+ const struct put_mac_binding *pmb;
+ HMAP_FOR_EACH (pmb, hmap_node, &put_mac_bindings) {
+ run_put_mac_binding(ctx, lports, pmb);
}
- flush_put_arps();
+ flush_put_mac_bindings();
}
static void
-wait_put_arps(struct controller_ctx *ctx)
+wait_put_mac_bindings(struct controller_ctx *ctx)
{
- if (ctx->ovnsb_idl_txn && !hmap_is_empty(&put_arps)) {
+ if (ctx->ovnsb_idl_txn && !hmap_is_empty(&put_mac_bindings)) {
poll_immediate_wake();
}
}
static void
-flush_put_arps(void)
+flush_put_mac_bindings(void)
{
- struct put_arp *pa;
- HMAP_FOR_EACH_POP (pa, hmap_node, &put_arps) {
- free(pa);
+ struct put_mac_binding *pmb;
+ HMAP_FOR_EACH_POP (pmb, hmap_node, &put_mac_bindings) {
+ free(pmb);
}
}
diff --git a/ovn/lib/actions.c b/ovn/lib/actions.c
index 6b8ea3fe0..b9d1205b0 100644
--- a/ovn/lib/actions.c
+++ b/ovn/lib/actions.c
@@ -424,7 +424,7 @@ parse_get_arp_action(struct action_context *ctx)
setup_args(ctx, args, ARRAY_SIZE(args));
put_load(0, MFF_ETH_DST, 0, 48, ctx->ofpacts);
- emit_resubmit(ctx, ctx->ap->arp_ptable);
+ emit_resubmit(ctx, ctx->ap->mac_bind_ptable);
restore_args(ctx, args, ARRAY_SIZE(args));
}
@@ -776,6 +776,56 @@ parse_ct_lb_action(struct action_context *ctx)
}
static void
+parse_get_nd_action(struct action_context *ctx)
+{
+ struct mf_subfield port, ip6;
+
+ if (!action_force_match(ctx, LEX_T_LPAREN)
+ || !action_parse_field(ctx, 0, &port)
+ || !action_force_match(ctx, LEX_T_COMMA)
+ || !action_parse_field(ctx, 128, &ip6)
+ || !action_force_match(ctx, LEX_T_RPAREN)) {
+ return;
+ }
+
+ const struct arg args[] = {
+ { &port, MFF_LOG_OUTPORT },
+ { &ip6, MFF_XXREG0 },
+ };
+ setup_args(ctx, args, ARRAY_SIZE(args));
+
+ put_load(0, MFF_ETH_DST, 0, 48, ctx->ofpacts);
+ emit_resubmit(ctx, ctx->ap->mac_bind_ptable);
+
+ restore_args(ctx, args, ARRAY_SIZE(args));
+}
+
+static void
+parse_put_nd_action(struct action_context *ctx)
+{
+ struct mf_subfield port, ip6, mac;
+
+ if (!action_force_match(ctx, LEX_T_LPAREN)
+ || !action_parse_field(ctx, 0, &port)
+ || !action_force_match(ctx, LEX_T_COMMA)
+ || !action_parse_field(ctx, 128, &ip6)
+ || !action_force_match(ctx, LEX_T_COMMA)
+ || !action_parse_field(ctx, 48, &mac)
+ || !action_force_match(ctx, LEX_T_RPAREN)) {
+ return;
+ }
+
+ const struct arg args[] = {
+ { &port, MFF_LOG_INPORT },
+ { &ip6, MFF_XXREG0 },
+ { &mac, MFF_ETH_SRC }
+ };
+ setup_args(ctx, args, ARRAY_SIZE(args));
+ put_controller_op(ctx->ofpacts, ACTION_OPCODE_PUT_ND);
+ restore_args(ctx, args, ARRAY_SIZE(args));
+}
+
+static void
emit_ct(struct action_context *ctx, bool recirc_next, bool commit,
int *ct_mark, int *ct_mark_mask,
ovs_be128 *ct_label, ovs_be128 *ct_label_mask)
@@ -1065,12 +1115,16 @@ parse_action(struct action_context *ctx)
parse_ct_lb_action(ctx);
} else if (lexer_match_id(ctx->lexer, "arp")) {
parse_nested_action(ctx, ACTION_OPCODE_ARP, "ip4");
- } else if (lexer_match_id(ctx->lexer, "nd_na")) {
- parse_nested_action(ctx, ACTION_OPCODE_ND_NA, "nd_ns");
} else if (lexer_match_id(ctx->lexer, "get_arp")) {
parse_get_arp_action(ctx);
} else if (lexer_match_id(ctx->lexer, "put_arp")) {
parse_put_arp_action(ctx);
+ } else if (lexer_match_id(ctx->lexer, "nd_na")) {
+ parse_nested_action(ctx, ACTION_OPCODE_ND_NA, "nd_ns");
+ } else if (lexer_match_id(ctx->lexer, "get_nd")) {
+ parse_get_nd_action(ctx);
+ } else if (lexer_match_id(ctx->lexer, "put_nd")) {
+ parse_put_nd_action(ctx);
} else {
action_syntax_error(ctx, "expecting action");
}
diff --git a/ovn/northd/ovn-northd.8.xml b/ovn/northd/ovn-northd.8.xml
index 168c38dc3..77974172f 100644
--- a/ovn/northd/ovn-northd.8.xml
+++ b/ovn/northd/ovn-northd.8.xml
@@ -843,7 +843,7 @@ output;
</li>
<li>
- ARP reply handling. These flows use ARP replies to populate the
+ ARP reply handling. This flow uses ARP replies to populate the
logical router's ARP table. A priority-90 flow with match <code>arp.op
== 2</code> has actions <code>put_arp(inport, arp.spa,
arp.sha);</code>.
@@ -851,21 +851,19 @@ output;
<li>
<p>
- Reply to IPv6 Neighbor Solicitations.
- </p>
-
- <p>
- These flows reply to Neighbor Solictation requests for the
- router's own IPv6 address. For each router port <var>P</var>
- that owns IPv6 address <var>A</var>, solicited node address
- <var>S</var>, and Ethernet address
- <var>E</var>, a priority-90 flow matches <code>inport ==
- <var>P</var> &amp;&amp; nd_ns &amp;&amp; ip6.dst == {<var>A</var>,
- <var>E</var>} &amp;&amp; nd.target == <var>A</var></code>
- with the following actions:
+ Reply to IPv6 Neighbor Solicitations. These flows reply to
+ Neighbor Solicitation requests for the router's own IPv6
+ address and populate the logical router's mac binding table.
+ For each router port <var>P</var> that owns IPv6 address
+ <var>A</var>, solicited node address <var>S</var>, and
+ Ethernet address <var>E</var>, a priority-90 flow matches
+ <code>inport == <var>P</var> &amp;&amp; nd_ns &amp;&amp;
+ ip6.dst == {<var>A</var>, <var>E</var>} &amp;&amp; nd.target
+ == <var>A</var></code> with the following actions:
</p>
<pre>
+put_nd(inport, ip6.src, nd.sll);
nd_na {
eth.src = <var>E</var>;
ip6.src = <var>A</var>;
@@ -879,6 +877,23 @@ nd_na {
</li>
<li>
+ IPv6 neighbor advertisement handling. This flow uses neighbor
+ advertisements to populate the logical router's mac binding
+ table. A priority-90 flow with match <code>nd_na</code>
+ has actions <code>put_nd(inport, nd.target, nd.tll);</code>.
+ </li>
+
+ <li>
+ IPv6 neighbor solicitation for non-hosted addresses handling.
+ This flow uses neighbor solicitations to populate the logical
+ router's mac binding table (ones that were directed at the
+ logical router would have matched the priority-90 neighbor
+ solicitation flow already). A priority-80 flow with match
+ <code>nd_ns</code> has actions
+ <code>put_nd(inport, ip6.src, nd.sll);</code>.
+ </li>
+
+ <li>
<p>
UDP port unreachable. Priority-80 flows generate ICMP port
unreachable messages in reply to UDP datagrams directed to the
@@ -1202,15 +1217,22 @@ next;
<li>
<p>
- Dynamic MAC bindings. This flows resolves MAC-to-IP bindings that
- have become known dynamically through ARP. (The next table will
- issue an ARP request for cases where the binding is not yet known.)
+ Dynamic MAC bindings. These flows resolve MAC-to-IP bindings
+ that have become known dynamically through ARP or neighbor
+ discovery. (The next table will issue an ARP or neighbor
+ solicitation request for cases where the binding is not yet
+ known.)
</p>
<p>
- A priority-0 logical flow with match <code>1</code> has actions
+ A priority-0 logical flow with match <code>ip4</code> has actions
<code>get_arp(outport, reg0); next;</code>.
</p>
+
+ <p>
+ A priority-0 logical flow with match <code>ip6</code> has actions
+ <code>get_nd(outport, xxreg0); next;</code>.
+ </p>
</li>
</ul>
diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c
index 14ce1bad7..d6c14cf3e 100644
--- a/ovn/northd/ovn-northd.c
+++ b/ovn/northd/ovn-northd.c
@@ -3046,6 +3046,17 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 30,
ds_cstr(&match), "drop;");
+ /* ND advertisement handling. Use advertisements to populate
+ * the logical router's ARP/ND table. */
+ ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 90, "nd_na",
+ "put_nd(inport, nd.target, nd.tll);");
+
+ /* Lean from neighbor solicitations that were not directed at
+ * us. (A priority-90 flow will respond to requests to us and
+ * learn the sender's mac address. */
+ ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 80, "nd_ns",
+ "put_nd(inport, ip6.src, nd.sll);");
+
/* Pass other traffic not already handled to the next table for
* routing. */
ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 0, "1", "next;");
@@ -3252,6 +3263,7 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
ds_clear(&actions);
ds_put_format(&actions,
+ "put_nd(inport, ip6.src, nd.sll); "
"nd_na { "
"eth.src = %s; "
"ip6.src = %s; "
@@ -3629,6 +3641,11 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 0, "1",
"get_arp(outport, reg0); next;");
+ ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 0, "ip4",
+ "get_arp(outport, reg0); next;");
+
+ ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 0, "ip6",
+ "get_nd(outport, xxreg0); next;");
}
/* Local router ingress table 6: ARP request.
diff --git a/ovn/ovn-architecture.7.xml b/ovn/ovn-architecture.7.xml
index b4240a270..092e41857 100644
--- a/ovn/ovn-architecture.7.xml
+++ b/ovn/ovn-architecture.7.xml
@@ -857,6 +857,7 @@
</dd>
<dt><code>get_arp(<var>P</var>, <var>A</var>);</code></dt>
+ <dt><code>get_nd(<var>P</var>, <var>A</var>);</code></dt>
<dd>
<p>
Implemented by storing arguments into OpenFlow fields, then
@@ -875,6 +876,7 @@
</dd>
<dt><code>put_arp(<var>P</var>, <var>A</var>, <var>E</var>);</code></dt>
+ <dt><code>put_nd(<var>P</var>, <var>A</var>, <var>E</var>);</code></dt>
<dd>
<p>
Implemented by storing the arguments into OpenFlow fields, then
diff --git a/ovn/ovn-sb.xml b/ovn/ovn-sb.xml
index 91b1cd7cd..13c952696 100644
--- a/ovn/ovn-sb.xml
+++ b/ovn/ovn-sb.xml
@@ -1073,6 +1073,44 @@
<p><b>Prerequisite:</b> <code>ip4</code></p>
</dd>
+ <dt><code>get_arp(<var>P</var>, <var>A</var>);</code></dt>
+
+ <dd>
+ <p>
+ <b>Parameters</b>: logical port string field <var>P</var>, 32-bit
+ IP address field <var>A</var>.
+ </p>
+
+ <p>
+ Looks up <var>A</var> in <var>P</var>'s mac binding table.
+ If an entry is found, stores its Ethernet address in
+ <code>eth.dst</code>, otherwise stores
+ <code>00:00:00:00:00:00</code> in <code>eth.dst</code>.
+ </p>
+
+ <p><b>Example:</b> <code>get_arp(outport, ip4.dst);</code></p>
+ </dd>
+
+ <dt>
+ <code>put_arp(<var>P</var>, <var>A</var>, <var>E</var>);</code>
+ </dt>
+
+ <dd>
+ <p>
+ <b>Parameters</b>: logical port string field <var>P</var>, 32-bit
+ IP address field <var>A</var>, 48-bit Ethernet address field
+ <var>E</var>.
+ </p>
+
+ <p>
+ Adds or updates the entry for IP address <var>A</var> in
+ logical port <var>P</var>'s mac binding table, setting its
+ Ethernet address to <var>E</var>.
+ </p>
+
+ <p><b>Example:</b> <code>put_arp(inport, arp.spa, arp.sha);</code></p>
+ </dd>
+
<dt>
<code>nd_na { <var>action</var>; </code>...<code> };</code>
</dt>
@@ -1113,42 +1151,42 @@
</p>
</dd>
- <dt><code>get_arp(<var>P</var>, <var>A</var>);</code></dt>
+ <dt><code>get_nd(<var>P</var>, <var>A</var>);</code></dt>
<dd>
<p>
- <b>Parameters</b>: logical port string field <var>P</var>, 32-bit
- IP address field <var>A</var>.
+ <b>Parameters</b>: logical port string field <var>P</var>, 128-bit
+ IPv6 address field <var>A</var>.
</p>
<p>
- Looks up <var>A</var> in <var>P</var>'s ARP table. If an entry is
- found, stores its Ethernet address in <code>eth.dst</code>,
- otherwise stores <code>00:00:00:00:00:00</code> in
- <code>eth.dst</code>.
+ Looks up <var>A</var> in <var>P</var>'s mac binding table.
+ If an entry is found, stores its Ethernet address in
+ <code>eth.dst</code>, otherwise stores
+ <code>00:00:00:00:00:00</code> in <code>eth.dst</code>.
</p>
- <p><b>Example:</b> <code>get_arp(outport, ip4.dst);</code></p>
+ <p><b>Example:</b> <code>get_nd(outport, ip6.dst);</code></p>
</dd>
<dt>
- <code>put_arp(<var>P</var>, <var>A</var>, <var>E</var>);</code>
+ <code>put_nd(<var>P</var>, <var>A</var>, <var>E</var>);</code>
</dt>
<dd>
<p>
- <b>Parameters</b>: logical port string field <var>P</var>, 32-bit
- IP address field <var>A</var>, 48-bit Ethernet address field
- <var>E</var>.
+ <b>Parameters</b>: logical port string field <var>P</var>,
+ 128-bit IPv6 address field <var>A</var>, 48-bit Ethernet
+ address field <var>E</var>.
</p>
<p>
- Adds or updates the entry for IP address <var>A</var> in logical
- port <var>P</var>'s ARP table, setting its Ethernet address to
- <var>E</var>.
+ Adds or updates the entry for IPv6 address <var>A</var> in
+ logical port <var>P</var>'s mac binding table, setting its
+ Ethernet address to <var>E</var>.
</p>
- <p><b>Example:</b> <code>put_arp(inport, arp.spa, arp.sha);</code></p>
+ <p><b>Example:</b> <code>put_nd(inport, nd.target, nd.tll);</code></p>
</dd>
<dt>
diff --git a/tests/ovn.at b/tests/ovn.at
index 33b901032..54fa8c53c 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -688,6 +688,21 @@ reg1[0] = put_dhcp_opts(offerip=1.2.3.4, domain=1.2.3.4); => DHCP option domain
# 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; }; => actions=controller(userdata=00.00.00.03.00.00.00.00.00.19.00.10.80.00.08.06.12.34.56.78.9a.bc.00.00.00.19.00.10.80.00.42.06.12.34.56.78.9a.bc.00.00.ff.ff.00.18.00.00.23.20.00.06.00.20.00.00.00.00.00.01.1c.04.00.01.1e.04.00.19.00.10.00.01.1c.04.00.00.00.00.00.00.00.00.ff.ff.00.10.00.00.23.20.00.0e.ff.f8.40.00.00.00), prereqs=nd_ns
+# get_nd
+get_nd(outport, ip6.dst); => actions=push:NXM_NX_XXREG0[],push:NXM_NX_IPV6_DST[],pop:NXM_NX_XXREG0[],set_field:00:00:00:00:00:00->eth_dst,resubmit(,65),pop:NXM_NX_XXREG0[], prereqs=eth.type == 0x86dd
+get_nd(inport, xxreg0); => actions=push:NXM_NX_REG15[],push:NXM_NX_REG14[],pop:NXM_NX_REG15[],set_field:00:00:00:00:00:00->eth_dst,resubmit(,65),pop:NXM_NX_REG15[], prereqs=1
+get_nd; => Syntax error at `;' expecting `('.
+get_nd(); => Syntax error at `)' expecting field name.
+get_nd(inport); => Syntax error at `)' expecting `,'.
+get_nd(inport ip6.dst); => Syntax error at `ip6.dst' expecting `,'.
+get_nd(inport, ip6.dst; => Syntax error at `;' expecting `)'.
+get_nd(inport, eth.dst); => Cannot use 48-bit field eth.dst[0..47] where 128-bit field is required.
+get_nd(inport, outport); => Cannot use string field outport where numeric field is required.
+get_nd(xxreg0, ip6.dst); => Cannot use numeric field xxreg0 where string field is required.
+
+# put_nd
+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)
+
# Contradictionary prerequisites (allowed but not useful):
ip4.src = ip6.src[0..31]; => actions=move:NXM_NX_IPV6_SRC[0..31]->NXM_OF_IP_SRC[], prereqs=eth.type == 0x800 && eth.type == 0x86dd
ip4.src <-> ip6.src[0..31]; => actions=push:NXM_NX_IPV6_SRC[0..31],push:NXM_OF_IP_SRC[],pop:NXM_NX_IPV6_SRC[0..31],pop:NXM_OF_IP_SRC[], prereqs=eth.type == 0x800 && eth.type == 0x86dd
diff --git a/tests/test-ovn.c b/tests/test-ovn.c
index af197099f..acb6a99c5 100644
--- a/tests/test-ovn.c
+++ b/tests/test-ovn.c
@@ -1320,7 +1320,7 @@ test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED)
.first_ptable = 16,
.cur_ltable = 10,
.output_ptable = 64,
- .arp_ptable = 65,
+ .mac_bind_ptable = 65,
};
error = actions_parse_string(ds_cstr(&input), &ap, &ofpacts, &prereqs);
if (!error) {