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) {