chiark / gitweb /
b1f2fa063f66ef12f57ae7649d98807afb5ff0ad
[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 sd_dhcp_server *sd_dhcp_server_ref(sd_dhcp_server *server) {
31         if (server)
32                 assert_se(REFCNT_INC(server->n_ref) >= 2);
33
34         return server;
35 }
36
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");
40
41                 sd_dhcp_server_stop(server);
42
43                 sd_event_unref(server->event);
44                 free(server);
45         }
46
47         return NULL;
48 }
49
50 int sd_dhcp_server_new(sd_dhcp_server **ret, int ifindex) {
51         _cleanup_dhcp_server_unref_ sd_dhcp_server *server = NULL;
52
53         assert_return(ret, -EINVAL);
54         assert_return(ifindex > 0, -EINVAL);
55
56         server = new0(sd_dhcp_server, 1);
57         if (!server)
58                 return -ENOMEM;
59
60         server->n_ref = REFCNT_INIT;
61         server->fd = -1;
62         server->index = ifindex;
63
64         *ret = server;
65         server = NULL;
66
67         return 0;
68 }
69
70 int sd_dhcp_server_attach_event(sd_dhcp_server *server, sd_event *event, int priority) {
71         int r;
72
73         assert_return(server, -EINVAL);
74         assert_return(!server->event, -EBUSY);
75
76         if (event)
77                 server->event = sd_event_ref(event);
78         else {
79                 r = sd_event_default(&server->event);
80                 if (r < 0)
81                         return r;
82         }
83
84         server->event_priority = priority;
85
86         return 0;
87 }
88
89 int sd_dhcp_server_detach_event(sd_dhcp_server *server) {
90         assert_return(server, -EINVAL);
91
92         server->event = sd_event_unref(server->event);
93
94         return 0;
95 }
96
97 sd_event *sd_dhcp_server_get_event(sd_dhcp_server *server) {
98         assert_return(server, NULL);
99
100         return server->event;
101 }
102
103 int sd_dhcp_server_stop(sd_dhcp_server *server) {
104         assert_return(server, -EINVAL);
105
106         server->receive_message =
107                 sd_event_source_unref(server->receive_message);
108
109         server->fd = safe_close(server->fd);
110
111         log_dhcp_server(server, "STOPPED");
112
113         return 0;
114 }
115
116 static int parse_request(uint8_t code, uint8_t len, const uint8_t *option,
117                          void *user_data) {
118         DHCPRequest *req = user_data;
119
120         assert(req);
121
122         switch(code) {
123         case DHCP_OPTION_SERVER_IDENTIFIER:
124                 if (len == 4)
125                         req->server_id = *(be32_t*)option;
126
127                 break;
128         case DHCP_OPTION_CLIENT_IDENTIFIER:
129                 if (len >= 2) {
130                         uint8_t *data;
131
132                         data = memdup(option, len);
133                         if (!data)
134                                 return -ENOMEM;
135
136                         free(req->client_id.data);
137                         req->client_id.data = data;
138                         req->client_id.length = len;
139                 }
140
141                 break;
142         case DHCP_OPTION_MAXIMUM_MESSAGE_SIZE:
143                 if (len == 2)
144                         req->max_optlen = be16toh(*(be16_t*)option) -
145                                           - sizeof(DHCPPacket);
146
147                 break;
148         }
149
150         return 0;
151 }
152
153 static void dhcp_request_free(DHCPRequest *req) {
154         if (!req)
155                 return;
156
157         free(req->client_id.data);
158         free(req);
159 }
160
161 DEFINE_TRIVIAL_CLEANUP_FUNC(DHCPRequest*, dhcp_request_free);
162 #define _cleanup_dhcp_request_free_ _cleanup_(dhcp_request_freep)
163
164 static int ensure_sane_request(DHCPRequest *req, DHCPMessage *message) {
165         assert(req);
166         assert(message);
167
168         req->message = message;
169
170         /* set client id based on mac address if client did not send an explicit one */
171         if (!req->client_id.data) {
172                 uint8_t *data;
173
174                 data = new0(uint8_t, ETH_ALEN + 1);
175                 if (!data)
176                         return -ENOMEM;
177
178                 req->client_id.length = ETH_ALEN + 1;
179                 req->client_id.data = data;
180                 req->client_id.data[0] = 0x01;
181                 memcpy(&req->client_id.data[1], &message->chaddr, ETH_ALEN);
182         }
183
184         if (req->max_optlen < DHCP_MIN_OPTIONS_SIZE)
185                 req->max_optlen = DHCP_MIN_OPTIONS_SIZE;
186
187         return 0;
188 }
189
190 int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
191                                size_t length) {
192         _cleanup_dhcp_request_free_ DHCPRequest *req = NULL;
193         int type, r;
194
195         assert(server);
196         assert(message);
197
198         if (message->op != BOOTREQUEST ||
199             message->htype != ARPHRD_ETHER ||
200             message->hlen != ETHER_ADDR_LEN)
201                 return 0;
202
203         req = new0(DHCPRequest, 1);
204         if (!req)
205                 return -ENOMEM;
206
207         type = dhcp_option_parse(message, length, parse_request, req);
208         if (type < 0)
209                 return 0;
210
211         r = ensure_sane_request(req, message);
212         if (r < 0)
213                 /* this only fails on critical errors */
214                 return r;
215
216         log_dhcp_server(server, "received message of type %d", type);
217
218         return 1;
219 }
220
221 static int server_receive_message(sd_event_source *s, int fd,
222                                   uint32_t revents, void *userdata) {
223         _cleanup_free_ DHCPMessage *message = NULL;
224         uint8_t cmsgbuf[CMSG_LEN(sizeof(struct in_pktinfo))];
225         sd_dhcp_server *server = userdata;
226         struct iovec iov = {};
227         struct msghdr msg = {
228                 .msg_iov = &iov,
229                 .msg_iovlen = 1,
230                 .msg_control = cmsgbuf,
231                 .msg_controllen = sizeof(cmsgbuf),
232         };
233         struct cmsghdr *cmsg;
234         int buflen = 0, len, r;
235
236         assert(server);
237
238         r = ioctl(fd, FIONREAD, &buflen);
239         if (r < 0)
240                 return r;
241         if (buflen < 0)
242                 return -EIO;
243
244         message = malloc0(buflen);
245         if (!message)
246                 return -ENOMEM;
247
248         iov.iov_base = message;
249         iov.iov_len = buflen;
250
251         len = recvmsg(fd, &msg, 0);
252         if (len < buflen)
253                 return 0;
254         else if ((size_t)len < sizeof(DHCPMessage))
255                 return 0;
256
257         for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
258                 if (cmsg->cmsg_level == IPPROTO_IP &&
259                     cmsg->cmsg_type == IP_PKTINFO &&
260                     cmsg->cmsg_len == CMSG_LEN(sizeof(struct in_pktinfo))) {
261                         struct in_pktinfo *info = (struct in_pktinfo*)CMSG_DATA(cmsg);
262
263                         /* TODO figure out if this can be done as a filter on the socket, like for IPv6 */
264                         if (server->index != info->ipi_ifindex)
265                                 return 0;
266
267                         break;
268                 }
269         }
270
271         return dhcp_server_handle_message(server, message, (size_t)len);
272 }
273
274 int sd_dhcp_server_start(sd_dhcp_server *server) {
275         int r;
276
277         assert_return(server, -EINVAL);
278         assert_return(server->event, -EINVAL);
279         assert_return(!server->receive_message, -EBUSY);
280         assert_return(server->fd == -1, -EBUSY);
281
282         r = dhcp_network_bind_udp_socket(INADDR_ANY, DHCP_PORT_SERVER);
283         if (r < 0) {
284                 sd_dhcp_server_stop(server);
285                 return r;
286         }
287         server->fd = r;
288
289         r = sd_event_add_io(server->event, &server->receive_message,
290                             server->fd, EPOLLIN,
291                             server_receive_message, server);
292         if (r < 0) {
293                 sd_dhcp_server_stop(server);
294                 return r;
295         }
296
297         r = sd_event_source_set_priority(server->receive_message,
298                                          server->event_priority);
299         if (r < 0) {
300                 sd_dhcp_server_stop(server);
301                 return r;
302         }
303
304         log_dhcp_server(server, "STARTED");
305
306         return 0;
307 }