diff options
Diffstat (limited to '')
-rw-r--r-- | src/nspawn/nspawn-expose-ports.c | 220 |
1 files changed, 220 insertions, 0 deletions
diff --git a/src/nspawn/nspawn-expose-ports.c b/src/nspawn/nspawn-expose-ports.c new file mode 100644 index 0000000..bb54f8c --- /dev/null +++ b/src/nspawn/nspawn-expose-ports.c @@ -0,0 +1,220 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-netlink.h" + +#include "af-list.h" +#include "alloc-util.h" +#include "fd-util.h" +#include "firewall-util.h" +#include "in-addr-util.h" +#include "local-addresses.h" +#include "netlink-util.h" +#include "nspawn-expose-ports.h" +#include "parse-util.h" +#include "socket-util.h" +#include "string-util.h" +#include "util.h" + +int expose_port_parse(ExposePort **l, const char *s) { + const char *split, *e; + uint16_t container_port, host_port; + ExposePort *port; + int protocol; + int r; + + assert(l); + assert(s); + + if ((e = startswith(s, "tcp:"))) + protocol = IPPROTO_TCP; + else if ((e = startswith(s, "udp:"))) + protocol = IPPROTO_UDP; + else { + e = s; + protocol = IPPROTO_TCP; + } + + split = strchr(e, ':'); + if (split) { + char v[split - e + 1]; + + memcpy(v, e, split - e); + v[split - e] = 0; + + r = parse_ip_port(v, &host_port); + if (r < 0) + return -EINVAL; + + r = parse_ip_port(split + 1, &container_port); + } else { + r = parse_ip_port(e, &container_port); + host_port = container_port; + } + + if (r < 0) + return r; + + LIST_FOREACH(ports, p, *l) + if (p->protocol == protocol && p->host_port == host_port) + return -EEXIST; + + port = new(ExposePort, 1); + if (!port) + return -ENOMEM; + + *port = (ExposePort) { + .protocol = protocol, + .host_port = host_port, + .container_port = container_port, + }; + + LIST_PREPEND(ports, *l, port); + + return 0; +} + +void expose_port_free_all(ExposePort *p) { + + while (p) { + ExposePort *q = p; + LIST_REMOVE(ports, p, q); + free(q); + } +} + +int expose_port_flush(FirewallContext **fw_ctx, ExposePort* l, int af, union in_addr_union *exposed) { + int r; + + assert(exposed); + + if (!l) + return 0; + + if (!in_addr_is_set(af, exposed)) + return 0; + + log_debug("Lost IP address."); + + LIST_FOREACH(ports, p, l) { + r = fw_add_local_dnat(fw_ctx, + false, + af, + p->protocol, + p->host_port, + exposed, + p->container_port, + NULL); + if (r < 0) + log_warning_errno(r, "Failed to modify %s firewall: %m", af_to_name(af)); + } + + *exposed = IN_ADDR_NULL; + return 0; +} + +int expose_port_execute(sd_netlink *rtnl, FirewallContext **fw_ctx, ExposePort *l, int af, union in_addr_union *exposed) { + _cleanup_free_ struct local_address *addresses = NULL; + union in_addr_union new_exposed; + bool add; + int r; + + assert(exposed); + + /* Invoked each time an address is added or removed inside the + * container */ + + if (!l) + return 0; + + r = local_addresses(rtnl, 0, af, &addresses); + if (r < 0) + return log_error_errno(r, "Failed to enumerate local addresses: %m"); + + add = r > 0 && + addresses[0].family == af && + addresses[0].scope < RT_SCOPE_LINK; + + if (!add) + return expose_port_flush(fw_ctx, l, af, exposed); + + new_exposed = addresses[0].address; + if (in_addr_equal(af, exposed, &new_exposed)) + return 0; + + log_debug("New container IP is %s.", IN_ADDR_TO_STRING(af, &new_exposed)); + + LIST_FOREACH(ports, p, l) { + r = fw_add_local_dnat(fw_ctx, + true, + af, + p->protocol, + p->host_port, + &new_exposed, + p->container_port, + in_addr_is_set(af, exposed) ? exposed : NULL); + if (r < 0) + log_warning_errno(r, "Failed to modify %s firewall: %m", af_to_name(af)); + } + + *exposed = new_exposed; + return 0; +} + +int expose_port_send_rtnl(int send_fd) { + _cleanup_close_ int fd = -1; + int r; + + assert(send_fd >= 0); + + fd = socket(AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, NETLINK_ROUTE); + if (fd < 0) + return log_error_errno(errno, "Failed to allocate container netlink: %m"); + + /* Store away the fd in the socket, so that it stays open as + * long as we run the child */ + r = send_one_fd(send_fd, fd, 0); + if (r < 0) + return log_error_errno(r, "Failed to send netlink fd: %m"); + + return 0; +} + +int expose_port_watch_rtnl( + sd_event *event, + int recv_fd, + sd_netlink_message_handler_t handler, + void *userdata, + sd_netlink **ret) { + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + int fd, r; + + assert(event); + assert(recv_fd >= 0); + assert(ret); + + fd = receive_one_fd(recv_fd, 0); + if (fd < 0) + return log_error_errno(fd, "Failed to recv netlink fd: %m"); + + r = sd_netlink_open_fd(&rtnl, fd); + if (r < 0) { + safe_close(fd); + return log_error_errno(r, "Failed to create rtnl object: %m"); + } + + r = sd_netlink_add_match(rtnl, NULL, RTM_NEWADDR, handler, NULL, userdata, "nspawn-NEWADDR"); + if (r < 0) + return log_error_errno(r, "Failed to subscribe to RTM_NEWADDR messages: %m"); + + r = sd_netlink_add_match(rtnl, NULL, RTM_DELADDR, handler, NULL, userdata, "nspawn-DELADDR"); + if (r < 0) + return log_error_errno(r, "Failed to subscribe to RTM_DELADDR messages: %m"); + + r = sd_netlink_attach_event(rtnl, event, 0); + if (r < 0) + return log_error_errno(r, "Failed to add to event loop: %m"); + + *ret = TAKE_PTR(rtnl); + + return 0; +} |