| // SPDX-License-Identifier: GPL-2.0 |
| /* Copyright (c) 2018 Facebook */ |
| |
| #include <stdlib.h> |
| #include <linux/in.h> |
| #include <linux/ip.h> |
| #include <linux/ipv6.h> |
| #include <linux/tcp.h> |
| #include <linux/udp.h> |
| #include <linux/bpf.h> |
| #include <linux/types.h> |
| #include <linux/if_ether.h> |
| |
| #include <bpf/bpf_endian.h> |
| #include <bpf/bpf_helpers.h> |
| #include "test_select_reuseport_common.h" |
| |
| #ifndef offsetof |
| #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) |
| #endif |
| |
| struct { |
| __uint(type, BPF_MAP_TYPE_ARRAY_OF_MAPS); |
| __uint(max_entries, 1); |
| __type(key, __u32); |
| __type(value, __u32); |
| } outer_map SEC(".maps"); |
| |
| struct { |
| __uint(type, BPF_MAP_TYPE_ARRAY); |
| __uint(max_entries, NR_RESULTS); |
| __type(key, __u32); |
| __type(value, __u32); |
| } result_map SEC(".maps"); |
| |
| struct { |
| __uint(type, BPF_MAP_TYPE_ARRAY); |
| __uint(max_entries, 1); |
| __type(key, __u32); |
| __type(value, int); |
| } tmp_index_ovr_map SEC(".maps"); |
| |
| struct { |
| __uint(type, BPF_MAP_TYPE_ARRAY); |
| __uint(max_entries, 1); |
| __type(key, __u32); |
| __type(value, __u32); |
| } linum_map SEC(".maps"); |
| |
| struct { |
| __uint(type, BPF_MAP_TYPE_ARRAY); |
| __uint(max_entries, 1); |
| __type(key, __u32); |
| __type(value, struct data_check); |
| } data_check_map SEC(".maps"); |
| |
| #define GOTO_DONE(_result) ({ \ |
| result = (_result); \ |
| linum = __LINE__; \ |
| goto done; \ |
| }) |
| |
| SEC("sk_reuseport") |
| int _select_by_skb_data(struct sk_reuseport_md *reuse_md) |
| { |
| __u32 linum, index = 0, flags = 0, index_zero = 0; |
| __u32 *result_cnt; |
| struct data_check data_check = {}; |
| struct cmd *cmd, cmd_copy; |
| void *data, *data_end; |
| void *reuseport_array; |
| enum result result; |
| int *index_ovr; |
| int err; |
| |
| data = reuse_md->data; |
| data_end = reuse_md->data_end; |
| data_check.len = reuse_md->len; |
| data_check.eth_protocol = reuse_md->eth_protocol; |
| data_check.ip_protocol = reuse_md->ip_protocol; |
| data_check.hash = reuse_md->hash; |
| data_check.bind_inany = reuse_md->bind_inany; |
| if (data_check.eth_protocol == bpf_htons(ETH_P_IP)) { |
| if (bpf_skb_load_bytes_relative(reuse_md, |
| offsetof(struct iphdr, saddr), |
| data_check.skb_addrs, 8, |
| BPF_HDR_START_NET)) |
| GOTO_DONE(DROP_MISC); |
| } else { |
| if (bpf_skb_load_bytes_relative(reuse_md, |
| offsetof(struct ipv6hdr, saddr), |
| data_check.skb_addrs, 32, |
| BPF_HDR_START_NET)) |
| GOTO_DONE(DROP_MISC); |
| } |
| |
| /* |
| * The ip_protocol could be a compile time decision |
| * if the bpf_prog.o is dedicated to either TCP or |
| * UDP. |
| * |
| * Otherwise, reuse_md->ip_protocol or |
| * the protocol field in the iphdr can be used. |
| */ |
| if (data_check.ip_protocol == IPPROTO_TCP) { |
| struct tcphdr *th = data; |
| |
| if (th + 1 > data_end) |
| GOTO_DONE(DROP_MISC); |
| |
| data_check.skb_ports[0] = th->source; |
| data_check.skb_ports[1] = th->dest; |
| |
| if (th->fin) |
| /* The connection is being torn down at the end of a |
| * test. It can't contain a cmd, so return early. |
| */ |
| return SK_PASS; |
| |
| if ((th->doff << 2) + sizeof(*cmd) > data_check.len) |
| GOTO_DONE(DROP_ERR_SKB_DATA); |
| if (bpf_skb_load_bytes(reuse_md, th->doff << 2, &cmd_copy, |
| sizeof(cmd_copy))) |
| GOTO_DONE(DROP_MISC); |
| cmd = &cmd_copy; |
| } else if (data_check.ip_protocol == IPPROTO_UDP) { |
| struct udphdr *uh = data; |
| |
| if (uh + 1 > data_end) |
| GOTO_DONE(DROP_MISC); |
| |
| data_check.skb_ports[0] = uh->source; |
| data_check.skb_ports[1] = uh->dest; |
| |
| if (sizeof(struct udphdr) + sizeof(*cmd) > data_check.len) |
| GOTO_DONE(DROP_ERR_SKB_DATA); |
| if (data + sizeof(struct udphdr) + sizeof(*cmd) > data_end) { |
| if (bpf_skb_load_bytes(reuse_md, sizeof(struct udphdr), |
| &cmd_copy, sizeof(cmd_copy))) |
| GOTO_DONE(DROP_MISC); |
| cmd = &cmd_copy; |
| } else { |
| cmd = data + sizeof(struct udphdr); |
| } |
| } else { |
| GOTO_DONE(DROP_MISC); |
| } |
| |
| reuseport_array = bpf_map_lookup_elem(&outer_map, &index_zero); |
| if (!reuseport_array) |
| GOTO_DONE(DROP_ERR_INNER_MAP); |
| |
| index = cmd->reuseport_index; |
| index_ovr = bpf_map_lookup_elem(&tmp_index_ovr_map, &index_zero); |
| if (!index_ovr) |
| GOTO_DONE(DROP_MISC); |
| |
| if (*index_ovr != -1) { |
| index = *index_ovr; |
| *index_ovr = -1; |
| } |
| err = bpf_sk_select_reuseport(reuse_md, reuseport_array, &index, |
| flags); |
| if (!err) |
| GOTO_DONE(PASS); |
| |
| if (cmd->pass_on_failure) |
| GOTO_DONE(PASS_ERR_SK_SELECT_REUSEPORT); |
| else |
| GOTO_DONE(DROP_ERR_SK_SELECT_REUSEPORT); |
| |
| done: |
| result_cnt = bpf_map_lookup_elem(&result_map, &result); |
| if (!result_cnt) |
| return SK_DROP; |
| |
| bpf_map_update_elem(&linum_map, &index_zero, &linum, BPF_ANY); |
| bpf_map_update_elem(&data_check_map, &index_zero, &data_check, BPF_ANY); |
| |
| (*result_cnt)++; |
| return result < PASS ? SK_DROP : SK_PASS; |
| } |
| |
| char _license[] SEC("license") = "GPL"; |