Skip to content

Commit 72f91e0

Browse files
avasylevmeta-codesync[bot]
authored andcommitted
Shiv Laser: Add destination IP/port matching to healthchecking BPF
Summary: Stack of diffs to provide "Shiv Laser" functionality as library for Traffic Realtime's load testing. ## Context Some historical context first. We've been using healthchicking BPF prog to send traffic directly to specific Shiv backend instance for multiple use cases beyond healthchecking. For historical reasons multiple places load BPF prog but not supporting C++ classes. To fix that issue we have implemented Encapsulator C++ wrapper, but didn't update existing usages to it. Most users don't need fine grained control of so_marks allowing to redirect VIPs to many reals (like we need for healthchecking), but want all traffic to given VIP be direct to single real. For that during CoIn hackathon I stitched togather bash script "shiv-laser" that tied iptables to add so marks & encapsulator binary. This turned to be very popular script. Now Traffic Realtime folks tested shiv-laser script with their Callagen load generator and it works well. Providing BPF functionality and C++ API they can call to setup encapsulated redirection from their service. ## Diff stack The main feature of this diff stack is to match packets for encapsulation by dst IP and port. Expose it via Encapsulator library API and replace shiv-laser with single binary. Keeping "Shiv Laser" naming of the functionality for cooler branding :) ## This diff BPF changes to match egress packets by dst IP/port. List of IP/ports in BPF map. If port value is 0, we'll match by dst IP only. Allows redirecting packets to VIP address regardless of VIP port. Scoped by #ifdef since we don't need this feature in healtmon. Reviewed By: nikhildl12 Differential Revision: D94374505 fbshipit-source-id: fe0e72cb77e2910c6f3c8d1eb3e509d970a1546c
1 parent 671964b commit 72f91e0

8 files changed

Lines changed: 109 additions & 15 deletions

katran/lib/KatranLb.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2282,6 +2282,7 @@ HealthCheckProgStats KatranLb::getStatsForHealthCheckProgram() {
22822282
total_stats.packetsSkipped += perCpuStat.packetsSkipped;
22832283
total_stats.packetsDropped += perCpuStat.packetsDropped;
22842284
total_stats.packetsTooBig += perCpuStat.packetsTooBig;
2285+
total_stats.packetsDstMatched += perCpuStat.packetsDstMatched;
22852286
}
22862287
}
22872288
}

katran/lib/KatranLbStructs.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@ struct HealthCheckProgStats {
275275
uint64_t packetsDropped{0};
276276
uint64_t packetsSkipped{0};
277277
uint64_t packetsTooBig{0};
278+
uint64_t packetsDstMatched{0};
278279
};
279280

280281
/**

katran/lib/bpf/healthchecking.bpf.c

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,38 @@ int healthcheck_encap(struct __sk_buff* skb) {
5353
return TC_ACT_UNSPEC;
5454
}
5555

56-
if (somark == 0) {
57-
prog_stats->pckts_skipped += 1;
58-
return TC_ACT_UNSPEC;
56+
if ((skb->data + sizeof(struct ethhdr)) > skb->data_end) {
57+
prog_stats->pckts_dropped += 1;
58+
return TC_ACT_SHOT;
59+
}
60+
61+
ethh = (void*)(long)skb->data;
62+
if (ethh->h_proto == BE_ETH_P_IPV6) {
63+
is_ipv6 = true;
64+
}
65+
66+
struct hc_real_definition* real = NULL;
67+
if (somark != 0) {
68+
real = bpf_map_lookup_elem(&hc_reals_map, &somark);
69+
}
70+
71+
#ifdef HC_DST_MATCH
72+
if (!real) {
73+
struct hc_dst_key dst_key = {};
74+
if (set_hc_dst_key(skb, &dst_key, is_ipv6)) {
75+
real = bpf_map_lookup_elem(&hc_dst_reals_map, &dst_key);
76+
if (!real && dst_key.port != 0) {
77+
dst_key.port = 0;
78+
real = bpf_map_lookup_elem(&hc_dst_reals_map, &dst_key);
79+
}
80+
if (real) {
81+
prog_stats->pckts_dst_matched += 1;
82+
}
83+
}
5984
}
85+
#endif // HC_DST_MATCH
6086

61-
struct hc_real_definition* real = bpf_map_lookup_elem(&hc_reals_map, &somark);
6287
if (!real) {
63-
// some strange (w/ fwmark; but not a healthcheck) local packet
6488
prog_stats->pckts_skipped += 1;
6589
return TC_ACT_UNSPEC;
6690
}
@@ -98,16 +122,6 @@ int healthcheck_encap(struct __sk_buff* skb) {
98122
}
99123
#endif /* HC_WITH_REDIRECT */
100124

101-
if ((skb->data + sizeof(struct ethhdr)) > skb->data_end) {
102-
prog_stats->pckts_dropped += 1;
103-
return TC_ACT_SHOT;
104-
}
105-
106-
ethh = (void*)(long)skb->data;
107-
if (ethh->h_proto == BE_ETH_P_IPV6) {
108-
is_ipv6 = true;
109-
}
110-
111125
struct hc_key hckey = {};
112126
bool hc_key_parseable = set_hc_key(skb, &hckey, is_ipv6);
113127

katran/lib/bpf/healthchecking_consts.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,6 @@
5353
#define HC_SRC_MAC_POS 0
5454
#define HC_DST_MAC_POS 1
5555

56+
#define HC_MAX_DST 100
57+
5658
#endif // of __HEALTHCHECKING_CONSTS_H

katran/lib/bpf/healthchecking_helpers.h

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,56 @@ set_hc_key(const struct __sk_buff* skb, struct hc_key* hckey, bool is_ipv6) {
7676
return true;
7777
}
7878

79+
#ifdef HC_DST_MATCH
80+
__attribute__((__always_inline__)) static inline bool set_hc_dst_key(
81+
const struct __sk_buff* skb,
82+
struct hc_dst_key* key,
83+
bool is_ipv6) {
84+
void* iphdr = (void*)(long)skb->data + sizeof(struct ethhdr);
85+
void* transport_hdr;
86+
__u8 proto;
87+
88+
if (is_ipv6) {
89+
struct ipv6hdr* ip6h = iphdr;
90+
if (ip6h + 1 > (void*)(long)skb->data_end) {
91+
return false;
92+
}
93+
transport_hdr = iphdr + sizeof(struct ipv6hdr);
94+
memcpy(key->addrv6, ip6h->daddr.s6_addr32, 16);
95+
key->flags = V6DADDR;
96+
proto = ip6h->nexthdr;
97+
} else {
98+
struct iphdr* iph = iphdr;
99+
if (iph + 1 > (void*)(long)skb->data_end) {
100+
return false;
101+
}
102+
transport_hdr = iphdr + sizeof(struct iphdr);
103+
key->addr = iph->daddr;
104+
key->flags = 0;
105+
proto = iph->protocol;
106+
}
107+
108+
if (proto == IPPROTO_TCP) {
109+
struct tcphdr* tcp = transport_hdr;
110+
if (tcp + 1 > (void*)(long)skb->data_end) {
111+
return false;
112+
}
113+
key->port = tcp->dest;
114+
} else if (proto == IPPROTO_UDP) {
115+
struct udphdr* udp = transport_hdr;
116+
if (udp + 1 > (void*)(long)skb->data_end) {
117+
return false;
118+
}
119+
key->port = udp->dest;
120+
} else {
121+
key->port = 0;
122+
}
123+
124+
key->pad = 0;
125+
return true;
126+
}
127+
#endif // HC_DST_MATCH
128+
79129
__attribute__((__always_inline__)) static inline bool hc_encap_ipip(
80130
struct __sk_buff* skb,
81131
struct hc_real_definition* real,

katran/lib/bpf/healthchecking_ipip.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ struct hc_stats {
6060
__u64 pckts_dropped;
6161
__u64 pckts_skipped;
6262
__u64 pckts_too_big;
63+
__u64 pckts_dst_matched;
6364
};
6465

6566
struct {

katran/lib/bpf/healthchecking_maps.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,14 @@ struct {
7979
__uint(map_flags, NO_FLAGS);
8080
} hc_key_map SEC(".maps");
8181

82+
#ifdef HC_DST_MATCH
83+
struct {
84+
__uint(type, BPF_MAP_TYPE_HASH);
85+
__type(key, struct hc_dst_key);
86+
__type(value, struct hc_real_definition);
87+
__uint(max_entries, HC_MAX_DST);
88+
__uint(map_flags, NO_FLAGS);
89+
} hc_dst_reals_map SEC(".maps");
90+
#endif // HC_DST_MATCH
91+
8292
#endif // of __HEALTHCHECKING_MAPS_H

katran/lib/bpf/healthchecking_structs.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ struct hc_stats {
3131
__u64 pckts_dropped;
3232
__u64 pckts_skipped;
3333
__u64 pckts_too_big;
34+
__u64 pckts_dst_matched;
3435
};
3536

3637
// hc_key's definition
@@ -48,4 +49,18 @@ struct hc_mac {
4849
__u8 mac[6];
4950
};
5051

52+
#ifdef HC_DST_MATCH
53+
// Key for destination-based healthcheck matching
54+
struct hc_dst_key {
55+
union {
56+
__be32 addr;
57+
__be32 addrv6[4];
58+
};
59+
__be16 port; // destination port in network byte order;
60+
// 0 = wildcard (match any port)
61+
__u8 flags; // V6DADDR if IPv6
62+
__u8 pad;
63+
};
64+
#endif // HC_DST_MATCH
65+
5166
#endif // of __HEALTHCHECKING_STRUCTS_H

0 commit comments

Comments
 (0)