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 sd_dhcp_server *sd_dhcp_server_ref(sd_dhcp_server *server) {
32 assert_se(REFCNT_INC(server->n_ref) >= 2);
37 sd_dhcp_server *sd_dhcp_server_unref(sd_dhcp_server *server) {
38 if (server && REFCNT_DEC(server->n_ref) <= 0) {
39 log_dhcp_server(server, "UNREF");
41 sd_dhcp_server_stop(server);
43 sd_event_unref(server->event);
50 int sd_dhcp_server_new(sd_dhcp_server **ret, int ifindex) {
51 _cleanup_dhcp_server_unref_ sd_dhcp_server *server = NULL;
53 assert_return(ret, -EINVAL);
54 assert_return(ifindex > 0, -EINVAL);
56 server = new0(sd_dhcp_server, 1);
60 server->n_ref = REFCNT_INIT;
62 server->index = ifindex;
70 int sd_dhcp_server_attach_event(sd_dhcp_server *server, sd_event *event, int priority) {
73 assert_return(server, -EINVAL);
74 assert_return(!server->event, -EBUSY);
77 server->event = sd_event_ref(event);
79 r = sd_event_default(&server->event);
84 server->event_priority = priority;
89 int sd_dhcp_server_detach_event(sd_dhcp_server *server) {
90 assert_return(server, -EINVAL);
92 server->event = sd_event_unref(server->event);
97 sd_event *sd_dhcp_server_get_event(sd_dhcp_server *server) {
98 assert_return(server, NULL);
100 return server->event;
103 int sd_dhcp_server_stop(sd_dhcp_server *server) {
104 assert_return(server, -EINVAL);
106 server->receive_message =
107 sd_event_source_unref(server->receive_message);
109 server->fd = safe_close(server->fd);
111 log_dhcp_server(server, "STOPPED");
116 static int parse_request(uint8_t code, uint8_t len, const uint8_t *option,
118 DHCPRequest *req = user_data;
123 case DHCP_OPTION_SERVER_IDENTIFIER:
125 req->server_id = *(be32_t*)option;
128 case DHCP_OPTION_CLIENT_IDENTIFIER:
132 data = memdup(option, len);
136 free(req->client_id.data);
137 req->client_id.data = data;
138 req->client_id.length = len;
142 case DHCP_OPTION_MAXIMUM_MESSAGE_SIZE:
144 req->max_optlen = be16toh(*(be16_t*)option) -
145 - sizeof(DHCPPacket);
153 static void dhcp_request_free(DHCPRequest *req) {
157 free(req->client_id.data);
161 DEFINE_TRIVIAL_CLEANUP_FUNC(DHCPRequest*, dhcp_request_free);
162 #define _cleanup_dhcp_request_free_ _cleanup_(dhcp_request_freep)
164 static int ensure_sane_request(DHCPRequest *req, DHCPMessage *message) {
168 req->message = message;
170 /* set client id based on mac address if client did not send an explicit one */
171 if (!req->client_id.data) {
174 data = new0(uint8_t, ETH_ALEN + 1);
178 req->client_id.length = ETH_ALEN + 1;
179 req->client_id.data = data;
180 req->client_id.data[0] = 0x01;
181 memcpy(&req->client_id.data[1], &message->chaddr, ETH_ALEN);
184 if (req->max_optlen < DHCP_MIN_OPTIONS_SIZE)
185 req->max_optlen = DHCP_MIN_OPTIONS_SIZE;
190 int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
192 _cleanup_dhcp_request_free_ DHCPRequest *req = NULL;
198 if (message->op != BOOTREQUEST ||
199 message->htype != ARPHRD_ETHER ||
200 message->hlen != ETHER_ADDR_LEN)
203 req = new0(DHCPRequest, 1);
207 type = dhcp_option_parse(message, length, parse_request, req);
211 r = ensure_sane_request(req, message);
213 /* this only fails on critical errors */
216 log_dhcp_server(server, "received message of type %d", type);
221 static int server_receive_message(sd_event_source *s, int fd,
222 uint32_t revents, void *userdata) {
223 _cleanup_free_ DHCPMessage *message = NULL;
224 uint8_t cmsgbuf[CMSG_LEN(sizeof(struct in_pktinfo))];
225 sd_dhcp_server *server = userdata;
226 struct iovec iov = {};
227 struct msghdr msg = {
230 .msg_control = cmsgbuf,
231 .msg_controllen = sizeof(cmsgbuf),
233 struct cmsghdr *cmsg;
234 int buflen = 0, len, r;
238 r = ioctl(fd, FIONREAD, &buflen);
244 message = malloc0(buflen);
248 iov.iov_base = message;
249 iov.iov_len = buflen;
251 len = recvmsg(fd, &msg, 0);
254 else if ((size_t)len < sizeof(DHCPMessage))
257 for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
258 if (cmsg->cmsg_level == IPPROTO_IP &&
259 cmsg->cmsg_type == IP_PKTINFO &&
260 cmsg->cmsg_len == CMSG_LEN(sizeof(struct in_pktinfo))) {
261 struct in_pktinfo *info = (struct in_pktinfo*)CMSG_DATA(cmsg);
263 /* TODO figure out if this can be done as a filter on the socket, like for IPv6 */
264 if (server->index != info->ipi_ifindex)
271 return dhcp_server_handle_message(server, message, (size_t)len);
274 int sd_dhcp_server_start(sd_dhcp_server *server) {
277 assert_return(server, -EINVAL);
278 assert_return(server->event, -EINVAL);
279 assert_return(!server->receive_message, -EBUSY);
280 assert_return(server->fd == -1, -EBUSY);
282 r = dhcp_network_bind_udp_socket(INADDR_ANY, DHCP_PORT_SERVER);
284 sd_dhcp_server_stop(server);
289 r = sd_event_add_io(server->event, &server->receive_message,
291 server_receive_message, server);
293 sd_dhcp_server_stop(server);
297 r = sd_event_source_set_priority(server->receive_message,
298 server->event_priority);
300 sd_dhcp_server_stop(server);
304 log_dhcp_server(server, "STARTED");