]>
Commit | Line | Data |
---|---|---|
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 | |
20af7091 TG |
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 | ||
b44cd882 TG |
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 | ||
ff734080 TG |
52 | sd_dhcp_server_stop(server); |
53 | ||
b44cd882 TG |
54 | sd_event_unref(server->event); |
55 | free(server); | |
56 | } | |
57 | ||
58 | return NULL; | |
59 | } | |
60 | ||
3a864fe4 | 61 | int sd_dhcp_server_new(sd_dhcp_server **ret, int ifindex) { |
b44cd882 TG |
62 | _cleanup_dhcp_server_unref_ sd_dhcp_server *server = NULL; |
63 | ||
64 | assert_return(ret, -EINVAL); | |
3a864fe4 | 65 | assert_return(ifindex > 0, -EINVAL); |
b44cd882 TG |
66 | |
67 | server = new0(sd_dhcp_server, 1); | |
68 | if (!server) | |
69 | return -ENOMEM; | |
70 | ||
71 | server->n_ref = REFCNT_INIT; | |
8de4a226 | 72 | server->fd_raw = -1; |
ff734080 | 73 | server->fd = -1; |
20af7091 | 74 | server->address = htobe32(INADDR_ANY); |
3a864fe4 | 75 | server->index = ifindex; |
b44cd882 TG |
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 | } | |
ff734080 TG |
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 | ||
8de4a226 | 122 | server->fd_raw = safe_close(server->fd_raw); |
ff734080 TG |
123 | server->fd = safe_close(server->fd); |
124 | ||
125 | log_dhcp_server(server, "STOPPED"); | |
126 | ||
127 | return 0; | |
128 | } | |
129 | ||
816e2e7a TG |
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 | ||
be077570 TG |
204 | int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, |
205 | size_t length) { | |
816e2e7a TG |
206 | _cleanup_dhcp_request_free_ DHCPRequest *req = NULL; |
207 | int type, r; | |
be077570 TG |
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 | ||
816e2e7a TG |
217 | req = new0(DHCPRequest, 1); |
218 | if (!req) | |
219 | return -ENOMEM; | |
220 | ||
221 | type = dhcp_option_parse(message, length, parse_request, req); | |
be077570 TG |
222 | if (type < 0) |
223 | return 0; | |
224 | ||
816e2e7a TG |
225 | r = ensure_sane_request(req, message); |
226 | if (r < 0) | |
227 | /* this only fails on critical errors */ | |
228 | return r; | |
229 | ||
be077570 TG |
230 | log_dhcp_server(server, "received message of type %d", type); |
231 | ||
232 | return 1; | |
233 | } | |
234 | ||
ff734080 TG |
235 | static int server_receive_message(sd_event_source *s, int fd, |
236 | uint32_t revents, void *userdata) { | |
be077570 | 237 | _cleanup_free_ DHCPMessage *message = NULL; |
3a864fe4 | 238 | uint8_t cmsgbuf[CMSG_LEN(sizeof(struct in_pktinfo))]; |
ff734080 TG |
239 | sd_dhcp_server *server = userdata; |
240 | struct iovec iov = {}; | |
241 | struct msghdr msg = { | |
242 | .msg_iov = &iov, | |
243 | .msg_iovlen = 1, | |
3a864fe4 TG |
244 | .msg_control = cmsgbuf, |
245 | .msg_controllen = sizeof(cmsgbuf), | |
ff734080 | 246 | }; |
3a864fe4 | 247 | struct cmsghdr *cmsg; |
ff734080 TG |
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; | |
be077570 TG |
268 | else if ((size_t)len < sizeof(DHCPMessage)) |
269 | return 0; | |
ff734080 | 270 | |
3a864fe4 TG |
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 | ||
be077570 | 285 | return dhcp_server_handle_message(server, message, (size_t)len); |
ff734080 TG |
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); | |
8de4a226 | 294 | assert_return(server->fd_raw == -1, -EBUSY); |
ff734080 | 295 | assert_return(server->fd == -1, -EBUSY); |
20af7091 | 296 | assert_return(server->address != htobe32(INADDR_ANY), -EUNATCH); |
ff734080 | 297 | |
8de4a226 TG |
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 | ||
ff734080 TG |
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 | } |