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 <mmich...@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 > d...@openvswitch.org > https://mail.openvswitch.org/mailman/listinfo/ovs-dev > -- Krzysztof Klimonda kklimo...@syntaxhighlighted.com _______________________________________________ dev mailing list d...@openvswitch.org https://mail.openvswitch.org/mailman/listinfo/ovs-dev