chiark / gitweb /
sd-dhcp-server: add basic DISCOVER/OFFER 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_address(sd_dhcp_server *server, struct in_addr *address) {
33         assert_return(server, -EINVAL);
34         assert_return(address, -EINVAL);
35         assert_return(address->s_addr, -EINVAL);
36         assert_return(server->address == htobe32(INADDR_ANY), -EBUSY);
37
38         server->address = address->s_addr;
39
40         return 0;
41 }
42
43 sd_dhcp_server *sd_dhcp_server_ref(sd_dhcp_server *server) {
44         if (server)
45                 assert_se(REFCNT_INC(server->n_ref) >= 2);
46
47         return server;
48 }
49
50 sd_dhcp_server *sd_dhcp_server_unref(sd_dhcp_server *server) {
51         if (server && REFCNT_DEC(server->n_ref) <= 0) {
52                 log_dhcp_server(server, "UNREF");
53
54                 sd_dhcp_server_stop(server);
55
56                 sd_event_unref(server->event);
57                 free(server);
58         }
59
60         return NULL;
61 }
62
63 int sd_dhcp_server_new(sd_dhcp_server **ret, int ifindex) {
64         _cleanup_dhcp_server_unref_ sd_dhcp_server *server = NULL;
65
66         assert_return(ret, -EINVAL);
67         assert_return(ifindex > 0, -EINVAL);
68
69         server = new0(sd_dhcp_server, 1);
70         if (!server)
71                 return -ENOMEM;
72
73         server->n_ref = REFCNT_INIT;
74         server->fd_raw = -1;
75         server->fd = -1;
76         server->address = htobe32(INADDR_ANY);
77         server->index = ifindex;
78
79         *ret = server;
80         server = NULL;
81
82         return 0;
83 }
84
85 int sd_dhcp_server_attach_event(sd_dhcp_server *server, sd_event *event, int priority) {
86         int r;
87
88         assert_return(server, -EINVAL);
89         assert_return(!server->event, -EBUSY);
90
91         if (event)
92                 server->event = sd_event_ref(event);
93         else {
94                 r = sd_event_default(&server->event);
95                 if (r < 0)
96                         return r;
97         }
98
99         server->event_priority = priority;
100
101         return 0;
102 }
103
104 int sd_dhcp_server_detach_event(sd_dhcp_server *server) {
105         assert_return(server, -EINVAL);
106
107         server->event = sd_event_unref(server->event);
108
109         return 0;
110 }
111
112 sd_event *sd_dhcp_server_get_event(sd_dhcp_server *server) {
113         assert_return(server, NULL);
114
115         return server->event;
116 }
117
118 int sd_dhcp_server_stop(sd_dhcp_server *server) {
119         assert_return(server, -EINVAL);
120
121         server->receive_message =
122                 sd_event_source_unref(server->receive_message);
123
124         server->fd_raw = safe_close(server->fd_raw);
125         server->fd = safe_close(server->fd);
126
127         log_dhcp_server(server, "STOPPED");
128
129         return 0;
130 }
131
132 static int dhcp_server_send_unicast_raw(sd_dhcp_server *server, DHCPPacket *packet,
133                                         size_t len) {
134         union sockaddr_union link = {
135                 .ll.sll_family = AF_PACKET,
136                 .ll.sll_protocol = htons(ETH_P_IP),
137                 .ll.sll_ifindex = server->index,
138                 .ll.sll_halen = ETH_ALEN,
139         };
140         int r;
141
142         assert(server);
143         assert(server->index > 0);
144         assert(server->address);
145         assert(packet);
146         assert(len > sizeof(DHCPPacket));
147
148         memcpy(&link.ll.sll_addr, &packet->dhcp.chaddr, ETH_ALEN);
149
150         dhcp_packet_append_ip_headers(packet, server->address, DHCP_PORT_SERVER,
151                                       packet->dhcp.yiaddr, DHCP_PORT_CLIENT, len);
152
153         r = dhcp_network_send_raw_socket(server->fd_raw, &link, packet, len);
154         if (r < 0)
155                 return r;
156
157         return 0;
158 }
159
160 static int dhcp_server_send_udp(sd_dhcp_server *server, be32_t destination,
161                                 DHCPMessage *message, size_t len) {
162         union sockaddr_union dest = {
163                 .in.sin_family = AF_INET,
164                 .in.sin_port = htobe16(DHCP_PORT_CLIENT),
165                 .in.sin_addr.s_addr = destination,
166         };
167         struct iovec iov = {
168                 .iov_base = message,
169                 .iov_len = len,
170         };
171         uint8_t cmsgbuf[CMSG_LEN(sizeof(struct in_pktinfo))] = {};
172         struct msghdr msg = {
173                 .msg_name = &dest,
174                 .msg_namelen = sizeof(dest.in),
175                 .msg_iov = &iov,
176                 .msg_iovlen = 1,
177                 .msg_control = cmsgbuf,
178                 .msg_controllen = sizeof(cmsgbuf),
179         };
180         struct cmsghdr *cmsg;
181         struct in_pktinfo *pktinfo;
182         int r;
183
184         assert(server);
185         assert(server->fd > 0);
186         assert(message);
187         assert(len > sizeof(DHCPMessage));
188
189         cmsg = CMSG_FIRSTHDR(&msg);
190         assert(cmsg);
191
192         cmsg->cmsg_level = IPPROTO_IP;
193         cmsg->cmsg_type = IP_PKTINFO;
194         cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
195
196         /* we attach source interface and address info to the message
197            rather than binding the socket. This will be mostly useful
198            when we gain support for arbitrary number of server addresses
199          */
200         pktinfo = (struct in_pktinfo*) CMSG_DATA(cmsg);
201         assert(pktinfo);
202
203         pktinfo->ipi_ifindex = server->index;
204         pktinfo->ipi_spec_dst.s_addr = server->address;
205
206         r = sendmsg(server->fd, &msg, 0);
207         if (r < 0)
208                 return -errno;
209
210         return 0;
211 }
212
213 static bool requested_broadcast(DHCPRequest *req) {
214         assert(req);
215
216         return req->message->flags & htobe16(0x8000);
217 }
218
219 int dhcp_server_send_packet(sd_dhcp_server *server,
220                             DHCPRequest *req, DHCPPacket *packet,
221                             int type, size_t optoffset) {
222         be32_t destination = INADDR_ANY;
223         int r;
224
225         assert(server);
226         assert(req);
227         assert(req->max_optlen);
228         assert(optoffset <= req->max_optlen);
229         assert(packet);
230
231         r = dhcp_option_append(&packet->dhcp, req->max_optlen, &optoffset, 0,
232                                DHCP_OPTION_SERVER_IDENTIFIER,
233                                4, &server->address);
234         if (r < 0)
235                 return r;
236
237         r = dhcp_option_append(&packet->dhcp, req->max_optlen, &optoffset, 0,
238                                DHCP_OPTION_END, 0, NULL);
239         if (r < 0)
240                 return r;
241
242         /* RFC 2131 Section 4.1
243
244            If the â€™giaddr’ field in a DHCP message from a client is non-zero,
245            the server sends any return messages to the â€™DHCP server’ port on the
246            BOOTP relay agent whose address appears in â€™giaddr’. If the â€™giaddr’
247            field is zero and the â€™ciaddr’ field is nonzero, then the server
248            unicasts DHCPOFFER and DHCPACK messages to the address in â€™ciaddr’.
249            If â€™giaddr’ is zero and â€™ciaddr’ is zero, and the broadcast bit is
250            set, then the server broadcasts DHCPOFFER and DHCPACK messages to
251            0xffffffff. If the broadcast bit is not set and â€™giaddr’ is zero and
252            â€™ciaddr’ is zero, then the server unicasts DHCPOFFER and DHCPACK
253            messages to the client’s hardware address and â€™yiaddr’ address. In
254            all cases, when â€™giaddr’ is zero, the server broadcasts any DHCPNAK
255            messages to 0xffffffff.
256
257            Section 4.3.2
258
259            If â€™giaddr’ is set in the DHCPREQUEST message, the client is on a
260            different subnet. The server MUST set the broadcast bit in the
261            DHCPNAK, so that the relay agent will broadcast the DHCPNAK to the
262            client, because the client may not have a correct network address
263            or subnet mask, and the client may not be answering ARP requests.
264          */
265         if (req->message->giaddr) {
266                 destination = req->message->giaddr;
267                 if (type == DHCP_NAK)
268                         packet->dhcp.flags = htobe16(0x8000);
269         } else if (req->message->ciaddr && type != DHCP_NAK)
270                 destination = req->message->ciaddr;
271
272         if (destination || requested_broadcast(req) || type == DHCP_NAK)
273                 return dhcp_server_send_udp(server, destination, &packet->dhcp,
274                                             sizeof(DHCPMessage) + optoffset);
275         else
276                 /* we cannot send UDP packet to specific MAC address when the address is
277                    not yet configured, so must fall back to raw packets */
278                 return dhcp_server_send_unicast_raw(server, packet,
279                                                     sizeof(DHCPPacket) + optoffset);
280 }
281
282 static int server_message_init(sd_dhcp_server *server, DHCPPacket **ret,
283                                uint8_t type, size_t *_optoffset, DHCPRequest *req) {
284         _cleanup_free_ DHCPPacket *packet = NULL;
285         size_t optoffset;
286         int r;
287
288         assert(server);
289         assert(ret);
290         assert(_optoffset);
291         assert(type == DHCP_OFFER);
292
293         packet = malloc0(sizeof(DHCPPacket) + req->max_optlen);
294         if (!packet)
295                 return -ENOMEM;
296
297         r = dhcp_message_init(&packet->dhcp, BOOTREPLY, be32toh(req->message->xid),
298                               type, req->max_optlen, &optoffset);
299         if (r < 0)
300                 return r;
301
302         packet->dhcp.flags = req->message->flags;
303         packet->dhcp.giaddr = req->message->giaddr;
304         memcpy(&packet->dhcp.chaddr, &req->message->chaddr, ETH_ALEN);
305
306         *_optoffset = optoffset;
307         *ret = packet;
308         packet = NULL;
309
310         return 0;
311 }
312
313 static int server_send_offer(sd_dhcp_server *server, DHCPRequest *req) {
314         _cleanup_free_ DHCPPacket *packet = NULL;
315         size_t offset;
316         be32_t lease_time;
317         int r;
318
319         r = server_message_init(server, &packet, DHCP_OFFER, &offset, req);
320         if (r < 0)
321                 return r;
322
323         /* for now offer a random IP */
324         packet->dhcp.yiaddr = random_u32();
325
326         /* for one minute */
327         lease_time = htobe32(DHCP_DEFAULT_LEASE_TIME);
328         r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0,
329                                DHCP_OPTION_IP_ADDRESS_LEASE_TIME, 4, &lease_time);
330         if (r < 0)
331                 return r;
332
333         r = dhcp_server_send_packet(server, req, packet, DHCP_OFFER, offset);
334         if (r < 0)
335                 return r;
336
337         return 0;
338 }
339
340 static int parse_request(uint8_t code, uint8_t len, const uint8_t *option,
341                          void *user_data) {
342         DHCPRequest *req = user_data;
343
344         assert(req);
345
346         switch(code) {
347         case DHCP_OPTION_SERVER_IDENTIFIER:
348                 if (len == 4)
349                         req->server_id = *(be32_t*)option;
350
351                 break;
352         case DHCP_OPTION_CLIENT_IDENTIFIER:
353                 if (len >= 2) {
354                         uint8_t *data;
355
356                         data = memdup(option, len);
357                         if (!data)
358                                 return -ENOMEM;
359
360                         free(req->client_id.data);
361                         req->client_id.data = data;
362                         req->client_id.length = len;
363                 }
364
365                 break;
366         case DHCP_OPTION_MAXIMUM_MESSAGE_SIZE:
367                 if (len == 2)
368                         req->max_optlen = be16toh(*(be16_t*)option) -
369                                           - sizeof(DHCPPacket);
370
371                 break;
372         }
373
374         return 0;
375 }
376
377 static void dhcp_request_free(DHCPRequest *req) {
378         if (!req)
379                 return;
380
381         free(req->client_id.data);
382         free(req);
383 }
384
385 DEFINE_TRIVIAL_CLEANUP_FUNC(DHCPRequest*, dhcp_request_free);
386 #define _cleanup_dhcp_request_free_ _cleanup_(dhcp_request_freep)
387
388 static int ensure_sane_request(DHCPRequest *req, DHCPMessage *message) {
389         assert(req);
390         assert(message);
391
392         req->message = message;
393
394         /* set client id based on mac address if client did not send an explicit one */
395         if (!req->client_id.data) {
396                 uint8_t *data;
397
398                 data = new0(uint8_t, ETH_ALEN + 1);
399                 if (!data)
400                         return -ENOMEM;
401
402                 req->client_id.length = ETH_ALEN + 1;
403                 req->client_id.data = data;
404                 req->client_id.data[0] = 0x01;
405                 memcpy(&req->client_id.data[1], &message->chaddr, ETH_ALEN);
406         }
407
408         if (req->max_optlen < DHCP_MIN_OPTIONS_SIZE)
409                 req->max_optlen = DHCP_MIN_OPTIONS_SIZE;
410
411         return 0;
412 }
413
414 int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
415                                size_t length) {
416         _cleanup_dhcp_request_free_ DHCPRequest *req = NULL;
417         int type, r;
418
419         assert(server);
420         assert(message);
421
422         if (message->op != BOOTREQUEST ||
423             message->htype != ARPHRD_ETHER ||
424             message->hlen != ETHER_ADDR_LEN)
425                 return 0;
426
427         req = new0(DHCPRequest, 1);
428         if (!req)
429                 return -ENOMEM;
430
431         type = dhcp_option_parse(message, length, parse_request, req);
432         if (type < 0)
433                 return 0;
434
435         r = ensure_sane_request(req, message);
436         if (r < 0)
437                 /* this only fails on critical errors */
438                 return r;
439
440         switch(type) {
441         case DHCP_DISCOVER:
442                 log_dhcp_server(server, "DISCOVER (0x%x)",
443                                 be32toh(req->message->xid));
444
445                 r = server_send_offer(server, req);
446                 if (r < 0) {
447                         /* this only fails on critical errors */
448                         log_dhcp_server(server, "could not send offer: %s",
449                                         strerror(-r));
450                         return r;
451                 } else {
452                         log_dhcp_server(server, "OFFER (0x%x)",
453                                         be32toh(req->message->xid));
454                         return DHCP_OFFER;
455                 }
456
457                 break;
458         }
459
460         return 0;
461 }
462
463 static int server_receive_message(sd_event_source *s, int fd,
464                                   uint32_t revents, void *userdata) {
465         _cleanup_free_ DHCPMessage *message = NULL;
466         uint8_t cmsgbuf[CMSG_LEN(sizeof(struct in_pktinfo))];
467         sd_dhcp_server *server = userdata;
468         struct iovec iov = {};
469         struct msghdr msg = {
470                 .msg_iov = &iov,
471                 .msg_iovlen = 1,
472                 .msg_control = cmsgbuf,
473                 .msg_controllen = sizeof(cmsgbuf),
474         };
475         struct cmsghdr *cmsg;
476         int buflen = 0, len, r;
477
478         assert(server);
479
480         r = ioctl(fd, FIONREAD, &buflen);
481         if (r < 0)
482                 return r;
483         if (buflen < 0)
484                 return -EIO;
485
486         message = malloc0(buflen);
487         if (!message)
488                 return -ENOMEM;
489
490         iov.iov_base = message;
491         iov.iov_len = buflen;
492
493         len = recvmsg(fd, &msg, 0);
494         if (len < buflen)
495                 return 0;
496         else if ((size_t)len < sizeof(DHCPMessage))
497                 return 0;
498
499         for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
500                 if (cmsg->cmsg_level == IPPROTO_IP &&
501                     cmsg->cmsg_type == IP_PKTINFO &&
502                     cmsg->cmsg_len == CMSG_LEN(sizeof(struct in_pktinfo))) {
503                         struct in_pktinfo *info = (struct in_pktinfo*)CMSG_DATA(cmsg);
504
505                         /* TODO figure out if this can be done as a filter on the socket, like for IPv6 */
506                         if (server->index != info->ipi_ifindex)
507                                 return 0;
508
509                         break;
510                 }
511         }
512
513         return dhcp_server_handle_message(server, message, (size_t)len);
514 }
515
516 int sd_dhcp_server_start(sd_dhcp_server *server) {
517         int r;
518
519         assert_return(server, -EINVAL);
520         assert_return(server->event, -EINVAL);
521         assert_return(!server->receive_message, -EBUSY);
522         assert_return(server->fd_raw == -1, -EBUSY);
523         assert_return(server->fd == -1, -EBUSY);
524         assert_return(server->address != htobe32(INADDR_ANY), -EUNATCH);
525
526         r = socket(AF_PACKET, SOCK_DGRAM | SOCK_NONBLOCK, 0);
527         if (r < 0) {
528                 r = -errno;
529                 sd_dhcp_server_stop(server);
530                 return r;
531         }
532         server->fd_raw = r;
533
534         r = dhcp_network_bind_udp_socket(INADDR_ANY, DHCP_PORT_SERVER);
535         if (r < 0) {
536                 sd_dhcp_server_stop(server);
537                 return r;
538         }
539         server->fd = r;
540
541         r = sd_event_add_io(server->event, &server->receive_message,
542                             server->fd, EPOLLIN,
543                             server_receive_message, server);
544         if (r < 0) {
545                 sd_dhcp_server_stop(server);
546                 return r;
547         }
548
549         r = sd_event_source_set_priority(server->receive_message,
550                                          server->event_priority);
551         if (r < 0) {
552                 sd_dhcp_server_stop(server);
553                 return r;
554         }
555
556         log_dhcp_server(server, "STARTED");
557
558         return 0;
559 }