diff --git a/sys/compat/linux/linux.c b/sys/compat/linux/linux.c index 340fdb37e2a2..1a0459fd3f7b 100644 --- a/sys/compat/linux/linux.c +++ b/sys/compat/linux/linux.c @@ -1,712 +1,725 @@ /*- * Copyright (c) 2015 Dmitry Chagin * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include #include #include CTASSERT(LINUX_IFNAMSIZ == IFNAMSIZ); static int bsd_to_linux_sigtbl[LINUX_SIGTBLSZ] = { LINUX_SIGHUP, /* SIGHUP */ LINUX_SIGINT, /* SIGINT */ LINUX_SIGQUIT, /* SIGQUIT */ LINUX_SIGILL, /* SIGILL */ LINUX_SIGTRAP, /* SIGTRAP */ LINUX_SIGABRT, /* SIGABRT */ 0, /* SIGEMT */ LINUX_SIGFPE, /* SIGFPE */ LINUX_SIGKILL, /* SIGKILL */ LINUX_SIGBUS, /* SIGBUS */ LINUX_SIGSEGV, /* SIGSEGV */ LINUX_SIGSYS, /* SIGSYS */ LINUX_SIGPIPE, /* SIGPIPE */ LINUX_SIGALRM, /* SIGALRM */ LINUX_SIGTERM, /* SIGTERM */ LINUX_SIGURG, /* SIGURG */ LINUX_SIGSTOP, /* SIGSTOP */ LINUX_SIGTSTP, /* SIGTSTP */ LINUX_SIGCONT, /* SIGCONT */ LINUX_SIGCHLD, /* SIGCHLD */ LINUX_SIGTTIN, /* SIGTTIN */ LINUX_SIGTTOU, /* SIGTTOU */ LINUX_SIGIO, /* SIGIO */ LINUX_SIGXCPU, /* SIGXCPU */ LINUX_SIGXFSZ, /* SIGXFSZ */ LINUX_SIGVTALRM,/* SIGVTALRM */ LINUX_SIGPROF, /* SIGPROF */ LINUX_SIGWINCH, /* SIGWINCH */ 0, /* SIGINFO */ LINUX_SIGUSR1, /* SIGUSR1 */ LINUX_SIGUSR2 /* SIGUSR2 */ }; #define LINUX_SIGPWREMU (SIGRTMIN + (LINUX_SIGRTMAX - LINUX_SIGRTMIN) + 1) static int linux_to_bsd_sigtbl[LINUX_SIGTBLSZ] = { SIGHUP, /* LINUX_SIGHUP */ SIGINT, /* LINUX_SIGINT */ SIGQUIT, /* LINUX_SIGQUIT */ SIGILL, /* LINUX_SIGILL */ SIGTRAP, /* LINUX_SIGTRAP */ SIGABRT, /* LINUX_SIGABRT */ SIGBUS, /* LINUX_SIGBUS */ SIGFPE, /* LINUX_SIGFPE */ SIGKILL, /* LINUX_SIGKILL */ SIGUSR1, /* LINUX_SIGUSR1 */ SIGSEGV, /* LINUX_SIGSEGV */ SIGUSR2, /* LINUX_SIGUSR2 */ SIGPIPE, /* LINUX_SIGPIPE */ SIGALRM, /* LINUX_SIGALRM */ SIGTERM, /* LINUX_SIGTERM */ SIGBUS, /* LINUX_SIGSTKFLT */ SIGCHLD, /* LINUX_SIGCHLD */ SIGCONT, /* LINUX_SIGCONT */ SIGSTOP, /* LINUX_SIGSTOP */ SIGTSTP, /* LINUX_SIGTSTP */ SIGTTIN, /* LINUX_SIGTTIN */ SIGTTOU, /* LINUX_SIGTTOU */ SIGURG, /* LINUX_SIGURG */ SIGXCPU, /* LINUX_SIGXCPU */ SIGXFSZ, /* LINUX_SIGXFSZ */ SIGVTALRM, /* LINUX_SIGVTALARM */ SIGPROF, /* LINUX_SIGPROF */ SIGWINCH, /* LINUX_SIGWINCH */ SIGIO, /* LINUX_SIGIO */ /* * FreeBSD does not have SIGPWR signal, map Linux SIGPWR signal * to the first unused FreeBSD signal number. Since Linux supports * signals from 1 to 64 we are ok here as our SIGRTMIN = 65. */ LINUX_SIGPWREMU,/* LINUX_SIGPWR */ SIGSYS /* LINUX_SIGSYS */ }; static struct cdev *dev_shm_cdev; static struct cdevsw dev_shm_cdevsw = { .d_version = D_VERSION, .d_name = "dev_shm", }; /* * Map Linux RT signals to the FreeBSD RT signals. */ static inline int linux_to_bsd_rt_signal(int sig) { return (SIGRTMIN + sig - LINUX_SIGRTMIN); } static inline int bsd_to_linux_rt_signal(int sig) { return (sig - SIGRTMIN + LINUX_SIGRTMIN); } int linux_to_bsd_signal(int sig) { KASSERT(sig > 0 && sig <= LINUX_SIGRTMAX, ("invalid Linux signal %d\n", sig)); if (sig < LINUX_SIGRTMIN) return (linux_to_bsd_sigtbl[_SIG_IDX(sig)]); return (linux_to_bsd_rt_signal(sig)); } int bsd_to_linux_signal(int sig) { if (sig <= LINUX_SIGTBLSZ) return (bsd_to_linux_sigtbl[_SIG_IDX(sig)]); if (sig == LINUX_SIGPWREMU) return (LINUX_SIGPWR); return (bsd_to_linux_rt_signal(sig)); } int linux_to_bsd_sigaltstack(int lsa) { int bsa = 0; if (lsa & LINUX_SS_DISABLE) bsa |= SS_DISABLE; /* * Linux ignores SS_ONSTACK flag for ss * parameter while FreeBSD prohibits it. */ return (bsa); } int bsd_to_linux_sigaltstack(int bsa) { int lsa = 0; if (bsa & SS_DISABLE) lsa |= LINUX_SS_DISABLE; if (bsa & SS_ONSTACK) lsa |= LINUX_SS_ONSTACK; return (lsa); } void linux_to_bsd_sigset(l_sigset_t *lss, sigset_t *bss) { int b, l; SIGEMPTYSET(*bss); for (l = 1; l <= LINUX_SIGRTMAX; l++) { if (LINUX_SIGISMEMBER(*lss, l)) { b = linux_to_bsd_signal(l); if (b) SIGADDSET(*bss, b); } } } void bsd_to_linux_sigset(sigset_t *bss, l_sigset_t *lss) { int b, l; LINUX_SIGEMPTYSET(*lss); for (b = 1; b <= SIGRTMAX; b++) { if (SIGISMEMBER(*bss, b)) { l = bsd_to_linux_signal(b); if (l) LINUX_SIGADDSET(*lss, l); } } } /* * Translate a Linux interface name to a FreeBSD interface name, * and return the associated ifnet structure * bsdname and lxname need to be least IFNAMSIZ bytes long, but * can point to the same buffer. */ struct ifnet * ifname_linux_to_bsd(struct thread *td, const char *lxname, char *bsdname) { struct ifnet *ifp; int len, unit; char *ep; int index; bool is_eth, is_lo; for (len = 0; len < LINUX_IFNAMSIZ; ++len) if (!isalpha(lxname[len]) || lxname[len] == '\0') break; if (len == 0 || len == LINUX_IFNAMSIZ) return (NULL); /* Linux loopback interface name is lo (not lo0) */ is_lo = (len == 2 && strncmp(lxname, "lo", len) == 0); unit = (int)strtoul(lxname + len, &ep, 10); if ((ep == NULL || ep == lxname + len || ep >= lxname + LINUX_IFNAMSIZ) && is_lo == 0) return (NULL); index = 0; is_eth = (len == 3 && strncmp(lxname, "eth", len) == 0); CURVNET_SET(TD_TO_VNET(td)); IFNET_RLOCK(); CK_STAILQ_FOREACH(ifp, &V_ifnet, if_link) { /* * Allow Linux programs to use FreeBSD names. Don't presume * we never have an interface named "eth", so don't make * the test optional based on is_eth. */ if (strncmp(ifp->if_xname, lxname, LINUX_IFNAMSIZ) == 0) break; if (is_eth && IFP_IS_ETH(ifp) && unit == index++) break; if (is_lo && IFP_IS_LOOP(ifp)) break; } IFNET_RUNLOCK(); CURVNET_RESTORE(); if (ifp != NULL && bsdname != NULL) strlcpy(bsdname, ifp->if_xname, IFNAMSIZ); return (ifp); } void linux_ifflags(struct ifnet *ifp, short *flags) { unsigned short fl; fl = (ifp->if_flags | ifp->if_drv_flags) & 0xffff; *flags = 0; if (fl & IFF_UP) *flags |= LINUX_IFF_UP; if (fl & IFF_BROADCAST) *flags |= LINUX_IFF_BROADCAST; if (fl & IFF_DEBUG) *flags |= LINUX_IFF_DEBUG; if (fl & IFF_LOOPBACK) *flags |= LINUX_IFF_LOOPBACK; if (fl & IFF_POINTOPOINT) *flags |= LINUX_IFF_POINTOPOINT; if (fl & IFF_DRV_RUNNING) *flags |= LINUX_IFF_RUNNING; if (fl & IFF_NOARP) *flags |= LINUX_IFF_NOARP; if (fl & IFF_PROMISC) *flags |= LINUX_IFF_PROMISC; if (fl & IFF_ALLMULTI) *flags |= LINUX_IFF_ALLMULTI; if (fl & IFF_MULTICAST) *flags |= LINUX_IFF_MULTICAST; } int linux_ifhwaddr(struct ifnet *ifp, struct l_sockaddr *lsa) { struct ifaddr *ifa; struct sockaddr_dl *sdl; if (IFP_IS_LOOP(ifp)) { bzero(lsa, sizeof(*lsa)); lsa->sa_family = LINUX_ARPHRD_LOOPBACK; return (0); } if (!IFP_IS_ETH(ifp)) return (ENOENT); CK_STAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) { sdl = (struct sockaddr_dl*)ifa->ifa_addr; if (sdl != NULL && (sdl->sdl_family == AF_LINK) && (sdl->sdl_type == IFT_ETHER)) { bzero(lsa, sizeof(*lsa)); lsa->sa_family = LINUX_ARPHRD_ETHER; bcopy(LLADDR(sdl), lsa->sa_data, LINUX_IFHWADDRLEN); return (0); } } return (ENOENT); } int linux_to_bsd_domain(int domain) { switch (domain) { case LINUX_AF_UNSPEC: return (AF_UNSPEC); case LINUX_AF_UNIX: return (AF_LOCAL); case LINUX_AF_INET: return (AF_INET); case LINUX_AF_INET6: return (AF_INET6); case LINUX_AF_AX25: return (AF_CCITT); case LINUX_AF_IPX: return (AF_IPX); case LINUX_AF_APPLETALK: return (AF_APPLETALK); + case LINUX_AF_NETLINK: + return (AF_NETLINK); } return (-1); } int bsd_to_linux_domain(int domain) { switch (domain) { case AF_UNSPEC: return (LINUX_AF_UNSPEC); case AF_LOCAL: return (LINUX_AF_UNIX); case AF_INET: return (LINUX_AF_INET); case AF_INET6: return (LINUX_AF_INET6); case AF_CCITT: return (LINUX_AF_AX25); case AF_IPX: return (LINUX_AF_IPX); case AF_APPLETALK: return (LINUX_AF_APPLETALK); + case AF_NETLINK: + return (LINUX_AF_NETLINK); } return (-1); } /* * Based on the fact that: * 1. Native and Linux storage of struct sockaddr * and struct sockaddr_in6 are equal. * 2. On Linux sa_family is the first member of all struct sockaddr. */ int bsd_to_linux_sockaddr(const struct sockaddr *sa, struct l_sockaddr **lsa, socklen_t len) { struct l_sockaddr *kosa; int bdom; *lsa = NULL; if (len < 2 || len > UCHAR_MAX) return (EINVAL); bdom = bsd_to_linux_domain(sa->sa_family); if (bdom == -1) return (EAFNOSUPPORT); kosa = malloc(len, M_LINUX, M_WAITOK); bcopy(sa, kosa, len); kosa->sa_family = bdom; *lsa = kosa; return (0); } int linux_to_bsd_sockaddr(const struct l_sockaddr *osa, struct sockaddr **sap, socklen_t *len) { struct sockaddr *sa; struct l_sockaddr *kosa; #ifdef INET6 struct sockaddr_in6 *sin6; bool oldv6size; #endif char *name; int salen, bdom, error, hdrlen, namelen; if (*len < 2 || *len > UCHAR_MAX) return (EINVAL); salen = *len; #ifdef INET6 oldv6size = false; /* * Check for old (pre-RFC2553) sockaddr_in6. We may accept it * if it's a v4-mapped address, so reserve the proper space * for it. */ if (salen == sizeof(struct sockaddr_in6) - sizeof(uint32_t)) { salen += sizeof(uint32_t); oldv6size = true; } #endif kosa = malloc(salen, M_SONAME, M_WAITOK); if ((error = copyin(osa, kosa, *len))) goto out; bdom = linux_to_bsd_domain(kosa->sa_family); if (bdom == -1) { error = EAFNOSUPPORT; goto out; } #ifdef INET6 /* * Older Linux IPv6 code uses obsolete RFC2133 struct sockaddr_in6, * which lacks the scope id compared with RFC2553 one. If we detect * the situation, reject the address and write a message to system log. * * Still accept addresses for which the scope id is not used. */ if (oldv6size) { if (bdom == AF_INET6) { sin6 = (struct sockaddr_in6 *)kosa; if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr) || (!IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr) && !IN6_IS_ADDR_SITELOCAL(&sin6->sin6_addr) && !IN6_IS_ADDR_V4COMPAT(&sin6->sin6_addr) && !IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr) && !IN6_IS_ADDR_MULTICAST(&sin6->sin6_addr))) { sin6->sin6_scope_id = 0; } else { linux_msg(curthread, "obsolete pre-RFC2553 sockaddr_in6 rejected"); error = EINVAL; goto out; } } else salen -= sizeof(uint32_t); } #endif if (bdom == AF_INET) { if (salen < sizeof(struct sockaddr_in)) { error = EINVAL; goto out; } salen = sizeof(struct sockaddr_in); } if (bdom == AF_LOCAL && salen > sizeof(struct sockaddr_un)) { hdrlen = offsetof(struct sockaddr_un, sun_path); name = ((struct sockaddr_un *)kosa)->sun_path; if (*name == '\0') { /* * Linux abstract namespace starts with a NULL byte. * XXX We do not support abstract namespace yet. */ namelen = strnlen(name + 1, salen - hdrlen - 1) + 1; } else namelen = strnlen(name, salen - hdrlen); salen = hdrlen + namelen; if (salen > sizeof(struct sockaddr_un)) { error = ENAMETOOLONG; goto out; } } + if (bdom == AF_NETLINK) { + if (salen < sizeof(struct sockaddr_nl)) { + error = EINVAL; + goto out; + } + salen = sizeof(struct sockaddr_nl); + } + sa = (struct sockaddr *)kosa; sa->sa_family = bdom; sa->sa_len = salen; *sap = sa; *len = salen; return (0); out: free(kosa, M_SONAME); return (error); } void linux_dev_shm_create(void) { int error; error = make_dev_p(MAKEDEV_CHECKNAME | MAKEDEV_WAITOK, &dev_shm_cdev, &dev_shm_cdevsw, NULL, UID_ROOT, GID_WHEEL, 0, "shm/.mountpoint"); if (error != 0) { printf("%s: failed to create device node, error %d\n", __func__, error); } } void linux_dev_shm_destroy(void) { destroy_dev(dev_shm_cdev); } int bsd_to_linux_bits_(int value, struct bsd_to_linux_bitmap *bitmap, size_t mapcnt, int no_value) { int bsd_mask, bsd_value, linux_mask, linux_value; int linux_ret; size_t i; bool applied; applied = false; linux_ret = 0; for (i = 0; i < mapcnt; ++i) { bsd_mask = bitmap[i].bsd_mask; bsd_value = bitmap[i].bsd_value; if (bsd_mask == 0) bsd_mask = bsd_value; linux_mask = bitmap[i].linux_mask; linux_value = bitmap[i].linux_value; if (linux_mask == 0) linux_mask = linux_value; /* * If a mask larger than just the value is set, we explicitly * want to make sure that only this bit we mapped within that * mask is set. */ if ((value & bsd_mask) == bsd_value) { linux_ret = (linux_ret & ~linux_mask) | linux_value; applied = true; } } if (!applied) return (no_value); return (linux_ret); } int linux_to_bsd_bits_(int value, struct bsd_to_linux_bitmap *bitmap, size_t mapcnt, int no_value) { int bsd_mask, bsd_value, linux_mask, linux_value; int bsd_ret; size_t i; bool applied; applied = false; bsd_ret = 0; for (i = 0; i < mapcnt; ++i) { bsd_mask = bitmap[i].bsd_mask; bsd_value = bitmap[i].bsd_value; if (bsd_mask == 0) bsd_mask = bsd_value; linux_mask = bitmap[i].linux_mask; linux_value = bitmap[i].linux_value; if (linux_mask == 0) linux_mask = linux_value; /* * If a mask larger than just the value is set, we explicitly * want to make sure that only this bit we mapped within that * mask is set. */ if ((value & linux_mask) == linux_value) { bsd_ret = (bsd_ret & ~bsd_mask) | bsd_value; applied = true; } } if (!applied) return (no_value); return (bsd_ret); } void linux_to_bsd_poll_events(struct thread *td, int fd, short lev, short *bev) { struct file *fp; int error; short bits = 0; if (lev & LINUX_POLLIN) bits |= POLLIN; if (lev & LINUX_POLLPRI) bits |= POLLPRI; if (lev & LINUX_POLLOUT) bits |= POLLOUT; if (lev & LINUX_POLLERR) bits |= POLLERR; if (lev & LINUX_POLLHUP) bits |= POLLHUP; if (lev & LINUX_POLLNVAL) bits |= POLLNVAL; if (lev & LINUX_POLLRDNORM) bits |= POLLRDNORM; if (lev & LINUX_POLLRDBAND) bits |= POLLRDBAND; if (lev & LINUX_POLLWRBAND) bits |= POLLWRBAND; if (lev & LINUX_POLLWRNORM) bits |= POLLWRNORM; if (lev & LINUX_POLLRDHUP) { /* * It seems that the Linux silencly ignores POLLRDHUP * on non-socket file descriptors unlike FreeBSD, where * events bits is more strictly checked (POLLSTANDARD). */ error = fget_unlocked(td, fd, &cap_no_rights, &fp); if (error == 0) { /* * XXX. On FreeBSD POLLRDHUP applies only to * stream sockets. */ if (fp->f_type == DTYPE_SOCKET) bits |= POLLRDHUP; fdrop(fp, td); } } if (lev & LINUX_POLLMSG) LINUX_RATELIMIT_MSG_OPT1("unsupported POLLMSG, events(%d)", lev); if (lev & LINUX_POLLREMOVE) LINUX_RATELIMIT_MSG_OPT1("unsupported POLLREMOVE, events(%d)", lev); *bev = bits; } void bsd_to_linux_poll_events(short bev, short *lev) { short bits = 0; if (bev & POLLIN) bits |= LINUX_POLLIN; if (bev & POLLPRI) bits |= LINUX_POLLPRI; if (bev & (POLLOUT | POLLWRNORM)) /* * POLLWRNORM is equal to POLLOUT on FreeBSD, * but not on Linux */ bits |= LINUX_POLLOUT; if (bev & POLLERR) bits |= LINUX_POLLERR; if (bev & POLLHUP) bits |= LINUX_POLLHUP; if (bev & POLLNVAL) bits |= LINUX_POLLNVAL; if (bev & POLLRDNORM) bits |= LINUX_POLLRDNORM; if (bev & POLLRDBAND) bits |= LINUX_POLLRDBAND; if (bev & POLLWRBAND) bits |= LINUX_POLLWRBAND; if (bev & POLLRDHUP) bits |= LINUX_POLLRDHUP; *lev = bits; } diff --git a/sys/compat/linux/linux.h b/sys/compat/linux/linux.h index 72bcb25bd05a..6400de4cb228 100644 --- a/sys/compat/linux/linux.h +++ b/sys/compat/linux/linux.h @@ -1,274 +1,277 @@ /*- * Copyright (c) 2015 Dmitry Chagin * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #ifndef _LINUX_MI_H_ #define _LINUX_MI_H_ #include /* * Private Brandinfo flags */ #define LINUX_BI_FUTEX_REQUEUE 0x01000000 /* * poll() */ #define LINUX_POLLIN 0x0001 #define LINUX_POLLPRI 0x0002 #define LINUX_POLLOUT 0x0004 #define LINUX_POLLERR 0x0008 #define LINUX_POLLHUP 0x0010 #define LINUX_POLLNVAL 0x0020 #define LINUX_POLLRDNORM 0x0040 #define LINUX_POLLRDBAND 0x0080 #define LINUX_POLLWRNORM 0x0100 #define LINUX_POLLWRBAND 0x0200 #define LINUX_POLLMSG 0x0400 #define LINUX_POLLREMOVE 0x1000 #define LINUX_POLLRDHUP 0x2000 #define LINUX_IFHWADDRLEN 6 #define LINUX_IFNAMSIZ 16 struct l_sockaddr { unsigned short sa_family; char sa_data[14]; }; #define LINUX_ARPHRD_ETHER 1 #define LINUX_ARPHRD_LOOPBACK 772 /* * Supported address families */ #define LINUX_AF_UNSPEC 0 #define LINUX_AF_UNIX 1 #define LINUX_AF_INET 2 #define LINUX_AF_AX25 3 #define LINUX_AF_IPX 4 #define LINUX_AF_APPLETALK 5 #define LINUX_AF_INET6 10 #define LINUX_AF_NETLINK 16 #define LINUX_NETLINK_ROUTE 0 #define LINUX_NETLINK_SOCK_DIAG 4 #define LINUX_NETLINK_NFLOG 5 #define LINUX_NETLINK_SELINUX 7 #define LINUX_NETLINK_AUDIT 9 #define LINUX_NETLINK_FIB_LOOKUP 10 #define LINUX_NETLINK_NETFILTER 12 #define LINUX_NETLINK_KOBJECT_UEVENT 15 /* * net device flags */ #define LINUX_IFF_UP 0x0001 #define LINUX_IFF_BROADCAST 0x0002 #define LINUX_IFF_DEBUG 0x0004 #define LINUX_IFF_LOOPBACK 0x0008 #define LINUX_IFF_POINTOPOINT 0x0010 #define LINUX_IFF_NOTRAILERS 0x0020 #define LINUX_IFF_RUNNING 0x0040 #define LINUX_IFF_NOARP 0x0080 #define LINUX_IFF_PROMISC 0x0100 #define LINUX_IFF_ALLMULTI 0x0200 #define LINUX_IFF_MASTER 0x0400 #define LINUX_IFF_SLAVE 0x0800 #define LINUX_IFF_MULTICAST 0x1000 #define LINUX_IFF_PORTSEL 0x2000 #define LINUX_IFF_AUTOMEDIA 0x4000 #define LINUX_IFF_DYNAMIC 0x8000 /* sigaltstack */ #define LINUX_SS_ONSTACK 1 #define LINUX_SS_DISABLE 2 int linux_to_bsd_sigaltstack(int lsa); int bsd_to_linux_sigaltstack(int bsa); /* sigset */ typedef struct { uint64_t __mask; } l_sigset_t; /* primitives to manipulate sigset_t */ #define LINUX_SIGEMPTYSET(set) (set).__mask = 0 #define LINUX_SIGISMEMBER(set, sig) (1ULL & ((set).__mask >> _SIG_IDX(sig))) #define LINUX_SIGADDSET(set, sig) (set).__mask |= 1ULL << _SIG_IDX(sig) void linux_to_bsd_sigset(l_sigset_t *, sigset_t *); void bsd_to_linux_sigset(sigset_t *, l_sigset_t *); /* signaling */ #define LINUX_SIGHUP 1 #define LINUX_SIGINT 2 #define LINUX_SIGQUIT 3 #define LINUX_SIGILL 4 #define LINUX_SIGTRAP 5 #define LINUX_SIGABRT 6 #define LINUX_SIGIOT LINUX_SIGABRT #define LINUX_SIGBUS 7 #define LINUX_SIGFPE 8 #define LINUX_SIGKILL 9 #define LINUX_SIGUSR1 10 #define LINUX_SIGSEGV 11 #define LINUX_SIGUSR2 12 #define LINUX_SIGPIPE 13 #define LINUX_SIGALRM 14 #define LINUX_SIGTERM 15 #define LINUX_SIGSTKFLT 16 #define LINUX_SIGCHLD 17 #define LINUX_SIGCONT 18 #define LINUX_SIGSTOP 19 #define LINUX_SIGTSTP 20 #define LINUX_SIGTTIN 21 #define LINUX_SIGTTOU 22 #define LINUX_SIGURG 23 #define LINUX_SIGXCPU 24 #define LINUX_SIGXFSZ 25 #define LINUX_SIGVTALRM 26 #define LINUX_SIGPROF 27 #define LINUX_SIGWINCH 28 #define LINUX_SIGIO 29 #define LINUX_SIGPOLL LINUX_SIGIO #define LINUX_SIGPWR 30 #define LINUX_SIGSYS 31 #define LINUX_SIGTBLSZ 31 #define LINUX_SIGRTMIN 32 #define LINUX_SIGRTMAX 64 #define LINUX_SIG_VALID(sig) ((sig) <= LINUX_SIGRTMAX && (sig) > 0) int linux_to_bsd_signal(int sig); int bsd_to_linux_signal(int sig); /* sigprocmask actions */ #define LINUX_SIG_BLOCK 0 #define LINUX_SIG_UNBLOCK 1 #define LINUX_SIG_SETMASK 2 void linux_dev_shm_create(void); void linux_dev_shm_destroy(void); /* * mask=0 is not sensible for this application, so it will be taken to mean * a mask equivalent to the value. Otherwise, (word & mask) == value maps to * (word & ~mask) | value in a bitfield for the platform we're converting to. */ struct bsd_to_linux_bitmap { int bsd_mask; int bsd_value; int linux_mask; int linux_value; }; int bsd_to_linux_bits_(int value, struct bsd_to_linux_bitmap *bitmap, size_t mapcnt, int no_value); int linux_to_bsd_bits_(int value, struct bsd_to_linux_bitmap *bitmap, size_t mapcnt, int no_value); /* * These functions are used for simplification of BSD <-> Linux bit conversions. * Given `value`, a bit field, these functions will walk the given bitmap table * and set the appropriate bits for the target platform. If any bits were * successfully converted, then the return value is the equivalent of value * represented with the bit values appropriate for the target platform. * Otherwise, the value supplied as `no_value` is returned. */ #define bsd_to_linux_bits(_val, _bmap, _noval) \ bsd_to_linux_bits_((_val), (_bmap), nitems((_bmap)), (_noval)) #define linux_to_bsd_bits(_val, _bmap, _noval) \ linux_to_bsd_bits_((_val), (_bmap), nitems((_bmap)), (_noval)) /* * Easy mapping helpers. BITMAP_EASY_LINUX represents a single bit to be * translated, and the FreeBSD and Linux values are supplied. BITMAP_1t1_LINUX * is the extreme version of this, where not only is it a single bit, but the * name of the macro used to represent the Linux version of a bit literally has * LINUX_ prepended to the normal name. */ #define BITMAP_EASY_LINUX(_name, _linux_name) \ { \ .bsd_value = (_name), \ .linux_value = (_linux_name), \ } #define BITMAP_1t1_LINUX(_name) BITMAP_EASY_LINUX(_name, LINUX_##_name) int bsd_to_linux_errno(int error); void linux_check_errtbl(void); #define STATX_BASIC_STATS 0x07ff #define STATX_BTIME 0x0800 #define STATX_ALL 0x0fff #define STATX_ATTR_COMPRESSED 0x0004 #define STATX_ATTR_IMMUTABLE 0x0010 #define STATX_ATTR_APPEND 0x0020 #define STATX_ATTR_NODUMP 0x0040 #define STATX_ATTR_ENCRYPTED 0x0800 #define STATX_ATTR_AUTOMOUNT 0x1000 struct l_statx_timestamp { int64_t tv_sec; int32_t tv_nsec; int32_t __spare0; }; struct l_statx { uint32_t stx_mask; uint32_t stx_blksize; uint64_t stx_attributes; uint32_t stx_nlink; uint32_t stx_uid; uint32_t stx_gid; uint16_t stx_mode; uint16_t __spare0[1]; uint64_t stx_ino; uint64_t stx_size; uint64_t stx_blocks; uint64_t stx_attributes_mask; struct l_statx_timestamp stx_atime; struct l_statx_timestamp stx_btime; struct l_statx_timestamp stx_ctime; struct l_statx_timestamp stx_mtime; uint32_t stx_rdev_major; uint32_t stx_rdev_minor; uint32_t stx_dev_major; uint32_t stx_dev_minor; uint64_t stx_mnt_id; uint64_t __spare2[13]; }; #define lower_32_bits(n) ((uint32_t)((n) & 0xffffffff)) #ifdef KTRACE #define linux_ktrsigset(s, l) \ ktrstruct("l_sigset_t", (s), l) #endif +void linux_netlink_register(void); +void linux_netlink_deregister(void); + #endif /* _LINUX_MI_H_ */ diff --git a/sys/compat/linux/linux_common.c b/sys/compat/linux/linux_common.c index 0009d9d26164..9762f16fd747 100644 --- a/sys/compat/linux/linux_common.c +++ b/sys/compat/linux/linux_common.c @@ -1,86 +1,89 @@ /*- * Copyright (c) 2014 Vassilis Laganakos * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include SET_DECLARE(linux_device_handler_set, struct linux_device_handler); TAILQ_HEAD(, linux_ioctl_handler_element) linux_ioctl_handlers = TAILQ_HEAD_INITIALIZER(linux_ioctl_handlers); struct sx linux_ioctl_sx; SX_SYSINIT(linux_ioctl, &linux_ioctl_sx, "Linux ioctl handlers"); static int linux_common_modevent(module_t mod, int type, void *data) { struct linux_device_handler **ldhp; switch(type) { case MOD_LOAD: #ifdef INVARIANTS linux_check_errtbl(); #endif linux_dev_shm_create(); linux_osd_jail_register(); SET_FOREACH(ldhp, linux_device_handler_set) linux_device_register_handler(*ldhp); + linux_netlink_register(); break; case MOD_UNLOAD: linux_dev_shm_destroy(); linux_osd_jail_deregister(); SET_FOREACH(ldhp, linux_device_handler_set) linux_device_unregister_handler(*ldhp); + linux_netlink_deregister(); break; default: return (EOPNOTSUPP); } return (0); } static moduledata_t linux_common_mod = { "linux_common", linux_common_modevent, 0 }; DECLARE_MODULE(linux_common, linux_common_mod, SI_SUB_EXEC, SI_ORDER_ANY); MODULE_VERSION(linux_common, 1); +MODULE_DEPEND(linux_common, netlink, 1, 1, 1); diff --git a/sys/compat/linux/linux_netlink.c b/sys/compat/linux/linux_netlink.c new file mode 100644 index 000000000000..816d9f34f19f --- /dev/null +++ b/sys/compat/linux/linux_netlink.c @@ -0,0 +1,573 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2022 Alexander V. Chernikov + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); +#include "opt_inet.h" +#include "opt_inet6.h" +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define DEBUG_MOD_NAME nl_linux +#define DEBUG_MAX_LEVEL LOG_DEBUG3 +#include +_DECLARE_DEBUG(LOG_DEBUG); + +static bool +valid_rta_size(const struct rtattr *rta, int sz) +{ + return (NL_RTA_DATA_LEN(rta) == sz); +} + +static bool +valid_rta_u32(const struct rtattr *rta) +{ + return (valid_rta_size(rta, sizeof(uint32_t))); +} + +static uint32_t +_rta_get_uint32(const struct rtattr *rta) +{ + return (*((const uint32_t *)NL_RTA_DATA_CONST(rta))); +} + +static struct nlmsghdr * +rtnl_neigh_from_linux(struct nlmsghdr *hdr, struct nl_pstate *npt) +{ + struct ndmsg *ndm = (struct ndmsg *)(hdr + 1); + + if (hdr->nlmsg_len >= sizeof(struct nlmsghdr) + sizeof(struct ndmsg)) + ndm->ndm_family = linux_to_bsd_domain(ndm->ndm_family); + + return (hdr); +} + +static struct nlmsghdr * +rtnl_ifaddr_from_linux(struct nlmsghdr *hdr, struct nl_pstate *npt) +{ + struct ifaddrmsg *ifam = (struct ifaddrmsg *)(hdr + 1); + + if (hdr->nlmsg_len >= sizeof(struct nlmsghdr) + sizeof(struct ifaddrmsg)) + ifam->ifa_family = linux_to_bsd_domain(ifam->ifa_family); + + return (hdr); +} + +static struct nlmsghdr * +rtnl_route_from_linux(struct nlmsghdr *hdr, struct nl_pstate *npt) +{ + /* Tweak address families and default fib only */ + struct rtmsg *rtm = (struct rtmsg *)(hdr + 1); + struct nlattr *nla, *nla_head; + int attrs_len; + + rtm->rtm_family = linux_to_bsd_domain(rtm->rtm_family); + + if (rtm->rtm_table == 254) + rtm->rtm_table = 0; + + attrs_len = hdr->nlmsg_len - sizeof(struct nlmsghdr); + attrs_len -= NETLINK_ALIGN(sizeof(struct rtmsg)); + nla_head = (struct nlattr *)((char *)rtm + NETLINK_ALIGN(sizeof(struct rtmsg))); + + NLA_FOREACH(nla, nla_head, attrs_len) { + RT_LOG(LOG_DEBUG3, "GOT type %d len %d total %d", + nla->nla_type, nla->nla_len, attrs_len); + struct rtattr *rta = (struct rtattr *)nla; + if (rta->rta_len < sizeof(struct rtattr)) { + break; + } + switch (rta->rta_type) { + case NL_RTA_TABLE: + if (!valid_rta_u32(rta)) + goto done; + rtm->rtm_table = 0; + uint32_t fibnum = _rta_get_uint32(rta); + RT_LOG(LOG_DEBUG3, "GET RTABLE: %u", fibnum); + if (fibnum == 254) { + *((uint32_t *)NL_RTA_DATA(rta)) = 0; + } + break; + } + } + +done: + return (hdr); +} + +static struct nlmsghdr * +rtnl_from_linux(struct nlmsghdr *hdr, struct nl_pstate *npt) +{ + switch (hdr->nlmsg_type) { + case NL_RTM_GETROUTE: + case NL_RTM_NEWROUTE: + case NL_RTM_DELROUTE: + return (rtnl_route_from_linux(hdr, npt)); + case NL_RTM_GETNEIGH: + return (rtnl_neigh_from_linux(hdr, npt)); + case NL_RTM_GETADDR: + return (rtnl_ifaddr_from_linux(hdr, npt)); + /* Silence warning for the messages where no translation is required */ + case NL_RTM_NEWLINK: + case NL_RTM_DELLINK: + case NL_RTM_GETLINK: + break; + default: + RT_LOG(LOG_DEBUG, "Passing message type %d untranslated", + hdr->nlmsg_type); + } + + return (hdr); +} + +static struct nlmsghdr * +nlmsg_from_linux(int netlink_family, struct nlmsghdr *hdr, + struct nl_pstate *npt) +{ + switch (netlink_family) { + case NETLINK_ROUTE: + return (rtnl_from_linux(hdr, npt)); + } + + return (hdr); +} + + +/************************************************************ + * Kernel -> Linux + ************************************************************/ + +static bool +handle_default_out(struct nlmsghdr *hdr, struct nl_writer *nw) +{ + char *out_hdr; + out_hdr = nlmsg_reserve_data(nw, NLMSG_ALIGN(hdr->nlmsg_len), char); + + if (out_hdr != NULL) { + memcpy(out_hdr, hdr, hdr->nlmsg_len); + return (true); + } + return (false); +} + +static bool +nlmsg_copy_header(struct nlmsghdr *hdr, struct nl_writer *nw) +{ + return (nlmsg_add(nw, hdr->nlmsg_pid, hdr->nlmsg_seq, hdr->nlmsg_type, + hdr->nlmsg_flags, 0)); +} + +static void * +_nlmsg_copy_next_header(struct nlmsghdr *hdr, struct nl_writer *nw, int sz) +{ + void *next_hdr = nlmsg_reserve_data(nw, sz, void); + memcpy(next_hdr, hdr + 1, NLMSG_ALIGN(sz)); + + return (next_hdr); +} +#define nlmsg_copy_next_header(_hdr, _ns, _t) \ + ((_t *)(_nlmsg_copy_next_header(_hdr, _ns, sizeof(_t)))) + +static bool +nlmsg_copy_nla(const struct nlattr *nla_orig, struct nl_writer *nw) +{ + struct nlattr *nla = nlmsg_reserve_data(nw, nla_orig->nla_len, struct nlattr); + if (nla != NULL) { + memcpy(nla, nla_orig, nla_orig->nla_len); + return (true); + } + return (false); +} + +static bool +nlmsg_copy_all_nla(struct nlmsghdr *hdr, int raw_hdrlen, struct nl_writer *nw) +{ + struct nlattr *nla; + + int hdrlen = NETLINK_ALIGN(raw_hdrlen); + int attrs_len = hdr->nlmsg_len - sizeof(struct nlmsghdr) - hdrlen; + struct nlattr *nla_head = (struct nlattr *)((char *)(hdr + 1) + hdrlen); + + NLA_FOREACH(nla, nla_head, attrs_len) { + RT_LOG(LOG_DEBUG3, "reading attr %d len %d", nla->nla_type, nla->nla_len); + if (nla->nla_len < sizeof(struct nlattr)) { + return (false); + } + if (!nlmsg_copy_nla(nla, nw)) + return (false); + } + return (true); +} + +static unsigned int +rtnl_if_flags_to_linux(unsigned int if_flags) +{ + unsigned int result = 0; + + for (int i = 0; i < 31; i++) { + unsigned int flag = 1 << i; + if (!(flag & if_flags)) + continue; + switch (flag) { + case IFF_UP: + case IFF_BROADCAST: + case IFF_DEBUG: + case IFF_LOOPBACK: + case IFF_POINTOPOINT: + case IFF_DRV_RUNNING: + case IFF_NOARP: + case IFF_PROMISC: + case IFF_ALLMULTI: + result |= flag; + break; + case IFF_KNOWSEPOCH: + case IFF_DRV_OACTIVE: + case IFF_SIMPLEX: + case IFF_LINK0: + case IFF_LINK1: + case IFF_LINK2: + case IFF_CANTCONFIG: + case IFF_PPROMISC: + case IFF_MONITOR: + case IFF_STATICARP: + case IFF_STICKYARP: + case IFF_DYING: + case IFF_RENAMING: + case IFF_NOGROUP: + /* No Linux analogue */ + break; + case IFF_MULTICAST: + result |= 1 << 12; + } + } + return (result); +} + +static bool +rtnl_newlink_to_linux(struct nlmsghdr *hdr, struct nlpcb *nlp, + struct nl_writer *nw) +{ + if (!nlmsg_copy_header(hdr, nw)) + return (false); + + struct ifinfomsg *ifinfo; + ifinfo = nlmsg_copy_next_header(hdr, nw, struct ifinfomsg); + + ifinfo->ifi_family = bsd_to_linux_domain(ifinfo->ifi_family); + /* Convert interface type */ + switch (ifinfo->ifi_type) { + case IFT_ETHER: + ifinfo->ifi_type = 1; // ARPHRD_ETHER + break; + } + ifinfo->ifi_flags = rtnl_if_flags_to_linux(ifinfo->ifi_flags); + + /* Copy attributes unchanged */ + if (!nlmsg_copy_all_nla(hdr, sizeof(struct ifinfomsg), nw)) + return (false); + + /* make ip(8) happy */ + if (!nlattr_add_string(nw, IFLA_QDISC, "noqueue")) + return (false); + + if (!nlattr_add_u32(nw, IFLA_TXQLEN, 1000)) + return (false); + + nlmsg_end(nw); + RT_LOG(LOG_DEBUG2, "done processing nw %p", nw); + return (true); +} + +static bool +rtnl_newaddr_to_linux(struct nlmsghdr *hdr, struct nlpcb *nlp, + struct nl_writer *nw) +{ + if (!nlmsg_copy_header(hdr, nw)) + return (false); + + struct ifaddrmsg *ifamsg; + ifamsg = nlmsg_copy_next_header(hdr, nw, struct ifaddrmsg); + + ifamsg->ifa_family = bsd_to_linux_domain(ifamsg->ifa_family); + /* XXX: fake ifa_flags? */ + + /* Copy attributes unchanged */ + if (!nlmsg_copy_all_nla(hdr, sizeof(struct ifaddrmsg), nw)) + return (false); + + nlmsg_end(nw); + RT_LOG(LOG_DEBUG2, "done processing nw %p", nw); + return (true); +} + +static bool +rtnl_newneigh_to_linux(struct nlmsghdr *hdr, struct nlpcb *nlp, + struct nl_writer *nw) +{ + if (!nlmsg_copy_header(hdr, nw)) + return (false); + + struct ndmsg *ndm; + ndm = nlmsg_copy_next_header(hdr, nw, struct ndmsg); + + ndm->ndm_family = bsd_to_linux_domain(ndm->ndm_family); + + /* Copy attributes unchanged */ + if (!nlmsg_copy_all_nla(hdr, sizeof(struct ndmsg), nw)) + return (false); + + nlmsg_end(nw); + RT_LOG(LOG_DEBUG2, "done processing nw %p", nw); + return (true); +} + +static bool +rtnl_newroute_to_linux(struct nlmsghdr *hdr, struct nlpcb *nlp, + struct nl_writer *nw) +{ + if (!nlmsg_copy_header(hdr, nw)) + return (false); + + struct rtmsg *rtm; + rtm = nlmsg_copy_next_header(hdr, nw, struct rtmsg); + rtm->rtm_family = bsd_to_linux_domain(rtm->rtm_family); + + struct nlattr *nla; + + int hdrlen = NETLINK_ALIGN(sizeof(struct rtmsg)); + int attrs_len = hdr->nlmsg_len - sizeof(struct nlmsghdr) - hdrlen; + struct nlattr *nla_head = (struct nlattr *)((char *)(hdr + 1) + hdrlen); + + NLA_FOREACH(nla, nla_head, attrs_len) { + struct rtattr *rta = (struct rtattr *)nla; + //RT_LOG(LOG_DEBUG, "READING attr %d len %d", nla->nla_type, nla->nla_len); + if (rta->rta_len < sizeof(struct rtattr)) { + break; + } + + switch (rta->rta_type) { + case NL_RTA_TABLE: + { + uint32_t fibnum; + fibnum = _rta_get_uint32(rta); + if (fibnum == 0) + fibnum = 254; + RT_LOG(LOG_DEBUG3, "XFIBNUM %u", fibnum); + if (!nlattr_add_u32(nw, NL_RTA_TABLE, fibnum)) + return (false); + } + break; + default: + if (!nlmsg_copy_nla(nla, nw)) + return (false); + break; + } + } + + nlmsg_end(nw); + RT_LOG(LOG_DEBUG2, "done processing nw %p", nw); + return (true); +} + +static bool +rtnl_to_linux(struct nlmsghdr *hdr, struct nlpcb *nlp, struct nl_writer *nw) +{ + RT_LOG(LOG_DEBUG2, "Got message type %d", hdr->nlmsg_type); + + switch (hdr->nlmsg_type) { + case NL_RTM_NEWLINK: + case NL_RTM_DELLINK: + case NL_RTM_GETLINK: + return (rtnl_newlink_to_linux(hdr, nlp, nw)); + case NL_RTM_NEWADDR: + case NL_RTM_DELADDR: + return (rtnl_newaddr_to_linux(hdr, nlp, nw)); + case NL_RTM_NEWROUTE: + case NL_RTM_DELROUTE: + return (rtnl_newroute_to_linux(hdr, nlp, nw)); + case NL_RTM_NEWNEIGH: + case NL_RTM_DELNEIGH: + case NL_RTM_GETNEIGH: + return (rtnl_newneigh_to_linux(hdr, nlp, nw)); + default: + RT_LOG(LOG_DEBUG, "[WARN] Passing message type %d untranslated", + hdr->nlmsg_type); + return (handle_default_out(hdr, nw)); + } +} + +static bool +nlmsg_error_to_linux(struct nlmsghdr *hdr, struct nlpcb *nlp, struct nl_writer *nw) +{ + if (!nlmsg_copy_header(hdr, nw)) + return (false); + + struct nlmsgerr *nlerr; + nlerr = nlmsg_copy_next_header(hdr, nw, struct nlmsgerr); + nlerr->error = bsd_to_linux_errno(nlerr->error); + + int copied_len = sizeof(struct nlmsghdr) + sizeof(struct nlmsgerr); + if (hdr->nlmsg_len == copied_len) { + nlmsg_end(nw); + return (true); + } + + /* + * CAP_ACK was not set. Original request needs to be translated. + * XXX: implement translation of the original message + */ + RT_LOG(LOG_DEBUG, "[WARN] Passing ack message type %d untranslated", + nlerr->msg.nlmsg_type); + char *dst_payload, *src_payload; + int copy_len = hdr->nlmsg_len - copied_len; + dst_payload = nlmsg_reserve_data(nw, NLMSG_ALIGN(copy_len), char); + + src_payload = (char *)hdr + copied_len; + + memcpy(dst_payload, src_payload, copy_len); + nlmsg_end(nw); + + return (true); +} + +static bool +nlmsg_to_linux(int netlink_family, struct nlmsghdr *hdr, struct nlpcb *nlp, + struct nl_writer *nw) +{ + if (hdr->nlmsg_type < NLMSG_MIN_TYPE) { + switch (hdr->nlmsg_type) { + case NLMSG_ERROR: + return (nlmsg_error_to_linux(hdr, nlp, nw)); + case NLMSG_NOOP: + case NLMSG_DONE: + case NLMSG_OVERRUN: + return (handle_default_out(hdr, nw)); + default: + RT_LOG(LOG_DEBUG, "[WARN] Passing message type %d untranslated", + hdr->nlmsg_type); + return (handle_default_out(hdr, nw)); + } + } + + switch (netlink_family) { + case NETLINK_ROUTE: + return (rtnl_to_linux(hdr, nlp, nw)); + default: + return (handle_default_out(hdr, nw)); + } +} + +static struct mbuf * +nlmsgs_to_linux(int netlink_family, char *buf, int data_length, struct nlpcb *nlp) +{ + RT_LOG(LOG_DEBUG3, "LINUX: get %p size %d", buf, data_length); + struct nl_writer nw = {}; + + struct mbuf *m = NULL; + if (!nlmsg_get_chain_writer(&nw, data_length, &m)) { + RT_LOG(LOG_DEBUG, "unable to setup chain writer for size %d", + data_length); + return (NULL); + } + + /* Assume correct headers. Buffer IS mutable */ + int count = 0; + for (int offset = 0; offset + sizeof(struct nlmsghdr) <= data_length;) { + struct nlmsghdr *hdr = (struct nlmsghdr *)&buf[offset]; + int msglen = NLMSG_ALIGN(hdr->nlmsg_len); + count++; + + if (!nlmsg_to_linux(netlink_family, hdr, nlp, &nw)) { + RT_LOG(LOG_DEBUG, "failed to process msg type %d", + hdr->nlmsg_type); + m_freem(m); + return (NULL); + } + offset += msglen; + } + nlmsg_flush(&nw); + RT_LOG(LOG_DEBUG3, "Processed %d messages, chain size %d", count, + m ? m_length(m, NULL) : 0); + + return (m); +} + +static struct mbuf * +mbufs_to_linux(int netlink_family, struct mbuf *m, struct nlpcb *nlp) +{ + /* XXX: easiest solution, not optimized for performance */ + int data_length = m_length(m, NULL); + char *buf = malloc(data_length, M_LINUX, M_NOWAIT); + if (buf == NULL) { + RT_LOG(LOG_DEBUG, "unable to allocate %d bytes, dropping message", + data_length); + m_freem(m); + return (NULL); + } + m_copydata(m, 0, data_length, buf); + m_freem(m); + + m = nlmsgs_to_linux(netlink_family, buf, data_length, nlp); + free(buf, M_LINUX); + + return (m); +} + +static struct linux_netlink_provider linux_netlink_v1 = { + .mbufs_to_linux = mbufs_to_linux, + .msgs_to_linux = nlmsgs_to_linux, + .msg_from_linux = nlmsg_from_linux, +}; + +void +linux_netlink_register() +{ + linux_netlink_p = &linux_netlink_v1; +} + +void +linux_netlink_deregister() +{ + linux_netlink_p = NULL; +} diff --git a/sys/compat/linux/linux_socket.c b/sys/compat/linux/linux_socket.c index fa9c39673fee..42dd7bcb3d36 100644 --- a/sys/compat/linux/linux_socket.c +++ b/sys/compat/linux/linux_socket.c @@ -1,2563 +1,2569 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 1995 Søren Schmidt * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); /* XXX we use functions that might not exist. */ #include "opt_compat.h" #include "opt_inet6.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef INET6 #include #include #endif #ifdef COMPAT_LINUX32 #include #include #else #include #include #endif #include #include #include #include #include #include #include #define SECURITY_CONTEXT_STRING "unconfined" static int linux_sendmsg_common(struct thread *, l_int, struct l_msghdr *, l_uint); static int linux_recvmsg_common(struct thread *, l_int, struct l_msghdr *, l_uint, struct msghdr *); static int linux_set_socket_flags(int, int *); +#define SOL_NETLINK 270 + static int linux_to_bsd_sockopt_level(int level) { if (level == LINUX_SOL_SOCKET) return (SOL_SOCKET); /* Remaining values are RFC-defined protocol numbers. */ return (level); } static int bsd_to_linux_sockopt_level(int level) { if (level == SOL_SOCKET) return (LINUX_SOL_SOCKET); return (level); } static int linux_to_bsd_ip_sockopt(int opt) { switch (opt) { /* known and translated sockopts */ case LINUX_IP_TOS: return (IP_TOS); case LINUX_IP_TTL: return (IP_TTL); case LINUX_IP_HDRINCL: return (IP_HDRINCL); case LINUX_IP_OPTIONS: return (IP_OPTIONS); case LINUX_IP_RECVOPTS: LINUX_RATELIMIT_MSG_NOTTESTED("IPv4 socket option IP_RECVOPTS"); return (IP_RECVOPTS); case LINUX_IP_RETOPTS: LINUX_RATELIMIT_MSG_NOTTESTED("IPv4 socket option IP_REETOPTS"); return (IP_RETOPTS); case LINUX_IP_RECVTTL: LINUX_RATELIMIT_MSG_NOTTESTED("IPv4 socket option IP_RECVTTL"); return (IP_RECVTTL); case LINUX_IP_RECVTOS: LINUX_RATELIMIT_MSG_NOTTESTED("IPv4 socket option IP_RECVTOS"); return (IP_RECVTOS); case LINUX_IP_FREEBIND: LINUX_RATELIMIT_MSG_NOTTESTED("IPv4 socket option IP_FREEBIND"); return (IP_BINDANY); case LINUX_IP_IPSEC_POLICY: /* we have this option, but not documented in ip(4) manpage */ LINUX_RATELIMIT_MSG_NOTTESTED("IPv4 socket option IP_IPSEC_POLICY"); return (IP_IPSEC_POLICY); case LINUX_IP_MINTTL: LINUX_RATELIMIT_MSG_NOTTESTED("IPv4 socket option IP_MINTTL"); return (IP_MINTTL); case LINUX_IP_MULTICAST_IF: return (IP_MULTICAST_IF); case LINUX_IP_MULTICAST_TTL: return (IP_MULTICAST_TTL); case LINUX_IP_MULTICAST_LOOP: return (IP_MULTICAST_LOOP); case LINUX_IP_ADD_MEMBERSHIP: return (IP_ADD_MEMBERSHIP); case LINUX_IP_DROP_MEMBERSHIP: return (IP_DROP_MEMBERSHIP); case LINUX_IP_UNBLOCK_SOURCE: LINUX_RATELIMIT_MSG_NOTTESTED("IPv4 socket option IP_UNBLOCK_SOURCE"); return (IP_UNBLOCK_SOURCE); case LINUX_IP_BLOCK_SOURCE: LINUX_RATELIMIT_MSG_NOTTESTED("IPv4 socket option IP_BLOCK_SOURCE"); return (IP_BLOCK_SOURCE); case LINUX_IP_ADD_SOURCE_MEMBERSHIP: LINUX_RATELIMIT_MSG_NOTTESTED("IPv4 socket option IP_ADD_SOURCE_MEMBERSHIP"); return (IP_ADD_SOURCE_MEMBERSHIP); case LINUX_IP_DROP_SOURCE_MEMBERSHIP: LINUX_RATELIMIT_MSG_NOTTESTED("IPv4 socket option IP_DROP_SOURCE_MEMBERSHIP"); return (IP_DROP_SOURCE_MEMBERSHIP); case LINUX_MCAST_JOIN_GROUP: LINUX_RATELIMIT_MSG_NOTTESTED("IPv4 socket option IP_MCAST_JOIN_GROUP"); return (MCAST_JOIN_GROUP); case LINUX_MCAST_LEAVE_GROUP: LINUX_RATELIMIT_MSG_NOTTESTED("IPv4 socket option IP_MCAST_LEAVE_GROUP"); return (MCAST_LEAVE_GROUP); case LINUX_MCAST_JOIN_SOURCE_GROUP: LINUX_RATELIMIT_MSG_NOTTESTED("IPv4 socket option IP_MCAST_JOIN_SOURCE_GROUP"); return (MCAST_JOIN_SOURCE_GROUP); case LINUX_MCAST_LEAVE_SOURCE_GROUP: LINUX_RATELIMIT_MSG_NOTTESTED("IPv4 socket option IP_MCAST_LEAVE_SOURCE_GROUP"); return (MCAST_LEAVE_SOURCE_GROUP); case LINUX_IP_RECVORIGDSTADDR: return (IP_RECVORIGDSTADDR); /* known but not implemented sockopts */ case LINUX_IP_ROUTER_ALERT: LINUX_RATELIMIT_MSG_OPT1( "unsupported IPv4 socket option IP_ROUTER_ALERT (%d), you can not do user-space routing from linux programs", opt); return (-2); case LINUX_IP_PKTINFO: LINUX_RATELIMIT_MSG_OPT1( "unsupported IPv4 socket option IP_PKTINFO (%d), you can not get extended packet info for datagram sockets in linux programs", opt); return (-2); case LINUX_IP_PKTOPTIONS: LINUX_RATELIMIT_MSG_OPT1( "unsupported IPv4 socket option IP_PKTOPTIONS (%d)", opt); return (-2); case LINUX_IP_MTU_DISCOVER: LINUX_RATELIMIT_MSG_OPT1( "unsupported IPv4 socket option IP_MTU_DISCOVER (%d), your linux program can not control path-MTU discovery", opt); return (-2); case LINUX_IP_RECVERR: /* needed by steam */ LINUX_RATELIMIT_MSG_OPT1( "unsupported IPv4 socket option IP_RECVERR (%d), you can not get extended reliability info in linux programs", opt); return (-2); case LINUX_IP_MTU: LINUX_RATELIMIT_MSG_OPT1( "unsupported IPv4 socket option IP_MTU (%d), your linux program can not control the MTU on this socket", opt); return (-2); case LINUX_IP_XFRM_POLICY: LINUX_RATELIMIT_MSG_OPT1( "unsupported IPv4 socket option IP_XFRM_POLICY (%d)", opt); return (-2); case LINUX_IP_PASSSEC: /* needed by steam */ LINUX_RATELIMIT_MSG_OPT1( "unsupported IPv4 socket option IP_PASSSEC (%d), you can not get IPSEC related credential information associated with this socket in linux programs -- if you do not use IPSEC, you can ignore this", opt); return (-2); case LINUX_IP_TRANSPARENT: /* IP_BINDANY or more? */ LINUX_RATELIMIT_MSG_OPT1( "unsupported IPv4 socket option IP_TRANSPARENT (%d), you can not enable transparent proxying in linux programs -- note, IP_FREEBIND is supported, no idea if the FreeBSD IP_BINDANY is equivalent to the Linux IP_TRANSPARENT or not, any info is welcome", opt); return (-2); case LINUX_IP_NODEFRAG: LINUX_RATELIMIT_MSG_OPT1( "unsupported IPv4 socket option IP_NODEFRAG (%d)", opt); return (-2); case LINUX_IP_CHECKSUM: LINUX_RATELIMIT_MSG_OPT1( "unsupported IPv4 socket option IP_CHECKSUM (%d)", opt); return (-2); case LINUX_IP_BIND_ADDRESS_NO_PORT: LINUX_RATELIMIT_MSG_OPT1( "unsupported IPv4 socket option IP_BIND_ADDRESS_NO_PORT (%d)", opt); return (-2); case LINUX_IP_RECVFRAGSIZE: LINUX_RATELIMIT_MSG_OPT1( "unsupported IPv4 socket option IP_RECVFRAGSIZE (%d)", opt); return (-2); case LINUX_MCAST_MSFILTER: LINUX_RATELIMIT_MSG_OPT1( "unsupported IPv4 socket option IP_MCAST_MSFILTER (%d)", opt); return (-2); case LINUX_IP_MULTICAST_ALL: LINUX_RATELIMIT_MSG_OPT1( "unsupported IPv4 socket option IP_MULTICAST_ALL (%d), your linux program will not see all multicast groups joined by the entire system, only those the program joined itself on this socket", opt); return (-2); case LINUX_IP_UNICAST_IF: LINUX_RATELIMIT_MSG_OPT1( "unsupported IPv4 socket option IP_UNICAST_IF (%d)", opt); return (-2); /* unknown sockopts */ default: return (-1); } } static int linux_to_bsd_ip6_sockopt(int opt) { switch (opt) { /* known and translated sockopts */ case LINUX_IPV6_2292PKTINFO: LINUX_RATELIMIT_MSG_NOTTESTED("IPv6 socket option IPV6_2292PKTINFO"); return (IPV6_2292PKTINFO); case LINUX_IPV6_2292HOPOPTS: LINUX_RATELIMIT_MSG_NOTTESTED("IPv6 socket option IPV6_2292HOPOPTS"); return (IPV6_2292HOPOPTS); case LINUX_IPV6_2292DSTOPTS: LINUX_RATELIMIT_MSG_NOTTESTED("IPv6 socket option IPV6_2292DSTOPTS"); return (IPV6_2292DSTOPTS); case LINUX_IPV6_2292RTHDR: LINUX_RATELIMIT_MSG_NOTTESTED("IPv6 socket option IPV6_2292RTHDR"); return (IPV6_2292RTHDR); case LINUX_IPV6_2292PKTOPTIONS: LINUX_RATELIMIT_MSG_NOTTESTED("IPv6 socket option IPV6_2292PKTOPTIONS"); return (IPV6_2292PKTOPTIONS); case LINUX_IPV6_CHECKSUM: LINUX_RATELIMIT_MSG_NOTTESTED("IPv6 socket option IPV6_CHECKSUM"); return (IPV6_CHECKSUM); case LINUX_IPV6_2292HOPLIMIT: LINUX_RATELIMIT_MSG_NOTTESTED("IPv6 socket option IPV6_2292HOPLIMIT"); return (IPV6_2292HOPLIMIT); case LINUX_IPV6_NEXTHOP: return (IPV6_NEXTHOP); case LINUX_IPV6_UNICAST_HOPS: return (IPV6_UNICAST_HOPS); case LINUX_IPV6_MULTICAST_IF: return (IPV6_MULTICAST_IF); case LINUX_IPV6_MULTICAST_HOPS: return (IPV6_MULTICAST_HOPS); case LINUX_IPV6_MULTICAST_LOOP: return (IPV6_MULTICAST_LOOP); case LINUX_IPV6_ADD_MEMBERSHIP: return (IPV6_JOIN_GROUP); case LINUX_IPV6_DROP_MEMBERSHIP: return (IPV6_LEAVE_GROUP); case LINUX_IPV6_V6ONLY: return (IPV6_V6ONLY); case LINUX_IPV6_IPSEC_POLICY: /* we have this option, but not documented in ip6(4) manpage */ LINUX_RATELIMIT_MSG_NOTTESTED("IPv6 socket option IPV6_IPSEC_POLICY"); return (IPV6_IPSEC_POLICY); case LINUX_MCAST_JOIN_GROUP: LINUX_RATELIMIT_MSG_NOTTESTED("IPv6 socket option IPV6_JOIN_GROUP"); return (IPV6_JOIN_GROUP); case LINUX_MCAST_LEAVE_GROUP: LINUX_RATELIMIT_MSG_NOTTESTED("IPv6 socket option IPV6_LEAVE_GROUP"); return (IPV6_LEAVE_GROUP); case LINUX_IPV6_RECVPKTINFO: LINUX_RATELIMIT_MSG_NOTTESTED("IPv6 socket option IPV6_RECVPKTINFO"); return (IPV6_RECVPKTINFO); case LINUX_IPV6_PKTINFO: LINUX_RATELIMIT_MSG_NOTTESTED("IPv6 socket option IPV6_PKTINFO"); return (IPV6_PKTINFO); case LINUX_IPV6_RECVHOPLIMIT: LINUX_RATELIMIT_MSG_NOTTESTED("IPv6 socket option IPV6_RECVHOPLIMIT"); return (IPV6_RECVHOPLIMIT); case LINUX_IPV6_HOPLIMIT: LINUX_RATELIMIT_MSG_NOTTESTED("IPv6 socket option IPV6_HOPLIMIT"); return (IPV6_HOPLIMIT); case LINUX_IPV6_RECVHOPOPTS: LINUX_RATELIMIT_MSG_NOTTESTED("IPv6 socket option IPV6_RECVHOPOPTS"); return (IPV6_RECVHOPOPTS); case LINUX_IPV6_HOPOPTS: LINUX_RATELIMIT_MSG_NOTTESTED("IPv6 socket option IPV6_HOPOPTS"); return (IPV6_HOPOPTS); case LINUX_IPV6_RTHDRDSTOPTS: LINUX_RATELIMIT_MSG_NOTTESTED("IPv6 socket option IPV6_RTHDRDSTOPTS"); return (IPV6_RTHDRDSTOPTS); case LINUX_IPV6_RECVRTHDR: LINUX_RATELIMIT_MSG_NOTTESTED("IPv6 socket option IPV6_RECVRTHDR"); return (IPV6_RECVRTHDR); case LINUX_IPV6_RTHDR: LINUX_RATELIMIT_MSG_NOTTESTED("IPv6 socket option IPV6_RTHDR"); return (IPV6_RTHDR); case LINUX_IPV6_RECVDSTOPTS: LINUX_RATELIMIT_MSG_NOTTESTED("IPv6 socket option IPV6_RECVDSTOPTS"); return (IPV6_RECVDSTOPTS); case LINUX_IPV6_DSTOPTS: LINUX_RATELIMIT_MSG_NOTTESTED("IPv6 socket option IPV6_DSTOPTS"); return (IPV6_DSTOPTS); case LINUX_IPV6_RECVPATHMTU: LINUX_RATELIMIT_MSG_NOTTESTED("IPv6 socket option IPV6_RECVPATHMTU"); return (IPV6_RECVPATHMTU); case LINUX_IPV6_PATHMTU: LINUX_RATELIMIT_MSG_NOTTESTED("IPv6 socket option IPV6_PATHMTU"); return (IPV6_PATHMTU); case LINUX_IPV6_DONTFRAG: return (IPV6_DONTFRAG); case LINUX_IPV6_AUTOFLOWLABEL: LINUX_RATELIMIT_MSG_NOTTESTED("IPv6 socket option IPV6_AUTOFLOWLABEL"); return (IPV6_AUTOFLOWLABEL); case LINUX_IPV6_ORIGDSTADDR: LINUX_RATELIMIT_MSG_NOTTESTED("IPv6 socket option IPV6_ORIGDSTADDR"); return (IPV6_ORIGDSTADDR); case LINUX_IPV6_FREEBIND: LINUX_RATELIMIT_MSG_NOTTESTED("IPv6 socket option IPV6_FREEBIND"); return (IPV6_BINDANY); /* known but not implemented sockopts */ case LINUX_IPV6_ADDRFORM: LINUX_RATELIMIT_MSG_OPT1( "unsupported IPv6 socket option IPV6_ADDRFORM (%d), you linux program can not convert the socket to IPv4", opt); return (-2); case LINUX_IPV6_AUTHHDR: LINUX_RATELIMIT_MSG_OPT1( "unsupported IPv6 socket option IPV6_AUTHHDR (%d), your linux program can not get the authentication header info of IPv6 packets", opt); return (-2); case LINUX_IPV6_FLOWINFO: LINUX_RATELIMIT_MSG_OPT1( "unsupported IPv6 socket option IPV6_FLOWINFO (%d), your linux program can not get the flowid of IPv6 packets", opt); return (-2); case LINUX_IPV6_ROUTER_ALERT: LINUX_RATELIMIT_MSG_OPT1( "unsupported IPv6 socket option IPV6_ROUTER_ALERT (%d), you can not do user-space routing from linux programs", opt); return (-2); case LINUX_IPV6_MTU_DISCOVER: LINUX_RATELIMIT_MSG_OPT1( "unsupported IPv6 socket option IPV6_MTU_DISCOVER (%d), your linux program can not control path-MTU discovery", opt); return (-2); case LINUX_IPV6_MTU: LINUX_RATELIMIT_MSG_OPT1( "unsupported IPv6 socket option IPV6_MTU (%d), your linux program can not control the MTU on this socket", opt); return (-2); case LINUX_IPV6_JOIN_ANYCAST: LINUX_RATELIMIT_MSG_OPT1( "unsupported IPv6 socket option IPV6_JOIN_ANYCAST (%d)", opt); return (-2); case LINUX_IPV6_LEAVE_ANYCAST: LINUX_RATELIMIT_MSG_OPT1( "unsupported IPv6 socket option IPV6_LEAVE_ANYCAST (%d)", opt); return (-2); case LINUX_IPV6_MULTICAST_ALL: LINUX_RATELIMIT_MSG_OPT1( "unsupported IPv6 socket option IPV6_MULTICAST_ALL (%d)", opt); return (-2); case LINUX_IPV6_ROUTER_ALERT_ISOLATE: LINUX_RATELIMIT_MSG_OPT1( "unsupported IPv6 socket option IPV6_ROUTER_ALERT_ISOLATE (%d)", opt); return (-2); case LINUX_IPV6_FLOWLABEL_MGR: LINUX_RATELIMIT_MSG_OPT1( "unsupported IPv6 socket option IPV6_FLOWLABEL_MGR (%d)", opt); return (-2); case LINUX_IPV6_FLOWINFO_SEND: LINUX_RATELIMIT_MSG_OPT1( "unsupported IPv6 socket option IPV6_FLOWINFO_SEND (%d)", opt); return (-2); case LINUX_IPV6_XFRM_POLICY: LINUX_RATELIMIT_MSG_OPT1( "unsupported IPv6 socket option IPV6_XFRM_POLICY (%d)", opt); return (-2); case LINUX_IPV6_HDRINCL: LINUX_RATELIMIT_MSG_OPT1( "unsupported IPv6 socket option IPV6_HDRINCL (%d)", opt); return (-2); case LINUX_MCAST_BLOCK_SOURCE: LINUX_RATELIMIT_MSG_OPT1( "unsupported IPv6 socket option MCAST_BLOCK_SOURCE (%d), your linux program may see more multicast stuff than it wants", opt); return (-2); case LINUX_MCAST_UNBLOCK_SOURCE: LINUX_RATELIMIT_MSG_OPT1( "unsupported IPv6 socket option MCAST_UNBLOCK_SOURCE (%d), your linux program may not see all the multicast stuff it wants", opt); return (-2); case LINUX_MCAST_JOIN_SOURCE_GROUP: LINUX_RATELIMIT_MSG_OPT1( "unsupported IPv6 socket option MCAST_JOIN_SOURCE_GROUP (%d), your linux program is not able to join a multicast source group", opt); return (-2); case LINUX_MCAST_LEAVE_SOURCE_GROUP: LINUX_RATELIMIT_MSG_OPT1( "unsupported IPv6 socket option MCAST_LEAVE_SOURCE_GROUP (%d), your linux program is not able to leave a multicast source group -- but it was also not able to join one, so no issue", opt); return (-2); case LINUX_MCAST_MSFILTER: LINUX_RATELIMIT_MSG_OPT1( "unsupported IPv6 socket option MCAST_MSFILTER (%d), your linux program can not manipulate the multicast filter, it may see more multicast data than it wants to see", opt); return (-2); case LINUX_IPV6_ADDR_PREFERENCES: LINUX_RATELIMIT_MSG_OPT1( "unsupported IPv6 socket option IPV6_ADDR_PREFERENCES (%d)", opt); return (-2); case LINUX_IPV6_MINHOPCOUNT: LINUX_RATELIMIT_MSG_OPT1( "unsupported IPv6 socket option IPV6_MINHOPCOUNT (%d)", opt); return (-2); case LINUX_IPV6_TRANSPARENT: /* IP_BINDANY or more? */ LINUX_RATELIMIT_MSG_OPT1( "unsupported IPv6 socket option IPV6_TRANSPARENT (%d), you can not enable transparent proxying in linux programs -- note, IP_FREEBIND is supported, no idea if the FreeBSD IP_BINDANY is equivalent to the Linux IP_TRANSPARENT or not, any info is welcome", opt); return (-2); case LINUX_IPV6_UNICAST_IF: LINUX_RATELIMIT_MSG_OPT1( "unsupported IPv6 socket option IPV6_UNICAST_IF (%d)", opt); return (-2); case LINUX_IPV6_RECVFRAGSIZE: LINUX_RATELIMIT_MSG_OPT1( "unsupported IPv6 socket option IPV6_RECVFRAGSIZE (%d)", opt); return (-2); /* unknown sockopts */ default: return (-1); } } static int linux_to_bsd_so_sockopt(int opt) { switch (opt) { case LINUX_SO_DEBUG: return (SO_DEBUG); case LINUX_SO_REUSEADDR: return (SO_REUSEADDR); case LINUX_SO_TYPE: return (SO_TYPE); case LINUX_SO_ERROR: return (SO_ERROR); case LINUX_SO_DONTROUTE: return (SO_DONTROUTE); case LINUX_SO_BROADCAST: return (SO_BROADCAST); case LINUX_SO_SNDBUF: case LINUX_SO_SNDBUFFORCE: return (SO_SNDBUF); case LINUX_SO_RCVBUF: case LINUX_SO_RCVBUFFORCE: return (SO_RCVBUF); case LINUX_SO_KEEPALIVE: return (SO_KEEPALIVE); case LINUX_SO_OOBINLINE: return (SO_OOBINLINE); case LINUX_SO_LINGER: return (SO_LINGER); case LINUX_SO_REUSEPORT: return (SO_REUSEPORT_LB); case LINUX_SO_PASSCRED: return (LOCAL_CREDS_PERSISTENT); case LINUX_SO_PEERCRED: return (LOCAL_PEERCRED); case LINUX_SO_RCVLOWAT: return (SO_RCVLOWAT); case LINUX_SO_SNDLOWAT: return (SO_SNDLOWAT); case LINUX_SO_RCVTIMEO: return (SO_RCVTIMEO); case LINUX_SO_SNDTIMEO: return (SO_SNDTIMEO); case LINUX_SO_TIMESTAMPO: case LINUX_SO_TIMESTAMPN: return (SO_TIMESTAMP); case LINUX_SO_TIMESTAMPNSO: case LINUX_SO_TIMESTAMPNSN: return (SO_BINTIME); case LINUX_SO_ACCEPTCONN: return (SO_ACCEPTCONN); case LINUX_SO_PROTOCOL: return (SO_PROTOCOL); case LINUX_SO_DOMAIN: return (SO_DOMAIN); } return (-1); } static int linux_to_bsd_tcp_sockopt(int opt) { switch (opt) { case LINUX_TCP_NODELAY: return (TCP_NODELAY); case LINUX_TCP_MAXSEG: return (TCP_MAXSEG); case LINUX_TCP_CORK: return (TCP_NOPUSH); case LINUX_TCP_KEEPIDLE: return (TCP_KEEPIDLE); case LINUX_TCP_KEEPINTVL: return (TCP_KEEPINTVL); case LINUX_TCP_KEEPCNT: return (TCP_KEEPCNT); case LINUX_TCP_INFO: LINUX_RATELIMIT_MSG_OPT1( "unsupported TCP socket option TCP_INFO (%d)", opt); return (-2); case LINUX_TCP_MD5SIG: return (TCP_MD5SIG); } return (-1); } static int linux_to_bsd_msg_flags(int flags) { int ret_flags = 0; if (flags & LINUX_MSG_OOB) ret_flags |= MSG_OOB; if (flags & LINUX_MSG_PEEK) ret_flags |= MSG_PEEK; if (flags & LINUX_MSG_DONTROUTE) ret_flags |= MSG_DONTROUTE; if (flags & LINUX_MSG_CTRUNC) ret_flags |= MSG_CTRUNC; if (flags & LINUX_MSG_TRUNC) ret_flags |= MSG_TRUNC; if (flags & LINUX_MSG_DONTWAIT) ret_flags |= MSG_DONTWAIT; if (flags & LINUX_MSG_EOR) ret_flags |= MSG_EOR; if (flags & LINUX_MSG_WAITALL) ret_flags |= MSG_WAITALL; if (flags & LINUX_MSG_NOSIGNAL) ret_flags |= MSG_NOSIGNAL; if (flags & LINUX_MSG_PROXY) LINUX_RATELIMIT_MSG_OPT1("socket message flag MSG_PROXY (%d) not handled", LINUX_MSG_PROXY); if (flags & LINUX_MSG_FIN) LINUX_RATELIMIT_MSG_OPT1("socket message flag MSG_FIN (%d) not handled", LINUX_MSG_FIN); if (flags & LINUX_MSG_SYN) LINUX_RATELIMIT_MSG_OPT1("socket message flag MSG_SYN (%d) not handled", LINUX_MSG_SYN); if (flags & LINUX_MSG_CONFIRM) LINUX_RATELIMIT_MSG_OPT1("socket message flag MSG_CONFIRM (%d) not handled", LINUX_MSG_CONFIRM); if (flags & LINUX_MSG_RST) LINUX_RATELIMIT_MSG_OPT1("socket message flag MSG_RST (%d) not handled", LINUX_MSG_RST); if (flags & LINUX_MSG_ERRQUEUE) LINUX_RATELIMIT_MSG_OPT1("socket message flag MSG_ERRQUEUE (%d) not handled", LINUX_MSG_ERRQUEUE); return (ret_flags); } static int linux_to_bsd_cmsg_type(int cmsg_type) { switch (cmsg_type) { case LINUX_SCM_RIGHTS: return (SCM_RIGHTS); case LINUX_SCM_CREDENTIALS: return (SCM_CREDS); } return (-1); } static int bsd_to_linux_ip_cmsg_type(int cmsg_type) { switch (cmsg_type) { case IP_RECVORIGDSTADDR: return (LINUX_IP_RECVORIGDSTADDR); } return (-1); } static int bsd_to_linux_cmsg_type(struct proc *p, int cmsg_type, int cmsg_level) { struct linux_pemuldata *pem; if (cmsg_level == IPPROTO_IP) return (bsd_to_linux_ip_cmsg_type(cmsg_type)); if (cmsg_level != SOL_SOCKET) return (-1); pem = pem_find(p); switch (cmsg_type) { case SCM_RIGHTS: return (LINUX_SCM_RIGHTS); case SCM_CREDS: return (LINUX_SCM_CREDENTIALS); case SCM_CREDS2: return (LINUX_SCM_CREDENTIALS); case SCM_TIMESTAMP: return (pem->so_timestamp); case SCM_BINTIME: return (pem->so_timestampns); } return (-1); } static int linux_to_bsd_msghdr(struct msghdr *bhdr, const struct l_msghdr *lhdr) { if (lhdr->msg_controllen > INT_MAX) return (ENOBUFS); bhdr->msg_name = PTRIN(lhdr->msg_name); bhdr->msg_namelen = lhdr->msg_namelen; bhdr->msg_iov = PTRIN(lhdr->msg_iov); bhdr->msg_iovlen = lhdr->msg_iovlen; bhdr->msg_control = PTRIN(lhdr->msg_control); /* * msg_controllen is skipped since BSD and LINUX control messages * are potentially different sizes (e.g. the cred structure used * by SCM_CREDS is different between the two operating system). * * The caller can set it (if necessary) after converting all the * control messages. */ bhdr->msg_flags = linux_to_bsd_msg_flags(lhdr->msg_flags); return (0); } static int bsd_to_linux_msghdr(const struct msghdr *bhdr, struct l_msghdr *lhdr) { lhdr->msg_name = PTROUT(bhdr->msg_name); lhdr->msg_namelen = bhdr->msg_namelen; lhdr->msg_iov = PTROUT(bhdr->msg_iov); lhdr->msg_iovlen = bhdr->msg_iovlen; lhdr->msg_control = PTROUT(bhdr->msg_control); /* * msg_controllen is skipped since BSD and LINUX control messages * are potentially different sizes (e.g. the cred structure used * by SCM_CREDS is different between the two operating system). * * The caller can set it (if necessary) after converting all the * control messages. */ /* msg_flags skipped */ return (0); } static int linux_set_socket_flags(int lflags, int *flags) { if (lflags & ~(LINUX_SOCK_CLOEXEC | LINUX_SOCK_NONBLOCK)) return (EINVAL); if (lflags & LINUX_SOCK_NONBLOCK) *flags |= SOCK_NONBLOCK; if (lflags & LINUX_SOCK_CLOEXEC) *flags |= SOCK_CLOEXEC; return (0); } static int linux_copyout_sockaddr(const struct sockaddr *sa, void *uaddr, size_t len) { struct l_sockaddr *lsa; int error; error = bsd_to_linux_sockaddr(sa, &lsa, len); if (error != 0) return (error); error = copyout(lsa, uaddr, len); free(lsa, M_LINUX); return (error); } static int linux_sendit(struct thread *td, int s, struct msghdr *mp, int flags, struct mbuf *control, enum uio_seg segflg) { struct sockaddr *to; int error, len; if (mp->msg_name != NULL) { len = mp->msg_namelen; error = linux_to_bsd_sockaddr(mp->msg_name, &to, &len); if (error != 0) return (error); mp->msg_name = to; } else to = NULL; error = kern_sendit(td, s, mp, linux_to_bsd_msg_flags(flags), control, segflg); if (to) free(to, M_SONAME); return (error); } /* Return 0 if IP_HDRINCL is set for the given socket. */ static int linux_check_hdrincl(struct thread *td, int s) { int error, optval; socklen_t size_val; size_val = sizeof(optval); error = kern_getsockopt(td, s, IPPROTO_IP, IP_HDRINCL, &optval, UIO_SYSSPACE, &size_val); if (error != 0) return (error); return (optval == 0); } /* * Updated sendto() when IP_HDRINCL is set: * tweak endian-dependent fields in the IP packet. */ static int linux_sendto_hdrincl(struct thread *td, struct linux_sendto_args *linux_args) { /* * linux_ip_copysize defines how many bytes we should copy * from the beginning of the IP packet before we customize it for BSD. * It should include all the fields we modify (ip_len and ip_off). */ #define linux_ip_copysize 8 struct ip *packet; struct msghdr msg; struct iovec aiov[1]; int error; /* Check that the packet isn't too big or too small. */ if (linux_args->len < linux_ip_copysize || linux_args->len > IP_MAXPACKET) return (EINVAL); packet = (struct ip *)malloc(linux_args->len, M_LINUX, M_WAITOK); /* Make kernel copy of the packet to be sent */ if ((error = copyin(PTRIN(linux_args->msg), packet, linux_args->len))) goto goout; /* Convert fields from Linux to BSD raw IP socket format */ packet->ip_len = linux_args->len; packet->ip_off = ntohs(packet->ip_off); /* Prepare the msghdr and iovec structures describing the new packet */ msg.msg_name = PTRIN(linux_args->to); msg.msg_namelen = linux_args->tolen; msg.msg_iov = aiov; msg.msg_iovlen = 1; msg.msg_control = NULL; msg.msg_flags = 0; aiov[0].iov_base = (char *)packet; aiov[0].iov_len = linux_args->len; error = linux_sendit(td, linux_args->s, &msg, linux_args->flags, NULL, UIO_SYSSPACE); goout: free(packet, M_LINUX); return (error); } static const char *linux_netlink_names[] = { [LINUX_NETLINK_ROUTE] = "ROUTE", [LINUX_NETLINK_SOCK_DIAG] = "SOCK_DIAG", [LINUX_NETLINK_NFLOG] = "NFLOG", [LINUX_NETLINK_SELINUX] = "SELINUX", [LINUX_NETLINK_AUDIT] = "AUDIT", [LINUX_NETLINK_FIB_LOOKUP] = "FIB_LOOKUP", [LINUX_NETLINK_NETFILTER] = "NETFILTER", [LINUX_NETLINK_KOBJECT_UEVENT] = "KOBJECT_UEVENT", }; int linux_socket(struct thread *td, struct linux_socket_args *args) { int domain, retval_socket, type; type = args->type & LINUX_SOCK_TYPE_MASK; if (type < 0 || type > LINUX_SOCK_MAX) return (EINVAL); retval_socket = linux_set_socket_flags(args->type & ~LINUX_SOCK_TYPE_MASK, &type); if (retval_socket != 0) return (retval_socket); domain = linux_to_bsd_domain(args->domain); if (domain == -1) { /* Mask off SOCK_NONBLOCK / CLOEXEC for error messages. */ type = args->type & LINUX_SOCK_TYPE_MASK; if (args->domain == LINUX_AF_NETLINK && args->protocol == LINUX_NETLINK_AUDIT) { ; /* Do nothing, quietly. */ } else if (args->domain == LINUX_AF_NETLINK) { const char *nl_name; if (args->protocol >= 0 && args->protocol < nitems(linux_netlink_names)) nl_name = linux_netlink_names[args->protocol]; else nl_name = NULL; if (nl_name != NULL) linux_msg(curthread, "unsupported socket(AF_NETLINK, %d, " "NETLINK_%s)", type, nl_name); else linux_msg(curthread, "unsupported socket(AF_NETLINK, %d, %d)", type, args->protocol); } else { linux_msg(curthread, "unsupported socket domain %d, " "type %d, protocol %d", args->domain, type, args->protocol); } return (EAFNOSUPPORT); } retval_socket = kern_socket(td, domain, type, args->protocol); if (retval_socket) return (retval_socket); if (type == SOCK_RAW && (args->protocol == IPPROTO_RAW || args->protocol == 0) && domain == PF_INET) { /* It's a raw IP socket: set the IP_HDRINCL option. */ int hdrincl; hdrincl = 1; /* We ignore any error returned by kern_setsockopt() */ kern_setsockopt(td, td->td_retval[0], IPPROTO_IP, IP_HDRINCL, &hdrincl, UIO_SYSSPACE, sizeof(hdrincl)); } #ifdef INET6 /* * Linux AF_INET6 socket has IPV6_V6ONLY setsockopt set to 0 by default * and some apps depend on this. So, set V6ONLY to 0 for Linux apps. * For simplicity we do this unconditionally of the net.inet6.ip6.v6only * sysctl value. */ if (domain == PF_INET6) { int v6only; v6only = 0; /* We ignore any error returned by setsockopt() */ kern_setsockopt(td, td->td_retval[0], IPPROTO_IPV6, IPV6_V6ONLY, &v6only, UIO_SYSSPACE, sizeof(v6only)); } #endif return (retval_socket); } int linux_bind(struct thread *td, struct linux_bind_args *args) { struct sockaddr *sa; int error; error = linux_to_bsd_sockaddr(PTRIN(args->name), &sa, &args->namelen); if (error != 0) return (error); error = kern_bindat(td, AT_FDCWD, args->s, sa); free(sa, M_SONAME); /* XXX */ if (error == EADDRNOTAVAIL && args->namelen != sizeof(struct sockaddr_in)) return (EINVAL); return (error); } int linux_connect(struct thread *td, struct linux_connect_args *args) { struct socket *so; struct sockaddr *sa; struct file *fp; int error; error = linux_to_bsd_sockaddr(PTRIN(args->name), &sa, &args->namelen); if (error != 0) return (error); error = kern_connectat(td, AT_FDCWD, args->s, sa); free(sa, M_SONAME); if (error != EISCONN) return (error); /* * Linux doesn't return EISCONN the first time it occurs, * when on a non-blocking socket. Instead it returns the * error getsockopt(SOL_SOCKET, SO_ERROR) would return on BSD. */ error = getsock(td, args->s, &cap_connect_rights, &fp); if (error != 0) return (error); error = EISCONN; so = fp->f_data; if (atomic_load_int(&fp->f_flag) & FNONBLOCK) { SOCK_LOCK(so); if (so->so_emuldata == 0) error = so->so_error; so->so_emuldata = (void *)1; SOCK_UNLOCK(so); } fdrop(fp, td); return (error); } int linux_listen(struct thread *td, struct linux_listen_args *args) { return (kern_listen(td, args->s, args->backlog)); } static int linux_accept_common(struct thread *td, int s, l_uintptr_t addr, l_uintptr_t namelen, int flags) { struct sockaddr *sa; struct file *fp, *fp1; int bflags, len; struct socket *so; int error, error1; bflags = 0; fp = NULL; sa = NULL; error = linux_set_socket_flags(flags, &bflags); if (error != 0) return (error); if (PTRIN(addr) == NULL) { len = 0; error = kern_accept4(td, s, NULL, NULL, bflags, NULL); } else { error = copyin(PTRIN(namelen), &len, sizeof(len)); if (error != 0) return (error); if (len < 0) return (EINVAL); error = kern_accept4(td, s, &sa, &len, bflags, &fp); } /* * Translate errno values into ones used by Linux. */ if (error != 0) { /* * XXX. This is wrong, different sockaddr structures * have different sizes. */ switch (error) { case EFAULT: if (namelen != sizeof(struct sockaddr_in)) error = EINVAL; break; case EINVAL: error1 = getsock(td, s, &cap_accept_rights, &fp1); if (error1 != 0) { error = error1; break; } so = fp1->f_data; if (so->so_type == SOCK_DGRAM) error = EOPNOTSUPP; fdrop(fp1, td); break; } return (error); } if (len != 0) { error = linux_copyout_sockaddr(sa, PTRIN(addr), len); if (error == 0) error = copyout(&len, PTRIN(namelen), sizeof(len)); if (error != 0) { fdclose(td, fp, td->td_retval[0]); td->td_retval[0] = 0; } } if (fp != NULL) fdrop(fp, td); free(sa, M_SONAME); return (error); } int linux_accept(struct thread *td, struct linux_accept_args *args) { return (linux_accept_common(td, args->s, args->addr, args->namelen, 0)); } int linux_accept4(struct thread *td, struct linux_accept4_args *args) { return (linux_accept_common(td, args->s, args->addr, args->namelen, args->flags)); } int linux_getsockname(struct thread *td, struct linux_getsockname_args *args) { struct sockaddr *sa; int len, error; error = copyin(PTRIN(args->namelen), &len, sizeof(len)); if (error != 0) return (error); error = kern_getsockname(td, args->s, &sa, &len); if (error != 0) return (error); if (len != 0) error = linux_copyout_sockaddr(sa, PTRIN(args->addr), len); free(sa, M_SONAME); if (error == 0) error = copyout(&len, PTRIN(args->namelen), sizeof(len)); return (error); } int linux_getpeername(struct thread *td, struct linux_getpeername_args *args) { struct sockaddr *sa; int len, error; error = copyin(PTRIN(args->namelen), &len, sizeof(len)); if (error != 0) return (error); if (len < 0) return (EINVAL); error = kern_getpeername(td, args->s, &sa, &len); if (error != 0) return (error); if (len != 0) error = linux_copyout_sockaddr(sa, PTRIN(args->addr), len); free(sa, M_SONAME); if (error == 0) error = copyout(&len, PTRIN(args->namelen), sizeof(len)); return (error); } int linux_socketpair(struct thread *td, struct linux_socketpair_args *args) { int domain, error, sv[2], type; domain = linux_to_bsd_domain(args->domain); if (domain != PF_LOCAL) return (EAFNOSUPPORT); type = args->type & LINUX_SOCK_TYPE_MASK; if (type < 0 || type > LINUX_SOCK_MAX) return (EINVAL); error = linux_set_socket_flags(args->type & ~LINUX_SOCK_TYPE_MASK, &type); if (error != 0) return (error); if (args->protocol != 0 && args->protocol != PF_UNIX) { /* * Use of PF_UNIX as protocol argument is not right, * but Linux does it. * Do not map PF_UNIX as its Linux value is identical * to FreeBSD one. */ return (EPROTONOSUPPORT); } error = kern_socketpair(td, domain, type, 0, sv); if (error != 0) return (error); error = copyout(sv, PTRIN(args->rsv), 2 * sizeof(int)); if (error != 0) { (void)kern_close(td, sv[0]); (void)kern_close(td, sv[1]); } return (error); } #if defined(__i386__) || (defined(__amd64__) && defined(COMPAT_LINUX32)) struct linux_send_args { register_t s; register_t msg; register_t len; register_t flags; }; static int linux_send(struct thread *td, struct linux_send_args *args) { struct sendto_args /* { int s; caddr_t buf; int len; int flags; caddr_t to; int tolen; } */ bsd_args; struct file *fp; int error; bsd_args.s = args->s; bsd_args.buf = (caddr_t)PTRIN(args->msg); bsd_args.len = args->len; bsd_args.flags = linux_to_bsd_msg_flags(args->flags); bsd_args.to = NULL; bsd_args.tolen = 0; error = sys_sendto(td, &bsd_args); if (error == ENOTCONN) { /* * Linux doesn't return ENOTCONN for non-blocking sockets. * Instead it returns the EAGAIN. */ error = getsock(td, args->s, &cap_send_rights, &fp); if (error == 0) { if (atomic_load_int(&fp->f_flag) & FNONBLOCK) error = EAGAIN; fdrop(fp, td); } } return (error); } struct linux_recv_args { register_t s; register_t msg; register_t len; register_t flags; }; static int linux_recv(struct thread *td, struct linux_recv_args *args) { struct recvfrom_args /* { int s; caddr_t buf; int len; int flags; struct sockaddr *from; socklen_t fromlenaddr; } */ bsd_args; bsd_args.s = args->s; bsd_args.buf = (caddr_t)PTRIN(args->msg); bsd_args.len = args->len; bsd_args.flags = linux_to_bsd_msg_flags(args->flags); bsd_args.from = NULL; bsd_args.fromlenaddr = 0; return (sys_recvfrom(td, &bsd_args)); } #endif /* __i386__ || (__amd64__ && COMPAT_LINUX32) */ int linux_sendto(struct thread *td, struct linux_sendto_args *args) { struct msghdr msg; struct iovec aiov; struct socket *so; struct file *fp; int error; if (linux_check_hdrincl(td, args->s) == 0) /* IP_HDRINCL set, tweak the packet before sending */ return (linux_sendto_hdrincl(td, args)); bzero(&msg, sizeof(msg)); error = getsock(td, args->s, &cap_send_connect_rights, &fp); if (error != 0) return (error); so = fp->f_data; if ((so->so_state & (SS_ISCONNECTED|SS_ISCONNECTING)) == 0) { msg.msg_name = PTRIN(args->to); msg.msg_namelen = args->tolen; } msg.msg_iov = &aiov; msg.msg_iovlen = 1; aiov.iov_base = PTRIN(args->msg); aiov.iov_len = args->len; fdrop(fp, td); return (linux_sendit(td, args->s, &msg, args->flags, NULL, UIO_USERSPACE)); } int linux_recvfrom(struct thread *td, struct linux_recvfrom_args *args) { struct sockaddr *sa; struct msghdr msg; struct iovec aiov; int error, fromlen; if (PTRIN(args->fromlen) != NULL) { error = copyin(PTRIN(args->fromlen), &fromlen, sizeof(fromlen)); if (error != 0) return (error); if (fromlen < 0) return (EINVAL); fromlen = min(fromlen, SOCK_MAXADDRLEN); sa = malloc(fromlen, M_SONAME, M_WAITOK); } else { fromlen = 0; sa = NULL; } msg.msg_name = sa; msg.msg_namelen = fromlen; msg.msg_iov = &aiov; msg.msg_iovlen = 1; aiov.iov_base = PTRIN(args->buf); aiov.iov_len = args->len; msg.msg_control = 0; msg.msg_flags = linux_to_bsd_msg_flags(args->flags); error = kern_recvit(td, args->s, &msg, UIO_SYSSPACE, NULL); if (error != 0) goto out; /* * XXX. Seems that FreeBSD is different from Linux here. Linux * fill source address if underlying protocol provides it, while * FreeBSD fill it if underlying protocol is not connection-oriented. * So, kern_recvit() set msg.msg_namelen to 0 if protocol pr_flags * does not contains PR_ADDR flag. */ if (PTRIN(args->from) != NULL && msg.msg_namelen != 0) error = linux_copyout_sockaddr(sa, PTRIN(args->from), msg.msg_namelen); if (error == 0 && PTRIN(args->fromlen) != NULL) error = copyout(&msg.msg_namelen, PTRIN(args->fromlen), sizeof(msg.msg_namelen)); out: free(sa, M_SONAME); return (error); } static int linux_sendmsg_common(struct thread *td, l_int s, struct l_msghdr *msghdr, l_uint flags) { struct cmsghdr *cmsg; struct mbuf *control; struct msghdr msg; struct l_cmsghdr linux_cmsg; struct l_cmsghdr *ptr_cmsg; struct l_msghdr linux_msghdr; struct iovec *iov; socklen_t datalen; struct sockaddr *sa; struct socket *so; sa_family_t sa_family; struct file *fp; void *data; l_size_t len; l_size_t clen; int error; error = copyin(msghdr, &linux_msghdr, sizeof(linux_msghdr)); if (error != 0) return (error); /* * Some Linux applications (ping) define a non-NULL control data * pointer, but a msg_controllen of 0, which is not allowed in the * FreeBSD system call interface. NULL the msg_control pointer in * order to handle this case. This should be checked, but allows the * Linux ping to work. */ if (PTRIN(linux_msghdr.msg_control) != NULL && linux_msghdr.msg_controllen == 0) linux_msghdr.msg_control = PTROUT(NULL); error = linux_to_bsd_msghdr(&msg, &linux_msghdr); if (error != 0) return (error); #ifdef COMPAT_LINUX32 error = linux32_copyiniov(PTRIN(msg.msg_iov), msg.msg_iovlen, &iov, EMSGSIZE); #else error = copyiniov(msg.msg_iov, msg.msg_iovlen, &iov, EMSGSIZE); #endif if (error != 0) return (error); control = NULL; error = kern_getsockname(td, s, &sa, &datalen); if (error != 0) goto bad; sa_family = sa->sa_family; free(sa, M_SONAME); if (flags & LINUX_MSG_OOB) { error = EOPNOTSUPP; if (sa_family == AF_UNIX) goto bad; error = getsock(td, s, &cap_send_rights, &fp); if (error != 0) goto bad; so = fp->f_data; if (so->so_type != SOCK_STREAM) error = EOPNOTSUPP; fdrop(fp, td); if (error != 0) goto bad; } if (linux_msghdr.msg_controllen >= sizeof(struct l_cmsghdr)) { error = ENOBUFS; control = m_get(M_WAITOK, MT_CONTROL); MCLGET(control, M_WAITOK); data = mtod(control, void *); datalen = 0; ptr_cmsg = PTRIN(linux_msghdr.msg_control); clen = linux_msghdr.msg_controllen; do { error = copyin(ptr_cmsg, &linux_cmsg, sizeof(struct l_cmsghdr)); if (error != 0) goto bad; error = EINVAL; if (linux_cmsg.cmsg_len < sizeof(struct l_cmsghdr) || linux_cmsg.cmsg_len > clen) goto bad; if (datalen + CMSG_HDRSZ > MCLBYTES) goto bad; /* * Now we support only SCM_RIGHTS and SCM_CRED, * so return EINVAL in any other cmsg_type */ cmsg = data; cmsg->cmsg_type = linux_to_bsd_cmsg_type(linux_cmsg.cmsg_type); cmsg->cmsg_level = linux_to_bsd_sockopt_level(linux_cmsg.cmsg_level); if (cmsg->cmsg_type == -1 || cmsg->cmsg_level != SOL_SOCKET) { linux_msg(curthread, "unsupported sendmsg cmsg level %d type %d", linux_cmsg.cmsg_level, linux_cmsg.cmsg_type); goto bad; } /* * Some applications (e.g. pulseaudio) attempt to * send ancillary data even if the underlying protocol * doesn't support it which is not allowed in the * FreeBSD system call interface. */ if (sa_family != AF_UNIX) goto next; if (cmsg->cmsg_type == SCM_CREDS) { len = sizeof(struct cmsgcred); if (datalen + CMSG_SPACE(len) > MCLBYTES) goto bad; /* * The lower levels will fill in the structure */ memset(CMSG_DATA(data), 0, len); } else { len = linux_cmsg.cmsg_len - L_CMSG_HDRSZ; if (datalen + CMSG_SPACE(len) < datalen || datalen + CMSG_SPACE(len) > MCLBYTES) goto bad; error = copyin(LINUX_CMSG_DATA(ptr_cmsg), CMSG_DATA(data), len); if (error != 0) goto bad; } cmsg->cmsg_len = CMSG_LEN(len); data = (char *)data + CMSG_SPACE(len); datalen += CMSG_SPACE(len); next: if (clen <= LINUX_CMSG_ALIGN(linux_cmsg.cmsg_len)) break; clen -= LINUX_CMSG_ALIGN(linux_cmsg.cmsg_len); ptr_cmsg = (struct l_cmsghdr *)((char *)ptr_cmsg + LINUX_CMSG_ALIGN(linux_cmsg.cmsg_len)); } while(clen >= sizeof(struct l_cmsghdr)); control->m_len = datalen; if (datalen == 0) { m_freem(control); control = NULL; } } msg.msg_iov = iov; msg.msg_flags = 0; error = linux_sendit(td, s, &msg, flags, control, UIO_USERSPACE); control = NULL; bad: m_freem(control); free(iov, M_IOV); return (error); } int linux_sendmsg(struct thread *td, struct linux_sendmsg_args *args) { return (linux_sendmsg_common(td, args->s, PTRIN(args->msg), args->flags)); } int linux_sendmmsg(struct thread *td, struct linux_sendmmsg_args *args) { struct l_mmsghdr *msg; l_uint retval; int error, datagrams; if (args->vlen > UIO_MAXIOV) args->vlen = UIO_MAXIOV; msg = PTRIN(args->msg); datagrams = 0; while (datagrams < args->vlen) { error = linux_sendmsg_common(td, args->s, &msg->msg_hdr, args->flags); if (error != 0) break; retval = td->td_retval[0]; error = copyout(&retval, &msg->msg_len, sizeof(msg->msg_len)); if (error != 0) break; ++msg; ++datagrams; } if (error == 0) td->td_retval[0] = datagrams; return (error); } static int recvmsg_scm_rights(struct thread *td, l_uint flags, socklen_t *datalen, void **data, void **udata) { int i, fd, fds, *fdp; if (flags & LINUX_MSG_CMSG_CLOEXEC) { fds = *datalen / sizeof(int); fdp = *data; for (i = 0; i < fds; i++) { fd = *fdp++; (void)kern_fcntl(td, fd, F_SETFD, FD_CLOEXEC); } } return (0); } static int recvmsg_scm_creds(socklen_t *datalen, void **data, void **udata) { struct cmsgcred *cmcred; struct l_ucred lu; cmcred = *data; lu.pid = cmcred->cmcred_pid; lu.uid = cmcred->cmcred_uid; lu.gid = cmcred->cmcred_gid; memmove(*data, &lu, sizeof(lu)); *datalen = sizeof(lu); return (0); } _Static_assert(sizeof(struct cmsgcred) >= sizeof(struct l_ucred), "scm_creds sizeof l_ucred"); static int recvmsg_scm_creds2(socklen_t *datalen, void **data, void **udata) { struct sockcred2 *scred; struct l_ucred lu; scred = *data; lu.pid = scred->sc_pid; lu.uid = scred->sc_uid; lu.gid = scred->sc_gid; memmove(*data, &lu, sizeof(lu)); *datalen = sizeof(lu); return (0); } _Static_assert(sizeof(struct sockcred2) >= sizeof(struct l_ucred), "scm_creds2 sizeof l_ucred"); #if defined(__i386__) || (defined(__amd64__) && defined(COMPAT_LINUX32)) static int recvmsg_scm_timestamp(l_int msg_type, socklen_t *datalen, void **data, void **udata) { l_sock_timeval ltv64; l_timeval ltv; struct timeval *tv; socklen_t len; void *buf; if (*datalen != sizeof(struct timeval)) return (EMSGSIZE); tv = *data; #if defined(COMPAT_LINUX32) if (msg_type == LINUX_SCM_TIMESTAMPO && (tv->tv_sec > INT_MAX || tv->tv_sec < INT_MIN)) return (EOVERFLOW); #endif if (msg_type == LINUX_SCM_TIMESTAMPN) len = sizeof(ltv64); else len = sizeof(ltv); buf = malloc(len, M_LINUX, M_WAITOK); if (msg_type == LINUX_SCM_TIMESTAMPN) { ltv64.tv_sec = tv->tv_sec; ltv64.tv_usec = tv->tv_usec; memmove(buf, <v64, len); } else { ltv.tv_sec = tv->tv_sec; ltv.tv_usec = tv->tv_usec; memmove(buf, <v, len); } *data = *udata = buf; *datalen = len; return (0); } #else _Static_assert(sizeof(struct timeval) == sizeof(l_timeval), "scm_timestamp sizeof l_timeval"); #endif /* __i386__ || (__amd64__ && COMPAT_LINUX32) */ #if defined(__i386__) || (defined(__amd64__) && defined(COMPAT_LINUX32)) static int recvmsg_scm_timestampns(l_int msg_type, socklen_t *datalen, void **data, void **udata) { struct l_timespec64 ts64; struct l_timespec ts32; struct timespec ts; socklen_t len; void *buf; if (msg_type == LINUX_SCM_TIMESTAMPNSO) len = sizeof(ts32); else len = sizeof(ts64); buf = malloc(len, M_LINUX, M_WAITOK); bintime2timespec(*data, &ts); if (msg_type == LINUX_SCM_TIMESTAMPNSO) { ts32.tv_sec = ts.tv_sec; ts32.tv_nsec = ts.tv_nsec; memmove(buf, &ts32, len); } else { ts64.tv_sec = ts.tv_sec; ts64.tv_nsec = ts.tv_nsec; memmove(buf, &ts64, len); } *data = *udata = buf; *datalen = len; return (0); } #else static int recvmsg_scm_timestampns(l_int msg_type, socklen_t *datalen, void **data, void **udata) { struct timespec ts; bintime2timespec(*data, &ts); memmove(*data, &ts, sizeof(struct timespec)); *datalen = sizeof(struct timespec); return (0); } _Static_assert(sizeof(struct bintime) >= sizeof(struct timespec), "scm_timestampns sizeof timespec"); #endif /* __i386__ || (__amd64__ && COMPAT_LINUX32) */ static int recvmsg_scm_ip_origdstaddr(socklen_t *datalen, void **data, void **udata) { struct l_sockaddr *lsa; int error; error = bsd_to_linux_sockaddr(*data, &lsa, *datalen); if (error == 0) { *data = *udata = lsa; *datalen = sizeof(*lsa); } return (error); } static int linux_recvmsg_common(struct thread *td, l_int s, struct l_msghdr *msghdr, l_uint flags, struct msghdr *msg) { struct proc *p = td->td_proc; struct cmsghdr *cm; struct l_cmsghdr *lcm = NULL; socklen_t datalen, maxlen, outlen; struct l_msghdr l_msghdr; struct iovec *iov, *uiov; struct mbuf *m, *control = NULL; struct mbuf **controlp; struct sockaddr *sa; caddr_t outbuf; void *data, *udata; int error; error = copyin(msghdr, &l_msghdr, sizeof(l_msghdr)); if (error != 0) return (error); /* * Pass user-supplied recvmsg() flags in msg_flags field, * following sys_recvmsg() convention. */ l_msghdr.msg_flags = flags; error = linux_to_bsd_msghdr(msg, &l_msghdr); if (error != 0) return (error); #ifdef COMPAT_LINUX32 error = linux32_copyiniov(PTRIN(msg->msg_iov), msg->msg_iovlen, &iov, EMSGSIZE); #else error = copyiniov(msg->msg_iov, msg->msg_iovlen, &iov, EMSGSIZE); #endif if (error != 0) return (error); if (msg->msg_name != NULL && msg->msg_namelen > 0) { msg->msg_namelen = min(msg->msg_namelen, SOCK_MAXADDRLEN); sa = malloc(msg->msg_namelen, M_SONAME, M_WAITOK); msg->msg_name = sa; } else { sa = NULL; msg->msg_name = NULL; } uiov = msg->msg_iov; msg->msg_iov = iov; controlp = (msg->msg_control != NULL) ? &control : NULL; error = kern_recvit(td, s, msg, UIO_SYSSPACE, controlp); msg->msg_iov = uiov; if (error != 0) goto bad; /* * Note that kern_recvit() updates msg->msg_namelen. */ if (msg->msg_name != NULL && msg->msg_namelen > 0) { msg->msg_name = PTRIN(l_msghdr.msg_name); error = linux_copyout_sockaddr(sa, msg->msg_name, msg->msg_namelen); if (error != 0) goto bad; } error = bsd_to_linux_msghdr(msg, &l_msghdr); if (error != 0) goto bad; maxlen = l_msghdr.msg_controllen; l_msghdr.msg_controllen = 0; if (control == NULL) goto out; lcm = malloc(L_CMSG_HDRSZ, M_LINUX, M_WAITOK | M_ZERO); msg->msg_control = mtod(control, struct cmsghdr *); msg->msg_controllen = control->m_len; outbuf = PTRIN(l_msghdr.msg_control); outlen = 0; for (m = control; m != NULL; m = m->m_next) { cm = mtod(m, struct cmsghdr *); lcm->cmsg_type = bsd_to_linux_cmsg_type(p, cm->cmsg_type, cm->cmsg_level); lcm->cmsg_level = bsd_to_linux_sockopt_level(cm->cmsg_level); data = CMSG_DATA(cm); datalen = (caddr_t)cm + cm->cmsg_len - (caddr_t)data; udata = NULL; error = 0; /* Process non SOL_SOCKET types. */ if (cm->cmsg_level == IPPROTO_IP && lcm->cmsg_type == LINUX_IP_ORIGDSTADDR) { error = recvmsg_scm_ip_origdstaddr(&datalen, &data, &udata); goto cont; } if (lcm->cmsg_type == -1 || cm->cmsg_level != SOL_SOCKET) { LINUX_RATELIMIT_MSG_OPT2( "unsupported recvmsg cmsg level %d type %d", cm->cmsg_level, cm->cmsg_type); error = EINVAL; goto bad; } switch (cm->cmsg_type) { case SCM_RIGHTS: error = recvmsg_scm_rights(td, flags, &datalen, &data, &udata); break; case SCM_CREDS: error = recvmsg_scm_creds(&datalen, &data, &udata); break; case SCM_CREDS2: error = recvmsg_scm_creds2(&datalen, &data, &udata); break; case SCM_TIMESTAMP: #if defined(__i386__) || (defined(__amd64__) && defined(COMPAT_LINUX32)) error = recvmsg_scm_timestamp(lcm->cmsg_type, &datalen, &data, &udata); #endif break; case SCM_BINTIME: error = recvmsg_scm_timestampns(lcm->cmsg_type, &datalen, &data, &udata); break; } cont: if (error != 0) goto bad; if (outlen + LINUX_CMSG_LEN(datalen) > maxlen) { if (outlen == 0) { error = EMSGSIZE; goto err; } else { l_msghdr.msg_flags |= LINUX_MSG_CTRUNC; m_dispose_extcontrolm(control); free(udata, M_LINUX); goto out; } } lcm->cmsg_len = LINUX_CMSG_LEN(datalen); error = copyout(lcm, outbuf, L_CMSG_HDRSZ); if (error == 0) { outbuf += L_CMSG_HDRSZ; error = copyout(data, outbuf, datalen); if (error == 0) { outbuf += LINUX_CMSG_ALIGN(datalen); outlen += LINUX_CMSG_LEN(datalen); } } err: free(udata, M_LINUX); if (error != 0) goto bad; } l_msghdr.msg_controllen = outlen; out: error = copyout(&l_msghdr, msghdr, sizeof(l_msghdr)); bad: if (control != NULL) { if (error != 0) m_dispose_extcontrolm(control); m_freem(control); } free(iov, M_IOV); free(lcm, M_LINUX); free(sa, M_SONAME); return (error); } int linux_recvmsg(struct thread *td, struct linux_recvmsg_args *args) { struct msghdr bsd_msg; struct file *fp; int error; error = getsock(td, args->s, &cap_recv_rights, &fp); if (error != 0) return (error); fdrop(fp, td); return (linux_recvmsg_common(td, args->s, PTRIN(args->msg), args->flags, &bsd_msg)); } static int linux_recvmmsg_common(struct thread *td, l_int s, struct l_mmsghdr *msg, l_uint vlen, l_uint flags, struct timespec *tts) { struct msghdr bsd_msg; struct timespec ts; struct file *fp; l_uint retval; int error, datagrams; error = getsock(td, s, &cap_recv_rights, &fp); if (error != 0) return (error); datagrams = 0; while (datagrams < vlen) { error = linux_recvmsg_common(td, s, &msg->msg_hdr, flags & ~LINUX_MSG_WAITFORONE, &bsd_msg); if (error != 0) break; retval = td->td_retval[0]; error = copyout(&retval, &msg->msg_len, sizeof(msg->msg_len)); if (error != 0) break; ++msg; ++datagrams; /* * MSG_WAITFORONE turns on MSG_DONTWAIT after one packet. */ if (flags & LINUX_MSG_WAITFORONE) flags |= LINUX_MSG_DONTWAIT; /* * See BUGS section of recvmmsg(2). */ if (tts) { getnanotime(&ts); timespecsub(&ts, tts, &ts); if (!timespecisset(&ts) || ts.tv_sec > 0) break; } /* Out of band data, return right away. */ if (bsd_msg.msg_flags & MSG_OOB) break; } if (error == 0) td->td_retval[0] = datagrams; fdrop(fp, td); return (error); } int linux_recvmmsg(struct thread *td, struct linux_recvmmsg_args *args) { struct timespec ts, tts, *ptts; int error; if (args->timeout) { error = linux_get_timespec(&ts, args->timeout); if (error != 0) return (error); getnanotime(&tts); timespecadd(&tts, &ts, &tts); ptts = &tts; } else ptts = NULL; return (linux_recvmmsg_common(td, args->s, PTRIN(args->msg), args->vlen, args->flags, ptts)); } #if defined(__i386__) || (defined(__amd64__) && defined(COMPAT_LINUX32)) int linux_recvmmsg_time64(struct thread *td, struct linux_recvmmsg_time64_args *args) { struct timespec ts, tts, *ptts; int error; if (args->timeout) { error = linux_get_timespec64(&ts, args->timeout); if (error != 0) return (error); getnanotime(&tts); timespecadd(&tts, &ts, &tts); ptts = &tts; } else ptts = NULL; return (linux_recvmmsg_common(td, args->s, PTRIN(args->msg), args->vlen, args->flags, ptts)); } #endif int linux_shutdown(struct thread *td, struct linux_shutdown_args *args) { return (kern_shutdown(td, args->s, args->how)); } int linux_setsockopt(struct thread *td, struct linux_setsockopt_args *args) { struct proc *p = td->td_proc; struct linux_pemuldata *pem; l_timeval linux_tv; struct sockaddr *sa; struct timeval tv; socklen_t len; int error, level, name, val; level = linux_to_bsd_sockopt_level(args->level); switch (level) { case SOL_SOCKET: name = linux_to_bsd_so_sockopt(args->optname); switch (name) { case LOCAL_CREDS_PERSISTENT: level = SOL_LOCAL; break; case SO_RCVTIMEO: /* FALLTHROUGH */ case SO_SNDTIMEO: error = copyin(PTRIN(args->optval), &linux_tv, sizeof(linux_tv)); if (error != 0) return (error); tv.tv_sec = linux_tv.tv_sec; tv.tv_usec = linux_tv.tv_usec; return (kern_setsockopt(td, args->s, level, name, &tv, UIO_SYSSPACE, sizeof(tv))); /* NOTREACHED */ case SO_TIMESTAMP: /* overwrite SO_BINTIME */ val = 0; error = kern_setsockopt(td, args->s, level, SO_BINTIME, &val, UIO_SYSSPACE, sizeof(val)); if (error != 0) return (error); pem = pem_find(p); pem->so_timestamp = args->optname; break; case SO_BINTIME: /* overwrite SO_TIMESTAMP */ val = 0; error = kern_setsockopt(td, args->s, level, SO_TIMESTAMP, &val, UIO_SYSSPACE, sizeof(val)); if (error != 0) return (error); pem = pem_find(p); pem->so_timestampns = args->optname; break; default: break; } break; case IPPROTO_IP: if (args->optname == LINUX_IP_RECVERR && linux_ignore_ip_recverr) { /* * XXX: This is a hack to unbreak DNS resolution * with glibc 2.30 and above. */ return (0); } name = linux_to_bsd_ip_sockopt(args->optname); break; case IPPROTO_IPV6: name = linux_to_bsd_ip6_sockopt(args->optname); break; case IPPROTO_TCP: name = linux_to_bsd_tcp_sockopt(args->optname); break; + case SOL_NETLINK: + level = SOL_SOCKET; + name = args->optname; + break; default: name = -1; break; } if (name < 0) { if (name == -1) linux_msg(curthread, "unsupported setsockopt level %d optname %d", args->level, args->optname); return (ENOPROTOOPT); } if (name == IPV6_NEXTHOP) { len = args->optlen; error = linux_to_bsd_sockaddr(PTRIN(args->optval), &sa, &len); if (error != 0) return (error); error = kern_setsockopt(td, args->s, level, name, sa, UIO_SYSSPACE, len); free(sa, M_SONAME); } else { error = kern_setsockopt(td, args->s, level, name, PTRIN(args->optval), UIO_USERSPACE, args->optlen); } return (error); } static int linux_sockopt_copyout(struct thread *td, void *val, socklen_t len, struct linux_getsockopt_args *args) { int error; error = copyout(val, PTRIN(args->optval), len); if (error == 0) error = copyout(&len, PTRIN(args->optlen), sizeof(len)); return (error); } static int linux_getsockopt_so_peergroups(struct thread *td, struct linux_getsockopt_args *args) { struct xucred xu; socklen_t xulen, len; int error, i; xulen = sizeof(xu); error = kern_getsockopt(td, args->s, 0, LOCAL_PEERCRED, &xu, UIO_SYSSPACE, &xulen); if (error != 0) return (error); len = xu.cr_ngroups * sizeof(l_gid_t); if (args->optlen < len) { error = copyout(&len, PTRIN(args->optlen), sizeof(len)); if (error == 0) error = ERANGE; return (error); } /* * "- 1" to skip the primary group. */ for (i = 0; i < xu.cr_ngroups - 1; i++) { error = copyout(xu.cr_groups + i + 1, (void *)(args->optval + i * sizeof(l_gid_t)), sizeof(l_gid_t)); if (error != 0) return (error); } error = copyout(&len, PTRIN(args->optlen), sizeof(len)); return (error); } static int linux_getsockopt_so_peersec(struct thread *td, struct linux_getsockopt_args *args) { socklen_t len; int error; len = sizeof(SECURITY_CONTEXT_STRING); if (args->optlen < len) { error = copyout(&len, PTRIN(args->optlen), sizeof(len)); if (error == 0) error = ERANGE; return (error); } return (linux_sockopt_copyout(td, SECURITY_CONTEXT_STRING, len, args)); } static int linux_getsockopt_so_linger(struct thread *td, struct linux_getsockopt_args *args) { struct linger ling; socklen_t len; int error; len = sizeof(ling); error = kern_getsockopt(td, args->s, SOL_SOCKET, SO_LINGER, &ling, UIO_SYSSPACE, &len); if (error != 0) return (error); ling.l_onoff = ((ling.l_onoff & SO_LINGER) != 0); return (linux_sockopt_copyout(td, &ling, len, args)); } int linux_getsockopt(struct thread *td, struct linux_getsockopt_args *args) { l_timeval linux_tv; struct timeval tv; socklen_t tv_len, xulen, len; struct sockaddr *sa; struct xucred xu; struct l_ucred lxu; int error, level, name, newval; level = linux_to_bsd_sockopt_level(args->level); switch (level) { case SOL_SOCKET: switch (args->optname) { case LINUX_SO_PEERGROUPS: return (linux_getsockopt_so_peergroups(td, args)); case LINUX_SO_PEERSEC: return (linux_getsockopt_so_peersec(td, args)); default: break; } name = linux_to_bsd_so_sockopt(args->optname); switch (name) { case LOCAL_CREDS_PERSISTENT: level = SOL_LOCAL; break; case SO_RCVTIMEO: /* FALLTHROUGH */ case SO_SNDTIMEO: tv_len = sizeof(tv); error = kern_getsockopt(td, args->s, level, name, &tv, UIO_SYSSPACE, &tv_len); if (error != 0) return (error); linux_tv.tv_sec = tv.tv_sec; linux_tv.tv_usec = tv.tv_usec; return (linux_sockopt_copyout(td, &linux_tv, sizeof(linux_tv), args)); /* NOTREACHED */ case LOCAL_PEERCRED: if (args->optlen < sizeof(lxu)) return (EINVAL); /* * LOCAL_PEERCRED is not served at the SOL_SOCKET level, * but by the Unix socket's level 0. */ level = 0; xulen = sizeof(xu); error = kern_getsockopt(td, args->s, level, name, &xu, UIO_SYSSPACE, &xulen); if (error != 0) return (error); lxu.pid = xu.cr_pid; lxu.uid = xu.cr_uid; lxu.gid = xu.cr_gid; return (linux_sockopt_copyout(td, &lxu, sizeof(lxu), args)); /* NOTREACHED */ case SO_ERROR: len = sizeof(newval); error = kern_getsockopt(td, args->s, level, name, &newval, UIO_SYSSPACE, &len); if (error != 0) return (error); newval = -bsd_to_linux_errno(newval); return (linux_sockopt_copyout(td, &newval, len, args)); /* NOTREACHED */ case SO_DOMAIN: len = sizeof(newval); error = kern_getsockopt(td, args->s, level, name, &newval, UIO_SYSSPACE, &len); if (error != 0) return (error); newval = bsd_to_linux_domain(newval); if (newval == -1) return (ENOPROTOOPT); return (linux_sockopt_copyout(td, &newval, len, args)); /* NOTREACHED */ case SO_LINGER: return (linux_getsockopt_so_linger(td, args)); /* NOTREACHED */ default: break; } break; case IPPROTO_IP: name = linux_to_bsd_ip_sockopt(args->optname); break; case IPPROTO_IPV6: name = linux_to_bsd_ip6_sockopt(args->optname); break; case IPPROTO_TCP: name = linux_to_bsd_tcp_sockopt(args->optname); break; default: name = -1; break; } if (name < 0) { if (name == -1) linux_msg(curthread, "unsupported getsockopt level %d optname %d", args->level, args->optname); return (EINVAL); } if (name == IPV6_NEXTHOP) { error = copyin(PTRIN(args->optlen), &len, sizeof(len)); if (error != 0) return (error); sa = malloc(len, M_SONAME, M_WAITOK); error = kern_getsockopt(td, args->s, level, name, sa, UIO_SYSSPACE, &len); if (error != 0) goto out; error = linux_copyout_sockaddr(sa, PTRIN(args->optval), len); if (error == 0) error = copyout(&len, PTRIN(args->optlen), sizeof(len)); out: free(sa, M_SONAME); } else { if (args->optval) { error = copyin(PTRIN(args->optlen), &len, sizeof(len)); if (error != 0) return (error); } error = kern_getsockopt(td, args->s, level, name, PTRIN(args->optval), UIO_USERSPACE, &len); if (error == 0) error = copyout(&len, PTRIN(args->optlen), sizeof(len)); } return (error); } static int linux_sendfile_common(struct thread *td, l_int out, l_int in, l_loff_t *offset, l_size_t count) { off_t bytes_read; int error; l_loff_t current_offset; struct file *fp; AUDIT_ARG_FD(in); error = fget_read(td, in, &cap_pread_rights, &fp); if (error != 0) return (error); if (offset != NULL) { current_offset = *offset; } else { error = (fp->f_ops->fo_flags & DFLAG_SEEKABLE) != 0 ? fo_seek(fp, 0, SEEK_CUR, td) : ESPIPE; if (error != 0) goto drop; current_offset = td->td_uretoff.tdu_off; } bytes_read = 0; /* Linux cannot have 0 count. */ if (count <= 0 || current_offset < 0) { error = EINVAL; goto drop; } error = fo_sendfile(fp, out, NULL, NULL, current_offset, count, &bytes_read, 0, td); if (error != 0) goto drop; current_offset += bytes_read; if (offset != NULL) { *offset = current_offset; } else { error = fo_seek(fp, current_offset, SEEK_SET, td); if (error != 0) goto drop; } td->td_retval[0] = (ssize_t)bytes_read; drop: fdrop(fp, td); if (error == ENOTSOCK) error = EINVAL; return (error); } int linux_sendfile(struct thread *td, struct linux_sendfile_args *arg) { /* * Differences between FreeBSD and Linux sendfile: * - Linux doesn't send anything when count is 0 (FreeBSD uses 0 to * mean send the whole file.) In linux_sendfile given fds are still * checked for validity when the count is 0. * - Linux can send to any fd whereas FreeBSD only supports sockets. * The same restriction follows for linux_sendfile. * - Linux doesn't have an equivalent for FreeBSD's flags and sf_hdtr. * - Linux takes an offset pointer and updates it to the read location. * FreeBSD takes in an offset and a 'bytes read' parameter which is * only filled if it isn't NULL. We use this parameter to update the * offset pointer if it exists. * - Linux sendfile returns bytes read on success while FreeBSD * returns 0. We use the 'bytes read' parameter to get this value. */ l_loff_t offset64; l_long offset; int ret; int error; if (arg->offset != NULL) { error = copyin(arg->offset, &offset, sizeof(offset)); if (error != 0) return (error); offset64 = (l_loff_t)offset; } ret = linux_sendfile_common(td, arg->out, arg->in, arg->offset != NULL ? &offset64 : NULL, arg->count); if (arg->offset != NULL) { #if defined(__i386__) || defined(__arm__) || \ (defined(__amd64__) && defined(COMPAT_LINUX32)) if (offset64 > INT32_MAX) return (EOVERFLOW); #endif offset = (l_long)offset64; error = copyout(&offset, arg->offset, sizeof(offset)); if (error != 0) return (error); } return (ret); } #if defined(__i386__) || defined(__arm__) || \ (defined(__amd64__) && defined(COMPAT_LINUX32)) int linux_sendfile64(struct thread *td, struct linux_sendfile64_args *arg) { l_loff_t offset; int ret; int error; if (arg->offset != NULL) { error = copyin(arg->offset, &offset, sizeof(offset)); if (error != 0) return (error); } ret = linux_sendfile_common(td, arg->out, arg->in, arg->offset != NULL ? &offset : NULL, arg->count); if (arg->offset != NULL) { error = copyout(&offset, arg->offset, sizeof(offset)); if (error != 0) return (error); } return (ret); } /* Argument list sizes for linux_socketcall */ static const unsigned char lxs_args_cnt[] = { 0 /* unused*/, 3 /* socket */, 3 /* bind */, 3 /* connect */, 2 /* listen */, 3 /* accept */, 3 /* getsockname */, 3 /* getpeername */, 4 /* socketpair */, 4 /* send */, 4 /* recv */, 6 /* sendto */, 6 /* recvfrom */, 2 /* shutdown */, 5 /* setsockopt */, 5 /* getsockopt */, 3 /* sendmsg */, 3 /* recvmsg */, 4 /* accept4 */, 5 /* recvmmsg */, 4 /* sendmmsg */, 4 /* sendfile */ }; #define LINUX_ARGS_CNT (nitems(lxs_args_cnt) - 1) #define LINUX_ARG_SIZE(x) (lxs_args_cnt[x] * sizeof(l_ulong)) int linux_socketcall(struct thread *td, struct linux_socketcall_args *args) { l_ulong a[6]; #if defined(__amd64__) && defined(COMPAT_LINUX32) register_t l_args[6]; #endif void *arg; int error; if (args->what < LINUX_SOCKET || args->what > LINUX_ARGS_CNT) return (EINVAL); error = copyin(PTRIN(args->args), a, LINUX_ARG_SIZE(args->what)); if (error != 0) return (error); #if defined(__amd64__) && defined(COMPAT_LINUX32) for (int i = 0; i < lxs_args_cnt[args->what]; ++i) l_args[i] = a[i]; arg = l_args; #else arg = a; #endif switch (args->what) { case LINUX_SOCKET: return (linux_socket(td, arg)); case LINUX_BIND: return (linux_bind(td, arg)); case LINUX_CONNECT: return (linux_connect(td, arg)); case LINUX_LISTEN: return (linux_listen(td, arg)); case LINUX_ACCEPT: return (linux_accept(td, arg)); case LINUX_GETSOCKNAME: return (linux_getsockname(td, arg)); case LINUX_GETPEERNAME: return (linux_getpeername(td, arg)); case LINUX_SOCKETPAIR: return (linux_socketpair(td, arg)); case LINUX_SEND: return (linux_send(td, arg)); case LINUX_RECV: return (linux_recv(td, arg)); case LINUX_SENDTO: return (linux_sendto(td, arg)); case LINUX_RECVFROM: return (linux_recvfrom(td, arg)); case LINUX_SHUTDOWN: return (linux_shutdown(td, arg)); case LINUX_SETSOCKOPT: return (linux_setsockopt(td, arg)); case LINUX_GETSOCKOPT: return (linux_getsockopt(td, arg)); case LINUX_SENDMSG: return (linux_sendmsg(td, arg)); case LINUX_RECVMSG: return (linux_recvmsg(td, arg)); case LINUX_ACCEPT4: return (linux_accept4(td, arg)); case LINUX_RECVMMSG: return (linux_recvmmsg(td, arg)); case LINUX_SENDMMSG: return (linux_sendmmsg(td, arg)); case LINUX_SENDFILE: return (linux_sendfile(td, arg)); } linux_msg(td, "socket type %d not implemented", args->what); return (ENOSYS); } #endif /* __i386__ || __arm__ || (__amd64__ && COMPAT_LINUX32) */ diff --git a/sys/modules/linux_common/Makefile b/sys/modules/linux_common/Makefile index 0c4983ef98ca..6e67e7ab2578 100644 --- a/sys/modules/linux_common/Makefile +++ b/sys/modules/linux_common/Makefile @@ -1,26 +1,26 @@ # $FreeBSD$ .PATH: ${SRCTOP}/sys/compat/linux ${SRCTOP}/sys/x86/linux KMOD= linux_common SRCS= linux_common.c linux_mib.c linux_mmap.c linux_util.c linux_emul.c \ - linux_dummy.c linux_errno.c \ + linux_dummy.c linux_errno.c linux_netlink.c \ linux.c device_if.h vnode_if.h bus_if.h opt_inet6.h .if ${MACHINE_CPUARCH} == "amd64" SRCS+= linux_x86.c linux_vdso_selector_x86.c .endif EXPORT_SYMS= EXPORT_SYMS+= linux_emul_path EXPORT_SYMS+= linux_get_osname EXPORT_SYMS+= linux_get_osrelease EXPORT_SYMS+= linux_use_real_ifname .if !defined(KERNBUILDDIR) .warning Building Linuxulator outside of a kernel does not make sense .endif EXPORT_SYMS= YES .include