From patchwork Tue Jul 12 06:56:54 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Justin Pettit X-Patchwork-Id: 647320 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 3rpdKX5R1Bz9s9N for ; Tue, 12 Jul 2016 20:21:20 +1000 (AEST) Received: from archives.nicira.com (localhost [127.0.0.1]) by archives.nicira.com (Postfix) with ESMTP id 0710D108B8; Tue, 12 Jul 2016 03:20:32 -0700 (PDT) X-Original-To: dev@openvswitch.org Delivered-To: dev@openvswitch.org Received: from mx1e3.cudamail.com (mx1.cudamail.com [69.90.118.67]) by archives.nicira.com (Postfix) with ESMTPS id 9476E106DE for ; Tue, 12 Jul 2016 03:20:26 -0700 (PDT) Received: from bar5.cudamail.com (localhost [127.0.0.1]) by mx1e3.cudamail.com (Postfix) with ESMTPS id 28BE4420233 for ; Tue, 12 Jul 2016 04:20:26 -0600 (MDT) X-ASG-Debug-ID: 1468318825-09eadd72130c2d0001-byXFYA Received: from mx3-pf2.cudamail.com ([192.168.14.1]) by bar5.cudamail.com with ESMTP id olG8WkgTDgLQZLpk (version=TLSv1 cipher=DHE-RSA-AES256-SHA bits=256 verify=NO) for ; Tue, 12 Jul 2016 04:20:25 -0600 (MDT) X-Barracuda-Envelope-From: jpettit@ovn.org X-Barracuda-RBL-Trusted-Forwarder: 192.168.14.1 Received: from unknown (HELO slow1-d.mail.gandi.net) (217.70.178.86) by mx3-pf2.cudamail.com with SMTP; 12 Jul 2016 10:20:24 -0000 Received-SPF: pass (mx3-pf2.cudamail.com: SPF record at ovn.org designates 217.70.178.86 as permitted sender) X-Barracuda-Apparent-Source-IP: 217.70.178.86 X-Barracuda-RBL-IP: 217.70.178.86 Received: from relay2-d.mail.gandi.net (relay2-d.mail.gandi.net [217.70.183.194]) by slow1-d.mail.gandi.net (Postfix) with ESMTP id 0F6D04B665F for ; Tue, 12 Jul 2016 12:17:17 +0200 (CEST) X-Originating-IP: 98.234.50.139 Received: from localhost.localdomain (unknown [98.234.50.139]) (Authenticated sender: jpettit@ovn.org) by relay2-d.mail.gandi.net (Postfix) with ESMTPSA id 6E1DCC5B0D for ; Tue, 12 Jul 2016 12:17:17 +0200 (CEST) X-CudaMail-Envelope-Sender: jpettit@ovn.org From: Justin Pettit To: dev@openvswitch.org X-CudaMail-Whitelist-To: dev@openvswitch.org X-CudaMail-MID: CM-V2-711003384 X-CudaMail-DTE: 071216 X-CudaMail-Originating-IP: 217.70.178.86 Date: Mon, 11 Jul 2016 23:56:54 -0700 X-ASG-Orig-Subj: [##CM-V2-711003384##][ovn-ipv6 24/26] [RFC] ovn-northd: Implement basic IPv6 routing. Message-Id: <1468306616-125783-25-git-send-email-jpettit@ovn.org> X-Mailer: git-send-email 1.9.1 In-Reply-To: <1468306616-125783-1-git-send-email-jpettit@ovn.org> References: <1468306616-125783-1-git-send-email-jpettit@ovn.org> MIME-Version: 1.0 X-Barracuda-Connect: UNKNOWN[192.168.14.1] X-Barracuda-Start-Time: 1468318825 X-Barracuda-Encrypted: DHE-RSA-AES256-SHA X-Barracuda-URL: https://web.cudamail.com:443/cgi-mod/mark.cgi X-ASG-Whitelist: Header =?UTF-8?B?eFwtY3VkYW1haWxcLXdoaXRlbGlzdFwtdG8=?= X-Virus-Scanned: by bsmtpd at cudamail.com X-Barracuda-BRTS-Status: 1 Subject: [ovs-dev] [ovn-ipv6 24/26] [RFC] ovn-northd: Implement basic IPv6 routing. 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: , Errors-To: dev-bounces@openvswitch.org Sender: "dev" Future commits will add support for dynamic IPv6/MAC bindings. TODO: - ovn-northd man page needs updated flows. - Add unit tests. --- ovn/TODO | 19 +- ovn/northd/ovn-northd.c | 475 ++++++++++++++++++++++++++++++++++++------------ 2 files changed, 371 insertions(+), 123 deletions(-) diff --git a/ovn/TODO b/ovn/TODO index 4f134a4..cbe6a5c 100644 --- a/ovn/TODO +++ b/ovn/TODO @@ -46,11 +46,24 @@ we can use parse_int_string() to support larger integers. ** IPv6 -*** ND versus ARP +*** Drop invalid source IPv6 addresses -*** IPv6 routing +*** Don't forward non-routable addresses -*** ICMPv6 +*** ICMPv6 action + +Similar to the ICMPv4 action, ICMPv6 messages should be generated. + +*** Neighbor Discovery + +**** ND Router Advertisements + +The router ports should periodically send out ND Router Advertisements +and respond to Router Solicitations. + +**** Learn MAC bindings on ND Solicitations + +**** Properly set RSO flags ** Dynamic IP to MAC bindings diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c index b99c183..e0e40d0 100644 --- a/ovn/northd/ovn-northd.c +++ b/ovn/northd/ovn-northd.c @@ -1866,37 +1866,34 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, ds_cstr(&match), ds_cstr(&actions)); } - if (op->lsp_addrs[i].n_ipv6_addrs > 0) { + /* For ND solicitations, we need to listen for both the + * unicast IPv6 address and its all-nodes multicast address, + * but always respond with the unicast IPv6 address. */ + for (size_t j = 0; j < op->lsp_addrs[i].n_ipv6_addrs; j++) { ds_clear(&match); - ds_put_cstr(&match, "icmp6 && icmp6.type == 135 && "); - if (op->lsp_addrs[i].n_ipv6_addrs == 1) { - ds_put_format(&match, "nd.target == %s", - op->lsp_addrs[i].ipv6_addrs[0].addr_s); - } else { - ds_put_cstr(&match, "("); - for (size_t j = 0; j < op->lsp_addrs[i].n_ipv6_addrs; j++) { - ds_put_format(&match, "nd.target == %s || ", - op->lsp_addrs[i].ipv6_addrs[j].addr_s); - } - ds_chomp(&match, ' '); - ds_chomp(&match, '|'); - ds_chomp(&match, '|'); - ds_chomp(&match, ' '); - ds_put_cstr(&match, ")"); - } + ds_put_format(&match, + "nd_sol && ip6.dst == {%s, %s} && nd.target == %s", + op->lsp_addrs[i].ipv6_addrs[j].addr_s, + op->lsp_addrs[i].ipv6_addrs[j].sn_addr_s, + op->lsp_addrs[i].ipv6_addrs[j].addr_s); + ds_clear(&actions); ds_put_format(&actions, - "nd_adv { eth.src = %s; " - "nd.tll = %s; " - "outport = inport; " - "inport = \"\"; /* Allow sending out inport. */ " - "output; };", - op->lsp_addrs[i].ea_s, - op->lsp_addrs[i].ea_s); - + "nd_adv { " + "eth.src = %s; " + "ip6.src = %s; " + "nd.target = %s; " + "nd.tll = %s; " + "outport = inport; " + "inport = \"\"; /* Allow sending out inport. */ " + "output; " + "};", + op->lsp_addrs[i].ea_s, + op->lsp_addrs[i].ipv6_addrs[j].addr_s, + op->lsp_addrs[i].ipv6_addrs[j].addr_s, + op->lsp_addrs[i].ea_s); ovn_lflow_add(lflows, op->od, S_SWITCH_IN_ARP_ND_RSP, 50, ds_cstr(&match), ds_cstr(&actions)); - } } } @@ -2037,23 +2034,49 @@ lrport_is_enabled(const struct nbrec_logical_router_port *lrport) static const char * find_lrp_member_ip(const struct ovn_port *op, const char *ip_s) { - uint32_t ip; + bool is_ipv4 = strchr(ip_s, '.') ? true : false; - if (!ip_parse(ip_s, &ip)) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "bad ip address %s", ip_s); - return NULL; - } + if (is_ipv4) { + uint32_t ip; - for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { - const struct ipv4_netaddr *na = &op->lrp_networks.ipv4_addrs[i]; + if (!ip_parse(ip_s, &ip)) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_WARN_RL(&rl, "bad ip address %s", ip_s); + return NULL; + } + + for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { + const struct ipv4_netaddr *na = &op->lrp_networks.ipv4_addrs[i]; + + if (!((na->network ^ ip) & na->mask)) { + /* There should be only 1 interface that matches the + * supplied IP. Otherwise, it's a configuration error, + * because subnets of a router's interfaces should NOT + * overlap. */ + return na->addr_s; + } + } + } else { + struct in6_addr ip6; - if (!((na->network ^ ip) & na->mask)) { - /* There should be only 1 interface that matches the - * next hop. Otherwise, it's a configuration error, - * because subnets of router's interfaces should NOT - * overlap. */ - return na->addr_s; + if (!ipv6_parse(ip_s, &ip6)) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_WARN_RL(&rl, "bad ipv6 address %s", ip_s); + return NULL; + } + + for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { + const struct ipv6_netaddr *na = &op->lrp_networks.ipv6_addrs[i]; + struct in6_addr xor_addr = ipv6_addr_bitxor(&na->network, &ip6); + struct in6_addr and_addr = ipv6_addr_bitand(&xor_addr, &na->mask); + + if (ipv6_is_zero(&and_addr)) { + /* There should be only 1 interface that matches the + * supplied IP. Otherwise, it's a configuration error, + * because subnets of a router's interfaces should NOT + * overlap. */ + return na->addr_s; + } } } @@ -2065,21 +2088,26 @@ add_route(struct hmap *lflows, const struct ovn_port *op, const char *lrp_addr_s, const char *network_s, int plen, const char *gateway) { - char *match = xasprintf("ip4.dst == %s/%d", network_s, plen); + bool is_ipv4 = strchr(network_s, '.') ? true : false; + + char *match = xasprintf("ip%s.dst == %s/%d", is_ipv4 ? "4" : "6", + network_s, plen); struct ds actions = DS_EMPTY_INITIALIZER; - ds_put_cstr(&actions, "ip.ttl--; reg0 = "); + ds_put_format(&actions, "ip.ttl--; %sreg0 = ", is_ipv4 ? "" : "xx"); + if (gateway) { ds_put_cstr(&actions, gateway); } else { - ds_put_cstr(&actions, "ip4.dst"); + ds_put_format(&actions, "ip%s.dst", is_ipv4 ? "4" : "6"); } ds_put_format(&actions, "; " - "reg1 = %s; " + "%sreg1 = %s; " "eth.src = %s; " "outport = %s; " "inport = \"\"; /* Allow sending out inport. */ " "next;", + is_ipv4 ? "" : "xx", lrp_addr_s, op->lrp_networks.ea_s, op->json_key); @@ -2097,26 +2125,68 @@ build_static_route_flow(struct hmap *lflows, struct ovn_datapath *od, struct hmap *ports, const struct nbrec_logical_router_static_route *route) { - ovs_be32 prefix, nexthop, mask; + ovs_be32 nexthop; const char *lrp_addr_s; + unsigned int plen; + bool is_ipv4; - /* Verify that next hop is an IP address with 32 bits mask. */ - char *error = ip_parse_masked(route->nexthop, &nexthop, &mask); - if (error || mask != OVS_BE32_MAX) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "bad next hop ip address %s", route->nexthop); + /* Verify that the next hop is an IP address with an all-ones mask. */ + char *error = ip_parse_cidr(route->nexthop, &nexthop, &plen); + if (!error) { + if (plen != 32) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_WARN_RL(&rl, "bad next hop mask %s", route->nexthop); + return; + } + is_ipv4 = true; + } else { free(error); - return; + + struct in6_addr ip6; + char *error = ipv6_parse_cidr(route->nexthop, &ip6, &plen); + if (!error) { + if (plen != 128) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_WARN_RL(&rl, "bad next hop mask %s", route->nexthop); + return; + } + is_ipv4 = false; + } else { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_WARN_RL(&rl, "bad next hop ip address %s", route->nexthop); + free(error); + return; + } } - /* Verify that ip prefix is a valid CIDR address. */ - error = ip_parse_masked(route->ip_prefix, &prefix, &mask); - if (error || !ip_is_cidr(mask)) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "bad 'ip_prefix' in static routes %s", - route->ip_prefix); - free(error); - return; + char *prefix_s; + if (is_ipv4) { + ovs_be32 prefix; + /* Verify that ip prefix is a valid IPv4 address. */ + error = ip_parse_cidr(route->ip_prefix, &prefix, &plen); + if (error) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_WARN_RL(&rl, "bad 'ip_prefix' in static routes %s", + route->ip_prefix); + free(error); + return; + } + prefix_s = xasprintf(IP_FMT, IP_ARGS(prefix & be32_prefix_mask(plen))); + } else { + /* Verify that ip prefix is a valid IPv6 address. */ + struct in6_addr prefix; + error = ipv6_parse_cidr(route->ip_prefix, &prefix, &plen); + if (error) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_WARN_RL(&rl, "bad 'ip_prefix' in static routes %s", + route->ip_prefix); + free(error); + return; + } + struct in6_addr mask = ipv6_create_mask(plen); + struct in6_addr network = ipv6_addr_bitand(&prefix, &mask); + prefix_s = xmalloc(INET6_ADDRSTRLEN); + inet_ntop(AF_INET6, &network, prefix_s, INET6_ADDRSTRLEN); } /* Find the outgoing port. */ @@ -2127,7 +2197,7 @@ build_static_route_flow(struct hmap *lflows, struct ovn_datapath *od, static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); VLOG_WARN_RL(&rl, "Bad out port %s for static route %s", route->output_port, route->ip_prefix); - return; + goto free_prefix_s; } lrp_addr_s = find_lrp_member_ip(out_port, route->nexthop); } else { @@ -2154,17 +2224,17 @@ build_static_route_flow(struct hmap *lflows, struct ovn_datapath *od, static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); VLOG_WARN_RL(&rl, "No path for static route %s; next hop %s", route->ip_prefix, route->nexthop); - return; + goto free_prefix_s; } - char *prefix_s = xasprintf(IP_FMT, IP_ARGS(prefix & mask)); - add_route(lflows, out_port, lrp_addr_s, prefix_s, - ip_count_cidr_bits(mask), route->nexthop); + add_route(lflows, out_port, lrp_addr_s, prefix_s, plen, route->nexthop); + +free_prefix_s: free(prefix_s); } static void -op_put_networks(struct ds *ds, const struct ovn_port *op, bool add_bcast) +op_put_v4_networks(struct ds *ds, const struct ovn_port *op, bool add_bcast) { if (!add_bcast && op->lrp_networks.n_ipv4_addrs == 1) { ds_put_format(ds, "%s", op->lrp_networks.ipv4_addrs[0].addr_s); @@ -2184,6 +2254,23 @@ op_put_networks(struct ds *ds, const struct ovn_port *op, bool add_bcast) } static void +op_put_v6_networks(struct ds *ds, const struct ovn_port *op) +{ + if (op->lrp_networks.n_ipv6_addrs == 1) { + ds_put_format(ds, "%s", op->lrp_networks.ipv6_addrs[0].addr_s); + return; + } + + ds_put_cstr(ds, "{"); + for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { + ds_put_format(ds, "%s, ", op->lrp_networks.ipv6_addrs[i].addr_s); + } + ds_chomp(ds, ' '); + ds_chomp(ds, ','); + ds_put_cstr(ds, "}"); +} + +static void build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, struct hmap *lflows) { @@ -2271,39 +2358,43 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 0, "1", "next;"); } + /* Logical router ingress table 1: IP Input for IPv4. */ HMAP_FOR_EACH (op, key_node, ports) { if (!op->nbr) { continue; } - /* L3 admission control: drop packets that originate from an IP address - * owned by the router or a broadcast address known to the router - * (priority 100). */ - ds_clear(&match); - ds_put_cstr(&match, "ip4.src == "); - op_put_networks(&match, op, true); - ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 100, - ds_cstr(&match), "drop;"); - /* ICMP echo reply. These flows reply to ICMP echo requests - * received for the router's IP address. Since packets only - * get here as part of the logical router datapath, the inport - * (i.e. the incoming locally attached net) does not matter. - * The ip.ttl also does not matter (RFC1812 section 4.2.2.9) */ - ds_clear(&match); - ds_put_cstr(&match, "ip4.dst == "); - op_put_networks(&match, op, false); - ds_put_cstr(&match, " && icmp4.type == 8 && icmp4.code == 0"); - - ds_clear(&actions); - ds_put_format(&actions, - "ip4.dst <-> ip4.src; " - "ip.ttl = 255; " - "icmp4.type = 0; " - "inport = \"\"; /* Allow sending out inport. */ " - "next; "); - ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90, - ds_cstr(&match), ds_cstr(&actions)); + if (op->lrp_networks.n_ipv4_addrs) { + /* L3 admission control: drop packets that originate from an + * IPv4 address owned by the router or a broadcast address + * known to the router (priority 100). */ + ds_clear(&match); + ds_put_cstr(&match, "ip4.src == "); + op_put_v4_networks(&match, op, true); + ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 100, + ds_cstr(&match), "drop;"); + + /* ICMP echo reply. These flows reply to ICMP echo requests + * received for the router's IP address. Since packets only + * get here as part of the logical router datapath, the inport + * (i.e. the incoming locally attached net) does not matter. + * The ip.ttl also does not matter (RFC1812 section 4.2.2.9) */ + ds_clear(&match); + ds_put_cstr(&match, "ip4.dst == "); + op_put_v4_networks(&match, op, false); + ds_put_cstr(&match, " && icmp4.type == 8 && icmp4.code == 0"); + + ds_clear(&actions); + ds_put_format(&actions, + "ip4.dst <-> ip4.src; " + "ip.ttl = 255; " + "icmp4.type = 0; " + "inport = \"\"; /* Allow sending out inport. */ " + "next; "); + ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90, + ds_cstr(&match), ds_cstr(&actions)); + } /* ARP reply. These flows reply to ARP requests for the router's own * IP address. */ @@ -2428,6 +2519,77 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, free(nat_ips); } + /* Logical router ingress table 1: IP Input for IPv6. */ + HMAP_FOR_EACH (op, key_node, ports) { + if (!op->nbr) { + continue; + } + + if (op->lrp_networks.n_ipv6_addrs) { + /* L3 admission control: drop packets that originate from an + * IPv6 address owned by the router or a broadcast address + * known to the router (priority 100). */ + ds_clear(&match); + ds_put_cstr(&match, "ip6.src == "); + op_put_v6_networks(&match, op); + ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 100, + ds_cstr(&match), "drop;"); + + /* ICMPv6 echo reply. These flows reply to echo requests + * received for the router's IP address. */ + ds_clear(&match); + ds_put_cstr(&match, "ip6.dst == "); + op_put_v6_networks(&match, op); + ds_put_cstr(&match, " && icmp6.type == 128 && icmp6.code == 0"); + + ds_clear(&actions); + ds_put_cstr(&actions, + "ip6.dst <-> ip6.src; " + "ip.ttl = 255; " + "icmp6.type = 129; " + "inport = \"\"; /* Allow sending out inport. */ " + "next; "); + ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90, + ds_cstr(&match), ds_cstr(&actions)); + + /* Drop IPv6 traffic to this router. */ + ds_clear(&match); + ds_put_cstr(&match, "ip6.dst == "); + op_put_v6_networks(&match, op); + ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 60, + ds_cstr(&match), "drop;"); + } + + /* ND reply. These flows reply to ND solicitations for the + * router's own IP address. */ + for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { + ds_clear(&match); + ds_put_format(&match, + "nd_sol && ip6.dst == {%s, %s} && nd.target == %s", + op->lrp_networks.ipv6_addrs[i].addr_s, + op->lrp_networks.ipv6_addrs[i].sn_addr_s, + op->lrp_networks.ipv6_addrs[i].addr_s); + + ds_clear(&actions); + ds_put_format(&actions, + "nd_adv { " + "eth.src = %s; " + "ip6.src = %s; " + "nd.target = %s; " + "nd.tll = %s; " + "outport = inport; " + "inport = \"\"; /* Allow sending out inport. */ " + "output; " + "};", + op->lrp_networks.ea_s, + op->lrp_networks.ipv6_addrs[i].addr_s, + op->lrp_networks.ipv6_addrs[i].addr_s, + op->lrp_networks.ea_s); + ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90, + ds_cstr(&match), ds_cstr(&actions)); + } + } + /* NAT in Gateway routers. */ HMAP_FOR_EACH (od, key_node, datapaths) { if (!od->nbr) { @@ -2558,10 +2720,11 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, /* Logical router ingress table 4: IP Routing. * * A packet that arrives at this table is an IP packet that should be - * routed to the address in ip4.dst. This table sets outport to the correct - * output port, eth.src to the output port's MAC address, and reg0 to the - * next-hop IP address (leaving ip4.dst, the packet’s final destination, - * unchanged), and advances to the next table for ARP resolution. */ + * routed to the address in 'ip[46].dst'. This table sets outport to + * the correct output port, eth.src to the output port's MAC + * address, and '[xx]reg0' to the next-hop IP address (leaving + * 'ip[46].dst', the packet’s final destination, unchanged), and + * advances to the next table for ARP/ND resolution. */ HMAP_FOR_EACH (op, key_node, ports) { if (!op->nbr) { continue; @@ -2572,14 +2735,20 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, op->lrp_networks.ipv4_addrs[i].network_s, op->lrp_networks.ipv4_addrs[i].plen, NULL); } + + for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { + add_route(lflows, op, op->lrp_networks.ipv6_addrs[i].addr_s, + op->lrp_networks.ipv6_addrs[i].network_s, + op->lrp_networks.ipv6_addrs[i].plen, NULL); + } } + /* Convert the static routes to flows. */ HMAP_FOR_EACH (od, key_node, datapaths) { if (!od->nbr) { continue; } - /* Convert the static routes to flows. */ for (int i = 0; i < od->nbr->n_static_routes; i++) { const struct nbrec_logical_router_static_route *route; @@ -2587,6 +2756,7 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, build_static_route_flow(lflows, od, ports, route); } } + /* XXX destination unreachable */ /* Local router ingress table 5: ARP Resolution. @@ -2597,10 +2767,11 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, * Ethernet address in eth.dst. */ HMAP_FOR_EACH (op, key_node, ports) { if (op->nbr) { - /* This is a logical router port. If next-hop IP address in 'reg0' - * matches ip address of this router port, then the packet is - * intended to eventually be sent to this logical port. Set the - * destination mac address using this port's mac address. + /* This is a logical router port. If next-hop IP address in + * '[xx]reg0' matches IP address of this router port, then + * the packet is intended to eventually be sent to this + * logical port. Set the destination mac address using this + * port's mac address. * * The packet is still in peer's logical pipeline. So the match * should be on peer's outport. */ @@ -2610,23 +2781,38 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, continue; } - ds_clear(&match); - ds_put_format(&match, "outport == %s && reg0 == ", - peer->json_key); - op_put_networks(&match, op, false); + if (op->lrp_networks.n_ipv4_addrs) { + ds_clear(&match); + ds_put_format(&match, "outport == %s && reg0 == ", + peer->json_key); + op_put_v4_networks(&match, op, false); + + ds_clear(&actions); + ds_put_format(&actions, "eth.dst = %s; next;", + op->lrp_networks.ea_s); + ovn_lflow_add(lflows, peer->od, S_ROUTER_IN_ARP_RESOLVE, + 100, ds_cstr(&match), ds_cstr(&actions)); + } - ds_clear(&actions); - ds_put_format(&actions, "eth.dst = %s; next;", - op->lrp_networks.ea_s); - ovn_lflow_add(lflows, peer->od, S_ROUTER_IN_ARP_RESOLVE, - 100, ds_cstr(&match), ds_cstr(&actions)); + if (op->lrp_networks.n_ipv6_addrs) { + ds_clear(&match); + ds_put_format(&match, "outport == %s && xxreg0 == ", + peer->json_key); + op_put_v6_networks(&match, op); + + ds_clear(&actions); + ds_put_format(&actions, "eth.dst = %s; next;", + op->lrp_networks.ea_s); + ovn_lflow_add(lflows, peer->od, S_ROUTER_IN_ARP_RESOLVE, + 100, ds_cstr(&match), ds_cstr(&actions)); + } } } else if (op->od->n_router_ports && strcmp(op->nbs->type, "router")) { /* This is a logical switch port that backs a VM or a container. * Extract its addresses. For each of the address, go through all * the router ports attached to the switch (to which this port * connects) and if the address in question is reachable from the - * router port, add an ARP entry in that router's pipeline. */ + * router port, add an ARP/ND entry in that router's pipeline. */ for (size_t i = 0; i < op->n_lsp_addrs; i++) { const char *ea_s = op->lsp_addrs[i].ea_s; @@ -2663,6 +2849,40 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, ds_cstr(&match), ds_cstr(&actions)); } } + + for (size_t j = 0; j < op->lsp_addrs[i].n_ipv6_addrs; j++) { + const char *ip_s = op->lsp_addrs[i].ipv6_addrs[j].addr_s; + for (size_t k = 0; k < op->od->n_router_ports; k++) { + /* Get the Logical_Router_Port that the + * Logical_Switch_Port is connected to, as + * 'peer'. */ + const char *peer_name = smap_get( + &op->od->router_ports[k]->nbs->options, + "router-port"); + if (!peer_name) { + continue; + } + + struct ovn_port *peer = ovn_port_find(ports, peer_name); + if (!peer || !peer->nbr) { + continue; + } + + if (!find_lrp_member_ip(peer, ip_s)) { + continue; + } + + ds_clear(&match); + ds_put_format(&match, "outport == %s && xxreg0 == %s", + peer->json_key, ip_s); + + ds_clear(&actions); + ds_put_format(&actions, "eth.dst = %s; next;", ea_s); + ovn_lflow_add(lflows, peer->od, + S_ROUTER_IN_ARP_RESOLVE, 100, + ds_cstr(&match), ds_cstr(&actions)); + } + } } } else if (!strcmp(op->nbs->type, "router")) { /* This is a logical switch port that connects to a router. */ @@ -2698,16 +2918,31 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, continue; } - ds_clear(&match); - ds_put_format(&match, "outport == %s && reg0 == ", - peer->json_key); - op_put_networks(&match, router_port, false); + if (router_port->lrp_networks.n_ipv4_addrs) { + ds_clear(&match); + ds_put_format(&match, "outport == %s && reg0 == ", + peer->json_key); + op_put_v4_networks(&match, router_port, false); + + ds_clear(&actions); + ds_put_format(&actions, "eth.dst = %s; next;", + router_port->lrp_networks.ea_s); + ovn_lflow_add(lflows, peer->od, S_ROUTER_IN_ARP_RESOLVE, + 100, ds_cstr(&match), ds_cstr(&actions)); + } - ds_clear(&actions); - ds_put_format(&actions, "eth.dst = %s; next;", - router_port->lrp_networks.ea_s); - ovn_lflow_add(lflows, peer->od, S_ROUTER_IN_ARP_RESOLVE, - 100, ds_cstr(&match), ds_cstr(&actions)); + if (router_port->lrp_networks.n_ipv6_addrs) { + ds_clear(&match); + ds_put_format(&match, "outport == %s && xxreg0 == ", + peer->json_key); + op_put_v6_networks(&match, router_port); + + ds_clear(&actions); + ds_put_format(&actions, "eth.dst = %s; next;", + router_port->lrp_networks.ea_s); + ovn_lflow_add(lflows, peer->od, S_ROUTER_IN_ARP_RESOLVE, + 100, ds_cstr(&match), ds_cstr(&actions)); + } } } }