]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/nspawn/nspawn-expose-ports.c
Merge pull request #1191 from poettering/nspawn-split
[thirdparty/systemd.git] / src / nspawn / nspawn-expose-ports.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4 This file is part of systemd.
5
6 Copyright 2015 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 "sd-netlink.h"
23
24 #include "util.h"
25 #include "in-addr-util.h"
26 #include "firewall-util.h"
27 #include "local-addresses.h"
28 #include "netlink-util.h"
29
30 #include "nspawn-expose-ports.h"
31
32 int expose_port_parse(ExposePort **l, const char *s) {
33
34 const char *split, *e;
35 uint16_t container_port, host_port;
36 int protocol;
37 ExposePort *p;
38 int r;
39
40 assert(l);
41 assert(s);
42
43 if ((e = startswith(s, "tcp:")))
44 protocol = IPPROTO_TCP;
45 else if ((e = startswith(s, "udp:")))
46 protocol = IPPROTO_UDP;
47 else {
48 e = s;
49 protocol = IPPROTO_TCP;
50 }
51
52 split = strchr(e, ':');
53 if (split) {
54 char v[split - e + 1];
55
56 memcpy(v, e, split - e);
57 v[split - e] = 0;
58
59 r = safe_atou16(v, &host_port);
60 if (r < 0 || host_port <= 0)
61 return -EINVAL;
62
63 r = safe_atou16(split + 1, &container_port);
64 } else {
65 r = safe_atou16(e, &container_port);
66 host_port = container_port;
67 }
68
69 if (r < 0 || container_port <= 0)
70 return -EINVAL;
71
72 LIST_FOREACH(ports, p, *l)
73 if (p->protocol == protocol && p->host_port == host_port)
74 return -EEXIST;
75
76 p = new(ExposePort, 1);
77 if (!p)
78 return -ENOMEM;
79
80 p->protocol = protocol;
81 p->host_port = host_port;
82 p->container_port = container_port;
83
84 LIST_PREPEND(ports, *l, p);
85
86 return 0;
87 }
88
89 void expose_port_free_all(ExposePort *p) {
90
91 while (p) {
92 ExposePort *q = p;
93 LIST_REMOVE(ports, p, q);
94 free(q);
95 }
96 }
97
98 int expose_port_flush(ExposePort* l, union in_addr_union *exposed) {
99 ExposePort *p;
100 int r, af = AF_INET;
101
102 assert(exposed);
103
104 if (!l)
105 return 0;
106
107 if (in_addr_is_null(af, exposed))
108 return 0;
109
110 log_debug("Lost IP address.");
111
112 LIST_FOREACH(ports, p, l) {
113 r = fw_add_local_dnat(false,
114 af,
115 p->protocol,
116 NULL,
117 NULL, 0,
118 NULL, 0,
119 p->host_port,
120 exposed,
121 p->container_port,
122 NULL);
123 if (r < 0)
124 log_warning_errno(r, "Failed to modify firewall: %m");
125 }
126
127 *exposed = IN_ADDR_NULL;
128 return 0;
129 }
130
131 int expose_port_execute(sd_netlink *rtnl, ExposePort *l, union in_addr_union *exposed) {
132 _cleanup_free_ struct local_address *addresses = NULL;
133 _cleanup_free_ char *pretty = NULL;
134 union in_addr_union new_exposed;
135 ExposePort *p;
136 bool add;
137 int af = AF_INET, r;
138
139 assert(exposed);
140
141 /* Invoked each time an address is added or removed inside the
142 * container */
143
144 if (!l)
145 return 0;
146
147 r = local_addresses(rtnl, 0, af, &addresses);
148 if (r < 0)
149 return log_error_errno(r, "Failed to enumerate local addresses: %m");
150
151 add = r > 0 &&
152 addresses[0].family == af &&
153 addresses[0].scope < RT_SCOPE_LINK;
154
155 if (!add)
156 return expose_port_flush(l, exposed);
157
158 new_exposed = addresses[0].address;
159 if (in_addr_equal(af, exposed, &new_exposed))
160 return 0;
161
162 in_addr_to_string(af, &new_exposed, &pretty);
163 log_debug("New container IP is %s.", strna(pretty));
164
165 LIST_FOREACH(ports, p, l) {
166
167 r = fw_add_local_dnat(true,
168 af,
169 p->protocol,
170 NULL,
171 NULL, 0,
172 NULL, 0,
173 p->host_port,
174 &new_exposed,
175 p->container_port,
176 in_addr_is_null(af, exposed) ? NULL : exposed);
177 if (r < 0)
178 log_warning_errno(r, "Failed to modify firewall: %m");
179 }
180
181 *exposed = new_exposed;
182 return 0;
183 }
184
185 int expose_port_send_rtnl(int send_fd) {
186 union {
187 struct cmsghdr cmsghdr;
188 uint8_t buf[CMSG_SPACE(sizeof(int))];
189 } control = {};
190 struct msghdr mh = {
191 .msg_control = &control,
192 .msg_controllen = sizeof(control),
193 };
194 struct cmsghdr *cmsg;
195 _cleanup_close_ int fd = -1;
196 ssize_t k;
197
198 assert(send_fd >= 0);
199
200 fd = socket(PF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, NETLINK_ROUTE);
201 if (fd < 0)
202 return log_error_errno(errno, "Failed to allocate container netlink: %m");
203
204 cmsg = CMSG_FIRSTHDR(&mh);
205 cmsg->cmsg_level = SOL_SOCKET;
206 cmsg->cmsg_type = SCM_RIGHTS;
207 cmsg->cmsg_len = CMSG_LEN(sizeof(int));
208 memcpy(CMSG_DATA(cmsg), &fd, sizeof(int));
209
210 mh.msg_controllen = cmsg->cmsg_len;
211
212 /* Store away the fd in the socket, so that it stays open as
213 * long as we run the child */
214 k = sendmsg(send_fd, &mh, MSG_NOSIGNAL);
215 if (k < 0)
216 return log_error_errno(errno, "Failed to send netlink fd: %m");
217
218 return 0;
219 }
220
221 int expose_port_watch_rtnl(
222 sd_event *event,
223 int recv_fd,
224 sd_netlink_message_handler_t handler,
225 union in_addr_union *exposed,
226 sd_netlink **ret) {
227
228 union {
229 struct cmsghdr cmsghdr;
230 uint8_t buf[CMSG_SPACE(sizeof(int))];
231 } control = {};
232 struct msghdr mh = {
233 .msg_control = &control,
234 .msg_controllen = sizeof(control),
235 };
236 struct cmsghdr *cmsg;
237 _cleanup_netlink_unref_ sd_netlink *rtnl = NULL;
238 int fd, r;
239 ssize_t k;
240
241 assert(event);
242 assert(recv_fd >= 0);
243 assert(ret);
244
245 k = recvmsg(recv_fd, &mh, MSG_NOSIGNAL);
246 if (k < 0)
247 return log_error_errno(errno, "Failed to recv netlink fd: %m");
248
249 cmsg = CMSG_FIRSTHDR(&mh);
250 assert(cmsg->cmsg_level == SOL_SOCKET);
251 assert(cmsg->cmsg_type == SCM_RIGHTS);
252 assert(cmsg->cmsg_len == CMSG_LEN(sizeof(int)));
253 memcpy(&fd, CMSG_DATA(cmsg), sizeof(int));
254
255 r = sd_netlink_open_fd(&rtnl, fd);
256 if (r < 0) {
257 safe_close(fd);
258 return log_error_errno(r, "Failed to create rtnl object: %m");
259 }
260
261 r = sd_netlink_add_match(rtnl, RTM_NEWADDR, handler, exposed);
262 if (r < 0)
263 return log_error_errno(r, "Failed to subscribe to RTM_NEWADDR messages: %m");
264
265 r = sd_netlink_add_match(rtnl, RTM_DELADDR, handler, exposed);
266 if (r < 0)
267 return log_error_errno(r, "Failed to subscribe to RTM_DELADDR messages: %m");
268
269 r = sd_netlink_attach_event(rtnl, event, 0);
270 if (r < 0)
271 return log_error_errno(r, "Failed to add to even loop: %m");
272
273 *ret = rtnl;
274 rtnl = NULL;
275
276 return 0;
277 }