]>
Commit | Line | Data |
---|---|---|
011feef8 PF |
1 | /*** |
2 | This file is part of systemd. | |
3 | ||
4 | Copyright (C) 2013 Intel Corporation. All rights reserved. | |
5 | ||
6 | systemd is free software; you can redistribute it and/or modify it | |
7 | under the terms of the GNU Lesser General Public License as published by | |
8 | the Free Software Foundation; either version 2.1 of the License, or | |
9 | (at your option) any later version. | |
10 | ||
11 | systemd is distributed in the hope that it will be useful, but | |
12 | WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
14 | Lesser General Public License for more details. | |
15 | ||
16 | You should have received a copy of the GNU Lesser General Public License | |
17 | along with systemd; If not, see <http://www.gnu.org/licenses/>. | |
18 | ***/ | |
19 | ||
20 | #include <stdlib.h> | |
21 | #include <errno.h> | |
22 | #include <string.h> | |
23 | #include <stdio.h> | |
46a66b79 | 24 | #include <net/ethernet.h> |
aba26854 | 25 | #include <sys/param.h> |
011feef8 PF |
26 | |
27 | #include "util.h" | |
28 | #include "list.h" | |
29 | ||
30 | #include "dhcp-protocol.h" | |
a6cc569e | 31 | #include "dhcp-lease.h" |
46a66b79 | 32 | #include "dhcp-internal.h" |
011feef8 PF |
33 | #include "sd-dhcp-client.h" |
34 | ||
46a66b79 PF |
35 | #define DHCP_CLIENT_MIN_OPTIONS_SIZE 312 |
36 | ||
011feef8 PF |
37 | struct sd_dhcp_client { |
38 | DHCPState state; | |
d3d8ac2f | 39 | sd_event *event; |
b25ef18b | 40 | int event_priority; |
d3d8ac2f | 41 | sd_event_source *timeout_resend; |
011feef8 | 42 | int index; |
8c00042c PF |
43 | int fd; |
44 | union sockaddr_union link; | |
45 | sd_event_source *receive_message; | |
011feef8 | 46 | uint8_t *req_opts; |
7a7c74ca | 47 | size_t req_opts_allocated; |
011feef8 | 48 | size_t req_opts_size; |
3b349af6 | 49 | be32_t last_addr; |
46a66b79 PF |
50 | struct ether_addr mac_addr; |
51 | uint32_t xid; | |
d3d8ac2f | 52 | usec_t start_time; |
40e39f62 | 53 | uint16_t secs; |
e2dfc79f | 54 | unsigned int attempt; |
51debc1e PF |
55 | usec_t request_sent; |
56 | sd_event_source *timeout_t1; | |
57 | sd_event_source *timeout_t2; | |
58 | sd_event_source *timeout_expire; | |
751246ee PF |
59 | sd_dhcp_client_cb_t cb; |
60 | void *userdata; | |
a6cc569e | 61 | sd_dhcp_lease *lease; |
011feef8 PF |
62 | }; |
63 | ||
64 | static const uint8_t default_req_opts[] = { | |
65 | DHCP_OPTION_SUBNET_MASK, | |
66 | DHCP_OPTION_ROUTER, | |
67 | DHCP_OPTION_HOST_NAME, | |
68 | DHCP_OPTION_DOMAIN_NAME, | |
69 | DHCP_OPTION_DOMAIN_NAME_SERVER, | |
70 | DHCP_OPTION_NTP_SERVER, | |
71 | }; | |
72 | ||
aba26854 PF |
73 | static int client_receive_message(sd_event_source *s, int fd, |
74 | uint32_t revents, void *userdata); | |
75 | ||
751246ee | 76 | int sd_dhcp_client_set_callback(sd_dhcp_client *client, sd_dhcp_client_cb_t cb, |
5ee482df | 77 | void *userdata) { |
751246ee PF |
78 | assert_return(client, -EINVAL); |
79 | ||
80 | client->cb = cb; | |
81 | client->userdata = userdata; | |
82 | ||
83 | return 0; | |
84 | } | |
85 | ||
5ee482df | 86 | int sd_dhcp_client_set_request_option(sd_dhcp_client *client, uint8_t option) { |
011feef8 PF |
87 | size_t i; |
88 | ||
89 | assert_return(client, -EINVAL); | |
90 | assert_return (client->state == DHCP_STATE_INIT, -EBUSY); | |
91 | ||
92 | switch(option) { | |
93 | case DHCP_OPTION_PAD: | |
94 | case DHCP_OPTION_OVERLOAD: | |
95 | case DHCP_OPTION_MESSAGE_TYPE: | |
96 | case DHCP_OPTION_PARAMETER_REQUEST_LIST: | |
97 | case DHCP_OPTION_END: | |
98 | return -EINVAL; | |
99 | ||
100 | default: | |
101 | break; | |
102 | } | |
103 | ||
104 | for (i = 0; i < client->req_opts_size; i++) | |
105 | if (client->req_opts[i] == option) | |
106 | return -EEXIST; | |
107 | ||
7a7c74ca | 108 | if (!GREEDY_REALLOC(client->req_opts, client->req_opts_allocated, |
011feef8 PF |
109 | client->req_opts_size + 1)) |
110 | return -ENOMEM; | |
111 | ||
7a7c74ca | 112 | client->req_opts[client->req_opts_size++] = option; |
011feef8 PF |
113 | |
114 | return 0; | |
115 | } | |
116 | ||
117 | int sd_dhcp_client_set_request_address(sd_dhcp_client *client, | |
5ee482df | 118 | const struct in_addr *last_addr) { |
011feef8 PF |
119 | assert_return(client, -EINVAL); |
120 | assert_return(client->state == DHCP_STATE_INIT, -EBUSY); | |
121 | ||
122 | if (last_addr) | |
123 | client->last_addr = last_addr->s_addr; | |
124 | else | |
125 | client->last_addr = INADDR_ANY; | |
126 | ||
127 | return 0; | |
128 | } | |
129 | ||
5ee482df | 130 | int sd_dhcp_client_set_index(sd_dhcp_client *client, int interface_index) { |
011feef8 PF |
131 | assert_return(client, -EINVAL); |
132 | assert_return(client->state == DHCP_STATE_INIT, -EBUSY); | |
133 | assert_return(interface_index >= -1, -EINVAL); | |
134 | ||
135 | client->index = interface_index; | |
136 | ||
137 | return 0; | |
138 | } | |
139 | ||
46a66b79 | 140 | int sd_dhcp_client_set_mac(sd_dhcp_client *client, |
5ee482df | 141 | const struct ether_addr *addr) { |
46a66b79 PF |
142 | assert_return(client, -EINVAL); |
143 | assert_return(client->state == DHCP_STATE_INIT, -EBUSY); | |
144 | ||
145 | memcpy(&client->mac_addr, addr, ETH_ALEN); | |
146 | ||
147 | return 0; | |
148 | } | |
149 | ||
a6cc569e | 150 | int sd_dhcp_client_get_lease(sd_dhcp_client *client, sd_dhcp_lease **ret) { |
751246ee | 151 | assert_return(client, -EINVAL); |
a6cc569e | 152 | assert_return(ret, -EINVAL); |
751246ee | 153 | |
a6cc569e TG |
154 | if (client->state != DHCP_STATE_BOUND && |
155 | client->state != DHCP_STATE_RENEWING && | |
156 | client->state != DHCP_STATE_REBINDING) | |
157 | return -EADDRNOTAVAIL; | |
751246ee | 158 | |
a6cc569e | 159 | *ret = sd_dhcp_lease_ref(client->lease); |
4f882b2a TG |
160 | |
161 | return 0; | |
162 | } | |
163 | ||
5ee482df | 164 | static int client_notify(sd_dhcp_client *client, int event) { |
751246ee PF |
165 | if (client->cb) |
166 | client->cb(client, event, client->userdata); | |
167 | ||
3e3d8f78 PF |
168 | return 0; |
169 | } | |
170 | ||
5ee482df | 171 | static int client_stop(sd_dhcp_client *client, int error) { |
bbdf06d9 | 172 | assert_return(client, -EINVAL); |
bbdf06d9 | 173 | |
8c00042c PF |
174 | client->receive_message = |
175 | sd_event_source_unref(client->receive_message); | |
176 | ||
177 | if (client->fd >= 0) | |
178 | close(client->fd); | |
179 | client->fd = -1; | |
180 | ||
d3d8ac2f PF |
181 | client->timeout_resend = sd_event_source_unref(client->timeout_resend); |
182 | ||
51debc1e PF |
183 | client->timeout_t1 = sd_event_source_unref(client->timeout_t1); |
184 | client->timeout_t2 = sd_event_source_unref(client->timeout_t2); | |
185 | client->timeout_expire = sd_event_source_unref(client->timeout_expire); | |
186 | ||
e2dfc79f PF |
187 | client->attempt = 1; |
188 | ||
751246ee PF |
189 | client_notify(client, error); |
190 | ||
f8fdefe4 | 191 | client->start_time = 0; |
40e39f62 | 192 | client->secs = 0; |
f8fdefe4 | 193 | client->state = DHCP_STATE_INIT; |
bbdf06d9 | 194 | |
a6cc569e TG |
195 | if (client->lease) |
196 | client->lease = sd_dhcp_lease_unref(client->lease); | |
8c00042c | 197 | |
bbdf06d9 PF |
198 | return 0; |
199 | } | |
200 | ||
46a66b79 | 201 | static int client_packet_init(sd_dhcp_client *client, uint8_t type, |
f5a70de7 | 202 | DHCPMessage *message, uint16_t secs, |
5ee482df | 203 | uint8_t **opt, size_t *optlen) { |
46a66b79 | 204 | int err; |
564ba3b0 | 205 | be16_t max_size; |
46a66b79 PF |
206 | |
207 | *opt = (uint8_t *)(message + 1); | |
208 | ||
209 | if (*optlen < 4) | |
210 | return -ENOBUFS; | |
211 | *optlen -= 4; | |
212 | ||
213 | message->op = BOOTREQUEST; | |
214 | message->htype = 1; | |
215 | message->hlen = ETHER_ADDR_LEN; | |
216 | message->xid = htobe32(client->xid); | |
217 | ||
f5a70de7 PF |
218 | /* Although 'secs' field is a SHOULD in RFC 2131, certain DHCP servers |
219 | refuse to issue an DHCP lease if 'secs' is set to zero */ | |
220 | message->secs = htobe16(secs); | |
221 | ||
3dd71400 PF |
222 | if (client->state == DHCP_STATE_RENEWING || |
223 | client->state == DHCP_STATE_REBINDING) | |
aba26854 PF |
224 | message->ciaddr = client->lease->address; |
225 | ||
46a66b79 PF |
226 | memcpy(&message->chaddr, &client->mac_addr, ETH_ALEN); |
227 | (*opt)[0] = 0x63; | |
228 | (*opt)[1] = 0x82; | |
229 | (*opt)[2] = 0x53; | |
230 | (*opt)[3] = 0x63; | |
231 | ||
232 | *opt += 4; | |
233 | ||
234 | err = dhcp_option_append(opt, optlen, DHCP_OPTION_MESSAGE_TYPE, 1, | |
235 | &type); | |
236 | if (err < 0) | |
237 | return err; | |
238 | ||
239 | /* Some DHCP servers will refuse to issue an DHCP lease if the Cliient | |
240 | Identifier option is not set */ | |
241 | err = dhcp_option_append(opt, optlen, DHCP_OPTION_CLIENT_IDENTIFIER, | |
242 | ETH_ALEN, &client->mac_addr); | |
243 | if (err < 0) | |
244 | return err; | |
245 | ||
246 | if (type == DHCP_DISCOVER || type == DHCP_REQUEST) { | |
247 | err = dhcp_option_append(opt, optlen, | |
248 | DHCP_OPTION_PARAMETER_REQUEST_LIST, | |
249 | client->req_opts_size, | |
250 | client->req_opts); | |
251 | if (err < 0) | |
252 | return err; | |
564ba3b0 PF |
253 | |
254 | /* Some DHCP servers will send bigger DHCP packets than the | |
255 | defined default size unless the Maximum Messge Size option | |
256 | is explicitely set */ | |
257 | max_size = htobe16(DHCP_IP_UDP_SIZE + DHCP_MESSAGE_SIZE + | |
258 | DHCP_CLIENT_MIN_OPTIONS_SIZE); | |
259 | err = dhcp_option_append(opt, optlen, | |
260 | DHCP_OPTION_MAXIMUM_MESSAGE_SIZE, | |
261 | 2, &max_size); | |
262 | if (err < 0) | |
263 | return err; | |
46a66b79 PF |
264 | } |
265 | ||
266 | return 0; | |
267 | } | |
268 | ||
5ee482df | 269 | static uint16_t client_checksum(void *buf, int len) { |
46a66b79 PF |
270 | uint32_t sum; |
271 | uint16_t *check; | |
272 | int i; | |
273 | uint8_t *odd; | |
274 | ||
275 | sum = 0; | |
276 | check = buf; | |
277 | ||
278 | for (i = 0; i < len / 2 ; i++) | |
279 | sum += check[i]; | |
280 | ||
281 | if (len & 0x01) { | |
282 | odd = buf; | |
0c6a3c88 | 283 | sum += odd[len - 1]; |
46a66b79 PF |
284 | } |
285 | ||
0c6a3c88 PF |
286 | while (sum >> 16) |
287 | sum = (sum & 0xffff) + (sum >> 16); | |
288 | ||
289 | return ~sum; | |
46a66b79 PF |
290 | } |
291 | ||
5ee482df | 292 | static void client_append_ip_headers(DHCPPacket *packet, uint16_t len) { |
e2dfc79f PF |
293 | packet->ip.version = IPVERSION; |
294 | packet->ip.ihl = DHCP_IP_SIZE / 4; | |
295 | packet->ip.tot_len = htobe16(len); | |
296 | ||
297 | packet->ip.protocol = IPPROTO_UDP; | |
298 | packet->ip.saddr = INADDR_ANY; | |
299 | packet->ip.daddr = INADDR_BROADCAST; | |
300 | ||
301 | packet->udp.source = htobe16(DHCP_PORT_CLIENT); | |
302 | packet->udp.dest = htobe16(DHCP_PORT_SERVER); | |
303 | packet->udp.len = htobe16(len - DHCP_IP_SIZE); | |
304 | ||
305 | packet->ip.check = packet->udp.len; | |
306 | packet->udp.check = client_checksum(&packet->ip.ttl, len - 8); | |
307 | ||
308 | packet->ip.ttl = IPDEFTTL; | |
309 | packet->ip.check = 0; | |
310 | packet->ip.check = client_checksum(&packet->ip, DHCP_IP_SIZE); | |
311 | } | |
312 | ||
5ee482df | 313 | static int client_send_discover(sd_dhcp_client *client, uint16_t secs) { |
46a66b79 PF |
314 | int err = 0; |
315 | _cleanup_free_ DHCPPacket *discover; | |
316 | size_t optlen, len; | |
317 | uint8_t *opt; | |
318 | ||
319 | optlen = DHCP_CLIENT_MIN_OPTIONS_SIZE; | |
320 | len = sizeof(DHCPPacket) + optlen; | |
321 | ||
322 | discover = malloc0(len); | |
323 | ||
324 | if (!discover) | |
325 | return -ENOMEM; | |
326 | ||
327 | err = client_packet_init(client, DHCP_DISCOVER, &discover->dhcp, | |
f5a70de7 | 328 | secs, &opt, &optlen); |
46a66b79 PF |
329 | if (err < 0) |
330 | return err; | |
331 | ||
332 | if (client->last_addr != INADDR_ANY) { | |
333 | err = dhcp_option_append(&opt, &optlen, | |
334 | DHCP_OPTION_REQUESTED_IP_ADDRESS, | |
335 | 4, &client->last_addr); | |
336 | if (err < 0) | |
337 | return err; | |
338 | } | |
339 | ||
340 | err = dhcp_option_append(&opt, &optlen, DHCP_OPTION_END, 0, NULL); | |
341 | if (err < 0) | |
342 | return err; | |
343 | ||
e2dfc79f PF |
344 | client_append_ip_headers(discover, len); |
345 | ||
346 | err = dhcp_network_send_raw_socket(client->fd, &client->link, | |
347 | discover, len); | |
348 | ||
349 | return err; | |
350 | } | |
351 | ||
5ee482df | 352 | static int client_send_request(sd_dhcp_client *client, uint16_t secs) { |
e2dfc79f PF |
353 | _cleanup_free_ DHCPPacket *request; |
354 | size_t optlen, len; | |
355 | int err; | |
356 | uint8_t *opt; | |
357 | ||
358 | optlen = DHCP_CLIENT_MIN_OPTIONS_SIZE; | |
359 | len = DHCP_MESSAGE_SIZE + optlen; | |
360 | ||
361 | request = malloc0(len); | |
362 | if (!request) | |
363 | return -ENOMEM; | |
364 | ||
365 | err = client_packet_init(client, DHCP_REQUEST, &request->dhcp, secs, | |
366 | &opt, &optlen); | |
367 | if (err < 0) | |
368 | return err; | |
46a66b79 | 369 | |
e2dfc79f PF |
370 | if (client->state == DHCP_STATE_REQUESTING) { |
371 | err = dhcp_option_append(&opt, &optlen, | |
372 | DHCP_OPTION_REQUESTED_IP_ADDRESS, | |
373 | 4, &client->lease->address); | |
374 | if (err < 0) | |
375 | return err; | |
46a66b79 | 376 | |
e2dfc79f PF |
377 | err = dhcp_option_append(&opt, &optlen, |
378 | DHCP_OPTION_SERVER_IDENTIFIER, | |
379 | 4, &client->lease->server_address); | |
380 | if (err < 0) | |
381 | return err; | |
382 | } | |
46a66b79 | 383 | |
e2dfc79f PF |
384 | err = dhcp_option_append(&opt, &optlen, DHCP_OPTION_END, 0, NULL); |
385 | if (err < 0) | |
386 | return err; | |
46a66b79 | 387 | |
aba26854 PF |
388 | if (client->state == DHCP_STATE_RENEWING) { |
389 | err = dhcp_network_send_udp_socket(client->fd, | |
390 | client->lease->server_address, | |
391 | &request->dhcp, | |
392 | len - DHCP_IP_UDP_SIZE); | |
393 | } else { | |
394 | client_append_ip_headers(request, len); | |
46a66b79 | 395 | |
aba26854 PF |
396 | err = dhcp_network_send_raw_socket(client->fd, &client->link, |
397 | request, len); | |
398 | } | |
46a66b79 | 399 | |
8c00042c | 400 | return err; |
46a66b79 PF |
401 | } |
402 | ||
40e39f62 PF |
403 | static uint16_t client_update_secs(sd_dhcp_client *client, usec_t time_now) |
404 | { | |
405 | client->secs = (time_now - client->start_time) / USEC_PER_SEC; | |
406 | ||
407 | return client->secs; | |
408 | } | |
409 | ||
d3d8ac2f | 410 | static int client_timeout_resend(sd_event_source *s, uint64_t usec, |
5ee482df | 411 | void *userdata) { |
d3d8ac2f | 412 | sd_dhcp_client *client = userdata; |
aba26854 PF |
413 | usec_t next_timeout = 0; |
414 | uint32_t time_left; | |
b25ef18b TG |
415 | int r = 0; |
416 | ||
417 | assert(s); | |
418 | assert(client); | |
419 | assert(client->event); | |
d3d8ac2f | 420 | |
aba26854 PF |
421 | switch (client->state) { |
422 | case DHCP_STATE_RENEWING: | |
423 | ||
424 | time_left = (client->lease->t2 - client->lease->t1)/2; | |
425 | if (time_left < 60) | |
426 | time_left = 60; | |
d3d8ac2f | 427 | |
aba26854 | 428 | next_timeout = usec + time_left * USEC_PER_SEC; |
d3d8ac2f | 429 | |
aba26854 PF |
430 | break; |
431 | ||
3dd71400 PF |
432 | case DHCP_STATE_REBINDING: |
433 | ||
434 | time_left = (client->lease->lifetime - client->lease->t2)/2; | |
435 | if (time_left < 60) | |
436 | time_left = 60; | |
437 | ||
438 | next_timeout = usec + time_left * USEC_PER_SEC; | |
3dd71400 PF |
439 | break; |
440 | ||
aba26854 PF |
441 | case DHCP_STATE_INIT: |
442 | case DHCP_STATE_INIT_REBOOT: | |
443 | case DHCP_STATE_REBOOTING: | |
444 | case DHCP_STATE_SELECTING: | |
445 | case DHCP_STATE_REQUESTING: | |
446 | case DHCP_STATE_BOUND: | |
aba26854 PF |
447 | |
448 | if (client->attempt < 64) | |
449 | client->attempt *= 2; | |
450 | ||
451 | next_timeout = usec + (client->attempt - 1) * USEC_PER_SEC; | |
452 | ||
453 | break; | |
454 | } | |
455 | ||
9bf3b535 | 456 | next_timeout += (random_u32() & 0x1fffff); |
d3d8ac2f | 457 | |
b25ef18b | 458 | r = sd_event_add_monotonic(client->event, next_timeout, |
e2dfc79f PF |
459 | 10 * USEC_PER_MSEC, |
460 | client_timeout_resend, client, | |
461 | &client->timeout_resend); | |
b25ef18b TG |
462 | if (r < 0) |
463 | goto error; | |
464 | ||
465 | r = sd_event_source_set_priority(client->timeout_resend, client->event_priority); | |
466 | if (r < 0) | |
e2dfc79f | 467 | goto error; |
d3d8ac2f | 468 | |
e2dfc79f PF |
469 | switch (client->state) { |
470 | case DHCP_STATE_INIT: | |
40e39f62 PF |
471 | |
472 | client_update_secs(client, usec); | |
473 | ||
474 | r = client_send_discover(client, client->secs); | |
b25ef18b | 475 | if (r >= 0) { |
e2dfc79f PF |
476 | client->state = DHCP_STATE_SELECTING; |
477 | client->attempt = 1; | |
478 | } else { | |
479 | if (client->attempt >= 64) | |
480 | goto error; | |
481 | } | |
482 | ||
483 | break; | |
484 | ||
485 | case DHCP_STATE_SELECTING: | |
40e39f62 PF |
486 | client_update_secs(client, usec); |
487 | ||
488 | r = client_send_discover(client, client->secs); | |
b25ef18b | 489 | if (r < 0 && client->attempt >= 64) |
d3d8ac2f PF |
490 | goto error; |
491 | ||
e2dfc79f PF |
492 | break; |
493 | ||
494 | case DHCP_STATE_REQUESTING: | |
aba26854 | 495 | case DHCP_STATE_RENEWING: |
3dd71400 | 496 | case DHCP_STATE_REBINDING: |
40e39f62 | 497 | r = client_send_request(client, client->secs); |
b25ef18b | 498 | if (r < 0 && client->attempt >= 64) |
e2dfc79f | 499 | goto error; |
d3d8ac2f | 500 | |
51debc1e PF |
501 | client->request_sent = usec; |
502 | ||
d3d8ac2f PF |
503 | break; |
504 | ||
505 | case DHCP_STATE_INIT_REBOOT: | |
506 | case DHCP_STATE_REBOOTING: | |
d3d8ac2f | 507 | case DHCP_STATE_BOUND: |
d3d8ac2f PF |
508 | |
509 | break; | |
510 | } | |
511 | ||
512 | return 0; | |
513 | ||
514 | error: | |
b25ef18b | 515 | client_stop(client, r); |
d3d8ac2f PF |
516 | |
517 | /* Errors were dealt with when stopping the client, don't spill | |
518 | errors into the event loop handler */ | |
519 | return 0; | |
520 | } | |
521 | ||
5ee482df | 522 | static int client_initialize_events(sd_dhcp_client *client, usec_t usec) { |
6a1cd41e PF |
523 | int r; |
524 | ||
b25ef18b TG |
525 | assert(client); |
526 | assert(client->event); | |
527 | ||
6a1cd41e PF |
528 | r = sd_event_add_io(client->event, client->fd, EPOLLIN, |
529 | client_receive_message, client, | |
530 | &client->receive_message); | |
531 | if (r < 0) | |
532 | goto error; | |
533 | ||
b25ef18b TG |
534 | r = sd_event_source_set_priority(client->receive_message, client->event_priority); |
535 | if (r < 0) | |
536 | goto error; | |
537 | ||
6a1cd41e PF |
538 | r = sd_event_add_monotonic(client->event, usec, 0, |
539 | client_timeout_resend, client, | |
540 | &client->timeout_resend); | |
b25ef18b TG |
541 | if (r < 0) |
542 | goto error; | |
543 | ||
544 | r = sd_event_source_set_priority(client->timeout_resend, client->event_priority); | |
6a1cd41e PF |
545 | |
546 | error: | |
547 | if (r < 0) | |
548 | client_stop(client, r); | |
549 | ||
550 | return 0; | |
551 | ||
552 | } | |
553 | ||
51debc1e | 554 | static int client_timeout_expire(sd_event_source *s, uint64_t usec, |
5ee482df | 555 | void *userdata) { |
751246ee PF |
556 | sd_dhcp_client *client = userdata; |
557 | ||
558 | client_stop(client, DHCP_EVENT_EXPIRED); | |
559 | ||
51debc1e PF |
560 | return 0; |
561 | } | |
562 | ||
5ee482df | 563 | static int client_timeout_t2(sd_event_source *s, uint64_t usec, void *userdata) { |
3dd71400 PF |
564 | sd_dhcp_client *client = userdata; |
565 | int r; | |
566 | ||
567 | if (client->fd >= 0) { | |
568 | client->receive_message = | |
569 | sd_event_source_unref(client->receive_message); | |
570 | close(client->fd); | |
571 | client->fd = -1; | |
572 | } | |
573 | ||
574 | client->state = DHCP_STATE_REBINDING; | |
575 | client->attempt = 1; | |
576 | ||
577 | r = dhcp_network_bind_raw_socket(client->index, &client->link); | |
578 | if (r < 0) { | |
579 | client_stop(client, r); | |
580 | return 0; | |
581 | } | |
582 | ||
583 | client->fd = r; | |
584 | ||
585 | return client_initialize_events(client, usec); | |
51debc1e PF |
586 | } |
587 | ||
5ee482df | 588 | static int client_timeout_t1(sd_event_source *s, uint64_t usec, void *userdata) { |
aba26854 PF |
589 | sd_dhcp_client *client = userdata; |
590 | int r; | |
591 | ||
592 | client->state = DHCP_STATE_RENEWING; | |
593 | client->attempt = 1; | |
594 | ||
595 | r = dhcp_network_bind_udp_socket(client->index, | |
596 | client->lease->address); | |
6a1cd41e PF |
597 | if (r < 0) { |
598 | client_stop(client, r); | |
599 | return 0; | |
600 | } | |
aba26854 PF |
601 | |
602 | client->fd = r; | |
aba26854 | 603 | |
6a1cd41e | 604 | return client_initialize_events(client, usec); |
51debc1e PF |
605 | } |
606 | ||
3e3d8f78 | 607 | static int client_verify_headers(sd_dhcp_client *client, DHCPPacket *message, |
5ee482df | 608 | size_t len) { |
8c00042c | 609 | size_t hdrlen; |
8c00042c PF |
610 | |
611 | if (len < (DHCP_IP_UDP_SIZE + DHCP_MESSAGE_SIZE)) | |
612 | return -EINVAL; | |
613 | ||
3e3d8f78 PF |
614 | hdrlen = message->ip.ihl * 4; |
615 | if (hdrlen < 20 || hdrlen > len || client_checksum(&message->ip, | |
8c00042c PF |
616 | hdrlen)) |
617 | return -EINVAL; | |
618 | ||
1aed1cbc | 619 | if (hdrlen + be16toh(message->udp.len) > len) |
8c00042c PF |
620 | return -EINVAL; |
621 | ||
1aed1cbc PF |
622 | if (message->udp.check) { |
623 | message->ip.check = message->udp.len; | |
624 | message->ip.ttl = 0; | |
625 | ||
626 | if (client_checksum(&message->ip.ttl, | |
627 | be16toh(message->udp.len) + 12)) | |
628 | return -EINVAL; | |
629 | } | |
630 | ||
3e3d8f78 PF |
631 | if (be16toh(message->udp.source) != DHCP_PORT_SERVER || |
632 | be16toh(message->udp.dest) != DHCP_PORT_CLIENT) | |
8c00042c PF |
633 | return -EINVAL; |
634 | ||
3e3d8f78 | 635 | if (message->dhcp.op != BOOTREPLY) |
8c00042c PF |
636 | return -EINVAL; |
637 | ||
3e3d8f78 | 638 | if (be32toh(message->dhcp.xid) != client->xid) |
8c00042c PF |
639 | return -EINVAL; |
640 | ||
3e3d8f78 | 641 | if (memcmp(&message->dhcp.chaddr[0], &client->mac_addr.ether_addr_octet, |
8c00042c PF |
642 | ETHER_ADDR_LEN)) |
643 | return -EINVAL; | |
644 | ||
3e3d8f78 PF |
645 | return 0; |
646 | } | |
647 | ||
648 | static int client_receive_offer(sd_dhcp_client *client, DHCPPacket *offer, | |
5ee482df | 649 | size_t len) { |
a6cc569e | 650 | _cleanup_dhcp_lease_unref_ sd_dhcp_lease *lease = NULL; |
5ee482df | 651 | int r; |
3e3d8f78 | 652 | |
5ee482df TG |
653 | r = client_verify_headers(client, offer, len); |
654 | if (r < 0) | |
655 | return r; | |
3e3d8f78 | 656 | |
a6cc569e TG |
657 | r = dhcp_lease_new(&lease); |
658 | if (r < 0) | |
659 | return r; | |
8c00042c PF |
660 | |
661 | len = len - DHCP_IP_UDP_SIZE; | |
a6cc569e | 662 | r = dhcp_option_parse(&offer->dhcp, len, dhcp_lease_parse_options, |
5ee482df TG |
663 | lease); |
664 | if (r != DHCP_OFFER) | |
665 | return -ENOMSG; | |
8c00042c PF |
666 | |
667 | lease->address = offer->dhcp.yiaddr; | |
668 | ||
669 | if (lease->address == INADDR_ANY || | |
670 | lease->server_address == INADDR_ANY || | |
671 | lease->subnet_mask == INADDR_ANY || | |
672 | lease->lifetime == 0) | |
5ee482df | 673 | return -ENOMSG; |
8c00042c PF |
674 | |
675 | client->lease = lease; | |
5ee482df | 676 | lease = NULL; |
8c00042c PF |
677 | |
678 | return 0; | |
8c00042c PF |
679 | } |
680 | ||
aba26854 | 681 | static int client_receive_ack(sd_dhcp_client *client, const uint8_t *buf, |
5ee482df | 682 | size_t len) { |
aba26854 PF |
683 | DHCPPacket *ack; |
684 | DHCPMessage *dhcp; | |
a6cc569e | 685 | _cleanup_dhcp_lease_unref_ sd_dhcp_lease *lease = NULL; |
5ee482df | 686 | int r; |
3e3d8f78 | 687 | |
aba26854 PF |
688 | if (client->state == DHCP_STATE_RENEWING) { |
689 | dhcp = (DHCPMessage *)buf; | |
690 | } else { | |
691 | ack = (DHCPPacket *)buf; | |
692 | ||
693 | r = client_verify_headers(client, ack, len); | |
694 | if (r < 0) | |
695 | return r; | |
696 | ||
697 | dhcp = &ack->dhcp; | |
698 | len -= DHCP_IP_UDP_SIZE; | |
699 | } | |
3e3d8f78 | 700 | |
a6cc569e TG |
701 | r = dhcp_lease_new(&lease); |
702 | if (r < 0) | |
703 | return r; | |
3e3d8f78 | 704 | |
a6cc569e | 705 | r = dhcp_option_parse(dhcp, len, dhcp_lease_parse_options, lease); |
5ee482df TG |
706 | if (r == DHCP_NAK) |
707 | return DHCP_EVENT_NO_LEASE; | |
3e3d8f78 | 708 | |
5ee482df TG |
709 | if (r != DHCP_ACK) |
710 | return -ENOMSG; | |
3e3d8f78 | 711 | |
aba26854 | 712 | lease->address = dhcp->yiaddr; |
3e3d8f78 PF |
713 | |
714 | if (lease->address == INADDR_ANY || | |
715 | lease->server_address == INADDR_ANY || | |
5ee482df TG |
716 | lease->subnet_mask == INADDR_ANY || lease->lifetime == 0) |
717 | return -ENOMSG; | |
3e3d8f78 PF |
718 | |
719 | r = DHCP_EVENT_IP_ACQUIRE; | |
720 | if (client->lease) { | |
721 | if (client->lease->address != lease->address || | |
722 | client->lease->subnet_mask != lease->subnet_mask || | |
723 | client->lease->router != lease->router) { | |
724 | r = DHCP_EVENT_IP_CHANGE; | |
725 | } | |
726 | ||
a6cc569e | 727 | client->lease = sd_dhcp_lease_unref(client->lease); |
3e3d8f78 PF |
728 | } |
729 | ||
730 | client->lease = lease; | |
5ee482df | 731 | lease = NULL; |
3e3d8f78 PF |
732 | |
733 | return r; | |
734 | } | |
735 | ||
51debc1e | 736 | static uint64_t client_compute_timeout(uint64_t request_sent, |
5ee482df | 737 | uint32_t lifetime) { |
51debc1e | 738 | return request_sent + (lifetime - 3) * USEC_PER_SEC + |
9bf3b535 | 739 | + (random_u32() & 0x1fffff); |
51debc1e PF |
740 | } |
741 | ||
5ee482df | 742 | static int client_set_lease_timeouts(sd_dhcp_client *client, uint64_t usec) { |
51debc1e | 743 | uint64_t next_timeout; |
5ee482df | 744 | int r; |
51debc1e | 745 | |
b25ef18b TG |
746 | assert(client); |
747 | assert(client->event); | |
748 | ||
51debc1e PF |
749 | if (client->lease->lifetime < 10) |
750 | return -EINVAL; | |
751 | ||
aba26854 PF |
752 | client->timeout_t1 = sd_event_source_unref(client->timeout_t1); |
753 | client->timeout_t2 = sd_event_source_unref(client->timeout_t2); | |
754 | client->timeout_expire = sd_event_source_unref(client->timeout_expire); | |
755 | ||
51debc1e PF |
756 | if (!client->lease->t1) |
757 | client->lease->t1 = client->lease->lifetime / 2; | |
758 | ||
759 | next_timeout = client_compute_timeout(client->request_sent, | |
760 | client->lease->t1); | |
761 | if (next_timeout < usec) | |
762 | return -EINVAL; | |
763 | ||
5ee482df | 764 | r = sd_event_add_monotonic(client->event, next_timeout, |
51debc1e PF |
765 | 10 * USEC_PER_MSEC, |
766 | client_timeout_t1, client, | |
767 | &client->timeout_t1); | |
5ee482df TG |
768 | if (r < 0) |
769 | return r; | |
51debc1e | 770 | |
b25ef18b TG |
771 | r = sd_event_source_set_priority(client->timeout_t1, client->event_priority); |
772 | if (r < 0) | |
773 | return r; | |
774 | ||
51debc1e PF |
775 | if (!client->lease->t2) |
776 | client->lease->t2 = client->lease->lifetime * 7 / 8; | |
777 | ||
778 | if (client->lease->t2 < client->lease->t1) | |
779 | return -EINVAL; | |
780 | ||
781 | if (client->lease->lifetime < client->lease->t2) | |
782 | return -EINVAL; | |
783 | ||
784 | next_timeout = client_compute_timeout(client->request_sent, | |
785 | client->lease->t2); | |
786 | if (next_timeout < usec) | |
787 | return -EINVAL; | |
788 | ||
5ee482df | 789 | r = sd_event_add_monotonic(client->event, next_timeout, |
51debc1e PF |
790 | 10 * USEC_PER_MSEC, |
791 | client_timeout_t2, client, | |
792 | &client->timeout_t2); | |
5ee482df TG |
793 | if (r < 0) |
794 | return r; | |
51debc1e | 795 | |
b25ef18b TG |
796 | r = sd_event_source_set_priority(client->timeout_t2, client->event_priority); |
797 | if (r < 0) | |
798 | return r; | |
799 | ||
51debc1e PF |
800 | next_timeout = client_compute_timeout(client->request_sent, |
801 | client->lease->lifetime); | |
802 | if (next_timeout < usec) | |
803 | return -EINVAL; | |
804 | ||
5ee482df | 805 | r = sd_event_add_monotonic(client->event, next_timeout, |
51debc1e PF |
806 | 10 * USEC_PER_MSEC, |
807 | client_timeout_expire, client, | |
808 | &client->timeout_expire); | |
5ee482df TG |
809 | if (r < 0) |
810 | return r; | |
51debc1e | 811 | |
b25ef18b TG |
812 | r = sd_event_source_set_priority(client->timeout_expire, client->event_priority); |
813 | if (r < 0) | |
814 | return r; | |
815 | ||
51debc1e PF |
816 | return 0; |
817 | } | |
818 | ||
aba26854 | 819 | static int client_receive_message(sd_event_source *s, int fd, |
5ee482df | 820 | uint32_t revents, void *userdata) { |
8c00042c PF |
821 | sd_dhcp_client *client = userdata; |
822 | uint8_t buf[sizeof(DHCPPacket) + DHCP_CLIENT_MIN_OPTIONS_SIZE]; | |
823 | int buflen = sizeof(buf); | |
aba26854 | 824 | int len, r = 0, notify_event = 0; |
8c00042c | 825 | DHCPPacket *message; |
e2dfc79f | 826 | usec_t time_now; |
8c00042c | 827 | |
b25ef18b TG |
828 | assert(s); |
829 | assert(client); | |
830 | assert(client->event); | |
831 | ||
8c00042c PF |
832 | len = read(fd, &buf, buflen); |
833 | if (len < 0) | |
e2dfc79f PF |
834 | return 0; |
835 | ||
3e3d8f78 PF |
836 | r = sd_event_get_now_monotonic(client->event, &time_now); |
837 | if (r < 0) | |
8c00042c PF |
838 | goto error; |
839 | ||
8c00042c PF |
840 | switch (client->state) { |
841 | case DHCP_STATE_SELECTING: | |
842 | ||
aba26854 PF |
843 | message = (DHCPPacket *)&buf; |
844 | ||
8c00042c PF |
845 | if (client_receive_offer(client, message, len) >= 0) { |
846 | ||
8c00042c PF |
847 | client->timeout_resend = |
848 | sd_event_source_unref(client->timeout_resend); | |
849 | ||
850 | client->state = DHCP_STATE_REQUESTING; | |
e2dfc79f PF |
851 | client->attempt = 1; |
852 | ||
3e3d8f78 PF |
853 | r = sd_event_add_monotonic(client->event, time_now, 0, |
854 | client_timeout_resend, | |
855 | client, | |
856 | &client->timeout_resend); | |
857 | if (r < 0) | |
e2dfc79f | 858 | goto error; |
b25ef18b TG |
859 | |
860 | r = sd_event_source_set_priority(client->timeout_resend, client->event_priority); | |
861 | if (r < 0) | |
862 | goto error; | |
8c00042c PF |
863 | } |
864 | ||
865 | break; | |
866 | ||
3e3d8f78 | 867 | case DHCP_STATE_REQUESTING: |
aba26854 | 868 | case DHCP_STATE_RENEWING: |
3dd71400 | 869 | case DHCP_STATE_REBINDING: |
aba26854 PF |
870 | |
871 | r = client_receive_ack(client, buf, len); | |
3e3d8f78 | 872 | |
3e3d8f78 PF |
873 | if (r == DHCP_EVENT_NO_LEASE) |
874 | goto error; | |
875 | ||
876 | if (r >= 0) { | |
877 | client->timeout_resend = | |
878 | sd_event_source_unref(client->timeout_resend); | |
879 | ||
aba26854 PF |
880 | if (client->state == DHCP_STATE_REQUESTING) |
881 | notify_event = DHCP_EVENT_IP_ACQUIRE; | |
882 | else if (r != DHCP_EVENT_IP_ACQUIRE) | |
883 | notify_event = r; | |
884 | ||
3e3d8f78 PF |
885 | client->state = DHCP_STATE_BOUND; |
886 | client->attempt = 1; | |
887 | ||
888 | client->last_addr = client->lease->address; | |
889 | ||
51debc1e | 890 | r = client_set_lease_timeouts(client, time_now); |
aba26854 | 891 | if (r < 0) |
51debc1e PF |
892 | goto error; |
893 | ||
aba26854 PF |
894 | if (notify_event) |
895 | client_notify(client, notify_event); | |
3e3d8f78 | 896 | |
3e3d8f78 PF |
897 | client->receive_message = |
898 | sd_event_source_unref(client->receive_message); | |
2ed0375c PF |
899 | close(client->fd); |
900 | client->fd = -1; | |
3e3d8f78 | 901 | } |
97b9372d PF |
902 | |
903 | r = 0; | |
904 | ||
3e3d8f78 PF |
905 | break; |
906 | ||
8c00042c PF |
907 | case DHCP_STATE_INIT: |
908 | case DHCP_STATE_INIT_REBOOT: | |
909 | case DHCP_STATE_REBOOTING: | |
8c00042c | 910 | case DHCP_STATE_BOUND: |
8c00042c PF |
911 | |
912 | break; | |
913 | } | |
914 | ||
915 | error: | |
97b9372d | 916 | if (r < 0 || r == DHCP_EVENT_NO_LEASE) |
3e3d8f78 | 917 | return client_stop(client, r); |
e2dfc79f | 918 | |
8c00042c PF |
919 | return 0; |
920 | } | |
921 | ||
5ee482df | 922 | int sd_dhcp_client_start(sd_dhcp_client *client) { |
6a1cd41e | 923 | int r; |
d3d8ac2f | 924 | |
46a66b79 | 925 | assert_return(client, -EINVAL); |
b25ef18b | 926 | assert_return(client->event, -EINVAL); |
23f30ed3 | 927 | assert_return(client->index > 0, -EINVAL); |
46a66b79 PF |
928 | assert_return(client->state == DHCP_STATE_INIT || |
929 | client->state == DHCP_STATE_INIT_REBOOT, -EBUSY); | |
930 | ||
9bf3b535 | 931 | client->xid = random_u32(); |
46a66b79 | 932 | |
6a1cd41e | 933 | r = dhcp_network_bind_raw_socket(client->index, &client->link); |
8c00042c | 934 | |
6a1cd41e PF |
935 | if (r < 0) { |
936 | client_stop(client, r); | |
937 | return r; | |
8c00042c PF |
938 | } |
939 | ||
6a1cd41e | 940 | client->fd = r; |
e2dfc79f | 941 | client->start_time = now(CLOCK_MONOTONIC); |
40e39f62 | 942 | client->secs = 0; |
d3d8ac2f | 943 | |
6a1cd41e | 944 | return client_initialize_events(client, client->start_time); |
46a66b79 PF |
945 | } |
946 | ||
5ee482df | 947 | int sd_dhcp_client_stop(sd_dhcp_client *client) { |
751246ee | 948 | return client_stop(client, DHCP_EVENT_STOP); |
bbdf06d9 PF |
949 | } |
950 | ||
b25ef18b TG |
951 | int sd_dhcp_client_attach_event(sd_dhcp_client *client, sd_event *event, int priority) { |
952 | int r; | |
953 | ||
954 | assert_return(client, -EINVAL); | |
955 | assert_return(!client->event, -EBUSY); | |
956 | ||
957 | if (event) | |
958 | client->event = sd_event_ref(event); | |
959 | else { | |
960 | r = sd_event_default(&client->event); | |
961 | if (r < 0) | |
962 | return 0; | |
963 | } | |
964 | ||
965 | client->event_priority = priority; | |
966 | ||
967 | return 0; | |
968 | } | |
969 | ||
970 | int sd_dhcp_client_detach_event(sd_dhcp_client *client) { | |
971 | assert_return(client, -EINVAL); | |
972 | ||
973 | client->event = sd_event_unref(client->event); | |
974 | ||
975 | return 0; | |
976 | } | |
977 | ||
978 | sd_event *sd_dhcp_client_get_event(sd_dhcp_client *client) { | |
979 | if (!client) | |
980 | return NULL; | |
981 | ||
982 | return client->event; | |
983 | } | |
984 | ||
985 | void sd_dhcp_client_free(sd_dhcp_client *client) { | |
986 | if (!client) | |
987 | return; | |
d2fe46b5 PF |
988 | |
989 | sd_dhcp_client_stop(client); | |
b25ef18b | 990 | sd_dhcp_client_detach_event(client); |
d2fe46b5 | 991 | |
d2fe46b5 PF |
992 | free(client->req_opts); |
993 | free(client); | |
d2fe46b5 PF |
994 | } |
995 | ||
b25ef18b TG |
996 | DEFINE_TRIVIAL_CLEANUP_FUNC(sd_dhcp_client*, sd_dhcp_client_free); |
997 | #define _cleanup_dhcp_client_free_ _cleanup_(sd_dhcp_client_freep) | |
998 | ||
999 | int sd_dhcp_client_new(sd_dhcp_client **ret) { | |
1000 | _cleanup_dhcp_client_free_ sd_dhcp_client *client = NULL; | |
011feef8 | 1001 | |
b25ef18b | 1002 | assert_return(ret, -EINVAL); |
d3d8ac2f | 1003 | |
011feef8 PF |
1004 | client = new0(sd_dhcp_client, 1); |
1005 | if (!client) | |
b25ef18b | 1006 | return -ENOMEM; |
011feef8 PF |
1007 | |
1008 | client->state = DHCP_STATE_INIT; | |
1009 | client->index = -1; | |
8c00042c | 1010 | client->fd = -1; |
e2dfc79f | 1011 | client->attempt = 1; |
011feef8 PF |
1012 | |
1013 | client->req_opts_size = ELEMENTSOF(default_req_opts); | |
1014 | ||
1015 | client->req_opts = memdup(default_req_opts, client->req_opts_size); | |
b25ef18b TG |
1016 | if (!client->req_opts) |
1017 | return -ENOMEM; | |
1018 | ||
1019 | *ret = client; | |
1020 | client = NULL; | |
011feef8 | 1021 | |
b25ef18b | 1022 | return 0; |
011feef8 | 1023 | } |