mirror of
https://passt.top/passt
synced 2024-11-09 22:29:56 +00:00
882599e180
EPOLL_TYPE_UDP is now only used for "listening" sockets; long lived sockets which can initiate new flows. Rename to EPOLL_TYPE_UDP_LISTEN and associated functions to match. Along with that, remove the .orig field from union udp_listen_epoll_ref, since it is now always true. Signed-off-by: David Gibson <david@gibson.dropbear.id.au> Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
687 lines
15 KiB
C
687 lines
15 KiB
C
// SPDX-License-Identifier: GPL-2.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
|
|
*
|
|
* util.c - Convenience helpers
|
|
*
|
|
* Copyright (c) 2020-2021 Red Hat GmbH
|
|
* Author: Stefano Brivio <sbrivio@redhat.com>
|
|
*/
|
|
|
|
#include <sched.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <arpa/inet.h>
|
|
#include <net/ethernet.h>
|
|
#include <sys/epoll.h>
|
|
#include <sys/uio.h>
|
|
#include <fcntl.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <errno.h>
|
|
#include <stdbool.h>
|
|
#include <linux/errqueue.h>
|
|
|
|
#include "util.h"
|
|
#include "iov.h"
|
|
#include "passt.h"
|
|
#include "packet.h"
|
|
#include "log.h"
|
|
|
|
/**
|
|
* sock_l4_sa() - Create and bind socket to socket address, add to epoll list
|
|
* @c: Execution context
|
|
* @type: epoll type
|
|
* @sa: Socket address to bind to
|
|
* @sl: Length of @sa
|
|
* @ifname: Interface for binding, NULL for any
|
|
* @v6only: Set IPV6_V6ONLY socket option
|
|
* @data: epoll reference portion for protocol handlers
|
|
*
|
|
* Return: newly created socket, negative error code on failure
|
|
*/
|
|
int sock_l4_sa(const struct ctx *c, enum epoll_type type,
|
|
const void *sa, socklen_t sl,
|
|
const char *ifname, bool v6only, uint32_t data)
|
|
{
|
|
sa_family_t af = ((const struct sockaddr *)sa)->sa_family;
|
|
union epoll_ref ref = { .type = type, .data = data };
|
|
struct epoll_event ev;
|
|
int fd, y = 1, ret;
|
|
uint8_t proto;
|
|
int socktype;
|
|
|
|
switch (type) {
|
|
case EPOLL_TYPE_TCP_LISTEN:
|
|
proto = IPPROTO_TCP;
|
|
socktype = SOCK_STREAM | SOCK_NONBLOCK;
|
|
break;
|
|
case EPOLL_TYPE_UDP_LISTEN:
|
|
case EPOLL_TYPE_UDP_REPLY:
|
|
proto = IPPROTO_UDP;
|
|
socktype = SOCK_DGRAM | SOCK_NONBLOCK;
|
|
break;
|
|
case EPOLL_TYPE_PING:
|
|
if (af == AF_INET)
|
|
proto = IPPROTO_ICMP;
|
|
else
|
|
proto = IPPROTO_ICMPV6;
|
|
socktype = SOCK_DGRAM | SOCK_NONBLOCK;
|
|
break;
|
|
default:
|
|
ASSERT(0);
|
|
}
|
|
|
|
fd = socket(af, socktype, proto);
|
|
|
|
ret = -errno;
|
|
if (fd < 0) {
|
|
warn("L4 socket: %s", strerror(-ret));
|
|
return ret;
|
|
}
|
|
|
|
if (fd > FD_REF_MAX) {
|
|
close(fd);
|
|
return -EBADF;
|
|
}
|
|
|
|
ref.fd = fd;
|
|
|
|
if (v6only)
|
|
if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &y, sizeof(y)))
|
|
debug("Failed to set IPV6_V6ONLY on socket %i", fd);
|
|
|
|
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
|
|
* it's unsupported, don't bind the socket at all, because the
|
|
* user might rely on this to filter incoming connections.
|
|
*/
|
|
if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE,
|
|
ifname, strlen(ifname))) {
|
|
char str[SOCKADDR_STRLEN];
|
|
|
|
ret = -errno;
|
|
warn("Can't bind %s socket for %s to %s, closing",
|
|
EPOLL_TYPE_STR(proto),
|
|
sockaddr_ntop(sa, str, sizeof(str)), ifname);
|
|
close(fd);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
if (bind(fd, sa, sl) < 0) {
|
|
/* We'll fail to bind to low ports if we don't have enough
|
|
* capabilities, and we'll fail to bind on already bound ports,
|
|
* this is fine. This might also fail for ICMP because of a
|
|
* broken SELinux policy, see icmp_tap_handler().
|
|
*/
|
|
if (type != EPOLL_TYPE_PING) {
|
|
ret = -errno;
|
|
close(fd);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
if (type == EPOLL_TYPE_TCP_LISTEN && listen(fd, 128) < 0) {
|
|
ret = -errno;
|
|
warn("TCP socket listen: %s", strerror(-ret));
|
|
close(fd);
|
|
return ret;
|
|
}
|
|
|
|
ev.events = EPOLLIN;
|
|
ev.data.u64 = ref.u64;
|
|
if (epoll_ctl(c->epollfd, EPOLL_CTL_ADD, fd, &ev) == -1) {
|
|
ret = -errno;
|
|
warn("L4 epoll_ctl: %s", strerror(-ret));
|
|
return ret;
|
|
}
|
|
|
|
return fd;
|
|
}
|
|
/**
|
|
* sock_l4() - Create and bind socket for given L4, add to epoll list
|
|
* @c: Execution context
|
|
* @af: Address family, AF_INET or AF_INET6
|
|
* @type: epoll type
|
|
* @bind_addr: Address for binding, NULL for any
|
|
* @ifname: Interface for binding, NULL for any
|
|
* @port: Port, host order
|
|
* @data: epoll reference portion for protocol handlers
|
|
*
|
|
* Return: newly created socket, negative error code on failure
|
|
*/
|
|
int sock_l4(const struct ctx *c, sa_family_t af, enum epoll_type type,
|
|
const void *bind_addr, const char *ifname, uint16_t port,
|
|
uint32_t data)
|
|
{
|
|
switch (af) {
|
|
case AF_INET: {
|
|
struct sockaddr_in addr4 = {
|
|
.sin_family = AF_INET,
|
|
.sin_port = htons(port),
|
|
{ 0 }, { 0 },
|
|
};
|
|
if (bind_addr)
|
|
addr4.sin_addr = *(struct in_addr *)bind_addr;
|
|
return sock_l4_sa(c, type, &addr4, sizeof(addr4), ifname,
|
|
false, data);
|
|
}
|
|
|
|
case AF_UNSPEC:
|
|
if (!DUAL_STACK_SOCKETS || bind_addr)
|
|
return -EINVAL;
|
|
/* fallthrough */
|
|
case AF_INET6: {
|
|
struct sockaddr_in6 addr6 = {
|
|
.sin6_family = AF_INET6,
|
|
.sin6_port = htons(port),
|
|
0, IN6ADDR_ANY_INIT, 0,
|
|
};
|
|
if (bind_addr) {
|
|
addr6.sin6_addr = *(struct in6_addr *)bind_addr;
|
|
|
|
if (!memcmp(bind_addr, &c->ip6.addr_ll,
|
|
sizeof(c->ip6.addr_ll)))
|
|
addr6.sin6_scope_id = c->ifi6;
|
|
}
|
|
return sock_l4_sa(c, type, &addr6, sizeof(addr6), ifname,
|
|
af == AF_INET6, data);
|
|
}
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* sock_probe_mem() - Check if setting high SO_SNDBUF and SO_RCVBUF is allowed
|
|
* @c: Execution context
|
|
*/
|
|
void sock_probe_mem(struct ctx *c)
|
|
{
|
|
int v = INT_MAX / 2, s;
|
|
socklen_t sl;
|
|
|
|
if ((s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
|
|
c->low_wmem = c->low_rmem = 1;
|
|
return;
|
|
}
|
|
|
|
sl = sizeof(v);
|
|
if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, &v, sizeof(v)) ||
|
|
getsockopt(s, SOL_SOCKET, SO_SNDBUF, &v, &sl) ||
|
|
(size_t)v < SNDBUF_BIG)
|
|
c->low_wmem = 1;
|
|
|
|
v = INT_MAX / 2;
|
|
if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, &v, sizeof(v)) ||
|
|
getsockopt(s, SOL_SOCKET, SO_RCVBUF, &v, &sl) ||
|
|
(size_t)v < RCVBUF_BIG)
|
|
c->low_rmem = 1;
|
|
|
|
close(s);
|
|
}
|
|
|
|
|
|
/**
|
|
* timespec_diff_ms() - Report difference in milliseconds between two timestamps
|
|
* @a: Minuend timestamp
|
|
* @b: Subtrahend timestamp
|
|
*
|
|
* Return: difference in milliseconds
|
|
*/
|
|
long timespec_diff_ms(const struct timespec *a, const struct timespec *b)
|
|
{
|
|
if (a->tv_nsec < b->tv_nsec) {
|
|
return (b->tv_nsec - a->tv_nsec) / 1000000 +
|
|
(a->tv_sec - b->tv_sec - 1) * 1000;
|
|
}
|
|
|
|
return (a->tv_nsec - b->tv_nsec) / 1000000 +
|
|
(a->tv_sec - b->tv_sec) * 1000;
|
|
}
|
|
|
|
/**
|
|
* bitmap_set() - Set single bit in bitmap
|
|
* @map: Pointer to bitmap
|
|
* @bit: Bit number to set
|
|
*/
|
|
void bitmap_set(uint8_t *map, unsigned bit)
|
|
{
|
|
unsigned long *word = (unsigned long *)map + BITMAP_WORD(bit);
|
|
|
|
*word |= BITMAP_BIT(bit);
|
|
}
|
|
|
|
/**
|
|
* bitmap_clear() - Clear single bit in bitmap
|
|
* @map: Pointer to bitmap
|
|
* @bit: Bit number to clear
|
|
*/
|
|
void bitmap_clear(uint8_t *map, unsigned bit)
|
|
{
|
|
unsigned long *word = (unsigned long *)map + BITMAP_WORD(bit);
|
|
|
|
*word &= ~BITMAP_BIT(bit);
|
|
}
|
|
|
|
/**
|
|
* bitmap_isset() - Check for set bit in bitmap
|
|
* @map: Pointer to bitmap
|
|
* @bit: Bit number to check
|
|
*
|
|
* Return: true if given bit is set, false if it's not
|
|
*/
|
|
bool bitmap_isset(const uint8_t *map, unsigned bit)
|
|
{
|
|
const unsigned long *word
|
|
= (const unsigned long *)map + BITMAP_WORD(bit);
|
|
|
|
return !!(*word & BITMAP_BIT(bit));
|
|
}
|
|
|
|
/**
|
|
* bitmap_or() - Logical disjunction (OR) of two bitmaps
|
|
* @dst: Pointer to result bitmap
|
|
* @size: Size of bitmaps, in bytes
|
|
* @a: First operand
|
|
* @b: Second operand
|
|
*/
|
|
void bitmap_or(uint8_t *dst, size_t size, const uint8_t *a, const uint8_t *b)
|
|
{
|
|
unsigned long *dw = (unsigned long *)dst;
|
|
unsigned long *aw = (unsigned long *)a;
|
|
unsigned long *bw = (unsigned long *)b;
|
|
size_t i;
|
|
|
|
for (i = 0; i < size / sizeof(long); i++, dw++, aw++, bw++)
|
|
*dw = *aw | *bw;
|
|
|
|
for (i = size / sizeof(long) * sizeof(long); i < size; i++)
|
|
dst[i] = a[i] | b[i];
|
|
}
|
|
|
|
/*
|
|
* ns_enter() - Enter configured user (unless already joined) and network ns
|
|
* @c: Execution context
|
|
*
|
|
* Won't return on failure
|
|
*
|
|
* #syscalls:pasta setns
|
|
*/
|
|
void ns_enter(const struct ctx *c)
|
|
{
|
|
if (setns(c->pasta_netns_fd, CLONE_NEWNET))
|
|
die_perror("setns() failed entering netns");
|
|
}
|
|
|
|
/**
|
|
* ns_is_init() - Is the caller running in the "init" user namespace?
|
|
*
|
|
* Return: true if caller is in init, false otherwise, won't return on failure
|
|
*/
|
|
bool ns_is_init(void)
|
|
{
|
|
const char root_uid_map[] = " 0 0 4294967295\n";
|
|
char buf[sizeof(root_uid_map)] = { 0 };
|
|
bool ret = true;
|
|
int fd;
|
|
|
|
if ((fd = open("/proc/self/uid_map", O_RDONLY | O_CLOEXEC)) < 0)
|
|
die_perror("Can't determine if we're in init namespace");
|
|
|
|
if (read(fd, buf, sizeof(root_uid_map)) != sizeof(root_uid_map) - 1 ||
|
|
strncmp(buf, root_uid_map, sizeof(root_uid_map)))
|
|
ret = false;
|
|
|
|
close(fd);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* struct open_in_ns_args - Parameters for do_open_in_ns()
|
|
* @c: Execution context
|
|
* @fd: Filled in with return value from open()
|
|
* @err: Filled in with errno if open() failed
|
|
* @path: Path to open
|
|
* @flags: open() flags
|
|
*/
|
|
struct open_in_ns_args {
|
|
const struct ctx *c;
|
|
int fd;
|
|
int err;
|
|
const char *path;
|
|
int flags;
|
|
};
|
|
|
|
/**
|
|
* do_open_in_ns() - Enter namespace and open a file
|
|
* @arg: See struct open_in_ns_args
|
|
*
|
|
* Must be called via NS_CALL()
|
|
*/
|
|
static int do_open_in_ns(void *arg)
|
|
{
|
|
struct open_in_ns_args *a = (struct open_in_ns_args *)arg;
|
|
|
|
ns_enter(a->c);
|
|
|
|
a->fd = open(a->path, a->flags);
|
|
a->err = errno;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* open_in_ns() - open() within the pasta namespace
|
|
* @c: Execution context
|
|
* @path: Path to open
|
|
* @flags: open() flags
|
|
*
|
|
* Return: fd of open()ed file or -1 on error, errno is set to indicate error
|
|
*/
|
|
int open_in_ns(const struct ctx *c, const char *path, int flags)
|
|
{
|
|
struct open_in_ns_args arg = {
|
|
.c = c, .path = path, .flags = flags,
|
|
};
|
|
|
|
NS_CALL(do_open_in_ns, &arg);
|
|
errno = arg.err;
|
|
return arg.fd;
|
|
}
|
|
|
|
/**
|
|
* pidfile_write() - Write PID to file, if requested to do so, and close it
|
|
* @fd: Open PID file descriptor, closed on exit, -1 to skip writing it
|
|
* @pid: PID value to write
|
|
*/
|
|
void pidfile_write(int fd, pid_t pid)
|
|
{
|
|
char pid_buf[12];
|
|
int n;
|
|
|
|
if (fd == -1)
|
|
return;
|
|
|
|
n = snprintf(pid_buf, sizeof(pid_buf), "%i\n", pid);
|
|
|
|
if (write(fd, pid_buf, n) < 0) {
|
|
perror("PID file write");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
close(fd);
|
|
}
|
|
|
|
/**
|
|
* pidfile_open() - Open PID file if needed
|
|
* @path: Path for PID file, empty string if no PID file is requested
|
|
*
|
|
* Return: descriptor for PID file, -1 if path is NULL, won't return on failure
|
|
*/
|
|
int pidfile_open(const char *path)
|
|
{
|
|
int fd;
|
|
|
|
if (!*path)
|
|
return -1;
|
|
|
|
if ((fd = open(path, O_CREAT | O_TRUNC | O_WRONLY | O_CLOEXEC,
|
|
S_IRUSR | S_IWUSR)) < 0) {
|
|
perror("PID file open");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
return fd;
|
|
}
|
|
|
|
/**
|
|
* __daemon() - daemon()-like function writing PID file before parent exits
|
|
* @pidfile_fd: Open PID file descriptor
|
|
* @devnull_fd: Open file descriptor for /dev/null
|
|
*
|
|
* Return: child PID on success, won't return on failure
|
|
*/
|
|
int __daemon(int pidfile_fd, int devnull_fd)
|
|
{
|
|
pid_t pid = fork();
|
|
|
|
if (pid == -1) {
|
|
perror("fork");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (pid) {
|
|
pidfile_write(pidfile_fd, pid);
|
|
exit(EXIT_SUCCESS);
|
|
}
|
|
|
|
errno = 0;
|
|
|
|
setsid();
|
|
|
|
dup2(devnull_fd, STDIN_FILENO);
|
|
dup2(devnull_fd, STDOUT_FILENO);
|
|
dup2(devnull_fd, STDERR_FILENO);
|
|
close(devnull_fd);
|
|
|
|
if (errno)
|
|
exit(EXIT_FAILURE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* fls() - Find last (most significant) bit set in word
|
|
* @x: Word
|
|
*
|
|
* Return: position of most significant bit set, starting from 0, -1 if none
|
|
*/
|
|
int fls(unsigned long x)
|
|
{
|
|
int y = 0;
|
|
|
|
if (!x)
|
|
return -1;
|
|
|
|
while (x >>= 1)
|
|
y++;
|
|
|
|
return y;
|
|
}
|
|
|
|
/**
|
|
* write_file() - Replace contents of file with a string
|
|
* @path: File to write
|
|
* @buf: String to write
|
|
*
|
|
* Return: 0 on success, -1 on any error
|
|
*/
|
|
int write_file(const char *path, const char *buf)
|
|
{
|
|
int fd = open(path, O_WRONLY | O_TRUNC | O_CLOEXEC);
|
|
size_t len = strlen(buf);
|
|
|
|
if (fd < 0) {
|
|
warn_perror("Could not open %s", path);
|
|
return -1;
|
|
}
|
|
|
|
while (len) {
|
|
ssize_t rc = write(fd, buf, len);
|
|
|
|
if (rc <= 0) {
|
|
warn_perror("Couldn't write to %s", path);
|
|
break;
|
|
}
|
|
|
|
buf += rc;
|
|
len -= rc;
|
|
}
|
|
|
|
close(fd);
|
|
return len == 0 ? 0 : -1;
|
|
}
|
|
|
|
#ifdef __ia64__
|
|
/* Needed by do_clone() below: glibc doesn't export the prototype of __clone2(),
|
|
* use the description from clone(2).
|
|
*/
|
|
int __clone2(int (*fn)(void *), void *stack_base, size_t stack_size, int flags,
|
|
void *arg, ... /* pid_t *parent_tid, struct user_desc *tls,
|
|
pid_t *child_tid */ );
|
|
#endif
|
|
|
|
/**
|
|
* do_clone() - Wrapper of __clone2() for ia64, clone() for other architectures
|
|
* @fn: Entry point for child
|
|
* @stack_area: Stack area for child: we'll point callees to the middle of it
|
|
* @stack_size: Total size of stack area, passed to callee divided by two
|
|
* @flags: clone() system call flags
|
|
* @arg: Argument to @fn
|
|
*
|
|
* Return: thread ID of child, -1 on failure
|
|
*/
|
|
int do_clone(int (*fn)(void *), char *stack_area, size_t stack_size, int flags,
|
|
void *arg)
|
|
{
|
|
#ifdef __ia64__
|
|
return __clone2(fn, stack_area + stack_size / 2, stack_size / 2,
|
|
flags, arg);
|
|
#else
|
|
return clone(fn, stack_area + stack_size / 2, flags, arg);
|
|
#endif
|
|
}
|
|
|
|
/* write_remainder() - write the tail of an IO vector to an fd
|
|
* @fd: File descriptor
|
|
* @iov: IO vector
|
|
* @iovcnt: Number of entries in @iov
|
|
* @skip: Number of bytes of the vector to skip writing
|
|
*
|
|
* Return: 0 on success, -1 on error (with errno set)
|
|
*
|
|
* #syscalls write writev
|
|
*/
|
|
int write_remainder(int fd, const struct iovec *iov, int iovcnt, size_t skip)
|
|
{
|
|
int i;
|
|
size_t offset;
|
|
|
|
while ((i = iov_skip_bytes(iov, iovcnt, skip, &offset)) < iovcnt) {
|
|
ssize_t rc;
|
|
|
|
if (offset) {
|
|
rc = write(fd, (char *)iov[i].iov_base + offset,
|
|
iov[i].iov_len - offset);
|
|
} else {
|
|
rc = writev(fd, &iov[i], iovcnt - i);
|
|
}
|
|
|
|
if (rc < 0)
|
|
return -1;
|
|
|
|
skip += rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/** sockaddr_ntop() - Convert a socket address to text format
|
|
* @sa: Socket address
|
|
* @dst: output buffer, minimum SOCKADDR_STRLEN bytes
|
|
* @size: size of buffer at @dst
|
|
*
|
|
* Return: On success, a non-null pointer to @dst, NULL on failure
|
|
*/
|
|
const char *sockaddr_ntop(const void *sa, char *dst, socklen_t size)
|
|
{
|
|
sa_family_t family = ((const struct sockaddr *)sa)->sa_family;
|
|
socklen_t off = 0;
|
|
|
|
#define IPRINTF(...) \
|
|
do { \
|
|
off += snprintf(dst + off, size - off, __VA_ARGS__); \
|
|
if (off >= size) \
|
|
return NULL; \
|
|
} while (0)
|
|
|
|
#define INTOP(af, addr) \
|
|
do { \
|
|
if (!inet_ntop((af), (addr), dst + off, size - off)) \
|
|
return NULL; \
|
|
off += strlen(dst + off); \
|
|
} while (0)
|
|
|
|
switch (family) {
|
|
case AF_UNSPEC:
|
|
IPRINTF("<unspecified>");
|
|
break;
|
|
|
|
case AF_INET: {
|
|
const struct sockaddr_in *sa4 = sa;
|
|
|
|
INTOP(AF_INET, &sa4->sin_addr);
|
|
IPRINTF(":%hu", ntohs(sa4->sin_port));
|
|
break;
|
|
}
|
|
|
|
case AF_INET6: {
|
|
const struct sockaddr_in6 *sa6 = sa;
|
|
|
|
IPRINTF("[");
|
|
INTOP(AF_INET6, &sa6->sin6_addr);
|
|
IPRINTF("]:%hu", ntohs(sa6->sin6_port));
|
|
break;
|
|
}
|
|
|
|
/* FIXME: Implement AF_UNIX */
|
|
default:
|
|
errno = EAFNOSUPPORT;
|
|
return NULL;
|
|
}
|
|
|
|
#undef IPRINTF
|
|
#undef INTOP
|
|
|
|
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>";
|
|
}
|