/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2017 Joyent, Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "socksdp.h" #include /* * SDP sockfs sonode operations */ static int sosdp_init(struct sonode *, struct sonode *, struct cred *, int); static int sosdp_accept(struct sonode *, int, struct cred *, struct sonode **); static int sosdp_bind(struct sonode *, struct sockaddr *, socklen_t, int, struct cred *); static int sosdp_listen(struct sonode *, int, struct cred *); static int sosdp_connect(struct sonode *, struct sockaddr *, socklen_t, int, int, struct cred *); static int sosdp_recvmsg(struct sonode *, struct nmsghdr *, struct uio *, struct cred *); static int sosdp_sendmsg(struct sonode *, struct nmsghdr *, struct uio *, struct cred *); static int sosdp_getpeername(struct sonode *, struct sockaddr *, socklen_t *, boolean_t, struct cred *); static int sosdp_getsockname(struct sonode *, struct sockaddr *, socklen_t *, struct cred *); static int sosdp_shutdown(struct sonode *, int, struct cred *); static int sosdp_getsockopt(struct sonode *, int, int, void *, socklen_t *, int, struct cred *); static int sosdp_setsockopt(struct sonode *, int, int, const void *, socklen_t, struct cred *); static int sosdp_ioctl(struct sonode *, int, intptr_t, int, struct cred *, int32_t *); static int sosdp_poll(struct sonode *, short, int, short *, struct pollhead **); static int sosdp_close(struct sonode *, int, struct cred *); void sosdp_fini(struct sonode *, struct cred *); /* * Socket upcalls */ static void *sdp_sock_newconn(void *parenthandle, void *connind); static void sdp_sock_connected(void *handle); static void sdp_sock_disconnected(void *handle, int error); static void sdp_sock_connfail(void *handle, int error); static int sdp_sock_recv(void *handle, mblk_t *mp, int flags); static void sdp_sock_xmitted(void *handle, int txqueued); static void sdp_sock_urgdata(void *handle); static void sdp_sock_ordrel(void *handle); sonodeops_t sosdp_sonodeops = { sosdp_init, /* sop_init */ sosdp_accept, /* sop_accept */ sosdp_bind, /* sop_bind */ sosdp_listen, /* sop_listen */ sosdp_connect, /* sop_connect */ sosdp_recvmsg, /* sop_recvmsg */ sosdp_sendmsg, /* sop_sendmsg */ so_sendmblk_notsupp, /* sop_sendmblk */ sosdp_getpeername, /* sop_getpeername */ sosdp_getsockname, /* sop_getsockname */ sosdp_shutdown, /* sop_shutdown */ sosdp_getsockopt, /* sop_getsockopt */ sosdp_setsockopt, /* sop_setsockopt */ sosdp_ioctl, /* sop_ioctl */ sosdp_poll, /* sop_poll */ sosdp_close, /* sop_close */ }; sdp_upcalls_t sosdp_sock_upcalls = { sdp_sock_newconn, sdp_sock_connected, sdp_sock_disconnected, sdp_sock_connfail, sdp_sock_recv, sdp_sock_xmitted, sdp_sock_urgdata, sdp_sock_ordrel, }; /* ARGSUSED */ static int sosdp_init(struct sonode *so, struct sonode *pso, struct cred *cr, int flags) { int error = 0; sdp_sockbuf_limits_t sbl; sdp_upcalls_t *upcalls; if (pso != NULL) { /* passive open, just inherit settings from parent */ mutex_enter(&so->so_lock); so->so_state |= (SS_ISBOUND | SS_ISCONNECTED | (pso->so_state & SS_ASYNC)); sosdp_so_inherit(pso, so); so->so_proto_props = pso->so_proto_props; mutex_exit(&so->so_lock); return (0); } if ((error = secpolicy_basic_net_access(cr)) != 0) return (error); upcalls = &sosdp_sock_upcalls; so->so_proto_handle = (sock_lower_handle_t)sdp_create(so, NULL, so->so_family, SDP_CAN_BLOCK, upcalls, &sbl, cr, &error); if (so->so_proto_handle == NULL) return (ENOMEM); so->so_rcvbuf = sbl.sbl_rxbuf; so->so_rcvlowat = sbl.sbl_rxlowat; so->so_sndbuf = sbl.sbl_txbuf; so->so_sndlowat = sbl.sbl_txlowat; return (error); } /* * Accept incoming connection. */ /* ARGSUSED */ static int sosdp_accept(struct sonode *lso, int fflag, struct cred *cr, struct sonode **nsop) { int error = 0; struct sonode *nso; dprint(3, ("sosdp_accept: so:%p so_proto_handle:%p", (void *)lso, (void *)lso->so_proto_handle)); if (!(lso->so_state & SS_ACCEPTCONN)) { /* * Not a listen socket. */ eprintsoline(lso, EINVAL); return (EINVAL); } /* * Returns right away if socket is nonblocking. */ error = so_acceptq_dequeue(lso, (fflag & (FNONBLOCK|FNDELAY)), &nso); if (error != 0) { eprintsoline(lso, error); dprint(4, ("sosdp_accept: failed %d:lso:%p so_proto_handle:%p", error, (void *)lso, (void *)lso->so_proto_handle)); return (error); } dprint(2, ("sosdp_accept: new %p\n", (void *)nso)); *nsop = nso; return (0); } /* * Bind local endpoint. */ /* ARGSUSED */ int sosdp_bind(struct sonode *so, struct sockaddr *name, socklen_t namelen, int flags, struct cred *cr) { int error = 0; if (!(flags & _SOBIND_LOCK_HELD)) { mutex_enter(&so->so_lock); so_lock_single(so); /* Set SOLOCKED */ } else { ASSERT(MUTEX_HELD(&so->so_lock)); ASSERT(so->so_flag & SOLOCKED); } if ((so->so_state & SS_ISBOUND) || name == NULL || namelen == 0) { /* * Multiple binds not allowed for any SDP socket. * Also binding with null address is not supported. */ error = EINVAL; eprintsoline(so, error); goto done; } /* * X/Open requires this check */ if (so->so_state & SS_CANTSENDMORE) { error = EINVAL; goto done; } /* * Protocol module does address family checks */ mutex_exit(&so->so_lock); error = sdp_bind((struct sdp_conn_struct_t *)so->so_proto_handle, name, namelen); mutex_enter(&so->so_lock); if (error == 0) { so->so_state |= SS_ISBOUND; } else { eprintsoline(so, error); } done: if (!(flags & _SOBIND_LOCK_HELD)) { so_unlock_single(so, SOLOCKED); mutex_exit(&so->so_lock); } else { /* If the caller held the lock don't release it here */ ASSERT(MUTEX_HELD(&so->so_lock)); ASSERT(so->so_flag & SOLOCKED); } return (error); } /* * Turn socket into a listen socket. */ /* ARGSUSED */ static int sosdp_listen(struct sonode *so, int backlog, struct cred *cr) { int error = 0; mutex_enter(&so->so_lock); so_lock_single(so); /* * If this socket is trying to do connect, or if it has * been connected, disallow. */ if (so->so_state & (SS_ISCONNECTING | SS_ISCONNECTED | SS_ISDISCONNECTING | SS_CANTRCVMORE | SS_CANTSENDMORE)) { error = EINVAL; eprintsoline(so, EINVAL); goto done; } /* * If listen() is only called to change backlog, we don't * need to notify protocol module. */ if (so->so_state & SS_ACCEPTCONN) { so->so_backlog = backlog; goto done; } mutex_exit(&so->so_lock); error = sdp_listen((struct sdp_conn_struct_t *)so->so_proto_handle, backlog); mutex_enter(&so->so_lock); if (error == 0) { so->so_state |= (SS_ACCEPTCONN | SS_ISBOUND); so->so_backlog = backlog; } else { eprintsoline(so, error); } done: so_unlock_single(so, SOLOCKED); mutex_exit(&so->so_lock); return (error); } /* * Active open. */ /*ARGSUSED*/ static int sosdp_connect(struct sonode *so, struct sockaddr *name, socklen_t namelen, int fflag, int flags, struct cred *cr) { int error = 0; mutex_enter(&so->so_lock); so_lock_single(so); /* * Can't connect() after listen(), or if the socket is already * connected. */ if (so->so_state & (SS_ACCEPTCONN|SS_ISCONNECTED|SS_ISCONNECTING)) { if (so->so_state & SS_ISCONNECTED) { error = EISCONN; } else if (so->so_state & SS_ISCONNECTING) { error = EALREADY; } else { error = EOPNOTSUPP; } eprintsoline(so, error); goto done; } /* * check for failure of an earlier call */ if (so->so_error != 0) { error = sogeterr(so, B_TRUE); eprintsoline(so, error); goto done; } /* * Connection is closing, or closed, don't allow reconnect. * TCP allows this to proceed, but the socket remains unwriteable. * BSD returns EINVAL. */ if (so->so_state & (SS_ISDISCONNECTING|SS_CANTRCVMORE| SS_CANTSENDMORE)) { error = EINVAL; eprintsoline(so, error); goto done; } if (name == NULL || namelen == 0) { eprintsoline(so, EINVAL); goto done; } soisconnecting(so); mutex_exit(&so->so_lock); error = sdp_connect((struct sdp_conn_struct_t *)so->so_proto_handle, name, namelen); mutex_enter(&so->so_lock); if (error == 0) { /* * Allow other threads to access the socket */ error = sowaitconnected(so, fflag, 0); dprint(4, ("sosdp_connect: wait on so:%p " "so_proto_handle:%p failed:%d", (void *)so, (void *)so->so_proto_handle, error)); } switch (error) { case 0: case EINPROGRESS: case EALREADY: case EINTR: /* Non-fatal errors */ so->so_state |= SS_ISBOUND; break; default: /* clear SS_ISCONNECTING in case it was set */ so->so_state &= ~SS_ISCONNECTING; break; } done: so_unlock_single(so, SOLOCKED); mutex_exit(&so->so_lock); return (error); } /* * Receive data. */ /* ARGSUSED */ int sosdp_recvmsg(struct sonode *so, struct nmsghdr *msg, struct uio *uiop, struct cred *cr) { int flags, error = 0; int size; flags = msg->msg_flags; msg->msg_flags = 0; if (!(so->so_state & (SS_ISCONNECTED|SS_ISCONNECTING| SS_CANTRCVMORE))) { return (ENOTCONN); } /* * flag possibilities: * * MSG_PEEK Don't consume data * MSG_WAITALL Wait for full quantity of data (ignored if MSG_PEEK) * MSG_DONTWAIT Non-blocking (same as FNDELAY | FNONBLOCK) * * MSG_WAITALL can return less than the full buffer if either * * 1. we would block and we are non-blocking * 2. a full message cannot be delivered * */ mutex_enter(&so->so_lock); /* * Allow just one reader at a time. */ error = so_lock_read_intr(so, uiop->uio_fmode | ((flags & MSG_DONTWAIT) ? FNONBLOCK : 0)); if (error != 0) { mutex_exit(&so->so_lock); return (error); } size = uiop->uio_resid; mutex_exit(&so->so_lock); if (!(so->so_state & SS_CANTRCVMORE)) { if (uiop->uio_fmode & (FNDELAY|FNONBLOCK)) { flags |= MSG_DONTWAIT; } error = sdp_recv( (struct sdp_conn_struct_t *)so->so_proto_handle, msg, size, flags, uiop); } else { msg->msg_controllen = 0; msg->msg_namelen = 0; } done: mutex_enter(&so->so_lock); so_unlock_read(so); mutex_exit(&so->so_lock); return (error); } /* * Send message. */ /* ARGSUSED */ static int sosdp_sendmsg(struct sonode *so, struct nmsghdr *msg, struct uio *uiop, struct cred *cr) { int flags; ssize_t count; int error; ASSERT(so->so_type == SOCK_STREAM); dprint(4, ("sosdp_sendmsg: so:%p so_proto_handle:%p", (void *)so, (void *)so->so_proto_handle)); flags = msg->msg_flags; if (msg->msg_controllen != 0) { return (EOPNOTSUPP); } mutex_enter(&so->so_lock); if (so->so_state & SS_CANTSENDMORE) { mutex_exit(&so->so_lock); return (EPIPE); } if (so->so_error != 0) { error = sogeterr(so, B_TRUE); mutex_exit(&so->so_lock); return (error); } if (uiop->uio_fmode & (FNDELAY|FNONBLOCK)) flags |= MSG_DONTWAIT; count = uiop->uio_resid; if (!(so->so_state & (SS_ISCONNECTING | SS_ISCONNECTED))) { dprint(4, ("sosdp_sendmsg: invalid state: <%x>", so->so_state)); mutex_exit(&so->so_lock); return (ENOTCONN); } mutex_exit(&so->so_lock); error = sdp_send((struct sdp_conn_struct_t *)so->so_proto_handle, msg, count, flags, uiop); return (error); } /* * Get address of remote node. */ /* ARGSUSED */ static int sosdp_getpeername(struct sonode *so, struct sockaddr *addr, socklen_t *addrlen, boolean_t accept, struct cred *cr) { if (!accept && !(so->so_state & SS_ISCONNECTED)) { return (ENOTCONN); } else { return (sdp_getpeername( (struct sdp_conn_struct_t *)so->so_proto_handle, addr, addrlen)); } } /* * Get local address. */ /* ARGSUSED */ static int sosdp_getsockname(struct sonode *so, struct sockaddr *addr, socklen_t *addrlen, struct cred *cr) { mutex_enter(&so->so_lock); if (!(so->so_state & SS_ISBOUND)) { /* * Zero address, except for address family */ if (so->so_family == AF_INET || so->so_family == AF_INET6) { bzero(addr, *addrlen); *addrlen = (so->so_family == AF_INET6) ? sizeof (struct sockaddr_in6) : sizeof (struct sockaddr_in); addr->sa_family = so->so_family; } mutex_exit(&so->so_lock); return (0); } else { mutex_exit(&so->so_lock); return (sdp_getsockname( (struct sdp_conn_struct_t *)so->so_proto_handle, addr, addrlen)); } } /* * Called from shutdown(). */ /* ARGSUSED */ static int sosdp_shutdown(struct sonode *so, int how, struct cred *cr) { uint_t state_change; int error = 0; mutex_enter(&so->so_lock); so_lock_single(so); /* * Record the current state and then perform any state changes. * Then use the difference between the old and new states to * determine which needs to be done. */ state_change = so->so_state; if (!(state_change & SS_ISCONNECTED)) { error = ENOTCONN; goto done; } switch (how) { case SHUT_RD: socantrcvmore(so); break; case SHUT_WR: socantsendmore(so); break; case SHUT_RDWR: socantsendmore(so); socantrcvmore(so); break; default: error = EINVAL; goto done; } state_change = so->so_state & ~state_change; if (state_change & SS_CANTSENDMORE) { so->so_state |= SS_ISDISCONNECTING; } so_notify_shutdown(so); if (state_change & SS_CANTSENDMORE) { error = sdp_shutdown( (struct sdp_conn_struct_t *)so->so_proto_handle, how); } mutex_enter(&so->so_lock); done: so_unlock_single(so, SOLOCKED); mutex_exit(&so->so_lock); /* * HACK: sdp_disconnect() may return EWOULDBLOCK. But this error is * not documented in standard socket API. Catch it here. */ if (error == EWOULDBLOCK) error = 0; return (error); } /* * Get socket options. */ /*ARGSUSED*/ static int sosdp_getsockopt(struct sonode *so, int level, int option_name, void *optval, socklen_t *optlenp, int flags, struct cred *cr) { int error = 0; void *option = NULL; socklen_t maxlen = *optlenp, len, optlen; uint32_t value; uint8_t buffer[4]; void *optbuf = &buffer; mutex_enter(&so->so_lock); if (level == SOL_SOCKET) { switch (option_name) { case SO_TYPE: case SO_ERROR: case SO_DEBUG: case SO_ACCEPTCONN: case SO_REUSEADDR: case SO_KEEPALIVE: case SO_DONTROUTE: case SO_BROADCAST: case SO_USELOOPBACK: case SO_OOBINLINE: case SO_SNDBUF: case SO_RCVBUF: case SO_SNDLOWAT: case SO_RCVLOWAT: case SO_DGRAM_ERRIND: if (maxlen < (t_uscalar_t)sizeof (int32_t)) { error = EINVAL; eprintsoline(so, error); goto done; } break; case SO_LINGER: if (maxlen < (t_uscalar_t)sizeof (struct linger)) { error = EINVAL; eprintsoline(so, error); goto done; } break; } len = (t_uscalar_t)sizeof (uint32_t); /* Default */ option = &value; switch (option_name) { case SO_TYPE: value = so->so_type; goto copyout; case SO_ERROR: value = sogeterr(so, B_TRUE); goto copyout; case SO_ACCEPTCONN: value = (so->so_state & SS_ACCEPTCONN) ? SO_ACCEPTCONN : 0; goto copyout; case SO_DEBUG: case SO_REUSEADDR: case SO_KEEPALIVE: case SO_DONTROUTE: case SO_BROADCAST: case SO_USELOOPBACK: case SO_OOBINLINE: case SO_DGRAM_ERRIND: value = (so->so_options & option_name); goto copyout; /* * The following options are only returned by sockfs * when sdp_get_opt() fails. */ case SO_LINGER: option = &so->so_linger; len = (t_uscalar_t)sizeof (struct linger); break; case SO_SNDBUF: value = so->so_sndbuf; len = (t_uscalar_t)sizeof (int); goto copyout; case SO_RCVBUF: value = so->so_rcvbuf; len = (t_uscalar_t)sizeof (int); goto copyout; case SO_SNDLOWAT: value = so->so_sndlowat; len = (t_uscalar_t)sizeof (int); goto copyout; case SO_RCVLOWAT: value = so->so_rcvlowat; len = (t_uscalar_t)sizeof (int); goto copyout; default: option = NULL; break; } } if (maxlen > sizeof (buffer)) { optbuf = kmem_alloc(maxlen, KM_SLEEP); } optlen = maxlen; mutex_exit(&so->so_lock); error = sdp_get_opt((struct sdp_conn_struct_t *)so->so_proto_handle, level, option_name, optbuf, &optlen); mutex_enter(&so->so_lock); ASSERT(optlen <= maxlen); if (error != 0) { if (option == NULL) { /* We have no fallback value */ eprintsoline(so, error); goto free; } error = 0; goto copyout; } option = optbuf; len = optlen; copyout: len = MIN(len, maxlen); bcopy(option, optval, len); *optlenp = len; free: if (optbuf != &buffer) { kmem_free(optbuf, maxlen); } done: mutex_exit(&so->so_lock); return (error); } /* * Set socket options */ /* ARGSUSED */ static int sosdp_setsockopt(struct sonode *so, int level, int option_name, const void *optval, t_uscalar_t optlen, struct cred *cr) { void *conn = NULL; int error = 0; if (so->so_state & SS_CANTSENDMORE) { return (EINVAL); } mutex_enter(&so->so_lock); so_lock_single(so); if (so->so_type == SOCK_STREAM) { conn = (void *)so->so_proto_handle; } dprint(2, ("sosdp_setsockopt (%d) - conn %p %d %d \n", so->so_type, conn, level, option_name)); if (conn != NULL) { mutex_exit(&so->so_lock); error = sdp_set_opt((struct sdp_conn_struct_t *)conn, level, option_name, optval, optlen); mutex_enter(&so->so_lock); } /* * Check for SOL_SOCKET options and record their values. * If we know about a SOL_SOCKET parameter and the transport * failed it with TBADOPT or TOUTSTATE (i.e. ENOPROTOOPT or * EPROTO) we let the setsockopt succeed. */ if (level == SOL_SOCKET) { boolean_t handled = B_FALSE; /* Check parameters */ switch (option_name) { case SO_DEBUG: case SO_REUSEADDR: case SO_KEEPALIVE: case SO_DONTROUTE: case SO_BROADCAST: case SO_USELOOPBACK: case SO_OOBINLINE: case SO_SNDBUF: case SO_RCVBUF: case SO_SNDLOWAT: case SO_RCVLOWAT: case SO_DGRAM_ERRIND: if (optlen != (t_uscalar_t)sizeof (int32_t)) { error = EINVAL; eprintsoline(so, error); goto done; } ASSERT(optval); handled = B_TRUE; break; case SO_LINGER: if (optlen != (t_uscalar_t)sizeof (struct linger)) { error = EINVAL; eprintsoline(so, error); goto done; } ASSERT(optval); handled = B_TRUE; break; } #define intvalue (*(int32_t *)optval) switch (option_name) { case SO_TYPE: case SO_ERROR: case SO_ACCEPTCONN: /* Can't be set */ error = ENOPROTOOPT; goto done; case SO_LINGER: { struct linger *l = (struct linger *)optval; so->so_linger.l_linger = l->l_linger; if (l->l_onoff) { so->so_linger.l_onoff = SO_LINGER; so->so_options |= SO_LINGER; } else { so->so_linger.l_onoff = 0; so->so_options &= ~SO_LINGER; } break; } case SO_DEBUG: case SO_REUSEADDR: case SO_KEEPALIVE: case SO_DONTROUTE: case SO_BROADCAST: case SO_USELOOPBACK: case SO_OOBINLINE: case SO_DGRAM_ERRIND: if (intvalue != 0) { dprintso(so, 1, ("sosdp_setsockopt: setting 0x%x\n", option_name)); so->so_options |= option_name; } else { dprintso(so, 1, ("sosdp_setsockopt: clearing 0x%x\n", option_name)); so->so_options &= ~option_name; } break; case SO_SNDBUF: so->so_sndbuf = intvalue; if (so->so_sndlowat > so->so_sndbuf) { so->so_sndlowat = so->so_sndbuf; } break; case SO_RCVBUF: so->so_rcvbuf = intvalue; if (so->so_rcvlowat > so->so_rcvbuf) { so->so_rcvlowat = so->so_rcvbuf; } break; case SO_SNDLOWAT: if (so->so_sndlowat > so->so_sndbuf) { so->so_sndlowat = so->so_sndbuf; } break; case SO_RCVLOWAT: if (so->so_rcvlowat > so->so_rcvbuf) { so->so_rcvlowat = so->so_rcvbuf; } break; } #undef intvalue if (error != 0) { if ((error == ENOPROTOOPT || error == EPROTO || error == EINVAL) && handled) { dprintso(so, 1, ("sosdp_setsockopt: ignoring error %d " "for 0x%x\n", error, option_name)); error = 0; } } } done: so_unlock_single(so, SOLOCKED); mutex_exit(&so->so_lock); return (error); } /* ARGSUSED */ static int sosdp_ioctl(struct sonode *so, int cmd, intptr_t arg, int mode, struct cred *cr, int32_t *rvalp) { int32_t value; int error, intval; pid_t pid; /* handle socket specific ioctls */ switch (cmd) { case FIONBIO: if (so_copyin((void *)arg, &value, sizeof (int32_t), (mode & (int)FKIOCTL))) { return (EFAULT); } mutex_enter(&so->so_lock); if (value != 0) { so->so_state |= SS_NDELAY; } else { so->so_state &= ~SS_NDELAY; } mutex_exit(&so->so_lock); return (0); case FIOASYNC: if (so_copyin((void *)arg, &value, sizeof (int32_t), (mode & (int)FKIOCTL))) { return (EFAULT); } mutex_enter(&so->so_lock); if (value) { /* Turn on SIGIO */ so->so_state |= SS_ASYNC; } else { /* Turn off SIGIO */ so->so_state &= ~SS_ASYNC; } mutex_exit(&so->so_lock); return (0); case SIOCSPGRP: case FIOSETOWN: if (so_copyin((void *)arg, &pid, sizeof (pid_t), (mode & (int)FKIOCTL))) { return (EFAULT); } mutex_enter(&so->so_lock); error = (pid != so->so_pgrp) ? socket_chgpgrp(so, pid) : 0; mutex_exit(&so->so_lock); return (error); case SIOCGPGRP: case FIOGETOWN: if (so_copyout(&so->so_pgrp, (void *)arg, sizeof (pid_t), (mode & (int)FKIOCTL))) return (EFAULT); return (0); case SIOCATMARK: intval = 0; error = sdp_ioctl( (struct sdp_conn_struct_t *)so->so_proto_handle, cmd, &intval, cr); if (so_copyout(&intval, (void *)arg, sizeof (int), (mode & (int)FKIOCTL))) return (EFAULT); return (0); case SIOCSENABLESDP: { int32_t enable; /* * System wide enable SDP */ if (so_copyin((void *)arg, &enable, sizeof (int32_t), mode & (int)FKIOCTL)) return (EFAULT); error = sdp_ioctl( (struct sdp_conn_struct_t *)so->so_proto_handle, cmd, &enable, cr); if (so_copyout(&enable, (void *)arg, sizeof (int32_t), (mode & (int)FKIOCTL))) return (EFAULT); return (0); } /* from strioctl */ case FIONREAD: /* * Return number of bytes of data in all data messages * in queue in "arg". * For stream socket, amount of available data. */ if (so->so_state & SS_ACCEPTCONN) { intval = 0; } else { mutex_enter(&so->so_lock); intval = sdp_polldata( (struct sdp_conn_struct_t *)so->so_proto_handle, SDP_READ); mutex_exit(&so->so_lock); } if (so_copyout(&intval, (void *)arg, sizeof (intval), (mode & (int)FKIOCTL))) return (EFAULT); return (0); default: return (EINVAL); } } /* * Check socktpi_poll() on why so_lock is not held in this function. */ static int sosdp_poll(struct sonode *so, short events, int anyyet, short *reventsp, struct pollhead **phpp) { short origevents = events; int so_state; so_state = so->so_state; ASSERT(so->so_version != SOV_STREAM); if (!(so_state & SS_ISCONNECTED) && (so->so_type == SOCK_STREAM)) { /* * Not connected yet - turn off write side events */ events &= ~(POLLOUT|POLLWRBAND); } /* * Check for errors */ if (so->so_error != 0 && ((POLLIN|POLLRDNORM|POLLOUT) & origevents) != 0) { *reventsp = (POLLIN|POLLRDNORM|POLLOUT) & origevents; goto done; } *reventsp = 0; if (so->so_type != SOCK_STREAM) { goto done; } /* * Don't mark socket writable until TX queued data is below watermark. */ if (sdp_polldata((struct sdp_conn_struct_t *)so->so_proto_handle, SDP_XMIT)) { *reventsp |= POLLOUT & events; } if (sdp_polldata((struct sdp_conn_struct_t *)so->so_proto_handle, SDP_READ)) { *reventsp |= (POLLIN|POLLRDNORM) & events; } if ((so_state & SS_CANTRCVMORE) || (so->so_acceptq_len > 0)) { *reventsp |= (POLLIN|POLLRDNORM) & events; } done: if ((*reventsp == 0 && !anyyet) || (events & POLLET)) { *phpp = &so->so_poll_list; } return (0); } /* ARGSUSED */ static int sosdp_close(struct sonode *so, int flag, struct cred *cr) { int error = 0; mutex_enter(&so->so_lock); so_lock_single(so); /* * Need to set flags as there might be ops in progress on * this socket. * * If socket already disconnected/disconnecting, * don't send signal (again). */ soisdisconnected(so, 0); mutex_exit(&so->so_lock); /* * Initiate connection shutdown. */ error = sdp_disconnect((struct sdp_conn_struct_t *)so->so_proto_handle, flag); mutex_enter(&so->so_lock); so_unlock_single(so, SOLOCKED); so_notify_disconnected(so, B_FALSE, error); return (error); } /* ARGSUSED */ void sosdp_fini(struct sonode *so, struct cred *cr) { dprint(3, ("sosdp_fini: so:%p so_proto_handle:%p", (void *)so, (void *)so->so_proto_handle)); ASSERT(so->so_ops == &sosdp_sonodeops); if (so->so_proto_handle != NULL) sdp_close((struct sdp_conn_struct_t *)so->so_proto_handle); so->so_proto_handle = NULL; mutex_enter(&so->so_lock); so_acceptq_flush(so, B_TRUE); mutex_exit(&so->so_lock); sonode_fini(so); } /* * Upcalls from SDP */ /* * Incoming connection on listen socket. */ static void * sdp_sock_newconn(void *parenthandle, void *connind) { struct sonode *lso = parenthandle; struct sonode *nso; int error; ASSERT(lso->so_state & SS_ACCEPTCONN); ASSERT(lso->so_proto_handle != NULL); /* closed conn */ ASSERT(lso->so_type == SOCK_STREAM); dprint(3, ("sosdp_newconn A: so:%p so_proto_handle:%p", (void *)lso, (void *)lso->so_proto_handle)); /* * Check current # of queued conns against backlog */ if (lso->so_rcv_queued >= lso->so_backlog) { return (NULL); } nso = socket_newconn(lso, connind, NULL, SOCKET_NOSLEEP, &error); if (nso == NULL) { eprintsoline(lso, error); return (NULL); } dprint(2, ("sdp_stream_newconn: new %p\n", (void *)nso)); (void) so_acceptq_enqueue(lso, nso); mutex_enter(&lso->so_lock); so_notify_newconn(lso); return (nso); } /* * For outgoing connections, the connection has been established. */ static void sdp_sock_connected(void *handle) { struct sonode *so = handle; ASSERT(so->so_type == SOCK_STREAM); dprint(3, ("sosdp_connected C: so:%p so_proto_handle:%p", (void *)so, (void *)so->so_proto_handle)); mutex_enter(&so->so_lock); ASSERT(so->so_proto_handle); /* closed conn */ ASSERT(!(so->so_state & SS_ACCEPTCONN)); soisconnected(so); so_notify_connected(so); } /* * Connection got disconnected. Either with an error, or through * normal handshake. */ static void sdp_sock_disconnected(void *handle, int error) { struct sonode *so = handle; ASSERT(so->so_type == SOCK_STREAM); dprint(2, ("sosdp_disconnected C: so:%p so_proto_handle:%p error:%d", (void *)so, (void *)so->so_proto_handle, error)); mutex_enter(&so->so_lock); ASSERT(so->so_proto_handle != NULL); /* closed conn */ soisdisconnected(so, error); so_notify_disconnected(so, B_FALSE, error); } /* * Incoming data. */ /*ARGSUSED*/ static int sdp_sock_recv(void *handle, mblk_t *mp, int flags) { struct sonode *so = handle; ASSERT(so->so_type == SOCK_STREAM); mutex_enter(&so->so_lock); so_notify_data(so, 0); return (so->so_rcvbuf); } /* * TX queued data got acknowledged. */ static void sdp_sock_xmitted(void *handle, int writeable) { struct sonode *so = handle; dprint(4, ("sosdp_sock_xmitted: so:%p so_proto_handle:%p txq:%d", (void *)so, (void *)so->so_proto_handle, writeable)); mutex_enter(&so->so_lock); ASSERT(so->so_proto_handle != NULL); /* closed conn */ /* * Only do pollwakeup if the amount of queued data is less than * watermark. */ if (!writeable) { so_notify_writable(so); } else { mutex_exit(&so->so_lock); } } /* * SDP notifies socket for presence of urgent data. */ static void sdp_sock_urgdata(void *handle) { struct sonode *so = handle; ASSERT(so->so_type == SOCK_STREAM); mutex_enter(&so->so_lock); ASSERT(so->so_proto_handle != NULL); /* closed conn */ so_notify_oobsig(so); } /* * SDP notifies socket about receiving of conn close request from peer side. */ static void sdp_sock_ordrel(void *handle) { struct sonode *so = handle; ASSERT(so->so_type == SOCK_STREAM); dprint(4, ("sdp_sock_ordrel : so:%p, so_proto_handle:%p", (void *)so, (void *)so->so_proto_handle)); mutex_enter(&so->so_lock); socantrcvmore(so); so_notify_eof(so); } static void sdp_sock_connfail(void *handle, int error) { struct sonode *so = handle; dprint(3, ("sosdp_conn Failed: so:%p so_proto_handle:%p", (void *)so, (void *)so->so_proto_handle)); mutex_enter(&so->so_lock); ASSERT(so->so_proto_handle != NULL); /* closed conn */ so->so_state &= ~(SS_ISCONNECTING|SS_ISCONNECTED|SS_ISDISCONNECTING); so->so_error = (ushort_t)error; mutex_exit(&so->so_lock); cv_broadcast(&so->so_state_cv); }