]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
5e0534f1 YW |
2 | |
3 | #include <netinet/in.h> | |
4 | #include <linux/if.h> | |
5 | ||
6 | #include "missing_network.h" | |
7 | #include "networkd-link.h" | |
8 | #include "networkd-network.h" | |
9 | #include "networkd-sysctl.h" | |
10 | #include "socket-util.h" | |
11 | #include "string-table.h" | |
12 | #include "sysctl-util.h" | |
13 | ||
14 | static int link_update_ipv6_sysctl(Link *link) { | |
15 | assert(link); | |
16 | ||
17 | if (link->flags & IFF_LOOPBACK) | |
18 | return 0; | |
19 | ||
20 | if (!link_ipv6_enabled(link)) | |
21 | return 0; | |
22 | ||
23 | return sysctl_write_ip_property_boolean(AF_INET6, link->ifname, "disable_ipv6", false); | |
24 | } | |
25 | ||
26 | static int link_set_proxy_arp(Link *link) { | |
27 | assert(link); | |
28 | ||
29 | if (link->flags & IFF_LOOPBACK) | |
30 | return 0; | |
31 | ||
32 | if (!link->network) | |
33 | return 0; | |
34 | ||
35 | if (link->network->proxy_arp < 0) | |
36 | return 0; | |
37 | ||
38 | return sysctl_write_ip_property_boolean(AF_INET, link->ifname, "proxy_arp", link->network->proxy_arp > 0); | |
39 | } | |
40 | ||
3773eb54 | 41 | static bool link_ip_forward_enabled(Link *link, int family) { |
5e0534f1 YW |
42 | assert(link); |
43 | assert(IN_SET(family, AF_INET, AF_INET6)); | |
44 | ||
45 | if (family == AF_INET6 && !socket_ipv6_is_supported()) | |
46 | return false; | |
47 | ||
48 | if (link->flags & IFF_LOOPBACK) | |
49 | return false; | |
50 | ||
51 | if (!link->network) | |
52 | return false; | |
53 | ||
54 | return link->network->ip_forward & (family == AF_INET ? ADDRESS_FAMILY_IPV4 : ADDRESS_FAMILY_IPV6); | |
55 | } | |
56 | ||
57 | static int link_set_ipv4_forward(Link *link) { | |
58 | assert(link); | |
59 | ||
60 | if (!link_ip_forward_enabled(link, AF_INET)) | |
61 | return 0; | |
62 | ||
63 | /* We propagate the forwarding flag from one interface to the | |
64 | * global setting one way. This means: as long as at least one | |
65 | * interface was configured at any time that had IP forwarding | |
66 | * enabled the setting will stay on for good. We do this | |
67 | * primarily to keep IPv4 and IPv6 packet forwarding behaviour | |
68 | * somewhat in sync (see below). */ | |
69 | ||
70 | return sysctl_write_ip_property(AF_INET, NULL, "ip_forward", "1"); | |
71 | } | |
72 | ||
73 | static int link_set_ipv6_forward(Link *link) { | |
74 | assert(link); | |
75 | ||
76 | if (!link_ip_forward_enabled(link, AF_INET6)) | |
77 | return 0; | |
78 | ||
79 | /* On Linux, the IPv6 stack does not know a per-interface | |
80 | * packet forwarding setting: either packet forwarding is on | |
81 | * for all, or off for all. We hence don't bother with a | |
82 | * per-interface setting, but simply propagate the interface | |
83 | * flag, if it is set, to the global flag, one-way. Note that | |
84 | * while IPv4 would allow a per-interface flag, we expose the | |
85 | * same behaviour there and also propagate the setting from | |
86 | * one to all, to keep things simple (see above). */ | |
87 | ||
88 | return sysctl_write_ip_property(AF_INET6, "all", "forwarding", "1"); | |
89 | } | |
90 | ||
91 | static int link_set_ipv6_privacy_extensions(Link *link) { | |
92 | assert(link); | |
93 | ||
94 | if (!socket_ipv6_is_supported()) | |
95 | return 0; | |
96 | ||
97 | if (link->flags & IFF_LOOPBACK) | |
98 | return 0; | |
99 | ||
100 | if (!link->network) | |
101 | return 0; | |
102 | ||
d3ccb1b4 FK |
103 | // this is the special "kernel" value |
104 | if (link->network->ipv6_privacy_extensions == _IPV6_PRIVACY_EXTENSIONS_INVALID) | |
105 | return 0; | |
106 | ||
5e0534f1 YW |
107 | return sysctl_write_ip_property_int(AF_INET6, link->ifname, "use_tempaddr", (int) link->network->ipv6_privacy_extensions); |
108 | } | |
109 | ||
110 | static int link_set_ipv6_accept_ra(Link *link) { | |
111 | assert(link); | |
112 | ||
113 | /* Make this a NOP if IPv6 is not available */ | |
114 | if (!socket_ipv6_is_supported()) | |
115 | return 0; | |
116 | ||
117 | if (link->flags & IFF_LOOPBACK) | |
118 | return 0; | |
119 | ||
120 | if (!link->network) | |
121 | return 0; | |
122 | ||
123 | return sysctl_write_ip_property(AF_INET6, link->ifname, "accept_ra", "0"); | |
124 | } | |
125 | ||
126 | static int link_set_ipv6_dad_transmits(Link *link) { | |
127 | assert(link); | |
128 | ||
129 | /* Make this a NOP if IPv6 is not available */ | |
130 | if (!socket_ipv6_is_supported()) | |
131 | return 0; | |
132 | ||
133 | if (link->flags & IFF_LOOPBACK) | |
134 | return 0; | |
135 | ||
136 | if (!link->network) | |
137 | return 0; | |
138 | ||
139 | if (link->network->ipv6_dad_transmits < 0) | |
140 | return 0; | |
141 | ||
142 | return sysctl_write_ip_property_int(AF_INET6, link->ifname, "dad_transmits", link->network->ipv6_dad_transmits); | |
143 | } | |
144 | ||
145 | static int link_set_ipv6_hop_limit(Link *link) { | |
146 | assert(link); | |
147 | ||
148 | /* Make this a NOP if IPv6 is not available */ | |
149 | if (!socket_ipv6_is_supported()) | |
150 | return 0; | |
151 | ||
152 | if (link->flags & IFF_LOOPBACK) | |
153 | return 0; | |
154 | ||
155 | if (!link->network) | |
156 | return 0; | |
157 | ||
158 | if (link->network->ipv6_hop_limit < 0) | |
159 | return 0; | |
160 | ||
161 | return sysctl_write_ip_property_int(AF_INET6, link->ifname, "hop_limit", link->network->ipv6_hop_limit); | |
162 | } | |
163 | ||
d8350b60 YW |
164 | static int link_set_ipv6_proxy_ndp(Link *link) { |
165 | bool v; | |
166 | ||
167 | assert(link); | |
168 | ||
169 | if (!socket_ipv6_is_supported()) | |
170 | return 0; | |
171 | ||
172 | if (link->flags & IFF_LOOPBACK) | |
173 | return 0; | |
174 | ||
175 | if (!link->network) | |
176 | return 0; | |
177 | ||
178 | if (link->network->ipv6_proxy_ndp >= 0) | |
179 | v = link->network->ipv6_proxy_ndp; | |
180 | else | |
181 | v = !set_isempty(link->network->ipv6_proxy_ndp_addresses); | |
182 | ||
183 | return sysctl_write_ip_property_boolean(AF_INET6, link->ifname, "proxy_ndp", v); | |
184 | } | |
185 | ||
fa283812 | 186 | int link_set_ipv6_mtu(Link *link) { |
e56e1a15 YW |
187 | uint32_t mtu; |
188 | ||
fa283812 YW |
189 | assert(link); |
190 | ||
191 | /* Make this a NOP if IPv6 is not available */ | |
192 | if (!socket_ipv6_is_supported()) | |
193 | return 0; | |
194 | ||
195 | if (link->flags & IFF_LOOPBACK) | |
196 | return 0; | |
197 | ||
198 | if (!link->network) | |
199 | return 0; | |
200 | ||
201 | if (link->network->ipv6_mtu == 0) | |
202 | return 0; | |
203 | ||
e56e1a15 YW |
204 | mtu = link->network->ipv6_mtu; |
205 | if (mtu > link->max_mtu) { | |
206 | log_link_warning(link, "Reducing requested IPv6 MTU %"PRIu32" to the interface's maximum MTU %"PRIu32".", | |
207 | mtu, link->max_mtu); | |
208 | mtu = link->max_mtu; | |
209 | } | |
210 | ||
211 | return sysctl_write_ip_property_uint32(AF_INET6, link->ifname, "mtu", mtu); | |
fa283812 YW |
212 | } |
213 | ||
5e0534f1 YW |
214 | static int link_set_ipv4_accept_local(Link *link) { |
215 | assert(link); | |
216 | ||
217 | if (link->flags & IFF_LOOPBACK) | |
218 | return 0; | |
219 | ||
220 | if (link->network->ipv4_accept_local < 0) | |
221 | return 0; | |
222 | ||
223 | return sysctl_write_ip_property_boolean(AF_INET, link->ifname, "accept_local", link->network->ipv4_accept_local > 0); | |
224 | } | |
225 | ||
d75bf6cf SS |
226 | static int link_set_ipv4_route_localnet(Link *link) { |
227 | assert(link); | |
228 | ||
229 | if (link->flags & IFF_LOOPBACK) | |
230 | return 0; | |
231 | ||
232 | if (link->network->ipv4_route_localnet < 0) | |
233 | return 0; | |
234 | ||
235 | return sysctl_write_ip_property_boolean(AF_INET, link->ifname, "route_localnet", link->network->ipv4_route_localnet > 0); | |
236 | } | |
237 | ||
5e0534f1 YW |
238 | int link_set_sysctl(Link *link) { |
239 | int r; | |
240 | ||
241 | assert(link); | |
242 | ||
243 | /* If IPv6 configured that is static IPv6 address and IPv6LL autoconfiguration is enabled | |
244 | * for this interface, then enable IPv6 */ | |
245 | r = link_update_ipv6_sysctl(link); | |
246 | if (r < 0) | |
247 | log_link_warning_errno(link, r, "Cannot enable IPv6, ignoring: %m"); | |
248 | ||
249 | r = link_set_proxy_arp(link); | |
250 | if (r < 0) | |
251 | log_link_warning_errno(link, r, "Cannot configure proxy ARP for interface, ignoring: %m"); | |
252 | ||
253 | r = link_set_ipv4_forward(link); | |
254 | if (r < 0) | |
255 | log_link_warning_errno(link, r, "Cannot turn on IPv4 packet forwarding, ignoring: %m"); | |
256 | ||
257 | r = link_set_ipv6_forward(link); | |
258 | if (r < 0) | |
891fc28b | 259 | log_link_warning_errno(link, r, "Cannot configure IPv6 packet forwarding, ignoring: %m"); |
5e0534f1 YW |
260 | |
261 | r = link_set_ipv6_privacy_extensions(link); | |
262 | if (r < 0) | |
7eeaf72b | 263 | log_link_warning_errno(link, r, "Cannot configure IPv6 privacy extensions for interface, ignoring: %m"); |
5e0534f1 YW |
264 | |
265 | r = link_set_ipv6_accept_ra(link); | |
266 | if (r < 0) | |
267 | log_link_warning_errno(link, r, "Cannot disable kernel IPv6 accept_ra for interface, ignoring: %m"); | |
268 | ||
269 | r = link_set_ipv6_dad_transmits(link); | |
270 | if (r < 0) | |
271 | log_link_warning_errno(link, r, "Cannot set IPv6 dad transmits for interface, ignoring: %m"); | |
272 | ||
273 | r = link_set_ipv6_hop_limit(link); | |
274 | if (r < 0) | |
275 | log_link_warning_errno(link, r, "Cannot set IPv6 hop limit for interface, ignoring: %m"); | |
276 | ||
d8350b60 YW |
277 | r = link_set_ipv6_proxy_ndp(link); |
278 | if (r < 0) | |
279 | log_link_warning_errno(link, r, "Cannot set IPv6 proxy NDP, ignoring: %m"); | |
280 | ||
fa283812 YW |
281 | r = link_set_ipv6_mtu(link); |
282 | if (r < 0) | |
283 | log_link_warning_errno(link, r, "Cannot set IPv6 MTU, ignoring: %m"); | |
284 | ||
9e1432d5 YW |
285 | r = link_set_ipv6ll_stable_secret(link); |
286 | if (r < 0) | |
f81ac115 | 287 | log_link_warning_errno(link, r, "Cannot set stable secret address for IPv6 link-local address: %m"); |
9e1432d5 | 288 | |
5e0534f1 YW |
289 | r = link_set_ipv4_accept_local(link); |
290 | if (r < 0) | |
291 | log_link_warning_errno(link, r, "Cannot set IPv4 accept_local flag for interface, ignoring: %m"); | |
292 | ||
d75bf6cf SS |
293 | r = link_set_ipv4_route_localnet(link); |
294 | if (r < 0) | |
295 | log_link_warning_errno(link, r, "Cannot set IPv4 route_localnet flag for interface, ignoring: %m"); | |
296 | ||
a4176853 YW |
297 | /* If promote_secondaries is not set, DHCP will work only as long as the IP address does not |
298 | * changes between leases. The kernel will remove all secondary IP addresses of an interface | |
299 | * otherwise. The way systemd-networkd works is that the new IP of a lease is added as a | |
300 | * secondary IP and when the primary one expires it relies on the kernel to promote the | |
301 | * secondary IP. See also https://github.com/systemd/systemd/issues/7163 */ | |
302 | r = sysctl_write_ip_property_boolean(AF_INET, link->ifname, "promote_secondaries", true); | |
303 | if (r < 0) | |
304 | log_link_warning_errno(link, r, "Cannot enable promote_secondaries for interface, ignoring: %m"); | |
305 | ||
5e0534f1 YW |
306 | return 0; |
307 | } | |
308 | ||
5e0534f1 YW |
309 | static const char* const ipv6_privacy_extensions_table[_IPV6_PRIVACY_EXTENSIONS_MAX] = { |
310 | [IPV6_PRIVACY_EXTENSIONS_NO] = "no", | |
311 | [IPV6_PRIVACY_EXTENSIONS_PREFER_PUBLIC] = "prefer-public", | |
312 | [IPV6_PRIVACY_EXTENSIONS_YES] = "yes", | |
313 | }; | |
314 | ||
315 | DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(ipv6_privacy_extensions, IPv6PrivacyExtensions, | |
316 | IPV6_PRIVACY_EXTENSIONS_YES); | |
317 | ||
318 | int config_parse_ipv6_privacy_extensions( | |
319 | const char* unit, | |
320 | const char *filename, | |
321 | unsigned line, | |
322 | const char *section, | |
323 | unsigned section_line, | |
324 | const char *lvalue, | |
325 | int ltype, | |
326 | const char *rvalue, | |
327 | void *data, | |
328 | void *userdata) { | |
329 | ||
99534007 | 330 | IPv6PrivacyExtensions s, *ipv6_privacy_extensions = ASSERT_PTR(data); |
5e0534f1 YW |
331 | |
332 | assert(filename); | |
333 | assert(lvalue); | |
334 | assert(rvalue); | |
5e0534f1 YW |
335 | |
336 | s = ipv6_privacy_extensions_from_string(rvalue); | |
337 | if (s < 0) { | |
338 | if (streq(rvalue, "kernel")) | |
339 | s = _IPV6_PRIVACY_EXTENSIONS_INVALID; | |
340 | else { | |
341 | log_syntax(unit, LOG_WARNING, filename, line, 0, | |
342 | "Failed to parse IPv6 privacy extensions option, ignoring: %s", rvalue); | |
343 | return 0; | |
344 | } | |
345 | } | |
346 | ||
347 | *ipv6_privacy_extensions = s; | |
348 | ||
349 | return 0; | |
350 | } |