]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/libsystemd-network/test-ndisc-send.c
Merge pull request #32266 from yuwata/libsystemd-network-trivial-cleanups
[thirdparty/systemd.git] / src / libsystemd-network / test-ndisc-send.c
CommitLineData
447fe37e
YW
1/* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3#include <getopt.h>
4
5#include "build.h"
6#include "ether-addr-util.h"
7#include "fd-util.h"
8#include "hexdecoct.h"
9#include "icmp6-util.h"
10#include "in-addr-util.h"
11#include "main-func.h"
12#include "ndisc-option.h"
13#include "netlink-util.h"
14#include "network-common.h"
15#include "parse-util.h"
16#include "socket-util.h"
17#include "strv.h"
18#include "time-util.h"
19
20static int arg_ifindex = 0;
21static int arg_icmp6_type = 0;
22static union in_addr_union arg_dest = IN_ADDR_NULL;
23static uint8_t arg_hop_limit = 0;
24static uint8_t arg_ra_flags = 0;
25static uint8_t arg_preference = false;
26static usec_t arg_lifetime = 0;
27static usec_t arg_reachable = 0;
28static usec_t arg_retransmit = 0;
29static uint32_t arg_na_flags = 0;
30static union in_addr_union arg_target_address = IN_ADDR_NULL;
31static union in_addr_union arg_redirect_destination = IN_ADDR_NULL;
32static bool arg_set_source_mac = false;
33static struct ether_addr arg_source_mac = {};
34static bool arg_set_target_mac = false;
35static struct ether_addr arg_target_mac = {};
36static struct ip6_hdr *arg_redirected_header = NULL;
37static bool arg_set_mtu = false;
38static uint32_t arg_mtu = 0;
39
40STATIC_DESTRUCTOR_REGISTER(arg_redirected_header, freep);
41
42static int parse_icmp6_type(const char *str) {
43 if (STR_IN_SET(str, "router-solicit", "rs", "RS"))
44 return ND_ROUTER_SOLICIT;
45 if (STR_IN_SET(str, "router-advertisement", "ra", "RA"))
46 return ND_ROUTER_ADVERT;
47 if (STR_IN_SET(str, "neighbor-solicit", "ns", "NS"))
48 return ND_NEIGHBOR_SOLICIT;
49 if (STR_IN_SET(str, "neighbor-advertisement", "na", "NA"))
50 return ND_NEIGHBOR_ADVERT;
51 if (STR_IN_SET(str, "redirect", "rd", "RD"))
52 return ND_REDIRECT;
53 return -EINVAL;
54}
55
56static int parse_preference(const char *str) {
57 if (streq(str, "low"))
58 return SD_NDISC_PREFERENCE_LOW;
59 if (streq(str, "medium"))
60 return SD_NDISC_PREFERENCE_MEDIUM;
61 if (streq(str, "high"))
62 return SD_NDISC_PREFERENCE_HIGH;
63 if (streq(str, "reserved"))
64 return SD_NDISC_PREFERENCE_RESERVED;
65 return -EINVAL;
66}
67
68static int parse_argv(int argc, char *argv[]) {
69 enum {
70 ARG_VERSION = 0x100,
71 ARG_RA_HOP_LIMIT,
72 ARG_RA_MANAGED,
73 ARG_RA_OTHER,
74 ARG_RA_HOME_AGENT,
75 ARG_RA_PREFERENCE,
76 ARG_RA_LIFETIME,
77 ARG_RA_REACHABLE,
78 ARG_RA_RETRANSMIT,
79 ARG_NA_ROUTER,
80 ARG_NA_SOLICITED,
81 ARG_NA_OVERRIDE,
82 ARG_TARGET_ADDRESS,
83 ARG_REDIRECT_DESTINATION,
84 ARG_OPTION_SOURCE_LL,
85 ARG_OPTION_TARGET_LL,
86 ARG_OPTION_REDIRECTED_HEADER,
87 ARG_OPTION_MTU,
88 };
89
90 static const struct option options[] = {
91 { "version", no_argument, NULL, ARG_VERSION },
92 { "interface", required_argument, NULL, 'i' },
93 { "type", required_argument, NULL, 't' },
94 { "dest", required_argument, NULL, 'd' },
95 /* For Router Advertisement */
96 { "hop-limit", required_argument, NULL, ARG_RA_HOP_LIMIT },
97 { "managed", required_argument, NULL, ARG_RA_MANAGED },
98 { "other", required_argument, NULL, ARG_RA_OTHER },
99 { "home-agent", required_argument, NULL, ARG_RA_HOME_AGENT },
100 { "preference", required_argument, NULL, ARG_RA_PREFERENCE },
101 { "lifetime", required_argument, NULL, ARG_RA_LIFETIME },
102 { "reachable-time", required_argument, NULL, ARG_RA_REACHABLE },
103 { "retransmit-timer", required_argument, NULL, ARG_RA_RETRANSMIT },
104 /* For Neighbor Advertisement */
105 { "is-router", required_argument, NULL, ARG_NA_ROUTER },
106 { "is-solicited", required_argument, NULL, ARG_NA_SOLICITED },
107 { "is-override", required_argument, NULL, ARG_NA_OVERRIDE },
108 /* For Neighbor Solicit, Neighbor Advertisement, and Redirect */
109 { "target-address", required_argument, NULL, ARG_TARGET_ADDRESS },
110 /* For Redirect */
111 { "redirect-destination", required_argument, NULL, ARG_REDIRECT_DESTINATION },
112 /* Options */
113 { "source-ll-address", required_argument, NULL, ARG_OPTION_SOURCE_LL },
114 { "target-ll-address", required_argument, NULL, ARG_OPTION_TARGET_LL },
115 { "redirected-header", required_argument, NULL, ARG_OPTION_REDIRECTED_HEADER },
116 { "mtu", required_argument, NULL, ARG_OPTION_MTU },
117 {}
118 };
119
120 _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
121 int c, r;
122
123 assert(argc >= 0);
124 assert(argv);
125
126 while ((c = getopt_long(argc, argv, "i:t:d:", options, NULL)) >= 0) {
127
128 switch (c) {
129
130 case ARG_VERSION:
131 return version();
132
133 case 'i':
134 r = rtnl_resolve_interface_or_warn(&rtnl, optarg);
135 if (r < 0)
136 return r;
137 arg_ifindex = r;
138 break;
139
140 case 't':
141 r = parse_icmp6_type(optarg);
142 if (r < 0)
143 return log_error_errno(r, "Failed to parse message type: %m");
144 arg_icmp6_type = r;
145 break;
146
147 case 'd':
148 r = in_addr_from_string(AF_INET6, optarg, &arg_dest);
149 if (r < 0)
150 return log_error_errno(r, "Failed to parse destination address: %m");
151 if (!in6_addr_is_link_local(&arg_dest.in6))
152 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
153 "The destination address %s is not a link-local address.", optarg);
154 break;
155
156 case ARG_RA_HOP_LIMIT:
157 r = safe_atou8(optarg, &arg_hop_limit);
158 if (r < 0)
159 return log_error_errno(r, "Failed to parse hop limit: %m");
160 break;
161
162 case ARG_RA_MANAGED:
163 r = parse_boolean(optarg);
164 if (r < 0)
165 return log_error_errno(r, "Failed to parse managed flag: %m");
166 SET_FLAG(arg_ra_flags, ND_RA_FLAG_MANAGED, r);
167 break;
168
169 case ARG_RA_OTHER:
170 r = parse_boolean(optarg);
171 if (r < 0)
172 return log_error_errno(r, "Failed to parse other flag: %m");
173 SET_FLAG(arg_ra_flags, ND_RA_FLAG_OTHER, r);
174 break;
175
176 case ARG_RA_HOME_AGENT:
177 r = parse_boolean(optarg);
178 if (r < 0)
179 return log_error_errno(r, "Failed to parse home-agent flag: %m");
180 SET_FLAG(arg_ra_flags, ND_RA_FLAG_HOME_AGENT, r);
181 break;
182
183 case ARG_RA_PREFERENCE:
184 r = parse_preference(optarg);
185 if (r < 0)
186 return log_error_errno(r, "Failed to parse preference: %m");
187 arg_preference = r;
188 break;
189
190 case ARG_RA_LIFETIME:
191 r = parse_sec(optarg, &arg_lifetime);
192 if (r < 0)
193 return log_error_errno(r, "Failed to parse lifetime: %m");
194 break;
195
196 case ARG_RA_REACHABLE:
197 r = parse_sec(optarg, &arg_reachable);
198 if (r < 0)
199 return log_error_errno(r, "Failed to parse reachable time: %m");
200 break;
201
202 case ARG_RA_RETRANSMIT:
203 r = parse_sec(optarg, &arg_retransmit);
204 if (r < 0)
205 return log_error_errno(r, "Failed to parse retransmit timer: %m");
206 break;
207
208 case ARG_NA_ROUTER:
209 r = parse_boolean(optarg);
210 if (r < 0)
211 return log_error_errno(r, "Failed to parse is-router flag: %m");
212 SET_FLAG(arg_na_flags, ND_NA_FLAG_ROUTER, r);
213 break;
214
215 case ARG_NA_SOLICITED:
216 r = parse_boolean(optarg);
217 if (r < 0)
218 return log_error_errno(r, "Failed to parse is-solicited flag: %m");
219 SET_FLAG(arg_na_flags, ND_NA_FLAG_SOLICITED, r);
220 break;
221
222 case ARG_NA_OVERRIDE:
223 r = parse_boolean(optarg);
224 if (r < 0)
225 return log_error_errno(r, "Failed to parse is-override flag: %m");
226 SET_FLAG(arg_na_flags, ND_NA_FLAG_OVERRIDE, r);
227 break;
228
229 case ARG_TARGET_ADDRESS:
230 r = in_addr_from_string(AF_INET6, optarg, &arg_target_address);
231 if (r < 0)
232 return log_error_errno(r, "Failed to parse target address: %m");
233 break;
234
235 case ARG_REDIRECT_DESTINATION:
236 r = in_addr_from_string(AF_INET6, optarg, &arg_redirect_destination);
237 if (r < 0)
238 return log_error_errno(r, "Failed to parse destination address: %m");
239 break;
240
241 case ARG_OPTION_SOURCE_LL:
242 r = parse_boolean(optarg);
243 if (r < 0)
244 return log_error_errno(r, "Failed to parse source LL address option: %m");
245 arg_set_source_mac = r;
246 break;
247
248 case ARG_OPTION_TARGET_LL:
249 r = parse_ether_addr(optarg, &arg_target_mac);
250 if (r < 0)
251 return log_error_errno(r, "Failed to parse target LL address option: %m");
252 arg_set_target_mac = true;
253 break;
254
255 case ARG_OPTION_REDIRECTED_HEADER: {
256 _cleanup_free_ void *p = NULL;
257 size_t len;
258
259 r = unbase64mem(optarg, &p, &len);
260 if (r < 0)
261 return log_error_errno(r, "Failed to parse redirected header: %m");
262
263 if (len < sizeof(struct ip6_hdr))
264 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid redirected header.");
265
266 arg_redirected_header = TAKE_PTR(p);
267 break;
268 }
269 case ARG_OPTION_MTU:
270 r = safe_atou32(optarg, &arg_mtu);
271 if (r < 0)
272 return log_error_errno(r, "Failed to parse MTU: %m");
273 arg_set_mtu = true;
274 break;
275
276 case '?':
277 return -EINVAL;
278
279 default:
280 assert_not_reached();
281 }
282 }
283
284 if (arg_ifindex <= 0)
285 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--interface/-i option is mandatory.");
286
287 if (arg_icmp6_type <= 0)
288 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--type/-t option is mandatory.");
289
290 if (in6_addr_is_null(&arg_dest.in6)) {
291 if (IN_SET(arg_icmp6_type, ND_ROUTER_ADVERT, ND_NEIGHBOR_ADVERT, ND_REDIRECT))
2c28eb02 292 arg_dest.in6 = IN6_ADDR_ALL_NODES_MULTICAST;
447fe37e 293 else
2c28eb02 294 arg_dest.in6 = IN6_ADDR_ALL_ROUTERS_MULTICAST;
447fe37e
YW
295 }
296
297 if (arg_set_source_mac) {
298 struct hw_addr_data hw_addr;
299
300 r = rtnl_get_link_info(&rtnl, arg_ifindex,
301 /* ret_iftype = */ NULL,
302 /* ret_flags = */ NULL,
303 /* ret_kind = */ NULL,
304 &hw_addr,
305 /* ret_permanent_hw_addr = */ NULL);
306 if (r < 0)
307 return log_error_errno(r, "Failed to get the source link-layer address: %m");
308
309 if (hw_addr.length != sizeof(struct ether_addr))
310 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
311 "Unsupported hardware address length %zu: %m",
312 hw_addr.length);
313
314 arg_source_mac = hw_addr.ether;
315 }
316
317 return 1;
318}
319
320static int send_icmp6(int fd, const struct icmp6_hdr *hdr) {
321 _cleanup_set_free_ Set *options = NULL;
322 int r;
323
324 assert(fd >= 0);
325 assert(hdr);
326
327 if (arg_set_source_mac) {
328 r = ndisc_option_add_link_layer_address(&options, 0, SD_NDISC_OPTION_SOURCE_LL_ADDRESS, &arg_source_mac);
329 if (r < 0)
330 return r;
331 }
332
333 if (arg_set_target_mac) {
334 r = ndisc_option_add_link_layer_address(&options, 0, SD_NDISC_OPTION_TARGET_LL_ADDRESS, &arg_target_mac);
335 if (r < 0)
336 return r;
337 }
338
339 if (arg_redirected_header) {
340 r = ndisc_option_add_redirected_header(&options, 0, arg_redirected_header);
341 if (r < 0)
342 return r;
343 }
344
345 if (arg_set_mtu) {
346 r = ndisc_option_add_mtu(&options, 0, arg_mtu);
347 if (r < 0)
348 return r;
349 }
350
ac336e75 351 return ndisc_send(fd, &arg_dest.in6, hdr, options, now(CLOCK_BOOTTIME));
447fe37e
YW
352}
353
354static int send_router_solicit(int fd) {
355 struct nd_router_solicit hdr = {
356 .nd_rs_type = ND_ROUTER_SOLICIT,
357 };
358
359 assert(fd >= 0);
360
361 return send_icmp6(fd, &hdr.nd_rs_hdr);
362}
363
364static int send_router_advertisement(int fd) {
365 struct nd_router_advert hdr = {
366 .nd_ra_type = ND_ROUTER_ADVERT,
367 .nd_ra_router_lifetime = usec_to_be16_sec(arg_lifetime),
368 .nd_ra_reachable = usec_to_be32_msec(arg_reachable),
369 .nd_ra_retransmit = usec_to_be32_msec(arg_retransmit),
370 };
371
372 assert(fd >= 0);
373
374 /* The nd_ra_curhoplimit and nd_ra_flags_reserved fields cannot specified with nd_ra_router_lifetime
375 * simultaneously in the structured initializer in the above. */
376 hdr.nd_ra_curhoplimit = arg_hop_limit;
377 hdr.nd_ra_flags_reserved = arg_ra_flags;
378
379 return send_icmp6(fd, &hdr.nd_ra_hdr);
380}
381
382static int send_neighbor_solicit(int fd) {
383 struct nd_neighbor_solicit hdr = {
384 .nd_ns_type = ND_NEIGHBOR_SOLICIT,
385 .nd_ns_target = arg_target_address.in6,
386 };
387
388 assert(fd >= 0);
389
390 return send_icmp6(fd, &hdr.nd_ns_hdr);
391}
392
393static int send_neighbor_advertisement(int fd) {
394 struct nd_neighbor_advert hdr = {
395 .nd_na_type = ND_NEIGHBOR_ADVERT,
396 .nd_na_flags_reserved = arg_na_flags,
397 .nd_na_target = arg_target_address.in6,
398 };
399
400 assert(fd >= 0);
401
402 return send_icmp6(fd, &hdr.nd_na_hdr);
403}
404
405static int send_redirect(int fd) {
406 struct nd_redirect hdr = {
407 .nd_rd_type = ND_REDIRECT,
408 .nd_rd_target = arg_target_address.in6,
409 .nd_rd_dst = arg_redirect_destination.in6,
410 };
411
412 assert(fd >= 0);
413
414 return send_icmp6(fd, &hdr.nd_rd_hdr);
415}
416
417static int run(int argc, char *argv[]) {
418 _cleanup_close_ int fd = -EBADF;
419 int r;
420
421 log_setup();
422
423 r = parse_argv(argc, argv);
424 if (r <= 0)
425 return r;
426
427 fd = icmp6_bind(arg_ifindex, /* is_router = */ false);
428 if (fd < 0)
429 return log_error_errno(fd, "Failed to bind socket to interface: %m");
430
431 switch (arg_icmp6_type) {
432 case ND_ROUTER_SOLICIT:
433 return send_router_solicit(fd);
434 case ND_ROUTER_ADVERT:
435 return send_router_advertisement(fd);
436 case ND_NEIGHBOR_SOLICIT:
437 return send_neighbor_solicit(fd);
438 case ND_NEIGHBOR_ADVERT:
439 return send_neighbor_advertisement(fd);
440 case ND_REDIRECT:
441 return send_redirect(fd);
442 default:
443 assert_not_reached();
444 }
445
446 return 0;
447}
448
449DEFINE_MAIN_FUNCTION(run);