chiark / gitweb /
sd-dhcp-server: add basic REQUEST/ACK support
[elogind.git] / src / libsystemd-network / sd-dhcp-server.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) 2013 Intel Corporation. All rights reserved.
7   Copyright (C) 2014 Tom Gundersen
8
9   systemd is free software; you can redistribute it and/or modify it
10   under the terms of the GNU Lesser General Public License as published by
11   the Free Software Foundation; either version 2.1 of the License, or
12   (at your option) any later version.
13
14   systemd is distributed in the hope that it will be useful, but
15   WITHOUT ANY WARRANTY; without even the implied warranty of
16   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17   Lesser General Public License for more details.
18
19   You should have received a copy of the GNU Lesser General Public License
20   along with systemd; If not, see <http://www.gnu.org/licenses/>.
21 ***/
22
23 #include <sys/ioctl.h>
24 #include <netinet/if_ether.h>
25
26 #include "sd-dhcp-server.h"
27 #include "dhcp-server-internal.h"
28 #include "dhcp-internal.h"
29
30 #define DHCP_DEFAULT_LEASE_TIME         60
31
32 int sd_dhcp_server_set_lease_pool(sd_dhcp_server *server, struct in_addr *address,
33                                   size_t size) {
34         assert_return(server, -EINVAL);
35         assert_return(address, -EINVAL);
36         assert_return(address->s_addr, -EINVAL);
37         assert_return(size, -EINVAL);
38         assert_return(server->pool_start == htobe32(INADDR_ANY), -EBUSY);
39         assert_return(!server->pool_size, -EBUSY);
40
41         server->pool_start = address->s_addr;
42         server->pool_size = size;
43
44         return 0;
45 }
46
47 int sd_dhcp_server_set_address(sd_dhcp_server *server, struct in_addr *address) {
48         assert_return(server, -EINVAL);
49         assert_return(address, -EINVAL);
50         assert_return(address->s_addr, -EINVAL);
51         assert_return(server->address == htobe32(INADDR_ANY), -EBUSY);
52
53         server->address = address->s_addr;
54
55         return 0;
56 }
57
58 sd_dhcp_server *sd_dhcp_server_ref(sd_dhcp_server *server) {
59         if (server)
60                 assert_se(REFCNT_INC(server->n_ref) >= 2);
61
62         return server;
63 }
64
65 sd_dhcp_server *sd_dhcp_server_unref(sd_dhcp_server *server) {
66         if (server && REFCNT_DEC(server->n_ref) <= 0) {
67                 log_dhcp_server(server, "UNREF");
68
69                 sd_dhcp_server_stop(server);
70
71                 sd_event_unref(server->event);
72                 free(server);
73         }
74
75         return NULL;
76 }
77
78 int sd_dhcp_server_new(sd_dhcp_server **ret, int ifindex) {
79         _cleanup_dhcp_server_unref_ sd_dhcp_server *server = NULL;
80
81         assert_return(ret, -EINVAL);
82         assert_return(ifindex > 0, -EINVAL);
83
84         server = new0(sd_dhcp_server, 1);
85         if (!server)
86                 return -ENOMEM;
87
88         server->n_ref = REFCNT_INIT;
89         server->fd_raw = -1;
90         server->fd = -1;
91         server->address = htobe32(INADDR_ANY);
92         server->index = ifindex;
93
94         *ret = server;
95         server = NULL;
96
97         return 0;
98 }
99
100 int sd_dhcp_server_attach_event(sd_dhcp_server *server, sd_event *event, int priority) {
101         int r;
102
103         assert_return(server, -EINVAL);
104         assert_return(!server->event, -EBUSY);
105
106         if (event)
107                 server->event = sd_event_ref(event);
108         else {
109                 r = sd_event_default(&server->event);
110                 if (r < 0)
111                         return r;
112         }
113
114         server->event_priority = priority;
115
116         return 0;
117 }
118
119 int sd_dhcp_server_detach_event(sd_dhcp_server *server) {
120         assert_return(server, -EINVAL);
121
122         server->event = sd_event_unref(server->event);
123
124         return 0;
125 }
126
127 sd_event *sd_dhcp_server_get_event(sd_dhcp_server *server) {
128         assert_return(server, NULL);
129
130         return server->event;
131 }
132
133 int sd_dhcp_server_stop(sd_dhcp_server *server) {
134         assert_return(server, -EINVAL);
135
136         server->receive_message =
137                 sd_event_source_unref(server->receive_message);
138
139         server->fd_raw = safe_close(server->fd_raw);
140         server->fd = safe_close(server->fd);
141
142         log_dhcp_server(server, "STOPPED");
143
144         return 0;
145 }
146
147 static int dhcp_server_send_unicast_raw(sd_dhcp_server *server, DHCPPacket *packet,
148                                         size_t len) {
149         union sockaddr_union link = {
150                 .ll.sll_family = AF_PACKET,
151                 .ll.sll_protocol = htons(ETH_P_IP),
152                 .ll.sll_ifindex = server->index,
153                 .ll.sll_halen = ETH_ALEN,
154         };
155         int r;
156
157         assert(server);
158         assert(server->index > 0);
159         assert(server->address);
160         assert(packet);
161         assert(len > sizeof(DHCPPacket));
162
163         memcpy(&link.ll.sll_addr, &packet->dhcp.chaddr, ETH_ALEN);
164
165         dhcp_packet_append_ip_headers(packet, server->address, DHCP_PORT_SERVER,
166                                       packet->dhcp.yiaddr, DHCP_PORT_CLIENT, len);
167
168         r = dhcp_network_send_raw_socket(server->fd_raw, &link, packet, len);
169         if (r < 0)
170                 return r;
171
172         return 0;
173 }
174
175 static int dhcp_server_send_udp(sd_dhcp_server *server, be32_t destination,
176                                 DHCPMessage *message, size_t len) {
177         union sockaddr_union dest = {
178                 .in.sin_family = AF_INET,
179                 .in.sin_port = htobe16(DHCP_PORT_CLIENT),
180                 .in.sin_addr.s_addr = destination,
181         };
182         struct iovec iov = {
183                 .iov_base = message,
184                 .iov_len = len,
185         };
186         uint8_t cmsgbuf[CMSG_LEN(sizeof(struct in_pktinfo))] = {};
187         struct msghdr msg = {
188                 .msg_name = &dest,
189                 .msg_namelen = sizeof(dest.in),
190                 .msg_iov = &iov,
191                 .msg_iovlen = 1,
192                 .msg_control = cmsgbuf,
193                 .msg_controllen = sizeof(cmsgbuf),
194         };
195         struct cmsghdr *cmsg;
196         struct in_pktinfo *pktinfo;
197         int r;
198
199         assert(server);
200         assert(server->fd > 0);
201         assert(message);
202         assert(len > sizeof(DHCPMessage));
203
204         cmsg = CMSG_FIRSTHDR(&msg);
205         assert(cmsg);
206
207         cmsg->cmsg_level = IPPROTO_IP;
208         cmsg->cmsg_type = IP_PKTINFO;
209         cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
210
211         /* we attach source interface and address info to the message
212            rather than binding the socket. This will be mostly useful
213            when we gain support for arbitrary number of server addresses
214          */
215         pktinfo = (struct in_pktinfo*) CMSG_DATA(cmsg);
216         assert(pktinfo);
217
218         pktinfo->ipi_ifindex = server->index;
219         pktinfo->ipi_spec_dst.s_addr = server->address;
220
221         r = sendmsg(server->fd, &msg, 0);
222         if (r < 0)
223                 return -errno;
224
225         return 0;
226 }
227
228 static bool requested_broadcast(DHCPRequest *req) {
229         assert(req);
230
231         return req->message->flags & htobe16(0x8000);
232 }
233
234 int dhcp_server_send_packet(sd_dhcp_server *server,
235                             DHCPRequest *req, DHCPPacket *packet,
236                             int type, size_t optoffset) {
237         be32_t destination = INADDR_ANY;
238         int r;
239
240         assert(server);
241         assert(req);
242         assert(req->max_optlen);
243         assert(optoffset <= req->max_optlen);
244         assert(packet);
245
246         r = dhcp_option_append(&packet->dhcp, req->max_optlen, &optoffset, 0,
247                                DHCP_OPTION_SERVER_IDENTIFIER,
248                                4, &server->address);
249         if (r < 0)
250                 return r;
251
252         r = dhcp_option_append(&packet->dhcp, req->max_optlen, &optoffset, 0,
253                                DHCP_OPTION_END, 0, NULL);
254         if (r < 0)
255                 return r;
256
257         /* RFC 2131 Section 4.1
258
259            If the â€™giaddr’ field in a DHCP message from a client is non-zero,
260            the server sends any return messages to the â€™DHCP server’ port on the
261            BOOTP relay agent whose address appears in â€™giaddr’. If the â€™giaddr’
262            field is zero and the â€™ciaddr’ field is nonzero, then the server
263            unicasts DHCPOFFER and DHCPACK messages to the address in â€™ciaddr’.
264            If â€™giaddr’ is zero and â€™ciaddr’ is zero, and the broadcast bit is
265            set, then the server broadcasts DHCPOFFER and DHCPACK messages to
266            0xffffffff. If the broadcast bit is not set and â€™giaddr’ is zero and
267            â€™ciaddr’ is zero, then the server unicasts DHCPOFFER and DHCPACK
268            messages to the client’s hardware address and â€™yiaddr’ address. In
269            all cases, when â€™giaddr’ is zero, the server broadcasts any DHCPNAK
270            messages to 0xffffffff.
271
272            Section 4.3.2
273
274            If â€™giaddr’ is set in the DHCPREQUEST message, the client is on a
275            different subnet. The server MUST set the broadcast bit in the
276            DHCPNAK, so that the relay agent will broadcast the DHCPNAK to the
277            client, because the client may not have a correct network address
278            or subnet mask, and the client may not be answering ARP requests.
279          */
280         if (req->message->giaddr) {
281                 destination = req->message->giaddr;
282                 if (type == DHCP_NAK)
283                         packet->dhcp.flags = htobe16(0x8000);
284         } else if (req->message->ciaddr && type != DHCP_NAK)
285                 destination = req->message->ciaddr;
286
287         if (destination || requested_broadcast(req) || type == DHCP_NAK)
288                 return dhcp_server_send_udp(server, destination, &packet->dhcp,
289                                             sizeof(DHCPMessage) + optoffset);
290         else
291                 /* we cannot send UDP packet to specific MAC address when the address is
292                    not yet configured, so must fall back to raw packets */
293                 return dhcp_server_send_unicast_raw(server, packet,
294                                                     sizeof(DHCPPacket) + optoffset);
295 }
296
297 static int server_message_init(sd_dhcp_server *server, DHCPPacket **ret,
298                                uint8_t type, size_t *_optoffset, DHCPRequest *req) {
299         _cleanup_free_ DHCPPacket *packet = NULL;
300         size_t optoffset;
301         int r;
302
303         assert(server);
304         assert(ret);
305         assert(_optoffset);
306         assert(IN_SET(type, DHCP_OFFER, DHCP_ACK));
307
308         packet = malloc0(sizeof(DHCPPacket) + req->max_optlen);
309         if (!packet)
310                 return -ENOMEM;
311
312         r = dhcp_message_init(&packet->dhcp, BOOTREPLY, be32toh(req->message->xid),
313                               type, req->max_optlen, &optoffset);
314         if (r < 0)
315                 return r;
316
317         packet->dhcp.flags = req->message->flags;
318         packet->dhcp.giaddr = req->message->giaddr;
319         memcpy(&packet->dhcp.chaddr, &req->message->chaddr, ETH_ALEN);
320
321         *_optoffset = optoffset;
322         *ret = packet;
323         packet = NULL;
324
325         return 0;
326 }
327
328 static int server_send_offer(sd_dhcp_server *server, DHCPRequest *req, be32_t address) {
329         _cleanup_free_ DHCPPacket *packet = NULL;
330         size_t offset;
331         be32_t lease_time;
332         int r;
333
334         r = server_message_init(server, &packet, DHCP_OFFER, &offset, req);
335         if (r < 0)
336                 return r;
337
338         packet->dhcp.yiaddr = address;
339
340         /* for one minute */
341         lease_time = htobe32(DHCP_DEFAULT_LEASE_TIME);
342         r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0,
343                                DHCP_OPTION_IP_ADDRESS_LEASE_TIME, 4, &lease_time);
344         if (r < 0)
345                 return r;
346
347         r = dhcp_server_send_packet(server, req, packet, DHCP_OFFER, offset);
348         if (r < 0)
349                 return r;
350
351         return 0;
352 }
353
354 static int server_send_ack(sd_dhcp_server *server, DHCPRequest *req, be32_t address) {
355         _cleanup_free_ DHCPPacket *packet = NULL;
356         size_t offset;
357         be32_t lease_time;
358         int r;
359
360         r = server_message_init(server, &packet, DHCP_ACK, &offset, req);
361         if (r < 0)
362                 return r;
363
364         packet->dhcp.yiaddr = address;
365
366         /* for ten seconds */
367         lease_time = htobe32(DHCP_DEFAULT_LEASE_TIME);
368         r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0,
369                                DHCP_OPTION_IP_ADDRESS_LEASE_TIME, 4, &lease_time);
370         if (r < 0)
371                 return r;
372
373         r = dhcp_server_send_packet(server, req, packet, DHCP_ACK, offset);
374         if (r < 0)
375                 return r;
376
377         return 0;
378 }
379
380 static int parse_request(uint8_t code, uint8_t len, const uint8_t *option,
381                          void *user_data) {
382         DHCPRequest *req = user_data;
383
384         assert(req);
385
386         switch(code) {
387         case DHCP_OPTION_REQUESTED_IP_ADDRESS:
388                 if (len == 4)
389                         req->requested_ip = *(be32_t*)option;
390
391                 break;
392         case DHCP_OPTION_SERVER_IDENTIFIER:
393                 if (len == 4)
394                         req->server_id = *(be32_t*)option;
395
396                 break;
397         case DHCP_OPTION_CLIENT_IDENTIFIER:
398                 if (len >= 2) {
399                         uint8_t *data;
400
401                         data = memdup(option, len);
402                         if (!data)
403                                 return -ENOMEM;
404
405                         free(req->client_id.data);
406                         req->client_id.data = data;
407                         req->client_id.length = len;
408                 }
409
410                 break;
411         case DHCP_OPTION_MAXIMUM_MESSAGE_SIZE:
412                 if (len == 2)
413                         req->max_optlen = be16toh(*(be16_t*)option) -
414                                           - sizeof(DHCPPacket);
415
416                 break;
417         }
418
419         return 0;
420 }
421
422 static void dhcp_request_free(DHCPRequest *req) {
423         if (!req)
424                 return;
425
426         free(req->client_id.data);
427         free(req);
428 }
429
430 DEFINE_TRIVIAL_CLEANUP_FUNC(DHCPRequest*, dhcp_request_free);
431 #define _cleanup_dhcp_request_free_ _cleanup_(dhcp_request_freep)
432
433 static int ensure_sane_request(DHCPRequest *req, DHCPMessage *message) {
434         assert(req);
435         assert(message);
436
437         req->message = message;
438
439         /* set client id based on mac address if client did not send an explicit one */
440         if (!req->client_id.data) {
441                 uint8_t *data;
442
443                 data = new0(uint8_t, ETH_ALEN + 1);
444                 if (!data)
445                         return -ENOMEM;
446
447                 req->client_id.length = ETH_ALEN + 1;
448                 req->client_id.data = data;
449                 req->client_id.data[0] = 0x01;
450                 memcpy(&req->client_id.data[1], &message->chaddr, ETH_ALEN);
451         }
452
453         if (req->max_optlen < DHCP_MIN_OPTIONS_SIZE)
454                 req->max_optlen = DHCP_MIN_OPTIONS_SIZE;
455
456         return 0;
457 }
458
459 int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
460                                size_t length) {
461         _cleanup_dhcp_request_free_ DHCPRequest *req = NULL;
462         int type, r;
463
464         assert(server);
465         assert(message);
466
467         if (message->op != BOOTREQUEST ||
468             message->htype != ARPHRD_ETHER ||
469             message->hlen != ETHER_ADDR_LEN)
470                 return 0;
471
472         req = new0(DHCPRequest, 1);
473         if (!req)
474                 return -ENOMEM;
475
476         type = dhcp_option_parse(message, length, parse_request, req);
477         if (type < 0)
478                 return 0;
479
480         r = ensure_sane_request(req, message);
481         if (r < 0)
482                 /* this only fails on critical errors */
483                 return r;
484
485         switch(type) {
486         case DHCP_DISCOVER:
487         {
488                 be32_t address;
489
490                 log_dhcp_server(server, "DISCOVER (0x%x)",
491                                 be32toh(req->message->xid));
492
493                 if (!server->pool_size)
494                         /* no pool allocated */
495                         return 0;
496
497                 /* for now pick a random address from the pool */
498                 address = htobe32(be32toh(server->pool_start) +
499                                       (random_u32() % server->pool_size));
500
501                 r = server_send_offer(server, req, address);
502                 if (r < 0) {
503                         /* this only fails on critical errors */
504                         log_dhcp_server(server, "could not send offer: %s",
505                                         strerror(-r));
506                         return r;
507                 } else {
508                         log_dhcp_server(server, "OFFER (0x%x)",
509                                         be32toh(req->message->xid));
510                         return DHCP_OFFER;
511                 }
512
513                 break;
514         }
515         case DHCP_REQUEST:
516         {
517                 be32_t address;
518
519                 /* see RFC 2131, section 4.3.2 */
520
521                 if (req->server_id) {
522                         log_dhcp_server(server, "REQUEST (selecting) (0x%x)",
523                                         be32toh(req->message->xid));
524
525                         /* SELECTING */
526                         if (req->server_id != server->address)
527                                 /* client did not pick us */
528                                 return 0;
529
530                         if (req->message->ciaddr)
531                                 /* this MUST be zero */
532                                 return 0;
533
534                         if (!req->requested_ip)
535                                 /* this must be filled in with the yiaddr
536                                    from the chosen OFFER */
537                                 return 0;
538
539                         address = req->requested_ip;
540                 } else if (req->requested_ip) {
541                         log_dhcp_server(server, "REQUEST (init-reboot) (0x%x)",
542                                         be32toh(req->message->xid));
543
544                         /* INIT-REBOOT */
545                         if (req->message->ciaddr)
546                                 /* this MUST be zero */
547                                 return 0;
548
549                         /* TODO: check if requested IP is correct, NAK if not */
550                         address = req->requested_ip;
551                 } else {
552                         log_dhcp_server(server, "REQUEST (rebinding/renewing) (0x%x)",
553                                         be32toh(req->message->xid));
554
555                         /* REBINDING / RENEWING */
556                         if (!req->message->ciaddr)
557                                 /* this MUST be filled in with clients IP address */
558                                 return 0;
559
560                         address = req->message->ciaddr;
561                 }
562
563                 /* for now we just verify that the address is from the pool, not
564                    whether or not it is taken */
565                 if (htobe32(req->requested_ip) >= htobe32(server->pool_start) &&
566                     htobe32(req->requested_ip) < htobe32(server->pool_start) +
567                                                   + server->pool_size) {
568                         r = server_send_ack(server, req, address);
569                         if (r < 0) {
570                                 /* this only fails on critical errors */
571                                 log_dhcp_server(server, "could not send ack: %s",
572                                                 strerror(-r));
573                                 return r;
574                         } else {
575                                 log_dhcp_server(server, "ACK (0x%x)",
576                                                 be32toh(req->message->xid));
577                                 return DHCP_ACK;
578                         }
579                 } else
580                         return 0;
581
582                 break;
583         }
584         }
585
586         return 0;
587 }
588
589 static int server_receive_message(sd_event_source *s, int fd,
590                                   uint32_t revents, void *userdata) {
591         _cleanup_free_ DHCPMessage *message = NULL;
592         uint8_t cmsgbuf[CMSG_LEN(sizeof(struct in_pktinfo))];
593         sd_dhcp_server *server = userdata;
594         struct iovec iov = {};
595         struct msghdr msg = {
596                 .msg_iov = &iov,
597                 .msg_iovlen = 1,
598                 .msg_control = cmsgbuf,
599                 .msg_controllen = sizeof(cmsgbuf),
600         };
601         struct cmsghdr *cmsg;
602         int buflen = 0, len, r;
603
604         assert(server);
605
606         r = ioctl(fd, FIONREAD, &buflen);
607         if (r < 0)
608                 return r;
609         if (buflen < 0)
610                 return -EIO;
611
612         message = malloc0(buflen);
613         if (!message)
614                 return -ENOMEM;
615
616         iov.iov_base = message;
617         iov.iov_len = buflen;
618
619         len = recvmsg(fd, &msg, 0);
620         if (len < buflen)
621                 return 0;
622         else if ((size_t)len < sizeof(DHCPMessage))
623                 return 0;
624
625         for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
626                 if (cmsg->cmsg_level == IPPROTO_IP &&
627                     cmsg->cmsg_type == IP_PKTINFO &&
628                     cmsg->cmsg_len == CMSG_LEN(sizeof(struct in_pktinfo))) {
629                         struct in_pktinfo *info = (struct in_pktinfo*)CMSG_DATA(cmsg);
630
631                         /* TODO figure out if this can be done as a filter on the socket, like for IPv6 */
632                         if (server->index != info->ipi_ifindex)
633                                 return 0;
634
635                         break;
636                 }
637         }
638
639         return dhcp_server_handle_message(server, message, (size_t)len);
640 }
641
642 int sd_dhcp_server_start(sd_dhcp_server *server) {
643         int r;
644
645         assert_return(server, -EINVAL);
646         assert_return(server->event, -EINVAL);
647         assert_return(!server->receive_message, -EBUSY);
648         assert_return(server->fd_raw == -1, -EBUSY);
649         assert_return(server->fd == -1, -EBUSY);
650         assert_return(server->address != htobe32(INADDR_ANY), -EUNATCH);
651
652         r = socket(AF_PACKET, SOCK_DGRAM | SOCK_NONBLOCK, 0);
653         if (r < 0) {
654                 r = -errno;
655                 sd_dhcp_server_stop(server);
656                 return r;
657         }
658         server->fd_raw = r;
659
660         r = dhcp_network_bind_udp_socket(INADDR_ANY, DHCP_PORT_SERVER);
661         if (r < 0) {
662                 sd_dhcp_server_stop(server);
663                 return r;
664         }
665         server->fd = r;
666
667         r = sd_event_add_io(server->event, &server->receive_message,
668                             server->fd, EPOLLIN,
669                             server_receive_message, server);
670         if (r < 0) {
671                 sd_dhcp_server_stop(server);
672                 return r;
673         }
674
675         r = sd_event_source_set_priority(server->receive_message,
676                                          server->event_priority);
677         if (r < 0) {
678                 sd_dhcp_server_stop(server);
679                 return r;
680         }
681
682         log_dhcp_server(server, "STARTED");
683
684         return 0;
685 }