]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/network/networkd-nexthop.c
Merge pull request #18007 from fw-strlen/ipv6_masq_and_dnat
[thirdparty/systemd.git] / src / network / networkd-nexthop.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later
2 * Copyright © 2019 VMware, Inc.
3 */
4
5 #include <linux/nexthop.h>
6
7 #include "alloc-util.h"
8 #include "netlink-util.h"
9 #include "networkd-link.h"
10 #include "networkd-manager.h"
11 #include "networkd-network.h"
12 #include "networkd-nexthop.h"
13 #include "parse-util.h"
14 #include "set.h"
15 #include "string-util.h"
16
17 NextHop *nexthop_free(NextHop *nexthop) {
18 if (!nexthop)
19 return NULL;
20
21 if (nexthop->network) {
22 assert(nexthop->section);
23 hashmap_remove(nexthop->network->nexthops_by_section, nexthop->section);
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
33 return mfree(nexthop);
34 }
35
36 DEFINE_NETWORK_SECTION_FUNCTIONS(NextHop, nexthop_free);
37
38 static int nexthop_new(NextHop **ret) {
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);
61 assert(filename);
62 assert(section_line > 0);
63
64 r = network_config_section_new(filename, section_line, &n);
65 if (r < 0)
66 return r;
67
68 nexthop = hashmap_get(network->nexthops_by_section, n);
69 if (nexthop) {
70 *ret = TAKE_PTR(nexthop);
71 return 0;
72 }
73
74 r = nexthop_new(&nexthop);
75 if (r < 0)
76 return r;
77
78 nexthop->protocol = RTPROT_STATIC;
79 nexthop->network = network;
80 nexthop->section = TAKE_PTR(n);
81
82 r = hashmap_ensure_put(&network->nexthops_by_section, &network_config_hash_ops, nexthop->section, nexthop);
83 if (r < 0)
84 return r;
85
86 *ret = TAKE_PTR(nexthop);
87 return 0;
88 }
89
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);
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
115 r = CMP(a->family, b->family);
116 if (r != 0)
117 return r;
118
119 if (IN_SET(a->family, AF_INET, AF_INET6))
120 return memcmp(&a->gw, &b->gw, FAMILY_ADDRESS_SIZE(a->family));
121
122 return 0;
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
132 static int nexthop_get(Link *link, NextHop *in, NextHop **ret) {
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;
168 nexthop->family = in->family;
169 nexthop->gw = in->gw;
170
171 r = set_ensure_put(nexthops, &nexthop_hash_ops, nexthop);
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
182 TAKE_PTR(nexthop);
183 return 0;
184 }
185
186 static int nexthop_add_foreign(Link *link, NextHop *in, NextHop **ret) {
187 return nexthop_add_internal(link, &link->nexthops_foreign, in, ret);
188 }
189
190 static int nexthop_add(Link *link, NextHop *in, NextHop **ret) {
191 bool is_new = false;
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;
201 is_new = true;
202 } else if (r == 0) {
203 /* Take over a foreign nexthop */
204 r = set_ensure_put(&link->nexthops, &nexthop_hash_ops, nexthop);
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;
217 return is_new;
218 }
219
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) {
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));
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");
284 }
285
286 r = netlink_call_async(link->manager->rtnl, NULL, req, nexthop_handler,
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
297 return r;
298 }
299
300 int link_set_nexthop(Link *link) {
301 NextHop *nh;
302 int r;
303
304 assert(link);
305 assert(link->network);
306
307 if (link->nexthop_messages != 0) {
308 log_link_debug(link, "Nexthops are configuring.");
309 return 0;
310 }
311
312 link->static_nexthops_configured = false;
313
314 HASHMAP_FOREACH(nh, link->network->nexthops_by_section) {
315 r = nexthop_configure(nh, link);
316 if (r < 0)
317 return log_link_warning_errno(link, r, "Could not set nexthop: %m");
318
319 link->nexthop_messages++;
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
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;
337 uint32_t ifindex;
338 uint16_t type;
339 Link *link;
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
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
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) {
388 log_link_warning_errno(link, r, "rtnl: could not get nexthop family, ignoring: %m");
389 return 0;
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);
392
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;
397 }
398
399 r = sd_netlink_message_read_u32(message, NHA_ID, &tmp->id);
400 if (r < 0 && r != -ENODATA) {
401 log_link_warning_errno(link, r, "rtnl: could not get NHA_ID attribute, ignoring: %m");
402 return 0;
403 }
404
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)
413 log_link_debug(link, "Received remembered nexthop: %s, id: %d", strna(gateway), tmp->id);
414 else {
415 log_link_debug(link, "Remembering foreign nexthop: %s, id: %d", strna(gateway), tmp->id);
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) {
425 log_link_debug(link, "Forgetting nexthop: %s, id: %d", strna(gateway), tmp->id);
426 nexthop_free(nexthop);
427 } else
428 log_link_debug(link, "Kernel removed a nexthop we don't remember: %s, id: %d, ignoring.",
429 strna(gateway), tmp->id);
430 break;
431
432 default:
433 assert_not_reached("Received invalid RTNL message type");
434 }
435
436 return 1;
437 }
438
439 static int nexthop_section_verify(NextHop *nh) {
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
449 void network_drop_invalid_nexthops(Network *network) {
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
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)
483 return log_oom();
484
485 r = safe_atou32(rvalue, &n->id);
486 if (r < 0) {
487 log_syntax(unit, LOG_WARNING, filename, line, r,
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)
520 return log_oom();
521
522 r = in_addr_from_string_auto(rvalue, &n->family, &n->gw);
523 if (r < 0) {
524 log_syntax(unit, LOG_WARNING, filename, line, r,
525 "Invalid %s='%s', ignoring assignment: %m", lvalue, rvalue);
526 return 0;
527 }
528
529 TAKE_PTR(n);
530 return 0;
531 }