test_misc_tcp_hdr_options.c (8269B)
1// SPDX-License-Identifier: GPL-2.0 2/* Copyright (c) 2020 Facebook */ 3 4#include <stddef.h> 5#include <errno.h> 6#include <stdbool.h> 7#include <sys/types.h> 8#include <sys/socket.h> 9#include <linux/ipv6.h> 10#include <linux/tcp.h> 11#include <linux/socket.h> 12#include <linux/bpf.h> 13#include <linux/types.h> 14#include <bpf/bpf_helpers.h> 15#include <bpf/bpf_endian.h> 16#define BPF_PROG_TEST_TCP_HDR_OPTIONS 17#include "test_tcp_hdr_options.h" 18 19__u16 last_addr16_n = __bpf_htons(1); 20__u16 active_lport_n = 0; 21__u16 active_lport_h = 0; 22__u16 passive_lport_n = 0; 23__u16 passive_lport_h = 0; 24 25/* options received at passive side */ 26unsigned int nr_pure_ack = 0; 27unsigned int nr_data = 0; 28unsigned int nr_syn = 0; 29unsigned int nr_fin = 0; 30 31/* Check the header received from the active side */ 32static int __check_active_hdr_in(struct bpf_sock_ops *skops, bool check_syn) 33{ 34 union { 35 struct tcphdr th; 36 struct ipv6hdr ip6; 37 struct tcp_exprm_opt exprm_opt; 38 struct tcp_opt reg_opt; 39 __u8 data[100]; /* IPv6 (40) + Max TCP hdr (60) */ 40 } hdr = {}; 41 __u64 load_flags = check_syn ? BPF_LOAD_HDR_OPT_TCP_SYN : 0; 42 struct tcphdr *pth; 43 int ret; 44 45 hdr.reg_opt.kind = 0xB9; 46 47 /* The option is 4 bytes long instead of 2 bytes */ 48 ret = bpf_load_hdr_opt(skops, &hdr.reg_opt, 2, load_flags); 49 if (ret != -ENOSPC) 50 RET_CG_ERR(ret); 51 52 /* Test searching magic with regular kind */ 53 hdr.reg_opt.len = 4; 54 ret = bpf_load_hdr_opt(skops, &hdr.reg_opt, sizeof(hdr.reg_opt), 55 load_flags); 56 if (ret != -EINVAL) 57 RET_CG_ERR(ret); 58 59 hdr.reg_opt.len = 0; 60 ret = bpf_load_hdr_opt(skops, &hdr.reg_opt, sizeof(hdr.reg_opt), 61 load_flags); 62 if (ret != 4 || hdr.reg_opt.len != 4 || hdr.reg_opt.kind != 0xB9 || 63 hdr.reg_opt.data[0] != 0xfa || hdr.reg_opt.data[1] != 0xce) 64 RET_CG_ERR(ret); 65 66 /* Test searching experimental option with invalid kind length */ 67 hdr.exprm_opt.kind = TCPOPT_EXP; 68 hdr.exprm_opt.len = 5; 69 hdr.exprm_opt.magic = 0; 70 ret = bpf_load_hdr_opt(skops, &hdr.exprm_opt, sizeof(hdr.exprm_opt), 71 load_flags); 72 if (ret != -EINVAL) 73 RET_CG_ERR(ret); 74 75 /* Test searching experimental option with 0 magic value */ 76 hdr.exprm_opt.len = 4; 77 ret = bpf_load_hdr_opt(skops, &hdr.exprm_opt, sizeof(hdr.exprm_opt), 78 load_flags); 79 if (ret != -ENOMSG) 80 RET_CG_ERR(ret); 81 82 hdr.exprm_opt.magic = __bpf_htons(0xeB9F); 83 ret = bpf_load_hdr_opt(skops, &hdr.exprm_opt, sizeof(hdr.exprm_opt), 84 load_flags); 85 if (ret != 4 || hdr.exprm_opt.len != 4 || 86 hdr.exprm_opt.kind != TCPOPT_EXP || 87 hdr.exprm_opt.magic != __bpf_htons(0xeB9F)) 88 RET_CG_ERR(ret); 89 90 if (!check_syn) 91 return CG_OK; 92 93 /* Test loading from skops->syn_skb if sk_state == TCP_NEW_SYN_RECV 94 * 95 * Test loading from tp->saved_syn for other sk_state. 96 */ 97 ret = bpf_getsockopt(skops, SOL_TCP, TCP_BPF_SYN_IP, &hdr.ip6, 98 sizeof(hdr.ip6)); 99 if (ret != -ENOSPC) 100 RET_CG_ERR(ret); 101 102 if (hdr.ip6.saddr.s6_addr16[7] != last_addr16_n || 103 hdr.ip6.daddr.s6_addr16[7] != last_addr16_n) 104 RET_CG_ERR(0); 105 106 ret = bpf_getsockopt(skops, SOL_TCP, TCP_BPF_SYN_IP, &hdr, sizeof(hdr)); 107 if (ret < 0) 108 RET_CG_ERR(ret); 109 110 pth = (struct tcphdr *)(&hdr.ip6 + 1); 111 if (pth->dest != passive_lport_n || pth->source != active_lport_n) 112 RET_CG_ERR(0); 113 114 ret = bpf_getsockopt(skops, SOL_TCP, TCP_BPF_SYN, &hdr, sizeof(hdr)); 115 if (ret < 0) 116 RET_CG_ERR(ret); 117 118 if (hdr.th.dest != passive_lport_n || hdr.th.source != active_lport_n) 119 RET_CG_ERR(0); 120 121 return CG_OK; 122} 123 124static int check_active_syn_in(struct bpf_sock_ops *skops) 125{ 126 return __check_active_hdr_in(skops, true); 127} 128 129static int check_active_hdr_in(struct bpf_sock_ops *skops) 130{ 131 struct tcphdr *th; 132 133 if (__check_active_hdr_in(skops, false) == CG_ERR) 134 return CG_ERR; 135 136 th = skops->skb_data; 137 if (th + 1 > skops->skb_data_end) 138 RET_CG_ERR(0); 139 140 if (tcp_hdrlen(th) < skops->skb_len) 141 nr_data++; 142 143 if (th->fin) 144 nr_fin++; 145 146 if (th->ack && !th->fin && tcp_hdrlen(th) == skops->skb_len) 147 nr_pure_ack++; 148 149 return CG_OK; 150} 151 152static int active_opt_len(struct bpf_sock_ops *skops) 153{ 154 int err; 155 156 /* Reserve more than enough to allow the -EEXIST test in 157 * the write_active_opt(). 158 */ 159 err = bpf_reserve_hdr_opt(skops, 12, 0); 160 if (err) 161 RET_CG_ERR(err); 162 163 return CG_OK; 164} 165 166static int write_active_opt(struct bpf_sock_ops *skops) 167{ 168 struct tcp_exprm_opt exprm_opt = {}; 169 struct tcp_opt win_scale_opt = {}; 170 struct tcp_opt reg_opt = {}; 171 struct tcphdr *th; 172 int err, ret; 173 174 exprm_opt.kind = TCPOPT_EXP; 175 exprm_opt.len = 4; 176 exprm_opt.magic = __bpf_htons(0xeB9F); 177 178 reg_opt.kind = 0xB9; 179 reg_opt.len = 4; 180 reg_opt.data[0] = 0xfa; 181 reg_opt.data[1] = 0xce; 182 183 win_scale_opt.kind = TCPOPT_WINDOW; 184 185 err = bpf_store_hdr_opt(skops, &exprm_opt, sizeof(exprm_opt), 0); 186 if (err) 187 RET_CG_ERR(err); 188 189 /* Store the same exprm option */ 190 err = bpf_store_hdr_opt(skops, &exprm_opt, sizeof(exprm_opt), 0); 191 if (err != -EEXIST) 192 RET_CG_ERR(err); 193 194 err = bpf_store_hdr_opt(skops, ®_opt, sizeof(reg_opt), 0); 195 if (err) 196 RET_CG_ERR(err); 197 err = bpf_store_hdr_opt(skops, ®_opt, sizeof(reg_opt), 0); 198 if (err != -EEXIST) 199 RET_CG_ERR(err); 200 201 /* Check the option has been written and can be searched */ 202 ret = bpf_load_hdr_opt(skops, &exprm_opt, sizeof(exprm_opt), 0); 203 if (ret != 4 || exprm_opt.len != 4 || exprm_opt.kind != TCPOPT_EXP || 204 exprm_opt.magic != __bpf_htons(0xeB9F)) 205 RET_CG_ERR(ret); 206 207 reg_opt.len = 0; 208 ret = bpf_load_hdr_opt(skops, ®_opt, sizeof(reg_opt), 0); 209 if (ret != 4 || reg_opt.len != 4 || reg_opt.kind != 0xB9 || 210 reg_opt.data[0] != 0xfa || reg_opt.data[1] != 0xce) 211 RET_CG_ERR(ret); 212 213 th = skops->skb_data; 214 if (th + 1 > skops->skb_data_end) 215 RET_CG_ERR(0); 216 217 if (th->syn) { 218 active_lport_h = skops->local_port; 219 active_lport_n = th->source; 220 221 /* Search the win scale option written by kernel 222 * in the SYN packet. 223 */ 224 ret = bpf_load_hdr_opt(skops, &win_scale_opt, 225 sizeof(win_scale_opt), 0); 226 if (ret != 3 || win_scale_opt.len != 3 || 227 win_scale_opt.kind != TCPOPT_WINDOW) 228 RET_CG_ERR(ret); 229 230 /* Write the win scale option that kernel 231 * has already written. 232 */ 233 err = bpf_store_hdr_opt(skops, &win_scale_opt, 234 sizeof(win_scale_opt), 0); 235 if (err != -EEXIST) 236 RET_CG_ERR(err); 237 } 238 239 return CG_OK; 240} 241 242static int handle_hdr_opt_len(struct bpf_sock_ops *skops) 243{ 244 __u8 tcp_flags = skops_tcp_flags(skops); 245 246 if ((tcp_flags & TCPHDR_SYNACK) == TCPHDR_SYNACK) 247 /* Check the SYN from bpf_sock_ops_kern->syn_skb */ 248 return check_active_syn_in(skops); 249 250 /* Passive side should have cleared the write hdr cb by now */ 251 if (skops->local_port == passive_lport_h) 252 RET_CG_ERR(0); 253 254 return active_opt_len(skops); 255} 256 257static int handle_write_hdr_opt(struct bpf_sock_ops *skops) 258{ 259 if (skops->local_port == passive_lport_h) 260 RET_CG_ERR(0); 261 262 return write_active_opt(skops); 263} 264 265static int handle_parse_hdr(struct bpf_sock_ops *skops) 266{ 267 /* Passive side is not writing any non-standard/unknown 268 * option, so the active side should never be called. 269 */ 270 if (skops->local_port == active_lport_h) 271 RET_CG_ERR(0); 272 273 return check_active_hdr_in(skops); 274} 275 276static int handle_passive_estab(struct bpf_sock_ops *skops) 277{ 278 int err; 279 280 /* No more write hdr cb */ 281 bpf_sock_ops_cb_flags_set(skops, 282 skops->bpf_sock_ops_cb_flags & 283 ~BPF_SOCK_OPS_WRITE_HDR_OPT_CB_FLAG); 284 285 /* Recheck the SYN but check the tp->saved_syn this time */ 286 err = check_active_syn_in(skops); 287 if (err == CG_ERR) 288 return err; 289 290 nr_syn++; 291 292 /* The ack has header option written by the active side also */ 293 return check_active_hdr_in(skops); 294} 295 296SEC("sockops") 297int misc_estab(struct bpf_sock_ops *skops) 298{ 299 int true_val = 1; 300 301 switch (skops->op) { 302 case BPF_SOCK_OPS_TCP_LISTEN_CB: 303 passive_lport_h = skops->local_port; 304 passive_lport_n = __bpf_htons(passive_lport_h); 305 bpf_setsockopt(skops, SOL_TCP, TCP_SAVE_SYN, 306 &true_val, sizeof(true_val)); 307 set_hdr_cb_flags(skops, 0); 308 break; 309 case BPF_SOCK_OPS_TCP_CONNECT_CB: 310 set_hdr_cb_flags(skops, 0); 311 break; 312 case BPF_SOCK_OPS_PARSE_HDR_OPT_CB: 313 return handle_parse_hdr(skops); 314 case BPF_SOCK_OPS_HDR_OPT_LEN_CB: 315 return handle_hdr_opt_len(skops); 316 case BPF_SOCK_OPS_WRITE_HDR_OPT_CB: 317 return handle_write_hdr_opt(skops); 318 case BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB: 319 return handle_passive_estab(skops); 320 } 321 322 return CG_OK; 323} 324 325char _license[] SEC("license") = "GPL";