Loading...
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 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 | // SPDX-License-Identifier: GPL-2.0 /* Copyright (c) 2020 Facebook */ #include <stddef.h> #include <errno.h> #include <stdbool.h> #include <sys/types.h> #include <sys/socket.h> #include <linux/ipv6.h> #include <linux/tcp.h> #include <linux/socket.h> #include <linux/bpf.h> #include <linux/types.h> #include <bpf/bpf_helpers.h> #include <bpf/bpf_endian.h> #define BPF_PROG_TEST_TCP_HDR_OPTIONS #include "test_tcp_hdr_options.h" __u16 last_addr16_n = __bpf_htons(1); __u16 active_lport_n = 0; __u16 active_lport_h = 0; __u16 passive_lport_n = 0; __u16 passive_lport_h = 0; /* options received at passive side */ unsigned int nr_pure_ack = 0; unsigned int nr_data = 0; unsigned int nr_syn = 0; unsigned int nr_fin = 0; unsigned int nr_hwtstamp = 0; /* Check the header received from the active side */ static int __check_active_hdr_in(struct bpf_sock_ops *skops, bool check_syn) { union { struct tcphdr th; struct ipv6hdr ip6; struct tcp_exprm_opt exprm_opt; struct tcp_opt reg_opt; __u8 data[100]; /* IPv6 (40) + Max TCP hdr (60) */ } hdr = {}; __u64 load_flags = check_syn ? BPF_LOAD_HDR_OPT_TCP_SYN : 0; struct tcphdr *pth; int ret; hdr.reg_opt.kind = 0xB9; /* The option is 4 bytes long instead of 2 bytes */ ret = bpf_load_hdr_opt(skops, &hdr.reg_opt, 2, load_flags); if (ret != -ENOSPC) RET_CG_ERR(ret); /* Test searching magic with regular kind */ hdr.reg_opt.len = 4; ret = bpf_load_hdr_opt(skops, &hdr.reg_opt, sizeof(hdr.reg_opt), load_flags); if (ret != -EINVAL) RET_CG_ERR(ret); hdr.reg_opt.len = 0; ret = bpf_load_hdr_opt(skops, &hdr.reg_opt, sizeof(hdr.reg_opt), load_flags); if (ret != 4 || hdr.reg_opt.len != 4 || hdr.reg_opt.kind != 0xB9 || hdr.reg_opt.data[0] != 0xfa || hdr.reg_opt.data[1] != 0xce) RET_CG_ERR(ret); /* Test searching experimental option with invalid kind length */ hdr.exprm_opt.kind = TCPOPT_EXP; hdr.exprm_opt.len = 5; hdr.exprm_opt.magic = 0; ret = bpf_load_hdr_opt(skops, &hdr.exprm_opt, sizeof(hdr.exprm_opt), load_flags); if (ret != -EINVAL) RET_CG_ERR(ret); /* Test searching experimental option with 0 magic value */ hdr.exprm_opt.len = 4; ret = bpf_load_hdr_opt(skops, &hdr.exprm_opt, sizeof(hdr.exprm_opt), load_flags); if (ret != -ENOMSG) RET_CG_ERR(ret); hdr.exprm_opt.magic = __bpf_htons(0xeB9F); ret = bpf_load_hdr_opt(skops, &hdr.exprm_opt, sizeof(hdr.exprm_opt), load_flags); if (ret != 4 || hdr.exprm_opt.len != 4 || hdr.exprm_opt.kind != TCPOPT_EXP || hdr.exprm_opt.magic != __bpf_htons(0xeB9F)) RET_CG_ERR(ret); if (!check_syn) return CG_OK; /* Test loading from skops->syn_skb if sk_state == TCP_NEW_SYN_RECV * * Test loading from tp->saved_syn for other sk_state. */ ret = bpf_getsockopt(skops, SOL_TCP, TCP_BPF_SYN_IP, &hdr.ip6, sizeof(hdr.ip6)); if (ret != -ENOSPC) RET_CG_ERR(ret); if (hdr.ip6.saddr.s6_addr16[7] != last_addr16_n || hdr.ip6.daddr.s6_addr16[7] != last_addr16_n) RET_CG_ERR(0); ret = bpf_getsockopt(skops, SOL_TCP, TCP_BPF_SYN_IP, &hdr, sizeof(hdr)); if (ret < 0) RET_CG_ERR(ret); pth = (struct tcphdr *)(&hdr.ip6 + 1); if (pth->dest != passive_lport_n || pth->source != active_lport_n) RET_CG_ERR(0); ret = bpf_getsockopt(skops, SOL_TCP, TCP_BPF_SYN, &hdr, sizeof(hdr)); if (ret < 0) RET_CG_ERR(ret); if (hdr.th.dest != passive_lport_n || hdr.th.source != active_lport_n) RET_CG_ERR(0); return CG_OK; } static int check_active_syn_in(struct bpf_sock_ops *skops) { return __check_active_hdr_in(skops, true); } static int check_active_hdr_in(struct bpf_sock_ops *skops) { struct tcphdr *th; if (__check_active_hdr_in(skops, false) == CG_ERR) return CG_ERR; th = skops->skb_data; if (th + 1 > skops->skb_data_end) RET_CG_ERR(0); if (tcp_hdrlen(th) < skops->skb_len) nr_data++; if (th->fin) nr_fin++; if (th->ack && !th->fin && tcp_hdrlen(th) == skops->skb_len) nr_pure_ack++; if (skops->skb_hwtstamp) nr_hwtstamp++; return CG_OK; } static int active_opt_len(struct bpf_sock_ops *skops) { int err; /* Reserve more than enough to allow the -EEXIST test in * the write_active_opt(). */ err = bpf_reserve_hdr_opt(skops, 12, 0); if (err) RET_CG_ERR(err); return CG_OK; } static int write_active_opt(struct bpf_sock_ops *skops) { struct tcp_exprm_opt exprm_opt = {}; struct tcp_opt win_scale_opt = {}; struct tcp_opt reg_opt = {}; struct tcphdr *th; int err, ret; exprm_opt.kind = TCPOPT_EXP; exprm_opt.len = 4; exprm_opt.magic = __bpf_htons(0xeB9F); reg_opt.kind = 0xB9; reg_opt.len = 4; reg_opt.data[0] = 0xfa; reg_opt.data[1] = 0xce; win_scale_opt.kind = TCPOPT_WINDOW; err = bpf_store_hdr_opt(skops, &exprm_opt, sizeof(exprm_opt), 0); if (err) RET_CG_ERR(err); /* Store the same exprm option */ err = bpf_store_hdr_opt(skops, &exprm_opt, sizeof(exprm_opt), 0); if (err != -EEXIST) RET_CG_ERR(err); err = bpf_store_hdr_opt(skops, ®_opt, sizeof(reg_opt), 0); if (err) RET_CG_ERR(err); err = bpf_store_hdr_opt(skops, ®_opt, sizeof(reg_opt), 0); if (err != -EEXIST) RET_CG_ERR(err); /* Check the option has been written and can be searched */ ret = bpf_load_hdr_opt(skops, &exprm_opt, sizeof(exprm_opt), 0); if (ret != 4 || exprm_opt.len != 4 || exprm_opt.kind != TCPOPT_EXP || exprm_opt.magic != __bpf_htons(0xeB9F)) RET_CG_ERR(ret); reg_opt.len = 0; ret = bpf_load_hdr_opt(skops, ®_opt, sizeof(reg_opt), 0); if (ret != 4 || reg_opt.len != 4 || reg_opt.kind != 0xB9 || reg_opt.data[0] != 0xfa || reg_opt.data[1] != 0xce) RET_CG_ERR(ret); th = skops->skb_data; if (th + 1 > skops->skb_data_end) RET_CG_ERR(0); if (th->syn) { active_lport_h = skops->local_port; active_lport_n = th->source; /* Search the win scale option written by kernel * in the SYN packet. */ ret = bpf_load_hdr_opt(skops, &win_scale_opt, sizeof(win_scale_opt), 0); if (ret != 3 || win_scale_opt.len != 3 || win_scale_opt.kind != TCPOPT_WINDOW) RET_CG_ERR(ret); /* Write the win scale option that kernel * has already written. */ err = bpf_store_hdr_opt(skops, &win_scale_opt, sizeof(win_scale_opt), 0); if (err != -EEXIST) RET_CG_ERR(err); } return CG_OK; } static int handle_hdr_opt_len(struct bpf_sock_ops *skops) { __u8 tcp_flags = skops_tcp_flags(skops); if ((tcp_flags & TCPHDR_SYNACK) == TCPHDR_SYNACK) /* Check the SYN from bpf_sock_ops_kern->syn_skb */ return check_active_syn_in(skops); /* Passive side should have cleared the write hdr cb by now */ if (skops->local_port == passive_lport_h) RET_CG_ERR(0); return active_opt_len(skops); } static int handle_write_hdr_opt(struct bpf_sock_ops *skops) { if (skops->local_port == passive_lport_h) RET_CG_ERR(0); return write_active_opt(skops); } static int handle_parse_hdr(struct bpf_sock_ops *skops) { /* Passive side is not writing any non-standard/unknown * option, so the active side should never be called. */ if (skops->local_port == active_lport_h) RET_CG_ERR(0); return check_active_hdr_in(skops); } static int handle_passive_estab(struct bpf_sock_ops *skops) { int err; /* No more write hdr cb */ bpf_sock_ops_cb_flags_set(skops, skops->bpf_sock_ops_cb_flags & ~BPF_SOCK_OPS_WRITE_HDR_OPT_CB_FLAG); /* Recheck the SYN but check the tp->saved_syn this time */ err = check_active_syn_in(skops); if (err == CG_ERR) return err; nr_syn++; /* The ack has header option written by the active side also */ return check_active_hdr_in(skops); } SEC("sockops") int misc_estab(struct bpf_sock_ops *skops) { int true_val = 1; switch (skops->op) { case BPF_SOCK_OPS_TCP_LISTEN_CB: passive_lport_h = skops->local_port; passive_lport_n = __bpf_htons(passive_lport_h); bpf_setsockopt(skops, SOL_TCP, TCP_SAVE_SYN, &true_val, sizeof(true_val)); set_hdr_cb_flags(skops, 0); break; case BPF_SOCK_OPS_TCP_CONNECT_CB: set_hdr_cb_flags(skops, 0); break; case BPF_SOCK_OPS_PARSE_HDR_OPT_CB: return handle_parse_hdr(skops); case BPF_SOCK_OPS_HDR_OPT_LEN_CB: return handle_hdr_opt_len(skops); case BPF_SOCK_OPS_WRITE_HDR_OPT_CB: return handle_write_hdr_opt(skops); case BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB: return handle_passive_estab(skops); } return CG_OK; } char _license[] SEC("license") = "GPL"; |