]>
Commit | Line | Data |
---|---|---|
f47781d8 MP |
1 | /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ |
2 | ||
3 | /*** | |
4 | This file is part of systemd. | |
5 | ||
6 | Copyright (C) 2014 Intel Corporation. All rights reserved. | |
7 | ||
8 | systemd is free software; you can redistribute it and/or modify it | |
9 | under the terms of the GNU Lesser General Public License as published by | |
10 | the Free Software Foundation; either version 2.1 of the License, or | |
11 | (at your option) any later version. | |
12 | ||
13 | systemd is distributed in the hope that it will be useful, but | |
14 | WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
16 | Lesser General Public License for more details. | |
17 | ||
18 | You should have received a copy of the GNU Lesser General Public License | |
19 | along with systemd; If not, see <http://www.gnu.org/licenses/>. | |
20 | ***/ | |
21 | ||
22 | #include <netinet/ether.h> | |
23 | #include <linux/if.h> | |
24 | ||
25 | #include "networkd-link.h" | |
26 | #include "network-internal.h" | |
27 | ||
28 | #include "sd-icmp6-nd.h" | |
29 | #include "sd-dhcp6-client.h" | |
30 | ||
e3bff60a MP |
31 | static int dhcp6_lease_address_acquired(sd_dhcp6_client *client, Link *link); |
32 | ||
e735f4d4 MP |
33 | static int dhcp6_lease_information_acquired(sd_dhcp6_client *client, |
34 | Link *link) { | |
35 | return 0; | |
36 | } | |
37 | ||
38 | static int dhcp6_address_handler(sd_rtnl *rtnl, sd_rtnl_message *m, | |
39 | void *userdata) { | |
40 | _cleanup_link_unref_ Link *link = userdata; | |
41 | int r; | |
42 | ||
43 | assert(link); | |
44 | ||
45 | r = sd_rtnl_message_get_errno(m); | |
46 | if (r < 0 && r != -EEXIST) { | |
e3bff60a MP |
47 | if (link->rtnl_extended_attrs) { |
48 | log_link_warning(link, "Could not set extended netlink attributes, reverting to fallback mechanism"); | |
49 | ||
50 | link->rtnl_extended_attrs = false; | |
51 | dhcp6_lease_address_acquired(link->dhcp6_client, link); | |
52 | ||
53 | return 1; | |
54 | } | |
55 | ||
e735f4d4 MP |
56 | log_link_error(link, "Could not set DHCPv6 address: %s", |
57 | strerror(-r)); | |
58 | ||
59 | link_enter_failed(link); | |
60 | ||
61 | } else if (r >= 0) | |
62 | link_rtnl_process_address(rtnl, m, link->manager); | |
63 | ||
64 | return 1; | |
65 | } | |
66 | ||
67 | static int dhcp6_address_update(Link *link, struct in6_addr *ip6_addr, | |
68 | uint8_t prefixlen, uint32_t lifetime_preferred, | |
69 | uint32_t lifetime_valid) { | |
70 | int r; | |
71 | _cleanup_address_free_ Address *addr = NULL; | |
72 | ||
73 | r = address_new_dynamic(&addr); | |
74 | if (r < 0) | |
75 | return r; | |
76 | ||
77 | addr->family = AF_INET6; | |
78 | memcpy(&addr->in_addr.in6, ip6_addr, sizeof(*ip6_addr)); | |
e3bff60a MP |
79 | |
80 | addr->flags = IFA_F_NOPREFIXROUTE; | |
e735f4d4 MP |
81 | addr->prefixlen = prefixlen; |
82 | ||
83 | addr->cinfo.ifa_prefered = lifetime_preferred; | |
84 | addr->cinfo.ifa_valid = lifetime_valid; | |
85 | ||
e3bff60a MP |
86 | log_link_info(link, |
87 | "DHCPv6 address "SD_ICMP6_ADDRESS_FORMAT_STR"/%d timeout preferred %d valid %d", | |
88 | SD_ICMP6_ADDRESS_FORMAT_VAL(addr->in_addr.in6), | |
89 | addr->prefixlen, lifetime_preferred, lifetime_valid); | |
e735f4d4 MP |
90 | |
91 | r = address_update(addr, link, dhcp6_address_handler); | |
92 | if (r < 0) | |
e3bff60a | 93 | log_link_warning_errno(link, r, "Could not assign DHCPv6 address: %m"); |
e735f4d4 MP |
94 | |
95 | return r; | |
96 | } | |
97 | ||
e735f4d4 MP |
98 | static int dhcp6_lease_address_acquired(sd_dhcp6_client *client, Link *link) { |
99 | int r; | |
100 | sd_dhcp6_lease *lease; | |
101 | struct in6_addr ip6_addr; | |
102 | uint32_t lifetime_preferred, lifetime_valid; | |
103 | uint8_t prefixlen; | |
104 | ||
105 | r = sd_dhcp6_client_get_lease(client, &lease); | |
106 | if (r < 0) | |
107 | return r; | |
108 | ||
109 | sd_dhcp6_lease_reset_address_iter(lease); | |
110 | ||
111 | while (sd_dhcp6_lease_get_address(lease, &ip6_addr, | |
112 | &lifetime_preferred, | |
113 | &lifetime_valid) >= 0) { | |
114 | ||
115 | r = sd_icmp6_ra_get_prefixlen(link->icmp6_router_discovery, | |
116 | &ip6_addr, &prefixlen); | |
117 | if (r < 0 && r != -EADDRNOTAVAIL) { | |
118 | log_link_warning(link, "Could not get prefix information: %s", | |
119 | strerror(-r)); | |
120 | return r; | |
121 | } | |
122 | ||
123 | if (r == -EADDRNOTAVAIL) | |
124 | prefixlen = 128; | |
125 | ||
126 | r = dhcp6_address_update(link, &ip6_addr, prefixlen, | |
127 | lifetime_preferred, lifetime_valid); | |
128 | if (r < 0) | |
129 | return r; | |
130 | } | |
131 | ||
132 | return 0; | |
133 | } | |
134 | ||
f47781d8 | 135 | static void dhcp6_handler(sd_dhcp6_client *client, int event, void *userdata) { |
e735f4d4 | 136 | int r; |
f47781d8 MP |
137 | Link *link = userdata; |
138 | ||
139 | assert(link); | |
140 | assert(link->network); | |
141 | assert(link->manager); | |
142 | ||
143 | if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) | |
144 | return; | |
145 | ||
146 | switch(event) { | |
147 | case DHCP6_EVENT_STOP: | |
148 | case DHCP6_EVENT_RESEND_EXPIRE: | |
149 | case DHCP6_EVENT_RETRANS_MAX: | |
e735f4d4 MP |
150 | log_link_debug(link, "DHCPv6 event %d", event); |
151 | break; | |
152 | ||
f47781d8 | 153 | case DHCP6_EVENT_IP_ACQUIRE: |
e735f4d4 MP |
154 | r = dhcp6_lease_address_acquired(client, link); |
155 | if (r < 0) { | |
156 | link_enter_failed(link); | |
157 | return; | |
158 | } | |
159 | ||
160 | /* fall through */ | |
f47781d8 | 161 | case DHCP6_EVENT_INFORMATION_REQUEST: |
e735f4d4 MP |
162 | r = dhcp6_lease_information_acquired(client, link); |
163 | if (r < 0) { | |
164 | link_enter_failed(link); | |
165 | return; | |
166 | } | |
f47781d8 MP |
167 | |
168 | break; | |
169 | ||
170 | default: | |
171 | if (event < 0) | |
172 | log_link_warning(link, "DHCPv6 error: %s", | |
173 | strerror(-event)); | |
174 | else | |
175 | log_link_warning(link, "DHCPv6 unknown event: %d", | |
176 | event); | |
177 | return; | |
178 | } | |
179 | } | |
180 | ||
181 | static int dhcp6_configure(Link *link, int event) { | |
182 | int r; | |
183 | bool information_request; | |
184 | ||
185 | assert_return(link, -EINVAL); | |
186 | ||
187 | if (link->dhcp6_client) { | |
188 | if (event != ICMP6_EVENT_ROUTER_ADVERTISMENT_MANAGED) | |
189 | return 0; | |
190 | ||
191 | r = sd_dhcp6_client_get_information_request(link->dhcp6_client, | |
192 | &information_request); | |
193 | if (r < 0) { | |
e735f4d4 MP |
194 | log_link_warning(link, "Could not get DHCPv6 Information request setting: %s", |
195 | strerror(-r)); | |
f47781d8 MP |
196 | link->dhcp6_client = |
197 | sd_dhcp6_client_unref(link->dhcp6_client); | |
198 | return r; | |
199 | } | |
200 | ||
201 | if (!information_request) | |
202 | return r; | |
203 | ||
204 | r = sd_dhcp6_client_set_information_request(link->dhcp6_client, | |
205 | false); | |
206 | if (r < 0) { | |
e735f4d4 MP |
207 | log_link_warning(link, "Could not unset DHCPv6 Information request: %s", |
208 | strerror(-r)); | |
f47781d8 MP |
209 | link->dhcp6_client = |
210 | sd_dhcp6_client_unref(link->dhcp6_client); | |
211 | return r; | |
212 | } | |
213 | ||
214 | r = sd_dhcp6_client_start(link->dhcp6_client); | |
215 | if (r < 0) { | |
e735f4d4 MP |
216 | log_link_warning(link, "Could not restart DHCPv6 after enabling Information request: %s", |
217 | strerror(-r)); | |
f47781d8 MP |
218 | link->dhcp6_client = |
219 | sd_dhcp6_client_unref(link->dhcp6_client); | |
220 | return r; | |
221 | } | |
222 | ||
223 | return r; | |
224 | } | |
225 | ||
226 | r = sd_dhcp6_client_new(&link->dhcp6_client); | |
227 | if (r < 0) | |
228 | return r; | |
229 | ||
230 | r = sd_dhcp6_client_attach_event(link->dhcp6_client, NULL, 0); | |
231 | if (r < 0) { | |
232 | link->dhcp6_client = sd_dhcp6_client_unref(link->dhcp6_client); | |
233 | return r; | |
234 | } | |
235 | ||
236 | r = sd_dhcp6_client_set_mac(link->dhcp6_client, | |
237 | (const uint8_t *) &link->mac, | |
238 | sizeof (link->mac), ARPHRD_ETHER); | |
239 | if (r < 0) { | |
240 | link->dhcp6_client = sd_dhcp6_client_unref(link->dhcp6_client); | |
241 | return r; | |
242 | } | |
243 | ||
244 | r = sd_dhcp6_client_set_index(link->dhcp6_client, link->ifindex); | |
245 | if (r < 0) { | |
246 | link->dhcp6_client = sd_dhcp6_client_unref(link->dhcp6_client); | |
247 | return r; | |
248 | } | |
249 | ||
250 | r = sd_dhcp6_client_set_callback(link->dhcp6_client, dhcp6_handler, | |
251 | link); | |
252 | if (r < 0) { | |
253 | link->dhcp6_client = sd_dhcp6_client_unref(link->dhcp6_client); | |
254 | return r; | |
255 | } | |
256 | ||
257 | if (event == ICMP6_EVENT_ROUTER_ADVERTISMENT_OTHER) { | |
258 | r = sd_dhcp6_client_set_information_request(link->dhcp6_client, | |
259 | true); | |
260 | if (r < 0) { | |
261 | link->dhcp6_client = | |
262 | sd_dhcp6_client_unref(link->dhcp6_client); | |
263 | return r; | |
264 | } | |
265 | } | |
266 | ||
267 | r = sd_dhcp6_client_start(link->dhcp6_client); | |
268 | if (r < 0) | |
269 | link->dhcp6_client = sd_dhcp6_client_unref(link->dhcp6_client); | |
270 | ||
271 | return r; | |
272 | } | |
273 | ||
e3bff60a MP |
274 | static int dhcp6_prefix_expired(Link *link) { |
275 | int r; | |
276 | sd_dhcp6_lease *lease; | |
277 | struct in6_addr *expired_prefix, ip6_addr; | |
278 | uint8_t expired_prefixlen; | |
279 | uint32_t lifetime_preferred, lifetime_valid; | |
280 | ||
281 | r = sd_icmp6_ra_get_expired_prefix(link->icmp6_router_discovery, | |
282 | &expired_prefix, &expired_prefixlen); | |
283 | if (r < 0) | |
284 | return r; | |
285 | ||
286 | r = sd_dhcp6_client_get_lease(link->dhcp6_client, &lease); | |
287 | if (r < 0) | |
288 | return r; | |
289 | ||
290 | log_link_info(link, "IPv6 prefix "SD_ICMP6_ADDRESS_FORMAT_STR"/%d expired", | |
291 | SD_ICMP6_ADDRESS_FORMAT_VAL(*expired_prefix), | |
292 | expired_prefixlen); | |
293 | ||
294 | sd_dhcp6_lease_reset_address_iter(lease); | |
295 | ||
296 | while (sd_dhcp6_lease_get_address(lease, &ip6_addr, | |
297 | &lifetime_preferred, | |
298 | &lifetime_valid) >= 0) { | |
299 | ||
300 | r = sd_icmp6_prefix_match(expired_prefix, expired_prefixlen, | |
301 | &ip6_addr); | |
302 | if (r < 0) | |
303 | continue; | |
304 | ||
305 | log_link_info(link, "IPv6 prefix length updated "SD_ICMP6_ADDRESS_FORMAT_STR"/%d", SD_ICMP6_ADDRESS_FORMAT_VAL(ip6_addr), 128); | |
306 | ||
307 | dhcp6_address_update(link, &ip6_addr, 128, lifetime_preferred, lifetime_valid); | |
308 | } | |
309 | ||
310 | return 0; | |
311 | } | |
312 | ||
f47781d8 MP |
313 | static void icmp6_router_handler(sd_icmp6_nd *nd, int event, void *userdata) { |
314 | Link *link = userdata; | |
315 | ||
316 | assert(link); | |
317 | assert(link->network); | |
318 | assert(link->manager); | |
319 | ||
320 | if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) | |
321 | return; | |
322 | ||
323 | switch(event) { | |
324 | case ICMP6_EVENT_ROUTER_ADVERTISMENT_NONE: | |
325 | return; | |
326 | ||
327 | case ICMP6_EVENT_ROUTER_ADVERTISMENT_TIMEOUT: | |
328 | case ICMP6_EVENT_ROUTER_ADVERTISMENT_OTHER: | |
329 | case ICMP6_EVENT_ROUTER_ADVERTISMENT_MANAGED: | |
e735f4d4 MP |
330 | dhcp6_configure(link, event); |
331 | ||
332 | break; | |
333 | ||
334 | case ICMP6_EVENT_ROUTER_ADVERTISMENT_PREFIX_EXPIRED: | |
e3bff60a MP |
335 | if (!link->rtnl_extended_attrs) |
336 | dhcp6_prefix_expired(link); | |
e735f4d4 | 337 | |
f47781d8 MP |
338 | break; |
339 | ||
340 | default: | |
341 | if (event < 0) | |
342 | log_link_warning(link, "ICMPv6 error: %s", | |
343 | strerror(-event)); | |
344 | else | |
345 | log_link_warning(link, "ICMPv6 unknown event: %d", | |
346 | event); | |
347 | ||
e735f4d4 | 348 | break; |
f47781d8 MP |
349 | } |
350 | ||
f47781d8 MP |
351 | } |
352 | ||
353 | int icmp6_configure(Link *link) { | |
354 | int r; | |
355 | ||
356 | assert_return(link, -EINVAL); | |
357 | ||
358 | r = sd_icmp6_nd_new(&link->icmp6_router_discovery); | |
359 | if (r < 0) | |
360 | return r; | |
361 | ||
362 | r = sd_icmp6_nd_attach_event(link->icmp6_router_discovery, NULL, 0); | |
363 | if (r < 0) | |
364 | return r; | |
365 | ||
366 | r = sd_icmp6_nd_set_mac(link->icmp6_router_discovery, &link->mac); | |
367 | if (r < 0) | |
368 | return r; | |
369 | ||
370 | r = sd_icmp6_nd_set_index(link->icmp6_router_discovery, link->ifindex); | |
371 | if (r < 0) | |
372 | return r; | |
373 | ||
374 | r = sd_icmp6_nd_set_callback(link->icmp6_router_discovery, | |
375 | icmp6_router_handler, link); | |
376 | ||
377 | return r; | |
378 | } |