From patchwork Wed Jul 27 08:02:11 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Zong Kai LI X-Patchwork-Id: 653193 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from archives.nicira.com (archives.nicira.com [96.126.127.54]) by ozlabs.org (Postfix) with ESMTP id 3rznYr6KKVz9t25 for ; Wed, 27 Jul 2016 18:03:44 +1000 (AEST) Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b=FyTExbcL; dkim-atps=neutral Received: from archives.nicira.com (localhost [127.0.0.1]) by archives.nicira.com (Postfix) with ESMTP id B756710CC1; Wed, 27 Jul 2016 01:03:43 -0700 (PDT) X-Original-To: dev@openvswitch.org Delivered-To: dev@openvswitch.org Received: from mx3v3.cudamail.com (mx3.cudamail.com [64.34.241.5]) by archives.nicira.com (Postfix) with ESMTPS id CBCB110CBF for ; Wed, 27 Jul 2016 01:03:42 -0700 (PDT) Received: from bar6.cudamail.com (localhost [127.0.0.1]) by mx3v3.cudamail.com (Postfix) with ESMTPS id 30B53162328 for ; Wed, 27 Jul 2016 02:03:42 -0600 (MDT) X-ASG-Debug-ID: 1469606619-0b32374775b5b90001-byXFYA Received: from mx1-pf2.cudamail.com ([192.168.24.2]) by bar6.cudamail.com with ESMTP id 52A7xEAwjcOn7XGC (version=TLSv1 cipher=DHE-RSA-AES256-SHA bits=256 verify=NO) for ; Wed, 27 Jul 2016 02:03:39 -0600 (MDT) X-Barracuda-Envelope-From: zealokii@gmail.com X-Barracuda-RBL-Trusted-Forwarder: 192.168.24.2 Received: from unknown (HELO mail-it0-f67.google.com) (209.85.214.67) by mx1-pf2.cudamail.com with ESMTPS (AES128-SHA encrypted); 27 Jul 2016 08:03:38 -0000 Received-SPF: pass (mx1-pf2.cudamail.com: SPF record at _netblocks.google.com designates 209.85.214.67 as permitted sender) X-Barracuda-Apparent-Source-IP: 209.85.214.67 X-Barracuda-RBL-IP: 209.85.214.67 Received: by mail-it0-f67.google.com with SMTP id f6so2248664ith.2 for ; Wed, 27 Jul 2016 01:03:38 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=from:to:cc:subject:date:message-id; bh=tVJvjLnnZu+FY/EvAPZiZ4Ck9uhsJ34o2JHNTDsb+vU=; b=FyTExbcL75oeFvoQe+I2KAjCEcwPh3P/gtLYD+npKM8BzA52t8w+kiFgUi1mhK3ExX wjNNjq+v+v3g0vg+JqvoBtGFwA+g/dxJJFSZKUvY8iQLk/GBcO3GdTx+TOra2Y/sJrib IAeTuesbx7gaRVZswyX+AjfYlTcGehPLPmXzQJO9XApRTrAeZCYMgTvk92RHtn0S9sfV dg4FPMziSP5mumaAj7y667Gi22YNpyl1ioYEYCOgABU/JdZXvNrBx3z08godngNhHANz hIsID07MeygOm+Fm4Wcg8VZ1jwTH7Axrnsksmm54VBKlzLcsw2TBlvhJtnyIBAPFZvX+ N0qQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:from:to:cc:subject:date:message-id; bh=tVJvjLnnZu+FY/EvAPZiZ4Ck9uhsJ34o2JHNTDsb+vU=; b=C6trYH6uw1G/E5IUMzdZ6AF1QlPbakrUI5+vl5AVjWIwjV3q2Nm4dI27qrQf/xebzY bNxnW331yC3/wfLCgxcwBiOdVpBbbOIFIeTYmcNFylun5rayNsCFGvDmRca4df/2mtNz XQ4qU29TWTqssuWd5lR+VPEna3KXqs54Fqsd0WcYJfUHwUT8ji6rkwacmWhh+AVs+dIm VMZwJK9v7EW74kmNZjcWjIPMG6roGI247E2EmM7YymPTtNimtGNaYCDoYDUcoKhTVXTI h0Vq2gI7vvsL/yDdOhLHneNN1wqyvL7pfJHXNfk3+1Zl/KND3mb4IN1olGoskZPnyNqk wxOQ== X-Gm-Message-State: ALyK8tJ15aOSj9FkHwKbvtRbdfXw1AEw22VoQSQue1SzisZyGhxERgqokGkj5mRjjjHtPA== X-Received: by 10.36.19.16 with SMTP id 16mr102420048itz.41.1469606617057; Wed, 27 Jul 2016 01:03:37 -0700 (PDT) Received: from localhost.localdomain ([106.38.0.69]) by smtp.gmail.com with ESMTPSA id b3sm13235309itg.17.2016.07.27.01.03.11 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Wed, 27 Jul 2016 01:03:36 -0700 (PDT) X-CudaMail-Envelope-Sender: zealokii@gmail.com From: Zong Kai LI To: dev@openvswitch.org X-CudaMail-MID: CM-E2-726001938 X-CudaMail-DTE: 072716 X-CudaMail-Originating-IP: 209.85.214.67 Date: Wed, 27 Jul 2016 16:02:11 +0800 X-ASG-Orig-Subj: [##CM-E2-726001938##][ovs-dev] [PATCH RFC v1] ovn: add SLAAC support for IPv6 Message-Id: <1469606531-18168-1-git-send-email-zealokii@gmail.com> X-Mailer: git-send-email 1.9.1 X-GBUdb-Analysis: 0, 209.85.214.67, Ugly c=0.331706 p=-0.12 Source Normal X-MessageSniffer-Rules: 0-0-0-32767-c X-Barracuda-Connect: UNKNOWN[192.168.24.2] X-Barracuda-Start-Time: 1469606619 X-Barracuda-Encrypted: DHE-RSA-AES256-SHA X-Barracuda-URL: https://web.cudamail.com:443/cgi-mod/mark.cgi X-Virus-Scanned: by bsmtpd at cudamail.com X-Barracuda-BRTS-Status: 1 X-Barracuda-Spam-Score: 0.60 X-Barracuda-Spam-Status: No, SCORE=0.60 using global scores of TAG_LEVEL=3.5 QUARANTINE_LEVEL=1000.0 KILL_LEVEL=4.0 tests=BSF_SC5_MJ1963, DKIM_SIGNED, RDNS_NONE X-Barracuda-Spam-Report: Code version 3.2, rules version 3.2.3.31550 Rule breakdown below pts rule name description ---- ---------------------- -------------------------------------------------- 0.00 DKIM_SIGNED Domain Keys Identified Mail: message has a signature 0.10 RDNS_NONE Delivered to trusted network by a host with no rDNS 0.50 BSF_SC5_MJ1963 Custom Rule MJ1963 Subject: [ovs-dev] [PATCH RFC v1] ovn: add SLAAC support for IPv6 X-BeenThere: dev@openvswitch.org X-Mailman-Version: 2.1.16 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Errors-To: dev-bounces@openvswitch.org Sender: "dev" This patch introduces(from north to south): - OVN NB DB: * mtu(integer) column for Logical_Switch, used by Router Advertisement (RA) message MTU option. * slaac(bool) column for Logical_Router_Port to specify the IPv6 networks on a rotuer port is in SLAAC mode or not, if it has IPv6 networks. [1] - ovn-northd: New table ls_in_ra_rsp to install RA responder flows, which matching Router Solicitation (RS) packets and have actions such as: action=(ra(fd80:1234::/64,2001:db8::/64,1450,fa:16:3e:12:34:56); outport = inport; inport = ""; output;) [2] while 'ra' is new action. The RA responder flows are built on logical switch pipeline per each logical switch. [3] - ovn-controller: New action 'ra' has ordered parameters and uses 'continuation'.[4] The ordered parameters including: at least 1 IPv6 prefix, 1 MTU, and 1 ether address. They will be used in prefix information option(s), MTU option, SLL option for RA packet. - lib/packet: * compose_ra to compose a RA packet with 1 prefix information option, 1 MTU option and 1 SLL option. * insert_ra_prefix_opt insert a prefix information option into a RA packet. [1] A logical switch may have different IPv6 subnets using DHCPv6 or SLAAC, so set 'slaac' as Logical_Switch column will not work well. Adding 'slaac' as Logical_Router_Port may not be a good solution, but it works for now. [2] SLAAC is the first aim of this patch, but not RA responder. So it wont take care of lots of flags or options in RA packet. A further work will try to support those flags or options. [3] This works for case (a) all logical switch IPv6 subnets attached onto the same router port, which OpenStack Neutron using this way. And it won't need flows to route RS packets to router ports which is necessary for installing RA responder flows on logical router pipeline. A further work will replace this by installing RA responder flows on logical router pipeline, which will support case (b) logical switch have IPv6 subnets in SLAAC mode attached onto different router ports. [4] Ordered parameters isn't a friendly way to use 'ra' action, it will be fixed with [2]. From my side, it's fine to use 'continuation' for case (a) in [3]. But for case (b) in [3], since RS packet will be dispatched several router ports, which means ovn-controller will compose several RA packets for one RS packet, I'm not sure whether it's good to keep use 'continuation'. Signed-off-by: Zong Kai LI --- lib/packets.c | 105 +++++++++++++++++++++++++++++++++++++++++++++++ lib/packets.h | 58 ++++++++++++++++++++++++++ ovn/controller/pinctrl.c | 87 +++++++++++++++++++++++++++++++++++++++ ovn/lib/actions.c | 70 +++++++++++++++++++++++++++++++ ovn/lib/actions.h | 9 ++++ ovn/lib/expr.c | 11 +---- ovn/lib/expr.h | 11 +++++ ovn/northd/ovn-northd.c | 56 +++++++++++++++++++++++-- ovn/ovn-nb.ovsschema | 6 ++- ovn/ovn-nb.xml | 11 +++++ ovn/ovn-sb.xml | 24 +++++++++++ tests/ovn.at | 79 +++++++++++++++++++++++++++++++++++ 12 files changed, 512 insertions(+), 15 deletions(-) diff --git a/lib/packets.c b/lib/packets.c index 1bf887e..f4e5e4d 100644 --- a/lib/packets.c +++ b/lib/packets.c @@ -35,6 +35,7 @@ const struct in6_addr in6addr_exact = IN6ADDR_EXACT_INIT; const struct in6_addr in6addr_all_hosts = IN6ADDR_ALL_HOSTS_INIT; +const struct in6_addr in6addr_all_routers = IN6ADDR_ALL_ROUTERS_INIT; struct in6_addr flow_tnl_dst(const struct flow_tnl *tnl) @@ -1404,6 +1405,110 @@ compose_na(struct dp_packet *b, ND_MSG_LEN + ND_OPT_LEN)); } +void +compose_ra(struct dp_packet *b, + const struct eth_addr eth_src, const ovs_be32 ipv6_src[4], + const ovs_be32 router_prefix[4], uint8_t plen, + ovs_be16 hlmo_flags, ovs_be16 router_lifetime, + ovs_be32 reachable_time,ovs_be32 retrans_timer, + uint8_t la_flags, ovs_be32 valid_lifetime, + ovs_be32 preferred_lifetime, ovs_be16 mtu) +{ + struct ovs_ra_msg *ra; + struct ovs_ra_prefix *ra_prefix; + struct ovs_ra_mtu *ra_mtu; + struct ovs_nd_opt *ra_sll; + uint32_t icmp_csum; + + eth_compose(b, eth_addr_ip6_all_hosts, eth_src, + ETH_TYPE_IPV6, IPV6_HEADER_LEN); + ra = compose_ipv6(b, IPPROTO_ICMPV6, ipv6_src, + ALIGNED_CAST(ovs_be32 *, in6addr_all_hosts.s6_addr), + 0, 0, 255, + RA_MSG_LEN + RA_PREFIX_LEN + RA_MTU_LEN + ND_OPT_LEN); + + ra->icmph.icmp6_type = ND_ROUTER_ADVERT; + ra->icmph.icmp6_code = 0; + ra->hlmo_flags = hlmo_flags; + ra->router_lifetime = router_lifetime; + ra->reachable_time = reachable_time; + ra->retrans_timer = retrans_timer; + + /* Set prefix information option. */ + ra_prefix = &ra->prefix_info[0]; + ra_prefix->ra_opt_type = ND_OPT_PREFIX_INFORMATION; + ra_prefix->ra_opt_len = 4; + ra_prefix->ra_opt_plen = plen; + ra_prefix->ra_opt_la = la_flags; + ra_prefix->ra_opt_valid_lifetime = valid_lifetime; + ra_prefix->ra_opt_preferred_lifetime = preferred_lifetime; + ra_prefix->ra_opt_reserved = 0; + packet_update_csum128(b, IPPROTO_ICMPV6, + ra_prefix->prefix.be32, router_prefix); + memcpy(ra_prefix->prefix.be32, router_prefix, sizeof(ovs_be32[4])); + + /* Set MTU option. */ + ra_mtu = (struct ovs_ra_mtu *)(ra_prefix + 1); + ra_mtu->mtu_opt_type = ND_OPT_MTU; + ra_mtu->mtu_opt_len = 1; + ra_mtu->mtu_opt_reserved = 0; + ovs_be16 *csum = &(ra->icmph.icmp6_cksum); + *csum = recalc_csum32(*csum, ra_mtu->mtu, mtu); + ra_mtu->mtu = mtu; + + /* Set Source Link-Layer. */ + ra_sll = (struct ovs_nd_opt *)(ra_mtu + 1); + ra_sll->nd_opt_type = ND_OPT_SOURCE_LINKADDR; + ra_sll->nd_opt_len = 1; + csum = &(ra->icmph.icmp6_cksum); + *csum = recalc_csum48(*csum, ra_sll->nd_opt_mac, eth_src); + ra_sll->nd_opt_mac = eth_src; + + ra->icmph.icmp6_cksum = 0; + icmp_csum = packet_csum_pseudoheader6(dp_packet_l3(b)); + ra->icmph.icmp6_cksum = csum_finish(csum_continue( + icmp_csum, ra, RA_MSG_LEN + RA_PREFIX_LEN + RA_MTU_LEN + ND_OPT_LEN)); +} + +void +insert_ra_prefix_opt(struct dp_packet *b, + const ovs_be32 router_prefix[4], uint8_t plen, + uint8_t la_flags, ovs_be32 valid_lifetime, + ovs_be32 preferred_lifetime) +{ + struct ip6_hdr *nh = dp_packet_l3(b); + struct ovs_ra_msg *ra = dp_packet_l4(b); + struct ovs_ra_prefix *ra_prefix; + uint32_t icmp_csum; + size_t prev_l4_size = dp_packet_l4_size(b); + + /* Insert new prefix information option before previous ones. */ + dp_packet_put_uninit(b, sizeof(struct ovs_ra_prefix)); + nh->ip6_plen = htons(prev_l4_size + RA_PREFIX_LEN); + char *src = (char *) dp_packet_l4(b) + RA_MSG_LEN; + char *dst = src + RA_PREFIX_LEN; + memmove(dst, src, prev_l4_size - RA_MSG_LEN); + ra_prefix = &ra->prefix_info[0]; + + /* Set prefix information option. */ + ra_prefix->ra_opt_type = ND_OPT_PREFIX_INFORMATION; + ra_prefix->ra_opt_len = 4; + ra_prefix->ra_opt_plen = plen; + ra_prefix->ra_opt_la = la_flags; + ra_prefix->ra_opt_valid_lifetime = valid_lifetime; + ra_prefix->ra_opt_preferred_lifetime = preferred_lifetime; + ra_prefix->ra_opt_reserved = 0; + packet_update_csum128(b, IPPROTO_ICMPV6, + ra_prefix->prefix.be32, router_prefix); + memcpy(ra_prefix->prefix.be32, router_prefix, sizeof(ovs_be32[4])); + + /* Recalculate checksum. */ + ra->icmph.icmp6_cksum = 0; + icmp_csum = packet_csum_pseudoheader6(dp_packet_l3(b)); + ra->icmph.icmp6_cksum = csum_finish(csum_continue( + icmp_csum, ra, prev_l4_size + RA_PREFIX_LEN)); +} + uint32_t packet_csum_pseudoheader(const struct ip_header *ip) { diff --git a/lib/packets.h b/lib/packets.h index 5fd1e51..7fb0473 100644 --- a/lib/packets.h +++ b/lib/packets.h @@ -161,6 +161,12 @@ static const struct eth_addr eth_addr_lacp OVS_UNUSED static const struct eth_addr eth_addr_bfd OVS_UNUSED = { { { 0x00, 0x23, 0x20, 0x00, 0x00, 0x01 } } }; +static const struct eth_addr eth_addr_ip6_all_hosts OVS_UNUSED + = { { { 0x33, 0x33, 0x00, 0x00, 0x00, 0x01 } } }; + +static const struct eth_addr eth_addr_ip6_all_routers OVS_UNUSED + = { { { 0x33, 0x33, 0x00, 0x00, 0x00, 0x02 } } }; + static inline bool eth_addr_is_broadcast(const struct eth_addr a) { return (a.be16[0] & a.be16[1] & a.be16[2]) == htons(0xffff); @@ -844,6 +850,43 @@ BUILD_ASSERT_DECL(ND_MSG_LEN == sizeof(struct ovs_nd_msg)); #define ND_RSO_SOLICITED 0x40000000 #define ND_RSO_OVERRIDE 0x20000000 +/* Router Advertisment Prefix Information option field. */ +#define RA_PREFIX_LEN 32 +struct ovs_ra_prefix { + uint8_t ra_opt_type; /* Values defined in icmp6.h */ + uint8_t ra_opt_len; + uint8_t ra_opt_plen; + /* on-link flag and autonomous address-configuration flag */ + uint8_t ra_opt_la; + ovs_be32 ra_opt_valid_lifetime; + ovs_be32 ra_opt_preferred_lifetime; + ovs_be32 ra_opt_reserved; + union ovs_16aligned_in6_addr prefix; +}; +BUILD_ASSERT_DECL(RA_PREFIX_LEN == sizeof(struct ovs_ra_prefix)); + +#define RA_MTU_LEN 8 +struct ovs_ra_mtu { + uint8_t mtu_opt_type; /* Values defined in icmp6.h */ + uint8_t mtu_opt_len; + ovs_be16 mtu_opt_reserved; + ovs_be32 mtu; +}; +BUILD_ASSERT_DECL(RA_MTU_LEN == sizeof(struct ovs_ra_mtu)); + +#define RA_MSG_LEN 16 +struct ovs_ra_msg { + struct icmp6_header icmph; + ovs_be16 hlmo_flags; + ovs_be16 router_lifetime; + ovs_be32 reachable_time; + ovs_be32 retrans_timer; + struct ovs_ra_prefix prefix_info[0]; + struct ovs_ra_mtu mtu[0]; + struct ovs_nd_opt sll[0]; +}; +BUILD_ASSERT_DECL(RA_MSG_LEN == sizeof(struct ovs_ra_msg)); + /* * Use the same struct for MLD and MLD2, naming members as the defined fields in * in the corresponding version of the protocol, though they are reserved in the @@ -898,6 +941,10 @@ extern const struct in6_addr in6addr_all_hosts; #define IN6ADDR_ALL_HOSTS_INIT { { { 0xff,0x02,0x00,0x00,0x00,0x00,0x00,0x00, \ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01 } } } +extern const struct in6_addr in6addr_all_routers; +#define IN6ADDR_ALL_ROUTERS_INIT { { { 0xff,0x02,0x00,0x00,0x00,0x00,0x00,0x00, \ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02 } } } + static inline bool ipv6_addr_equals(const struct in6_addr *a, const struct in6_addr *b) { @@ -1093,6 +1140,17 @@ void compose_na(struct dp_packet *, const struct eth_addr eth_src, const struct eth_addr eth_dst, const ovs_be32 ipv6_src[4], const ovs_be32 ipv6_dst[4], ovs_be32 rso_flags); +void compose_ra(struct dp_packet *, + const struct eth_addr eth_src, const ovs_be32 ipv6_src[4], + const ovs_be32 router_prefix[4], uint8_t plen, + ovs_be16 hlmo_flags, ovs_be16 router_lifetime, + ovs_be32 reachable_time,ovs_be32 retrans_timer, + uint8_t la_flags, ovs_be32 valid_lifetime, + ovs_be32 preferred_lifetime, ovs_be16 mtu); +void insert_ra_prefix_opt(struct dp_packet *, + const ovs_be32 router_prefix[4], uint8_t plen, + uint8_t la_flags, ovs_be32 valid_lifetime, + ovs_be32 preferred_lifetime); uint32_t packet_csum_pseudoheader(const struct ip_header *); void IP_ECN_set_ce(struct dp_packet *pkt, bool is_ipv6); diff --git a/ovn/controller/pinctrl.c b/ovn/controller/pinctrl.c index 0ae6501..b9a71e4 100644 --- a/ovn/controller/pinctrl.c +++ b/ovn/controller/pinctrl.c @@ -72,6 +72,9 @@ static void send_garp_run(const struct ovsrec_bridge *, static void pinctrl_handle_na(const struct flow *ip_flow, const struct match *md, struct ofpbuf *userdata); +static void pinctrl_handle_ra(struct ofputil_packet_in *pin, + struct ofpbuf *userdata, + struct ofpbuf *continuation); static void reload_metadata(struct ofpbuf *ofpacts, const struct match *md); @@ -414,6 +417,10 @@ process_packet_in(const struct ofp_header *msg) pinctrl_handle_na(&headers, &pin.flow_metadata, &userdata); break; + case ACTION_OPCODE_RA: + pinctrl_handle_ra(&pin, &userdata, &continuation); + break; + default: VLOG_WARN_RL(&rl, "unrecognized packet-in opcode %"PRIu32, ntohl(ah->opcode)); @@ -1008,3 +1015,83 @@ exit: dp_packet_uninit(&packet); ofpbuf_uninit(&ofpacts); } + +static void +pinctrl_handle_ra(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); + + /* Get number of prefixes. */ + uint8_t *n_prefixes = ofpbuf_try_pull(userdata, 1); + if (!n_prefixes || *n_prefixes == 0) { + VLOG_WARN_RL(&rl, "Failed to parse prefixes number."); + return; + } + + /* Get prefixes. */ + size_t prefixes_size = sizeof(struct ipv6_netaddr) * (*n_prefixes); + struct ipv6_netaddr *prefixes = xmalloc(prefixes_size); + struct in6_addr *prefix; + uint8_t *prefix_len = NULL; + for (size_t i = 0; i < *n_prefixes; i++) { + prefix = ofpbuf_try_pull(userdata, sizeof(struct in6_addr)); + if (!prefix) { + VLOG_WARN_RL(&rl, "Failed to parse ipv6 prefix."); + goto exit; + } + prefix_len = ofpbuf_try_pull(userdata, 1); + if (!prefix_len || *prefix_len == 0) { + VLOG_WARN_RL(&rl, "Failed to parse prefix len."); + goto exit; + } + memcpy(&prefixes[i].addr, prefix, sizeof(struct in6_addr)); + prefixes[i].plen = *prefix_len; + } + + /* Get MTU. */ + ovs_be16 *mtu = ofpbuf_try_pull(userdata, sizeof(ovs_be16)); + + /* Get SLL. */ + struct eth_addr *sll = ofpbuf_try_pull(userdata, + sizeof(struct eth_addr)); + + struct in6_addr lla; + in6_generate_lla(*sll, &lla); + ovs_be32 ipv6_src[4], router_prefix[4]; + memcpy(ipv6_src, &lla, sizeof ipv6_src); + memcpy(router_prefix, &prefixes[0].addr, sizeof router_prefix); + + /* Flags: hop-limit = 64, M=0, O=0. */ + ovs_be16 hlmo_flags = htons(0x4000); + ovs_be16 router_lifetime = htons(0x2a30); // 3 hours. + uint8_t la_flags = 0xc0; + ovs_be32 valid_lifetime = htonl(0x2a30); + ovs_be32 preferred_lifetime = htonl(0x2a30); + /* Frame the RA packet. */ + uint64_t packet_stub[256 / 8]; + struct dp_packet packet; + dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub); + compose_ra(&packet, + *sll, ipv6_src, + router_prefix, prefixes[0].plen, + hlmo_flags, router_lifetime, 0, 0, + la_flags, valid_lifetime, preferred_lifetime, + *mtu); + for (size_t i = 1; i < *n_prefixes; i++) { + memcpy(router_prefix, &prefixes[i].addr, sizeof router_prefix); + insert_ra_prefix_opt(&packet, router_prefix, prefixes[i].plen, + la_flags, valid_lifetime, preferred_lifetime); + } + + pin->packet = dp_packet_data(&packet); + pin->packet_len = dp_packet_size(&packet); + queue_msg(ofputil_encode_resume(pin, continuation, proto)); + +exit: + dp_packet_uninit(&packet); + free(prefixes); +} diff --git a/ovn/lib/actions.c b/ovn/lib/actions.c index 6e2bf93..e411517 100644 --- a/ovn/lib/actions.c +++ b/ovn/lib/actions.c @@ -1029,6 +1029,74 @@ parse_ct_nat(struct action_context *ctx, bool snat) ofpbuf_push_uninit(ctx->ofpacts, ct_offset); } +static void +parse_ra_action(struct action_context *ctx) +{ + if (!lexer_match(ctx->lexer, LEX_T_LPAREN)) { + action_syntax_error(ctx, "expecting '('"); + return; + } + + struct expr_constant_set cs = { + .type = EXPR_C_INTEGER, + .values = NULL, + .n_values = 0, + .in_curlies = false, + }; + struct expr_context expr_ctx = { + .lexer = ctx->lexer, + .symtab = NULL, + }; + size_t allocated_values = 0; + + do { + if (!parse_constant(&expr_ctx, &cs, &allocated_values)) { + break; + } + lexer_match(expr_ctx.lexer, LEX_T_COMMA); + } while(!lexer_match(expr_ctx.lexer, LEX_T_RPAREN)); + /* Multiple prefixes, as least 1 MTU, at least 1 SLL. */ + if (cs.n_values < 3) { + action_syntax_error(ctx, "not enough constants found"); + goto exit; + } + /* Check prefixes. */ + for (size_t i = 0; i < cs.n_values - 2; i++) { + if (cs.values[i].format != LEX_F_IPV6) { + action_syntax_error(ctx, "expecting IPv6 address format"); + goto exit; + } + } + + /* Set controller data. */ + size_t oc_offset = start_controller_op(ctx->ofpacts, ACTION_OPCODE_RA, + true); + cs.n_values = cs.n_values - 2; + /* Put prefixes number. */ + ofpbuf_put(ctx->ofpacts, &cs.n_values, 1); + /* Put prefixes. */ + int plen = 0; + for (size_t i = 0; i < cs.n_values; i++) { + ofpbuf_put(ctx->ofpacts, &cs.values[i].value.ipv6, + sizeof(struct in6_addr)); + plen = ipv6_count_cidr_bits(&cs.values[i].mask.ipv6); + ofpbuf_put(ctx->ofpacts, &plen, 1); + } + /* Put MTU. */ + ofpbuf_put(ctx->ofpacts, &cs.values[cs.n_values].value.be16[63], + sizeof(ovs_be16)); + /* Put SLL. */ + ofpbuf_put(ctx->ofpacts, &cs.values[cs.n_values + 1].value.mac, + sizeof(struct eth_addr)); + finish_controller_op(ctx->ofpacts, oc_offset); + + expr_destroy(ctx->prereqs); + add_prerequisite(ctx, "icmp6 && icmp6.type == 133 && icmp6.code == 0"); + +exit: + expr_constant_set_destroy(&cs); +} + static bool parse_action(struct action_context *ctx) { @@ -1066,6 +1134,8 @@ parse_action(struct action_context *ctx) parse_nested_action(ctx, ACTION_OPCODE_ARP, "ip4"); } else if (lexer_match_id(ctx->lexer, "na")) { parse_nested_action(ctx, ACTION_OPCODE_NA, "nd"); + } else if (lexer_match_id(ctx->lexer, "ra")) { + parse_ra_action(ctx); } else if (lexer_match_id(ctx->lexer, "get_arp")) { parse_get_arp_action(ctx); } else if (lexer_match_id(ctx->lexer, "put_arp")) { diff --git a/ovn/lib/actions.h b/ovn/lib/actions.h index 114c71e..c77c463 100644 --- a/ovn/lib/actions.h +++ b/ovn/lib/actions.h @@ -78,6 +78,15 @@ enum action_opcode { * The actions, in OpenFlow 1.3 format, follow the action_header. */ ACTION_OPCODE_NA, + + /* ra (prefix0, [prefix1, ...,] mtu, sll)". + * + * Arguments follow the action_header, in this format: + * - One or more IPv6 prefixes. + * - A 16-bit MTU. + * - A 48-bit Ether address. + */ + ACTION_OPCODE_RA, }; /* Header. */ diff --git a/ovn/lib/expr.c b/ovn/lib/expr.c index 1c38b99..e210faf 100644 --- a/ovn/lib/expr.c +++ b/ovn/lib/expr.c @@ -406,15 +406,6 @@ expr_print(const struct expr *e) /* Parsing. */ -/* Context maintained during expr_parse(). */ -struct expr_context { - struct lexer *lexer; /* Lexer for pulling more tokens. */ - const struct shash *symtab; /* Symbol table. */ - const struct shash *macros; /* Table of macros. */ - char *error; /* Error, if any, otherwise NULL. */ - bool not; /* True inside odd number of NOT operators. */ -}; - struct expr *expr_parse__(struct expr_context *); static void expr_not(struct expr *); static bool parse_field(struct expr_context *, struct expr_field *); @@ -757,7 +748,7 @@ parse_macros(struct expr_context *ctx, struct expr_constant_set *cs, return true; } -static bool +bool parse_constant(struct expr_context *ctx, struct expr_constant_set *cs, size_t *allocated_values) { diff --git a/ovn/lib/expr.h b/ovn/lib/expr.h index d790c49..150f2e7 100644 --- a/ovn/lib/expr.h +++ b/ovn/lib/expr.h @@ -450,6 +450,17 @@ struct expr_constant_set { bool in_curlies; /* Whether the constants were in {}. */ }; +/* Context maintained during expr_parse(). */ +struct expr_context { + struct lexer *lexer; /* Lexer for pulling more tokens. */ + const struct shash *symtab; /* Symbol table. */ + const struct shash *macros; /* Table of macros. */ + char *error; /* Error, if any, otherwise NULL. */ + bool not; /* True inside odd number of NOT operators. */ +}; + +bool parse_constant(struct expr_context *ctx, struct expr_constant_set *cs, + size_t *allocated_values); char *expr_parse_constant_set(struct lexer *, const struct shash *symtab, struct expr_constant_set *cs) OVS_WARN_UNUSED_RESULT; diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c index 17fbf29..0c3f336 100644 --- a/ovn/northd/ovn-northd.c +++ b/ovn/northd/ovn-northd.c @@ -102,7 +102,8 @@ enum ovn_stage { PIPELINE_STAGE(SWITCH, IN, ARP_ND_RSP, 9, "ls_in_arp_rsp") \ PIPELINE_STAGE(SWITCH, IN, DHCP_OPTIONS, 10, "ls_in_dhcp_options") \ PIPELINE_STAGE(SWITCH, IN, DHCP_RESPONSE, 11, "ls_in_dhcp_response") \ - PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 12, "ls_in_l2_lkup") \ + PIPELINE_STAGE(SWITCH, IN, RA_RSP, 12, "ls_in_ra_rsp") \ + PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 13, "ls_in_l2_lkup") \ \ /* Logical switch egress stages. */ \ PIPELINE_STAGE(SWITCH, OUT, PRE_LB, 0, "ls_out_pre_lb") \ @@ -2218,7 +2219,54 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, ovn_lflow_add(lflows, od, S_SWITCH_IN_DHCP_RESPONSE, 0, "1", "next;"); } - /* Ingress table 12: Destination lookup, broadcast and multicast handling + /* Ingress table 12: RA responder. */ + HMAP_FOR_EACH (od, key_node, datapaths) { + if (!od->nbs) { + continue; + } + + /* By default goto next. (priority 0)*/ + ovn_lflow_add(lflows, od, S_SWITCH_IN_RA_RSP, 0, "1", "next;"); + + /* Reply for known router ports. (priority 50) */ + if (!od->n_router_ports) { + continue; + } + ds_clear(&match); + ds_put_cstr(&match, "eth.dst == 33:33:00:00:00:02" + " && ip6.dst == ff02::2 && icmp6" + " && icmp6.type == 133 && icmp6.code == 0"); + ds_clear(&actions); + ds_put_format(&actions, "ra("); + size_t actions_len = actions.length; + for (size_t i = 0; i != od->n_router_ports; i++) { + op = od->router_ports[i]; + if (!op->peer) { + continue; + } + op = op->peer; + if (!op->lrp_networks.n_ipv6_addrs + || !op->nbrp->slaac + || !*op->nbrp->slaac) { + continue; + } + for (size_t j = 0; j != op->lrp_networks.n_ipv6_addrs; j++) { + ds_put_format(&actions, "%s/%u,", + op->lrp_networks.ipv6_addrs[j].network_s, + op->lrp_networks.ipv6_addrs[j].plen); + } + ds_put_format(&actions, "%ld,", od->nbs->mtu); + ds_put_cstr(&actions, op->lrp_networks.ea_s); + } + if (actions.length != actions_len) { + ds_put_cstr(&actions, "); outport = inport; inport = \"\"; " + "output;"); + ovn_lflow_add(lflows, od, S_SWITCH_IN_RA_RSP, 50, + ds_cstr(&match), ds_cstr(&actions)); + } + } + + /* Ingress table 13: Destination lookup, broadcast and multicast handling * (priority 100). */ HMAP_FOR_EACH (op, key_node, ports) { if (!op->nbsp) { @@ -2238,7 +2286,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, "outport = \""MC_FLOOD"\"; output;"); } - /* Ingress table 12: Destination lookup, unicast handling (priority 50), */ + /* Ingress table 13: Destination lookup, unicast handling (priority 50), */ HMAP_FOR_EACH (op, key_node, ports) { if (!op->nbsp) { continue; @@ -2271,7 +2319,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, } } - /* Ingress table 12: Destination lookup for unknown MACs (priority 0). */ + /* Ingress table 13: Destination lookup for unknown MACs (priority 0). */ HMAP_FOR_EACH (od, key_node, datapaths) { if (!od->nbs) { continue; diff --git a/ovn/ovn-nb.ovsschema b/ovn/ovn-nb.ovsschema index 3cf07c1..7ef23d3 100644 --- a/ovn/ovn-nb.ovsschema +++ b/ovn/ovn-nb.ovsschema @@ -1,7 +1,7 @@ { "name": "OVN_Northbound", "version": "5.1.0", - "cksum": "2201958537 8295", + "cksum": "912529793 8553", "tables": { "Logical_Switch": { "columns": { @@ -21,6 +21,9 @@ "refType": "strong"}, "min": 0, "max": 1}}, + "mtu": {"type": {"key": {"type": "integer", + "minInteger": 0, + "maxInteger": 65535}}}, "external_ids": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}}, @@ -134,6 +137,7 @@ "mac": {"type": "string"}, "peer": {"type": {"key": "string", "min": 0, "max": 1}}, "enabled": {"type": {"key": "boolean", "min": 0, "max": 1}}, + "slaac": {"type": {"key": "boolean", "min": 0, "max": 1}}, "external_ids": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}}, diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml index abd0340..d01c9f1 100644 --- a/ovn/ovn-nb.xml +++ b/ovn/ovn-nb.xml @@ -78,6 +78,10 @@ Access control rules that apply to packets within the logical switch. + + Logical Switch MTU. + + See External IDs at the beginning of this document. @@ -820,6 +824,13 @@ port has all ingress and egress traffic dropped. + + Setting true specifies the logical switch subnets behind + this router port IPv6 networks are going to use SLAAC as IPv6 address + configuration and RA mode. Otherwise set this column to + false, or this router port has no IPv6 networks. + +

A given router port serves one of two purposes: diff --git a/ovn/ovn-sb.xml b/ovn/ovn-sb.xml index 3d26e65..bde036d 100644 --- a/ovn/ovn-sb.xml +++ b/ovn/ovn-sb.xml @@ -1112,6 +1112,30 @@

+
+ ra (p, m, s); +
+ +
+

+ Generate an IPv6 router advertisement (RA) packet per 'ra' + action ordered parameters. + Parameters: One or more IPv6 prefixes p, + 16-bit MUT m, 48-bit source link-layer + address s. +

+ +

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

+ +

+ Prerequisite: + icmp6 && icmp6.type == 133 && icmp6.code == 0 +

+
+
get_arp(P, A);
diff --git a/tests/ovn.at b/tests/ovn.at index 86efcf5..f315747 100644 --- a/tests/ovn.at +++ b/tests/ovn.at @@ -655,6 +655,9 @@ reg1[0] = put_dhcp_opts(offerip=1.2.3.4, domain=1.2.3.4); => DHCP option domain # na na { eth.src = 12:34:56:78:9a:bc; nd.tll = 12:34:56:78:9a:bc; outport = inport; inport = ""; /* Allow sending out inport. */ output; }; => actions=controller(userdata=00.00.00.03.00.00.00.00.00.19.00.10.80.00.08.06.12.34.56.78.9a.bc.00.00.00.19.00.10.80.00.42.06.12.34.56.78.9a.bc.00.00.ff.ff.00.18.00.00.23.20.00.06.00.20.00.00.00.00.00.01.1c.04.00.01.1e.04.00.19.00.10.00.01.1c.04.00.00.00.00.00.00.00.00.00.19.00.10.00.00.00.02.00.00.00.00.00.00.00.00.ff.ff.00.10.00.00.23.20.00.0e.ff.f8.40.00.00.00), prereqs=nd +# ra +ra(fdad:a0f9:a012::/64,fdad:a0f9:c593::/64,1422,fa:16:3e:32:3c:e0); => actions=controller(userdata=00.00.00.04.00.00.00.00.02.fd.ad.a0.f9.a0.12.00.00.00.00.00.00.00.00.00.00.40.fd.ad.a0.f9.c5.93.00.00.00.00.00.00.00.00.00.00.40.05.8e.fa.16.3e.32.3c.e0,pause), prereqs=icmp6 && icmp6.type == 0x85 && icmp6.code == 0 + # Contradictionary prerequisites (allowed but not useful): ip4.src = ip6.src[0..31]; => actions=move:NXM_NX_IPV6_SRC[0..31]->NXM_OF_IP_SRC[], prereqs=eth.type == 0x800 && eth.type == 0x86dd ip4.src <-> ip6.src[0..31]; => actions=push:NXM_NX_IPV6_SRC[0..31],push:NXM_OF_IP_SRC[],pop:NXM_NX_IPV6_SRC[0..31],pop:NXM_OF_IP_SRC[], prereqs=eth.type == 0x800 && eth.type == 0x86dd @@ -3791,6 +3794,82 @@ OVN_CLEANUP([hv1]) AT_CLEANUP +AT_SETUP([ovn -- router advertisement responder]) +AT_KEYWORDS([ovn-ra]) +AT_SKIP_IF([test $HAVE_PYTHON = no]) +ovn_start + +# In this test cases we create 1 lswitch, it has 2 VIF ports attached +# with. NS packet we test, from one VIF for another VIF, will be replied +# by local ovn-controller, but not by target VIF. + +# Create hypervisors and logical switch lsw0, logical router lr0, attach lsw0 onto lr0. +ovn-nbctl ls-add lsw0 +ovn-nbctl lr-add lr0 +ovn-nbctl lrp-add lr0 lrp0 fa:16:3e:32:3c:e0 fdad:a0f9:a012::1/64 fdad:a0f9:c593::1/64 +ovn-nbctl set Logical_Router_Port lrp0 slaac="true" +ovn-nbctl \ + -- lsp-add lsw0 lsp0 \ + -- set Logical_Switch_Port lsp0 type=router \ + options:router-port=lrp0 \ + addresses='"fa:16:3e:32:3c:e0 fdad:a0f9:a012::1 fdad:a0f9:c593::1"' +net_add n1 +sim_add hv1 +as hv1 +ovs-vsctl add-br br-phys +ovn_attach n1 br-phys 192.168.0.2 + +# Add vif1 to hv1 and lsw0, turn on l2 port security on vif1. +ovs-vsctl add-port br-int vif1 -- set Interface vif1 external-ids:iface-id=lp1 options:tx_pcap=hv1/vif1-tx.pcap options:rxq_pcap=hv1/vif1-rx.pcap ofport-request=1 +ovn-nbctl lsp-add lsw0 lp1 +ovn-nbctl set logical-switch lsw0 mtu=1450 +ovn-nbctl lsp-set-addresses lp1 "fa:16:3e:6e:a1:42 10.0.0.12 fdad:a0f9:a012:0:f816:3eff:fe6e:a142 fdad:a0f9:c593:0:f816:3eff:fe6e:a142" +ovn-nbctl lsp-set-port-security lp1 "fa:16:3e:6e:a1:42 10.0.0.12 fdad:a0f9:a012:0:f816:3eff:fe6e:a142 fdad:a0f9:c593:0:f816:3eff:fe6e:a142" + +# 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 + +# Allow some time for ovn-northd and ovn-controller to catch up. +# XXX This should be more systematic. +sleep 1 + +# Given the name of a logical port, prints the name of the hypervisor +# on which it is located. +vif_to_hv() { + echo hv1${1%?} +} +trim_zeros() { + sed 's/\(00\)\{1,\}$//' +} +for i in 1 2; do + : > $i.expected +done + +# Complete Router Solicitation packet and Router Advertisement packet. +rs_packet=333300000002fa163e6ea14286dd6000000000103afffe80000000000000f8163efffe6ea142ff0200000000000000000000000000028500cb9e000000000101fa163e6ea142 +ra_packet=333300000001fa163e323ce086dd6000000000603afffe80000000000000f8163efffe323ce0ff02000000000000000000000000000186004b7240002a300000000000000000030440c000002a3000002a3000000000fdada0f9c59300000000000000000000030440c000002a3000002a3000000000fdada0f9a012000000000000000000000501000005aa00000101fa163e323ce0 + +as hv1 ovs-appctl netdev-dummy/receive vif1 $rs_packet +echo $ra_packet | trim_zeros >> 1.expected + +sleep 1 + +echo "------ hv1 dump ------" +as hv1 ovs-vsctl show +as hv1 ovs-ofctl -O OpenFlow13 show br-int +as hv1 ovs-ofctl -O OpenFlow13 dump-flows br-int + +file=hv1/vif1-tx.pcap +echo $file +$PYTHON "$top_srcdir/utilities/ovs-pcap.in" $file | trim_zeros > 1.packets +cat 1.expected > expout +AT_CHECK([cat 1.packets], [0], [expout]) + +OVN_CLEANUP([hv1]) + +AT_CLEANUP + AT_SETUP([ovn -- address sets modification/removal smoke test]) AT_KEYWORDS([ovn-addr]) ovn_start