From patchwork Wed Sep 16 17:12:09 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Anton Ivanov X-Patchwork-Id: 1365730 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=140.211.166.136; helo=silver.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=cambridgegreys.com Received: from silver.osuosl.org (smtp3.osuosl.org [140.211.166.136]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 4BsJVS5Yzdz9sWP for ; Thu, 17 Sep 2020 10:58:08 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by silver.osuosl.org (Postfix) with ESMTP id 1487D2DE25; Wed, 16 Sep 2020 17:12:34 +0000 (UTC) X-Virus-Scanned: amavisd-new at osuosl.org Received: from silver.osuosl.org ([127.0.0.1]) by localhost (.osuosl.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id Oj-G+RAsTBap; Wed, 16 Sep 2020 17:12:25 +0000 (UTC) Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56]) by silver.osuosl.org (Postfix) with ESMTP id 8504A2DE6B; Wed, 16 Sep 2020 17:12:25 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id 600D0C0864; Wed, 16 Sep 2020 17:12:25 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from fraxinus.osuosl.org (smtp4.osuosl.org [140.211.166.137]) by lists.linuxfoundation.org (Postfix) with ESMTP id 23EF7C0051 for ; Wed, 16 Sep 2020 17:12:23 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by fraxinus.osuosl.org (Postfix) with ESMTP id E709B86AFB for ; Wed, 16 Sep 2020 17:12:22 +0000 (UTC) X-Virus-Scanned: amavisd-new at osuosl.org Received: from fraxinus.osuosl.org ([127.0.0.1]) by localhost (.osuosl.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id rwkwpVFVtuRL for ; Wed, 16 Sep 2020 17:12:21 +0000 (UTC) X-Greylist: from auto-whitelisted by SQLgrey-1.7.6 Received: from www.kot-begemot.co.uk (ivanoab7.miniserver.com [37.128.132.42]) by fraxinus.osuosl.org (Postfix) with ESMTPS id 7D07586AE3 for ; Wed, 16 Sep 2020 17:12:21 +0000 (UTC) Received: from tun252.jain.kot-begemot.co.uk ([192.168.18.6] helo=jain.kot-begemot.co.uk) by www.kot-begemot.co.uk with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1kIayh-0004cM-Kn; Wed, 16 Sep 2020 17:12:19 +0000 Received: from jain.kot-begemot.co.uk ([192.168.3.3]) by jain.kot-begemot.co.uk with esmtp (Exim 4.92) (envelope-from ) id 1kIaye-0001Ii-Lw; Wed, 16 Sep 2020 18:12:18 +0100 From: anton.ivanov@cambridgegreys.com To: dev@openvswitch.org Date: Wed, 16 Sep 2020 18:12:09 +0100 Message-Id: <20200916171213.3472-2-anton.ivanov@cambridgegreys.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20200916171213.3472-1-anton.ivanov@cambridgegreys.com> References: <20200916171213.3472-1-anton.ivanov@cambridgegreys.com> MIME-Version: 1.0 X-Clacks-Overhead: GNU Terry Pratchett Cc: i.maximets@ovn.org, Anton Ivanov Subject: [ovs-dev] [PATCH ovn 1/5] ovn-northd: Move out misc build_lrouter_flows() local deny rules into a function X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: ovs-dev-bounces@openvswitch.org Sender: "dev" From: Anton Ivanov This moves various rules which deny local traffic which should not be forwarded such as ND, RA, ARP, etc into a separate function. Signed-off-by: Anton Ivanov --- northd/ovn-northd.c | 200 +++++++++++++++++++++++--------------------- 1 file changed, 105 insertions(+), 95 deletions(-) diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c index db14909fc..32f081d9d 100644 --- a/northd/ovn-northd.c +++ b/northd/ovn-northd.c @@ -8688,6 +8688,12 @@ build_egress_delivery_flows_for_lrouter_port( struct ovn_port *op, struct hmap *lflows, struct ds *match, struct ds *actions); +/* Filter rules for various local traffic which should not be forwarded + * by default */ +static void +build_misc_local_traffic_drop_flows_for_lrouter( + struct ovn_datapath *od, struct hmap *lflows); + static void build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, struct hmap *lflows, struct shash *meter_groups, @@ -8720,101 +8726,7 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, } HMAP_FOR_EACH (od, key_node, datapaths) { - if (!od->nbr) { - continue; - } - - /* L3 admission control: drop multicast and broadcast source, localhost - * source or destination, and zero network source or destination - * (priority 100). */ - ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 100, - "ip4.src_mcast ||" - "ip4.src == 255.255.255.255 || " - "ip4.src == 127.0.0.0/8 || " - "ip4.dst == 127.0.0.0/8 || " - "ip4.src == 0.0.0.0/8 || " - "ip4.dst == 0.0.0.0/8", - "drop;"); - - /* Priority-90-92 flows handle ARP requests and ND packets. Most are - * per logical port but DNAT addresses can be handled per datapath - * for non gateway router ports. - */ - struct sset snat_ips = SSET_INITIALIZER(&snat_ips); - for (int i = 0; i < od->nbr->n_nat; i++) { - struct ovn_nat *nat_entry = &od->nat_entries[i]; - const struct nbrec_nat *nat = nat_entry->nb; - - /* Skip entries we failed to parse. */ - if (!nat_entry_is_valid(nat_entry)) { - continue; - } - - struct lport_addresses *ext_addrs = &nat_entry->ext_addrs; - char *ext_addr = nat_entry_is_v6(nat_entry) ? - ext_addrs->ipv6_addrs[0].addr_s : - ext_addrs->ipv4_addrs[0].addr_s; - - if (!strcmp(nat->type, "snat")) { - if (!sset_add(&snat_ips, ext_addr)) { - continue; - } - } - - /* Priority 91 and 92 flows are added for each gateway router - * port to handle the special cases. In case we get the packet - * on a regular port, just reply with the port's ETH address. - */ - if (nat_entry_is_v6(nat_entry)) { - build_lrouter_nd_flow(od, NULL, "nd_na", - ext_addrs->ipv6_addrs[0].addr_s, - ext_addrs->ipv6_addrs[0].sn_addr_s, - REG_INPORT_ETH_ADDR, NULL, false, 90, - &nat->header_, lflows); - } else { - build_lrouter_arp_flow(od, NULL, - ext_addrs->ipv4_addrs[0].addr_s, - REG_INPORT_ETH_ADDR, NULL, false, 90, - &nat->header_, lflows); - } - } - sset_destroy(&snat_ips); - - /* Drop ARP packets (priority 85). ARP request packets for router's own - * IPs are handled with priority-90 flows. - * Drop IPv6 ND packets (priority 85). ND NA packets for router's own - * IPs are handled with priority-90 flows. - */ - ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 85, - "arp || nd", "drop;"); - - /* Allow IPv6 multicast traffic that's supposed to reach the - * router pipeline (e.g., router solicitations). - */ - ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 84, "nd_rs || nd_ra", - "next;"); - - /* Drop other reserved multicast. */ - ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 83, - "ip6.mcast_rsvd", "drop;"); - - /* Allow other multicast if relay enabled (priority 82). */ - ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 82, - "ip4.mcast || ip6.mcast", - od->mcast_info.rtr.relay ? "next;" : "drop;"); - - /* Drop Ethernet local broadcast. By definition this traffic should - * not be forwarded.*/ - ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 50, - "eth.bcast", "drop;"); - - /* TTL discard */ - ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 30, - "ip4 && ip.ttl == {0, 1}", "drop;"); - - /* Pass other traffic not already handled to the next table for - * routing. */ - ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 0, "1", "next;"); + build_misc_local_traffic_drop_flows_for_lrouter(od, lflows); } /* Logical router ingress table 3: IP Input for IPv4. */ @@ -11281,6 +11193,104 @@ build_egress_delivery_flows_for_lrouter_port( } +static void +build_misc_local_traffic_drop_flows_for_lrouter( + struct ovn_datapath *od, struct hmap *lflows) +{ + if (od->nbr) { + /* L3 admission control: drop multicast and broadcast source, localhost + * source or destination, and zero network source or destination + * (priority 100). */ + ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 100, + "ip4.src_mcast ||" + "ip4.src == 255.255.255.255 || " + "ip4.src == 127.0.0.0/8 || " + "ip4.dst == 127.0.0.0/8 || " + "ip4.src == 0.0.0.0/8 || " + "ip4.dst == 0.0.0.0/8", + "drop;"); + + /* Priority-90-92 flows handle ARP requests and ND packets. Most are + * per logical port but DNAT addresses can be handled per datapath + * for non gateway router ports. + */ + struct sset snat_ips = SSET_INITIALIZER(&snat_ips); + for (int i = 0; i < od->nbr->n_nat; i++) { + struct ovn_nat *nat_entry = &od->nat_entries[i]; + const struct nbrec_nat *nat = nat_entry->nb; + + /* Skip entries we failed to parse. */ + if (!nat_entry_is_valid(nat_entry)) { + continue; + } + + struct lport_addresses *ext_addrs = &nat_entry->ext_addrs; + char *ext_addr = nat_entry_is_v6(nat_entry) ? + ext_addrs->ipv6_addrs[0].addr_s : + ext_addrs->ipv4_addrs[0].addr_s; + + if (!strcmp(nat->type, "snat")) { + if (!sset_add(&snat_ips, ext_addr)) { + continue; + } + } + + /* Priority 91 and 92 flows are added for each gateway router + * port to handle the special cases. In case we get the packet + * on a regular port, just reply with the port's ETH address. + */ + if (nat_entry_is_v6(nat_entry)) { + build_lrouter_nd_flow(od, NULL, "nd_na", + ext_addrs->ipv6_addrs[0].addr_s, + ext_addrs->ipv6_addrs[0].sn_addr_s, + REG_INPORT_ETH_ADDR, NULL, false, 90, + &nat->header_, lflows); + } else { + build_lrouter_arp_flow(od, NULL, + ext_addrs->ipv4_addrs[0].addr_s, + REG_INPORT_ETH_ADDR, NULL, false, 90, + &nat->header_, lflows); + } + } + sset_destroy(&snat_ips); + + /* Drop ARP packets (priority 85). ARP request packets for router's own + * IPs are handled with priority-90 flows. + * Drop IPv6 ND packets (priority 85). ND NA packets for router's own + * IPs are handled with priority-90 flows. + */ + ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 85, + "arp || nd", "drop;"); + + /* Allow IPv6 multicast traffic that's supposed to reach the + * router pipeline (e.g., router solicitations). + */ + ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 84, "nd_rs || nd_ra", + "next;"); + + /* Drop other reserved multicast. */ + ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 83, + "ip6.mcast_rsvd", "drop;"); + + /* Allow other multicast if relay enabled (priority 82). */ + ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 82, + "ip4.mcast || ip6.mcast", + od->mcast_info.rtr.relay ? "next;" : "drop;"); + + /* Drop Ethernet local broadcast. By definition this traffic should + * not be forwarded.*/ + ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 50, + "eth.bcast", "drop;"); + + /* TTL discard */ + ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 30, + "ip4 && ip.ttl == {0, 1}", "drop;"); + + /* Pass other traffic not already handled to the next table for + * routing. */ + ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 0, "1", "next;"); + } +} /* Updates the Logical_Flow and Multicast_Group tables in the OVN_SB database, * constructing their contents based on the OVN_NB database. */ static void From patchwork Wed Sep 16 17:12:10 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Anton Ivanov X-Patchwork-Id: 1365505 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=140.211.166.133; helo=hemlock.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=cambridgegreys.com Received: from hemlock.osuosl.org (smtp2.osuosl.org [140.211.166.133]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 4BsHhK1Cspz9sVn for ; Thu, 17 Sep 2020 10:21:35 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by hemlock.osuosl.org (Postfix) with ESMTP id 17B4187006; Wed, 16 Sep 2020 17:12:35 +0000 (UTC) X-Virus-Scanned: amavisd-new at osuosl.org Received: from hemlock.osuosl.org ([127.0.0.1]) by localhost (.osuosl.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id ffiveOkWGb2a; Wed, 16 Sep 2020 17:12:30 +0000 (UTC) Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56]) by hemlock.osuosl.org (Postfix) with ESMTP id 73C7387422; Wed, 16 Sep 2020 17:12:30 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id 5B73CC0864; Wed, 16 Sep 2020 17:12:30 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from whitealder.osuosl.org (smtp1.osuosl.org [140.211.166.138]) by lists.linuxfoundation.org (Postfix) with ESMTP id D26F1C0864 for ; Wed, 16 Sep 2020 17:12:28 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by whitealder.osuosl.org (Postfix) with ESMTP id B68C98738E for ; Wed, 16 Sep 2020 17:12:28 +0000 (UTC) X-Virus-Scanned: amavisd-new at osuosl.org Received: from whitealder.osuosl.org ([127.0.0.1]) by localhost (.osuosl.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id m268WCtNuGMY for ; Wed, 16 Sep 2020 17:12:24 +0000 (UTC) X-Greylist: from auto-whitelisted by SQLgrey-1.7.6 Received: from www.kot-begemot.co.uk (ivanoab7.miniserver.com [37.128.132.42]) by whitealder.osuosl.org (Postfix) with ESMTPS id 6E46687360 for ; Wed, 16 Sep 2020 17:12:24 +0000 (UTC) Received: from tun252.jain.kot-begemot.co.uk ([192.168.18.6] helo=jain.kot-begemot.co.uk) by www.kot-begemot.co.uk with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1kIayk-0004cT-AO; Wed, 16 Sep 2020 17:12:23 +0000 Received: from jain.kot-begemot.co.uk ([192.168.3.3]) by jain.kot-begemot.co.uk with esmtp (Exim 4.92) (envelope-from ) id 1kIayg-0001Ii-Fe; Wed, 16 Sep 2020 18:12:20 +0100 From: anton.ivanov@cambridgegreys.com To: dev@openvswitch.org Date: Wed, 16 Sep 2020 18:12:10 +0100 Message-Id: <20200916171213.3472-3-anton.ivanov@cambridgegreys.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20200916171213.3472-1-anton.ivanov@cambridgegreys.com> References: <20200916171213.3472-1-anton.ivanov@cambridgegreys.com> MIME-Version: 1.0 X-Clacks-Overhead: GNU Terry Pratchett Cc: i.maximets@ovn.org, Anton Ivanov Subject: [ovs-dev] [PATCH ovn 2/5] ovn-northd: move IPv4 input lrouter flow generation into functions X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: ovs-dev-bounces@openvswitch.org Sender: "dev" From: Anton Ivanov Move out IPv4 input lrouter flow generation into a helper function and split it into more manageable chunks. Signed-off-by: Anton Ivanov --- northd/ovn-northd.c | 841 +++++++++++++++++++++++--------------------- 1 file changed, 441 insertions(+), 400 deletions(-) diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c index 32f081d9d..c3149d547 100644 --- a/northd/ovn-northd.c +++ b/northd/ovn-northd.c @@ -8694,6 +8694,12 @@ static void build_misc_local_traffic_drop_flows_for_lrouter( struct ovn_datapath *od, struct hmap *lflows); +/* Logical router ingress table 3: IP Input for IPv4. */ +static void +build_ipv4_input_flows_for_lrouter_port( + struct ovn_port *op, struct hmap *lflows, + struct ds *match, struct ds *actions); + static void build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, struct hmap *lflows, struct shash *meter_groups, @@ -8729,407 +8735,9 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, build_misc_local_traffic_drop_flows_for_lrouter(od, lflows); } - /* Logical router ingress table 3: IP Input for IPv4. */ HMAP_FOR_EACH (op, key_node, ports) { - if (!op->nbrp) { - continue; - } - - if (op->derived) { - /* No ingress packets are accepted on a chassisredirect - * port, so no need to program flows for that port. */ - continue; - } - - 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); - ds_put_cstr(&match, " && "REGBIT_EGRESS_LOOPBACK" == 0"); - ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 100, - ds_cstr(&match), "drop;", - &op->nbrp->header_); - - /* 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"); - - const char * icmp_actions = "ip4.dst <-> ip4.src; " - "ip.ttl = 255; " - "icmp4.type = 0; " - "flags.loopback = 1; " - "next; "; - ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90, - ds_cstr(&match), icmp_actions, - &op->nbrp->header_); - } - - /* ICMP time exceeded */ - for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { - ds_clear(&match); - ds_clear(&actions); - - ds_put_format(&match, - "inport == %s && ip4 && " - "ip.ttl == {0, 1} && !ip.later_frag", op->json_key); - ds_put_format(&actions, - "icmp4 {" - "eth.dst <-> eth.src; " - "icmp4.type = 11; /* Time exceeded */ " - "icmp4.code = 0; /* TTL exceeded in transit */ " - "ip4.dst = ip4.src; " - "ip4.src = %s; " - "ip.ttl = 255; " - "next; };", - op->lrp_networks.ipv4_addrs[i].addr_s); - ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 40, - ds_cstr(&match), ds_cstr(&actions), - &op->nbrp->header_); - } - - /* ARP reply. These flows reply to ARP requests for the router's own - * IP address. */ - for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { - ds_clear(&match); - ds_put_format(&match, "arp.spa == %s/%u", - op->lrp_networks.ipv4_addrs[i].network_s, - op->lrp_networks.ipv4_addrs[i].plen); - - if (op->od->l3dgw_port && op->od->l3redirect_port && op->peer - && op->peer->od->n_localnet_ports) { - bool add_chassis_resident_check = false; - if (op == op->od->l3dgw_port) { - /* Traffic with eth.src = l3dgw_port->lrp_networks.ea_s - * should only be sent from the "redirect-chassis", so that - * upstream MAC learning points to the "redirect-chassis". - * Also need to avoid generation of multiple ARP responses - * from different chassis. */ - add_chassis_resident_check = true; - } else { - /* Check if the option 'reside-on-redirect-chassis' - * is set to true on the router port. If set to true - * and if peer's logical switch has a localnet port, it - * means the router pipeline for the packets from - * peer's logical switch is be run on the chassis - * hosting the gateway port and it should reply to the - * ARP requests for the router port IPs. - */ - add_chassis_resident_check = smap_get_bool( - &op->nbrp->options, - "reside-on-redirect-chassis", false); - } - - if (add_chassis_resident_check) { - ds_put_format(&match, " && is_chassis_resident(%s)", - op->od->l3redirect_port->json_key); - } - } - - build_lrouter_arp_flow(op->od, op, - op->lrp_networks.ipv4_addrs[i].addr_s, - REG_INPORT_ETH_ADDR, &match, false, 90, - &op->nbrp->header_, lflows); - } - - /* A set to hold all load-balancer vips that need ARP responses. */ - struct sset all_ips_v4 = SSET_INITIALIZER(&all_ips_v4); - struct sset all_ips_v6 = SSET_INITIALIZER(&all_ips_v6); - get_router_load_balancer_ips(op->od, &all_ips_v4, &all_ips_v6); - - const char *ip_address; - SSET_FOR_EACH (ip_address, &all_ips_v4) { - ds_clear(&match); - if (op == op->od->l3dgw_port) { - ds_put_format(&match, "is_chassis_resident(%s)", - op->od->l3redirect_port->json_key); - } - - build_lrouter_arp_flow(op->od, op, - ip_address, REG_INPORT_ETH_ADDR, - &match, false, 90, NULL, lflows); - } - - SSET_FOR_EACH (ip_address, &all_ips_v6) { - ds_clear(&match); - if (op == op->od->l3dgw_port) { - ds_put_format(&match, "is_chassis_resident(%s)", - op->od->l3redirect_port->json_key); - } - - build_lrouter_nd_flow(op->od, op, "nd_na", - ip_address, NULL, REG_INPORT_ETH_ADDR, - &match, false, 90, NULL, lflows); - } - - sset_destroy(&all_ips_v4); - sset_destroy(&all_ips_v6); - - if (!smap_get(&op->od->nbr->options, "chassis") - && !op->od->l3dgw_port) { - /* UDP/TCP port unreachable. */ - for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { - ds_clear(&match); - ds_put_format(&match, - "ip4 && ip4.dst == %s && !ip.later_frag && udp", - op->lrp_networks.ipv4_addrs[i].addr_s); - const char *action = "icmp4 {" - "eth.dst <-> eth.src; " - "ip4.dst <-> ip4.src; " - "ip.ttl = 255; " - "icmp4.type = 3; " - "icmp4.code = 3; " - "next; };"; - ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, - 80, ds_cstr(&match), action, - &op->nbrp->header_); - - ds_clear(&match); - ds_put_format(&match, - "ip4 && ip4.dst == %s && !ip.later_frag && tcp", - op->lrp_networks.ipv4_addrs[i].addr_s); - action = "tcp_reset {" - "eth.dst <-> eth.src; " - "ip4.dst <-> ip4.src; " - "next; };"; - ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, - 80, ds_cstr(&match), action, - &op->nbrp->header_); - - ds_clear(&match); - ds_put_format(&match, - "ip4 && ip4.dst == %s && !ip.later_frag", - op->lrp_networks.ipv4_addrs[i].addr_s); - action = "icmp4 {" - "eth.dst <-> eth.src; " - "ip4.dst <-> ip4.src; " - "ip.ttl = 255; " - "icmp4.type = 3; " - "icmp4.code = 2; " - "next; };"; - ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, - 70, ds_cstr(&match), action, - &op->nbrp->header_); - } - } - - /* A gateway router can have 4 SNAT IP addresses to force DNATed and - * LBed traffic respectively to be SNATed. In addition, there can be - * a number of SNAT rules in the NAT table. */ - struct v46_ip *snat_ips = xmalloc(sizeof *snat_ips - * (op->od->nbr->n_nat + 4)); - size_t n_snat_ips = 0; - struct lport_addresses snat_addrs; - - if (get_force_snat_ip(op->od, "dnat", &snat_addrs)) { - if (snat_addrs.n_ipv4_addrs) { - snat_ips[n_snat_ips].family = AF_INET; - snat_ips[n_snat_ips++].ipv4 = snat_addrs.ipv4_addrs[0].addr; - } - if (snat_addrs.n_ipv6_addrs) { - snat_ips[n_snat_ips].family = AF_INET6; - snat_ips[n_snat_ips++].ipv6 = snat_addrs.ipv6_addrs[0].addr; - } - destroy_lport_addresses(&snat_addrs); - } - - memset(&snat_addrs, 0, sizeof(snat_addrs)); - if (get_force_snat_ip(op->od, "lb", &snat_addrs)) { - if (snat_addrs.n_ipv4_addrs) { - snat_ips[n_snat_ips].family = AF_INET; - snat_ips[n_snat_ips++].ipv4 = snat_addrs.ipv4_addrs[0].addr; - } - if (snat_addrs.n_ipv6_addrs) { - snat_ips[n_snat_ips].family = AF_INET6; - snat_ips[n_snat_ips++].ipv6 = snat_addrs.ipv6_addrs[0].addr; - } - destroy_lport_addresses(&snat_addrs); - } - - for (size_t i = 0; i < op->od->nbr->n_nat; i++) { - struct ovn_nat *nat_entry = &op->od->nat_entries[i]; - const struct nbrec_nat *nat = nat_entry->nb; - - /* Skip entries we failed to parse. */ - if (!nat_entry_is_valid(nat_entry)) { - continue; - } - - if (!strcmp(nat->type, "snat")) { - if (nat_entry_is_v6(nat_entry)) { - struct in6_addr *ipv6 = - &nat_entry->ext_addrs.ipv6_addrs[0].addr; - - snat_ips[n_snat_ips].family = AF_INET6; - snat_ips[n_snat_ips++].ipv6 = *ipv6; - } else { - ovs_be32 ip = nat_entry->ext_addrs.ipv4_addrs[0].addr; - snat_ips[n_snat_ips].family = AF_INET; - snat_ips[n_snat_ips++].ipv4 = ip; - } - } - } - - ds_clear(&match); - ds_put_cstr(&match, "ip4.dst == {"); - bool has_drop_ips = false; - for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { - bool snat_ip_is_router_ip = false; - for (int j = 0; j < n_snat_ips; j++) { - /* Packets to SNAT IPs should not be dropped. */ - if (snat_ips[j].family == AF_INET - && op->lrp_networks.ipv4_addrs[i].addr - == snat_ips[j].ipv4) { - snat_ip_is_router_ip = true; - break; - } - } - if (snat_ip_is_router_ip) { - continue; - } - ds_put_format(&match, "%s, ", - op->lrp_networks.ipv4_addrs[i].addr_s); - has_drop_ips = true; - } - if (has_drop_ips) { - ds_chomp(&match, ' '); - ds_chomp(&match, ','); - ds_put_cstr(&match, "} || ip6.dst == {"); - } else { - ds_clear(&match); - ds_put_cstr(&match, "ip6.dst == {"); - } - - for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { - bool snat_ip_is_router_ip = false; - for (int j = 0; j < n_snat_ips; j++) { - /* Packets to SNAT IPs should not be dropped. */ - if (snat_ips[j].family == AF_INET6 - && !memcmp(&op->lrp_networks.ipv6_addrs[i].addr, - &snat_ips[j].ipv6, sizeof snat_ips[j].ipv6)) { - snat_ip_is_router_ip = true; - break; - } - } - if (snat_ip_is_router_ip) { - continue; - } - ds_put_format(&match, "%s, ", - op->lrp_networks.ipv6_addrs[i].addr_s); - has_drop_ips = true; - } - - ds_chomp(&match, ' '); - ds_chomp(&match, ','); - ds_put_cstr(&match, "}"); - - if (has_drop_ips) { - /* Drop IP traffic to this router. */ - ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 60, - ds_cstr(&match), "drop;", - &op->nbrp->header_); - } - - free(snat_ips); - - /* ARP/NS packets are taken care of per router. The only exception - * is on the l3dgw_port where we might need to use a different - * ETH address. - */ - if (op != op->od->l3dgw_port) { - continue; - } - - struct sset sset_snat_ips = SSET_INITIALIZER(&sset_snat_ips); - for (size_t i = 0; i < op->od->nbr->n_nat; i++) { - struct ovn_nat *nat_entry = &op->od->nat_entries[i]; - const struct nbrec_nat *nat = nat_entry->nb; - - /* Skip entries we failed to parse. */ - if (!nat_entry_is_valid(nat_entry)) { - continue; - } - - struct lport_addresses *ext_addrs = &nat_entry->ext_addrs; - char *ext_addr = (nat_entry_is_v6(nat_entry) - ? ext_addrs->ipv6_addrs[0].addr_s - : ext_addrs->ipv4_addrs[0].addr_s); - if (!strcmp(nat->type, "snat")) { - if (!sset_add(&sset_snat_ips, ext_addr)) { - continue; - } - } - - /* Mac address to use when replying to ARP/NS. */ - const char *mac_s = REG_INPORT_ETH_ADDR; - - /* ARP / ND handling for external IP addresses. - * - * DNAT IP addresses are external IP addresses that need ARP - * handling. */ - - struct eth_addr mac; - - ds_clear(&match); - if (nat->external_mac && - eth_addr_from_string(nat->external_mac, &mac) - && nat->logical_port) { - /* distributed NAT case, use nat->external_mac */ - mac_s = nat->external_mac; - /* Traffic with eth.src = nat->external_mac should only be - * sent from the chassis where nat->logical_port is - * resident, so that upstream MAC learning points to the - * correct chassis. Also need to avoid generation of - * multiple ARP responses from different chassis. */ - ds_put_format(&match, "is_chassis_resident(\"%s\")", - nat->logical_port); - } else { - mac_s = REG_INPORT_ETH_ADDR; - /* Traffic with eth.src = l3dgw_port->lrp_networks.ea_s - * should only be sent from the "redirect-chassis", so that - * upstream MAC learning points to the "redirect-chassis". - * Also need to avoid generation of multiple ARP responses - * from different chassis. */ - if (op->od->l3redirect_port) { - ds_put_format(&match, "is_chassis_resident(%s)", - op->od->l3redirect_port->json_key); - } - } - - /* Respond to ARP/NS requests on the chassis that binds the gw - * port. Drop the ARP/NS requests on other chassis. - */ - if (nat_entry_is_v6(nat_entry)) { - build_lrouter_nd_flow(op->od, op, "nd_na", - ext_addrs->ipv6_addrs[0].addr_s, - ext_addrs->ipv6_addrs[0].sn_addr_s, - mac_s, &match, false, 92, - &nat->header_, lflows); - build_lrouter_nd_flow(op->od, op, "nd_na", - ext_addrs->ipv6_addrs[0].addr_s, - ext_addrs->ipv6_addrs[0].sn_addr_s, - mac_s, NULL, true, 91, - &nat->header_, lflows); - } else { - build_lrouter_arp_flow(op->od, op, - ext_addrs->ipv4_addrs[0].addr_s, - mac_s, &match, false, 92, - &nat->header_, lflows); - build_lrouter_arp_flow(op->od, op, - ext_addrs->ipv4_addrs[0].addr_s, - mac_s, NULL, true, 91, - &nat->header_, lflows); - } - } - sset_destroy(&sset_snat_ips); + build_ipv4_input_flows_for_lrouter_port( + op, lflows, &match, &actions); } /* DHCPv6 reply handling */ @@ -11291,6 +10899,439 @@ build_misc_local_traffic_drop_flows_for_lrouter( ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 0, "1", "next;"); } } + +static void + build_time_exceeded_ipv4_input_flows_for_lrouter_port( + struct ovn_port *op, struct hmap *lflows, + struct ds *match, struct ds *actions) +{ + /* ICMP time exceeded */ + for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { + ds_clear(match); + ds_clear(actions); + + ds_put_format(match, + "inport == %s && ip4 && " + "ip.ttl == {0, 1} && !ip.later_frag", op->json_key); + ds_put_format(actions, + "icmp4 {" + "eth.dst <-> eth.src; " + "icmp4.type = 11; /* Time exceeded */ " + "icmp4.code = 0; /* TTL exceeded in transit */ " + "ip4.dst = ip4.src; " + "ip4.src = %s; " + "ip.ttl = 255; " + "next; };", + op->lrp_networks.ipv4_addrs[i].addr_s); + ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 40, + ds_cstr(match), ds_cstr(actions), + &op->nbrp->header_); + } +} + +static void + build_arp_reply_ipv4_input_flows_for_lrouter_port( + struct ovn_port *op, struct hmap *lflows, + struct ds *match) +{ + /* ARP reply. These flows reply to ARP requests for the router's own + * IP address. */ + for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { + ds_clear(match); + ds_put_format(match, "arp.spa == %s/%u", + op->lrp_networks.ipv4_addrs[i].network_s, + op->lrp_networks.ipv4_addrs[i].plen); + + if (op->od->l3dgw_port && op->od->l3redirect_port && op->peer + && op->peer->od->n_localnet_ports) { + bool add_chassis_resident_check = false; + if (op == op->od->l3dgw_port) { + /* Traffic with eth.src = l3dgw_port->lrp_networks.ea_s + * should only be sent from the "redirect-chassis", so that + * upstream MAC learning points to the "redirect-chassis". + * Also need to avoid generation of multiple ARP responses + * from different chassis. */ + add_chassis_resident_check = true; + } else { + /* Check if the option 'reside-on-redirect-chassis' + * is set to true on the router port. If set to true + * and if peer's logical switch has a localnet port, it + * means the router pipeline for the packets from + * peer's logical switch is be run on the chassis + * hosting the gateway port and it should reply to the + * ARP requests for the router port IPs. + */ + add_chassis_resident_check = smap_get_bool( + &op->nbrp->options, + "reside-on-redirect-chassis", false); + } + + if (add_chassis_resident_check) { + ds_put_format(match, " && is_chassis_resident(%s)", + op->od->l3redirect_port->json_key); + } + } + build_lrouter_arp_flow(op->od, op, + op->lrp_networks.ipv4_addrs[i].addr_s, + REG_INPORT_ETH_ADDR, match, false, 90, + &op->nbrp->header_, lflows); + } +} + +static void + build_snat_handling_ipv4_input_flows_for_lrouter_port( + struct ovn_port *op, struct hmap *lflows, + struct ds *match) +{ + /* A gateway router can have 4 SNAT IP addresses to force DNATed and + * LBed traffic respectively to be SNATed. In addition, there can be + * a number of SNAT rules in the NAT table. */ + struct v46_ip *snat_ips = xmalloc(sizeof *snat_ips + * (op->od->nbr->n_nat + 4)); + size_t n_snat_ips = 0; + struct lport_addresses snat_addrs; + + if (get_force_snat_ip(op->od, "dnat", &snat_addrs)) { + if (snat_addrs.n_ipv4_addrs) { + snat_ips[n_snat_ips].family = AF_INET; + snat_ips[n_snat_ips++].ipv4 = snat_addrs.ipv4_addrs[0].addr; + } + if (snat_addrs.n_ipv6_addrs) { + snat_ips[n_snat_ips].family = AF_INET6; + snat_ips[n_snat_ips++].ipv6 = snat_addrs.ipv6_addrs[0].addr; + } + destroy_lport_addresses(&snat_addrs); + } + + memset(&snat_addrs, 0, sizeof(snat_addrs)); + if (get_force_snat_ip(op->od, "lb", &snat_addrs)) { + if (snat_addrs.n_ipv4_addrs) { + snat_ips[n_snat_ips].family = AF_INET; + snat_ips[n_snat_ips++].ipv4 = snat_addrs.ipv4_addrs[0].addr; + } + if (snat_addrs.n_ipv6_addrs) { + snat_ips[n_snat_ips].family = AF_INET6; + snat_ips[n_snat_ips++].ipv6 = snat_addrs.ipv6_addrs[0].addr; + } + destroy_lport_addresses(&snat_addrs); + } + + for (size_t i = 0; i < op->od->nbr->n_nat; i++) { + struct ovn_nat *nat_entry = &op->od->nat_entries[i]; + const struct nbrec_nat *nat = nat_entry->nb; + + /* Skip entries we failed to parse. */ + if (!nat_entry_is_valid(nat_entry)) { + continue; + } + + if (!strcmp(nat->type, "snat")) { + if (nat_entry_is_v6(nat_entry)) { + struct in6_addr *ipv6 = + &nat_entry->ext_addrs.ipv6_addrs[0].addr; + + snat_ips[n_snat_ips].family = AF_INET6; + snat_ips[n_snat_ips++].ipv6 = *ipv6; + } else { + ovs_be32 ip = nat_entry->ext_addrs.ipv4_addrs[0].addr; + snat_ips[n_snat_ips].family = AF_INET; + snat_ips[n_snat_ips++].ipv4 = ip; + } + } + } + + ds_clear(match); + ds_put_cstr(match, "ip4.dst == {"); + bool has_drop_ips = false; + for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { + bool snat_ip_is_router_ip = false; + for (int j = 0; j < n_snat_ips; j++) { + /* Packets to SNAT IPs should not be dropped. */ + if (snat_ips[j].family == AF_INET + && op->lrp_networks.ipv4_addrs[i].addr + == snat_ips[j].ipv4) { + snat_ip_is_router_ip = true; + break; + } + } + if (snat_ip_is_router_ip) { + continue; + } + ds_put_format(match, "%s, ", + op->lrp_networks.ipv4_addrs[i].addr_s); + has_drop_ips = true; + } + if (has_drop_ips) { + ds_chomp(match, ' '); + ds_chomp(match, ','); + ds_put_cstr(match, "} || ip6.dst == {"); + } else { + ds_clear(match); + ds_put_cstr(match, "ip6.dst == {"); + } + + for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { + bool snat_ip_is_router_ip = false; + for (int j = 0; j < n_snat_ips; j++) { + /* Packets to SNAT IPs should not be dropped. */ + if (snat_ips[j].family == AF_INET6 + && !memcmp(&op->lrp_networks.ipv6_addrs[i].addr, + &snat_ips[j].ipv6, sizeof snat_ips[j].ipv6)) { + snat_ip_is_router_ip = true; + break; + } + } + if (snat_ip_is_router_ip) { + continue; + } + ds_put_format(match, "%s, ", + op->lrp_networks.ipv6_addrs[i].addr_s); + has_drop_ips = true; + } + + ds_chomp(match, ' '); + ds_chomp(match, ','); + ds_put_cstr(match, "}"); + + if (has_drop_ips) { + /* Drop IP traffic to this router. */ + ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 60, + ds_cstr(match), "drop;", + &op->nbrp->header_); + } + + free(snat_ips); + + /* ARP/NS packets are taken care of per router. The only exception + * is on the l3dgw_port where we might need to use a different + * ETH address. + */ + if (op != op->od->l3dgw_port) { + return; + } + + struct sset sset_snat_ips = SSET_INITIALIZER(&sset_snat_ips); + for (size_t i = 0; i < op->od->nbr->n_nat; i++) { + struct ovn_nat *nat_entry = &op->od->nat_entries[i]; + const struct nbrec_nat *nat = nat_entry->nb; + + /* Skip entries we failed to parse. */ + if (!nat_entry_is_valid(nat_entry)) { + continue; + } + + struct lport_addresses *ext_addrs = &nat_entry->ext_addrs; + char *ext_addr = (nat_entry_is_v6(nat_entry) + ? ext_addrs->ipv6_addrs[0].addr_s + : ext_addrs->ipv4_addrs[0].addr_s); + if (!strcmp(nat->type, "snat")) { + if (!sset_add(&sset_snat_ips, ext_addr)) { + continue; + } + } + + /* Mac address to use when replying to ARP/NS. */ + const char *mac_s = REG_INPORT_ETH_ADDR; + + /* ARP / ND handling for external IP addresses. + * + * DNAT IP addresses are external IP addresses that need ARP + * handling. */ + + struct eth_addr mac; + + ds_clear(match); + if (nat->external_mac && + eth_addr_from_string(nat->external_mac, &mac) + && nat->logical_port) { + /* distributed NAT case, use nat->external_mac */ + mac_s = nat->external_mac; + /* Traffic with eth.src = nat->external_mac should only be + * sent from the chassis where nat->logical_port is + * resident, so that upstream MAC learning points to the + * correct chassis. Also need to avoid generation of + * multiple ARP responses from different chassis. */ + ds_put_format(match, "is_chassis_resident(\"%s\")", + nat->logical_port); + } else { + mac_s = REG_INPORT_ETH_ADDR; + /* Traffic with eth.src = l3dgw_port->lrp_networks.ea_s + * should only be sent from the "redirect-chassis", so that + * upstream MAC learning points to the "redirect-chassis". + * Also need to avoid generation of multiple ARP responses + * from different chassis. */ + if (op->od->l3redirect_port) { + ds_put_format(match, "is_chassis_resident(%s)", + op->od->l3redirect_port->json_key); + } + } + + /* Respond to ARP/NS requests on the chassis that binds the gw + * port. Drop the ARP/NS requests on other chassis. + */ + if (nat_entry_is_v6(nat_entry)) { + build_lrouter_nd_flow(op->od, op, "nd_na", + ext_addrs->ipv6_addrs[0].addr_s, + ext_addrs->ipv6_addrs[0].sn_addr_s, + mac_s, match, false, 92, + &nat->header_, lflows); + build_lrouter_nd_flow(op->od, op, "nd_na", + ext_addrs->ipv6_addrs[0].addr_s, + ext_addrs->ipv6_addrs[0].sn_addr_s, + mac_s, NULL, true, 91, + &nat->header_, lflows); + } else { + build_lrouter_arp_flow(op->od, op, + ext_addrs->ipv4_addrs[0].addr_s, + mac_s, match, false, 92, + &nat->header_, lflows); + build_lrouter_arp_flow(op->od, op, + ext_addrs->ipv4_addrs[0].addr_s, + mac_s, NULL, true, 91, + &nat->header_, lflows); + } + } + sset_destroy(&sset_snat_ips); +} + +static void +build_ipv4_input_flows_for_lrouter_port( + struct ovn_port *op, struct hmap *lflows, + struct ds *match, struct ds *actions) +{ + if (!op->nbrp) { + return; + } + + if (op->derived) { + /* No ingress packets are accepted on a chassisredirect + * port, so no need to program flows for that port. */ + return; + } + + 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); + ds_put_cstr(match, " && "REGBIT_EGRESS_LOOPBACK" == 0"); + ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 100, + ds_cstr(match), "drop;", + &op->nbrp->header_); + + /* 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"); + + const char * icmp_actions = "ip4.dst <-> ip4.src; " + "ip.ttl = 255; " + "icmp4.type = 0; " + "flags.loopback = 1; " + "next; "; + ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90, + ds_cstr(match), icmp_actions, + &op->nbrp->header_); + } + + build_time_exceeded_ipv4_input_flows_for_lrouter_port( + op, lflows, match, actions); + + build_arp_reply_ipv4_input_flows_for_lrouter_port( + op, lflows, match); + + + /* A set to hold all load-balancer vips that need ARP responses. */ + struct sset all_ips_v4 = SSET_INITIALIZER(&all_ips_v4); + struct sset all_ips_v6 = SSET_INITIALIZER(&all_ips_v6); + get_router_load_balancer_ips(op->od, &all_ips_v4, &all_ips_v6); + + const char *ip_address; + SSET_FOR_EACH (ip_address, &all_ips_v4) { + ds_clear(match); + if (op == op->od->l3dgw_port) { + ds_put_format(match, "is_chassis_resident(%s)", + op->od->l3redirect_port->json_key); + } + + build_lrouter_arp_flow(op->od, op, + ip_address, REG_INPORT_ETH_ADDR, + match, false, 90, NULL, lflows); + } + + SSET_FOR_EACH (ip_address, &all_ips_v6) { + ds_clear(match); + if (op == op->od->l3dgw_port) { + ds_put_format(match, "is_chassis_resident(%s)", + op->od->l3redirect_port->json_key); + } + + build_lrouter_nd_flow(op->od, op, "nd_na", + ip_address, NULL, REG_INPORT_ETH_ADDR, + match, false, 90, NULL, lflows); + } + + sset_destroy(&all_ips_v4); + sset_destroy(&all_ips_v6); + + if (!smap_get(&op->od->nbr->options, "chassis") + && !op->od->l3dgw_port) { + /* UDP/TCP port unreachable. */ + for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { + ds_clear(match); + ds_put_format(match, + "ip4 && ip4.dst == %s && !ip.later_frag && udp", + op->lrp_networks.ipv4_addrs[i].addr_s); + const char *action = "icmp4 {" + "eth.dst <-> eth.src; " + "ip4.dst <-> ip4.src; " + "ip.ttl = 255; " + "icmp4.type = 3; " + "icmp4.code = 3; " + "next; };"; + ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, + 80, ds_cstr(match), action, + &op->nbrp->header_); + + ds_clear(match); + ds_put_format(match, + "ip4 && ip4.dst == %s && !ip.later_frag && tcp", + op->lrp_networks.ipv4_addrs[i].addr_s); + action = "tcp_reset {" + "eth.dst <-> eth.src; " + "ip4.dst <-> ip4.src; " + "next; };"; + ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, + 80, ds_cstr(match), action, + &op->nbrp->header_); + + ds_clear(match); + ds_put_format(match, + "ip4 && ip4.dst == %s && !ip.later_frag", + op->lrp_networks.ipv4_addrs[i].addr_s); + action = "icmp4 {" + "eth.dst <-> eth.src; " + "ip4.dst <-> ip4.src; " + "ip.ttl = 255; " + "icmp4.type = 3; " + "icmp4.code = 2; " + "next; };"; + ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, + 70, ds_cstr(match), action, + &op->nbrp->header_); + } + } + build_snat_handling_ipv4_input_flows_for_lrouter_port( + op, lflows, match); +} + /* Updates the Logical_Flow and Multicast_Group tables in the OVN_SB database, * constructing their contents based on the OVN_NB database. */ static void From patchwork Wed Sep 16 17:12:11 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Anton Ivanov X-Patchwork-Id: 1365729 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=140.211.166.136; helo=silver.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=cambridgegreys.com Received: from silver.osuosl.org (smtp3.osuosl.org [140.211.166.136]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 4BsJVR73Zqz9sVw for ; Thu, 17 Sep 2020 10:58:07 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by silver.osuosl.org (Postfix) with ESMTP id 3DCE42E10D; Wed, 16 Sep 2020 17:12:45 +0000 (UTC) X-Virus-Scanned: amavisd-new at osuosl.org Received: from silver.osuosl.org ([127.0.0.1]) by localhost (.osuosl.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id ZMS5fHDTRfDK; Wed, 16 Sep 2020 17:12:33 +0000 (UTC) Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56]) by silver.osuosl.org (Postfix) with ESMTP id 585D52E0D7; Wed, 16 Sep 2020 17:12:27 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id 2864BC0864; Wed, 16 Sep 2020 17:12:27 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from whitealder.osuosl.org (smtp1.osuosl.org [140.211.166.138]) by lists.linuxfoundation.org (Postfix) with ESMTP id 4ABAAC0888 for ; Wed, 16 Sep 2020 17:12:26 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by whitealder.osuosl.org (Postfix) with ESMTP id 37F4C87379 for ; Wed, 16 Sep 2020 17:12:26 +0000 (UTC) X-Virus-Scanned: amavisd-new at osuosl.org Received: from whitealder.osuosl.org ([127.0.0.1]) by localhost (.osuosl.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id pZuiqmC63XCx for ; Wed, 16 Sep 2020 17:12:25 +0000 (UTC) X-Greylist: from auto-whitelisted by SQLgrey-1.7.6 Received: from www.kot-begemot.co.uk (ivanoab7.miniserver.com [37.128.132.42]) by whitealder.osuosl.org (Postfix) with ESMTPS id 8146B87366 for ; Wed, 16 Sep 2020 17:12:25 +0000 (UTC) Received: from tun252.jain.kot-begemot.co.uk ([192.168.18.6] helo=jain.kot-begemot.co.uk) by www.kot-begemot.co.uk with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1kIaym-0004cY-4W; Wed, 16 Sep 2020 17:12:24 +0000 Received: from jain.kot-begemot.co.uk ([192.168.3.3]) by jain.kot-begemot.co.uk with esmtp (Exim 4.92) (envelope-from ) id 1kIayi-0001Ii-NB; Wed, 16 Sep 2020 18:12:22 +0100 From: anton.ivanov@cambridgegreys.com To: dev@openvswitch.org Date: Wed, 16 Sep 2020 18:12:11 +0100 Message-Id: <20200916171213.3472-4-anton.ivanov@cambridgegreys.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20200916171213.3472-1-anton.ivanov@cambridgegreys.com> References: <20200916171213.3472-1-anton.ivanov@cambridgegreys.com> MIME-Version: 1.0 X-Clacks-Overhead: GNU Terry Pratchett Cc: i.maximets@ovn.org, Anton Ivanov Subject: [ovs-dev] [PATCH ovn 3/5] ovn-northd: move dhcpv6 reply in build_lrouter_flows() to a function X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: ovs-dev-bounces@openvswitch.org Sender: "dev" From: Anton Ivanov Signed-off-by: Anton Ivanov --- northd/ovn-northd.c | 45 +++++++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c index c3149d547..e81e7722a 100644 --- a/northd/ovn-northd.c +++ b/northd/ovn-northd.c @@ -8700,6 +8700,12 @@ build_ipv4_input_flows_for_lrouter_port( struct ovn_port *op, struct hmap *lflows, struct ds *match, struct ds *actions); +/* DHCPv6 reply handling */ +static void +build_dhcpv6_reply_flows_for_lrouter_port( + struct ovn_port *op, struct hmap *lflows, + struct ds *match); + static void build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, struct hmap *lflows, struct shash *meter_groups, @@ -8740,25 +8746,9 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, op, lflows, &match, &actions); } - /* DHCPv6 reply handling */ HMAP_FOR_EACH (op, key_node, ports) { - if (!op->nbrp) { - continue; - } - - if (op->derived) { - continue; - } - - for (size_t i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { - ds_clear(&match); - ds_put_format(&match, "ip6.dst == %s && udp.src == 547 &&" - " udp.dst == 546", - op->lrp_networks.ipv6_addrs[i].addr_s); - ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 100, - ds_cstr(&match), - "reg0 = 0; handle_dhcpv6_reply;"); - } + build_dhcpv6_reply_flows_for_lrouter_port( + op, lflows, &match); } /* Logical router ingress table 1: IP Input for IPv6. */ @@ -11332,6 +11322,25 @@ build_ipv4_input_flows_for_lrouter_port( op, lflows, match); } +static void +build_dhcpv6_reply_flows_for_lrouter_port( + struct ovn_port *op, struct hmap *lflows, + struct ds *match) +{ + if (op->nbrp && (!op->derived)) { + for (size_t i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { + ds_clear(match); + ds_put_format(match, "ip6.dst == %s && udp.src == 547 &&" + " udp.dst == 546", + op->lrp_networks.ipv6_addrs[i].addr_s); + ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 100, + ds_cstr(match), + "reg0 = 0; handle_dhcpv6_reply;"); + } + } + +} + /* Updates the Logical_Flow and Multicast_Group tables in the OVN_SB database, * constructing their contents based on the OVN_NB database. */ static void From patchwork Wed Sep 16 17:12:12 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Anton Ivanov X-Patchwork-Id: 1365504 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=140.211.166.133; helo=hemlock.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=cambridgegreys.com Received: from hemlock.osuosl.org (smtp2.osuosl.org [140.211.166.133]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 4BsHhK1DNYz9sVq for ; Thu, 17 Sep 2020 10:21:35 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by hemlock.osuosl.org (Postfix) with ESMTP id 2D3C28746C; Wed, 16 Sep 2020 17:12:38 +0000 (UTC) X-Virus-Scanned: amavisd-new at osuosl.org Received: from hemlock.osuosl.org ([127.0.0.1]) by localhost (.osuosl.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id odcOlm4nOtbA; Wed, 16 Sep 2020 17:12:34 +0000 (UTC) Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56]) by hemlock.osuosl.org (Postfix) with ESMTP id 76D8687433; Wed, 16 Sep 2020 17:12:31 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id 5F67DC0888; Wed, 16 Sep 2020 17:12:31 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from hemlock.osuosl.org (smtp2.osuosl.org [140.211.166.133]) by lists.linuxfoundation.org (Postfix) with ESMTP id 3E40FC0051 for ; Wed, 16 Sep 2020 17:12:29 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by hemlock.osuosl.org (Postfix) with ESMTP id 2CA8A87418 for ; Wed, 16 Sep 2020 17:12:29 +0000 (UTC) X-Virus-Scanned: amavisd-new at osuosl.org Received: from hemlock.osuosl.org ([127.0.0.1]) by localhost (.osuosl.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id DcI6S7hG+Qx7 for ; Wed, 16 Sep 2020 17:12:27 +0000 (UTC) X-Greylist: from auto-whitelisted by SQLgrey-1.7.6 Received: from www.kot-begemot.co.uk (ivanoab7.miniserver.com [37.128.132.42]) by hemlock.osuosl.org (Postfix) with ESMTPS id 6C9788741E for ; Wed, 16 Sep 2020 17:12:27 +0000 (UTC) Received: from tun252.jain.kot-begemot.co.uk ([192.168.18.6] helo=jain.kot-begemot.co.uk) by www.kot-begemot.co.uk with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1kIayn-0004ce-El; Wed, 16 Sep 2020 17:12:25 +0000 Received: from jain.kot-begemot.co.uk ([192.168.3.3]) by jain.kot-begemot.co.uk with esmtp (Exim 4.92) (envelope-from ) id 1kIayk-0001Ii-EA; Wed, 16 Sep 2020 18:12:24 +0100 From: anton.ivanov@cambridgegreys.com To: dev@openvswitch.org Date: Wed, 16 Sep 2020 18:12:12 +0100 Message-Id: <20200916171213.3472-5-anton.ivanov@cambridgegreys.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20200916171213.3472-1-anton.ivanov@cambridgegreys.com> References: <20200916171213.3472-1-anton.ivanov@cambridgegreys.com> MIME-Version: 1.0 X-Clacks-Overhead: GNU Terry Pratchett Cc: i.maximets@ovn.org, Anton Ivanov Subject: [ovs-dev] [PATCH ovn 4/5] ovn-northd: move ipv6 input flows out of build_lrouter_flows() X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: ovs-dev-bounces@openvswitch.org Sender: "dev" From: Anton Ivanov Signed-off-by: Anton Ivanov --- northd/ovn-northd.c | 271 +++++++++++++++++++++++--------------------- 1 file changed, 140 insertions(+), 131 deletions(-) diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c index e81e7722a..6687fe631 100644 --- a/northd/ovn-northd.c +++ b/northd/ovn-northd.c @@ -8706,6 +8706,12 @@ build_dhcpv6_reply_flows_for_lrouter_port( struct ovn_port *op, struct hmap *lflows, struct ds *match); +/* Logical router ingress table 1: IP Input for IPv6. */ +static void +build_ipv6_input_flows_for_lrouter_port( + struct ovn_port *op, struct hmap *lflows, + struct ds *match, struct ds *actions); + static void build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, struct hmap *lflows, struct shash *meter_groups, @@ -8751,138 +8757,9 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, op, lflows, &match); } - /* Logical router ingress table 1: IP Input for IPv6. */ HMAP_FOR_EACH (op, key_node, ports) { - if (!op->nbrp) { - continue; - } - - if (op->derived) { - /* No ingress packets are accepted on a chassisredirect - * port, so no need to program flows for that port. */ - continue; - } - - if (op->lrp_networks.n_ipv6_addrs) { - /* 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"); - - const char *lrp_actions = - "ip6.dst <-> ip6.src; " - "ip.ttl = 255; " - "icmp6.type = 129; " - "flags.loopback = 1; " - "next; "; - ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90, - ds_cstr(&match), lrp_actions, - &op->nbrp->header_); - } - - /* 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); - if (op->od->l3dgw_port && op == op->od->l3dgw_port - && op->od->l3redirect_port) { - /* Traffic with eth.src = l3dgw_port->lrp_networks.ea_s - * should only be sent from the "redirect-chassis", so that - * upstream MAC learning points to the "redirect-chassis". - * Also need to avoid generation of multiple ND replies - * from different chassis. */ - ds_put_format(&match, "is_chassis_resident(%s)", - op->od->l3redirect_port->json_key); - } - - build_lrouter_nd_flow(op->od, op, "nd_na_router", - op->lrp_networks.ipv6_addrs[i].addr_s, - op->lrp_networks.ipv6_addrs[i].sn_addr_s, - REG_INPORT_ETH_ADDR, &match, false, 90, - &op->nbrp->header_, lflows); - } - - /* UDP/TCP port unreachable */ - if (!smap_get(&op->od->nbr->options, "chassis") - && !op->od->l3dgw_port) { - for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { - ds_clear(&match); - ds_put_format(&match, - "ip6 && ip6.dst == %s && !ip.later_frag && tcp", - op->lrp_networks.ipv6_addrs[i].addr_s); - const char *action = "tcp_reset {" - "eth.dst <-> eth.src; " - "ip6.dst <-> ip6.src; " - "next; };"; - ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, - 80, ds_cstr(&match), action, - &op->nbrp->header_); - - ds_clear(&match); - ds_put_format(&match, - "ip6 && ip6.dst == %s && !ip.later_frag && udp", - op->lrp_networks.ipv6_addrs[i].addr_s); - action = "icmp6 {" - "eth.dst <-> eth.src; " - "ip6.dst <-> ip6.src; " - "ip.ttl = 255; " - "icmp6.type = 1; " - "icmp6.code = 4; " - "next; };"; - ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, - 80, ds_cstr(&match), action, - &op->nbrp->header_); - - ds_clear(&match); - ds_put_format(&match, - "ip6 && ip6.dst == %s && !ip.later_frag", - op->lrp_networks.ipv6_addrs[i].addr_s); - action = "icmp6 {" - "eth.dst <-> eth.src; " - "ip6.dst <-> ip6.src; " - "ip.ttl = 255; " - "icmp6.type = 1; " - "icmp6.code = 3; " - "next; };"; - ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, - 70, ds_cstr(&match), action, - &op->nbrp->header_); - } - } - - /* ICMPv6 time exceeded */ - for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { - /* skip link-local address */ - if (in6_is_lla(&op->lrp_networks.ipv6_addrs[i].network)) { - continue; - } - - ds_clear(&match); - ds_clear(&actions); - - ds_put_format(&match, - "inport == %s && ip6 && " - "ip6.src == %s/%d && " - "ip.ttl == {0, 1} && !ip.later_frag", - op->json_key, - op->lrp_networks.ipv6_addrs[i].network_s, - op->lrp_networks.ipv6_addrs[i].plen); - ds_put_format(&actions, - "icmp6 {" - "eth.dst <-> eth.src; " - "ip6.dst = ip6.src; " - "ip6.src = %s; " - "ip.ttl = 255; " - "icmp6.type = 3; /* Time exceeded */ " - "icmp6.code = 0; /* TTL exceeded in transit */ " - "next; };", - op->lrp_networks.ipv6_addrs[i].addr_s); - ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 40, - ds_cstr(&match), ds_cstr(&actions), - &op->nbrp->header_); - } + build_ipv6_input_flows_for_lrouter_port( + op, lflows, &match, &actions); } /* NAT, Defrag and load balancing. */ @@ -11341,6 +11218,138 @@ build_dhcpv6_reply_flows_for_lrouter_port( } +static void +build_ipv6_input_flows_for_lrouter_port( + struct ovn_port *op, struct hmap *lflows, + struct ds *match, struct ds *actions) +{ + if (op->nbrp && (!op->derived)) { + /* No ingress packets are accepted on a chassisredirect + * port, so no need to program flows for that port. */ + if (op->lrp_networks.n_ipv6_addrs) { + /* 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"); + + const char *lrp_actions = + "ip6.dst <-> ip6.src; " + "ip.ttl = 255; " + "icmp6.type = 129; " + "flags.loopback = 1; " + "next; "; + ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90, + ds_cstr(match), lrp_actions, + &op->nbrp->header_); + } + + /* 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); + if (op->od->l3dgw_port && op == op->od->l3dgw_port + && op->od->l3redirect_port) { + /* Traffic with eth.src = l3dgw_port->lrp_networks.ea_s + * should only be sent from the "redirect-chassis", so that + * upstream MAC learning points to the "redirect-chassis". + * Also need to avoid generation of multiple ND replies + * from different chassis. */ + ds_put_format(match, "is_chassis_resident(%s)", + op->od->l3redirect_port->json_key); + } + + build_lrouter_nd_flow(op->od, op, "nd_na_router", + op->lrp_networks.ipv6_addrs[i].addr_s, + op->lrp_networks.ipv6_addrs[i].sn_addr_s, + REG_INPORT_ETH_ADDR, match, false, 90, + &op->nbrp->header_, lflows); + } + + /* UDP/TCP port unreachable */ + if (!smap_get(&op->od->nbr->options, "chassis") + && !op->od->l3dgw_port) { + for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { + ds_clear(match); + ds_put_format(match, + "ip6 && ip6.dst == %s && !ip.later_frag && tcp", + op->lrp_networks.ipv6_addrs[i].addr_s); + const char *action = "tcp_reset {" + "eth.dst <-> eth.src; " + "ip6.dst <-> ip6.src; " + "next; };"; + ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, + 80, ds_cstr(match), action, + &op->nbrp->header_); + + ds_clear(match); + ds_put_format(match, + "ip6 && ip6.dst == %s && !ip.later_frag && udp", + op->lrp_networks.ipv6_addrs[i].addr_s); + action = "icmp6 {" + "eth.dst <-> eth.src; " + "ip6.dst <-> ip6.src; " + "ip.ttl = 255; " + "icmp6.type = 1; " + "icmp6.code = 4; " + "next; };"; + ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, + 80, ds_cstr(match), action, + &op->nbrp->header_); + + ds_clear(match); + ds_put_format(match, + "ip6 && ip6.dst == %s && !ip.later_frag", + op->lrp_networks.ipv6_addrs[i].addr_s); + action = "icmp6 {" + "eth.dst <-> eth.src; " + "ip6.dst <-> ip6.src; " + "ip.ttl = 255; " + "icmp6.type = 1; " + "icmp6.code = 3; " + "next; };"; + ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, + 70, ds_cstr(match), action, + &op->nbrp->header_); + } + } + + /* ICMPv6 time exceeded */ + for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { + /* skip link-local address */ + if (in6_is_lla(&op->lrp_networks.ipv6_addrs[i].network)) { + continue; + } + + ds_clear(match); + ds_clear(actions); + + ds_put_format(match, + "inport == %s && ip6 && " + "ip6.src == %s/%d && " + "ip.ttl == {0, 1} && !ip.later_frag", + op->json_key, + op->lrp_networks.ipv6_addrs[i].network_s, + op->lrp_networks.ipv6_addrs[i].plen); + ds_put_format(actions, + "icmp6 {" + "eth.dst <-> eth.src; " + "ip6.dst = ip6.src; " + "ip6.src = %s; " + "ip.ttl = 255; " + "icmp6.type = 3; /* Time exceeded */ " + "icmp6.code = 0; /* TTL exceeded in transit */ " + "next; };", + op->lrp_networks.ipv6_addrs[i].addr_s); + ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 40, + ds_cstr(match), ds_cstr(actions), + &op->nbrp->header_); + } + } + +} + /* Updates the Logical_Flow and Multicast_Group tables in the OVN_SB database, * constructing their contents based on the OVN_NB database. */ static void From patchwork Wed Sep 16 17:12:13 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Anton Ivanov X-Patchwork-Id: 1365732 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=140.211.166.138; helo=whitealder.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=cambridgegreys.com Received: from whitealder.osuosl.org (smtp1.osuosl.org [140.211.166.138]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 4BsJVg4lKXz9sWY for ; Thu, 17 Sep 2020 10:58:19 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by whitealder.osuosl.org (Postfix) with ESMTP id A862987365; Wed, 16 Sep 2020 17:13:33 +0000 (UTC) X-Virus-Scanned: amavisd-new at osuosl.org Received: from whitealder.osuosl.org ([127.0.0.1]) by localhost (.osuosl.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id dImvx25b4aK8; Wed, 16 Sep 2020 17:13:13 +0000 (UTC) Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56]) by whitealder.osuosl.org (Postfix) with ESMTP id 2CD7A8738A; Wed, 16 Sep 2020 17:12:56 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id 1417AC0893; Wed, 16 Sep 2020 17:12:56 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from whitealder.osuosl.org (smtp1.osuosl.org [140.211.166.138]) by lists.linuxfoundation.org (Postfix) with ESMTP id F1B4AC0051 for ; Wed, 16 Sep 2020 17:12:54 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by whitealder.osuosl.org (Postfix) with ESMTP id DB09487390 for ; Wed, 16 Sep 2020 17:12:54 +0000 (UTC) X-Virus-Scanned: amavisd-new at osuosl.org Received: from whitealder.osuosl.org ([127.0.0.1]) by localhost (.osuosl.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id jdbGqntpxO71 for ; Wed, 16 Sep 2020 17:12:34 +0000 (UTC) X-Greylist: from auto-whitelisted by SQLgrey-1.7.6 Received: from www.kot-begemot.co.uk (ivanoab7.miniserver.com [37.128.132.42]) by whitealder.osuosl.org (Postfix) with ESMTPS id 5AB478736F for ; Wed, 16 Sep 2020 17:12:31 +0000 (UTC) Received: from tun252.jain.kot-begemot.co.uk ([192.168.18.6] helo=jain.kot-begemot.co.uk) by www.kot-begemot.co.uk with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1kIayq-0004cl-QD; Wed, 16 Sep 2020 17:12:30 +0000 Received: from jain.kot-begemot.co.uk ([192.168.3.3]) by jain.kot-begemot.co.uk with esmtp (Exim 4.92) (envelope-from ) id 1kIaym-0001Ii-9W; Wed, 16 Sep 2020 18:12:26 +0100 From: anton.ivanov@cambridgegreys.com To: dev@openvswitch.org Date: Wed, 16 Sep 2020 18:12:13 +0100 Message-Id: <20200916171213.3472-6-anton.ivanov@cambridgegreys.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20200916171213.3472-1-anton.ivanov@cambridgegreys.com> References: <20200916171213.3472-1-anton.ivanov@cambridgegreys.com> MIME-Version: 1.0 X-Clacks-Overhead: GNU Terry Pratchett Cc: i.maximets@ovn.org, Anton Ivanov Subject: [ovs-dev] [PATCH ovn 5/5] ovn-northd: move NAT, DEFRAG and LB to a helper function X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: ovs-dev-bounces@openvswitch.org Sender: "dev" From: Anton Ivanov This moves NAT, DEFRAG and load balancing out of build_lrouter_flows() and into a helper function. Signed-off-by: Anton Ivanov --- northd/ovn-northd.c | 1327 ++++++++++++++++++++++--------------------- 1 file changed, 672 insertions(+), 655 deletions(-) diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c index 6687fe631..0f2bed545 100644 --- a/northd/ovn-northd.c +++ b/northd/ovn-northd.c @@ -8712,6 +8712,13 @@ build_ipv6_input_flows_for_lrouter_port( struct ovn_port *op, struct hmap *lflows, struct ds *match, struct ds *actions); +/* NAT, Defrag and load balancing. */ +static void +build_nat_defrag_and_lb_flows_for_lrouter( + struct ovn_datapath *od, struct hmap *lflows, + struct shash *meter_groups, struct hmap *lbs, + struct ds *match, struct ds *actions); + static void build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, struct hmap *lflows, struct shash *meter_groups, @@ -8762,662 +8769,9 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, op, lflows, &match, &actions); } - /* NAT, Defrag and load balancing. */ HMAP_FOR_EACH (od, key_node, datapaths) { - if (!od->nbr) { - continue; - } - - /* Packets are allowed by default. */ - ovn_lflow_add(lflows, od, S_ROUTER_IN_DEFRAG, 0, "1", "next;"); - ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 0, "1", "next;"); - ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 0, "1", "next;"); - ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 0, "1", "next;"); - ovn_lflow_add(lflows, od, S_ROUTER_OUT_UNDNAT, 0, "1", "next;"); - ovn_lflow_add(lflows, od, S_ROUTER_OUT_EGR_LOOP, 0, "1", "next;"); - ovn_lflow_add(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 0, "1", "next;"); - - /* Send the IPv6 NS packets to next table. When ovn-controller - * generates IPv6 NS (for the action - nd_ns{}), the injected - * packet would go through conntrack - which is not required. */ - ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 120, "nd_ns", "next;"); - - /* NAT rules are only valid on Gateway routers and routers with - * l3dgw_port (router has a port with "redirect-chassis" - * specified). */ - if (!smap_get(&od->nbr->options, "chassis") && !od->l3dgw_port) { - continue; - } - - struct sset nat_entries = SSET_INITIALIZER(&nat_entries); - - struct lport_addresses dnat_force_snat_addrs; - struct lport_addresses lb_force_snat_addrs; - bool dnat_force_snat_ip = get_force_snat_ip(od, "dnat", - &dnat_force_snat_addrs); - bool lb_force_snat_ip = get_force_snat_ip(od, "lb", - &lb_force_snat_addrs); - - for (int i = 0; i < od->nbr->n_nat; i++) { - const struct nbrec_nat *nat; - - nat = od->nbr->nat[i]; - - ovs_be32 ip, mask; - struct in6_addr ipv6, mask_v6, v6_exact = IN6ADDR_EXACT_INIT; - bool is_v6 = false; - bool stateless = lrouter_nat_is_stateless(nat); - struct nbrec_address_set *allowed_ext_ips = - nat->allowed_ext_ips; - struct nbrec_address_set *exempted_ext_ips = - nat->exempted_ext_ips; - - if (allowed_ext_ips && exempted_ext_ips) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); - VLOG_WARN_RL(&rl, "NAT rule: "UUID_FMT" not applied, since" - "both allowed and exempt external ips set", - UUID_ARGS(&(nat->header_.uuid))); - continue; - } - - char *error = ip_parse_masked(nat->external_ip, &ip, &mask); - if (error || mask != OVS_BE32_MAX) { - free(error); - error = ipv6_parse_masked(nat->external_ip, &ipv6, &mask_v6); - if (error || memcmp(&mask_v6, &v6_exact, sizeof(mask_v6))) { - /* Invalid for both IPv4 and IPv6 */ - static struct vlog_rate_limit rl = - VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "bad external ip %s for nat", - nat->external_ip); - free(error); - continue; - } - /* It was an invalid IPv4 address, but valid IPv6. - * Treat the rest of the handling of this NAT rule - * as IPv6. */ - is_v6 = true; - } - - /* Check the validity of nat->logical_ip. 'logical_ip' can - * be a subnet when the type is "snat". */ - if (is_v6) { - error = ipv6_parse_masked(nat->logical_ip, &ipv6, &mask_v6); - } else { - error = ip_parse_masked(nat->logical_ip, &ip, &mask); - } - if (!strcmp(nat->type, "snat")) { - if (error) { - /* Invalid for both IPv4 and IPv6 */ - static struct vlog_rate_limit rl = - VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "bad ip network or ip %s for snat " - "in router "UUID_FMT"", - nat->logical_ip, UUID_ARGS(&od->key)); - free(error); - continue; - } - } else { - if (error || (!is_v6 && mask != OVS_BE32_MAX) - || (is_v6 && memcmp(&mask_v6, &v6_exact, - sizeof mask_v6))) { - /* Invalid for both IPv4 and IPv6 */ - static struct vlog_rate_limit rl = - VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "bad ip %s for dnat in router " - ""UUID_FMT"", nat->logical_ip, UUID_ARGS(&od->key)); - free(error); - continue; - } - } - - /* For distributed router NAT, determine whether this NAT rule - * satisfies the conditions for distributed NAT processing. */ - bool distributed = false; - struct eth_addr mac; - if (od->l3dgw_port && !strcmp(nat->type, "dnat_and_snat") && - nat->logical_port && nat->external_mac) { - if (eth_addr_from_string(nat->external_mac, &mac)) { - distributed = true; - } else { - static struct vlog_rate_limit rl = - VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "bad mac %s for dnat in router " - ""UUID_FMT"", nat->external_mac, UUID_ARGS(&od->key)); - continue; - } - } - - /* Ingress UNSNAT table: It is for already established connections' - * reverse traffic. i.e., SNAT has already been done in egress - * pipeline and now the packet has entered the ingress pipeline as - * part of a reply. We undo the SNAT here. - * - * Undoing SNAT has to happen before DNAT processing. This is - * because when the packet was DNATed in ingress pipeline, it did - * not know about the possibility of eventual additional SNAT in - * egress pipeline. */ - if (!strcmp(nat->type, "snat") - || !strcmp(nat->type, "dnat_and_snat")) { - if (!od->l3dgw_port) { - /* Gateway router. */ - ds_clear(&match); - ds_clear(&actions); - ds_put_format(&match, "ip && ip%s.dst == %s", - is_v6 ? "6" : "4", - nat->external_ip); - if (!strcmp(nat->type, "dnat_and_snat") && stateless) { - ds_put_format(&actions, "ip%s.dst=%s; next;", - is_v6 ? "6" : "4", nat->logical_ip); - } else { - ds_put_cstr(&actions, "ct_snat;"); - } - - ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_UNSNAT, - 90, ds_cstr(&match), - ds_cstr(&actions), - &nat->header_); - } else { - /* Distributed router. */ - - /* Traffic received on l3dgw_port is subject to NAT. */ - ds_clear(&match); - ds_clear(&actions); - ds_put_format(&match, "ip && ip%s.dst == %s" - " && inport == %s", - is_v6 ? "6" : "4", - nat->external_ip, - od->l3dgw_port->json_key); - if (!distributed && od->l3redirect_port) { - /* Flows for NAT rules that are centralized are only - * programmed on the "redirect-chassis". */ - ds_put_format(&match, " && is_chassis_resident(%s)", - od->l3redirect_port->json_key); - } - - if (!strcmp(nat->type, "dnat_and_snat") && stateless) { - ds_put_format(&actions, "ip%s.dst=%s; next;", - is_v6 ? "6" : "4", nat->logical_ip); - } else { - ds_put_cstr(&actions, "ct_snat;"); - } - - ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_UNSNAT, - 100, - ds_cstr(&match), ds_cstr(&actions), - &nat->header_); - } - } - - /* Ingress DNAT table: Packets enter the pipeline with destination - * IP address that needs to be DNATted from a external IP address - * to a logical IP address. */ - if (!strcmp(nat->type, "dnat") - || !strcmp(nat->type, "dnat_and_snat")) { - if (!od->l3dgw_port) { - /* Gateway router. */ - /* Packet when it goes from the initiator to destination. - * We need to set flags.loopback because the router can - * send the packet back through the same interface. */ - ds_clear(&match); - ds_put_format(&match, "ip && ip%s.dst == %s", - is_v6 ? "6" : "4", - nat->external_ip); - ds_clear(&actions); - if (allowed_ext_ips || exempted_ext_ips) { - lrouter_nat_add_ext_ip_match(od, lflows, &match, nat, - is_v6, true, mask); - } - - if (dnat_force_snat_ip) { - /* Indicate to the future tables that a DNAT has taken - * place and a force SNAT needs to be done in the - * Egress SNAT table. */ - ds_put_format(&actions, - "flags.force_snat_for_dnat = 1; "); - } - - if (!strcmp(nat->type, "dnat_and_snat") && stateless) { - ds_put_format(&actions, "flags.loopback = 1; " - "ip%s.dst=%s; next;", - is_v6 ? "6" : "4", nat->logical_ip); - } else { - ds_put_format(&actions, "flags.loopback = 1; " - "ct_dnat(%s", nat->logical_ip); - - if (nat->external_port_range[0]) { - ds_put_format(&actions, ",%s", - nat->external_port_range); - } - ds_put_format(&actions, ");"); - } - - ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, 100, - ds_cstr(&match), ds_cstr(&actions), - &nat->header_); - } else { - /* Distributed router. */ - - /* Traffic received on l3dgw_port is subject to NAT. */ - ds_clear(&match); - ds_put_format(&match, "ip && ip%s.dst == %s" - " && inport == %s", - is_v6 ? "6" : "4", - nat->external_ip, - od->l3dgw_port->json_key); - if (!distributed && od->l3redirect_port) { - /* Flows for NAT rules that are centralized are only - * programmed on the "redirect-chassis". */ - ds_put_format(&match, " && is_chassis_resident(%s)", - od->l3redirect_port->json_key); - } - ds_clear(&actions); - if (allowed_ext_ips || exempted_ext_ips) { - lrouter_nat_add_ext_ip_match(od, lflows, &match, nat, - is_v6, true, mask); - } - - if (!strcmp(nat->type, "dnat_and_snat") && stateless) { - ds_put_format(&actions, "ip%s.dst=%s; next;", - is_v6 ? "6" : "4", nat->logical_ip); - } else { - ds_put_format(&actions, "ct_dnat(%s", nat->logical_ip); - if (nat->external_port_range[0]) { - ds_put_format(&actions, ",%s", - nat->external_port_range); - } - ds_put_format(&actions, ");"); - } - - ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, 100, - ds_cstr(&match), ds_cstr(&actions), - &nat->header_); - } - } - - /* ARP resolve for NAT IPs. */ - if (od->l3dgw_port) { - if (!strcmp(nat->type, "snat")) { - ds_clear(&match); - ds_put_format( - &match, "inport == %s && %s == %s", - od->l3dgw_port->json_key, - is_v6 ? "ip6.src" : "ip4.src", nat->external_ip); - ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_IP_INPUT, - 120, ds_cstr(&match), "next;", - &nat->header_); - } - - if (!sset_contains(&nat_entries, nat->external_ip)) { - ds_clear(&match); - ds_put_format( - &match, "outport == %s && %s == %s", - od->l3dgw_port->json_key, - is_v6 ? REG_NEXT_HOP_IPV6 : REG_NEXT_HOP_IPV4, - nat->external_ip); - ds_clear(&actions); - ds_put_format( - &actions, "eth.dst = %s; next;", - distributed ? nat->external_mac : - od->l3dgw_port->lrp_networks.ea_s); - ovn_lflow_add_with_hint(lflows, od, - S_ROUTER_IN_ARP_RESOLVE, - 100, ds_cstr(&match), - ds_cstr(&actions), - &nat->header_); - sset_add(&nat_entries, nat->external_ip); - } - } else { - /* Add the NAT external_ip to the nat_entries even for - * gateway routers. This is required for adding load balancer - * flows.*/ - sset_add(&nat_entries, nat->external_ip); - } - - /* Egress UNDNAT table: It is for already established connections' - * reverse traffic. i.e., DNAT has already been done in ingress - * pipeline and now the packet has entered the egress pipeline as - * part of a reply. We undo the DNAT here. - * - * Note that this only applies for NAT on a distributed router. - * Undo DNAT on a gateway router is done in the ingress DNAT - * pipeline stage. */ - if (od->l3dgw_port && (!strcmp(nat->type, "dnat") - || !strcmp(nat->type, "dnat_and_snat"))) { - ds_clear(&match); - ds_put_format(&match, "ip && ip%s.src == %s" - " && outport == %s", - is_v6 ? "6" : "4", - nat->logical_ip, - od->l3dgw_port->json_key); - if (!distributed && od->l3redirect_port) { - /* Flows for NAT rules that are centralized are only - * programmed on the "redirect-chassis". */ - ds_put_format(&match, " && is_chassis_resident(%s)", - od->l3redirect_port->json_key); - } - ds_clear(&actions); - if (distributed) { - ds_put_format(&actions, "eth.src = "ETH_ADDR_FMT"; ", - ETH_ADDR_ARGS(mac)); - } - - if (!strcmp(nat->type, "dnat_and_snat") && stateless) { - ds_put_format(&actions, "ip%s.src=%s; next;", - is_v6 ? "6" : "4", nat->external_ip); - } else { - ds_put_format(&actions, "ct_dnat;"); - } - - ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_UNDNAT, 100, - ds_cstr(&match), ds_cstr(&actions), - &nat->header_); - } - - /* Egress SNAT table: Packets enter the egress pipeline with - * source ip address that needs to be SNATted to a external ip - * address. */ - if (!strcmp(nat->type, "snat") - || !strcmp(nat->type, "dnat_and_snat")) { - if (!od->l3dgw_port) { - /* Gateway router. */ - ds_clear(&match); - ds_put_format(&match, "ip && ip%s.src == %s", - is_v6 ? "6" : "4", - nat->logical_ip); - ds_clear(&actions); - - if (allowed_ext_ips || exempted_ext_ips) { - lrouter_nat_add_ext_ip_match(od, lflows, &match, nat, - is_v6, false, mask); - } - - if (!strcmp(nat->type, "dnat_and_snat") && stateless) { - ds_put_format(&actions, "ip%s.src=%s; next;", - is_v6 ? "6" : "4", nat->external_ip); - } else { - ds_put_format(&actions, "ct_snat(%s", - nat->external_ip); - - if (nat->external_port_range[0]) { - ds_put_format(&actions, ",%s", - nat->external_port_range); - } - ds_put_format(&actions, ");"); - } - - /* The priority here is calculated such that the - * nat->logical_ip with the longest mask gets a higher - * priority. */ - ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_SNAT, - count_1bits(ntohl(mask)) + 1, - ds_cstr(&match), ds_cstr(&actions), - &nat->header_); - } else { - uint16_t priority = count_1bits(ntohl(mask)) + 1; - - /* Distributed router. */ - ds_clear(&match); - ds_put_format(&match, "ip && ip%s.src == %s" - " && outport == %s", - is_v6 ? "6" : "4", - nat->logical_ip, - od->l3dgw_port->json_key); - if (!distributed && od->l3redirect_port) { - /* Flows for NAT rules that are centralized are only - * programmed on the "redirect-chassis". */ - priority += 128; - ds_put_format(&match, " && is_chassis_resident(%s)", - od->l3redirect_port->json_key); - } - ds_clear(&actions); - - if (allowed_ext_ips || exempted_ext_ips) { - lrouter_nat_add_ext_ip_match(od, lflows, &match, nat, - is_v6, false, mask); - } - - if (distributed) { - ds_put_format(&actions, "eth.src = "ETH_ADDR_FMT"; ", - ETH_ADDR_ARGS(mac)); - } - - if (!strcmp(nat->type, "dnat_and_snat") && stateless) { - ds_put_format(&actions, "ip%s.src=%s; next;", - is_v6 ? "6" : "4", nat->external_ip); - } else { - ds_put_format(&actions, "ct_snat(%s", - nat->external_ip); - if (nat->external_port_range[0]) { - ds_put_format(&actions, ",%s", - nat->external_port_range); - } - ds_put_format(&actions, ");"); - } - - /* The priority here is calculated such that the - * nat->logical_ip with the longest mask gets a higher - * priority. */ - ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_SNAT, - priority, ds_cstr(&match), - ds_cstr(&actions), - &nat->header_); - } - } - - /* Logical router ingress table 0: - * For NAT on a distributed router, add rules allowing - * ingress traffic with eth.dst matching nat->external_mac - * on the l3dgw_port instance where nat->logical_port is - * resident. */ - if (distributed) { - /* Store the ethernet address of the port receiving the packet. - * This will save us from having to match on inport further - * down in the pipeline. - */ - ds_clear(&actions); - ds_put_format(&actions, REG_INPORT_ETH_ADDR " = %s; next;", - od->l3dgw_port->lrp_networks.ea_s); - - ds_clear(&match); - ds_put_format(&match, - "eth.dst == "ETH_ADDR_FMT" && inport == %s" - " && is_chassis_resident(\"%s\")", - ETH_ADDR_ARGS(mac), - od->l3dgw_port->json_key, - nat->logical_port); - ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ADMISSION, 50, - ds_cstr(&match), ds_cstr(&actions), - &nat->header_); - } - - /* Ingress Gateway Redirect Table: For NAT on a distributed - * router, add flows that are specific to a NAT rule. These - * flows indicate the presence of an applicable NAT rule that - * can be applied in a distributed manner. - * In particulr REG_SRC_IPV4/REG_SRC_IPV6 and eth.src are set to - * NAT external IP and NAT external mac so the ARP request - * generated in the following stage is sent out with proper IP/MAC - * src addresses. - */ - if (distributed) { - ds_clear(&match); - ds_clear(&actions); - ds_put_format(&match, - "ip%s.src == %s && outport == %s && " - "is_chassis_resident(\"%s\")", - is_v6 ? "6" : "4", nat->logical_ip, - od->l3dgw_port->json_key, nat->logical_port); - ds_put_format(&actions, "eth.src = %s; %s = %s; next;", - nat->external_mac, - is_v6 ? REG_SRC_IPV6 : REG_SRC_IPV4, - nat->external_ip); - ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_GW_REDIRECT, - 100, ds_cstr(&match), - ds_cstr(&actions), &nat->header_); - } - - /* Egress Loopback table: For NAT on a distributed router. - * If packets in the egress pipeline on the distributed - * gateway port have ip.dst matching a NAT external IP, then - * loop a clone of the packet back to the beginning of the - * ingress pipeline with inport = outport. */ - if (od->l3dgw_port) { - /* Distributed router. */ - ds_clear(&match); - ds_put_format(&match, "ip%s.dst == %s && outport == %s", - is_v6 ? "6" : "4", - nat->external_ip, - od->l3dgw_port->json_key); - if (!distributed) { - ds_put_format(&match, " && is_chassis_resident(%s)", - od->l3redirect_port->json_key); - } else { - ds_put_format(&match, " && is_chassis_resident(\"%s\")", - nat->logical_port); - } - - ds_clear(&actions); - ds_put_format(&actions, - "clone { ct_clear; " - "inport = outport; outport = \"\"; " - "flags = 0; flags.loopback = 1; "); - for (int j = 0; j < MFF_N_LOG_REGS; j++) { - ds_put_format(&actions, "reg%d = 0; ", j); - } - ds_put_format(&actions, REGBIT_EGRESS_LOOPBACK" = 1; " - "next(pipeline=ingress, table=0); };"); - ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_EGR_LOOP, 100, - ds_cstr(&match), ds_cstr(&actions), - &nat->header_); - } - } - - /* Handle force SNAT options set in the gateway router. */ - if (!od->l3dgw_port) { - if (dnat_force_snat_ip) { - if (dnat_force_snat_addrs.n_ipv4_addrs) { - build_lrouter_force_snat_flows(lflows, od, "4", - dnat_force_snat_addrs.ipv4_addrs[0].addr_s, "dnat"); - } - if (dnat_force_snat_addrs.n_ipv6_addrs) { - build_lrouter_force_snat_flows(lflows, od, "6", - dnat_force_snat_addrs.ipv6_addrs[0].addr_s, "dnat"); - } - } - if (lb_force_snat_ip) { - if (lb_force_snat_addrs.n_ipv4_addrs) { - build_lrouter_force_snat_flows(lflows, od, "4", - lb_force_snat_addrs.ipv4_addrs[0].addr_s, "lb"); - } - if (lb_force_snat_addrs.n_ipv6_addrs) { - build_lrouter_force_snat_flows(lflows, od, "6", - lb_force_snat_addrs.ipv6_addrs[0].addr_s, "lb"); - } - } - - /* For gateway router, re-circulate every packet through - * the DNAT zone. This helps with the following. - * - * Any packet that needs to be unDNATed in the reverse - * direction gets unDNATed. Ideally this could be done in - * the egress pipeline. But since the gateway router - * does not have any feature that depends on the source - * ip address being external IP address for IP routing, - * we can do it here, saving a future re-circulation. */ - ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 50, - "ip", "flags.loopback = 1; ct_dnat;"); - } - - if (dnat_force_snat_ip) { - destroy_lport_addresses(&dnat_force_snat_addrs); - } - if (lb_force_snat_ip) { - destroy_lport_addresses(&lb_force_snat_addrs); - } - - /* Load balancing and packet defrag are only valid on - * Gateway routers or router with gateway port. */ - if (!smap_get(&od->nbr->options, "chassis") && !od->l3dgw_port) { - sset_destroy(&nat_entries); - continue; - } - - /* A set to hold all ips that need defragmentation and tracking. */ - struct sset all_ips = SSET_INITIALIZER(&all_ips); - - for (int i = 0; i < od->nbr->n_load_balancer; i++) { - struct nbrec_load_balancer *nb_lb = od->nbr->load_balancer[i]; - struct ovn_lb *lb = - ovn_lb_find(lbs, &nb_lb->header_.uuid); - ovs_assert(lb); - - for (size_t j = 0; j < lb->n_vips; j++) { - struct lb_vip *lb_vip = &lb->vips[j]; - ds_clear(&actions); - build_lb_vip_ct_lb_actions(lb_vip, &actions, - lb->selection_fields); - - if (!sset_contains(&all_ips, lb_vip->vip)) { - sset_add(&all_ips, lb_vip->vip); - /* If there are any load balancing rules, we should send - * the packet to conntrack for defragmentation and - * tracking. This helps with two things. - * - * 1. With tracking, we can send only new connections to - * pick a DNAT ip address from a group. - * 2. If there are L4 ports in load balancing rules, we - * need the defragmentation to match on L4 ports. */ - ds_clear(&match); - if (lb_vip->addr_family == AF_INET) { - ds_put_format(&match, "ip && ip4.dst == %s", - lb_vip->vip); - } else if (lb_vip->addr_family == AF_INET6) { - ds_put_format(&match, "ip && ip6.dst == %s", - lb_vip->vip); - } - ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DEFRAG, - 100, ds_cstr(&match), "ct_next;", - &nb_lb->header_); - } - - /* Higher priority rules are added for load-balancing in DNAT - * table. For every match (on a VIP[:port]), we add two flows - * via add_router_lb_flow(). One flow is for specific matching - * on ct.new with an action of "ct_lb($targets);". The other - * flow is for ct.est with an action of "ct_dnat;". */ - ds_clear(&match); - if (lb_vip->addr_family == AF_INET) { - ds_put_format(&match, "ip && ip4.dst == %s", - lb_vip->vip); - } else if (lb_vip->addr_family == AF_INET6) { - ds_put_format(&match, "ip && ip6.dst == %s", - lb_vip->vip); - } - - int prio = 110; - bool is_udp = nullable_string_is_equal(nb_lb->protocol, "udp"); - bool is_sctp = nullable_string_is_equal(nb_lb->protocol, - "sctp"); - const char *proto = is_udp ? "udp" : is_sctp ? "sctp" : "tcp"; - - if (lb_vip->vip_port) { - ds_put_format(&match, " && %s && %s.dst == %d", proto, - proto, lb_vip->vip_port); - prio = 120; - } - - if (od->l3redirect_port) { - ds_put_format(&match, " && is_chassis_resident(%s)", - od->l3redirect_port->json_key); - } - add_router_lb_flow(lflows, od, &match, &actions, prio, - lb_force_snat_ip, lb_vip, proto, - nb_lb, meter_groups, &nat_entries); - } - } - sset_destroy(&all_ips); - sset_destroy(&nat_entries); + build_nat_defrag_and_lb_flows_for_lrouter( + od, lflows, meter_groups, lbs, &match, &actions); } HMAP_FOR_EACH (op, key_node, ports) { @@ -11350,6 +10704,669 @@ build_ipv6_input_flows_for_lrouter_port( } +static void +build_nat_defrag_and_lb_flows_for_lrouter( + struct ovn_datapath *od, struct hmap *lflows, + struct shash *meter_groups, struct hmap *lbs, + struct ds *match, struct ds *actions) +{ + if (!od->nbr) { + return; + } + + /* Packets are allowed by default. */ + ovn_lflow_add(lflows, od, S_ROUTER_IN_DEFRAG, 0, "1", "next;"); + ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 0, "1", "next;"); + ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 0, "1", "next;"); + ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 0, "1", "next;"); + ovn_lflow_add(lflows, od, S_ROUTER_OUT_UNDNAT, 0, "1", "next;"); + ovn_lflow_add(lflows, od, S_ROUTER_OUT_EGR_LOOP, 0, "1", "next;"); + ovn_lflow_add(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 0, "1", "next;"); + + /* Send the IPv6 NS packets to next table. When ovn-controller + * generates IPv6 NS (for the action - nd_ns{}), the injected + * packet would go through conntrack - which is not required. */ + ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 120, "nd_ns", "next;"); + + /* NAT rules are only valid on Gateway routers and routers with + * l3dgw_port (router has a port with "redirect-chassis" + * specified). */ + if (!smap_get(&od->nbr->options, "chassis") && !od->l3dgw_port) { + return; + } + + struct sset nat_entries = SSET_INITIALIZER(&nat_entries); + + struct lport_addresses dnat_force_snat_addrs; + struct lport_addresses lb_force_snat_addrs; + bool dnat_force_snat_ip = get_force_snat_ip(od, "dnat", + &dnat_force_snat_addrs); + bool lb_force_snat_ip = get_force_snat_ip(od, "lb", + &lb_force_snat_addrs); + + for (int i = 0; i < od->nbr->n_nat; i++) { + const struct nbrec_nat *nat; + + nat = od->nbr->nat[i]; + + ovs_be32 ip, mask; + struct in6_addr ipv6, mask_v6, v6_exact = IN6ADDR_EXACT_INIT; + bool is_v6 = false; + bool stateless = lrouter_nat_is_stateless(nat); + struct nbrec_address_set *allowed_ext_ips = + nat->allowed_ext_ips; + struct nbrec_address_set *exempted_ext_ips = + nat->exempted_ext_ips; + + if (allowed_ext_ips && exempted_ext_ips) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); + VLOG_WARN_RL(&rl, "NAT rule: "UUID_FMT" not applied, since" + "both allowed and exempt external ips set", + UUID_ARGS(&(nat->header_.uuid))); + continue; + } + + char *error = ip_parse_masked(nat->external_ip, &ip, &mask); + if (error || mask != OVS_BE32_MAX) { + free(error); + error = ipv6_parse_masked(nat->external_ip, &ipv6, &mask_v6); + if (error || memcmp(&mask_v6, &v6_exact, sizeof(mask_v6))) { + /* Invalid for both IPv4 and IPv6 */ + static struct vlog_rate_limit rl = + VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_WARN_RL(&rl, "bad external ip %s for nat", + nat->external_ip); + free(error); + continue; + } + /* It was an invalid IPv4 address, but valid IPv6. + * Treat the rest of the handling of this NAT rule + * as IPv6. */ + is_v6 = true; + } + + /* Check the validity of nat->logical_ip. 'logical_ip' can + * be a subnet when the type is "snat". */ + if (is_v6) { + error = ipv6_parse_masked(nat->logical_ip, &ipv6, &mask_v6); + } else { + error = ip_parse_masked(nat->logical_ip, &ip, &mask); + } + if (!strcmp(nat->type, "snat")) { + if (error) { + /* Invalid for both IPv4 and IPv6 */ + static struct vlog_rate_limit rl = + VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_WARN_RL(&rl, "bad ip network or ip %s for snat " + "in router "UUID_FMT"", + nat->logical_ip, UUID_ARGS(&od->key)); + free(error); + continue; + } + } else { + if (error || (!is_v6 && mask != OVS_BE32_MAX) + || (is_v6 && memcmp(&mask_v6, &v6_exact, + sizeof mask_v6))) { + /* Invalid for both IPv4 and IPv6 */ + static struct vlog_rate_limit rl = + VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_WARN_RL(&rl, "bad ip %s for dnat in router " + ""UUID_FMT"", nat->logical_ip, UUID_ARGS(&od->key)); + free(error); + continue; + } + } + + /* For distributed router NAT, determine whether this NAT rule + * satisfies the conditions for distributed NAT processing. */ + bool distributed = false; + struct eth_addr mac; + if (od->l3dgw_port && !strcmp(nat->type, "dnat_and_snat") && + nat->logical_port && nat->external_mac) { + if (eth_addr_from_string(nat->external_mac, &mac)) { + distributed = true; + } else { + static struct vlog_rate_limit rl = + VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_WARN_RL(&rl, "bad mac %s for dnat in router " + ""UUID_FMT"", nat->external_mac, UUID_ARGS(&od->key)); + continue; + } + } + + /* Ingress UNSNAT table: It is for already established connections' + * reverse traffic. i.e., SNAT has already been done in egress + * pipeline and now the packet has entered the ingress pipeline as + * part of a reply. We undo the SNAT here. + * + * Undoing SNAT has to happen before DNAT processing. This is + * because when the packet was DNATed in ingress pipeline, it did + * not know about the possibility of eventual additional SNAT in + * egress pipeline. */ + if (!strcmp(nat->type, "snat") + || !strcmp(nat->type, "dnat_and_snat")) { + if (!od->l3dgw_port) { + /* Gateway router. */ + ds_clear(match); + ds_clear(actions); + ds_put_format(match, "ip && ip%s.dst == %s", + is_v6 ? "6" : "4", + nat->external_ip); + if (!strcmp(nat->type, "dnat_and_snat") && stateless) { + ds_put_format(actions, "ip%s.dst=%s; next;", + is_v6 ? "6" : "4", nat->logical_ip); + } else { + ds_put_cstr(actions, "ct_snat;"); + } + + ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_UNSNAT, + 90, ds_cstr(match), + ds_cstr(actions), + &nat->header_); + } else { + /* Distributed router. */ + + /* Traffic received on l3dgw_port is subject to NAT. */ + ds_clear(match); + ds_clear(actions); + ds_put_format(match, "ip && ip%s.dst == %s" + " && inport == %s", + is_v6 ? "6" : "4", + nat->external_ip, + od->l3dgw_port->json_key); + if (!distributed && od->l3redirect_port) { + /* Flows for NAT rules that are centralized are only + * programmed on the "redirect-chassis". */ + ds_put_format(match, " && is_chassis_resident(%s)", + od->l3redirect_port->json_key); + } + + if (!strcmp(nat->type, "dnat_and_snat") && stateless) { + ds_put_format(actions, "ip%s.dst=%s; next;", + is_v6 ? "6" : "4", nat->logical_ip); + } else { + ds_put_cstr(actions, "ct_snat;"); + } + + ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_UNSNAT, + 100, + ds_cstr(match), ds_cstr(actions), + &nat->header_); + } + } + + /* Ingress DNAT table: Packets enter the pipeline with destination + * IP address that needs to be DNATted from a external IP address + * to a logical IP address. */ + if (!strcmp(nat->type, "dnat") + || !strcmp(nat->type, "dnat_and_snat")) { + if (!od->l3dgw_port) { + /* Gateway router. */ + /* Packet when it goes from the initiator to destination. + * We need to set flags.loopback because the router can + * send the packet back through the same interface. */ + ds_clear(match); + ds_put_format(match, "ip && ip%s.dst == %s", + is_v6 ? "6" : "4", + nat->external_ip); + ds_clear(actions); + if (allowed_ext_ips || exempted_ext_ips) { + lrouter_nat_add_ext_ip_match(od, lflows, match, nat, + is_v6, true, mask); + } + + if (dnat_force_snat_ip) { + /* Indicate to the future tables that a DNAT has taken + * place and a force SNAT needs to be done in the + * Egress SNAT table. */ + ds_put_format(actions, + "flags.force_snat_for_dnat = 1; "); + } + + if (!strcmp(nat->type, "dnat_and_snat") && stateless) { + ds_put_format(actions, "flags.loopback = 1; " + "ip%s.dst=%s; next;", + is_v6 ? "6" : "4", nat->logical_ip); + } else { + ds_put_format(actions, "flags.loopback = 1; " + "ct_dnat(%s", nat->logical_ip); + + if (nat->external_port_range[0]) { + ds_put_format(actions, ",%s", + nat->external_port_range); + } + ds_put_format(actions, ");"); + } + + ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, 100, + ds_cstr(match), ds_cstr(actions), + &nat->header_); + } else { + /* Distributed router. */ + + /* Traffic received on l3dgw_port is subject to NAT. */ + ds_clear(match); + ds_put_format(match, "ip && ip%s.dst == %s" + " && inport == %s", + is_v6 ? "6" : "4", + nat->external_ip, + od->l3dgw_port->json_key); + if (!distributed && od->l3redirect_port) { + /* Flows for NAT rules that are centralized are only + * programmed on the "redirect-chassis". */ + ds_put_format(match, " && is_chassis_resident(%s)", + od->l3redirect_port->json_key); + } + ds_clear(actions); + if (allowed_ext_ips || exempted_ext_ips) { + lrouter_nat_add_ext_ip_match(od, lflows, match, nat, + is_v6, true, mask); + } + + if (!strcmp(nat->type, "dnat_and_snat") && stateless) { + ds_put_format(actions, "ip%s.dst=%s; next;", + is_v6 ? "6" : "4", nat->logical_ip); + } else { + ds_put_format(actions, "ct_dnat(%s", nat->logical_ip); + if (nat->external_port_range[0]) { + ds_put_format(actions, ",%s", + nat->external_port_range); + } + ds_put_format(actions, ");"); + } + + ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, 100, + ds_cstr(match), ds_cstr(actions), + &nat->header_); + } + } + + /* ARP resolve for NAT IPs. */ + if (od->l3dgw_port) { + if (!strcmp(nat->type, "snat")) { + ds_clear(match); + ds_put_format( + match, "inport == %s && %s == %s", + od->l3dgw_port->json_key, + is_v6 ? "ip6.src" : "ip4.src", nat->external_ip); + ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_IP_INPUT, + 120, ds_cstr(match), "next;", + &nat->header_); + } + + if (!sset_contains(&nat_entries, nat->external_ip)) { + ds_clear(match); + ds_put_format( + match, "outport == %s && %s == %s", + od->l3dgw_port->json_key, + is_v6 ? REG_NEXT_HOP_IPV6 : REG_NEXT_HOP_IPV4, + nat->external_ip); + ds_clear(actions); + ds_put_format( + actions, "eth.dst = %s; next;", + distributed ? nat->external_mac : + od->l3dgw_port->lrp_networks.ea_s); + ovn_lflow_add_with_hint(lflows, od, + S_ROUTER_IN_ARP_RESOLVE, + 100, ds_cstr(match), + ds_cstr(actions), + &nat->header_); + sset_add(&nat_entries, nat->external_ip); + } + } else { + /* Add the NAT external_ip to the nat_entries even for + * gateway routers. This is required for adding load balancer + * flows.*/ + sset_add(&nat_entries, nat->external_ip); + } + + /* Egress UNDNAT table: It is for already established connections' + * reverse traffic. i.e., DNAT has already been done in ingress + * pipeline and now the packet has entered the egress pipeline as + * part of a reply. We undo the DNAT here. + * + * Note that this only applies for NAT on a distributed router. + * Undo DNAT on a gateway router is done in the ingress DNAT + * pipeline stage. */ + if (od->l3dgw_port && (!strcmp(nat->type, "dnat") + || !strcmp(nat->type, "dnat_and_snat"))) { + ds_clear(match); + ds_put_format(match, "ip && ip%s.src == %s" + " && outport == %s", + is_v6 ? "6" : "4", + nat->logical_ip, + od->l3dgw_port->json_key); + if (!distributed && od->l3redirect_port) { + /* Flows for NAT rules that are centralized are only + * programmed on the "redirect-chassis". */ + ds_put_format(match, " && is_chassis_resident(%s)", + od->l3redirect_port->json_key); + } + ds_clear(actions); + if (distributed) { + ds_put_format(actions, "eth.src = "ETH_ADDR_FMT"; ", + ETH_ADDR_ARGS(mac)); + } + + if (!strcmp(nat->type, "dnat_and_snat") && stateless) { + ds_put_format(actions, "ip%s.src=%s; next;", + is_v6 ? "6" : "4", nat->external_ip); + } else { + ds_put_format(actions, "ct_dnat;"); + } + + ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_UNDNAT, 100, + ds_cstr(match), ds_cstr(actions), + &nat->header_); + } + + /* Egress SNAT table: Packets enter the egress pipeline with + * source ip address that needs to be SNATted to a external ip + * address. */ + if (!strcmp(nat->type, "snat") + || !strcmp(nat->type, "dnat_and_snat")) { + if (!od->l3dgw_port) { + /* Gateway router. */ + ds_clear(match); + ds_put_format(match, "ip && ip%s.src == %s", + is_v6 ? "6" : "4", + nat->logical_ip); + ds_clear(actions); + + if (allowed_ext_ips || exempted_ext_ips) { + lrouter_nat_add_ext_ip_match(od, lflows, match, nat, + is_v6, false, mask); + } + + if (!strcmp(nat->type, "dnat_and_snat") && stateless) { + ds_put_format(actions, "ip%s.src=%s; next;", + is_v6 ? "6" : "4", nat->external_ip); + } else { + ds_put_format(actions, "ct_snat(%s", + nat->external_ip); + + if (nat->external_port_range[0]) { + ds_put_format(actions, ",%s", + nat->external_port_range); + } + ds_put_format(actions, ");"); + } + + /* The priority here is calculated such that the + * nat->logical_ip with the longest mask gets a higher + * priority. */ + ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_SNAT, + count_1bits(ntohl(mask)) + 1, + ds_cstr(match), ds_cstr(actions), + &nat->header_); + } else { + uint16_t priority = count_1bits(ntohl(mask)) + 1; + + /* Distributed router. */ + ds_clear(match); + ds_put_format(match, "ip && ip%s.src == %s" + " && outport == %s", + is_v6 ? "6" : "4", + nat->logical_ip, + od->l3dgw_port->json_key); + if (!distributed && od->l3redirect_port) { + /* Flows for NAT rules that are centralized are only + * programmed on the "redirect-chassis". */ + priority += 128; + ds_put_format(match, " && is_chassis_resident(%s)", + od->l3redirect_port->json_key); + } + ds_clear(actions); + + if (allowed_ext_ips || exempted_ext_ips) { + lrouter_nat_add_ext_ip_match(od, lflows, match, nat, + is_v6, false, mask); + } + + if (distributed) { + ds_put_format(actions, "eth.src = "ETH_ADDR_FMT"; ", + ETH_ADDR_ARGS(mac)); + } + + if (!strcmp(nat->type, "dnat_and_snat") && stateless) { + ds_put_format(actions, "ip%s.src=%s; next;", + is_v6 ? "6" : "4", nat->external_ip); + } else { + ds_put_format(actions, "ct_snat(%s", + nat->external_ip); + if (nat->external_port_range[0]) { + ds_put_format(actions, ",%s", + nat->external_port_range); + } + ds_put_format(actions, ");"); + } + + /* The priority here is calculated such that the + * nat->logical_ip with the longest mask gets a higher + * priority. */ + ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_SNAT, + priority, ds_cstr(match), + ds_cstr(actions), + &nat->header_); + } + } + + /* Logical router ingress table 0: + * For NAT on a distributed router, add rules allowing + * ingress traffic with eth.dst matching nat->external_mac + * on the l3dgw_port instance where nat->logical_port is + * resident. */ + if (distributed) { + /* Store the ethernet address of the port receiving the packet. + * This will save us from having to match on inport further + * down in the pipeline. + */ + ds_clear(actions); + ds_put_format(actions, REG_INPORT_ETH_ADDR " = %s; next;", + od->l3dgw_port->lrp_networks.ea_s); + + ds_clear(match); + ds_put_format(match, + "eth.dst == "ETH_ADDR_FMT" && inport == %s" + " && is_chassis_resident(\"%s\")", + ETH_ADDR_ARGS(mac), + od->l3dgw_port->json_key, + nat->logical_port); + ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ADMISSION, 50, + ds_cstr(match), ds_cstr(actions), + &nat->header_); + } + + /* Ingress Gateway Redirect Table: For NAT on a distributed + * router, add flows that are specific to a NAT rule. These + * flows indicate the presence of an applicable NAT rule that + * can be applied in a distributed manner. + * In particulr REG_SRC_IPV4/REG_SRC_IPV6 and eth.src are set to + * NAT external IP and NAT external mac so the ARP request + * generated in the following stage is sent out with proper IP/MAC + * src addresses. + */ + if (distributed) { + ds_clear(match); + ds_clear(actions); + ds_put_format(match, + "ip%s.src == %s && outport == %s && " + "is_chassis_resident(\"%s\")", + is_v6 ? "6" : "4", nat->logical_ip, + od->l3dgw_port->json_key, nat->logical_port); + ds_put_format(actions, "eth.src = %s; %s = %s; next;", + nat->external_mac, + is_v6 ? REG_SRC_IPV6 : REG_SRC_IPV4, + nat->external_ip); + ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_GW_REDIRECT, + 100, ds_cstr(match), + ds_cstr(actions), &nat->header_); + } + + /* Egress Loopback table: For NAT on a distributed router. + * If packets in the egress pipeline on the distributed + * gateway port have ip.dst matching a NAT external IP, then + * loop a clone of the packet back to the beginning of the + * ingress pipeline with inport = outport. */ + if (od->l3dgw_port) { + /* Distributed router. */ + ds_clear(match); + ds_put_format(match, "ip%s.dst == %s && outport == %s", + is_v6 ? "6" : "4", + nat->external_ip, + od->l3dgw_port->json_key); + if (!distributed) { + ds_put_format(match, " && is_chassis_resident(%s)", + od->l3redirect_port->json_key); + } else { + ds_put_format(match, " && is_chassis_resident(\"%s\")", + nat->logical_port); + } + + ds_clear(actions); + ds_put_format(actions, + "clone { ct_clear; " + "inport = outport; outport = \"\"; " + "flags = 0; flags.loopback = 1; "); + for (int j = 0; j < MFF_N_LOG_REGS; j++) { + ds_put_format(actions, "reg%d = 0; ", j); + } + ds_put_format(actions, REGBIT_EGRESS_LOOPBACK" = 1; " + "next(pipeline=ingress, table=0); };"); + ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_EGR_LOOP, 100, + ds_cstr(match), ds_cstr(actions), + &nat->header_); + } + } + + /* Handle force SNAT options set in the gateway router. */ + if (!od->l3dgw_port) { + if (dnat_force_snat_ip) { + if (dnat_force_snat_addrs.n_ipv4_addrs) { + build_lrouter_force_snat_flows(lflows, od, "4", + dnat_force_snat_addrs.ipv4_addrs[0].addr_s, "dnat"); + } + if (dnat_force_snat_addrs.n_ipv6_addrs) { + build_lrouter_force_snat_flows(lflows, od, "6", + dnat_force_snat_addrs.ipv6_addrs[0].addr_s, "dnat"); + } + } + if (lb_force_snat_ip) { + if (lb_force_snat_addrs.n_ipv4_addrs) { + build_lrouter_force_snat_flows(lflows, od, "4", + lb_force_snat_addrs.ipv4_addrs[0].addr_s, "lb"); + } + if (lb_force_snat_addrs.n_ipv6_addrs) { + build_lrouter_force_snat_flows(lflows, od, "6", + lb_force_snat_addrs.ipv6_addrs[0].addr_s, "lb"); + } + } + + /* For gateway router, re-circulate every packet through + * the DNAT zone. This helps with the following. + * + * Any packet that needs to be unDNATed in the reverse + * direction gets unDNATed. Ideally this could be done in + * the egress pipeline. But since the gateway router + * does not have any feature that depends on the source + * ip address being external IP address for IP routing, + * we can do it here, saving a future re-circulation. */ + ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 50, + "ip", "flags.loopback = 1; ct_dnat;"); + } + + if (dnat_force_snat_ip) { + destroy_lport_addresses(&dnat_force_snat_addrs); + } + if (lb_force_snat_ip) { + destroy_lport_addresses(&lb_force_snat_addrs); + } + + /* Load balancing and packet defrag are only valid on + * Gateway routers or router with gateway port. */ + if (!smap_get(&od->nbr->options, "chassis") && !od->l3dgw_port) { + sset_destroy(&nat_entries); + return; + } + + /* A set to hold all ips that need defragmentation and tracking. */ + struct sset all_ips = SSET_INITIALIZER(&all_ips); + + for (int i = 0; i < od->nbr->n_load_balancer; i++) { + struct nbrec_load_balancer *nb_lb = od->nbr->load_balancer[i]; + struct ovn_lb *lb = + ovn_lb_find(lbs, &nb_lb->header_.uuid); + ovs_assert(lb); + + for (size_t j = 0; j < lb->n_vips; j++) { + struct lb_vip *lb_vip = &lb->vips[j]; + ds_clear(actions); + build_lb_vip_ct_lb_actions(lb_vip, actions, + lb->selection_fields); + + if (!sset_contains(&all_ips, lb_vip->vip)) { + sset_add(&all_ips, lb_vip->vip); + /* If there are any load balancing rules, we should send + * the packet to conntrack for defragmentation and + * tracking. This helps with two things. + * + * 1. With tracking, we can send only new connections to + * pick a DNAT ip address from a group. + * 2. If there are L4 ports in load balancing rules, we + * need the defragmentation to match on L4 ports. */ + ds_clear(match); + if (lb_vip->addr_family == AF_INET) { + ds_put_format(match, "ip && ip4.dst == %s", + lb_vip->vip); + } else if (lb_vip->addr_family == AF_INET6) { + ds_put_format(match, "ip && ip6.dst == %s", + lb_vip->vip); + } + ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DEFRAG, + 100, ds_cstr(match), "ct_next;", + &nb_lb->header_); + } + + /* Higher priority rules are added for load-balancing in DNAT + * table. For every match (on a VIP[:port]), we add two flows + * via add_router_lb_flow(). One flow is for specific matching + * on ct.new with an action of "ct_lb($targets);". The other + * flow is for ct.est with an action of "ct_dnat;". */ + ds_clear(match); + if (lb_vip->addr_family == AF_INET) { + ds_put_format(match, "ip && ip4.dst == %s", + lb_vip->vip); + } else if (lb_vip->addr_family == AF_INET6) { + ds_put_format(match, "ip && ip6.dst == %s", + lb_vip->vip); + } + + int prio = 110; + bool is_udp = nullable_string_is_equal(nb_lb->protocol, "udp"); + bool is_sctp = nullable_string_is_equal(nb_lb->protocol, + "sctp"); + const char *proto = is_udp ? "udp" : is_sctp ? "sctp" : "tcp"; + + if (lb_vip->vip_port) { + ds_put_format(match, " && %s && %s.dst == %d", proto, + proto, lb_vip->vip_port); + prio = 120; + } + + if (od->l3redirect_port) { + ds_put_format(match, " && is_chassis_resident(%s)", + od->l3redirect_port->json_key); + } + add_router_lb_flow(lflows, od, match, actions, prio, + lb_force_snat_ip, lb_vip, proto, + nb_lb, meter_groups, &nat_entries); + } + } + sset_destroy(&all_ips); + sset_destroy(&nat_entries); + +} + /* Updates the Logical_Flow and Multicast_Group tables in the OVN_SB database, * constructing their contents based on the OVN_NB database. */ static void