diff options
author | Stefano Brivio <sbrivio@redhat.com> | 2021-08-12 15:42:43 +0200 |
---|---|---|
committer | Stefano Brivio <sbrivio@redhat.com> | 2021-09-01 17:00:27 +0200 |
commit | 1e49d194d01788afbc4b8216e27c794651a4facf (patch) | |
tree | 3397d4b687a74fe9552e057138c3a795917a5afa /passt.c | |
parent | 1b1b27c06a27067a7d7a380f1df545e72268c411 (diff) | |
download | passt-1e49d194d01788afbc4b8216e27c794651a4facf.tar passt-1e49d194d01788afbc4b8216e27c794651a4facf.tar.gz passt-1e49d194d01788afbc4b8216e27c794651a4facf.tar.bz2 passt-1e49d194d01788afbc4b8216e27c794651a4facf.tar.lz passt-1e49d194d01788afbc4b8216e27c794651a4facf.tar.xz passt-1e49d194d01788afbc4b8216e27c794651a4facf.tar.zst passt-1e49d194d01788afbc4b8216e27c794651a4facf.zip |
passt, pasta: Introduce command-line options and port re-mapping
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
Diffstat (limited to 'passt.c')
-rw-r--r-- | passt.c | 524 |
1 files changed, 101 insertions, 423 deletions
@@ -25,13 +25,13 @@ #include <sys/epoll.h> #include <sys/socket.h> #include <sys/types.h> -#include <sys/ioctl.h> +#include <sys/stat.h> +#include <fcntl.h> #include <sys/resource.h> #include <sys/uio.h> -#include <ifaddrs.h> +#include <sys/wait.h> #include <linux/if_ether.h> #include <linux/if_packet.h> -#include <arpa/inet.h> #include <linux/ip.h> #include <linux/ipv6.h> #include <linux/tcp.h> @@ -46,8 +46,6 @@ #include <netdb.h> #include <string.h> #include <errno.h> -#include <linux/netlink.h> -#include <linux/rtnetlink.h> #include <time.h> #include <syslog.h> #include <sys/stat.h> @@ -60,6 +58,7 @@ #include "udp.h" #include "pcap.h" #include "tap.h" +#include "conf.h" #define EPOLL_EVENTS 10 @@ -68,7 +67,6 @@ char pkt_buf [PKT_BUF_BYTES]; -#ifdef DEBUG char *ip_proto_str[IPPROTO_SCTP + 1] = { [IPPROTO_ICMP] = "ICMP", [IPPROTO_TCP] = "TCP", @@ -76,318 +74,6 @@ char *ip_proto_str[IPPROTO_SCTP + 1] = { [IPPROTO_ICMPV6] = "ICMPV6", [IPPROTO_SCTP] = "SCTP", }; -#endif - -/** - * struct nl_request - Netlink request filled and sent by get_routes() - * @nlh: Netlink message header - * @rtm: Routing Netlink message - */ -struct nl_request { - struct nlmsghdr nlh; - struct rtmsg rtm; -}; - -/** - * get_routes() - Get default route and fill in routable interface name - * @c: Execution context - */ -static void get_routes(struct ctx *c) -{ - struct nl_request req = { - .nlh.nlmsg_type = RTM_GETROUTE, - .nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP | NLM_F_EXCL, - .nlh.nlmsg_len = sizeof(struct nl_request), - .nlh.nlmsg_seq = 1, - .rtm.rtm_family = AF_INET, - .rtm.rtm_table = RT_TABLE_MAIN, - .rtm.rtm_scope = RT_SCOPE_UNIVERSE, - .rtm.rtm_type = RTN_UNICAST, - }; - struct sockaddr_nl addr = { - .nl_family = AF_NETLINK, - }; - struct nlmsghdr *nlh; - struct rtattr *rta; - struct rtmsg *rtm; - char buf[BUFSIZ]; - int s, n, na; - - c->v6 = -1; - - s = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); - if (s < 0) { - perror("netlink socket"); - goto out; - } - - if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) { - perror("netlink bind"); - goto out; - } - -v6: - if (send(s, &req, sizeof(req), 0) < 0) { - perror("netlink send"); - goto out; - } - - n = recv(s, &buf, sizeof(buf), 0); - if (n < 0) { - perror("netlink recv"); - goto out; - } - - nlh = (struct nlmsghdr *)buf; - for ( ; NLMSG_OK(nlh, n); nlh = NLMSG_NEXT(nlh, n)) { - rtm = (struct rtmsg *)NLMSG_DATA(nlh); - - if (rtm->rtm_dst_len || - (rtm->rtm_family != AF_INET && rtm->rtm_family != AF_INET6)) - continue; - - rta = (struct rtattr *)RTM_RTA(rtm); - na = RTM_PAYLOAD(nlh); - for ( ; RTA_OK(rta, na); rta = RTA_NEXT(rta, na)) { - if (rta->rta_type == RTA_GATEWAY && - rtm->rtm_family == AF_INET && !c->v4) { - memcpy(&c->gw4, RTA_DATA(rta), sizeof(c->gw4)); - c->v4 = 1; - } - - if (rta->rta_type == RTA_GATEWAY && - rtm->rtm_family == AF_INET6 && !c->v6) { - memcpy(&c->gw6, RTA_DATA(rta), sizeof(c->gw6)); - c->v6 = 1; - } - - if (rta->rta_type == RTA_OIF && !*c->ifn) { - if_indextoname(*(unsigned *)RTA_DATA(rta), - c->ifn); - } - } - - if (nlh->nlmsg_type == NLMSG_DONE) - break; - } - - if (c->v6 == -1) { - c->v6 = 0; - req.rtm.rtm_family = AF_INET6; - req.nlh.nlmsg_seq++; - recv(s, &buf, sizeof(buf), 0); - goto v6; - } - -out: - close(s); - - if (!(c->v4 || c->v6) || !*c->ifn) { - err("No routing information"); - exit(EXIT_FAILURE); - } -} - -/** - * get_addrs() - Fetch MAC, IP addresses, masks of external routable interface - * @c: Execution context - */ -static void get_addrs(struct ctx *c) -{ - struct ifreq ifr = { - .ifr_addr.sa_family = AF_INET, - }; - struct ifaddrs *ifaddr, *ifa; - int s, v4 = 0, v6 = 0; - - if (getifaddrs(&ifaddr) == -1) { - perror("getifaddrs"); - goto out; - } - - for (ifa = ifaddr; ifa; ifa = ifa->ifa_next) { - struct sockaddr_in *in_addr; - struct sockaddr_in6 *in6_addr; - - if (strcmp(ifa->ifa_name, c->ifn)) - continue; - - if (!ifa->ifa_addr) - continue; - - if (ifa->ifa_addr->sa_family == AF_INET && !v4) { - in_addr = (struct sockaddr_in *)ifa->ifa_addr; - c->addr4_seen = c->addr4 = in_addr->sin_addr.s_addr; - in_addr = (struct sockaddr_in *)ifa->ifa_netmask; - c->mask4 = in_addr->sin_addr.s_addr; - v4 = 1; - } else if (ifa->ifa_addr->sa_family == AF_INET6 && !v6) { - in6_addr = (struct sockaddr_in6 *)ifa->ifa_addr; - memcpy(&c->addr6, &in6_addr->sin6_addr, - sizeof(c->addr6)); - memcpy(&c->addr6_seen, &in6_addr->sin6_addr, - sizeof(c->addr6_seen)); - memcpy(&c->addr6_ll_seen, &in6_addr->sin6_addr, - sizeof(c->addr6_seen)); - v6 = 1; - } - - if (v4 == c->v4 && v6 == c->v6) - break; - } - - freeifaddrs(ifaddr); - - if (v4 != c->v4 || v6 != c->v6) - goto out; - - s = socket(AF_INET, SOCK_DGRAM, 0); - if (s < 0) { - perror("socket SIOCGIFHWADDR"); - goto out; - } - - strncpy(ifr.ifr_name, c->ifn, IF_NAMESIZE); - if (ioctl(s, SIOCGIFHWADDR, &ifr) < 0) { - perror("SIOCGIFHWADDR"); - goto out; - } - - close(s); - memcpy(c->mac, ifr.ifr_hwaddr.sa_data, ETH_ALEN); - - return; -out: - err("Couldn't get addresses for routable interface"); - exit(EXIT_FAILURE); -} - -/** - * get_dns() - Get nameserver addresses from local /etc/resolv.conf - * @c: Execution context - */ -static void get_dns(struct ctx *c) -{ - struct in6_addr *dns6 = &c->dns6[0]; - struct fqdn *s = c->dns_search; - uint32_t *dns4 = &c->dns4[0]; - char buf[BUFSIZ], *p, *end; - FILE *r; - - r = fopen("/etc/resolv.conf", "r"); - while (fgets(buf, BUFSIZ, r)) { - if (strstr(buf, "nameserver ") == buf) { - p = strrchr(buf, ' '); - if (!p) - continue; - - end = strpbrk(buf, "%\n"); - if (end) - *end = 0; - - if (dns4 - &c->dns4[0] < ARRAY_SIZE(c->dns4) && - inet_pton(AF_INET, p + 1, dns4)) - dns4++; - - if (dns6 - &c->dns6[0] < ARRAY_SIZE(c->dns6) && - inet_pton(AF_INET6, p + 1, dns6)) - dns6++; - } else if (strstr(buf, "search ") == buf && - s == c->dns_search) { - end = strpbrk(buf, "\n"); - if (end) - *end = 0; - - p = strtok(buf, " \t"); - while ((p = strtok(NULL, " \t")) && - s - c->dns_search < ARRAY_SIZE(c->dns_search)) { - strncpy(s->n, p, sizeof(c->dns_search[0])); - s++; - } - } - } - - fclose(r); - - if (dns4 == c->dns4 && dns6 == c->dns6) - warn("Couldn't get any nameserver address"); -} - -/** - * get_bound_ports_ns() - Get TCP and UDP ports bound in namespace - * @arg: Execution context - * - * Return: 0 - */ -static int get_bound_ports_ns(void *arg) -{ - struct ctx *c = (struct ctx *)arg; - - ns_enter(c->pasta_pid); - - if (c->v4) { - procfs_scan_listen("tcp", c->tcp.port4_to_tap); - procfs_scan_listen("tcp", c->udp.port4_to_tap); - procfs_scan_listen("udp", c->udp.port4_to_tap); - - procfs_scan_listen("tcp", c->tcp.port4_to_ns); - procfs_scan_listen("tcp", c->udp.port4_to_ns); - procfs_scan_listen("udp", c->udp.port4_to_ns); - } - - if (c->v6) { - if (c->v4) { - procfs_scan_listen("tcp6", c->tcp.port4_to_tap); - procfs_scan_listen("tcp6", c->udp.port4_to_tap); - procfs_scan_listen("udp6", c->udp.port4_to_tap); - - procfs_scan_listen("tcp6", c->tcp.port4_to_ns); - procfs_scan_listen("tcp6", c->udp.port4_to_ns); - procfs_scan_listen("udp6", c->udp.port4_to_ns); - } - - procfs_scan_listen("tcp6", c->tcp.port6_to_tap); - procfs_scan_listen("tcp6", c->udp.port6_to_tap); - procfs_scan_listen("udp6", c->udp.port6_to_tap); - - procfs_scan_listen("tcp6", c->tcp.port6_to_ns); - procfs_scan_listen("tcp6", c->udp.port6_to_ns); - procfs_scan_listen("udp6", c->udp.port6_to_ns); - } - - return 0; -} - -/** - * get_bound_ports() - Get maps of ports that should have bound sockets - * @c: Execution context - */ -static void get_bound_ports(struct ctx *c) -{ - char ns_fn_stack[NS_FN_STACK_SIZE]; - - clone(get_bound_ports_ns, ns_fn_stack + sizeof(ns_fn_stack) / 2, - CLONE_VM | CLONE_VFORK | CLONE_FILES | SIGCHLD, (void *)c); - - if (c->v4) { - procfs_scan_listen("tcp", c->tcp.port4_to_init); - procfs_scan_listen("tcp", c->udp.port4_to_init); - procfs_scan_listen("udp", c->udp.port4_to_init); - } - - if (c->v6) { - if (c->v4) { - procfs_scan_listen("tcp6", c->tcp.port4_to_init); - procfs_scan_listen("tcp6", c->udp.port4_to_init); - procfs_scan_listen("udp6", c->udp.port4_to_init); - } - - procfs_scan_listen("tcp6", c->tcp.port6_to_init); - procfs_scan_listen("tcp6", c->udp.port6_to_init); - procfs_scan_listen("udp6", c->udp.port6_to_init); - - } -} /** * sock_handler() - Event handler for L4 sockets @@ -401,11 +87,12 @@ static void sock_handler(struct ctx *c, union epoll_ref ref, uint32_t events, { debug("%s packet from socket %i", IP_PROTO_STR(ref.proto), ref.s); - if (ref.proto == IPPROTO_TCP) + if (!c->no_tcp && ref.proto == IPPROTO_TCP) tcp_sock_handler( c, ref, events, now); - else if (ref.proto == IPPROTO_UDP) + else if (!c->no_udp && ref.proto == IPPROTO_UDP) udp_sock_handler( c, ref, events, now); - else if (ref.proto == IPPROTO_ICMP || ref.proto == IPPROTO_ICMPV6) + else if (!c->no_icmp && + (ref.proto == IPPROTO_ICMP || ref.proto == IPPROTO_ICMPV6)) icmp_sock_handler(c, ref, events, now); } @@ -416,17 +103,20 @@ static void sock_handler(struct ctx *c, union epoll_ref ref, uint32_t events, */ static void timer_handler(struct ctx *c, struct timespec *now) { - if (timespec_diff_ms(now, &c->tcp.timer_run) >= TCP_TIMER_INTERVAL) { + if (!c->no_tcp && + timespec_diff_ms(now, &c->tcp.timer_run) >= TCP_TIMER_INTERVAL) { tcp_timer(c, now); c->tcp.timer_run = *now; } - if (timespec_diff_ms(now, &c->udp.timer_run) >= UDP_TIMER_INTERVAL) { + if (!c->no_udp && + timespec_diff_ms(now, &c->udp.timer_run) >= UDP_TIMER_INTERVAL) { udp_timer(c, now); c->udp.timer_run = *now; } - if (timespec_diff_ms(now, &c->icmp.timer_run) >= ICMP_TIMER_INTERVAL) { + if (!c->no_icmp && + timespec_diff_ms(now, &c->icmp.timer_run) >= ICMP_TIMER_INTERVAL) { icmp_timer(c, now); c->icmp.timer_run = *now; } @@ -445,68 +135,115 @@ void proto_update_l2_buf(unsigned char *eth_d, unsigned char *eth_s, udp_update_l2_buf(eth_d, eth_s, ip_da); } +static int pasta_child_pid; + /** - * usage_passt() - Print usage for "passt" mode and exit - * @name: Executable name + * pasta_child_handler() - Exit once shell spawned by pasta_start_ns() exits + * @signal: Unused, handler deals with SIGCHLD only */ -void usage_passt(const char *name) +static void pasta_child_handler(int signal) { - fprintf(stderr, "Usage: %s\n", name); + siginfo_t infop; - exit(EXIT_FAILURE); + (void)signal; + + if (!waitid(P_PID, pasta_child_pid, &infop, WEXITED | WNOHANG)) { + if (infop.si_pid == pasta_child_pid) + exit(EXIT_SUCCESS); + } } /** - * usage_pasta() - Print usage for "pasta" mode and exit - * @name: Executable name + * pasta_start_ns() - Fork shell in new namespace if target PID is not given + * @c: Execution context */ -void usage_pasta(const char *name) +static void pasta_start_ns(struct ctx *c) { - fprintf(stderr, "Usage: %s TARGET_PID\n", name); + char buf[BUFSIZ], *shell; + int euid = geteuid(); + struct sigaction sa; + int fd; + + c->foreground = 1; + if (!c->debug) + c->quiet = 1; + + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_handler = pasta_child_handler; + sigaction(SIGCHLD, &sa, NULL); + + if ((c->pasta_pid = fork()) == -1) { + perror("fork"); + exit(EXIT_FAILURE); + } + + if ((pasta_child_pid = c->pasta_pid)) + return; + if (unshare(CLONE_NEWNET | CLONE_NEWUSER)) { + perror("unshare"); + exit(EXIT_FAILURE); + } + + snprintf(buf, BUFSIZ, "%u %u %u", 0, euid, 1); + + fd = open("/proc/self/uid_map", O_WRONLY); + write(fd, buf, strlen(buf)); + close(fd); + + fd = open("/proc/self/setgroups", O_WRONLY); + write(fd, "deny", sizeof("deny")); + close(fd); + + fd = open("/proc/self/gid_map", O_WRONLY); + write(fd, buf, strlen(buf)); + close(fd); + + shell = getenv("SHELL") ? getenv("SHELL") : "/bin/sh"; + if (strstr(shell, "/bash")) + execve(shell, ((char *[]) { shell, "-l", NULL }), environ); + else + execve(shell, ((char *[]) { shell, NULL }), environ); + + perror("execve"); exit(EXIT_FAILURE); } /** * main() - Entry point and main loop * @argc: Argument count - * @argv: Target PID for pasta mode + * @argv: Options, plus optional target PID for pasta mode * * Return: 0 once interrupted, non-zero on failure */ int main(int argc, char **argv) { - char buf6[INET6_ADDRSTRLEN], buf4[INET_ADDRSTRLEN], *log_name; struct epoll_event events[EPOLL_EVENTS]; struct ctx c = { 0 }; struct rlimit limit; struct timespec now; + char *log_name; int nfds, i; if (strstr(argv[0], "pasta") || strstr(argv[0], "passt4netns")) { - if (argc != 2) - usage_pasta(argv[0]); - - errno = 0; - c.pasta_pid = strtol(argv[1], NULL, 0); - if (c.pasta_pid < 0 || errno) - usage_pasta(argv[0]); - c.mode = MODE_PASTA; log_name = "pasta"; } else { - if (argc != 1) - usage_passt(argv[0]); - c.mode = MODE_PASST; log_name = "passt"; - memset(&c.mac_guest, 0xff, sizeof(c.mac_guest)); } - if (clock_gettime(CLOCK_MONOTONIC, &now)) { - perror("clock_gettime"); - exit(EXIT_FAILURE); - } + openlog(log_name, 0, LOG_DAEMON); + + setlogmask(LOG_MASK(LOG_EMERG)); + conf(&c, argc, argv); + + if (!c.debug && (c.stderr || isatty(fileno(stdout)))) + openlog(log_name, LOG_PERROR, LOG_DAEMON); + + if (c.mode == MODE_PASTA && !c.pasta_pid) + pasta_start_ns(&c); c.epollfd = epoll_create1(0); if (c.epollfd == -1) { @@ -524,85 +261,26 @@ int main(int argc, char **argv) exit(EXIT_FAILURE); } -#if DEBUG - openlog(log_name, 0, LOG_DAEMON); -#else - openlog(log_name, isatty(fileno(stdout)) ? 0 : LOG_PERROR, LOG_DAEMON); -#endif - - get_routes(&c); - get_addrs(&c); - get_dns(&c); - - if (c.mode == MODE_PASST) { - memset(&c.tcp.port4_to_tap, 0xff, PORT_EPHEMERAL_MIN / 8); - memset(&c.tcp.port6_to_tap, 0xff, PORT_EPHEMERAL_MIN / 8); - memset(&c.udp.port4_to_tap, 0xff, PORT_EPHEMERAL_MIN / 8); - memset(&c.udp.port6_to_tap, 0xff, PORT_EPHEMERAL_MIN / 8); - } else { - get_bound_ports(&c); - } - proto_update_l2_buf(c.mac_guest, c.mac, &c.addr4); - if (udp_sock_init(&c) || tcp_sock_init(&c)) + tap_sock_init(&c); + + if ((!c.no_udp && udp_sock_init(&c)) || + (!c.no_tcp && tcp_sock_init(&c))) exit(EXIT_FAILURE); - if (c.v6) + if (c.v6 && !c.no_dhcpv6) dhcpv6_init(&c); - if (c.v4) { - info("ARP:"); - info(" address: %02x:%02x:%02x:%02x:%02x:%02x from %s", - c.mac[0], c.mac[1], c.mac[2], c.mac[3], c.mac[4], c.mac[5], - c.ifn); - info("DHCP:"); - info(" assign: %s", - inet_ntop(AF_INET, &c.addr4, buf4, sizeof(buf4))); - info(" mask: %s", - inet_ntop(AF_INET, &c.mask4, buf4, sizeof(buf4))); - info(" router: %s", - inet_ntop(AF_INET, &c.gw4, buf4, sizeof(buf4))); - for (i = 0; c.dns4[i]; i++) { - if (!i) - info(" DNS:"); - inet_ntop(AF_INET, &c.dns4[i], buf4, sizeof(buf4)); - info(" %s", buf4); - } - for (i = 0; *c.dns_search[i].n; i++) { - if (!i) - info(" search:"); - info(" %s", c.dns_search[i].n); - } - } - if (c.v6) { - info("NDP/DHCPv6:"); - info(" assign: %s", - inet_ntop(AF_INET6, &c.addr6, buf6, sizeof(buf6))); - info(" router: %s", - inet_ntop(AF_INET6, &c.gw6, buf6, sizeof(buf6))); - for (i = 0; !IN6_IS_ADDR_UNSPECIFIED(&c.dns6[i]); i++) { - if (!i) - info(" DNS:"); - inet_ntop(AF_INET6, &c.dns6[i], buf6, sizeof(buf6)); - info(" %s", buf6); - } - for (i = 0; *c.dns_search[i].n; i++) { - if (!i) - info(" search:"); - info(" %s", c.dns_search[i].n); - } - } - - tap_sock_init(&c); - -#ifndef DEBUG - if (isatty(fileno(stdout)) && daemon(0, 0)) { - fprintf(stderr, "Failed to fork into background\n"); - exit(EXIT_FAILURE); - } -#endif + if (c.debug) + setlogmask(LOG_UPTO(LOG_DEBUG)); + else if (c.quiet) + setlogmask(LOG_UPTO(LOG_ERR)); + else + setlogmask(LOG_UPTO(LOG_INFO)); + if (isatty(fileno(stdout)) && !c.foreground) + daemon(0, 0); loop: nfds = epoll_wait(c.epollfd, events, EPOLL_EVENTS, TIMER_INTERVAL); if (nfds == -1 && errno != EINTR) { |