chiark / gitweb /
service: minor modernization
[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 "siphash24.h"
27
28 #include "sd-dhcp-server.h"
29 #include "dhcp-server-internal.h"
30 #include "dhcp-internal.h"
31
32 #define DHCP_DEFAULT_LEASE_TIME         3600 /* one hour */
33
34 int sd_dhcp_server_set_lease_pool(sd_dhcp_server *server, struct in_addr *address,
35                                   size_t size) {
36         assert_return(server, -EINVAL);
37         assert_return(address, -EINVAL);
38         assert_return(address->s_addr, -EINVAL);
39         assert_return(size, -EINVAL);
40         assert_return(server->pool_start == htobe32(INADDR_ANY), -EBUSY);
41         assert_return(!server->pool_size, -EBUSY);
42         assert_return(!server->bound_leases, -EBUSY);
43
44         server->bound_leases = new0(DHCPLease*, size);
45         if (!server->bound_leases)
46                 return -ENOMEM;
47
48         server->pool_start = address->s_addr;
49         server->pool_size = size;
50
51         return 0;
52 }
53
54 int sd_dhcp_server_set_address(sd_dhcp_server *server, struct in_addr *address) {
55         assert_return(server, -EINVAL);
56         assert_return(address, -EINVAL);
57         assert_return(address->s_addr, -EINVAL);
58         assert_return(server->address == htobe32(INADDR_ANY), -EBUSY);
59
60         server->address = address->s_addr;
61
62         return 0;
63 }
64
65 bool sd_dhcp_server_is_running(sd_dhcp_server *server) {
66         assert_return(server, -EINVAL);
67
68         return !!server->receive_message;
69 }
70
71 sd_dhcp_server *sd_dhcp_server_ref(sd_dhcp_server *server) {
72         if (server)
73                 assert_se(REFCNT_INC(server->n_ref) >= 2);
74
75         return server;
76 }
77
78 unsigned long client_id_hash_func(const void *p, const uint8_t hash_key[HASH_KEY_SIZE]) {
79         uint64_t u;
80         const DHCPClientId *id = p;
81
82         assert(id);
83         assert(id->length);
84         assert(id->data);
85
86         siphash24((uint8_t*) &u, id->data, id->length, hash_key);
87
88         return (unsigned long) u;
89 }
90
91 int client_id_compare_func(const void *_a, const void *_b) {
92         const DHCPClientId *a, *b;
93
94         a = _a;
95         b = _b;
96
97         assert(!a->length || a->data);
98         assert(!b->length || b->data);
99
100         if (a->length != b->length)
101                 return a->length < b->length ? -1 : 1;
102
103         return memcmp(a->data, b->data, a->length);
104 }
105
106 static void dhcp_lease_free(DHCPLease *lease) {
107         if (!lease)
108                 return;
109
110         free(lease->client_id.data);
111         free(lease);
112 }
113
114 sd_dhcp_server *sd_dhcp_server_unref(sd_dhcp_server *server) {
115         DHCPLease *lease;
116
117         if (!server)
118                 return NULL;
119
120         if (REFCNT_DEC(server->n_ref) > 0)
121                 return NULL;
122
123         log_dhcp_server(server, "UNREF");
124
125         sd_dhcp_server_stop(server);
126
127         sd_event_unref(server->event);
128
129         while ((lease = hashmap_steal_first(server->leases_by_client_id)))
130                 dhcp_lease_free(lease);
131         hashmap_free(server->leases_by_client_id);
132
133         free(server->bound_leases);
134         free(server);
135
136         return NULL;
137 }
138
139 int sd_dhcp_server_new(sd_dhcp_server **ret, int ifindex) {
140         _cleanup_dhcp_server_unref_ sd_dhcp_server *server = NULL;
141
142         assert_return(ret, -EINVAL);
143         assert_return(ifindex > 0, -EINVAL);
144
145         server = new0(sd_dhcp_server, 1);
146         if (!server)
147                 return -ENOMEM;
148
149         server->n_ref = REFCNT_INIT;
150         server->fd_raw = -1;
151         server->fd = -1;
152         server->address = htobe32(INADDR_ANY);
153         server->index = ifindex;
154         server->leases_by_client_id = hashmap_new(client_id_hash_func, client_id_compare_func);
155
156         *ret = server;
157         server = NULL;
158
159         return 0;
160 }
161
162 int sd_dhcp_server_attach_event(sd_dhcp_server *server, sd_event *event, int priority) {
163         int r;
164
165         assert_return(server, -EINVAL);
166         assert_return(!server->event, -EBUSY);
167
168         if (event)
169                 server->event = sd_event_ref(event);
170         else {
171                 r = sd_event_default(&server->event);
172                 if (r < 0)
173                         return r;
174         }
175
176         server->event_priority = priority;
177
178         return 0;
179 }
180
181 int sd_dhcp_server_detach_event(sd_dhcp_server *server) {
182         assert_return(server, -EINVAL);
183
184         server->event = sd_event_unref(server->event);
185
186         return 0;
187 }
188
189 sd_event *sd_dhcp_server_get_event(sd_dhcp_server *server) {
190         assert_return(server, NULL);
191
192         return server->event;
193 }
194
195 int sd_dhcp_server_stop(sd_dhcp_server *server) {
196         assert_return(server, -EINVAL);
197
198         server->receive_message =
199                 sd_event_source_unref(server->receive_message);
200
201         server->fd_raw = safe_close(server->fd_raw);
202         server->fd = safe_close(server->fd);
203
204         log_dhcp_server(server, "STOPPED");
205
206         return 0;
207 }
208
209 static int dhcp_server_send_unicast_raw(sd_dhcp_server *server, DHCPPacket *packet,
210                                         size_t len) {
211         union sockaddr_union link = {
212                 .ll.sll_family = AF_PACKET,
213                 .ll.sll_protocol = htons(ETH_P_IP),
214                 .ll.sll_ifindex = server->index,
215                 .ll.sll_halen = ETH_ALEN,
216         };
217         int r;
218
219         assert(server);
220         assert(server->index > 0);
221         assert(server->address);
222         assert(packet);
223         assert(len > sizeof(DHCPPacket));
224
225         memcpy(&link.ll.sll_addr, &packet->dhcp.chaddr, ETH_ALEN);
226
227         dhcp_packet_append_ip_headers(packet, server->address, DHCP_PORT_SERVER,
228                                       packet->dhcp.yiaddr, DHCP_PORT_CLIENT, len);
229
230         r = dhcp_network_send_raw_socket(server->fd_raw, &link, packet, len);
231         if (r < 0)
232                 return r;
233
234         return 0;
235 }
236
237 static int dhcp_server_send_udp(sd_dhcp_server *server, be32_t destination,
238                                 DHCPMessage *message, size_t len) {
239         union sockaddr_union dest = {
240                 .in.sin_family = AF_INET,
241                 .in.sin_port = htobe16(DHCP_PORT_CLIENT),
242                 .in.sin_addr.s_addr = destination,
243         };
244         struct iovec iov = {
245                 .iov_base = message,
246                 .iov_len = len,
247         };
248         uint8_t cmsgbuf[CMSG_LEN(sizeof(struct in_pktinfo))] = {};
249         struct msghdr msg = {
250                 .msg_name = &dest,
251                 .msg_namelen = sizeof(dest.in),
252                 .msg_iov = &iov,
253                 .msg_iovlen = 1,
254                 .msg_control = cmsgbuf,
255                 .msg_controllen = sizeof(cmsgbuf),
256         };
257         struct cmsghdr *cmsg;
258         struct in_pktinfo *pktinfo;
259         int r;
260
261         assert(server);
262         assert(server->fd > 0);
263         assert(message);
264         assert(len > sizeof(DHCPMessage));
265
266         cmsg = CMSG_FIRSTHDR(&msg);
267         assert(cmsg);
268
269         cmsg->cmsg_level = IPPROTO_IP;
270         cmsg->cmsg_type = IP_PKTINFO;
271         cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
272
273         /* we attach source interface and address info to the message
274            rather than binding the socket. This will be mostly useful
275            when we gain support for arbitrary number of server addresses
276          */
277         pktinfo = (struct in_pktinfo*) CMSG_DATA(cmsg);
278         assert(pktinfo);
279
280         pktinfo->ipi_ifindex = server->index;
281         pktinfo->ipi_spec_dst.s_addr = server->address;
282
283         r = sendmsg(server->fd, &msg, 0);
284         if (r < 0)
285                 return -errno;
286
287         return 0;
288 }
289
290 static bool requested_broadcast(DHCPRequest *req) {
291         assert(req);
292
293         return req->message->flags & htobe16(0x8000);
294 }
295
296 int dhcp_server_send_packet(sd_dhcp_server *server,
297                             DHCPRequest *req, DHCPPacket *packet,
298                             int type, size_t optoffset) {
299         be32_t destination = INADDR_ANY;
300         int r;
301
302         assert(server);
303         assert(req);
304         assert(req->max_optlen);
305         assert(optoffset <= req->max_optlen);
306         assert(packet);
307
308         r = dhcp_option_append(&packet->dhcp, req->max_optlen, &optoffset, 0,
309                                DHCP_OPTION_SERVER_IDENTIFIER,
310                                4, &server->address);
311         if (r < 0)
312                 return r;
313
314         r = dhcp_option_append(&packet->dhcp, req->max_optlen, &optoffset, 0,
315                                DHCP_OPTION_END, 0, NULL);
316         if (r < 0)
317                 return r;
318
319         /* RFC 2131 Section 4.1
320
321            If the â€™giaddr’ field in a DHCP message from a client is non-zero,
322            the server sends any return messages to the â€™DHCP server’ port on the
323            BOOTP relay agent whose address appears in â€™giaddr’. If the â€™giaddr’
324            field is zero and the â€™ciaddr’ field is nonzero, then the server
325            unicasts DHCPOFFER and DHCPACK messages to the address in â€™ciaddr’.
326            If â€™giaddr’ is zero and â€™ciaddr’ is zero, and the broadcast bit is
327            set, then the server broadcasts DHCPOFFER and DHCPACK messages to
328            0xffffffff. If the broadcast bit is not set and â€™giaddr’ is zero and
329            â€™ciaddr’ is zero, then the server unicasts DHCPOFFER and DHCPACK
330            messages to the client’s hardware address and â€™yiaddr’ address. In
331            all cases, when â€™giaddr’ is zero, the server broadcasts any DHCPNAK
332            messages to 0xffffffff.
333
334            Section 4.3.2
335
336            If â€™giaddr’ is set in the DHCPREQUEST message, the client is on a
337            different subnet. The server MUST set the broadcast bit in the
338            DHCPNAK, so that the relay agent will broadcast the DHCPNAK to the
339            client, because the client may not have a correct network address
340            or subnet mask, and the client may not be answering ARP requests.
341          */
342         if (req->message->giaddr) {
343                 destination = req->message->giaddr;
344                 if (type == DHCP_NAK)
345                         packet->dhcp.flags = htobe16(0x8000);
346         } else if (req->message->ciaddr && type != DHCP_NAK)
347                 destination = req->message->ciaddr;
348
349         if (destination != INADDR_ANY)
350                 return dhcp_server_send_udp(server, destination, &packet->dhcp,
351                                             sizeof(DHCPMessage) + optoffset);
352         else if (requested_broadcast(req) || type == DHCP_NAK)
353                 return dhcp_server_send_udp(server, INADDR_BROADCAST, &packet->dhcp,
354                                             sizeof(DHCPMessage) + optoffset);
355         else
356                 /* we cannot send UDP packet to specific MAC address when the address is
357                    not yet configured, so must fall back to raw packets */
358                 return dhcp_server_send_unicast_raw(server, packet,
359                                                     sizeof(DHCPPacket) + optoffset);
360 }
361
362 static int server_message_init(sd_dhcp_server *server, DHCPPacket **ret,
363                                uint8_t type, size_t *_optoffset, DHCPRequest *req) {
364         _cleanup_free_ DHCPPacket *packet = NULL;
365         size_t optoffset;
366         int r;
367
368         assert(server);
369         assert(ret);
370         assert(_optoffset);
371         assert(IN_SET(type, DHCP_OFFER, DHCP_ACK, DHCP_NAK));
372
373         packet = malloc0(sizeof(DHCPPacket) + req->max_optlen);
374         if (!packet)
375                 return -ENOMEM;
376
377         r = dhcp_message_init(&packet->dhcp, BOOTREPLY, be32toh(req->message->xid),
378                               type, req->max_optlen, &optoffset);
379         if (r < 0)
380                 return r;
381
382         packet->dhcp.flags = req->message->flags;
383         packet->dhcp.giaddr = req->message->giaddr;
384         memcpy(&packet->dhcp.chaddr, &req->message->chaddr, ETH_ALEN);
385
386         *_optoffset = optoffset;
387         *ret = packet;
388         packet = NULL;
389
390         return 0;
391 }
392
393 static int server_send_offer(sd_dhcp_server *server, DHCPRequest *req, be32_t address) {
394         _cleanup_free_ DHCPPacket *packet = NULL;
395         size_t offset;
396         be32_t lease_time;
397         int r;
398
399         r = server_message_init(server, &packet, DHCP_OFFER, &offset, req);
400         if (r < 0)
401                 return r;
402
403         packet->dhcp.yiaddr = address;
404
405         lease_time = htobe32(req->lifetime);
406         r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0,
407                                DHCP_OPTION_IP_ADDRESS_LEASE_TIME, 4, &lease_time);
408         if (r < 0)
409                 return r;
410
411         r = dhcp_server_send_packet(server, req, packet, DHCP_OFFER, offset);
412         if (r < 0)
413                 return r;
414
415         return 0;
416 }
417
418 static int server_send_ack(sd_dhcp_server *server, DHCPRequest *req, be32_t address) {
419         _cleanup_free_ DHCPPacket *packet = NULL;
420         size_t offset;
421         be32_t lease_time;
422         int r;
423
424         r = server_message_init(server, &packet, DHCP_ACK, &offset, req);
425         if (r < 0)
426                 return r;
427
428         packet->dhcp.yiaddr = address;
429
430         lease_time = htobe32(req->lifetime);
431         r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0,
432                                DHCP_OPTION_IP_ADDRESS_LEASE_TIME, 4, &lease_time);
433         if (r < 0)
434                 return r;
435
436         r = dhcp_server_send_packet(server, req, packet, DHCP_ACK, offset);
437         if (r < 0)
438                 return r;
439
440         return 0;
441 }
442
443 static int server_send_nak(sd_dhcp_server *server, DHCPRequest *req) {
444         _cleanup_free_ DHCPPacket *packet = NULL;
445         size_t offset;
446         int r;
447
448         r = server_message_init(server, &packet, DHCP_NAK, &offset, req);
449         if (r < 0)
450                 return r;
451
452         r = dhcp_server_send_packet(server, req, packet, DHCP_NAK, offset);
453         if (r < 0)
454                 return r;
455
456         return 0;
457 }
458
459 static int parse_request(uint8_t code, uint8_t len, const uint8_t *option,
460                          void *user_data) {
461         DHCPRequest *req = user_data;
462
463         assert(req);
464
465         switch(code) {
466         case DHCP_OPTION_IP_ADDRESS_LEASE_TIME:
467                 if (len == 4)
468                         req->lifetime = be32toh(*(be32_t*)option);
469
470                 break;
471         case DHCP_OPTION_REQUESTED_IP_ADDRESS:
472                 if (len == 4)
473                         req->requested_ip = *(be32_t*)option;
474
475                 break;
476         case DHCP_OPTION_SERVER_IDENTIFIER:
477                 if (len == 4)
478                         req->server_id = *(be32_t*)option;
479
480                 break;
481         case DHCP_OPTION_CLIENT_IDENTIFIER:
482                 if (len >= 2) {
483                         uint8_t *data;
484
485                         data = memdup(option, len);
486                         if (!data)
487                                 return -ENOMEM;
488
489                         free(req->client_id.data);
490                         req->client_id.data = data;
491                         req->client_id.length = len;
492                 }
493
494                 break;
495         case DHCP_OPTION_MAXIMUM_MESSAGE_SIZE:
496                 if (len == 2)
497                         req->max_optlen = be16toh(*(be16_t*)option) -
498                                           - sizeof(DHCPPacket);
499
500                 break;
501         }
502
503         return 0;
504 }
505
506 static void dhcp_request_free(DHCPRequest *req) {
507         if (!req)
508                 return;
509
510         free(req->client_id.data);
511         free(req);
512 }
513
514 DEFINE_TRIVIAL_CLEANUP_FUNC(DHCPRequest*, dhcp_request_free);
515 #define _cleanup_dhcp_request_free_ _cleanup_(dhcp_request_freep)
516
517 static int ensure_sane_request(DHCPRequest *req, DHCPMessage *message) {
518         assert(req);
519         assert(message);
520
521         req->message = message;
522
523         /* set client id based on mac address if client did not send an explicit one */
524         if (!req->client_id.data) {
525                 uint8_t *data;
526
527                 data = new0(uint8_t, ETH_ALEN + 1);
528                 if (!data)
529                         return -ENOMEM;
530
531                 req->client_id.length = ETH_ALEN + 1;
532                 req->client_id.data = data;
533                 req->client_id.data[0] = 0x01;
534                 memcpy(&req->client_id.data[1], &message->chaddr, ETH_ALEN);
535         }
536
537         if (req->max_optlen < DHCP_MIN_OPTIONS_SIZE)
538                 req->max_optlen = DHCP_MIN_OPTIONS_SIZE;
539
540         if (!req->lifetime)
541                 req->lifetime = DHCP_DEFAULT_LEASE_TIME;
542
543         return 0;
544 }
545
546 static int get_pool_offset(sd_dhcp_server *server, be32_t requested_ip) {
547         assert(server);
548
549         if (!server->pool_size)
550                 return -EINVAL;
551
552         if (be32toh(requested_ip) < be32toh(server->pool_start) ||
553             be32toh(requested_ip) >= be32toh(server->pool_start) +
554                                              + server->pool_size)
555                 return -EINVAL;
556
557         return be32toh(requested_ip) - be32toh(server->pool_start);
558 }
559
560 int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
561                                size_t length) {
562         _cleanup_dhcp_request_free_ DHCPRequest *req = NULL;
563         DHCPLease *existing_lease;
564         int type, r;
565
566         assert(server);
567         assert(message);
568
569         if (message->op != BOOTREQUEST ||
570             message->htype != ARPHRD_ETHER ||
571             message->hlen != ETHER_ADDR_LEN)
572                 return 0;
573
574         req = new0(DHCPRequest, 1);
575         if (!req)
576                 return -ENOMEM;
577
578         type = dhcp_option_parse(message, length, parse_request, req);
579         if (type < 0)
580                 return 0;
581
582         r = ensure_sane_request(req, message);
583         if (r < 0)
584                 /* this only fails on critical errors */
585                 return r;
586
587         existing_lease = hashmap_get(server->leases_by_client_id, &req->client_id);
588
589         switch(type) {
590         case DHCP_DISCOVER:
591         {
592                 be32_t address = INADDR_ANY;
593                 unsigned i;
594
595                 log_dhcp_server(server, "DISCOVER (0x%x)",
596                                 be32toh(req->message->xid));
597
598                 if (!server->pool_size)
599                         /* no pool allocated */
600                         return 0;
601
602                 /* for now pick a random free address from the pool */
603                 if (existing_lease)
604                         address = existing_lease->address;
605                 else {
606                         for (i = 0; i < server->pool_size; i++) {
607                                 if (!server->bound_leases[server->next_offer]) {
608                                         address = htobe32(be32toh(server->pool_start) + server->next_offer);
609                                         break;
610                                 } else
611                                         server->next_offer = (server->next_offer + 1) % server->pool_size;
612                         }
613                 }
614
615                 if (address == INADDR_ANY)
616                         /* no free addresses left */
617                         return 0;
618
619                 r = server_send_offer(server, req, address);
620                 if (r < 0) {
621                         /* this only fails on critical errors */
622                         log_dhcp_server(server, "could not send offer: %s",
623                                         strerror(-r));
624                         return r;
625                 } else {
626                         log_dhcp_server(server, "OFFER (0x%x)",
627                                         be32toh(req->message->xid));
628                         return DHCP_OFFER;
629                 }
630
631                 break;
632         }
633         case DHCP_DECLINE:
634                 log_dhcp_server(server, "DECLINE (0x%x)",
635                                 be32toh(req->message->xid));
636
637                 /* TODO: make sure we don't offer this address again */
638
639                 return 1;
640
641                 break;
642         case DHCP_REQUEST:
643         {
644                 be32_t address;
645                 bool init_reboot = false;
646                 int pool_offset;
647
648                 /* see RFC 2131, section 4.3.2 */
649
650                 if (req->server_id) {
651                         log_dhcp_server(server, "REQUEST (selecting) (0x%x)",
652                                         be32toh(req->message->xid));
653
654                         /* SELECTING */
655                         if (req->server_id != server->address)
656                                 /* client did not pick us */
657                                 return 0;
658
659                         if (req->message->ciaddr)
660                                 /* this MUST be zero */
661                                 return 0;
662
663                         if (!req->requested_ip)
664                                 /* this must be filled in with the yiaddr
665                                    from the chosen OFFER */
666                                 return 0;
667
668                         address = req->requested_ip;
669                 } else if (req->requested_ip) {
670                         log_dhcp_server(server, "REQUEST (init-reboot) (0x%x)",
671                                         be32toh(req->message->xid));
672
673                         /* INIT-REBOOT */
674                         if (req->message->ciaddr)
675                                 /* this MUST be zero */
676                                 return 0;
677
678                         /* TODO: check more carefully if IP is correct */
679                         address = req->requested_ip;
680                         init_reboot = true;
681                 } else {
682                         log_dhcp_server(server, "REQUEST (rebinding/renewing) (0x%x)",
683                                         be32toh(req->message->xid));
684
685                         /* REBINDING / RENEWING */
686                         if (!req->message->ciaddr)
687                                 /* this MUST be filled in with clients IP address */
688                                 return 0;
689
690                         address = req->message->ciaddr;
691                 }
692
693                 pool_offset = get_pool_offset(server, address);
694
695                 /* verify that the requested address is from the pool, and either
696                    owned by the current client or free */
697                 if (pool_offset >= 0 &&
698                     server->bound_leases[pool_offset] == existing_lease) {
699                         DHCPLease *lease;
700                         usec_t time_now;
701
702                         if (!existing_lease) {
703                                 lease = new0(DHCPLease, 1);
704                                 lease->address = req->requested_ip;
705                                 lease->client_id.data = memdup(req->client_id.data,
706                                                                req->client_id.length);
707                                 if (!lease->client_id.data) {
708                                         free(lease);
709                                         return -ENOMEM;
710                                 }
711                                 lease->client_id.length = req->client_id.length;
712                         } else
713                                 lease = existing_lease;
714
715                         r = sd_event_now(server->event, CLOCK_MONOTONIC, &time_now);
716                         if (r < 0)
717                                 time_now = now(CLOCK_MONOTONIC);
718                         lease->expiration = req->lifetime * USEC_PER_SEC + time_now;
719
720                         r = server_send_ack(server, req, address);
721                         if (r < 0) {
722                                 /* this only fails on critical errors */
723                                 log_dhcp_server(server, "could not send ack: %s",
724                                                 strerror(-r));
725
726                                 if (!existing_lease)
727                                         dhcp_lease_free(lease);
728
729                                 return r;
730                         } else {
731                                 log_dhcp_server(server, "ACK (0x%x)",
732                                                 be32toh(req->message->xid));
733
734                                 server->bound_leases[pool_offset] = lease;
735                                 hashmap_put(server->leases_by_client_id, &lease->client_id, lease);
736
737                                 return DHCP_ACK;
738                         }
739                 } else if (init_reboot) {
740                         r = server_send_nak(server, req);
741                         if (r < 0) {
742                                 /* this only fails on critical errors */
743                                 log_dhcp_server(server, "could not send nak: %s",
744                                                 strerror(-r));
745                                 return r;
746                         } else {
747                                 log_dhcp_server(server, "NAK (0x%x)",
748                                                 be32toh(req->message->xid));
749                                 return DHCP_NAK;
750                         }
751                 }
752
753                 break;
754         }
755         case DHCP_RELEASE: {
756                 int pool_offset;
757
758                 log_dhcp_server(server, "RELEASE (0x%x)",
759                                 be32toh(req->message->xid));
760
761                 if (!existing_lease)
762                         return 0;
763
764                 if (existing_lease->address != req->message->ciaddr)
765                         return 0;
766
767                 pool_offset = get_pool_offset(server, req->message->ciaddr);
768                 if (pool_offset < 0)
769                         return 0;
770
771                 if (server->bound_leases[pool_offset] == existing_lease) {
772                         server->bound_leases[pool_offset] = NULL;
773                         hashmap_remove(server->leases_by_client_id, existing_lease);
774                         dhcp_lease_free(existing_lease);
775
776                         return 1;
777                 } else
778                         return 0;
779         }
780         }
781
782         return 0;
783 }
784
785 static int server_receive_message(sd_event_source *s, int fd,
786                                   uint32_t revents, void *userdata) {
787         _cleanup_free_ DHCPMessage *message = NULL;
788         uint8_t cmsgbuf[CMSG_LEN(sizeof(struct in_pktinfo))];
789         sd_dhcp_server *server = userdata;
790         struct iovec iov = {};
791         struct msghdr msg = {
792                 .msg_iov = &iov,
793                 .msg_iovlen = 1,
794                 .msg_control = cmsgbuf,
795                 .msg_controllen = sizeof(cmsgbuf),
796         };
797         struct cmsghdr *cmsg;
798         int buflen = 0, len, r;
799
800         assert(server);
801
802         r = ioctl(fd, FIONREAD, &buflen);
803         if (r < 0)
804                 return r;
805         if (buflen < 0)
806                 return -EIO;
807
808         message = malloc0(buflen);
809         if (!message)
810                 return -ENOMEM;
811
812         iov.iov_base = message;
813         iov.iov_len = buflen;
814
815         len = recvmsg(fd, &msg, 0);
816         if (len < buflen)
817                 return 0;
818         else if ((size_t)len < sizeof(DHCPMessage))
819                 return 0;
820
821         for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
822                 if (cmsg->cmsg_level == IPPROTO_IP &&
823                     cmsg->cmsg_type == IP_PKTINFO &&
824                     cmsg->cmsg_len == CMSG_LEN(sizeof(struct in_pktinfo))) {
825                         struct in_pktinfo *info = (struct in_pktinfo*)CMSG_DATA(cmsg);
826
827                         /* TODO figure out if this can be done as a filter on the socket, like for IPv6 */
828                         if (server->index != info->ipi_ifindex)
829                                 return 0;
830
831                         break;
832                 }
833         }
834
835         return dhcp_server_handle_message(server, message, (size_t)len);
836 }
837
838 int sd_dhcp_server_start(sd_dhcp_server *server) {
839         int r;
840
841         assert_return(server, -EINVAL);
842         assert_return(server->event, -EINVAL);
843         assert_return(!server->receive_message, -EBUSY);
844         assert_return(server->fd_raw == -1, -EBUSY);
845         assert_return(server->fd == -1, -EBUSY);
846         assert_return(server->address != htobe32(INADDR_ANY), -EUNATCH);
847
848         r = socket(AF_PACKET, SOCK_DGRAM | SOCK_NONBLOCK, 0);
849         if (r < 0) {
850                 r = -errno;
851                 sd_dhcp_server_stop(server);
852                 return r;
853         }
854         server->fd_raw = r;
855
856         r = dhcp_network_bind_udp_socket(INADDR_ANY, DHCP_PORT_SERVER);
857         if (r < 0) {
858                 sd_dhcp_server_stop(server);
859                 return r;
860         }
861         server->fd = r;
862
863         r = sd_event_add_io(server->event, &server->receive_message,
864                             server->fd, EPOLLIN,
865                             server_receive_message, server);
866         if (r < 0) {
867                 sd_dhcp_server_stop(server);
868                 return r;
869         }
870
871         r = sd_event_source_set_priority(server->receive_message,
872                                          server->event_priority);
873         if (r < 0) {
874                 sd_dhcp_server_stop(server);
875                 return r;
876         }
877
878         log_dhcp_server(server, "STARTED");
879
880         return 0;
881 }