1 // SPDX-License-Identifier: GPL-2.0 2 #include <error.h> 3 #include <netinet/tcp.h> 4 #include <test_progs.h> 5 #include "sockmap_helpers.h" 6 #include "test_skmsg_load_helpers.skel.h" 7 #include "test_sockmap_strp.skel.h" 8 9 #define STRP_PKT_HEAD_LEN 4 10 #define STRP_PKT_BODY_LEN 6 11 #define STRP_PKT_FULL_LEN (STRP_PKT_HEAD_LEN + STRP_PKT_BODY_LEN) 12 13 static const char packet[STRP_PKT_FULL_LEN] = "head+body\0"; 14 static const int test_packet_num = 100; 15 16 /* Current implementation of tcp_bpf_recvmsg_parser() invokes data_ready 17 * with sk held if an skb exists in sk_receive_queue. Then for the 18 * data_ready implementation of strparser, it will delay the read 19 * operation if sk is held and EAGAIN is returned. 20 */ 21 static int sockmap_strp_consume_pre_data(int p) 22 { 23 int recvd; 24 bool retried = false; 25 char rcv[10]; 26 27 retry: 28 errno = 0; 29 recvd = recv_timeout(p, rcv, sizeof(rcv), 0, 1); 30 if (recvd < 0 && errno == EAGAIN && retried == false) { 31 /* On the first call, EAGAIN will certainly be returned. 32 * A 1-second wait is enough for the workqueue to finish. 33 */ 34 sleep(1); 35 retried = true; 36 goto retry; 37 } 38 39 if (!ASSERT_EQ(recvd, STRP_PKT_FULL_LEN, "recv error or truncated data") || 40 !ASSERT_OK(memcmp(packet, rcv, STRP_PKT_FULL_LEN), 41 "data mismatch")) 42 return -1; 43 return 0; 44 } 45 46 static struct test_sockmap_strp *sockmap_strp_init(int *out_map, bool pass, 47 bool need_parser) 48 { 49 struct test_sockmap_strp *strp = NULL; 50 int verdict, parser; 51 int err; 52 53 strp = test_sockmap_strp__open_and_load(); 54 *out_map = bpf_map__fd(strp->maps.sock_map); 55 56 if (need_parser) 57 parser = bpf_program__fd(strp->progs.prog_skb_parser_partial); 58 else 59 parser = bpf_program__fd(strp->progs.prog_skb_parser); 60 61 if (pass) 62 verdict = bpf_program__fd(strp->progs.prog_skb_verdict_pass); 63 else 64 verdict = bpf_program__fd(strp->progs.prog_skb_verdict); 65 66 err = bpf_prog_attach(parser, *out_map, BPF_SK_SKB_STREAM_PARSER, 0); 67 if (!ASSERT_OK(err, "bpf_prog_attach stream parser")) 68 goto err; 69 70 err = bpf_prog_attach(verdict, *out_map, BPF_SK_SKB_STREAM_VERDICT, 0); 71 if (!ASSERT_OK(err, "bpf_prog_attach stream verdict")) 72 goto err; 73 74 return strp; 75 err: 76 test_sockmap_strp__destroy(strp); 77 return NULL; 78 } 79 80 /* Dispatch packets to different socket by packet size: 81 * 82 * ------ ------ 83 * | pkt4 || pkt1 |... > remote socket 84 * ------ ------ / ------ ------ 85 * | pkt8 | pkt7 |... 86 * ------ ------ \ ------ ------ 87 * | pkt3 || pkt2 |... > local socket 88 * ------ ------ 89 */ 90 static void test_sockmap_strp_dispatch_pkt(int family, int sotype) 91 { 92 int i, j, zero = 0, one = 1, recvd; 93 int err, map; 94 int c0 = -1, p0 = -1, c1 = -1, p1 = -1; 95 struct test_sockmap_strp *strp = NULL; 96 int test_cnt = 6; 97 char rcv[10]; 98 struct { 99 char data[7]; 100 int data_len; 101 int send_cnt; 102 int *receiver; 103 } send_dir[2] = { 104 /* data expected to deliver to local */ 105 {"llllll", 6, 0, &p0}, 106 /* data expected to deliver to remote */ 107 {"rrrrr", 5, 0, &c1} 108 }; 109 110 strp = sockmap_strp_init(&map, false, false); 111 if (!ASSERT_TRUE(strp, "sockmap_strp_init")) 112 return; 113 114 err = create_socket_pairs(family, sotype, &c0, &c1, &p0, &p1); 115 if (!ASSERT_OK(err, "create_socket_pairs()")) 116 goto out; 117 118 err = bpf_map_update_elem(map, &zero, &p0, BPF_NOEXIST); 119 if (!ASSERT_OK(err, "bpf_map_update_elem(p0)")) 120 goto out_close; 121 122 err = bpf_map_update_elem(map, &one, &p1, BPF_NOEXIST); 123 if (!ASSERT_OK(err, "bpf_map_update_elem(p1)")) 124 goto out_close; 125 126 err = setsockopt(c1, IPPROTO_TCP, TCP_NODELAY, &zero, sizeof(zero)); 127 if (!ASSERT_OK(err, "setsockopt(TCP_NODELAY)")) 128 goto out_close; 129 130 /* deliver data with data size greater than 5 to local */ 131 strp->data->verdict_max_size = 5; 132 133 for (i = 0; i < test_cnt; i++) { 134 int d = i % 2; 135 136 xsend(c0, send_dir[d].data, send_dir[d].data_len, 0); 137 send_dir[d].send_cnt++; 138 } 139 140 for (i = 0; i < 2; i++) { 141 for (j = 0; j < send_dir[i].send_cnt; j++) { 142 int expected = send_dir[i].data_len; 143 144 recvd = recv_timeout(*send_dir[i].receiver, rcv, 145 expected, MSG_DONTWAIT, 146 IO_TIMEOUT_SEC); 147 if (!ASSERT_EQ(recvd, expected, "recv_timeout()")) 148 goto out_close; 149 if (!ASSERT_OK(memcmp(send_dir[i].data, rcv, recvd), 150 "data mismatch")) 151 goto out_close; 152 } 153 } 154 out_close: 155 close(c0); 156 close(c1); 157 close(p0); 158 close(p1); 159 out: 160 test_sockmap_strp__destroy(strp); 161 } 162 163 /* We have multiple packets in one skb 164 * ------------ ------------ ------------ 165 * | packet1 | packet2 | ... 166 * ------------ ------------ ------------ 167 */ 168 static void test_sockmap_strp_multiple_pkt(int family, int sotype) 169 { 170 int i, zero = 0; 171 int sent, recvd, total; 172 int err, map; 173 int c = -1, p = -1; 174 struct test_sockmap_strp *strp = NULL; 175 char *snd = NULL, *rcv = NULL; 176 177 strp = sockmap_strp_init(&map, true, true); 178 if (!ASSERT_TRUE(strp, "sockmap_strp_init")) 179 return; 180 181 err = create_pair(family, sotype, &c, &p); 182 if (err) 183 goto out; 184 185 err = bpf_map_update_elem(map, &zero, &p, BPF_NOEXIST); 186 if (!ASSERT_OK(err, "bpf_map_update_elem(zero, p)")) 187 goto out_close; 188 189 /* construct multiple packets in one buffer */ 190 total = test_packet_num * STRP_PKT_FULL_LEN; 191 snd = malloc(total); 192 rcv = malloc(total + 1); 193 if (!ASSERT_TRUE(snd, "malloc(snd)") || 194 !ASSERT_TRUE(rcv, "malloc(rcv)")) 195 goto out_close; 196 197 for (i = 0; i < test_packet_num; i++) { 198 memcpy(snd + i * STRP_PKT_FULL_LEN, 199 packet, STRP_PKT_FULL_LEN); 200 } 201 202 sent = xsend(c, snd, total, 0); 203 if (!ASSERT_EQ(sent, total, "xsend(c)")) 204 goto out_close; 205 206 /* try to recv one more byte to avoid truncation check */ 207 recvd = recv_timeout(p, rcv, total + 1, MSG_DONTWAIT, IO_TIMEOUT_SEC); 208 if (!ASSERT_EQ(recvd, total, "recv(rcv)")) 209 goto out_close; 210 211 /* we sent TCP segment with multiple encapsulation 212 * then check whether packets are handled correctly 213 */ 214 if (!ASSERT_OK(memcmp(snd, rcv, total), "data mismatch")) 215 goto out_close; 216 217 out_close: 218 close(c); 219 close(p); 220 if (snd) 221 free(snd); 222 if (rcv) 223 free(rcv); 224 out: 225 test_sockmap_strp__destroy(strp); 226 } 227 228 /* Test strparser with partial read */ 229 static void test_sockmap_strp_partial_read(int family, int sotype) 230 { 231 int zero = 0, recvd, off; 232 int err, map; 233 int c = -1, p = -1; 234 struct test_sockmap_strp *strp = NULL; 235 char rcv[STRP_PKT_FULL_LEN + 1] = "0"; 236 237 strp = sockmap_strp_init(&map, true, true); 238 if (!ASSERT_TRUE(strp, "sockmap_strp_init")) 239 return; 240 241 err = create_pair(family, sotype, &c, &p); 242 if (err) 243 goto out; 244 245 /* sk_data_ready of 'p' will be replaced by strparser handler */ 246 err = bpf_map_update_elem(map, &zero, &p, BPF_NOEXIST); 247 if (!ASSERT_OK(err, "bpf_map_update_elem(zero, p)")) 248 goto out_close; 249 250 /* 1.1 send partial head, 1 byte header left */ 251 off = STRP_PKT_HEAD_LEN - 1; 252 xsend(c, packet, off, 0); 253 recvd = recv_timeout(p, rcv, sizeof(rcv), MSG_DONTWAIT, 1); 254 if (!ASSERT_EQ(-1, recvd, "partial head sent, expected no data")) 255 goto out_close; 256 257 /* 1.2 send remaining head and body */ 258 xsend(c, packet + off, STRP_PKT_FULL_LEN - off, 0); 259 recvd = recv_timeout(p, rcv, sizeof(rcv), MSG_DONTWAIT, IO_TIMEOUT_SEC); 260 if (!ASSERT_EQ(recvd, STRP_PKT_FULL_LEN, "expected full data")) 261 goto out_close; 262 263 /* 2.1 send partial head, 1 byte header left */ 264 off = STRP_PKT_HEAD_LEN - 1; 265 xsend(c, packet, off, 0); 266 267 /* 2.2 send remaining head and partial body, 1 byte body left */ 268 xsend(c, packet + off, STRP_PKT_FULL_LEN - off - 1, 0); 269 off = STRP_PKT_FULL_LEN - 1; 270 recvd = recv_timeout(p, rcv, sizeof(rcv), MSG_DONTWAIT, 1); 271 if (!ASSERT_EQ(-1, recvd, "partial body sent, expected no data")) 272 goto out_close; 273 274 /* 2.3 send remaining body */ 275 xsend(c, packet + off, STRP_PKT_FULL_LEN - off, 0); 276 recvd = recv_timeout(p, rcv, sizeof(rcv), MSG_DONTWAIT, IO_TIMEOUT_SEC); 277 if (!ASSERT_EQ(recvd, STRP_PKT_FULL_LEN, "expected full data")) 278 goto out_close; 279 280 out_close: 281 close(c); 282 close(p); 283 284 out: 285 test_sockmap_strp__destroy(strp); 286 } 287 288 /* Test simple socket read/write with strparser + FIONREAD */ 289 static void test_sockmap_strp_pass(int family, int sotype, bool fionread) 290 { 291 int zero = 0, pkt_size = STRP_PKT_FULL_LEN, sent, recvd, avail; 292 int err, map; 293 int c = -1, p = -1; 294 int test_cnt = 10, i; 295 struct test_sockmap_strp *strp = NULL; 296 char rcv[STRP_PKT_FULL_LEN + 1] = "0"; 297 298 strp = sockmap_strp_init(&map, true, true); 299 if (!ASSERT_TRUE(strp, "sockmap_strp_init")) 300 return; 301 302 err = create_pair(family, sotype, &c, &p); 303 if (err) 304 goto out; 305 306 /* inject some data before bpf process, it should be read 307 * correctly because we check sk_receive_queue in 308 * tcp_bpf_recvmsg_parser(). 309 */ 310 sent = xsend(c, packet, pkt_size, 0); 311 if (!ASSERT_EQ(sent, pkt_size, "xsend(pre-data)")) 312 goto out_close; 313 314 /* sk_data_ready of 'p' will be replaced by strparser handler */ 315 err = bpf_map_update_elem(map, &zero, &p, BPF_NOEXIST); 316 if (!ASSERT_OK(err, "bpf_map_update_elem(p)")) 317 goto out_close; 318 319 /* consume previous data we injected */ 320 if (sockmap_strp_consume_pre_data(p)) 321 goto out_close; 322 323 /* Previously, we encountered issues such as deadlocks and 324 * sequence errors that resulted in the inability to read 325 * continuously. Therefore, we perform multiple iterations 326 * of testing here. 327 */ 328 for (i = 0; i < test_cnt; i++) { 329 sent = xsend(c, packet, pkt_size, 0); 330 if (!ASSERT_EQ(sent, pkt_size, "xsend(c)")) 331 goto out_close; 332 333 recvd = recv_timeout(p, rcv, sizeof(rcv), MSG_DONTWAIT, 334 IO_TIMEOUT_SEC); 335 if (!ASSERT_EQ(recvd, pkt_size, "recv_timeout(p)") || 336 !ASSERT_OK(memcmp(packet, rcv, pkt_size), 337 "memcmp, data mismatch")) 338 goto out_close; 339 } 340 341 if (fionread) { 342 sent = xsend(c, packet, pkt_size, 0); 343 if (!ASSERT_EQ(sent, pkt_size, "second xsend(c)")) 344 goto out_close; 345 346 err = ioctl(p, FIONREAD, &avail); 347 if (!ASSERT_OK(err, "ioctl(FIONREAD) error") || 348 !ASSERT_EQ(avail, pkt_size, "ioctl(FIONREAD)")) 349 goto out_close; 350 351 recvd = recv_timeout(p, rcv, sizeof(rcv), MSG_DONTWAIT, 352 IO_TIMEOUT_SEC); 353 if (!ASSERT_EQ(recvd, pkt_size, "second recv_timeout(p)") || 354 !ASSERT_OK(memcmp(packet, rcv, pkt_size), 355 "second memcmp, data mismatch")) 356 goto out_close; 357 } 358 359 out_close: 360 close(c); 361 close(p); 362 363 out: 364 test_sockmap_strp__destroy(strp); 365 } 366 367 /* Test strparser with verdict mode */ 368 static void test_sockmap_strp_verdict(int family, int sotype) 369 { 370 int zero = 0, one = 1, sent, recvd, off; 371 int err, map; 372 int c0 = -1, p0 = -1, c1 = -1, p1 = -1; 373 struct test_sockmap_strp *strp = NULL; 374 char rcv[STRP_PKT_FULL_LEN + 1] = "0"; 375 376 strp = sockmap_strp_init(&map, false, true); 377 if (!ASSERT_TRUE(strp, "sockmap_strp_init")) 378 return; 379 380 /* We simulate a reverse proxy server. 381 * When p0 receives data from c0, we forward it to c1. 382 * From c1's perspective, it will consider this data 383 * as being sent by p1. 384 */ 385 err = create_socket_pairs(family, sotype, &c0, &c1, &p0, &p1); 386 if (!ASSERT_OK(err, "create_socket_pairs()")) 387 goto out; 388 389 err = bpf_map_update_elem(map, &zero, &p0, BPF_NOEXIST); 390 if (!ASSERT_OK(err, "bpf_map_update_elem(p0)")) 391 goto out_close; 392 393 err = bpf_map_update_elem(map, &one, &p1, BPF_NOEXIST); 394 if (!ASSERT_OK(err, "bpf_map_update_elem(p1)")) 395 goto out_close; 396 397 sent = xsend(c0, packet, STRP_PKT_FULL_LEN, 0); 398 if (!ASSERT_EQ(sent, STRP_PKT_FULL_LEN, "xsend(c0)")) 399 goto out_close; 400 401 recvd = recv_timeout(c1, rcv, sizeof(rcv), MSG_DONTWAIT, 402 IO_TIMEOUT_SEC); 403 if (!ASSERT_EQ(recvd, STRP_PKT_FULL_LEN, "recv_timeout(c1)") || 404 !ASSERT_OK(memcmp(packet, rcv, STRP_PKT_FULL_LEN), 405 "received data does not match the sent data")) 406 goto out_close; 407 408 /* send again to ensure the stream is functioning correctly. */ 409 sent = xsend(c0, packet, STRP_PKT_FULL_LEN, 0); 410 if (!ASSERT_EQ(sent, STRP_PKT_FULL_LEN, "second xsend(c0)")) 411 goto out_close; 412 413 /* partial read */ 414 off = STRP_PKT_FULL_LEN / 2; 415 recvd = recv_timeout(c1, rcv, off, MSG_DONTWAIT, 416 IO_TIMEOUT_SEC); 417 recvd += recv_timeout(c1, rcv + off, sizeof(rcv) - off, MSG_DONTWAIT, 418 IO_TIMEOUT_SEC); 419 420 if (!ASSERT_EQ(recvd, STRP_PKT_FULL_LEN, "partial recv_timeout(c1)") || 421 !ASSERT_OK(memcmp(packet, rcv, STRP_PKT_FULL_LEN), 422 "partial received data does not match the sent data")) 423 goto out_close; 424 425 out_close: 426 close(c0); 427 close(c1); 428 close(p0); 429 close(p1); 430 out: 431 test_sockmap_strp__destroy(strp); 432 } 433 434 void test_sockmap_strp(void) 435 { 436 if (test__start_subtest("sockmap strp tcp pass")) 437 test_sockmap_strp_pass(AF_INET, SOCK_STREAM, false); 438 if (test__start_subtest("sockmap strp tcp v6 pass")) 439 test_sockmap_strp_pass(AF_INET6, SOCK_STREAM, false); 440 if (test__start_subtest("sockmap strp tcp pass fionread")) 441 test_sockmap_strp_pass(AF_INET, SOCK_STREAM, true); 442 if (test__start_subtest("sockmap strp tcp v6 pass fionread")) 443 test_sockmap_strp_pass(AF_INET6, SOCK_STREAM, true); 444 if (test__start_subtest("sockmap strp tcp verdict")) 445 test_sockmap_strp_verdict(AF_INET, SOCK_STREAM); 446 if (test__start_subtest("sockmap strp tcp v6 verdict")) 447 test_sockmap_strp_verdict(AF_INET6, SOCK_STREAM); 448 if (test__start_subtest("sockmap strp tcp partial read")) 449 test_sockmap_strp_partial_read(AF_INET, SOCK_STREAM); 450 if (test__start_subtest("sockmap strp tcp multiple packets")) 451 test_sockmap_strp_multiple_pkt(AF_INET, SOCK_STREAM); 452 if (test__start_subtest("sockmap strp tcp dispatch")) 453 test_sockmap_strp_dispatch_pkt(AF_INET, SOCK_STREAM); 454 } 455