]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/network/networkd-ndisc.c
networkd: ndisc/dhcpv6 - handle starting running clients
[thirdparty/systemd.git] / src / network / networkd-ndisc.c
CommitLineData
a13c50e7
TG
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>
9d96e6c3 23#include <netinet/icmp6.h>
a13c50e7
TG
24#include <linux/if.h>
25
a13c50e7
TG
26#include "sd-ndisc.h"
27
07630cea
LP
28#include "networkd-link.h"
29
3b015d40
TG
30static 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
53static 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(&address->in_addr.in6 + 8, &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;
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
106static 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
9d96e6c3 144static void ndisc_router_handler(sd_ndisc *nd, uint8_t flags, const struct in6_addr *gateway, unsigned lifetime, int pref, void *userdata) {
3b015d40 145 _cleanup_route_free_ Route *route = NULL;
a13c50e7 146 Link *link = userdata;
3b015d40 147 usec_t time_now;
7a695d8e 148 int r;
a13c50e7
TG
149
150 assert(link);
151 assert(link->network);
3b015d40 152 assert(link->manager);
a13c50e7
TG
153
154 if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
155 return;
156
7a695d8e
TG
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);
63348d13 162 if (r < 0 && r != -EBUSY)
7a695d8e
TG
163 log_link_warning_errno(link, r, "Starting DHCPv6 client on NDisc request failed: %m");
164 }
3b015d40
TG
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 ++;
9d96e6c3 192}
a13c50e7 193
9d96e6c3
TG
194static void ndisc_handler(sd_ndisc *nd, int event, void *userdata) {
195 Link *link = userdata;
7a695d8e 196 int r;
a13c50e7 197
9d96e6c3 198 assert(link);
a13c50e7 199
9d96e6c3
TG
200 if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
201 return;
a13c50e7 202
9d96e6c3
TG
203 switch (event) {
204 case SD_NDISC_EVENT_TIMEOUT:
7a695d8e
TG
205 dhcp6_request_address(link);
206
207 r = sd_dhcp6_client_start(link->dhcp6_client);
63348d13 208 if (r < 0 && r != -EBUSY)
7a695d8e 209 log_link_warning_errno(link, r, "Starting DHCPv6 client after NDisc timeout failed: %m");
a13c50e7 210 break;
9d96e6c3
TG
211 case SD_NDISC_EVENT_STOP:
212 break;
213 default:
214 log_link_warning(link, "IPv6 Neighbor Discovery unknown event: %d", event);
a13c50e7
TG
215 }
216}
217
218int ndisc_configure(Link *link) {
219 int r;
220
221 assert_return(link, -EINVAL);
222
223 r = sd_ndisc_new(&link->ndisc_router_discovery);
224 if (r < 0)
225 return r;
226
227 r = sd_ndisc_attach_event(link->ndisc_router_discovery, NULL, 0);
228 if (r < 0)
229 return r;
230
231 r = sd_ndisc_set_mac(link->ndisc_router_discovery, &link->mac);
232 if (r < 0)
233 return r;
234
235 r = sd_ndisc_set_index(link->ndisc_router_discovery, link->ifindex);
236 if (r < 0)
237 return r;
238
239 r = sd_ndisc_set_callback(link->ndisc_router_discovery,
9d96e6c3 240 ndisc_router_handler,
3b015d40
TG
241 ndisc_prefix_onlink_handler,
242 ndisc_prefix_autonomous_handler,
9d96e6c3
TG
243 ndisc_handler,
244 link);
a13c50e7
TG
245
246 return r;
247}