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;
63 server->index = ifindex;
71 int sd_dhcp_server_attach_event(sd_dhcp_server *server, sd_event *event, int priority) {
74 assert_return(server, -EINVAL);
75 assert_return(!server->event, -EBUSY);
78 server->event = sd_event_ref(event);
80 r = sd_event_default(&server->event);
85 server->event_priority = priority;
90 int sd_dhcp_server_detach_event(sd_dhcp_server *server) {
91 assert_return(server, -EINVAL);
93 server->event = sd_event_unref(server->event);
98 sd_event *sd_dhcp_server_get_event(sd_dhcp_server *server) {
99 assert_return(server, NULL);
101 return server->event;
104 int sd_dhcp_server_stop(sd_dhcp_server *server) {
105 assert_return(server, -EINVAL);
107 server->receive_message =
108 sd_event_source_unref(server->receive_message);
110 server->fd_raw = safe_close(server->fd_raw);
111 server->fd = safe_close(server->fd);
113 log_dhcp_server(server, "STOPPED");
118 static int parse_request(uint8_t code, uint8_t len, const uint8_t *option,
120 DHCPRequest *req = user_data;
125 case DHCP_OPTION_SERVER_IDENTIFIER:
127 req->server_id = *(be32_t*)option;
130 case DHCP_OPTION_CLIENT_IDENTIFIER:
134 data = memdup(option, len);
138 free(req->client_id.data);
139 req->client_id.data = data;
140 req->client_id.length = len;
144 case DHCP_OPTION_MAXIMUM_MESSAGE_SIZE:
146 req->max_optlen = be16toh(*(be16_t*)option) -
147 - sizeof(DHCPPacket);
155 static void dhcp_request_free(DHCPRequest *req) {
159 free(req->client_id.data);
163 DEFINE_TRIVIAL_CLEANUP_FUNC(DHCPRequest*, dhcp_request_free);
164 #define _cleanup_dhcp_request_free_ _cleanup_(dhcp_request_freep)
166 static int ensure_sane_request(DHCPRequest *req, DHCPMessage *message) {
170 req->message = message;
172 /* set client id based on mac address if client did not send an explicit one */
173 if (!req->client_id.data) {
176 data = new0(uint8_t, ETH_ALEN + 1);
180 req->client_id.length = ETH_ALEN + 1;
181 req->client_id.data = data;
182 req->client_id.data[0] = 0x01;
183 memcpy(&req->client_id.data[1], &message->chaddr, ETH_ALEN);
186 if (req->max_optlen < DHCP_MIN_OPTIONS_SIZE)
187 req->max_optlen = DHCP_MIN_OPTIONS_SIZE;
192 int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
194 _cleanup_dhcp_request_free_ DHCPRequest *req = NULL;
200 if (message->op != BOOTREQUEST ||
201 message->htype != ARPHRD_ETHER ||
202 message->hlen != ETHER_ADDR_LEN)
205 req = new0(DHCPRequest, 1);
209 type = dhcp_option_parse(message, length, parse_request, req);
213 r = ensure_sane_request(req, message);
215 /* this only fails on critical errors */
218 log_dhcp_server(server, "received message of type %d", type);
223 static int server_receive_message(sd_event_source *s, int fd,
224 uint32_t revents, void *userdata) {
225 _cleanup_free_ DHCPMessage *message = NULL;
226 uint8_t cmsgbuf[CMSG_LEN(sizeof(struct in_pktinfo))];
227 sd_dhcp_server *server = userdata;
228 struct iovec iov = {};
229 struct msghdr msg = {
232 .msg_control = cmsgbuf,
233 .msg_controllen = sizeof(cmsgbuf),
235 struct cmsghdr *cmsg;
236 int buflen = 0, len, r;
240 r = ioctl(fd, FIONREAD, &buflen);
246 message = malloc0(buflen);
250 iov.iov_base = message;
251 iov.iov_len = buflen;
253 len = recvmsg(fd, &msg, 0);
256 else if ((size_t)len < sizeof(DHCPMessage))
259 for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
260 if (cmsg->cmsg_level == IPPROTO_IP &&
261 cmsg->cmsg_type == IP_PKTINFO &&
262 cmsg->cmsg_len == CMSG_LEN(sizeof(struct in_pktinfo))) {
263 struct in_pktinfo *info = (struct in_pktinfo*)CMSG_DATA(cmsg);
265 /* TODO figure out if this can be done as a filter on the socket, like for IPv6 */
266 if (server->index != info->ipi_ifindex)
273 return dhcp_server_handle_message(server, message, (size_t)len);
276 int sd_dhcp_server_start(sd_dhcp_server *server) {
279 assert_return(server, -EINVAL);
280 assert_return(server->event, -EINVAL);
281 assert_return(!server->receive_message, -EBUSY);
282 assert_return(server->fd_raw == -1, -EBUSY);
283 assert_return(server->fd == -1, -EBUSY);
285 r = socket(AF_PACKET, SOCK_DGRAM | SOCK_NONBLOCK, 0);
288 sd_dhcp_server_stop(server);
293 r = dhcp_network_bind_udp_socket(INADDR_ANY, DHCP_PORT_SERVER);
295 sd_dhcp_server_stop(server);
300 r = sd_event_add_io(server->event, &server->receive_message,
302 server_receive_message, server);
304 sd_dhcp_server_stop(server);
308 r = sd_event_source_set_priority(server->receive_message,
309 server->event_priority);
311 sd_dhcp_server_stop(server);
315 log_dhcp_server(server, "STARTED");