diff options
-rw-r--r-- | udp.c | 60 | ||||
-rw-r--r-- | util.c | 29 | ||||
-rw-r--r-- | util.h | 3 |
3 files changed, 92 insertions, 0 deletions
@@ -110,6 +110,7 @@ #include <sys/socket.h> #include <sys/uio.h> #include <time.h> +#include <linux/errqueue.h> #include "checksum.h" #include "util.h" @@ -729,6 +730,59 @@ static void udp_tap_prepare(const struct ctx *c, const struct mmsghdr *mmh, } /** + * udp_sock_recverr() - Receive and clear an error from a socket + * @s: Socket to receive from + * + * Return: true if errors received and processed, false if no more errors + * + * #syscalls recvmsg + */ +static bool udp_sock_recverr(int s) +{ + const struct sock_extended_err *ee; + const struct cmsghdr *hdr; + char buf[CMSG_SPACE(sizeof(*ee))]; + struct msghdr mh = { + .msg_name = NULL, + .msg_namelen = 0, + .msg_iov = NULL, + .msg_iovlen = 0, + .msg_control = buf, + .msg_controllen = sizeof(buf), + }; + ssize_t rc; + + rc = recvmsg(s, &mh, MSG_ERRQUEUE); + if (rc < 0) { + if (errno != EAGAIN && errno != EWOULDBLOCK) + err_perror("Failed to read error queue"); + return false; + } + + if (!(mh.msg_flags & MSG_ERRQUEUE)) { + err("Missing MSG_ERRQUEUE flag reading error queue"); + return false; + } + + hdr = CMSG_FIRSTHDR(&mh); + if (!((hdr->cmsg_level == IPPROTO_IP && + hdr->cmsg_type == IP_RECVERR) || + (hdr->cmsg_level == IPPROTO_IPV6 && + hdr->cmsg_type == IPV6_RECVERR))) { + err("Unexpected cmsg reading error queue"); + return false; + } + + ee = (const struct sock_extended_err *)CMSG_DATA(hdr); + + /* TODO: When possible propagate and otherwise handle errors */ + debug("%s error on UDP socket %i: %s", + str_ee_origin(ee), s, strerror(ee->ee_errno)); + + return true; +} + +/** * udp_sock_recv() - Receive datagrams from a socket * @c: Execution context * @s: Socket to receive from @@ -751,6 +805,12 @@ static int udp_sock_recv(const struct ctx *c, int s, uint32_t events, ASSERT(!c->no_udp); + /* Clear any errors first */ + if (events & EPOLLERR) { + while (udp_sock_recverr(s)) + ; + } + if (!(events & EPOLLIN)) return 0; @@ -25,6 +25,7 @@ #include <time.h> #include <errno.h> #include <stdbool.h> +#include <linux/errqueue.h> #include "util.h" #include "iov.h" @@ -97,6 +98,14 @@ static int sock_l4_sa(const struct ctx *c, enum epoll_type type, if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &y, sizeof(y))) debug("Failed to set SO_REUSEADDR on socket %i", fd); + if (proto == IPPROTO_UDP) { + int level = af == AF_INET ? IPPROTO_IP : IPPROTO_IPV6; + int opt = af == AF_INET ? IP_RECVERR : IPV6_RECVERR; + + if (setsockopt(fd, level, opt, &y, sizeof(y))) + die_perror("Failed to set RECVERR on socket %i", fd); + } + if (ifname && *ifname) { /* Supported since kernel version 5.7, commit c427bfec18f2 * ("net: core: enable SO_BINDTODEVICE for non-root users"). If @@ -654,3 +663,23 @@ const char *sockaddr_ntop(const void *sa, char *dst, socklen_t size) return dst; } + +/** str_ee_origin() - Convert socket extended error origin to a string + * @ee: Socket extended error structure + * + * Return: Static string describing error origin + */ +const char *str_ee_origin(const struct sock_extended_err *ee) +{ + const char *const desc[] = { + [SO_EE_ORIGIN_NONE] = "<no origin>", + [SO_EE_ORIGIN_LOCAL] = "Local", + [SO_EE_ORIGIN_ICMP] = "ICMP", + [SO_EE_ORIGIN_ICMP6] = "ICMPv6", + }; + + if (ee->ee_origin < ARRAY_SIZE(desc)) + return desc[ee->ee_origin]; + + return "<invalid>"; +} @@ -194,7 +194,10 @@ static inline const char *af_name(sa_family_t af) #define SOCKADDR_STRLEN MAX(SOCKADDR_INET_STRLEN, SOCKADDR_INET6_STRLEN) +struct sock_extended_err; + const char *sockaddr_ntop(const void *sa, char *dst, socklen_t size); +const char *str_ee_origin(const struct sock_extended_err *ee); /** * mod_sub() - Modular arithmetic subtraction |