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