]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/nspawn/nspawn-expose-ports.c
core: introduce parse_ip_port (#4825)
[thirdparty/systemd.git] / src / nspawn / nspawn-expose-ports.c
1 /***
2 This file is part of systemd.
3
4 Copyright 2015 Lennart Poettering
5
6 systemd is free software; you can redistribute it and/or modify it
7 under the terms of the GNU Lesser General Public License as published by
8 the Free Software Foundation; either version 2.1 of the License, or
9 (at your option) any later version.
10
11 systemd is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public License
17 along with systemd; If not, see <http://www.gnu.org/licenses/>.
18 ***/
19
20 #include "sd-netlink.h"
21
22 #include "alloc-util.h"
23 #include "fd-util.h"
24 #include "firewall-util.h"
25 #include "in-addr-util.h"
26 #include "local-addresses.h"
27 #include "netlink-util.h"
28 #include "nspawn-expose-ports.h"
29 #include "parse-util.h"
30 #include "socket-util.h"
31 #include "string-util.h"
32 #include "util.h"
33
34 int expose_port_parse(ExposePort **l, const char *s) {
35
36 const char *split, *e;
37 uint16_t container_port, host_port;
38 int protocol;
39 ExposePort *p;
40 int r;
41
42 assert(l);
43 assert(s);
44
45 if ((e = startswith(s, "tcp:")))
46 protocol = IPPROTO_TCP;
47 else if ((e = startswith(s, "udp:")))
48 protocol = IPPROTO_UDP;
49 else {
50 e = s;
51 protocol = IPPROTO_TCP;
52 }
53
54 split = strchr(e, ':');
55 if (split) {
56 char v[split - e + 1];
57
58 memcpy(v, e, split - e);
59 v[split - e] = 0;
60
61 r = parse_ip_port(v, &host_port);
62 if (r < 0)
63 return -EINVAL;
64
65 r = parse_ip_port(split + 1, &container_port);
66 } else {
67 r = parse_ip_port(e, &container_port);
68 host_port = container_port;
69 }
70
71 if (r < 0)
72 return -EINVAL;
73
74 LIST_FOREACH(ports, p, *l)
75 if (p->protocol == protocol && p->host_port == host_port)
76 return -EEXIST;
77
78 p = new(ExposePort, 1);
79 if (!p)
80 return -ENOMEM;
81
82 p->protocol = protocol;
83 p->host_port = host_port;
84 p->container_port = container_port;
85
86 LIST_PREPEND(ports, *l, p);
87
88 return 0;
89 }
90
91 void expose_port_free_all(ExposePort *p) {
92
93 while (p) {
94 ExposePort *q = p;
95 LIST_REMOVE(ports, p, q);
96 free(q);
97 }
98 }
99
100 int expose_port_flush(ExposePort* l, union in_addr_union *exposed) {
101 ExposePort *p;
102 int r, af = AF_INET;
103
104 assert(exposed);
105
106 if (!l)
107 return 0;
108
109 if (in_addr_is_null(af, exposed))
110 return 0;
111
112 log_debug("Lost IP address.");
113
114 LIST_FOREACH(ports, p, l) {
115 r = fw_add_local_dnat(false,
116 af,
117 p->protocol,
118 NULL,
119 NULL, 0,
120 NULL, 0,
121 p->host_port,
122 exposed,
123 p->container_port,
124 NULL);
125 if (r < 0)
126 log_warning_errno(r, "Failed to modify firewall: %m");
127 }
128
129 *exposed = IN_ADDR_NULL;
130 return 0;
131 }
132
133 int expose_port_execute(sd_netlink *rtnl, ExposePort *l, union in_addr_union *exposed) {
134 _cleanup_free_ struct local_address *addresses = NULL;
135 _cleanup_free_ char *pretty = NULL;
136 union in_addr_union new_exposed;
137 ExposePort *p;
138 bool add;
139 int af = AF_INET, r;
140
141 assert(exposed);
142
143 /* Invoked each time an address is added or removed inside the
144 * container */
145
146 if (!l)
147 return 0;
148
149 r = local_addresses(rtnl, 0, af, &addresses);
150 if (r < 0)
151 return log_error_errno(r, "Failed to enumerate local addresses: %m");
152
153 add = r > 0 &&
154 addresses[0].family == af &&
155 addresses[0].scope < RT_SCOPE_LINK;
156
157 if (!add)
158 return expose_port_flush(l, exposed);
159
160 new_exposed = addresses[0].address;
161 if (in_addr_equal(af, exposed, &new_exposed))
162 return 0;
163
164 in_addr_to_string(af, &new_exposed, &pretty);
165 log_debug("New container IP is %s.", strna(pretty));
166
167 LIST_FOREACH(ports, p, l) {
168
169 r = fw_add_local_dnat(true,
170 af,
171 p->protocol,
172 NULL,
173 NULL, 0,
174 NULL, 0,
175 p->host_port,
176 &new_exposed,
177 p->container_port,
178 in_addr_is_null(af, exposed) ? NULL : exposed);
179 if (r < 0)
180 log_warning_errno(r, "Failed to modify firewall: %m");
181 }
182
183 *exposed = new_exposed;
184 return 0;
185 }
186
187 int expose_port_send_rtnl(int send_fd) {
188 _cleanup_close_ int fd = -1;
189 int r;
190
191 assert(send_fd >= 0);
192
193 fd = socket(PF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, NETLINK_ROUTE);
194 if (fd < 0)
195 return log_error_errno(errno, "Failed to allocate container netlink: %m");
196
197 /* Store away the fd in the socket, so that it stays open as
198 * long as we run the child */
199 r = send_one_fd(send_fd, fd, 0);
200 if (r < 0)
201 return log_error_errno(r, "Failed to send netlink fd: %m");
202
203 return 0;
204 }
205
206 int expose_port_watch_rtnl(
207 sd_event *event,
208 int recv_fd,
209 sd_netlink_message_handler_t handler,
210 union in_addr_union *exposed,
211 sd_netlink **ret) {
212 _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
213 int fd, r;
214
215 assert(event);
216 assert(recv_fd >= 0);
217 assert(ret);
218
219 fd = receive_one_fd(recv_fd, 0);
220 if (fd < 0)
221 return log_error_errno(fd, "Failed to recv netlink fd: %m");
222
223 r = sd_netlink_open_fd(&rtnl, fd);
224 if (r < 0) {
225 safe_close(fd);
226 return log_error_errno(r, "Failed to create rtnl object: %m");
227 }
228
229 r = sd_netlink_add_match(rtnl, RTM_NEWADDR, handler, exposed);
230 if (r < 0)
231 return log_error_errno(r, "Failed to subscribe to RTM_NEWADDR messages: %m");
232
233 r = sd_netlink_add_match(rtnl, RTM_DELADDR, handler, exposed);
234 if (r < 0)
235 return log_error_errno(r, "Failed to subscribe to RTM_DELADDR messages: %m");
236
237 r = sd_netlink_attach_event(rtnl, event, 0);
238 if (r < 0)
239 return log_error_errno(r, "Failed to add to even loop: %m");
240
241 *ret = rtnl;
242 rtnl = NULL;
243
244 return 0;
245 }