| /* |
| * 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 2009 Sun Microsystems, Inc. All rights reserved. |
| * Use is subject to license terms. |
| */ |
| |
| #include <sys/types.h> |
| #include <sys/t_lock.h> |
| #include <sys/param.h> |
| #include <sys/systm.h> |
| #include <sys/buf.h> |
| #include <sys/conf.h> |
| #include <sys/cred.h> |
| #include <sys/kmem.h> |
| #include <sys/kmem_impl.h> |
| #include <sys/sysmacros.h> |
| #include <sys/vfs.h> |
| #include <sys/vnode.h> |
| #include <sys/debug.h> |
| #include <sys/errno.h> |
| #include <sys/time.h> |
| #include <sys/file.h> |
| #include <sys/open.h> |
| #include <sys/user.h> |
| #include <sys/termios.h> |
| #include <sys/stream.h> |
| #include <sys/strsubr.h> |
| #include <sys/strsun.h> |
| #include <sys/suntpi.h> |
| #include <sys/ddi.h> |
| #include <sys/esunddi.h> |
| #include <sys/flock.h> |
| #include <sys/modctl.h> |
| #include <sys/vtrace.h> |
| #include <sys/cmn_err.h> |
| #include <sys/pathname.h> |
| |
| #include <sys/socket.h> |
| #include <sys/socketvar.h> |
| #include <sys/sockio.h> |
| #include <netinet/in.h> |
| #include <sys/un.h> |
| #include <sys/strsun.h> |
| |
| #include <sys/tiuser.h> |
| #define _SUN_TPI_VERSION 2 |
| #include <sys/tihdr.h> |
| #include <sys/timod.h> /* TI_GETMYNAME, TI_GETPEERNAME */ |
| |
| #include <c2/audit.h> |
| |
| #include <inet/common.h> |
| #include <inet/ip.h> |
| #include <inet/ip6.h> |
| #include <inet/tcp.h> |
| #include <inet/udp_impl.h> |
| |
| #include <sys/zone.h> |
| |
| #include <fs/sockfs/nl7c.h> |
| #include <fs/sockfs/nl7curi.h> |
| |
| #include <inet/kssl/ksslapi.h> |
| |
| #include <fs/sockfs/sockcommon.h> |
| #include <fs/sockfs/socktpi.h> |
| #include <fs/sockfs/socktpi_impl.h> |
| |
| /* |
| * Possible failures when memory can't be allocated. The documented behavior: |
| * |
| * 5.5: 4.X: XNET: |
| * accept: ENOMEM/ENOSR/EINTR - (EINTR) ENOMEM/ENOBUFS/ENOSR/ |
| * EINTR |
| * (4.X does not document EINTR but returns it) |
| * bind: ENOSR - ENOBUFS/ENOSR |
| * connect: EINTR EINTR ENOBUFS/ENOSR/EINTR |
| * getpeername: ENOMEM/ENOSR ENOBUFS (-) ENOBUFS/ENOSR |
| * getsockname: ENOMEM/ENOSR ENOBUFS (-) ENOBUFS/ENOSR |
| * (4.X getpeername and getsockname do not fail in practice) |
| * getsockopt: ENOMEM/ENOSR - ENOBUFS/ENOSR |
| * listen: - - ENOBUFS |
| * recv: ENOMEM/ENOSR/EINTR EINTR ENOBUFS/ENOMEM/ENOSR/ |
| * EINTR |
| * send: ENOMEM/ENOSR/EINTR ENOBUFS/EINTR ENOBUFS/ENOMEM/ENOSR/ |
| * EINTR |
| * setsockopt: ENOMEM/ENOSR - ENOBUFS/ENOMEM/ENOSR |
| * shutdown: ENOMEM/ENOSR - ENOBUFS/ENOSR |
| * socket: ENOMEM/ENOSR ENOBUFS ENOBUFS/ENOMEM/ENOSR |
| * socketpair: ENOMEM/ENOSR - ENOBUFS/ENOMEM/ENOSR |
| * |
| * Resolution. When allocation fails: |
| * recv: return EINTR |
| * send: return EINTR |
| * connect, accept: EINTR |
| * bind, listen, shutdown (unbind, unix_close, disconnect): sleep |
| * socket, socketpair: ENOBUFS |
| * getpeername, getsockname: sleep |
| * getsockopt, setsockopt: sleep |
| */ |
| |
| #ifdef SOCK_TEST |
| /* |
| * Variables that make sockfs do something other than the standard TPI |
| * for the AF_INET transports. |
| * |
| * solisten_tpi_tcp: |
| * TCP can handle a O_T_BIND_REQ with an increased backlog even though |
| * the transport is already bound. This is needed to avoid loosing the |
| * port number should listen() do a T_UNBIND_REQ followed by a |
| * O_T_BIND_REQ. |
| * |
| * soconnect_tpi_udp: |
| * UDP and ICMP can handle a T_CONN_REQ. |
| * This is needed to make the sequence of connect(), getsockname() |
| * return the local IP address used to send packets to the connected to |
| * destination. |
| * |
| * soconnect_tpi_tcp: |
| * TCP can handle a T_CONN_REQ without seeing a O_T_BIND_REQ. |
| * Set this to non-zero to send TPI conformant messages to TCP in this |
| * respect. This is a performance optimization. |
| * |
| * soaccept_tpi_tcp: |
| * TCP can handle a T_CONN_REQ without the acceptor being bound. |
| * This is a performance optimization that has been picked up in XTI. |
| * |
| * soaccept_tpi_multioptions: |
| * When inheriting SOL_SOCKET options from the listener to the accepting |
| * socket send them as a single message for AF_INET{,6}. |
| */ |
| int solisten_tpi_tcp = 0; |
| int soconnect_tpi_udp = 0; |
| int soconnect_tpi_tcp = 0; |
| int soaccept_tpi_tcp = 0; |
| int soaccept_tpi_multioptions = 1; |
| #else /* SOCK_TEST */ |
| #define soconnect_tpi_tcp 0 |
| #define soconnect_tpi_udp 0 |
| #define solisten_tpi_tcp 0 |
| #define soaccept_tpi_tcp 0 |
| #define soaccept_tpi_multioptions 1 |
| #endif /* SOCK_TEST */ |
| |
| #ifdef SOCK_TEST |
| extern int do_useracc; |
| extern clock_t sock_test_timelimit; |
| #endif /* SOCK_TEST */ |
| |
| /* |
| * Some X/Open added checks might have to be backed out to keep SunOS 4.X |
| * applications working. Turn on this flag to disable these checks. |
| */ |
| int xnet_skip_checks = 0; |
| int xnet_check_print = 0; |
| int xnet_truncate_print = 0; |
| |
| static void sotpi_destroy(struct sonode *); |
| static struct sonode *sotpi_create(struct sockparams *, int, int, int, int, |
| int, int *, cred_t *cr); |
| |
| static boolean_t sotpi_info_create(struct sonode *, int); |
| static void sotpi_info_init(struct sonode *); |
| static void sotpi_info_fini(struct sonode *); |
| static void sotpi_info_destroy(struct sonode *); |
| |
| /* |
| * Do direct function call to the transport layer below; this would |
| * also allow the transport to utilize read-side synchronous stream |
| * interface if necessary. This is a /etc/system tunable that must |
| * not be modified on a running system. By default this is enabled |
| * for performance reasons and may be disabled for debugging purposes. |
| */ |
| boolean_t socktpi_direct = B_TRUE; |
| |
| static struct kmem_cache *socktpi_cache, *socktpi_unix_cache; |
| |
| extern void sigintr(k_sigset_t *, int); |
| extern void sigunintr(k_sigset_t *); |
| |
| /* Sockets acting as an in-kernel SSL proxy */ |
| extern mblk_t *strsock_kssl_input(vnode_t *, mblk_t *, strwakeup_t *, |
| strsigset_t *, strsigset_t *, strpollset_t *); |
| extern mblk_t *strsock_kssl_output(vnode_t *, mblk_t *, strwakeup_t *, |
| strsigset_t *, strsigset_t *, strpollset_t *); |
| |
| static int sotpi_unbind(struct sonode *, int); |
| |
| /* TPI sockfs sonode operations */ |
| int sotpi_init(struct sonode *, struct sonode *, struct cred *, |
| int); |
| static int sotpi_accept(struct sonode *, int, struct cred *, |
| struct sonode **); |
| static int sotpi_bind(struct sonode *, struct sockaddr *, socklen_t, |
| int, struct cred *); |
| static int sotpi_listen(struct sonode *, int, struct cred *); |
| static int sotpi_connect(struct sonode *, const struct sockaddr *, |
| socklen_t, int, int, struct cred *); |
| extern int sotpi_recvmsg(struct sonode *, struct nmsghdr *, |
| struct uio *, struct cred *); |
| static int sotpi_sendmsg(struct sonode *, struct nmsghdr *, |
| struct uio *, struct cred *); |
| static int sotpi_sendmblk(struct sonode *, struct nmsghdr *, int, |
| struct cred *, mblk_t **); |
| static int sosend_dgramcmsg(struct sonode *, struct sockaddr *, socklen_t, |
| struct uio *, void *, t_uscalar_t, int); |
| static int sodgram_direct(struct sonode *, struct sockaddr *, |
| socklen_t, struct uio *, int); |
| extern int sotpi_getpeername(struct sonode *, struct sockaddr *, |
| socklen_t *, boolean_t, struct cred *); |
| static int sotpi_getsockname(struct sonode *, struct sockaddr *, |
| socklen_t *, struct cred *); |
| static int sotpi_shutdown(struct sonode *, int, struct cred *); |
| extern int sotpi_getsockopt(struct sonode *, int, int, void *, |
| socklen_t *, int, struct cred *); |
| extern int sotpi_setsockopt(struct sonode *, int, int, const void *, |
| socklen_t, struct cred *); |
| static int sotpi_ioctl(struct sonode *, int, intptr_t, int, struct cred *, |
| int32_t *); |
| static int socktpi_plumbioctl(struct vnode *, int, intptr_t, int, |
| struct cred *, int32_t *); |
| static int sotpi_poll(struct sonode *, short, int, short *, |
| struct pollhead **); |
| static int sotpi_close(struct sonode *, int, struct cred *); |
| |
| static int i_sotpi_info_constructor(sotpi_info_t *); |
| static void i_sotpi_info_destructor(sotpi_info_t *); |
| |
| sonodeops_t sotpi_sonodeops = { |
| sotpi_init, /* sop_init */ |
| sotpi_accept, /* sop_accept */ |
| sotpi_bind, /* sop_bind */ |
| sotpi_listen, /* sop_listen */ |
| sotpi_connect, /* sop_connect */ |
| sotpi_recvmsg, /* sop_recvmsg */ |
| sotpi_sendmsg, /* sop_sendmsg */ |
| sotpi_sendmblk, /* sop_sendmblk */ |
| sotpi_getpeername, /* sop_getpeername */ |
| sotpi_getsockname, /* sop_getsockname */ |
| sotpi_shutdown, /* sop_shutdown */ |
| sotpi_getsockopt, /* sop_getsockopt */ |
| sotpi_setsockopt, /* sop_setsockopt */ |
| sotpi_ioctl, /* sop_ioctl */ |
| sotpi_poll, /* sop_poll */ |
| sotpi_close, /* sop_close */ |
| }; |
| |
| /* |
| * Return a TPI socket vnode. |
| * |
| * Note that sockets assume that the driver will clone (either itself |
| * or by using the clone driver) i.e. a socket() call will always |
| * result in a new vnode being created. |
| */ |
| |
| /* |
| * Common create code for socket and accept. If tso is set the values |
| * from that node is used instead of issuing a T_INFO_REQ. |
| */ |
| |
| /* ARGSUSED */ |
| static struct sonode * |
| sotpi_create(struct sockparams *sp, int family, int type, int protocol, |
| int version, int sflags, int *errorp, cred_t *cr) |
| { |
| struct sonode *so; |
| kmem_cache_t *cp; |
| int sfamily = family; |
| |
| ASSERT(sp->sp_sdev_info.sd_vnode != NULL); |
| |
| if (family == AF_NCA) { |
| /* |
| * The request is for an NCA socket so for NL7C use the |
| * INET domain instead and mark NL7C_AF_NCA below. |
| */ |
| family = AF_INET; |
| /* |
| * NL7C is not supported in the non-global zone, |
| * we enforce this restriction here. |
| */ |
| if (getzoneid() != GLOBAL_ZONEID) { |
| *errorp = ENOTSUP; |
| return (NULL); |
| } |
| } |
| |
| /* |
| * to be compatible with old tpi socket implementation ignore |
| * sleep flag (sflags) passed in |
| */ |
| cp = (family == AF_UNIX) ? socktpi_unix_cache : socktpi_cache; |
| so = kmem_cache_alloc(cp, KM_SLEEP); |
| if (so == NULL) { |
| *errorp = ENOMEM; |
| return (NULL); |
| } |
| |
| sonode_init(so, sp, family, type, protocol, &sotpi_sonodeops); |
| sotpi_info_init(so); |
| |
| if (sfamily == AF_NCA) { |
| SOTOTPI(so)->sti_nl7c_flags = NL7C_AF_NCA; |
| } |
| |
| if (version == SOV_DEFAULT) |
| version = so_default_version; |
| |
| so->so_version = (short)version; |
| *errorp = 0; |
| |
| return (so); |
| } |
| |
| static void |
| sotpi_destroy(struct sonode *so) |
| { |
| kmem_cache_t *cp; |
| struct sockparams *origsp; |
| |
| /* |
| * If there is a new dealloc function (ie. smod_destroy_func), |
| * then it should check the correctness of the ops. |
| */ |
| |
| ASSERT(so->so_ops == &sotpi_sonodeops); |
| |
| origsp = SOTOTPI(so)->sti_orig_sp; |
| |
| sotpi_info_fini(so); |
| |
| if (so->so_state & SS_FALLBACK_COMP) { |
| /* |
| * A fallback happend, which means that a sotpi_info_t struct |
| * was allocated (as opposed to being allocated from the TPI |
| * sonode cache. Therefore we explicitly free the struct |
| * here. |
| */ |
| sotpi_info_destroy(so); |
| ASSERT(origsp != NULL); |
| |
| origsp->sp_smod_info->smod_sock_destroy_func(so); |
| SOCKPARAMS_DEC_REF(origsp); |
| } else { |
| sonode_fini(so); |
| cp = (so->so_family == AF_UNIX) ? socktpi_unix_cache : |
| socktpi_cache; |
| kmem_cache_free(cp, so); |
| } |
| } |
| |
| /* ARGSUSED1 */ |
| int |
| sotpi_init(struct sonode *so, struct sonode *tso, struct cred *cr, int flags) |
| { |
| major_t maj; |
| dev_t newdev; |
| struct vnode *vp; |
| int error = 0; |
| struct stdata *stp; |
| |
| sotpi_info_t *sti = SOTOTPI(so); |
| |
| dprint(1, ("sotpi_init()\n")); |
| |
| /* |
| * over write the sleep flag passed in but that is ok |
| * as tpi socket does not honor sleep flag. |
| */ |
| flags |= FREAD|FWRITE; |
| |
| /* |
| * Record in so_flag that it is a clone. |
| */ |
| if (getmajor(sti->sti_dev) == clone_major) |
| so->so_flag |= SOCLONE; |
| |
| if ((so->so_type == SOCK_STREAM || so->so_type == SOCK_DGRAM) && |
| (so->so_family == AF_INET || so->so_family == AF_INET6) && |
| (so->so_protocol == IPPROTO_TCP || so->so_protocol == IPPROTO_UDP || |
| so->so_protocol == IPPROTO_IP)) { |
| /* Tell tcp or udp that it's talking to sockets */ |
| flags |= SO_SOCKSTR; |
| |
| /* |
| * Here we indicate to socktpi_open() our attempt to |
| * make direct calls between sockfs and transport. |
| * The final decision is left to socktpi_open(). |
| */ |
| sti->sti_direct = 1; |
| |
| ASSERT(so->so_type != SOCK_DGRAM || tso == NULL); |
| if (so->so_type == SOCK_STREAM && tso != NULL) { |
| if (SOTOTPI(tso)->sti_direct) { |
| /* |
| * Inherit sti_direct from listener and pass |
| * SO_ACCEPTOR open flag to tcp, indicating |
| * that this is an accept fast-path instance. |
| */ |
| flags |= SO_ACCEPTOR; |
| } else { |
| /* |
| * sti_direct is not set on listener, meaning |
| * that the listener has been converted from |
| * a socket to a stream. Ensure that the |
| * acceptor inherits these settings. |
| */ |
| sti->sti_direct = 0; |
| flags &= ~SO_SOCKSTR; |
| } |
| } |
| } |
| |
| /* |
| * Tell local transport that it is talking to sockets. |
| */ |
| if (so->so_family == AF_UNIX) { |
| flags |= SO_SOCKSTR; |
| } |
| |
| vp = SOTOV(so); |
| newdev = vp->v_rdev; |
| maj = getmajor(newdev); |
| ASSERT(STREAMSTAB(maj)); |
| |
| error = stropen(vp, &newdev, flags, cr); |
| |
| stp = vp->v_stream; |
| if (error == 0) { |
| if (so->so_flag & SOCLONE) |
| ASSERT(newdev != vp->v_rdev); |
| mutex_enter(&so->so_lock); |
| sti->sti_dev = newdev; |
| vp->v_rdev = newdev; |
| mutex_exit(&so->so_lock); |
| |
| if (stp->sd_flag & STRISTTY) { |
| /* |
| * this is a post SVR4 tty driver - a socket can not |
| * be a controlling terminal. Fail the open. |
| */ |
| (void) sotpi_close(so, flags, cr); |
| return (ENOTTY); /* XXX */ |
| } |
| |
| ASSERT(stp->sd_wrq != NULL); |
| sti->sti_provinfo = tpi_findprov(stp->sd_wrq); |
| |
| /* |
| * If caller is interested in doing direct function call |
| * interface to/from transport module, probe the module |
| * directly beneath the streamhead to see if it qualifies. |
| * |
| * We turn off the direct interface when qualifications fail. |
| * In the acceptor case, we simply turn off the sti_direct |
| * flag on the socket. We do the fallback after the accept |
| * has completed, before the new socket is returned to the |
| * application. |
| */ |
| if (sti->sti_direct) { |
| queue_t *tq = stp->sd_wrq->q_next; |
| |
| /* |
| * sti_direct is currently supported and tested |
| * only for tcp/udp; this is the main reason to |
| * have the following assertions. |
| */ |
| ASSERT(so->so_family == AF_INET || |
| so->so_family == AF_INET6); |
| ASSERT(so->so_protocol == IPPROTO_UDP || |
| so->so_protocol == IPPROTO_TCP || |
| so->so_protocol == IPPROTO_IP); |
| ASSERT(so->so_type == SOCK_DGRAM || |
| so->so_type == SOCK_STREAM); |
| |
| /* |
| * Abort direct call interface if the module directly |
| * underneath the stream head is not defined with the |
| * _D_DIRECT flag. This could happen in the tcp or |
| * udp case, when some other module is autopushed |
| * above it, or for some reasons the expected module |
| * isn't purely D_MP (which is the main requirement). |
| */ |
| if (!socktpi_direct || !(tq->q_flag & _QDIRECT) || |
| !(_OTHERQ(tq)->q_flag & _QDIRECT)) { |
| int rval; |
| |
| /* Continue on without direct calls */ |
| sti->sti_direct = 0; |
| |
| /* |
| * Cannot issue ioctl on fallback socket since |
| * there is no conn associated with the queue. |
| * The fallback downcall will notify the proto |
| * of the change. |
| */ |
| if (!(flags & SO_ACCEPTOR) && |
| !(flags & SO_FALLBACK)) { |
| if ((error = strioctl(vp, |
| _SIOCSOCKFALLBACK, 0, 0, K_TO_K, |
| cr, &rval)) != 0) { |
| (void) sotpi_close(so, flags, |
| cr); |
| return (error); |
| } |
| } |
| } |
| } |
| |
| if (flags & SO_FALLBACK) { |
| /* |
| * The stream created does not have a conn. |
| * do stream set up after conn has been assigned |
| */ |
| return (error); |
| } |
| if (error = so_strinit(so, tso)) { |
| (void) sotpi_close(so, flags, cr); |
| return (error); |
| } |
| |
| /* Wildcard */ |
| if (so->so_protocol != so->so_sockparams->sp_protocol) { |
| int protocol = so->so_protocol; |
| /* |
| * Issue SO_PROTOTYPE setsockopt. |
| */ |
| error = sotpi_setsockopt(so, SOL_SOCKET, SO_PROTOTYPE, |
| &protocol, (t_uscalar_t)sizeof (protocol), cr); |
| if (error != 0) { |
| (void) sotpi_close(so, flags, cr); |
| /* |
| * Setsockopt often fails with ENOPROTOOPT but |
| * socket() should fail with |
| * EPROTONOSUPPORT/EPROTOTYPE. |
| */ |
| return (EPROTONOSUPPORT); |
| } |
| } |
| |
| } else { |
| /* |
| * While the same socket can not be reopened (unlike specfs) |
| * the stream head sets STREOPENFAIL when the autopush fails. |
| */ |
| if ((stp != NULL) && |
| (stp->sd_flag & STREOPENFAIL)) { |
| /* |
| * Open failed part way through. |
| */ |
| mutex_enter(&stp->sd_lock); |
| stp->sd_flag &= ~STREOPENFAIL; |
| mutex_exit(&stp->sd_lock); |
| (void) sotpi_close(so, flags, cr); |
| return (error); |
| /*NOTREACHED*/ |
| } |
| ASSERT(stp == NULL); |
| } |
| TRACE_4(TR_FAC_SOCKFS, TR_SOCKFS_OPEN, |
| "sockfs open:maj %d vp %p so %p error %d", |
| maj, vp, so, error); |
| return (error); |
| } |
| |
| /* |
| * Bind the socket to an unspecified address in sockfs only. |
| * Used for TCP/UDP transports where we know that the O_T_BIND_REQ isn't |
| * required in all cases. |
| */ |
| static void |
| so_automatic_bind(struct sonode *so) |
| { |
| sotpi_info_t *sti = SOTOTPI(so); |
| ASSERT(so->so_family == AF_INET || so->so_family == AF_INET6); |
| |
| ASSERT(MUTEX_HELD(&so->so_lock)); |
| ASSERT(!(so->so_state & SS_ISBOUND)); |
| ASSERT(sti->sti_unbind_mp); |
| |
| ASSERT(sti->sti_laddr_len <= sti->sti_laddr_maxlen); |
| bzero(sti->sti_laddr_sa, sti->sti_laddr_len); |
| sti->sti_laddr_sa->sa_family = so->so_family; |
| so->so_state |= SS_ISBOUND; |
| } |
| |
| |
| /* |
| * bind the socket. |
| * |
| * If the socket is already bound and none of _SOBIND_SOCKBSD or _SOBIND_XPG4_2 |
| * are passed in we allow rebinding. Note that for backwards compatibility |
| * even "svr4" sockets pass in _SOBIND_SOCKBSD/SOV_SOCKBSD to sobind/bind. |
| * Thus the rebinding code is currently not executed. |
| * |
| * The constraints for rebinding are: |
| * - it is a SOCK_DGRAM, or |
| * - it is a SOCK_STREAM/SOCK_SEQPACKET that has not been connected |
| * and no listen() has been done. |
| * This rebinding code was added based on some language in the XNET book |
| * about not returning EINVAL it the protocol allows rebinding. However, |
| * this language is not present in the Posix socket draft. Thus maybe the |
| * rebinding logic should be deleted from the source. |
| * |
| * A null "name" can be used to unbind the socket if: |
| * - it is a SOCK_DGRAM, or |
| * - it is a SOCK_STREAM/SOCK_SEQPACKET that has not been connected |
| * and no listen() has been done. |
| */ |
| /* ARGSUSED */ |
| static int |
| sotpi_bindlisten(struct sonode *so, struct sockaddr *name, |
| socklen_t namelen, int backlog, int flags, struct cred *cr) |
| { |
| struct T_bind_req bind_req; |
| struct T_bind_ack *bind_ack; |
| int error = 0; |
| mblk_t *mp; |
| void *addr; |
| t_uscalar_t addrlen; |
| int unbind_on_err = 1; |
| boolean_t clear_acceptconn_on_err = B_FALSE; |
| boolean_t restore_backlog_on_err = B_FALSE; |
| int save_so_backlog; |
| t_scalar_t PRIM_type = O_T_BIND_REQ; |
| boolean_t tcp_udp_xport; |
| void *nl7c = NULL; |
| sotpi_info_t *sti = SOTOTPI(so); |
| |
| dprintso(so, 1, ("sotpi_bindlisten(%p, %p, %d, %d, 0x%x) %s\n", |
| (void *)so, (void *)name, namelen, backlog, flags, |
| pr_state(so->so_state, so->so_mode))); |
| |
| tcp_udp_xport = so->so_type == SOCK_STREAM || so->so_type == SOCK_DGRAM; |
| |
| 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); |
| } |
| |
| /* |
| * Make sure that there is a preallocated unbind_req message |
| * before binding. This message allocated when the socket is |
| * created but it might be have been consumed. |
| */ |
| if (sti->sti_unbind_mp == NULL) { |
| dprintso(so, 1, ("sobind: allocating unbind_req\n")); |
| /* NOTE: holding so_lock while sleeping */ |
| sti->sti_unbind_mp = |
| soallocproto(sizeof (struct T_unbind_req), _ALLOC_SLEEP, |
| cr); |
| } |
| |
| if (flags & _SOBIND_REBIND) { |
| /* |
| * Called from solisten after doing an sotpi_unbind() or |
| * potentially without the unbind (latter for AF_INET{,6}). |
| */ |
| ASSERT(name == NULL && namelen == 0); |
| |
| if (so->so_family == AF_UNIX) { |
| ASSERT(sti->sti_ux_bound_vp); |
| addr = &sti->sti_ux_laddr; |
| addrlen = (t_uscalar_t)sizeof (sti->sti_ux_laddr); |
| dprintso(so, 1, ("sobind rebind UNIX: addrlen %d, " |
| "addr 0x%p, vp %p\n", |
| addrlen, |
| (void *)((struct so_ux_addr *)addr)->soua_vp, |
| (void *)sti->sti_ux_bound_vp)); |
| } else { |
| addr = sti->sti_laddr_sa; |
| addrlen = (t_uscalar_t)sti->sti_laddr_len; |
| } |
| } else if (flags & _SOBIND_UNSPEC) { |
| ASSERT(name == NULL && namelen == 0); |
| |
| /* |
| * The caller checked SS_ISBOUND but not necessarily |
| * under so_lock |
| */ |
| if (so->so_state & SS_ISBOUND) { |
| /* No error */ |
| goto done; |
| } |
| |
| /* Set an initial local address */ |
| switch (so->so_family) { |
| case AF_UNIX: |
| /* |
| * Use an address with same size as struct sockaddr |
| * just like BSD. |
| */ |
| sti->sti_laddr_len = |
| (socklen_t)sizeof (struct sockaddr); |
| ASSERT(sti->sti_laddr_len <= sti->sti_laddr_maxlen); |
| bzero(sti->sti_laddr_sa, sti->sti_laddr_len); |
| sti->sti_laddr_sa->sa_family = so->so_family; |
| |
| /* |
| * Pass down an address with the implicit bind |
| * magic number and the rest all zeros. |
| * The transport will return a unique address. |
| */ |
| sti->sti_ux_laddr.soua_vp = NULL; |
| sti->sti_ux_laddr.soua_magic = SOU_MAGIC_IMPLICIT; |
| addr = &sti->sti_ux_laddr; |
| addrlen = (t_uscalar_t)sizeof (sti->sti_ux_laddr); |
| break; |
| |
| case AF_INET: |
| case AF_INET6: |
| /* |
| * An unspecified bind in TPI has a NULL address. |
| * Set the address in sockfs to have the sa_family. |
| */ |
| sti->sti_laddr_len = (so->so_family == AF_INET) ? |
| (socklen_t)sizeof (sin_t) : |
| (socklen_t)sizeof (sin6_t); |
| ASSERT(sti->sti_laddr_len <= sti->sti_laddr_maxlen); |
| bzero(sti->sti_laddr_sa, sti->sti_laddr_len); |
| sti->sti_laddr_sa->sa_family = so->so_family; |
| addr = NULL; |
| addrlen = 0; |
| break; |
| |
| default: |
| /* |
| * An unspecified bind in TPI has a NULL address. |
| * Set the address in sockfs to be zero length. |
| * |
| * Can not assume there is a sa_family for all |
| * protocol families. For example, AF_X25 does not |
| * have a family field. |
| */ |
| bzero(sti->sti_laddr_sa, sti->sti_laddr_len); |
| sti->sti_laddr_len = 0; /* XXX correct? */ |
| addr = NULL; |
| addrlen = 0; |
| break; |
| } |
| |
| } else { |
| if (so->so_state & SS_ISBOUND) { |
| /* |
| * If it is ok to rebind the socket, first unbind |
| * with the transport. A rebind to the NULL address |
| * is interpreted as an unbind. |
| * Note that a bind to NULL in BSD does unbind the |
| * socket but it fails with EINVAL. |
| * Note that regular sockets set SOV_SOCKBSD i.e. |
| * _SOBIND_SOCKBSD gets set here hence no type of |
| * socket does currently allow rebinding. |
| * |
| * If the name is NULL just do an unbind. |
| */ |
| if (flags & (_SOBIND_SOCKBSD|_SOBIND_XPG4_2) && |
| name != NULL) { |
| error = EINVAL; |
| unbind_on_err = 0; |
| eprintsoline(so, error); |
| goto done; |
| } |
| if ((so->so_mode & SM_CONNREQUIRED) && |
| (so->so_state & SS_CANTREBIND)) { |
| error = EINVAL; |
| unbind_on_err = 0; |
| eprintsoline(so, error); |
| goto done; |
| } |
| error = sotpi_unbind(so, 0); |
| if (error) { |
| eprintsoline(so, error); |
| goto done; |
| } |
| ASSERT(!(so->so_state & SS_ISBOUND)); |
| if (name == NULL) { |
| so->so_state &= |
| ~(SS_ISCONNECTED|SS_ISCONNECTING); |
| goto done; |
| } |
| } |
| |
| /* X/Open requires this check */ |
| if ((so->so_state & SS_CANTSENDMORE) && !xnet_skip_checks) { |
| if (xnet_check_print) { |
| printf("sockfs: X/Open bind state check " |
| "caused EINVAL\n"); |
| } |
| error = EINVAL; |
| goto done; |
| } |
| |
| switch (so->so_family) { |
| case AF_UNIX: |
| /* |
| * All AF_UNIX addresses are nul terminated |
| * when copied (copyin_name) in so the minimum |
| * length is 3 bytes. |
| */ |
| if (name == NULL || |
| (ssize_t)namelen <= sizeof (short) + 1) { |
| error = EISDIR; |
| eprintsoline(so, error); |
| goto done; |
| } |
| /* |
| * Verify so_family matches the bound family. |
| * BSD does not check this for AF_UNIX resulting |
| * in funny mknods. |
| */ |
| if (name->sa_family != so->so_family) { |
| error = EAFNOSUPPORT; |
| goto done; |
| } |
| break; |
| case AF_INET: |
| if (name == NULL) { |
| error = EINVAL; |
| eprintsoline(so, error); |
| goto done; |
| } |
| if ((size_t)namelen != sizeof (sin_t)) { |
| error = name->sa_family != so->so_family ? |
| EAFNOSUPPORT : EINVAL; |
| eprintsoline(so, error); |
| goto done; |
| } |
| if ((flags & _SOBIND_XPG4_2) && |
| (name->sa_family != so->so_family)) { |
| /* |
| * This check has to be made for X/Open |
| * sockets however application failures have |
| * been observed when it is applied to |
| * all sockets. |
| */ |
| error = EAFNOSUPPORT; |
| eprintsoline(so, error); |
| goto done; |
| } |
| /* |
| * Force a zero sa_family to match so_family. |
| * |
| * Some programs like inetd(1M) don't set the |
| * family field. Other programs leave |
| * sin_family set to garbage - SunOS 4.X does |
| * not check the family field on a bind. |
| * We use the family field that |
| * was passed in to the socket() call. |
| */ |
| name->sa_family = so->so_family; |
| break; |
| |
| case AF_INET6: { |
| #ifdef DEBUG |
| sin6_t *sin6 = (sin6_t *)name; |
| #endif /* DEBUG */ |
| |
| if (name == NULL) { |
| error = EINVAL; |
| eprintsoline(so, error); |
| goto done; |
| } |
| if ((size_t)namelen != sizeof (sin6_t)) { |
| error = name->sa_family != so->so_family ? |
| EAFNOSUPPORT : EINVAL; |
| eprintsoline(so, error); |
| goto done; |
| } |
| if (name->sa_family != so->so_family) { |
| /* |
| * With IPv6 we require the family to match |
| * unlike in IPv4. |
| */ |
| error = EAFNOSUPPORT; |
| eprintsoline(so, error); |
| goto done; |
| } |
| #ifdef DEBUG |
| /* |
| * Verify that apps don't forget to clear |
| * sin6_scope_id etc |
| */ |
| if (sin6->sin6_scope_id != 0 && |
| !IN6_IS_ADDR_LINKSCOPE(&sin6->sin6_addr)) { |
| zcmn_err(getzoneid(), CE_WARN, |
| "bind with uninitialized sin6_scope_id " |
| "(%d) on socket. Pid = %d\n", |
| (int)sin6->sin6_scope_id, |
| (int)curproc->p_pid); |
| } |
| if (sin6->__sin6_src_id != 0) { |
| zcmn_err(getzoneid(), CE_WARN, |
| "bind with uninitialized __sin6_src_id " |
| "(%d) on socket. Pid = %d\n", |
| (int)sin6->__sin6_src_id, |
| (int)curproc->p_pid); |
| } |
| #endif /* DEBUG */ |
| break; |
| } |
| default: |
| /* |
| * Don't do any length or sa_family check to allow |
| * non-sockaddr style addresses. |
| */ |
| if (name == NULL) { |
| error = EINVAL; |
| eprintsoline(so, error); |
| goto done; |
| } |
| break; |
| } |
| |
| if (namelen > (t_uscalar_t)sti->sti_laddr_maxlen) { |
| error = ENAMETOOLONG; |
| eprintsoline(so, error); |
| goto done; |
| } |
| /* |
| * Save local address. |
| */ |
| sti->sti_laddr_len = (socklen_t)namelen; |
| ASSERT(sti->sti_laddr_len <= sti->sti_laddr_maxlen); |
| bcopy(name, sti->sti_laddr_sa, namelen); |
| |
| addr = sti->sti_laddr_sa; |
| addrlen = (t_uscalar_t)sti->sti_laddr_len; |
| switch (so->so_family) { |
| case AF_INET6: |
| case AF_INET: |
| break; |
| case AF_UNIX: { |
| struct sockaddr_un *soun = |
| (struct sockaddr_un *)sti->sti_laddr_sa; |
| struct vnode *vp, *rvp; |
| struct vattr vattr; |
| |
| ASSERT(sti->sti_ux_bound_vp == NULL); |
| /* |
| * Create vnode for the specified path name. |
| * Keep vnode held with a reference in sti_ux_bound_vp. |
| * Use the vnode pointer as the address used in the |
| * bind with the transport. |
| * |
| * Use the same mode as in BSD. In particular this does |
| * not observe the umask. |
| */ |
| /* MAXPATHLEN + soun_family + nul termination */ |
| if (sti->sti_laddr_len > |
| (socklen_t)(MAXPATHLEN + sizeof (short) + 1)) { |
| error = ENAMETOOLONG; |
| eprintsoline(so, error); |
| goto done; |
| } |
| vattr.va_type = VSOCK; |
| vattr.va_mode = 0777 & ~PTOU(curproc)->u_cmask; |
| vattr.va_mask = AT_TYPE|AT_MODE; |
| /* NOTE: holding so_lock */ |
| error = vn_create(soun->sun_path, UIO_SYSSPACE, &vattr, |
| EXCL, 0, &vp, CRMKNOD, 0, 0); |
| if (error) { |
| if (error == EEXIST) |
| error = EADDRINUSE; |
| eprintsoline(so, error); |
| goto done; |
| } |
| /* |
| * Establish pointer from the underlying filesystem |
| * vnode to the socket node. |
| * sti_ux_bound_vp and v_stream->sd_vnode form the |
| * cross-linkage between the underlying filesystem |
| * node and the socket node. |
| */ |
| |
| if ((VOP_REALVP(vp, &rvp, NULL) == 0) && (vp != rvp)) { |
| VN_HOLD(rvp); |
| VN_RELE(vp); |
| vp = rvp; |
| } |
| |
| ASSERT(SOTOV(so)->v_stream); |
| mutex_enter(&vp->v_lock); |
| vp->v_stream = SOTOV(so)->v_stream; |
| sti->sti_ux_bound_vp = vp; |
| mutex_exit(&vp->v_lock); |
| |
| /* |
| * Use the vnode pointer value as a unique address |
| * (together with the magic number to avoid conflicts |
| * with implicit binds) in the transport provider. |
| */ |
| sti->sti_ux_laddr.soua_vp = |
| (void *)sti->sti_ux_bound_vp; |
| sti->sti_ux_laddr.soua_magic = SOU_MAGIC_EXPLICIT; |
| addr = &sti->sti_ux_laddr; |
| addrlen = (t_uscalar_t)sizeof (sti->sti_ux_laddr); |
| dprintso(so, 1, ("sobind UNIX: addrlen %d, addr %p\n", |
| addrlen, |
| (void *)((struct so_ux_addr *)addr)->soua_vp)); |
| break; |
| } |
| } /* end switch (so->so_family) */ |
| } |
| |
| /* |
| * set SS_ACCEPTCONN before sending down O_T_BIND_REQ since |
| * the transport can start passing up T_CONN_IND messages |
| * as soon as it receives the bind req and strsock_proto() |
| * insists that SS_ACCEPTCONN is set when processing T_CONN_INDs. |
| */ |
| if (flags & _SOBIND_LISTEN) { |
| if ((so->so_state & SS_ACCEPTCONN) == 0) |
| clear_acceptconn_on_err = B_TRUE; |
| save_so_backlog = so->so_backlog; |
| restore_backlog_on_err = B_TRUE; |
| so->so_state |= SS_ACCEPTCONN; |
| so->so_backlog = backlog; |
| } |
| |
| /* |
| * If NL7C addr(s) have been configured check for addr/port match, |
| * or if an implicit NL7C socket via AF_NCA mark socket as NL7C. |
| * |
| * NL7C supports the TCP transport only so check AF_INET and AF_INET6 |
| * family sockets only. If match mark as such. |
| */ |
| if (nl7c_enabled && ((addr != NULL && |
| (so->so_family == AF_INET || so->so_family == AF_INET6) && |
| (nl7c = nl7c_lookup_addr(addr, addrlen))) || |
| sti->sti_nl7c_flags == NL7C_AF_NCA)) { |
| /* |
| * NL7C is not supported in non-global zones, |
| * we enforce this restriction here. |
| */ |
| if (so->so_zoneid == GLOBAL_ZONEID) { |
| /* An NL7C socket, mark it */ |
| sti->sti_nl7c_flags |= NL7C_ENABLED; |
| if (nl7c == NULL) { |
| /* |
| * Was an AF_NCA bind() so add it to the |
| * addr list for reporting purposes. |
| */ |
| nl7c = nl7c_add_addr(addr, addrlen); |
| } |
| } else |
| nl7c = NULL; |
| } |
| |
| /* |
| * We send a T_BIND_REQ for TCP/UDP since we know it supports it, |
| * for other transports we will send in a O_T_BIND_REQ. |
| */ |
| if (tcp_udp_xport && |
| (so->so_family == AF_INET || so->so_family == AF_INET6)) |
| PRIM_type = T_BIND_REQ; |
| |
| bind_req.PRIM_type = PRIM_type; |
| bind_req.ADDR_length = addrlen; |
| bind_req.ADDR_offset = (t_scalar_t)sizeof (bind_req); |
| bind_req.CONIND_number = backlog; |
| /* NOTE: holding so_lock while sleeping */ |
| mp = soallocproto2(&bind_req, sizeof (bind_req), |
| addr, addrlen, 0, _ALLOC_SLEEP, cr); |
| sti->sti_laddr_valid = 0; |
| |
| /* Done using sti_laddr_sa - can drop the lock */ |
| mutex_exit(&so->so_lock); |
| |
| /* |
| * Intercept the bind_req message here to check if this <address/port> |
| * was configured as an SSL proxy server, or if another endpoint was |
| * already configured to act as a proxy for us. |
| * |
| * Note, only if NL7C not enabled for this socket. |
| */ |
| if (nl7c == NULL && |
| (so->so_family == AF_INET || so->so_family == AF_INET6) && |
| so->so_type == SOCK_STREAM) { |
| |
| if (sti->sti_kssl_ent != NULL) { |
| kssl_release_ent(sti->sti_kssl_ent, so, |
| sti->sti_kssl_type); |
| sti->sti_kssl_ent = NULL; |
| } |
| |
| sti->sti_kssl_type = kssl_check_proxy(mp, so, |
| &sti->sti_kssl_ent); |
| switch (sti->sti_kssl_type) { |
| case KSSL_NO_PROXY: |
| break; |
| |
| case KSSL_HAS_PROXY: |
| mutex_enter(&so->so_lock); |
| goto skip_transport; |
| |
| case KSSL_IS_PROXY: |
| break; |
| } |
| } |
| |
| error = kstrputmsg(SOTOV(so), mp, NULL, 0, 0, |
| MSG_BAND|MSG_HOLDSIG|MSG_IGNERROR, 0); |
| if (error) { |
| eprintsoline(so, error); |
| mutex_enter(&so->so_lock); |
| goto done; |
| } |
| |
| mutex_enter(&so->so_lock); |
| error = sowaitprim(so, PRIM_type, T_BIND_ACK, |
| (t_uscalar_t)sizeof (*bind_ack), &mp, 0); |
| if (error) { |
| eprintsoline(so, error); |
| goto done; |
| } |
| skip_transport: |
| ASSERT(mp); |
| /* |
| * Even if some TPI message (e.g. T_DISCON_IND) was received in |
| * strsock_proto while the lock was dropped above, the bind |
| * is allowed to complete. |
| */ |
| |
| /* Mark as bound. This will be undone if we detect errors below. */ |
| if (flags & _SOBIND_NOXLATE) { |
| ASSERT(so->so_family == AF_UNIX); |
| sti->sti_faddr_noxlate = 1; |
| } |
| ASSERT(!(so->so_state & SS_ISBOUND) || (flags & _SOBIND_REBIND)); |
| so->so_state |= SS_ISBOUND; |
| ASSERT(sti->sti_unbind_mp); |
| |
| /* note that we've already set SS_ACCEPTCONN above */ |
| |
| /* |
| * Recompute addrlen - an unspecied bind sent down an |
| * address of length zero but we expect the appropriate length |
| * in return. |
| */ |
| addrlen = (t_uscalar_t)(so->so_family == AF_UNIX ? |
| sizeof (sti->sti_ux_laddr) : sti->sti_laddr_len); |
| |
| bind_ack = (struct T_bind_ack *)mp->b_rptr; |
| /* |
| * The alignment restriction is really too strict but |
| * we want enough alignment to inspect the fields of |
| * a sockaddr_in. |
| */ |
| addr = sogetoff(mp, bind_ack->ADDR_offset, |
| bind_ack->ADDR_length, |
| __TPI_ALIGN_SIZE); |
| if (addr == NULL) { |
| freemsg(mp); |
| error = EPROTO; |
| eprintsoline(so, error); |
| goto done; |
| } |
| if (!(flags & _SOBIND_UNSPEC)) { |
| /* |
| * Verify that the transport didn't return something we |
| * did not want e.g. an address other than what we asked for. |
| * |
| * NOTE: These checks would go away if/when we switch to |
| * using the new TPI (in which the transport would fail |
| * the request instead of assigning a different address). |
| * |
| * NOTE2: For protocols that we don't know (i.e. any |
| * other than AF_INET6, AF_INET and AF_UNIX), we |
| * cannot know if the transport should be expected to |
| * return the same address as that requested. |
| * |
| * NOTE3: For AF_INET and AF_INET6, TCP/UDP, we send |
| * down a T_BIND_REQ. We use O_T_BIND_REQ for others. |
| * |
| * For example, in the case of netatalk it may be |
| * inappropriate for the transport to return the |
| * requested address (as it may have allocated a local |
| * port number in behaviour similar to that of an |
| * AF_INET bind request with a port number of zero). |
| * |
| * Given the definition of O_T_BIND_REQ, where the |
| * transport may bind to an address other than the |
| * requested address, it's not possible to determine |
| * whether a returned address that differs from the |
| * requested address is a reason to fail (because the |
| * requested address was not available) or succeed |
| * (because the transport allocated an appropriate |
| * address and/or port). |
| * |
| * sockfs currently requires that the transport return |
| * the requested address in the T_BIND_ACK, unless |
| * there is code here to allow for any discrepancy. |
| * Such code exists for AF_INET and AF_INET6. |
| * |
| * Netatalk chooses to return the requested address |
| * rather than the (correct) allocated address. This |
| * means that netatalk violates the TPI specification |
| * (and would not function correctly if used from a |
| * TLI application), but it does mean that it works |
| * with sockfs. |
| * |
| * As noted above, using the newer XTI bind primitive |
| * (T_BIND_REQ) in preference to O_T_BIND_REQ would |
| * allow sockfs to be more sure about whether or not |
| * the bind request had succeeded (as transports are |
| * not permitted to bind to a different address than |
| * that requested - they must return failure). |
| * Unfortunately, support for T_BIND_REQ may not be |
| * present in all transport implementations (netatalk, |
| * for example, doesn't have it), making the |
| * transition difficult. |
| */ |
| if (bind_ack->ADDR_length != addrlen) { |
| /* Assumes that the requested address was in use */ |
| freemsg(mp); |
| error = EADDRINUSE; |
| eprintsoline(so, error); |
| goto done; |
| } |
| |
| switch (so->so_family) { |
| case AF_INET6: |
| case AF_INET: { |
| sin_t *rname, *aname; |
| |
| rname = (sin_t *)addr; |
| aname = (sin_t *)sti->sti_laddr_sa; |
| |
| /* |
| * Take advantage of the alignment |
| * of sin_port and sin6_port which fall |
| * in the same place in their data structures. |
| * Just use sin_port for either address family. |
| * |
| * This may become a problem if (heaven forbid) |
| * there's a separate ipv6port_reserved... :-P |
| * |
| * Binding to port 0 has the semantics of letting |
| * the transport bind to any port. |
| * |
| * If the transport is TCP or UDP since we had sent |
| * a T_BIND_REQ we would not get a port other than |
| * what we asked for. |
| */ |
| if (tcp_udp_xport) { |
| /* |
| * Pick up the new port number if we bound to |
| * port 0. |
| */ |
| if (aname->sin_port == 0) |
| aname->sin_port = rname->sin_port; |
| sti->sti_laddr_valid = 1; |
| break; |
| } |
| if (aname->sin_port != 0 && |
| aname->sin_port != rname->sin_port) { |
| freemsg(mp); |
| error = EADDRINUSE; |
| eprintsoline(so, error); |
| goto done; |
| } |
| /* |
| * Pick up the new port number if we bound to port 0. |
| */ |
| aname->sin_port = rname->sin_port; |
| |
| /* |
| * Unfortunately, addresses aren't _quite_ the same. |
| */ |
| if (so->so_family == AF_INET) { |
| if (aname->sin_addr.s_addr != |
| rname->sin_addr.s_addr) { |
| freemsg(mp); |
| error = EADDRNOTAVAIL; |
| eprintsoline(so, error); |
| goto done; |
| } |
| } else { |
| sin6_t *rname6 = (sin6_t *)rname; |
| sin6_t *aname6 = (sin6_t *)aname; |
| |
| if (!IN6_ARE_ADDR_EQUAL(&aname6->sin6_addr, |
| &rname6->sin6_addr)) { |
| freemsg(mp); |
| error = EADDRNOTAVAIL; |
| eprintsoline(so, error); |
| goto done; |
| } |
| } |
| break; |
| } |
| case AF_UNIX: |
| if (bcmp(addr, &sti->sti_ux_laddr, addrlen) != 0) { |
| freemsg(mp); |
| error = EADDRINUSE; |
| eprintsoline(so, error); |
| eprintso(so, |
| ("addrlen %d, addr 0x%x, vp %p\n", |
| addrlen, *((int *)addr), |
| (void *)sti->sti_ux_bound_vp)); |
| goto done; |
| } |
| sti->sti_laddr_valid = 1; |
| break; |
| default: |
| /* |
| * NOTE: This assumes that addresses can be |
| * byte-compared for equivalence. |
| */ |
| if (bcmp(addr, sti->sti_laddr_sa, addrlen) != 0) { |
| freemsg(mp); |
| error = EADDRINUSE; |
| eprintsoline(so, error); |
| goto done; |
| } |
| /* |
| * Don't mark sti_laddr_valid, as we cannot be |
| * sure that the returned address is the real |
| * bound address when talking to an unknown |
| * transport. |
| */ |
| break; |
| } |
| } else { |
| /* |
| * Save for returned address for getsockname. |
| * Needed for unspecific bind unless transport supports |
| * the TI_GETMYNAME ioctl. |
| * Do this for AF_INET{,6} even though they do, as |
| * caching info here is much better performance than |
| * a TPI/STREAMS trip to the transport for getsockname. |
| * Any which can't for some reason _must_ _not_ set |
| * sti_laddr_valid here for the caching version of |
| * getsockname to not break; |
| */ |
| switch (so->so_family) { |
| case AF_UNIX: |
| /* |
| * Record the address bound with the transport |
| * for use by socketpair. |
| */ |
| bcopy(addr, &sti->sti_ux_laddr, addrlen); |
| sti->sti_laddr_valid = 1; |
| break; |
| case AF_INET: |
| case AF_INET6: |
| ASSERT(sti->sti_laddr_len <= sti->sti_laddr_maxlen); |
| bcopy(addr, sti->sti_laddr_sa, sti->sti_laddr_len); |
| sti->sti_laddr_valid = 1; |
| break; |
| default: |
| /* |
| * Don't mark sti_laddr_valid, as we cannot be |
| * sure that the returned address is the real |
| * bound address when talking to an unknown |
| * transport. |
| */ |
| break; |
| } |
| } |
| |
| if (nl7c != NULL) { |
| /* Register listen()er sonode pointer with NL7C */ |
| nl7c_listener_addr(nl7c, so); |
| } |
| |
| freemsg(mp); |
| |
| done: |
| if (error) { |
| /* reset state & backlog to values held on entry */ |
| if (clear_acceptconn_on_err == B_TRUE) |
| so->so_state &= ~SS_ACCEPTCONN; |
| if (restore_backlog_on_err == B_TRUE) |
| so->so_backlog = save_so_backlog; |
| |
| if (unbind_on_err && so->so_state & SS_ISBOUND) { |
| int err; |
| |
| err = sotpi_unbind(so, 0); |
| /* LINTED - statement has no consequent: if */ |
| if (err) { |
| eprintsoline(so, error); |
| } else { |
| ASSERT(!(so->so_state & SS_ISBOUND)); |
| } |
| } |
| } |
| if (!(flags & _SOBIND_LOCK_HELD)) { |
| so_unlock_single(so, SOLOCKED); |
| mutex_exit(&so->so_lock); |
| } else { |
| ASSERT(MUTEX_HELD(&so->so_lock)); |
| ASSERT(so->so_flag & SOLOCKED); |
| } |
| return (error); |
| } |
| |
| /* bind the socket */ |
| static int |
| sotpi_bind(struct sonode *so, struct sockaddr *name, socklen_t namelen, |
| int flags, struct cred *cr) |
| { |
| if ((flags & _SOBIND_SOCKETPAIR) == 0) |
| return (sotpi_bindlisten(so, name, namelen, 0, flags, cr)); |
| |
| flags &= ~_SOBIND_SOCKETPAIR; |
| return (sotpi_bindlisten(so, name, namelen, 1, flags, cr)); |
| } |
| |
| /* |
| * Unbind a socket - used when bind() fails, when bind() specifies a NULL |
| * address, or when listen needs to unbind and bind. |
| * If the _SOUNBIND_REBIND flag is specified the addresses are retained |
| * so that a sobind can pick them up. |
| */ |
| static int |
| sotpi_unbind(struct sonode *so, int flags) |
| { |
| struct T_unbind_req unbind_req; |
| int error = 0; |
| mblk_t *mp; |
| sotpi_info_t *sti = SOTOTPI(so); |
| |
| dprintso(so, 1, ("sotpi_unbind(%p, 0x%x) %s\n", |
| (void *)so, flags, pr_state(so->so_state, so->so_mode))); |
| |
| ASSERT(MUTEX_HELD(&so->so_lock)); |
| ASSERT(so->so_flag & SOLOCKED); |
| |
| if (!(so->so_state & SS_ISBOUND)) { |
| error = EINVAL; |
| eprintsoline(so, error); |
| goto done; |
| } |
| |
| mutex_exit(&so->so_lock); |
| |
| /* |
| * Flush the read and write side (except stream head read queue) |
| * and send down T_UNBIND_REQ. |
| */ |
| (void) putnextctl1(strvp2wq(SOTOV(so)), M_FLUSH, FLUSHRW); |
| |
| unbind_req.PRIM_type = T_UNBIND_REQ; |
| mp = soallocproto1(&unbind_req, sizeof (unbind_req), |
| 0, _ALLOC_SLEEP, CRED()); |
| error = kstrputmsg(SOTOV(so), mp, NULL, 0, 0, |
| MSG_BAND|MSG_HOLDSIG|MSG_IGNERROR, 0); |
| mutex_enter(&so->so_lock); |
| if (error) { |
| eprintsoline(so, error); |
| goto done; |
| } |
| |
| error = sowaitokack(so, T_UNBIND_REQ); |
| if (error) { |
| eprintsoline(so, error); |
| goto done; |
| } |
| |
| /* |
| * Even if some TPI message (e.g. T_DISCON_IND) was received in |
| * strsock_proto while the lock was dropped above, the unbind |
| * is allowed to complete. |
| */ |
| if (!(flags & _SOUNBIND_REBIND)) { |
| /* |
| * Clear out bound address. |
| */ |
| vnode_t *vp; |
| |
| if ((vp = sti->sti_ux_bound_vp) != NULL) { |
| |
| /* Undo any SSL proxy setup */ |
| if ((so->so_family == AF_INET || |
| so->so_family == AF_INET6) && |
| (so->so_type == SOCK_STREAM) && |
| (sti->sti_kssl_ent != NULL)) { |
| kssl_release_ent(sti->sti_kssl_ent, so, |
| sti->sti_kssl_type); |
| sti->sti_kssl_ent = NULL; |
| sti->sti_kssl_type = KSSL_NO_PROXY; |
| } |
| sti->sti_ux_bound_vp = NULL; |
| vn_rele_stream(vp); |
| } |
| /* Clear out address */ |
| sti->sti_laddr_len = 0; |
| } |
| so->so_state &= ~(SS_ISBOUND|SS_ACCEPTCONN); |
| sti->sti_laddr_valid = 0; |
| |
| done: |
| |
| /* If the caller held the lock don't release it here */ |
| ASSERT(MUTEX_HELD(&so->so_lock)); |
| ASSERT(so->so_flag & SOLOCKED); |
| |
| return (error); |
| } |
| |
| /* |
| * listen on the socket. |
| * For TPI conforming transports this has to first unbind with the transport |
| * and then bind again using the new backlog. |
| */ |
| /* ARGSUSED */ |
| int |
| sotpi_listen(struct sonode *so, int backlog, struct cred *cr) |
| { |
| int error = 0; |
| sotpi_info_t *sti = SOTOTPI(so); |
| |
| dprintso(so, 1, ("sotpi_listen(%p, %d) %s\n", |
| (void *)so, backlog, pr_state(so->so_state, so->so_mode))); |
| |
| if (sti->sti_serv_type == T_CLTS) |
| return (EOPNOTSUPP); |
| |
| /* |
| * If the socket is ready to accept connections already, then |
| * return without doing anything. This avoids a problem where |
| * a second listen() call fails if a connection is pending and |
| * leaves the socket unbound. Only when we are not unbinding |
| * with the transport can we safely increase the backlog. |
| */ |
| if (so->so_state & SS_ACCEPTCONN && |
| !((so->so_family == AF_INET || so->so_family == AF_INET6) && |
| /*CONSTCOND*/ |
| !solisten_tpi_tcp)) |
| return (0); |
| |
| if (so->so_state & SS_ISCONNECTED) |
| return (EINVAL); |
| |
| mutex_enter(&so->so_lock); |
| so_lock_single(so); /* Set SOLOCKED */ |
| |
| /* |
| * If the listen doesn't change the backlog we do nothing. |
| * This avoids an EPROTO error from the transport. |
| */ |
| if ((so->so_state & SS_ACCEPTCONN) && |
| so->so_backlog == backlog) |
| goto done; |
| |
| if (!(so->so_state & SS_ISBOUND)) { |
| /* |
| * Must have been explicitly bound in the UNIX domain. |
| */ |
| if (so->so_family == AF_UNIX) { |
| error = EINVAL; |
| goto done; |
| } |
| error = sotpi_bindlisten(so, NULL, 0, backlog, |
| _SOBIND_UNSPEC|_SOBIND_LOCK_HELD|_SOBIND_LISTEN, cr); |
| } else if (backlog > 0) { |
| /* |
| * AF_INET{,6} hack to avoid losing the port. |
| * Assumes that all AF_INET{,6} transports can handle a |
| * O_T_BIND_REQ with a non-zero CONIND_number when the TPI |
| * has already bound thus it is possible to avoid the unbind. |
| */ |
| if (!((so->so_family == AF_INET || so->so_family == AF_INET6) && |
| /*CONSTCOND*/ |
| !solisten_tpi_tcp)) { |
| error = sotpi_unbind(so, _SOUNBIND_REBIND); |
| if (error) |
| goto done; |
| } |
| error = sotpi_bindlisten(so, NULL, 0, backlog, |
| _SOBIND_REBIND|_SOBIND_LOCK_HELD|_SOBIND_LISTEN, cr); |
| } else { |
| so->so_state |= SS_ACCEPTCONN; |
| so->so_backlog = backlog; |
| } |
| if (error) |
| goto done; |
| ASSERT(so->so_state & SS_ACCEPTCONN); |
| done: |
| so_unlock_single(so, SOLOCKED); |
| mutex_exit(&so->so_lock); |
| return (error); |
| } |
| |
| /* |
| * Disconnect either a specified seqno or all (-1). |
| * The former is used on listening sockets only. |
| * |
| * When seqno == -1 sodisconnect could call sotpi_unbind. However, |
| * the current use of sodisconnect(seqno == -1) is only for shutdown |
| * so there is no point (and potentially incorrect) to unbind. |
| */ |
| static int |
| sodisconnect(struct sonode *so, t_scalar_t seqno, int flags) |
| { |
| struct T_discon_req discon_req; |
| int error = 0; |
| mblk_t *mp; |
| |
| dprintso(so, 1, ("sodisconnect(%p, %d, 0x%x) %s\n", |
| (void *)so, seqno, flags, pr_state(so->so_state, so->so_mode))); |
| |
| if (!(flags & _SODISCONNECT_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_ISCONNECTED|SS_ISCONNECTING|SS_ACCEPTCONN))) { |
| error = EINVAL; |
| eprintsoline(so, error); |
| goto done; |
| } |
| |
| mutex_exit(&so->so_lock); |
| /* |
| * Flush the write side (unless this is a listener) |
| * and then send down a T_DISCON_REQ. |
| * (Don't flush on listener since it could flush {O_}T_CONN_RES |
| * and other messages.) |
| */ |
| if (!(so->so_state & SS_ACCEPTCONN)) |
| (void) putnextctl1(strvp2wq(SOTOV(so)), M_FLUSH, FLUSHW); |
| |
| discon_req.PRIM_type = T_DISCON_REQ; |
| discon_req.SEQ_number = seqno; |
| mp = soallocproto1(&discon_req, sizeof (discon_req), |
| 0, _ALLOC_SLEEP, CRED()); |
| error = kstrputmsg(SOTOV(so), mp, NULL, 0, 0, |
| MSG_BAND|MSG_HOLDSIG|MSG_IGNERROR, 0); |
| mutex_enter(&so->so_lock); |
| if (error) { |
| eprintsoline(so, error); |
| goto done; |
| } |
| |
| error = sowaitokack(so, T_DISCON_REQ); |
| if (error) { |
| eprintsoline(so, error); |
| goto done; |
| } |
| /* |
| * Even if some TPI message (e.g. T_DISCON_IND) was received in |
| * strsock_proto while the lock was dropped above, the disconnect |
| * is allowed to complete. However, it is not possible to |
| * assert that SS_ISCONNECTED|SS_ISCONNECTING are set. |
| */ |
| so->so_state &= ~(SS_ISCONNECTED|SS_ISCONNECTING); |
| SOTOTPI(so)->sti_laddr_valid = 0; |
| SOTOTPI(so)->sti_faddr_valid = 0; |
| done: |
| if (!(flags & _SODISCONNECT_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); |
| } |
| |
| /* ARGSUSED */ |
| int |
| sotpi_accept(struct sonode *so, int fflag, struct cred *cr, |
| struct sonode **nsop) |
| { |
| struct T_conn_ind *conn_ind; |
| struct T_conn_res *conn_res; |
| int error = 0; |
| mblk_t *mp, *ctxmp, *ack_mp; |
| struct sonode *nso; |
| vnode_t *nvp; |
| void *src; |
| t_uscalar_t srclen; |
| void *opt; |
| t_uscalar_t optlen; |
| t_scalar_t PRIM_type; |
| t_scalar_t SEQ_number; |
| size_t sinlen; |
| sotpi_info_t *sti = SOTOTPI(so); |
| sotpi_info_t *nsti; |
| |
| dprintso(so, 1, ("sotpi_accept(%p, 0x%x, %p) %s\n", |
| (void *)so, fflag, (void *)nsop, |
| pr_state(so->so_state, so->so_mode))); |
| |
| /* |
| * Defer single-threading the accepting socket until |
| * the T_CONN_IND has been received and parsed and the |
| * new sonode has been opened. |
| */ |
| |
| /* Check that we are not already connected */ |
| if ((so->so_state & SS_ACCEPTCONN) == 0) |
| goto conn_bad; |
| again: |
| if ((error = sowaitconnind(so, fflag, &mp)) != 0) |
| goto e_bad; |
| |
| ASSERT(mp != NULL); |
| conn_ind = (struct T_conn_ind *)mp->b_rptr; |
| ctxmp = mp->b_cont; |
| |
| /* |
| * Save SEQ_number for error paths. |
| */ |
| SEQ_number = conn_ind->SEQ_number; |
| |
| srclen = conn_ind->SRC_length; |
| src = sogetoff(mp, conn_ind->SRC_offset, srclen, 1); |
| if (src == NULL) { |
| error = EPROTO; |
| freemsg(mp); |
| eprintsoline(so, error); |
| goto disconnect_unlocked; |
| } |
| optlen = conn_ind->OPT_length; |
| switch (so->so_family) { |
| case AF_INET: |
| case AF_INET6: |
| if ((optlen == sizeof (intptr_t)) && (sti->sti_direct != 0)) { |
| bcopy(mp->b_rptr + conn_ind->OPT_offset, |
| &opt, conn_ind->OPT_length); |
| } else { |
| /* |
| * The transport (in this case TCP) hasn't sent up |
| * a pointer to an instance for the accept fast-path. |
| * Disable fast-path completely because the call to |
| * sotpi_create() below would otherwise create an |
| * incomplete TCP instance, which would lead to |
| * problems when sockfs sends a normal T_CONN_RES |
| * message down the new stream. |
| */ |
| if (sti->sti_direct) { |
| int rval; |
| /* |
| * For consistency we inform tcp to disable |
| * direct interface on the listener, though |
| * we can certainly live without doing this |
| * because no data will ever travel upstream |
| * on the listening socket. |
| */ |
| sti->sti_direct = 0; |
| (void) strioctl(SOTOV(so), _SIOCSOCKFALLBACK, |
| 0, 0, K_TO_K, cr, &rval); |
| } |
| opt = NULL; |
| optlen = 0; |
| } |
| break; |
| case AF_UNIX: |
| default: |
| if (optlen != 0) { |
| opt = sogetoff(mp, conn_ind->OPT_offset, optlen, |
| __TPI_ALIGN_SIZE); |
| if (opt == NULL) { |
| error = EPROTO; |
| freemsg(mp); |
| eprintsoline(so, error); |
| goto disconnect_unlocked; |
| } |
| } |
| if (so->so_family == AF_UNIX) { |
| if (!sti->sti_faddr_noxlate) { |
| src = NULL; |
| srclen = 0; |
| } |
| /* Extract src address from options */ |
| if (optlen != 0) |
| so_getopt_srcaddr(opt, optlen, &src, &srclen); |
| } |
| break; |
| } |
| |
| /* |
| * Create the new socket. |
| */ |
| nso = socket_newconn(so, NULL, NULL, SOCKET_SLEEP, &error); |
| if (nso == NULL) { |
| ASSERT(error != 0); |
| /* |
| * Accept can not fail with ENOBUFS. sotpi_create |
| * sleeps waiting for memory until a signal is caught |
| * so return EINTR. |
| */ |
| freemsg(mp); |
| if (error == ENOBUFS) |
| error = EINTR; |
| goto e_disc_unl; |
| } |
| nvp = SOTOV(nso); |
| nsti = SOTOTPI(nso); |
| |
| /* |
| * If the transport sent up an SSL connection context, then attach |
| * it the new socket, and set the (sd_wputdatafunc)() and |
| * (sd_rputdatafunc)() stream head hooks to intercept and process |
| * SSL records. |
| */ |
| if (ctxmp != NULL) { |
| /* |
| * This kssl_ctx_t is already held for us by the transport. |
| * So, we don't need to do a kssl_hold_ctx() here. |
| */ |
| nsti->sti_kssl_ctx = *((kssl_ctx_t *)ctxmp->b_rptr); |
| freemsg(ctxmp); |
| mp->b_cont = NULL; |
| strsetrwputdatahooks(nvp, strsock_kssl_input, |
| strsock_kssl_output); |
| } |
| #ifdef DEBUG |
| /* |
| * SO_DEBUG is used to trigger the dprint* and eprint* macros thus |
| * it's inherited early to allow debugging of the accept code itself. |
| */ |
| nso->so_options |= so->so_options & SO_DEBUG; |
| #endif /* DEBUG */ |
| |
| /* |
| * Save the SRC address from the T_CONN_IND |
| * for getpeername to work on AF_UNIX and on transports that do not |
| * support TI_GETPEERNAME. |
| * |
| * NOTE: AF_UNIX NUL termination is ensured by the sender's |
| * copyin_name(). |
| */ |
| if (srclen > (t_uscalar_t)nsti->sti_faddr_maxlen) { |
| error = EINVAL; |
| freemsg(mp); |
| eprintsoline(so, error); |
| goto disconnect_vp_unlocked; |
| } |
| nsti->sti_faddr_len = (socklen_t)srclen; |
| ASSERT(sti->sti_faddr_len <= sti->sti_faddr_maxlen); |
| bcopy(src, nsti->sti_faddr_sa, srclen); |
| nsti->sti_faddr_valid = 1; |
| |
| /* |
| * Record so_peercred and so_cpid from a cred in the T_CONN_IND. |
| */ |
| if ((DB_REF(mp) > 1) || MBLKSIZE(mp) < |
| (sizeof (struct T_conn_res) + sizeof (intptr_t))) { |
| cred_t *cr; |
| pid_t cpid; |
| |
| cr = msg_getcred(mp, &cpid); |
| if (cr != NULL) { |
| crhold(cr); |
| nso->so_peercred = cr; |
| nso->so_cpid = cpid; |
| } |
| freemsg(mp); |
| |
| mp = soallocproto1(NULL, sizeof (struct T_conn_res) + |
| sizeof (intptr_t), 0, _ALLOC_INTR, cr); |
| if (mp == NULL) { |
| /* |
| * Accept can not fail with ENOBUFS. |
| * A signal was caught so return EINTR. |
| */ |
| error = EINTR; |
| eprintsoline(so, error); |
| goto disconnect_vp_unlocked; |
| } |
| conn_res = (struct T_conn_res *)mp->b_rptr; |
| } else { |
| /* |
| * For efficency reasons we use msg_extractcred; no crhold |
| * needed since db_credp is cleared (i.e., we move the cred |
| * from the message to so_peercred. |
| */ |
| nso->so_peercred = msg_extractcred(mp, &nso->so_cpid); |
| |
| mp->b_rptr = DB_BASE(mp); |
| conn_res = (struct T_conn_res *)mp->b_rptr; |
| mp->b_wptr = mp->b_rptr + sizeof (struct T_conn_res); |
| |
| mblk_setcred(mp, cr, curproc->p_pid); |
| } |
| |
| /* |
| * New socket must be bound at least in sockfs and, except for AF_INET, |
| * (or AF_INET6) it also has to be bound in the transport provider. |
| * We set the local address in the sonode from the T_OK_ACK of the |
| * T_CONN_RES. For this reason the address we bind to here isn't |
| * important. |
| */ |
| if ((nso->so_family == AF_INET || nso->so_family == AF_INET6) && |
| /*CONSTCOND*/ |
| nso->so_type == SOCK_STREAM && !soaccept_tpi_tcp) { |
| /* |
| * Optimization for AF_INET{,6} transports |
| * that can handle a T_CONN_RES without being bound. |
| */ |
| mutex_enter(&nso->so_lock); |
| so_automatic_bind(nso); |
| mutex_exit(&nso->so_lock); |
| } else { |
| /* Perform NULL bind with the transport provider. */ |
| if ((error = sotpi_bind(nso, NULL, 0, _SOBIND_UNSPEC, |
| cr)) != 0) { |
| ASSERT(error != ENOBUFS); |
| freemsg(mp); |
| eprintsoline(nso, error); |
| goto disconnect_vp_unlocked; |
| } |
| } |
| |
| /* |
| * Inherit SIOCSPGRP, SS_ASYNC before we send the {O_}T_CONN_RES |
| * so that any data arriving on the new socket will cause the |
| * appropriate signals to be delivered for the new socket. |
| * |
| * No other thread (except strsock_proto and strsock_misc) |
| * can access the new socket thus we relax the locking. |
| */ |
| nso->so_pgrp = so->so_pgrp; |
| nso->so_state |= so->so_state & SS_ASYNC; |
| nsti->sti_faddr_noxlate = sti->sti_faddr_noxlate; |
| |
| if (nso->so_pgrp != 0) { |
| if ((error = so_set_events(nso, nvp, cr)) != 0) { |
| eprintsoline(nso, error); |
| error = 0; |
| nso->so_pgrp = 0; |
| } |
| } |
| |
| /* |
| * Make note of the socket level options. TCP and IP level options |
| * are already inherited. We could do all this after accept is |
| * successful but doing it here simplifies code and no harm done |
| * for error case. |
| */ |
| nso->so_options = so->so_options & (SO_DEBUG|SO_REUSEADDR|SO_KEEPALIVE| |
| SO_DONTROUTE|SO_BROADCAST|SO_USELOOPBACK| |
| SO_OOBINLINE|SO_DGRAM_ERRIND|SO_LINGER); |
| nso->so_sndbuf = so->so_sndbuf; |
| nso->so_rcvbuf = so->so_rcvbuf; |
| if (nso->so_options & SO_LINGER) |
| nso->so_linger = so->so_linger; |
| |
| /* |
| * Note that the following sti_direct code path should be |
| * removed once we are confident that the direct sockets |
| * do not result in any degradation. |
| */ |
| if (sti->sti_direct) { |
| |
| ASSERT(opt != NULL); |
| |
| conn_res->OPT_length = optlen; |
| conn_res->OPT_offset = MBLKL(mp); |
| bcopy(&opt, mp->b_wptr, optlen); |
| mp->b_wptr += optlen; |
| conn_res->PRIM_type = T_CONN_RES; |
| conn_res->ACCEPTOR_id = 0; |
| PRIM_type = T_CONN_RES; |
| |
| /* Send down the T_CONN_RES on acceptor STREAM */ |
| error = kstrputmsg(SOTOV(nso), mp, NULL, |
| 0, 0, MSG_BAND|MSG_HOLDSIG|MSG_IGNERROR, 0); |
| if (error) { |
| mutex_enter(&so->so_lock); |
| so_lock_single(so); |
| eprintsoline(so, error); |
| goto disconnect_vp; |
| } |
| mutex_enter(&nso->so_lock); |
| error = sowaitprim(nso, T_CONN_RES, T_OK_ACK, |
| (t_uscalar_t)sizeof (struct T_ok_ack), &ack_mp, 0); |
| if (error) { |
| mutex_exit(&nso->so_lock); |
| mutex_enter(&so->so_lock); |
| so_lock_single(so); |
| eprintsoline(so, error); |
| goto disconnect_vp; |
| } |
| if (nso->so_family == AF_INET) { |
| sin_t *sin; |
| |
| sin = (sin_t *)(ack_mp->b_rptr + |
| sizeof (struct T_ok_ack)); |
| bcopy(sin, nsti->sti_laddr_sa, sizeof (sin_t)); |
| nsti->sti_laddr_len = sizeof (sin_t); |
| } else { |
| sin6_t *sin6; |
| |
| sin6 = (sin6_t *)(ack_mp->b_rptr + |
| sizeof (struct T_ok_ack)); |
| bcopy(sin6, nsti->sti_laddr_sa, sizeof (sin6_t)); |
| nsti->sti_laddr_len = sizeof (sin6_t); |
| } |
| freemsg(ack_mp); |
| |
| nso->so_state |= SS_ISCONNECTED; |
| nso->so_proto_handle = (sock_lower_handle_t)opt; |
| nsti->sti_laddr_valid = 1; |
| |
| if (sti->sti_nl7c_flags & NL7C_ENABLED) { |
| /* |
| * A NL7C marked listen()er so the new socket |
| * inherits the listen()er's NL7C state, except |
| * for NL7C_POLLIN. |
| * |
| * Only call NL7C to process the new socket if |
| * the listen socket allows blocking i/o. |
| */ |
| nsti->sti_nl7c_flags = |
| sti->sti_nl7c_flags & (~NL7C_POLLIN); |
| if (so->so_state & (SS_NONBLOCK|SS_NDELAY)) { |
| /* |
| * Nonblocking accept() just make it |
| * persist to defer processing to the |
| * read-side syscall (e.g. read). |
| */ |
| nsti->sti_nl7c_flags |= NL7C_SOPERSIST; |
| } else if (nl7c_process(nso, B_FALSE)) { |
| /* |
| * NL7C has completed processing on the |
| * socket, close the socket and back to |
| * the top to await the next T_CONN_IND. |
| */ |
| mutex_exit(&nso->so_lock); |
| (void) VOP_CLOSE(nvp, 0, 1, (offset_t)0, |
| cr, NULL); |
| VN_RELE(nvp); |
| goto again; |
| } |
| /* Pass the new socket out */ |
| } |
| |
| mutex_exit(&nso->so_lock); |
| |
| /* |
| * It's possible, through the use of autopush for example, |
| * that the acceptor stream may not support sti_direct |
| * semantics. If the new socket does not support sti_direct |
| * we issue a _SIOCSOCKFALLBACK to inform the transport |
| * as we would in the I_PUSH case. |
| */ |
| if (nsti->sti_direct == 0) { |
| int rval; |
| |
| if ((error = strioctl(SOTOV(nso), _SIOCSOCKFALLBACK, |
| 0, 0, K_TO_K, cr, &rval)) != 0) { |
| mutex_enter(&so->so_lock); |
| so_lock_single(so); |
| eprintsoline(so, error); |
| goto disconnect_vp; |
| } |
| } |
| |
| /* |
| * Pass out new socket. |
| */ |
| if (nsop != NULL) |
| *nsop = nso; |
| |
| return (0); |
| } |
| |
| /* |
| * This is the non-performance case for sockets (e.g. AF_UNIX sockets) |
| * which don't support the FireEngine accept fast-path. It is also |
| * used when the virtual "sockmod" has been I_POP'd and I_PUSH'd |
| * again. Neither sockfs nor TCP attempt to find out if some other |
| * random module has been inserted in between (in which case we |
| * should follow TLI accept behaviour). We blindly assume the worst |
| * case and revert back to old behaviour i.e. TCP will not send us |
| * any option (eager) and the accept should happen on the listener |
| * queue. Any queued T_conn_ind have already got their options removed |
| * by so_sock2_stream() when "sockmod" was I_POP'd. |
| */ |
| /* |
| * Fill in the {O_}T_CONN_RES before getting SOLOCKED. |
| */ |
| if ((nso->so_mode & SM_ACCEPTOR_ID) == 0) { |
| #ifdef _ILP32 |
| queue_t *q; |
| |
| /* |
| * Find read queue in driver |
| * Can safely do this since we "own" nso/nvp. |
| */ |
| q = strvp2wq(nvp)->q_next; |
| while (SAMESTR(q)) |
| q = q->q_next; |
| q = RD(q); |
| conn_res->ACCEPTOR_id = (t_uscalar_t)q; |
| #else |
| conn_res->ACCEPTOR_id = (t_uscalar_t)getminor(nvp->v_rdev); |
| #endif /* _ILP32 */ |
| conn_res->PRIM_type = O_T_CONN_RES; |
| PRIM_type = O_T_CONN_RES; |
| } else { |
| conn_res->ACCEPTOR_id = nsti->sti_acceptor_id; |
| conn_res->PRIM_type = T_CONN_RES; |
| PRIM_type = T_CONN_RES; |
| } |
| conn_res->SEQ_number = SEQ_number; |
| conn_res->OPT_length = 0; |
| conn_res->OPT_offset = 0; |
| |
| mutex_enter(&so->so_lock); |
| so_lock_single(so); /* Set SOLOCKED */ |
| mutex_exit(&so->so_lock); |
| |
| error = kstrputmsg(SOTOV(so), mp, NULL, |
| 0, 0, MSG_BAND|MSG_HOLDSIG|MSG_IGNERROR, 0); |
| mutex_enter(&so->so_lock); |
| if (error) { |
| eprintsoline(so, error); |
| goto disconnect_vp; |
| } |
| error = sowaitprim(so, PRIM_type, T_OK_ACK, |
| (t_uscalar_t)sizeof (struct T_ok_ack), &ack_mp, 0); |
| if (error) { |
| eprintsoline(so, error); |
| goto disconnect_vp; |
| } |
| /* |
| * If there is a sin/sin6 appended onto the T_OK_ACK use |
| * that to set the local address. If this is not present |
| * then we zero out the address and don't set the |
| * sti_laddr_valid bit. For AF_UNIX endpoints we copy over |
| * the pathname from the listening socket. |
| */ |
| sinlen = (nso->so_family == AF_INET) ? sizeof (sin_t) : sizeof (sin6_t); |
| if ((nso->so_family == AF_INET) || (nso->so_family == AF_INET6) && |
| MBLKL(ack_mp) == (sizeof (struct T_ok_ack) + sinlen)) { |
| ack_mp->b_rptr += sizeof (struct T_ok_ack); |
| bcopy(ack_mp->b_rptr, nsti->sti_laddr_sa, sinlen); |
| nsti->sti_laddr_len = sinlen; |
| nsti->sti_laddr_valid = 1; |
| } else if (nso->so_family == AF_UNIX) { |
| ASSERT(so->so_family == AF_UNIX); |
| nsti->sti_laddr_len = sti->sti_laddr_len; |
| ASSERT(nsti->sti_laddr_len <= nsti->sti_laddr_maxlen); |
| bcopy(sti->sti_laddr_sa, nsti->sti_laddr_sa, |
| nsti->sti_laddr_len); |
| nsti->sti_laddr_valid = 1; |
| } else { |
| nsti->sti_laddr_len = sti->sti_laddr_len; |
| ASSERT(nsti->sti_laddr_len <= nsti->sti_laddr_maxlen); |
| bzero(nsti->sti_laddr_sa, nsti->sti_addr_size); |
| nsti->sti_laddr_sa->sa_family = nso->so_family; |
| } |
| freemsg(ack_mp); |
| |
| so_unlock_single(so, SOLOCKED); |
| mutex_exit(&so->so_lock); |
| |
| nso->so_state |= SS_ISCONNECTED; |
| |
| /* |
| * Pass out new socket. |
| */ |
| if (nsop != NULL) |
| *nsop = nso; |
| |
| return (0); |
| |
| |
| eproto_disc_unl: |
| error = EPROTO; |
| e_disc_unl: |
| eprintsoline(so, error); |
| goto disconnect_unlocked; |
| |
| pr_disc_vp_unl: |
| eprintsoline(so, error); |
| disconnect_vp_unlocked: |
| (void) VOP_CLOSE(nvp, 0, 1, 0, cr, NULL); |
| VN_RELE(nvp); |
| disconnect_unlocked: |
| (void) sodisconnect(so, SEQ_number, 0); |
| return (error); |
| |
| pr_disc_vp: |
| eprintsoline(so, error); |
| disconnect_vp: |
| (void) sodisconnect(so, SEQ_number, _SODISCONNECT_LOCK_HELD); |
| so_unlock_single(so, SOLOCKED); |
| mutex_exit(&so->so_lock); |
| (void) VOP_CLOSE(nvp, 0, 1, 0, cr, NULL); |
| VN_RELE(nvp); |
| return (error); |
| |
| conn_bad: /* Note: SunOS 4/BSD unconditionally returns EINVAL here */ |
| error = (so->so_type == SOCK_DGRAM || so->so_type == SOCK_RAW) |
| ? EOPNOTSUPP : EINVAL; |
| e_bad: |
| eprintsoline(so, error); |
| return (error); |
| } |
| |
| /* |
| * connect a socket. |
| * |
| * Allow SOCK_DGRAM sockets to reconnect (by specifying a new address) and to |
| * unconnect (by specifying a null address). |
| */ |
| int |
| sotpi_connect(struct sonode *so, |
| const struct sockaddr *name, |
| socklen_t namelen, |
| int fflag, |
| int flags, |
| struct cred *cr) |
| { |
| struct T_conn_req conn_req; |
| int error = 0; |
| mblk_t *mp; |
| void *src; |
| socklen_t srclen; |
| void *addr; |
| socklen_t addrlen; |
| boolean_t need_unlock; |
| sotpi_info_t *sti = SOTOTPI(so); |
| |
| dprintso(so, 1, ("sotpi_connect(%p, %p, %d, 0x%x, 0x%x) %s\n", |
| (void *)so, (void *)name, namelen, fflag, flags, |
| pr_state(so->so_state, so->so_mode))); |
| |
| /* |
| * Preallocate the T_CONN_REQ mblk before grabbing SOLOCKED to |
| * avoid sleeping for memory with SOLOCKED held. |
| * We know that the T_CONN_REQ can't be larger than 2 * sti_faddr_maxlen |
| * + sizeof (struct T_opthdr). |
| * (the AF_UNIX so_ux_addr_xlate() does not make the address |
| * exceed sti_faddr_maxlen). |
| */ |
| mp = soallocproto(sizeof (struct T_conn_req) + |
| 2 * sti->sti_faddr_maxlen + sizeof (struct T_opthdr), _ALLOC_INTR, |
| cr); |
| if (mp == NULL) { |
| /* |
| * Connect can not fail with ENOBUFS. A signal was |
| * caught so return EINTR. |
| */ |
| error = EINTR; |
| eprintsoline(so, error); |
| return (error); |
| } |
| |
| mutex_enter(&so->so_lock); |
| /* |
| * Make sure there is a preallocated T_unbind_req message |
| * before any binding. This message is allocated when the |
| * socket is created. Since another thread can consume |
| * so_unbind_mp by the time we return from so_lock_single(), |
| * we should check the availability of so_unbind_mp after |
| * we return from so_lock_single(). |
| */ |
| |
| so_lock_single(so); /* Set SOLOCKED */ |
| need_unlock = B_TRUE; |
| |
| if (sti->sti_unbind_mp == NULL) { |
| dprintso(so, 1, ("sotpi_connect: allocating unbind_req\n")); |
| /* NOTE: holding so_lock while sleeping */ |
| sti->sti_unbind_mp = |
| soallocproto(sizeof (struct T_unbind_req), _ALLOC_INTR, cr); |
| if (sti->sti_unbind_mp == NULL) { |
| error = EINTR; |
| goto done; |
| } |
| } |
| |
| /* |
| * Can't have done a listen before connecting. |
| */ |
| if (so->so_state & SS_ACCEPTCONN) { |
| error = EOPNOTSUPP; |
| goto done; |
| } |
| |
| /* |
| * Must be bound with the transport |
| */ |
| if (!(so->so_state & SS_ISBOUND)) { |
| if ((so->so_family == AF_INET || so->so_family == AF_INET6) && |
| /*CONSTCOND*/ |
| so->so_type == SOCK_STREAM && !soconnect_tpi_tcp) { |
| /* |
| * Optimization for AF_INET{,6} transports |
| * that can handle a T_CONN_REQ without being bound. |
| */ |
| so_automatic_bind(so); |
| } else { |
| error = sotpi_bind(so, NULL, 0, |
| _SOBIND_UNSPEC|_SOBIND_LOCK_HELD, cr); |
| if (error) |
| goto done; |
| } |
| ASSERT(so->so_state & SS_ISBOUND); |
| flags |= _SOCONNECT_DID_BIND; |
| } |
| |
| /* |
| * Handle a connect to a name parameter of type AF_UNSPEC like a |
| * connect to a null address. This is the portable method to |
| * unconnect a socket. |
| */ |
| if ((namelen >= sizeof (sa_family_t)) && |
| (name->sa_family == AF_UNSPEC)) { |
| name = NULL; |
| namelen = 0; |
| } |
| |
| /* |
| * Check that we are not already connected. |
| * A connection-oriented socket cannot be reconnected. |
| * A connected connection-less socket can be |
| * - connected to a different address by a subsequent connect |
| * - "unconnected" by a connect to the NULL address |
| */ |
| if (so->so_state & (SS_ISCONNECTED|SS_ISCONNECTING)) { |
| ASSERT(!(flags & _SOCONNECT_DID_BIND)); |
| if (so->so_mode & SM_CONNREQUIRED) { |
| /* Connection-oriented socket */ |
| error = so->so_state & SS_ISCONNECTED ? |
| EISCONN : EALREADY; |
| goto done; |
| } |
| /* Connection-less socket */ |
| if (name == NULL) { |
| /* |
| * Remove the connected state and clear SO_DGRAM_ERRIND |
| * since it was set when the socket was connected. |
| * If this is UDP also send down a T_DISCON_REQ. |
| */ |
| int val; |
| |
| if ((so->so_family == AF_INET || |
| so->so_family == AF_INET6) && |
| (so->so_type == SOCK_DGRAM || |
| so->so_type == SOCK_RAW) && |
| /*CONSTCOND*/ |
| !soconnect_tpi_udp) { |
| /* XXX What about implicitly unbinding here? */ |
| error = sodisconnect(so, -1, |
| _SODISCONNECT_LOCK_HELD); |
| } else { |
| so->so_state &= |
| ~(SS_ISCONNECTED | SS_ISCONNECTING); |
| sti->sti_faddr_valid = 0; |
| sti->sti_faddr_len = 0; |
| } |
| |
| /* Remove SOLOCKED since setsockopt will grab it */ |
| so_unlock_single(so, SOLOCKED); |
| mutex_exit(&so->so_lock); |
| |
| val = 0; |
| (void) sotpi_setsockopt(so, SOL_SOCKET, |
| SO_DGRAM_ERRIND, &val, (t_uscalar_t)sizeof (val), |
| cr); |
| |
| mutex_enter(&so->so_lock); |
| so_lock_single(so); /* Set SOLOCKED */ |
| goto done; |
| } |
| } |
| ASSERT(so->so_state & SS_ISBOUND); |
| |
| if (name == NULL || namelen == 0) { |
| error = EINVAL; |
| goto done; |
| } |
| /* |
| * Mark the socket if sti_faddr_sa represents the transport level |
| * address. |
| */ |
| if (flags & _SOCONNECT_NOXLATE) { |
| struct sockaddr_ux *soaddr_ux; |
| |
| ASSERT(so->so_family == AF_UNIX); |
| if (namelen != sizeof (struct sockaddr_ux)) { |
| error = EINVAL; |
| goto done; |
| } |
| soaddr_ux = (struct sockaddr_ux *)name; |
| name = (struct sockaddr *)&soaddr_ux->sou_addr; |
| namelen = sizeof (soaddr_ux->sou_addr); |
| sti->sti_faddr_noxlate = 1; |
| } |
| |
| /* |
| * Length and family checks. |
| */ |
| error = so_addr_verify(so, name, namelen); |
| if (error) |
| goto bad; |
| |
| /* |
| * Save foreign address. Needed for AF_UNIX as well as |
| * transport providers that do not support TI_GETPEERNAME. |
| * Also used for cached foreign address for TCP and UDP. |
| */ |
| if (namelen > (t_uscalar_t)sti->sti_faddr_maxlen) { |
| error = EINVAL; |
| goto done; |
| } |
| sti->sti_faddr_len = (socklen_t)namelen; |
| ASSERT(sti->sti_faddr_len <= sti->sti_faddr_maxlen); |
| bcopy(name, sti->sti_faddr_sa, namelen); |
| sti->sti_faddr_valid = 1; |
| |
| if (so->so_family == AF_UNIX) { |
| if (sti->sti_faddr_noxlate) { |
| /* |
| * Already have a transport internal address. Do not |
| * pass any (transport internal) source address. |
| */ |
| addr = sti->sti_faddr_sa; |
| addrlen = (t_uscalar_t)sti->sti_faddr_len; |
| src = NULL; |
| srclen = 0; |
| } else { |
| /* |
| * Pass the sockaddr_un source address as an option |
| * and translate the remote address. |
| * Holding so_lock thus sti_laddr_sa can not change. |
| */ |
| src = sti->sti_laddr_sa; |
| srclen = (t_uscalar_t)sti->sti_laddr_len; |
| dprintso(so, 1, |
| ("sotpi_connect UNIX: srclen %d, src %p\n", |
| srclen, src)); |
| error = so_ux_addr_xlate(so, |
| sti->sti_faddr_sa, (socklen_t)sti->sti_faddr_len, |
| (flags & _SOCONNECT_XPG4_2), |
| &addr, &addrlen); |
| if (error) |
| goto bad; |
| } |
| } else { |
| addr = sti->sti_faddr_sa; |
| addrlen = (t_uscalar_t)sti->sti_faddr_len; |
| src = NULL; |
| srclen = 0; |
| } |
| /* |
| * When connecting a datagram socket we issue the SO_DGRAM_ERRIND |
| * option which asks the transport provider to send T_UDERR_IND |
| * messages. These T_UDERR_IND messages are used to return connected |
| * style errors (e.g. ECONNRESET) for connected datagram sockets. |
| * |
| * In addition, for UDP (and SOCK_RAW AF_INET{,6} sockets) |
| * we send down a T_CONN_REQ. This is needed to let the |
| * transport assign a local address that is consistent with |
| * the remote address. Applications depend on a getsockname() |
| * after a connect() to retrieve the "source" IP address for |
| * the connected socket. Invalidate the cached local address |
| * to force getsockname() to enquire of the transport. |
| */ |
| if (!(so->so_mode & SM_CONNREQUIRED)) { |
| /* |
| * Datagram socket. |
| */ |
| int32_t val; |
| |
| so_unlock_single(so, SOLOCKED); |
| mutex_exit(&so->so_lock); |
| |
| val = 1; |
| (void) sotpi_setsockopt(so, SOL_SOCKET, SO_DGRAM_ERRIND, |
| &val, (t_uscalar_t)sizeof (val), cr); |
| |
| mutex_enter(&so->so_lock); |
| so_lock_single(so); /* Set SOLOCKED */ |
| if ((so->so_family != AF_INET && so->so_family != AF_INET6) || |
| (so->so_type != SOCK_DGRAM && so->so_type != SOCK_RAW) || |
| soconnect_tpi_udp) { |
| soisconnected(so); |
| goto done; |
| } |
| /* |
| * Send down T_CONN_REQ etc. |
| * Clear fflag to avoid returning EWOULDBLOCK. |
| */ |
| fflag = 0; |
| ASSERT(so->so_family != AF_UNIX); |
| sti->sti_laddr_valid = 0; |
| } else if (sti->sti_laddr_len != 0) { |
| /* |
| * If the local address or port was "any" then it may be |
| * changed by the transport as a result of the |
| * connect. Invalidate the cached version if we have one. |
| */ |
| switch (so->so_family) { |
| case AF_INET: |
| ASSERT(sti->sti_laddr_len == (socklen_t)sizeof (sin_t)); |
| if (((sin_t *)sti->sti_laddr_sa)->sin_addr.s_addr == |
| INADDR_ANY || |
| ((sin_t *)sti->sti_laddr_sa)->sin_port == 0) |
| sti->sti_laddr_valid = 0; |
| break; |
| |
| case AF_INET6: |
| ASSERT(sti->sti_laddr_len == |
| (socklen_t)sizeof (sin6_t)); |
| if (IN6_IS_ADDR_UNSPECIFIED( |
| &((sin6_t *)sti->sti_laddr_sa) ->sin6_addr) || |
| IN6_IS_ADDR_V4MAPPED_ANY( |
| &((sin6_t *)sti->sti_laddr_sa)->sin6_addr) || |
| ((sin6_t *)sti->sti_laddr_sa)->sin6_port == 0) |
| sti->sti_laddr_valid = 0; |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| /* |
| * Check for failure of an earlier call |
| */ |
| if (so->so_error != 0) |
| goto so_bad; |
| |
| /* |
| * Send down T_CONN_REQ. Message was allocated above. |
| */ |
| conn_req.PRIM_type = T_CONN_REQ; |
| conn_req.DEST_length = addrlen; |
| conn_req.DEST_offset = (t_scalar_t)sizeof (conn_req); |
| if (srclen == 0) { |
| conn_req.OPT_length = 0; |
| conn_req.OPT_offset = 0; |
| soappendmsg(mp, &conn_req, sizeof (conn_req)); |
| soappendmsg(mp, addr, addrlen); |
| } else { |
| /* |
| * There is a AF_UNIX sockaddr_un to include as a source |
| * address option. |
| */ |
| struct T_opthdr toh; |
| |
| toh.level = SOL_SOCKET; |
| toh.name = SO_SRCADDR; |
| toh.len = (t_uscalar_t)(srclen + sizeof (struct T_opthdr)); |
| toh.status = 0; |
| conn_req.OPT_length = |
| (t_scalar_t)(sizeof (toh) + _TPI_ALIGN_TOPT(srclen)); |
| conn_req.OPT_offset = (t_scalar_t)(sizeof (conn_req) + |
| _TPI_ALIGN_TOPT(addrlen)); |
| |
| soappendmsg(mp, &conn_req, sizeof (conn_req)); |
| soappendmsg(mp, addr, addrlen); |
| mp->b_wptr += _TPI_ALIGN_TOPT(addrlen) - addrlen; |
| soappendmsg(mp, &toh, sizeof (toh)); |
| soappendmsg(mp, src, srclen); |
| mp->b_wptr += _TPI_ALIGN_TOPT(srclen) - srclen; |
| ASSERT(mp->b_wptr <= mp->b_datap->db_lim); |
| } |
| /* |
| * Set SS_ISCONNECTING before sending down the T_CONN_REQ |
| * in order to have the right state when the T_CONN_CON shows up. |
| */ |
| soisconnecting(so); |
| mutex_exit(&so->so_lock); |
| |
| if (audit_active) |
| audit_sock(T_CONN_REQ, strvp2wq(SOTOV(so)), mp, 0); |
| |
| error = kstrputmsg(SOTOV(so), mp, NULL, 0, 0, |
| MSG_BAND|MSG_HOLDSIG|MSG_IGNERROR, 0); |
| mp = NULL; |
| mutex_enter(&so->so_lock); |
| if (error != 0) |
| goto bad; |
| |
| if ((error = sowaitokack(so, T_CONN_REQ)) != 0) |
| goto bad; |
| |
| /* Allow other threads to access the socket */ |
| so_unlock_single(so, SOLOCKED); |
| need_unlock = B_FALSE; |
| |
| /* |
| * Wait until we get a T_CONN_CON or an error |
| */ |
| if ((error = sowaitconnected(so, fflag, 0)) != 0) { |
| so_lock_single(so); /* Set SOLOCKED */ |
| need_unlock = B_TRUE; |
| } |
| |
| done: |
| freemsg(mp); |
| switch (error) { |
| case EINPROGRESS: |
| case EALREADY: |
| case EISCONN: |
| case EINTR: |
| /* Non-fatal errors */ |
| sti->sti_laddr_valid = 0; |
| /* FALLTHRU */ |
| case 0: |
| break; |
| default: |
| ASSERT(need_unlock); |
| /* |
| * Fatal errors: clear SS_ISCONNECTING in case it was set, |
| * and invalidate local-address cache |
| */ |
| so->so_state &= ~SS_ISCONNECTING; |
| sti->sti_laddr_valid = 0; |
| /* A discon_ind might have already unbound us */ |
| if ((flags & _SOCONNECT_DID_BIND) && |
| (so->so_state & SS_ISBOUND)) { |
| int err; |
| |
| err = sotpi_unbind(so, 0); |
| /* LINTED - statement has no conseq */ |
| if (err) { |
| eprintsoline(so, err); |
| } |
| } |
| break; |
| } |
| if (need_unlock) |
| so_unlock_single(so, SOLOCKED); |
| mutex_exit(&so->so_lock); |
| return (error); |
| |
| so_bad: error = sogeterr(so, B_TRUE); |
| bad: eprintsoline(so, error); |
| goto done; |
| } |
| |
| /* ARGSUSED */ |
| int |
| sotpi_shutdown(struct sonode *so, int how, struct cred *cr) |
| { |
| struct T_ordrel_req ordrel_req; |
| mblk_t *mp; |
| uint_t old_state, state_change; |
| int error = 0; |
| sotpi_info_t *sti = SOTOTPI(so); |
| |
| dprintso(so, 1, ("sotpi_shutdown(%p, %d) %s\n", |
| (void *)so, how, pr_state(so->so_state, so->so_mode))); |
| |
| mutex_enter(&so->so_lock); |
| so_lock_single(so); /* Set SOLOCKED */ |
| |
| /* |
| * SunOS 4.X has no check for datagram sockets. |
| * 5.X checks that it is connected (ENOTCONN) |
| * X/Open requires that we check the connected state. |
| */ |
| if (!(so->so_state & SS_ISCONNECTED)) { |
| if (!xnet_skip_checks) { |
| error = ENOTCONN; |
| if (xnet_check_print) { |
| printf("sockfs: X/Open shutdown check " |
| "caused ENOTCONN\n"); |
| } |
| } |
| goto done; |
| } |
| /* |
| * Record the current state and then perform any state changes. |
| * Then use the difference between the old and new states to |
| * determine which messages need to be sent. |
| * This prevents e.g. duplicate T_ORDREL_REQ when there are |
| * duplicate calls to shutdown(). |
| */ |
| old_state = so->so_state; |
| |
| switch (how) { |
| case 0: |
| socantrcvmore(so); |
| break; |
| case 1: |
| socantsendmore(so); |
| break; |
| case 2: |
| socantsendmore(so); |
| socantrcvmore(so); |
| break; |
| default: |
| error = EINVAL; |
| goto done; |
| } |
| |
| /* |
| * Assumes that the SS_CANT* flags are never cleared in the above code. |
| */ |
| state_change = (so->so_state & (SS_CANTRCVMORE|SS_CANTSENDMORE)) - |
| (old_state & (SS_CANTRCVMORE|SS_CANTSENDMORE)); |
| ASSERT((state_change & ~(SS_CANTRCVMORE|SS_CANTSENDMORE)) == 0); |
| |
| switch (state_change) { |
| case 0: |
| dprintso(so, 1, |
| ("sotpi_shutdown: nothing to send in state 0x%x\n", |
| so->so_state)); |
| goto done; |
| |
| case SS_CANTRCVMORE: |
| mutex_exit(&so->so_lock); |
| strseteof(SOTOV(so), 1); |
| /* |
| * strseteof takes care of read side wakeups, |
| * pollwakeups, and signals. |
| */ |
| /* |
| * Get the read lock before flushing data to avoid problems |
| * with the T_EXDATA_IND MSG_PEEK code in sotpi_recvmsg. |
| */ |
| mutex_enter(&so->so_lock); |
| (void) so_lock_read(so, 0); /* Set SOREADLOCKED */ |
| mutex_exit(&so->so_lock); |
| |
| /* Flush read side queue */ |
| strflushrq(SOTOV(so), FLUSHALL); |
| |
| mutex_enter(&so->so_lock); |
| so_unlock_read(so); /* Clear SOREADLOCKED */ |
| break; |
| |
| case SS_CANTSENDMORE: |
| mutex_exit(&so->so_lock); |
| strsetwerror(SOTOV(so), 0, 0, sogetwrerr); |
| mutex_enter(&so->so_lock); |
| break; |
| |
| case SS_CANTSENDMORE|SS_CANTRCVMORE: |
| mutex_exit(&so->so_lock); |
| strsetwerror(SOTOV(so), 0, 0, sogetwrerr); |
| strseteof(SOTOV(so), 1); |
| /* |
| * strseteof takes care of read side wakeups, |
| * pollwakeups, and signals. |
| */ |
| /* |
| * Get the read lock before flushing data to avoid problems |
| * with the T_EXDATA_IND MSG_PEEK code in sotpi_recvmsg. |
| */ |
| mutex_enter(&so->so_lock); |
| (void) so_lock_read(so, 0); /* Set SOREADLOCKED */ |
| mutex_exit(&so->so_lock); |
| |
| /* Flush read side queue */ |
| strflushrq(SOTOV(so), FLUSHALL); |
| |
| mutex_enter(&so->so_lock); |
| so_unlock_read(so); /* Clear SOREADLOCKED */ |
| break; |
| } |
| |
| ASSERT(MUTEX_HELD(&so->so_lock)); |
| |
| /* |
| * If either SS_CANTSENDMORE or SS_CANTRCVMORE or both of them |
| * was set due to this call and the new state has both of them set: |
| * Send the AF_UNIX close indication |
| * For T_COTS send a discon_ind |
| * |
| * If cantsend was set due to this call: |
| * For T_COTSORD send an ordrel_ind |
| * |
| * Note that for T_CLTS there is no message sent here. |
| */ |
| if ((so->so_state & (SS_CANTRCVMORE|SS_CANTSENDMORE)) == |
| (SS_CANTRCVMORE|SS_CANTSENDMORE)) { |
| /* |
| * For SunOS 4.X compatibility we tell the other end |
| * that we are unable to receive at this point. |
| */ |
| if (so->so_family == AF_UNIX && sti->sti_serv_type != T_CLTS) |
| so_unix_close(so); |
| |
| if (sti->sti_serv_type == T_COTS) |
| error = sodisconnect(so, -1, _SODISCONNECT_LOCK_HELD); |
| } |
| if ((state_change & SS_CANTSENDMORE) && |
| (sti->sti_serv_type == T_COTS_ORD)) { |
| /* Send an orderly release */ |
| ordrel_req.PRIM_type = T_ORDREL_REQ; |
| |
| mutex_exit(&so->so_lock); |
| mp = soallocproto1(&ordrel_req, sizeof (ordrel_req), |
| 0, _ALLOC_SLEEP, cr); |
| /* |
| * Send down the T_ORDREL_REQ even if there is flow control. |
| * This prevents shutdown from blocking. |
| * Note that there is no T_OK_ACK for ordrel_req. |
| */ |
| error = kstrputmsg(SOTOV(so), mp, NULL, 0, 0, |
| MSG_BAND|MSG_HOLDSIG|MSG_IGNERROR|MSG_IGNFLOW, 0); |
| mutex_enter(&so->so_lock); |
| if (error) { |
| eprintsoline(so, error); |
| goto done; |
| } |
| } |
| |
| done: |
| so_unlock_single(so, SOLOCKED); |
| mutex_exit(&so->so_lock); |
| return (error); |
| } |
| |
| /* |
| * For any connected SOCK_STREAM/SOCK_SEQPACKET AF_UNIX socket we send |
| * a zero-length T_OPTDATA_REQ with the SO_UNIX_CLOSE option to inform the peer |
| * that we have closed. |
| * Also, for connected AF_UNIX SOCK_DGRAM sockets we send a zero-length |
| * T_UNITDATA_REQ containing the same option. |
| * |
| * For SOCK_DGRAM half-connections (somebody connected to this end |
| * but this end is not connect) we don't know where to send any |
| * SO_UNIX_CLOSE. |
| * |
| * We have to ignore stream head errors just in case there has been |
| * a shutdown(output). |
| * Ignore any flow control to try to get the message more quickly to the peer. |
| * While locally ignoring flow control solves the problem when there |
| * is only the loopback transport on the stream it would not provide |
| * the correct AF_UNIX socket semantics when one or more modules have |
| * been pushed. |
| */ |
| void |
| so_unix_close(struct sonode *so) |
| { |
| int error; |
| struct T_opthdr toh; |
| mblk_t * |