]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/network/networkd-ndisc.c
Fix IPv6PrivacyExtension (networkd-ndisc.c)
[thirdparty/systemd.git] / src / network / networkd-ndisc.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 <netinet/icmp6.h>
24 #include <linux/if.h>
25
26 #include "sd-ndisc.h"
27
28 #include "networkd-link.h"
29
30 static int ndisc_netlink_handler(sd_netlink *rtnl, sd_netlink_message *m, void *userdata) {
31 _cleanup_link_unref_ Link *link = userdata;
32 int r;
33
34 assert(link);
35 assert(link->ndisc_messages > 0);
36
37 link->ndisc_messages --;
38
39 r = sd_netlink_message_get_errno(m);
40 if (r < 0 && r != -EEXIST) {
41 log_link_error_errno(link, r, "Could not set NDisc route or address: %m");
42 link_enter_failed(link);
43 }
44
45 if (link->ndisc_messages == 0) {
46 link->ndisc_configured = true;
47 link_check_ready(link);
48 }
49
50 return 1;
51 }
52
53 static void ndisc_prefix_autonomous_handler(sd_ndisc *nd, const struct in6_addr *prefix, unsigned prefixlen,
54 unsigned lifetime_preferred, unsigned lifetime_valid, void *userdata) {
55 _cleanup_address_free_ Address *address = NULL;
56 Link *link = userdata;
57 usec_t time_now;
58 int r;
59
60 assert(nd);
61 assert(link);
62 assert(link->network);
63
64 if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
65 return;
66
67 r = address_new(&address);
68 if (r < 0) {
69 log_link_error_errno(link, r, "Could not allocate address: %m");
70 return;
71 }
72
73 assert_se(sd_event_now(link->manager->event, clock_boottime_or_monotonic(), &time_now) >= 0);
74
75 address->family = AF_INET6;
76 address->in_addr.in6 = *prefix;
77 if (in_addr_is_null(AF_INET6, (const union in_addr_union *) &link->network->ipv6_token) == 0)
78 memcpy(((char *)&address->in_addr.in6) + 8, ((char *)&link->network->ipv6_token) + 8, 8);
79 else {
80 /* see RFC4291 section 2.5.1 */
81 address->in_addr.in6.__in6_u.__u6_addr8[8] = link->mac.ether_addr_octet[0];
82 address->in_addr.in6.__in6_u.__u6_addr8[8] ^= 1 << 1;
83 address->in_addr.in6.__in6_u.__u6_addr8[9] = link->mac.ether_addr_octet[1];
84 address->in_addr.in6.__in6_u.__u6_addr8[10] = link->mac.ether_addr_octet[2];
85 address->in_addr.in6.__in6_u.__u6_addr8[11] = 0xff;
86 address->in_addr.in6.__in6_u.__u6_addr8[12] = 0xfe;
87 address->in_addr.in6.__in6_u.__u6_addr8[13] = link->mac.ether_addr_octet[3];
88 address->in_addr.in6.__in6_u.__u6_addr8[14] = link->mac.ether_addr_octet[4];
89 address->in_addr.in6.__in6_u.__u6_addr8[15] = link->mac.ether_addr_octet[5];
90 }
91 address->prefixlen = prefixlen;
92 address->flags = IFA_F_NOPREFIXROUTE|IFA_F_MANAGETEMPADDR;
93 address->cinfo.ifa_prefered = lifetime_preferred;
94 address->cinfo.ifa_valid = lifetime_valid;
95
96 r = address_configure(address, link, ndisc_netlink_handler, true);
97 if (r < 0) {
98 log_link_warning_errno(link, r, "Could not set SLAAC address: %m");
99 link_enter_failed(link);
100 return;
101 }
102
103 link->ndisc_messages ++;
104 }
105
106 static void ndisc_prefix_onlink_handler(sd_ndisc *nd, const struct in6_addr *prefix, unsigned prefixlen, unsigned lifetime, void *userdata) {
107 _cleanup_route_free_ Route *route = NULL;
108 Link *link = userdata;
109 usec_t time_now;
110 int r;
111
112 assert(nd);
113 assert(link);
114
115 if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
116 return;
117
118 r = route_new(&route);
119 if (r < 0) {
120 log_link_error_errno(link, r, "Could not allocate route: %m");
121 return;
122 }
123
124 assert_se(sd_event_now(link->manager->event, clock_boottime_or_monotonic(), &time_now) >= 0);
125
126 route->family = AF_INET6;
127 route->table = RT_TABLE_MAIN;
128 route->protocol = RTPROT_RA;
129 route->flags = RTM_F_PREFIX;
130 route->dst.in6 = *prefix;
131 route->dst_prefixlen = prefixlen;
132 route->lifetime = time_now + lifetime * USEC_PER_SEC;
133
134 r = route_configure(route, link, ndisc_netlink_handler);
135 if (r < 0) {
136 log_link_warning_errno(link, r, "Could not set prefix route: %m");
137 link_enter_failed(link);
138 return;
139 }
140
141 link->ndisc_messages ++;
142 }
143
144 static void ndisc_router_handler(sd_ndisc *nd, uint8_t flags, const struct in6_addr *gateway, unsigned lifetime, int pref, void *userdata) {
145 _cleanup_route_free_ Route *route = NULL;
146 Link *link = userdata;
147 usec_t time_now;
148 int r;
149
150 assert(link);
151 assert(link->network);
152 assert(link->manager);
153
154 if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
155 return;
156
157 if (flags & (ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER)) {
158 if (flags & ND_RA_FLAG_MANAGED)
159 dhcp6_request_address(link);
160
161 r = sd_dhcp6_client_start(link->dhcp6_client);
162 if (r < 0 && r != -EBUSY)
163 log_link_warning_errno(link, r, "Starting DHCPv6 client on NDisc request failed: %m");
164 }
165
166 if (!gateway)
167 return;
168
169 r = route_new(&route);
170 if (r < 0) {
171 log_link_error_errno(link, r, "Could not allocate route: %m");
172 return;
173 }
174
175 assert_se(sd_event_now(link->manager->event, clock_boottime_or_monotonic(), &time_now) >= 0);
176
177 route->family = AF_INET6;
178 route->table = RT_TABLE_MAIN;
179 route->protocol = RTPROT_RA;
180 route->pref = pref;
181 route->gw.in6 = *gateway;
182 route->lifetime = time_now + lifetime * USEC_PER_SEC;
183
184 r = route_configure(route, link, ndisc_netlink_handler);
185 if (r < 0) {
186 log_link_warning_errno(link, r, "Could not set default route: %m");
187 link_enter_failed(link);
188 return;
189 }
190
191 link->ndisc_messages ++;
192 }
193
194 static void ndisc_handler(sd_ndisc *nd, int event, void *userdata) {
195 Link *link = userdata;
196 int r;
197
198 assert(link);
199
200 if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
201 return;
202
203 switch (event) {
204 case SD_NDISC_EVENT_TIMEOUT:
205 dhcp6_request_address(link);
206
207 r = sd_dhcp6_client_start(link->dhcp6_client);
208 if (r < 0 && r != -EBUSY)
209 log_link_warning_errno(link, r, "Starting DHCPv6 client after NDisc timeout failed: %m");
210
211 link->ndisc_configured = true;
212 link_check_ready(link);
213
214 break;
215 case SD_NDISC_EVENT_STOP:
216 break;
217 default:
218 log_link_warning(link, "IPv6 Neighbor Discovery unknown event: %d", event);
219 }
220 }
221
222 int ndisc_configure(Link *link) {
223 int r;
224
225 assert_return(link, -EINVAL);
226
227 r = sd_ndisc_new(&link->ndisc_router_discovery);
228 if (r < 0)
229 return r;
230
231 r = sd_ndisc_attach_event(link->ndisc_router_discovery, NULL, 0);
232 if (r < 0)
233 return r;
234
235 r = sd_ndisc_set_mac(link->ndisc_router_discovery, &link->mac);
236 if (r < 0)
237 return r;
238
239 r = sd_ndisc_set_index(link->ndisc_router_discovery, link->ifindex);
240 if (r < 0)
241 return r;
242
243 r = sd_ndisc_set_callback(link->ndisc_router_discovery,
244 ndisc_router_handler,
245 ndisc_prefix_onlink_handler,
246 ndisc_prefix_autonomous_handler,
247 ndisc_handler,
248 link);
249
250 return r;
251 }