chiark / gitweb /
sd-dhcp-server: add support for sending messages
[elogind.git] / src / libsystemd-network / sd-dhcp-server.c
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
23 #include <sys/ioctl.h>
24 #include <netinet/if_ether.h>
25
26 #include "sd-dhcp-server.h"
27 #include "dhcp-server-internal.h"
28 #include "dhcp-internal.h"
29
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);
35
36         server->address = address->s_addr;
37
38         return 0;
39 }
40
41 sd_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
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");
51
52                 sd_dhcp_server_stop(server);
53
54                 sd_event_unref(server->event);
55                 free(server);
56         }
57
58         return NULL;
59 }
60
61 int sd_dhcp_server_new(sd_dhcp_server **ret, int ifindex) {
62         _cleanup_dhcp_server_unref_ sd_dhcp_server *server = NULL;
63
64         assert_return(ret, -EINVAL);
65         assert_return(ifindex > 0, -EINVAL);
66
67         server = new0(sd_dhcp_server, 1);
68         if (!server)
69                 return -ENOMEM;
70
71         server->n_ref = REFCNT_INIT;
72         server->fd_raw = -1;
73         server->fd = -1;
74         server->address = htobe32(INADDR_ANY);
75         server->index = ifindex;
76
77         *ret = server;
78         server = NULL;
79
80         return 0;
81 }
82
83 int 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
102 int 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
110 sd_event *sd_dhcp_server_get_event(sd_dhcp_server *server) {
111         assert_return(server, NULL);
112
113         return server->event;
114 }
115
116 int 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
122         server->fd_raw = safe_close(server->fd_raw);
123         server->fd = safe_close(server->fd);
124
125         log_dhcp_server(server, "STOPPED");
126
127         return 0;
128 }
129
130 static int dhcp_server_send_unicast_raw(sd_dhcp_server *server, DHCPPacket *packet,
131                                         size_t len) {
132         union sockaddr_union link = {
133                 .ll.sll_family = AF_PACKET,
134                 .ll.sll_protocol = htons(ETH_P_IP),
135                 .ll.sll_ifindex = server->index,
136                 .ll.sll_halen = ETH_ALEN,
137         };
138         int r;
139
140         assert(server);
141         assert(server->index > 0);
142         assert(server->address);
143         assert(packet);
144         assert(len > sizeof(DHCPPacket));
145
146         memcpy(&link.ll.sll_addr, &packet->dhcp.chaddr, ETH_ALEN);
147
148         dhcp_packet_append_ip_headers(packet, server->address, DHCP_PORT_SERVER,
149                                       packet->dhcp.yiaddr, DHCP_PORT_CLIENT, len);
150
151         r = dhcp_network_send_raw_socket(server->fd_raw, &link, packet, len);
152         if (r < 0)
153                 return r;
154
155         return 0;
156 }
157
158 static int dhcp_server_send_udp(sd_dhcp_server *server, be32_t destination,
159                                 DHCPMessage *message, size_t len) {
160         union sockaddr_union dest = {
161                 .in.sin_family = AF_INET,
162                 .in.sin_port = htobe16(DHCP_PORT_CLIENT),
163                 .in.sin_addr.s_addr = destination,
164         };
165         struct iovec iov = {
166                 .iov_base = message,
167                 .iov_len = len,
168         };
169         uint8_t cmsgbuf[CMSG_LEN(sizeof(struct in_pktinfo))] = {};
170         struct msghdr msg = {
171                 .msg_name = &dest,
172                 .msg_namelen = sizeof(dest.in),
173                 .msg_iov = &iov,
174                 .msg_iovlen = 1,
175                 .msg_control = cmsgbuf,
176                 .msg_controllen = sizeof(cmsgbuf),
177         };
178         struct cmsghdr *cmsg;
179         struct in_pktinfo *pktinfo;
180         int r;
181
182         assert(server);
183         assert(server->fd > 0);
184         assert(message);
185         assert(len > sizeof(DHCPMessage));
186
187         cmsg = CMSG_FIRSTHDR(&msg);
188         assert(cmsg);
189
190         cmsg->cmsg_level = IPPROTO_IP;
191         cmsg->cmsg_type = IP_PKTINFO;
192         cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
193
194         /* we attach source interface and address info to the message
195            rather than binding the socket. This will be mostly useful
196            when we gain support for arbitrary number of server addresses
197          */
198         pktinfo = (struct in_pktinfo*) CMSG_DATA(cmsg);
199         assert(pktinfo);
200
201         pktinfo->ipi_ifindex = server->index;
202         pktinfo->ipi_spec_dst.s_addr = server->address;
203
204         r = sendmsg(server->fd, &msg, 0);
205         if (r < 0)
206                 return -errno;
207
208         return 0;
209 }
210
211 static bool requested_broadcast(DHCPRequest *req) {
212         assert(req);
213
214         return req->message->flags & htobe16(0x8000);
215 }
216
217 int dhcp_server_send_packet(sd_dhcp_server *server,
218                             DHCPRequest *req, DHCPPacket *packet,
219                             int type, size_t optoffset) {
220         be32_t destination = INADDR_ANY;
221         int r;
222
223         assert(server);
224         assert(req);
225         assert(req->max_optlen);
226         assert(optoffset <= req->max_optlen);
227         assert(packet);
228
229         r = dhcp_option_append(&packet->dhcp, req->max_optlen, &optoffset, 0,
230                                DHCP_OPTION_SERVER_IDENTIFIER,
231                                4, &server->address);
232         if (r < 0)
233                 return r;
234
235         r = dhcp_option_append(&packet->dhcp, req->max_optlen, &optoffset, 0,
236                                DHCP_OPTION_END, 0, NULL);
237         if (r < 0)
238                 return r;
239
240         /* RFC 2131 Section 4.1
241
242            If the â€™giaddr’ field in a DHCP message from a client is non-zero,
243            the server sends any return messages to the â€™DHCP server’ port on the
244            BOOTP relay agent whose address appears in â€™giaddr’. If the â€™giaddr’
245            field is zero and the â€™ciaddr’ field is nonzero, then the server
246            unicasts DHCPOFFER and DHCPACK messages to the address in â€™ciaddr’.
247            If â€™giaddr’ is zero and â€™ciaddr’ is zero, and the broadcast bit is
248            set, then the server broadcasts DHCPOFFER and DHCPACK messages to
249            0xffffffff. If the broadcast bit is not set and â€™giaddr’ is zero and
250            â€™ciaddr’ is zero, then the server unicasts DHCPOFFER and DHCPACK
251            messages to the client’s hardware address and â€™yiaddr’ address. In
252            all cases, when â€™giaddr’ is zero, the server broadcasts any DHCPNAK
253            messages to 0xffffffff.
254
255            Section 4.3.2
256
257            If â€™giaddr’ is set in the DHCPREQUEST message, the client is on a
258            different subnet. The server MUST set the broadcast bit in the
259            DHCPNAK, so that the relay agent will broadcast the DHCPNAK to the
260            client, because the client may not have a correct network address
261            or subnet mask, and the client may not be answering ARP requests.
262          */
263         if (req->message->giaddr) {
264                 destination = req->message->giaddr;
265                 if (type == DHCP_NAK)
266                         packet->dhcp.flags = htobe16(0x8000);
267         } else if (req->message->ciaddr && type != DHCP_NAK)
268                 destination = req->message->ciaddr;
269
270         if (destination || requested_broadcast(req) || type == DHCP_NAK)
271                 return dhcp_server_send_udp(server, destination, &packet->dhcp,
272                                             sizeof(DHCPMessage) + optoffset);
273         else
274                 /* we cannot send UDP packet to specific MAC address when the address is
275                    not yet configured, so must fall back to raw packets */
276                 return dhcp_server_send_unicast_raw(server, packet,
277                                                     sizeof(DHCPPacket) + optoffset);
278 }
279
280 static int parse_request(uint8_t code, uint8_t len, const uint8_t *option,
281                          void *user_data) {
282         DHCPRequest *req = user_data;
283
284         assert(req);
285
286         switch(code) {
287         case DHCP_OPTION_SERVER_IDENTIFIER:
288                 if (len == 4)
289                         req->server_id = *(be32_t*)option;
290
291                 break;
292         case DHCP_OPTION_CLIENT_IDENTIFIER:
293                 if (len >= 2) {
294                         uint8_t *data;
295
296                         data = memdup(option, len);
297                         if (!data)
298                                 return -ENOMEM;
299
300                         free(req->client_id.data);
301                         req->client_id.data = data;
302                         req->client_id.length = len;
303                 }
304
305                 break;
306         case DHCP_OPTION_MAXIMUM_MESSAGE_SIZE:
307                 if (len == 2)
308                         req->max_optlen = be16toh(*(be16_t*)option) -
309                                           - sizeof(DHCPPacket);
310
311                 break;
312         }
313
314         return 0;
315 }
316
317 static void dhcp_request_free(DHCPRequest *req) {
318         if (!req)
319                 return;
320
321         free(req->client_id.data);
322         free(req);
323 }
324
325 DEFINE_TRIVIAL_CLEANUP_FUNC(DHCPRequest*, dhcp_request_free);
326 #define _cleanup_dhcp_request_free_ _cleanup_(dhcp_request_freep)
327
328 static int ensure_sane_request(DHCPRequest *req, DHCPMessage *message) {
329         assert(req);
330         assert(message);
331
332         req->message = message;
333
334         /* set client id based on mac address if client did not send an explicit one */
335         if (!req->client_id.data) {
336                 uint8_t *data;
337
338                 data = new0(uint8_t, ETH_ALEN + 1);
339                 if (!data)
340                         return -ENOMEM;
341
342                 req->client_id.length = ETH_ALEN + 1;
343                 req->client_id.data = data;
344                 req->client_id.data[0] = 0x01;
345                 memcpy(&req->client_id.data[1], &message->chaddr, ETH_ALEN);
346         }
347
348         if (req->max_optlen < DHCP_MIN_OPTIONS_SIZE)
349                 req->max_optlen = DHCP_MIN_OPTIONS_SIZE;
350
351         return 0;
352 }
353
354 int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
355                                size_t length) {
356         _cleanup_dhcp_request_free_ DHCPRequest *req = NULL;
357         int type, r;
358
359         assert(server);
360         assert(message);
361
362         if (message->op != BOOTREQUEST ||
363             message->htype != ARPHRD_ETHER ||
364             message->hlen != ETHER_ADDR_LEN)
365                 return 0;
366
367         req = new0(DHCPRequest, 1);
368         if (!req)
369                 return -ENOMEM;
370
371         type = dhcp_option_parse(message, length, parse_request, req);
372         if (type < 0)
373                 return 0;
374
375         r = ensure_sane_request(req, message);
376         if (r < 0)
377                 /* this only fails on critical errors */
378                 return r;
379
380         log_dhcp_server(server, "received message of type %d", type);
381
382         return 1;
383 }
384
385 static int server_receive_message(sd_event_source *s, int fd,
386                                   uint32_t revents, void *userdata) {
387         _cleanup_free_ DHCPMessage *message = NULL;
388         uint8_t cmsgbuf[CMSG_LEN(sizeof(struct in_pktinfo))];
389         sd_dhcp_server *server = userdata;
390         struct iovec iov = {};
391         struct msghdr msg = {
392                 .msg_iov = &iov,
393                 .msg_iovlen = 1,
394                 .msg_control = cmsgbuf,
395                 .msg_controllen = sizeof(cmsgbuf),
396         };
397         struct cmsghdr *cmsg;
398         int buflen = 0, len, r;
399
400         assert(server);
401
402         r = ioctl(fd, FIONREAD, &buflen);
403         if (r < 0)
404                 return r;
405         if (buflen < 0)
406                 return -EIO;
407
408         message = malloc0(buflen);
409         if (!message)
410                 return -ENOMEM;
411
412         iov.iov_base = message;
413         iov.iov_len = buflen;
414
415         len = recvmsg(fd, &msg, 0);
416         if (len < buflen)
417                 return 0;
418         else if ((size_t)len < sizeof(DHCPMessage))
419                 return 0;
420
421         for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
422                 if (cmsg->cmsg_level == IPPROTO_IP &&
423                     cmsg->cmsg_type == IP_PKTINFO &&
424                     cmsg->cmsg_len == CMSG_LEN(sizeof(struct in_pktinfo))) {
425                         struct in_pktinfo *info = (struct in_pktinfo*)CMSG_DATA(cmsg);
426
427                         /* TODO figure out if this can be done as a filter on the socket, like for IPv6 */
428                         if (server->index != info->ipi_ifindex)
429                                 return 0;
430
431                         break;
432                 }
433         }
434
435         return dhcp_server_handle_message(server, message, (size_t)len);
436 }
437
438 int sd_dhcp_server_start(sd_dhcp_server *server) {
439         int r;
440
441         assert_return(server, -EINVAL);
442         assert_return(server->event, -EINVAL);
443         assert_return(!server->receive_message, -EBUSY);
444         assert_return(server->fd_raw == -1, -EBUSY);
445         assert_return(server->fd == -1, -EBUSY);
446         assert_return(server->address != htobe32(INADDR_ANY), -EUNATCH);
447
448         r = socket(AF_PACKET, SOCK_DGRAM | SOCK_NONBLOCK, 0);
449         if (r < 0) {
450                 r = -errno;
451                 sd_dhcp_server_stop(server);
452                 return r;
453         }
454         server->fd_raw = r;
455
456         r = dhcp_network_bind_udp_socket(INADDR_ANY, DHCP_PORT_SERVER);
457         if (r < 0) {
458                 sd_dhcp_server_stop(server);
459                 return r;
460         }
461         server->fd = r;
462
463         r = sd_event_add_io(server->event, &server->receive_message,
464                             server->fd, EPOLLIN,
465                             server_receive_message, server);
466         if (r < 0) {
467                 sd_dhcp_server_stop(server);
468                 return r;
469         }
470
471         r = sd_event_source_set_priority(server->receive_message,
472                                          server->event_priority);
473         if (r < 0) {
474                 sd_dhcp_server_stop(server);
475                 return r;
476         }
477
478         log_dhcp_server(server, "STARTED");
479
480         return 0;
481 }