]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later |
c16c7808 SS |
2 | * Copyright © 2019 VMware, Inc. |
3 | */ | |
4 | ||
5 | #include <linux/nexthop.h> | |
6 | ||
7 | #include "alloc-util.h" | |
c16c7808 | 8 | #include "netlink-util.h" |
75156ccb | 9 | #include "networkd-link.h" |
c16c7808 | 10 | #include "networkd-manager.h" |
75156ccb | 11 | #include "networkd-network.h" |
c16c7808 SS |
12 | #include "networkd-nexthop.h" |
13 | #include "parse-util.h" | |
14 | #include "set.h" | |
15 | #include "string-util.h" | |
c16c7808 | 16 | |
75156ccb | 17 | NextHop *nexthop_free(NextHop *nexthop) { |
4736035a | 18 | if (!nexthop) |
75156ccb | 19 | return NULL; |
4736035a YW |
20 | |
21 | if (nexthop->network) { | |
b82663dd YW |
22 | assert(nexthop->section); |
23 | hashmap_remove(nexthop->network->nexthops_by_section, nexthop->section); | |
4736035a YW |
24 | } |
25 | ||
26 | network_config_section_free(nexthop->section); | |
27 | ||
28 | if (nexthop->link) { | |
29 | set_remove(nexthop->link->nexthops, nexthop); | |
30 | set_remove(nexthop->link->nexthops_foreign, nexthop); | |
31 | } | |
32 | ||
75156ccb | 33 | return mfree(nexthop); |
4736035a YW |
34 | } |
35 | ||
36 | DEFINE_NETWORK_SECTION_FUNCTIONS(NextHop, nexthop_free); | |
37 | ||
38 | static int nexthop_new(NextHop **ret) { | |
c16c7808 SS |
39 | _cleanup_(nexthop_freep) NextHop *nexthop = NULL; |
40 | ||
41 | nexthop = new(NextHop, 1); | |
42 | if (!nexthop) | |
43 | return -ENOMEM; | |
44 | ||
45 | *nexthop = (NextHop) { | |
46 | .family = AF_UNSPEC, | |
47 | }; | |
48 | ||
49 | *ret = TAKE_PTR(nexthop); | |
50 | ||
51 | return 0; | |
52 | } | |
53 | ||
54 | static int nexthop_new_static(Network *network, const char *filename, unsigned section_line, NextHop **ret) { | |
55 | _cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL; | |
56 | _cleanup_(nexthop_freep) NextHop *nexthop = NULL; | |
57 | int r; | |
58 | ||
59 | assert(network); | |
60 | assert(ret); | |
b82663dd YW |
61 | assert(filename); |
62 | assert(section_line > 0); | |
c16c7808 | 63 | |
b82663dd YW |
64 | r = network_config_section_new(filename, section_line, &n); |
65 | if (r < 0) | |
66 | return r; | |
c16c7808 | 67 | |
b82663dd YW |
68 | nexthop = hashmap_get(network->nexthops_by_section, n); |
69 | if (nexthop) { | |
70 | *ret = TAKE_PTR(nexthop); | |
71 | return 0; | |
c16c7808 SS |
72 | } |
73 | ||
74 | r = nexthop_new(&nexthop); | |
75 | if (r < 0) | |
76 | return r; | |
77 | ||
78 | nexthop->protocol = RTPROT_STATIC; | |
79 | nexthop->network = network; | |
b82663dd | 80 | nexthop->section = TAKE_PTR(n); |
c16c7808 | 81 | |
a307a7dd | 82 | r = hashmap_ensure_put(&network->nexthops_by_section, &network_config_hash_ops, nexthop->section, nexthop); |
b82663dd YW |
83 | if (r < 0) |
84 | return r; | |
c16c7808 SS |
85 | |
86 | *ret = TAKE_PTR(nexthop); | |
c16c7808 SS |
87 | return 0; |
88 | } | |
89 | ||
c16c7808 SS |
90 | static void nexthop_hash_func(const NextHop *nexthop, struct siphash *state) { |
91 | assert(nexthop); | |
92 | ||
93 | siphash24_compress(&nexthop->id, sizeof(nexthop->id), state); | |
c16c7808 SS |
94 | siphash24_compress(&nexthop->family, sizeof(nexthop->family), state); |
95 | ||
96 | switch (nexthop->family) { | |
97 | case AF_INET: | |
98 | case AF_INET6: | |
99 | siphash24_compress(&nexthop->gw, FAMILY_ADDRESS_SIZE(nexthop->family), state); | |
100 | ||
101 | break; | |
102 | default: | |
103 | /* treat any other address family as AF_UNSPEC */ | |
104 | break; | |
105 | } | |
106 | } | |
107 | ||
108 | static int nexthop_compare_func(const NextHop *a, const NextHop *b) { | |
109 | int r; | |
110 | ||
111 | r = CMP(a->id, b->id); | |
112 | if (r != 0) | |
113 | return r; | |
114 | ||
c16c7808 SS |
115 | r = CMP(a->family, b->family); |
116 | if (r != 0) | |
117 | return r; | |
118 | ||
cf5a228f YW |
119 | if (IN_SET(a->family, AF_INET, AF_INET6)) |
120 | return memcmp(&a->gw, &b->gw, FAMILY_ADDRESS_SIZE(a->family)); | |
c16c7808 | 121 | |
cf5a228f | 122 | return 0; |
c16c7808 SS |
123 | } |
124 | ||
125 | DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR( | |
126 | nexthop_hash_ops, | |
127 | NextHop, | |
128 | nexthop_hash_func, | |
129 | nexthop_compare_func, | |
130 | nexthop_free); | |
131 | ||
4736035a | 132 | static int nexthop_get(Link *link, NextHop *in, NextHop **ret) { |
c16c7808 SS |
133 | NextHop *existing; |
134 | ||
135 | assert(link); | |
136 | assert(in); | |
137 | ||
138 | existing = set_get(link->nexthops, in); | |
139 | if (existing) { | |
140 | if (ret) | |
141 | *ret = existing; | |
142 | return 1; | |
143 | } | |
144 | ||
145 | existing = set_get(link->nexthops_foreign, in); | |
146 | if (existing) { | |
147 | if (ret) | |
148 | *ret = existing; | |
149 | return 0; | |
150 | } | |
151 | ||
152 | return -ENOENT; | |
153 | } | |
154 | ||
155 | static int nexthop_add_internal(Link *link, Set **nexthops, NextHop *in, NextHop **ret) { | |
156 | _cleanup_(nexthop_freep) NextHop *nexthop = NULL; | |
157 | int r; | |
158 | ||
159 | assert(link); | |
160 | assert(nexthops); | |
161 | assert(in); | |
162 | ||
163 | r = nexthop_new(&nexthop); | |
164 | if (r < 0) | |
165 | return r; | |
166 | ||
167 | nexthop->id = in->id; | |
c16c7808 SS |
168 | nexthop->family = in->family; |
169 | nexthop->gw = in->gw; | |
170 | ||
de7fef4b | 171 | r = set_ensure_put(nexthops, &nexthop_hash_ops, nexthop); |
c16c7808 SS |
172 | if (r < 0) |
173 | return r; | |
174 | if (r == 0) | |
175 | return -EEXIST; | |
176 | ||
177 | nexthop->link = link; | |
178 | ||
179 | if (ret) | |
180 | *ret = nexthop; | |
181 | ||
fb7a534f | 182 | TAKE_PTR(nexthop); |
c16c7808 SS |
183 | return 0; |
184 | } | |
185 | ||
4736035a | 186 | static int nexthop_add_foreign(Link *link, NextHop *in, NextHop **ret) { |
c16c7808 SS |
187 | return nexthop_add_internal(link, &link->nexthops_foreign, in, ret); |
188 | } | |
189 | ||
4736035a | 190 | static int nexthop_add(Link *link, NextHop *in, NextHop **ret) { |
d9eee312 | 191 | bool is_new = false; |
c16c7808 SS |
192 | NextHop *nexthop; |
193 | int r; | |
194 | ||
195 | r = nexthop_get(link, in, &nexthop); | |
196 | if (r == -ENOENT) { | |
197 | /* NextHop does not exist, create a new one */ | |
198 | r = nexthop_add_internal(link, &link->nexthops, in, &nexthop); | |
199 | if (r < 0) | |
200 | return r; | |
d9eee312 | 201 | is_new = true; |
c16c7808 SS |
202 | } else if (r == 0) { |
203 | /* Take over a foreign nexthop */ | |
de7fef4b | 204 | r = set_ensure_put(&link->nexthops, &nexthop_hash_ops, nexthop); |
c16c7808 SS |
205 | if (r < 0) |
206 | return r; | |
207 | ||
208 | set_remove(link->nexthops_foreign, nexthop); | |
209 | } else if (r == 1) { | |
210 | /* NextHop exists, do nothing */ | |
211 | ; | |
212 | } else | |
213 | return r; | |
214 | ||
215 | if (ret) | |
216 | *ret = nexthop; | |
d9eee312 | 217 | return is_new; |
c16c7808 SS |
218 | } |
219 | ||
c133770a YW |
220 | static int nexthop_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { |
221 | int r; | |
222 | ||
223 | assert(link); | |
224 | assert(link->nexthop_messages > 0); | |
225 | ||
226 | link->nexthop_messages--; | |
227 | ||
228 | if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) | |
229 | return 1; | |
230 | ||
231 | r = sd_netlink_message_get_errno(m); | |
232 | if (r < 0 && r != -EEXIST) { | |
233 | log_link_message_warning_errno(link, m, r, "Could not set nexthop"); | |
234 | link_enter_failed(link); | |
235 | return 1; | |
236 | } | |
237 | ||
238 | if (link->nexthop_messages == 0) { | |
239 | log_link_debug(link, "Nexthop set"); | |
240 | link->static_nexthops_configured = true; | |
241 | link_check_ready(link); | |
242 | } | |
243 | ||
244 | return 1; | |
245 | } | |
246 | ||
247 | static int nexthop_configure(NextHop *nexthop, Link *link) { | |
c16c7808 SS |
248 | _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; |
249 | int r; | |
250 | ||
251 | assert(link); | |
252 | assert(link->manager); | |
253 | assert(link->manager->rtnl); | |
254 | assert(link->ifindex > 0); | |
255 | assert(IN_SET(nexthop->family, AF_INET, AF_INET6)); | |
c16c7808 SS |
256 | |
257 | if (DEBUG_LOGGING) { | |
258 | _cleanup_free_ char *gw = NULL; | |
259 | ||
260 | if (!in_addr_is_null(nexthop->family, &nexthop->gw)) | |
261 | (void) in_addr_to_string(nexthop->family, &nexthop->gw, &gw); | |
262 | ||
263 | log_link_debug(link, "Configuring nexthop: gw: %s", strna(gw)); | |
264 | } | |
265 | ||
266 | r = sd_rtnl_message_new_nexthop(link->manager->rtnl, &req, | |
267 | RTM_NEWNEXTHOP, nexthop->family, | |
268 | nexthop->protocol); | |
269 | if (r < 0) | |
270 | return log_link_error_errno(link, r, "Could not create RTM_NEWNEXTHOP message: %m"); | |
271 | ||
272 | r = sd_netlink_message_append_u32(req, NHA_ID, nexthop->id); | |
273 | if (r < 0) | |
274 | return log_link_error_errno(link, r, "Could not append NHA_ID attribute: %m"); | |
275 | ||
276 | r = sd_netlink_message_append_u32(req, NHA_OIF, link->ifindex); | |
277 | if (r < 0) | |
278 | return log_link_error_errno(link, r, "Could not append NHA_OIF attribute: %m"); | |
279 | ||
280 | if (in_addr_is_null(nexthop->family, &nexthop->gw) == 0) { | |
281 | r = netlink_message_append_in_addr_union(req, NHA_GATEWAY, nexthop->family, &nexthop->gw); | |
282 | if (r < 0) | |
283 | return log_link_error_errno(link, r, "Could not append NHA_GATEWAY attribute: %m"); | |
c16c7808 SS |
284 | } |
285 | ||
c133770a | 286 | r = netlink_call_async(link->manager->rtnl, NULL, req, nexthop_handler, |
c16c7808 SS |
287 | link_netlink_destroy_callback, link); |
288 | if (r < 0) | |
289 | return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); | |
290 | ||
291 | link_ref(link); | |
292 | ||
293 | r = nexthop_add(link, nexthop, &nexthop); | |
294 | if (r < 0) | |
295 | return log_link_error_errno(link, r, "Could not add nexthop: %m"); | |
296 | ||
d9eee312 | 297 | return r; |
c16c7808 SS |
298 | } |
299 | ||
c133770a YW |
300 | int link_set_nexthop(Link *link) { |
301 | NextHop *nh; | |
302 | int r; | |
303 | ||
304 | assert(link); | |
305 | assert(link->network); | |
306 | ||
bd4733da YW |
307 | if (link->nexthop_messages != 0) { |
308 | log_link_debug(link, "Nexthops are configuring."); | |
309 | return 0; | |
310 | } | |
311 | ||
c133770a YW |
312 | link->static_nexthops_configured = false; |
313 | ||
b82663dd | 314 | HASHMAP_FOREACH(nh, link->network->nexthops_by_section) { |
c133770a YW |
315 | r = nexthop_configure(nh, link); |
316 | if (r < 0) | |
317 | return log_link_warning_errno(link, r, "Could not set nexthop: %m"); | |
b82663dd YW |
318 | |
319 | link->nexthop_messages++; | |
c133770a YW |
320 | } |
321 | ||
322 | if (link->nexthop_messages == 0) { | |
323 | link->static_nexthops_configured = true; | |
324 | link_check_ready(link); | |
325 | } else { | |
326 | log_link_debug(link, "Setting nexthop"); | |
327 | link_set_state(link, LINK_STATE_CONFIGURING); | |
328 | } | |
329 | ||
330 | return 1; | |
331 | } | |
332 | ||
454c87b5 YW |
333 | int manager_rtnl_process_nexthop(sd_netlink *rtnl, sd_netlink_message *message, Manager *m) { |
334 | _cleanup_(nexthop_freep) NextHop *tmp = NULL; | |
335 | _cleanup_free_ char *gateway = NULL; | |
336 | NextHop *nexthop = NULL; | |
8c112218 | 337 | uint32_t ifindex; |
454c87b5 | 338 | uint16_t type; |
8c112218 | 339 | Link *link; |
454c87b5 YW |
340 | int r; |
341 | ||
342 | assert(rtnl); | |
343 | assert(message); | |
344 | assert(m); | |
345 | ||
346 | if (sd_netlink_message_is_error(message)) { | |
347 | r = sd_netlink_message_get_errno(message); | |
348 | if (r < 0) | |
349 | log_message_warning_errno(message, r, "rtnl: failed to receive rule message, ignoring"); | |
350 | ||
351 | return 0; | |
352 | } | |
353 | ||
354 | r = sd_netlink_message_get_type(message, &type); | |
355 | if (r < 0) { | |
356 | log_warning_errno(r, "rtnl: could not get message type, ignoring: %m"); | |
357 | return 0; | |
358 | } else if (!IN_SET(type, RTM_NEWNEXTHOP, RTM_DELNEXTHOP)) { | |
359 | log_warning("rtnl: received unexpected message type %u when processing nexthop, ignoring.", type); | |
360 | return 0; | |
361 | } | |
362 | ||
8c112218 YW |
363 | r = sd_netlink_message_read_u32(message, NHA_OIF, &ifindex); |
364 | if (r == -ENODATA) { | |
365 | log_warning_errno(r, "rtnl: received nexthop message without NHA_OIF attribute, ignoring: %m"); | |
366 | return 0; | |
367 | } else if (r < 0) { | |
368 | log_warning_errno(r, "rtnl: could not get NHA_OIF attribute, ignoring: %m"); | |
369 | return 0; | |
370 | } else if (ifindex <= 0) { | |
371 | log_warning("rtnl: received nexthop message with invalid ifindex %"PRIu32", ignoring.", ifindex); | |
372 | return 0; | |
373 | } | |
374 | ||
375 | r = link_get(m, ifindex, &link); | |
376 | if (r < 0 || !link) { | |
377 | if (!m->enumerating) | |
378 | log_warning("rtnl: received nexthop message for link (%"PRIu32") we do not know about, ignoring", ifindex); | |
379 | return 0; | |
380 | } | |
381 | ||
454c87b5 YW |
382 | r = nexthop_new(&tmp); |
383 | if (r < 0) | |
384 | return log_oom(); | |
385 | ||
386 | r = sd_rtnl_message_get_family(message, &tmp->family); | |
387 | if (r < 0) { | |
47a277f1 | 388 | log_link_warning_errno(link, r, "rtnl: could not get nexthop family, ignoring: %m"); |
454c87b5 | 389 | return 0; |
47a277f1 YW |
390 | } else if (!IN_SET(tmp->family, AF_INET, AF_INET6)) |
391 | return log_link_debug(link, "rtnl: received nexthop message with invalid family %d, ignoring.", tmp->family); | |
454c87b5 | 392 | |
f96f4ebc YW |
393 | r = netlink_message_read_in_addr_union(message, NHA_GATEWAY, tmp->family, &tmp->gw); |
394 | if (r < 0 && r != -ENODATA) { | |
395 | log_link_warning_errno(link, r, "rtnl: could not get NHA_GATEWAY attribute, ignoring: %m"); | |
396 | return 0; | |
454c87b5 YW |
397 | } |
398 | ||
399 | r = sd_netlink_message_read_u32(message, NHA_ID, &tmp->id); | |
400 | if (r < 0 && r != -ENODATA) { | |
47a277f1 | 401 | log_link_warning_errno(link, r, "rtnl: could not get NHA_ID attribute, ignoring: %m"); |
454c87b5 YW |
402 | return 0; |
403 | } | |
404 | ||
454c87b5 YW |
405 | (void) nexthop_get(link, tmp, &nexthop); |
406 | ||
407 | if (DEBUG_LOGGING) | |
408 | (void) in_addr_to_string(tmp->family, &tmp->gw, &gateway); | |
409 | ||
410 | switch (type) { | |
411 | case RTM_NEWNEXTHOP: | |
412 | if (nexthop) | |
8c112218 | 413 | log_link_debug(link, "Received remembered nexthop: %s, id: %d", strna(gateway), tmp->id); |
454c87b5 | 414 | else { |
8c112218 | 415 | log_link_debug(link, "Remembering foreign nexthop: %s, id: %d", strna(gateway), tmp->id); |
454c87b5 YW |
416 | r = nexthop_add_foreign(link, tmp, &nexthop); |
417 | if (r < 0) { | |
418 | log_link_warning_errno(link, r, "Could not remember foreign nexthop, ignoring: %m"); | |
419 | return 0; | |
420 | } | |
421 | } | |
422 | break; | |
423 | case RTM_DELNEXTHOP: | |
424 | if (nexthop) { | |
8c112218 | 425 | log_link_debug(link, "Forgetting nexthop: %s, id: %d", strna(gateway), tmp->id); |
454c87b5 YW |
426 | nexthop_free(nexthop); |
427 | } else | |
8c112218 YW |
428 | log_link_debug(link, "Kernel removed a nexthop we don't remember: %s, id: %d, ignoring.", |
429 | strna(gateway), tmp->id); | |
454c87b5 YW |
430 | break; |
431 | ||
432 | default: | |
433 | assert_not_reached("Received invalid RTNL message type"); | |
434 | } | |
435 | ||
436 | return 1; | |
437 | } | |
438 | ||
0992f9fb | 439 | static int nexthop_section_verify(NextHop *nh) { |
c16c7808 SS |
440 | if (section_is_invalid(nh->section)) |
441 | return -EINVAL; | |
442 | ||
443 | if (in_addr_is_null(nh->family, &nh->gw) < 0) | |
444 | return -EINVAL; | |
445 | ||
446 | return 0; | |
447 | } | |
448 | ||
13ffa39f | 449 | void network_drop_invalid_nexthops(Network *network) { |
0992f9fb YW |
450 | NextHop *nh; |
451 | ||
452 | assert(network); | |
453 | ||
454 | HASHMAP_FOREACH(nh, network->nexthops_by_section) | |
455 | if (nexthop_section_verify(nh) < 0) | |
456 | nexthop_free(nh); | |
457 | } | |
458 | ||
c16c7808 SS |
459 | int config_parse_nexthop_id( |
460 | const char *unit, | |
461 | const char *filename, | |
462 | unsigned line, | |
463 | const char *section, | |
464 | unsigned section_line, | |
465 | const char *lvalue, | |
466 | int ltype, | |
467 | const char *rvalue, | |
468 | void *data, | |
469 | void *userdata) { | |
470 | ||
471 | _cleanup_(nexthop_free_or_set_invalidp) NextHop *n = NULL; | |
472 | Network *network = userdata; | |
473 | int r; | |
474 | ||
475 | assert(filename); | |
476 | assert(section); | |
477 | assert(lvalue); | |
478 | assert(rvalue); | |
479 | assert(data); | |
480 | ||
481 | r = nexthop_new_static(network, filename, section_line, &n); | |
482 | if (r < 0) | |
d96edb2c | 483 | return log_oom(); |
c16c7808 SS |
484 | |
485 | r = safe_atou32(rvalue, &n->id); | |
486 | if (r < 0) { | |
d96edb2c | 487 | log_syntax(unit, LOG_WARNING, filename, line, r, |
c16c7808 SS |
488 | "Could not parse nexthop id \"%s\", ignoring assignment: %m", rvalue); |
489 | return 0; | |
490 | } | |
491 | ||
492 | TAKE_PTR(n); | |
493 | return 0; | |
494 | } | |
495 | ||
496 | int config_parse_nexthop_gateway( | |
497 | const char *unit, | |
498 | const char *filename, | |
499 | unsigned line, | |
500 | const char *section, | |
501 | unsigned section_line, | |
502 | const char *lvalue, | |
503 | int ltype, | |
504 | const char *rvalue, | |
505 | void *data, | |
506 | void *userdata) { | |
507 | ||
508 | _cleanup_(nexthop_free_or_set_invalidp) NextHop *n = NULL; | |
509 | Network *network = userdata; | |
510 | int r; | |
511 | ||
512 | assert(filename); | |
513 | assert(section); | |
514 | assert(lvalue); | |
515 | assert(rvalue); | |
516 | assert(data); | |
517 | ||
518 | r = nexthop_new_static(network, filename, section_line, &n); | |
519 | if (r < 0) | |
d96edb2c | 520 | return log_oom(); |
c16c7808 SS |
521 | |
522 | r = in_addr_from_string_auto(rvalue, &n->family, &n->gw); | |
523 | if (r < 0) { | |
d96edb2c | 524 | log_syntax(unit, LOG_WARNING, filename, line, r, |
c16c7808 SS |
525 | "Invalid %s='%s', ignoring assignment: %m", lvalue, rvalue); |
526 | return 0; | |
527 | } | |
528 | ||
529 | TAKE_PTR(n); | |
530 | return 0; | |
531 | } |