]>
Commit | Line | Data |
---|---|---|
c8a49431 PB |
1 | // SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB |
2 | /* - | |
3 | * m_ct.c Connection tracking action | |
4 | * | |
5 | * Authors: Paul Blakey <paulb@mellanox.com> | |
6 | * Yossi Kuperman <yossiku@mellanox.com> | |
7 | * Marcelo Ricardo Leitner <marcelo.leitner@gmail.com> | |
8 | */ | |
9 | ||
10 | #include <stdio.h> | |
11 | #include <stdlib.h> | |
12 | #include <unistd.h> | |
13 | #include <string.h> | |
14 | #include "utils.h" | |
15 | #include "tc_util.h" | |
4cdce041 | 16 | #include "rt_names.h" |
c8a49431 PB |
17 | #include <linux/tc_act/tc_ct.h> |
18 | ||
19 | static void | |
20 | usage(void) | |
21 | { | |
22 | fprintf(stderr, | |
23 | "Usage: ct clear\n" | |
4cdce041 | 24 | " ct commit [force] [zone ZONE] [mark MASKED_MARK] [label MASKED_LABEL] [nat NAT_SPEC] [helper HELPER]\n" |
c8a49431 PB |
25 | " ct [nat] [zone ZONE]\n" |
26 | "Where: ZONE is the conntrack zone table number\n" | |
27 | " NAT_SPEC is {src|dst} addr addr1[-addr2] [port port1[-port2]]\n" | |
4cdce041 | 28 | " HELPER is family-proto-name such as ipv4-tcp-ftp\n" |
c8a49431 PB |
29 | "\n"); |
30 | exit(-1); | |
31 | } | |
32 | ||
33 | static int ct_parse_nat_addr_range(const char *str, struct nlmsghdr *n) | |
34 | { | |
35 | inet_prefix addr = { .family = AF_UNSPEC, }; | |
36 | char *addr1, *addr2 = 0; | |
37 | SPRINT_BUF(buffer); | |
38 | int attr; | |
39 | int ret; | |
40 | ||
41 | strncpy(buffer, str, sizeof(buffer) - 1); | |
42 | ||
43 | addr1 = buffer; | |
44 | addr2 = strchr(addr1, '-'); | |
45 | if (addr2) { | |
46 | *addr2 = '\0'; | |
47 | addr2++; | |
48 | } | |
49 | ||
50 | ret = get_addr(&addr, addr1, AF_UNSPEC); | |
51 | if (ret) | |
52 | return ret; | |
53 | attr = addr.family == AF_INET ? TCA_CT_NAT_IPV4_MIN : | |
54 | TCA_CT_NAT_IPV6_MIN; | |
55 | addattr_l(n, MAX_MSG, attr, addr.data, addr.bytelen); | |
56 | ||
57 | if (addr2) { | |
58 | ret = get_addr(&addr, addr2, addr.family); | |
59 | if (ret) | |
60 | return ret; | |
61 | } | |
62 | attr = addr.family == AF_INET ? TCA_CT_NAT_IPV4_MAX : | |
63 | TCA_CT_NAT_IPV6_MAX; | |
64 | addattr_l(n, MAX_MSG, attr, addr.data, addr.bytelen); | |
65 | ||
66 | return 0; | |
67 | } | |
68 | ||
69 | static int ct_parse_nat_port_range(const char *str, struct nlmsghdr *n) | |
70 | { | |
71 | char *port1, *port2 = 0; | |
72 | SPRINT_BUF(buffer); | |
73 | __be16 port; | |
74 | int ret; | |
75 | ||
76 | strncpy(buffer, str, sizeof(buffer) - 1); | |
77 | ||
78 | port1 = buffer; | |
79 | port2 = strchr(port1, '-'); | |
80 | if (port2) { | |
81 | *port2 = '\0'; | |
82 | port2++; | |
83 | } | |
84 | ||
85 | ret = get_be16(&port, port1, 10); | |
86 | if (ret) | |
87 | return -1; | |
88 | addattr16(n, MAX_MSG, TCA_CT_NAT_PORT_MIN, port); | |
89 | ||
90 | if (port2) { | |
91 | ret = get_be16(&port, port2, 10); | |
92 | if (ret) | |
93 | return -1; | |
94 | } | |
95 | addattr16(n, MAX_MSG, TCA_CT_NAT_PORT_MAX, port); | |
96 | ||
97 | return 0; | |
98 | } | |
99 | ||
100 | ||
101 | static int ct_parse_u16(char *str, int value_type, int mask_type, | |
102 | struct nlmsghdr *n) | |
103 | { | |
104 | __u16 value, mask; | |
105 | char *slash = 0; | |
106 | ||
107 | if (mask_type != TCA_CT_UNSPEC) { | |
108 | slash = strchr(str, '/'); | |
109 | if (slash) | |
110 | *slash = '\0'; | |
111 | } | |
112 | ||
113 | if (get_u16(&value, str, 0)) | |
114 | return -1; | |
115 | ||
116 | if (slash) { | |
117 | if (get_u16(&mask, slash + 1, 0)) | |
118 | return -1; | |
119 | } else { | |
120 | mask = UINT16_MAX; | |
121 | } | |
122 | ||
123 | addattr16(n, MAX_MSG, value_type, value); | |
124 | if (mask_type != TCA_CT_UNSPEC) | |
125 | addattr16(n, MAX_MSG, mask_type, mask); | |
126 | ||
127 | return 0; | |
128 | } | |
129 | ||
130 | static int ct_parse_u32(char *str, int value_type, int mask_type, | |
131 | struct nlmsghdr *n) | |
132 | { | |
133 | __u32 value, mask; | |
134 | char *slash; | |
135 | ||
136 | slash = strchr(str, '/'); | |
137 | if (slash) | |
138 | *slash = '\0'; | |
139 | ||
140 | if (get_u32(&value, str, 0)) | |
141 | return -1; | |
142 | ||
143 | if (slash) { | |
144 | if (get_u32(&mask, slash + 1, 0)) | |
145 | return -1; | |
146 | } else { | |
147 | mask = UINT32_MAX; | |
148 | } | |
149 | ||
150 | addattr32(n, MAX_MSG, value_type, value); | |
151 | addattr32(n, MAX_MSG, mask_type, mask); | |
152 | ||
153 | return 0; | |
154 | } | |
155 | ||
156 | static int ct_parse_mark(char *str, struct nlmsghdr *n) | |
157 | { | |
158 | return ct_parse_u32(str, TCA_CT_MARK, TCA_CT_MARK_MASK, n); | |
159 | } | |
160 | ||
4cdce041 XL |
161 | static int ct_parse_helper(char *str, struct nlmsghdr *n) |
162 | { | |
163 | char f[32], p[32], name[32]; | |
ac6d95a8 SH |
164 | __u8 family; |
165 | int proto; | |
4cdce041 XL |
166 | |
167 | if (strlen(str) >= 32 || | |
168 | sscanf(str, "%[^-]-%[^-]-%[^-]", f, p, name) != 3) | |
169 | return -1; | |
170 | if (!strcmp(f, "ipv4")) | |
171 | family = AF_INET; | |
172 | else if (!strcmp(f, "ipv6")) | |
173 | family = AF_INET6; | |
174 | else | |
175 | return -1; | |
ac6d95a8 | 176 | |
4cdce041 XL |
177 | proto = inet_proto_a2n(p); |
178 | if (proto < 0) | |
179 | return -1; | |
180 | ||
181 | addattr8(n, MAX_MSG, TCA_CT_HELPER_FAMILY, family); | |
182 | addattr8(n, MAX_MSG, TCA_CT_HELPER_PROTO, proto); | |
183 | addattrstrz(n, MAX_MSG, TCA_CT_HELPER_NAME, name); | |
184 | return 0; | |
185 | } | |
186 | ||
c8a49431 PB |
187 | static int ct_parse_labels(char *str, struct nlmsghdr *n) |
188 | { | |
189 | #define LABELS_SIZE 16 | |
190 | uint8_t labels[LABELS_SIZE], lmask[LABELS_SIZE]; | |
191 | char *slash, *mask = NULL; | |
192 | size_t slen, slen_mask = 0; | |
193 | ||
194 | slash = index(str, '/'); | |
195 | if (slash) { | |
196 | *slash = 0; | |
197 | mask = slash+1; | |
198 | slen_mask = strlen(mask); | |
199 | } | |
200 | ||
201 | slen = strlen(str); | |
202 | if (slen > LABELS_SIZE*2 || slen_mask > LABELS_SIZE*2) { | |
203 | char errmsg[128]; | |
204 | ||
205 | snprintf(errmsg, sizeof(errmsg), | |
206 | "%zd Max allowed size %d", | |
207 | slen, LABELS_SIZE*2); | |
208 | invarg(errmsg, str); | |
209 | } | |
210 | ||
211 | if (hex2mem(str, labels, slen/2) < 0) | |
212 | invarg("ct: labels must be a hex string\n", str); | |
213 | addattr_l(n, MAX_MSG, TCA_CT_LABELS, labels, slen/2); | |
214 | ||
215 | if (mask) { | |
216 | if (hex2mem(mask, lmask, slen_mask/2) < 0) | |
217 | invarg("ct: labels mask must be a hex string\n", mask); | |
218 | } else { | |
219 | memset(lmask, 0xff, sizeof(lmask)); | |
220 | slen_mask = sizeof(lmask)*2; | |
221 | } | |
222 | addattr_l(n, MAX_MSG, TCA_CT_LABELS_MASK, lmask, slen_mask/2); | |
223 | ||
224 | return 0; | |
225 | } | |
226 | ||
227 | static int | |
38b0e6c1 | 228 | parse_ct(const struct action_util *a, int *argc_p, char ***argv_p, int tca_id, |
c8a49431 PB |
229 | struct nlmsghdr *n) |
230 | { | |
231 | struct tc_ct sel = {}; | |
232 | char **argv = *argv_p; | |
233 | struct rtattr *tail; | |
234 | int argc = *argc_p; | |
235 | int ct_action = 0; | |
236 | int ret; | |
237 | ||
238 | tail = addattr_nest(n, MAX_MSG, tca_id); | |
239 | ||
240 | if (argc && matches(*argv, "ct") == 0) | |
241 | NEXT_ARG_FWD(); | |
242 | ||
243 | while (argc > 0) { | |
244 | if (matches(*argv, "zone") == 0) { | |
245 | NEXT_ARG(); | |
246 | ||
247 | if (ct_parse_u16(*argv, | |
248 | TCA_CT_ZONE, TCA_CT_UNSPEC, n)) { | |
249 | fprintf(stderr, "ct: Illegal \"zone\"\n"); | |
250 | return -1; | |
251 | } | |
252 | } else if (matches(*argv, "nat") == 0) { | |
253 | ct_action |= TCA_CT_ACT_NAT; | |
254 | ||
255 | NEXT_ARG(); | |
256 | if (matches(*argv, "src") == 0) | |
257 | ct_action |= TCA_CT_ACT_NAT_SRC; | |
258 | else if (matches(*argv, "dst") == 0) | |
259 | ct_action |= TCA_CT_ACT_NAT_DST; | |
260 | else | |
261 | continue; | |
262 | ||
263 | NEXT_ARG(); | |
264 | if (matches(*argv, "addr") != 0) | |
265 | usage(); | |
266 | ||
267 | NEXT_ARG(); | |
268 | ret = ct_parse_nat_addr_range(*argv, n); | |
269 | if (ret) { | |
270 | fprintf(stderr, "ct: Illegal nat address range\n"); | |
271 | return -1; | |
272 | } | |
273 | ||
4de59102 | 274 | NEXT_ARG(); |
c8a49431 PB |
275 | if (matches(*argv, "port") != 0) |
276 | continue; | |
277 | ||
278 | NEXT_ARG(); | |
279 | ret = ct_parse_nat_port_range(*argv, n); | |
280 | if (ret) { | |
281 | fprintf(stderr, "ct: Illegal nat port range\n"); | |
282 | return -1; | |
283 | } | |
284 | } else if (matches(*argv, "clear") == 0) { | |
285 | ct_action |= TCA_CT_ACT_CLEAR; | |
286 | } else if (matches(*argv, "commit") == 0) { | |
287 | ct_action |= TCA_CT_ACT_COMMIT; | |
288 | } else if (matches(*argv, "force") == 0) { | |
289 | ct_action |= TCA_CT_ACT_FORCE; | |
290 | } else if (matches(*argv, "index") == 0) { | |
291 | NEXT_ARG(); | |
292 | if (get_u32(&sel.index, *argv, 10)) { | |
293 | fprintf(stderr, "ct: Illegal \"index\"\n"); | |
294 | return -1; | |
295 | } | |
296 | } else if (matches(*argv, "mark") == 0) { | |
297 | NEXT_ARG(); | |
298 | ||
299 | ret = ct_parse_mark(*argv, n); | |
300 | if (ret) { | |
301 | fprintf(stderr, "ct: Illegal \"mark\"\n"); | |
302 | return -1; | |
303 | } | |
304 | } else if (matches(*argv, "label") == 0) { | |
305 | NEXT_ARG(); | |
306 | ||
307 | ret = ct_parse_labels(*argv, n); | |
308 | if (ret) { | |
309 | fprintf(stderr, "ct: Illegal \"label\"\n"); | |
310 | return -1; | |
311 | } | |
312 | } else if (matches(*argv, "help") == 0) { | |
313 | usage(); | |
4cdce041 XL |
314 | } else if (matches(*argv, "helper") == 0) { |
315 | NEXT_ARG(); | |
316 | ||
317 | ret = ct_parse_helper(*argv, n); | |
318 | if (ret) { | |
319 | fprintf(stderr, "ct: Illegal \"helper\"\n"); | |
320 | return -1; | |
321 | } | |
c8a49431 PB |
322 | } else { |
323 | break; | |
324 | } | |
325 | NEXT_ARG_FWD(); | |
326 | } | |
327 | ||
328 | if (ct_action & TCA_CT_ACT_CLEAR && | |
329 | ct_action & ~TCA_CT_ACT_CLEAR) { | |
330 | fprintf(stderr, "ct: clear can only be used alone\n"); | |
331 | return -1; | |
332 | } | |
333 | ||
334 | if (ct_action & TCA_CT_ACT_NAT_SRC && | |
335 | ct_action & TCA_CT_ACT_NAT_DST) { | |
336 | fprintf(stderr, "ct: src and dst nat can't be used together\n"); | |
337 | return -1; | |
338 | } | |
339 | ||
340 | if ((ct_action & TCA_CT_ACT_COMMIT) && | |
341 | (ct_action & TCA_CT_ACT_NAT) && | |
342 | !(ct_action & (TCA_CT_ACT_NAT_SRC | TCA_CT_ACT_NAT_DST))) { | |
343 | fprintf(stderr, "ct: commit and nat must set src or dst\n"); | |
344 | return -1; | |
345 | } | |
346 | ||
347 | if (!(ct_action & TCA_CT_ACT_COMMIT) && | |
348 | (ct_action & (TCA_CT_ACT_NAT_SRC | TCA_CT_ACT_NAT_DST))) { | |
349 | fprintf(stderr, "ct: src or dst is only valid if commit is set\n"); | |
350 | return -1; | |
351 | } | |
352 | ||
353 | parse_action_control_dflt(&argc, &argv, &sel.action, false, | |
354 | TC_ACT_PIPE); | |
c8a49431 PB |
355 | |
356 | addattr16(n, MAX_MSG, TCA_CT_ACTION, ct_action); | |
357 | addattr_l(n, MAX_MSG, TCA_CT_PARMS, &sel, sizeof(sel)); | |
358 | addattr_nest_end(n, tail); | |
359 | ||
360 | *argc_p = argc; | |
361 | *argv_p = argv; | |
362 | return 0; | |
363 | } | |
364 | ||
365 | static int ct_sprint_port(char *buf, const char *prefix, struct rtattr *attr) | |
366 | { | |
367 | if (!attr) | |
368 | return 0; | |
369 | ||
370 | return sprintf(buf, "%s%d", prefix, rta_getattr_be16(attr)); | |
371 | } | |
372 | ||
373 | static int ct_sprint_ip_addr(char *buf, const char *prefix, | |
374 | struct rtattr *attr) | |
375 | { | |
376 | int family; | |
377 | size_t len; | |
378 | ||
379 | if (!attr) | |
380 | return 0; | |
381 | ||
382 | len = RTA_PAYLOAD(attr); | |
383 | ||
384 | if (len == 4) | |
385 | family = AF_INET; | |
386 | else if (len == 16) | |
387 | family = AF_INET6; | |
388 | else | |
389 | return 0; | |
390 | ||
391 | return sprintf(buf, "%s%s", prefix, rt_addr_n2a_rta(family, attr)); | |
392 | } | |
393 | ||
394 | static void ct_print_nat(int ct_action, struct rtattr **tb) | |
395 | { | |
396 | size_t done = 0; | |
397 | char out[256] = ""; | |
cad1b0bc | 398 | bool nat = false; |
c8a49431 PB |
399 | |
400 | if (!(ct_action & TCA_CT_ACT_NAT)) | |
401 | return; | |
402 | ||
403 | if (ct_action & TCA_CT_ACT_NAT_SRC) { | |
404 | nat = true; | |
405 | done += sprintf(out + done, "src"); | |
406 | } else if (ct_action & TCA_CT_ACT_NAT_DST) { | |
407 | nat = true; | |
408 | done += sprintf(out + done, "dst"); | |
409 | } | |
410 | ||
411 | if (nat) { | |
412 | done += ct_sprint_ip_addr(out + done, " addr ", | |
413 | tb[TCA_CT_NAT_IPV4_MIN]); | |
414 | done += ct_sprint_ip_addr(out + done, " addr ", | |
415 | tb[TCA_CT_NAT_IPV6_MIN]); | |
416 | if (tb[TCA_CT_NAT_IPV4_MAX] && | |
417 | memcmp(RTA_DATA(tb[TCA_CT_NAT_IPV4_MIN]), | |
418 | RTA_DATA(tb[TCA_CT_NAT_IPV4_MAX]), 4)) | |
419 | done += ct_sprint_ip_addr(out + done, "-", | |
420 | tb[TCA_CT_NAT_IPV4_MAX]); | |
421 | else if (tb[TCA_CT_NAT_IPV6_MAX] && | |
422 | memcmp(RTA_DATA(tb[TCA_CT_NAT_IPV6_MIN]), | |
423 | RTA_DATA(tb[TCA_CT_NAT_IPV6_MAX]), 16)) | |
424 | done += ct_sprint_ip_addr(out + done, "-", | |
425 | tb[TCA_CT_NAT_IPV6_MAX]); | |
426 | done += ct_sprint_port(out + done, " port ", | |
427 | tb[TCA_CT_NAT_PORT_MIN]); | |
428 | if (tb[TCA_CT_NAT_PORT_MAX] && | |
429 | memcmp(RTA_DATA(tb[TCA_CT_NAT_PORT_MIN]), | |
430 | RTA_DATA(tb[TCA_CT_NAT_PORT_MAX]), 2)) | |
431 | done += ct_sprint_port(out + done, "-", | |
432 | tb[TCA_CT_NAT_PORT_MAX]); | |
433 | } | |
434 | ||
435 | if (done) | |
436 | print_string(PRINT_ANY, "nat", " nat %s", out); | |
437 | else | |
438 | print_string(PRINT_ANY, "nat", " nat", ""); | |
439 | } | |
440 | ||
441 | static void ct_print_labels(struct rtattr *attr, | |
442 | struct rtattr *mask_attr) | |
443 | { | |
444 | const unsigned char *str; | |
445 | bool print_mask = false; | |
446 | char out[256], *p; | |
447 | int data_len, i; | |
448 | ||
449 | if (!attr) | |
450 | return; | |
451 | ||
452 | data_len = RTA_PAYLOAD(attr); | |
453 | hexstring_n2a(RTA_DATA(attr), data_len, out, sizeof(out)); | |
454 | p = out + data_len*2; | |
455 | ||
456 | data_len = RTA_PAYLOAD(attr); | |
457 | str = RTA_DATA(mask_attr); | |
458 | if (data_len != 16) | |
459 | print_mask = true; | |
460 | for (i = 0; !print_mask && i < data_len; i++) { | |
461 | if (str[i] != 0xff) | |
462 | print_mask = true; | |
463 | } | |
464 | if (print_mask) { | |
465 | *p++ = '/'; | |
466 | hexstring_n2a(RTA_DATA(mask_attr), data_len, p, | |
467 | sizeof(out)-(p-out)); | |
468 | p += data_len*2; | |
469 | } | |
470 | *p = '\0'; | |
471 | ||
472 | print_string(PRINT_ANY, "label", " label %s", out); | |
473 | } | |
474 | ||
4cdce041 XL |
475 | static void ct_print_helper(struct rtattr *family, struct rtattr *proto, struct rtattr *name) |
476 | { | |
477 | char helper[32], buf[32], *n; | |
478 | int *f, *p; | |
479 | ||
480 | if (!family || !proto || !name) | |
481 | return; | |
482 | ||
483 | f = RTA_DATA(family); | |
484 | p = RTA_DATA(proto); | |
485 | n = RTA_DATA(name); | |
486 | snprintf(helper, sizeof(helper), "%s-%s-%s", (*f == AF_INET) ? "ipv4" : "ipv6", | |
487 | inet_proto_n2a(*p, buf, sizeof(buf)), n); | |
488 | print_string(PRINT_ANY, "helper", " helper %s", helper); | |
489 | } | |
490 | ||
38b0e6c1 | 491 | static int print_ct(const struct action_util *au, FILE *f, struct rtattr *arg) |
c8a49431 PB |
492 | { |
493 | struct rtattr *tb[TCA_CT_MAX + 1]; | |
494 | const char *commit; | |
495 | struct tc_ct *p; | |
496 | int ct_action = 0; | |
497 | ||
a99ebeee | 498 | print_string(PRINT_ANY, "kind", "%s", "ct"); |
c8a49431 | 499 | if (arg == NULL) |
a99ebeee | 500 | return 0; |
c8a49431 PB |
501 | |
502 | parse_rtattr_nested(tb, TCA_CT_MAX, arg); | |
503 | if (tb[TCA_CT_PARMS] == NULL) { | |
504 | print_string(PRINT_FP, NULL, "%s", "[NULL ct parameters]"); | |
505 | return -1; | |
506 | } | |
507 | ||
508 | p = RTA_DATA(tb[TCA_CT_PARMS]); | |
509 | ||
c8a49431 PB |
510 | if (tb[TCA_CT_ACTION]) |
511 | ct_action = rta_getattr_u16(tb[TCA_CT_ACTION]); | |
512 | if (ct_action & TCA_CT_ACT_COMMIT) { | |
513 | commit = ct_action & TCA_CT_ACT_FORCE ? | |
514 | "commit force" : "commit"; | |
515 | print_string(PRINT_ANY, "action", " %s", commit); | |
516 | } else if (ct_action & TCA_CT_ACT_CLEAR) { | |
517 | print_string(PRINT_ANY, "action", " %s", "clear"); | |
518 | } | |
519 | ||
746e6c0f EB |
520 | print_masked_u32("mark", tb[TCA_CT_MARK], tb[TCA_CT_MARK_MASK], false); |
521 | print_masked_u16("zone", tb[TCA_CT_ZONE], NULL, false); | |
c8a49431 | 522 | ct_print_labels(tb[TCA_CT_LABELS], tb[TCA_CT_LABELS_MASK]); |
4cdce041 | 523 | ct_print_helper(tb[TCA_CT_HELPER_FAMILY], tb[TCA_CT_HELPER_PROTO], tb[TCA_CT_HELPER_NAME]); |
c8a49431 PB |
524 | ct_print_nat(ct_action, tb); |
525 | ||
526 | print_action_control(f, " ", p->action, ""); | |
527 | ||
7b0d424a SH |
528 | print_nl(); |
529 | print_uint(PRINT_ANY, "index", "\t index %u", p->index); | |
c8a49431 PB |
530 | print_int(PRINT_ANY, "ref", " ref %d", p->refcnt); |
531 | print_int(PRINT_ANY, "bind", " bind %d", p->bindcnt); | |
532 | ||
533 | if (show_stats) { | |
534 | if (tb[TCA_CT_TM]) { | |
535 | struct tcf_t *tm = RTA_DATA(tb[TCA_CT_TM]); | |
536 | ||
537 | print_tm(f, tm); | |
538 | } | |
539 | } | |
7b0d424a | 540 | print_nl(); |
c8a49431 PB |
541 | |
542 | return 0; | |
543 | } | |
544 | ||
545 | struct action_util ct_action_util = { | |
546 | .id = "ct", | |
547 | .parse_aopt = parse_ct, | |
548 | .print_aopt = print_ct, | |
549 | }; |