chiark / gitweb /
network-address,test-network: avoid undefined behaviour
[elogind.git] / src / network / networkd-dhcp6.c
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
31 static int dhcp6_lease_information_acquired(sd_dhcp6_client *client,
32                                         Link *link) {
33         return 0;
34 }
35
36 static int dhcp6_address_handler(sd_rtnl *rtnl, sd_rtnl_message *m,
37                                  void *userdata) {
38         _cleanup_link_unref_ Link *link = userdata;
39         int r;
40
41         assert(link);
42
43         r = sd_rtnl_message_get_errno(m);
44         if (r < 0 && r != -EEXIST) {
45                 log_link_error(link, "Could not set DHCPv6 address: %s",
46                                strerror(-r));
47
48                 link_enter_failed(link);
49
50         } else if (r >= 0)
51                 link_rtnl_process_address(rtnl, m, link->manager);
52
53         return 1;
54 }
55
56 static int dhcp6_address_update(Link *link, struct in6_addr *ip6_addr,
57                                 uint8_t prefixlen, uint32_t lifetime_preferred,
58                                 uint32_t lifetime_valid) {
59         int r;
60         _cleanup_address_free_ Address *addr = NULL;
61
62         r = address_new_dynamic(&addr);
63         if (r < 0)
64                 return r;
65
66         addr->family = AF_INET6;
67         memcpy(&addr->in_addr.in6, ip6_addr, sizeof(*ip6_addr));
68         addr->prefixlen = prefixlen;
69
70         addr->cinfo.ifa_prefered = lifetime_preferred;
71         addr->cinfo.ifa_valid = lifetime_valid;
72
73         log_link_struct(link, LOG_INFO, "MESSAGE=%-*s: DHCPv6 address "SD_ICMP6_ADDRESS_FORMAT_STR"/%d timeout preferred %d valid %d",
74                         IFNAMSIZ,
75                         link->ifname, SD_ICMP6_ADDRESS_FORMAT_VAL(addr->in_addr.in6),
76                         addr->prefixlen, lifetime_preferred, lifetime_valid,
77                         NULL);
78
79         r = address_update(addr, link, dhcp6_address_handler);
80         if (r < 0)
81                 log_link_warning(link, "Could not assign DHCPv6 address: %s",
82                                 strerror(-r));
83
84         return r;
85 }
86
87 static int dhcp6_prefix_expired(Link *link) {
88         int r;
89         sd_dhcp6_lease *lease;
90         struct in6_addr *expired_prefix, ip6_addr;
91         uint8_t expired_prefixlen;
92         uint32_t lifetime_preferred, lifetime_valid;
93
94         r = sd_icmp6_ra_get_expired_prefix(link->icmp6_router_discovery,
95                                         &expired_prefix, &expired_prefixlen);
96         if (r < 0)
97                 return r;
98
99         r = sd_dhcp6_client_get_lease(link->dhcp6_client, &lease);
100         if (r < 0)
101                 return r;
102
103         sd_dhcp6_lease_reset_address_iter(lease);
104
105         while (sd_dhcp6_lease_get_address(lease, &ip6_addr,
106                                                 &lifetime_preferred,
107                                                 &lifetime_valid) >= 0) {
108
109                 r = sd_icmp6_prefix_match(expired_prefix, expired_prefixlen,
110                                         &ip6_addr);
111                 if (r >= 0) {
112                         r = dhcp6_address_update(link, &ip6_addr, 128,
113                                                 lifetime_preferred,
114                                                 lifetime_valid);
115
116                         return r;
117                 }
118         }
119
120         return 0;
121 }
122
123 static int dhcp6_lease_address_acquired(sd_dhcp6_client *client, Link *link) {
124         int r;
125         sd_dhcp6_lease *lease;
126         struct in6_addr ip6_addr;
127         uint32_t lifetime_preferred, lifetime_valid;
128         uint8_t prefixlen;
129
130         r = sd_dhcp6_client_get_lease(client, &lease);
131         if (r < 0)
132                 return r;
133
134         sd_dhcp6_lease_reset_address_iter(lease);
135
136         while (sd_dhcp6_lease_get_address(lease, &ip6_addr,
137                                                 &lifetime_preferred,
138                                                 &lifetime_valid) >= 0) {
139
140                 r = sd_icmp6_ra_get_prefixlen(link->icmp6_router_discovery,
141                                         &ip6_addr, &prefixlen);
142                 if (r < 0 && r != -EADDRNOTAVAIL) {
143                         log_link_warning(link, "Could not get prefix information: %s",
144                                         strerror(-r));
145                         return r;
146                 }
147
148                 if (r == -EADDRNOTAVAIL)
149                         prefixlen = 128;
150
151                 r = dhcp6_address_update(link, &ip6_addr, prefixlen,
152                                         lifetime_preferred, lifetime_valid);
153                 if (r < 0)
154                         return r;
155         }
156
157         return 0;
158 }
159
160 static void dhcp6_handler(sd_dhcp6_client *client, int event, void *userdata) {
161         int r;
162         Link *link = userdata;
163
164         assert(link);
165         assert(link->network);
166         assert(link->manager);
167
168         if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
169                 return;
170
171         switch(event) {
172         case DHCP6_EVENT_STOP:
173         case DHCP6_EVENT_RESEND_EXPIRE:
174         case DHCP6_EVENT_RETRANS_MAX:
175                 log_link_debug(link, "DHCPv6 event %d", event);
176                 break;
177
178         case DHCP6_EVENT_IP_ACQUIRE:
179                 r = dhcp6_lease_address_acquired(client, link);
180                 if (r < 0) {
181                         link_enter_failed(link);
182                         return;
183                 }
184
185                 /* fall through */
186         case DHCP6_EVENT_INFORMATION_REQUEST:
187                 r = dhcp6_lease_information_acquired(client, link);
188                 if (r < 0) {
189                         link_enter_failed(link);
190                         return;
191                 }
192
193                 break;
194
195         default:
196                 if (event < 0)
197                         log_link_warning(link, "DHCPv6 error: %s",
198                                          strerror(-event));
199                 else
200                         log_link_warning(link, "DHCPv6 unknown event: %d",
201                                          event);
202                 return;
203         }
204 }
205
206 static int dhcp6_configure(Link *link, int event) {
207         int r;
208         bool information_request;
209
210         assert_return(link, -EINVAL);
211
212         if (link->dhcp6_client) {
213                 if (event != ICMP6_EVENT_ROUTER_ADVERTISMENT_MANAGED)
214                         return 0;
215
216                 r = sd_dhcp6_client_get_information_request(link->dhcp6_client,
217                                                         &information_request);
218                 if (r < 0) {
219                         log_link_warning(link, "Could not get DHCPv6 Information request setting: %s",
220                                         strerror(-r));
221                         link->dhcp6_client =
222                                 sd_dhcp6_client_unref(link->dhcp6_client);
223                         return r;
224                 }
225
226                 if (!information_request)
227                         return r;
228
229                 r = sd_dhcp6_client_set_information_request(link->dhcp6_client,
230                                                         false);
231                 if (r < 0) {
232                         log_link_warning(link, "Could not unset DHCPv6 Information request: %s",
233                                         strerror(-r));
234                         link->dhcp6_client =
235                                 sd_dhcp6_client_unref(link->dhcp6_client);
236                         return r;
237                 }
238
239                 r = sd_dhcp6_client_start(link->dhcp6_client);
240                 if (r < 0) {
241                         log_link_warning(link, "Could not restart DHCPv6 after enabling Information request: %s",
242                                         strerror(-r));
243                         link->dhcp6_client =
244                                 sd_dhcp6_client_unref(link->dhcp6_client);
245                         return r;
246                 }
247
248                 return r;
249         }
250
251         r = sd_dhcp6_client_new(&link->dhcp6_client);
252         if (r < 0)
253                 return r;
254
255         r = sd_dhcp6_client_attach_event(link->dhcp6_client, NULL, 0);
256         if (r < 0) {
257                 link->dhcp6_client = sd_dhcp6_client_unref(link->dhcp6_client);
258                 return r;
259         }
260
261         r = sd_dhcp6_client_set_mac(link->dhcp6_client,
262                                     (const uint8_t *) &link->mac,
263                                     sizeof (link->mac), ARPHRD_ETHER);
264         if (r < 0) {
265                 link->dhcp6_client = sd_dhcp6_client_unref(link->dhcp6_client);
266                 return r;
267         }
268
269         r = sd_dhcp6_client_set_index(link->dhcp6_client, link->ifindex);
270         if (r < 0) {
271                 link->dhcp6_client = sd_dhcp6_client_unref(link->dhcp6_client);
272                 return r;
273         }
274
275         r = sd_dhcp6_client_set_callback(link->dhcp6_client, dhcp6_handler,
276                                          link);
277         if (r < 0) {
278                 link->dhcp6_client = sd_dhcp6_client_unref(link->dhcp6_client);
279                 return r;
280         }
281
282         if (event == ICMP6_EVENT_ROUTER_ADVERTISMENT_OTHER) {
283                 r = sd_dhcp6_client_set_information_request(link->dhcp6_client,
284                                                         true);
285                 if (r < 0) {
286                         link->dhcp6_client =
287                                 sd_dhcp6_client_unref(link->dhcp6_client);
288                         return r;
289                 }
290         }
291
292         r = sd_dhcp6_client_start(link->dhcp6_client);
293         if (r < 0)
294                 link->dhcp6_client = sd_dhcp6_client_unref(link->dhcp6_client);
295
296         return r;
297 }
298
299 static void icmp6_router_handler(sd_icmp6_nd *nd, int event, void *userdata) {
300         Link *link = userdata;
301
302         assert(link);
303         assert(link->network);
304         assert(link->manager);
305
306         if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
307                 return;
308
309         switch(event) {
310         case ICMP6_EVENT_ROUTER_ADVERTISMENT_NONE:
311                 return;
312
313         case ICMP6_EVENT_ROUTER_ADVERTISMENT_TIMEOUT:
314         case ICMP6_EVENT_ROUTER_ADVERTISMENT_OTHER:
315         case ICMP6_EVENT_ROUTER_ADVERTISMENT_MANAGED:
316                 dhcp6_configure(link, event);
317
318                 break;
319
320         case ICMP6_EVENT_ROUTER_ADVERTISMENT_PREFIX_EXPIRED:
321                 dhcp6_prefix_expired(link);
322
323                 break;
324
325         default:
326                 if (event < 0)
327                         log_link_warning(link, "ICMPv6 error: %s",
328                                          strerror(-event));
329                 else
330                         log_link_warning(link, "ICMPv6 unknown event: %d",
331                                          event);
332
333                 break;
334         }
335
336 }
337
338 int icmp6_configure(Link *link) {
339         int r;
340
341         assert_return(link, -EINVAL);
342
343         r = sd_icmp6_nd_new(&link->icmp6_router_discovery);
344         if (r < 0)
345                 return r;
346
347         r = sd_icmp6_nd_attach_event(link->icmp6_router_discovery, NULL, 0);
348         if (r < 0)
349                 return r;
350
351         r = sd_icmp6_nd_set_mac(link->icmp6_router_discovery, &link->mac);
352         if (r < 0)
353                 return r;
354
355         r = sd_icmp6_nd_set_index(link->icmp6_router_discovery, link->ifindex);
356         if (r < 0)
357                 return r;
358
359         r = sd_icmp6_nd_set_callback(link->icmp6_router_discovery,
360                                 icmp6_router_handler, link);
361
362         return r;
363 }