aboutgitcodebugslistschat
path: root/passt.c
diff options
context:
space:
mode:
authorStefano Brivio <sbrivio@redhat.com>2021-08-12 15:42:43 +0200
committerStefano Brivio <sbrivio@redhat.com>2021-09-01 17:00:27 +0200
commit1e49d194d01788afbc4b8216e27c794651a4facf (patch)
tree3397d4b687a74fe9552e057138c3a795917a5afa /passt.c
parent1b1b27c06a27067a7d7a380f1df545e72268c411 (diff)
downloadpasst-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.c524
1 files changed, 101 insertions, 423 deletions
diff --git a/passt.c b/passt.c
index ff850e7..0f8ac77 100644
--- a/passt.c
+++ b/passt.c
@@ -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) {