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

Mark Michelson mmichels at redhat.com
Wed Jul 7 18:50:48 UTC 2021


On 7/7/21 1:41 PM, Numan Siddique wrote:
> On Wed, Jun 30, 2021 at 7:57 PM Mark Michelson <mmichels at redhat.com> 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>
> 
> Acked-by: Numan Siddique <numans at ovn.org>
> 
> I've one comment which probably can be addressed.
> 
> If a configured NAT entry on a logical router is unreachable within
> that router, this patch floods
> the packet for the ARP destined to that NAT IP in the logical switch pipeline.
> 
> Before adding the flow to flood, can't we check if the NAT IP is
> reachable from other router ports
> connected to this logical switch.  If so, we can do "outport ==
> <REACHABLE_LRP_PORT>" instead
> of flooding.
> 
> I think this is possible given that the logical flow is added in the
> switch pipeline.  We just need to loop through
> all the router ports of the logical switch.  The question is - is this
> efficient  and takes up some time on a scaled environment ?
> 
> What do you think ?  If this seems fine,  it can be a follow up patch too.

I don't think your suggestion would add any appreciable time compared to 
what we're already doing in ovn-northd. I will attempt this approach and 
let you know how it goes.

> 
> Numan
> 
>> ---
>>   northd/ovn-northd.8.xml |   8 ++
>>   northd/ovn-northd.c     | 162 +++++++++++++++++++++++++++-------------
>>   northd/ovn_northd.dl    |  91 ++++++++++++++++++----
>>   tests/ovn-northd.at     |  99 ++++++++++++++++++++++++
>>   tests/system-ovn.at     | 102 +++++++++++++++++++++++++
>>   5 files changed, 395 insertions(+), 67 deletions(-)
>>
>> diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
>> index beaf5a183..5aedd6619 100644
>> --- a/northd/ovn-northd.8.xml
>> +++ b/northd/ovn-northd.8.xml
>> @@ -1587,6 +1587,14 @@ output;
>>           logical ports.
>>         </li>
>>
>> +      <li>
>> +        Priority-90 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 f6fad281b..d0b325748 100644
>> --- a/northd/ovn-northd.c
>> +++ b/northd/ovn-northd.c
>> @@ -6555,38 +6555,41 @@ 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 ds *ip_match,
>> -                                        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 ds *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 == {");
>>       }
>>
>> -    ds_put_cstr(&match, ds_cstr_ro(ip_match));
>> -    ds_put_cstr(&match, "}");
>> +    ds_put_cstr(match, ds_cstr_ro(ips));
>> +    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 ds *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.
>> @@ -6609,6 +6612,30 @@ build_lswitch_rport_arp_req_flow_for_ip(struct ds *ip_match,
>>       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 ds *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_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.
>> @@ -6635,8 +6662,10 @@ build_lswitch_rport_arp_req_flows(struct ovn_port *op,
>>        * router port.
>>        * Priority: 80.
>>        */
>> -    struct ds ips_v4_match = DS_EMPTY_INITIALIZER;
>> -    struct ds ips_v6_match = DS_EMPTY_INITIALIZER;
>> +    struct ds ips_v4_match_reachable = DS_EMPTY_INITIALIZER;
>> +    struct ds ips_v6_match_reachable = DS_EMPTY_INITIALIZER;
>> +    struct ds ips_v4_match_unreachable = DS_EMPTY_INITIALIZER;
>> +    struct ds ips_v6_match_unreachable = DS_EMPTY_INITIALIZER;
>>
>>       const char *ip_addr;
>>       SSET_FOR_EACH (ip_addr, &op->od->lb_ips_v4) {
>> @@ -6645,9 +6674,12 @@ build_lswitch_rport_arp_req_flows(struct ovn_port *op,
>>           /* 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)) {
>> -            ds_put_format(&ips_v4_match, "%s, ", ip_addr);
>> +        if (ip_parse(ip_addr, &ipv4_addr)) {
>> +            if (lrouter_port_ipv4_reachable(op, ipv4_addr)) {
>> +                ds_put_format(&ips_v4_match_reachable, "%s, ", ip_addr);
>> +            } else {
>> +                ds_put_format(&ips_v4_match_unreachable, "%s, ", ip_addr);
>> +            }
>>           }
>>       }
>>       SSET_FOR_EACH (ip_addr, &op->od->lb_ips_v6) {
>> @@ -6656,9 +6688,12 @@ build_lswitch_rport_arp_req_flows(struct ovn_port *op,
>>           /* 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)) {
>> -            ds_put_format(&ips_v6_match, "%s, ", ip_addr);
>> +        if (ipv6_parse(ip_addr, &ipv6_addr)) {
>> +            if (lrouter_port_ipv6_reachable(op, &ipv6_addr)) {
>> +                ds_put_format(&ips_v6_match_reachable, "%s, ", ip_addr);
>> +            } else {
>> +                ds_put_format(&ips_v6_match_unreachable, "%s, ", ip_addr);
>> +            }
>>           }
>>       }
>>
>> @@ -6680,44 +6715,67 @@ build_lswitch_rport_arp_req_flows(struct ovn_port *op,
>>           if (nat_entry_is_v6(nat_entry)) {
>>               struct in6_addr *addr = &nat_entry->ext_addrs.ipv6_addrs[0].addr;
>>
>> -            if (lrouter_port_ipv6_reachable(op, addr) &&
>> -                    !sset_contains(&op->od->lb_ips_v6, nat->external_ip)) {
>> -                ds_put_format(&ips_v6_match, "%s, ", nat->external_ip);
>> +            if (!sset_contains(&op->od->lb_ips_v6, nat->external_ip)) {
>> +                if (lrouter_port_ipv6_reachable(op, addr)) {
>> +                    ds_put_format(&ips_v6_match_reachable, "%s, ",
>> +                                  nat->external_ip);
>> +                } else {
>> +                    ds_put_format(&ips_v6_match_unreachable, "%s, ",
>> +                                  nat->external_ip);
>> +                }
>>               }
>>           } else {
>>               ovs_be32 addr = nat_entry->ext_addrs.ipv4_addrs[0].addr;
>> -
>> -            if (lrouter_port_ipv4_reachable(op, addr) &&
>> -                    !sset_contains(&op->od->lb_ips_v4, nat->external_ip)) {
>> -                ds_put_format(&ips_v4_match, "%s, ", nat->external_ip);
>> +            if (!sset_contains(&op->od->lb_ips_v4, nat->external_ip)) {
>> +                if (lrouter_port_ipv4_reachable(op, addr)) {
>> +                    ds_put_format(&ips_v4_match_reachable, "%s, ",
>> +                                  nat->external_ip);
>> +                } else {
>> +                    ds_put_format(&ips_v4_match_unreachable, "%s, ",
>> +                                  nat->external_ip);
>> +                }
>>               }
>>           }
>>       }
>>
>>       for (size_t i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
>> -        ds_put_format(&ips_v4_match, "%s, ",
>> +        ds_put_format(&ips_v4_match_reachable, "%s, ",
>>                         op->lrp_networks.ipv4_addrs[i].addr_s);
>>       }
>>       for (size_t i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
>> -        ds_put_format(&ips_v6_match, "%s, ",
>> +        ds_put_format(&ips_v6_match_reachable, "%s, ",
>>                         op->lrp_networks.ipv6_addrs[i].addr_s);
>>       }
>>
>> -    if (ds_last(&ips_v4_match) != EOF) {
>> -        ds_chomp(&ips_v4_match, ' ');
>> -        ds_chomp(&ips_v4_match, ',');
>> -        build_lswitch_rport_arp_req_flow_for_ip(&ips_v4_match, AF_INET, sw_op,
>> -                                                sw_od, 80, lflows,
>> -                                                stage_hint);
>> +    if (ds_last(&ips_v4_match_reachable) != EOF) {
>> +        ds_chomp(&ips_v4_match_reachable, ' ');
>> +        ds_chomp(&ips_v4_match_reachable, ',');
>> +        build_lswitch_rport_arp_req_flow_for_reachable_ip(
>> +            &ips_v4_match_reachable, AF_INET, sw_op, sw_od, 80, lflows,
>> +            stage_hint);
>>       }
>> -    if (ds_last(&ips_v6_match) != EOF) {
>> -        ds_chomp(&ips_v6_match, ' ');
>> -        ds_chomp(&ips_v6_match, ',');
>> -        build_lswitch_rport_arp_req_flow_for_ip(&ips_v6_match, AF_INET6, sw_op,
>> -                                                sw_od, 80, lflows,
>> -                                                stage_hint);
>> +    if (ds_last(&ips_v6_match_reachable) != EOF) {
>> +        ds_chomp(&ips_v6_match_reachable, ' ');
>> +        ds_chomp(&ips_v6_match_reachable, ',');
>> +        build_lswitch_rport_arp_req_flow_for_reachable_ip(
>> +            &ips_v6_match_reachable, AF_INET6, sw_op, sw_od, 80, lflows,
>> +            stage_hint);
>>       }
>>
>> +    if (ds_last(&ips_v4_match_unreachable) != EOF) {
>> +        ds_chomp(&ips_v4_match_unreachable, ' ');
>> +        ds_chomp(&ips_v4_match_unreachable, ',');
>> +        build_lswitch_rport_arp_req_flow_for_unreachable_ip(
>> +            &ips_v4_match_unreachable, AF_INET, sw_od, 90, lflows,
>> +            stage_hint);
>> +    }
>> +    if (ds_last(&ips_v6_match_unreachable) != EOF) {
>> +        ds_chomp(&ips_v6_match_unreachable, ' ');
>> +        ds_chomp(&ips_v6_match_unreachable, ',');
>> +        build_lswitch_rport_arp_req_flow_for_unreachable_ip(
>> +            &ips_v6_match_unreachable, AF_INET6, sw_od, 90, lflows,
>> +            stage_hint);
>> +    }
>>       /* Self originated ARP requests/ND need to be flooded as usual.
>>        *
>>        * However, if the switch doesn't have any non-router ports we shouldn't
>> @@ -6728,8 +6786,10 @@ build_lswitch_rport_arp_req_flows(struct ovn_port *op,
>>       if (sw_od->n_router_ports != sw_od->nbs->n_ports) {
>>           build_lswitch_rport_arp_req_self_orig_flow(op, 75, sw_od, lflows);
>>       }
>> -    ds_destroy(&ips_v4_match);
>> -    ds_destroy(&ips_v6_match);
>> +    ds_destroy(&ips_v4_match_reachable);
>> +    ds_destroy(&ips_v6_match_reachable);
>> +    ds_destroy(&ips_v4_match_unreachable);
>> +    ds_destroy(&ips_v6_match_unreachable);
>>   }
>>
>>   static void
>> diff --git a/northd/ovn_northd.dl b/northd/ovn_northd.dl
>> index c9ee742d1..dd6430849 100644
>> --- a/northd/ovn_northd.dl
>> +++ b/northd/ovn_northd.dl
>> @@ -4109,9 +4109,13 @@ 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);
>> @@ -4121,7 +4125,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)
>>               },
>>               _ -> ()
>>           }
>> @@ -4132,7 +4138,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)
>>               },
>>               _ -> ()
>>           }
>> @@ -4145,22 +4153,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
>> @@ -4172,7 +4203,7 @@ Flow(.logical_datapath = sw._uuid,
>>        .priority         = 80,
>>        .__match          = fLAGBIT_NOT_VXLAN() ++
>>                            " && arp.op == 1 && arp.tpa == { " ++
>> -                         all_ips_v4.to_vec().join(", ") ++ "}",
>> +                         ipv4.to_vec().join(", ") ++ "}",
>>        .actions          = if (sw.has_non_router_port) {
>>                                "clone {outport = ${sp.json_name}; output; }; "
>>                                "outport = ${mc_flood_l2}; output;"
>> @@ -4182,15 +4213,15 @@ Flow(.logical_datapath = sw._uuid,
>>        .external_ids     = stage_hint(sp.lsp._uuid)) :-
>>       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(),
>> +    &SwitchPortARPForwards(.port = sp, .reachable_ips_v4 = ipv4),
>> +    not ipv4.is_empty(),
>>       var mc_flood_l2 = json_string_escape(mC_FLOOD_L2().0).
>>   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(", ") ++ "}",
>> +                         ipv6.to_vec().join(", ") ++ "}",
>>        .actions          = if (sw.has_non_router_port) {
>>                                "clone {outport = ${sp.json_name}; output; }; "
>>                                "outport = ${mc_flood_l2}; output;"
>> @@ -4200,10 +4231,38 @@ Flow(.logical_datapath = sw._uuid,
>>        .external_ids     = stage_hint(sp.lsp._uuid)) :-
>>       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(),
>> +    &SwitchPortARPForwards(.port = sp, .reachable_ips_v6 = ipv6),
>> +    not ipv6.is_empty(),
>>       var mc_flood_l2 = json_string_escape(mC_FLOOD_L2().0).
>>
>> +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)) :-
>> +    sp in &SwitchPort(.sw = sw, .peer = Some{rp}),
>> +    rp.is_enabled(),
>> +    &SwitchPortARPForwards(.port = sp, .unreachable_ips_v4 = ipv4),
>> +    not ipv4.is_empty(),
>> +    var flood = json_string_escape(mC_FLOOD().0).
>> +
>> +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)) :-
>> +    sp in &SwitchPort(.sw = sw, .peer = Some{rp}),
>> +    rp.is_enabled(),
>> +    &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 0c8a09ced..f591537e1 100644
>> --- a/tests/ovn-northd.at
>> +++ b/tests/ovn-northd.at
>> @@ -4033,3 +4033,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 a5f10ce38..bdd13b009 100644
>> --- a/tests/system-ovn.at
>> +++ b/tests/system-ovn.at
>> @@ -6482,3 +6482,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
>>
> 



More information about the dev mailing list