Skip to content

Commit 786a3b6

Browse files
tagrawal03meta-codesync[bot]
authored andcommitted
Add egress decap support: DECAP_EGRESS flag + XDP_TX path
Summary: Add a third decap behavior to Katran's inline GUE decapsulation path for egress DSR from cloud backends (EoPC). Today, the `decap_dst` BPF map stores a `__u32 flags` value per destination but the flags are never read — the code only checks key existence. This diff activates the flags field: - `flags = 0` (existing xpop behavior): GUE decap → recirculate through Katran for load-balancing the inner packet - `flags = DECAP_EGRESS (0x2)` (new): GUE decap → MAC swap → XDP_TX directly out the NIC, used for egress DSR responses from cloud Revproxy that need to be forwarded to the client BPF changes: - `check_decap_dst()`: reads `decap_dst_flags` value and passes it to the caller via new `out_decap_flags` parameter - `process_encaped_gue_pckt()`: new XDP_TX code path when `DECAP_EGRESS` flag is set — swaps src/dst MACs and returns XDP_TX - New counter `EGRESS_DECAP_CNTR` (position 20) tracks egress decap packets (v1=IPv4 inner, v2=IPv6 inner) Userspace changes: - `addEgressDecapDst()`: public API that calls `modifyDecapDst()` with `DECAP_EGRESS` flag (vs `addInlineDecapDst()` which uses 0) - `getEgressDecapStats()`: reads the new BPF counter Reviewed By: frankfeir, nikhildl12 Differential Revision: D99473916 fbshipit-source-id: 7fe9b2fd945813a61595eec80f957850f656ef0b
1 parent f3c6cfb commit 786a3b6

11 files changed

Lines changed: 316 additions & 8 deletions

katran/lib/KatranLb.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1666,6 +1666,32 @@ bool KatranLb::addInlineDecapDst(const std::string& dst) {
16661666
return true;
16671667
}
16681668

1669+
bool KatranLb::addEgressDecapDst(const std::string& dst) {
1670+
if (!features_.inlineDecap && !config_.testing) {
1671+
LOG(ERROR) << "inline decap is not enabled in forwarding plane";
1672+
return false;
1673+
}
1674+
if (validateAddress(dst) == AddressType::INVALID) {
1675+
LOG(ERROR) << "invalid egress decap destination address: " << dst;
1676+
return false;
1677+
}
1678+
folly::IPAddress daddr(dst);
1679+
if (decapDsts_.find(daddr) != decapDsts_.end()) {
1680+
LOG(ERROR) << "trying to add already existing egress decap dst";
1681+
return false;
1682+
}
1683+
if (decapDsts_.size() + 1 > config_.maxDecapDst) {
1684+
LOG(ERROR) << "size of decap destinations map is exhausted";
1685+
return false;
1686+
}
1687+
VLOG(2) << "adding egress decap dst " << dst;
1688+
decapDsts_.insert(daddr);
1689+
if (!config_.testing) {
1690+
modifyDecapDst(ModifyAction::ADD, daddr, kDecapEgress);
1691+
}
1692+
return true;
1693+
}
1694+
16691695
bool KatranLb::delInlineDecapDst(const std::string& dst) {
16701696
if (!features_.inlineDecap && !config_.testing) {
16711697
LOG(ERROR) << "source based routing is not enabled in forwarding plane";
@@ -2127,6 +2153,10 @@ lb_stats KatranLb::getXPopDecapSuccessfulStats() {
21272153
return getLbStats(config_.maxVips + kXPopDecapSuccessfulOffset);
21282154
}
21292155

2156+
lb_stats KatranLb::getEgressDecapStats() {
2157+
return getLbStats(config_.maxVips + kEgressDecapOffset);
2158+
}
2159+
21302160
lb_stats KatranLb::getUdpFlowMigrationStats() {
21312161
return getLbStats(config_.maxVips + kUdpFlowMigrationInvalidationOffset);
21322162
}

katran/lib/KatranLb.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ constexpr uint32_t kXdpTotalOffset = 16;
7474
constexpr uint32_t kXdpTxOffset = 17;
7575
constexpr uint32_t kXdpDropOffset = 18;
7676
constexpr uint32_t kXdpPassOffset = 19;
77+
constexpr uint32_t kEgressDecapOffset = 20;
78+
79+
// Flag value for egress decap destinations (must match DECAP_EGRESS in BPF)
80+
constexpr uint32_t kDecapEgress = 0x2;
7781

7882
/**
7983
* LRU map related constants
@@ -431,6 +435,17 @@ class KatranLb {
431435
*/
432436
bool addInlineDecapDst(const std::string& dst);
433437

438+
/**
439+
* @param string address for egress decap (XDP_TX after GUE decap)
440+
* @return bool true on success
441+
*
442+
* helper function to add address for egress decapsulation.
443+
* packets arriving at this destination are GUE-decapped and
444+
* XDP_TX'd directly (no recirculation), used for DSR egress
445+
* from cloud backends.
446+
*/
447+
bool addEgressDecapDst(const std::string& dst);
448+
434449
/**
435450
* @param string address for inline decapsulation
436451
* @return bool true on success
@@ -659,6 +674,14 @@ class KatranLb {
659674
*/
660675
lb_stats getXPopDecapSuccessfulStats();
661676

677+
/**
678+
* @return struct lb_stats w/ statistics of egress decap packets
679+
*
680+
* helper function which returns how many packets were successfully
681+
* egress-decapped (GUE decap + XDP_TX)
682+
*/
683+
lb_stats getEgressDecapStats();
684+
662685
/**
663686
* @return struct lb_stats w/ statistics of total invalidated dst
664687
*

katran/lib/bpf/balancer.bpf.c

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -200,8 +200,11 @@ __attribute__((__always_inline__)) static inline void connection_table_lookup(
200200
}
201201

202202
#ifdef INLINE_DECAP_GENERIC
203-
__attribute__((__always_inline__)) static inline int
204-
check_decap_dst(struct packet_description* pckt, bool is_ipv6, bool* pass) {
203+
__attribute__((__always_inline__)) static inline int check_decap_dst(
204+
struct packet_description* pckt,
205+
bool is_ipv6,
206+
bool* pass,
207+
__u32* out_decap_flags) {
205208
struct address dst_addr = {};
206209
struct lb_stats* data_stats;
207210

@@ -215,6 +218,7 @@ check_decap_dst(struct packet_description* pckt, bool is_ipv6, bool* pass) {
215218

216219
if (decap_dst_flags) {
217220
*pass = false;
221+
*out_decap_flags = *decap_dst_flags;
218222
__u32 stats_key = MAX_VIPS + REMOTE_ENCAP_CNTRS;
219223
data_stats = bpf_map_lookup_elem(&stats, &stats_key);
220224
if (!data_stats) {
@@ -435,7 +439,8 @@ __attribute__((__always_inline__)) static inline int process_encaped_gue_pckt(
435439
struct xdp_md* xdp,
436440
__u64 off,
437441
bool is_ipv6,
438-
bool pass) {
442+
bool pass,
443+
__u32 decap_flags) {
439444
int offset = 0;
440445
int action;
441446
bool inner_ipv6 = false;
@@ -490,6 +495,35 @@ __attribute__((__always_inline__)) static inline int process_encaped_gue_pckt(
490495
if (action >= 0) {
491496
return action;
492497
}
498+
if (!pass && (decap_flags & DECAP_EGRESS)) {
499+
// Egress decap: decap complete, XDP_TX the inner packet directly.
500+
// The inner packet is a DSR response (src=VIP, dst=client) that needs
501+
// to be sent out the NIC toward the router for forwarding to the client.
502+
void* eth_data = (void*)(long)xdp->data;
503+
void* eth_data_end = (void*)(long)xdp->data_end;
504+
struct ethhdr* eth = eth_data;
505+
if ((void*)(eth + 1) > eth_data_end) {
506+
return XDP_DROP;
507+
}
508+
// Swap MAC addresses: incoming packet has router MAC as src,
509+
// our NIC MAC as dst. Swap so packet goes back to the router.
510+
__u8 tmp_mac[ETH_ALEN];
511+
__builtin_memcpy(tmp_mac, eth->h_dest, ETH_ALEN);
512+
__builtin_memcpy(eth->h_dest, eth->h_source, ETH_ALEN);
513+
__builtin_memcpy(eth->h_source, tmp_mac, ETH_ALEN);
514+
515+
__u32 egress_stats_key = MAX_VIPS + EGRESS_DECAP_CNTR;
516+
struct lb_stats* egress_stats =
517+
bpf_map_lookup_elem(&stats, &egress_stats_key);
518+
if (egress_stats) {
519+
if (inner_ipv6) {
520+
egress_stats->v2 += 1;
521+
} else {
522+
egress_stats->v1 += 1;
523+
}
524+
}
525+
return XDP_TX;
526+
}
493527
if (pass) {
494528
// pass packet to kernel after decapsulation
495529
// increment stats after decapsulation
@@ -735,15 +769,17 @@ process_packet(struct xdp_md* xdp, __u64 nh_off, bool is_ipv6) {
735769
*/
736770
if (protocol == IPPROTO_IPIP) {
737771
bool pass = true;
738-
action = check_decap_dst(&pckt, is_ipv6, &pass);
772+
__u32 decap_flags = 0;
773+
action = check_decap_dst(&pckt, is_ipv6, &pass, &decap_flags);
739774
if (action >= 0) {
740775
return action;
741776
}
742777
return process_encaped_ipip_pckt(
743778
&data, &data_end, xdp, &is_ipv6, &protocol, pass);
744779
} else if (protocol == IPPROTO_IPV6) {
745780
bool pass = true;
746-
action = check_decap_dst(&pckt, is_ipv6, &pass);
781+
__u32 decap_flags = 0;
782+
action = check_decap_dst(&pckt, is_ipv6, &pass, &decap_flags);
747783
if (action >= 0) {
748784
return action;
749785
}
@@ -763,12 +799,13 @@ process_packet(struct xdp_md* xdp, __u64 nh_off, bool is_ipv6) {
763799
#ifdef INLINE_DECAP_GUE
764800
if (pckt.flow.port16[1] == bpf_htons(GUE_DPORT)) {
765801
bool pass = true;
766-
action = check_decap_dst(&pckt, is_ipv6, &pass);
802+
__u32 decap_flags = 0;
803+
action = check_decap_dst(&pckt, is_ipv6, &pass, &decap_flags);
767804
if (action >= 0) {
768805
return action;
769806
}
770807
return process_encaped_gue_pckt(
771-
&data, &data_end, xdp, nh_off, is_ipv6, pass);
808+
&data, &data_end, xdp, nh_off, is_ipv6, pass, decap_flags);
772809
}
773810
#endif // of INLINE_DECAP_GUE
774811
} else {

katran/lib/bpf/balancer_consts.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,9 @@
255255

256256
#define NO_FLAGS 0
257257

258+
// flag for egress decap destinations (XDP_TX after decap, no recirculate)
259+
#define DECAP_EGRESS 0x2
260+
258261
// offset of the lru cache hit related counters
259262
/* v1 tracks total vip packets (no longer used for lru calculations)
260263
v2 tracks lru misses */
@@ -292,6 +295,8 @@ v2 tracks misses for TCP non syns */
292295
#define XDP_TX_CNTR 17 // total packets sent to backend
293296
#define XDP_DROP_CNTR 18 // total packets dropped by katran
294297
#define XDP_PASS_CNTR 19 // packets passed up to the kernel
298+
// Tracks successful egress decap packets (XDP_TX after GUE decap)
299+
#define EGRESS_DECAP_CNTR 20
295300

296301
// indice for all stats maps defined above correspond to entries in the map
297302
// stats starting from the index MAX_VIPS. The max_entries of stats is
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// clang-format off
2+
3+
/* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
4+
*
5+
* This program is free software; you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation; version 2 of the License.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License along
15+
* with this program; if not, write to the Free Software Foundation, Inc.,
16+
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17+
*/
18+
19+
#pragma once
20+
#include <vector>
21+
#include "katran/lib/testing/tools/PacketAttributes.h"
22+
#include "katran/lib/testing/tools/PacketBuilder.h"
23+
24+
/**
25+
* Test fixtures for egress decapsulation (XDP_TX after GUE decap).
26+
*
27+
* Unlike xpop decap which recirculates the inner packet through Katran,
28+
* egress decap strips the GUE header, swaps MACs, and returns XDP_TX
29+
* directly. Used for DSR egress from cloud backends (EoPC).
30+
*
31+
* Input packets: GUE-encapsulated to the egress decap VIP (fc00:1405::1).
32+
* Expected output: inner packet with MACs swapped (dst=src, src=dst of input).
33+
*/
34+
35+
namespace katran {
36+
namespace testing {
37+
38+
const std::vector<::katran::PacketAttributes> egressDecapTestFixtures = {
39+
//1: Egress decap IPv4 inner packet — XDP_TX with MAC swap
40+
{
41+
.description = "Egress decap: GUE IPv6->IPv4 inner, XDP_TX with MAC swap.",
42+
.expectedReturnValue = "XDP_TX",
43+
.inputPacketBuilder = katran::testing::PacketBuilder::newPacket()
44+
.Eth("0x1", "0x2")
45+
.IPv6("100::64", "fc00:1405::1", 64)
46+
.UDP(31337, 9886)
47+
.IPv4("10.200.1.1", "192.168.1.3", 64)
48+
.UDP(80, 31337)
49+
.payload("egress decap test"),
50+
// After decap: inner packet with MACs swapped (0x2 -> dst, 0x1 -> src)
51+
.expectedOutputPacketBuilder = katran::testing::PacketBuilder::newPacket()
52+
.Eth("02:00:00:00:00:00", "01:00:00:00:00:00")
53+
.IPv4("10.200.1.1", "192.168.1.3", 63, 0, 1)
54+
.UDP(80, 31337)
55+
.payload("egress decap test")
56+
},
57+
//2: Egress decap IPv6 inner packet — XDP_TX with MAC swap
58+
{
59+
.description = "Egress decap: GUE IPv6->IPv6 inner, XDP_TX with MAC swap.",
60+
.expectedReturnValue = "XDP_TX",
61+
.inputPacketBuilder = katran::testing::PacketBuilder::newPacket()
62+
.Eth("0x1", "0x2")
63+
.IPv6("100::64", "fc00:1405::1", 64)
64+
.UDP(31337, 9886)
65+
.IPv6("fc00:1::1", "fc00:2307:1::2", 64)
66+
.TCP(80, 31337, 0, 0, 8192, 0x10)
67+
.payload("egress decap test"),
68+
.expectedOutputPacketBuilder = katran::testing::PacketBuilder::newPacket()
69+
.Eth("02:00:00:00:00:00", "01:00:00:00:00:00")
70+
.IPv6("fc00:1::1", "fc00:2307:1::2", 63)
71+
.TCP(80, 31337, 0, 0, 8192, 0x10)
72+
.payload("egress decap test")
73+
},
74+
//3: Egress decap with TTL expired — XDP_DROP
75+
{
76+
.description = "Egress decap: GUE IPv6->IPv4 inner with TTL expired, XDP_DROP.",
77+
.expectedReturnValue = "XDP_DROP",
78+
.inputPacketBuilder = katran::testing::PacketBuilder::newPacket()
79+
.Eth("0x1", "0x2")
80+
.IPv6("100::64", "fc00:1405::1", 64)
81+
.UDP(31337, 9886)
82+
.IPv4("10.200.1.1", "192.168.1.3", 1)
83+
.UDP(80, 31337)
84+
.payload("egress decap test"),
85+
.expectedOutputPacketBuilder = katran::testing::PacketBuilder::newPacket()
86+
.Eth("01:00:00:00:00:00", "02:00:00:00:00:00")
87+
.IPv4("10.200.1.1", "192.168.1.3", 0, 0, 1)
88+
.UDP(80, 31337)
89+
.payload("egress decap test")
90+
},
91+
};
92+
93+
} // namespace testing
94+
} // namespace katran

katran/lib/testing/framework/katran_tester.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include <gflags/gflags.h>
2626

2727
#include "katran/lib/MonitoringStructs.h"
28+
#include "katran/lib/testing/fixtures/KatranEgressDecapTestFixtures.h"
2829
#include "katran/lib/testing/fixtures/KatranHCTestFixtures.h"
2930
#include "katran/lib/testing/fixtures/KatranIcmpTooBigTestFixtures.h"
3031
#include "katran/lib/testing/fixtures/KatranLpmSrcLookupTestFixtures.h"
@@ -65,6 +66,7 @@ DEFINE_bool(
6566
DEFINE_bool(gue, false, "run GUE tests instead of IPIP ones");
6667
DEFINE_bool(stable_rt, false, "run UDP Stable Routing tests");
6768
DEFINE_bool(xpop_decap, false, "run cross pop decap tests");
69+
DEFINE_bool(egress_decap, false, "run egress decap tests");
6870
DEFINE_bool(udp_flow_migration, false, "run UDP flow migration tests");
6971
DEFINE_bool(
7072
tpr,
@@ -182,6 +184,14 @@ void runTestsFromFixture(
182184
auto xpopTestParams = createXPopDecapTestParam();
183185
testXPopDecapCounters(lb, xpopTestParams);
184186
}
187+
if (FLAGS_egress_decap) {
188+
prepareLbDataXpopDecap(lb);
189+
prepareLbDataEgressDecap(lb);
190+
tester.resetTestFixtures(katran::testing::egressDecapTestFixtures);
191+
tester.testFromFixture();
192+
auto egressTestParams = createEgressDecapTestParam();
193+
testEgressDecapCounters(lb, egressTestParams);
194+
}
185195
if (FLAGS_udp_flow_migration) {
186196
prepareUdpFlowMigrationTestData(lb);
187197
tester.resetTestFixtures(

katran/lib/testing/utils/KatranTestProvision.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,12 @@ void prepareLbDataXpopDecap(katran::KatranLb& lb) {
327327
lb.addHealthcheckerDst(3, "fc00::1");
328328
}
329329

330+
void prepareLbDataEgressDecap(katran::KatranLb& lb) {
331+
// Use the same VIPs as xpop (already set up by prepareLbDataXpopDecap).
332+
// Add an egress decap destination with DECAP_EGRESS flag.
333+
lb.addEgressDecapDst("fc00:1405::1");
334+
}
335+
330336
void prepareUdpFlowMigrationTestData(katran::KatranLb& lb) {
331337
lb.restartKatranMonitor(kMonitorLimit);
332338

@@ -511,6 +517,15 @@ uint64_t KatranTestParam::expectedXPopDecapSuccessfulV6() noexcept {
511517
uint64_t KatranTestParam::expectedUdpFlowMigrationInvalidation() noexcept {
512518
return _lookup_counter(KatranTestCounters::UDP_FLOW_MIGRATION_STATS);
513519
}
520+
uint64_t KatranTestParam::expectedEgressDecapPkts() noexcept {
521+
return _lookup_counter(KatranTestCounters::EGRESS_DECAP_PKTS);
522+
}
523+
uint64_t KatranTestParam::expectedEgressDecapPktsV4() noexcept {
524+
return _lookup_counter(KatranTestCounters::EGRESS_DECAP_PKTS_V4);
525+
}
526+
uint64_t KatranTestParam::expectedEgressDecapPktsV6() noexcept {
527+
return _lookup_counter(KatranTestCounters::EGRESS_DECAP_PKTS_V6);
528+
}
514529
uint64_t KatranTestParam::_lookup_counter(KatranTestCounters counter) noexcept {
515530
if (!expectedCounters.contains(counter)) {
516531
return 0;

katran/lib/testing/utils/KatranTestProvision.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,11 @@ enum class KatranTestCounters : uint8_t {
106106
XPOP_DECAP_SUCCESSFUL_V4 = 27,
107107
XPOP_DECAP_SUCCESSFUL_V6 = 28,
108108
// udp flow migration counters
109-
UDP_FLOW_MIGRATION_STATS = 29
109+
UDP_FLOW_MIGRATION_STATS = 29,
110+
// egress decap counters
111+
EGRESS_DECAP_PKTS = 30,
112+
EGRESS_DECAP_PKTS_V4 = 31,
113+
EGRESS_DECAP_PKTS_V6 = 32
110114
};
111115

112116
struct KatranTestParam {
@@ -146,6 +150,9 @@ struct KatranTestParam {
146150
uint64_t expectedXPopDecapSuccessfulV4() noexcept;
147151
uint64_t expectedXPopDecapSuccessfulV6() noexcept;
148152
uint64_t expectedUdpFlowMigrationInvalidation() noexcept;
153+
uint64_t expectedEgressDecapPkts() noexcept;
154+
uint64_t expectedEgressDecapPktsV4() noexcept;
155+
uint64_t expectedEgressDecapPktsV6() noexcept;
149156

150157
// helper method to lookup the expected counter value
151158
uint64_t _lookup_counter(KatranTestCounters counter) noexcept;
@@ -183,6 +190,8 @@ void preparePerfTestingLbData(katran::KatranLb& lb);
183190

184191
void prepareUdpFlowMigrationTestData(katran::KatranLb& lb);
185192

193+
void prepareLbDataEgressDecap(katran::KatranLb& lb);
194+
186195
void setDownHostForUdpFlowMigration(katran::KatranLb& lb);
187196
} // namespace testing
188197
} // namespace katran

0 commit comments

Comments
 (0)