]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
e3169126 | 2 | /*** |
810adae9 | 3 | Copyright © 2014 Intel Corporation. All rights reserved. |
e3169126 PF |
4 | ***/ |
5 | ||
6 | #include <netinet/icmp6.h> | |
07630cea | 7 | #include <netinet/in.h> |
e3169126 | 8 | |
07630cea LP |
9 | #include "sd-ndisc.h" |
10 | ||
b5efdb8a | 11 | #include "alloc-util.h" |
ff4b0321 | 12 | #include "event-util.h" |
1e7a0e21 | 13 | #include "fd-util.h" |
07630cea | 14 | #include "icmp6-util.h" |
9d96e6c3 | 15 | #include "in-addr-util.h" |
0a970718 | 16 | #include "memory-util.h" |
1e7a0e21 | 17 | #include "ndisc-internal.h" |
ca34b434 | 18 | #include "ndisc-router-internal.h" |
61a9fa8f | 19 | #include "network-common.h" |
1bd6f895 | 20 | #include "random-util.h" |
940367a0 | 21 | #include "socket-util.h" |
a2dcda32 | 22 | #include "string-table.h" |
d7fa4380 | 23 | #include "string-util.h" |
e3169126 | 24 | |
1bd6f895 | 25 | #define NDISC_TIMEOUT_NO_RA_USEC (NDISC_ROUTER_SOLICITATION_INTERVAL * NDISC_MAX_ROUTER_SOLICITATIONS) |
e3169126 | 26 | |
a2dcda32 YW |
27 | static const char * const ndisc_event_table[_SD_NDISC_EVENT_MAX] = { |
28 | [SD_NDISC_EVENT_TIMEOUT] = "timeout", | |
29 | [SD_NDISC_EVENT_ROUTER] = "router", | |
30 | }; | |
31 | ||
2324fd3a | 32 | DEFINE_STRING_TABLE_LOOKUP(ndisc_event, sd_ndisc_event_t); |
a2dcda32 | 33 | |
2324fd3a | 34 | static void ndisc_callback(sd_ndisc *ndisc, sd_ndisc_event_t event, sd_ndisc_router *rt) { |
1e7a0e21 | 35 | assert(ndisc); |
a2dcda32 | 36 | assert(event >= 0 && event < _SD_NDISC_EVENT_MAX); |
e3169126 | 37 | |
35388783 YW |
38 | if (!ndisc->callback) |
39 | return (void) log_ndisc(ndisc, "Received '%s' event.", ndisc_event_to_string(event)); | |
9d96e6c3 | 40 | |
35388783 | 41 | log_ndisc(ndisc, "Invoking callback for '%s' event.", ndisc_event_to_string(event)); |
1e7a0e21 | 42 | ndisc->callback(ndisc, event, rt, ndisc->userdata); |
5624c480 PF |
43 | } |
44 | ||
787e71e4 YW |
45 | int sd_ndisc_is_running(sd_ndisc *nd) { |
46 | if (!nd) | |
47 | return false; | |
48 | ||
49 | return sd_event_source_get_enabled(nd->recv_event_source, NULL) > 0; | |
50 | } | |
51 | ||
17347053 | 52 | int sd_ndisc_set_callback( |
a1140666 | 53 | sd_ndisc *nd, |
a1140666 LP |
54 | sd_ndisc_callback_t callback, |
55 | void *userdata) { | |
56 | ||
57 | assert_return(nd, -EINVAL); | |
e3169126 PF |
58 | |
59 | nd->callback = callback; | |
60 | nd->userdata = userdata; | |
61 | ||
62 | return 0; | |
63 | } | |
64 | ||
17347053 | 65 | int sd_ndisc_set_ifindex(sd_ndisc *nd, int ifindex) { |
2f8e7633 LP |
66 | assert_return(nd, -EINVAL); |
67 | assert_return(ifindex > 0, -EINVAL); | |
787e71e4 | 68 | assert_return(!sd_ndisc_is_running(nd), -EBUSY); |
e3169126 | 69 | |
2f8e7633 | 70 | nd->ifindex = ifindex; |
e3169126 PF |
71 | return 0; |
72 | } | |
73 | ||
61a9fa8f YW |
74 | int sd_ndisc_set_ifname(sd_ndisc *nd, const char *ifname) { |
75 | assert_return(nd, -EINVAL); | |
76 | assert_return(ifname, -EINVAL); | |
77 | ||
78 | if (!ifname_valid_full(ifname, IFNAME_VALID_ALTERNATIVE)) | |
79 | return -EINVAL; | |
80 | ||
81 | return free_and_strdup(&nd->ifname, ifname); | |
82 | } | |
83 | ||
5977b71f YW |
84 | int sd_ndisc_get_ifname(sd_ndisc *nd, const char **ret) { |
85 | int r; | |
86 | ||
87 | assert_return(nd, -EINVAL); | |
61a9fa8f | 88 | |
5977b71f YW |
89 | r = get_ifname(nd->ifindex, &nd->ifname); |
90 | if (r < 0) | |
91 | return r; | |
92 | ||
93 | if (ret) | |
94 | *ret = nd->ifname; | |
95 | ||
96 | return 0; | |
61a9fa8f YW |
97 | } |
98 | ||
17347053 | 99 | int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr) { |
a1140666 | 100 | assert_return(nd, -EINVAL); |
e3169126 PF |
101 | |
102 | if (mac_addr) | |
1e7a0e21 | 103 | nd->mac_addr = *mac_addr; |
e3169126 | 104 | else |
eccaf899 | 105 | zero(nd->mac_addr); |
e3169126 PF |
106 | |
107 | return 0; | |
e3169126 PF |
108 | } |
109 | ||
17347053 | 110 | int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int64_t priority) { |
e3169126 PF |
111 | int r; |
112 | ||
113 | assert_return(nd, -EINVAL); | |
787e71e4 | 114 | assert_return(!sd_ndisc_is_running(nd), -EBUSY); |
e3169126 PF |
115 | assert_return(!nd->event, -EBUSY); |
116 | ||
117 | if (event) | |
118 | nd->event = sd_event_ref(event); | |
119 | else { | |
120 | r = sd_event_default(&nd->event); | |
121 | if (r < 0) | |
122 | return 0; | |
123 | } | |
124 | ||
125 | nd->event_priority = priority; | |
126 | ||
127 | return 0; | |
128 | } | |
129 | ||
17347053 | 130 | int sd_ndisc_detach_event(sd_ndisc *nd) { |
1e7a0e21 | 131 | |
e3169126 | 132 | assert_return(nd, -EINVAL); |
787e71e4 | 133 | assert_return(!sd_ndisc_is_running(nd), -EBUSY); |
e3169126 PF |
134 | |
135 | nd->event = sd_event_unref(nd->event); | |
e3169126 PF |
136 | return 0; |
137 | } | |
138 | ||
17347053 | 139 | sd_event *sd_ndisc_get_event(sd_ndisc *nd) { |
a1140666 | 140 | assert_return(nd, NULL); |
e3169126 PF |
141 | |
142 | return nd->event; | |
143 | } | |
144 | ||
165ad41b | 145 | static void ndisc_reset(sd_ndisc *nd) { |
e3169126 PF |
146 | assert(nd); |
147 | ||
ff4b0321 YW |
148 | (void) event_source_disable(nd->timeout_event_source); |
149 | (void) event_source_disable(nd->timeout_no_ra); | |
1bd6f895 | 150 | nd->retransmit_time = 0; |
eb2f7502 | 151 | nd->recv_event_source = sd_event_source_disable_unref(nd->recv_event_source); |
1e7a0e21 | 152 | nd->fd = safe_close(nd->fd); |
e3169126 PF |
153 | } |
154 | ||
8301aa0b YW |
155 | static sd_ndisc *ndisc_free(sd_ndisc *nd) { |
156 | assert(nd); | |
e3169126 | 157 | |
5c4c338a | 158 | ndisc_reset(nd); |
eb2f7502 YW |
159 | |
160 | sd_event_source_unref(nd->timeout_event_source); | |
161 | sd_event_source_unref(nd->timeout_no_ra); | |
4d7b83da | 162 | sd_ndisc_detach_event(nd); |
eb2f7502 | 163 | |
61a9fa8f | 164 | free(nd->ifname); |
6b430fdb | 165 | return mfree(nd); |
e3169126 PF |
166 | } |
167 | ||
8301aa0b YW |
168 | DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_ndisc, sd_ndisc, ndisc_free); |
169 | ||
17347053 | 170 | int sd_ndisc_new(sd_ndisc **ret) { |
4afd3348 | 171 | _cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL; |
e3169126 | 172 | |
a1140666 | 173 | assert_return(ret, -EINVAL); |
e3169126 | 174 | |
144faa8e | 175 | nd = new(sd_ndisc, 1); |
e3169126 PF |
176 | if (!nd) |
177 | return -ENOMEM; | |
178 | ||
144faa8e YW |
179 | *nd = (sd_ndisc) { |
180 | .n_ref = 1, | |
254d1313 | 181 | .fd = -EBADF, |
144faa8e | 182 | }; |
e3169126 | 183 | |
1cc6c93a | 184 | *ret = TAKE_PTR(nd); |
e3169126 PF |
185 | |
186 | return 0; | |
187 | } | |
188 | ||
1e7a0e21 | 189 | static int ndisc_handle_datagram(sd_ndisc *nd, sd_ndisc_router *rt) { |
f6e0ce66 | 190 | int r; |
d77bde34 | 191 | |
f6e0ce66 | 192 | assert(nd); |
1e7a0e21 | 193 | assert(rt); |
d77bde34 | 194 | |
35388783 | 195 | r = ndisc_router_parse(nd, rt); |
f6e0ce66 | 196 | if (r < 0) |
ab8a8a4e | 197 | return r; |
09667885 | 198 | |
e7cb8047 YW |
199 | (void) event_source_disable(nd->timeout_event_source); |
200 | ||
6197db53 | 201 | log_ndisc(nd, "Received Router Advertisement: flags %s preference %s lifetime %s", |
1e7a0e21 LP |
202 | rt->flags & ND_RA_FLAG_MANAGED ? "MANAGED" : rt->flags & ND_RA_FLAG_OTHER ? "OTHER" : "none", |
203 | rt->preference == SD_NDISC_PREFERENCE_HIGH ? "high" : rt->preference == SD_NDISC_PREFERENCE_LOW ? "low" : "medium", | |
6197db53 | 204 | FORMAT_TIMESPAN(rt->lifetime_usec, USEC_PER_SEC)); |
09667885 | 205 | |
1e7a0e21 | 206 | ndisc_callback(nd, SD_NDISC_EVENT_ROUTER, rt); |
09667885 PF |
207 | return 0; |
208 | } | |
209 | ||
1e7a0e21 LP |
210 | static int ndisc_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) { |
211 | _cleanup_(sd_ndisc_router_unrefp) sd_ndisc_router *rt = NULL; | |
99534007 | 212 | sd_ndisc *nd = ASSERT_PTR(userdata); |
88d5a3db PF |
213 | ssize_t buflen; |
214 | int r; | |
e3169126 PF |
215 | |
216 | assert(s); | |
e3169126 PF |
217 | assert(nd->event); |
218 | ||
4edc2c9b | 219 | buflen = next_datagram_size_fd(fd); |
1f2db2e3 ZJS |
220 | if (ERRNO_IS_NEG_TRANSIENT(buflen) || ERRNO_IS_NEG_DISCONNECT(buflen)) |
221 | return 0; | |
35388783 YW |
222 | if (buflen < 0) { |
223 | log_ndisc_errno(nd, buflen, "Failed to determine datagram size to read, ignoring: %m"); | |
224 | return 0; | |
225 | } | |
cddf4d81 | 226 | |
1e7a0e21 LP |
227 | rt = ndisc_router_new(buflen); |
228 | if (!rt) | |
09667885 PF |
229 | return -ENOMEM; |
230 | ||
0afa4d56 | 231 | r = icmp6_receive(fd, NDISC_ROUTER_RAW(rt), rt->raw_size, &rt->address, &rt->timestamp); |
1f2db2e3 ZJS |
232 | if (ERRNO_IS_NEG_TRANSIENT(r) || ERRNO_IS_NEG_DISCONNECT(r)) |
233 | return 0; | |
234 | if (r < 0) | |
88d5a3db PF |
235 | switch (r) { |
236 | case -EADDRNOTAVAIL: | |
4961f566 | 237 | log_ndisc(nd, "Received RA from neither link-local nor null address. Ignoring."); |
1f2db2e3 | 238 | return 0; |
88d5a3db PF |
239 | |
240 | case -EMULTIHOP: | |
35388783 | 241 | log_ndisc(nd, "Received RA with invalid hop limit. Ignoring."); |
1f2db2e3 | 242 | return 0; |
88d5a3db PF |
243 | |
244 | case -EPFNOSUPPORT: | |
35388783 | 245 | log_ndisc(nd, "Received invalid source address from ICMPv6 socket. Ignoring."); |
1f2db2e3 | 246 | return 0; |
437524f1 | 247 | |
437524f1 | 248 | default: |
35388783 | 249 | log_ndisc_errno(nd, r, "Unexpected error while reading from ICMPv6, ignoring: %m"); |
1f2db2e3 | 250 | return 0; |
1e7a0e21 LP |
251 | } |
252 | ||
4961f566 YW |
253 | /* The function icmp6_receive() accepts the null source address, but RFC 4861 Section 6.1.2 states |
254 | * that hosts MUST discard messages with the null source address. */ | |
189eedda YW |
255 | if (in6_addr_is_null(&rt->address)) { |
256 | log_ndisc(nd, "Received an ICMPv6 packet from null address, ignoring."); | |
257 | return 0; | |
258 | } | |
4961f566 | 259 | |
ab8a8a4e YW |
260 | (void) ndisc_handle_datagram(nd, rt); |
261 | return 0; | |
e3169126 PF |
262 | } |
263 | ||
1bd6f895 PF |
264 | static usec_t ndisc_timeout_compute_random(usec_t val) { |
265 | /* compute a time that is random within ±10% of the given value */ | |
266 | return val - val / 10 + | |
267 | (random_u64() % (2 * USEC_PER_SEC)) * val / 10 / USEC_PER_SEC; | |
268 | } | |
269 | ||
1e7a0e21 | 270 | static int ndisc_timeout(sd_event_source *s, uint64_t usec, void *userdata) { |
99534007 | 271 | sd_ndisc *nd = ASSERT_PTR(userdata); |
1bd6f895 | 272 | usec_t time_now; |
e3169126 PF |
273 | int r; |
274 | ||
275 | assert(s); | |
e3169126 PF |
276 | assert(nd->event); |
277 | ||
ba4e0427 | 278 | assert_se(sd_event_now(nd->event, CLOCK_BOOTTIME, &time_now) >= 0); |
e3169126 | 279 | |
1bd6f895 PF |
280 | if (!nd->retransmit_time) |
281 | nd->retransmit_time = ndisc_timeout_compute_random(NDISC_ROUTER_SOLICITATION_INTERVAL); | |
282 | else { | |
283 | if (nd->retransmit_time > NDISC_MAX_ROUTER_SOLICITATION_INTERVAL / 2) | |
284 | nd->retransmit_time = ndisc_timeout_compute_random(NDISC_MAX_ROUTER_SOLICITATION_INTERVAL); | |
285 | else | |
286 | nd->retransmit_time += ndisc_timeout_compute_random(nd->retransmit_time); | |
1e7a0e21 | 287 | } |
9021bb9f | 288 | |
ff4b0321 | 289 | r = event_reset_time(nd->event, &nd->timeout_event_source, |
ba4e0427 | 290 | CLOCK_BOOTTIME, |
ff4b0321 YW |
291 | time_now + nd->retransmit_time, 10 * USEC_PER_MSEC, |
292 | ndisc_timeout, nd, | |
293 | nd->event_priority, "ndisc-timeout-no-ra", true); | |
1bd6f895 PF |
294 | if (r < 0) |
295 | goto fail; | |
296 | ||
e82a19cb | 297 | r = icmp6_send_router_solicitation(nd->fd, &nd->mac_addr); |
852bf938 YW |
298 | if (r < 0) |
299 | log_ndisc_errno(nd, r, "Failed to send Router Solicitation, next solicitation in %s, ignoring: %m", | |
300 | FORMAT_TIMESPAN(nd->retransmit_time, USEC_PER_SEC)); | |
301 | else | |
302 | log_ndisc(nd, "Sent Router Solicitation, next solicitation in %s", | |
303 | FORMAT_TIMESPAN(nd->retransmit_time, USEC_PER_SEC)); | |
1bd6f895 | 304 | |
e3169126 | 305 | return 0; |
b9e7b1cf LP |
306 | |
307 | fail: | |
76f713df | 308 | (void) sd_ndisc_stop(nd); |
b9e7b1cf | 309 | return 0; |
e3169126 PF |
310 | } |
311 | ||
1bd6f895 | 312 | static int ndisc_timeout_no_ra(sd_event_source *s, uint64_t usec, void *userdata) { |
99534007 | 313 | sd_ndisc *nd = ASSERT_PTR(userdata); |
1bd6f895 PF |
314 | |
315 | assert(s); | |
1bd6f895 | 316 | |
35388783 | 317 | log_ndisc(nd, "No RA received before link confirmation timeout"); |
1bd6f895 | 318 | |
ff4b0321 | 319 | (void) event_source_disable(nd->timeout_no_ra); |
1bd6f895 PF |
320 | ndisc_callback(nd, SD_NDISC_EVENT_TIMEOUT, NULL); |
321 | ||
322 | return 0; | |
323 | } | |
324 | ||
17347053 | 325 | int sd_ndisc_stop(sd_ndisc *nd) { |
c8bae363 YW |
326 | if (!nd) |
327 | return 0; | |
836cf090 | 328 | |
787e71e4 | 329 | if (!sd_ndisc_is_running(nd)) |
c1c9b211 LP |
330 | return 0; |
331 | ||
35388783 | 332 | log_ndisc(nd, "Stopping IPv6 Router Solicitation client"); |
836cf090 | 333 | |
5c4c338a | 334 | ndisc_reset(nd); |
1e7a0e21 | 335 | return 1; |
836cf090 PF |
336 | } |
337 | ||
17347053 | 338 | int sd_ndisc_start(sd_ndisc *nd) { |
e3169126 | 339 | int r; |
1bd6f895 | 340 | usec_t time_now; |
e3169126 | 341 | |
a1140666 LP |
342 | assert_return(nd, -EINVAL); |
343 | assert_return(nd->event, -EINVAL); | |
344 | assert_return(nd->ifindex > 0, -EINVAL); | |
e3169126 | 345 | |
787e71e4 | 346 | if (sd_ndisc_is_running(nd)) |
1e7a0e21 | 347 | return 0; |
e3169126 | 348 | |
1e7a0e21 | 349 | assert(!nd->recv_event_source); |
e3169126 | 350 | |
ba4e0427 | 351 | r = sd_event_now(nd->event, CLOCK_BOOTTIME, &time_now); |
1bd6f895 PF |
352 | if (r < 0) |
353 | goto fail; | |
354 | ||
37c011e7 | 355 | nd->fd = icmp6_bind(nd->ifindex, /* is_router = */ false); |
1e7a0e21 LP |
356 | if (nd->fd < 0) |
357 | return nd->fd; | |
358 | ||
359 | r = sd_event_add_io(nd->event, &nd->recv_event_source, nd->fd, EPOLLIN, ndisc_recv, nd); | |
e3169126 | 360 | if (r < 0) |
5c4c338a | 361 | goto fail; |
e3169126 | 362 | |
3e261cfd | 363 | r = sd_event_source_set_priority(nd->recv_event_source, nd->event_priority); |
e3169126 | 364 | if (r < 0) |
5c4c338a | 365 | goto fail; |
e3169126 | 366 | |
3e261cfd | 367 | (void) sd_event_source_set_description(nd->recv_event_source, "ndisc-receive-message"); |
9021bb9f | 368 | |
ff4b0321 | 369 | r = event_reset_time(nd->event, &nd->timeout_event_source, |
ba4e0427 | 370 | CLOCK_BOOTTIME, |
9973e6c4 | 371 | time_now + USEC_PER_SEC / 2, 1 * USEC_PER_SEC, /* See RFC 8415 sec. 18.2.1 */ |
ff4b0321 YW |
372 | ndisc_timeout, nd, |
373 | nd->event_priority, "ndisc-timeout", true); | |
e3169126 | 374 | if (r < 0) |
5c4c338a | 375 | goto fail; |
e3169126 | 376 | |
ff4b0321 | 377 | r = event_reset_time(nd->event, &nd->timeout_no_ra, |
ba4e0427 | 378 | CLOCK_BOOTTIME, |
ff4b0321 YW |
379 | time_now + NDISC_TIMEOUT_NO_RA_USEC, 10 * USEC_PER_MSEC, |
380 | ndisc_timeout_no_ra, nd, | |
381 | nd->event_priority, "ndisc-timeout-no-ra", true); | |
9021bb9f | 382 | if (r < 0) |
5c4c338a | 383 | goto fail; |
e3169126 | 384 | |
35388783 | 385 | log_ndisc(nd, "Started IPv6 Router Solicitation client"); |
1e7a0e21 | 386 | return 1; |
e3169126 | 387 | |
5c4c338a LP |
388 | fail: |
389 | ndisc_reset(nd); | |
e3169126 PF |
390 | return r; |
391 | } |