chiark / gitweb /
f98a29806e5cc377b8414df703330f261d2c725d
[elogind.git] / src / core / loopback-setup.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2010 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <errno.h>
23 #include <sys/socket.h>
24 #include <net/if.h>
25 #include <asm/types.h>
26 #include <netinet/in.h>
27 #include <string.h>
28 #include <stdlib.h>
29 #include <unistd.h>
30 #include <linux/netlink.h>
31 #include <linux/rtnetlink.h>
32
33 #include "util.h"
34 #include "macro.h"
35 #include "loopback-setup.h"
36 #include "socket-util.h"
37
38 #define NLMSG_TAIL(nmsg)                                                \
39         ((struct rtattr *) (((uint8_t*) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len)))
40
41 static int add_rtattr(struct nlmsghdr *n, size_t max_length, int type, const void *data, size_t data_length) {
42         size_t length;
43         struct rtattr *rta;
44
45         length = RTA_LENGTH(data_length);
46
47         if (NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(length) > max_length)
48                 return -E2BIG;
49
50         rta = NLMSG_TAIL(n);
51         rta->rta_type = type;
52         rta->rta_len = length;
53         memcpy(RTA_DATA(rta), data, data_length);
54         n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(length);
55
56         return 0;
57 }
58
59 static ssize_t sendto_loop(int fd, const void *buf, size_t buf_len, int flags, const struct sockaddr *sa, socklen_t sa_len) {
60
61         for (;;) {
62                 ssize_t l;
63
64                 l = sendto(fd, buf, buf_len, flags, sa, sa_len);
65                 if (l >= 0)
66                         return l;
67
68                 if (errno != EINTR)
69                         return -errno;
70         }
71 }
72
73 static ssize_t recvfrom_loop(int fd, void *buf, size_t buf_len, int flags, struct sockaddr *sa, socklen_t *sa_len) {
74
75         for (;;) {
76                 ssize_t l;
77
78                 l = recvfrom(fd, buf, buf_len, flags, sa, sa_len);
79                 if (l >= 0)
80                         return l;
81
82                 if (errno != EINTR)
83                         return -errno;
84         }
85 }
86
87 static int add_adresses(int fd, int if_loopback, unsigned *requests) {
88         union {
89                 struct sockaddr sa;
90                 struct sockaddr_nl nl;
91         } sa = {
92                 .nl.nl_family = AF_NETLINK,
93         };
94
95         union {
96                 struct nlmsghdr header;
97                 uint8_t buf[NLMSG_ALIGN(sizeof(struct nlmsghdr)) +
98                             NLMSG_ALIGN(sizeof(struct ifaddrmsg)) +
99                             RTA_LENGTH(sizeof(struct in6_addr))];
100         } request = {
101                 .header.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)),
102                 .header.nlmsg_type = RTM_NEWADDR,
103                 .header.nlmsg_flags = NLM_F_REQUEST|NLM_F_CREATE|NLM_F_ACK,
104                 .header.nlmsg_seq = *requests + 1,
105         };
106
107         struct ifaddrmsg *ifaddrmsg;
108         uint32_t ipv4_address = htonl(INADDR_LOOPBACK);
109         int r;
110
111         ifaddrmsg = NLMSG_DATA(&request.header);
112         ifaddrmsg->ifa_family = AF_INET;
113         ifaddrmsg->ifa_prefixlen = 8;
114         ifaddrmsg->ifa_flags = IFA_F_PERMANENT;
115         ifaddrmsg->ifa_scope = RT_SCOPE_HOST;
116         ifaddrmsg->ifa_index = if_loopback;
117
118         r = add_rtattr(&request.header, sizeof(request), IFA_LOCAL,
119                        &ipv4_address, sizeof(ipv4_address));
120         if (r < 0)
121                 return r;
122
123         if (sendto_loop(fd, &request, request.header.nlmsg_len, 0, &sa.sa, sizeof(sa)) < 0)
124                 return -errno;
125         (*requests)++;
126
127         if (!socket_ipv6_is_supported())
128                 return 0;
129
130         request.header.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg));
131         request.header.nlmsg_seq = *requests + 1;
132
133         ifaddrmsg->ifa_family = AF_INET6;
134         ifaddrmsg->ifa_prefixlen = 128;
135
136         r = add_rtattr(&request.header, sizeof(request), IFA_LOCAL,
137                        &in6addr_loopback, sizeof(in6addr_loopback));
138         if (r < 0)
139                 return r;
140
141         if (sendto_loop(fd, &request, request.header.nlmsg_len, 0, &sa.sa, sizeof(sa)) < 0)
142                 return -errno;
143         (*requests)++;
144
145         return 0;
146 }
147
148 static int start_interface(int fd, int if_loopback, unsigned *requests) {
149         union {
150                 struct sockaddr sa;
151                 struct sockaddr_nl nl;
152         } sa = {
153                 .nl.nl_family = AF_NETLINK,
154         };
155
156         union {
157                 struct nlmsghdr header;
158                 uint8_t buf[NLMSG_ALIGN(sizeof(struct nlmsghdr)) +
159                             NLMSG_ALIGN(sizeof(struct ifinfomsg))];
160         } request = {
161                 .header.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)),
162                 .header.nlmsg_type = RTM_NEWLINK,
163                 .header.nlmsg_flags = NLM_F_REQUEST|NLM_F_ACK,
164                 .header.nlmsg_seq = *requests + 1,
165         };
166
167         struct ifinfomsg *ifinfomsg;
168
169         ifinfomsg = NLMSG_DATA(&request.header);
170         ifinfomsg->ifi_family = AF_UNSPEC;
171         ifinfomsg->ifi_index = if_loopback;
172         ifinfomsg->ifi_flags = IFF_UP;
173         ifinfomsg->ifi_change = IFF_UP;
174
175         if (sendto_loop(fd, &request, request.header.nlmsg_len, 0, &sa.sa, sizeof(sa)) < 0)
176                 return -errno;
177
178         (*requests)++;
179
180         return 0;
181 }
182
183 static int read_response(int fd, unsigned requests_max) {
184         union {
185                 struct sockaddr sa;
186                 struct sockaddr_nl nl;
187         } sa;
188         union {
189                 struct nlmsghdr header;
190                 uint8_t buf[NLMSG_ALIGN(sizeof(struct nlmsghdr)) +
191                             NLMSG_ALIGN(sizeof(struct nlmsgerr))];
192         } response;
193
194         ssize_t l;
195         socklen_t sa_len = sizeof(sa);
196         struct nlmsgerr *nlmsgerr;
197
198         l = recvfrom_loop(fd, &response, sizeof(response), 0, &sa.sa, &sa_len);
199         if (l < 0)
200                 return -errno;
201
202         if (sa_len != sizeof(sa.nl) ||
203             sa.nl.nl_family != AF_NETLINK)
204                 return -EIO;
205
206         if (sa.nl.nl_pid != 0)
207                 return 0;
208
209         if ((size_t) l < sizeof(struct nlmsghdr))
210                 return -EIO;
211
212         if (response.header.nlmsg_type != NLMSG_ERROR ||
213             (pid_t) response.header.nlmsg_pid != getpid() ||
214             response.header.nlmsg_seq >= requests_max)
215                 return 0;
216
217         if ((size_t) l < NLMSG_LENGTH(sizeof(struct nlmsgerr)) ||
218             response.header.nlmsg_len < NLMSG_LENGTH(sizeof(struct nlmsgerr)))
219                 return -EIO;
220
221         nlmsgerr = NLMSG_DATA(&response.header);
222
223         if (nlmsgerr->error < 0 && nlmsgerr->error != -EEXIST)
224                 return nlmsgerr->error;
225
226         return response.header.nlmsg_seq;
227 }
228
229 static int check_loopback(void) {
230         int r;
231         int _cleanup_close_ fd;
232         union {
233                 struct sockaddr sa;
234                 struct sockaddr_in in;
235         } sa = {
236                 .in.sin_family = AF_INET,
237                 .in.sin_addr.s_addr = INADDR_LOOPBACK,
238         };
239
240         /* If we failed to set up the loop back device, check whether
241          * it might already be set up */
242
243         fd = socket(AF_INET, SOCK_DGRAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0);
244         if (fd < 0)
245                 return -errno;
246
247         if (bind(fd, &sa.sa, sizeof(sa.in)) >= 0)
248                 r = 1;
249         else
250                 r = errno == EADDRNOTAVAIL ? 0 : -errno;
251
252         return r;
253 }
254
255 int loopback_setup(void) {
256         int r, if_loopback;
257         union {
258                 struct sockaddr sa;
259                 struct sockaddr_nl nl;
260         } sa = {
261                 .nl.nl_family = AF_NETLINK,
262         };
263         unsigned requests = 0, i;
264         int _cleanup_close_ fd = -1;
265         bool eperm = false;
266
267         errno = 0;
268         if_loopback = (int) if_nametoindex("lo");
269         if (if_loopback <= 0)
270                 return errno ? -errno : -ENODEV;
271
272         fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
273         if (fd < 0)
274                 return -errno;
275
276         if (bind(fd, &sa.sa, sizeof(sa)) < 0) {
277                 r = -errno;
278                 goto error;
279         }
280
281         r = add_adresses(fd, if_loopback, &requests);
282         if (r < 0)
283                 goto error;
284
285         r = start_interface(fd, if_loopback, &requests);
286         if (r < 0)
287                 goto error;
288
289         for (i = 0; i < requests; i++) {
290                 r = read_response(fd, requests);
291
292                 if (r == -EPERM)
293                         eperm = true;
294                 else if (r  < 0)
295                         goto error;
296         }
297
298         if (eperm && check_loopback() < 0) {
299                 r = -EPERM;
300                 goto error;
301         }
302
303         return 0;
304
305 error:
306         log_warning("Failed to configure loopback device: %s", strerror(-r));
307         return r;
308 }