1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 
3 #include "sd-netlink.h"
4 
5 #include "af-list.h"
6 #include "alloc-util.h"
7 #include "fd-util.h"
8 #include "firewall-util.h"
9 #include "in-addr-util.h"
10 #include "local-addresses.h"
11 #include "netlink-util.h"
12 #include "nspawn-expose-ports.h"
13 #include "parse-util.h"
14 #include "socket-util.h"
15 #include "string-util.h"
16 #include "util.h"
17 
expose_port_parse(ExposePort ** l,const char * s)18 int expose_port_parse(ExposePort **l, const char *s) {
19         const char *split, *e;
20         uint16_t container_port, host_port;
21         ExposePort *port;
22         int protocol;
23         int r;
24 
25         assert(l);
26         assert(s);
27 
28         if ((e = startswith(s, "tcp:")))
29                 protocol = IPPROTO_TCP;
30         else if ((e = startswith(s, "udp:")))
31                 protocol = IPPROTO_UDP;
32         else {
33                 e = s;
34                 protocol = IPPROTO_TCP;
35         }
36 
37         split = strchr(e, ':');
38         if (split) {
39                 char v[split - e + 1];
40 
41                 memcpy(v, e, split - e);
42                 v[split - e] = 0;
43 
44                 r = parse_ip_port(v, &host_port);
45                 if (r < 0)
46                         return -EINVAL;
47 
48                 r = parse_ip_port(split + 1, &container_port);
49         } else {
50                 r = parse_ip_port(e, &container_port);
51                 host_port = container_port;
52         }
53 
54         if (r < 0)
55                 return r;
56 
57         LIST_FOREACH(ports, p, *l)
58                 if (p->protocol == protocol && p->host_port == host_port)
59                         return -EEXIST;
60 
61         port = new(ExposePort, 1);
62         if (!port)
63                 return -ENOMEM;
64 
65         *port = (ExposePort) {
66                 .protocol = protocol,
67                 .host_port = host_port,
68                 .container_port = container_port,
69         };
70 
71         LIST_PREPEND(ports, *l, port);
72 
73         return 0;
74 }
75 
expose_port_free_all(ExposePort * p)76 void expose_port_free_all(ExposePort *p) {
77 
78         while (p) {
79                 ExposePort *q = p;
80                 LIST_REMOVE(ports, p, q);
81                 free(q);
82         }
83 }
84 
expose_port_flush(FirewallContext ** fw_ctx,ExposePort * l,int af,union in_addr_union * exposed)85 int expose_port_flush(FirewallContext **fw_ctx, ExposePort* l, int af, union in_addr_union *exposed) {
86         int r;
87 
88         assert(exposed);
89 
90         if (!l)
91                 return 0;
92 
93         if (!in_addr_is_set(af, exposed))
94                 return 0;
95 
96         log_debug("Lost IP address.");
97 
98         LIST_FOREACH(ports, p, l) {
99                 r = fw_add_local_dnat(fw_ctx,
100                                       false,
101                                       af,
102                                       p->protocol,
103                                       p->host_port,
104                                       exposed,
105                                       p->container_port,
106                                       NULL);
107                 if (r < 0)
108                         log_warning_errno(r, "Failed to modify %s firewall: %m", af_to_name(af));
109         }
110 
111         *exposed = IN_ADDR_NULL;
112         return 0;
113 }
114 
expose_port_execute(sd_netlink * rtnl,FirewallContext ** fw_ctx,ExposePort * l,int af,union in_addr_union * exposed)115 int expose_port_execute(sd_netlink *rtnl, FirewallContext **fw_ctx, ExposePort *l, int af, union in_addr_union *exposed) {
116         _cleanup_free_ struct local_address *addresses = NULL;
117         union in_addr_union new_exposed;
118         bool add;
119         int r;
120 
121         assert(exposed);
122 
123         /* Invoked each time an address is added or removed inside the
124          * container */
125 
126         if (!l)
127                 return 0;
128 
129         r = local_addresses(rtnl, 0, af, &addresses);
130         if (r < 0)
131                 return log_error_errno(r, "Failed to enumerate local addresses: %m");
132 
133         add = r > 0 &&
134                 addresses[0].family == af &&
135                 addresses[0].scope < RT_SCOPE_LINK;
136 
137         if (!add)
138                 return expose_port_flush(fw_ctx, l, af, exposed);
139 
140         new_exposed = addresses[0].address;
141         if (in_addr_equal(af, exposed, &new_exposed))
142                 return 0;
143 
144         if (DEBUG_LOGGING) {
145                 _cleanup_free_ char *pretty = NULL;
146                 in_addr_to_string(af, &new_exposed, &pretty);
147                 log_debug("New container IP is %s.", strna(pretty));
148         }
149 
150         LIST_FOREACH(ports, p, l) {
151 
152                 r = fw_add_local_dnat(fw_ctx,
153                                       true,
154                                       af,
155                                       p->protocol,
156                                       p->host_port,
157                                       &new_exposed,
158                                       p->container_port,
159                                       in_addr_is_set(af, exposed) ? exposed : NULL);
160                 if (r < 0)
161                         log_warning_errno(r, "Failed to modify %s firewall: %m", af_to_name(af));
162         }
163 
164         *exposed = new_exposed;
165         return 0;
166 }
167 
expose_port_send_rtnl(int send_fd)168 int expose_port_send_rtnl(int send_fd) {
169         _cleanup_close_ int fd = -1;
170         int r;
171 
172         assert(send_fd >= 0);
173 
174         fd = socket(AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, NETLINK_ROUTE);
175         if (fd < 0)
176                 return log_error_errno(errno, "Failed to allocate container netlink: %m");
177 
178         /* Store away the fd in the socket, so that it stays open as
179          * long as we run the child */
180         r = send_one_fd(send_fd, fd, 0);
181         if (r < 0)
182                 return log_error_errno(r, "Failed to send netlink fd: %m");
183 
184         return 0;
185 }
186 
expose_port_watch_rtnl(sd_event * event,int recv_fd,sd_netlink_message_handler_t handler,void * userdata,sd_netlink ** ret)187 int expose_port_watch_rtnl(
188                 sd_event *event,
189                 int recv_fd,
190                 sd_netlink_message_handler_t handler,
191                 void *userdata,
192                 sd_netlink **ret) {
193         _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
194         int fd, r;
195 
196         assert(event);
197         assert(recv_fd >= 0);
198         assert(ret);
199 
200         fd = receive_one_fd(recv_fd, 0);
201         if (fd < 0)
202                 return log_error_errno(fd, "Failed to recv netlink fd: %m");
203 
204         r = sd_netlink_open_fd(&rtnl, fd);
205         if (r < 0) {
206                 safe_close(fd);
207                 return log_error_errno(r, "Failed to create rtnl object: %m");
208         }
209 
210         r = sd_netlink_add_match(rtnl, NULL, RTM_NEWADDR, handler, NULL, userdata, "nspawn-NEWADDR");
211         if (r < 0)
212                 return log_error_errno(r, "Failed to subscribe to RTM_NEWADDR messages: %m");
213 
214         r = sd_netlink_add_match(rtnl, NULL, RTM_DELADDR, handler, NULL, userdata, "nspawn-DELADDR");
215         if (r < 0)
216                 return log_error_errno(r, "Failed to subscribe to RTM_DELADDR messages: %m");
217 
218         r = sd_netlink_attach_event(rtnl, event, 0);
219         if (r < 0)
220                 return log_error_errno(r, "Failed to add to event loop: %m");
221 
222         *ret = TAKE_PTR(rtnl);
223 
224         return 0;
225 }
226