1 // SPDX-License-Identifier: GPL-2.0 2 3 #include <test_progs.h> 4 #include <linux/pkt_cls.h> 5 6 #include "test_tc_bpf.skel.h" 7 8 #define LO_IFINDEX 1 9 10 #define TEST_DECLARE_OPTS(__fd) \ 11 DECLARE_LIBBPF_OPTS(bpf_tc_opts, opts_h, .handle = 1); \ 12 DECLARE_LIBBPF_OPTS(bpf_tc_opts, opts_p, .priority = 1); \ 13 DECLARE_LIBBPF_OPTS(bpf_tc_opts, opts_f, .prog_fd = __fd); \ 14 DECLARE_LIBBPF_OPTS(bpf_tc_opts, opts_hp, .handle = 1, .priority = 1); \ 15 DECLARE_LIBBPF_OPTS(bpf_tc_opts, opts_hf, .handle = 1, .prog_fd = __fd); \ 16 DECLARE_LIBBPF_OPTS(bpf_tc_opts, opts_pf, .priority = 1, .prog_fd = __fd); \ 17 DECLARE_LIBBPF_OPTS(bpf_tc_opts, opts_hpf, .handle = 1, .priority = 1, .prog_fd = __fd); \ 18 DECLARE_LIBBPF_OPTS(bpf_tc_opts, opts_hpi, .handle = 1, .priority = 1, .prog_id = 42); \ 19 DECLARE_LIBBPF_OPTS(bpf_tc_opts, opts_hpr, .handle = 1, .priority = 1, \ 20 .flags = BPF_TC_F_REPLACE); \ 21 DECLARE_LIBBPF_OPTS(bpf_tc_opts, opts_hpfi, .handle = 1, .priority = 1, .prog_fd = __fd, \ 22 .prog_id = 42); \ 23 DECLARE_LIBBPF_OPTS(bpf_tc_opts, opts_prio_max, .handle = 1, .priority = UINT16_MAX + 1); 24 25 static int test_tc_bpf_basic(const struct bpf_tc_hook *hook, int fd) 26 { 27 DECLARE_LIBBPF_OPTS(bpf_tc_opts, opts, .handle = 1, .priority = 1, .prog_fd = fd); 28 struct bpf_prog_info info = {}; 29 __u32 info_len = sizeof(info); 30 int ret; 31 32 ret = bpf_obj_get_info_by_fd(fd, &info, &info_len); 33 if (!ASSERT_OK(ret, "bpf_obj_get_info_by_fd")) 34 return ret; 35 36 ret = bpf_tc_attach(hook, &opts); 37 if (!ASSERT_OK(ret, "bpf_tc_attach")) 38 return ret; 39 40 if (!ASSERT_EQ(opts.handle, 1, "handle set") || 41 !ASSERT_EQ(opts.priority, 1, "priority set") || 42 !ASSERT_EQ(opts.prog_id, info.id, "prog_id set")) 43 goto end; 44 45 opts.prog_id = 0; 46 opts.flags = BPF_TC_F_REPLACE; 47 ret = bpf_tc_attach(hook, &opts); 48 if (!ASSERT_OK(ret, "bpf_tc_attach replace mode")) 49 goto end; 50 51 opts.flags = opts.prog_fd = opts.prog_id = 0; 52 ret = bpf_tc_query(hook, &opts); 53 if (!ASSERT_OK(ret, "bpf_tc_query")) 54 goto end; 55 56 if (!ASSERT_EQ(opts.handle, 1, "handle set") || 57 !ASSERT_EQ(opts.priority, 1, "priority set") || 58 !ASSERT_EQ(opts.prog_id, info.id, "prog_id set")) 59 goto end; 60 61 end: 62 opts.flags = opts.prog_fd = opts.prog_id = 0; 63 ret = bpf_tc_detach(hook, &opts); 64 ASSERT_OK(ret, "bpf_tc_detach"); 65 return ret; 66 } 67 68 static int test_tc_bpf_api(struct bpf_tc_hook *hook, int fd) 69 { 70 DECLARE_LIBBPF_OPTS(bpf_tc_opts, attach_opts, .handle = 1, .priority = 1, .prog_fd = fd); 71 DECLARE_LIBBPF_OPTS(bpf_tc_hook, inv_hook, .attach_point = BPF_TC_INGRESS); 72 DECLARE_LIBBPF_OPTS(bpf_tc_opts, opts, .handle = 1, .priority = 1); 73 int ret; 74 75 ret = bpf_tc_hook_create(NULL); 76 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_hook_create invalid hook = NULL")) 77 return -EINVAL; 78 79 /* hook ifindex = 0 */ 80 ret = bpf_tc_hook_create(&inv_hook); 81 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_hook_create invalid hook ifindex == 0")) 82 return -EINVAL; 83 84 ret = bpf_tc_hook_destroy(&inv_hook); 85 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_hook_destroy invalid hook ifindex == 0")) 86 return -EINVAL; 87 88 ret = bpf_tc_attach(&inv_hook, &attach_opts); 89 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_attach invalid hook ifindex == 0")) 90 return -EINVAL; 91 attach_opts.prog_id = 0; 92 93 ret = bpf_tc_detach(&inv_hook, &opts); 94 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_detach invalid hook ifindex == 0")) 95 return -EINVAL; 96 97 ret = bpf_tc_query(&inv_hook, &opts); 98 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_query invalid hook ifindex == 0")) 99 return -EINVAL; 100 101 /* hook ifindex < 0 */ 102 inv_hook.ifindex = -1; 103 104 ret = bpf_tc_hook_create(&inv_hook); 105 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_hook_create invalid hook ifindex < 0")) 106 return -EINVAL; 107 108 ret = bpf_tc_hook_destroy(&inv_hook); 109 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_hook_destroy invalid hook ifindex < 0")) 110 return -EINVAL; 111 112 ret = bpf_tc_attach(&inv_hook, &attach_opts); 113 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_attach invalid hook ifindex < 0")) 114 return -EINVAL; 115 attach_opts.prog_id = 0; 116 117 ret = bpf_tc_detach(&inv_hook, &opts); 118 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_detach invalid hook ifindex < 0")) 119 return -EINVAL; 120 121 ret = bpf_tc_query(&inv_hook, &opts); 122 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_query invalid hook ifindex < 0")) 123 return -EINVAL; 124 125 inv_hook.ifindex = LO_IFINDEX; 126 127 /* hook.attach_point invalid */ 128 inv_hook.attach_point = 0xabcd; 129 ret = bpf_tc_hook_create(&inv_hook); 130 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_hook_create invalid hook.attach_point")) 131 return -EINVAL; 132 133 ret = bpf_tc_hook_destroy(&inv_hook); 134 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_hook_destroy invalid hook.attach_point")) 135 return -EINVAL; 136 137 ret = bpf_tc_attach(&inv_hook, &attach_opts); 138 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_attach invalid hook.attach_point")) 139 return -EINVAL; 140 141 ret = bpf_tc_detach(&inv_hook, &opts); 142 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_detach invalid hook.attach_point")) 143 return -EINVAL; 144 145 ret = bpf_tc_query(&inv_hook, &opts); 146 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_query invalid hook.attach_point")) 147 return -EINVAL; 148 149 inv_hook.attach_point = BPF_TC_INGRESS; 150 151 /* hook.attach_point valid, but parent invalid */ 152 inv_hook.parent = TC_H_MAKE(1UL << 16, 10); 153 ret = bpf_tc_hook_create(&inv_hook); 154 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_hook_create invalid hook parent")) 155 return -EINVAL; 156 157 ret = bpf_tc_hook_destroy(&inv_hook); 158 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_hook_destroy invalid hook parent")) 159 return -EINVAL; 160 161 ret = bpf_tc_attach(&inv_hook, &attach_opts); 162 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_attach invalid hook parent")) 163 return -EINVAL; 164 165 ret = bpf_tc_detach(&inv_hook, &opts); 166 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_detach invalid hook parent")) 167 return -EINVAL; 168 169 ret = bpf_tc_query(&inv_hook, &opts); 170 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_query invalid hook parent")) 171 return -EINVAL; 172 173 inv_hook.attach_point = BPF_TC_CUSTOM; 174 inv_hook.parent = 0; 175 /* These return EOPNOTSUPP instead of EINVAL as parent is checked after 176 * attach_point of the hook. 177 */ 178 ret = bpf_tc_hook_create(&inv_hook); 179 if (!ASSERT_EQ(ret, -EOPNOTSUPP, "bpf_tc_hook_create invalid hook parent")) 180 return -EINVAL; 181 182 ret = bpf_tc_hook_destroy(&inv_hook); 183 if (!ASSERT_EQ(ret, -EOPNOTSUPP, "bpf_tc_hook_destroy invalid hook parent")) 184 return -EINVAL; 185 186 ret = bpf_tc_attach(&inv_hook, &attach_opts); 187 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_attach invalid hook parent")) 188 return -EINVAL; 189 190 ret = bpf_tc_detach(&inv_hook, &opts); 191 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_detach invalid hook parent")) 192 return -EINVAL; 193 194 ret = bpf_tc_query(&inv_hook, &opts); 195 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_query invalid hook parent")) 196 return -EINVAL; 197 198 inv_hook.attach_point = BPF_TC_INGRESS; 199 200 /* detach */ 201 { 202 TEST_DECLARE_OPTS(fd); 203 204 ret = bpf_tc_detach(NULL, &opts_hp); 205 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_detach invalid hook = NULL")) 206 return -EINVAL; 207 208 ret = bpf_tc_detach(hook, NULL); 209 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_detach invalid opts = NULL")) 210 return -EINVAL; 211 212 ret = bpf_tc_detach(hook, &opts_hpr); 213 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_detach invalid flags set")) 214 return -EINVAL; 215 216 ret = bpf_tc_detach(hook, &opts_hpf); 217 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_detach invalid prog_fd set")) 218 return -EINVAL; 219 220 ret = bpf_tc_detach(hook, &opts_hpi); 221 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_detach invalid prog_id set")) 222 return -EINVAL; 223 224 ret = bpf_tc_detach(hook, &opts_p); 225 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_detach invalid handle unset")) 226 return -EINVAL; 227 228 ret = bpf_tc_detach(hook, &opts_h); 229 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_detach invalid priority unset")) 230 return -EINVAL; 231 232 ret = bpf_tc_detach(hook, &opts_prio_max); 233 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_detach invalid priority > UINT16_MAX")) 234 return -EINVAL; 235 } 236 237 /* query */ 238 { 239 TEST_DECLARE_OPTS(fd); 240 241 ret = bpf_tc_query(NULL, &opts); 242 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_query invalid hook = NULL")) 243 return -EINVAL; 244 245 ret = bpf_tc_query(hook, NULL); 246 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_query invalid opts = NULL")) 247 return -EINVAL; 248 249 ret = bpf_tc_query(hook, &opts_hpr); 250 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_query invalid flags set")) 251 return -EINVAL; 252 253 ret = bpf_tc_query(hook, &opts_hpf); 254 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_query invalid prog_fd set")) 255 return -EINVAL; 256 257 ret = bpf_tc_query(hook, &opts_hpi); 258 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_query invalid prog_id set")) 259 return -EINVAL; 260 261 ret = bpf_tc_query(hook, &opts_p); 262 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_query invalid handle unset")) 263 return -EINVAL; 264 265 ret = bpf_tc_query(hook, &opts_h); 266 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_query invalid priority unset")) 267 return -EINVAL; 268 269 ret = bpf_tc_query(hook, &opts_prio_max); 270 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_query invalid priority > UINT16_MAX")) 271 return -EINVAL; 272 273 /* when chain is not present, kernel returns -EINVAL */ 274 ret = bpf_tc_query(hook, &opts_hp); 275 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_query valid handle, priority set")) 276 return -EINVAL; 277 } 278 279 /* attach */ 280 { 281 TEST_DECLARE_OPTS(fd); 282 283 ret = bpf_tc_attach(NULL, &opts_hp); 284 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_attach invalid hook = NULL")) 285 return -EINVAL; 286 287 ret = bpf_tc_attach(hook, NULL); 288 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_attach invalid opts = NULL")) 289 return -EINVAL; 290 291 opts_hp.flags = 42; 292 ret = bpf_tc_attach(hook, &opts_hp); 293 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_attach invalid flags")) 294 return -EINVAL; 295 296 ret = bpf_tc_attach(hook, NULL); 297 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_attach invalid prog_fd unset")) 298 return -EINVAL; 299 300 ret = bpf_tc_attach(hook, &opts_hpi); 301 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_attach invalid prog_id set")) 302 return -EINVAL; 303 304 ret = bpf_tc_attach(hook, &opts_pf); 305 if (!ASSERT_OK(ret, "bpf_tc_attach valid handle unset")) 306 return -EINVAL; 307 opts_pf.prog_fd = opts_pf.prog_id = 0; 308 ASSERT_OK(bpf_tc_detach(hook, &opts_pf), "bpf_tc_detach"); 309 310 ret = bpf_tc_attach(hook, &opts_hf); 311 if (!ASSERT_OK(ret, "bpf_tc_attach valid priority unset")) 312 return -EINVAL; 313 opts_hf.prog_fd = opts_hf.prog_id = 0; 314 ASSERT_OK(bpf_tc_detach(hook, &opts_hf), "bpf_tc_detach"); 315 316 ret = bpf_tc_attach(hook, &opts_prio_max); 317 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_attach invalid priority > UINT16_MAX")) 318 return -EINVAL; 319 320 ret = bpf_tc_attach(hook, &opts_f); 321 if (!ASSERT_OK(ret, "bpf_tc_attach valid both handle and priority unset")) 322 return -EINVAL; 323 opts_f.prog_fd = opts_f.prog_id = 0; 324 ASSERT_OK(bpf_tc_detach(hook, &opts_f), "bpf_tc_detach"); 325 } 326 327 return 0; 328 } 329 330 void test_tc_bpf(void) 331 { 332 DECLARE_LIBBPF_OPTS(bpf_tc_hook, hook, .ifindex = LO_IFINDEX, 333 .attach_point = BPF_TC_INGRESS); 334 struct test_tc_bpf *skel = NULL; 335 bool hook_created = false; 336 int cls_fd, ret; 337 338 skel = test_tc_bpf__open_and_load(); 339 if (!ASSERT_OK_PTR(skel, "test_tc_bpf__open_and_load")) 340 return; 341 342 cls_fd = bpf_program__fd(skel->progs.cls); 343 344 ret = bpf_tc_hook_create(&hook); 345 if (ret == 0) 346 hook_created = true; 347 348 ret = ret == -EEXIST ? 0 : ret; 349 if (!ASSERT_OK(ret, "bpf_tc_hook_create(BPF_TC_INGRESS)")) 350 goto end; 351 352 hook.attach_point = BPF_TC_CUSTOM; 353 hook.parent = TC_H_MAKE(TC_H_CLSACT, TC_H_MIN_INGRESS); 354 ret = bpf_tc_hook_create(&hook); 355 if (!ASSERT_EQ(ret, -EOPNOTSUPP, "bpf_tc_hook_create invalid hook.attach_point")) 356 goto end; 357 358 ret = test_tc_bpf_basic(&hook, cls_fd); 359 if (!ASSERT_OK(ret, "test_tc_internal ingress")) 360 goto end; 361 362 ret = bpf_tc_hook_destroy(&hook); 363 if (!ASSERT_EQ(ret, -EOPNOTSUPP, "bpf_tc_hook_destroy invalid hook.attach_point")) 364 goto end; 365 366 hook.attach_point = BPF_TC_INGRESS; 367 hook.parent = 0; 368 bpf_tc_hook_destroy(&hook); 369 370 ret = test_tc_bpf_basic(&hook, cls_fd); 371 if (!ASSERT_OK(ret, "test_tc_internal ingress")) 372 goto end; 373 374 bpf_tc_hook_destroy(&hook); 375 376 hook.attach_point = BPF_TC_EGRESS; 377 ret = test_tc_bpf_basic(&hook, cls_fd); 378 if (!ASSERT_OK(ret, "test_tc_internal egress")) 379 goto end; 380 381 bpf_tc_hook_destroy(&hook); 382 383 ret = test_tc_bpf_api(&hook, cls_fd); 384 if (!ASSERT_OK(ret, "test_tc_bpf_api")) 385 goto end; 386 387 bpf_tc_hook_destroy(&hook); 388 389 end: 390 if (hook_created) { 391 hook.attach_point = BPF_TC_INGRESS | BPF_TC_EGRESS; 392 bpf_tc_hook_destroy(&hook); 393 } 394 test_tc_bpf__destroy(skel); 395 } 396