From patchwork Mon Oct 2 16:24:39 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Numan Siddique X-Patchwork-Id: 820618 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (mailfrom) smtp.mailfrom=openvswitch.org (client-ip=140.211.169.12; helo=mail.linuxfoundation.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=) Received: from mail.linuxfoundation.org (mail.linuxfoundation.org [140.211.169.12]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 3y5SDk11Snz9s7g for ; Tue, 3 Oct 2017 03:24:54 +1100 (AEDT) Received: from mail.linux-foundation.org (localhost [127.0.0.1]) by mail.linuxfoundation.org (Postfix) with ESMTP id 8E538B00; Mon, 2 Oct 2017 16:24:49 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@mail.linuxfoundation.org Received: from smtp1.linuxfoundation.org (smtp1.linux-foundation.org [172.17.192.35]) by mail.linuxfoundation.org (Postfix) with ESMTPS id A9C25ABB for ; Mon, 2 Oct 2017 16:24:48 +0000 (UTC) X-Greylist: domain auto-whitelisted by SQLgrey-1.7.6 Received: from mx1.redhat.com (mx1.redhat.com [209.132.183.28]) by smtp1.linuxfoundation.org (Postfix) with ESMTPS id 8ADD1151 for ; Mon, 2 Oct 2017 16:24:47 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx06.intmail.prod.int.phx2.redhat.com [10.5.11.16]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id 167B281E1D for ; Mon, 2 Oct 2017 16:24:47 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.3.2 mx1.redhat.com 167B281E1D Authentication-Results: ext-mx01.extmail.prod.ext.phx2.redhat.com; dmarc=none (p=none dis=none) header.from=redhat.com Authentication-Results: ext-mx01.extmail.prod.ext.phx2.redhat.com; spf=fail smtp.mailfrom=nusiddiq@redhat.com Received: from numans.blr.redhat.com (dhcp-0-126.blr.redhat.com [10.70.1.126]) by smtp.corp.redhat.com (Postfix) with ESMTP id E887390C6B; Mon, 2 Oct 2017 16:24:45 +0000 (UTC) From: nusiddiq@redhat.com To: dev@openvswitch.org Date: Mon, 2 Oct 2017 21:54:39 +0530 Message-Id: <20171002162439.1545-1-nusiddiq@redhat.com> In-Reply-To: <20171002161109.956-1-nusiddiq@redhat.com> References: <20171002161109.956-1-nusiddiq@redhat.com> X-Scanned-By: MIMEDefang 2.79 on 10.5.11.16 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.25]); Mon, 02 Oct 2017 16:24:47 +0000 (UTC) X-Spam-Status: No, score=-5.0 required=5.0 tests=RCVD_IN_DNSWL_HI, RP_MATCHES_RCVD autolearn=disabled version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on smtp1.linux-foundation.org Subject: [ovs-dev] [PATCH v9 1/4] ovn util: Refactor dhcp_opts_map to make it generic X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.12 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: ovs-dev-bounces@openvswitch.org Errors-To: ovs-dev-bounces@openvswitch.org From: Numan Siddique Renamed 'struct dhcp_opts_map' to 'struct gen_opts_map' and renamed ovn-dhcp.h to ovn-l7.h. An upcoming commit to support IPv6 Router Advertisement, will make use of the refactored code to store the IPv6 ND RA options in 'struct gen_opts_map'. Signed-off-by: Numan Siddique Acked-by: Miguel Angel Ajo Acked-by: Mark Michelson --- include/ovn/actions.h | 16 +++---- ovn/controller/lflow.c | 2 +- ovn/controller/pinctrl.c | 2 +- ovn/lib/actions.c | 100 ++++++++++++++++++++++----------------- ovn/lib/automake.mk | 2 +- ovn/lib/{ovn-dhcp.h => ovn-l7.h} | 67 ++++++++++++++++++-------- ovn/northd/ovn-northd.c | 14 +++--- ovn/utilities/ovn-trace.c | 14 +++--- tests/test-ovn.c | 3 +- 9 files changed, 129 insertions(+), 91 deletions(-) rename ovn/lib/{ovn-dhcp.h => ovn-l7.h} (78%) diff --git a/include/ovn/actions.h b/include/ovn/actions.h index 0a04af7aa..d13a3747b 100644 --- a/include/ovn/actions.h +++ b/include/ovn/actions.h @@ -68,8 +68,8 @@ struct simap; OVNACT(PUT_ARP, ovnact_put_mac_bind) \ OVNACT(GET_ND, ovnact_get_mac_bind) \ OVNACT(PUT_ND, ovnact_put_mac_bind) \ - OVNACT(PUT_DHCPV4_OPTS, ovnact_put_dhcp_opts) \ - OVNACT(PUT_DHCPV6_OPTS, ovnact_put_dhcp_opts) \ + OVNACT(PUT_DHCPV4_OPTS, ovnact_put_opts) \ + OVNACT(PUT_DHCPV6_OPTS, ovnact_put_opts) \ OVNACT(SET_QUEUE, ovnact_set_queue) \ OVNACT(DNS_LOOKUP, ovnact_dns_lookup) \ OVNACT(LOG, ovnact_log) @@ -234,16 +234,16 @@ struct ovnact_put_mac_bind { struct expr_field mac; /* 48-bit Ethernet address. */ }; -struct ovnact_dhcp_option { - const struct dhcp_opts_map *option; +struct ovnact_gen_option { + const struct gen_opts_map *option; struct expr_constant_set value; }; /* OVNACT_PUT_DHCPV4_OPTS, OVNACT_PUT_DHCPV6_OPTS. */ -struct ovnact_put_dhcp_opts { +struct ovnact_put_opts { struct ovnact ovnact; struct expr_field dst; /* 1-bit destination field. */ - struct ovnact_dhcp_option *options; + struct ovnact_gen_option *options; size_t n_options; }; @@ -432,10 +432,10 @@ struct ovnact_parse_params { * expr_parse()). */ const struct shash *symtab; - /* hmap of 'struct dhcp_opts_map' to support 'put_dhcp_opts' action */ + /* hmap of 'struct gen_opts_map' to support 'put_dhcp_opts' action */ const struct hmap *dhcp_opts; - /* hmap of 'struct dhcp_opts_map' to support 'put_dhcpv6_opts' action */ + /* hmap of 'struct gen_opts_map' to support 'put_dhcpv6_opts' action */ const struct hmap *dhcpv6_opts; /* Each OVN flow exists in a logical table within a logical pipeline. diff --git a/ovn/controller/lflow.c b/ovn/controller/lflow.c index 20a18c259..6b6b91abc 100644 --- a/ovn/controller/lflow.c +++ b/ovn/controller/lflow.c @@ -25,7 +25,7 @@ #include "ovn-controller.h" #include "ovn/actions.h" #include "ovn/expr.h" -#include "ovn/lib/ovn-dhcp.h" +#include "ovn/lib/ovn-l7.h" #include "ovn/lib/ovn-sb-idl.h" #include "packets.h" #include "physical.h" diff --git a/ovn/controller/pinctrl.c b/ovn/controller/pinctrl.c index 469a35586..43e3cba23 100644 --- a/ovn/controller/pinctrl.c +++ b/ovn/controller/pinctrl.c @@ -41,7 +41,7 @@ #include "ovn/lex.h" #include "ovn/lib/acl-log.h" #include "ovn/lib/logical-fields.h" -#include "ovn/lib/ovn-dhcp.h" +#include "ovn/lib/ovn-l7.h" #include "ovn/lib/ovn-util.h" #include "poll-loop.h" #include "rconn.h" diff --git a/ovn/lib/actions.c b/ovn/lib/actions.c index c9876436d..6f16d267a 100644 --- a/ovn/lib/actions.c +++ b/ovn/lib/actions.c @@ -20,7 +20,7 @@ #include "bitmap.h" #include "byte-order.h" #include "compiler.h" -#include "ovn-dhcp.h" +#include "ovn-l7.h" #include "hash.h" #include "logical-fields.h" #include "nx-match.h" @@ -1374,19 +1374,17 @@ ovnact_put_mac_bind_free(struct ovnact_put_mac_bind *put_mac OVS_UNUSED) } static void -parse_dhcp_opt(struct action_context *ctx, struct ovnact_dhcp_option *o, - bool v6) +parse_gen_opt(struct action_context *ctx, struct ovnact_gen_option *o, + const struct hmap *gen_opts, const char *opts_type) { if (ctx->lexer->token.type != LEX_T_ID) { lexer_syntax_error(ctx->lexer, NULL); return; } - const char *name = v6 ? "DHCPv6" : "DHCPv4"; - const struct hmap *map = v6 ? ctx->pp->dhcpv6_opts : ctx->pp->dhcp_opts; - o->option = map ? dhcp_opts_find(map, ctx->lexer->token.s) : NULL; + o->option = gen_opts ? gen_opts_find(gen_opts, ctx->lexer->token.s) : NULL; if (!o->option) { - lexer_syntax_error(ctx->lexer, "expecting %s option name", name); + lexer_syntax_error(ctx->lexer, "expecting %s option name", opts_type); return; } lexer_get(ctx->lexer); @@ -1403,22 +1401,22 @@ parse_dhcp_opt(struct action_context *ctx, struct ovnact_dhcp_option *o, if (!strcmp(o->option->type, "str")) { if (o->value.type != EXPR_C_STRING) { lexer_error(ctx->lexer, "%s option %s requires string value.", - name, o->option->name); + opts_type, o->option->name); return; } } else { if (o->value.type != EXPR_C_INTEGER) { lexer_error(ctx->lexer, "%s option %s requires numeric value.", - name, o->option->name); + opts_type, o->option->name); return; } } } -static const struct ovnact_dhcp_option * -find_offerip(const struct ovnact_dhcp_option *options, size_t n) +static const struct ovnact_gen_option * +find_offerip(const struct ovnact_gen_option *options, size_t n) { - for (const struct ovnact_dhcp_option *o = options; o < &options[n]; o++) { + for (const struct ovnact_gen_option *o = options; o < &options[n]; o++) { if (o->option->code == 0) { return o; } @@ -1427,21 +1425,18 @@ find_offerip(const struct ovnact_dhcp_option *options, size_t n) } static void -free_dhcp_options(struct ovnact_dhcp_option *options, size_t n) +free_gen_options(struct ovnact_gen_option *options, size_t n) { - for (struct ovnact_dhcp_option *o = options; o < &options[n]; o++) { + for (struct ovnact_gen_option *o = options; o < &options[n]; o++) { expr_constant_set_destroy(&o->value); } free(options); } -/* Parses the "put_dhcp_opts" and "put_dhcpv6_opts" actions. - * - * The caller has already consumed " =", so this just parses the rest. */ static void -parse_put_dhcp_opts(struct action_context *ctx, - const struct expr_field *dst, - struct ovnact_put_dhcp_opts *pdo) +parse_put_opts(struct action_context *ctx, const struct expr_field *dst, + struct ovnact_put_opts *po, const struct hmap *gen_opts, + const char *opts_type) { lexer_get(ctx->lexer); /* Skip put_dhcp[v6]_opts. */ lexer_get(ctx->lexer); /* Skip '('. */ @@ -1453,27 +1448,44 @@ parse_put_dhcp_opts(struct action_context *ctx, free(error); return; } - pdo->dst = *dst; + po->dst = *dst; size_t allocated_options = 0; while (!lexer_match(ctx->lexer, LEX_T_RPAREN)) { - if (pdo->n_options >= allocated_options) { - pdo->options = x2nrealloc(pdo->options, &allocated_options, - sizeof *pdo->options); + if (po->n_options >= allocated_options) { + po->options = x2nrealloc(po->options, &allocated_options, + sizeof *po->options); } - struct ovnact_dhcp_option *o = &pdo->options[pdo->n_options++]; + struct ovnact_gen_option *o = &po->options[po->n_options++]; memset(o, 0, sizeof *o); - parse_dhcp_opt(ctx, o, pdo->ovnact.type == OVNACT_PUT_DHCPV6_OPTS); + parse_gen_opt(ctx, o, gen_opts, opts_type); if (ctx->lexer->error) { return; } lexer_match(ctx->lexer, LEX_T_COMMA); } +} + +/* Parses the "put_dhcp_opts" and "put_dhcpv6_opts" actions. + * + * The caller has already consumed " =", so this just parses the rest. */ +static void +parse_put_dhcp_opts(struct action_context *ctx, + const struct expr_field *dst, + struct ovnact_put_opts *po) +{ + const struct hmap *dhcp_opts = + (po->ovnact.type == OVNACT_PUT_DHCPV6_OPTS) ? + ctx->pp->dhcpv6_opts : ctx->pp->dhcp_opts; + const char *opts_type = + (po->ovnact.type == OVNACT_PUT_DHCPV6_OPTS) ? "DHCPv6" : "DHCPv4"; + + parse_put_opts(ctx, dst, po, dhcp_opts, opts_type); - if (pdo->ovnact.type == OVNACT_PUT_DHCPV4_OPTS - && !find_offerip(pdo->options, pdo->n_options)) { + if (!ctx->lexer->error && po->ovnact.type == OVNACT_PUT_DHCPV4_OPTS + && !find_offerip(po->options, po->n_options)) { lexer_error(ctx->lexer, "put_dhcp_opts requires offerip to be specified."); return; @@ -1481,12 +1493,12 @@ parse_put_dhcp_opts(struct action_context *ctx, } static void -format_put_dhcp_opts(const char *name, - const struct ovnact_put_dhcp_opts *pdo, struct ds *s) +format_put_opts(const char *name, const struct ovnact_put_opts *pdo, + struct ds *s) { expr_field_format(&pdo->dst, s); ds_put_format(s, " = %s(", name); - for (const struct ovnact_dhcp_option *o = pdo->options; + for (const struct ovnact_gen_option *o = pdo->options; o < &pdo->options[pdo->n_options]; o++) { if (o != pdo->options) { ds_put_cstr(s, ", "); @@ -1498,19 +1510,19 @@ format_put_dhcp_opts(const char *name, } static void -format_PUT_DHCPV4_OPTS(const struct ovnact_put_dhcp_opts *pdo, struct ds *s) +format_PUT_DHCPV4_OPTS(const struct ovnact_put_opts *pdo, struct ds *s) { - format_put_dhcp_opts("put_dhcp_opts", pdo, s); + format_put_opts("put_dhcp_opts", pdo, s); } static void -format_PUT_DHCPV6_OPTS(const struct ovnact_put_dhcp_opts *pdo, struct ds *s) +format_PUT_DHCPV6_OPTS(const struct ovnact_put_opts *pdo, struct ds *s) { - format_put_dhcp_opts("put_dhcpv6_opts", pdo, s); + format_put_opts("put_dhcpv6_opts", pdo, s); } static void -encode_put_dhcpv4_option(const struct ovnact_dhcp_option *o, +encode_put_dhcpv4_option(const struct ovnact_gen_option *o, struct ofpbuf *ofpacts) { uint8_t *opt_header = ofpbuf_put_zeros(ofpacts, 2); @@ -1586,7 +1598,7 @@ encode_put_dhcpv4_option(const struct ovnact_dhcp_option *o, } static void -encode_put_dhcpv6_option(const struct ovnact_dhcp_option *o, +encode_put_dhcpv6_option(const struct ovnact_gen_option *o, struct ofpbuf *ofpacts) { struct dhcp_opt6_header *opt = ofpbuf_put_uninit(ofpacts, sizeof *opt); @@ -1614,7 +1626,7 @@ encode_put_dhcpv6_option(const struct ovnact_dhcp_option *o, } static void -encode_PUT_DHCPV4_OPTS(const struct ovnact_put_dhcp_opts *pdo, +encode_PUT_DHCPV4_OPTS(const struct ovnact_put_opts *pdo, const struct ovnact_encode_params *ep OVS_UNUSED, struct ofpbuf *ofpacts) { @@ -1629,12 +1641,12 @@ encode_PUT_DHCPV4_OPTS(const struct ovnact_put_dhcp_opts *pdo, /* Encode the offerip option first, because it's a special case and needs * to be first in the actual DHCP response, and then encode the rest * (skipping offerip the second time around). */ - const struct ovnact_dhcp_option *offerip_opt = find_offerip( + const struct ovnact_gen_option *offerip_opt = find_offerip( pdo->options, pdo->n_options); ovs_be32 offerip = offerip_opt->value.values[0].value.ipv4; ofpbuf_put(ofpacts, &offerip, sizeof offerip); - for (const struct ovnact_dhcp_option *o = pdo->options; + for (const struct ovnact_gen_option *o = pdo->options; o < &pdo->options[pdo->n_options]; o++) { if (o != offerip_opt) { encode_put_dhcpv4_option(o, ofpacts); @@ -1645,7 +1657,7 @@ encode_PUT_DHCPV4_OPTS(const struct ovnact_put_dhcp_opts *pdo, } static void -encode_PUT_DHCPV6_OPTS(const struct ovnact_put_dhcp_opts *pdo, +encode_PUT_DHCPV6_OPTS(const struct ovnact_put_opts *pdo, const struct ovnact_encode_params *ep OVS_UNUSED, struct ofpbuf *ofpacts) { @@ -1657,7 +1669,7 @@ encode_PUT_DHCPV6_OPTS(const struct ovnact_put_dhcp_opts *pdo, ovs_be32 ofs = htonl(dst.ofs); ofpbuf_put(ofpacts, &ofs, sizeof ofs); - for (const struct ovnact_dhcp_option *o = pdo->options; + for (const struct ovnact_gen_option *o = pdo->options; o < &pdo->options[pdo->n_options]; o++) { encode_put_dhcpv6_option(o, ofpacts); } @@ -1666,9 +1678,9 @@ encode_PUT_DHCPV6_OPTS(const struct ovnact_put_dhcp_opts *pdo, } static void -ovnact_put_dhcp_opts_free(struct ovnact_put_dhcp_opts *pdo) +ovnact_put_opts_free(struct ovnact_put_opts *pdo) { - free_dhcp_options(pdo->options, pdo->n_options); + free_gen_options(pdo->options, pdo->n_options); } static void diff --git a/ovn/lib/automake.mk b/ovn/lib/automake.mk index d9ed050f3..dae06a5c1 100644 --- a/ovn/lib/automake.mk +++ b/ovn/lib/automake.mk @@ -11,7 +11,7 @@ ovn_lib_libovn_la_SOURCES = \ ovn/lib/chassis-index.h \ ovn/lib/expr.c \ ovn/lib/lex.c \ - ovn/lib/ovn-dhcp.h \ + ovn/lib/ovn-l7.h \ ovn/lib/ovn-util.c \ ovn/lib/ovn-util.h \ ovn/lib/logical-fields.c \ diff --git a/ovn/lib/ovn-dhcp.h b/ovn/lib/ovn-l7.h similarity index 78% rename from ovn/lib/ovn-dhcp.h rename to ovn/lib/ovn-l7.h index d5561edfa..40bd75461 100644 --- a/ovn/lib/ovn-dhcp.h +++ b/ovn/lib/ovn-l7.h @@ -21,7 +21,8 @@ #include "openvswitch/hmap.h" #include "hash.h" -struct dhcp_opts_map { +/* Generic options map which is used to store dhcpv4 opts and dhcpv6 opts. */ +struct gen_opts_map { struct hmap_node hmap_node; char *name; char *type; @@ -68,45 +69,69 @@ struct dhcp_opts_map { #define DHCP_OPT_T2 DHCP_OPTION("T2", 59, "uint32") static inline uint32_t -dhcp_opt_hash(char *opt_name) +gen_opt_hash(char *opt_name) { return hash_string(opt_name, 0); } -static inline struct dhcp_opts_map * -dhcp_opts_find(const struct hmap *dhcp_opts, char *opt_name) +static inline uint32_t +dhcp_opt_hash(char *opt_name) { - struct dhcp_opts_map *dhcp_opt; - HMAP_FOR_EACH_WITH_HASH (dhcp_opt, hmap_node, dhcp_opt_hash(opt_name), - dhcp_opts) { - if (!strcmp(dhcp_opt->name, opt_name)) { - return dhcp_opt; + return gen_opt_hash(opt_name); +} + +static inline struct gen_opts_map * +gen_opts_find(const struct hmap *gen_opts, char *opt_name) +{ + struct gen_opts_map *gen_opt; + HMAP_FOR_EACH_WITH_HASH (gen_opt, hmap_node, gen_opt_hash(opt_name), + gen_opts) { + if (!strcmp(gen_opt->name, opt_name)) { + return gen_opt; } } return NULL; } +static inline struct gen_opts_map * +dhcp_opts_find(const struct hmap *dhcp_opts, char *opt_name) +{ + return gen_opts_find(dhcp_opts, opt_name); +} + +static inline void +gen_opt_add(struct hmap *gen_opts, char *opt_name, size_t code, char *type) +{ + struct gen_opts_map *gen_opt = xzalloc(sizeof *gen_opt); + gen_opt->name = xstrdup(opt_name); + gen_opt->code = code; + gen_opt->type = xstrdup(type); + hmap_insert(gen_opts, &gen_opt->hmap_node, gen_opt_hash(opt_name)); +} + static inline void dhcp_opt_add(struct hmap *dhcp_opts, char *opt_name, size_t code, char *type) { - struct dhcp_opts_map *dhcp_opt = xzalloc(sizeof *dhcp_opt); - dhcp_opt->name = xstrdup(opt_name); - dhcp_opt->code = code; - dhcp_opt->type = xstrdup(type); - hmap_insert(dhcp_opts, &dhcp_opt->hmap_node, dhcp_opt_hash(opt_name)); + gen_opt_add(dhcp_opts, opt_name, code, type); } static inline void -dhcp_opts_destroy(struct hmap *dhcp_opts) +gen_opts_destroy(struct hmap *gen_opts) { - struct dhcp_opts_map *dhcp_opt; - HMAP_FOR_EACH_POP(dhcp_opt, hmap_node, dhcp_opts) { - free(dhcp_opt->name); - free(dhcp_opt->type); - free(dhcp_opt); + struct gen_opts_map *gen_opt; + HMAP_FOR_EACH_POP (gen_opt, hmap_node, gen_opts) { + free(gen_opt->name); + free(gen_opt->type); + free(gen_opt); } - hmap_destroy(dhcp_opts); + hmap_destroy(gen_opts); +} + +static inline void +dhcp_opts_destroy(struct hmap *dhcp_opts) +{ + gen_opts_destroy(dhcp_opts); } /* Used in the OpenFlow PACKET_IN userdata */ diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c index 2db238073..75f2c6607 100644 --- a/ovn/northd/ovn-northd.c +++ b/ovn/northd/ovn-northd.c @@ -30,7 +30,7 @@ #include "ovn/lex.h" #include "ovn/lib/chassis-index.h" #include "ovn/lib/logical-fields.h" -#include "ovn/lib/ovn-dhcp.h" +#include "ovn/lib/ovn-l7.h" #include "ovn/lib/ovn-nb-idl.h" #include "ovn/lib/ovn-sb-idl.h" #include "ovn/lib/ovn-util.h" @@ -5995,7 +5995,7 @@ update_logical_port_status(struct northd_context *ctx) hmap_destroy(&lports_hmap); } -static struct dhcp_opts_map supported_dhcp_opts[] = { +static struct gen_opts_map supported_dhcp_opts[] = { OFFERIP, DHCP_OPT_NETMASK, DHCP_OPT_ROUTER, @@ -6022,7 +6022,7 @@ static struct dhcp_opts_map supported_dhcp_opts[] = { DHCP_OPT_T2 }; -static struct dhcp_opts_map supported_dhcpv6_opts[] = { +static struct gen_opts_map supported_dhcpv6_opts[] = { DHCPV6_OPT_IA_ADDR, DHCPV6_OPT_SERVER_ID, DHCPV6_OPT_DOMAIN_SEARCH, @@ -6041,7 +6041,7 @@ check_and_add_supported_dhcp_opts_to_sb_db(struct northd_context *ctx) const struct sbrec_dhcp_options *opt_row, *opt_row_next; SBREC_DHCP_OPTIONS_FOR_EACH_SAFE(opt_row, opt_row_next, ctx->ovnsb_idl) { - struct dhcp_opts_map *dhcp_opt = + struct gen_opts_map *dhcp_opt = dhcp_opts_find(&dhcp_opts_to_add, opt_row->name); if (dhcp_opt) { hmap_remove(&dhcp_opts_to_add, &dhcp_opt->hmap_node); @@ -6050,7 +6050,7 @@ check_and_add_supported_dhcp_opts_to_sb_db(struct northd_context *ctx) } } - struct dhcp_opts_map *opt; + struct gen_opts_map *opt; HMAP_FOR_EACH (opt, hmap_node, &dhcp_opts_to_add) { struct sbrec_dhcp_options *sbrec_dhcp_option = sbrec_dhcp_options_insert(ctx->ovnsb_txn); @@ -6074,7 +6074,7 @@ check_and_add_supported_dhcpv6_opts_to_sb_db(struct northd_context *ctx) 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 = + struct gen_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); @@ -6083,7 +6083,7 @@ check_and_add_supported_dhcpv6_opts_to_sb_db(struct northd_context *ctx) } } - struct dhcp_opts_map *opt; + struct gen_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); diff --git a/ovn/utilities/ovn-trace.c b/ovn/utilities/ovn-trace.c index 59083eebe..d9465c90c 100644 --- a/ovn/utilities/ovn-trace.c +++ b/ovn/utilities/ovn-trace.c @@ -36,8 +36,8 @@ #include "ovn/lex.h" #include "ovn/lib/acl-log.h" #include "ovn/lib/logical-fields.h" +#include "ovn/lib/ovn-l7.h" #include "ovn/lib/ovn-sb-idl.h" -#include "ovn/lib/ovn-dhcp.h" #include "ovn/lib/ovn-util.h" #include "ovsdb-idl.h" #include "poll-loop.h" @@ -418,8 +418,8 @@ static struct shash symtab; static struct shash address_sets; /* DHCP options. */ -static struct hmap dhcp_opts; /* Contains "struct dhcp_opts_map"s. */ -static struct hmap dhcpv6_opts; /* Contains "struct dhcp_opts_map"s. */ +static struct hmap dhcp_opts; /* Contains "struct gen_opts_map"s. */ +static struct hmap dhcpv6_opts; /* Contains "struct gen_opts_map"s. */ static struct ovntrace_datapath * ovntrace_datapath_find_by_sb_uuid(const struct uuid *sb_uuid) @@ -867,7 +867,7 @@ read_flows(void) } static void -read_dhcp_opts(void) +read_gen_opts(void) { hmap_init(&dhcp_opts); const struct sbrec_dhcp_options *sdo; @@ -931,7 +931,7 @@ read_db(void) read_ports(); read_mcgroups(); read_address_sets(); - read_dhcp_opts(); + read_gen_opts(); read_flows(); read_mac_bindings(); } @@ -1541,7 +1541,7 @@ execute_get_mac_bind(const struct ovnact_get_mac_bind *bind, } static void -execute_put_dhcp_opts(const struct ovnact_put_dhcp_opts *pdo, +execute_put_dhcp_opts(const struct ovnact_put_opts *pdo, const char *name, struct flow *uflow, struct ovs_list *super) { @@ -1551,7 +1551,7 @@ execute_put_dhcp_opts(const struct ovnact_put_dhcp_opts *pdo, /* Format the put_dhcp_opts action. */ struct ds s = DS_EMPTY_INITIALIZER; - for (const struct ovnact_dhcp_option *o = pdo->options; + for (const struct ovnact_gen_option *o = pdo->options; o < &pdo->options[pdo->n_options]; o++) { if (o != pdo->options) { ds_put_cstr(&s, ", "); diff --git a/tests/test-ovn.c b/tests/test-ovn.c index 4beb2b8d6..67221ea50 100644 --- a/tests/test-ovn.c +++ b/tests/test-ovn.c @@ -18,6 +18,7 @@ #include #include #include + #include "command-line.h" #include "dp-packet.h" #include "fatal-signal.h" @@ -31,7 +32,7 @@ #include "ovn/expr.h" #include "ovn/lex.h" #include "ovn/lib/logical-fields.h" -#include "ovn/lib/ovn-dhcp.h" +#include "ovn/lib/ovn-l7.h" #include "ovs-thread.h" #include "ovstest.h" #include "openvswitch/shash.h" From patchwork Mon Oct 2 16:25:34 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Numan Siddique X-Patchwork-Id: 820619 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (mailfrom) smtp.mailfrom=openvswitch.org (client-ip=140.211.169.12; helo=mail.linuxfoundation.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=) Received: from mail.linuxfoundation.org (mail.linuxfoundation.org [140.211.169.12]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 3y5SH01kdWz9s7g for ; Tue, 3 Oct 2017 03:26:52 +1100 (AEDT) Received: from mail.linux-foundation.org (localhost [127.0.0.1]) by mail.linuxfoundation.org (Postfix) with ESMTP id 21977B59; Mon, 2 Oct 2017 16:25:45 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@mail.linuxfoundation.org Received: from smtp1.linuxfoundation.org (smtp1.linux-foundation.org [172.17.192.35]) by mail.linuxfoundation.org (Postfix) with ESMTPS id 4486AB50 for ; Mon, 2 Oct 2017 16:25:44 +0000 (UTC) X-Greylist: domain auto-whitelisted by SQLgrey-1.7.6 Received: from mx1.redhat.com (mx1.redhat.com [209.132.183.28]) by smtp1.linuxfoundation.org (Postfix) with ESMTPS id B78F5FC for ; Mon, 2 Oct 2017 16:25:42 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx01.intmail.prod.int.phx2.redhat.com [10.5.11.11]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id 40F6813A98; Mon, 2 Oct 2017 16:25:42 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.3.2 mx1.redhat.com 40F6813A98 Authentication-Results: ext-mx05.extmail.prod.ext.phx2.redhat.com; dmarc=none (p=none dis=none) header.from=redhat.com Authentication-Results: ext-mx05.extmail.prod.ext.phx2.redhat.com; spf=fail smtp.mailfrom=nusiddiq@redhat.com Received: from numans.blr.redhat.com (dhcp-0-126.blr.redhat.com [10.70.1.126]) by smtp.corp.redhat.com (Postfix) with ESMTP id 6F3E491B2F; Mon, 2 Oct 2017 16:25:40 +0000 (UTC) From: nusiddiq@redhat.com To: dev@openvswitch.org Date: Mon, 2 Oct 2017 21:55:34 +0530 Message-Id: <20171002162534.1710-1-nusiddiq@redhat.com> In-Reply-To: <20171002161109.956-1-nusiddiq@redhat.com> References: <20171002161109.956-1-nusiddiq@redhat.com> X-Scanned-By: MIMEDefang 2.79 on 10.5.11.11 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.29]); Mon, 02 Oct 2017 16:25:42 +0000 (UTC) X-Spam-Status: No, score=-5.0 required=5.0 tests=RCVD_IN_DNSWL_HI, RP_MATCHES_RCVD autolearn=disabled version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on smtp1.linux-foundation.org Subject: [ovs-dev] [PATCH v9 2/4] ovn-controller: Add a new action - 'put_nd_ra_opts' X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.12 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: ovs-dev-bounces@openvswitch.org Errors-To: ovs-dev-bounces@openvswitch.org From: Numan Siddique This patch adds a new OVN action 'put_nd_ra_opts' to support native IPv6 Router Advertisement in OVN. This action can be used to respond to the IPv6 Router Solicitation requests. ovn-controller parses this action and adds a NXT_PACKET_IN2 OF flow with 'pause' flag set and the RA options stored in 'userdata' field. This action is similar to 'put_dhcp_opts' and 'put_dhcpv6_opts'. When a valid IPv6 RS packet is received by the pinctrl module of ovn-controller, it frames a new RA packet and sets the RA options from the 'userdata' field and resumes the packet storing 1 in the 1-bit result sub-field. If the packet is invalid, it resumes the packet without any modifications storing 0 in the 1-bit result sub-field. Eg. reg0[5] = put_nd_ra_opts(address_mode = "slaac", mtu = 1450, slla = 01:02:03:04:05:06, prefix = aef0::/64) Note that unlike DHCPv4/v6, a new table to store the supported IPv6 ND RA options is not added in SB DB since there are only 3 ND RA options. Co-authored-by: Zongkai LI Signed-off-by: Zongkai LI Signed-off-by: Numan Siddique Acked-by: Mark Michelson --- include/ovn/actions.h | 14 +++- ovn/controller/lflow.c | 13 +++- ovn/controller/pinctrl.c | 96 +++++++++++++++++++++++ ovn/lib/actions.c | 194 +++++++++++++++++++++++++++++++++++++++++++++- ovn/lib/ovn-l7.h | 48 ++++++++++++ ovn/ovn-sb.xml | 77 ++++++++++++++++++ ovn/utilities/ovn-trace.c | 51 ++++++++---- tests/ovn.at | 31 ++++++++ tests/test-ovn.c | 13 +++- 9 files changed, 516 insertions(+), 21 deletions(-) diff --git a/include/ovn/actions.h b/include/ovn/actions.h index d13a3747b..15cee478d 100644 --- a/include/ovn/actions.h +++ b/include/ovn/actions.h @@ -72,7 +72,8 @@ struct simap; OVNACT(PUT_DHCPV6_OPTS, ovnact_put_opts) \ OVNACT(SET_QUEUE, ovnact_set_queue) \ OVNACT(DNS_LOOKUP, ovnact_dns_lookup) \ - OVNACT(LOG, ovnact_log) + OVNACT(LOG, ovnact_log) \ + OVNACT(PUT_ND_RA_OPTS, ovnact_put_opts) /* enum ovnact_type, with a member OVNACT_ for each action. */ enum OVS_PACKED_ENUM ovnact_type { @@ -418,6 +419,14 @@ enum action_opcode { * - A variable length string containing the name. */ ACTION_OPCODE_LOG, + + /* "result = put_nd_ra_opts(option, ...)". + * Arguments follow the action_header, in this format: + * - A 32-bit or 64-bit OXM header designating the result field. + * - A 32-bit integer specifying a bit offset within the result field. + * - Any number of ICMPv6 options. + */ + ACTION_OPCODE_PUT_ND_RA_OPTS, }; /* Header. */ @@ -438,6 +447,9 @@ struct ovnact_parse_params { /* hmap of 'struct gen_opts_map' to support 'put_dhcpv6_opts' action */ const struct hmap *dhcpv6_opts; + /* hmap of 'struct gen_opts_map' to support 'put_nd_ra_opts' action */ + const struct hmap *nd_ra_opts; + /* Each OVN flow exists in a logical table within a logical pipeline. * These parameters express this context for a set of OVN actions being * parsed: diff --git a/ovn/controller/lflow.c b/ovn/controller/lflow.c index 6b6b91abc..a62ec6ebe 100644 --- a/ovn/controller/lflow.c +++ b/ovn/controller/lflow.c @@ -65,6 +65,7 @@ static void consider_logical_flow(struct controller_ctx *ctx, const struct sbrec_chassis *chassis, struct hmap *dhcp_opts, struct hmap *dhcpv6_opts, + struct hmap *nd_ra_opts, uint32_t *conj_id_ofs, const struct shash *addr_sets, struct hmap *flow_table, @@ -167,17 +168,21 @@ add_logical_flows(struct controller_ctx *ctx, dhcpv6_opt_row->type); } + struct hmap nd_ra_opts = HMAP_INITIALIZER(&nd_ra_opts); + nd_ra_opts_init(&nd_ra_opts); + SBREC_LOGICAL_FLOW_FOR_EACH (lflow, ctx->ovnsb_idl) { consider_logical_flow(ctx, chassis_index, lflow, local_datapaths, group_table, chassis, - &dhcp_opts, &dhcpv6_opts, &conj_id_ofs, - addr_sets, flow_table, active_tunnels, - local_lport_ids); + &dhcp_opts, &dhcpv6_opts, &nd_ra_opts, + &conj_id_ofs, addr_sets, flow_table, + active_tunnels, local_lport_ids); } dhcp_opts_destroy(&dhcp_opts); dhcp_opts_destroy(&dhcpv6_opts); + nd_ra_opts_destroy(&nd_ra_opts); } static void @@ -189,6 +194,7 @@ consider_logical_flow(struct controller_ctx *ctx, const struct sbrec_chassis *chassis, struct hmap *dhcp_opts, struct hmap *dhcpv6_opts, + struct hmap *nd_ra_opts, uint32_t *conj_id_ofs, const struct shash *addr_sets, struct hmap *flow_table, @@ -224,6 +230,7 @@ consider_logical_flow(struct controller_ctx *ctx, .symtab = &symtab, .dhcp_opts = dhcp_opts, .dhcpv6_opts = dhcpv6_opts, + .nd_ra_opts = nd_ra_opts, .pipeline = ingress ? OVNACT_P_INGRESS : OVNACT_P_EGRESS, .n_tables = LOG_PIPELINE_LEN, diff --git a/ovn/controller/pinctrl.c b/ovn/controller/pinctrl.c index 43e3cba23..3a1348937 100644 --- a/ovn/controller/pinctrl.c +++ b/ovn/controller/pinctrl.c @@ -81,6 +81,10 @@ static void pinctrl_handle_nd_na(const struct flow *ip_flow, struct ofpbuf *userdata); static void reload_metadata(struct ofpbuf *ofpacts, const struct match *md); +static void pinctrl_handle_put_nd_ra_opts( + const struct flow *ip_flow, struct dp_packet *pkt_in, + struct ofputil_packet_in *pin, struct ofpbuf *userdata, + struct ofpbuf *continuation); COVERAGE_DEFINE(pinctrl_drop_put_mac_binding); @@ -985,6 +989,11 @@ process_packet_in(const struct ofp_header *msg, struct controller_ctx *ctx) handle_acl_log(&headers, &userdata); break; + case ACTION_OPCODE_PUT_ND_RA_OPTS: + pinctrl_handle_put_nd_ra_opts(&headers, &packet, &pin, &userdata, + &continuation); + break; + default: VLOG_WARN_RL(&rl, "unrecognized packet-in opcode %"PRIu32, ntohl(ah->opcode)); @@ -1848,3 +1857,90 @@ exit: dp_packet_uninit(&packet); ofpbuf_uninit(&ofpacts); } + +static void +pinctrl_handle_put_nd_ra_opts( + const struct flow *in_flow, struct dp_packet *pkt_in, + struct ofputil_packet_in *pin, struct ofpbuf *userdata, + struct ofpbuf *continuation) +{ + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + enum ofp_version version = rconn_get_version(swconn); + enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version); + struct dp_packet *pkt_out_ptr = NULL; + uint32_t success = 0; + + /* Parse result field. */ + const struct mf_field *f; + enum ofperr ofperr = nx_pull_header(userdata, NULL, &f, NULL); + if (ofperr) { + VLOG_WARN_RL(&rl, "bad result OXM (%s)", ofperr_to_string(ofperr)); + goto exit; + } + + /* Parse result offset. */ + ovs_be32 *ofsp = ofpbuf_try_pull(userdata, sizeof *ofsp); + if (!ofsp) { + VLOG_WARN_RL(&rl, "offset not present in the userdata"); + goto exit; + } + + /* Check that the result is valid and writable. */ + struct mf_subfield dst = { .field = f, .ofs = ntohl(*ofsp), .n_bits = 1 }; + ofperr = mf_check_dst(&dst, NULL); + if (ofperr) { + VLOG_WARN_RL(&rl, "bad result bit (%s)", ofperr_to_string(ofperr)); + goto exit; + } + + if (!userdata->size) { + VLOG_WARN_RL(&rl, "IPv6 ND RA options not present in the userdata"); + goto exit; + } + + if (!is_icmpv6(in_flow, NULL) || in_flow->tp_dst != htons(0) || + in_flow->tp_src != htons(ND_ROUTER_SOLICIT)) { + VLOG_WARN_RL(&rl, "put_nd_ra action on invalid or unsupported packet"); + goto exit; + } + + size_t new_packet_size = pkt_in->l4_ofs + userdata->size; + struct dp_packet pkt_out; + dp_packet_init(&pkt_out, new_packet_size); + dp_packet_clear(&pkt_out); + dp_packet_prealloc_tailroom(&pkt_out, new_packet_size); + pkt_out_ptr = &pkt_out; + + /* Copy L2 and L3 headers from pkt_in. */ + dp_packet_put(&pkt_out, dp_packet_pull(pkt_in, pkt_in->l4_ofs), + pkt_in->l4_ofs); + + pkt_out.l2_5_ofs = pkt_in->l2_5_ofs; + pkt_out.l2_pad_size = pkt_in->l2_pad_size; + pkt_out.l3_ofs = pkt_in->l3_ofs; + pkt_out.l4_ofs = pkt_in->l4_ofs; + + /* Copy the ICMPv6 Router Advertisement data from 'userdata' field. */ + dp_packet_put(&pkt_out, userdata->data, userdata->size); + + /* Set the IPv6 payload length and calculate the ICMPv6 checksum. */ + struct ovs_16aligned_ip6_hdr *nh = dp_packet_l3(&pkt_out); + nh->ip6_plen = htons(userdata->size); + struct ovs_ra_msg *ra = dp_packet_l4(&pkt_out); + ra->icmph.icmp6_cksum = 0; + uint32_t icmp_csum = packet_csum_pseudoheader6(nh); + ra->icmph.icmp6_cksum = csum_finish(csum_continue( + icmp_csum, ra, userdata->size)); + pin->packet = dp_packet_data(&pkt_out); + pin->packet_len = dp_packet_size(&pkt_out); + success = 1; + +exit: + if (!ofperr) { + union mf_subvalue sv; + sv.u8_val = success; + mf_write_subfield(&dst, &sv, &pin->flow_metadata); + } + queue_msg(ofputil_encode_resume(pin, continuation, proto)); + dp_packet_uninit(pkt_out_ptr); +} diff --git a/ovn/lib/actions.c b/ovn/lib/actions.c index 6f16d267a..8d5863c0a 100644 --- a/ovn/lib/actions.c +++ b/ovn/lib/actions.c @@ -22,6 +22,7 @@ #include "compiler.h" #include "ovn-l7.h" #include "hash.h" +#include "lib/packets.h" #include "logical-fields.h" #include "nx-match.h" #include "openvswitch/dynamic-string.h" @@ -1438,7 +1439,7 @@ parse_put_opts(struct action_context *ctx, const struct expr_field *dst, struct ovnact_put_opts *po, const struct hmap *gen_opts, const char *opts_type) { - lexer_get(ctx->lexer); /* Skip put_dhcp[v6]_opts. */ + lexer_get(ctx->lexer); /* Skip put_dhcp[v6]_opts / put_nd_ra_opts. */ lexer_get(ctx->lexer); /* Skip '('. */ /* Validate that the destination is a 1-bit, modifiable field. */ @@ -1771,6 +1772,193 @@ static void ovnact_dns_lookup_free(struct ovnact_dns_lookup *dl OVS_UNUSED) { } + +/* Parses the "put_nd_ra_opts" action. + * The caller has already consumed " =", so this just parses the rest. */ +static void +parse_put_nd_ra_opts(struct action_context *ctx, const struct expr_field *dst, + struct ovnact_put_opts *po) +{ + parse_put_opts(ctx, dst, po, ctx->pp->nd_ra_opts, "IPv6 ND RA"); + + if (ctx->lexer->error) { + return; + } + + bool addr_mode_stateful = false; + bool prefix_set = false; + bool slla_present = false; + /* Let's validate the options. */ + for (struct ovnact_gen_option *o = po->options; + o < &po->options[po->n_options]; o++) { + const union expr_constant *c = o->value.values; + if (o->value.n_values > 1) { + lexer_error(ctx->lexer, "parse_put_nd_ra_opts -Invalid value for" + " the option %s.", o->option->name); + return; + } + + switch (o->option->code) { + case ND_RA_FLAG_ADDR_MODE: + if (!c->string || (strcmp(c->string, "slaac") && + strcmp(c->string, "dhcpv6_stateful") && + strcmp(c->string, "dhcpv6_stateless"))) { + lexer_error(ctx->lexer, "parse_put_nd_ra_opts -Invalid value " + "for the option %s.", o->option->name); + return; + } + + if (!strcmp(c->string, "dhcpv6_stateful")) { + addr_mode_stateful = true; + } + break; + + case ND_OPT_SOURCE_LINKADDR: + if (c->format != LEX_F_ETHERNET) { + lexer_error(ctx->lexer, "parse_put_nd_ra_opts -Invalid value " + "for the option %s.", o->option->name); + } + slla_present = true; + break; + + case ND_OPT_PREFIX_INFORMATION: + if (c->format != LEX_F_IPV6 || !c->masked) { + lexer_error(ctx->lexer, "parse_put_nd_ra_opts -Invalid value " + "for the option %s.", o->option->name); + } + prefix_set = true; + break; + + case ND_OPT_MTU: + if (c->format != LEX_F_DECIMAL) { + lexer_error(ctx->lexer, "parse_put_nd_ra_opts -Invalid value " + "for the option %s.", o->option->name); + } + break; + } + } + + if (ctx->lexer->error) { + return; + } + + if (!slla_present) { + lexer_error(ctx->lexer, "parse_put_nd_ra_opts - slla option not" + " present."); + return; + } + + if (addr_mode_stateful && prefix_set) { + lexer_error(ctx->lexer, "parse_put_nd_ra_opts - prefix option can't be" + " set when address mode is dhcpv6_stateful."); + return; + } + + if (!addr_mode_stateful && !prefix_set) { + lexer_error(ctx->lexer, "parse_put_nd_ra_opts - prefix option needs " + "to be set when address mode is slaac/dhcpv6_stateless."); + return; + } + + add_prerequisite(ctx, "ip6"); +} + +static void +format_PUT_ND_RA_OPTS(const struct ovnact_put_opts *po, + struct ds *s) +{ + format_put_opts("put_nd_ra_opts", po, s); +} + +static void +encode_put_nd_ra_option(const struct ovnact_gen_option *o, + struct ofpbuf *ofpacts, struct ovs_ra_msg *ra) +{ + const union expr_constant *c = o->value.values; + + switch (o->option->code) { + case ND_RA_FLAG_ADDR_MODE: + if (!strcmp(c->string, "dhcpv6_stateful")) { + ra->mo_flags = IPV6_ND_RA_FLAG_MANAGED_ADDR_CONFIG; + } else if (!strcmp(c->string, "dhcpv6_stateless")) { + ra->mo_flags = IPV6_ND_RA_FLAG_OTHER_ADDR_CONFIG; + } + break; + + case ND_OPT_SOURCE_LINKADDR: + { + struct ovs_nd_lla_opt *lla_opt = + ofpbuf_put_uninit(ofpacts, sizeof *lla_opt); + lla_opt->type = ND_OPT_SOURCE_LINKADDR; + lla_opt->len = 1; + lla_opt->mac = c->value.mac; + break; + } + + case ND_OPT_MTU: + { + struct ovs_nd_mtu_opt *mtu_opt = + ofpbuf_put_uninit(ofpacts, sizeof *mtu_opt); + mtu_opt->type = ND_OPT_MTU; + mtu_opt->len = 1; + mtu_opt->reserved = 0; + put_16aligned_be32(&mtu_opt->mtu, c->value.be32_int); + break; + } + + case ND_OPT_PREFIX_INFORMATION: + { + struct ovs_nd_prefix_opt *prefix_opt = + ofpbuf_put_uninit(ofpacts, sizeof *prefix_opt); + uint8_t prefix_len = ipv6_count_cidr_bits(&c->mask.ipv6); + prefix_opt->type = ND_OPT_PREFIX_INFORMATION; + prefix_opt->len = 4; + prefix_opt->prefix_len = prefix_len; + prefix_opt->la_flags = IPV6_ND_RA_OPT_PREFIX_FLAGS; + put_16aligned_be32(&prefix_opt->valid_lifetime, + htonl(IPV6_ND_RA_OPT_PREFIX_VALID_LIFETIME)); + put_16aligned_be32(&prefix_opt->preferred_lifetime, + htonl(IPV6_ND_RA_OPT_PREFIX_PREFERRED_LIFETIME)); + put_16aligned_be32(&prefix_opt->reserved, 0); + memcpy(prefix_opt->prefix.be32, &c->value.be128[7].be32, + sizeof(ovs_be32[4])); + break; + } + } +} + +static void +encode_PUT_ND_RA_OPTS(const struct ovnact_put_opts *po, + const struct ovnact_encode_params *ep OVS_UNUSED, + struct ofpbuf *ofpacts) +{ + struct mf_subfield dst = expr_resolve_field(&po->dst); + + size_t oc_offset = encode_start_controller_op( + ACTION_OPCODE_PUT_ND_RA_OPTS, true, ofpacts); + nx_put_header(ofpacts, dst.field->id, OFP13_VERSION, false); + ovs_be32 ofs = htonl(dst.ofs); + ofpbuf_put(ofpacts, &ofs, sizeof ofs); + + /* Frame the complete ICMPv6 Router Advertisement data encoding + * the ND RA options in it, in the userdata field, so that when + * pinctrl module receives the ICMPv6 Router Solicitation packet + * it can copy the userdata field AS IS and resume the packet. + */ + struct ovs_ra_msg *ra = ofpbuf_put_zeros(ofpacts, sizeof *ra); + ra->icmph.icmp6_type = ND_ROUTER_ADVERT; + ra->cur_hop_limit = IPV6_ND_RA_CUR_HOP_LIMIT; + ra->mo_flags = 0; + ra->router_lifetime = htons(IPV6_ND_RA_LIFETIME); + + for (const struct ovnact_gen_option *o = po->options; + o < &po->options[po->n_options]; o++) { + encode_put_nd_ra_option(o, ofpacts, ra); + } + + encode_finish_controller_op(oc_offset, ofpacts); +} + static void parse_log_arg(struct action_context *ctx, struct ovnact_log *log) @@ -1910,6 +2098,10 @@ parse_set_action(struct action_context *ctx) } else if (!strcmp(ctx->lexer->token.s, "dns_lookup") && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) { parse_dns_lookup(ctx, &lhs, ovnact_put_DNS_LOOKUP(ctx->ovnacts)); + } else if (!strcmp(ctx->lexer->token.s, "put_nd_ra_opts") + && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) { + parse_put_nd_ra_opts(ctx, &lhs, + ovnact_put_PUT_ND_RA_OPTS(ctx->ovnacts)); } else { parse_assignment_action(ctx, false, &lhs); } diff --git a/ovn/lib/ovn-l7.h b/ovn/lib/ovn-l7.h index 40bd75461..41cdacdfc 100644 --- a/ovn/lib/ovn-l7.h +++ b/ovn/lib/ovn-l7.h @@ -18,6 +18,7 @@ #define OVN_DHCP_H 1 #include +#include #include "openvswitch/hmap.h" #include "hash.h" @@ -206,4 +207,51 @@ struct dhcpv6_opt_ia_na { #define DHCPV6_OPT_PAYLOAD(opt) \ (void *)((char *)opt + sizeof(struct dhcpv6_opt_header)) +static inline struct gen_opts_map * +nd_ra_opts_find(const struct hmap *nd_ra_opts, char *opt_name) +{ + return gen_opts_find(nd_ra_opts, opt_name); +} + +static inline void +nd_ra_opt_add(struct hmap *nd_ra_opts, char *opt_name, size_t code, + char *type) +{ + gen_opt_add(nd_ra_opts, opt_name, code, type); +} + +static inline void +nd_ra_opts_destroy(struct hmap *nd_ra_opts) +{ + gen_opts_destroy(nd_ra_opts); +} + + +#define ND_RA_FLAG_ADDR_MODE 0 + + +/* Default values of various IPv6 Neighbor Discovery protocol options and + * flags. See RFC 4861 for more information. + * */ +#define IPV6_ND_RA_FLAG_MANAGED_ADDR_CONFIG 0x80 +#define IPV6_ND_RA_FLAG_OTHER_ADDR_CONFIG 0x40 + +#define IPV6_ND_RA_CUR_HOP_LIMIT 255 +#define IPV6_ND_RA_LIFETIME 0xffff +#define IPV6_ND_RA_REACHABLE_TIME 0 +#define IPV6_ND_RA_RETRANSMIT_TIMER 0 + +#define IPV6_ND_RA_OPT_PREFIX_FLAGS 0xc0 +#define IPV6_ND_RA_OPT_PREFIX_VALID_LIFETIME 0xffffffff +#define IPV6_ND_RA_OPT_PREFIX_PREFERRED_LIFETIME 0xffffffff + +static inline void +nd_ra_opts_init(struct hmap *nd_ra_opts) +{ + nd_ra_opt_add(nd_ra_opts, "addr_mode", ND_RA_FLAG_ADDR_MODE, "str"); + nd_ra_opt_add(nd_ra_opts, "slla", ND_OPT_SOURCE_LINKADDR, "mac"); + nd_ra_opt_add(nd_ra_opts, "prefix", ND_OPT_PREFIX_INFORMATION, "ipv6"); + nd_ra_opt_add(nd_ra_opts, "mtu", ND_OPT_MTU, "uint32"); +} + #endif /* OVN_DHCP_H */ diff --git a/ovn/ovn-sb.xml b/ovn/ovn-sb.xml index 0a894f8cb..fab3f9de6 100644 --- a/ovn/ovn-sb.xml +++ b/ovn/ovn-sb.xml @@ -1516,6 +1516,83 @@ Prerequisite: udp

+ +
+ R = put_nd_ra_opts(D1 = V1, D2 = V2, ..., Dn = Vn); +
+ +
+

+ Parameters: The following IPv6 ND Router Advertisement + option/value pairs as defined in RFC 4861. + +

    +
  • + addr_mode +

    + Mandatory parameter which specifies the address mode flag to + be set in the RA flag options field. The value of this option + is a string and the following values can be defined - + "slaac", "dhcpv6_stateful" and "dhcpv6_stateless". +

    +
  • + +
  • + slla +

    + Mandatory parameter which specifies the link-layer address of + the interface from which the Router Advertisement is sent. +

    +
  • + +
  • + mtu +

    + Optional parameter which specifies the MTU. +

    +
  • + +
  • + prefix +

    + Optional parameter which should be specified if the addr_mode + is "slaac" or "dhcpv6_stateless". The value should be an IPv6 + prefix which will be used for stateless IPv6 address + configuration. This option can be defined multiple times. +

    +
  • +
+

+ +

+ Result: stored to a 1-bit subfield R. +

+ +

+ Valid only in the ingress pipeline. +

+ +

+ When this action is applied to an IPv6 Router solicitation request + packet, it changes the packet into an IPv6 Router Advertisement + reply and adds the options specified in the parameters, and stores + 1 in R. +

+ +

+ When this action is applied to a non-IPv6 Router solicitation + packet or an invalid IPv6 request packet , it leaves the packet + unchanged and stores 0 in R. +

+ +

+ Example: + + reg0[3] = put_nd_ra_opts(addr_mode = "slaac", + slla = 00:00:00:00:10:02, prefix = aef0::/64, mtu = 1450); + +

+
diff --git a/ovn/utilities/ovn-trace.c b/ovn/utilities/ovn-trace.c index d9465c90c..211148b8b 100644 --- a/ovn/utilities/ovn-trace.c +++ b/ovn/utilities/ovn-trace.c @@ -420,6 +420,7 @@ static struct shash address_sets; /* DHCP options. */ static struct hmap dhcp_opts; /* Contains "struct gen_opts_map"s. */ static struct hmap dhcpv6_opts; /* Contains "struct gen_opts_map"s. */ +static struct hmap nd_ra_opts; /* Contains "struct gen_opts_map"s. */ static struct ovntrace_datapath * ovntrace_datapath_find_by_sb_uuid(const struct uuid *sb_uuid) @@ -806,6 +807,7 @@ read_flows(void) .symtab = &symtab, .dhcp_opts = &dhcp_opts, .dhcpv6_opts = &dhcpv6_opts, + .nd_ra_opts = &nd_ra_opts, .pipeline = (!strcmp(sblf->pipeline, "ingress") ? OVNACT_P_INGRESS : OVNACT_P_EGRESS), @@ -881,6 +883,9 @@ read_gen_opts(void) SBREC_DHCPV6_OPTIONS_FOR_EACH(sdo6, ovnsb_idl) { dhcp_opt_add(&dhcpv6_opts, sdo6->name, sdo6->code, sdo6->type); } + + hmap_init(&nd_ra_opts); + nd_ra_opts_init(&nd_ra_opts); } static void @@ -1541,19 +1546,15 @@ execute_get_mac_bind(const struct ovnact_get_mac_bind *bind, } static void -execute_put_dhcp_opts(const struct ovnact_put_opts *pdo, - const char *name, struct flow *uflow, - struct ovs_list *super) +execute_put_opts(const struct ovnact_put_opts *po, + const char *name, struct flow *uflow, + struct ovs_list *super) { - ovntrace_node_append( - super, OVNTRACE_NODE_ERROR, - "/* We assume that this packet is DHCPDISCOVER or DHCPREQUEST. */"); - /* Format the put_dhcp_opts action. */ struct ds s = DS_EMPTY_INITIALIZER; - for (const struct ovnact_gen_option *o = pdo->options; - o < &pdo->options[pdo->n_options]; o++) { - if (o != pdo->options) { + for (const struct ovnact_gen_option *o = po->options; + o < &po->options[po->n_options]; o++) { + if (o != po->options) { ds_put_cstr(&s, ", "); } ds_put_format(&s, "%s = ", o->option->name); @@ -1562,22 +1563,41 @@ execute_put_dhcp_opts(const struct ovnact_put_opts *pdo, ovntrace_node_append(super, OVNTRACE_NODE_MODIFY, "%s(%s)", name, ds_cstr(&s)); - struct mf_subfield dst = expr_resolve_field(&pdo->dst); + struct mf_subfield dst = expr_resolve_field(&po->dst); if (!mf_is_register(dst.field->id)) { /* Format assignment. */ ds_clear(&s); - expr_field_format(&pdo->dst, &s); + expr_field_format(&po->dst, &s); ovntrace_node_append(super, OVNTRACE_NODE_MODIFY, "%s = 1", ds_cstr(&s)); } ds_destroy(&s); - struct mf_subfield sf = expr_resolve_field(&pdo->dst); + struct mf_subfield sf = expr_resolve_field(&po->dst); union mf_subvalue sv = { .u8_val = 1 }; mf_write_subfield_flow(&sf, &sv, uflow); } static void +execute_put_dhcp_opts(const struct ovnact_put_opts *pdo, + const char *name, struct flow *uflow, + struct ovs_list *super) +{ + ovntrace_node_append( + super, OVNTRACE_NODE_ERROR, + "/* We assume that this packet is DHCPDISCOVER or DHCPREQUEST. */"); + execute_put_opts(pdo, name, uflow, super); +} + +static void +execute_put_nd_ra_opts(const struct ovnact_put_opts *pdo, + const char *name, struct flow *uflow, + struct ovs_list *super) +{ + execute_put_opts(pdo, name, uflow, super); +} + +static void execute_next(const struct ovnact_next *next, const struct ovntrace_datapath *dp, struct flow *uflow, enum ovnact_pipeline pipeline, struct ovs_list *super) @@ -1814,6 +1834,11 @@ trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len, "put_dhcpv6_opts", uflow, super); break; + case OVNACT_PUT_ND_RA_OPTS: + execute_put_nd_ra_opts(ovnact_get_PUT_DHCPV6_OPTS(a), + "put_nd_ra_opts", uflow, super); + break; + case OVNACT_SET_QUEUE: /* The set_queue action is slippery from a logical perspective. It * has no visible effect as long as the packet remains on the same diff --git a/tests/ovn.at b/tests/ovn.at index 6c38b973f..e56dc6232 100644 --- a/tests/ovn.at +++ b/tests/ovn.at @@ -1066,6 +1066,37 @@ reg1[0] = dns_lookup(); reg1[0] = dns_lookup("foo"); dns_lookup doesn't take any parameters +# put_nd_ra_opts +reg1[0] = put_nd_ra_opts(addr_mode = "slaac", mtu = 1500, prefix = aef0::/64, slla = ae:01:02:03:04:05); + encodes as controller(userdata=00.00.00.08.00.00.00.00.00.01.de.10.00.00.00.40.86.00.00.00.ff.00.ff.ff.00.00.00.00.00.00.00.00.05.01.00.00.00.00.05.dc.03.04.40.c0.ff.ff.ff.ff.ff.ff.ff.ff.00.00.00.00.ae.f0.00.00.00.00.00.00.00.00.00.00.00.00.00.00.01.01.ae.01.02.03.04.05,pause) + has prereqs ip6 +reg1[0] = put_nd_ra_opts(addr_mode = "dhcpv6_stateful", slla = ae:01:02:03:04:10, mtu = 1450); + encodes as controller(userdata=00.00.00.08.00.00.00.00.00.01.de.10.00.00.00.40.86.00.00.00.ff.80.ff.ff.00.00.00.00.00.00.00.00.01.01.ae.01.02.03.04.10.05.01.00.00.00.00.05.aa,pause) + has prereqs ip6 +reg1[0] = put_nd_ra_opts(addr_mode = "dhcpv6_stateless", slla = ae:01:02:03:04:06, prefix = aef0::/64); + encodes as controller(userdata=00.00.00.08.00.00.00.00.00.01.de.10.00.00.00.40.86.00.00.00.ff.40.ff.ff.00.00.00.00.00.00.00.00.01.01.ae.01.02.03.04.06.03.04.40.c0.ff.ff.ff.ff.ff.ff.ff.ff.00.00.00.00.ae.f0.00.00.00.00.00.00.00.00.00.00.00.00.00.00,pause) + has prereqs ip6 +reg1[0] = put_nd_ra_opts(addr_mode = "slaac", mtu = 1500, prefix = aef0::/64); + parse_put_nd_ra_opts - slla option not present. +reg1[0] = put_nd_ra_opts(addr_mode = "dhcpv6_stateful", mtu = 1450, prefix = aef0::/64, prefix = bef0::/64, slla = ae:01:02:03:04:10); + parse_put_nd_ra_opts - prefix option can't be set when address mode is dhcpv6_stateful. +reg1[0] = put_nd_ra_opts(addr_mode = "dhcpv6_stateful", mtu = 1450, prefix = aef0::/64, prefix = bef0::/64, slla = ae:01:02:03:04:10); + parse_put_nd_ra_opts - prefix option can't be set when address mode is dhcpv6_stateful. +reg1[0] = put_nd_ra_opts(addr_mode = "slaac", slla = ae:01:02:03:04:10); + parse_put_nd_ra_opts - prefix option needs to be set when address mode is slaac/dhcpv6_stateless. +reg1[0] = put_nd_ra_opts(addr_mode = "dhcpv6_stateless", slla = ae:01:02:03:04:10); + parse_put_nd_ra_opts - prefix option needs to be set when address mode is slaac/dhcpv6_stateless. +reg1[0] = put_nd_ra_opts(addr_mode = dhcpv6_stateless, prefix = aef0::/64, slla = ae:01:02:03:04:10); + Syntax error at `dhcpv6_stateless' expecting constant. +reg1[0] = put_nd_ra_opts(addr_mode = "slaac", mtu = 1500, prefix = aef0::, slla = ae:01:02:03:04:10); + parse_put_nd_ra_opts -Invalid value for the option prefix. +reg1[0] = put_nd_ra_opts(addr_mode = "foo", mtu = 1500, slla = ae:01:02:03:04:10); + parse_put_nd_ra_opts -Invalid value for the option addr_mode. +reg1[0] = put_nd_ra_opts(addr_mode = "slaac", mtu = "1500", slla = ae:01:02:03:04:10); + IPv6 ND RA option mtu requires numeric value. +reg1[0] = put_nd_ra_opts(addr_mode = "slaac", mtu = 10.0.0.4, slla = ae:01:02:03:04:10); + parse_put_nd_ra_opts -Invalid value for the option mtu. + # Contradictionary prerequisites (allowed but not useful): ip4.src = ip6.src[0..31]; encodes as move:NXM_NX_IPV6_SRC[0..31]->NXM_OF_IP_SRC[] diff --git a/tests/test-ovn.c b/tests/test-ovn.c index 67221ea50..f9a5085f7 100644 --- a/tests/test-ovn.c +++ b/tests/test-ovn.c @@ -155,7 +155,8 @@ create_symtab(struct shash *symtab) } static void -create_dhcp_opts(struct hmap *dhcp_opts, struct hmap *dhcpv6_opts) +create_gen_opts(struct hmap *dhcp_opts, struct hmap *dhcpv6_opts, + struct hmap *nd_ra_opts) { hmap_init(dhcp_opts); dhcp_opt_add(dhcp_opts, "offerip", 0, "ipv4"); @@ -187,6 +188,10 @@ create_dhcp_opts(struct hmap *dhcp_opts, struct hmap *dhcpv6_opts) dhcp_opt_add(dhcpv6_opts, "ia_addr", 5, "ipv6"); dhcp_opt_add(dhcpv6_opts, "dns_server", 23, "ipv6"); dhcp_opt_add(dhcpv6_opts, "domain_search", 24, "str"); + + /* IPv6 ND RA options. */ + hmap_init(nd_ra_opts); + nd_ra_opts_init(nd_ra_opts); } static void @@ -1193,12 +1198,13 @@ test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED) struct shash symtab; struct hmap dhcp_opts; struct hmap dhcpv6_opts; + struct hmap nd_ra_opts; struct simap ports; struct ds input; bool ok = true; create_symtab(&symtab); - create_dhcp_opts(&dhcp_opts, &dhcpv6_opts); + create_gen_opts(&dhcp_opts, &dhcpv6_opts, &nd_ra_opts); /* Initialize group ids. */ struct group_table group_table; @@ -1226,6 +1232,7 @@ test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED) .symtab = &symtab, .dhcp_opts = &dhcp_opts, .dhcpv6_opts = &dhcpv6_opts, + .nd_ra_opts = &nd_ra_opts, .n_tables = 24, .cur_ltable = 10, }; @@ -1310,7 +1317,7 @@ test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED) shash_destroy(&symtab); dhcp_opts_destroy(&dhcp_opts); dhcp_opts_destroy(&dhcpv6_opts); - + nd_ra_opts_destroy(&nd_ra_opts); exit(ok ? EXIT_SUCCESS : EXIT_FAILURE); } From patchwork Mon Oct 2 16:25:42 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Numan Siddique X-Patchwork-Id: 820621 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (mailfrom) smtp.mailfrom=openvswitch.org (client-ip=140.211.169.12; helo=mail.linuxfoundation.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=) Received: from mail.linuxfoundation.org (mail.linuxfoundation.org [140.211.169.12]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 3y5SJ312gRz9t6n for ; Tue, 3 Oct 2017 03:27:47 +1100 (AEDT) Received: from mail.linux-foundation.org (localhost [127.0.0.1]) by mail.linuxfoundation.org (Postfix) with ESMTP id 4A13CB69; Mon, 2 Oct 2017 16:25:53 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@mail.linuxfoundation.org Received: from smtp1.linuxfoundation.org (smtp1.linux-foundation.org [172.17.192.35]) by mail.linuxfoundation.org (Postfix) with ESMTPS id A181EB62 for ; Mon, 2 Oct 2017 16:25:51 +0000 (UTC) X-Greylist: domain auto-whitelisted by SQLgrey-1.7.6 Received: from mx1.redhat.com (mx1.redhat.com [209.132.183.28]) by smtp1.linuxfoundation.org (Postfix) with ESMTPS id 378EB1A9 for ; Mon, 2 Oct 2017 16:25:50 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx05.intmail.prod.int.phx2.redhat.com [10.5.11.15]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id AE214552EA; Mon, 2 Oct 2017 16:25:49 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.3.2 mx1.redhat.com AE214552EA Authentication-Results: ext-mx05.extmail.prod.ext.phx2.redhat.com; dmarc=none (p=none dis=none) header.from=redhat.com Authentication-Results: ext-mx05.extmail.prod.ext.phx2.redhat.com; spf=fail smtp.mailfrom=nusiddiq@redhat.com Received: from numans.blr.redhat.com (dhcp-0-126.blr.redhat.com [10.70.1.126]) by smtp.corp.redhat.com (Postfix) with ESMTP id 201305EDFF; Mon, 2 Oct 2017 16:25:47 +0000 (UTC) From: nusiddiq@redhat.com To: dev@openvswitch.org Date: Mon, 2 Oct 2017 21:55:42 +0530 Message-Id: <20171002162542.1848-1-nusiddiq@redhat.com> In-Reply-To: <20171002161109.956-1-nusiddiq@redhat.com> References: <20171002161109.956-1-nusiddiq@redhat.com> X-Scanned-By: MIMEDefang 2.79 on 10.5.11.15 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.29]); Mon, 02 Oct 2017 16:25:49 +0000 (UTC) X-Spam-Status: No, score=-5.0 required=5.0 tests=RCVD_IN_DNSWL_HI, RP_MATCHES_RCVD autolearn=disabled version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on smtp1.linux-foundation.org Subject: [ovs-dev] [PATCH v9 3/4] ovn-northd: Add logical flows to support native IPv6 RA X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.12 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: ovs-dev-bounces@openvswitch.org Errors-To: ovs-dev-bounces@openvswitch.org From: Zongkai LI This patch adds logical flows which sends IPv6 Router Advertisement packet in response to the IPv6 Router Solicitation request. It uses the actions "put_nd_ra_opts" to transform the RS packet to RA packet in the newly added ingress stage "lr_in_nd_ra_options" in router pipeline. If the action "put_nd_ra_opts" is successful, it sends the RA packet back to the originating port in the next ingress stage "lr_in_nd_ra_response". A new column "ipv6_ra_configs" is added in the Logical_Router_Port table, which the CMS is expected to configure IPv6 RA configurations - "address_mode" and "mtu" for adding these flows. Co-authored-by: Numan Siddique Signed-off-by: Zongkai LI Signed-off-by: Numan Siddique Acked-by: Miguel Angel Ajo Acked-by: Mark Michelson --- ovn/lib/logical-fields.c | 4 + ovn/northd/ovn-northd.8.xml | 83 ++++++++++++++- ovn/northd/ovn-northd.c | 137 +++++++++++++++++++++--- ovn/ovn-nb.ovsschema | 7 +- ovn/ovn-nb.xml | 39 +++++++ ovn/ovn-sb.xml | 4 + tests/ovn.at | 249 ++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 500 insertions(+), 23 deletions(-) diff --git a/ovn/lib/logical-fields.c b/ovn/lib/logical-fields.c index 26e336f5a..a8b5e3c51 100644 --- a/ovn/lib/logical-fields.c +++ b/ovn/lib/logical-fields.c @@ -183,6 +183,10 @@ ovn_init_symtab(struct shash *symtab) "icmp6.type == 135 && icmp6.code == 0 && ip.ttl == 255"); expr_symtab_add_predicate(symtab, "nd_na", "icmp6.type == 136 && icmp6.code == 0 && ip.ttl == 255"); + expr_symtab_add_predicate(symtab, "nd_rs", + "icmp6.type == 133 && icmp6.code == 0 && ip.ttl == 255"); + expr_symtab_add_predicate(symtab, "nd_ra", + "icmp6.type == 134 && icmp6.code == 0 && ip.ttl == 255"); expr_symtab_add_field(symtab, "nd.target", MFF_ND_TARGET, "nd", false); expr_symtab_add_field(symtab, "nd.sll", MFF_ND_SLL, "nd_ns", false); expr_symtab_add_field(symtab, "nd.tll", MFF_ND_TLL, "nd_na", false); diff --git a/ovn/northd/ovn-northd.8.xml b/ovn/northd/ovn-northd.8.xml index 0d85ec0d2..a994abf78 100644 --- a/ovn/northd/ovn-northd.8.xml +++ b/ovn/northd/ovn-northd.8.xml @@ -1584,7 +1584,82 @@ icmp4 { -

Ingress Table 5: IP Routing

+

Ingress Table 5: IPv6 ND RA option processing

+ +
    +
  • +

    + A priority-50 logical flow is added for each logical router port + configured with IPv6 ND RA options which matches IPv6 ND Router + Solicitation packet and applies the action + put_nd_ra_opts and advances the packet to the next + table. +

    + +
    +reg0[5] = put_nd_ra_opts(options);next;
    +        
    + +

    + For a valid IPv6 ND RS packet, this transforms the packet into an + IPv6 ND RA reply and sets the RA options to the packet and stores 1 + into reg0[5]. For other kinds of packets, it just stores 0 into + reg0[5]. Either way, it continues to the next table. +

    +
  • + +
  • + A priority-0 logical flow with match 1 has actions + next;. +
  • +
+ +

Ingress Table 6: IPv6 ND RA responder

+ +

+ This table implements IPv6 ND RA responder for the IPv6 ND RA replies + generated by the previous table. +

+ +
    +
  • +

    + A priority-50 logical flow is added for each logical router port + configured with IPv6 ND RA options which matches IPv6 ND RA + packets and reg0[5] == 1 and responds back to the + inport after applying these actions. + If reg0[5] is set to 1, it means that the action + put_nd_ra_opts was successful. +

    + +
    +eth.dst = eth.src;
    +eth.src = E;
    +ip6.dst = ip6.src;
    +ip6.src = I;
    +outport = P;
    +flags.loopback = 1;
    +output;
    +        
    + +

    + where E is the MAC address and I is the IPv6 + link local address of the logical router port. +

    + +

    + (This terminates packet processing in ingress pipeline; the packet + does not go to the next ingress table.) +

    +
  • + +
  • + A priority-0 logical flow with match 1 has actions + next;. +
  • +
+ +

Ingress Table 7: IP Routing

A packet that arrives at this table is an IP packet that should be @@ -1686,7 +1761,7 @@ next; -

Ingress Table 6: ARP/ND Resolution

+

Ingress Table 8: ARP/ND Resolution

Any packet that reaches this table is an IP packet whose next-hop @@ -1779,7 +1854,7 @@ next; -

Ingress Table 7: Gateway Redirect

+

Ingress Table 9: Gateway Redirect

For distributed logical routers where one of the logical router @@ -1836,7 +1911,7 @@ next; -

Ingress Table 8: ARP Request

+

Ingress Table 10: ARP Request

In the common case where the Ethernet destination has been resolved, this diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c index 75f2c6607..3da20d25b 100644 --- a/ovn/northd/ovn-northd.c +++ b/ovn/northd/ovn-northd.c @@ -129,15 +129,17 @@ enum ovn_stage { PIPELINE_STAGE(SWITCH, OUT, PORT_SEC_L2, 8, "ls_out_port_sec_l2") \ \ /* Logical router ingress stages. */ \ - PIPELINE_STAGE(ROUTER, IN, ADMISSION, 0, "lr_in_admission") \ - PIPELINE_STAGE(ROUTER, IN, IP_INPUT, 1, "lr_in_ip_input") \ - PIPELINE_STAGE(ROUTER, IN, DEFRAG, 2, "lr_in_defrag") \ - PIPELINE_STAGE(ROUTER, IN, UNSNAT, 3, "lr_in_unsnat") \ - PIPELINE_STAGE(ROUTER, IN, DNAT, 4, "lr_in_dnat") \ - PIPELINE_STAGE(ROUTER, IN, IP_ROUTING, 5, "lr_in_ip_routing") \ - PIPELINE_STAGE(ROUTER, IN, ARP_RESOLVE, 6, "lr_in_arp_resolve") \ - PIPELINE_STAGE(ROUTER, IN, GW_REDIRECT, 7, "lr_in_gw_redirect") \ - PIPELINE_STAGE(ROUTER, IN, ARP_REQUEST, 8, "lr_in_arp_request") \ + PIPELINE_STAGE(ROUTER, IN, ADMISSION, 0, "lr_in_admission") \ + PIPELINE_STAGE(ROUTER, IN, IP_INPUT, 1, "lr_in_ip_input") \ + PIPELINE_STAGE(ROUTER, IN, DEFRAG, 2, "lr_in_defrag") \ + PIPELINE_STAGE(ROUTER, IN, UNSNAT, 3, "lr_in_unsnat") \ + PIPELINE_STAGE(ROUTER, IN, DNAT, 4, "lr_in_dnat") \ + PIPELINE_STAGE(ROUTER, IN, ND_RA_OPTIONS, 5, "lr_in_nd_ra_options") \ + PIPELINE_STAGE(ROUTER, IN, ND_RA_RESPONSE, 6, "lr_in_nd_ra_response") \ + PIPELINE_STAGE(ROUTER, IN, IP_ROUTING, 7, "lr_in_ip_routing") \ + PIPELINE_STAGE(ROUTER, IN, ARP_RESOLVE, 8, "lr_in_arp_resolve") \ + PIPELINE_STAGE(ROUTER, IN, GW_REDIRECT, 9, "lr_in_gw_redirect") \ + PIPELINE_STAGE(ROUTER, IN, ARP_REQUEST, 10, "lr_in_arp_request") \ \ /* Logical router egress stages. */ \ PIPELINE_STAGE(ROUTER, OUT, UNDNAT, 0, "lr_out_undnat") \ @@ -159,11 +161,12 @@ enum ovn_stage { #define OVN_ACL_PRI_OFFSET 1000 /* Register definitions specific to switches. */ -#define REGBIT_CONNTRACK_DEFRAG "reg0[0]" -#define REGBIT_CONNTRACK_COMMIT "reg0[1]" -#define REGBIT_CONNTRACK_NAT "reg0[2]" -#define REGBIT_DHCP_OPTS_RESULT "reg0[3]" +#define REGBIT_CONNTRACK_DEFRAG "reg0[0]" +#define REGBIT_CONNTRACK_COMMIT "reg0[1]" +#define REGBIT_CONNTRACK_NAT "reg0[2]" +#define REGBIT_DHCP_OPTS_RESULT "reg0[3]" #define REGBIT_DNS_LOOKUP_RESULT "reg0[4]" +#define REGBIT_ND_RA_OPTS_RESULT "reg0[5]" /* Register definitions for switches and routers. */ #define REGBIT_NAT_REDIRECT "reg9[0]" @@ -2868,7 +2871,11 @@ build_pre_acls(struct ovn_datapath *od, struct hmap *lflows) * * Not to do conntrack on ND packets. */ ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_ACL, 110, "nd", "next;"); + ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_ACL, 110, "(nd_rs || nd_ra)", + "next;"); ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 110, "nd", "next;"); + ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 110, + "(nd_rs || nd_ra)", "next;"); /* Ingress and Egress Pre-ACL Table (Priority 100). * @@ -5319,7 +5326,103 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, sset_destroy(&all_ips); } - /* Logical router ingress table 5: IP Routing. + /* Logical router ingress table 5 and 6: IPv6 Router Adv (RA) options and + * response. */ + HMAP_FOR_EACH (op, key_node, ports) { + if (!op->nbrp || op->nbrp->peer || !op->peer) { + continue; + } + + if (!op->lrp_networks.n_ipv6_addrs) { + continue; + } + + const char *address_mode = smap_get( + &op->nbrp->ipv6_ra_configs, "address_mode"); + + if (!address_mode) { + continue; + } + if (strcmp(address_mode, "slaac") && + strcmp(address_mode, "dhcpv6_stateful") && + strcmp(address_mode, "dhcpv6_stateless")) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + VLOG_WARN_RL(&rl, "Invalid address mode [%s] defined", + address_mode); + continue; + } + + ds_clear(&match); + ds_put_format(&match, "inport == %s && ip6.dst == ff02::2 && nd_rs", + op->json_key); + ds_clear(&actions); + + const char *mtu_s = smap_get( + &op->nbrp->ipv6_ra_configs, "mtu"); + + /* As per RFC 2460, 1280 is minimum IPv6 MTU. */ + uint32_t mtu = (mtu_s && atoi(mtu_s) >= 1280) ? atoi(mtu_s) : 0; + + ds_put_format(&actions, REGBIT_ND_RA_OPTS_RESULT" = put_nd_ra_opts(" + "addr_mode = \"%s\", slla = %s", + address_mode, op->lrp_networks.ea_s); + if (mtu > 0) { + ds_put_format(&actions, ", mtu = %u", mtu); + } + + bool add_rs_response_flow = false; + + for (size_t i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { + if (in6_is_lla(&op->lrp_networks.ipv6_addrs[i].network)) { + continue; + } + + /* Add the prefix option if the address mode is slaac or + * dhcpv6_stateless. */ + if (strcmp(address_mode, "dhcpv6_stateful")) { + ds_put_format(&actions, ", prefix = %s/%u", + op->lrp_networks.ipv6_addrs[i].network_s, + op->lrp_networks.ipv6_addrs[i].plen); + } + add_rs_response_flow = true; + } + + if (add_rs_response_flow) { + ds_put_cstr(&actions, "); next;"); + ovn_lflow_add(lflows, op->od, S_ROUTER_IN_ND_RA_OPTIONS, 50, + ds_cstr(&match), ds_cstr(&actions)); + ds_clear(&actions); + ds_clear(&match); + ds_put_format(&match, "inport == %s && ip6.dst == ff02::2 && " + "nd_ra && "REGBIT_ND_RA_OPTS_RESULT, op->json_key); + + char ip6_str[INET6_ADDRSTRLEN + 1]; + struct in6_addr lla; + in6_generate_lla(op->lrp_networks.ea, &lla); + memset(ip6_str, 0, sizeof(ip6_str)); + ipv6_string_mapped(ip6_str, &lla); + ds_put_format(&actions, "eth.dst = eth.src; eth.src = %s; " + "ip6.dst = ip6.src; ip6.src = %s; " + "outport = inport; flags.loopback = 1; " + "output;", + op->lrp_networks.ea_s, ip6_str); + ovn_lflow_add(lflows, op->od, S_ROUTER_IN_ND_RA_RESPONSE, 50, + ds_cstr(&match), ds_cstr(&actions)); + } + } + + /* Logical router ingress table 5, 6: RS responder, by default goto next. + * (priority 0)*/ + HMAP_FOR_EACH (od, key_node, datapaths) { + if (!od->nbr) { + continue; + } + + ovn_lflow_add(lflows, od, S_ROUTER_IN_ND_RA_OPTIONS, 0, "1", "next;"); + ovn_lflow_add(lflows, od, S_ROUTER_IN_ND_RA_RESPONSE, 0, "1", "next;"); + } + + /* Logical router ingress table 7: IP Routing. * * A packet that arrives at this table is an IP packet that should be * routed to the address in 'ip[46].dst'. This table sets outport to @@ -5361,7 +5464,7 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, /* XXX destination unreachable */ - /* Local router ingress table 6: ARP Resolution. + /* Local router ingress table 8: ARP Resolution. * * Any packet that reaches this table is an IP packet whose next-hop IP * address is in reg0. (ip4.dst is the final destination.) This table @@ -5556,7 +5659,7 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, "get_nd(outport, xxreg0); next;"); } - /* Logical router ingress table 7: Gateway redirect. + /* Logical router ingress table 9: Gateway redirect. * * For traffic with outport equal to the l3dgw_port * on a distributed router, this table redirects a subset @@ -5596,7 +5699,7 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, ovn_lflow_add(lflows, od, S_ROUTER_IN_GW_REDIRECT, 0, "1", "next;"); } - /* Local router ingress table 8: ARP request. + /* Local router ingress table 10: ARP request. * * In the common case where the Ethernet destination has been resolved, * this table outputs the packet (priority 0). Otherwise, it composes diff --git a/ovn/ovn-nb.ovsschema b/ovn/ovn-nb.ovsschema index a077bfb81..fcd878cf2 100644 --- a/ovn/ovn-nb.ovsschema +++ b/ovn/ovn-nb.ovsschema @@ -1,7 +1,7 @@ { "name": "OVN_Northbound", - "version": "5.8.0", - "cksum": "2812300190 16766", + "version": "5.8.1", + "cksum": "607160660 16929", "tables": { "NB_Global": { "columns": { @@ -222,6 +222,9 @@ "mac": {"type": "string"}, "peer": {"type": {"key": "string", "min": 0, "max": 1}}, "enabled": {"type": {"key": "boolean", "min": 0, "max": 1}}, + "ipv6_ra_configs": { + "type": {"key": "string", "value": "string", + "min": 0, "max": "unlimited"}}, "external_ids": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}}, diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml index 9869d7ed7..8ad53cd7d 100644 --- a/ovn/ovn-nb.xml +++ b/ovn/ovn-nb.xml @@ -1332,6 +1332,45 @@ port has all ingress and egress traffic dropped. + +

+ This column defines the IPv6 ND RA address mode and ND MTU Option to be + included by ovn-controller when it replies to the IPv6 + Router solicitation requests. +

+ + + The address mode to be used for IPv6 address configuration. + The supported values are: +
    +
  • + slaac: Address configuration using Router + Advertisement (RA) packet. The IPv6 prefixes defined in the + table's + column will + be included in the RA's ICMPv6 option - Prefix information. +
  • + +
  • + dhcpv6_stateful: Address configuration using DHCPv6. +
  • + +
  • + dhcpv6_stateless: Address configuration using Router + Advertisement (RA) packet. Other IPv6 options are provided by + DHCPv6. +
  • +
+
+ + + The recommended MTU for the link. Default is 0, which means no MTU + Option will be included in RA packet replied by ovn-controller. + Per RFC 2460, the mtu value is recommended no less than 1280, so + any mtu value less than 1280 will be considered as no MTU Option. + + +

Additional options for the logical router port. diff --git a/ovn/ovn-sb.xml b/ovn/ovn-sb.xml index fab3f9de6..2e4f28b96 100644 --- a/ovn/ovn-sb.xml +++ b/ovn/ovn-sb.xml @@ -911,6 +911,10 @@

  • nd expands to icmp6.type == {135, 136} && icmp6.code == 0 && ip.ttl == 255
  • nd_ns expands to icmp6.type == 135 && icmp6.code == 0 && ip.ttl == 255
  • nd_na expands to icmp6.type == 136 && icmp6.code == 0 && ip.ttl == 255
  • +
  • nd_rs expands to icmp6.type == 133 && + icmp6.code == 0 && ip.ttl == 255
  • +
  • nd_ra expands to icmp6.type == 134 && + icmp6.code == 0 && ip.ttl == 255
  • tcp expands to ip.proto == 6
  • udp expands to ip.proto == 17
  • sctp expands to ip.proto == 132
  • diff --git a/tests/ovn.at b/tests/ovn.at index e56dc6232..3aa4e5e22 100644 --- a/tests/ovn.at +++ b/tests/ovn.at @@ -7847,6 +7847,255 @@ OVN_CLEANUP([hv1],[hv2],[hv3]) AT_CLEANUP +AT_SETUP([ovn -- IPv6 ND Router Solicitation responder]) +AT_KEYWORDS([ovn-nd_ra]) +AT_SKIP_IF([test $HAVE_PYTHON = no]) +ovn_start + +# In this test case we create 1 lswitch with 3 VIF ports attached, +# and a lrouter connected to the lswitch. +# We generate the Router solicitation packet and verify the Router Advertisement +# reply packet from the ovn-controller. + +# Create hypervisor and logical switch lsw0, logical router lr0, attach lsw0 +# onto lr0, set Logical_Router_Port.ipv6_ra_configs:address_mode column to +# 'slaac' to allow lrp0 send RA for SLAAC mode. +ovn-nbctl ls-add lsw0 +ovn-nbctl lr-add lr0 +ovn-nbctl lrp-add lr0 lrp0 fa:16:3e:00:00:01 fdad:1234:5678::1/64 +ovn-nbctl set Logical_Router_Port lrp0 ipv6_ra_configs:address_mode="slaac" +ovn-nbctl \ + -- lsp-add lsw0 lsp0 \ + -- set Logical_Switch_Port lsp0 type=router \ + options:router-port=lrp0 \ + addresses='"fa:16:3e:00:00:01 fdad:1234:5678::1"' +net_add n1 +sim_add hv1 +as hv1 +ovs-vsctl add-br br-phys +ovn_attach n1 br-phys 192.168.0.2 + +ovn-nbctl lsp-add lsw0 lp1 +ovn-nbctl lsp-set-addresses lp1 "fa:16:3e:00:00:02 10.0.0.12 fdad:1234:5678:0:f816:3eff:fe:2" +ovn-nbctl lsp-set-port-security lp1 "fa:16:3e:00:00:02 10.0.0.12 fdad:1234:5678:0:f816:3eff:fe:2" + +ovn-nbctl lsp-add lsw0 lp2 +ovn-nbctl lsp-set-addresses lp2 "fa:16:3e:00:00:03 10.0.0.13 fdad:1234:5678:0:f816:3eff:fe:3" +ovn-nbctl lsp-set-port-security lp2 "fa:16:3e:00:00:03 10.0.0.13 fdad:1234:5678:0:f816:3eff:fe:3" + +ovn-nbctl lsp-add lsw0 lp3 +ovn-nbctl lsp-set-addresses lp3 "fa:16:3e:00:00:04 10.0.0.14 fdad:1234:5678:0:f816:3eff:fe:4" +ovn-nbctl lsp-set-port-security lp3 "fa:16:3e:00:00:04 10.0.0.14 fdad:1234:5678:0:f816:3eff:fe:4" + +# Add ACL rule for ICMPv6 on lsw0 +ovn-nbctl acl-add lsw0 from-lport 1002 'ip6 && icmp6' allow-related +ovn-nbctl acl-add lsw0 to-lport 1002 'outport == "lp1" && ip6 && icmp6' allow-related +ovn-nbctl acl-add lsw0 to-lport 1002 'outport == "lp2" && ip6 && icmp6' allow-related +ovn-nbctl acl-add lsw0 to-lport 1002 'outport == "lp3" && ip6 && icmp6' allow-related + +ovs-vsctl -- add-port br-int hv1-vif1 -- \ + set interface hv1-vif1 external-ids:iface-id=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=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=lp3 \ + options:tx_pcap=hv1/vif3-tx.pcap \ + options:rxq_pcap=hv1/vif3-rx.pcap \ + ofport-request=3 + +# Allow some time for ovn-northd and ovn-controller to catch up. +# XXX This should be more systematic. +sleep 1 + +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 +} + +# Make sure that ovn-controller has installed the corresponding OF Flow. +OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`]) + +# This shell function sends a Router Solicitation packet. +# test_ipv6_ra INPORT SRC_MAC SRC_LLA ADDR_MODE MTU RA_PREFIX_OPT +test_ipv6_ra() { + local inport=$1 src_mac=$2 src_lla=$3 addr_mode=$4 mtu=$5 prefix_opt=$6 + local request=333300000002${src_mac}86dd6000000000103aff${src_lla}ff02000000000000000000000000000285000efc000000000101${src_mac} + + local len=24 + local mtu_opt="" + if test $mtu != 0; then + len=`expr $len + 8` + mtu_opt=05010000${mtu} + fi + + if test ${#prefix_opt} != 0; then + prefix_opt=${prefix_opt}fdad1234567800000000000000000000 + len=`expr $len + ${#prefix_opt} / 2` + fi + + len=$(printf "%x" $len) + local lrp_mac=fa163e000001 + local lrp_lla=fe80000000000000f8163efffe000001 + local reply=${src_mac}${lrp_mac}86dd6000000000${len}3aff${lrp_lla}${src_lla}8600XXXXff${addr_mode}ffff00000000000000000101${lrp_mac}${mtu_opt}${prefix_opt} + echo $reply >> $inport.expected + + as hv1 ovs-appctl netdev-dummy/receive hv1-vif${inport} $request +} + +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 + +# MTU is not set and the address mode is set to slaac +addr_mode=00 +default_prefix_option_config=030440c0ffffffffffffffff00000000 +src_mac=fa163e000002 +src_lla=fe80000000000000f8163efffe000002 +test_ipv6_ra 1 $src_mac $src_lla $addr_mode 0 $default_prefix_option_config + +# NXT_RESUME 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 > 1.packets + +cat 1.expected | cut -c -112 > expout +AT_CHECK([cat 1.packets | cut -c -112], [0], [expout]) + +# Skipping the ICMPv6 checksum. +cat 1.expected | cut -c 117- > expout +AT_CHECK([cat 1.packets | cut -c 117-], [0], [expout]) + +rm -f *.expected +reset_pcap_file hv1-vif1 hv1/vif1 +reset_pcap_file hv1-vif2 hv1/vif2 +reset_pcap_file hv1-vif3 hv1/vif3 + +# Set the MTU to 1500 +ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:mtu=1500 + +# Make sure that ovn-controller has installed the corresponding OF Flow. +OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`]) + +addr_mode=00 +default_prefix_option_config=030440c0ffffffffffffffff00000000 +src_mac=fa163e000003 +src_lla=fe80000000000000f8163efffe000003 +mtu=000005dc + +test_ipv6_ra 2 $src_mac $src_lla $addr_mode $mtu $default_prefix_option_config + +# NXT_RESUME should be 2. +OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) + +$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets + +cat 2.expected | cut -c -112 > expout +AT_CHECK([cat 2.packets | cut -c -112], [0], [expout]) + +# Skipping the ICMPv6 checksum. +cat 2.expected | cut -c 117- > expout +AT_CHECK([cat 2.packets | cut -c 117-], [0], [expout]) + +rm -f *.expected +reset_pcap_file hv1-vif1 hv1/vif1 +reset_pcap_file hv1-vif2 hv1/vif2 +reset_pcap_file hv1-vif3 hv1/vif3 + +# Set the address mode to dhcpv6_stateful +ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:address_mode=dhcpv6_stateful +# Make sure that ovn-controller has installed the corresponding OF Flow. +OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`]) + +addr_mode=80 +default_prefix_option_config="" +src_mac=fa163e000004 +src_lla=fe80000000000000f8163efffe000004 +mtu=000005dc + +test_ipv6_ra 3 $src_mac $src_lla $addr_mode $mtu $default_prefix_option_config + +# NXT_RESUME should be 3. +OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) + +$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif3-tx.pcap > 3.packets + +cat 3.expected | cut -c -112 > expout +AT_CHECK([cat 3.packets | cut -c -112], [0], [expout]) + +# Skipping the ICMPv6 checksum. +cat 3.expected | cut -c 117- > expout +AT_CHECK([cat 3.packets | cut -c 117-], [0], [expout]) + +rm -f *.expected +reset_pcap_file hv1-vif1 hv1/vif1 +reset_pcap_file hv1-vif2 hv1/vif2 +reset_pcap_file hv1-vif3 hv1/vif3 + +# Set the address mode to dhcpv6_stateless +ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:address_mode=dhcpv6_stateless +# Make sure that ovn-controller has installed the corresponding OF Flow. +OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`]) + +addr_mode=40 +default_prefix_option_config=030440c0ffffffffffffffff00000000 +src_mac=fa163e000002 +src_lla=fe80000000000000f8163efffe000002 +mtu=000005dc + +test_ipv6_ra 1 $src_mac $src_lla $addr_mode $mtu $default_prefix_option_config + +# NXT_RESUME should be 4. +OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) + +$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets + +cat 1.expected | cut -c -112 > expout +AT_CHECK([cat 1.packets | cut -c -112], [0], [expout]) + +# Skipping the ICMPv6 checksum. +cat 1.expected | cut -c 117- > expout +AT_CHECK([cat 1.packets | cut -c 117-], [0], [expout]) + +rm -f *.expected +reset_pcap_file hv1-vif1 hv1/vif1 +reset_pcap_file hv1-vif2 hv1/vif2 +reset_pcap_file hv1-vif3 hv1/vif3 + +# Set the address mode to invalid. +ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:address_mode=invalid +# Make sure that ovn-controller has not installed any OF Flow for IPv6 ND RA. +OVS_WAIT_UNTIL([test 0 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`]) + +addr_mode=40 +default_prefix_option_config="" +src_mac=fa163e000002 +src_lla=fe80000000000000f8163efffe000002 +mtu=000005dc + +test_ipv6_ra 1 $src_mac $src_lla $addr_mode $mtu $default_prefix_option_config + +# NXT_RESUME should be 4 only. +OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) + +$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets +AT_CHECK([cat 1.packets], [0], []) + +OVN_CLEANUP([hv1]) +AT_CLEANUP + AT_SETUP([ovn -- /32 router IP address]) AT_SKIP_IF([test $HAVE_PYTHON = no]) ovn_start From patchwork Mon Oct 2 16:25:50 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Numan Siddique X-Patchwork-Id: 820622 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (mailfrom) smtp.mailfrom=openvswitch.org (client-ip=140.211.169.12; helo=mail.linuxfoundation.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=) Received: from mail.linuxfoundation.org (mail.linuxfoundation.org [140.211.169.12]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 3y5SJt1KrXz9s7g for ; Tue, 3 Oct 2017 03:28:30 +1100 (AEDT) Received: from mail.linux-foundation.org (localhost [127.0.0.1]) by mail.linuxfoundation.org (Postfix) with ESMTP id 275C1B73; Mon, 2 Oct 2017 16:25:58 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@mail.linuxfoundation.org Received: from smtp1.linuxfoundation.org (smtp1.linux-foundation.org [172.17.192.35]) by mail.linuxfoundation.org (Postfix) with ESMTPS id AB86BB65 for ; Mon, 2 Oct 2017 16:25:56 +0000 (UTC) X-Greylist: domain auto-whitelisted by SQLgrey-1.7.6 Received: from mx1.redhat.com (mx1.redhat.com [209.132.183.28]) by smtp1.linuxfoundation.org (Postfix) with ESMTPS id 893851A9 for ; Mon, 2 Oct 2017 16:25:55 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx06.intmail.prod.int.phx2.redhat.com [10.5.11.16]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id 13A85806B3 for ; Mon, 2 Oct 2017 16:25:55 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.3.2 mx1.redhat.com 13A85806B3 Authentication-Results: ext-mx02.extmail.prod.ext.phx2.redhat.com; dmarc=none (p=none dis=none) header.from=redhat.com Authentication-Results: ext-mx02.extmail.prod.ext.phx2.redhat.com; spf=fail smtp.mailfrom=nusiddiq@redhat.com Received: from numans.blr.redhat.com (dhcp-0-126.blr.redhat.com [10.70.1.126]) by smtp.corp.redhat.com (Postfix) with ESMTP id B22194D72A; Mon, 2 Oct 2017 16:25:53 +0000 (UTC) From: nusiddiq@redhat.com To: dev@openvswitch.org Date: Mon, 2 Oct 2017 21:55:50 +0530 Message-Id: <20171002162550.1923-1-nusiddiq@redhat.com> In-Reply-To: <20171002161109.956-1-nusiddiq@redhat.com> References: <20171002161109.956-1-nusiddiq@redhat.com> X-Scanned-By: MIMEDefang 2.79 on 10.5.11.16 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.26]); Mon, 02 Oct 2017 16:25:55 +0000 (UTC) X-Spam-Status: No, score=-5.0 required=5.0 tests=RCVD_IN_DNSWL_HI, RP_MATCHES_RCVD autolearn=disabled version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on smtp1.linux-foundation.org Subject: [ovs-dev] [PATCH v9 4/4] ovn: Generate Neighbor Solicitation packet for unknown MAC IPv6 packets X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.12 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: ovs-dev-bounces@openvswitch.org Errors-To: ovs-dev-bounces@openvswitch.org From: Numan Siddique In the router ingress pipeline, if the destination mac is unresolved by the time the packet reaches the ARP_REQUEST stage, OVN should generate an IPv6 Neighbor Solicitation packet to learn the MAC address. This feature is presently missing. This patch adds this feature. A new action "nd_ns" is added which replaces an IPv6 packet being processed with an IPv6 Neighbor Solicitation packet. ovn-northd adds a flow in the ARP_REQUEST router ingress pipeline stage if the eth.dst is zero which applies this action. This action is similar to the IPv4 counterpart "arp" action. OVN already has the support to learn the MAC from the IPv6 Neighbor Advertisement packets and storing in the south bound MAC_Binding table. Signed-off-by: Numan Siddique Acked-by: Mark Michelson --- include/ovn/actions.h | 9 +++- ovn/controller/pinctrl.c | 122 +++++++++++++++++++++++--------------------- ovn/lib/actions.c | 22 ++++++++ ovn/northd/ovn-northd.8.xml | 24 ++++++--- ovn/northd/ovn-northd.c | 8 ++- ovn/ovn-sb.xml | 37 ++++++++++++++ ovn/utilities/ovn-trace.c | 30 +++++++++++ tests/ovn.at | 116 +++++++++++++++++++++++++++++++++++++++++ 8 files changed, 302 insertions(+), 66 deletions(-) diff --git a/include/ovn/actions.h b/include/ovn/actions.h index 15cee478d..8c7208ffc 100644 --- a/include/ovn/actions.h +++ b/include/ovn/actions.h @@ -73,7 +73,8 @@ struct simap; OVNACT(SET_QUEUE, ovnact_set_queue) \ OVNACT(DNS_LOOKUP, ovnact_dns_lookup) \ OVNACT(LOG, ovnact_log) \ - OVNACT(PUT_ND_RA_OPTS, ovnact_put_opts) + OVNACT(PUT_ND_RA_OPTS, ovnact_put_opts) \ + OVNACT(ND_NS, ovnact_nest) /* enum ovnact_type, with a member OVNACT_ for each action. */ enum OVS_PACKED_ENUM ovnact_type { @@ -427,6 +428,12 @@ enum action_opcode { * - Any number of ICMPv6 options. */ ACTION_OPCODE_PUT_ND_RA_OPTS, + + /* "nd_ns { ...actions... }". + * + * The actions, in OpenFlow 1.3 format, follow the action_header. + */ + ACTION_OPCODE_ND_NS, }; /* Header. */ diff --git a/ovn/controller/pinctrl.c b/ovn/controller/pinctrl.c index 3a1348937..5aedf7d0d 100644 --- a/ovn/controller/pinctrl.c +++ b/ovn/controller/pinctrl.c @@ -85,6 +85,9 @@ static void pinctrl_handle_put_nd_ra_opts( const struct flow *ip_flow, struct dp_packet *pkt_in, struct ofputil_packet_in *pin, struct ofpbuf *userdata, struct ofpbuf *continuation); +static void pinctrl_handle_nd_ns(const struct flow *ip_flow, + const struct match *md, + struct ofpbuf *userdata); COVERAGE_DEFINE(pinctrl_drop_put_mac_binding); @@ -132,6 +135,43 @@ set_switch_config(struct rconn *swconn, } static void +set_actions_and_enqueue_msg(const struct dp_packet *packet, + const struct match *md, + struct ofpbuf *userdata) +{ + /* Copy metadata from 'md' into the packet-out via "set_field" + * actions, then add actions from 'userdata'. + */ + uint64_t ofpacts_stub[4096 / 8]; + struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub); + enum ofp_version version = rconn_get_version(swconn); + + reload_metadata(&ofpacts, md); + enum ofperr error = ofpacts_pull_openflow_actions(userdata, userdata->size, + version, NULL, NULL, + &ofpacts); + if (error) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + VLOG_WARN_RL(&rl, "failed to parse actions from userdata (%s)", + ofperr_to_string(error)); + ofpbuf_uninit(&ofpacts); + return; + } + + struct ofputil_packet_out po = { + .packet = dp_packet_data(packet), + .packet_len = dp_packet_size(packet), + .buffer_id = UINT32_MAX, + .ofpacts = ofpacts.data, + .ofpacts_len = ofpacts.size, + }; + match_set_in_port(&po.flow_metadata, OFPP_CONTROLLER); + enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version); + queue_msg(ofputil_encode_packet_out(&po, proto)); + ofpbuf_uninit(&ofpacts); +} + +static void pinctrl_handle_arp(const struct flow *ip_flow, const struct match *md, struct ofpbuf *userdata) { @@ -166,40 +206,8 @@ pinctrl_handle_arp(const struct flow *ip_flow, const struct match *md, ip_flow->vlans[0].tci); } - /* Compose actions. - * - * First, copy metadata from 'md' into the packet-out via "set_field" - * actions, then add actions from 'userdata'. - */ - uint64_t ofpacts_stub[4096 / 8]; - struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub); - enum ofp_version version = rconn_get_version(swconn); - - reload_metadata(&ofpacts, md); - enum ofperr error = ofpacts_pull_openflow_actions(userdata, userdata->size, - version, NULL, NULL, - &ofpacts); - if (error) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); - VLOG_WARN_RL(&rl, "failed to parse arp actions (%s)", - ofperr_to_string(error)); - goto exit; - } - - struct ofputil_packet_out po = { - .packet = dp_packet_data(&packet), - .packet_len = dp_packet_size(&packet), - .buffer_id = UINT32_MAX, - .ofpacts = ofpacts.data, - .ofpacts_len = ofpacts.size, - }; - match_set_in_port(&po.flow_metadata, OFPP_CONTROLLER); - enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version); - queue_msg(ofputil_encode_packet_out(&po, proto)); - -exit: + set_actions_and_enqueue_msg(&packet, md, userdata); dp_packet_uninit(&packet); - ofpbuf_uninit(&ofpacts); } static void @@ -994,6 +1002,10 @@ process_packet_in(const struct ofp_header *msg, struct controller_ctx *ctx) &continuation); break; + case ACTION_OPCODE_ND_NS: + pinctrl_handle_nd_ns(&headers, &pin.flow_metadata, &userdata); + break; + default: VLOG_WARN_RL(&rl, "unrecognized packet-in opcode %"PRIu32, ntohl(ah->opcode)); @@ -1812,9 +1824,6 @@ pinctrl_handle_nd_na(const struct flow *ip_flow, const struct match *md, return; } - enum ofp_version version = rconn_get_version(swconn); - enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version); - uint64_t packet_stub[128 / 8]; struct dp_packet packet; dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub); @@ -1827,35 +1836,32 @@ pinctrl_handle_nd_na(const struct flow *ip_flow, const struct match *md, &ip_flow->nd_target, &ip_flow->ipv6_src, htonl(ND_RSO_SOLICITED | ND_RSO_OVERRIDE)); - /* Reload previous packet metadata. */ - uint64_t ofpacts_stub[4096 / 8]; - struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub); - reload_metadata(&ofpacts, md); + /* Reload previous packet metadata and set actions from userdata. */ + set_actions_and_enqueue_msg(&packet, md, userdata); + dp_packet_uninit(&packet); +} - enum ofperr error = ofpacts_pull_openflow_actions(userdata, userdata->size, - version, NULL, NULL, - &ofpacts); - if (error) { +static void +pinctrl_handle_nd_ns(const struct flow *ip_flow, const struct match *md, + struct ofpbuf *userdata) +{ + /* This action only works for IPv6 packets. */ + if (get_dl_type(ip_flow) != htons(ETH_TYPE_IPV6)) { static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); - VLOG_WARN_RL(&rl, "failed to parse actions for 'na' (%s)", - ofperr_to_string(error)); - goto exit; + VLOG_WARN_RL(&rl, "NS action on non-IPv6 packet"); + return; } - struct ofputil_packet_out po = { - .packet = dp_packet_data(&packet), - .packet_len = dp_packet_size(&packet), - .buffer_id = UINT32_MAX, - .ofpacts = ofpacts.data, - .ofpacts_len = ofpacts.size, - }; - match_set_in_port(&po.flow_metadata, OFPP_CONTROLLER); + uint64_t packet_stub[128 / 8]; + struct dp_packet packet; + dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub); - queue_msg(ofputil_encode_packet_out(&po, proto)); + compose_nd_ns(&packet, ip_flow->dl_src, &ip_flow->ipv6_src, + &ip_flow->ipv6_dst); -exit: + /* Reload previous packet metadata and set actions from userdata. */ + set_actions_and_enqueue_msg(&packet, md, userdata); dp_packet_uninit(&packet); - ofpbuf_uninit(&ofpacts); } static void diff --git a/ovn/lib/actions.c b/ovn/lib/actions.c index 8d5863c0a..d0a4d7753 100644 --- a/ovn/lib/actions.c +++ b/ovn/lib/actions.c @@ -1134,6 +1134,12 @@ parse_ND_NA(struct action_context *ctx) } static void +parse_ND_NS(struct action_context *ctx) +{ + parse_nested_action(ctx, OVNACT_ND_NS, "ip6"); +} + +static void parse_CLONE(struct action_context *ctx) { parse_nested_action(ctx, OVNACT_CLONE, NULL); @@ -1161,6 +1167,12 @@ format_ND_NA(const struct ovnact_nest *nest, struct ds *s) } static void +format_ND_NS(const struct ovnact_nest *nest, struct ds *s) +{ + format_nested_action(nest, "nd_ns", s); +} + +static void format_CLONE(const struct ovnact_nest *nest, struct ds *s) { format_nested_action(nest, "clone", s); @@ -1207,6 +1219,14 @@ encode_ND_NA(const struct ovnact_nest *on, } static void +encode_ND_NS(const struct ovnact_nest *on, + const struct ovnact_encode_params *ep, + struct ofpbuf *ofpacts) +{ + encode_nested_neighbor_actions(on, ep, ACTION_OPCODE_ND_NS, ofpacts); +} + +static void encode_CLONE(const struct ovnact_nest *on, const struct ovnact_encode_params *ep, struct ofpbuf *ofpacts) @@ -2146,6 +2166,8 @@ parse_action(struct action_context *ctx) parse_ARP(ctx); } else if (lexer_match_id(ctx->lexer, "nd_na")) { parse_ND_NA(ctx); + } else if (lexer_match_id(ctx->lexer, "nd_ns")) { + parse_ND_NS(ctx); } else if (lexer_match_id(ctx->lexer, "get_arp")) { parse_get_mac_bind(ctx, 32, ovnact_put_GET_ARP(ctx->ovnacts)); } else if (lexer_match_id(ctx->lexer, "put_arp")) { diff --git a/ovn/northd/ovn-northd.8.xml b/ovn/northd/ovn-northd.8.xml index a994abf78..17123c690 100644 --- a/ovn/northd/ovn-northd.8.xml +++ b/ovn/northd/ovn-northd.8.xml @@ -1915,15 +1915,15 @@ next;

    In the common case where the Ethernet destination has been resolved, this - table outputs the packet. Otherwise, it composes and sends an ARP - request. It holds the following flows: + table outputs the packet. Otherwise, it composes and sends an ARP or + IPv6 Neighbor Solicitation request. It holds the following flows:

    • - Unknown MAC address. A priority-100 flow with match eth.dst == - 00:00:00:00:00:00 has the following actions: + Unknown MAC address. A priority-100 flow for IPv4 packets with match + eth.dst == 00:00:00:00:00:00 has the following actions:

      @@ -1937,13 +1937,25 @@ arp {
               

      + Unknown MAC address. A priority-100 flow for IPv6 packets with match + eth.dst == 00:00:00:00:00:00 has the following actions: +

      + +
      +nd_ns {
      +    nd.target = xxreg0;
      +    output;
      +};
      +        
      + +

      (Ingress table IP Routing initialized reg1 with the IP address owned by outport and - reg0 with the next-hop IP address) + (xx)reg0 with the next-hop IP address)

      - The IP packet that triggers the ARP request is dropped. + The IP packet that triggers the ARP/IPv6 NS request is dropped.

    • diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c index 3da20d25b..b4ea34bc6 100644 --- a/ovn/northd/ovn-northd.c +++ b/ovn/northd/ovn-northd.c @@ -5703,7 +5703,7 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, * * In the common case where the Ethernet destination has been resolved, * this table outputs the packet (priority 0). Otherwise, it composes - * and sends an ARP request (priority 100). */ + * and sends an ARP/IPv6 NA request (priority 100). */ HMAP_FOR_EACH (od, key_node, datapaths) { if (!od->nbr) { continue; @@ -5718,6 +5718,12 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, "arp.op = 1; " /* ARP request */ "output; " "};"); + ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_REQUEST, 100, + "eth.dst == 00:00:00:00:00:00", + "nd_ns { " + "nd.target = xxreg0; " + "output; " + "};"); ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_REQUEST, 0, "1", "output;"); } diff --git a/ovn/ovn-sb.xml b/ovn/ovn-sb.xml index 2e4f28b96..ca8cbecdd 100644 --- a/ovn/ovn-sb.xml +++ b/ovn/ovn-sb.xml @@ -1258,6 +1258,43 @@

      Example: put_arp(inport, arp.spa, arp.sha);

      +
      nd_ns { action; ... };
      +
      +

      + Temporarily replaces the IPv6 packet being processed by an IPv6 + Neighbor Solicitation packet and executes each nested + action on the IPv6 NS packet. Actions following the + nd_ns action, if any, apply to the original, unmodified + packet. +

      + +

      + The IPv6 NS packet that this action operates on is initialized + based on the IPv6 packet being processed, as follows. These are + default values that the nested actions will probably want to + change: +

      + +
        +
      • eth.src unchanged
      • +
      • eth.dst set to IPv6 multicast MAC address
      • +
      • eth.type = 0x86dd
      • +
      • ip6.src copied from ip6.src
      • +
      • + ip6.dst set to IPv6 Solicited-Node multicast address +
      • +
      • icmp6.type = 135 (Neighbor Solicitation)
      • +
      • nd.target copied from ip6.dst
      • +
      + +

      + The IPv6 NS packet has the same VLAN header, if any, as the IP + packet it replaces. +

      + +

      Prerequisite: ip6

      +
      +
      nd_na { action; ... };
      diff --git a/ovn/utilities/ovn-trace.c b/ovn/utilities/ovn-trace.c index 211148b8b..e457284fc 100644 --- a/ovn/utilities/ovn-trace.c +++ b/ovn/utilities/ovn-trace.c @@ -1510,6 +1510,31 @@ execute_nd_na(const struct ovnact_nest *on, const struct ovntrace_datapath *dp, } static void +execute_nd_ns(const struct ovnact_nest *on, const struct ovntrace_datapath *dp, + const struct flow *uflow, uint8_t table_id, + enum ovnact_pipeline pipeline, struct ovs_list *super) +{ + struct flow na_flow = *uflow; + + /* Update fields for NA. */ + na_flow.dl_src = uflow->dl_src; + na_flow.ipv6_src = uflow->ipv6_src; + na_flow.ipv6_dst = uflow->ipv6_dst; + struct in6_addr sn_addr; + in6_addr_solicited_node(&sn_addr, &uflow->ipv6_dst); + ipv6_multicast_to_ethernet(&na_flow.dl_dst, &sn_addr); + na_flow.tp_src = htons(135); + na_flow.arp_sha = eth_addr_zero; + na_flow.arp_tha = uflow->dl_dst; + + struct ovntrace_node *node = ovntrace_node_append( + super, OVNTRACE_NODE_TRANSFORMATION, "nd_ns"); + + trace_actions(on->nested, on->nested_len, dp, &na_flow, + table_id, pipeline, &node->subs); +} + +static void execute_get_mac_bind(const struct ovnact_get_mac_bind *bind, const struct ovntrace_datapath *dp, struct flow *uflow, struct ovs_list *super) @@ -1811,6 +1836,11 @@ trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len, super); break; + case OVNACT_ND_NS: + execute_nd_ns(ovnact_get_ND_NS(a), dp, uflow, table_id, pipeline, + super); + break; + case OVNACT_GET_ARP: execute_get_mac_bind(ovnact_get_GET_ARP(a), dp, uflow, super); break; diff --git a/tests/ovn.at b/tests/ovn.at index 3aa4e5e22..13cdc1679 100644 --- a/tests/ovn.at +++ b/tests/ovn.at @@ -993,6 +993,16 @@ reg1[0] = put_dhcp_opts(offerip="xyzzy"); reg1[0] = put_dhcp_opts(offerip=1.2.3.4, domain=1.2.3.4); DHCPv4 option domain requires string value. +# nd_ns +nd_ns { nd.target = xxreg0; output; }; + encodes as controller(userdata=00.00.00.09.00.00.00.00.ff.ff.00.18.00.00.23.20.00.06.00.80.00.00.00.00.00.01.de.10.00.01.2e.10.ff.ff.00.10.00.00.23.20.00.0e.ff.f8.40.00.00.00) + has prereqs ip6 + +nd_ns { }; + formats as nd_ns { drop; }; + encodes as controller(userdata=00.00.00.09.00.00.00.00) + has prereqs ip6 + # 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; }; formats as nd_na { eth.src = 12:34:56:78:9a:bc; nd.tll = 12:34:56:78:9a:bc; outport = inport; inport = ""; output; }; @@ -8795,6 +8805,112 @@ OVN_CLEANUP([gw1],[gw2],[hv1]) AT_CLEANUP +AT_SETUP([ovn -- IPv6 Neighbor Solicitation for unknown MAC]) +AT_KEYWORDS([ovn-nd_ns for unknown mac]) +AT_SKIP_IF([test $HAVE_PYTHON = no]) +ovn_start + +ovn-nbctl ls-add sw0_ip6 +ovn-nbctl lsp-add sw0_ip6 sw0_ip6-port1 +ovn-nbctl lsp-set-addresses sw0_ip6-port1 \ +"50:64:00:00:00:02 aef0::5264:00ff:fe00:0002" + +ovn-nbctl lsp-set-port-security sw0_ip6-port1 \ +"50:64:00:00:00:02 aef0::5264:00ff:fe00:0002" + +ovn-nbctl lr-add lr0_ip6 +ovn-nbctl lrp-add lr0_ip6 lrp0_ip6 00:00:00:00:af:01 aef0::/64 +ovn-nbctl lsp-add sw0_ip6 lrp0_ip6-attachment +ovn-nbctl lsp-set-type lrp0_ip6-attachment router +ovn-nbctl lsp-set-addresses lrp0_ip6-attachment 00:00:00:00:af:01 +ovn-nbctl lsp-set-options lrp0_ip6-attachment router-port=lrp0_ip6 +ovn-nbctl set logical_router_port lrp0_ip6 ipv6_ra_configs:address_mode=slaac + +ovn-nbctl ls-add public +ovn-nbctl lsp-add public ln-public +ovn-nbctl lsp-set-addresses ln-public unknown +ovn-nbctl lsp-set-type ln-public localnet +ovn-nbctl lsp-set-options ln-public network_name=phys + +ovn-nbctl lrp-add lr0_ip6 ip6_public 00:00:02:01:02:04 \ +2001:db8:1:0:200:02ff:fe01:0204/64 \ +-- set Logical_Router_port ip6_public options:redirect-chassis="hv1" + + +ovn-nbctl lsp-add public rp-ip6_public -- set Logical_Switch_Port \ +rp-ip6_public type=router options:router-port=ip6_public \ +-- lsp-set-addresses rp-ip6_public router + +net_add n1 +sim_add hv1 +as hv1 +ovs-vsctl add-br br-phys +ovn_attach n1 br-phys 192.168.0.2 + +ovs-vsctl -- add-port br-int hv1-vif1 -- \ + set interface hv1-vif1 external-ids:iface-id=sw0_ip6-port1 \ + options:tx_pcap=hv1/vif1-tx.pcap \ + options:rxq_pcap=hv1/vif1-rx.pcap \ + ofport-request=1 +ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys + +# Allow some time for ovn-northd and ovn-controller to catch up. +# XXX This should be more systematic. +sleep 1 + +trim_zeros() { + sed 's/\(00\)\{1,\}$//' +} + +# Test the IPv6 Neighbor Solicitation (NS) - nd_ns action for unknown MAC +# addresses. ovn-controller should generate an IPv6 NS request for IPv6 +# packets whose MAC is unknown (in the ARP_REQUEST router pipeline stage. +# test_ipv6 INPORT SRC_MAC DST_MAC SRC_IP DST_IP OUTPORT... +# This function sends ipv6 packet +test_ipv6() { + local inport=$1 src_mac=$2 dst_mac=$3 src_ip=$4 + dst_ip=20010db800010000020002fffe010205 + + local packet=${dst_mac}${src_mac}86dd6000000000083aff${src_ip}${dst_ip} + packet=${packet}8000000000000000 + shift; shift; shift; shift + + dst_mac=3333ff010205 + src_mac=000002010204 + mcast_node_ip=ff0200000000000000000001ff010205 + expected_packet=${dst_mac}${src_mac}86dd6000000000203aff${src_ip} + expected_packet=${expected_packet}${mcast_node_ip}8700XXXX00000000${dst_ip} + expected_packet=${expected_packet}0101${src_mac} + + as hv1 ovs-appctl netdev-dummy/receive hv1-vif${inport} $packet + echo $expected_packet >> ipv6_ns.expected +} + +src_mac=506400000002 +dst_mac=00000000af01 +src_ip=aef0000000000000526400fffe000002 +# Send an IPv6 packet. Generated IPv6 Neighbor solicitation packet +# should be received by the ports attached to br-phys. +test_ipv6 1 $src_mac $dst_mac $src_ip 2 + +$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/br-phys_n1-tx.pcap | \ +trim_zeros > 1.packets +$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/br-phys-tx.pcap | \ +trim_zeros > 2.packets + +cat ipv6_ns.expected | cut -c -112 > expout +AT_CHECK([cat 1.packets | cut -c -112], [0], [expout]) +AT_CHECK([cat 2.packets | cut -c -112], [0], [expout]) + +# Skipping the ICMPv6 checksum +cat ipv6_ns.expected | cut -c 117- > expout +AT_CHECK([cat 1.packets | cut -c 117-], [0], [expout]) +AT_CHECK([cat 2.packets | cut -c 117-], [0], [expout]) + +OVN_CLEANUP([hv1]) + +AT_CLEANUP + AT_SETUP([ovn -- options:requested-chassis for logical port]) ovn_start