aboutgitcodebugslistschat
path: root/icmp.c
diff options
context:
space:
mode:
Diffstat (limited to 'icmp.c')
-rw-r--r--icmp.c185
1 files changed, 126 insertions, 59 deletions
diff --git a/icmp.c b/icmp.c
index 378e787..8f2fdb2 100644
--- a/icmp.c
+++ b/icmp.c
@@ -1,12 +1,15 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
/* PASST - Plug A Simple Socket Transport
+ * for qemu/UNIX domain socket mode
+ *
+ * PASTA - Pack A Subtle Tap Abstraction
+ * for network namespace/tap device mode
*
* icmp.c - ICMP/ICMPv6 echo proxy
*
* Copyright (c) 2021 Red Hat GmbH
* Author: Stefano Brivio <sbrivio@redhat.com>
- *
*/
#include <stdio.h>
@@ -28,57 +31,91 @@
#include <linux/icmpv6.h>
#include <time.h>
+#include "util.h"
#include "passt.h"
#include "tap.h"
-#include "util.h"
#include "icmp.h"
+#define ICMP_ECHO_TIMEOUT 60 /* s, timeout for ICMP socket activity */
+
+/**
+ * struct icmp_id - Tracking information for single ICMP echo identifier
+ * @sock: Bound socket for identifier
+ * @ts: Last associated activity from tap, seconds
+ * @seq: Last sequence number sent to tap, host order
+ */
+struct icmp_id {
+ int sock;
+ time_t ts;
+ uint16_t seq;
+};
+
/* Indexed by ICMP echo identifier */
-static int icmp_s_v4[USHRT_MAX];
-static int icmp_s_v6[USHRT_MAX];
+static struct icmp_id icmp_id_map [IP_VERSIONS][USHRT_MAX];
+
+/* Bitmaps, activity monitoring needed for identifier */
+static uint8_t icmp_act [IP_VERSIONS][USHRT_MAX / 8];
/**
* icmp_sock_handler() - Handle new data from socket
* @c: Execution context
- * @s: File descriptor number for socket
+ * @ref: epoll reference
* @events: epoll events bitmap
- * @pkt_buf: Buffer to receive packets, currently unused
* @now: Current timestamp, unused
*/
-void icmp_sock_handler(struct ctx *c, int s, uint32_t events, char *pkt_buf,
+void icmp_sock_handler(struct ctx *c, union epoll_ref ref, uint32_t events,
struct timespec *now)
{
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);
+ struct sockaddr_storage sr;
+ socklen_t sl = sizeof(sr);
char buf[USHRT_MAX];
+ uint16_t seq, id;
ssize_t n;
(void)events;
- (void)pkt_buf;
(void)now;
- n = recvfrom(s, buf, sizeof(buf), MSG_DONTWAIT,
- (struct sockaddr *)&sr, &slen);
+ n = recvfrom(ref.s, buf, sizeof(buf), 0, (struct sockaddr *)&sr, &sl);
if (n < 0)
return;
- if (getsockname(s, (struct sockaddr *)&sl, &slen))
- return;
+ if (ref.icmp.v6) {
+ struct sockaddr_in6 *sr6 = (struct sockaddr_in6 *)&sr;
+ struct icmp6hdr *ih = (struct icmp6hdr *)buf;
- if (sl.ss_family == AF_INET) {
+ /* In PASTA mode, we'll get any reply we send, discard them. */
+ if (c->mode == MODE_PASTA) {
+ seq = ntohs(ih->icmp6_sequence);
+ id = ntohs(ih->icmp6_identifier);
+
+ if (icmp_id_map[V6][id].seq == seq)
+ return;
+
+ icmp_id_map[V6][id].seq = seq;
+ }
+
+ tap_ip_send(c, &sr6->sin6_addr, IPPROTO_ICMPV6, buf, n);
+ } else {
struct sockaddr_in *sr4 = (struct sockaddr_in *)&sr;
+ struct icmphdr *ih = (struct icmphdr *)buf;
+
+ if (c->mode == MODE_PASTA) {
+ seq = ntohs(ih->un.echo.sequence);
+ id = ntohs(ih->un.echo.id);
+
+ if (icmp_id_map[V4][id].seq == seq)
+ return;
+
+ icmp_id_map[V4][id].seq = seq;
+ }
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);
}
}
@@ -86,101 +123,131 @@ void icmp_sock_handler(struct ctx *c, int s, uint32_t events, char *pkt_buf,
* icmp_tap_handler() - Handle packets from tap
* @c: Execution context
* @af: Address family, AF_INET or AF_INET6
+ * @
* @msg: Input message
* @count: Message count (always 1 for ICMP)
- * @now: Current timestamp, unused
+ * @now: Current timestamp
*
* Return: count of consumed packets (always 1, even if malformed)
*/
int icmp_tap_handler(struct ctx *c, int af, void *addr,
struct tap_msg *msg, int count, struct timespec *now)
{
- int s;
-
(void)count;
- (void)now;
- (void)c;
if (af == AF_INET) {
struct icmphdr *ih = (struct icmphdr *)msg[0].l4h;
+ union icmp_epoll_ref iref = { .v6 = 0 };
struct sockaddr_in sa = {
.sin_family = AF_INET,
.sin_addr = { .s_addr = INADDR_ANY },
.sin_port = ih->un.echo.id,
};
+ int id, s;
if (msg[0].l4_len < sizeof(*ih) || ih->type != ICMP_ECHO)
return 1;
- if ((s = icmp_s_v4[ntohs(ih->un.echo.id)]) < 0)
- return 1;
+ id = ntohs(ih->un.echo.id);
- bind(s, (struct sockaddr *)&sa, sizeof(sa));
+ if ((s = icmp_id_map[V4][id].sock) <= 0) {
+ s = sock_l4(c, AF_INET, IPPROTO_ICMP, id, 0, iref.u32);
+ if (s < 0)
+ goto fail_sock;
+
+ icmp_id_map[V4][id].sock = s;
+ }
+ icmp_id_map[V4][id].ts = now->tv_sec;
+ bitmap_set(icmp_act[V4], id);
sa.sin_addr = *(struct in_addr *)addr;
- sendto(s, msg[0].l4h, msg[0].l4_len,
- MSG_DONTWAIT | MSG_NOSIGNAL,
+ sendto(s, msg[0].l4h, msg[0].l4_len, MSG_NOSIGNAL,
(struct sockaddr *)&sa, sizeof(sa));
} else if (af == AF_INET6) {
struct icmp6hdr *ih = (struct icmp6hdr *)msg[0].l4h;
+ union icmp_epoll_ref iref = { .v6 = 1 };
struct sockaddr_in6 sa = {
.sin6_family = AF_INET6,
.sin6_addr = IN6ADDR_ANY_INIT,
.sin6_port = ih->icmp6_identifier,
};
+ int id, s;
if (msg[0].l4_len < sizeof(*ih) ||
(ih->icmp6_type != 128 && ih->icmp6_type != 129))
return 1;
- if ((s = icmp_s_v6[ntohs(ih->icmp6_identifier)]) < 0)
- return 1;
+ id = ntohs(ih->icmp6_identifier);
+ if ((s = icmp_id_map[V6][id].sock) <= 0) {
+ s = sock_l4(c, AF_INET6, IPPROTO_ICMPV6, id, 0,
+ iref.u32);
+ if (s < 0)
+ goto fail_sock;
- bind(s, (struct sockaddr *)&sa, sizeof(sa));
+ icmp_id_map[V6][id].sock = s;
+ }
+ icmp_id_map[V6][id].ts = now->tv_sec;
+ bitmap_set(icmp_act[V6], id);
sa.sin6_addr = *(struct in6_addr *)addr;
- sendto(s, msg[0].l4h, msg[0].l4_len,
- MSG_DONTWAIT | MSG_NOSIGNAL,
+ sendto(s, msg[0].l4h, msg[0].l4_len, MSG_NOSIGNAL,
(struct sockaddr *)&sa, sizeof(sa));
}
return 1;
+
+fail_sock:
+ warn("Cannot open \"ping\" socket. You might need to:");
+ warn(" sysctl -w net.ipv4.ping_group_range=\"0 2147483647\"");
+ warn("...echo requests/replies will fail.");
+ return 1;
}
/**
- * icmp_sock_init() - Create ICMP, ICMPv6 sockets for echo requests and replies
+ * icmp_timer_one() - Handler for timed events related to a given identifier
* @c: Execution context
- *
- * Return: 0 on success, -1 on failure
+ * @v6: Set for IPv6 echo identifier bindings
+ * @id: Echo identifier, host order
+ * @ts: Timestamp from caller
*/
-int icmp_sock_init(struct ctx *c)
+static void icmp_timer_one(struct ctx *c, int v6, uint16_t id,
+ struct timespec *ts)
{
- int i, fail = 0;
+ struct icmp_id *id_map = &icmp_id_map[v6 ? V6 : V4][id];
- c->icmp.fd_min = INT_MAX;
- c->icmp.fd_max = 0;
+ if (ts->tv_sec - id_map->ts <= ICMP_ECHO_TIMEOUT)
+ return;
- if (c->v4) {
- for (i = 0; i < USHRT_MAX; i++) {
- icmp_s_v4[i] = sock_l4(c, AF_INET, IPPROTO_ICMP, i);
- if (icmp_s_v4[i] < 0)
- fail = 1;
- }
- }
+ bitmap_clear(icmp_act[v6 ? V6 : V4], id);
+
+ epoll_ctl(c->epollfd, EPOLL_CTL_DEL, id_map->sock, NULL);
+ close(id_map->sock);
+ id_map->sock = 0;
+}
- if (c->v6) {
- for (i = 0; i < USHRT_MAX; i++) {
- icmp_s_v6[i] = sock_l4(c, AF_INET6, IPPROTO_ICMPV6, i);
- if (icmp_s_v6[i] < 0)
- fail = 1;
+/**
+ * icmp_timer() - Scan activity bitmap for identifiers with timed events
+ * @c: Execution context
+ * @ts: Timestamp from caller
+ */
+void icmp_timer(struct ctx *c, struct timespec *ts)
+{
+ long *word, tmp;
+ unsigned int i;
+ int n, v6 = 0;
+
+v6:
+ word = (long *)icmp_act[v6 ? V6 : V4];
+ for (i = 0; i < sizeof(icmp_act[0]) / sizeof(long); i++, word++) {
+ tmp = *word;
+ while ((n = ffsl(tmp))) {
+ tmp &= ~(1UL << (n - 1));
+ icmp_timer_one(c, v6, i * sizeof(long) * 8 + n - 1, ts);
}
}
- if (fail) {
- warn("Cannot open some \"ping\" sockets. You might need to:");
- warn(" sysctl -w net.ipv4.ping_group_range=\"0 2147483647\"");
- warn("...echo requests/replies might fail.");
+ if (!v6) {
+ v6 = 1;
+ goto v6;
}
-
- return 0;
}