1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Check if we can migrate child sockets. |
4 | * |
5 | * 1. If reuse_md->migrating_sk is NULL (SYN packet), |
6 | * return SK_PASS without selecting a listener. |
7 | * 2. If reuse_md->migrating_sk is not NULL (socket migration), |
8 | * select a listener (reuseport_map[migrate_map[cookie]]) |
9 | * |
10 | * Author: Kuniyuki Iwashima <kuniyu@amazon.co.jp> |
11 | */ |
12 | |
13 | #include <stddef.h> |
14 | #include <string.h> |
15 | #include <linux/bpf.h> |
16 | #include <linux/if_ether.h> |
17 | #include <linux/ip.h> |
18 | #include <linux/ipv6.h> |
19 | #include <linux/tcp.h> |
20 | #include <linux/in.h> |
21 | #include <bpf/bpf_endian.h> |
22 | #include <bpf/bpf_helpers.h> |
23 | |
24 | struct { |
25 | __uint(type, BPF_MAP_TYPE_REUSEPORT_SOCKARRAY); |
26 | __uint(max_entries, 256); |
27 | __type(key, int); |
28 | __type(value, __u64); |
29 | } reuseport_map SEC(".maps" ); |
30 | |
31 | struct { |
32 | __uint(type, BPF_MAP_TYPE_HASH); |
33 | __uint(max_entries, 256); |
34 | __type(key, __u64); |
35 | __type(value, int); |
36 | } migrate_map SEC(".maps" ); |
37 | |
38 | int migrated_at_close = 0; |
39 | int migrated_at_close_fastopen = 0; |
40 | int migrated_at_send_synack = 0; |
41 | int migrated_at_recv_ack = 0; |
42 | __be16 server_port; |
43 | |
44 | SEC("xdp" ) |
45 | int drop_ack(struct xdp_md *xdp) |
46 | { |
47 | void *data_end = (void *)(long)xdp->data_end; |
48 | void *data = (void *)(long)xdp->data; |
49 | struct ethhdr *eth = data; |
50 | struct tcphdr *tcp = NULL; |
51 | |
52 | if (eth + 1 > data_end) |
53 | goto pass; |
54 | |
55 | switch (bpf_ntohs(eth->h_proto)) { |
56 | case ETH_P_IP: { |
57 | struct iphdr *ip = (struct iphdr *)(eth + 1); |
58 | |
59 | if (ip + 1 > data_end) |
60 | goto pass; |
61 | |
62 | if (ip->protocol != IPPROTO_TCP) |
63 | goto pass; |
64 | |
65 | tcp = (struct tcphdr *)((void *)ip + ip->ihl * 4); |
66 | break; |
67 | } |
68 | case ETH_P_IPV6: { |
69 | struct ipv6hdr *ipv6 = (struct ipv6hdr *)(eth + 1); |
70 | |
71 | if (ipv6 + 1 > data_end) |
72 | goto pass; |
73 | |
74 | if (ipv6->nexthdr != IPPROTO_TCP) |
75 | goto pass; |
76 | |
77 | tcp = (struct tcphdr *)(ipv6 + 1); |
78 | break; |
79 | } |
80 | default: |
81 | goto pass; |
82 | } |
83 | |
84 | if (tcp + 1 > data_end) |
85 | goto pass; |
86 | |
87 | if (tcp->dest != server_port) |
88 | goto pass; |
89 | |
90 | if (!tcp->syn && tcp->ack) |
91 | return XDP_DROP; |
92 | |
93 | pass: |
94 | return XDP_PASS; |
95 | } |
96 | |
97 | SEC("sk_reuseport/migrate" ) |
98 | int migrate_reuseport(struct sk_reuseport_md *reuse_md) |
99 | { |
100 | int *key, flags = 0, state, err; |
101 | __u64 cookie; |
102 | |
103 | if (!reuse_md->migrating_sk) |
104 | return SK_PASS; |
105 | |
106 | state = reuse_md->migrating_sk->state; |
107 | cookie = bpf_get_socket_cookie(reuse_md->sk); |
108 | |
109 | key = bpf_map_lookup_elem(&migrate_map, &cookie); |
110 | if (!key) |
111 | return SK_DROP; |
112 | |
113 | err = bpf_sk_select_reuseport(reuse_md, &reuseport_map, key, flags); |
114 | if (err) |
115 | return SK_PASS; |
116 | |
117 | switch (state) { |
118 | case BPF_TCP_ESTABLISHED: |
119 | __sync_fetch_and_add(&migrated_at_close, 1); |
120 | break; |
121 | case BPF_TCP_SYN_RECV: |
122 | __sync_fetch_and_add(&migrated_at_close_fastopen, 1); |
123 | break; |
124 | case BPF_TCP_NEW_SYN_RECV: |
125 | if (!reuse_md->len) |
126 | __sync_fetch_and_add(&migrated_at_send_synack, 1); |
127 | else |
128 | __sync_fetch_and_add(&migrated_at_recv_ack, 1); |
129 | break; |
130 | } |
131 | |
132 | return SK_PASS; |
133 | } |
134 | |
135 | char _license[] SEC("license" ) = "GPL" ; |
136 | |