chiark / gitweb /
d3ba7ae8ede44a53386cd0a0900e95acaf553d2d
[elogind.git] / src / libsystemd-dhcp / dhcp-client.c
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>
24 #include <net/ethernet.h>
25
26 #include "util.h"
27 #include "list.h"
28
29 #include "dhcp-protocol.h"
30 #include "dhcp-internal.h"
31 #include "sd-dhcp-client.h"
32
33 #define DHCP_CLIENT_MIN_OPTIONS_SIZE            312
34
35 struct DHCPLease {
36         uint32_t lifetime;
37         uint32_t address;
38         uint32_t server_address;
39         uint32_t subnet_mask;
40         uint32_t router;
41 };
42
43 typedef struct DHCPLease DHCPLease;
44
45 struct sd_dhcp_client {
46         DHCPState state;
47         sd_event *event;
48         sd_event_source *timeout_resend;
49         int index;
50         int fd;
51         union sockaddr_union link;
52         sd_event_source *receive_message;
53         uint8_t *req_opts;
54         size_t req_opts_size;
55         uint32_t last_addr;
56         struct ether_addr mac_addr;
57         uint32_t xid;
58         usec_t start_time;
59         DHCPLease *lease;
60 };
61
62 static const uint8_t default_req_opts[] = {
63         DHCP_OPTION_SUBNET_MASK,
64         DHCP_OPTION_ROUTER,
65         DHCP_OPTION_HOST_NAME,
66         DHCP_OPTION_DOMAIN_NAME,
67         DHCP_OPTION_DOMAIN_NAME_SERVER,
68         DHCP_OPTION_NTP_SERVER,
69 };
70
71 int sd_dhcp_client_set_request_option(sd_dhcp_client *client, uint8_t option)
72 {
73         size_t i;
74
75         assert_return(client, -EINVAL);
76         assert_return (client->state == DHCP_STATE_INIT, -EBUSY);
77
78         switch(option) {
79         case DHCP_OPTION_PAD:
80         case DHCP_OPTION_OVERLOAD:
81         case DHCP_OPTION_MESSAGE_TYPE:
82         case DHCP_OPTION_PARAMETER_REQUEST_LIST:
83         case DHCP_OPTION_END:
84                 return -EINVAL;
85
86         default:
87                 break;
88         }
89
90         for (i = 0; i < client->req_opts_size; i++)
91                 if (client->req_opts[i] == option)
92                         return -EEXIST;
93
94         if (!GREEDY_REALLOC(client->req_opts, client->req_opts_size,
95                             client->req_opts_size + 1))
96                 return -ENOMEM;
97
98         client->req_opts[client->req_opts_size - 1] = option;
99
100         return 0;
101 }
102
103 int sd_dhcp_client_set_request_address(sd_dhcp_client *client,
104                                        const struct in_addr *last_addr)
105 {
106         assert_return(client, -EINVAL);
107         assert_return(client->state == DHCP_STATE_INIT, -EBUSY);
108
109         if (last_addr)
110                 client->last_addr = last_addr->s_addr;
111         else
112                 client->last_addr = INADDR_ANY;
113
114         return 0;
115 }
116
117 int sd_dhcp_client_set_index(sd_dhcp_client *client, int interface_index)
118 {
119         assert_return(client, -EINVAL);
120         assert_return(client->state == DHCP_STATE_INIT, -EBUSY);
121         assert_return(interface_index >= -1, -EINVAL);
122
123         client->index = interface_index;
124
125         return 0;
126 }
127
128 int sd_dhcp_client_set_mac(sd_dhcp_client *client,
129                            const struct ether_addr *addr)
130 {
131         assert_return(client, -EINVAL);
132         assert_return(client->state == DHCP_STATE_INIT, -EBUSY);
133
134         memcpy(&client->mac_addr, addr, ETH_ALEN);
135
136         return 0;
137 }
138
139 static int client_stop(sd_dhcp_client *client, int error)
140 {
141         assert_return(client, -EINVAL);
142         assert_return(client->state != DHCP_STATE_INIT &&
143                       client->state != DHCP_STATE_INIT_REBOOT, -EALREADY);
144
145         client->receive_message =
146                 sd_event_source_unref(client->receive_message);
147
148         if (client->fd >= 0)
149                 close(client->fd);
150         client->fd = -1;
151
152         client->timeout_resend = sd_event_source_unref(client->timeout_resend);
153
154         switch (client->state) {
155
156         case DHCP_STATE_INIT:
157         case DHCP_STATE_SELECTING:
158
159                 client->start_time = 0;
160                 client->state = DHCP_STATE_INIT;
161                 break;
162
163         case DHCP_STATE_INIT_REBOOT:
164         case DHCP_STATE_REBOOTING:
165         case DHCP_STATE_REQUESTING:
166         case DHCP_STATE_BOUND:
167         case DHCP_STATE_RENEWING:
168         case DHCP_STATE_REBINDING:
169
170                 break;
171         }
172
173         if (client->lease) {
174                 free(client->lease);
175                 client->lease = NULL;
176         }
177
178         return 0;
179 }
180
181 static int client_packet_init(sd_dhcp_client *client, uint8_t type,
182                               DHCPMessage *message, uint16_t secs,
183                               uint8_t **opt, size_t *optlen)
184 {
185         int err;
186
187         *opt = (uint8_t *)(message + 1);
188
189         if (*optlen < 4)
190                 return -ENOBUFS;
191         *optlen -= 4;
192
193         message->op = BOOTREQUEST;
194         message->htype = 1;
195         message->hlen = ETHER_ADDR_LEN;
196         message->xid = htobe32(client->xid);
197
198         /* Although 'secs' field is a SHOULD in RFC 2131, certain DHCP servers
199            refuse to issue an DHCP lease if 'secs' is set to zero */
200         message->secs = htobe16(secs);
201
202         memcpy(&message->chaddr, &client->mac_addr, ETH_ALEN);
203         (*opt)[0] = 0x63;
204         (*opt)[1] = 0x82;
205         (*opt)[2] = 0x53;
206         (*opt)[3] = 0x63;
207
208         *opt += 4;
209
210         err = dhcp_option_append(opt, optlen, DHCP_OPTION_MESSAGE_TYPE, 1,
211                                  &type);
212         if (err < 0)
213                 return err;
214
215         /* Some DHCP servers will refuse to issue an DHCP lease if the Cliient
216            Identifier option is not set */
217         err = dhcp_option_append(opt, optlen, DHCP_OPTION_CLIENT_IDENTIFIER,
218                                  ETH_ALEN, &client->mac_addr);
219         if (err < 0)
220                 return err;
221
222         if (type == DHCP_DISCOVER || type == DHCP_REQUEST) {
223                 err = dhcp_option_append(opt, optlen,
224                                          DHCP_OPTION_PARAMETER_REQUEST_LIST,
225                                          client->req_opts_size,
226                                          client->req_opts);
227                 if (err < 0)
228                         return err;
229         }
230
231         return 0;
232 }
233
234 static uint16_t client_checksum(void *buf, int len)
235 {
236         uint32_t sum;
237         uint16_t *check;
238         int i;
239         uint8_t *odd;
240
241         sum = 0;
242         check = buf;
243
244         for (i = 0; i < len / 2 ; i++)
245                 sum += check[i];
246
247         if (len & 0x01) {
248                 odd = buf;
249                 sum += odd[len];
250         }
251
252         return ~((sum & 0xffff) + (sum >> 16));
253 }
254
255 static int client_send_discover(sd_dhcp_client *client, uint16_t secs)
256 {
257         int err = 0;
258         _cleanup_free_ DHCPPacket *discover;
259         size_t optlen, len;
260         uint8_t *opt;
261
262         optlen = DHCP_CLIENT_MIN_OPTIONS_SIZE;
263         len = sizeof(DHCPPacket) + optlen;
264
265         discover = malloc0(len);
266
267         if (!discover)
268                 return -ENOMEM;
269
270         err = client_packet_init(client, DHCP_DISCOVER, &discover->dhcp,
271                                  secs, &opt, &optlen);
272         if (err < 0)
273                 return err;
274
275         if (client->last_addr != INADDR_ANY) {
276                 err = dhcp_option_append(&opt, &optlen,
277                                          DHCP_OPTION_REQUESTED_IP_ADDRESS,
278                                          4, &client->last_addr);
279                 if (err < 0)
280                         return err;
281         }
282
283         err = dhcp_option_append(&opt, &optlen, DHCP_OPTION_END, 0, NULL);
284         if (err < 0)
285                 return err;
286
287         discover->ip.version = IPVERSION;
288         discover->ip.ihl = sizeof(discover->ip) >> 2;
289         discover->ip.tot_len = htobe16(len);
290
291         discover->ip.protocol = IPPROTO_UDP;
292         discover->ip.saddr = INADDR_ANY;
293         discover->ip.daddr = INADDR_BROADCAST;
294
295         discover->udp.source = htobe16(DHCP_PORT_CLIENT);
296         discover->udp.dest = htobe16(DHCP_PORT_SERVER);
297         discover->udp.len = htobe16(len - sizeof(discover->ip));
298
299         discover->ip.check = discover->udp.len;
300         discover->udp.check = client_checksum(&discover->ip.ttl,
301                                               len - 8);
302
303         discover->ip.ttl = IPDEFTTL;
304         discover->ip.check = 0;
305         discover->ip.check = client_checksum(&discover->ip,
306                                              sizeof(discover->ip));
307
308         err = dhcp_network_send_raw_socket(client->fd, &client->link,
309                                            discover, len);
310
311         return err;
312 }
313
314 static int client_timeout_resend(sd_event_source *s, uint64_t usec,
315                                  void *userdata)
316 {
317         sd_dhcp_client *client = userdata;
318         usec_t next_timeout;
319         uint16_t secs;
320         int err = 0;
321
322         switch (client->state) {
323         case DHCP_STATE_INIT:
324         case DHCP_STATE_SELECTING:
325
326                 if (!client->start_time)
327                         client->start_time = usec;
328
329                 secs = (usec - client->start_time) / USEC_PER_SEC;
330
331                 next_timeout = usec + 2 * USEC_PER_SEC + (random() & 0x1fffff);
332
333                 err = sd_event_add_monotonic(client->event, next_timeout,
334                                              10 * USEC_PER_MSEC,
335                                              client_timeout_resend, client,
336                                              &client->timeout_resend);
337                 if (err < 0)
338                         goto error;
339
340                 if (client_send_discover(client, secs) >= 0)
341                         client->state = DHCP_STATE_SELECTING;
342
343                 break;
344
345         case DHCP_STATE_INIT_REBOOT:
346         case DHCP_STATE_REBOOTING:
347         case DHCP_STATE_REQUESTING:
348         case DHCP_STATE_BOUND:
349         case DHCP_STATE_RENEWING:
350         case DHCP_STATE_REBINDING:
351
352                 break;
353         }
354
355         return 0;
356
357 error:
358         client_stop(client, err);
359
360         /* Errors were dealt with when stopping the client, don't spill
361            errors into the event loop handler */
362         return 0;
363 }
364
365 static int client_parse_offer(uint8_t code, uint8_t len, const uint8_t *option,
366                               void *user_data)
367 {
368         DHCPLease *lease = user_data;
369         be32_t val;
370
371         switch(code) {
372
373         case DHCP_OPTION_IP_ADDRESS_LEASE_TIME:
374                 if (len == 4) {
375                         memcpy(&val, option, 4);
376                         lease->lifetime = be32toh(val);
377                 }
378
379                 break;
380
381         case DHCP_OPTION_SERVER_IDENTIFIER:
382                 if (len >= 4)
383                         memcpy(&lease->server_address, option, 4);
384
385                 break;
386
387         case DHCP_OPTION_SUBNET_MASK:
388                 if (len >= 4)
389                         memcpy(&lease->subnet_mask, option, 4);
390
391                 break;
392
393         case DHCP_OPTION_ROUTER:
394                 if (len >= 4)
395                         memcpy(&lease->router, option, 4);
396
397                 break;
398         }
399
400         return 0;
401 }
402
403 static int client_receive_offer(sd_dhcp_client *client, DHCPPacket *offer,
404                                 size_t len)
405 {
406         size_t hdrlen;
407         DHCPLease *lease;
408
409         if (len < (DHCP_IP_UDP_SIZE + DHCP_MESSAGE_SIZE))
410                 return -EINVAL;
411
412         hdrlen = offer->ip.ihl * 4;
413         if (hdrlen < 20 || hdrlen > len || client_checksum(&offer->ip,
414                                                            hdrlen))
415                 return -EINVAL;
416
417         offer->ip.check = offer->udp.len;
418         offer->ip.ttl = 0;
419
420         if (hdrlen + be16toh(offer->udp.len) > len ||
421             client_checksum(&offer->ip.ttl, be16toh(offer->udp.len) + 12))
422                 return -EINVAL;
423
424         if (be16toh(offer->udp.source) != DHCP_PORT_SERVER ||
425             be16toh(offer->udp.dest) != DHCP_PORT_CLIENT)
426                 return -EINVAL;
427
428         if (offer->dhcp.op != BOOTREPLY)
429                 return -EINVAL;
430
431         if (be32toh(offer->dhcp.xid) != client->xid)
432                 return -EINVAL;
433
434         if (memcmp(&offer->dhcp.chaddr[0], &client->mac_addr.ether_addr_octet,
435                     ETHER_ADDR_LEN))
436                 return -EINVAL;
437
438         lease = new0(DHCPLease, 1);
439         if (!lease)
440                 return -ENOMEM;
441
442         len = len - DHCP_IP_UDP_SIZE;
443         if (dhcp_option_parse(&offer->dhcp, len, client_parse_offer,
444                               lease) != DHCP_OFFER)
445                 goto error;
446
447         lease->address = offer->dhcp.yiaddr;
448
449         if (lease->address == INADDR_ANY ||
450             lease->server_address == INADDR_ANY ||
451             lease->subnet_mask == INADDR_ANY ||
452             lease->lifetime == 0)
453                 goto error;
454
455         client->lease = lease;
456
457         return 0;
458
459 error:
460         free(lease);
461
462         return -ENOMSG;
463 }
464
465 static int client_receive_raw_message(sd_event_source *s, int fd,
466                                       uint32_t revents, void *userdata)
467 {
468         sd_dhcp_client *client = userdata;
469         uint8_t buf[sizeof(DHCPPacket) + DHCP_CLIENT_MIN_OPTIONS_SIZE];
470         int buflen = sizeof(buf);
471         int len;
472         DHCPPacket *message;
473
474         len = read(fd, &buf, buflen);
475         if (len < 0)
476                 goto error;
477
478         message = (DHCPPacket *)&buf;
479
480         switch (client->state) {
481         case DHCP_STATE_SELECTING:
482
483                 if (client_receive_offer(client, message, len) >= 0) {
484
485                         client->receive_message =
486                                 sd_event_source_unref(client->receive_message);
487                         close(client->fd);
488                         client->fd = -1;
489
490                         client->timeout_resend =
491                                 sd_event_source_unref(client->timeout_resend);
492
493                         client->state = DHCP_STATE_REQUESTING;
494                 }
495
496                 break;
497
498         case DHCP_STATE_INIT:
499         case DHCP_STATE_INIT_REBOOT:
500         case DHCP_STATE_REBOOTING:
501         case DHCP_STATE_REQUESTING:
502         case DHCP_STATE_BOUND:
503         case DHCP_STATE_RENEWING:
504         case DHCP_STATE_REBINDING:
505
506                 break;
507         }
508
509 error:
510         return 0;
511 }
512
513 int sd_dhcp_client_start(sd_dhcp_client *client)
514 {
515         int err;
516
517         assert_return(client, -EINVAL);
518         assert_return(client->index >= 0, -EINVAL);
519         assert_return(client->state == DHCP_STATE_INIT ||
520                       client->state == DHCP_STATE_INIT_REBOOT, -EBUSY);
521
522         client->xid = random_u();
523
524         client->fd = dhcp_network_bind_raw_socket(client->index,
525                                                   &client->link);
526
527         if (client->fd < 0) {
528                 err = client->fd;
529                 goto error;
530         }
531
532         err = sd_event_add_io(client->event, client->fd, EPOLLIN,
533                               client_receive_raw_message, client,
534                               &client->receive_message);
535         if (err < 0)
536                 goto error;
537
538         err = sd_event_add_monotonic(client->event, now(CLOCK_MONOTONIC), 0,
539                                      client_timeout_resend, client,
540                                      &client->timeout_resend);
541         if (err < 0)
542                 goto error;
543
544         return 0;
545
546 error:
547         client_stop(client, err);
548
549         return err;
550 }
551
552 int sd_dhcp_client_stop(sd_dhcp_client *client)
553 {
554         return client_stop(client, 0);
555 }
556
557 sd_dhcp_client *sd_dhcp_client_new(sd_event *event)
558 {
559         sd_dhcp_client *client;
560
561         assert_return(event, NULL);
562
563         client = new0(sd_dhcp_client, 1);
564         if (!client)
565                 return NULL;
566
567         client->event = sd_event_ref(event);
568         client->state = DHCP_STATE_INIT;
569         client->index = -1;
570         client->fd = -1;
571
572         client->req_opts_size = ELEMENTSOF(default_req_opts);
573
574         client->req_opts = memdup(default_req_opts, client->req_opts_size);
575         if (!client->req_opts) {
576                 free(client);
577                 return NULL;
578         }
579
580         return client;
581 }