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