diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml index deffe8cccb..a16937a214 100644 --- a/northd/ovn-northd.8.xml +++ b/northd/ovn-northd.8.xml @@ -813,19 +813,12 @@
  • If the logical switch has load balancer(s) configured, then a priority-100 flow is added with the match - ip && ct.trk&& ct.dnat to check if the + ip && ct.trk to check if the packet needs to be hairpinned (if after load balancing the destination - IP matches the source IP) or not by executing the action - reg0[6] = chk_lb_hairpin(); and advances the packet to - the next table. -
  • - -
  • - If the logical switch has load balancer(s) configured, then a - priority-90 flow is added with the match ip to check if - the packet is a reply for a hairpinned connection or not by executing - the action reg0[6] = chk_lb_hairpin_reply(); and advances - the packet to the next table. + IP matches the source IP) or not by executing the actions + reg0[6] = chk_lb_hairpin(); and + reg0[12] = chk_lb_hairpin_reply(); and advances the packet + to the next table.
  • @@ -838,16 +831,25 @@
  • If the logical switch has load balancer(s) configured, then a priority-100 flow is added with the match - ip && (ct.new || ct.est) && ct.trk && - ct.dnat && reg0[6] == 1 which hairpins the traffic by + ip && ct.new && ct.trk && + reg0[6] == 1 which hairpins the traffic by NATting source IP to the load balancer VIP by executing the action ct_snat_to_vip and advances the packet to the next table.
  • +
  • + If the logical switch has load balancer(s) configured, then a + priority-100 flow is added with the match + ip && ct.est && ct.trk && + reg0[6] == 1 which hairpins the traffic by + NATting source IP to the load balancer VIP by executing the action + ct_snat and advances the packet to the next table. +
  • +
  • If the logical switch has load balancer(s) configured, then a priority-90 flow is added with the match - ip && reg0[6] == 1 which matches on the replies + ip && reg0[12] == 1 which matches on the replies of hairpinned traffic (i.e., destination IP is VIP, source IP is the backend IP and source L4 port is backend port for L4 load balancers) and executes ct_snat and advances the diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c index 5a81211a64..746995fd12 100644 --- a/northd/ovn-northd.c +++ b/northd/ovn-northd.c @@ -227,6 +227,7 @@ enum ovn_stage { #define REGBIT_ACL_HINT_DROP "reg0[9]" #define REGBIT_ACL_HINT_BLOCK "reg0[10]" #define REGBIT_LKUP_FDB "reg0[11]" +#define REGBIT_HAIRPIN_REPLY "reg0[12]" #define REG_ORIG_DIP_IPV4 "reg1" #define REG_ORIG_DIP_IPV6 "xxreg1" @@ -266,7 +267,8 @@ enum ovn_stage { * * Logical Switch pipeline: * +----+----------------------------------------------+---+------------------+ - * | R0 | REGBIT_{CONNTRACK/DHCP/DNS/HAIRPIN} | | | + * | R0 | REGBIT_{CONNTRACK/DHCP/DNS} | | | + * | | REGBIT_{HAIRPIN/HAIRPIN_REPLY} | X | | * | | REGBIT_ACL_HINT_{ALLOW_NEW/ALLOW/DROP/BLOCK} | X | | * +----+----------------------------------------------+ X | | * | R1 | ORIG_DIP_IPV4 (>= IN_STATEFUL) | R | | @@ -6036,39 +6038,49 @@ build_lb_hairpin(struct ovn_datapath *od, struct hmap *lflows) ovn_lflow_add(lflows, od, S_SWITCH_IN_HAIRPIN, 0, "1", "next;"); if (od->has_lb_vip) { - /* Check if the packet needs to be hairpinned. */ - ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_PRE_HAIRPIN, 100, - "ip && ct.trk && ct.dnat", - REGBIT_HAIRPIN " = chk_lb_hairpin(); next;", + /* Check if the packet needs to be hairpinned. + * Set REGBIT_HAIRPIN in the original direction and + * REGBIT_HAIRPIN_REPLY in the reply direction. + */ + ovn_lflow_add_with_hint( + lflows, od, S_SWITCH_IN_PRE_HAIRPIN, 100, "ip && ct.trk", + REGBIT_HAIRPIN " = chk_lb_hairpin(); " + REGBIT_HAIRPIN_REPLY " = chk_lb_hairpin_reply(); " + "next;", + &od->nbs->header_); + + /* If packet needs to be hairpinned, snat the src ip with the VIP + * for new sessions. */ + ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_NAT_HAIRPIN, 100, + "ip && ct.new && ct.trk" + " && "REGBIT_HAIRPIN " == 1", + "ct_snat_to_vip; next;", &od->nbs->header_); - /* Check if the packet is a reply of hairpinned traffic. */ - ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_PRE_HAIRPIN, 90, "ip", - REGBIT_HAIRPIN " = chk_lb_hairpin_reply(); " - "next;", &od->nbs->header_); - - /* If packet needs to be hairpinned, snat the src ip with the VIP. */ + /* If packet needs to be hairpinned, for established sessions there + * should already be an SNAT conntrack entry. + */ ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_NAT_HAIRPIN, 100, - "ip && (ct.new || ct.est) && ct.trk && ct.dnat" + "ip && ct.est && ct.trk" " && "REGBIT_HAIRPIN " == 1", - "ct_snat_to_vip; next;", + "ct_snat;", &od->nbs->header_); /* For the reply of hairpinned traffic, snat the src ip to the VIP. */ ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_NAT_HAIRPIN, 90, - "ip && "REGBIT_HAIRPIN " == 1", "ct_snat;", + "ip && "REGBIT_HAIRPIN_REPLY " == 1", + "ct_snat;", &od->nbs->header_); /* Ingress Hairpin table. * - Priority 1: Packets that were SNAT-ed for hairpinning should be * looped back (i.e., swap ETH addresses and send back on inport). */ - ovn_lflow_add(lflows, od, S_SWITCH_IN_HAIRPIN, 1, - REGBIT_HAIRPIN " == 1", - "eth.dst <-> eth.src;" - "outport = inport;" - "flags.loopback = 1;" - "output;"); + ovn_lflow_add( + lflows, od, S_SWITCH_IN_HAIRPIN, 1, + "("REGBIT_HAIRPIN " == 1 || " REGBIT_HAIRPIN_REPLY " == 1)", + "eth.dst <-> eth.src; outport = inport; flags.loopback = 1; " + "output;"); } } diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at index 379d27903f..6164196e3d 100644 --- a/tests/ovn-northd.at +++ b/tests/ovn-northd.at @@ -2186,6 +2186,35 @@ check_column "$lb0_uuid" sb:datapath_binding load_balancers external_ids:name=sw AT_CLEANUP +AT_SETUP([ovn -- LS load balancer hairpin logical flows]) +ovn_start + +check ovn-nbctl \ + -- ls-add sw0 \ + -- lb-add lb0 10.0.0.10:80 10.0.0.4:8080 \ + -- ls-lb-add sw0 lb0 + +check ovn-nbctl --wait=sb sync + +AT_CHECK([ovn-sbctl lflow-list sw0 | grep ls_in_pre_hairpin | sort], [0], [dnl + table=14(ls_in_pre_hairpin ), priority=0 , match=(1), action=(next;) + table=14(ls_in_pre_hairpin ), priority=100 , match=(ip && ct.trk), action=(reg0[[6]] = chk_lb_hairpin(); reg0[[12]] = chk_lb_hairpin_reply(); next;) +]) + +AT_CHECK([ovn-sbctl lflow-list sw0 | grep ls_in_nat_hairpin | sort], [0], [dnl + table=15(ls_in_nat_hairpin ), priority=0 , match=(1), action=(next;) + table=15(ls_in_nat_hairpin ), priority=100 , match=(ip && ct.est && ct.trk && reg0[[6]] == 1), action=(ct_snat;) + table=15(ls_in_nat_hairpin ), priority=100 , match=(ip && ct.new && ct.trk && reg0[[6]] == 1), action=(ct_snat_to_vip; next;) + table=15(ls_in_nat_hairpin ), priority=90 , match=(ip && reg0[[12]] == 1), action=(ct_snat;) +]) + +AT_CHECK([ovn-sbctl lflow-list sw0 | grep ls_in_hairpin | sort], [0], [dnl + table=16(ls_in_hairpin ), priority=0 , match=(1), action=(next;) + table=16(ls_in_hairpin ), priority=1 , match=((reg0[[6]] == 1 || reg0[[12]] == 1)), action=(eth.dst <-> eth.src; outport = inport; flags.loopback = 1; output;) +]) + +AT_CLEANUP + AT_SETUP([ovn -- logical gatapath groups]) AT_KEYWORDS([use_logical_dp_groups]) ovn_start