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 int sd_dhcp_server_set_address(sd_dhcp_server *server, struct in_addr *address) {
31 assert_return(server, -EINVAL);
32 assert_return(address, -EINVAL);
33 assert_return(address->s_addr, -EINVAL);
34 assert_return(server->address == htobe32(INADDR_ANY), -EBUSY);
36 server->address = address->s_addr;
41 sd_dhcp_server *sd_dhcp_server_ref(sd_dhcp_server *server) {
43 assert_se(REFCNT_INC(server->n_ref) >= 2);
48 sd_dhcp_server *sd_dhcp_server_unref(sd_dhcp_server *server) {
49 if (server && REFCNT_DEC(server->n_ref) <= 0) {
50 log_dhcp_server(server, "UNREF");
52 sd_dhcp_server_stop(server);
54 sd_event_unref(server->event);
61 int sd_dhcp_server_new(sd_dhcp_server **ret, int ifindex) {
62 _cleanup_dhcp_server_unref_ sd_dhcp_server *server = NULL;
64 assert_return(ret, -EINVAL);
65 assert_return(ifindex > 0, -EINVAL);
67 server = new0(sd_dhcp_server, 1);
71 server->n_ref = REFCNT_INIT;
74 server->address = htobe32(INADDR_ANY);
75 server->index = ifindex;
83 int sd_dhcp_server_attach_event(sd_dhcp_server *server, sd_event *event, int priority) {
86 assert_return(server, -EINVAL);
87 assert_return(!server->event, -EBUSY);
90 server->event = sd_event_ref(event);
92 r = sd_event_default(&server->event);
97 server->event_priority = priority;
102 int sd_dhcp_server_detach_event(sd_dhcp_server *server) {
103 assert_return(server, -EINVAL);
105 server->event = sd_event_unref(server->event);
110 sd_event *sd_dhcp_server_get_event(sd_dhcp_server *server) {
111 assert_return(server, NULL);
113 return server->event;
116 int sd_dhcp_server_stop(sd_dhcp_server *server) {
117 assert_return(server, -EINVAL);
119 server->receive_message =
120 sd_event_source_unref(server->receive_message);
122 server->fd_raw = safe_close(server->fd_raw);
123 server->fd = safe_close(server->fd);
125 log_dhcp_server(server, "STOPPED");
130 static int parse_request(uint8_t code, uint8_t len, const uint8_t *option,
132 DHCPRequest *req = user_data;
137 case DHCP_OPTION_SERVER_IDENTIFIER:
139 req->server_id = *(be32_t*)option;
142 case DHCP_OPTION_CLIENT_IDENTIFIER:
146 data = memdup(option, len);
150 free(req->client_id.data);
151 req->client_id.data = data;
152 req->client_id.length = len;
156 case DHCP_OPTION_MAXIMUM_MESSAGE_SIZE:
158 req->max_optlen = be16toh(*(be16_t*)option) -
159 - sizeof(DHCPPacket);
167 static void dhcp_request_free(DHCPRequest *req) {
171 free(req->client_id.data);
175 DEFINE_TRIVIAL_CLEANUP_FUNC(DHCPRequest*, dhcp_request_free);
176 #define _cleanup_dhcp_request_free_ _cleanup_(dhcp_request_freep)
178 static int ensure_sane_request(DHCPRequest *req, DHCPMessage *message) {
182 req->message = message;
184 /* set client id based on mac address if client did not send an explicit one */
185 if (!req->client_id.data) {
188 data = new0(uint8_t, ETH_ALEN + 1);
192 req->client_id.length = ETH_ALEN + 1;
193 req->client_id.data = data;
194 req->client_id.data[0] = 0x01;
195 memcpy(&req->client_id.data[1], &message->chaddr, ETH_ALEN);
198 if (req->max_optlen < DHCP_MIN_OPTIONS_SIZE)
199 req->max_optlen = DHCP_MIN_OPTIONS_SIZE;
204 int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
206 _cleanup_dhcp_request_free_ DHCPRequest *req = NULL;
212 if (message->op != BOOTREQUEST ||
213 message->htype != ARPHRD_ETHER ||
214 message->hlen != ETHER_ADDR_LEN)
217 req = new0(DHCPRequest, 1);
221 type = dhcp_option_parse(message, length, parse_request, req);
225 r = ensure_sane_request(req, message);
227 /* this only fails on critical errors */
230 log_dhcp_server(server, "received message of type %d", type);
235 static int server_receive_message(sd_event_source *s, int fd,
236 uint32_t revents, void *userdata) {
237 _cleanup_free_ DHCPMessage *message = NULL;
238 uint8_t cmsgbuf[CMSG_LEN(sizeof(struct in_pktinfo))];
239 sd_dhcp_server *server = userdata;
240 struct iovec iov = {};
241 struct msghdr msg = {
244 .msg_control = cmsgbuf,
245 .msg_controllen = sizeof(cmsgbuf),
247 struct cmsghdr *cmsg;
248 int buflen = 0, len, r;
252 r = ioctl(fd, FIONREAD, &buflen);
258 message = malloc0(buflen);
262 iov.iov_base = message;
263 iov.iov_len = buflen;
265 len = recvmsg(fd, &msg, 0);
268 else if ((size_t)len < sizeof(DHCPMessage))
271 for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
272 if (cmsg->cmsg_level == IPPROTO_IP &&
273 cmsg->cmsg_type == IP_PKTINFO &&
274 cmsg->cmsg_len == CMSG_LEN(sizeof(struct in_pktinfo))) {
275 struct in_pktinfo *info = (struct in_pktinfo*)CMSG_DATA(cmsg);
277 /* TODO figure out if this can be done as a filter on the socket, like for IPv6 */
278 if (server->index != info->ipi_ifindex)
285 return dhcp_server_handle_message(server, message, (size_t)len);
288 int sd_dhcp_server_start(sd_dhcp_server *server) {
291 assert_return(server, -EINVAL);
292 assert_return(server->event, -EINVAL);
293 assert_return(!server->receive_message, -EBUSY);
294 assert_return(server->fd_raw == -1, -EBUSY);
295 assert_return(server->fd == -1, -EBUSY);
296 assert_return(server->address != htobe32(INADDR_ANY), -EUNATCH);
298 r = socket(AF_PACKET, SOCK_DGRAM | SOCK_NONBLOCK, 0);
301 sd_dhcp_server_stop(server);
306 r = dhcp_network_bind_udp_socket(INADDR_ANY, DHCP_PORT_SERVER);
308 sd_dhcp_server_stop(server);
313 r = sd_event_add_io(server->event, &server->receive_message,
315 server_receive_message, server);
317 sd_dhcp_server_stop(server);
321 r = sd_event_source_set_priority(server->receive_message,
322 server->event_priority);
324 sd_dhcp_server_stop(server);
328 log_dhcp_server(server, "STARTED");