-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathneighsnoopd.bpf.c
266 lines (209 loc) · 7.11 KB
/
neighsnoopd.bpf.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
/* SPDX-License-Identifier: GPL-2.0-or-later */
/* SPDX-FileCopyrightText: 2024 - 1984 Hosting Company <[email protected]> */
/* SPDX-FileCopyrightText: 2024 - Freyx Solutions <[email protected]> */
/* SPDX-FileContributor: Freysteinn Alfredsson <[email protected]> */
/* SPDX-FileContributor: Julius Thor Bess Rikardsson <[email protected]> */
#include <linux/bpf.h>
#include <linux/ip.h>
#include <linux/if_arp.h>
#include <linux/if_packet.h>
#include <linux/if_ether.h>
#include <linux/if_vlan.h>
#include <linux/in.h>
#include <bpf/bpf_helpers.h>
#include <linux/pkt_cls.h>
#include "include/xdp/parsing_helpers.h"
#include "neighsnoopd_shared.h"
#define ND_NEIGHBOR_ADVERT 136
#define ND_OPT_MAX_CHAIN 3
#define ND_OPT_TARGET_LINKADDR 2
struct nd_opt_hdr {
__u8 nd_opt_type;
__u8 nd_opt_len; // Length in units of 8 octets
};
struct {
__uint(type, BPF_MAP_TYPE_LPM_TRIE);
__type(key, struct network_entry);
__type(value, struct network_value);
__uint(max_entries, 4096);
__uint(map_flags, BPF_F_NO_PREALLOC);
} target_networks SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, 1 << 24); // 16 MB
} neighbor_ringbuf SEC(".maps");
// Find ND Option Header of specified type
static __always_inline int find_nd_opt(struct hdr_cursor *nh,
void *data_end,
__u8 next_hdr_type)
{
for (int i = 0; i < ND_OPT_MAX_CHAIN; ++i) {
struct nd_opt_hdr *hdr = nh->pos;
if ((void *)(hdr + 1) > data_end)
return -1;
if (((void *)hdr) + hdr->nd_opt_len * 8 > data_end)
return -1;
switch (hdr->nd_opt_type) {
case ND_OPT_TARGET_LINKADDR:
return 0;
default:
nh->pos = ((void *)hdr) + hdr->nd_opt_len * 8;
}
}
return -1;
}
static __always_inline int handle_nd_reply(struct hdr_cursor *nh,
void *data_end, struct ethhdr *eth,
struct neighbor_reply *reply)
{
struct ipv6hdr *ip;
struct icmp6hdr *icmp6;
struct in6_addr *target_ipv6;
struct network_entry key;
struct network_value *value;
struct nd_opt_hdr *nd_opt_hdr;
__u8 *target_mac;
int ret = -1;
// Parse the IPv6 header
if (parse_ip6hdr(nh, data_end, &ip) != IPPROTO_ICMPV6)
goto out;
// Check if the message is a Neighbor Advertisement
if (parse_icmp6hdr(nh, data_end, &icmp6) != ND_NEIGHBOR_ADVERT)
goto out;
if ((void *)(icmp6 + 1) > data_end)
goto out;
nh->pos = icmp6 + 1;
// Check if the message is long enough to contain the target IPv6 address
target_ipv6 = nh->pos;
if ((void *)(target_ipv6 + 1) > data_end)
goto out;
nh->pos = target_ipv6 + 1;
// Check if the target IP address is in the list of target networks
key.prefixlen = 128;
__builtin_memcpy(&key.network, target_ipv6, sizeof(*target_ipv6));
value = bpf_map_lookup_elem(&target_networks, &key);
if (!value)
goto out;
// Parse options to find the Source Link-Layer Address (MAC address)
if (find_nd_opt(nh, data_end, ND_OPT_TARGET_LINKADDR)) {
target_mac = eth->h_source;
} else {
nd_opt_hdr = nh->pos;
if ((void *)(nd_opt_hdr + 1) > data_end)
goto out;
target_mac = (void *)(nd_opt_hdr + 1);
if ((void *)(target_mac + ETH_ALEN) > data_end)
goto out;
}
// Add the data to the ringbuffer
__builtin_memcpy(reply->mac, target_mac, ETH_ALEN);
__builtin_memcpy(&reply->ip, target_ipv6, sizeof(*target_ipv6));
reply->in_family = AF_INET6;
reply->network_id = value->network_id;
ret = 0;
out:
return ret;
}
static __always_inline int handle_arp_reply(struct hdr_cursor *nh, void *data_end,
struct neighbor_reply *reply)
{
struct arphdr *arp;
__u8 *sender_ip;
__u8 *sender_mac;
struct in6_addr target_ipv6;
struct network_entry key;
struct network_value *value;
int ret = -1;
if (nh->pos + sizeof(struct arphdr) > data_end)
goto out;
arp = nh->pos;
if (arp->ar_op != bpf_htons(ARPOP_REPLY))
goto out;
// Extract IPv4 and MAC addresses
sender_mac = (__u8 *)(arp + 1);
if (sender_mac + 6 > (__u8 *)data_end)
goto out;
sender_ip = sender_mac + arp->ar_hln;
if (sender_ip + 4 > (__u8 *)data_end)
goto out;
// Check if the target IP address is in the list of target networks
map_ipv4_to_ipv6(&target_ipv6, *(__be32 *)sender_ip);
key.prefixlen = 128;
__builtin_memcpy(&key.network, &target_ipv6, sizeof(target_ipv6));
value = bpf_map_lookup_elem(&target_networks, &key);
if (!value)
goto out;
// Add the data to the ringbuffer
__builtin_memcpy(reply->mac, sender_mac, ETH_ALEN);
__builtin_memcpy(&reply->ip, &target_ipv6, sizeof(target_ipv6));
reply->in_family = AF_INET;
reply->network_id = value->network_id;
ret = 0;
out:
return ret;
}
static __always_inline int handle_neighbor_reply(
void *data, void *data_end, struct neighbor_reply *reply)
{
int ret = -1;
struct collect_vlans vlans = { 0 };
struct ethhdr *eth;
int eth_type;
struct hdr_cursor nh;
nh.pos = data;
eth_type = parse_ethhdr_vlan(&nh, data_end, ð, &vlans);
if (eth_type == bpf_htons(ETH_P_IPV6))
ret = handle_nd_reply(&nh, data_end, eth, reply);
else if (eth_type == bpf_htons(ETH_P_ARP))
ret = handle_arp_reply(&nh, data_end, reply);
else
goto out;
reply->vlan_id = vlans.id[0];
out:
return ret;
}
SEC("xdp")
int handle_neighbor_reply_xdp(struct xdp_md *ctx)
{
void *data_end = (void *)(unsigned long long)ctx->data_end;
void *data = (void *)(unsigned long long)ctx->data;
int ret;
struct neighbor_reply *neighbor_reply = bpf_ringbuf_reserve(
&neighbor_ringbuf, sizeof(*neighbor_reply), 0);
if (!neighbor_reply)
goto out;
ret = handle_neighbor_reply(data, data_end, neighbor_reply);
if (ret) {
bpf_ringbuf_discard(neighbor_reply, 0);
goto out;
}
neighbor_reply->ingress_ifindex = ctx->ingress_ifindex;
// Send the data to userspace
bpf_ringbuf_submit(neighbor_reply, 0);
out:
return XDP_PASS;
}
SEC("tc")
int handle_neighbor_reply_tc(struct __sk_buff *skb)
{
void *data_end = (void *)(unsigned long long)skb->data_end;
void *data = (void *)(unsigned long long)skb->data;
int ret;
struct neighbor_reply *neighbor_reply = bpf_ringbuf_reserve(
&neighbor_ringbuf, sizeof(*neighbor_reply), 0);
if (!neighbor_reply)
goto out;
ret = handle_neighbor_reply(data, data_end, neighbor_reply);
if (ret) {
bpf_ringbuf_discard(neighbor_reply, 0);
goto out;
}
neighbor_reply->ingress_ifindex = skb->ifindex;
neighbor_reply->vlan_id = skb->vlan_present ? skb->vlan_tci
& VLAN_VID_MASK : 0;
// Send the data to userspace
bpf_ringbuf_submit(neighbor_reply, 0);
out:
return TC_ACT_OK;
}
char _license[] SEC("license") = "GPL";