]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/libsystemd-network/sd-dhcp-server.c
sd-dhcp-server: add support for setting the server address
[thirdparty/systemd.git] / src / libsystemd-network / sd-dhcp-server.c
CommitLineData
b44cd882
TG
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
ff734080 23#include <sys/ioctl.h>
be077570 24#include <netinet/if_ether.h>
ff734080 25
b44cd882
TG
26#include "sd-dhcp-server.h"
27#include "dhcp-server-internal.h"
ff734080 28#include "dhcp-internal.h"
b44cd882 29
20af7091
TG
30int 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);
35
36 server->address = address->s_addr;
37
38 return 0;
39}
40
b44cd882
TG
41sd_dhcp_server *sd_dhcp_server_ref(sd_dhcp_server *server) {
42 if (server)
43 assert_se(REFCNT_INC(server->n_ref) >= 2);
44
45 return server;
46}
47
48sd_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");
51
ff734080
TG
52 sd_dhcp_server_stop(server);
53
b44cd882
TG
54 sd_event_unref(server->event);
55 free(server);
56 }
57
58 return NULL;
59}
60
3a864fe4 61int sd_dhcp_server_new(sd_dhcp_server **ret, int ifindex) {
b44cd882
TG
62 _cleanup_dhcp_server_unref_ sd_dhcp_server *server = NULL;
63
64 assert_return(ret, -EINVAL);
3a864fe4 65 assert_return(ifindex > 0, -EINVAL);
b44cd882
TG
66
67 server = new0(sd_dhcp_server, 1);
68 if (!server)
69 return -ENOMEM;
70
71 server->n_ref = REFCNT_INIT;
8de4a226 72 server->fd_raw = -1;
ff734080 73 server->fd = -1;
20af7091 74 server->address = htobe32(INADDR_ANY);
3a864fe4 75 server->index = ifindex;
b44cd882
TG
76
77 *ret = server;
78 server = NULL;
79
80 return 0;
81}
82
83int sd_dhcp_server_attach_event(sd_dhcp_server *server, sd_event *event, int priority) {
84 int r;
85
86 assert_return(server, -EINVAL);
87 assert_return(!server->event, -EBUSY);
88
89 if (event)
90 server->event = sd_event_ref(event);
91 else {
92 r = sd_event_default(&server->event);
93 if (r < 0)
94 return r;
95 }
96
97 server->event_priority = priority;
98
99 return 0;
100}
101
102int sd_dhcp_server_detach_event(sd_dhcp_server *server) {
103 assert_return(server, -EINVAL);
104
105 server->event = sd_event_unref(server->event);
106
107 return 0;
108}
109
110sd_event *sd_dhcp_server_get_event(sd_dhcp_server *server) {
111 assert_return(server, NULL);
112
113 return server->event;
114}
ff734080
TG
115
116int sd_dhcp_server_stop(sd_dhcp_server *server) {
117 assert_return(server, -EINVAL);
118
119 server->receive_message =
120 sd_event_source_unref(server->receive_message);
121
8de4a226 122 server->fd_raw = safe_close(server->fd_raw);
ff734080
TG
123 server->fd = safe_close(server->fd);
124
125 log_dhcp_server(server, "STOPPED");
126
127 return 0;
128}
129
816e2e7a
TG
130static int parse_request(uint8_t code, uint8_t len, const uint8_t *option,
131 void *user_data) {
132 DHCPRequest *req = user_data;
133
134 assert(req);
135
136 switch(code) {
137 case DHCP_OPTION_SERVER_IDENTIFIER:
138 if (len == 4)
139 req->server_id = *(be32_t*)option;
140
141 break;
142 case DHCP_OPTION_CLIENT_IDENTIFIER:
143 if (len >= 2) {
144 uint8_t *data;
145
146 data = memdup(option, len);
147 if (!data)
148 return -ENOMEM;
149
150 free(req->client_id.data);
151 req->client_id.data = data;
152 req->client_id.length = len;
153 }
154
155 break;
156 case DHCP_OPTION_MAXIMUM_MESSAGE_SIZE:
157 if (len == 2)
158 req->max_optlen = be16toh(*(be16_t*)option) -
159 - sizeof(DHCPPacket);
160
161 break;
162 }
163
164 return 0;
165}
166
167static void dhcp_request_free(DHCPRequest *req) {
168 if (!req)
169 return;
170
171 free(req->client_id.data);
172 free(req);
173}
174
175DEFINE_TRIVIAL_CLEANUP_FUNC(DHCPRequest*, dhcp_request_free);
176#define _cleanup_dhcp_request_free_ _cleanup_(dhcp_request_freep)
177
178static int ensure_sane_request(DHCPRequest *req, DHCPMessage *message) {
179 assert(req);
180 assert(message);
181
182 req->message = message;
183
184 /* set client id based on mac address if client did not send an explicit one */
185 if (!req->client_id.data) {
186 uint8_t *data;
187
188 data = new0(uint8_t, ETH_ALEN + 1);
189 if (!data)
190 return -ENOMEM;
191
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);
196 }
197
198 if (req->max_optlen < DHCP_MIN_OPTIONS_SIZE)
199 req->max_optlen = DHCP_MIN_OPTIONS_SIZE;
200
201 return 0;
202}
203
be077570
TG
204int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
205 size_t length) {
816e2e7a
TG
206 _cleanup_dhcp_request_free_ DHCPRequest *req = NULL;
207 int type, r;
be077570
TG
208
209 assert(server);
210 assert(message);
211
212 if (message->op != BOOTREQUEST ||
213 message->htype != ARPHRD_ETHER ||
214 message->hlen != ETHER_ADDR_LEN)
215 return 0;
216
816e2e7a
TG
217 req = new0(DHCPRequest, 1);
218 if (!req)
219 return -ENOMEM;
220
221 type = dhcp_option_parse(message, length, parse_request, req);
be077570
TG
222 if (type < 0)
223 return 0;
224
816e2e7a
TG
225 r = ensure_sane_request(req, message);
226 if (r < 0)
227 /* this only fails on critical errors */
228 return r;
229
be077570
TG
230 log_dhcp_server(server, "received message of type %d", type);
231
232 return 1;
233}
234
ff734080
TG
235static int server_receive_message(sd_event_source *s, int fd,
236 uint32_t revents, void *userdata) {
be077570 237 _cleanup_free_ DHCPMessage *message = NULL;
3a864fe4 238 uint8_t cmsgbuf[CMSG_LEN(sizeof(struct in_pktinfo))];
ff734080
TG
239 sd_dhcp_server *server = userdata;
240 struct iovec iov = {};
241 struct msghdr msg = {
242 .msg_iov = &iov,
243 .msg_iovlen = 1,
3a864fe4
TG
244 .msg_control = cmsgbuf,
245 .msg_controllen = sizeof(cmsgbuf),
ff734080 246 };
3a864fe4 247 struct cmsghdr *cmsg;
ff734080
TG
248 int buflen = 0, len, r;
249
250 assert(server);
251
252 r = ioctl(fd, FIONREAD, &buflen);
253 if (r < 0)
254 return r;
255 if (buflen < 0)
256 return -EIO;
257
258 message = malloc0(buflen);
259 if (!message)
260 return -ENOMEM;
261
262 iov.iov_base = message;
263 iov.iov_len = buflen;
264
265 len = recvmsg(fd, &msg, 0);
266 if (len < buflen)
267 return 0;
be077570
TG
268 else if ((size_t)len < sizeof(DHCPMessage))
269 return 0;
ff734080 270
3a864fe4
TG
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);
276
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)
279 return 0;
280
281 break;
282 }
283 }
284
be077570 285 return dhcp_server_handle_message(server, message, (size_t)len);
ff734080
TG
286}
287
288int sd_dhcp_server_start(sd_dhcp_server *server) {
289 int r;
290
291 assert_return(server, -EINVAL);
292 assert_return(server->event, -EINVAL);
293 assert_return(!server->receive_message, -EBUSY);
8de4a226 294 assert_return(server->fd_raw == -1, -EBUSY);
ff734080 295 assert_return(server->fd == -1, -EBUSY);
20af7091 296 assert_return(server->address != htobe32(INADDR_ANY), -EUNATCH);
ff734080 297
8de4a226
TG
298 r = socket(AF_PACKET, SOCK_DGRAM | SOCK_NONBLOCK, 0);
299 if (r < 0) {
300 r = -errno;
301 sd_dhcp_server_stop(server);
302 return r;
303 }
304 server->fd_raw = r;
305
ff734080
TG
306 r = dhcp_network_bind_udp_socket(INADDR_ANY, DHCP_PORT_SERVER);
307 if (r < 0) {
308 sd_dhcp_server_stop(server);
309 return r;
310 }
311 server->fd = r;
312
313 r = sd_event_add_io(server->event, &server->receive_message,
314 server->fd, EPOLLIN,
315 server_receive_message, server);
316 if (r < 0) {
317 sd_dhcp_server_stop(server);
318 return r;
319 }
320
321 r = sd_event_source_set_priority(server->receive_message,
322 server->event_priority);
323 if (r < 0) {
324 sd_dhcp_server_stop(server);
325 return r;
326 }
327
328 log_dhcp_server(server, "STARTED");
329
330 return 0;
331}