]>
Commit | Line | Data |
---|---|---|
7e0767cd PA |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | ||
3 | #include <stdio.h> | |
4 | #include <string.h> | |
5 | #include <rt_names.h> | |
6 | #include <errno.h> | |
7 | ||
8 | #include <linux/genetlink.h> | |
9 | #include <linux/mptcp.h> | |
10 | ||
11 | #include "utils.h" | |
12 | #include "ip_common.h" | |
13 | #include "libgenl.h" | |
14 | #include "json_print.h" | |
15 | ||
16 | static void usage(void) | |
17 | { | |
18 | fprintf(stderr, | |
19 | "Usage: ip mptcp endpoint add ADDRESS [ dev NAME ] [ id ID ]\n" | |
20 | " [ FLAG-LIST ]\n" | |
21 | " ip mptcp endpoint delete id ID\n" | |
22 | " ip mptcp endpoint show [ id ID ]\n" | |
23 | " ip mptcp endpoint flush\n" | |
24 | " ip mptcp limits set [ subflows NR ] [ add_addr_accepted NR ]\n" | |
25 | " ip mptcp limits show\n" | |
26 | "FLAG-LIST := [ FLAG-LIST ] FLAG\n" | |
27 | "FLAG := [ signal | subflow | backup ]\n"); | |
28 | ||
29 | exit(-1); | |
30 | } | |
31 | ||
32 | /* netlink socket */ | |
33 | static struct rtnl_handle genl_rth = { .fd = -1 }; | |
34 | static int genl_family = -1; | |
35 | ||
36 | #define MPTCP_BUFLEN 4096 | |
37 | #define MPTCP_REQUEST(_req, _cmd, _flags) \ | |
38 | GENL_REQUEST(_req, MPTCP_BUFLEN, genl_family, 0, \ | |
39 | MPTCP_PM_VER, _cmd, _flags) | |
40 | ||
41 | /* Mapping from argument to address flag mask */ | |
42 | static const struct { | |
43 | const char *name; | |
44 | unsigned long value; | |
45 | } mptcp_addr_flag_names[] = { | |
46 | { "signal", MPTCP_PM_ADDR_FLAG_SIGNAL }, | |
47 | { "subflow", MPTCP_PM_ADDR_FLAG_SUBFLOW }, | |
48 | { "backup", MPTCP_PM_ADDR_FLAG_BACKUP }, | |
49 | }; | |
50 | ||
51 | static void print_mptcp_addr_flags(unsigned int flags) | |
52 | { | |
53 | unsigned int i; | |
54 | ||
55 | for (i = 0; i < ARRAY_SIZE(mptcp_addr_flag_names); i++) { | |
56 | unsigned long mask = mptcp_addr_flag_names[i].value; | |
57 | ||
58 | if (flags & mask) { | |
59 | print_string(PRINT_FP, NULL, "%s ", | |
60 | mptcp_addr_flag_names[i].name); | |
61 | print_bool(PRINT_JSON, | |
62 | mptcp_addr_flag_names[i].name, NULL, true); | |
63 | } | |
64 | ||
65 | flags &= ~mask; | |
66 | } | |
67 | ||
68 | if (flags) { | |
69 | /* unknown flags */ | |
70 | SPRINT_BUF(b1); | |
71 | ||
72 | snprintf(b1, sizeof(b1), "%02x", flags); | |
73 | print_string(PRINT_ANY, "rawflags", "rawflags %s ", b1); | |
74 | } | |
75 | } | |
76 | ||
77 | static int get_flags(const char *arg, __u32 *flags) | |
78 | { | |
79 | unsigned int i; | |
80 | ||
81 | for (i = 0; i < ARRAY_SIZE(mptcp_addr_flag_names); i++) { | |
82 | if (strcmp(arg, mptcp_addr_flag_names[i].name)) | |
83 | continue; | |
84 | ||
85 | *flags |= mptcp_addr_flag_names[i].value; | |
86 | return 0; | |
87 | } | |
88 | return -1; | |
89 | } | |
90 | ||
91 | static int mptcp_parse_opt(int argc, char **argv, struct nlmsghdr *n, | |
92 | bool adding) | |
93 | { | |
94 | struct rtattr *attr_addr; | |
95 | bool addr_set = false; | |
96 | inet_prefix address; | |
97 | bool id_set = false; | |
98 | __u32 index = 0; | |
99 | __u32 flags = 0; | |
100 | __u8 id = 0; | |
101 | ||
102 | ll_init_map(&rth); | |
103 | while (argc > 0) { | |
104 | if (get_flags(*argv, &flags) == 0) { | |
105 | } else if (matches(*argv, "id") == 0) { | |
106 | NEXT_ARG(); | |
107 | ||
108 | if (get_u8(&id, *argv, 0)) | |
109 | invarg("invalid ID\n", *argv); | |
110 | id_set = true; | |
111 | } else if (matches(*argv, "dev") == 0) { | |
112 | const char *ifname; | |
113 | ||
114 | NEXT_ARG(); | |
115 | ||
116 | ifname = *argv; | |
117 | ||
118 | if (check_ifname(ifname)) | |
119 | invarg("invalid interface name\n", ifname); | |
120 | ||
121 | index = ll_name_to_index(ifname); | |
122 | ||
123 | if (!index) | |
124 | invarg("device does not exist\n", ifname); | |
125 | ||
126 | } else if (get_addr(&address, *argv, AF_UNSPEC) == 0) { | |
127 | addr_set = true; | |
128 | } else { | |
129 | invarg("unknown argument", *argv); | |
130 | } | |
131 | NEXT_ARG_FWD(); | |
132 | } | |
133 | ||
134 | if (!addr_set && adding) | |
135 | missarg("ADDRESS"); | |
136 | ||
137 | if (!id_set && !adding) | |
138 | missarg("ID"); | |
139 | ||
140 | attr_addr = addattr_nest(n, MPTCP_BUFLEN, | |
141 | MPTCP_PM_ATTR_ADDR | NLA_F_NESTED); | |
142 | if (id_set) | |
143 | addattr8(n, MPTCP_BUFLEN, MPTCP_PM_ADDR_ATTR_ID, id); | |
144 | if (flags) | |
145 | addattr32(n, MPTCP_BUFLEN, MPTCP_PM_ADDR_ATTR_FLAGS, flags); | |
146 | if (index) | |
147 | addattr32(n, MPTCP_BUFLEN, MPTCP_PM_ADDR_ATTR_IF_IDX, index); | |
148 | if (addr_set) { | |
149 | int type; | |
150 | ||
151 | addattr16(n, MPTCP_BUFLEN, MPTCP_PM_ADDR_ATTR_FAMILY, | |
152 | address.family); | |
153 | type = address.family == AF_INET ? MPTCP_PM_ADDR_ATTR_ADDR4 : | |
154 | MPTCP_PM_ADDR_ATTR_ADDR6; | |
155 | addattr_l(n, MPTCP_BUFLEN, type, &address.data, | |
156 | address.bytelen); | |
157 | } | |
158 | ||
159 | addattr_nest_end(n, attr_addr); | |
160 | return 0; | |
161 | } | |
162 | ||
163 | static int mptcp_addr_modify(int argc, char **argv, int cmd) | |
164 | { | |
165 | MPTCP_REQUEST(req, cmd, NLM_F_REQUEST); | |
166 | int ret; | |
167 | ||
168 | ret = mptcp_parse_opt(argc, argv, &req.n, cmd == MPTCP_PM_CMD_ADD_ADDR); | |
169 | if (ret) | |
170 | return ret; | |
171 | ||
172 | if (rtnl_talk(&genl_rth, &req.n, NULL) < 0) | |
173 | return -2; | |
174 | ||
175 | return 0; | |
176 | } | |
177 | ||
178 | static int print_mptcp_addrinfo(struct rtattr *addrinfo) | |
179 | { | |
180 | struct rtattr *tb[MPTCP_PM_ADDR_ATTR_MAX + 1]; | |
181 | __u8 family = AF_UNSPEC, addr_attr_type; | |
182 | const char *ifname; | |
183 | unsigned int flags; | |
184 | int index; | |
185 | __u16 id; | |
186 | ||
187 | parse_rtattr_nested(tb, MPTCP_PM_ADDR_ATTR_MAX, addrinfo); | |
188 | ||
189 | open_json_object(NULL); | |
190 | if (tb[MPTCP_PM_ADDR_ATTR_FAMILY]) | |
191 | family = rta_getattr_u8(tb[MPTCP_PM_ADDR_ATTR_FAMILY]); | |
192 | ||
193 | addr_attr_type = family == AF_INET ? MPTCP_PM_ADDR_ATTR_ADDR4 : | |
194 | MPTCP_PM_ADDR_ATTR_ADDR6; | |
195 | if (tb[addr_attr_type]) { | |
196 | print_string(PRINT_ANY, "address", "%s ", | |
197 | format_host_rta(family, tb[addr_attr_type])); | |
198 | } | |
199 | if (tb[MPTCP_PM_ADDR_ATTR_ID]) { | |
200 | id = rta_getattr_u8(tb[MPTCP_PM_ADDR_ATTR_ID]); | |
201 | print_uint(PRINT_ANY, "id", "id %u ", id); | |
202 | } | |
203 | if (tb[MPTCP_PM_ADDR_ATTR_FLAGS]) { | |
204 | flags = rta_getattr_u32(tb[MPTCP_PM_ADDR_ATTR_FLAGS]); | |
205 | print_mptcp_addr_flags(flags); | |
206 | } | |
207 | if (tb[MPTCP_PM_ADDR_ATTR_IF_IDX]) { | |
208 | index = rta_getattr_s32(tb[MPTCP_PM_ADDR_ATTR_IF_IDX]); | |
209 | ifname = index ? ll_index_to_name(index) : NULL; | |
210 | ||
211 | if (ifname) | |
212 | print_string(PRINT_ANY, "dev", "dev %s ", ifname); | |
213 | } | |
214 | ||
215 | close_json_object(); | |
216 | print_string(PRINT_FP, NULL, "\n", NULL); | |
217 | fflush(stdout); | |
218 | ||
219 | return 0; | |
220 | } | |
221 | ||
222 | static int print_mptcp_addr(struct nlmsghdr *n, void *arg) | |
223 | { | |
224 | struct rtattr *tb[MPTCP_PM_ATTR_MAX + 1]; | |
225 | struct genlmsghdr *ghdr; | |
226 | struct rtattr *addrinfo; | |
227 | int len = n->nlmsg_len; | |
228 | ||
229 | if (n->nlmsg_type != genl_family) | |
230 | return 0; | |
231 | ||
232 | len -= NLMSG_LENGTH(GENL_HDRLEN); | |
233 | if (len < 0) | |
234 | return -1; | |
235 | ||
236 | ghdr = NLMSG_DATA(n); | |
237 | parse_rtattr_flags(tb, MPTCP_PM_ATTR_MAX, (void *) ghdr + GENL_HDRLEN, | |
238 | len, NLA_F_NESTED); | |
239 | addrinfo = tb[MPTCP_PM_ATTR_ADDR]; | |
240 | if (!addrinfo) | |
241 | return -1; | |
242 | ||
243 | ll_init_map(&rth); | |
244 | return print_mptcp_addrinfo(addrinfo); | |
245 | } | |
246 | ||
247 | static int mptcp_addr_dump(void) | |
248 | { | |
249 | MPTCP_REQUEST(req, MPTCP_PM_CMD_GET_ADDR, NLM_F_REQUEST | NLM_F_DUMP); | |
250 | ||
251 | if (rtnl_send(&genl_rth, &req.n, req.n.nlmsg_len) < 0) { | |
252 | perror("Cannot send show request"); | |
253 | exit(1); | |
254 | } | |
255 | ||
256 | new_json_obj(json); | |
257 | ||
258 | if (rtnl_dump_filter(&genl_rth, print_mptcp_addr, stdout) < 0) { | |
259 | fprintf(stderr, "Dump terminated\n"); | |
260 | delete_json_obj(); | |
261 | fflush(stdout); | |
262 | return -2; | |
263 | } | |
264 | ||
265 | close_json_object(); | |
266 | fflush(stdout); | |
267 | return 0; | |
268 | } | |
269 | ||
270 | static int mptcp_addr_show(int argc, char **argv) | |
271 | { | |
272 | MPTCP_REQUEST(req, MPTCP_PM_CMD_GET_ADDR, NLM_F_REQUEST); | |
273 | struct nlmsghdr *answer; | |
274 | int ret; | |
275 | ||
3a53ff7e | 276 | if (argc <= 0) |
7e0767cd PA |
277 | return mptcp_addr_dump(); |
278 | ||
279 | ret = mptcp_parse_opt(argc, argv, &req.n, false); | |
280 | if (ret) | |
281 | return ret; | |
282 | ||
283 | if (rtnl_talk(&genl_rth, &req.n, &answer) < 0) | |
284 | return -2; | |
285 | ||
286 | return print_mptcp_addr(answer, stdout); | |
287 | } | |
288 | ||
289 | static int mptcp_addr_flush(int argc, char **argv) | |
290 | { | |
291 | MPTCP_REQUEST(req, MPTCP_PM_CMD_FLUSH_ADDRS, NLM_F_REQUEST); | |
292 | ||
293 | if (rtnl_talk(&genl_rth, &req.n, NULL) < 0) | |
294 | return -2; | |
295 | ||
296 | return 0; | |
297 | } | |
298 | ||
299 | static int mptcp_parse_limit(int argc, char **argv, struct nlmsghdr *n) | |
300 | { | |
301 | bool set_rcv_add_addrs = false; | |
302 | bool set_subflows = false; | |
303 | __u32 rcv_add_addrs = 0; | |
304 | __u32 subflows = 0; | |
305 | ||
306 | while (argc > 0) { | |
307 | if (matches(*argv, "subflows") == 0) { | |
308 | NEXT_ARG(); | |
309 | ||
310 | if (get_u32(&subflows, *argv, 0)) | |
311 | invarg("invalid subflows\n", *argv); | |
312 | set_subflows = true; | |
313 | } else if (matches(*argv, "add_addr_accepted") == 0) { | |
314 | NEXT_ARG(); | |
315 | ||
316 | if (get_u32(&rcv_add_addrs, *argv, 0)) | |
317 | invarg("invalid add_addr_accepted\n", *argv); | |
318 | set_rcv_add_addrs = true; | |
319 | } else { | |
320 | invarg("unknown limit", *argv); | |
321 | } | |
322 | NEXT_ARG_FWD(); | |
323 | } | |
324 | ||
325 | if (set_rcv_add_addrs) | |
326 | addattr32(n, MPTCP_BUFLEN, MPTCP_PM_ATTR_RCV_ADD_ADDRS, | |
327 | rcv_add_addrs); | |
328 | if (set_subflows) | |
329 | addattr32(n, MPTCP_BUFLEN, MPTCP_PM_ATTR_SUBFLOWS, subflows); | |
330 | return set_rcv_add_addrs || set_subflows; | |
331 | } | |
332 | ||
333 | static int print_mptcp_limit(struct nlmsghdr *n, void *arg) | |
334 | { | |
335 | struct rtattr *tb[MPTCP_PM_ATTR_MAX + 1]; | |
336 | struct genlmsghdr *ghdr; | |
337 | int len = n->nlmsg_len; | |
338 | __u32 val; | |
339 | ||
340 | if (n->nlmsg_type != genl_family) | |
341 | return 0; | |
342 | ||
343 | len -= NLMSG_LENGTH(GENL_HDRLEN); | |
344 | if (len < 0) | |
345 | return -1; | |
346 | ||
347 | ghdr = NLMSG_DATA(n); | |
348 | parse_rtattr(tb, MPTCP_PM_ATTR_MAX, (void *) ghdr + GENL_HDRLEN, len); | |
349 | ||
350 | open_json_object(NULL); | |
351 | if (tb[MPTCP_PM_ATTR_RCV_ADD_ADDRS]) { | |
352 | val = rta_getattr_u32(tb[MPTCP_PM_ATTR_RCV_ADD_ADDRS]); | |
353 | ||
354 | print_uint(PRINT_ANY, "add_addr_accepted", | |
355 | "add_addr_accepted %d ", val); | |
356 | } | |
357 | ||
358 | if (tb[MPTCP_PM_ATTR_SUBFLOWS]) { | |
359 | val = rta_getattr_u32(tb[MPTCP_PM_ATTR_SUBFLOWS]); | |
360 | ||
361 | print_uint(PRINT_ANY, "subflows", "subflows %d ", val); | |
362 | } | |
363 | print_string(PRINT_FP, NULL, "%s", "\n"); | |
364 | fflush(stdout); | |
365 | close_json_object(); | |
366 | return 0; | |
367 | } | |
368 | ||
369 | static int mptcp_limit_get_set(int argc, char **argv, int cmd) | |
370 | { | |
371 | bool do_get = cmd == MPTCP_PM_CMD_GET_LIMITS; | |
372 | MPTCP_REQUEST(req, cmd, NLM_F_REQUEST); | |
373 | struct nlmsghdr *answer; | |
374 | int ret; | |
375 | ||
376 | ret = mptcp_parse_limit(argc, argv, &req.n); | |
377 | if (ret < 0) | |
378 | return -1; | |
379 | ||
380 | if (rtnl_talk(&genl_rth, &req.n, do_get ? &answer : NULL) < 0) | |
381 | return -2; | |
382 | ||
383 | if (do_get) | |
384 | return print_mptcp_limit(answer, stdout); | |
385 | return 0; | |
386 | } | |
387 | ||
388 | int do_mptcp(int argc, char **argv) | |
389 | { | |
390 | if (argc == 0) | |
391 | usage(); | |
392 | ||
393 | if (matches(*argv, "help") == 0) | |
394 | usage(); | |
395 | ||
396 | if (genl_init_handle(&genl_rth, MPTCP_PM_NAME, &genl_family)) | |
397 | exit(1); | |
398 | ||
399 | if (matches(*argv, "endpoint") == 0) { | |
400 | NEXT_ARG_FWD(); | |
401 | if (argc == 0) | |
402 | return mptcp_addr_show(0, NULL); | |
403 | ||
404 | if (matches(*argv, "add") == 0) | |
405 | return mptcp_addr_modify(argc-1, argv+1, | |
406 | MPTCP_PM_CMD_ADD_ADDR); | |
407 | if (matches(*argv, "delete") == 0) | |
408 | return mptcp_addr_modify(argc-1, argv+1, | |
409 | MPTCP_PM_CMD_DEL_ADDR); | |
410 | if (matches(*argv, "show") == 0) | |
411 | return mptcp_addr_show(argc-1, argv+1); | |
412 | if (matches(*argv, "flush") == 0) | |
413 | return mptcp_addr_flush(argc-1, argv+1); | |
414 | ||
415 | goto unknown; | |
416 | } | |
417 | ||
418 | if (matches(*argv, "limits") == 0) { | |
419 | NEXT_ARG_FWD(); | |
420 | if (argc == 0) | |
421 | return mptcp_limit_get_set(0, NULL, | |
422 | MPTCP_PM_CMD_GET_LIMITS); | |
423 | ||
424 | if (matches(*argv, "set") == 0) | |
425 | return mptcp_limit_get_set(argc-1, argv+1, | |
426 | MPTCP_PM_CMD_SET_LIMITS); | |
427 | if (matches(*argv, "show") == 0) | |
428 | return mptcp_limit_get_set(argc-1, argv+1, | |
429 | MPTCP_PM_CMD_GET_LIMITS); | |
430 | } | |
431 | ||
432 | unknown: | |
433 | fprintf(stderr, "Command \"%s\" is unknown, try \"ip mptcp help\".\n", | |
434 | *argv); | |
435 | exit(-1); | |
436 | } |