diff mbox series

[ovs-dev,v3,1/4] ovn-northd: Drop IP packets destined to router owned IPs (after NAT).

Message ID 20200917125050.19729.41529.stgit@dceara.remote.csb
State Accepted
Headers show
Series Drop packets destined to own IPs and refactor SNAT processing. | expand

Commit Message

Dumitru Ceara Sept. 17, 2020, 12:50 p.m. UTC
OVN was dropping IP packets destined to IPs owned by logical routers but
only if those IPs are not used for SNAT rules. However, if a packet
doesn't match an existing NAT session and its destination is still a
router owned IP, it can be safely dropped. Otherwise it will trigger an
unnecessary packet-in in stage lr_in_arp_request.

To achieve that we add flows that drop traffic to router owned SNAT IPs in
table lr_in_arp_resolve.

Reported-by: Tim Rozet <trozet@redhat.com>
Reported-at: https://bugzilla.redhat.com/1876174
Signed-off-by: Dumitru Ceara <dceara@redhat.com>
---
 northd/ovn-northd.8.xml |   24 ++++++
 northd/ovn-northd.c     |  194 +++++++++++++++++++++++++++--------------------
 tests/ovn.at            |   88 +++++++++++++++++++++
 3 files changed, 225 insertions(+), 81 deletions(-)

Comments

Han Zhou Sept. 21, 2020, 6:37 a.m. UTC | #1
On Thu, Sep 17, 2020 at 5:51 AM Dumitru Ceara <dceara@redhat.com> wrote:
>
> OVN was dropping IP packets destined to IPs owned by logical routers but
> only if those IPs are not used for SNAT rules. However, if a packet
> doesn't match an existing NAT session and its destination is still a
> router owned IP, it can be safely dropped. Otherwise it will trigger an
> unnecessary packet-in in stage lr_in_arp_request.
>
> To achieve that we add flows that drop traffic to router owned SNAT IPs in
> table lr_in_arp_resolve.
>
> Reported-by: Tim Rozet <trozet@redhat.com>
> Reported-at: https://bugzilla.redhat.com/1876174
> Signed-off-by: Dumitru Ceara <dceara@redhat.com>
> ---
>  northd/ovn-northd.8.xml |   24 ++++++
>  northd/ovn-northd.c     |  194
+++++++++++++++++++++++++++--------------------
>  tests/ovn.at            |   88 +++++++++++++++++++++
>  3 files changed, 225 insertions(+), 81 deletions(-)
>
> diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
> index bd42105..f1c7c9b 100644
> --- a/northd/ovn-northd.8.xml
> +++ b/northd/ovn-northd.8.xml
> @@ -3089,6 +3089,30 @@ outport = <var>P</var>;
>
>        <li>
>          <p>
> +          Traffic with IP destination an address owned by the router
should be
> +          dropped. Such traffic is normally dropped in ingress table
> +          <code>IP Input</code> except for IPs that are also shared with
SNAT
> +          rules. However, if there was no unSNAT operation that happened
> +          successfully until this point in the pipeline and the
destination IP
> +          of the packet is still a router owned IP, the packets can be
safely
> +          dropped.
> +        </p>
> +
> +        <p>
> +          A priority-1 logical flow with match <code>ip4.dst =
{..}</code>
> +          matches on traffic destined to router owned IPv4 addresses
which are
> +          also SNAT IPs. This flow has action <code>drop;</code>.
> +        </p>
> +
> +        <p>
> +          A priority-1 logical flow with match <code>ip6.dst =
{..}</code>
> +          matches on traffic destined to router owned IPv6 addresses
which are
> +          also SNAT IPs. This flow has action <code>drop;</code>.
> +        </p>
> +      </li>
> +
> +      <li>
> +        <p>
>            Dynamic MAC bindings.  These flows resolve MAC-to-IP bindings
>            that have become known dynamically through ARP or neighbor
>            discovery.  (The ingress table <code>ARP Request</code> will
> diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
> index cfec6a2..d5d7631 100644
> --- a/northd/ovn-northd.c
> +++ b/northd/ovn-northd.c
> @@ -623,6 +623,9 @@ struct ovn_datapath {
>      /* NAT entries configured on the router. */
>      struct ovn_nat *nat_entries;
>
> +    /* SNAT IPs used by the router. */
> +    struct sset snat_ips;
> +
>      struct ovn_port **localnet_ports;
>      size_t n_localnet_ports;
>
> @@ -641,6 +644,10 @@ struct ovn_nat {
>      struct lport_addresses ext_addrs;
>  };
>
> +static bool
> +get_force_snat_ip(struct ovn_datapath *od, const char *key_type,
> +                  struct lport_addresses *laddrs);
> +
>  /* Returns true if a 'nat_entry' is valid, i.e.:
>   * - parsing was successful.
>   * - the string yielded exactly one IPv4 address or exactly one IPv6
address.
> @@ -663,7 +670,35 @@ nat_entry_is_v6(const struct ovn_nat *nat_entry)
>  static void
>  init_nat_entries(struct ovn_datapath *od)
>  {
> -    if (!od->nbr || od->nbr->n_nat == 0) {
> +    struct lport_addresses snat_addrs;
> +
> +    if (!od->nbr) {
> +        return;
> +    }
> +
> +    sset_init(&od->snat_ips);
> +    if (get_force_snat_ip(od, "dnat", &snat_addrs)) {
> +        if (snat_addrs.n_ipv4_addrs) {
> +            sset_add(&od->snat_ips, snat_addrs.ipv4_addrs[0].addr_s);
> +        }
> +        if (snat_addrs.n_ipv6_addrs) {
> +            sset_add(&od->snat_ips, snat_addrs.ipv6_addrs[0].addr_s);
> +        }
> +        destroy_lport_addresses(&snat_addrs);
> +    }
> +
> +    memset(&snat_addrs, 0, sizeof(snat_addrs));
> +    if (get_force_snat_ip(od, "lb", &snat_addrs)) {
> +        if (snat_addrs.n_ipv4_addrs) {
> +            sset_add(&od->snat_ips, snat_addrs.ipv4_addrs[0].addr_s);
> +        }
> +        if (snat_addrs.n_ipv6_addrs) {
> +            sset_add(&od->snat_ips, snat_addrs.ipv6_addrs[0].addr_s);
> +        }
> +        destroy_lport_addresses(&snat_addrs);
> +    }
> +
> +    if (!od->nbr->n_nat) {
>          return;
>      }
>
> @@ -682,6 +717,13 @@ init_nat_entries(struct ovn_datapath *od)
>              VLOG_WARN_RL(&rl,
>                           "Bad ip address %s in nat configuration "
>                           "for router %s", nat->external_ip,
od->nbr->name);
> +            continue;
> +        }
> +
> +        if (!nat_entry_is_v6(nat_entry)) {
> +            sset_add(&od->snat_ips,
nat_entry->ext_addrs.ipv4_addrs[0].addr_s);
> +        } else {
> +            sset_add(&od->snat_ips,
nat_entry->ext_addrs.ipv6_addrs[0].addr_s);
>          }
>      }
>  }
> @@ -693,6 +735,7 @@ destroy_nat_entries(struct ovn_datapath *od)
>          return;
>      }
>
> +    sset_destroy(&od->snat_ips);
>      for (size_t i = 0; i < od->nbr->n_nat; i++) {
>          destroy_lport_addresses(&od->nat_entries[i].ext_addrs);
>      }
> @@ -8744,6 +8787,68 @@ build_lrouter_flows(struct hmap *datapaths, struct
hmap *ports,
>                  op, lflows, &match, &actions);
>      }
>
> +    /* Drop IP traffic destined to router owned IPs. Part of it is
dropped
> +     * in stage "lr_in_ip_input" but traffic that could have been
unSNATed
> +     * but didn't match any existing session might still end up here.
> +     */
> +    HMAP_FOR_EACH (op, key_node, ports) {
> +        if (!op->nbrp) {
> +            continue;
> +        }
> +
> +        if (op->lrp_networks.n_ipv4_addrs) {
> +            ds_clear(&match);
> +            for (size_t i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
> +                if (!sset_find(&op->od->snat_ips,
> +                               op->lrp_networks.ipv4_addrs[i].addr_s)) {
> +                    continue;
> +                }
> +                ds_put_format(&match, "%s, ",
> +                              op->lrp_networks.ipv4_addrs[i].addr_s);
> +            }
> +
> +            if (ds_last(&match) != EOF) {
> +                ds_chomp(&match, ' ');
> +                ds_chomp(&match, ',');
> +
> +                char *drop_match = xasprintf("ip4.dst == {%s}",
> +                                             ds_cstr(&match));
> +                /* Drop traffic with IP.dest == router-ip. */
> +                ovn_lflow_add_with_hint(lflows, op->od,
> +                                        S_ROUTER_IN_ARP_RESOLVE, 1,
> +                                        drop_match, "drop;",
> +                                        &op->nbrp->header_);
> +                free(drop_match);
> +            }
> +        }
> +
> +        if (op->lrp_networks.n_ipv6_addrs) {
> +            ds_clear(&match);
> +            for (size_t i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
> +                if (!sset_find(&op->od->snat_ips,
> +                               op->lrp_networks.ipv6_addrs[i].addr_s)) {
> +                    continue;
> +                }
> +                ds_put_format(&match, "%s, ",
> +                              op->lrp_networks.ipv6_addrs[i].addr_s);
> +            }
> +
> +            if (ds_last(&match) != EOF) {
> +                ds_chomp(&match, ' ');
> +                ds_chomp(&match, ',');
> +
> +                char *drop_match = xasprintf("ip6.dst == {%s}",
> +                                             ds_cstr(&match));
> +                /* Drop traffic with IP.dest == router-ip. */
> +                ovn_lflow_add_with_hint(lflows, op->od,
> +                                        S_ROUTER_IN_ARP_RESOLVE, 1,
> +                                        drop_match, "drop;",
> +                                        &op->nbrp->header_);
> +                free(drop_match);
> +            }
> +        }
> +    }
> +
>      HMAP_FOR_EACH (od, key_node, datapaths) {
>          if (!od->nbr) {
>              continue;
> @@ -9035,77 +9140,15 @@ build_lrouter_flows(struct hmap *datapaths,
struct hmap *ports,
>          }
>
>          /* 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;
> -                }
> -            }
> -        }
> -
> +         * LBed traffic respectively to be SNATed. In addition, there
can be
> +         * a number of SNAT rules in the NAT table.
> +         * Skip all of them for drop flows. */
>          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) {
> +            if (sset_find(&op->od->snat_ips,
> +                          op->lrp_networks.ipv4_addrs[i].addr_s)) {
>                  continue;
>              }
>              ds_put_format(&match, "%s, ",
> @@ -9122,17 +9165,8 @@ build_lrouter_flows(struct hmap *datapaths, struct
hmap *ports,
>          }
>
>          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) {
> +            if (sset_find(&op->od->snat_ips,
> +                          op->lrp_networks.ipv6_addrs[i].addr_s)) {
>                  continue;
>              }
>              ds_put_format(&match, "%s, ",
> @@ -9151,8 +9185,6 @@ build_lrouter_flows(struct hmap *datapaths, struct
hmap *ports,
>                                      &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.
> diff --git a/tests/ovn.at b/tests/ovn.at
> index a6f1fb5..cb7e7cc 100644
> --- a/tests/ovn.at
> +++ b/tests/ovn.at
> @@ -21659,6 +21659,94 @@ OVS_WAIT_UNTIL([test x$(as hv1 ovn-appctl -t
ovn-controller debug/status) = "xru
>  OVN_CLEANUP([hv1])
>  AT_CLEANUP
>
> +# Test dropping traffic destined to router owned IPs.
> +AT_SETUP([ovn -- gateway router drop traffic for own IPs])
> +ovn_start
> +
> +ovn-nbctl lr-add r1 -- set logical_router r1 options:chassis=hv1
> +ovn-nbctl ls-add s1
> +
> +# Connnect r1 to s1.
> +ovn-nbctl lrp-add r1 lrp-r1-s1 00:00:00:00:01:01 10.0.1.1/24
> +ovn-nbctl lsp-add s1 lsp-s1-r1 -- set Logical_Switch_Port lsp-s1-r1
type=router \
> +    options:router-port=lrp-r1-s1 addresses=router
> +
> +# Create logical port p1 in s1
> +ovn-nbctl lsp-add s1 p1 \
> +-- lsp-set-addresses p1 "f0:00:00:00:01:02 10.0.1.2"
> +
> +# Create two hypervisor and create OVS ports corresponding to logical
ports.
> +net_add n1
> +
> +sim_add hv1
> +as hv1
> +ovs-vsctl add-br br-phys
> +ovn_attach n1 br-phys 192.168.0.1
> +ovs-vsctl -- add-port br-int hv1-vif1 -- \
> +    set interface hv1-vif1 external-ids:iface-id=p1 \
> +    options:tx_pcap=hv1/vif1-tx.pcap \
> +    options:rxq_pcap=hv1/vif1-rx.pcap \
> +    ofport-request=1
> +
> +# Pre-populate the hypervisors' ARP tables so that we don't lose any
> +# packets for ARP resolution (native tunneling doesn't queue packets
> +# for ARP resolution).
> +OVN_POPULATE_ARP
> +
> +ovn-nbctl --wait=hv sync
> +
> +sw_key=$(ovn-sbctl --bare --columns tunnel_key list datapath_binding r1)
> +
> +AT_CHECK([ovn-sbctl lflow-list | grep lr_in_arp_resolve | grep
10.0.1.1], [1], [])
> +
> +ip_to_hex() {
> +    printf "%02x%02x%02x%02x" "$@"
> +}
> +
> +# Send ip packets from p1 to lrp-r1-s1
> +src_mac="f00000000102"
> +dst_mac="000000000101"
> +src_ip=`ip_to_hex 10 0 1 2`
> +dst_ip=`ip_to_hex 10 0 1 1`
>
+packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
> +as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
> +
> +# No packet-ins should reach ovn-controller.
> +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep "actions=controller"
| grep -v n_packets=0 -c], [1], [dnl
> +0
> +])
> +
> +# The packet should have been dropped in the lr_in_ip_input stage.
> +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep -E "table=11,
n_packets=1,.* priority=60,ip,metadata=0x${sw_key},nw_dst=10.0.1.1
actions=drop" -c], [0], [dnl
> +1
> +])
> +
> +# Use the router IP as SNAT IP.
> +ovn-nbctl set logical_router r1 options:lb_force_snat_ip=10.0.1.1
> +ovn-nbctl --wait=hv sync
> +
> +# Send ip packets from p1 to lrp-r1-s1
> +src_mac="f00000000102"
> +dst_mac="000000000101"
> +src_ip=`ip_to_hex 10 0 1 2`
> +dst_ip=`ip_to_hex 10 0 1 1`
>
+packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
> +as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
> +
> +# Even after configuring a router owned IP for SNAT, no packet-ins should
> +# reach ovn-controller.
> +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep "actions=controller"
| grep -v n_packets=0 -c], [1], [dnl
> +0
> +])
> +
> +# The packet should've been dropped in the lr_in_arp_resolve stage.
> +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep -E "table=21,
n_packets=1,.* priority=1,ip,metadata=0x${sw_key},nw_dst=10.0.1.1
actions=drop" -c], [0], [dnl
> +1
> +])
> +
> +OVN_CLEANUP([hv1])
> +AT_CLEANUP
> +
>  AT_SETUP([ovn -- nb_cfg timestamp])
>  ovn_start
>
>
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Acked-by: Han Zhou <hzhou@ovn.org>
Numan Siddique Sept. 22, 2020, 6:17 a.m. UTC | #2
On Mon, Sep 21, 2020 at 12:08 PM Han Zhou <hzhou@ovn.org> wrote:

> On Thu, Sep 17, 2020 at 5:51 AM Dumitru Ceara <dceara@redhat.com> wrote:
> >
> > OVN was dropping IP packets destined to IPs owned by logical routers but
> > only if those IPs are not used for SNAT rules. However, if a packet
> > doesn't match an existing NAT session and its destination is still a
> > router owned IP, it can be safely dropped. Otherwise it will trigger an
> > unnecessary packet-in in stage lr_in_arp_request.
> >
> > To achieve that we add flows that drop traffic to router owned SNAT IPs
> in
> > table lr_in_arp_resolve.
> >
> > Reported-by: Tim Rozet <trozet@redhat.com>
> > Reported-at: https://bugzilla.redhat.com/1876174
> > Signed-off-by: Dumitru Ceara <dceara@redhat.com>
> > ---
> >  northd/ovn-northd.8.xml |   24 ++++++
> >  northd/ovn-northd.c     |  194
> +++++++++++++++++++++++++++--------------------
> >  tests/ovn.at            |   88 +++++++++++++++++++++
> >  3 files changed, 225 insertions(+), 81 deletions(-)
> >
> > diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
> > index bd42105..f1c7c9b 100644
> > --- a/northd/ovn-northd.8.xml
> > +++ b/northd/ovn-northd.8.xml
> > @@ -3089,6 +3089,30 @@ outport = <var>P</var>;
> >
> >        <li>
> >          <p>
> > +          Traffic with IP destination an address owned by the router
> should be
> > +          dropped. Such traffic is normally dropped in ingress table
> > +          <code>IP Input</code> except for IPs that are also shared with
> SNAT
> > +          rules. However, if there was no unSNAT operation that happened
> > +          successfully until this point in the pipeline and the
> destination IP
> > +          of the packet is still a router owned IP, the packets can be
> safely
> > +          dropped.
> > +        </p>
> > +
> > +        <p>
> > +          A priority-1 logical flow with match <code>ip4.dst =
> {..}</code>
> > +          matches on traffic destined to router owned IPv4 addresses
> which are
> > +          also SNAT IPs. This flow has action <code>drop;</code>.
> > +        </p>
> > +
> > +        <p>
> > +          A priority-1 logical flow with match <code>ip6.dst =
> {..}</code>
> > +          matches on traffic destined to router owned IPv6 addresses
> which are
> > +          also SNAT IPs. This flow has action <code>drop;</code>.
> > +        </p>
> > +      </li>
> > +
> > +      <li>
> > +        <p>
> >            Dynamic MAC bindings.  These flows resolve MAC-to-IP bindings
> >            that have become known dynamically through ARP or neighbor
> >            discovery.  (The ingress table <code>ARP Request</code> will
> > diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
> > index cfec6a2..d5d7631 100644
> > --- a/northd/ovn-northd.c
> > +++ b/northd/ovn-northd.c
> > @@ -623,6 +623,9 @@ struct ovn_datapath {
> >      /* NAT entries configured on the router. */
> >      struct ovn_nat *nat_entries;
> >
> > +    /* SNAT IPs used by the router. */
> > +    struct sset snat_ips;
> > +
> >      struct ovn_port **localnet_ports;
> >      size_t n_localnet_ports;
> >
> > @@ -641,6 +644,10 @@ struct ovn_nat {
> >      struct lport_addresses ext_addrs;
> >  };
> >
> > +static bool
> > +get_force_snat_ip(struct ovn_datapath *od, const char *key_type,
> > +                  struct lport_addresses *laddrs);
> > +
> >  /* Returns true if a 'nat_entry' is valid, i.e.:
> >   * - parsing was successful.
> >   * - the string yielded exactly one IPv4 address or exactly one IPv6
> address.
> > @@ -663,7 +670,35 @@ nat_entry_is_v6(const struct ovn_nat *nat_entry)
> >  static void
> >  init_nat_entries(struct ovn_datapath *od)
> >  {
> > -    if (!od->nbr || od->nbr->n_nat == 0) {
> > +    struct lport_addresses snat_addrs;
> > +
> > +    if (!od->nbr) {
> > +        return;
> > +    }
> > +
> > +    sset_init(&od->snat_ips);
> > +    if (get_force_snat_ip(od, "dnat", &snat_addrs)) {
> > +        if (snat_addrs.n_ipv4_addrs) {
> > +            sset_add(&od->snat_ips, snat_addrs.ipv4_addrs[0].addr_s);
> > +        }
> > +        if (snat_addrs.n_ipv6_addrs) {
> > +            sset_add(&od->snat_ips, snat_addrs.ipv6_addrs[0].addr_s);
> > +        }
> > +        destroy_lport_addresses(&snat_addrs);
> > +    }
> > +
> > +    memset(&snat_addrs, 0, sizeof(snat_addrs));
> > +    if (get_force_snat_ip(od, "lb", &snat_addrs)) {
> > +        if (snat_addrs.n_ipv4_addrs) {
> > +            sset_add(&od->snat_ips, snat_addrs.ipv4_addrs[0].addr_s);
> > +        }
> > +        if (snat_addrs.n_ipv6_addrs) {
> > +            sset_add(&od->snat_ips, snat_addrs.ipv6_addrs[0].addr_s);
> > +        }
> > +        destroy_lport_addresses(&snat_addrs);
> > +    }
> > +
> > +    if (!od->nbr->n_nat) {
> >          return;
> >      }
> >
> > @@ -682,6 +717,13 @@ init_nat_entries(struct ovn_datapath *od)
> >              VLOG_WARN_RL(&rl,
> >                           "Bad ip address %s in nat configuration "
> >                           "for router %s", nat->external_ip,
> od->nbr->name);
> > +            continue;
> > +        }
> > +
> > +        if (!nat_entry_is_v6(nat_entry)) {
> > +            sset_add(&od->snat_ips,
> nat_entry->ext_addrs.ipv4_addrs[0].addr_s);
> > +        } else {
> > +            sset_add(&od->snat_ips,
> nat_entry->ext_addrs.ipv6_addrs[0].addr_s);
> >          }
> >      }
> >  }
> > @@ -693,6 +735,7 @@ destroy_nat_entries(struct ovn_datapath *od)
> >          return;
> >      }
> >
> > +    sset_destroy(&od->snat_ips);
> >      for (size_t i = 0; i < od->nbr->n_nat; i++) {
> >          destroy_lport_addresses(&od->nat_entries[i].ext_addrs);
> >      }
> > @@ -8744,6 +8787,68 @@ build_lrouter_flows(struct hmap *datapaths, struct
> hmap *ports,
> >                  op, lflows, &match, &actions);
> >      }
> >
> > +    /* Drop IP traffic destined to router owned IPs. Part of it is
> dropped
> > +     * in stage "lr_in_ip_input" but traffic that could have been
> unSNATed
> > +     * but didn't match any existing session might still end up here.
> > +     */
> > +    HMAP_FOR_EACH (op, key_node, ports) {
> > +        if (!op->nbrp) {
> > +            continue;
> > +        }
> > +
> > +        if (op->lrp_networks.n_ipv4_addrs) {
> > +            ds_clear(&match);
> > +            for (size_t i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
> > +                if (!sset_find(&op->od->snat_ips,
> > +                               op->lrp_networks.ipv4_addrs[i].addr_s)) {
> > +                    continue;
> > +                }
> > +                ds_put_format(&match, "%s, ",
> > +                              op->lrp_networks.ipv4_addrs[i].addr_s);
> > +            }
> > +
> > +            if (ds_last(&match) != EOF) {
> > +                ds_chomp(&match, ' ');
> > +                ds_chomp(&match, ',');
> > +
> > +                char *drop_match = xasprintf("ip4.dst == {%s}",
> > +                                             ds_cstr(&match));
> > +                /* Drop traffic with IP.dest == router-ip. */
> > +                ovn_lflow_add_with_hint(lflows, op->od,
> > +                                        S_ROUTER_IN_ARP_RESOLVE, 1,
> > +                                        drop_match, "drop;",
> > +                                        &op->nbrp->header_);
> > +                free(drop_match);
> > +            }
> > +        }
> > +
> > +        if (op->lrp_networks.n_ipv6_addrs) {
> > +            ds_clear(&match);
> > +            for (size_t i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
> > +                if (!sset_find(&op->od->snat_ips,
> > +                               op->lrp_networks.ipv6_addrs[i].addr_s)) {
> > +                    continue;
> > +                }
> > +                ds_put_format(&match, "%s, ",
> > +                              op->lrp_networks.ipv6_addrs[i].addr_s);
> > +            }
> > +
> > +            if (ds_last(&match) != EOF) {
> > +                ds_chomp(&match, ' ');
> > +                ds_chomp(&match, ',');
> > +
> > +                char *drop_match = xasprintf("ip6.dst == {%s}",
> > +                                             ds_cstr(&match));
> > +                /* Drop traffic with IP.dest == router-ip. */
> > +                ovn_lflow_add_with_hint(lflows, op->od,
> > +                                        S_ROUTER_IN_ARP_RESOLVE, 1,
> > +                                        drop_match, "drop;",
> > +                                        &op->nbrp->header_);
> > +                free(drop_match);
> > +            }
> > +        }
> > +    }
> > +
> >      HMAP_FOR_EACH (od, key_node, datapaths) {
> >          if (!od->nbr) {
> >              continue;
> > @@ -9035,77 +9140,15 @@ build_lrouter_flows(struct hmap *datapaths,
> struct hmap *ports,
> >          }
> >
> >          /* 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;
> > -                }
> > -            }
> > -        }
> > -
> > +         * LBed traffic respectively to be SNATed. In addition, there
> can be
> > +         * a number of SNAT rules in the NAT table.
> > +         * Skip all of them for drop flows. */
> >          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) {
> > +            if (sset_find(&op->od->snat_ips,
> > +                          op->lrp_networks.ipv4_addrs[i].addr_s)) {
> >                  continue;
> >              }
> >              ds_put_format(&match, "%s, ",
> > @@ -9122,17 +9165,8 @@ build_lrouter_flows(struct hmap *datapaths, struct
> hmap *ports,
> >          }
> >
> >          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) {
> > +            if (sset_find(&op->od->snat_ips,
> > +                          op->lrp_networks.ipv6_addrs[i].addr_s)) {
> >                  continue;
> >              }
> >              ds_put_format(&match, "%s, ",
> > @@ -9151,8 +9185,6 @@ build_lrouter_flows(struct hmap *datapaths, struct
> hmap *ports,
> >                                      &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.
> > diff --git a/tests/ovn.at b/tests/ovn.at
> > index a6f1fb5..cb7e7cc 100644
> > --- a/tests/ovn.at
> > +++ b/tests/ovn.at
> > @@ -21659,6 +21659,94 @@ OVS_WAIT_UNTIL([test x$(as hv1 ovn-appctl -t
> ovn-controller debug/status) = "xru
> >  OVN_CLEANUP([hv1])
> >  AT_CLEANUP
> >
> > +# Test dropping traffic destined to router owned IPs.
> > +AT_SETUP([ovn -- gateway router drop traffic for own IPs])
> > +ovn_start
> > +
> > +ovn-nbctl lr-add r1 -- set logical_router r1 options:chassis=hv1
> > +ovn-nbctl ls-add s1
> > +
> > +# Connnect r1 to s1.
> > +ovn-nbctl lrp-add r1 lrp-r1-s1 00:00:00:00:01:01 10.0.1.1/24
> > +ovn-nbctl lsp-add s1 lsp-s1-r1 -- set Logical_Switch_Port lsp-s1-r1
> type=router \
> > +    options:router-port=lrp-r1-s1 addresses=router
> > +
> > +# Create logical port p1 in s1
> > +ovn-nbctl lsp-add s1 p1 \
> > +-- lsp-set-addresses p1 "f0:00:00:00:01:02 10.0.1.2"
> > +
> > +# Create two hypervisor and create OVS ports corresponding to logical
> ports.
> > +net_add n1
> > +
> > +sim_add hv1
> > +as hv1
> > +ovs-vsctl add-br br-phys
> > +ovn_attach n1 br-phys 192.168.0.1
> > +ovs-vsctl -- add-port br-int hv1-vif1 -- \
> > +    set interface hv1-vif1 external-ids:iface-id=p1 \
> > +    options:tx_pcap=hv1/vif1-tx.pcap \
> > +    options:rxq_pcap=hv1/vif1-rx.pcap \
> > +    ofport-request=1
> > +
> > +# Pre-populate the hypervisors' ARP tables so that we don't lose any
> > +# packets for ARP resolution (native tunneling doesn't queue packets
> > +# for ARP resolution).
> > +OVN_POPULATE_ARP
> > +
> > +ovn-nbctl --wait=hv sync
> > +
> > +sw_key=$(ovn-sbctl --bare --columns tunnel_key list datapath_binding r1)
> > +
> > +AT_CHECK([ovn-sbctl lflow-list | grep lr_in_arp_resolve | grep
> 10.0.1.1], [1], [])
> > +
> > +ip_to_hex() {
> > +    printf "%02x%02x%02x%02x" "$@"
> > +}
> > +
> > +# Send ip packets from p1 to lrp-r1-s1
> > +src_mac="f00000000102"
> > +dst_mac="000000000101"
> > +src_ip=`ip_to_hex 10 0 1 2`
> > +dst_ip=`ip_to_hex 10 0 1 1`
> >
>
> +packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
> > +as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
> > +
> > +# No packet-ins should reach ovn-controller.
> > +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep "actions=controller"
> | grep -v n_packets=0 -c], [1], [dnl
> > +0
> > +])
> > +
> > +# The packet should have been dropped in the lr_in_ip_input stage.
> > +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep -E "table=11,
> n_packets=1,.* priority=60,ip,metadata=0x${sw_key},nw_dst=10.0.1.1
> actions=drop" -c], [0], [dnl
> > +1
> > +])
> > +
> > +# Use the router IP as SNAT IP.
> > +ovn-nbctl set logical_router r1 options:lb_force_snat_ip=10.0.1.1
> > +ovn-nbctl --wait=hv sync
> > +
> > +# Send ip packets from p1 to lrp-r1-s1
> > +src_mac="f00000000102"
> > +dst_mac="000000000101"
> > +src_ip=`ip_to_hex 10 0 1 2`
> > +dst_ip=`ip_to_hex 10 0 1 1`
> >
>
> +packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
> > +as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
> > +
> > +# Even after configuring a router owned IP for SNAT, no packet-ins
> should
> > +# reach ovn-controller.
> > +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep "actions=controller"
> | grep -v n_packets=0 -c], [1], [dnl
> > +0
> > +])
> > +
> > +# The packet should've been dropped in the lr_in_arp_resolve stage.
> > +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep -E "table=21,
> n_packets=1,.* priority=1,ip,metadata=0x${sw_key},nw_dst=10.0.1.1
> actions=drop" -c], [0], [dnl
> > +1
> > +])
> > +
> > +OVN_CLEANUP([hv1])
> > +AT_CLEANUP
> > +
> >  AT_SETUP([ovn -- nb_cfg timestamp])
> >  ovn_start
> >
> >
> > _______________________________________________
> > dev mailing list
> > dev@openvswitch.org
> > https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
> Acked-by: Han Zhou <hzhou@ovn.org>
>

Thanks Dumitru and Han. I applied this patch to master and branch-20.09.

The patch doesn't apply cleanly to branch-20.06. There were many conflicts
and I didn't try resolving myself.
Dumitru - please submit the backport patches to branch-20.06 (and
branch-20.03 if you would like).

Thanks
Numan


> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
>
Dumitru Ceara Sept. 28, 2020, 7:56 a.m. UTC | #3
On 9/22/20 8:17 AM, Numan Siddique wrote:
> 
> 
> On Mon, Sep 21, 2020 at 12:08 PM Han Zhou <hzhou@ovn.org
> <mailto:hzhou@ovn.org>> wrote:
> 
>     On Thu, Sep 17, 2020 at 5:51 AM Dumitru Ceara <dceara@redhat.com
>     <mailto:dceara@redhat.com>> wrote:
>     >
>     > OVN was dropping IP packets destined to IPs owned by logical
>     routers but
>     > only if those IPs are not used for SNAT rules. However, if a packet
>     > doesn't match an existing NAT session and its destination is still a
>     > router owned IP, it can be safely dropped. Otherwise it will
>     trigger an
>     > unnecessary packet-in in stage lr_in_arp_request.
>     >
>     > To achieve that we add flows that drop traffic to router owned
>     SNAT IPs in
>     > table lr_in_arp_resolve.
>     >
>     > Reported-by: Tim Rozet <trozet@redhat.com <mailto:trozet@redhat.com>>
>     > Reported-at: https://bugzilla.redhat.com/1876174
>     > Signed-off-by: Dumitru Ceara <dceara@redhat.com
>     <mailto:dceara@redhat.com>>
>     > ---
>     >  northd/ovn-northd.8.xml |   24 ++++++
>     >  northd/ovn-northd.c     |  194
>     +++++++++++++++++++++++++++--------------------
>     >  tests/ovn.at <http://ovn.at>            |   88 +++++++++++++++++++++
>     >  3 files changed, 225 insertions(+), 81 deletions(-)
>     >
>     > diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
>     > index bd42105..f1c7c9b 100644
>     > --- a/northd/ovn-northd.8.xml
>     > +++ b/northd/ovn-northd.8.xml
>     > @@ -3089,6 +3089,30 @@ outport = <var>P</var>;
>     >
>     >        <li>
>     >          <p>
>     > +          Traffic with IP destination an address owned by the router
>     should be
>     > +          dropped. Such traffic is normally dropped in ingress table
>     > +          <code>IP Input</code> except for IPs that are also
>     shared with
>     SNAT
>     > +          rules. However, if there was no unSNAT operation that
>     happened
>     > +          successfully until this point in the pipeline and the
>     destination IP
>     > +          of the packet is still a router owned IP, the packets
>     can be
>     safely
>     > +          dropped.
>     > +        </p>
>     > +
>     > +        <p>
>     > +          A priority-1 logical flow with match <code>ip4.dst =
>     {..}</code>
>     > +          matches on traffic destined to router owned IPv4 addresses
>     which are
>     > +          also SNAT IPs. This flow has action <code>drop;</code>.
>     > +        </p>
>     > +
>     > +        <p>
>     > +          A priority-1 logical flow with match <code>ip6.dst =
>     {..}</code>
>     > +          matches on traffic destined to router owned IPv6 addresses
>     which are
>     > +          also SNAT IPs. This flow has action <code>drop;</code>.
>     > +        </p>
>     > +      </li>
>     > +
>     > +      <li>
>     > +        <p>
>     >            Dynamic MAC bindings.  These flows resolve MAC-to-IP
>     bindings
>     >            that have become known dynamically through ARP or neighbor
>     >            discovery.  (The ingress table <code>ARP Request</code>
>     will
>     > diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
>     > index cfec6a2..d5d7631 100644
>     > --- a/northd/ovn-northd.c
>     > +++ b/northd/ovn-northd.c
>     > @@ -623,6 +623,9 @@ struct ovn_datapath {
>     >      /* NAT entries configured on the router. */
>     >      struct ovn_nat *nat_entries;
>     >
>     > +    /* SNAT IPs used by the router. */
>     > +    struct sset snat_ips;
>     > +
>     >      struct ovn_port **localnet_ports;
>     >      size_t n_localnet_ports;
>     >
>     > @@ -641,6 +644,10 @@ struct ovn_nat {
>     >      struct lport_addresses ext_addrs;
>     >  };
>     >
>     > +static bool
>     > +get_force_snat_ip(struct ovn_datapath *od, const char *key_type,
>     > +                  struct lport_addresses *laddrs);
>     > +
>     >  /* Returns true if a 'nat_entry' is valid, i.e.:
>     >   * - parsing was successful.
>     >   * - the string yielded exactly one IPv4 address or exactly one IPv6
>     address.
>     > @@ -663,7 +670,35 @@ nat_entry_is_v6(const struct ovn_nat *nat_entry)
>     >  static void
>     >  init_nat_entries(struct ovn_datapath *od)
>     >  {
>     > -    if (!od->nbr || od->nbr->n_nat == 0) {
>     > +    struct lport_addresses snat_addrs;
>     > +
>     > +    if (!od->nbr) {
>     > +        return;
>     > +    }
>     > +
>     > +    sset_init(&od->snat_ips);
>     > +    if (get_force_snat_ip(od, "dnat", &snat_addrs)) {
>     > +        if (snat_addrs.n_ipv4_addrs) {
>     > +            sset_add(&od->snat_ips, snat_addrs.ipv4_addrs[0].addr_s);
>     > +        }
>     > +        if (snat_addrs.n_ipv6_addrs) {
>     > +            sset_add(&od->snat_ips, snat_addrs.ipv6_addrs[0].addr_s);
>     > +        }
>     > +        destroy_lport_addresses(&snat_addrs);
>     > +    }
>     > +
>     > +    memset(&snat_addrs, 0, sizeof(snat_addrs));
>     > +    if (get_force_snat_ip(od, "lb", &snat_addrs)) {
>     > +        if (snat_addrs.n_ipv4_addrs) {
>     > +            sset_add(&od->snat_ips, snat_addrs.ipv4_addrs[0].addr_s);
>     > +        }
>     > +        if (snat_addrs.n_ipv6_addrs) {
>     > +            sset_add(&od->snat_ips, snat_addrs.ipv6_addrs[0].addr_s);
>     > +        }
>     > +        destroy_lport_addresses(&snat_addrs);
>     > +    }
>     > +
>     > +    if (!od->nbr->n_nat) {
>     >          return;
>     >      }
>     >
>     > @@ -682,6 +717,13 @@ init_nat_entries(struct ovn_datapath *od)
>     >              VLOG_WARN_RL(&rl,
>     >                           "Bad ip address %s in nat configuration "
>     >                           "for router %s", nat->external_ip,
>     od->nbr->name);
>     > +            continue;
>     > +        }
>     > +
>     > +        if (!nat_entry_is_v6(nat_entry)) {
>     > +            sset_add(&od->snat_ips,
>     nat_entry->ext_addrs.ipv4_addrs[0].addr_s);
>     > +        } else {
>     > +            sset_add(&od->snat_ips,
>     nat_entry->ext_addrs.ipv6_addrs[0].addr_s);
>     >          }
>     >      }
>     >  }
>     > @@ -693,6 +735,7 @@ destroy_nat_entries(struct ovn_datapath *od)
>     >          return;
>     >      }
>     >
>     > +    sset_destroy(&od->snat_ips);
>     >      for (size_t i = 0; i < od->nbr->n_nat; i++) {
>     >          destroy_lport_addresses(&od->nat_entries[i].ext_addrs);
>     >      }
>     > @@ -8744,6 +8787,68 @@ build_lrouter_flows(struct hmap *datapaths,
>     struct
>     hmap *ports,
>     >                  op, lflows, &match, &actions);
>     >      }
>     >
>     > +    /* Drop IP traffic destined to router owned IPs. Part of it is
>     dropped
>     > +     * in stage "lr_in_ip_input" but traffic that could have been
>     unSNATed
>     > +     * but didn't match any existing session might still end up here.
>     > +     */
>     > +    HMAP_FOR_EACH (op, key_node, ports) {
>     > +        if (!op->nbrp) {
>     > +            continue;
>     > +        }
>     > +
>     > +        if (op->lrp_networks.n_ipv4_addrs) {
>     > +            ds_clear(&match);
>     > +            for (size_t i = 0; i < op->lrp_networks.n_ipv4_addrs;
>     i++) {
>     > +                if (!sset_find(&op->od->snat_ips,
>     > +                             
>      op->lrp_networks.ipv4_addrs[i].addr_s)) {
>     > +                    continue;
>     > +                }
>     > +                ds_put_format(&match, "%s, ",
>     > +                              op->lrp_networks.ipv4_addrs[i].addr_s);
>     > +            }
>     > +
>     > +            if (ds_last(&match) != EOF) {
>     > +                ds_chomp(&match, ' ');
>     > +                ds_chomp(&match, ',');
>     > +
>     > +                char *drop_match = xasprintf("ip4.dst == {%s}",
>     > +                                             ds_cstr(&match));
>     > +                /* Drop traffic with IP.dest == router-ip. */
>     > +                ovn_lflow_add_with_hint(lflows, op->od,
>     > +                                        S_ROUTER_IN_ARP_RESOLVE, 1,
>     > +                                        drop_match, "drop;",
>     > +                                        &op->nbrp->header_);
>     > +                free(drop_match);
>     > +            }
>     > +        }
>     > +
>     > +        if (op->lrp_networks.n_ipv6_addrs) {
>     > +            ds_clear(&match);
>     > +            for (size_t i = 0; i < op->lrp_networks.n_ipv6_addrs;
>     i++) {
>     > +                if (!sset_find(&op->od->snat_ips,
>     > +                             
>      op->lrp_networks.ipv6_addrs[i].addr_s)) {
>     > +                    continue;
>     > +                }
>     > +                ds_put_format(&match, "%s, ",
>     > +                              op->lrp_networks.ipv6_addrs[i].addr_s);
>     > +            }
>     > +
>     > +            if (ds_last(&match) != EOF) {
>     > +                ds_chomp(&match, ' ');
>     > +                ds_chomp(&match, ',');
>     > +
>     > +                char *drop_match = xasprintf("ip6.dst == {%s}",
>     > +                                             ds_cstr(&match));
>     > +                /* Drop traffic with IP.dest == router-ip. */
>     > +                ovn_lflow_add_with_hint(lflows, op->od,
>     > +                                        S_ROUTER_IN_ARP_RESOLVE, 1,
>     > +                                        drop_match, "drop;",
>     > +                                        &op->nbrp->header_);
>     > +                free(drop_match);
>     > +            }
>     > +        }
>     > +    }
>     > +
>     >      HMAP_FOR_EACH (od, key_node, datapaths) {
>     >          if (!od->nbr) {
>     >              continue;
>     > @@ -9035,77 +9140,15 @@ build_lrouter_flows(struct hmap *datapaths,
>     struct hmap *ports,
>     >          }
>     >
>     >          /* 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;
>     > -                }
>     > -            }
>     > -        }
>     > -
>     > +         * LBed traffic respectively to be SNATed. In addition, there
>     can be
>     > +         * a number of SNAT rules in the NAT table.
>     > +         * Skip all of them for drop flows. */
>     >          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) {
>     > +            if (sset_find(&op->od->snat_ips,
>     > +                          op->lrp_networks.ipv4_addrs[i].addr_s)) {
>     >                  continue;
>     >              }
>     >              ds_put_format(&match, "%s, ",
>     > @@ -9122,17 +9165,8 @@ build_lrouter_flows(struct hmap *datapaths,
>     struct
>     hmap *ports,
>     >          }
>     >
>     >          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) {
>     > +            if (sset_find(&op->od->snat_ips,
>     > +                          op->lrp_networks.ipv6_addrs[i].addr_s)) {
>     >                  continue;
>     >              }
>     >              ds_put_format(&match, "%s, ",
>     > @@ -9151,8 +9185,6 @@ build_lrouter_flows(struct hmap *datapaths,
>     struct
>     hmap *ports,
>     >                                      &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.
>     > diff --git a/tests/ovn.at <http://ovn.at> b/tests/ovn.at
>     <http://ovn.at>
>     > index a6f1fb5..cb7e7cc 100644
>     > --- a/tests/ovn.at <http://ovn.at>
>     > +++ b/tests/ovn.at <http://ovn.at>
>     > @@ -21659,6 +21659,94 @@ OVS_WAIT_UNTIL([test x$(as hv1 ovn-appctl -t
>     ovn-controller debug/status) = "xru
>     >  OVN_CLEANUP([hv1])
>     >  AT_CLEANUP
>     >
>     > +# Test dropping traffic destined to router owned IPs.
>     > +AT_SETUP([ovn -- gateway router drop traffic for own IPs])
>     > +ovn_start
>     > +
>     > +ovn-nbctl lr-add r1 -- set logical_router r1 options:chassis=hv1
>     > +ovn-nbctl ls-add s1
>     > +
>     > +# Connnect r1 to s1.
>     > +ovn-nbctl lrp-add r1 lrp-r1-s1 00:00:00:00:01:01 10.0.1.1/24
>     <http://10.0.1.1/24>
>     > +ovn-nbctl lsp-add s1 lsp-s1-r1 -- set Logical_Switch_Port lsp-s1-r1
>     type=router \
>     > +    options:router-port=lrp-r1-s1 addresses=router
>     > +
>     > +# Create logical port p1 in s1
>     > +ovn-nbctl lsp-add s1 p1 \
>     > +-- lsp-set-addresses p1 "f0:00:00:00:01:02 10.0.1.2"
>     > +
>     > +# Create two hypervisor and create OVS ports corresponding to logical
>     ports.
>     > +net_add n1
>     > +
>     > +sim_add hv1
>     > +as hv1
>     > +ovs-vsctl add-br br-phys
>     > +ovn_attach n1 br-phys 192.168.0.1
>     > +ovs-vsctl -- add-port br-int hv1-vif1 -- \
>     > +    set interface hv1-vif1 external-ids:iface-id=p1 \
>     > +    options:tx_pcap=hv1/vif1-tx.pcap \
>     > +    options:rxq_pcap=hv1/vif1-rx.pcap \
>     > +    ofport-request=1
>     > +
>     > +# Pre-populate the hypervisors' ARP tables so that we don't lose any
>     > +# packets for ARP resolution (native tunneling doesn't queue packets
>     > +# for ARP resolution).
>     > +OVN_POPULATE_ARP
>     > +
>     > +ovn-nbctl --wait=hv sync
>     > +
>     > +sw_key=$(ovn-sbctl --bare --columns tunnel_key list
>     datapath_binding r1)
>     > +
>     > +AT_CHECK([ovn-sbctl lflow-list | grep lr_in_arp_resolve | grep
>     10.0.1.1], [1], [])
>     > +
>     > +ip_to_hex() {
>     > +    printf "%02x%02x%02x%02x" "$@"
>     > +}
>     > +
>     > +# Send ip packets from p1 to lrp-r1-s1
>     > +src_mac="f00000000102"
>     > +dst_mac="000000000101"
>     > +src_ip=`ip_to_hex 10 0 1 2`
>     > +dst_ip=`ip_to_hex 10 0 1 1`
>     >
>     +packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
>     > +as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
>     > +
>     > +# No packet-ins should reach ovn-controller.
>     > +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep
>     "actions=controller"
>     | grep -v n_packets=0 -c], [1], [dnl
>     > +0
>     > +])
>     > +
>     > +# The packet should have been dropped in the lr_in_ip_input stage.
>     > +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep -E "table=11,
>     n_packets=1,.* priority=60,ip,metadata=0x${sw_key},nw_dst=10.0.1.1
>     actions=drop" -c], [0], [dnl
>     > +1
>     > +])
>     > +
>     > +# Use the router IP as SNAT IP.
>     > +ovn-nbctl set logical_router r1 options:lb_force_snat_ip=10.0.1.1
>     > +ovn-nbctl --wait=hv sync
>     > +
>     > +# Send ip packets from p1 to lrp-r1-s1
>     > +src_mac="f00000000102"
>     > +dst_mac="000000000101"
>     > +src_ip=`ip_to_hex 10 0 1 2`
>     > +dst_ip=`ip_to_hex 10 0 1 1`
>     >
>     +packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
>     > +as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
>     > +
>     > +# Even after configuring a router owned IP for SNAT, no
>     packet-ins should
>     > +# reach ovn-controller.
>     > +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep
>     "actions=controller"
>     | grep -v n_packets=0 -c], [1], [dnl
>     > +0
>     > +])
>     > +
>     > +# The packet should've been dropped in the lr_in_arp_resolve stage.
>     > +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep -E "table=21,
>     n_packets=1,.* priority=1,ip,metadata=0x${sw_key},nw_dst=10.0.1.1
>     actions=drop" -c], [0], [dnl
>     > +1
>     > +])
>     > +
>     > +OVN_CLEANUP([hv1])
>     > +AT_CLEANUP
>     > +
>     >  AT_SETUP([ovn -- nb_cfg timestamp])
>     >  ovn_start
>     >
>     >
>     > _______________________________________________
>     > dev mailing list
>     > dev@openvswitch.org <mailto:dev@openvswitch.org>
>     > https://mail.openvswitch.org/mailman/listinfo/ovs-dev
> 
>     Acked-by: Han Zhou <hzhou@ovn.org <mailto:hzhou@ovn.org>>
> 
> 
> Thanks Dumitru and Han. I applied this patch to master and branch-20.09.
> 
> The patch doesn't apply cleanly to branch-20.06. There were many
> conflicts and I didn't try resolving myself.
> Dumitru - please submit the backport patches to branch-20.06 (and
> branch-20.03 if you would like).
> 
> Thanks
> Numan
>  

Thanks! While this is a bug fix I don't think it's critical to have it
on branch-20.06/branch-20.03. Unless someone requires it on older
releases too I'd skip sending a backport patch for it.

Regards,
Dumitru
diff mbox series

Patch

diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
index bd42105..f1c7c9b 100644
--- a/northd/ovn-northd.8.xml
+++ b/northd/ovn-northd.8.xml
@@ -3089,6 +3089,30 @@  outport = <var>P</var>;
 
       <li>
         <p>
+          Traffic with IP destination an address owned by the router should be
+          dropped. Such traffic is normally dropped in ingress table
+          <code>IP Input</code> except for IPs that are also shared with SNAT
+          rules. However, if there was no unSNAT operation that happened
+          successfully until this point in the pipeline and the destination IP
+          of the packet is still a router owned IP, the packets can be safely
+          dropped.
+        </p>
+
+        <p>
+          A priority-1 logical flow with match <code>ip4.dst = {..}</code>
+          matches on traffic destined to router owned IPv4 addresses which are
+          also SNAT IPs. This flow has action <code>drop;</code>.
+        </p>
+
+        <p>
+          A priority-1 logical flow with match <code>ip6.dst = {..}</code>
+          matches on traffic destined to router owned IPv6 addresses which are
+          also SNAT IPs. This flow has action <code>drop;</code>.
+        </p>
+      </li>
+
+      <li>
+        <p>
           Dynamic MAC bindings.  These flows resolve MAC-to-IP bindings
           that have become known dynamically through ARP or neighbor
           discovery.  (The ingress table <code>ARP Request</code> will
diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
index cfec6a2..d5d7631 100644
--- a/northd/ovn-northd.c
+++ b/northd/ovn-northd.c
@@ -623,6 +623,9 @@  struct ovn_datapath {
     /* NAT entries configured on the router. */
     struct ovn_nat *nat_entries;
 
+    /* SNAT IPs used by the router. */
+    struct sset snat_ips;
+
     struct ovn_port **localnet_ports;
     size_t n_localnet_ports;
 
@@ -641,6 +644,10 @@  struct ovn_nat {
     struct lport_addresses ext_addrs;
 };
 
+static bool
+get_force_snat_ip(struct ovn_datapath *od, const char *key_type,
+                  struct lport_addresses *laddrs);
+
 /* Returns true if a 'nat_entry' is valid, i.e.:
  * - parsing was successful.
  * - the string yielded exactly one IPv4 address or exactly one IPv6 address.
@@ -663,7 +670,35 @@  nat_entry_is_v6(const struct ovn_nat *nat_entry)
 static void
 init_nat_entries(struct ovn_datapath *od)
 {
-    if (!od->nbr || od->nbr->n_nat == 0) {
+    struct lport_addresses snat_addrs;
+
+    if (!od->nbr) {
+        return;
+    }
+
+    sset_init(&od->snat_ips);
+    if (get_force_snat_ip(od, "dnat", &snat_addrs)) {
+        if (snat_addrs.n_ipv4_addrs) {
+            sset_add(&od->snat_ips, snat_addrs.ipv4_addrs[0].addr_s);
+        }
+        if (snat_addrs.n_ipv6_addrs) {
+            sset_add(&od->snat_ips, snat_addrs.ipv6_addrs[0].addr_s);
+        }
+        destroy_lport_addresses(&snat_addrs);
+    }
+
+    memset(&snat_addrs, 0, sizeof(snat_addrs));
+    if (get_force_snat_ip(od, "lb", &snat_addrs)) {
+        if (snat_addrs.n_ipv4_addrs) {
+            sset_add(&od->snat_ips, snat_addrs.ipv4_addrs[0].addr_s);
+        }
+        if (snat_addrs.n_ipv6_addrs) {
+            sset_add(&od->snat_ips, snat_addrs.ipv6_addrs[0].addr_s);
+        }
+        destroy_lport_addresses(&snat_addrs);
+    }
+
+    if (!od->nbr->n_nat) {
         return;
     }
 
@@ -682,6 +717,13 @@  init_nat_entries(struct ovn_datapath *od)
             VLOG_WARN_RL(&rl,
                          "Bad ip address %s in nat configuration "
                          "for router %s", nat->external_ip, od->nbr->name);
+            continue;
+        }
+
+        if (!nat_entry_is_v6(nat_entry)) {
+            sset_add(&od->snat_ips, nat_entry->ext_addrs.ipv4_addrs[0].addr_s);
+        } else {
+            sset_add(&od->snat_ips, nat_entry->ext_addrs.ipv6_addrs[0].addr_s);
         }
     }
 }
@@ -693,6 +735,7 @@  destroy_nat_entries(struct ovn_datapath *od)
         return;
     }
 
+    sset_destroy(&od->snat_ips);
     for (size_t i = 0; i < od->nbr->n_nat; i++) {
         destroy_lport_addresses(&od->nat_entries[i].ext_addrs);
     }
@@ -8744,6 +8787,68 @@  build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
                 op, lflows, &match, &actions);
     }
 
+    /* Drop IP traffic destined to router owned IPs. Part of it is dropped
+     * in stage "lr_in_ip_input" but traffic that could have been unSNATed
+     * but didn't match any existing session might still end up here.
+     */
+    HMAP_FOR_EACH (op, key_node, ports) {
+        if (!op->nbrp) {
+            continue;
+        }
+
+        if (op->lrp_networks.n_ipv4_addrs) {
+            ds_clear(&match);
+            for (size_t i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
+                if (!sset_find(&op->od->snat_ips,
+                               op->lrp_networks.ipv4_addrs[i].addr_s)) {
+                    continue;
+                }
+                ds_put_format(&match, "%s, ",
+                              op->lrp_networks.ipv4_addrs[i].addr_s);
+            }
+
+            if (ds_last(&match) != EOF) {
+                ds_chomp(&match, ' ');
+                ds_chomp(&match, ',');
+
+                char *drop_match = xasprintf("ip4.dst == {%s}",
+                                             ds_cstr(&match));
+                /* Drop traffic with IP.dest == router-ip. */
+                ovn_lflow_add_with_hint(lflows, op->od,
+                                        S_ROUTER_IN_ARP_RESOLVE, 1,
+                                        drop_match, "drop;",
+                                        &op->nbrp->header_);
+                free(drop_match);
+            }
+        }
+
+        if (op->lrp_networks.n_ipv6_addrs) {
+            ds_clear(&match);
+            for (size_t i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
+                if (!sset_find(&op->od->snat_ips,
+                               op->lrp_networks.ipv6_addrs[i].addr_s)) {
+                    continue;
+                }
+                ds_put_format(&match, "%s, ",
+                              op->lrp_networks.ipv6_addrs[i].addr_s);
+            }
+
+            if (ds_last(&match) != EOF) {
+                ds_chomp(&match, ' ');
+                ds_chomp(&match, ',');
+
+                char *drop_match = xasprintf("ip6.dst == {%s}",
+                                             ds_cstr(&match));
+                /* Drop traffic with IP.dest == router-ip. */
+                ovn_lflow_add_with_hint(lflows, op->od,
+                                        S_ROUTER_IN_ARP_RESOLVE, 1,
+                                        drop_match, "drop;",
+                                        &op->nbrp->header_);
+                free(drop_match);
+            }
+        }
+    }
+
     HMAP_FOR_EACH (od, key_node, datapaths) {
         if (!od->nbr) {
             continue;
@@ -9035,77 +9140,15 @@  build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
         }
 
         /* 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;
-                }
-            }
-        }
-
+         * LBed traffic respectively to be SNATed. In addition, there can be
+         * a number of SNAT rules in the NAT table.
+         * Skip all of them for drop flows. */
         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) {
+            if (sset_find(&op->od->snat_ips,
+                          op->lrp_networks.ipv4_addrs[i].addr_s)) {
                 continue;
             }
             ds_put_format(&match, "%s, ",
@@ -9122,17 +9165,8 @@  build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
         }
 
         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) {
+            if (sset_find(&op->od->snat_ips,
+                          op->lrp_networks.ipv6_addrs[i].addr_s)) {
                 continue;
             }
             ds_put_format(&match, "%s, ",
@@ -9151,8 +9185,6 @@  build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
                                     &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.
diff --git a/tests/ovn.at b/tests/ovn.at
index a6f1fb5..cb7e7cc 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -21659,6 +21659,94 @@  OVS_WAIT_UNTIL([test x$(as hv1 ovn-appctl -t ovn-controller debug/status) = "xru
 OVN_CLEANUP([hv1])
 AT_CLEANUP
 
+# Test dropping traffic destined to router owned IPs.
+AT_SETUP([ovn -- gateway router drop traffic for own IPs])
+ovn_start
+
+ovn-nbctl lr-add r1 -- set logical_router r1 options:chassis=hv1
+ovn-nbctl ls-add s1
+
+# Connnect r1 to s1.
+ovn-nbctl lrp-add r1 lrp-r1-s1 00:00:00:00:01:01 10.0.1.1/24
+ovn-nbctl lsp-add s1 lsp-s1-r1 -- set Logical_Switch_Port lsp-s1-r1 type=router \
+    options:router-port=lrp-r1-s1 addresses=router
+
+# Create logical port p1 in s1
+ovn-nbctl lsp-add s1 p1 \
+-- lsp-set-addresses p1 "f0:00:00:00:01:02 10.0.1.2"
+
+# Create two hypervisor and create OVS ports corresponding to logical ports.
+net_add n1
+
+sim_add hv1
+as hv1
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.1
+ovs-vsctl -- add-port br-int hv1-vif1 -- \
+    set interface hv1-vif1 external-ids:iface-id=p1 \
+    options:tx_pcap=hv1/vif1-tx.pcap \
+    options:rxq_pcap=hv1/vif1-rx.pcap \
+    ofport-request=1
+
+# Pre-populate the hypervisors' ARP tables so that we don't lose any
+# packets for ARP resolution (native tunneling doesn't queue packets
+# for ARP resolution).
+OVN_POPULATE_ARP
+
+ovn-nbctl --wait=hv sync
+
+sw_key=$(ovn-sbctl --bare --columns tunnel_key list datapath_binding r1)
+
+AT_CHECK([ovn-sbctl lflow-list | grep lr_in_arp_resolve | grep 10.0.1.1], [1], [])
+
+ip_to_hex() {
+    printf "%02x%02x%02x%02x" "$@"
+}
+
+# Send ip packets from p1 to lrp-r1-s1
+src_mac="f00000000102"
+dst_mac="000000000101"
+src_ip=`ip_to_hex 10 0 1 2`
+dst_ip=`ip_to_hex 10 0 1 1`
+packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
+as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
+
+# No packet-ins should reach ovn-controller.
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep "actions=controller" | grep -v n_packets=0 -c], [1], [dnl
+0
+])
+
+# The packet should have been dropped in the lr_in_ip_input stage.
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep -E "table=11, n_packets=1,.* priority=60,ip,metadata=0x${sw_key},nw_dst=10.0.1.1 actions=drop" -c], [0], [dnl
+1
+])
+
+# Use the router IP as SNAT IP.
+ovn-nbctl set logical_router r1 options:lb_force_snat_ip=10.0.1.1
+ovn-nbctl --wait=hv sync
+
+# Send ip packets from p1 to lrp-r1-s1
+src_mac="f00000000102"
+dst_mac="000000000101"
+src_ip=`ip_to_hex 10 0 1 2`
+dst_ip=`ip_to_hex 10 0 1 1`
+packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
+as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
+
+# Even after configuring a router owned IP for SNAT, no packet-ins should
+# reach ovn-controller.
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep "actions=controller" | grep -v n_packets=0 -c], [1], [dnl
+0
+])
+
+# The packet should've been dropped in the lr_in_arp_resolve stage.
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep -E "table=21, n_packets=1,.* priority=1,ip,metadata=0x${sw_key},nw_dst=10.0.1.1 actions=drop" -c], [0], [dnl
+1
+])
+
+OVN_CLEANUP([hv1])
+AT_CLEANUP
+
 AT_SETUP([ovn -- nb_cfg timestamp])
 ovn_start