chiark / gitweb /
sd-dhcp-server: add support for setting the server address
[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 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
167 static void dhcp_request_free(DHCPRequest *req) {
168         if (!req)
169                 return;
170
171         free(req->client_id.data);
172         free(req);
173 }
174
175 DEFINE_TRIVIAL_CLEANUP_FUNC(DHCPRequest*, dhcp_request_free);
176 #define _cleanup_dhcp_request_free_ _cleanup_(dhcp_request_freep)
177
178 static 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
204 int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
205                                size_t length) {
206         _cleanup_dhcp_request_free_ DHCPRequest *req = NULL;
207         int type, r;
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
217         req = new0(DHCPRequest, 1);
218         if (!req)
219                 return -ENOMEM;
220
221         type = dhcp_option_parse(message, length, parse_request, req);
222         if (type < 0)
223                 return 0;
224
225         r = ensure_sane_request(req, message);
226         if (r < 0)
227                 /* this only fails on critical errors */
228                 return r;
229
230         log_dhcp_server(server, "received message of type %d", type);
231
232         return 1;
233 }
234
235 static int server_receive_message(sd_event_source *s, int fd,
236                                   uint32_t revents, void *userdata) {
237         _cleanup_free_ DHCPMessage *message = NULL;
238         uint8_t cmsgbuf[CMSG_LEN(sizeof(struct in_pktinfo))];
239         sd_dhcp_server *server = userdata;
240         struct iovec iov = {};
241         struct msghdr msg = {
242                 .msg_iov = &iov,
243                 .msg_iovlen = 1,
244                 .msg_control = cmsgbuf,
245                 .msg_controllen = sizeof(cmsgbuf),
246         };
247         struct cmsghdr *cmsg;
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;
268         else if ((size_t)len < sizeof(DHCPMessage))
269                 return 0;
270
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
285         return dhcp_server_handle_message(server, message, (size_t)len);
286 }
287
288 int 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);
294         assert_return(server->fd_raw == -1, -EBUSY);
295         assert_return(server->fd == -1, -EBUSY);
296         assert_return(server->address != htobe32(INADDR_ANY), -EUNATCH);
297
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
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 }