Skip to content

Commit f3c6cfb

Browse files
p-shah256meta-codesync[bot]
authored andcommitted
add a tc prog to match src ips
Summary: Used when client sends packets behind a SNATs (like cilium in k8s). SNATs usually overwrite the src ip of the outer packet; while inner packet still holds the container ip. This can break the connection state in servers. To allow such clients, we use the SNATed ip as the source of truth, and overwrite the inner packet's src ip to match outer packet. This also helps in enforcing some level of security where a malicious clients sends packet with a valid outer src ip, and the spoofed inner src ip. FWs usually only look at outer packets, allowing the packet to pass thru. NON invasive = does not terminate progs. Chain and run before a tc decap prog. Reviewed By: nikhildl12 Differential Revision: D99894226 fbshipit-source-id: e62ec36a310d35805c893f81d8cfe56bd4d2768d
1 parent 821308e commit f3c6cfb

3 files changed

Lines changed: 269 additions & 0 deletions

File tree

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/* Copyright (C) 2019-present, Facebook, Inc.
2+
*
3+
* This program is free software; you can redistribute it and/or modify
4+
* it under the terms of the GNU General Public License as published by
5+
* the Free Software Foundation; version 2 of the License.
6+
*
7+
* This program is distributed in the hope that it will be useful,
8+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
9+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10+
* GNU General Public License for more details.
11+
*
12+
* You should have received a copy of the GNU General Public License along
13+
* with this program; if not, write to the Free Software Foundation, Inc.,
14+
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
15+
*/
16+
17+
#include <linux/if_ether.h>
18+
#include <linux/in.h>
19+
#include <linux/ip.h>
20+
#include <linux/ipv6.h>
21+
#include <linux/pkt_cls.h>
22+
#include <linux/tcp.h>
23+
#include <linux/udp.h>
24+
#include <stdbool.h>
25+
#include <stddef.h>
26+
27+
#include "katran/lib/linux_includes/bpf.h"
28+
#include "katran/lib/linux_includes/bpf_endian.h"
29+
#include "katran/lib/linux_includes/bpf_helpers.h"
30+
31+
#include "katran/lib/bpf/balancer_consts.h"
32+
33+
/*
34+
* Used when client sends packets behind a SNATs (like cilium in k8s). SNATs
35+
* usually overwrite the src ip of the outer packet; while inner packet still
36+
* holds the container ip. This can break the connection state in servers. To
37+
* allow such clients, we use the SNATed ip as the source of truth, and
38+
* overwrite the inner packet's src ip to match outer packet.
39+
*
40+
* This also helps in enforcing some level of security where a malicious client
41+
* sends packet with a valid outer src ip, and the spoofed inner src ip. FWs
42+
* usually only look at outer packets, allowing the packet to pass thru.
43+
*
44+
* NON invasive = does not terminate progs. Chain and run before a tc decap
45+
* prog.
46+
*/
47+
SEC("tc") int tc_srcmatch(struct __sk_buff* skb) {
48+
void* data = (void*)(long)skb->data;
49+
void* data_end = (void*)(long)skb->data_end;
50+
51+
if (data + sizeof(struct ethhdr) > data_end) {
52+
return TC_ACT_SHOT;
53+
}
54+
55+
struct ethhdr* eth = data;
56+
if (eth + 1 > data_end || eth->h_proto != BE_ETH_P_IPV6) {
57+
return TC_ACT_PIPE;
58+
}
59+
60+
struct ipv6hdr* o_ip6hdr = data + sizeof(struct ethhdr);
61+
if (o_ip6hdr + 1 > data_end || o_ip6hdr->nexthdr != IPPROTO_UDP) {
62+
return TC_ACT_PIPE;
63+
}
64+
65+
struct udphdr* udp_hdr =
66+
data + sizeof(struct ethhdr) + sizeof(struct ipv6hdr);
67+
if (udp_hdr + 1 > data_end || udp_hdr->dest != bpf_htons(KDE_GUE_PORT)) {
68+
return TC_ACT_PIPE;
69+
}
70+
71+
__u8* i_ipv6_proto = data + sizeof(struct ethhdr) + sizeof(struct ipv6hdr) +
72+
sizeof(struct udphdr);
73+
if (i_ipv6_proto + 1 > data_end || !(*i_ipv6_proto & GUEV1_IPV6MASK)) {
74+
return TC_ACT_PIPE;
75+
}
76+
77+
struct ipv6hdr* i_ip6hdr = data + sizeof(struct ethhdr) +
78+
sizeof(struct ipv6hdr) + sizeof(struct udphdr);
79+
if (i_ip6hdr + 1 > data_end || i_ip6hdr->nexthdr != IPPROTO_TCP) {
80+
return TC_ACT_PIPE;
81+
}
82+
83+
__u64 tcp_csum_offset = sizeof(struct ethhdr) + sizeof(struct ipv6hdr) +
84+
sizeof(struct udphdr) + sizeof(struct ipv6hdr) +
85+
offsetof(struct tcphdr, check);
86+
87+
// Copy o_saddr to stack; bpf_skb_store_bytes can't read from packet memory
88+
struct in6_addr o_saddr = o_ip6hdr->saddr;
89+
struct in6_addr i_saddr = i_ip6hdr->saddr;
90+
#pragma unroll
91+
for (int i = 0; i < 4; i++) {
92+
bpf_l4_csum_replace(
93+
/*skb=*/skb,
94+
/*offset=*/tcp_csum_offset,
95+
/*from=*/i_saddr.in6_u.u6_addr32[i],
96+
/*to=*/o_saddr.in6_u.u6_addr32[i],
97+
/*flags=*/4); // 4-byte replacement
98+
}
99+
100+
__u64 i_ip6hdr_src_offset = sizeof(struct ethhdr) + sizeof(struct ipv6hdr) +
101+
sizeof(struct udphdr) + offsetof(struct ipv6hdr, saddr);
102+
103+
bpf_skb_store_bytes(
104+
/*skb=*/skb,
105+
/*offset=*/i_ip6hdr_src_offset,
106+
/*from=*/&o_saddr,
107+
/*len=*/sizeof(struct in6_addr),
108+
/*flags=*/BPF_F_RECOMPUTE_CSUM // as we change src
109+
);
110+
111+
return TC_ACT_PIPE;
112+
}
113+
114+
char _license[] SEC("license") = "GPL";
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// @nolint
2+
3+
/* Copyright (C) 2019-present, Facebook, Inc.
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 <cstring>
21+
#include <string>
22+
#include <utility>
23+
#include <vector>
24+
#include <folly/base64.h>
25+
#include <katran/lib/testing/tools/PacketAttributes.h>
26+
#include <katran/lib/testing/tools/PacketBuilder.h>
27+
28+
namespace katran {
29+
namespace testing {
30+
31+
inline std::vector<PacketAttributes> buildTcSrcMatchFixtures() {
32+
// shivless packet (9887 gue encapped, ipv6, tcp)
33+
auto before = PacketBuilder::newPacket()
34+
.Eth("00:00:00:00:00:01", "00:00:00:00:00:02")
35+
.IPv6("2001:db8::1", "2001:db8::2")
36+
.UDP(1234, 9887)
37+
.IPv6("2001:db8::99", "2001:db8::3")
38+
.TCP(80, 443)
39+
.payload("test")
40+
.build();
41+
auto after = PacketBuilder::newPacket()
42+
.Eth("00:00:00:00:00:01", "00:00:00:00:00:02")
43+
.IPv6("2001:db8::1", "2001:db8::2")
44+
.UDP(1234, 9887)
45+
.IPv6("2001:db8::1", "2001:db8::3")
46+
.TCP(80, 443)
47+
.payload("test")
48+
.build();
49+
50+
// Plain IPv4 — pass through
51+
auto plainIpv4 = PacketBuilder::newPacket()
52+
.Eth("00:00:00:00:00:01", "00:00:00:00:00:02")
53+
.IPv4("10.0.0.1", "10.0.0.2")
54+
.TCP(80, 443)
55+
.build();
56+
57+
// IPv6 TCP (not UDP) — pass through
58+
auto ipv6Tcp = PacketBuilder::newPacket()
59+
.Eth("00:00:00:00:00:01", "00:00:00:00:00:02")
60+
.IPv6("2001:db8::1", "2001:db8::2")
61+
.TCP(80, 443)
62+
.build();
63+
64+
// IPv6 UDP wrong port — pass through
65+
auto wrongPort = PacketBuilder::newPacket()
66+
.Eth("00:00:00:00:00:01", "00:00:00:00:00:02")
67+
.IPv6("2001:db8::1", "2001:db8::2")
68+
.UDP(1234, 53)
69+
.payload("dns query")
70+
.build();
71+
72+
return {
73+
{
74+
.inputPacket = before.base64Packet,
75+
.description = "GUE IPv6-in-IPv6: rewrite inner src to match outer",
76+
.expectedReturnValue = "TC_ACT_PIPE",
77+
.expectedOutputPacket = after.base64Packet,
78+
},
79+
{
80+
.inputPacket = plainIpv4.base64Packet,
81+
.description = "Plain IPv4: pass through",
82+
.expectedReturnValue = "TC_ACT_PIPE",
83+
.expectedOutputPacket = plainIpv4.base64Packet,
84+
},
85+
{
86+
.inputPacket = ipv6Tcp.base64Packet,
87+
.description = "IPv6 TCP: not UDP, pass through",
88+
.expectedReturnValue = "TC_ACT_PIPE",
89+
.expectedOutputPacket = ipv6Tcp.base64Packet,
90+
},
91+
{
92+
.inputPacket = wrongPort.base64Packet,
93+
.description = "IPv6 UDP port 53: not GUE, pass through",
94+
.expectedReturnValue = "TC_ACT_PIPE",
95+
.expectedOutputPacket = wrongPort.base64Packet,
96+
},
97+
};
98+
}
99+
100+
} // namespace testing
101+
} // namespace katran
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/* Copyright (C) 2019-present, Facebook, Inc.
2+
*
3+
* This program is free software; you can redistribute it and/or modify
4+
* it under the terms of the GNU General Public License as published by
5+
* the Free Software Foundation; version 2 of the License.
6+
*
7+
* This program is distributed in the hope that it will be useful,
8+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
9+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10+
* GNU General Public License for more details.
11+
*
12+
* You should have received a copy of the GNU General Public License along
13+
* with this program; if not, write to the Free Software Foundation, Inc.,
14+
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
15+
*/
16+
17+
#include <gflags/gflags.h>
18+
#include <glog/logging.h>
19+
20+
#include <katran/decap/tc_bpf/tc_srcmatch.skel.h>
21+
#include "katran/decap/testing/TcSrcMatchTestFixtures.h"
22+
#include "katran/lib/testing/framework/BpfTester.h"
23+
24+
int main(int argc, char** argv) {
25+
gflags::ParseCommandLineFlags(&argc, &argv, true);
26+
google::InitGoogleLogging(argv[0]);
27+
FLAGS_logtostderr = 1;
28+
29+
auto* skel = tc_srcmatch__open();
30+
if (!skel) {
31+
LOG(FATAL) << "Failed to open tc_srcmatch skeleton";
32+
}
33+
34+
if (tc_srcmatch__load(skel)) {
35+
LOG(FATAL) << "Failed to load tc_srcmatch skeleton";
36+
}
37+
38+
auto progFd = bpf_program__fd(skel->progs.tc_srcmatch);
39+
if (progFd < 0) {
40+
LOG(FATAL) << "Failed to get prog fd for tc_srcmatch";
41+
}
42+
43+
auto fixtures = katran::testing::buildTcSrcMatchFixtures();
44+
45+
katran::TesterConfig config;
46+
config.testData = fixtures;
47+
katran::BpfTester tester(config);
48+
49+
std::vector<struct __sk_buff> ctxs(fixtures.size());
50+
auto success = tester.testClsFromFixture(progFd, ctxs);
51+
52+
tc_srcmatch__destroy(skel);
53+
return success ? 0 : 1;
54+
}

0 commit comments

Comments
 (0)