]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/libsystemd-network/sd-dhcp-server.c
sd-dhcp-server: add basic DISCOVER/OFFER support
[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
4dc35568
TG
30#define DHCP_DEFAULT_LEASE_TIME 60
31
20af7091
TG
32int 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);
37
38 server->address = address->s_addr;
39
40 return 0;
41}
42
b44cd882
TG
43sd_dhcp_server *sd_dhcp_server_ref(sd_dhcp_server *server) {
44 if (server)
45 assert_se(REFCNT_INC(server->n_ref) >= 2);
46
47 return server;
48}
49
50sd_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");
53
ff734080
TG
54 sd_dhcp_server_stop(server);
55
b44cd882
TG
56 sd_event_unref(server->event);
57 free(server);
58 }
59
60 return NULL;
61}
62
3a864fe4 63int sd_dhcp_server_new(sd_dhcp_server **ret, int ifindex) {
b44cd882
TG
64 _cleanup_dhcp_server_unref_ sd_dhcp_server *server = NULL;
65
66 assert_return(ret, -EINVAL);
3a864fe4 67 assert_return(ifindex > 0, -EINVAL);
b44cd882
TG
68
69 server = new0(sd_dhcp_server, 1);
70 if (!server)
71 return -ENOMEM;
72
73 server->n_ref = REFCNT_INIT;
8de4a226 74 server->fd_raw = -1;
ff734080 75 server->fd = -1;
20af7091 76 server->address = htobe32(INADDR_ANY);
3a864fe4 77 server->index = ifindex;
b44cd882
TG
78
79 *ret = server;
80 server = NULL;
81
82 return 0;
83}
84
85int sd_dhcp_server_attach_event(sd_dhcp_server *server, sd_event *event, int priority) {
86 int r;
87
88 assert_return(server, -EINVAL);
89 assert_return(!server->event, -EBUSY);
90
91 if (event)
92 server->event = sd_event_ref(event);
93 else {
94 r = sd_event_default(&server->event);
95 if (r < 0)
96 return r;
97 }
98
99 server->event_priority = priority;
100
101 return 0;
102}
103
104int sd_dhcp_server_detach_event(sd_dhcp_server *server) {
105 assert_return(server, -EINVAL);
106
107 server->event = sd_event_unref(server->event);
108
109 return 0;
110}
111
112sd_event *sd_dhcp_server_get_event(sd_dhcp_server *server) {
113 assert_return(server, NULL);
114
115 return server->event;
116}
ff734080
TG
117
118int sd_dhcp_server_stop(sd_dhcp_server *server) {
119 assert_return(server, -EINVAL);
120
121 server->receive_message =
122 sd_event_source_unref(server->receive_message);
123
8de4a226 124 server->fd_raw = safe_close(server->fd_raw);
ff734080
TG
125 server->fd = safe_close(server->fd);
126
127 log_dhcp_server(server, "STOPPED");
128
129 return 0;
130}
131
969b009d
TG
132static int dhcp_server_send_unicast_raw(sd_dhcp_server *server, DHCPPacket *packet,
133 size_t len) {
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,
139 };
140 int r;
141
142 assert(server);
143 assert(server->index > 0);
144 assert(server->address);
145 assert(packet);
146 assert(len > sizeof(DHCPPacket));
147
148 memcpy(&link.ll.sll_addr, &packet->dhcp.chaddr, ETH_ALEN);
149
150 dhcp_packet_append_ip_headers(packet, server->address, DHCP_PORT_SERVER,
151 packet->dhcp.yiaddr, DHCP_PORT_CLIENT, len);
152
153 r = dhcp_network_send_raw_socket(server->fd_raw, &link, packet, len);
154 if (r < 0)
155 return r;
156
157 return 0;
158}
159
160static 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,
166 };
167 struct iovec iov = {
168 .iov_base = message,
169 .iov_len = len,
170 };
171 uint8_t cmsgbuf[CMSG_LEN(sizeof(struct in_pktinfo))] = {};
172 struct msghdr msg = {
173 .msg_name = &dest,
174 .msg_namelen = sizeof(dest.in),
175 .msg_iov = &iov,
176 .msg_iovlen = 1,
177 .msg_control = cmsgbuf,
178 .msg_controllen = sizeof(cmsgbuf),
179 };
180 struct cmsghdr *cmsg;
181 struct in_pktinfo *pktinfo;
182 int r;
183
184 assert(server);
185 assert(server->fd > 0);
186 assert(message);
187 assert(len > sizeof(DHCPMessage));
188
189 cmsg = CMSG_FIRSTHDR(&msg);
190 assert(cmsg);
191
192 cmsg->cmsg_level = IPPROTO_IP;
193 cmsg->cmsg_type = IP_PKTINFO;
194 cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
195
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
199 */
200 pktinfo = (struct in_pktinfo*) CMSG_DATA(cmsg);
201 assert(pktinfo);
202
203 pktinfo->ipi_ifindex = server->index;
204 pktinfo->ipi_spec_dst.s_addr = server->address;
205
206 r = sendmsg(server->fd, &msg, 0);
207 if (r < 0)
208 return -errno;
209
210 return 0;
211}
212
213static bool requested_broadcast(DHCPRequest *req) {
214 assert(req);
215
216 return req->message->flags & htobe16(0x8000);
217}
218
219int 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;
223 int r;
224
225 assert(server);
226 assert(req);
227 assert(req->max_optlen);
228 assert(optoffset <= req->max_optlen);
229 assert(packet);
230
231 r = dhcp_option_append(&packet->dhcp, req->max_optlen, &optoffset, 0,
232 DHCP_OPTION_SERVER_IDENTIFIER,
233 4, &server->address);
234 if (r < 0)
235 return r;
236
237 r = dhcp_option_append(&packet->dhcp, req->max_optlen, &optoffset, 0,
238 DHCP_OPTION_END, 0, NULL);
239 if (r < 0)
240 return r;
241
242 /* RFC 2131 Section 4.1
243
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.
256
257 Section 4.3.2
258
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.
264 */
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;
271
272 if (destination || requested_broadcast(req) || type == DHCP_NAK)
273 return dhcp_server_send_udp(server, destination, &packet->dhcp,
274 sizeof(DHCPMessage) + optoffset);
275 else
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);
280}
281
4dc35568
TG
282static 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;
285 size_t optoffset;
286 int r;
287
288 assert(server);
289 assert(ret);
290 assert(_optoffset);
291 assert(type == DHCP_OFFER);
292
293 packet = malloc0(sizeof(DHCPPacket) + req->max_optlen);
294 if (!packet)
295 return -ENOMEM;
296
297 r = dhcp_message_init(&packet->dhcp, BOOTREPLY, be32toh(req->message->xid),
298 type, req->max_optlen, &optoffset);
299 if (r < 0)
300 return r;
301
302 packet->dhcp.flags = req->message->flags;
303 packet->dhcp.giaddr = req->message->giaddr;
304 memcpy(&packet->dhcp.chaddr, &req->message->chaddr, ETH_ALEN);
305
306 *_optoffset = optoffset;
307 *ret = packet;
308 packet = NULL;
309
310 return 0;
311}
312
313static int server_send_offer(sd_dhcp_server *server, DHCPRequest *req) {
314 _cleanup_free_ DHCPPacket *packet = NULL;
315 size_t offset;
316 be32_t lease_time;
317 int r;
318
319 r = server_message_init(server, &packet, DHCP_OFFER, &offset, req);
320 if (r < 0)
321 return r;
322
323 /* for now offer a random IP */
324 packet->dhcp.yiaddr = random_u32();
325
326 /* for one minute */
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);
330 if (r < 0)
331 return r;
332
333 r = dhcp_server_send_packet(server, req, packet, DHCP_OFFER, offset);
334 if (r < 0)
335 return r;
336
337 return 0;
338}
339
816e2e7a
TG
340static int parse_request(uint8_t code, uint8_t len, const uint8_t *option,
341 void *user_data) {
342 DHCPRequest *req = user_data;
343
344 assert(req);
345
346 switch(code) {
347 case DHCP_OPTION_SERVER_IDENTIFIER:
348 if (len == 4)
349 req->server_id = *(be32_t*)option;
350
351 break;
352 case DHCP_OPTION_CLIENT_IDENTIFIER:
353 if (len >= 2) {
354 uint8_t *data;
355
356 data = memdup(option, len);
357 if (!data)
358 return -ENOMEM;
359
360 free(req->client_id.data);
361 req->client_id.data = data;
362 req->client_id.length = len;
363 }
364
365 break;
366 case DHCP_OPTION_MAXIMUM_MESSAGE_SIZE:
367 if (len == 2)
368 req->max_optlen = be16toh(*(be16_t*)option) -
369 - sizeof(DHCPPacket);
370
371 break;
372 }
373
374 return 0;
375}
376
377static void dhcp_request_free(DHCPRequest *req) {
378 if (!req)
379 return;
380
381 free(req->client_id.data);
382 free(req);
383}
384
385DEFINE_TRIVIAL_CLEANUP_FUNC(DHCPRequest*, dhcp_request_free);
386#define _cleanup_dhcp_request_free_ _cleanup_(dhcp_request_freep)
387
388static int ensure_sane_request(DHCPRequest *req, DHCPMessage *message) {
389 assert(req);
390 assert(message);
391
392 req->message = message;
393
394 /* set client id based on mac address if client did not send an explicit one */
395 if (!req->client_id.data) {
396 uint8_t *data;
397
398 data = new0(uint8_t, ETH_ALEN + 1);
399 if (!data)
400 return -ENOMEM;
401
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);
406 }
407
408 if (req->max_optlen < DHCP_MIN_OPTIONS_SIZE)
409 req->max_optlen = DHCP_MIN_OPTIONS_SIZE;
410
411 return 0;
412}
413
be077570
TG
414int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
415 size_t length) {
816e2e7a
TG
416 _cleanup_dhcp_request_free_ DHCPRequest *req = NULL;
417 int type, r;
be077570
TG
418
419 assert(server);
420 assert(message);
421
422 if (message->op != BOOTREQUEST ||
423 message->htype != ARPHRD_ETHER ||
424 message->hlen != ETHER_ADDR_LEN)
425 return 0;
426
816e2e7a
TG
427 req = new0(DHCPRequest, 1);
428 if (!req)
429 return -ENOMEM;
430
431 type = dhcp_option_parse(message, length, parse_request, req);
be077570
TG
432 if (type < 0)
433 return 0;
434
816e2e7a
TG
435 r = ensure_sane_request(req, message);
436 if (r < 0)
437 /* this only fails on critical errors */
438 return r;
439
4dc35568
TG
440 switch(type) {
441 case DHCP_DISCOVER:
442 log_dhcp_server(server, "DISCOVER (0x%x)",
443 be32toh(req->message->xid));
444
445 r = server_send_offer(server, req);
446 if (r < 0) {
447 /* this only fails on critical errors */
448 log_dhcp_server(server, "could not send offer: %s",
449 strerror(-r));
450 return r;
451 } else {
452 log_dhcp_server(server, "OFFER (0x%x)",
453 be32toh(req->message->xid));
454 return DHCP_OFFER;
455 }
be077570 456
4dc35568
TG
457 break;
458 }
459
460 return 0;
be077570
TG
461}
462
ff734080
TG
463static int server_receive_message(sd_event_source *s, int fd,
464 uint32_t revents, void *userdata) {
be077570 465 _cleanup_free_ DHCPMessage *message = NULL;
3a864fe4 466 uint8_t cmsgbuf[CMSG_LEN(sizeof(struct in_pktinfo))];
ff734080
TG
467 sd_dhcp_server *server = userdata;
468 struct iovec iov = {};
469 struct msghdr msg = {
470 .msg_iov = &iov,
471 .msg_iovlen = 1,
3a864fe4
TG
472 .msg_control = cmsgbuf,
473 .msg_controllen = sizeof(cmsgbuf),
ff734080 474 };
3a864fe4 475 struct cmsghdr *cmsg;
ff734080
TG
476 int buflen = 0, len, r;
477
478 assert(server);
479
480 r = ioctl(fd, FIONREAD, &buflen);
481 if (r < 0)
482 return r;
483 if (buflen < 0)
484 return -EIO;
485
486 message = malloc0(buflen);
487 if (!message)
488 return -ENOMEM;
489
490 iov.iov_base = message;
491 iov.iov_len = buflen;
492
493 len = recvmsg(fd, &msg, 0);
494 if (len < buflen)
495 return 0;
be077570
TG
496 else if ((size_t)len < sizeof(DHCPMessage))
497 return 0;
ff734080 498
3a864fe4
TG
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);
504
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)
507 return 0;
508
509 break;
510 }
511 }
512
be077570 513 return dhcp_server_handle_message(server, message, (size_t)len);
ff734080
TG
514}
515
516int sd_dhcp_server_start(sd_dhcp_server *server) {
517 int r;
518
519 assert_return(server, -EINVAL);
520 assert_return(server->event, -EINVAL);
521 assert_return(!server->receive_message, -EBUSY);
8de4a226 522 assert_return(server->fd_raw == -1, -EBUSY);
ff734080 523 assert_return(server->fd == -1, -EBUSY);
20af7091 524 assert_return(server->address != htobe32(INADDR_ANY), -EUNATCH);
ff734080 525
8de4a226
TG
526 r = socket(AF_PACKET, SOCK_DGRAM | SOCK_NONBLOCK, 0);
527 if (r < 0) {
528 r = -errno;
529 sd_dhcp_server_stop(server);
530 return r;
531 }
532 server->fd_raw = r;
533
ff734080
TG
534 r = dhcp_network_bind_udp_socket(INADDR_ANY, DHCP_PORT_SERVER);
535 if (r < 0) {
536 sd_dhcp_server_stop(server);
537 return r;
538 }
539 server->fd = r;
540
541 r = sd_event_add_io(server->event, &server->receive_message,
542 server->fd, EPOLLIN,
543 server_receive_message, server);
544 if (r < 0) {
545 sd_dhcp_server_stop(server);
546 return r;
547 }
548
549 r = sd_event_source_set_priority(server->receive_message,
550 server->event_priority);
551 if (r < 0) {
552 sd_dhcp_server_stop(server);
553 return r;
554 }
555
556 log_dhcp_server(server, "STARTED");
557
558 return 0;
559}