#include <sys/file.h>
#include <sys/stropts.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/sysmacros.h>
#include <sys/filio.h>
#include <sys/sockio.h>
#include <sys/poll_impl.h>
#include <sys/cmn_err.h>
#include <sys/ksocket.h>
#include <io/ksocket/ksocket_impl.h>
#include <fs/sockfs/sockcommon.h>
#define SOCKETMOD_TCP "tcp"
#define SOCKETMOD_UDP "udp"
int
ksocket_socket(ksocket_t *ksp, int domain, int type, int protocol, int flags,
struct cred *cr)
{
static const int version = SOV_DEFAULT;
int error = 0;
struct sonode *so;
*ksp = NULL;
ASSERT(cr != NULL);
ASSERT(flags == KSOCKET_SLEEP || flags == KSOCKET_NOSLEEP);
so = socket_create(domain, type, protocol, NULL, NULL, version, flags,
cr, &error);
if (so == NULL) {
if (error == EAFNOSUPPORT) {
char *mod = NULL;
if (type == SOCK_STREAM && (domain == AF_INET ||
domain == AF_INET6) && (protocol == 0 ||
protocol == IPPROTO_TCP)) {
mod = SOCKETMOD_TCP;
} else if (type == SOCK_DGRAM && (domain == AF_INET ||
domain == AF_INET6) && (protocol == 0 ||
protocol == IPPROTO_UDP)) {
mod = SOCKETMOD_UDP;
} else {
return (EAFNOSUPPORT);
}
so = socket_create(domain, type, protocol, NULL,
mod, version, flags, cr, &error);
if (so == NULL)
return (error);
} else {
return (error);
}
}
so->so_mode |= SM_KERNEL;
*ksp = SOTOKS(so);
return (0);
}
int
ksocket_bind(ksocket_t ks, struct sockaddr *addr, socklen_t addrlen,
struct cred *cr)
{
int error;
ASSERT(cr != NULL);
if (!KSOCKET_VALID(ks))
return (ENOTSOCK);
error = socket_bind(KSTOSO(ks), addr, addrlen, _SOBIND_SOCKBSD, cr);
return (error);
}
int
ksocket_listen(ksocket_t ks, int backlog, struct cred *cr)
{
ASSERT(cr != NULL);
if (!KSOCKET_VALID(ks))
return (ENOTSOCK);
return (socket_listen(KSTOSO(ks), backlog, cr));
}
int
ksocket_accept(ksocket_t ks, struct sockaddr *addr,
socklen_t *addrlenp, ksocket_t *nks, struct cred *cr)
{
int error;
struct sonode *nso = NULL;
ASSERT(cr != NULL);
*nks = NULL;
if (!KSOCKET_VALID(ks))
return (ENOTSOCK);
if (addr != NULL && addrlenp == NULL)
return (EFAULT);
error = socket_accept(KSTOSO(ks), KSOCKET_FMODE(ks), cr, &nso);
if (error != 0)
return (error);
ASSERT(nso != NULL);
nso->so_mode |= SM_KERNEL;
if (addr != NULL && addrlenp != NULL) {
error = socket_getpeername(nso, addr, addrlenp, B_TRUE, cr);
if (error != 0) {
(void) socket_close(nso, 0, cr);
socket_destroy(nso);
return ((error == ENOTCONN) ? ECONNABORTED : error);
}
}
*nks = SOTOKS(nso);
return (error);
}
int
ksocket_connect(ksocket_t ks, struct sockaddr *addr, socklen_t addrlen,
struct cred *cr)
{
ASSERT(cr != NULL);
if (!KSOCKET_VALID(ks))
return (ENOTSOCK);
return (socket_connect(KSTOSO(ks), addr, addrlen,
KSOCKET_FMODE(ks), 0, cr));
}
int
ksocket_send(ksocket_t ks, void *msg, size_t msglen, int flags,
size_t *sent, struct cred *cr)
{
int error;
struct nmsghdr msghdr;
struct uio auio;
struct iovec iov;
ASSERT(cr != NULL);
if (!KSOCKET_VALID(ks)) {
if (sent != NULL)
*sent = 0;
return (ENOTSOCK);
}
iov.iov_base = msg;
iov.iov_len = msglen;
bzero(&auio, sizeof (struct uio));
auio.uio_loffset = 0;
auio.uio_iov = &iov;
auio.uio_iovcnt = 1;
auio.uio_resid = msglen;
if (flags & MSG_USERSPACE)
auio.uio_segflg = UIO_USERSPACE;
else
auio.uio_segflg = UIO_SYSSPACE;
auio.uio_extflg = UIO_COPY_DEFAULT;
auio.uio_limit = 0;
auio.uio_fmode = KSOCKET_FMODE(ks);
msghdr.msg_name = NULL;
msghdr.msg_namelen = 0;
msghdr.msg_control = NULL;
msghdr.msg_controllen = 0;
msghdr.msg_flags = flags | MSG_EOR;
error = socket_sendmsg(KSTOSO(ks), &msghdr, &auio, cr);
if (error != 0) {
if (sent != NULL)
*sent = 0;
return (error);
}
if (sent != NULL)
*sent = msglen - auio.uio_resid;
return (0);
}
int
ksocket_sendto(ksocket_t ks, void *msg, size_t msglen, int flags,
struct sockaddr *name, socklen_t namelen, size_t *sent, struct cred *cr)
{
int error;
struct nmsghdr msghdr;
struct uio auio;
struct iovec iov;
ASSERT(cr != NULL);
if (!KSOCKET_VALID(ks)) {
if (sent != NULL)
*sent = 0;
return (ENOTSOCK);
}
iov.iov_base = msg;
iov.iov_len = msglen;
bzero(&auio, sizeof (struct uio));
auio.uio_loffset = 0;
auio.uio_iov = &iov;
auio.uio_iovcnt = 1;
auio.uio_resid = msglen;
if (flags & MSG_USERSPACE)
auio.uio_segflg = UIO_USERSPACE;
else
auio.uio_segflg = UIO_SYSSPACE;
auio.uio_extflg = UIO_COPY_DEFAULT;
auio.uio_limit = 0;
auio.uio_fmode = KSOCKET_FMODE(ks);
msghdr.msg_iov = &iov;
msghdr.msg_iovlen = 1;
msghdr.msg_name = (char *)name;
msghdr.msg_namelen = namelen;
msghdr.msg_control = NULL;
msghdr.msg_controllen = 0;
msghdr.msg_flags = flags | MSG_EOR;
error = socket_sendmsg(KSTOSO(ks), &msghdr, &auio, cr);
if (error != 0) {
if (sent != NULL)
*sent = 0;
return (error);
}
if (sent != NULL)
*sent = msglen - auio.uio_resid;
return (0);
}
int
ksocket_sendmsg(ksocket_t ks, struct nmsghdr *msg, int flags,
size_t *sent, struct cred *cr)
{
int error;
ssize_t len;
int i;
struct uio auio;
ASSERT(cr != NULL);
if (!KSOCKET_VALID(ks)) {
if (sent != NULL)
*sent = 0;
return (ENOTSOCK);
}
bzero(&auio, sizeof (struct uio));
auio.uio_loffset = 0;
auio.uio_iov = msg->msg_iov;
auio.uio_iovcnt = msg->msg_iovlen;
if (flags & MSG_USERSPACE)
auio.uio_segflg = UIO_USERSPACE;
else
auio.uio_segflg = UIO_SYSSPACE;
auio.uio_extflg = UIO_COPY_DEFAULT;
auio.uio_limit = 0;
auio.uio_fmode = KSOCKET_FMODE(ks);
len = 0;
for (i = 0; i < msg->msg_iovlen; i++) {
ssize_t iovlen;
iovlen = (msg->msg_iov)[i].iov_len;
len += iovlen;
if (len < 0 || iovlen < 0)
return (EINVAL);
}
auio.uio_resid = len;
msg->msg_flags = flags | MSG_EOR;
error = socket_sendmsg(KSTOSO(ks), msg, &auio, cr);
if (error != 0) {
if (sent != NULL)
*sent = 0;
return (error);
}
if (sent != NULL)
*sent = len - auio.uio_resid;
return (0);
}
int
ksocket_recv(ksocket_t ks, void *msg, size_t msglen, int flags,
size_t *recv, struct cred *cr)
{
int error;
struct nmsghdr msghdr;
struct uio auio;
struct iovec iov;
ASSERT(cr != NULL);
if (!KSOCKET_VALID(ks)) {
if (recv != NULL)
*recv = 0;
return (ENOTSOCK);
}
iov.iov_base = msg;
iov.iov_len = msglen;
bzero(&auio, sizeof (struct uio));
auio.uio_loffset = 0;
auio.uio_iov = &iov;
auio.uio_iovcnt = 1;
auio.uio_resid = msglen;
if (flags & MSG_USERSPACE)
auio.uio_segflg = UIO_USERSPACE;
else
auio.uio_segflg = UIO_SYSSPACE;
auio.uio_extflg = UIO_COPY_DEFAULT;
auio.uio_limit = 0;
auio.uio_fmode = KSOCKET_FMODE(ks);
msghdr.msg_name = NULL;
msghdr.msg_namelen = 0;
msghdr.msg_control = NULL;
msghdr.msg_controllen = 0;
msghdr.msg_flags = flags & (MSG_OOB | MSG_PEEK | MSG_WAITALL |
MSG_DONTWAIT | MSG_USERSPACE);
error = socket_recvmsg(KSTOSO(ks), &msghdr, &auio, cr);
if (error != 0) {
if (recv != NULL)
*recv = 0;
return (error);
}
if (recv != NULL)
*recv = msglen - auio.uio_resid;
return (0);
}
int
ksocket_recvfrom(ksocket_t ks, void *msg, size_t msglen, int flags,
struct sockaddr *name, socklen_t *namelen, size_t *recv, struct cred *cr)
{
int error;
struct nmsghdr msghdr;
struct uio auio;
struct iovec iov;
ASSERT(cr != NULL);
if (!KSOCKET_VALID(ks)) {
if (recv != NULL)
*recv = 0;
return (ENOTSOCK);
}
iov.iov_base = msg;
iov.iov_len = msglen;
bzero(&auio, sizeof (struct uio));
auio.uio_loffset = 0;
auio.uio_iov = &iov;
auio.uio_iovcnt = 1;
auio.uio_resid = msglen;
if (flags & MSG_USERSPACE)
auio.uio_segflg = UIO_USERSPACE;
else
auio.uio_segflg = UIO_SYSSPACE;
auio.uio_extflg = UIO_COPY_DEFAULT;
auio.uio_limit = 0;
auio.uio_fmode = KSOCKET_FMODE(ks);
msghdr.msg_name = (char *)name;
msghdr.msg_namelen = *namelen;
msghdr.msg_control = NULL;
msghdr.msg_controllen = 0;
msghdr.msg_flags = flags & (MSG_OOB | MSG_PEEK | MSG_WAITALL |
MSG_DONTWAIT | MSG_USERSPACE);
error = socket_recvmsg(KSTOSO(ks), &msghdr, &auio, cr);
if (error != 0) {
if (recv != NULL)
*recv = 0;
return (error);
}
if (recv != NULL)
*recv = msglen - auio.uio_resid;
bcopy(msghdr.msg_name, name, msghdr.msg_namelen);
bcopy(&msghdr.msg_namelen, namelen, sizeof (msghdr.msg_namelen));
return (0);
}
int
ksocket_recvmsg(ksocket_t ks, struct nmsghdr *msg, int flags, size_t *recv,
struct cred *cr)
{
int error;
ssize_t len;
int i;
struct uio auio;
ASSERT(cr != NULL);
if (!KSOCKET_VALID(ks)) {
if (recv != NULL)
*recv = 0;
return (ENOTSOCK);
}
bzero(&auio, sizeof (struct uio));
auio.uio_loffset = 0;
auio.uio_iov = msg->msg_iov;
auio.uio_iovcnt = msg->msg_iovlen;
if (msg->msg_flags & MSG_USERSPACE)
auio.uio_segflg = UIO_USERSPACE;
else
auio.uio_segflg = UIO_SYSSPACE;
auio.uio_extflg = UIO_COPY_DEFAULT;
auio.uio_limit = 0;
auio.uio_fmode = KSOCKET_FMODE(ks);
len = 0;
for (i = 0; i < msg->msg_iovlen; i++) {
ssize_t iovlen;
iovlen = (msg->msg_iov)[i].iov_len;
len += iovlen;
if (len < 0 || iovlen < 0)
return (EINVAL);
}
auio.uio_resid = len;
msg->msg_flags = flags & (MSG_OOB | MSG_PEEK | MSG_WAITALL |
MSG_DONTWAIT | MSG_USERSPACE);
error = socket_recvmsg(KSTOSO(ks), msg, &auio, cr);
if (error != 0) {
if (recv != NULL)
*recv = 0;
return (error);
}
if (recv != NULL)
*recv = len - auio.uio_resid;
return (0);
}
int
ksocket_shutdown(ksocket_t ks, int how, struct cred *cr)
{
struct sonode *so;
ASSERT(cr != NULL);
if (!KSOCKET_VALID(ks))
return (ENOTSOCK);
so = KSTOSO(ks);
return (socket_shutdown(so, how, cr));
}
int
ksocket_close(ksocket_t ks, struct cred *cr)
{
struct sonode *so;
so = KSTOSO(ks);
ASSERT(cr != NULL);
mutex_enter(&so->so_lock);
if (!KSOCKET_VALID(ks)) {
mutex_exit(&so->so_lock);
return (ENOTSOCK);
}
so->so_state |= SS_CLOSING;
if (so->so_count > 1) {
mutex_enter(&so->so_acceptq_lock);
cv_broadcast(&so->so_acceptq_cv);
mutex_exit(&so->so_acceptq_lock);
cv_broadcast(&so->so_rcv_cv);
cv_broadcast(&so->so_state_cv);
cv_broadcast(&so->so_single_cv);
cv_broadcast(&so->so_read_cv);
cv_broadcast(&so->so_snd_cv);
cv_broadcast(&so->so_copy_cv);
}
while (so->so_count > 1)
cv_wait(&so->so_closing_cv, &so->so_lock);
mutex_exit(&so->so_lock);
(void) ksocket_setcallbacks(ks, NULL, NULL, cr);
(void) socket_close(so, 0, cr);
socket_destroy(so);
return (0);
}
int
ksocket_getsockname(ksocket_t ks, struct sockaddr *addr, socklen_t *addrlen,
struct cred *cr)
{
struct sonode *so;
ASSERT(cr != NULL);
if (!KSOCKET_VALID(ks))
return (ENOTSOCK);
so = KSTOSO(ks);
if (addrlen == NULL || (addr == NULL && *addrlen != 0))
return (EFAULT);
return (socket_getsockname(so, addr, addrlen, cr));
}
int
ksocket_getpeername(ksocket_t ks, struct sockaddr *addr, socklen_t *addrlen,
struct cred *cr)
{
struct sonode *so;
ASSERT(cr != NULL);
if (!KSOCKET_VALID(ks))
return (ENOTSOCK);
so = KSTOSO(ks);
if (addrlen == NULL || (addr == NULL && *addrlen != 0))
return (EFAULT);
return (socket_getpeername(so, addr, addrlen, B_FALSE, cr));
}
int
ksocket_getsockopt(ksocket_t ks, int level, int optname, void *optval,
int *optlen, struct cred *cr)
{
struct sonode *so;
ASSERT(cr != NULL);
if (!KSOCKET_VALID(ks))
return (ENOTSOCK);
so = KSTOSO(ks);
if (optlen == NULL)
return (EFAULT);
if (*optlen > SO_MAXARGSIZE)
return (EINVAL);
return (socket_getsockopt(so, level, optname, optval,
(socklen_t *)optlen, 0, cr));
}
int
ksocket_setsockopt(ksocket_t ks, int level, int optname, const void *optval,
int optlen, struct cred *cr)
{
struct sonode *so;
ASSERT(cr != NULL);
if (!KSOCKET_VALID(ks))
return (ENOTSOCK);
so = KSTOSO(ks);
if (optval == NULL)
optlen = 0;
return (socket_setsockopt(so, level, optname, optval,
(t_uscalar_t)optlen, cr));
}
int
ksocket_setcallbacks(ksocket_t ks, ksocket_callbacks_t *cb, void *arg,
struct cred *cr)
{
struct sonode *so;
ASSERT(cr != NULL);
if (!KSOCKET_VALID(ks))
return (ENOTSOCK);
so = KSTOSO(ks);
if (cb == NULL && arg != NULL)
return (EFAULT);
if (cb == NULL) {
mutex_enter(&so->so_lock);
bzero(&(so->so_ksock_callbacks), sizeof (ksocket_callbacks_t));
so->so_ksock_cb_arg = NULL;
mutex_exit(&so->so_lock);
} else {
mutex_enter(&so->so_lock);
SETCALLBACK(so, cb, connected, KSOCKET_CB_CONNECTED)
SETCALLBACK(so, cb, connectfailed, KSOCKET_CB_CONNECTFAILED)
SETCALLBACK(so, cb, disconnected, KSOCKET_CB_DISCONNECTED)
SETCALLBACK(so, cb, newdata, KSOCKET_CB_NEWDATA)
SETCALLBACK(so, cb, newconn, KSOCKET_CB_NEWCONN)
SETCALLBACK(so, cb, cansend, KSOCKET_CB_CANSEND)
SETCALLBACK(so, cb, oobdata, KSOCKET_CB_OOBDATA)
SETCALLBACK(so, cb, cantsendmore, KSOCKET_CB_CANTSENDMORE)
SETCALLBACK(so, cb, cantrecvmore, KSOCKET_CB_CANTRECVMORE)
so->so_ksock_cb_arg = arg;
mutex_exit(&so->so_lock);
}
return (0);
}
int
ksocket_ioctl(ksocket_t ks, int cmd, intptr_t arg, int *rvalp, struct cred *cr)
{
struct sonode *so;
int rval;
ASSERT(cr != NULL);
if (!KSOCKET_VALID(ks))
return (ENOTSOCK);
so = KSTOSO(ks);
switch (cmd) {
default:
if ((cmd & 0xffffff00U) == STR) {
rval = EOPNOTSUPP;
} else {
rval = socket_ioctl(so, cmd, arg,
KSOCKET_FMODE(ks) | FKIOCTL, cr, rvalp);
}
break;
case FIOASYNC:
case SIOCSPGRP:
case FIOSETOWN:
case SIOCGPGRP:
case FIOGETOWN:
rval = EOPNOTSUPP;
break;
}
return (rval);
}
int
ksocket_spoll(ksocket_t ks, int timo, short events, short *revents,
struct cred *cr)
{
struct sonode *so;
pollhead_t *php, *php2;
polldat_t *pdp;
pollcache_t *pcp;
int error;
clock_t expires = 0;
clock_t rval;
ASSERT(cr != NULL);
ASSERT(curthread->t_pollcache == NULL);
if (revents == NULL)
return (EINVAL);
if (!KSOCKET_VALID(ks))
return (ENOTSOCK);
so = KSTOSO(ks);
php = NULL;
*revents = 0;
pcp = pcache_alloc();
pcache_create(pcp, 1);
mutex_enter(&pcp->pc_lock);
curthread->t_pollcache = pcp;
error = socket_poll(so, (short)events, (timo == 0),
revents, &php);
curthread->t_pollcache = NULL;
mutex_exit(&pcp->pc_lock);
if (error != 0 || *revents != 0 || timo == 0)
goto out;
if (php == NULL) {
error = EIO;
goto out;
}
if (timo > 0)
expires = ddi_get_lbolt() +
MSEC_TO_TICK_ROUNDUP(timo);
pdp = kmem_zalloc(sizeof (*pdp), KM_SLEEP);
pdp->pd_fd = 0;
pdp->pd_events = events;
pdp->pd_pcache = pcp;
pcache_insert_fd(pcp, pdp, 1);
polldat_associate(pdp, php);
mutex_enter(&pcp->pc_lock);
while (!(so->so_state & SS_CLOSING)) {
pcp->pc_flag = 0;
curthread->t_pollcache = pcp;
error = socket_poll(so, (short)events, 0,
revents, &php2);
curthread->t_pollcache = NULL;
ASSERT(php2 == php);
if (error != 0 || *revents != 0)
break;
if (pcp->pc_flag & PC_POLLWAKE)
continue;
if (timo == -1) {
rval = cv_wait_sig(&pcp->pc_cv, &pcp->pc_lock);
} else {
rval = cv_timedwait_sig(&pcp->pc_cv, &pcp->pc_lock,
expires);
}
if (rval <= 0) {
if (rval == 0)
error = EINTR;
break;
}
}
mutex_exit(&pcp->pc_lock);
polldat_disassociate(pdp);
pdp->pd_fd = 0;
mutex_enter(&pcp->pc_no_exit);
ASSERT(pcp->pc_busy >= 0);
while (pcp->pc_busy > 0)
cv_wait(&pcp->pc_busy_cv, &pcp->pc_no_exit);
mutex_exit(&pcp->pc_no_exit);
out:
pcache_destroy(pcp);
return (error);
}
int
ksocket_sendmblk(ksocket_t ks, struct nmsghdr *msg, int flags,
mblk_t **mpp, cred_t *cr)
{
struct sonode *so;
int i_val;
socklen_t val_len;
mblk_t *mp = *mpp;
int error;
ASSERT(cr != NULL);
if (!KSOCKET_VALID(ks))
return (ENOTSOCK);
so = KSTOSO(ks);
if (flags & MSG_MBLK_QUICKRELE) {
error = socket_getsockopt(so, SOL_SOCKET, SO_SND_COPYAVOID,
&i_val, &val_len, 0, cr);
if (error != 0)
return (error);
if (i_val == 0)
return (ECANCELED);
for (; mp != NULL; mp = mp->b_cont)
mp->b_datap->db_struioflag |= STRUIO_ZC;
}
error = socket_sendmblk(so, msg, flags, cr, mpp);
return (error);
}
void
ksocket_hold(ksocket_t ks)
{
struct sonode *so;
so = KSTOSO(ks);
if (!mutex_owned(&so->so_lock)) {
mutex_enter(&so->so_lock);
so->so_count++;
mutex_exit(&so->so_lock);
} else
so->so_count++;
}
void
ksocket_rele(ksocket_t ks)
{
struct sonode *so;
so = KSTOSO(ks);
if (so->so_count < 2)
cmn_err(CE_PANIC, "ksocket_rele: sonode ref count 0 or 1");
if (!mutex_owned(&so->so_lock)) {
mutex_enter(&so->so_lock);
if (--so->so_count == 1)
cv_signal(&so->so_closing_cv);
mutex_exit(&so->so_lock);
} else {
if (--so->so_count == 1)
cv_signal(&so->so_closing_cv);
}
}
int
ksocket_krecv_set(ksocket_t ks, ksocket_krecv_f cb, void *arg)
{
return (so_krecv_set(KSTOSO(ks), (so_krecv_f)cb, arg));
}
void
ksocket_krecv_unblock(ksocket_t ks)
{
return (so_krecv_unblock(KSTOSO(ks)));
}