chiark / gitweb /
sd-dhcp-server: add basic message handling and verification
[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 int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
117                                size_t length) {
118         int type;
119
120         assert(server);
121         assert(message);
122
123         if (message->op != BOOTREQUEST ||
124             message->htype != ARPHRD_ETHER ||
125             message->hlen != ETHER_ADDR_LEN)
126                 return 0;
127
128         type = dhcp_option_parse(message, length, NULL, NULL);
129         if (type < 0)
130                 return 0;
131
132         log_dhcp_server(server, "received message of type %d", type);
133
134         return 1;
135 }
136
137 static int server_receive_message(sd_event_source *s, int fd,
138                                   uint32_t revents, void *userdata) {
139         _cleanup_free_ DHCPMessage *message = NULL;
140         uint8_t cmsgbuf[CMSG_LEN(sizeof(struct in_pktinfo))];
141         sd_dhcp_server *server = userdata;
142         struct iovec iov = {};
143         struct msghdr msg = {
144                 .msg_iov = &iov,
145                 .msg_iovlen = 1,
146                 .msg_control = cmsgbuf,
147                 .msg_controllen = sizeof(cmsgbuf),
148         };
149         struct cmsghdr *cmsg;
150         int buflen = 0, len, r;
151
152         assert(server);
153
154         r = ioctl(fd, FIONREAD, &buflen);
155         if (r < 0)
156                 return r;
157         if (buflen < 0)
158                 return -EIO;
159
160         message = malloc0(buflen);
161         if (!message)
162                 return -ENOMEM;
163
164         iov.iov_base = message;
165         iov.iov_len = buflen;
166
167         len = recvmsg(fd, &msg, 0);
168         if (len < buflen)
169                 return 0;
170         else if ((size_t)len < sizeof(DHCPMessage))
171                 return 0;
172
173         for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
174                 if (cmsg->cmsg_level == IPPROTO_IP &&
175                     cmsg->cmsg_type == IP_PKTINFO &&
176                     cmsg->cmsg_len == CMSG_LEN(sizeof(struct in_pktinfo))) {
177                         struct in_pktinfo *info = (struct in_pktinfo*)CMSG_DATA(cmsg);
178
179                         /* TODO figure out if this can be done as a filter on the socket, like for IPv6 */
180                         if (server->index != info->ipi_ifindex)
181                                 return 0;
182
183                         break;
184                 }
185         }
186
187         return dhcp_server_handle_message(server, message, (size_t)len);
188 }
189
190 int sd_dhcp_server_start(sd_dhcp_server *server) {
191         int r;
192
193         assert_return(server, -EINVAL);
194         assert_return(server->event, -EINVAL);
195         assert_return(!server->receive_message, -EBUSY);
196         assert_return(server->fd == -1, -EBUSY);
197
198         r = dhcp_network_bind_udp_socket(INADDR_ANY, DHCP_PORT_SERVER);
199         if (r < 0) {
200                 sd_dhcp_server_stop(server);
201                 return r;
202         }
203         server->fd = r;
204
205         r = sd_event_add_io(server->event, &server->receive_message,
206                             server->fd, EPOLLIN,
207                             server_receive_message, server);
208         if (r < 0) {
209                 sd_dhcp_server_stop(server);
210                 return r;
211         }
212
213         r = sd_event_source_set_priority(server->receive_message,
214                                          server->event_priority);
215         if (r < 0) {
216                 sd_dhcp_server_stop(server);
217                 return r;
218         }
219
220         log_dhcp_server(server, "STARTED");
221
222         return 0;
223 }