7f2d13d607
Mainly focus on files that use BSD 2-Clause license, however the tool I was using misidentified many licenses so this was mostly a manual - error prone - task. The Software Package Data Exchange (SPDX) group provides a specification to make it easier for automated tools to detect and summarize well known opensource licenses. We are gradually adopting the specification, noting that the tags are considered only advisory and do not, in any way, superceed or replace the license texts.
1770 lines
41 KiB
C
1770 lines
41 KiB
C
/*-
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*
|
|
* 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
|
|
* in this position and unchanged.
|
|
* 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.
|
|
* 3. The name of the author may not be used to endorse or promote products
|
|
* derived from this software without specific prior written permission
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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 <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
/* XXX we use functions that might not exist. */
|
|
#include "opt_compat.h"
|
|
#include "opt_inet6.h"
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/proc.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/sysproto.h>
|
|
#include <sys/capsicum.h>
|
|
#include <sys/fcntl.h>
|
|
#include <sys/file.h>
|
|
#include <sys/limits.h>
|
|
#include <sys/lock.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/mutex.h>
|
|
#include <sys/mbuf.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/socketvar.h>
|
|
#include <sys/syscallsubr.h>
|
|
#include <sys/uio.h>
|
|
#include <sys/syslog.h>
|
|
#include <sys/un.h>
|
|
|
|
#include <net/if.h>
|
|
#include <net/vnet.h>
|
|
#include <netinet/in.h>
|
|
#include <netinet/in_systm.h>
|
|
#include <netinet/ip.h>
|
|
#include <netinet/tcp.h>
|
|
#ifdef INET6
|
|
#include <netinet/ip6.h>
|
|
#include <netinet6/ip6_var.h>
|
|
#endif
|
|
|
|
#ifdef COMPAT_LINUX32
|
|
#include <machine/../linux32/linux.h>
|
|
#include <machine/../linux32/linux32_proto.h>
|
|
#else
|
|
#include <machine/../linux/linux.h>
|
|
#include <machine/../linux/linux_proto.h>
|
|
#endif
|
|
#include <compat/linux/linux_file.h>
|
|
#include <compat/linux/linux_socket.h>
|
|
#include <compat/linux/linux_timer.h>
|
|
#include <compat/linux/linux_util.h>
|
|
|
|
static int linux_to_bsd_domain(int);
|
|
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 *);
|
|
|
|
/*
|
|
* Reads a linux sockaddr and does any necessary translation.
|
|
* Linux sockaddrs don't have a length field, only a family.
|
|
* Copy the osockaddr structure pointed to by osa to kernel, adjust
|
|
* family and convert to sockaddr.
|
|
*/
|
|
static int
|
|
linux_getsockaddr(struct sockaddr **sap, const struct osockaddr *osa, int salen)
|
|
{
|
|
struct sockaddr *sa;
|
|
struct osockaddr *kosa;
|
|
#ifdef INET6
|
|
struct sockaddr_in6 *sin6;
|
|
int oldv6size;
|
|
#endif
|
|
char *name;
|
|
int bdom, error, hdrlen, namelen;
|
|
|
|
if (salen < 2 || salen > UCHAR_MAX || !osa)
|
|
return (EINVAL);
|
|
|
|
#ifdef INET6
|
|
oldv6size = 0;
|
|
/*
|
|
* 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 = 1;
|
|
}
|
|
#endif
|
|
|
|
kosa = malloc(salen, M_SONAME, M_WAITOK);
|
|
|
|
if ((error = copyin(osa, kosa, salen)))
|
|
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 {
|
|
log(LOG_DEBUG,
|
|
"obsolete pre-RFC2553 sockaddr_in6 rejected\n");
|
|
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;
|
|
}
|
|
}
|
|
|
|
sa = (struct sockaddr *)kosa;
|
|
sa->sa_family = bdom;
|
|
sa->sa_len = salen;
|
|
|
|
*sap = sa;
|
|
return (0);
|
|
|
|
out:
|
|
free(kosa, M_SONAME);
|
|
return (error);
|
|
}
|
|
|
|
static 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);
|
|
}
|
|
return (-1);
|
|
}
|
|
|
|
static 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);
|
|
}
|
|
return (-1);
|
|
}
|
|
|
|
static int
|
|
linux_to_bsd_sockopt_level(int level)
|
|
{
|
|
|
|
switch (level) {
|
|
case LINUX_SOL_SOCKET:
|
|
return (SOL_SOCKET);
|
|
}
|
|
return (level);
|
|
}
|
|
|
|
static int
|
|
bsd_to_linux_sockopt_level(int level)
|
|
{
|
|
|
|
switch (level) {
|
|
case SOL_SOCKET:
|
|
return (LINUX_SOL_SOCKET);
|
|
}
|
|
return (level);
|
|
}
|
|
|
|
static int
|
|
linux_to_bsd_ip_sockopt(int opt)
|
|
{
|
|
|
|
switch (opt) {
|
|
case LINUX_IP_TOS:
|
|
return (IP_TOS);
|
|
case LINUX_IP_TTL:
|
|
return (IP_TTL);
|
|
case LINUX_IP_OPTIONS:
|
|
return (IP_OPTIONS);
|
|
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_HDRINCL:
|
|
return (IP_HDRINCL);
|
|
}
|
|
return (-1);
|
|
}
|
|
|
|
static int
|
|
linux_to_bsd_ip6_sockopt(int opt)
|
|
{
|
|
|
|
switch (opt) {
|
|
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_DONTFRAG:
|
|
return (IPV6_DONTFRAG);
|
|
#if 0
|
|
case LINUX_IPV6_CHECKSUM:
|
|
return (IPV6_CHECKSUM);
|
|
case LINUX_IPV6_RECVPKTINFO:
|
|
return (IPV6_RECVPKTINFO);
|
|
case LINUX_IPV6_PKTINFO:
|
|
return (IPV6_PKTINFO);
|
|
case LINUX_IPV6_RECVHOPLIMIT:
|
|
return (IPV6_RECVHOPLIMIT);
|
|
case LINUX_IPV6_HOPLIMIT:
|
|
return (IPV6_HOPLIMIT);
|
|
case LINUX_IPV6_RECVHOPOPTS:
|
|
return (IPV6_RECVHOPOPTS);
|
|
case LINUX_IPV6_HOPOPTS:
|
|
return (IPV6_HOPOPTS);
|
|
case LINUX_IPV6_RTHDRDSTOPTS:
|
|
return (IPV6_RTHDRDSTOPTS);
|
|
case LINUX_IPV6_RECVRTHDR:
|
|
return (IPV6_RECVRTHDR);
|
|
case LINUX_IPV6_RTHDR:
|
|
return (IPV6_RTHDR);
|
|
case LINUX_IPV6_RECVDSTOPTS:
|
|
return (IPV6_RECVDSTOPTS);
|
|
case LINUX_IPV6_DSTOPTS:
|
|
return (IPV6_DSTOPTS);
|
|
case LINUX_IPV6_RECVPATHMTU:
|
|
return (IPV6_RECVPATHMTU);
|
|
case LINUX_IPV6_PATHMTU:
|
|
return (IPV6_PATHMTU);
|
|
#endif
|
|
}
|
|
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:
|
|
return (SO_SNDBUF);
|
|
case LINUX_SO_RCVBUF:
|
|
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_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_TIMESTAMP:
|
|
return (SO_TIMESTAMP);
|
|
case LINUX_SO_ACCEPTCONN:
|
|
return (SO_ACCEPTCONN);
|
|
}
|
|
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_KEEPIDLE:
|
|
return (TCP_KEEPIDLE);
|
|
case LINUX_TCP_KEEPINTVL:
|
|
return (TCP_KEEPINTVL);
|
|
case LINUX_TCP_KEEPCNT:
|
|
return (TCP_KEEPCNT);
|
|
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 0 /* not handled */
|
|
if (flags & LINUX_MSG_PROXY)
|
|
;
|
|
if (flags & LINUX_MSG_FIN)
|
|
;
|
|
if (flags & LINUX_MSG_SYN)
|
|
;
|
|
if (flags & LINUX_MSG_CONFIRM)
|
|
;
|
|
if (flags & LINUX_MSG_RST)
|
|
;
|
|
if (flags & LINUX_MSG_ERRQUEUE)
|
|
;
|
|
#endif
|
|
return (ret_flags);
|
|
}
|
|
|
|
/*
|
|
* If bsd_to_linux_sockaddr() or linux_to_bsd_sockaddr() faults, then the
|
|
* native syscall will fault. Thus, we don't really need to check the
|
|
* return values for these functions.
|
|
*/
|
|
|
|
static int
|
|
bsd_to_linux_sockaddr(struct sockaddr *arg)
|
|
{
|
|
struct sockaddr sa;
|
|
size_t sa_len = sizeof(struct sockaddr);
|
|
int error, bdom;
|
|
|
|
if ((error = copyin(arg, &sa, sa_len)))
|
|
return (error);
|
|
|
|
bdom = bsd_to_linux_domain(sa.sa_family);
|
|
if (bdom == -1)
|
|
return (EAFNOSUPPORT);
|
|
|
|
*(u_short *)&sa = bdom;
|
|
return (copyout(&sa, arg, sa_len));
|
|
}
|
|
|
|
static int
|
|
linux_to_bsd_sockaddr(struct sockaddr *arg, int len)
|
|
{
|
|
struct sockaddr sa;
|
|
size_t sa_len = sizeof(struct sockaddr);
|
|
int error, bdom;
|
|
|
|
if ((error = copyin(arg, &sa, sa_len)))
|
|
return (error);
|
|
|
|
bdom = linux_to_bsd_domain(*(sa_family_t *)&sa);
|
|
if (bdom == -1)
|
|
return (EAFNOSUPPORT);
|
|
|
|
sa.sa_family = bdom;
|
|
sa.sa_len = len;
|
|
return (copyout(&sa, arg, sa_len));
|
|
}
|
|
|
|
static int
|
|
linux_sa_put(struct osockaddr *osa)
|
|
{
|
|
struct osockaddr sa;
|
|
int error, bdom;
|
|
|
|
/*
|
|
* Only read/write the osockaddr family part, the rest is
|
|
* not changed.
|
|
*/
|
|
error = copyin(osa, &sa, sizeof(sa.sa_family));
|
|
if (error != 0)
|
|
return (error);
|
|
|
|
bdom = bsd_to_linux_domain(sa.sa_family);
|
|
if (bdom == -1)
|
|
return (EINVAL);
|
|
|
|
sa.sa_family = bdom;
|
|
return (copyout(&sa, osa, sizeof(sa.sa_family)));
|
|
}
|
|
|
|
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_cmsg_type(int cmsg_type)
|
|
{
|
|
|
|
switch (cmsg_type) {
|
|
case SCM_RIGHTS:
|
|
return (LINUX_SCM_RIGHTS);
|
|
case SCM_CREDS:
|
|
return (LINUX_SCM_CREDENTIALS);
|
|
case SCM_TIMESTAMP:
|
|
return (LINUX_SCM_TIMESTAMP);
|
|
}
|
|
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_sendit(struct thread *td, int s, struct msghdr *mp, int flags,
|
|
struct mbuf *control, enum uio_seg segflg)
|
|
{
|
|
struct sockaddr *to;
|
|
int error;
|
|
|
|
if (mp->msg_name != NULL) {
|
|
error = linux_getsockaddr(&to, mp->msg_name, mp->msg_namelen);
|
|
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);
|
|
}
|
|
|
|
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)
|
|
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_getsockaddr(&sa, PTRIN(args->name),
|
|
args->namelen);
|
|
if (error != 0)
|
|
return (error);
|
|
|
|
error = kern_bindat(td, AT_FDCWD, args->s, sa);
|
|
free(sa, M_SONAME);
|
|
if (error == EADDRNOTAVAIL && args->namelen != sizeof(struct sockaddr_in))
|
|
return (EINVAL);
|
|
return (error);
|
|
}
|
|
|
|
int
|
|
linux_connect(struct thread *td, struct linux_connect_args *args)
|
|
{
|
|
cap_rights_t rights;
|
|
struct socket *so;
|
|
struct sockaddr *sa;
|
|
struct file *fp;
|
|
u_int fflag;
|
|
int error;
|
|
|
|
error = linux_getsockaddr(&sa, (struct osockaddr *)PTRIN(args->name),
|
|
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_cap(td, args->s, cap_rights_init(&rights, CAP_CONNECT),
|
|
&fp, &fflag, NULL);
|
|
if (error != 0)
|
|
return (error);
|
|
|
|
error = EISCONN;
|
|
so = fp->f_data;
|
|
if (fflag & 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 accept4_args /* {
|
|
int s;
|
|
struct sockaddr * __restrict name;
|
|
socklen_t * __restrict anamelen;
|
|
int flags;
|
|
} */ bsd_args;
|
|
cap_rights_t rights;
|
|
struct socket *so;
|
|
struct file *fp;
|
|
int error, error1;
|
|
|
|
bsd_args.s = s;
|
|
bsd_args.name = (struct sockaddr * __restrict)PTRIN(addr);
|
|
bsd_args.anamelen = PTRIN(namelen);
|
|
bsd_args.flags = 0;
|
|
error = linux_set_socket_flags(flags, &bsd_args.flags);
|
|
if (error != 0)
|
|
return (error);
|
|
error = sys_accept4(td, &bsd_args);
|
|
bsd_to_linux_sockaddr((struct sockaddr *)bsd_args.name);
|
|
if (error != 0) {
|
|
if (error == EFAULT && namelen != sizeof(struct sockaddr_in))
|
|
return (EINVAL);
|
|
if (error == EINVAL) {
|
|
error1 = getsock_cap(td, s,
|
|
cap_rights_init(&rights, CAP_ACCEPT), &fp, NULL, NULL);
|
|
if (error1 != 0)
|
|
return (error1);
|
|
so = fp->f_data;
|
|
if (so->so_type == SOCK_DGRAM) {
|
|
fdrop(fp, td);
|
|
return (EOPNOTSUPP);
|
|
}
|
|
fdrop(fp, td);
|
|
}
|
|
return (error);
|
|
}
|
|
if (addr)
|
|
error = linux_sa_put(PTRIN(addr));
|
|
if (error != 0) {
|
|
(void)kern_close(td, td->td_retval[0]);
|
|
td->td_retval[0] = 0;
|
|
}
|
|
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 getsockname_args /* {
|
|
int fdes;
|
|
struct sockaddr * __restrict asa;
|
|
socklen_t * __restrict alen;
|
|
} */ bsd_args;
|
|
int error;
|
|
|
|
bsd_args.fdes = args->s;
|
|
bsd_args.asa = (struct sockaddr * __restrict)PTRIN(args->addr);
|
|
bsd_args.alen = PTRIN(args->namelen);
|
|
error = sys_getsockname(td, &bsd_args);
|
|
bsd_to_linux_sockaddr((struct sockaddr *)bsd_args.asa);
|
|
if (error != 0)
|
|
return (error);
|
|
return (linux_sa_put(PTRIN(args->addr)));
|
|
}
|
|
|
|
int
|
|
linux_getpeername(struct thread *td, struct linux_getpeername_args *args)
|
|
{
|
|
struct getpeername_args /* {
|
|
int fdes;
|
|
caddr_t asa;
|
|
int *alen;
|
|
} */ bsd_args;
|
|
int error;
|
|
|
|
bsd_args.fdes = args->s;
|
|
bsd_args.asa = (struct sockaddr *)PTRIN(args->addr);
|
|
bsd_args.alen = (socklen_t *)PTRIN(args->namelen);
|
|
error = sys_getpeername(td, &bsd_args);
|
|
bsd_to_linux_sockaddr((struct sockaddr *)bsd_args.asa);
|
|
if (error != 0)
|
|
return (error);
|
|
return (linux_sa_put(PTRIN(args->addr)));
|
|
}
|
|
|
|
int
|
|
linux_socketpair(struct thread *td, struct linux_socketpair_args *args)
|
|
{
|
|
struct socketpair_args /* {
|
|
int domain;
|
|
int type;
|
|
int protocol;
|
|
int *rsv;
|
|
} */ bsd_args;
|
|
int error;
|
|
|
|
bsd_args.domain = linux_to_bsd_domain(args->domain);
|
|
if (bsd_args.domain != PF_LOCAL)
|
|
return (EAFNOSUPPORT);
|
|
bsd_args.type = args->type & LINUX_SOCK_TYPE_MASK;
|
|
if (bsd_args.type < 0 || bsd_args.type > LINUX_SOCK_MAX)
|
|
return (EINVAL);
|
|
error = linux_set_socket_flags(args->type & ~LINUX_SOCK_TYPE_MASK,
|
|
&bsd_args.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);
|
|
else
|
|
bsd_args.protocol = 0;
|
|
bsd_args.rsv = (int *)PTRIN(args->rsv);
|
|
return (sys_socketpair(td, &bsd_args));
|
|
}
|
|
|
|
#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;
|
|
|
|
bsd_args.s = args->s;
|
|
bsd_args.buf = (caddr_t)PTRIN(args->msg);
|
|
bsd_args.len = args->len;
|
|
bsd_args.flags = args->flags;
|
|
bsd_args.to = NULL;
|
|
bsd_args.tolen = 0;
|
|
return (sys_sendto(td, &bsd_args));
|
|
}
|
|
|
|
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;
|
|
|
|
if (linux_check_hdrincl(td, args->s) == 0)
|
|
/* IP_HDRINCL set, tweak the packet before sending */
|
|
return (linux_sendto_hdrincl(td, args));
|
|
|
|
msg.msg_name = PTRIN(args->to);
|
|
msg.msg_namelen = args->tolen;
|
|
msg.msg_iov = &aiov;
|
|
msg.msg_iovlen = 1;
|
|
msg.msg_control = NULL;
|
|
msg.msg_flags = 0;
|
|
aiov.iov_base = PTRIN(args->msg);
|
|
aiov.iov_len = args->len;
|
|
return (linux_sendit(td, args->s, &msg, args->flags, NULL,
|
|
UIO_USERSPACE));
|
|
}
|
|
|
|
int
|
|
linux_recvfrom(struct thread *td, struct linux_recvfrom_args *args)
|
|
{
|
|
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);
|
|
msg.msg_namelen = fromlen;
|
|
} else
|
|
msg.msg_namelen = 0;
|
|
|
|
msg.msg_name = (struct sockaddr * __restrict)PTRIN(args->from);
|
|
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_USERSPACE, NULL);
|
|
if (error != 0)
|
|
return (error);
|
|
|
|
if (PTRIN(args->from) != NULL) {
|
|
error = bsd_to_linux_sockaddr((struct sockaddr *)
|
|
PTRIN(args->from));
|
|
if (error != 0)
|
|
return (error);
|
|
|
|
error = linux_sa_put((struct osockaddr *)
|
|
PTRIN(args->from));
|
|
}
|
|
|
|
if (PTRIN(args->fromlen) != NULL)
|
|
error = copyout(&msg.msg_namelen, PTRIN(args->fromlen),
|
|
sizeof(msg.msg_namelen));
|
|
|
|
return (error);
|
|
}
|
|
|
|
static int
|
|
linux_sendmsg_common(struct thread *td, l_int s, struct l_msghdr *msghdr,
|
|
l_uint flags)
|
|
{
|
|
struct cmsghdr *cmsg;
|
|
struct cmsgcred cmcred;
|
|
struct mbuf *control;
|
|
struct msghdr msg;
|
|
struct l_cmsghdr linux_cmsg;
|
|
struct l_cmsghdr *ptr_cmsg;
|
|
struct l_msghdr linux_msg;
|
|
struct iovec *iov;
|
|
socklen_t datalen;
|
|
struct sockaddr *sa;
|
|
sa_family_t sa_family;
|
|
void *data;
|
|
int error;
|
|
|
|
error = copyin(msghdr, &linux_msg, sizeof(linux_msg));
|
|
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_msg.msg_control) != NULL && linux_msg.msg_controllen == 0)
|
|
linux_msg.msg_control = PTROUT(NULL);
|
|
|
|
error = linux_to_bsd_msghdr(&msg, &linux_msg);
|
|
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;
|
|
cmsg = NULL;
|
|
|
|
if ((ptr_cmsg = LINUX_CMSG_FIRSTHDR(&linux_msg)) != NULL) {
|
|
error = kern_getsockname(td, s, &sa, &datalen);
|
|
if (error != 0)
|
|
goto bad;
|
|
sa_family = sa->sa_family;
|
|
free(sa, M_SONAME);
|
|
|
|
error = ENOBUFS;
|
|
cmsg = malloc(CMSG_HDRSZ, M_LINUX, M_WAITOK|M_ZERO);
|
|
control = m_get(M_WAITOK, MT_CONTROL);
|
|
|
|
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))
|
|
goto bad;
|
|
|
|
/*
|
|
* Now we support only SCM_RIGHTS and SCM_CRED,
|
|
* so return EINVAL in any other cmsg_type
|
|
*/
|
|
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)
|
|
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)
|
|
continue;
|
|
|
|
data = LINUX_CMSG_DATA(ptr_cmsg);
|
|
datalen = linux_cmsg.cmsg_len - L_CMSG_HDRSZ;
|
|
|
|
switch (cmsg->cmsg_type)
|
|
{
|
|
case SCM_RIGHTS:
|
|
break;
|
|
|
|
case SCM_CREDS:
|
|
data = &cmcred;
|
|
datalen = sizeof(cmcred);
|
|
|
|
/*
|
|
* The lower levels will fill in the structure
|
|
*/
|
|
bzero(data, datalen);
|
|
break;
|
|
}
|
|
|
|
cmsg->cmsg_len = CMSG_LEN(datalen);
|
|
|
|
error = ENOBUFS;
|
|
if (!m_append(control, CMSG_HDRSZ, (c_caddr_t)cmsg))
|
|
goto bad;
|
|
if (!m_append(control, datalen, (c_caddr_t)data))
|
|
goto bad;
|
|
} while ((ptr_cmsg = LINUX_CMSG_NXTHDR(&linux_msg, ptr_cmsg)));
|
|
|
|
if (m_length(control, NULL) == 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);
|
|
if (cmsg)
|
|
free(cmsg, M_LINUX);
|
|
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
|
|
linux_recvmsg_common(struct thread *td, l_int s, struct l_msghdr *msghdr,
|
|
l_uint flags, struct msghdr *msg)
|
|
{
|
|
struct cmsghdr *cm;
|
|
struct cmsgcred *cmcred;
|
|
struct l_cmsghdr *linux_cmsg = NULL;
|
|
struct l_ucred linux_ucred;
|
|
socklen_t datalen, outlen;
|
|
struct l_msghdr linux_msg;
|
|
struct iovec *iov, *uiov;
|
|
struct mbuf *control = NULL;
|
|
struct mbuf **controlp;
|
|
struct timeval *ftmvl;
|
|
l_timeval ltmvl;
|
|
caddr_t outbuf;
|
|
void *data;
|
|
int error, i, fd, fds, *fdp;
|
|
|
|
error = copyin(msghdr, &linux_msg, sizeof(linux_msg));
|
|
if (error != 0)
|
|
return (error);
|
|
|
|
error = linux_to_bsd_msghdr(msg, &linux_msg);
|
|
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) {
|
|
error = linux_to_bsd_sockaddr((struct sockaddr *)msg->msg_name,
|
|
msg->msg_namelen);
|
|
if (error != 0)
|
|
goto bad;
|
|
}
|
|
|
|
uiov = msg->msg_iov;
|
|
msg->msg_iov = iov;
|
|
controlp = (msg->msg_control != NULL) ? &control : NULL;
|
|
error = kern_recvit(td, s, msg, UIO_USERSPACE, controlp);
|
|
msg->msg_iov = uiov;
|
|
if (error != 0)
|
|
goto bad;
|
|
|
|
error = bsd_to_linux_msghdr(msg, &linux_msg);
|
|
if (error != 0)
|
|
goto bad;
|
|
|
|
if (linux_msg.msg_name) {
|
|
error = bsd_to_linux_sockaddr((struct sockaddr *)
|
|
PTRIN(linux_msg.msg_name));
|
|
if (error != 0)
|
|
goto bad;
|
|
}
|
|
if (linux_msg.msg_name && linux_msg.msg_namelen > 2) {
|
|
error = linux_sa_put(PTRIN(linux_msg.msg_name));
|
|
if (error != 0)
|
|
goto bad;
|
|
}
|
|
|
|
outbuf = PTRIN(linux_msg.msg_control);
|
|
outlen = 0;
|
|
|
|
if (control) {
|
|
linux_cmsg = malloc(L_CMSG_HDRSZ, M_LINUX, M_WAITOK | M_ZERO);
|
|
|
|
msg->msg_control = mtod(control, struct cmsghdr *);
|
|
msg->msg_controllen = control->m_len;
|
|
|
|
cm = CMSG_FIRSTHDR(msg);
|
|
|
|
while (cm != NULL) {
|
|
linux_cmsg->cmsg_type =
|
|
bsd_to_linux_cmsg_type(cm->cmsg_type);
|
|
linux_cmsg->cmsg_level =
|
|
bsd_to_linux_sockopt_level(cm->cmsg_level);
|
|
if (linux_cmsg->cmsg_type == -1
|
|
|| cm->cmsg_level != SOL_SOCKET)
|
|
{
|
|
error = EINVAL;
|
|
goto bad;
|
|
}
|
|
|
|
data = CMSG_DATA(cm);
|
|
datalen = (caddr_t)cm + cm->cmsg_len - (caddr_t)data;
|
|
|
|
switch (cm->cmsg_type)
|
|
{
|
|
case SCM_RIGHTS:
|
|
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);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SCM_CREDS:
|
|
/*
|
|
* Currently LOCAL_CREDS is never in
|
|
* effect for Linux so no need to worry
|
|
* about sockcred
|
|
*/
|
|
if (datalen != sizeof(*cmcred)) {
|
|
error = EMSGSIZE;
|
|
goto bad;
|
|
}
|
|
cmcred = (struct cmsgcred *)data;
|
|
bzero(&linux_ucred, sizeof(linux_ucred));
|
|
linux_ucred.pid = cmcred->cmcred_pid;
|
|
linux_ucred.uid = cmcred->cmcred_uid;
|
|
linux_ucred.gid = cmcred->cmcred_gid;
|
|
data = &linux_ucred;
|
|
datalen = sizeof(linux_ucred);
|
|
break;
|
|
|
|
case SCM_TIMESTAMP:
|
|
if (datalen != sizeof(struct timeval)) {
|
|
error = EMSGSIZE;
|
|
goto bad;
|
|
}
|
|
ftmvl = (struct timeval *)data;
|
|
ltmvl.tv_sec = ftmvl->tv_sec;
|
|
ltmvl.tv_usec = ftmvl->tv_usec;
|
|
data = <mvl;
|
|
datalen = sizeof(ltmvl);
|
|
break;
|
|
}
|
|
|
|
if (outlen + LINUX_CMSG_LEN(datalen) >
|
|
linux_msg.msg_controllen) {
|
|
if (outlen == 0) {
|
|
error = EMSGSIZE;
|
|
goto bad;
|
|
} else {
|
|
linux_msg.msg_flags |=
|
|
LINUX_MSG_CTRUNC;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
linux_cmsg->cmsg_len = LINUX_CMSG_LEN(datalen);
|
|
|
|
error = copyout(linux_cmsg, outbuf, L_CMSG_HDRSZ);
|
|
if (error != 0)
|
|
goto bad;
|
|
outbuf += L_CMSG_HDRSZ;
|
|
|
|
error = copyout(data, outbuf, datalen);
|
|
if (error != 0)
|
|
goto bad;
|
|
|
|
outbuf += LINUX_CMSG_ALIGN(datalen);
|
|
outlen += LINUX_CMSG_LEN(datalen);
|
|
|
|
cm = CMSG_NXTHDR(msg, cm);
|
|
}
|
|
}
|
|
|
|
out:
|
|
linux_msg.msg_controllen = outlen;
|
|
error = copyout(&linux_msg, msghdr, sizeof(linux_msg));
|
|
|
|
bad:
|
|
free(iov, M_IOV);
|
|
m_freem(control);
|
|
free(linux_cmsg, M_LINUX);
|
|
|
|
return (error);
|
|
}
|
|
|
|
int
|
|
linux_recvmsg(struct thread *td, struct linux_recvmsg_args *args)
|
|
{
|
|
struct msghdr bsd_msg;
|
|
|
|
return (linux_recvmsg_common(td, args->s, PTRIN(args->msg),
|
|
args->flags, &bsd_msg));
|
|
}
|
|
|
|
int
|
|
linux_recvmmsg(struct thread *td, struct linux_recvmmsg_args *args)
|
|
{
|
|
struct l_mmsghdr *msg;
|
|
struct msghdr bsd_msg;
|
|
struct l_timespec lts;
|
|
struct timespec ts, tts;
|
|
l_uint retval;
|
|
int error, datagrams;
|
|
|
|
if (args->timeout) {
|
|
error = copyin(args->timeout, <s, sizeof(struct l_timespec));
|
|
if (error != 0)
|
|
return (error);
|
|
error = linux_to_native_timespec(&ts, <s);
|
|
if (error != 0)
|
|
return (error);
|
|
getnanotime(&tts);
|
|
timespecadd(&tts, &ts);
|
|
}
|
|
|
|
msg = PTRIN(args->msg);
|
|
datagrams = 0;
|
|
while (datagrams < args->vlen) {
|
|
error = linux_recvmsg_common(td, args->s, &msg->msg_hdr,
|
|
args->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 (args->flags & LINUX_MSG_WAITFORONE)
|
|
args->flags |= LINUX_MSG_DONTWAIT;
|
|
|
|
/*
|
|
* See BUGS section of recvmmsg(2).
|
|
*/
|
|
if (args->timeout) {
|
|
getnanotime(&ts);
|
|
timespecsub(&ts, &tts);
|
|
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;
|
|
return (error);
|
|
}
|
|
|
|
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 setsockopt_args /* {
|
|
int s;
|
|
int level;
|
|
int name;
|
|
caddr_t val;
|
|
int valsize;
|
|
} */ bsd_args;
|
|
l_timeval linux_tv;
|
|
struct timeval tv;
|
|
int error, name;
|
|
|
|
bsd_args.s = args->s;
|
|
bsd_args.level = linux_to_bsd_sockopt_level(args->level);
|
|
switch (bsd_args.level) {
|
|
case SOL_SOCKET:
|
|
name = linux_to_bsd_so_sockopt(args->optname);
|
|
switch (name) {
|
|
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, bsd_args.level,
|
|
name, &tv, UIO_SYSSPACE, sizeof(tv)));
|
|
/* 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 == -1)
|
|
return (ENOPROTOOPT);
|
|
|
|
bsd_args.name = name;
|
|
bsd_args.val = PTRIN(args->optval);
|
|
bsd_args.valsize = args->optlen;
|
|
|
|
if (name == IPV6_NEXTHOP) {
|
|
linux_to_bsd_sockaddr((struct sockaddr *)bsd_args.val,
|
|
bsd_args.valsize);
|
|
error = sys_setsockopt(td, &bsd_args);
|
|
bsd_to_linux_sockaddr((struct sockaddr *)bsd_args.val);
|
|
} else
|
|
error = sys_setsockopt(td, &bsd_args);
|
|
|
|
return (error);
|
|
}
|
|
|
|
int
|
|
linux_getsockopt(struct thread *td, struct linux_getsockopt_args *args)
|
|
{
|
|
struct getsockopt_args /* {
|
|
int s;
|
|
int level;
|
|
int name;
|
|
caddr_t val;
|
|
int *avalsize;
|
|
} */ bsd_args;
|
|
l_timeval linux_tv;
|
|
struct timeval tv;
|
|
socklen_t tv_len, xulen, len;
|
|
struct xucred xu;
|
|
struct l_ucred lxu;
|
|
int error, name, newval;
|
|
|
|
bsd_args.s = args->s;
|
|
bsd_args.level = linux_to_bsd_sockopt_level(args->level);
|
|
switch (bsd_args.level) {
|
|
case SOL_SOCKET:
|
|
name = linux_to_bsd_so_sockopt(args->optname);
|
|
switch (name) {
|
|
case SO_RCVTIMEO:
|
|
/* FALLTHROUGH */
|
|
case SO_SNDTIMEO:
|
|
tv_len = sizeof(tv);
|
|
error = kern_getsockopt(td, args->s, bsd_args.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 (copyout(&linux_tv, PTRIN(args->optval),
|
|
sizeof(linux_tv)));
|
|
/* NOTREACHED */
|
|
case LOCAL_PEERCRED:
|
|
if (args->optlen < sizeof(lxu))
|
|
return (EINVAL);
|
|
xulen = sizeof(xu);
|
|
error = kern_getsockopt(td, args->s, bsd_args.level,
|
|
name, &xu, UIO_SYSSPACE, &xulen);
|
|
if (error != 0)
|
|
return (error);
|
|
/*
|
|
* XXX Use 0 for pid as the FreeBSD does not cache peer pid.
|
|
*/
|
|
lxu.pid = 0;
|
|
lxu.uid = xu.cr_uid;
|
|
lxu.gid = xu.cr_gid;
|
|
return (copyout(&lxu, PTRIN(args->optval), sizeof(lxu)));
|
|
/* NOTREACHED */
|
|
case SO_ERROR:
|
|
len = sizeof(newval);
|
|
error = kern_getsockopt(td, args->s, bsd_args.level,
|
|
name, &newval, UIO_SYSSPACE, &len);
|
|
if (error != 0)
|
|
return (error);
|
|
newval = -SV_ABI_ERRNO(td->td_proc, newval);
|
|
return (copyout(&newval, PTRIN(args->optval), len));
|
|
/* 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 == -1)
|
|
return (EINVAL);
|
|
|
|
bsd_args.name = name;
|
|
bsd_args.val = PTRIN(args->optval);
|
|
bsd_args.avalsize = PTRIN(args->optlen);
|
|
|
|
if (name == IPV6_NEXTHOP) {
|
|
error = sys_getsockopt(td, &bsd_args);
|
|
bsd_to_linux_sockaddr((struct sockaddr *)bsd_args.val);
|
|
} else
|
|
error = sys_getsockopt(td, &bsd_args);
|
|
|
|
return (error);
|
|
}
|
|
|
|
#if defined(__i386__) || (defined(__amd64__) && defined(COMPAT_LINUX32))
|
|
|
|
/* 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 */
|
|
};
|
|
#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));
|
|
}
|
|
|
|
uprintf("LINUX: 'socket' typ=%d not implemented\n", args->what);
|
|
return (ENOSYS);
|
|
}
|
|
#endif /* __i386__ || (__amd64__ && COMPAT_LINUX32) */
|