1ad2f8eb0SMartin KaFai Lau // SPDX-License-Identifier: GPL-2.0
2ad2f8eb0SMartin KaFai Lau /* Copyright (c) 2020 Facebook */
3ad2f8eb0SMartin KaFai Lau
4ad2f8eb0SMartin KaFai Lau #include <stddef.h>
5ad2f8eb0SMartin KaFai Lau #include <errno.h>
6ad2f8eb0SMartin KaFai Lau #include <stdbool.h>
7ad2f8eb0SMartin KaFai Lau #include <sys/types.h>
8ad2f8eb0SMartin KaFai Lau #include <sys/socket.h>
9ad2f8eb0SMartin KaFai Lau #include <linux/tcp.h>
10ad2f8eb0SMartin KaFai Lau #include <linux/socket.h>
11ad2f8eb0SMartin KaFai Lau #include <linux/bpf.h>
12ad2f8eb0SMartin KaFai Lau #include <linux/types.h>
13ad2f8eb0SMartin KaFai Lau #include <bpf/bpf_helpers.h>
14ad2f8eb0SMartin KaFai Lau #include <bpf/bpf_endian.h>
15ad2f8eb0SMartin KaFai Lau #define BPF_PROG_TEST_TCP_HDR_OPTIONS
16ad2f8eb0SMartin KaFai Lau #include "test_tcp_hdr_options.h"
17ad2f8eb0SMartin KaFai Lau
18ad2f8eb0SMartin KaFai Lau #ifndef sizeof_field
19ad2f8eb0SMartin KaFai Lau #define sizeof_field(TYPE, MEMBER) sizeof((((TYPE *)0)->MEMBER))
20ad2f8eb0SMartin KaFai Lau #endif
21ad2f8eb0SMartin KaFai Lau
22ad2f8eb0SMartin KaFai Lau __u8 test_kind = TCPOPT_EXP;
23ad2f8eb0SMartin KaFai Lau __u16 test_magic = 0xeB9F;
2496d46c50SMartin KaFai Lau __u32 inherit_cb_flags = 0;
25ad2f8eb0SMartin KaFai Lau
26ad2f8eb0SMartin KaFai Lau struct bpf_test_option passive_synack_out = {};
27ad2f8eb0SMartin KaFai Lau struct bpf_test_option passive_fin_out = {};
28ad2f8eb0SMartin KaFai Lau
29ad2f8eb0SMartin KaFai Lau struct bpf_test_option passive_estab_in = {};
30ad2f8eb0SMartin KaFai Lau struct bpf_test_option passive_fin_in = {};
31ad2f8eb0SMartin KaFai Lau
32ad2f8eb0SMartin KaFai Lau struct bpf_test_option active_syn_out = {};
33ad2f8eb0SMartin KaFai Lau struct bpf_test_option active_fin_out = {};
34ad2f8eb0SMartin KaFai Lau
35ad2f8eb0SMartin KaFai Lau struct bpf_test_option active_estab_in = {};
36ad2f8eb0SMartin KaFai Lau struct bpf_test_option active_fin_in = {};
37ad2f8eb0SMartin KaFai Lau
38ad2f8eb0SMartin KaFai Lau struct {
39ad2f8eb0SMartin KaFai Lau __uint(type, BPF_MAP_TYPE_SK_STORAGE);
40ad2f8eb0SMartin KaFai Lau __uint(map_flags, BPF_F_NO_PREALLOC);
41ad2f8eb0SMartin KaFai Lau __type(key, int);
42ad2f8eb0SMartin KaFai Lau __type(value, struct hdr_stg);
43ad2f8eb0SMartin KaFai Lau } hdr_stg_map SEC(".maps");
44ad2f8eb0SMartin KaFai Lau
skops_want_cookie(const struct bpf_sock_ops * skops)45ad2f8eb0SMartin KaFai Lau static bool skops_want_cookie(const struct bpf_sock_ops *skops)
46ad2f8eb0SMartin KaFai Lau {
47ad2f8eb0SMartin KaFai Lau return skops->args[0] == BPF_WRITE_HDR_TCP_SYNACK_COOKIE;
48ad2f8eb0SMartin KaFai Lau }
49ad2f8eb0SMartin KaFai Lau
skops_current_mss(const struct bpf_sock_ops * skops)50ad2f8eb0SMartin KaFai Lau static bool skops_current_mss(const struct bpf_sock_ops *skops)
51ad2f8eb0SMartin KaFai Lau {
52ad2f8eb0SMartin KaFai Lau return skops->args[0] == BPF_WRITE_HDR_TCP_CURRENT_MSS;
53ad2f8eb0SMartin KaFai Lau }
54ad2f8eb0SMartin KaFai Lau
option_total_len(__u8 flags)55ad2f8eb0SMartin KaFai Lau static __u8 option_total_len(__u8 flags)
56ad2f8eb0SMartin KaFai Lau {
57ad2f8eb0SMartin KaFai Lau __u8 i, len = 1; /* +1 for flags */
58ad2f8eb0SMartin KaFai Lau
59ad2f8eb0SMartin KaFai Lau if (!flags)
60ad2f8eb0SMartin KaFai Lau return 0;
61ad2f8eb0SMartin KaFai Lau
62ad2f8eb0SMartin KaFai Lau /* RESEND bit does not use a byte */
63ad2f8eb0SMartin KaFai Lau for (i = OPTION_RESEND + 1; i < __NR_OPTION_FLAGS; i++)
64ad2f8eb0SMartin KaFai Lau len += !!TEST_OPTION_FLAGS(flags, i);
65ad2f8eb0SMartin KaFai Lau
66ad2f8eb0SMartin KaFai Lau if (test_kind == TCPOPT_EXP)
67ad2f8eb0SMartin KaFai Lau return len + TCP_BPF_EXPOPT_BASE_LEN;
68ad2f8eb0SMartin KaFai Lau else
69ad2f8eb0SMartin KaFai Lau return len + 2; /* +1 kind, +1 kind-len */
70ad2f8eb0SMartin KaFai Lau }
71ad2f8eb0SMartin KaFai Lau
write_test_option(const struct bpf_test_option * test_opt,__u8 * data)72ad2f8eb0SMartin KaFai Lau static void write_test_option(const struct bpf_test_option *test_opt,
73ad2f8eb0SMartin KaFai Lau __u8 *data)
74ad2f8eb0SMartin KaFai Lau {
75ad2f8eb0SMartin KaFai Lau __u8 offset = 0;
76ad2f8eb0SMartin KaFai Lau
77ad2f8eb0SMartin KaFai Lau data[offset++] = test_opt->flags;
78ad2f8eb0SMartin KaFai Lau if (TEST_OPTION_FLAGS(test_opt->flags, OPTION_MAX_DELACK_MS))
79ad2f8eb0SMartin KaFai Lau data[offset++] = test_opt->max_delack_ms;
80ad2f8eb0SMartin KaFai Lau
81ad2f8eb0SMartin KaFai Lau if (TEST_OPTION_FLAGS(test_opt->flags, OPTION_RAND))
82ad2f8eb0SMartin KaFai Lau data[offset++] = test_opt->rand;
83ad2f8eb0SMartin KaFai Lau }
84ad2f8eb0SMartin KaFai Lau
store_option(struct bpf_sock_ops * skops,const struct bpf_test_option * test_opt)85ad2f8eb0SMartin KaFai Lau static int store_option(struct bpf_sock_ops *skops,
86ad2f8eb0SMartin KaFai Lau const struct bpf_test_option *test_opt)
87ad2f8eb0SMartin KaFai Lau {
88ad2f8eb0SMartin KaFai Lau union {
89ad2f8eb0SMartin KaFai Lau struct tcp_exprm_opt exprm;
90ad2f8eb0SMartin KaFai Lau struct tcp_opt regular;
91ad2f8eb0SMartin KaFai Lau } write_opt;
92ad2f8eb0SMartin KaFai Lau int err;
93ad2f8eb0SMartin KaFai Lau
94ad2f8eb0SMartin KaFai Lau if (test_kind == TCPOPT_EXP) {
95ad2f8eb0SMartin KaFai Lau write_opt.exprm.kind = TCPOPT_EXP;
96ad2f8eb0SMartin KaFai Lau write_opt.exprm.len = option_total_len(test_opt->flags);
97ad2f8eb0SMartin KaFai Lau write_opt.exprm.magic = __bpf_htons(test_magic);
98ad2f8eb0SMartin KaFai Lau write_opt.exprm.data32 = 0;
99ad2f8eb0SMartin KaFai Lau write_test_option(test_opt, write_opt.exprm.data);
100ad2f8eb0SMartin KaFai Lau err = bpf_store_hdr_opt(skops, &write_opt.exprm,
101ad2f8eb0SMartin KaFai Lau sizeof(write_opt.exprm), 0);
102ad2f8eb0SMartin KaFai Lau } else {
103ad2f8eb0SMartin KaFai Lau write_opt.regular.kind = test_kind;
104ad2f8eb0SMartin KaFai Lau write_opt.regular.len = option_total_len(test_opt->flags);
105ad2f8eb0SMartin KaFai Lau write_opt.regular.data32 = 0;
106ad2f8eb0SMartin KaFai Lau write_test_option(test_opt, write_opt.regular.data);
107ad2f8eb0SMartin KaFai Lau err = bpf_store_hdr_opt(skops, &write_opt.regular,
108ad2f8eb0SMartin KaFai Lau sizeof(write_opt.regular), 0);
109ad2f8eb0SMartin KaFai Lau }
110ad2f8eb0SMartin KaFai Lau
111ad2f8eb0SMartin KaFai Lau if (err)
112ad2f8eb0SMartin KaFai Lau RET_CG_ERR(err);
113ad2f8eb0SMartin KaFai Lau
114ad2f8eb0SMartin KaFai Lau return CG_OK;
115ad2f8eb0SMartin KaFai Lau }
116ad2f8eb0SMartin KaFai Lau
parse_test_option(struct bpf_test_option * opt,const __u8 * start)117ad2f8eb0SMartin KaFai Lau static int parse_test_option(struct bpf_test_option *opt, const __u8 *start)
118ad2f8eb0SMartin KaFai Lau {
119ad2f8eb0SMartin KaFai Lau opt->flags = *start++;
120ad2f8eb0SMartin KaFai Lau
121ad2f8eb0SMartin KaFai Lau if (TEST_OPTION_FLAGS(opt->flags, OPTION_MAX_DELACK_MS))
122ad2f8eb0SMartin KaFai Lau opt->max_delack_ms = *start++;
123ad2f8eb0SMartin KaFai Lau
124ad2f8eb0SMartin KaFai Lau if (TEST_OPTION_FLAGS(opt->flags, OPTION_RAND))
125ad2f8eb0SMartin KaFai Lau opt->rand = *start++;
126ad2f8eb0SMartin KaFai Lau
127ad2f8eb0SMartin KaFai Lau return 0;
128ad2f8eb0SMartin KaFai Lau }
129ad2f8eb0SMartin KaFai Lau
load_option(struct bpf_sock_ops * skops,struct bpf_test_option * test_opt,bool from_syn)130ad2f8eb0SMartin KaFai Lau static int load_option(struct bpf_sock_ops *skops,
131ad2f8eb0SMartin KaFai Lau struct bpf_test_option *test_opt, bool from_syn)
132ad2f8eb0SMartin KaFai Lau {
133ad2f8eb0SMartin KaFai Lau union {
134ad2f8eb0SMartin KaFai Lau struct tcp_exprm_opt exprm;
135ad2f8eb0SMartin KaFai Lau struct tcp_opt regular;
136ad2f8eb0SMartin KaFai Lau } search_opt;
137ad2f8eb0SMartin KaFai Lau int ret, load_flags = from_syn ? BPF_LOAD_HDR_OPT_TCP_SYN : 0;
138ad2f8eb0SMartin KaFai Lau
139ad2f8eb0SMartin KaFai Lau if (test_kind == TCPOPT_EXP) {
140ad2f8eb0SMartin KaFai Lau search_opt.exprm.kind = TCPOPT_EXP;
141ad2f8eb0SMartin KaFai Lau search_opt.exprm.len = 4;
142ad2f8eb0SMartin KaFai Lau search_opt.exprm.magic = __bpf_htons(test_magic);
143ad2f8eb0SMartin KaFai Lau search_opt.exprm.data32 = 0;
144ad2f8eb0SMartin KaFai Lau ret = bpf_load_hdr_opt(skops, &search_opt.exprm,
145ad2f8eb0SMartin KaFai Lau sizeof(search_opt.exprm), load_flags);
146ad2f8eb0SMartin KaFai Lau if (ret < 0)
147ad2f8eb0SMartin KaFai Lau return ret;
148ad2f8eb0SMartin KaFai Lau return parse_test_option(test_opt, search_opt.exprm.data);
149ad2f8eb0SMartin KaFai Lau } else {
150ad2f8eb0SMartin KaFai Lau search_opt.regular.kind = test_kind;
151ad2f8eb0SMartin KaFai Lau search_opt.regular.len = 0;
152ad2f8eb0SMartin KaFai Lau search_opt.regular.data32 = 0;
153ad2f8eb0SMartin KaFai Lau ret = bpf_load_hdr_opt(skops, &search_opt.regular,
154ad2f8eb0SMartin KaFai Lau sizeof(search_opt.regular), load_flags);
155ad2f8eb0SMartin KaFai Lau if (ret < 0)
156ad2f8eb0SMartin KaFai Lau return ret;
157ad2f8eb0SMartin KaFai Lau return parse_test_option(test_opt, search_opt.regular.data);
158ad2f8eb0SMartin KaFai Lau }
159ad2f8eb0SMartin KaFai Lau }
160ad2f8eb0SMartin KaFai Lau
synack_opt_len(struct bpf_sock_ops * skops)161ad2f8eb0SMartin KaFai Lau static int synack_opt_len(struct bpf_sock_ops *skops)
162ad2f8eb0SMartin KaFai Lau {
163ad2f8eb0SMartin KaFai Lau struct bpf_test_option test_opt = {};
164ad2f8eb0SMartin KaFai Lau __u8 optlen;
165ad2f8eb0SMartin KaFai Lau int err;
166ad2f8eb0SMartin KaFai Lau
167ad2f8eb0SMartin KaFai Lau if (!passive_synack_out.flags)
168ad2f8eb0SMartin KaFai Lau return CG_OK;
169ad2f8eb0SMartin KaFai Lau
170ad2f8eb0SMartin KaFai Lau err = load_option(skops, &test_opt, true);
171ad2f8eb0SMartin KaFai Lau
172ad2f8eb0SMartin KaFai Lau /* bpf_test_option is not found */
173ad2f8eb0SMartin KaFai Lau if (err == -ENOMSG)
174ad2f8eb0SMartin KaFai Lau return CG_OK;
175ad2f8eb0SMartin KaFai Lau
176ad2f8eb0SMartin KaFai Lau if (err)
177ad2f8eb0SMartin KaFai Lau RET_CG_ERR(err);
178ad2f8eb0SMartin KaFai Lau
179ad2f8eb0SMartin KaFai Lau optlen = option_total_len(passive_synack_out.flags);
180ad2f8eb0SMartin KaFai Lau if (optlen) {
181ad2f8eb0SMartin KaFai Lau err = bpf_reserve_hdr_opt(skops, optlen, 0);
182ad2f8eb0SMartin KaFai Lau if (err)
183ad2f8eb0SMartin KaFai Lau RET_CG_ERR(err);
184ad2f8eb0SMartin KaFai Lau }
185ad2f8eb0SMartin KaFai Lau
186ad2f8eb0SMartin KaFai Lau return CG_OK;
187ad2f8eb0SMartin KaFai Lau }
188ad2f8eb0SMartin KaFai Lau
write_synack_opt(struct bpf_sock_ops * skops)189ad2f8eb0SMartin KaFai Lau static int write_synack_opt(struct bpf_sock_ops *skops)
190ad2f8eb0SMartin KaFai Lau {
191ad2f8eb0SMartin KaFai Lau struct bpf_test_option opt;
192ad2f8eb0SMartin KaFai Lau
193ad2f8eb0SMartin KaFai Lau if (!passive_synack_out.flags)
194ad2f8eb0SMartin KaFai Lau /* We should not even be called since no header
195ad2f8eb0SMartin KaFai Lau * space has been reserved.
196ad2f8eb0SMartin KaFai Lau */
197ad2f8eb0SMartin KaFai Lau RET_CG_ERR(0);
198ad2f8eb0SMartin KaFai Lau
199ad2f8eb0SMartin KaFai Lau opt = passive_synack_out;
200ad2f8eb0SMartin KaFai Lau if (skops_want_cookie(skops))
201ad2f8eb0SMartin KaFai Lau SET_OPTION_FLAGS(opt.flags, OPTION_RESEND);
202ad2f8eb0SMartin KaFai Lau
203ad2f8eb0SMartin KaFai Lau return store_option(skops, &opt);
204ad2f8eb0SMartin KaFai Lau }
205ad2f8eb0SMartin KaFai Lau
syn_opt_len(struct bpf_sock_ops * skops)206ad2f8eb0SMartin KaFai Lau static int syn_opt_len(struct bpf_sock_ops *skops)
207ad2f8eb0SMartin KaFai Lau {
208ad2f8eb0SMartin KaFai Lau __u8 optlen;
209ad2f8eb0SMartin KaFai Lau int err;
210ad2f8eb0SMartin KaFai Lau
211ad2f8eb0SMartin KaFai Lau if (!active_syn_out.flags)
212ad2f8eb0SMartin KaFai Lau return CG_OK;
213ad2f8eb0SMartin KaFai Lau
214ad2f8eb0SMartin KaFai Lau optlen = option_total_len(active_syn_out.flags);
215ad2f8eb0SMartin KaFai Lau if (optlen) {
216ad2f8eb0SMartin KaFai Lau err = bpf_reserve_hdr_opt(skops, optlen, 0);
217ad2f8eb0SMartin KaFai Lau if (err)
218ad2f8eb0SMartin KaFai Lau RET_CG_ERR(err);
219ad2f8eb0SMartin KaFai Lau }
220ad2f8eb0SMartin KaFai Lau
221ad2f8eb0SMartin KaFai Lau return CG_OK;
222ad2f8eb0SMartin KaFai Lau }
223ad2f8eb0SMartin KaFai Lau
write_syn_opt(struct bpf_sock_ops * skops)224ad2f8eb0SMartin KaFai Lau static int write_syn_opt(struct bpf_sock_ops *skops)
225ad2f8eb0SMartin KaFai Lau {
226ad2f8eb0SMartin KaFai Lau if (!active_syn_out.flags)
227ad2f8eb0SMartin KaFai Lau RET_CG_ERR(0);
228ad2f8eb0SMartin KaFai Lau
229ad2f8eb0SMartin KaFai Lau return store_option(skops, &active_syn_out);
230ad2f8eb0SMartin KaFai Lau }
231ad2f8eb0SMartin KaFai Lau
fin_opt_len(struct bpf_sock_ops * skops)232ad2f8eb0SMartin KaFai Lau static int fin_opt_len(struct bpf_sock_ops *skops)
233ad2f8eb0SMartin KaFai Lau {
234ad2f8eb0SMartin KaFai Lau struct bpf_test_option *opt;
235ad2f8eb0SMartin KaFai Lau struct hdr_stg *hdr_stg;
236ad2f8eb0SMartin KaFai Lau __u8 optlen;
237ad2f8eb0SMartin KaFai Lau int err;
238ad2f8eb0SMartin KaFai Lau
239ad2f8eb0SMartin KaFai Lau if (!skops->sk)
240ad2f8eb0SMartin KaFai Lau RET_CG_ERR(0);
241ad2f8eb0SMartin KaFai Lau
242ad2f8eb0SMartin KaFai Lau hdr_stg = bpf_sk_storage_get(&hdr_stg_map, skops->sk, NULL, 0);
243ad2f8eb0SMartin KaFai Lau if (!hdr_stg)
244ad2f8eb0SMartin KaFai Lau RET_CG_ERR(0);
245ad2f8eb0SMartin KaFai Lau
246ad2f8eb0SMartin KaFai Lau if (hdr_stg->active)
247ad2f8eb0SMartin KaFai Lau opt = &active_fin_out;
248ad2f8eb0SMartin KaFai Lau else
249ad2f8eb0SMartin KaFai Lau opt = &passive_fin_out;
250ad2f8eb0SMartin KaFai Lau
251ad2f8eb0SMartin KaFai Lau optlen = option_total_len(opt->flags);
252ad2f8eb0SMartin KaFai Lau if (optlen) {
253ad2f8eb0SMartin KaFai Lau err = bpf_reserve_hdr_opt(skops, optlen, 0);
254ad2f8eb0SMartin KaFai Lau if (err)
255ad2f8eb0SMartin KaFai Lau RET_CG_ERR(err);
256ad2f8eb0SMartin KaFai Lau }
257ad2f8eb0SMartin KaFai Lau
258ad2f8eb0SMartin KaFai Lau return CG_OK;
259ad2f8eb0SMartin KaFai Lau }
260ad2f8eb0SMartin KaFai Lau
write_fin_opt(struct bpf_sock_ops * skops)261ad2f8eb0SMartin KaFai Lau static int write_fin_opt(struct bpf_sock_ops *skops)
262ad2f8eb0SMartin KaFai Lau {
263ad2f8eb0SMartin KaFai Lau struct bpf_test_option *opt;
264ad2f8eb0SMartin KaFai Lau struct hdr_stg *hdr_stg;
265ad2f8eb0SMartin KaFai Lau
266ad2f8eb0SMartin KaFai Lau if (!skops->sk)
267ad2f8eb0SMartin KaFai Lau RET_CG_ERR(0);
268ad2f8eb0SMartin KaFai Lau
269ad2f8eb0SMartin KaFai Lau hdr_stg = bpf_sk_storage_get(&hdr_stg_map, skops->sk, NULL, 0);
270ad2f8eb0SMartin KaFai Lau if (!hdr_stg)
271ad2f8eb0SMartin KaFai Lau RET_CG_ERR(0);
272ad2f8eb0SMartin KaFai Lau
273ad2f8eb0SMartin KaFai Lau if (hdr_stg->active)
274ad2f8eb0SMartin KaFai Lau opt = &active_fin_out;
275ad2f8eb0SMartin KaFai Lau else
276ad2f8eb0SMartin KaFai Lau opt = &passive_fin_out;
277ad2f8eb0SMartin KaFai Lau
278ad2f8eb0SMartin KaFai Lau if (!opt->flags)
279ad2f8eb0SMartin KaFai Lau RET_CG_ERR(0);
280ad2f8eb0SMartin KaFai Lau
281ad2f8eb0SMartin KaFai Lau return store_option(skops, opt);
282ad2f8eb0SMartin KaFai Lau }
283ad2f8eb0SMartin KaFai Lau
resend_in_ack(struct bpf_sock_ops * skops)284ad2f8eb0SMartin KaFai Lau static int resend_in_ack(struct bpf_sock_ops *skops)
285ad2f8eb0SMartin KaFai Lau {
286ad2f8eb0SMartin KaFai Lau struct hdr_stg *hdr_stg;
287ad2f8eb0SMartin KaFai Lau
288ad2f8eb0SMartin KaFai Lau if (!skops->sk)
289ad2f8eb0SMartin KaFai Lau return -1;
290ad2f8eb0SMartin KaFai Lau
291ad2f8eb0SMartin KaFai Lau hdr_stg = bpf_sk_storage_get(&hdr_stg_map, skops->sk, NULL, 0);
292ad2f8eb0SMartin KaFai Lau if (!hdr_stg)
293ad2f8eb0SMartin KaFai Lau return -1;
294ad2f8eb0SMartin KaFai Lau
295ad2f8eb0SMartin KaFai Lau return !!hdr_stg->resend_syn;
296ad2f8eb0SMartin KaFai Lau }
297ad2f8eb0SMartin KaFai Lau
nodata_opt_len(struct bpf_sock_ops * skops)298ad2f8eb0SMartin KaFai Lau static int nodata_opt_len(struct bpf_sock_ops *skops)
299ad2f8eb0SMartin KaFai Lau {
300ad2f8eb0SMartin KaFai Lau int resend;
301ad2f8eb0SMartin KaFai Lau
302ad2f8eb0SMartin KaFai Lau resend = resend_in_ack(skops);
303ad2f8eb0SMartin KaFai Lau if (resend < 0)
304ad2f8eb0SMartin KaFai Lau RET_CG_ERR(0);
305ad2f8eb0SMartin KaFai Lau
306ad2f8eb0SMartin KaFai Lau if (resend)
307ad2f8eb0SMartin KaFai Lau return syn_opt_len(skops);
308ad2f8eb0SMartin KaFai Lau
309ad2f8eb0SMartin KaFai Lau return CG_OK;
310ad2f8eb0SMartin KaFai Lau }
311ad2f8eb0SMartin KaFai Lau
write_nodata_opt(struct bpf_sock_ops * skops)312ad2f8eb0SMartin KaFai Lau static int write_nodata_opt(struct bpf_sock_ops *skops)
313ad2f8eb0SMartin KaFai Lau {
314ad2f8eb0SMartin KaFai Lau int resend;
315ad2f8eb0SMartin KaFai Lau
316ad2f8eb0SMartin KaFai Lau resend = resend_in_ack(skops);
317ad2f8eb0SMartin KaFai Lau if (resend < 0)
318ad2f8eb0SMartin KaFai Lau RET_CG_ERR(0);
319ad2f8eb0SMartin KaFai Lau
320ad2f8eb0SMartin KaFai Lau if (resend)
321ad2f8eb0SMartin KaFai Lau return write_syn_opt(skops);
322ad2f8eb0SMartin KaFai Lau
323ad2f8eb0SMartin KaFai Lau return CG_OK;
324ad2f8eb0SMartin KaFai Lau }
325ad2f8eb0SMartin KaFai Lau
data_opt_len(struct bpf_sock_ops * skops)326ad2f8eb0SMartin KaFai Lau static int data_opt_len(struct bpf_sock_ops *skops)
327ad2f8eb0SMartin KaFai Lau {
328ad2f8eb0SMartin KaFai Lau /* Same as the nodata version. Mostly to show
329ad2f8eb0SMartin KaFai Lau * an example usage on skops->skb_len.
330ad2f8eb0SMartin KaFai Lau */
331ad2f8eb0SMartin KaFai Lau return nodata_opt_len(skops);
332ad2f8eb0SMartin KaFai Lau }
333ad2f8eb0SMartin KaFai Lau
write_data_opt(struct bpf_sock_ops * skops)334ad2f8eb0SMartin KaFai Lau static int write_data_opt(struct bpf_sock_ops *skops)
335ad2f8eb0SMartin KaFai Lau {
336ad2f8eb0SMartin KaFai Lau return write_nodata_opt(skops);
337ad2f8eb0SMartin KaFai Lau }
338ad2f8eb0SMartin KaFai Lau
current_mss_opt_len(struct bpf_sock_ops * skops)339ad2f8eb0SMartin KaFai Lau static int current_mss_opt_len(struct bpf_sock_ops *skops)
340ad2f8eb0SMartin KaFai Lau {
341ad2f8eb0SMartin KaFai Lau /* Reserve maximum that may be needed */
342ad2f8eb0SMartin KaFai Lau int err;
343ad2f8eb0SMartin KaFai Lau
344ad2f8eb0SMartin KaFai Lau err = bpf_reserve_hdr_opt(skops, option_total_len(OPTION_MASK), 0);
345ad2f8eb0SMartin KaFai Lau if (err)
346ad2f8eb0SMartin KaFai Lau RET_CG_ERR(err);
347ad2f8eb0SMartin KaFai Lau
348ad2f8eb0SMartin KaFai Lau return CG_OK;
349ad2f8eb0SMartin KaFai Lau }
350ad2f8eb0SMartin KaFai Lau
handle_hdr_opt_len(struct bpf_sock_ops * skops)351ad2f8eb0SMartin KaFai Lau static int handle_hdr_opt_len(struct bpf_sock_ops *skops)
352ad2f8eb0SMartin KaFai Lau {
353ad2f8eb0SMartin KaFai Lau __u8 tcp_flags = skops_tcp_flags(skops);
354ad2f8eb0SMartin KaFai Lau
355ad2f8eb0SMartin KaFai Lau if ((tcp_flags & TCPHDR_SYNACK) == TCPHDR_SYNACK)
356ad2f8eb0SMartin KaFai Lau return synack_opt_len(skops);
357ad2f8eb0SMartin KaFai Lau
358ad2f8eb0SMartin KaFai Lau if (tcp_flags & TCPHDR_SYN)
359ad2f8eb0SMartin KaFai Lau return syn_opt_len(skops);
360ad2f8eb0SMartin KaFai Lau
361ad2f8eb0SMartin KaFai Lau if (tcp_flags & TCPHDR_FIN)
362ad2f8eb0SMartin KaFai Lau return fin_opt_len(skops);
363ad2f8eb0SMartin KaFai Lau
364ad2f8eb0SMartin KaFai Lau if (skops_current_mss(skops))
365ad2f8eb0SMartin KaFai Lau /* The kernel is calculating the MSS */
366ad2f8eb0SMartin KaFai Lau return current_mss_opt_len(skops);
367ad2f8eb0SMartin KaFai Lau
368ad2f8eb0SMartin KaFai Lau if (skops->skb_len)
369ad2f8eb0SMartin KaFai Lau return data_opt_len(skops);
370ad2f8eb0SMartin KaFai Lau
371ad2f8eb0SMartin KaFai Lau return nodata_opt_len(skops);
372ad2f8eb0SMartin KaFai Lau }
373ad2f8eb0SMartin KaFai Lau
handle_write_hdr_opt(struct bpf_sock_ops * skops)374ad2f8eb0SMartin KaFai Lau static int handle_write_hdr_opt(struct bpf_sock_ops *skops)
375ad2f8eb0SMartin KaFai Lau {
376ad2f8eb0SMartin KaFai Lau __u8 tcp_flags = skops_tcp_flags(skops);
377ad2f8eb0SMartin KaFai Lau struct tcphdr *th;
378ad2f8eb0SMartin KaFai Lau
379ad2f8eb0SMartin KaFai Lau if ((tcp_flags & TCPHDR_SYNACK) == TCPHDR_SYNACK)
380ad2f8eb0SMartin KaFai Lau return write_synack_opt(skops);
381ad2f8eb0SMartin KaFai Lau
382ad2f8eb0SMartin KaFai Lau if (tcp_flags & TCPHDR_SYN)
383ad2f8eb0SMartin KaFai Lau return write_syn_opt(skops);
384ad2f8eb0SMartin KaFai Lau
385ad2f8eb0SMartin KaFai Lau if (tcp_flags & TCPHDR_FIN)
386ad2f8eb0SMartin KaFai Lau return write_fin_opt(skops);
387ad2f8eb0SMartin KaFai Lau
388ad2f8eb0SMartin KaFai Lau th = skops->skb_data;
389ad2f8eb0SMartin KaFai Lau if (th + 1 > skops->skb_data_end)
390ad2f8eb0SMartin KaFai Lau RET_CG_ERR(0);
391ad2f8eb0SMartin KaFai Lau
392ad2f8eb0SMartin KaFai Lau if (skops->skb_len > tcp_hdrlen(th))
393ad2f8eb0SMartin KaFai Lau return write_data_opt(skops);
394ad2f8eb0SMartin KaFai Lau
395ad2f8eb0SMartin KaFai Lau return write_nodata_opt(skops);
396ad2f8eb0SMartin KaFai Lau }
397ad2f8eb0SMartin KaFai Lau
set_delack_max(struct bpf_sock_ops * skops,__u8 max_delack_ms)398ad2f8eb0SMartin KaFai Lau static int set_delack_max(struct bpf_sock_ops *skops, __u8 max_delack_ms)
399ad2f8eb0SMartin KaFai Lau {
400ad2f8eb0SMartin KaFai Lau __u32 max_delack_us = max_delack_ms * 1000;
401ad2f8eb0SMartin KaFai Lau
402ad2f8eb0SMartin KaFai Lau return bpf_setsockopt(skops, SOL_TCP, TCP_BPF_DELACK_MAX,
403ad2f8eb0SMartin KaFai Lau &max_delack_us, sizeof(max_delack_us));
404ad2f8eb0SMartin KaFai Lau }
405ad2f8eb0SMartin KaFai Lau
set_rto_min(struct bpf_sock_ops * skops,__u8 peer_max_delack_ms)406ad2f8eb0SMartin KaFai Lau static int set_rto_min(struct bpf_sock_ops *skops, __u8 peer_max_delack_ms)
407ad2f8eb0SMartin KaFai Lau {
408ad2f8eb0SMartin KaFai Lau __u32 min_rto_us = peer_max_delack_ms * 1000;
409ad2f8eb0SMartin KaFai Lau
410ad2f8eb0SMartin KaFai Lau return bpf_setsockopt(skops, SOL_TCP, TCP_BPF_RTO_MIN, &min_rto_us,
411ad2f8eb0SMartin KaFai Lau sizeof(min_rto_us));
412ad2f8eb0SMartin KaFai Lau }
413ad2f8eb0SMartin KaFai Lau
handle_active_estab(struct bpf_sock_ops * skops)414ad2f8eb0SMartin KaFai Lau static int handle_active_estab(struct bpf_sock_ops *skops)
415ad2f8eb0SMartin KaFai Lau {
416ad2f8eb0SMartin KaFai Lau struct hdr_stg init_stg = {
417ad2f8eb0SMartin KaFai Lau .active = true,
418ad2f8eb0SMartin KaFai Lau };
419ad2f8eb0SMartin KaFai Lau int err;
420ad2f8eb0SMartin KaFai Lau
421ad2f8eb0SMartin KaFai Lau err = load_option(skops, &active_estab_in, false);
422ad2f8eb0SMartin KaFai Lau if (err && err != -ENOMSG)
423ad2f8eb0SMartin KaFai Lau RET_CG_ERR(err);
424ad2f8eb0SMartin KaFai Lau
425ad2f8eb0SMartin KaFai Lau init_stg.resend_syn = TEST_OPTION_FLAGS(active_estab_in.flags,
426ad2f8eb0SMartin KaFai Lau OPTION_RESEND);
427ad2f8eb0SMartin KaFai Lau if (!skops->sk || !bpf_sk_storage_get(&hdr_stg_map, skops->sk,
428ad2f8eb0SMartin KaFai Lau &init_stg,
429ad2f8eb0SMartin KaFai Lau BPF_SK_STORAGE_GET_F_CREATE))
430ad2f8eb0SMartin KaFai Lau RET_CG_ERR(0);
431ad2f8eb0SMartin KaFai Lau
432ad2f8eb0SMartin KaFai Lau if (init_stg.resend_syn)
433ad2f8eb0SMartin KaFai Lau /* Don't clear the write_hdr cb now because
434ad2f8eb0SMartin KaFai Lau * the ACK may get lost and retransmit may
435ad2f8eb0SMartin KaFai Lau * be needed.
436ad2f8eb0SMartin KaFai Lau *
437ad2f8eb0SMartin KaFai Lau * PARSE_ALL_HDR cb flag is set to learn if this
438ad2f8eb0SMartin KaFai Lau * resend_syn option has received by the peer.
439ad2f8eb0SMartin KaFai Lau *
440ad2f8eb0SMartin KaFai Lau * The header option will be resent until a valid
441ad2f8eb0SMartin KaFai Lau * packet is received at handle_parse_hdr()
442ad2f8eb0SMartin KaFai Lau * and all hdr cb flags will be cleared in
443ad2f8eb0SMartin KaFai Lau * handle_parse_hdr().
444ad2f8eb0SMartin KaFai Lau */
445ad2f8eb0SMartin KaFai Lau set_parse_all_hdr_cb_flags(skops);
446ad2f8eb0SMartin KaFai Lau else if (!active_fin_out.flags)
447ad2f8eb0SMartin KaFai Lau /* No options will be written from now */
448ad2f8eb0SMartin KaFai Lau clear_hdr_cb_flags(skops);
449ad2f8eb0SMartin KaFai Lau
450ad2f8eb0SMartin KaFai Lau if (active_syn_out.max_delack_ms) {
451ad2f8eb0SMartin KaFai Lau err = set_delack_max(skops, active_syn_out.max_delack_ms);
452ad2f8eb0SMartin KaFai Lau if (err)
453ad2f8eb0SMartin KaFai Lau RET_CG_ERR(err);
454ad2f8eb0SMartin KaFai Lau }
455ad2f8eb0SMartin KaFai Lau
456ad2f8eb0SMartin KaFai Lau if (active_estab_in.max_delack_ms) {
457ad2f8eb0SMartin KaFai Lau err = set_rto_min(skops, active_estab_in.max_delack_ms);
458ad2f8eb0SMartin KaFai Lau if (err)
459ad2f8eb0SMartin KaFai Lau RET_CG_ERR(err);
460ad2f8eb0SMartin KaFai Lau }
461ad2f8eb0SMartin KaFai Lau
462ad2f8eb0SMartin KaFai Lau return CG_OK;
463ad2f8eb0SMartin KaFai Lau }
464ad2f8eb0SMartin KaFai Lau
handle_passive_estab(struct bpf_sock_ops * skops)465ad2f8eb0SMartin KaFai Lau static int handle_passive_estab(struct bpf_sock_ops *skops)
466ad2f8eb0SMartin KaFai Lau {
467ad2f8eb0SMartin KaFai Lau struct hdr_stg init_stg = {};
468ad2f8eb0SMartin KaFai Lau struct tcphdr *th;
469ad2f8eb0SMartin KaFai Lau int err;
470ad2f8eb0SMartin KaFai Lau
47196d46c50SMartin KaFai Lau inherit_cb_flags = skops->bpf_sock_ops_cb_flags;
47296d46c50SMartin KaFai Lau
473ad2f8eb0SMartin KaFai Lau err = load_option(skops, &passive_estab_in, true);
474ad2f8eb0SMartin KaFai Lau if (err == -ENOENT) {
475ad2f8eb0SMartin KaFai Lau /* saved_syn is not found. It was in syncookie mode.
476ad2f8eb0SMartin KaFai Lau * We have asked the active side to resend the options
477ad2f8eb0SMartin KaFai Lau * in ACK, so try to find the bpf_test_option from ACK now.
478ad2f8eb0SMartin KaFai Lau */
479ad2f8eb0SMartin KaFai Lau err = load_option(skops, &passive_estab_in, false);
480ad2f8eb0SMartin KaFai Lau init_stg.syncookie = true;
481ad2f8eb0SMartin KaFai Lau }
482ad2f8eb0SMartin KaFai Lau
483ad2f8eb0SMartin KaFai Lau /* ENOMSG: The bpf_test_option is not found which is fine.
484ad2f8eb0SMartin KaFai Lau * Bail out now for all other errors.
485ad2f8eb0SMartin KaFai Lau */
486ad2f8eb0SMartin KaFai Lau if (err && err != -ENOMSG)
487ad2f8eb0SMartin KaFai Lau RET_CG_ERR(err);
488ad2f8eb0SMartin KaFai Lau
489ad2f8eb0SMartin KaFai Lau th = skops->skb_data;
490ad2f8eb0SMartin KaFai Lau if (th + 1 > skops->skb_data_end)
491ad2f8eb0SMartin KaFai Lau RET_CG_ERR(0);
492ad2f8eb0SMartin KaFai Lau
493ad2f8eb0SMartin KaFai Lau if (th->syn) {
494ad2f8eb0SMartin KaFai Lau /* Fastopen */
495ad2f8eb0SMartin KaFai Lau
496ad2f8eb0SMartin KaFai Lau /* Cannot clear cb_flags to stop write_hdr cb.
497ad2f8eb0SMartin KaFai Lau * synack is not sent yet for fast open.
498ad2f8eb0SMartin KaFai Lau * Even it was, the synack may need to be retransmitted.
499ad2f8eb0SMartin KaFai Lau *
500ad2f8eb0SMartin KaFai Lau * PARSE_ALL_HDR cb flag is set to learn
501ad2f8eb0SMartin KaFai Lau * if synack has reached the peer.
502ad2f8eb0SMartin KaFai Lau * All cb_flags will be cleared in handle_parse_hdr().
503ad2f8eb0SMartin KaFai Lau */
504ad2f8eb0SMartin KaFai Lau set_parse_all_hdr_cb_flags(skops);
505ad2f8eb0SMartin KaFai Lau init_stg.fastopen = true;
506ad2f8eb0SMartin KaFai Lau } else if (!passive_fin_out.flags) {
507ad2f8eb0SMartin KaFai Lau /* No options will be written from now */
508ad2f8eb0SMartin KaFai Lau clear_hdr_cb_flags(skops);
509ad2f8eb0SMartin KaFai Lau }
510ad2f8eb0SMartin KaFai Lau
511ad2f8eb0SMartin KaFai Lau if (!skops->sk ||
512ad2f8eb0SMartin KaFai Lau !bpf_sk_storage_get(&hdr_stg_map, skops->sk, &init_stg,
513ad2f8eb0SMartin KaFai Lau BPF_SK_STORAGE_GET_F_CREATE))
514ad2f8eb0SMartin KaFai Lau RET_CG_ERR(0);
515ad2f8eb0SMartin KaFai Lau
516ad2f8eb0SMartin KaFai Lau if (passive_synack_out.max_delack_ms) {
517ad2f8eb0SMartin KaFai Lau err = set_delack_max(skops, passive_synack_out.max_delack_ms);
518ad2f8eb0SMartin KaFai Lau if (err)
519ad2f8eb0SMartin KaFai Lau RET_CG_ERR(err);
520ad2f8eb0SMartin KaFai Lau }
521ad2f8eb0SMartin KaFai Lau
522ad2f8eb0SMartin KaFai Lau if (passive_estab_in.max_delack_ms) {
523ad2f8eb0SMartin KaFai Lau err = set_rto_min(skops, passive_estab_in.max_delack_ms);
524ad2f8eb0SMartin KaFai Lau if (err)
525ad2f8eb0SMartin KaFai Lau RET_CG_ERR(err);
526ad2f8eb0SMartin KaFai Lau }
527ad2f8eb0SMartin KaFai Lau
528ad2f8eb0SMartin KaFai Lau return CG_OK;
529ad2f8eb0SMartin KaFai Lau }
530ad2f8eb0SMartin KaFai Lau
handle_parse_hdr(struct bpf_sock_ops * skops)531ad2f8eb0SMartin KaFai Lau static int handle_parse_hdr(struct bpf_sock_ops *skops)
532ad2f8eb0SMartin KaFai Lau {
533ad2f8eb0SMartin KaFai Lau struct hdr_stg *hdr_stg;
534ad2f8eb0SMartin KaFai Lau struct tcphdr *th;
535ad2f8eb0SMartin KaFai Lau
536ad2f8eb0SMartin KaFai Lau if (!skops->sk)
537ad2f8eb0SMartin KaFai Lau RET_CG_ERR(0);
538ad2f8eb0SMartin KaFai Lau
539ad2f8eb0SMartin KaFai Lau th = skops->skb_data;
540ad2f8eb0SMartin KaFai Lau if (th + 1 > skops->skb_data_end)
541ad2f8eb0SMartin KaFai Lau RET_CG_ERR(0);
542ad2f8eb0SMartin KaFai Lau
543ad2f8eb0SMartin KaFai Lau hdr_stg = bpf_sk_storage_get(&hdr_stg_map, skops->sk, NULL, 0);
544ad2f8eb0SMartin KaFai Lau if (!hdr_stg)
545ad2f8eb0SMartin KaFai Lau RET_CG_ERR(0);
546ad2f8eb0SMartin KaFai Lau
547ad2f8eb0SMartin KaFai Lau if (hdr_stg->resend_syn || hdr_stg->fastopen)
548ad2f8eb0SMartin KaFai Lau /* The PARSE_ALL_HDR cb flag was turned on
549ad2f8eb0SMartin KaFai Lau * to ensure that the previously written
550ad2f8eb0SMartin KaFai Lau * options have reached the peer.
551ad2f8eb0SMartin KaFai Lau * Those previously written option includes:
552ad2f8eb0SMartin KaFai Lau * - Active side: resend_syn in ACK during syncookie
553ad2f8eb0SMartin KaFai Lau * or
554ad2f8eb0SMartin KaFai Lau * - Passive side: SYNACK during fastopen
555ad2f8eb0SMartin KaFai Lau *
556ad2f8eb0SMartin KaFai Lau * A valid packet has been received here after
557ad2f8eb0SMartin KaFai Lau * the 3WHS, so the PARSE_ALL_HDR cb flag
558ad2f8eb0SMartin KaFai Lau * can be cleared now.
559ad2f8eb0SMartin KaFai Lau */
560ad2f8eb0SMartin KaFai Lau clear_parse_all_hdr_cb_flags(skops);
561ad2f8eb0SMartin KaFai Lau
562ad2f8eb0SMartin KaFai Lau if (hdr_stg->resend_syn && !active_fin_out.flags)
563ad2f8eb0SMartin KaFai Lau /* Active side resent the syn option in ACK
564ad2f8eb0SMartin KaFai Lau * because the server was in syncookie mode.
565ad2f8eb0SMartin KaFai Lau * A valid packet has been received, so
566ad2f8eb0SMartin KaFai Lau * clear header cb flags if there is no
567ad2f8eb0SMartin KaFai Lau * more option to send.
568ad2f8eb0SMartin KaFai Lau */
569ad2f8eb0SMartin KaFai Lau clear_hdr_cb_flags(skops);
570ad2f8eb0SMartin KaFai Lau
571ad2f8eb0SMartin KaFai Lau if (hdr_stg->fastopen && !passive_fin_out.flags)
572ad2f8eb0SMartin KaFai Lau /* Passive side was in fastopen.
573ad2f8eb0SMartin KaFai Lau * A valid packet has been received, so
574ad2f8eb0SMartin KaFai Lau * the SYNACK has reached the peer.
575ad2f8eb0SMartin KaFai Lau * Clear header cb flags if there is no more
576ad2f8eb0SMartin KaFai Lau * option to send.
577ad2f8eb0SMartin KaFai Lau */
578ad2f8eb0SMartin KaFai Lau clear_hdr_cb_flags(skops);
579ad2f8eb0SMartin KaFai Lau
580ad2f8eb0SMartin KaFai Lau if (th->fin) {
581ad2f8eb0SMartin KaFai Lau struct bpf_test_option *fin_opt;
582ad2f8eb0SMartin KaFai Lau int err;
583ad2f8eb0SMartin KaFai Lau
584ad2f8eb0SMartin KaFai Lau if (hdr_stg->active)
585ad2f8eb0SMartin KaFai Lau fin_opt = &active_fin_in;
586ad2f8eb0SMartin KaFai Lau else
587ad2f8eb0SMartin KaFai Lau fin_opt = &passive_fin_in;
588ad2f8eb0SMartin KaFai Lau
589ad2f8eb0SMartin KaFai Lau err = load_option(skops, fin_opt, false);
590ad2f8eb0SMartin KaFai Lau if (err && err != -ENOMSG)
591ad2f8eb0SMartin KaFai Lau RET_CG_ERR(err);
592ad2f8eb0SMartin KaFai Lau }
593ad2f8eb0SMartin KaFai Lau
594ad2f8eb0SMartin KaFai Lau return CG_OK;
595ad2f8eb0SMartin KaFai Lau }
596ad2f8eb0SMartin KaFai Lau
597*15669e1dSAndrii Nakryiko SEC("sockops")
estab(struct bpf_sock_ops * skops)598ad2f8eb0SMartin KaFai Lau int estab(struct bpf_sock_ops *skops)
599ad2f8eb0SMartin KaFai Lau {
600ad2f8eb0SMartin KaFai Lau int true_val = 1;
601ad2f8eb0SMartin KaFai Lau
602ad2f8eb0SMartin KaFai Lau switch (skops->op) {
603ad2f8eb0SMartin KaFai Lau case BPF_SOCK_OPS_TCP_LISTEN_CB:
604ad2f8eb0SMartin KaFai Lau bpf_setsockopt(skops, SOL_TCP, TCP_SAVE_SYN,
605ad2f8eb0SMartin KaFai Lau &true_val, sizeof(true_val));
60696d46c50SMartin KaFai Lau set_hdr_cb_flags(skops, BPF_SOCK_OPS_STATE_CB_FLAG);
607ad2f8eb0SMartin KaFai Lau break;
608ad2f8eb0SMartin KaFai Lau case BPF_SOCK_OPS_TCP_CONNECT_CB:
60996d46c50SMartin KaFai Lau set_hdr_cb_flags(skops, 0);
610ad2f8eb0SMartin KaFai Lau break;
611ad2f8eb0SMartin KaFai Lau case BPF_SOCK_OPS_PARSE_HDR_OPT_CB:
612ad2f8eb0SMartin KaFai Lau return handle_parse_hdr(skops);
613ad2f8eb0SMartin KaFai Lau case BPF_SOCK_OPS_HDR_OPT_LEN_CB:
614ad2f8eb0SMartin KaFai Lau return handle_hdr_opt_len(skops);
615ad2f8eb0SMartin KaFai Lau case BPF_SOCK_OPS_WRITE_HDR_OPT_CB:
616ad2f8eb0SMartin KaFai Lau return handle_write_hdr_opt(skops);
617ad2f8eb0SMartin KaFai Lau case BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB:
618ad2f8eb0SMartin KaFai Lau return handle_passive_estab(skops);
619ad2f8eb0SMartin KaFai Lau case BPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB:
620ad2f8eb0SMartin KaFai Lau return handle_active_estab(skops);
621ad2f8eb0SMartin KaFai Lau }
622ad2f8eb0SMartin KaFai Lau
623ad2f8eb0SMartin KaFai Lau return CG_OK;
624ad2f8eb0SMartin KaFai Lau }
625ad2f8eb0SMartin KaFai Lau
626ad2f8eb0SMartin KaFai Lau char _license[] SEC("license") = "GPL";
627