1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright (C) 2013 Intel Corporation. All rights reserved.
7 Copyright (C) 2014 Tom Gundersen
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.
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.
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/>.
23 #include <sys/ioctl.h>
24 #include <netinet/if_ether.h>
26 #include "sd-dhcp-server.h"
27 #include "dhcp-server-internal.h"
28 #include "dhcp-internal.h"
30 #define DHCP_DEFAULT_LEASE_TIME 60
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);
38 server->address = address->s_addr;
43 sd_dhcp_server *sd_dhcp_server_ref(sd_dhcp_server *server) {
45 assert_se(REFCNT_INC(server->n_ref) >= 2);
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");
54 sd_dhcp_server_stop(server);
56 sd_event_unref(server->event);
63 int sd_dhcp_server_new(sd_dhcp_server **ret, int ifindex) {
64 _cleanup_dhcp_server_unref_ sd_dhcp_server *server = NULL;
66 assert_return(ret, -EINVAL);
67 assert_return(ifindex > 0, -EINVAL);
69 server = new0(sd_dhcp_server, 1);
73 server->n_ref = REFCNT_INIT;
76 server->address = htobe32(INADDR_ANY);
77 server->index = ifindex;
85 int sd_dhcp_server_attach_event(sd_dhcp_server *server, sd_event *event, int priority) {
88 assert_return(server, -EINVAL);
89 assert_return(!server->event, -EBUSY);
92 server->event = sd_event_ref(event);
94 r = sd_event_default(&server->event);
99 server->event_priority = priority;
104 int sd_dhcp_server_detach_event(sd_dhcp_server *server) {
105 assert_return(server, -EINVAL);
107 server->event = sd_event_unref(server->event);
112 sd_event *sd_dhcp_server_get_event(sd_dhcp_server *server) {
113 assert_return(server, NULL);
115 return server->event;
118 int sd_dhcp_server_stop(sd_dhcp_server *server) {
119 assert_return(server, -EINVAL);
121 server->receive_message =
122 sd_event_source_unref(server->receive_message);
124 server->fd_raw = safe_close(server->fd_raw);
125 server->fd = safe_close(server->fd);
127 log_dhcp_server(server, "STOPPED");
132 static int dhcp_server_send_unicast_raw(sd_dhcp_server *server, DHCPPacket *packet,
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,
143 assert(server->index > 0);
144 assert(server->address);
146 assert(len > sizeof(DHCPPacket));
148 memcpy(&link.ll.sll_addr, &packet->dhcp.chaddr, ETH_ALEN);
150 dhcp_packet_append_ip_headers(packet, server->address, DHCP_PORT_SERVER,
151 packet->dhcp.yiaddr, DHCP_PORT_CLIENT, len);
153 r = dhcp_network_send_raw_socket(server->fd_raw, &link, packet, len);
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,
171 uint8_t cmsgbuf[CMSG_LEN(sizeof(struct in_pktinfo))] = {};
172 struct msghdr msg = {
174 .msg_namelen = sizeof(dest.in),
177 .msg_control = cmsgbuf,
178 .msg_controllen = sizeof(cmsgbuf),
180 struct cmsghdr *cmsg;
181 struct in_pktinfo *pktinfo;
185 assert(server->fd > 0);
187 assert(len > sizeof(DHCPMessage));
189 cmsg = CMSG_FIRSTHDR(&msg);
192 cmsg->cmsg_level = IPPROTO_IP;
193 cmsg->cmsg_type = IP_PKTINFO;
194 cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
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
200 pktinfo = (struct in_pktinfo*) CMSG_DATA(cmsg);
203 pktinfo->ipi_ifindex = server->index;
204 pktinfo->ipi_spec_dst.s_addr = server->address;
206 r = sendmsg(server->fd, &msg, 0);
213 static bool requested_broadcast(DHCPRequest *req) {
216 return req->message->flags & htobe16(0x8000);
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;
227 assert(req->max_optlen);
228 assert(optoffset <= req->max_optlen);
231 r = dhcp_option_append(&packet->dhcp, req->max_optlen, &optoffset, 0,
232 DHCP_OPTION_SERVER_IDENTIFIER,
233 4, &server->address);
237 r = dhcp_option_append(&packet->dhcp, req->max_optlen, &optoffset, 0,
238 DHCP_OPTION_END, 0, NULL);
242 /* RFC 2131 Section 4.1
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.
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.
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;
272 if (destination || requested_broadcast(req) || type == DHCP_NAK)
273 return dhcp_server_send_udp(server, destination, &packet->dhcp,
274 sizeof(DHCPMessage) + optoffset);
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);
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;
291 assert(type == DHCP_OFFER);
293 packet = malloc0(sizeof(DHCPPacket) + req->max_optlen);
297 r = dhcp_message_init(&packet->dhcp, BOOTREPLY, be32toh(req->message->xid),
298 type, req->max_optlen, &optoffset);
302 packet->dhcp.flags = req->message->flags;
303 packet->dhcp.giaddr = req->message->giaddr;
304 memcpy(&packet->dhcp.chaddr, &req->message->chaddr, ETH_ALEN);
306 *_optoffset = optoffset;
313 static int server_send_offer(sd_dhcp_server *server, DHCPRequest *req) {
314 _cleanup_free_ DHCPPacket *packet = NULL;
319 r = server_message_init(server, &packet, DHCP_OFFER, &offset, req);
323 /* for now offer a random IP */
324 packet->dhcp.yiaddr = random_u32();
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);
333 r = dhcp_server_send_packet(server, req, packet, DHCP_OFFER, offset);
340 static int parse_request(uint8_t code, uint8_t len, const uint8_t *option,
342 DHCPRequest *req = user_data;
347 case DHCP_OPTION_SERVER_IDENTIFIER:
349 req->server_id = *(be32_t*)option;
352 case DHCP_OPTION_CLIENT_IDENTIFIER:
356 data = memdup(option, len);
360 free(req->client_id.data);
361 req->client_id.data = data;
362 req->client_id.length = len;
366 case DHCP_OPTION_MAXIMUM_MESSAGE_SIZE:
368 req->max_optlen = be16toh(*(be16_t*)option) -
369 - sizeof(DHCPPacket);
377 static void dhcp_request_free(DHCPRequest *req) {
381 free(req->client_id.data);
385 DEFINE_TRIVIAL_CLEANUP_FUNC(DHCPRequest*, dhcp_request_free);
386 #define _cleanup_dhcp_request_free_ _cleanup_(dhcp_request_freep)
388 static int ensure_sane_request(DHCPRequest *req, DHCPMessage *message) {
392 req->message = message;
394 /* set client id based on mac address if client did not send an explicit one */
395 if (!req->client_id.data) {
398 data = new0(uint8_t, ETH_ALEN + 1);
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);
408 if (req->max_optlen < DHCP_MIN_OPTIONS_SIZE)
409 req->max_optlen = DHCP_MIN_OPTIONS_SIZE;
414 int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
416 _cleanup_dhcp_request_free_ DHCPRequest *req = NULL;
422 if (message->op != BOOTREQUEST ||
423 message->htype != ARPHRD_ETHER ||
424 message->hlen != ETHER_ADDR_LEN)
427 req = new0(DHCPRequest, 1);
431 type = dhcp_option_parse(message, length, parse_request, req);
435 r = ensure_sane_request(req, message);
437 /* this only fails on critical errors */
442 log_dhcp_server(server, "DISCOVER (0x%x)",
443 be32toh(req->message->xid));
445 r = server_send_offer(server, req);
447 /* this only fails on critical errors */
448 log_dhcp_server(server, "could not send offer: %s",
452 log_dhcp_server(server, "OFFER (0x%x)",
453 be32toh(req->message->xid));
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 = {
472 .msg_control = cmsgbuf,
473 .msg_controllen = sizeof(cmsgbuf),
475 struct cmsghdr *cmsg;
476 int buflen = 0, len, r;
480 r = ioctl(fd, FIONREAD, &buflen);
486 message = malloc0(buflen);
490 iov.iov_base = message;
491 iov.iov_len = buflen;
493 len = recvmsg(fd, &msg, 0);
496 else if ((size_t)len < sizeof(DHCPMessage))
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);
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)
513 return dhcp_server_handle_message(server, message, (size_t)len);
516 int sd_dhcp_server_start(sd_dhcp_server *server) {
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);
526 r = socket(AF_PACKET, SOCK_DGRAM | SOCK_NONBLOCK, 0);
529 sd_dhcp_server_stop(server);
534 r = dhcp_network_bind_udp_socket(INADDR_ANY, DHCP_PORT_SERVER);
536 sd_dhcp_server_stop(server);
541 r = sd_event_add_io(server->event, &server->receive_message,
543 server_receive_message, server);
545 sd_dhcp_server_stop(server);
549 r = sd_event_source_set_priority(server->receive_message,
550 server->event_priority);
552 sd_dhcp_server_stop(server);
556 log_dhcp_server(server, "STARTED");