]>
Commit | Line | Data |
---|---|---|
53e1b683 | 1 | /* SPDX-License-Identifier: LGPL-2.1+ */ |
f579559b TG |
2 | /*** |
3 | This file is part of systemd. | |
4 | ||
5 | Copyright 2013 Tom Gundersen <teg@jklm.no> | |
6 | ||
7 | systemd is free software; you can redistribute it and/or modify it | |
8 | under the terms of the GNU Lesser General Public License as published by | |
9 | the Free Software Foundation; either version 2.1 of the License, or | |
10 | (at your option) any later version. | |
11 | ||
12 | systemd is distributed in the hope that it will be useful, but | |
13 | WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
15 | Lesser General Public License for more details. | |
16 | ||
17 | You should have received a copy of the GNU Lesser General Public License | |
18 | along with systemd; If not, see <http://www.gnu.org/licenses/>. | |
19 | ***/ | |
20 | ||
b5bf6f64 SS |
21 | #include <linux/icmpv6.h> |
22 | ||
b5efdb8a | 23 | #include "alloc-util.h" |
f579559b | 24 | #include "conf-parser.h" |
bb7ae737 | 25 | #include "in-addr-util.h" |
fc2f9534 | 26 | #include "netlink-util.h" |
23f53b99 | 27 | #include "networkd-manager.h" |
6bedfcbb | 28 | #include "networkd-route.h" |
6bedfcbb | 29 | #include "parse-util.h" |
1c8e710c | 30 | #include "set.h" |
07630cea | 31 | #include "string-util.h" |
47d2d30d | 32 | #include "sysctl-util.h" |
07630cea | 33 | #include "util.h" |
f579559b | 34 | |
47d2d30d ZJS |
35 | #define ROUTES_DEFAULT_MAX_PER_FAMILY 4096U |
36 | ||
37 | static unsigned routes_max(void) { | |
38 | static thread_local unsigned cached = 0; | |
39 | ||
40 | _cleanup_free_ char *s4 = NULL, *s6 = NULL; | |
41 | unsigned val4 = ROUTES_DEFAULT_MAX_PER_FAMILY, val6 = ROUTES_DEFAULT_MAX_PER_FAMILY; | |
42 | ||
43 | if (cached > 0) | |
44 | return cached; | |
45 | ||
46 | if (sysctl_read("net/ipv4/route/max_size", &s4) >= 0) { | |
47 | truncate_nl(s4); | |
48 | if (safe_atou(s4, &val4) >= 0 && | |
49 | val4 == 2147483647U) | |
50 | /* This is the default "no limit" value in the kernel */ | |
51 | val4 = ROUTES_DEFAULT_MAX_PER_FAMILY; | |
52 | } | |
53 | ||
54 | if (sysctl_read("net/ipv6/route/max_size", &s6) >= 0) { | |
55 | truncate_nl(s6); | |
56 | (void) safe_atou(s6, &val6); | |
57 | } | |
58 | ||
59 | cached = MAX(ROUTES_DEFAULT_MAX_PER_FAMILY, val4) + | |
60 | MAX(ROUTES_DEFAULT_MAX_PER_FAMILY, val6); | |
61 | return cached; | |
62 | } | |
8c34b963 | 63 | |
ed9e361a | 64 | int route_new(Route **ret) { |
f0213e37 TG |
65 | _cleanup_route_free_ Route *route = NULL; |
66 | ||
67 | route = new0(Route, 1); | |
68 | if (!route) | |
69 | return -ENOMEM; | |
70 | ||
71 | route->family = AF_UNSPEC; | |
72 | route->scope = RT_SCOPE_UNIVERSE; | |
ed9e361a | 73 | route->protocol = RTPROT_UNSPEC; |
983226f3 | 74 | route->type = RTN_UNICAST; |
a0d95bbc | 75 | route->table = RT_TABLE_MAIN; |
f833694d | 76 | route->lifetime = USEC_INFINITY; |
f0213e37 TG |
77 | |
78 | *ret = route; | |
79 | route = NULL; | |
80 | ||
81 | return 0; | |
82 | } | |
83 | ||
f4859fc7 SS |
84 | int route_new_static(Network *network, const char *filename, unsigned section_line, Route **ret) { |
85 | _cleanup_network_config_section_free_ NetworkConfigSection *n = NULL; | |
f579559b | 86 | _cleanup_route_free_ Route *route = NULL; |
f0213e37 | 87 | int r; |
f579559b | 88 | |
8c34b963 LP |
89 | assert(network); |
90 | assert(ret); | |
48317c39 | 91 | assert(!!filename == (section_line > 0)); |
8c34b963 | 92 | |
48317c39 | 93 | if (filename) { |
f4859fc7 SS |
94 | r = network_config_section_new(filename, section_line, &n); |
95 | if (r < 0) | |
96 | return r; | |
97 | ||
98 | route = hashmap_get(network->routes_by_section, n); | |
6ae115c1 TG |
99 | if (route) { |
100 | *ret = route; | |
101 | route = NULL; | |
102 | ||
103 | return 0; | |
104 | } | |
105 | } | |
106 | ||
47d2d30d | 107 | if (network->n_static_routes >= routes_max()) |
8c34b963 LP |
108 | return -E2BIG; |
109 | ||
ed9e361a | 110 | r = route_new(&route); |
f0213e37 TG |
111 | if (r < 0) |
112 | return r; | |
801bd9e8 | 113 | |
ed9e361a | 114 | route->protocol = RTPROT_STATIC; |
f579559b | 115 | |
48317c39 | 116 | if (filename) { |
f4859fc7 | 117 | route->section = n; |
fd45e522 | 118 | n = NULL; |
cacc1dbf | 119 | |
fcc48287 | 120 | r = hashmap_put(network->routes_by_section, route->section, route); |
21b39268 LP |
121 | if (r < 0) |
122 | return r; | |
6ae115c1 TG |
123 | } |
124 | ||
21b39268 | 125 | route->network = network; |
cacc1dbf | 126 | LIST_PREPEND(routes, network->static_routes, route); |
8c34b963 | 127 | network->n_static_routes++; |
21b39268 | 128 | |
f579559b TG |
129 | *ret = route; |
130 | route = NULL; | |
131 | ||
132 | return 0; | |
133 | } | |
134 | ||
135 | void route_free(Route *route) { | |
136 | if (!route) | |
137 | return; | |
138 | ||
f048a16b | 139 | if (route->network) { |
3d3d4255 | 140 | LIST_REMOVE(routes, route->network->static_routes, route); |
f579559b | 141 | |
8c34b963 LP |
142 | assert(route->network->n_static_routes > 0); |
143 | route->network->n_static_routes--; | |
144 | ||
fd45e522 | 145 | if (route->section) |
f4859fc7 | 146 | hashmap_remove(route->network->routes_by_section, route->section); |
f048a16b | 147 | } |
6ae115c1 | 148 | |
fd45e522 ZJS |
149 | network_config_section_free(route->section); |
150 | ||
1c8e710c TG |
151 | if (route->link) { |
152 | set_remove(route->link->routes, route); | |
153 | set_remove(route->link->routes_foreign, route); | |
154 | } | |
155 | ||
f833694d TG |
156 | sd_event_source_unref(route->expire); |
157 | ||
f579559b TG |
158 | free(route); |
159 | } | |
160 | ||
bb7ae737 TG |
161 | static void route_hash_func(const void *b, struct siphash *state) { |
162 | const Route *route = b; | |
163 | ||
164 | assert(route); | |
165 | ||
166 | siphash24_compress(&route->family, sizeof(route->family), state); | |
167 | ||
168 | switch (route->family) { | |
169 | case AF_INET: | |
170 | case AF_INET6: | |
171 | /* Equality of routes are given by the 4-touple | |
172 | (dst_prefix,dst_prefixlen,tos,priority,table) */ | |
2ce40956 | 173 | siphash24_compress(&route->dst, FAMILY_ADDRESS_SIZE(route->family), state); |
bb7ae737 TG |
174 | siphash24_compress(&route->dst_prefixlen, sizeof(route->dst_prefixlen), state); |
175 | siphash24_compress(&route->tos, sizeof(route->tos), state); | |
176 | siphash24_compress(&route->priority, sizeof(route->priority), state); | |
177 | siphash24_compress(&route->table, sizeof(route->table), state); | |
178 | ||
179 | break; | |
180 | default: | |
181 | /* treat any other address family as AF_UNSPEC */ | |
182 | break; | |
183 | } | |
184 | } | |
185 | ||
186 | static int route_compare_func(const void *_a, const void *_b) { | |
187 | const Route *a = _a, *b = _b; | |
188 | ||
189 | if (a->family < b->family) | |
190 | return -1; | |
191 | if (a->family > b->family) | |
192 | return 1; | |
193 | ||
194 | switch (a->family) { | |
195 | case AF_INET: | |
196 | case AF_INET6: | |
bb7ae737 TG |
197 | if (a->dst_prefixlen < b->dst_prefixlen) |
198 | return -1; | |
199 | if (a->dst_prefixlen > b->dst_prefixlen) | |
200 | return 1; | |
201 | ||
202 | if (a->tos < b->tos) | |
203 | return -1; | |
204 | if (a->tos > b->tos) | |
205 | return 1; | |
206 | ||
207 | if (a->priority < b->priority) | |
208 | return -1; | |
209 | if (a->priority > b->priority) | |
210 | return 1; | |
211 | ||
212 | if (a->table < b->table) | |
213 | return -1; | |
214 | if (a->table > b->table) | |
215 | return 1; | |
216 | ||
2ce40956 | 217 | return memcmp(&a->dst, &b->dst, FAMILY_ADDRESS_SIZE(a->family)); |
bb7ae737 TG |
218 | default: |
219 | /* treat any other address family as AF_UNSPEC */ | |
220 | return 0; | |
221 | } | |
222 | } | |
223 | ||
224 | static const struct hash_ops route_hash_ops = { | |
225 | .hash = route_hash_func, | |
226 | .compare = route_compare_func | |
227 | }; | |
228 | ||
1c8e710c TG |
229 | int route_get(Link *link, |
230 | int family, | |
1b566071 | 231 | const union in_addr_union *dst, |
1c8e710c TG |
232 | unsigned char dst_prefixlen, |
233 | unsigned char tos, | |
234 | uint32_t priority, | |
14d20d2b | 235 | uint32_t table, |
1c8e710c | 236 | Route **ret) { |
1b566071 LP |
237 | |
238 | Route route, *existing; | |
239 | ||
240 | assert(link); | |
241 | assert(dst); | |
242 | ||
243 | route = (Route) { | |
1c8e710c | 244 | .family = family, |
1b566071 | 245 | .dst = *dst, |
1c8e710c TG |
246 | .dst_prefixlen = dst_prefixlen, |
247 | .tos = tos, | |
248 | .priority = priority, | |
249 | .table = table, | |
1b566071 | 250 | }; |
1c8e710c TG |
251 | |
252 | existing = set_get(link->routes, &route); | |
253 | if (existing) { | |
1b566071 LP |
254 | if (ret) |
255 | *ret = existing; | |
1c8e710c | 256 | return 1; |
1c8e710c TG |
257 | } |
258 | ||
1b566071 LP |
259 | existing = set_get(link->routes_foreign, &route); |
260 | if (existing) { | |
261 | if (ret) | |
262 | *ret = existing; | |
263 | return 0; | |
264 | } | |
1c8e710c | 265 | |
1b566071 | 266 | return -ENOENT; |
1c8e710c TG |
267 | } |
268 | ||
889b550f LP |
269 | static int route_add_internal( |
270 | Link *link, | |
271 | Set **routes, | |
272 | int family, | |
273 | const union in_addr_union *dst, | |
274 | unsigned char dst_prefixlen, | |
275 | unsigned char tos, | |
276 | uint32_t priority, | |
14d20d2b | 277 | uint32_t table, |
889b550f LP |
278 | Route **ret) { |
279 | ||
1c8e710c TG |
280 | _cleanup_route_free_ Route *route = NULL; |
281 | int r; | |
282 | ||
283 | assert(link); | |
284 | assert(routes); | |
285 | assert(dst); | |
286 | ||
287 | r = route_new(&route); | |
288 | if (r < 0) | |
289 | return r; | |
290 | ||
291 | route->family = family; | |
292 | route->dst = *dst; | |
293 | route->dst_prefixlen = dst_prefixlen; | |
294 | route->tos = tos; | |
295 | route->priority = priority; | |
296 | route->table = table; | |
297 | ||
298 | r = set_ensure_allocated(routes, &route_hash_ops); | |
299 | if (r < 0) | |
300 | return r; | |
301 | ||
302 | r = set_put(*routes, route); | |
303 | if (r < 0) | |
304 | return r; | |
305 | ||
306 | route->link = link; | |
307 | ||
308 | if (ret) | |
309 | *ret = route; | |
310 | ||
311 | route = NULL; | |
312 | ||
313 | return 0; | |
314 | } | |
315 | ||
889b550f LP |
316 | int route_add_foreign( |
317 | Link *link, | |
318 | int family, | |
319 | const union in_addr_union *dst, | |
320 | unsigned char dst_prefixlen, | |
321 | unsigned char tos, | |
322 | uint32_t priority, | |
14d20d2b | 323 | uint32_t table, |
889b550f LP |
324 | Route **ret) { |
325 | ||
1c8e710c TG |
326 | return route_add_internal(link, &link->routes_foreign, family, dst, dst_prefixlen, tos, priority, table, ret); |
327 | } | |
328 | ||
889b550f LP |
329 | int route_add( |
330 | Link *link, | |
1c8e710c | 331 | int family, |
889b550f | 332 | const union in_addr_union *dst, |
1c8e710c TG |
333 | unsigned char dst_prefixlen, |
334 | unsigned char tos, | |
335 | uint32_t priority, | |
14d20d2b | 336 | uint32_t table, |
889b550f LP |
337 | Route **ret) { |
338 | ||
1c8e710c TG |
339 | Route *route; |
340 | int r; | |
341 | ||
342 | r = route_get(link, family, dst, dst_prefixlen, tos, priority, table, &route); | |
343 | if (r == -ENOENT) { | |
344 | /* Route does not exist, create a new one */ | |
345 | r = route_add_internal(link, &link->routes, family, dst, dst_prefixlen, tos, priority, table, &route); | |
346 | if (r < 0) | |
347 | return r; | |
348 | } else if (r == 0) { | |
349 | /* Take over a foreign route */ | |
350 | r = set_ensure_allocated(&link->routes, &route_hash_ops); | |
351 | if (r < 0) | |
352 | return r; | |
353 | ||
354 | r = set_put(link->routes, route); | |
355 | if (r < 0) | |
356 | return r; | |
357 | ||
358 | set_remove(link->routes_foreign, route); | |
359 | } else if (r == 1) { | |
360 | /* Route exists, do nothing */ | |
361 | ; | |
362 | } else | |
363 | return r; | |
364 | ||
856e309d MC |
365 | if (ret) |
366 | *ret = route; | |
1c8e710c TG |
367 | |
368 | return 0; | |
369 | } | |
370 | ||
371 | int route_update(Route *route, | |
889b550f | 372 | const union in_addr_union *src, |
1c8e710c | 373 | unsigned char src_prefixlen, |
889b550f LP |
374 | const union in_addr_union *gw, |
375 | const union in_addr_union *prefsrc, | |
1c8e710c | 376 | unsigned char scope, |
983226f3 SS |
377 | unsigned char protocol, |
378 | unsigned char type) { | |
889b550f | 379 | |
1c8e710c TG |
380 | assert(route); |
381 | assert(src); | |
382 | assert(gw); | |
383 | assert(prefsrc); | |
384 | ||
385 | route->src = *src; | |
386 | route->src_prefixlen = src_prefixlen; | |
387 | route->gw = *gw; | |
388 | route->prefsrc = *prefsrc; | |
389 | route->scope = scope; | |
390 | route->protocol = protocol; | |
983226f3 | 391 | route->type = type; |
1c8e710c TG |
392 | |
393 | return 0; | |
394 | } | |
395 | ||
91b5f997 | 396 | int route_remove(Route *route, Link *link, |
1c4baffc | 397 | sd_netlink_message_handler_t callback) { |
4afd3348 | 398 | _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; |
5c1d3fc9 UTL |
399 | int r; |
400 | ||
401 | assert(link); | |
402 | assert(link->manager); | |
403 | assert(link->manager->rtnl); | |
404 | assert(link->ifindex > 0); | |
4c701096 | 405 | assert(IN_SET(route->family, AF_INET, AF_INET6)); |
5c1d3fc9 UTL |
406 | |
407 | r = sd_rtnl_message_new_route(link->manager->rtnl, &req, | |
28cc555d DW |
408 | RTM_DELROUTE, route->family, |
409 | route->protocol); | |
f647962d MS |
410 | if (r < 0) |
411 | return log_error_errno(r, "Could not create RTM_DELROUTE message: %m"); | |
5c1d3fc9 | 412 | |
2ce40956 | 413 | if (!in_addr_is_null(route->family, &route->gw)) { |
59580681 | 414 | if (route->family == AF_INET) |
2ce40956 | 415 | r = sd_netlink_message_append_in_addr(req, RTA_GATEWAY, &route->gw.in); |
59580681 | 416 | else if (route->family == AF_INET6) |
2ce40956 | 417 | r = sd_netlink_message_append_in6_addr(req, RTA_GATEWAY, &route->gw.in6); |
f647962d MS |
418 | if (r < 0) |
419 | return log_error_errno(r, "Could not append RTA_GATEWAY attribute: %m"); | |
5c1d3fc9 UTL |
420 | } |
421 | ||
422 | if (route->dst_prefixlen) { | |
423 | if (route->family == AF_INET) | |
2ce40956 | 424 | r = sd_netlink_message_append_in_addr(req, RTA_DST, &route->dst.in); |
5c1d3fc9 | 425 | else if (route->family == AF_INET6) |
2ce40956 | 426 | r = sd_netlink_message_append_in6_addr(req, RTA_DST, &route->dst.in6); |
f647962d MS |
427 | if (r < 0) |
428 | return log_error_errno(r, "Could not append RTA_DST attribute: %m"); | |
5c1d3fc9 UTL |
429 | |
430 | r = sd_rtnl_message_route_set_dst_prefixlen(req, route->dst_prefixlen); | |
f647962d MS |
431 | if (r < 0) |
432 | return log_error_errno(r, "Could not set destination prefix length: %m"); | |
5c1d3fc9 UTL |
433 | } |
434 | ||
9e7e4408 TG |
435 | if (route->src_prefixlen) { |
436 | if (route->family == AF_INET) | |
2ce40956 | 437 | r = sd_netlink_message_append_in_addr(req, RTA_SRC, &route->src.in); |
9e7e4408 | 438 | else if (route->family == AF_INET6) |
2ce40956 | 439 | r = sd_netlink_message_append_in6_addr(req, RTA_SRC, &route->src.in6); |
9e7e4408 | 440 | if (r < 0) |
d9d94393 | 441 | return log_error_errno(r, "Could not append RTA_SRC attribute: %m"); |
9e7e4408 TG |
442 | |
443 | r = sd_rtnl_message_route_set_src_prefixlen(req, route->src_prefixlen); | |
444 | if (r < 0) | |
445 | return log_error_errno(r, "Could not set source prefix length: %m"); | |
446 | } | |
447 | ||
2ce40956 | 448 | if (!in_addr_is_null(route->family, &route->prefsrc)) { |
46b0c76e | 449 | if (route->family == AF_INET) |
2ce40956 | 450 | r = sd_netlink_message_append_in_addr(req, RTA_PREFSRC, &route->prefsrc.in); |
46b0c76e | 451 | else if (route->family == AF_INET6) |
2ce40956 | 452 | r = sd_netlink_message_append_in6_addr(req, RTA_PREFSRC, &route->prefsrc.in6); |
f647962d MS |
453 | if (r < 0) |
454 | return log_error_errno(r, "Could not append RTA_PREFSRC attribute: %m"); | |
46b0c76e ERB |
455 | } |
456 | ||
5c1d3fc9 | 457 | r = sd_rtnl_message_route_set_scope(req, route->scope); |
f647962d MS |
458 | if (r < 0) |
459 | return log_error_errno(r, "Could not set scope: %m"); | |
5c1d3fc9 | 460 | |
86655331 | 461 | r = sd_netlink_message_append_u32(req, RTA_PRIORITY, route->priority); |
f647962d MS |
462 | if (r < 0) |
463 | return log_error_errno(r, "Could not append RTA_PRIORITY attribute: %m"); | |
5c1d3fc9 | 464 | |
983226f3 SS |
465 | if (!IN_SET(route->type, RTN_UNREACHABLE, RTN_PROHIBIT, RTN_BLACKHOLE)) { |
466 | r = sd_netlink_message_append_u32(req, RTA_OIF, link->ifindex); | |
467 | if (r < 0) | |
468 | return log_error_errno(r, "Could not append RTA_OIF attribute: %m"); | |
469 | } | |
5c1d3fc9 | 470 | |
1c4baffc | 471 | r = sd_netlink_call_async(link->manager->rtnl, req, callback, link, 0, NULL); |
f647962d MS |
472 | if (r < 0) |
473 | return log_error_errno(r, "Could not send rtnetlink message: %m"); | |
5c1d3fc9 | 474 | |
563c69c6 TG |
475 | link_ref(link); |
476 | ||
5c1d3fc9 UTL |
477 | return 0; |
478 | } | |
479 | ||
fe7ca21a SS |
480 | static int route_expire_callback(sd_netlink *rtnl, sd_netlink_message *m, void *userdata) { |
481 | Link *link = userdata; | |
482 | int r; | |
483 | ||
484 | assert(rtnl); | |
485 | assert(m); | |
486 | assert(link); | |
487 | assert(link->ifname); | |
fe7ca21a SS |
488 | |
489 | if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) | |
490 | return 1; | |
491 | ||
fe7ca21a SS |
492 | r = sd_netlink_message_get_errno(m); |
493 | if (r < 0 && r != -EEXIST) | |
494 | log_link_warning_errno(link, r, "could not remove route: %m"); | |
495 | ||
fe7ca21a SS |
496 | return 1; |
497 | } | |
498 | ||
f833694d TG |
499 | int route_expire_handler(sd_event_source *s, uint64_t usec, void *userdata) { |
500 | Route *route = userdata; | |
501 | int r; | |
502 | ||
503 | assert(route); | |
504 | ||
fe7ca21a | 505 | r = route_remove(route, route->link, route_expire_callback); |
f833694d TG |
506 | if (r < 0) |
507 | log_warning_errno(r, "Could not remove route: %m"); | |
3bdccf69 | 508 | else |
fe7ca21a | 509 | route_free(route); |
f833694d TG |
510 | |
511 | return 1; | |
512 | } | |
513 | ||
1b566071 LP |
514 | int route_configure( |
515 | Route *route, | |
516 | Link *link, | |
517 | sd_netlink_message_handler_t callback) { | |
518 | ||
4afd3348 LP |
519 | _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; |
520 | _cleanup_(sd_event_source_unrefp) sd_event_source *expire = NULL; | |
f833694d | 521 | usec_t lifetime; |
f579559b TG |
522 | int r; |
523 | ||
f579559b | 524 | assert(link); |
f882c247 TG |
525 | assert(link->manager); |
526 | assert(link->manager->rtnl); | |
f579559b | 527 | assert(link->ifindex > 0); |
4c701096 | 528 | assert(IN_SET(route->family, AF_INET, AF_INET6)); |
f579559b | 529 | |
1b566071 | 530 | if (route_get(link, route->family, &route->dst, route->dst_prefixlen, route->tos, route->priority, route->table, NULL) <= 0 && |
47d2d30d | 531 | set_size(link->routes) >= routes_max()) |
1b566071 LP |
532 | return -E2BIG; |
533 | ||
151b9b96 | 534 | r = sd_rtnl_message_new_route(link->manager->rtnl, &req, |
28cc555d DW |
535 | RTM_NEWROUTE, route->family, |
536 | route->protocol); | |
f647962d MS |
537 | if (r < 0) |
538 | return log_error_errno(r, "Could not create RTM_NEWROUTE message: %m"); | |
f579559b | 539 | |
2ce40956 | 540 | if (!in_addr_is_null(route->family, &route->gw)) { |
59580681 | 541 | if (route->family == AF_INET) |
2ce40956 | 542 | r = sd_netlink_message_append_in_addr(req, RTA_GATEWAY, &route->gw.in); |
59580681 | 543 | else if (route->family == AF_INET6) |
2ce40956 | 544 | r = sd_netlink_message_append_in6_addr(req, RTA_GATEWAY, &route->gw.in6); |
f647962d MS |
545 | if (r < 0) |
546 | return log_error_errno(r, "Could not append RTA_GATEWAY attribute: %m"); | |
c953b24c SS |
547 | |
548 | r = sd_rtnl_message_route_set_family(req, route->family); | |
549 | if (r < 0) | |
550 | return log_error_errno(r, "Could not set route family: %m"); | |
f579559b TG |
551 | } |
552 | ||
0a0dc69b TG |
553 | if (route->dst_prefixlen) { |
554 | if (route->family == AF_INET) | |
2ce40956 | 555 | r = sd_netlink_message_append_in_addr(req, RTA_DST, &route->dst.in); |
0a0dc69b | 556 | else if (route->family == AF_INET6) |
2ce40956 | 557 | r = sd_netlink_message_append_in6_addr(req, RTA_DST, &route->dst.in6); |
f647962d MS |
558 | if (r < 0) |
559 | return log_error_errno(r, "Could not append RTA_DST attribute: %m"); | |
6ae115c1 | 560 | |
ae4c67a7 | 561 | r = sd_rtnl_message_route_set_dst_prefixlen(req, route->dst_prefixlen); |
f647962d MS |
562 | if (r < 0) |
563 | return log_error_errno(r, "Could not set destination prefix length: %m"); | |
1f01fb4f TG |
564 | } |
565 | ||
9e7e4408 TG |
566 | if (route->src_prefixlen) { |
567 | if (route->family == AF_INET) | |
2ce40956 | 568 | r = sd_netlink_message_append_in_addr(req, RTA_SRC, &route->src.in); |
9e7e4408 | 569 | else if (route->family == AF_INET6) |
2ce40956 | 570 | r = sd_netlink_message_append_in6_addr(req, RTA_SRC, &route->src.in6); |
9e7e4408 TG |
571 | if (r < 0) |
572 | return log_error_errno(r, "Could not append RTA_SRC attribute: %m"); | |
573 | ||
574 | r = sd_rtnl_message_route_set_src_prefixlen(req, route->src_prefixlen); | |
575 | if (r < 0) | |
576 | return log_error_errno(r, "Could not set source prefix length: %m"); | |
577 | } | |
578 | ||
2ce40956 | 579 | if (!in_addr_is_null(route->family, &route->prefsrc)) { |
46b0c76e | 580 | if (route->family == AF_INET) |
2ce40956 | 581 | r = sd_netlink_message_append_in_addr(req, RTA_PREFSRC, &route->prefsrc.in); |
46b0c76e | 582 | else if (route->family == AF_INET6) |
2ce40956 | 583 | r = sd_netlink_message_append_in6_addr(req, RTA_PREFSRC, &route->prefsrc.in6); |
f647962d MS |
584 | if (r < 0) |
585 | return log_error_errno(r, "Could not append RTA_PREFSRC attribute: %m"); | |
46b0c76e ERB |
586 | } |
587 | ||
5c1d3fc9 | 588 | r = sd_rtnl_message_route_set_scope(req, route->scope); |
f647962d MS |
589 | if (r < 0) |
590 | return log_error_errno(r, "Could not set scope: %m"); | |
5c1d3fc9 | 591 | |
3b015d40 TG |
592 | r = sd_rtnl_message_route_set_flags(req, route->flags); |
593 | if (r < 0) | |
c953b24c SS |
594 | return log_error_errno(r, "Could not set flags: %m"); |
595 | ||
a0d95bbc | 596 | if (route->table != RT_TABLE_MAIN) { |
c953b24c SS |
597 | if (route->table < 256) { |
598 | r = sd_rtnl_message_route_set_table(req, route->table); | |
599 | if (r < 0) | |
600 | return log_error_errno(r, "Could not set route table: %m"); | |
601 | } else { | |
c953b24c SS |
602 | r = sd_rtnl_message_route_set_table(req, RT_TABLE_UNSPEC); |
603 | if (r < 0) | |
604 | return log_error_errno(r, "Could not set route table: %m"); | |
605 | ||
06976f5b | 606 | /* Table attribute to allow more than 256. */ |
c953b24c SS |
607 | r = sd_netlink_message_append_data(req, RTA_TABLE, &route->table, sizeof(route->table)); |
608 | if (r < 0) | |
609 | return log_error_errno(r, "Could not append RTA_TABLE attribute: %m"); | |
610 | } | |
611 | } | |
3b015d40 | 612 | |
86655331 | 613 | r = sd_netlink_message_append_u32(req, RTA_PRIORITY, route->priority); |
f647962d MS |
614 | if (r < 0) |
615 | return log_error_errno(r, "Could not append RTA_PRIORITY attribute: %m"); | |
5c1d3fc9 | 616 | |
3b015d40 TG |
617 | r = sd_netlink_message_append_u8(req, RTA_PREF, route->pref); |
618 | if (r < 0) | |
619 | return log_error_errno(r, "Could not append RTA_PREF attribute: %m"); | |
620 | ||
983226f3 | 621 | r = sd_rtnl_message_route_set_type(req, route->type); |
f647962d | 622 | if (r < 0) |
983226f3 SS |
623 | return log_error_errno(r, "Could not set route type: %m"); |
624 | ||
625 | if (!IN_SET(route->type, RTN_UNREACHABLE, RTN_PROHIBIT, RTN_BLACKHOLE)) { | |
626 | r = sd_netlink_message_append_u32(req, RTA_OIF, link->ifindex); | |
627 | if (r < 0) | |
628 | return log_error_errno(r, "Could not append RTA_OIF attribute: %m"); | |
629 | } | |
f579559b | 630 | |
d6fceaf1 SS |
631 | r = sd_netlink_message_open_container(req, RTA_METRICS); |
632 | if (r < 0) | |
633 | return log_error_errno(r, "Could not append RTA_METRICS attribute: %m"); | |
634 | ||
635 | if (route->mtu > 0) { | |
636 | r = sd_netlink_message_append_u32(req, RTAX_MTU, route->mtu); | |
637 | if (r < 0) | |
638 | return log_error_errno(r, "Could not append RTAX_MTU attribute: %m"); | |
639 | } | |
640 | ||
641 | r = sd_netlink_message_close_container(req); | |
642 | if (r < 0) | |
643 | return log_error_errno(r, "Could not append RTA_METRICS attribute: %m"); | |
644 | ||
1c4baffc | 645 | r = sd_netlink_call_async(link->manager->rtnl, req, callback, link, 0, NULL); |
f647962d MS |
646 | if (r < 0) |
647 | return log_error_errno(r, "Could not send rtnetlink message: %m"); | |
f579559b | 648 | |
563c69c6 TG |
649 | link_ref(link); |
650 | ||
f833694d TG |
651 | lifetime = route->lifetime; |
652 | ||
653 | r = route_add(link, route->family, &route->dst, route->dst_prefixlen, route->tos, route->priority, route->table, &route); | |
1c8e710c TG |
654 | if (r < 0) |
655 | return log_error_errno(r, "Could not add route: %m"); | |
656 | ||
f833694d TG |
657 | /* TODO: drop expiration handling once it can be pushed into the kernel */ |
658 | route->lifetime = lifetime; | |
659 | ||
660 | if (route->lifetime != USEC_INFINITY) { | |
661 | r = sd_event_add_time(link->manager->event, &expire, clock_boottime_or_monotonic(), | |
662 | route->lifetime, 0, route_expire_handler, route); | |
663 | if (r < 0) | |
664 | return log_error_errno(r, "Could not arm expiration timer: %m"); | |
665 | } | |
666 | ||
667 | sd_event_source_unref(route->expire); | |
668 | route->expire = expire; | |
669 | expire = NULL; | |
670 | ||
f579559b TG |
671 | return 0; |
672 | } | |
673 | ||
674 | int config_parse_gateway(const char *unit, | |
675 | const char *filename, | |
676 | unsigned line, | |
677 | const char *section, | |
71a61510 | 678 | unsigned section_line, |
f579559b TG |
679 | const char *lvalue, |
680 | int ltype, | |
681 | const char *rvalue, | |
682 | void *data, | |
683 | void *userdata) { | |
44e7b949 | 684 | |
6ae115c1 | 685 | Network *network = userdata; |
f579559b | 686 | _cleanup_route_free_ Route *n = NULL; |
44e7b949 LP |
687 | union in_addr_union buffer; |
688 | int r, f; | |
f579559b TG |
689 | |
690 | assert(filename); | |
6ae115c1 | 691 | assert(section); |
f579559b TG |
692 | assert(lvalue); |
693 | assert(rvalue); | |
694 | assert(data); | |
695 | ||
92fe133a TG |
696 | if (streq(section, "Network")) { |
697 | /* we are not in an Route section, so treat | |
698 | * this as the special '0' section */ | |
f4859fc7 SS |
699 | r = route_new_static(network, NULL, 0, &n); |
700 | } else | |
701 | r = route_new_static(network, filename, section_line, &n); | |
92fe133a | 702 | |
f579559b TG |
703 | if (r < 0) |
704 | return r; | |
705 | ||
44e7b949 | 706 | r = in_addr_from_string_auto(rvalue, &f, &buffer); |
f579559b | 707 | if (r < 0) { |
12ca818f | 708 | log_syntax(unit, LOG_ERR, filename, line, r, "Route is invalid, ignoring assignment: %s", rvalue); |
f579559b TG |
709 | return 0; |
710 | } | |
711 | ||
44e7b949 | 712 | n->family = f; |
2ce40956 | 713 | n->gw = buffer; |
f579559b TG |
714 | n = NULL; |
715 | ||
716 | return 0; | |
717 | } | |
6ae115c1 | 718 | |
0d07e595 JK |
719 | int config_parse_preferred_src(const char *unit, |
720 | const char *filename, | |
721 | unsigned line, | |
722 | const char *section, | |
723 | unsigned section_line, | |
724 | const char *lvalue, | |
725 | int ltype, | |
726 | const char *rvalue, | |
727 | void *data, | |
728 | void *userdata) { | |
729 | ||
730 | Network *network = userdata; | |
731 | _cleanup_route_free_ Route *n = NULL; | |
732 | union in_addr_union buffer; | |
733 | int r, f; | |
734 | ||
735 | assert(filename); | |
736 | assert(section); | |
737 | assert(lvalue); | |
738 | assert(rvalue); | |
739 | assert(data); | |
740 | ||
f4859fc7 | 741 | r = route_new_static(network, filename, section_line, &n); |
0d07e595 JK |
742 | if (r < 0) |
743 | return r; | |
744 | ||
745 | r = in_addr_from_string_auto(rvalue, &f, &buffer); | |
746 | if (r < 0) { | |
747 | log_syntax(unit, LOG_ERR, filename, line, EINVAL, | |
748 | "Preferred source is invalid, ignoring assignment: %s", rvalue); | |
749 | return 0; | |
750 | } | |
751 | ||
752 | n->family = f; | |
2ce40956 | 753 | n->prefsrc = buffer; |
0d07e595 JK |
754 | n = NULL; |
755 | ||
756 | return 0; | |
757 | } | |
758 | ||
6ae115c1 TG |
759 | int config_parse_destination(const char *unit, |
760 | const char *filename, | |
761 | unsigned line, | |
762 | const char *section, | |
763 | unsigned section_line, | |
764 | const char *lvalue, | |
765 | int ltype, | |
766 | const char *rvalue, | |
767 | void *data, | |
768 | void *userdata) { | |
44e7b949 | 769 | |
6ae115c1 TG |
770 | Network *network = userdata; |
771 | _cleanup_route_free_ Route *n = NULL; | |
44e7b949 | 772 | union in_addr_union buffer; |
9e7e4408 | 773 | unsigned char prefixlen; |
ca3bad65 | 774 | int r; |
6ae115c1 TG |
775 | |
776 | assert(filename); | |
777 | assert(section); | |
778 | assert(lvalue); | |
779 | assert(rvalue); | |
780 | assert(data); | |
781 | ||
f4859fc7 | 782 | r = route_new_static(network, filename, section_line, &n); |
6ae115c1 TG |
783 | if (r < 0) |
784 | return r; | |
785 | ||
36423ff4 | 786 | r = in_addr_prefix_from_string(rvalue, AF_INET, &buffer, &prefixlen); |
6ae115c1 | 787 | if (r < 0) { |
36423ff4 | 788 | r = in_addr_prefix_from_string(rvalue, AF_INET6, &buffer, &prefixlen); |
ae4c67a7 | 789 | if (r < 0) { |
d84ed2bd ZJS |
790 | log_syntax(unit, LOG_ERR, filename, line, r, |
791 | "Route %s= prefix is invalid, ignoring assignment: %s", | |
792 | lvalue, rvalue); | |
ae4c67a7 TG |
793 | return 0; |
794 | } | |
ae4c67a7 | 795 | |
36423ff4 SS |
796 | n->family = AF_INET6; |
797 | } else | |
798 | n->family = AF_INET; | |
799 | ||
9e7e4408 | 800 | if (streq(lvalue, "Destination")) { |
2ce40956 | 801 | n->dst = buffer; |
9e7e4408 TG |
802 | n->dst_prefixlen = prefixlen; |
803 | } else if (streq(lvalue, "Source")) { | |
2ce40956 | 804 | n->src = buffer; |
9e7e4408 TG |
805 | n->src_prefixlen = prefixlen; |
806 | } else | |
807 | assert_not_reached(lvalue); | |
808 | ||
6ae115c1 TG |
809 | n = NULL; |
810 | ||
811 | return 0; | |
812 | } | |
5d8e593d SS |
813 | |
814 | int config_parse_route_priority(const char *unit, | |
815 | const char *filename, | |
816 | unsigned line, | |
817 | const char *section, | |
818 | unsigned section_line, | |
819 | const char *lvalue, | |
820 | int ltype, | |
821 | const char *rvalue, | |
822 | void *data, | |
823 | void *userdata) { | |
824 | Network *network = userdata; | |
825 | _cleanup_route_free_ Route *n = NULL; | |
1c4b1179 | 826 | uint32_t k; |
5d8e593d SS |
827 | int r; |
828 | ||
829 | assert(filename); | |
830 | assert(section); | |
831 | assert(lvalue); | |
832 | assert(rvalue); | |
833 | assert(data); | |
834 | ||
f4859fc7 | 835 | r = route_new_static(network, filename, section_line, &n); |
5d8e593d SS |
836 | if (r < 0) |
837 | return r; | |
838 | ||
1c4b1179 SS |
839 | r = safe_atou32(rvalue, &k); |
840 | if (r < 0) { | |
841 | log_syntax(unit, LOG_ERR, filename, line, r, | |
842 | "Could not parse route priority \"%s\", ignoring assignment: %m", rvalue); | |
843 | return 0; | |
844 | } | |
5d8e593d | 845 | |
1c4b1179 | 846 | n->priority = k; |
5d8e593d SS |
847 | n = NULL; |
848 | ||
849 | return 0; | |
850 | } | |
769b56a3 TG |
851 | |
852 | int config_parse_route_scope(const char *unit, | |
853 | const char *filename, | |
854 | unsigned line, | |
855 | const char *section, | |
856 | unsigned section_line, | |
857 | const char *lvalue, | |
858 | int ltype, | |
859 | const char *rvalue, | |
860 | void *data, | |
861 | void *userdata) { | |
862 | Network *network = userdata; | |
863 | _cleanup_route_free_ Route *n = NULL; | |
864 | int r; | |
865 | ||
866 | assert(filename); | |
867 | assert(section); | |
868 | assert(lvalue); | |
869 | assert(rvalue); | |
870 | assert(data); | |
871 | ||
f4859fc7 | 872 | r = route_new_static(network, filename, section_line, &n); |
769b56a3 TG |
873 | if (r < 0) |
874 | return r; | |
875 | ||
876 | if (streq(rvalue, "host")) | |
877 | n->scope = RT_SCOPE_HOST; | |
878 | else if (streq(rvalue, "link")) | |
879 | n->scope = RT_SCOPE_LINK; | |
880 | else if (streq(rvalue, "global")) | |
881 | n->scope = RT_SCOPE_UNIVERSE; | |
882 | else { | |
12ca818f | 883 | log_syntax(unit, LOG_ERR, filename, line, 0, "Unknown route scope: %s", rvalue); |
769b56a3 TG |
884 | return 0; |
885 | } | |
886 | ||
887 | n = NULL; | |
888 | ||
889 | return 0; | |
890 | } | |
c953b24c SS |
891 | |
892 | int config_parse_route_table(const char *unit, | |
893 | const char *filename, | |
894 | unsigned line, | |
895 | const char *section, | |
896 | unsigned section_line, | |
897 | const char *lvalue, | |
898 | int ltype, | |
899 | const char *rvalue, | |
900 | void *data, | |
901 | void *userdata) { | |
902 | _cleanup_route_free_ Route *n = NULL; | |
903 | Network *network = userdata; | |
904 | uint32_t k; | |
905 | int r; | |
906 | ||
907 | assert(filename); | |
908 | assert(section); | |
909 | assert(lvalue); | |
910 | assert(rvalue); | |
911 | assert(data); | |
912 | ||
f4859fc7 | 913 | r = route_new_static(network, filename, section_line, &n); |
c953b24c SS |
914 | if (r < 0) |
915 | return r; | |
916 | ||
917 | r = safe_atou32(rvalue, &k); | |
918 | if (r < 0) { | |
919 | log_syntax(unit, LOG_ERR, filename, line, r, | |
920 | "Could not parse route table number \"%s\", ignoring assignment: %m", rvalue); | |
921 | return 0; | |
922 | } | |
923 | ||
924 | n->table = k; | |
925 | ||
926 | n = NULL; | |
927 | ||
928 | return 0; | |
929 | } | |
28959f7d SS |
930 | |
931 | int config_parse_gateway_onlink(const char *unit, | |
932 | const char *filename, | |
933 | unsigned line, | |
934 | const char *section, | |
935 | unsigned section_line, | |
936 | const char *lvalue, | |
937 | int ltype, | |
938 | const char *rvalue, | |
939 | void *data, | |
940 | void *userdata) { | |
941 | Network *network = userdata; | |
942 | _cleanup_route_free_ Route *n = NULL; | |
943 | int r; | |
944 | ||
945 | assert(filename); | |
946 | assert(section); | |
947 | assert(lvalue); | |
948 | assert(rvalue); | |
949 | assert(data); | |
950 | ||
951 | r = route_new_static(network, filename, section_line, &n); | |
952 | if (r < 0) | |
953 | return r; | |
954 | ||
955 | r = parse_boolean(rvalue); | |
956 | if (r < 0) { | |
957 | log_syntax(unit, LOG_ERR, filename, line, r, | |
958 | "Could not parse gateway onlink \"%s\", ignoring assignment: %m", rvalue); | |
959 | return 0; | |
960 | } | |
961 | ||
ab8ee0f2 | 962 | SET_FLAG(n->flags, RTNH_F_ONLINK, r); |
b5bf6f64 SS |
963 | n = NULL; |
964 | ||
965 | return 0; | |
966 | } | |
967 | ||
968 | int config_parse_ipv6_route_preference(const char *unit, | |
969 | const char *filename, | |
970 | unsigned line, | |
971 | const char *section, | |
972 | unsigned section_line, | |
973 | const char *lvalue, | |
974 | int ltype, | |
975 | const char *rvalue, | |
976 | void *data, | |
977 | void *userdata) { | |
978 | Network *network = userdata; | |
979 | _cleanup_route_free_ Route *n = NULL; | |
980 | int r; | |
981 | ||
4c7bd9cf SS |
982 | r = route_new_static(network, filename, section_line, &n); |
983 | if (r < 0) | |
984 | return r; | |
985 | ||
b5bf6f64 SS |
986 | if (streq(rvalue, "low")) |
987 | n->pref = ICMPV6_ROUTER_PREF_LOW; | |
988 | else if (streq(rvalue, "medium")) | |
989 | n->pref = ICMPV6_ROUTER_PREF_MEDIUM; | |
990 | else if (streq(rvalue, "high")) | |
991 | n->pref = ICMPV6_ROUTER_PREF_HIGH; | |
992 | else { | |
993 | log_syntax(unit, LOG_ERR, filename, line, 0, "Unknown route preference: %s", rvalue); | |
994 | return 0; | |
995 | } | |
28959f7d SS |
996 | |
997 | n = NULL; | |
998 | ||
999 | return 0; | |
1000 | } | |
c83ecc04 SS |
1001 | |
1002 | int config_parse_route_protocol(const char *unit, | |
1003 | const char *filename, | |
1004 | unsigned line, | |
1005 | const char *section, | |
1006 | unsigned section_line, | |
1007 | const char *lvalue, | |
1008 | int ltype, | |
1009 | const char *rvalue, | |
1010 | void *data, | |
1011 | void *userdata) { | |
1012 | Network *network = userdata; | |
1013 | _cleanup_route_free_ Route *n = NULL; | |
1014 | int r; | |
1015 | ||
1016 | r = route_new_static(network, filename, section_line, &n); | |
1017 | if (r < 0) | |
1018 | return r; | |
1019 | ||
1020 | if (streq(rvalue, "kernel")) | |
1021 | n->protocol = RTPROT_KERNEL; | |
1022 | else if (streq(rvalue, "boot")) | |
1023 | n->protocol = RTPROT_BOOT; | |
1024 | else if (streq(rvalue, "static")) | |
1025 | n->protocol = RTPROT_STATIC; | |
1026 | else { | |
1027 | r = safe_atou8(rvalue , &n->protocol); | |
1028 | if (r < 0) { | |
1029 | log_syntax(unit, LOG_ERR, filename, line, r, "Could not parse route protocol \"%s\", ignoring assignment: %m", rvalue); | |
1030 | return 0; | |
1031 | } | |
1032 | } | |
1033 | ||
1034 | n = NULL; | |
1035 | ||
1036 | return 0; | |
1037 | } | |
983226f3 SS |
1038 | |
1039 | int config_parse_route_type(const char *unit, | |
1040 | const char *filename, | |
1041 | unsigned line, | |
1042 | const char *section, | |
1043 | unsigned section_line, | |
1044 | const char *lvalue, | |
1045 | int ltype, | |
1046 | const char *rvalue, | |
1047 | void *data, | |
1048 | void *userdata) { | |
1049 | Network *network = userdata; | |
1050 | _cleanup_route_free_ Route *n = NULL; | |
1051 | int r; | |
1052 | ||
1053 | r = route_new_static(network, filename, section_line, &n); | |
1054 | if (r < 0) | |
1055 | return r; | |
1056 | ||
1057 | if (streq(rvalue, "unicast")) | |
1058 | n->type = RTN_UNICAST; | |
1059 | else if (streq(rvalue, "blackhole")) | |
1060 | n->type = RTN_BLACKHOLE; | |
1061 | else if (streq(rvalue, "unreachable")) | |
1062 | n->type = RTN_UNREACHABLE; | |
1063 | else if (streq(rvalue, "prohibit")) | |
1064 | n->type = RTN_PROHIBIT; | |
1065 | else { | |
1066 | log_syntax(unit, LOG_ERR, filename, line, r, "Could not parse route type \"%s\", ignoring assignment: %m", rvalue); | |
1067 | return 0; | |
1068 | } | |
1069 | ||
1070 | n = NULL; | |
1071 | ||
1072 | return 0; | |
1073 | } |