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