[ovs-dev] [PATCH ovn v8 6/6] northd: Flood ARPs to routers for "unreachable" addresses.

Krzysztof Klimonda kklimonda at syntaxhighlighted.com
Wed Jun 23 13:00:43 UTC 2021


Hi Mark,

Would it be possible to install /32 IP on the router to avoid flooding all router ports? This can be hundreds if not thousands of ports and I think I've seen issues with flooding at that scale before. I'd have to test it in the lab though first.

Best Regards,
Krzysztof

On Thu, Jun 3, 2021, at 20:49, Mark Michelson wrote:
> Previously, ARP TPAs were filtered down only to "reachable" addresses.
> Reachable addresses are all router interface addresses, as well as NAT
> external addresses and load balancer VIPs that are within the subnet
> handled by a router's port.
> 
> However, it is possible that in some configurations, CMSes purposely
> configure NAT or load balancer addresses on a router that are outside
> the router's subnets, and they expect the router to respond to ARPs for
> those addresses.
> 
> This commit adds a higher priority flow to logical switches that makes
> it so ARPs targeted at "unreachable" addresses are flooded to all ports.
> This way, the ARPs can reach the router appropriately and receive a
> response.
> 
> Reported at: https://bugzilla.redhat.com/show_bug.cgi?id=1929901
> 
> Signed-off-by: Mark Michelson <mmichels at redhat.com>
> ---
>  northd/ovn-northd.8.xml |   8 +++
>  northd/ovn-northd.c     | 153 +++++++++++++++++++++++++++-------------
>  northd/ovn_northd.dl    | 101 ++++++++++++++++++++------
>  tests/ovn-northd.at     |  99 ++++++++++++++++++++++++++
>  tests/system-ovn.at     | 102 +++++++++++++++++++++++++++
>  5 files changed, 391 insertions(+), 72 deletions(-)
> 
> diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
> index bb77689de..8bb77bf6c 100644
> --- a/northd/ovn-northd.8.xml
> +++ b/northd/ovn-northd.8.xml
> @@ -1549,6 +1549,14 @@ output;
>          logical ports.
>        </li>
>  
> +      <li>
> +        Priority-80 flows for each IP address/VIP/NAT address 
> configured
> +        outside its owning router port's subnet. These flows match ARP
> +        requests and ND packets for the specific IP addresses.  
> Matched packets
> +        are forwarded to the <code>MC_FLOOD</code> multicast group 
> which
> +        contains all connected logical ports.
> +      </li>
> +
>        <li>
>          Priority-75 flows for each port connected to a logical router
>          matching self originated ARP request/ND packets.  These packets
> diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
> index 414bf9c48..eacbab96a 100644
> --- a/northd/ovn-northd.c
> +++ b/northd/ovn-northd.c
> @@ -6539,44 +6539,48 @@ 
> build_lswitch_rport_arp_req_self_orig_flow(struct ovn_port *op,
>      ds_destroy(&match);
>  }
>  
> -/*
> - * Ingress table 19: Flows that forward ARP/ND requests only to the routers
> - * that own the addresses. Other ARP/ND packets are still flooded in the
> - * switching domain as regular broadcast.
> - */
>  static void
> -build_lswitch_rport_arp_req_flow_for_ip(struct sset *ips,
> -                                        int addr_family,
> -                                        struct ovn_port *patch_op,
> -                                        struct ovn_datapath *od,
> -                                        uint32_t priority,
> -                                        struct hmap *lflows,
> -                                        const struct ovsdb_idl_row *stage_hint)
> +arp_nd_ns_match(struct sset *ips, int addr_family, struct ds *match)
>  {
> -    struct ds match   = DS_EMPTY_INITIALIZER;
> -    struct ds actions = DS_EMPTY_INITIALIZER;
>  
>      /* Packets received from VXLAN tunnels have already been through the
>       * router pipeline so we should skip them. Normally this is done by the
>       * multicast_group implementation (VXLAN packets skip table 32 which
>       * delivers to patch ports) but we're bypassing multicast_groups.
>       */
> -    ds_put_cstr(&match, FLAGBIT_NOT_VXLAN " && ");
> +    ds_put_cstr(match, FLAGBIT_NOT_VXLAN " && ");
>  
>      if (addr_family == AF_INET) {
> -        ds_put_cstr(&match, "arp.op == 1 && arp.tpa == { ");
> +        ds_put_cstr(match, "arp.op == 1 && arp.tpa == {");
>      } else {
> -        ds_put_cstr(&match, "nd_ns && nd.target == { ");
> +        ds_put_cstr(match, "nd_ns && nd.target == {");
>      }
>  
>      const char *ip_address;
>      SSET_FOR_EACH (ip_address, ips) {
> -        ds_put_format(&match, "%s, ", ip_address);
> +        ds_put_format(match, "%s, ", ip_address);
>      }
>  
> -    ds_chomp(&match, ' ');
> -    ds_chomp(&match, ',');
> -    ds_put_cstr(&match, "}");
> +    ds_chomp(match, ' ');
> +    ds_chomp(match, ',');
> +    ds_put_cstr(match, "}");
> +}
> +
> +/*
> + * Ingress table 19: Flows that forward ARP/ND requests only to the routers
> + * that own the addresses. Other ARP/ND packets are still flooded in the
> + * switching domain as regular broadcast.
> + */
> +static void
> +build_lswitch_rport_arp_req_flow_for_reachable_ip(struct sset *ips,
> +    int addr_family, struct ovn_port *patch_op, struct ovn_datapath *od,
> +    uint32_t priority, struct hmap *lflows,
> +    const struct ovsdb_idl_row *stage_hint)
> +{
> +    struct ds match   = DS_EMPTY_INITIALIZER;
> +    struct ds actions = DS_EMPTY_INITIALIZER;
> +
> +    arp_nd_ns_match(ips, addr_family, &match);
>  
>      /* Send a the packet to the router pipeline.  If the switch has non-router
>       * ports then flood it there as well.
> @@ -6599,6 +6603,30 @@ build_lswitch_rport_arp_req_flow_for_ip(struct sset *ips,
>      ds_destroy(&actions);
>  }
>  
> +/*
> + * Ingress table 19: Flows that forward ARP/ND requests for "unreachable" IPs
> + * (NAT or load balancer IPs configured on a router that are outside the
> + * router's configured subnets).
> + * These ARP/ND packets are flooded in the switching domain as regular
> + * broadcast.
> + */
> +static void
> +build_lswitch_rport_arp_req_flow_for_unreachable_ip(struct sset *ips,
> +    int addr_family, struct ovn_datapath *od, uint32_t priority,
> +    struct hmap *lflows, const struct ovsdb_idl_row *stage_hint)
> +{
> +    struct ds match = DS_EMPTY_INITIALIZER;
> +
> +    arp_nd_ns_match(ips, addr_family, &match);
> +
> +    ovn_lflow_add_unique_with_hint(lflows, od, S_SWITCH_IN_L2_LKUP,
> +                                   priority, ds_cstr(&match),
> +                                   "outport = \""MC_FLOOD"\"; output;",
> +                                   stage_hint);
> +
> +    ds_destroy(&match);
> +}
> +
>  /*
>   * Ingress table 19: Flows that forward ARP/ND requests only to the routers
>   * that own the addresses.
> @@ -6625,39 +6653,48 @@ build_lswitch_rport_arp_req_flows(struct ovn_port *op,
>       * router port.
>       * Priority: 80.
>       */
> -    struct sset all_ips_v4 = SSET_INITIALIZER(&all_ips_v4);
> -    struct sset all_ips_v6 = SSET_INITIALIZER(&all_ips_v6);
> +    struct sset lb_ips_v4 = SSET_INITIALIZER(&lb_ips_v4);
> +    struct sset lb_ips_v6 = SSET_INITIALIZER(&lb_ips_v6);
>  
> -    get_router_load_balancer_ips(op->od, false, &all_ips_v4, &all_ips_v6);
> +    get_router_load_balancer_ips(op->od, false, &lb_ips_v4, &lb_ips_v6);
> +
> +    struct sset reachable_ips_v4 = SSET_INITIALIZER(&reachable_ips_v4);
> +    struct sset reachable_ips_v6 = SSET_INITIALIZER(&reachable_ips_v6);
> +    struct sset unreachable_ips_v4 = SSET_INITIALIZER(&unreachable_ips_v4);
> +    struct sset unreachable_ips_v6 = SSET_INITIALIZER(&unreachable_ips_v6);
>  
>      const char *ip_addr;
>      const char *ip_addr_next;
> -    SSET_FOR_EACH_SAFE (ip_addr, ip_addr_next, &all_ips_v4) {
> +    SSET_FOR_EACH_SAFE (ip_addr, ip_addr_next, &lb_ips_v4) {
>          ovs_be32 ipv4_addr;
>  
>          /* Check if the ovn port has a network configured on which we could
>           * expect ARP requests for the LB VIP.
>           */
> -        if (ip_parse(ip_addr, &ipv4_addr) &&
> -                lrouter_port_ipv4_reachable(op, ipv4_addr)) {
> -            continue;
> +        if (ip_parse(ip_addr, &ipv4_addr)) {
> +            if (lrouter_port_ipv4_reachable(op, ipv4_addr)) {
> +                sset_add(&reachable_ips_v4, ip_addr);
> +            } else {
> +                sset_add(&unreachable_ips_v4, ip_addr);
> +            }
>          }
> -
> -        sset_delete(&all_ips_v4, SSET_NODE_FROM_NAME(ip_addr));
>      }
> -    SSET_FOR_EACH_SAFE (ip_addr, ip_addr_next, &all_ips_v6) {
> +    SSET_FOR_EACH_SAFE (ip_addr, ip_addr_next, &lb_ips_v6) {
>          struct in6_addr ipv6_addr;
>  
>          /* Check if the ovn port has a network configured on which we could
>           * expect NS requests for the LB VIP.
>           */
> -        if (ipv6_parse(ip_addr, &ipv6_addr) &&
> -                lrouter_port_ipv6_reachable(op, &ipv6_addr)) {
> -            continue;
> +        if (ipv6_parse(ip_addr, &ipv6_addr)) {
> +            if (lrouter_port_ipv6_reachable(op, &ipv6_addr)) {
> +                sset_add(&reachable_ips_v6, ip_addr);
> +            } else {
> +                sset_add(&unreachable_ips_v6, ip_addr);
> +            }
>          }
> -
> -        sset_delete(&all_ips_v6, SSET_NODE_FROM_NAME(ip_addr));
>      }
> +    sset_destroy(&lb_ips_v4);
> +    sset_destroy(&lb_ips_v6);
>  
>      for (size_t i = 0; i < op->od->nbr->n_nat; i++) {
>          struct ovn_nat *nat_entry = &op->od->nat_entries[i];
> @@ -6678,37 +6715,53 @@ build_lswitch_rport_arp_req_flows(struct ovn_port *op,
>              struct in6_addr *addr = &nat_entry->ext_addrs.ipv6_addrs[0].addr;
>  
>              if (lrouter_port_ipv6_reachable(op, addr)) {
> -                sset_add(&all_ips_v6, nat->external_ip);
> +                sset_add(&reachable_ips_v6, nat->external_ip);
> +            } else {
> +                sset_add(&unreachable_ips_v6, nat->external_ip);
>              }
>          } else {
>              ovs_be32 addr = nat_entry->ext_addrs.ipv4_addrs[0].addr;
>  
>              if (lrouter_port_ipv4_reachable(op, addr)) {
> -                sset_add(&all_ips_v4, nat->external_ip);
> +                sset_add(&reachable_ips_v4, nat->external_ip);
> +            } else {
> +                sset_add(&unreachable_ips_v4, nat->external_ip);
>              }
>          }
>      }
>  
>      for (size_t i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
> -        sset_add(&all_ips_v4, op->lrp_networks.ipv4_addrs[i].addr_s);
> +        sset_add(&reachable_ips_v4, op->lrp_networks.ipv4_addrs[i].addr_s);
>      }
>      for (size_t i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
> -        sset_add(&all_ips_v6, op->lrp_networks.ipv6_addrs[i].addr_s);
> +        sset_add(&reachable_ips_v6, op->lrp_networks.ipv6_addrs[i].addr_s);
>      }
>  
> -    if (!sset_is_empty(&all_ips_v4)) {
> -        build_lswitch_rport_arp_req_flow_for_ip(&all_ips_v4, AF_INET, sw_op,
> -                                                sw_od, 80, lflows,
> -                                                stage_hint);
> +    if (!sset_is_empty(&reachable_ips_v4)) {
> +        build_lswitch_rport_arp_req_flow_for_reachable_ip(&reachable_ips_v4,
> +                                                          AF_INET, sw_op,
> +                                                          sw_od, 80, lflows,
> +                                                          stage_hint);
> +    }
> +    if (!sset_is_empty(&reachable_ips_v6)) {
> +        build_lswitch_rport_arp_req_flow_for_reachable_ip(&reachable_ips_v6,
> +                                                          AF_INET6, sw_op,
> +                                                          sw_od, 80, lflows,
> +                                                          stage_hint);
>      }
> -    if (!sset_is_empty(&all_ips_v6)) {
> -        build_lswitch_rport_arp_req_flow_for_ip(&all_ips_v6, AF_INET6, sw_op,
> -                                                sw_od, 80, lflows,
> -                                                stage_hint);
> +    if (!sset_is_empty(&unreachable_ips_v4)) {
> +        build_lswitch_rport_arp_req_flow_for_unreachable_ip(
> +            &unreachable_ips_v4, AF_INET, sw_od, 90, lflows, stage_hint);
> +    }
> +    if (!sset_is_empty(&unreachable_ips_v6)) {
> +        build_lswitch_rport_arp_req_flow_for_unreachable_ip(
> +            &unreachable_ips_v6, AF_INET6, sw_od, 90, lflows, stage_hint);
>      }
>  
> -    sset_destroy(&all_ips_v4);
> -    sset_destroy(&all_ips_v6);
> +    sset_destroy(&reachable_ips_v4);
> +    sset_destroy(&reachable_ips_v6);
> +    sset_destroy(&unreachable_ips_v4);
> +    sset_destroy(&unreachable_ips_v6);
>  
>      /* Self originated ARP requests/ND need to be flooded as usual.
>       *
> diff --git a/northd/ovn_northd.dl b/northd/ovn_northd.dl
> index 794b86ee1..1061fae8b 100644
> --- a/northd/ovn_northd.dl
> +++ b/northd/ovn_northd.dl
> @@ -4103,9 +4103,13 @@ UniqueFlow[Flow{.logical_datapath = sw._uuid,
>   * router port.
>   * Priority: 80.
>   */
> -function get_arp_forward_ips(rp: Intern<RouterPort>): (Set<string>, 
> Set<string>) = {
> -    var all_ips_v4 = set_empty();
> -    var all_ips_v6 = set_empty();
> +function get_arp_forward_ips(rp: Intern<RouterPort>):
> +    (Set<string>, Set<string>, Set<string>, Set<string>) =
> +{
> +    var reachable_ips_v4 = set_empty();
> +    var reachable_ips_v6 = set_empty();
> +    var unreachable_ips_v4 = set_empty();
> +    var unreachable_ips_v6 = set_empty();
>  
>      (var lb_ips_v4, var lb_ips_v6)
>          = get_router_load_balancer_ips(rp.router, false);
> @@ -4115,7 +4119,9 @@ function get_arp_forward_ips(rp: 
> Intern<RouterPort>): (Set<string>, Set<string>)
>           */
>          match (ip_parse(a)) {
>              Some{ipv4} -> if (lrouter_port_ip_reachable(rp, 
> IPv4{ipv4})) {
> -                all_ips_v4.insert(a)
> +                reachable_ips_v4.insert(a)
> +            } else {
> +                unreachable_ips_v4.insert(a)
>              },
>              _ -> ()
>          }
> @@ -4126,7 +4132,9 @@ function get_arp_forward_ips(rp: 
> Intern<RouterPort>): (Set<string>, Set<string>)
>           */
>          match (ipv6_parse(a)) {
>              Some{ipv6} -> if (lrouter_port_ip_reachable(rp, 
> IPv6{ipv6})) {
> -                all_ips_v6.insert(a)
> +                reachable_ips_v6.insert(a)
> +            } else {
> +                unreachable_ips_v6.insert(a)
>              },
>              _ -> ()
>          }
> @@ -4139,22 +4147,45 @@ function get_arp_forward_ips(rp: 
> Intern<RouterPort>): (Set<string>, Set<string>)
>               */
>              if (lrouter_port_ip_reachable(rp, nat.external_ip)) {
>                  match (nat.external_ip) {
> -                    IPv4{_} -> all_ips_v4.insert(nat.nat.external_ip),
> -                    IPv6{_} -> all_ips_v6.insert(nat.nat.external_ip)
> +                    IPv4{_} -> 
> reachable_ips_v4.insert(nat.nat.external_ip),
> +                    IPv6{_} -> 
> reachable_ips_v6.insert(nat.nat.external_ip)
> +                }
> +            } else {
> +                match (nat.external_ip) {
> +                    IPv4{_} -> 
> unreachable_ips_v4.insert(nat.nat.external_ip),
> +                    IPv6{_} -> 
> unreachable_ips_v6.insert(nat.nat.external_ip),
>                  }
>              }
>          }
>      };
>  
>      for (a in rp.networks.ipv4_addrs) {
> -        all_ips_v4.insert("${a.addr}")
> +        reachable_ips_v4.insert("${a.addr}")
>      };
>      for (a in rp.networks.ipv6_addrs) {
> -        all_ips_v6.insert("${a.addr}")
> +        reachable_ips_v6.insert("${a.addr}")
>      };
>  
> -    (all_ips_v4, all_ips_v6)
> +    (reachable_ips_v4, reachable_ips_v6, unreachable_ips_v4, 
> unreachable_ips_v6)
>  }
> +
> +relation &SwitchPortARPForwards(
> +    port: Intern<SwitchPort>,
> +    reachable_ips_v4: Set<string>,
> +    reachable_ips_v6: Set<string>,
> +    unreachable_ips_v4: Set<string>,
> +    unreachable_ips_v6: Set<string>
> +)
> +
> +&SwitchPortARPForwards(.port = port,
> +                       .reachable_ips_v4 = reachable_ips_v4,
> +                       .reachable_ips_v6 = reachable_ips_v6,
> +                       .unreachable_ips_v4 = unreachable_ips_v4,
> +                       .unreachable_ips_v6 = unreachable_ips_v6) :-
> +    port in &SwitchPort(.peer = Some{rp}),
> +    rp.is_enabled(),
> +    (var reachable_ips_v4, var reachable_ips_v6, var 
> unreachable_ips_v4, var unreachable_ips_v6) = get_arp_forward_ips(rp).
> +
>  /* Packets received from VXLAN tunnels have already been through the
>   * router pipeline so we should skip them. Normally this is done by the
>   * multicast_group implementation (VXLAN packets skip table 32 which
> @@ -4165,8 +4196,8 @@ AnnotatedFlow(.f = Flow{.logical_datapath = 
> sw._uuid,
>                          .stage            = s_SWITCH_IN_L2_LKUP(),
>                          .priority         = 80,
>                          .__match          = fLAGBIT_NOT_VXLAN() ++
> -                                            " && arp.op == 1 && 
> arp.tpa == { " ++
> -                                            
> all_ips_v4.to_vec().join(", ") ++ "}",
> +                                            " && arp.op == 1 && 
> arp.tpa == {" ++
> +                                            ipv4.to_vec().join(", ") 
> ++ "}",
>                          .actions          = if 
> (sw.has_non_router_port) {
>                                                  "clone {outport = 
> ${sp.json_name}; output; }; "
>                                                  "outport = 
> ${mc_flood_l2}; output;"
> @@ -4175,17 +4206,16 @@ AnnotatedFlow(.f = Flow{.logical_datapath = 
> sw._uuid,
>                                              },
>                          .external_ids     = stage_hint(sp.lsp._uuid)},
>                .shared = not sw.has_non_router_port) :-
> -    sp in &SwitchPort(.sw = sw, .peer = Some{rp}),
> -    rp.is_enabled(),
> -    (var all_ips_v4, _) = get_arp_forward_ips(rp),
> -    not all_ips_v4.is_empty(),
> +    sp in &SwitchPort(.sw = sw),
> +    &SwitchPortARPForwards(.port = sp, .reachable_ips_v4 = ipv4),
> +    not ipv4.is_empty(),
>      var mc_flood_l2 = json_string_escape(mC_FLOOD_L2().0).
>  AnnotatedFlow(.f = Flow{.logical_datapath = sw._uuid,
>                          .stage            = s_SWITCH_IN_L2_LKUP(),
>                          .priority         = 80,
>                          .__match          = fLAGBIT_NOT_VXLAN() ++
> -                                            " && nd_ns && nd.target == 
> { " ++
> -                                            
> all_ips_v6.to_vec().join(", ") ++ "}",
> +                                            " && nd_ns && nd.target == 
> {" ++
> +                                            ipv6.to_vec().join(", ") 
> ++ "}",
>                          .actions          = if 
> (sw.has_non_router_port) {
>                                                  "clone {outport = 
> ${sp.json_name}; output; }; "
>                                                  "outport = 
> ${mc_flood_l2}; output;"
> @@ -4194,12 +4224,39 @@ AnnotatedFlow(.f = Flow{.logical_datapath = 
> sw._uuid,
>                                              },
>                          .external_ids     = stage_hint(sp.lsp._uuid)},
>                .shared = not sw.has_non_router_port) :-
> -    sp in &SwitchPort(.sw = sw, .peer = Some{rp}),
> -    rp.is_enabled(),
> -    (_, var all_ips_v6) = get_arp_forward_ips(rp),
> -    not all_ips_v6.is_empty(),
> +    sp in &SwitchPort(.sw = sw),
> +    &SwitchPortARPForwards(.port = sp, .reachable_ips_v6 = ipv6),
> +    not ipv6.is_empty(),
>      var mc_flood_l2 = json_string_escape(mC_FLOOD_L2().0).
>  
> +AnnotatedFlow(.f = Flow{.logical_datapath = sw._uuid,
> +                        .stage            = s_SWITCH_IN_L2_LKUP(),
> +                        .priority         = 90,
> +                        .__match          = fLAGBIT_NOT_VXLAN() ++
> +                                            " && arp.op == 1 && 
> arp.tpa == {" ++
> +                                            ipv4.to_vec().join(", ") 
> ++ "}",
> +                        .actions          = "outport = ${flood}; 
> output;",
> +                        .external_ids     = stage_hint(sp.lsp._uuid)},
> +              .shared = not sw.has_non_router_port) :-
> +    sp in &SwitchPort(.sw = sw),
> +    &SwitchPortARPForwards(.port = sp, .unreachable_ips_v4 = ipv4),
> +    not ipv4.is_empty(),
> +    var flood = json_string_escape(mC_FLOOD().0).
> +
> +AnnotatedFlow(.f = Flow{.logical_datapath = sw._uuid,
> +                        .stage            = s_SWITCH_IN_L2_LKUP(),
> +                        .priority         = 90,
> +                        .__match          = fLAGBIT_NOT_VXLAN() ++
> +                                            " && nd_ns && nd.target == 
> {" ++
> +                                            ipv6.to_vec().join(", ") 
> ++ "}",
> +                        .actions          = "outport = ${flood}; 
> output;",
> +                        .external_ids     = stage_hint(sp.lsp._uuid)},
> +              .shared = not sw.has_non_router_port) :-
> +    sp in &SwitchPort(.sw = sw),
> +    &SwitchPortARPForwards(.port = sp, .unreachable_ips_v6 = ipv6),
> +    not ipv6.is_empty(),
> +    var flood = json_string_escape(mC_FLOOD().0).
> +
>  for (SwitchPortNewDynamicAddress(.port = &SwitchPort{.lsp = lsp, 
> .json_name = json_name, .sw = sw},
>                                   .address = Some{addrs})
>       if lsp.__type != "external") {
> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
> index 9e4f2ca62..13ead49ba 100644
> --- a/tests/ovn-northd.at
> +++ b/tests/ovn-northd.at
> @@ -3840,3 +3840,102 @@ check ovn-nbctl --wait=sb clear 
> logical_router_port ro2-sw ha_chassis_group
>  check_lflows 0
>  
>  AT_CLEANUP
> +
> +OVN_FOR_EACH_NORTHD([
> +AT_SETUP([ovn -- ARP flood for unreachable addresses])
> +ovn_start
> +
> +AS_BOX([Setting up the logical network])
> +
> +# This network is the same as the one from "Router Address Propagation"
> +check ovn-nbctl ls-add sw
> +
> +check ovn-nbctl lr-add ro1
> +check ovn-nbctl lrp-add ro1 ro1-sw 00:00:00:00:00:01 10.0.0.1/24
> +check ovn-nbctl lsp-add sw sw-ro1
> +check ovn-nbctl lsp-set-type sw-ro1 router
> +check ovn-nbctl lsp-set-addresses sw-ro1 router
> +check ovn-nbctl lsp-set-options sw-ro1 router-port=ro1-sw
> +
> +check ovn-nbctl lr-add ro2
> +check ovn-nbctl lrp-add ro2 ro2-sw 00:00:00:00:00:02 20.0.0.1/24
> +check ovn-nbctl lsp-add sw sw-ro2
> +check ovn-nbctl lsp-set-type sw-ro2 router
> +check ovn-nbctl lsp-set-addresses sw-ro2 router
> +check ovn-nbctl --wait=sb lsp-set-options sw-ro2 router-port=ro2-sw
> +
> +check ovn-nbctl ls-add ls1
> +check ovn-nbctl lsp-add ls1 vm1
> +check ovn-nbctl lsp-set-addresses vm1 "00:00:00:00:01:02 192.168.1.2"
> +check ovn-nbctl lrp-add ro1 ro1-ls1 00:00:00:00:01:01 192.168.1.1/24
> +check ovn-nbctl lsp-add ls1 ls1-ro1
> +check ovn-nbctl lsp-set-type ls1-ro1 router
> +check ovn-nbctl lsp-set-addresses ls1-ro1 router
> +check ovn-nbctl lsp-set-options ls1-ro1 router-port=ro1-ls1
> +
> +check ovn-nbctl ls-add ls2
> +check ovn-nbctl lsp-add ls2 vm2
> +check ovn-nbctl lsp-set-addresses vm2 "00:00:00:00:02:02 192.168.2.2"
> +check ovn-nbctl lrp-add ro2 ro2-ls2 00:00:00:00:02:01 192.168.2.1/24
> +check ovn-nbctl lsp-add ls2 ls2-ro2
> +check ovn-nbctl lsp-set-type ls2-ro2 router
> +check ovn-nbctl lsp-set-addresses ls2-ro2 router
> +check ovn-nbctl lsp-set-options ls2-ro2 router-port=ro2-ls2
> +
> +AS_BOX([Ensure that unreachable flood flows are not installed, since 
> no addresses are unreachable])
> +
> +AT_CHECK([ovn-sbctl lflow-list sw | grep "ls_in_l2_lkup" | grep 
> "priority=90" -c], [1], [dnl
> +0
> +])
> +
> +AS_BOX([Adding some reachable NAT addresses])
> +
> +check ovn-nbctl lr-nat-add ro1 dnat 10.0.0.100 192.168.1.100
> +check ovn-nbctl lr-nat-add ro1 snat 10.0.0.200 192.168.1.200/30
> +
> +check ovn-nbctl lr-nat-add ro2 dnat 20.0.0.100 192.168.2.100
> +check ovn-nbctl --wait=sb lr-nat-add ro2 snat 20.0.0.200 
> 192.168.2.200/30
> +
> +AS_BOX([Ensure that unreachable flood flows are not installed, since 
> all addresses are reachable])
> +
> +AT_CHECK([ovn-sbctl lflow-list sw | grep "ls_in_l2_lkup" | grep 
> "priority=90" -c], [1], [dnl
> +0
> +])
> +
> +AS_BOX([Adding some unreachable NAT addresses])
> +
> +check ovn-nbctl lr-nat-add ro1 dnat 30.0.0.100 192.168.1.130
> +check ovn-nbctl lr-nat-add ro1 snat 30.0.0.200 192.168.1.148/30
> +
> +check ovn-nbctl lr-nat-add ro2 dnat 40.0.0.100 192.168.2.130
> +check ovn-nbctl --wait=sb lr-nat-add ro2 snat 40.0.0.200 
> 192.168.2.148/30
> +
> +AS_BOX([Ensure that unreachable flood flows are installed, since there 
> are unreachable addresses])
> +
> +ovn-sbctl lflow-list
> +
> +# We expect two flows to be installed, one per connected router port 
> on sw
> +AT_CHECK([ovn-sbctl lflow-list sw | grep ls_in_l2_lkup | grep 
> priority=90 -c], [0], [dnl
> +2
> +])
> +
> +# We expect that the installed flows will match the unreachable DNAT 
> addresses only.
> +AT_CHECK([ovn-sbctl lflow-list sw | grep ls_in_l2_lkup | grep 
> priority=90 | grep "arp.tpa == {30.0.0.100}" -c], [0], [dnl
> +1
> +])
> +
> +AT_CHECK([ovn-sbctl lflow-list sw | grep ls_in_l2_lkup | grep 
> priority=90 | grep "arp.tpa == {40.0.0.100}" -c], [0], [dnl
> +1
> +])
> +
> +# Ensure that we do not create flows for SNAT addresses
> +AT_CHECK([ovn-sbctl lflow-list sw | grep ls_in_l2_lkup | grep 
> priority=90 | grep "arp.tpa == {30.0.0.200}" -c], [1], [dnl
> +0
> +])
> +
> +AT_CHECK([ovn-sbctl lflow-list sw | grep ls_in_l2_lkup | grep 
> priority=90 | grep "arp.tpa == {40.0.0.200}" -c], [1], [dnl
> +0
> +])
> +
> +AT_CLEANUP
> +])
> diff --git a/tests/system-ovn.at b/tests/system-ovn.at
> index 3824788c4..6539a66cb 100644
> --- a/tests/system-ovn.at
> +++ b/tests/system-ovn.at
> @@ -6293,3 +6293,105 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/.*error 
> receiving.*/d
>  
>  AT_CLEANUP
>  ])
> +
> +OVN_FOR_EACH_NORTHD([
> +AT_SETUP([ovn -- Floating IP outside router subnet IPv4])
> +AT_KEYWORDS(NAT)
> +
> +ovn_start
> +
> +OVS_TRAFFIC_VSWITCHD_START()
> +ADD_BR([br-int])
> +
> +# Set external-ids in br-int needed for ovn-controller
> +ovs-vsctl \
> +        -- set Open_vSwitch . external-ids:system-id=hv1 \
> +        -- set Open_vSwitch . 
> external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
> +        -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
> +        -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
> +        -- set bridge br-int fail-mode=secure 
> other-config:disable-in-band=true
> +
> +start_daemon ovn-controller
> +
> +# Logical network:
> +# Two VMs
> +#   * VM1 with IP address 192.168.100.5
> +#   * VM2 with IP address 192.168.200.5
> +#
> +# VM1 connects to logical switch ls1. ls1 connects to logical router 
> lr1.
> +# VM2 connects to logical switch ls2. ls2 connects to logical router 
> lr2.
> +# lr1 and lr2 both connect to logical switch ls-pub.
> +# * lr1's interface that connects to ls-pub has IP address 
> 172.18.2.110/24
> +# * lr2's interface that connects to ls-pub has IP address 
> 172.18.1.173/24
> +#
> +# lr1 has the following attributes:
> +#   * It has a DNAT rule that translates 172.18.2.11 to 192.168.100.5 
> (VM1)
> +#
> +# lr2 has the following attributes:
> +#   * It has a DNAT rule that translates 172.18.2.12 to 192.168.200.5 
> (VM2)
> +#
> +# In this test, we want to ensure that a ping from VM1 to IP address 
> 172.18.2.12 reaches VM2.
> +# When the NAT rules are set up, there should be MAC_Bindings created 
> that allow for traffic
> +# to exit lr1, go through ls-pub, and reach the NAT external IP 
> configured on lr2.
> +
> +check ovn-nbctl ls-add ls1
> +check ovn-nbctl lsp-add ls1 vm1 -- lsp-set-addresses vm1 
> "00:00:00:00:01:05 192.168.100.5"
> +
> +check ovn-nbctl ls-add ls2
> +check ovn-nbctl lsp-add ls2 vm2 -- lsp-set-addresses vm2 
> "00:00:00:00:02:05 192.168.200.5"
> +
> +check ovn-nbctl ls-add ls-pub
> +
> +check ovn-nbctl lr-add lr1
> +check ovn-nbctl lrp-add lr1 lr1-ls1 00:00:00:00:01:01 192.168.100.1/24
> +check ovn-nbctl lsp-add ls1 ls1-lr1                      \
> +    -- lsp-set-type ls1-lr1 router                 \
> +    -- lsp-set-addresses ls1-lr1 router            \
> +    -- lsp-set-options ls1-lr1 router-port=lr1-ls1
> +
> +check ovn-nbctl lr-add lr2
> +check ovn-nbctl lrp-add lr2 lr2-ls2 00:00:00:00:02:01 192.168.200.1/24
> +check ovn-nbctl lsp-add ls2 ls2-lr2                      \
> +    -- lsp-set-type ls2-lr2 router                 \
> +    -- lsp-set-addresses ls2-lr2 router            \
> +    -- lsp-set-options ls2-lr2 router-port=lr2-ls2
> +
> +check ovn-nbctl lrp-add lr1 lr1-ls-pub 00:00:00:00:03:01 
> 172.18.2.110/24
> +check ovn-nbctl lrp-set-gateway-chassis lr1-ls-pub hv1
> +check ovn-nbctl lsp-add ls-pub ls-pub-lr1                      \
> +    -- lsp-set-type ls-pub-lr1 router                    \
> +    -- lsp-set-addresses ls-pub-lr1 router               \
> +    -- lsp-set-options ls-pub-lr1 router-port=lr1-ls-pub
> +
> +check ovn-nbctl lrp-add lr2 lr2-ls-pub 00:00:00:00:03:02 
> 172.18.1.173/24
> +check ovn-nbctl lrp-set-gateway-chassis lr2-ls-pub hv1
> +check ovn-nbctl lsp-add ls-pub ls-pub-lr2                      \
> +    -- lsp-set-type ls-pub-lr2 router                    \
> +    -- lsp-set-addresses ls-pub-lr2 router               \
> +    -- lsp-set-options ls-pub-lr2 router-port=lr2-ls-pub
> +
> +# Putting --add-route on these NAT rules means there is no need to
> +# add any static routes.
> +check ovn-nbctl --add-route lr-nat-add lr1 dnat_and_snat 172.18.2.11 
> 192.168.100.5 vm1 00:00:00:00:03:01
> +check ovn-nbctl --add-route lr-nat-add lr2 dnat_and_snat 172.18.2.12 
> 192.168.200.5 vm2 00:00:00:00:03:02
> +
> +ADD_NAMESPACES(vm1)
> +ADD_VETH(vm1, vm1, br-int, "192.168.100.5/24", "00:00:00:00:01:05", \
> +         "192.168.100.1")
> +
> +ADD_NAMESPACES(vm2)
> +ADD_VETH(vm2, vm2, br-int, "192.168.200.5/24", "00:00:00:00:02:05", \
> +         "192.168.200.1")
> +
> +OVN_POPULATE_ARP
> +check ovn-nbctl --wait=hv sync
> +
> +AS_BOX([Testing a ping])
> +
> +NS_CHECK_EXEC([vm1], [ping -q -c 3 -i 0.3 -w 2 172.18.2.12 | 
> FORMAT_PING], \
> +[0], [dnl
> +3 packets transmitted, 3 received, 0% packet loss, time 0ms
> +])
> +
> +AT_CLEANUP
> +])
> -- 
> 2.31.1
> 
> _______________________________________________
> dev mailing list
> dev at openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
> 


-- 
  Krzysztof Klimonda
  kklimonda at syntaxhighlighted.com


More information about the dev mailing list