From 1d807fc720bda7ec446c683bbc1a5e32897ca04e Mon Sep 17 00:00:00 2001 From: Stefano Brivio Date: Wed, 17 Mar 2021 10:57:44 +0100 Subject: [PATCH] passt: Introduce ICMP echo proxy It's nice to be able to confirm connectivity using ICMP or ICMPv6 echo requests, and "ping" sockets on Linux (IPPROTO_ICMP datagram) allow us to do that without any special capability. Signed-off-by: Stefano Brivio --- Makefile | 4 +- doc/demo.sh | 1 + icmp.c | 136 ++++++++++++++++++++++++++++++++++++++++++++++++++++ icmp.h | 20 ++++++++ passt.c | 24 ++++++---- passt.h | 2 + util.c | 7 ++- 7 files changed, 183 insertions(+), 11 deletions(-) create mode 100644 icmp.c create mode 100644 icmp.h diff --git a/Makefile b/Makefile index ebd5b20..e35ebd7 100644 --- a/Makefile +++ b/Makefile @@ -2,8 +2,8 @@ CFLAGS += -Wall -Wextra -pedantic all: passt qrap -passt: passt.c passt.h arp.c arp.h dhcp.c dhcp.h ndp.c ndp.h siphash.c siphash.h tap.c tap.h tcp.c tcp.h udp.c udp.h util.c util.h - $(CC) $(CFLAGS) passt.c arp.c dhcp.c ndp.c siphash.c tap.c tcp.c udp.c util.c -o passt +passt: passt.c passt.h arp.c arp.h dhcp.c dhcp.h ndp.c ndp.h siphash.c siphash.h tap.c tap.h icmp.c icmp.h tcp.c tcp.h udp.c udp.h util.c util.h + $(CC) $(CFLAGS) passt.c arp.c dhcp.c ndp.c siphash.c tap.c icmp.c tcp.c udp.c util.c -o passt qrap: qrap.c passt.h $(CC) $(CFLAGS) qrap.c -o qrap diff --git a/doc/demo.sh b/doc/demo.sh index 3735130..a5b45b5 100755 --- a/doc/demo.sh +++ b/doc/demo.sh @@ -74,6 +74,7 @@ sysctl -w net.ipv6.conf.all.forwarding=1 ethtool -K veth_passt tx off ip netns exec passt ethtool -K veth_passt tx off +ip netns exec passt sysctl -w net.ipv4.ping_group_range="0 2147483647" ulimit -n 300000 diff --git a/icmp.c b/icmp.c new file mode 100644 index 0000000..b66c329 --- /dev/null +++ b/icmp.c @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +/* PASST - Plug A Simple Socket Transport + * + * icmp.c - ICMP/ICMPv6 echo proxy + * + * Copyright (c) 2021 Red Hat GmbH + * Author: Stefano Brivio + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "passt.h" +#include "tap.h" +#include "util.h" +#include "icmp.h" + +/** + * icmp_sock_handler() - Handle new data from socket + * @c: Execution context + * @s: File descriptor number for socket + * @events: epoll events bitmap + */ +void icmp_sock_handler(struct ctx *c, int s, uint32_t events) +{ + struct in6_addr a6 = { .s6_addr = { 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0xff, 0xff, + 0, 0, 0, 0 } }; + struct sockaddr_storage sr, sl; + socklen_t slen = sizeof(sr); + char buf[USHRT_MAX]; + ssize_t n; + + (void)events; + + n = recvfrom(s, buf, sizeof(buf), MSG_DONTWAIT, + (struct sockaddr *)&sr, &slen); + if (n < 0) + return; + + if (getsockname(s, (struct sockaddr *)&sl, &slen)) + return; + + if (sl.ss_family == AF_INET) { + struct sockaddr_in *sr4 = (struct sockaddr_in *)&sr; + + memcpy(&a6.s6_addr[12], &sr4->sin_addr, sizeof(sr4->sin_addr)); + + tap_ip_send(c, &a6, IPPROTO_ICMP, buf, n); + } else if (sl.ss_family == AF_INET6) { + struct sockaddr_in6 *sr6 = (struct sockaddr_in6 *)&sr; + + tap_ip_send(c, &sr6->sin6_addr, IPPROTO_ICMPV6, buf, n); + } +} + +/** + * icmp_tap_handler() - Handle packets from tap + * @c: Execution context + * @af: Address family, AF_INET or AF_INET6 + * @in: Input buffer + * @len: Length, including UDP header + */ +void icmp_tap_handler(struct ctx *c, int af, void *addr, char *in, size_t len) +{ + if (af == AF_INET) { + struct icmphdr *ih = (struct icmphdr *)in; + struct sockaddr_in sa = { + .sin_family = AF_INET, + .sin_addr.s_addr = htonl(INADDR_ANY), + }; + + if (len < sizeof(*ih) || ih->type != ICMP_ECHO) + return; + + sa.sin_port = ih->un.echo.id; + bind(c->icmp.s4, (struct sockaddr *)&sa, sizeof(sa)); + + sa.sin_addr = *(struct in_addr *)addr; + sendto(c->icmp.s4, in, len, MSG_DONTWAIT, + (struct sockaddr *)&sa, sizeof(sa)); + } else if (af == AF_INET6) { + struct sockaddr_in6 sa = { + .sin6_family = AF_INET6, + .sin6_addr = IN6ADDR_ANY_INIT, + }; + struct icmp6hdr *ih = (struct icmp6hdr *)in; + + if (len < sizeof(*ih) || + (ih->icmp6_type != 128 && ih->icmp6_type != 129)) + return; + + sa.sin6_port = ih->icmp6_identifier; + bind(c->icmp.s6, (struct sockaddr *)&sa, sizeof(sa)); + + sa.sin6_addr = *(struct in6_addr *)addr; + sendto(c->icmp.s6, in, len, MSG_DONTWAIT | MSG_NOSIGNAL, + (struct sockaddr *)&sa, sizeof(sa)); + } +} + +/** + * icmp_sock_init() - Create ICMP, ICMPv6 sockets for echo requests and replies + * @c: Execution context + * + * Return: 0 on success, -1 on failure + */ +int icmp_sock_init(struct ctx *c) +{ + if (c->v4 && (c->icmp.s4 = sock_l4_add(c, 4, IPPROTO_ICMP, 0)) < 0) + return -1; + + if (c->v6 && (c->icmp.s6 = sock_l4_add(c, 6, IPPROTO_ICMPV6, 0)) < 0) + return -1; + + return 0; +} diff --git a/icmp.h b/icmp.h new file mode 100644 index 0000000..7820fbe --- /dev/null +++ b/icmp.h @@ -0,0 +1,20 @@ +#ifndef ICMP_H +#define ICMP_H + +struct ctx; + +void icmp_sock_handler(struct ctx *c, int s, uint32_t events); +void icmp_tap_handler(struct ctx *c, int af, void *addr, char *in, size_t len); +int icmp_sock_init(struct ctx *c); + +/** + * struct icmp_ctx - Execution context for ICMP routines + * @s4: ICMP socket number + * @s6: ICMPv6 socket number + */ +struct icmp_ctx { + int s4; + int s6; +}; + +#endif /* ICMP_H */ diff --git a/passt.c b/passt.c index c37b445..98576a6 100644 --- a/passt.c +++ b/passt.c @@ -46,6 +46,7 @@ #include "dhcp.h" #include "ndp.h" #include "util.h" +#include "icmp.h" #include "tcp.h" #include "udp.h" @@ -335,6 +336,8 @@ static void tap4_handler(struct ctx *c, char *in, size_t len) tcp_tap_handler(c, AF_INET, &iph->daddr, l4h, len); else if (iph->protocol == IPPROTO_UDP) udp_tap_handler(c, AF_INET, &iph->daddr, l4h, len); + else if (iph->protocol == IPPROTO_ICMP) + icmp_tap_handler(c, AF_INET, &iph->daddr, l4h, len); } /** @@ -391,6 +394,8 @@ static void tap6_handler(struct ctx *c, char *in, size_t len) tcp_tap_handler(c, AF_INET6, &ip6h->daddr, l4h, len); else if (proto == IPPROTO_UDP) udp_tap_handler(c, AF_INET6, &ip6h->daddr, l4h, len); + else if (proto == IPPROTO_ICMPV6) + icmp_tap_handler(c, AF_INET6, &ip6h->daddr, l4h, len); } /** @@ -449,15 +454,18 @@ static void sock_handler(struct ctx *c, int fd, uint32_t events) sl = sizeof(so); - if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &so, &sl) || - so == SOCK_STREAM) { - fprintf(stderr, "TCP: packet from socket %i\n", fd); + if (getsockopt(fd, SOL_SOCKET, SO_PROTOCOL, &so, &sl)) + return; + + fprintf(stderr, "%s: packet from socket %i\n", + getprotobynumber(so)->p_name, fd); + + if (so == IPPROTO_ICMP || so == IPPROTO_ICMPV6) + icmp_sock_handler(c, fd, events); + else if (so == IPPROTO_TCP) tcp_sock_handler(c, fd, events); - } - else if (so == SOCK_DGRAM) { + else if (so == IPPROTO_UDP) udp_sock_handler(c, fd, events); - fprintf(stderr, "UDP: packet from socket %i\n", fd); - } } /** @@ -546,7 +554,7 @@ int main(int argc, char **argv) exit(EXIT_FAILURE); } - if (tcp_sock_init(&c) || udp_sock_init(&c)) + if (icmp_sock_init(&c) || tcp_sock_init(&c) || udp_sock_init(&c)) exit(EXIT_FAILURE); fd_unix = sock_unix(); diff --git a/passt.h b/passt.h index ff16134..9844858 100644 --- a/passt.h +++ b/passt.h @@ -1,5 +1,6 @@ #define UNIX_SOCK_PATH "/tmp/passt.socket" +#include "icmp.h" #include "tcp.h" /** @@ -39,5 +40,6 @@ struct ctx { char ifn[IF_NAMESIZE]; + struct icmp_ctx icmp; struct tcp_ctx tcp; }; diff --git a/util.c b/util.c index e8ee57f..44357b7 100644 --- a/util.c +++ b/util.c @@ -160,7 +160,8 @@ int sock_l4_add(struct ctx *c, int v, uint16_t proto, uint16_t port) const struct sockaddr *sa; int fd, sl, one = 1; - if (proto != IPPROTO_TCP && proto != IPPROTO_UDP) + if (proto != IPPROTO_TCP && proto != IPPROTO_UDP && + proto != IPPROTO_ICMP && proto != IPPROTO_ICMPV6) return -1; /* Not implemented. */ fd = socket(v == 4 ? AF_INET : AF_INET6, @@ -170,6 +171,9 @@ int sock_l4_add(struct ctx *c, int v, uint16_t proto, uint16_t port) return -1; } + if (proto == IPPROTO_ICMP || proto == IPPROTO_ICMPV6) + goto epoll_add; + if (v == 4) { sa = (const struct sockaddr *)&addr4; sl = sizeof(addr4); @@ -195,6 +199,7 @@ int sock_l4_add(struct ctx *c, int v, uint16_t proto, uint16_t port) return -1; } +epoll_add: ev.events = EPOLLIN; ev.data.fd = fd; if (epoll_ctl(c->epollfd, EPOLL_CTL_ADD, fd, &ev) == -1) {