From patchwork Fri Jul 29 06:26:20 2016
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
X-Patchwork-Submitter: Justin Pettit
X-Patchwork-Id: 654000
Return-Path:
X-Original-To: incoming@patchwork.ozlabs.org
Delivered-To: patchwork-incoming@bilbo.ozlabs.org
Received: from archives.nicira.com (archives.nicira.com [96.126.127.54])
by ozlabs.org (Postfix) with ESMTP id 3s0zL94C55z9t0G
for ;
Fri, 29 Jul 2016 16:27:45 +1000 (AEST)
Received: from archives.nicira.com (localhost [127.0.0.1])
by archives.nicira.com (Postfix) with ESMTP id 43356114DA;
Thu, 28 Jul 2016 23:26:49 -0700 (PDT)
X-Original-To: dev@openvswitch.org
Delivered-To: dev@openvswitch.org
Received: from mx3v3.cudamail.com (mx3.cudamail.com [64.34.241.5])
by archives.nicira.com (Postfix) with ESMTPS id 7ABEB114A9
for ; Thu, 28 Jul 2016 23:26:45 -0700 (PDT)
Received: from bar6.cudamail.com (localhost [127.0.0.1])
by mx3v3.cudamail.com (Postfix) with ESMTPS id 13E4B1624CA
for ; Fri, 29 Jul 2016 00:26:45 -0600 (MDT)
X-ASG-Debug-ID: 1469773604-0b32374775383990001-byXFYA
Received: from mx3-pf1.cudamail.com ([192.168.14.2]) by bar6.cudamail.com
with
ESMTP id NDxVWm8fkO5QGD39 (version=TLSv1 cipher=DHE-RSA-AES256-SHA
bits=256 verify=NO) for ;
Fri, 29 Jul 2016 00:26:44 -0600 (MDT)
X-Barracuda-Envelope-From: jpettit@ovn.org
X-Barracuda-RBL-Trusted-Forwarder: 192.168.14.2
Received: from unknown (HELO relay5-d.mail.gandi.net) (217.70.183.197)
by mx3-pf1.cudamail.com with ESMTPS (DHE-RSA-AES256-SHA encrypted);
29 Jul 2016 06:26:44 -0000
Received-SPF: pass (mx3-pf1.cudamail.com: SPF record at ovn.org designates
217.70.183.197 as permitted sender)
X-Barracuda-Apparent-Source-IP: 217.70.183.197
X-Barracuda-RBL-IP: 217.70.183.197
Received: from mfilter42-d.gandi.net (mfilter42-d.gandi.net [217.70.178.172])
by relay5-d.mail.gandi.net (Postfix) with ESMTP id C1ADF41C089
for ; Fri, 29 Jul 2016 08:26:42 +0200 (CEST)
X-Virus-Scanned: Debian amavisd-new at mfilter42-d.gandi.net
Received: from relay5-d.mail.gandi.net ([IPv6:::ffff:217.70.183.197])
by mfilter42-d.gandi.net (mfilter42-d.gandi.net [::ffff:10.0.15.180])
(amavisd-new, port 10024)
with ESMTP id sc4ZFuomLKW5 for ;
Fri, 29 Jul 2016 08:26:40 +0200 (CEST)
X-Originating-IP: 108.70.244.32
Received: from raznick.localdomain (unknown [108.70.244.32])
(Authenticated sender: jpettit@ovn.org)
by relay5-d.mail.gandi.net (Postfix) with ESMTPSA id E013641C095
for ; Fri, 29 Jul 2016 08:26:39 +0200 (CEST)
X-CudaMail-Envelope-Sender: jpettit@ovn.org
From: Justin Pettit
To: dev@openvswitch.org
X-CudaMail-Whitelist-To: dev@openvswitch.org
X-CudaMail-MID: CM-V1-728000265
X-CudaMail-DTE: 072916
X-CudaMail-Originating-IP: 217.70.183.197
Date: Thu, 28 Jul 2016 23:26:20 -0700
X-ASG-Orig-Subj: [##CM-V1-728000265##][IPv6 v2 10/10] ovn: Add support for
IPv6 dynamic bindings.
Message-Id: <1469773580-33112-10-git-send-email-jpettit@ovn.org>
X-Mailer: git-send-email 1.9.1
In-Reply-To: <1469773580-33112-1-git-send-email-jpettit@ovn.org>
References: <1469773580-33112-1-git-send-email-jpettit@ovn.org>
X-Barracuda-Connect: UNKNOWN[192.168.14.2]
X-Barracuda-Start-Time: 1469773604
X-Barracuda-Encrypted: DHE-RSA-AES256-SHA
X-Barracuda-URL: https://web.cudamail.com:443/cgi-mod/mark.cgi
X-ASG-Whitelist: Header =?UTF-8?B?eFwtY3VkYW1haWxcLXdoaXRlbGlzdFwtdG8=?=
X-Virus-Scanned: by bsmtpd at cudamail.com
X-Barracuda-BRTS-Status: 1
Subject: [ovs-dev] [IPv6 v2 10/10] ovn: Add support for IPv6 dynamic
bindings.
X-BeenThere: dev@openvswitch.org
X-Mailman-Version: 2.1.16
Precedence: list
List-Id:
List-Unsubscribe: ,
List-Archive:
List-Post:
List-Help:
List-Subscribe: ,
MIME-Version: 1.0
Errors-To: dev-bounces@openvswitch.org
Sender: "dev"
This commit also introduces "get_nd" and "put_nd" logical actions.
Signed-off-by: Justin Pettit
Acked-by: Ben Pfaff
---
include/ovn/actions.h | 13 +++-
ovn/controller/lflow.c | 27 +++++--
ovn/controller/pinctrl.c | 170 ++++++++++++++++++++++++--------------------
ovn/lib/actions.c | 60 +++++++++++++++-
ovn/northd/ovn-northd.8.xml | 56 ++++++++++-----
ovn/northd/ovn-northd.c | 17 +++++
ovn/ovn-architecture.7.xml | 2 +
ovn/ovn-sb.xml | 70 +++++++++++++-----
tests/ovn.at | 15 ++++
tests/test-ovn.c | 2 +-
10 files changed, 311 insertions(+), 121 deletions(-)
diff --git a/include/ovn/actions.h b/include/ovn/actions.h
index 2ae9cd2..a395ce9 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 674b756..fda10eb 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 c03e2cf..ed12dd3 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;
/* 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;
+ pmb->ip_s = xstrdup(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,41 @@ 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->ip_s);
+ free(pmb);
}
}
diff --git a/ovn/lib/actions.c b/ovn/lib/actions.c
index 6b8ea3f..b9d1205 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 bfcaeb5..9bee3a3 100644
--- a/ovn/northd/ovn-northd.8.xml
+++ b/ovn/northd/ovn-northd.8.xml
@@ -843,7 +843,7 @@ output;
- 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 arp.op
== 2
has actions put_arp(inport, arp.spa,
arp.sha);
.
@@ -851,21 +851,19 @@ output;
- Reply to IPv6 Neighbor Solicitations.
-
-
-
- These flows reply to Neighbor Solictation requests for the
- router's own IPv6 address. For each router port P
- that owns IPv6 address A, solicited node address
- S, and Ethernet address
- E, a priority-90 flow matches inport ==
- P && nd_ns && ip6.dst == {A,
- E} && nd.target == A
- with the following actions:
+ Reply to IPv6 Neighbor Solicitations. These flows reply to
+ Neighbor Solictation requests for the router's own IPv6
+ address and populate the logical router's mac binding table.
+ For each router port P that owns IPv6 address
+ A, solicited node address S, and
+ Ethernet address E, a priority-90 flow matches
+ inport == P && nd_ns &&
+ ip6.dst == {A, E} && nd.target
+ == A
with the following actions:
+put_nd(inport, ip6.src, nd.sll);
nd_na {
eth.src = E;
ip6.src = A;
@@ -879,6 +877,23 @@ nd_na {
+ IPv6 neighbor advertisement handling. This flow uses neighbor
+ advertisements to populate the logical router's mac binding
+ table. A priority-90 flow with match nd_na
+ has actions put_nd(inport, nd.target, nd.tll);
.
+
+
+
+ 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
+ nd_ns
has actions
+ put_nd(inport, ip6.src, nd.sll);
.
+
+
+
UDP port unreachable. Priority-80 flows generate ICMP port
unreachable messages in reply to UDP datagrams directed to the
@@ -1234,15 +1249,22 @@ icmp4 {
- 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.)
- A priority-0 logical flow with match 1
has actions
+ A priority-0 logical flow with match ip4
has actions
get_arp(outport, reg0); next;
.
+
+
+ A priority-0 logical flow with match ip6
has actions
+ get_nd(outport, xxreg0); next;
.
+
diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c
index f0f72f0..f9d9823 100644
--- a/ovn/northd/ovn-northd.c
+++ b/ovn/northd/ovn-northd.c
@@ -3042,6 +3042,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;");
@@ -3248,6 +3259,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; "
@@ -3625,6 +3637,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 a9258e7..bf90dac 100644
--- a/ovn/ovn-architecture.7.xml
+++ b/ovn/ovn-architecture.7.xml
@@ -857,6 +857,7 @@
get_arp(P, A);
+ get_nd(P, A);
Implemented by storing arguments into OpenFlow fields, then
@@ -875,6 +876,7 @@
put_arp(P, A, E);
+ put_nd(P, A, E);
Implemented by storing the arguments into OpenFlow fields, then
diff --git a/ovn/ovn-sb.xml b/ovn/ovn-sb.xml
index d134e74..28ab0ae 100644
--- a/ovn/ovn-sb.xml
+++ b/ovn/ovn-sb.xml
@@ -1109,6 +1109,44 @@
Prerequisite: ip4
+ get_arp(P, A);
+
+
+
+ Parameters: logical port string field P, 32-bit
+ IP address field A.
+
+
+
+ Looks up A in P's mac binding table.
+ If an entry is found, stores its Ethernet address in
+ eth.dst
, otherwise stores
+ 00:00:00:00:00:00
in eth.dst
.
+
+
+ Example: get_arp(outport, ip4.dst);
+
+
+
+ put_arp(P, A, E);
+
+
+
+
+ Parameters: logical port string field P, 32-bit
+ IP address field A, 48-bit Ethernet address field
+ E.
+
+
+
+ Adds or updates the entry for IP address A in
+ logical port P's mac binding table, setting its
+ Ethernet address to E.
+
+
+ Example: put_arp(inport, arp.spa, arp.sha);
+
+
nd_na { action;
... };
@@ -1149,42 +1187,42 @@
- get_arp(P, A);
+ get_nd(P, A);
- Parameters: logical port string field P, 32-bit
- IP address field A.
+ Parameters: logical port string field P, 128-bit
+ IPv6 address field A.
- Looks up A in P's ARP table. If an entry is
- found, stores its Ethernet address in eth.dst
,
- otherwise stores 00:00:00:00:00:00
in
- eth.dst
.
+ Looks up A in P's mac binding table.
+ If an entry is found, stores its Ethernet address in
+ eth.dst
, otherwise stores
+ 00:00:00:00:00:00
in eth.dst
.
- Example: get_arp(outport, ip4.dst);
+ Example: get_nd(outport, ip6.dst);
- put_arp(P, A, E);
+ put_nd(P, A, E);
- Parameters: logical port string field P, 32-bit
- IP address field A, 48-bit Ethernet address field
- E.
+ Parameters: logical port string field P,
+ 128-bit IPv6 address field A, 48-bit Ethernet
+ address field E.
- Adds or updates the entry for IP address A in logical
- port P's ARP table, setting its Ethernet address to
- E.
+ Adds or updates the entry for IPv6 address A in
+ logical port P's mac binding table, setting its
+ Ethernet address to E.
- Example: put_arp(inport, arp.spa, arp.sha);
+ Example: put_nd(inport, nd.target, nd.tll);
diff --git a/tests/ovn.at b/tests/ovn.at
index 72d63f2..3d2ebe5 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -699,6 +699,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 e226b3b..9a0101b 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) {