]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/network/networkd-neighbor.c
537f6be9e16c7b9cd4bea5083e5b4b3830a5bb1a
[thirdparty/systemd.git] / src / network / networkd-neighbor.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2
3 #include "sd-netlink.h"
4
5 #include "alloc-util.h"
6 #include "conf-parser.h"
7 #include "ether-addr-util.h"
8 #include "hashmap.h"
9 #include "in-addr-util.h"
10 #include "netlink-util.h"
11 #include "networkd-link.h"
12 #include "networkd-manager.h"
13 #include "networkd-neighbor.h"
14 #include "set.h"
15
16 void neighbor_free(Neighbor *neighbor) {
17 if (!neighbor)
18 return;
19
20 if (neighbor->network) {
21 LIST_REMOVE(neighbors, neighbor->network->neighbors, neighbor);
22 assert(neighbor->network->n_neighbors > 0);
23 neighbor->network->n_neighbors--;
24
25 if (neighbor->section)
26 hashmap_remove(neighbor->network->neighbors_by_section, neighbor->section);
27 }
28
29 network_config_section_free(neighbor->section);
30
31 if (neighbor->link) {
32 set_remove(neighbor->link->neighbors, neighbor);
33 set_remove(neighbor->link->neighbors_foreign, neighbor);
34 }
35
36 free(neighbor);
37 }
38
39 static int neighbor_new_static(Network *network, const char *filename, unsigned section_line, Neighbor **ret) {
40 _cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL;
41 _cleanup_(neighbor_freep) Neighbor *neighbor = NULL;
42 int r;
43
44 assert(network);
45 assert(ret);
46 assert(!!filename == (section_line > 0));
47
48 if (filename) {
49 r = network_config_section_new(filename, section_line, &n);
50 if (r < 0)
51 return r;
52
53 neighbor = hashmap_get(network->neighbors_by_section, n);
54 if (neighbor) {
55 *ret = TAKE_PTR(neighbor);
56
57 return 0;
58 }
59 }
60
61 neighbor = new(Neighbor, 1);
62 if (!neighbor)
63 return -ENOMEM;
64
65 *neighbor = (Neighbor) {
66 .network = network,
67 .family = AF_UNSPEC,
68 };
69
70 LIST_APPEND(neighbors, network->neighbors, neighbor);
71 network->n_neighbors++;
72
73 if (filename) {
74 neighbor->section = TAKE_PTR(n);
75
76 r = hashmap_ensure_allocated(&network->neighbors_by_section, &network_config_hash_ops);
77 if (r < 0)
78 return r;
79
80 r = hashmap_put(network->neighbors_by_section, neighbor->section, neighbor);
81 if (r < 0)
82 return r;
83 }
84
85 *ret = TAKE_PTR(neighbor);
86
87 return 0;
88 }
89
90 static int neighbor_configure_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
91 int r;
92
93 assert(m);
94 assert(link);
95 assert(link->neighbor_messages > 0);
96
97 link->neighbor_messages--;
98
99 if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
100 return 1;
101
102 r = sd_netlink_message_get_errno(m);
103 if (r < 0 && r != -EEXIST)
104 /* Neighbor may not exist yet. So, do not enter failed state here. */
105 log_link_warning_errno(link, r, "Could not set neighbor, ignoring: %m");
106
107 if (link->neighbor_messages == 0) {
108 log_link_debug(link, "Neighbors set");
109 link->neighbors_configured = true;
110 link_check_ready(link);
111 }
112
113 return 1;
114 }
115
116 int neighbor_configure(Neighbor *neighbor, Link *link, link_netlink_message_handler_t callback) {
117 _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
118 int r;
119
120 assert(neighbor);
121 assert(link);
122 assert(link->ifindex > 0);
123 assert(link->manager);
124 assert(link->manager->rtnl);
125
126 r = sd_rtnl_message_new_neigh(link->manager->rtnl, &req, RTM_NEWNEIGH,
127 link->ifindex, neighbor->family);
128 if (r < 0)
129 return log_error_errno(r, "Could not allocate RTM_NEWNEIGH message: %m");
130
131 r = sd_rtnl_message_neigh_set_state(req, NUD_PERMANENT);
132 if (r < 0)
133 return log_error_errno(r, "Could not set state: %m");
134
135 r = sd_netlink_message_set_flags(req, NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE);
136 if (r < 0)
137 return log_error_errno(r, "Could not set flags: %m");
138
139 r = sd_netlink_message_append_data(req, NDA_LLADDR, &neighbor->lladdr, neighbor->lladdr_size);
140 if (r < 0)
141 return log_error_errno(r, "Could not append NDA_LLADDR attribute: %m");
142
143 r = netlink_message_append_in_addr_union(req, NDA_DST, neighbor->family, &neighbor->in_addr);
144 if (r < 0)
145 return log_error_errno(r, "Could not append NDA_DST attribute: %m");
146
147 r = netlink_call_async(link->manager->rtnl, NULL, req, callback ?: neighbor_configure_handler,
148 link_netlink_destroy_callback, link);
149 if (r < 0)
150 return log_error_errno(r, "Could not send rtnetlink message: %m");
151
152 link->neighbor_messages++;
153 link_ref(link);
154
155 r = neighbor_add(link, neighbor->family, &neighbor->in_addr, &neighbor->lladdr, neighbor->lladdr_size, NULL);
156 if (r < 0)
157 return log_link_error_errno(link, r, "Could not add neighbor: %m");
158
159 return 0;
160 }
161
162 static int neighbor_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
163 int r;
164
165 assert(m);
166 assert(link);
167
168 if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
169 return 1;
170
171 r = sd_netlink_message_get_errno(m);
172 if (r < 0 && r != -ESRCH)
173 /* Neighbor may not exist because it already got deleted, ignore that. */
174 log_link_warning_errno(link, r, "Could not remove neighbor: %m");
175
176 return 1;
177 }
178
179 int neighbor_remove(Neighbor *neighbor, Link *link, link_netlink_message_handler_t callback) {
180 _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
181 int r;
182
183 assert(neighbor);
184 assert(link);
185 assert(link->ifindex > 0);
186 assert(link->manager);
187 assert(link->manager->rtnl);
188
189 r = sd_rtnl_message_new_neigh(link->manager->rtnl, &req, RTM_DELNEIGH,
190 link->ifindex, neighbor->family);
191 if (r < 0)
192 return log_error_errno(r, "Could not allocate RTM_DELNEIGH message: %m");
193
194 r = netlink_message_append_in_addr_union(req, NDA_DST, neighbor->family, &neighbor->in_addr);
195 if (r < 0)
196 return log_error_errno(r, "Could not append NDA_DST attribute: %m");
197
198 r = netlink_call_async(link->manager->rtnl, NULL, req, callback ?: neighbor_remove_handler,
199 link_netlink_destroy_callback, link);
200 if (r < 0)
201 return log_error_errno(r, "Could not send rtnetlink message: %m");
202
203 link_ref(link);
204
205 return 0;
206 }
207
208 static void neighbor_hash_func(const Neighbor *neighbor, struct siphash *state) {
209 assert(neighbor);
210
211 siphash24_compress(&neighbor->family, sizeof(neighbor->family), state);
212
213 switch (neighbor->family) {
214 case AF_INET:
215 case AF_INET6:
216 /* Equality of neighbors are given by the pair (addr,lladdr) */
217 siphash24_compress(&neighbor->in_addr, FAMILY_ADDRESS_SIZE(neighbor->family), state);
218 siphash24_compress(&neighbor->lladdr, neighbor->lladdr_size, state);
219 break;
220 default:
221 /* treat any other address family as AF_UNSPEC */
222 break;
223 }
224 }
225
226 static int neighbor_compare_func(const Neighbor *a, const Neighbor *b) {
227 int r;
228
229 r = CMP(a->family, b->family);
230 if (r != 0)
231 return r;
232
233 r = CMP(a->lladdr_size, b->lladdr_size);
234 if (r != 0)
235 return r;
236
237 switch (a->family) {
238 case AF_INET:
239 case AF_INET6:
240 r = memcmp(&a->in_addr, &b->in_addr, FAMILY_ADDRESS_SIZE(a->family));
241 if (r != 0)
242 return r;
243 }
244
245 return memcmp(&a->lladdr, &b->lladdr, a->lladdr_size);
246 }
247
248 DEFINE_PRIVATE_HASH_OPS(neighbor_hash_ops, Neighbor, neighbor_hash_func, neighbor_compare_func);
249
250 int neighbor_get(Link *link, int family, const union in_addr_union *addr, const union lladdr_union *lladdr, size_t lladdr_size, Neighbor **ret) {
251 Neighbor neighbor, *existing;
252
253 assert(link);
254 assert(addr);
255 assert(lladdr);
256
257 neighbor = (Neighbor) {
258 .family = family,
259 .in_addr = *addr,
260 .lladdr = *lladdr,
261 .lladdr_size = lladdr_size,
262 };
263
264 existing = set_get(link->neighbors, &neighbor);
265 if (existing) {
266 if (ret)
267 *ret = existing;
268 return 1;
269 }
270
271 existing = set_get(link->neighbors_foreign, &neighbor);
272 if (existing) {
273 if (ret)
274 *ret = existing;
275 return 0;
276 }
277
278 return -ENOENT;
279 }
280
281 static int neighbor_add_internal(Link *link, Set **neighbors, int family, const union in_addr_union *addr, const union lladdr_union *lladdr, size_t lladdr_size, Neighbor **ret) {
282 _cleanup_(neighbor_freep) Neighbor *neighbor = NULL;
283 int r;
284
285 assert(link);
286 assert(neighbors);
287 assert(addr);
288 assert(lladdr);
289
290 neighbor = new(Neighbor, 1);
291 if (!neighbor)
292 return -ENOMEM;
293
294 *neighbor = (Neighbor) {
295 .family = family,
296 .in_addr = *addr,
297 .lladdr = *lladdr,
298 .lladdr_size = lladdr_size,
299 };
300
301 r = set_ensure_allocated(neighbors, &neighbor_hash_ops);
302 if (r < 0)
303 return r;
304
305 r = set_put(*neighbors, neighbor);
306 if (r < 0)
307 return r;
308 if (r == 0)
309 return -EEXIST;
310
311 neighbor->link = link;
312
313 if (ret)
314 *ret = neighbor;
315
316 neighbor = NULL;
317
318 return 0;
319 }
320
321 int neighbor_add(Link *link, int family, const union in_addr_union *addr, const union lladdr_union *lladdr, size_t lladdr_size, Neighbor **ret) {
322 Neighbor *neighbor;
323 int r;
324
325 r = neighbor_get(link, family, addr, lladdr, lladdr_size, &neighbor);
326 if (r == -ENOENT) {
327 /* Neighbor doesn't exist, make a new one */
328 r = neighbor_add_internal(link, &link->neighbors, family, addr, lladdr, lladdr_size, &neighbor);
329 if (r < 0)
330 return r;
331 } else if (r == 0) {
332 /* Neighbor is foreign, claim it as recognized */
333 r = set_ensure_allocated(&link->neighbors, &neighbor_hash_ops);
334 if (r < 0)
335 return r;
336
337 r = set_put(link->neighbors, neighbor);
338 if (r < 0)
339 return r;
340
341 set_remove(link->neighbors_foreign, neighbor);
342 } else if (r == 1) {
343 /* Neighbor already exists */
344 } else
345 return r;
346
347 if (ret)
348 *ret = neighbor;
349 return 0;
350 }
351
352 int neighbor_add_foreign(Link *link, int family, const union in_addr_union *addr, const union lladdr_union *lladdr, size_t lladdr_size, Neighbor **ret) {
353 return neighbor_add_internal(link, &link->neighbors_foreign, family, addr, lladdr, lladdr_size, ret);
354 }
355
356 bool neighbor_equal(const Neighbor *n1, const Neighbor *n2) {
357 if (n1 == n2)
358 return true;
359
360 if (!n1 || !n2)
361 return false;
362
363 return neighbor_compare_func(n1, n2) == 0;
364 }
365
366 int neighbor_section_verify(Neighbor *neighbor) {
367 if (section_is_invalid(neighbor->section))
368 return -EINVAL;
369
370 if (neighbor->family == AF_UNSPEC)
371 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
372 "%s: Neighbor section without Address= configured. "
373 "Ignoring [Neighbor] section from line %u.",
374 neighbor->section->filename, neighbor->section->line);
375
376 if (neighbor->lladdr_size == 0)
377 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
378 "%s: Neighbor section without LinkLayerAddress= configured. "
379 "Ignoring [Neighbor] section from line %u.",
380 neighbor->section->filename, neighbor->section->line);
381
382 return 0;
383 }
384
385 int config_parse_neighbor_address(
386 const char *unit,
387 const char *filename,
388 unsigned line,
389 const char *section,
390 unsigned section_line,
391 const char *lvalue,
392 int ltype,
393 const char *rvalue,
394 void *data,
395 void *userdata) {
396
397 Network *network = userdata;
398 _cleanup_(neighbor_free_or_set_invalidp) Neighbor *n = NULL;
399 int r;
400
401 assert(filename);
402 assert(section);
403 assert(lvalue);
404 assert(rvalue);
405 assert(data);
406
407 r = neighbor_new_static(network, filename, section_line, &n);
408 if (r < 0)
409 return r;
410
411 r = in_addr_from_string_auto(rvalue, &n->family, &n->in_addr);
412 if (r < 0) {
413 log_syntax(unit, LOG_ERR, filename, line, r, "Neighbor Address is invalid, ignoring assignment: %s", rvalue);
414 return 0;
415 }
416
417 TAKE_PTR(n);
418
419 return 0;
420 }
421
422 int config_parse_neighbor_lladdr(
423 const char *unit,
424 const char *filename,
425 unsigned line,
426 const char *section,
427 unsigned section_line,
428 const char *lvalue,
429 int ltype,
430 const char *rvalue,
431 void *data,
432 void *userdata) {
433
434 Network *network = userdata;
435 _cleanup_(neighbor_free_or_set_invalidp) Neighbor *n = NULL;
436 int family, r;
437
438 assert(filename);
439 assert(section);
440 assert(lvalue);
441 assert(rvalue);
442 assert(data);
443
444 r = neighbor_new_static(network, filename, section_line, &n);
445 if (r < 0)
446 return r;
447
448 r = ether_addr_from_string(rvalue, &n->lladdr.mac);
449 if (r >= 0)
450 n->lladdr_size = sizeof(n->lladdr.mac);
451 else {
452 r = in_addr_from_string_auto(rvalue, &family, &n->lladdr.ip);
453 if (r < 0) {
454 log_syntax(unit, LOG_ERR, filename, line, r,
455 "Neighbor LinkLayerAddress= is invalid, ignoring assignment: %s",
456 rvalue);
457 return 0;
458 }
459 n->lladdr_size = family == AF_INET ? sizeof(n->lladdr.ip.in) : sizeof(n->lladdr.ip.in6);
460 }
461
462 TAKE_PTR(n);
463
464 return 0;
465 }
466
467 int config_parse_neighbor_hwaddr(
468 const char *unit,
469 const char *filename,
470 unsigned line,
471 const char *section,
472 unsigned section_line,
473 const char *lvalue,
474 int ltype,
475 const char *rvalue,
476 void *data,
477 void *userdata) {
478
479 Network *network = userdata;
480 _cleanup_(neighbor_free_or_set_invalidp) Neighbor *n = NULL;
481 int r;
482
483 assert(filename);
484 assert(section);
485 assert(lvalue);
486 assert(rvalue);
487 assert(data);
488
489 r = neighbor_new_static(network, filename, section_line, &n);
490 if (r < 0)
491 return r;
492
493 r = ether_addr_from_string(rvalue, &n->lladdr.mac);
494 if (r < 0) {
495 log_syntax(unit, LOG_ERR, filename, line, r,
496 "Neighbor MACAddress= is invalid, ignoring assignment: %s", rvalue);
497 return 0;
498 }
499
500 n->lladdr_size = sizeof(n->lladdr.mac);
501 TAKE_PTR(n);
502
503 return 0;
504 }