/* uncomp_usrreq.c - AF_LOCAL/COMP protocol implementation */ /* * Copyright (c) 2003-2009, 2011-2013 Wind River Systems, Inc. * * The right to copy, distribute, modify or otherwise make use * of this software may be licensed only pursuant to the terms * of an applicable Wind River license agreement. */ /* modification history -------------------- 01z,04sep13,xms fix TAINTED_SCALAR error. (WIND00414265) 01y,11jul12,shi clean up warnings (WIND00357529) 01x,28jun11,shi LP64 conversion 01w,21apr09,hps LP64: move drv_val_t to private header iosLibP.h 01v,12may08,jpb Modifications for source build. Renamed WV_INSTRUMENTATION to _WRS_CONFIG_SV_INSTRUMENTATION. 01u,19jun07,ebh Changes to allow COMP to run on SMP systems (SMP-safe) 01t,04may07,ebh update to handle EINTR on sends (Defect #91328) 01s,06sep06,kch corrected diab compiler errors. 01r,06sep06,kch cleanup coreip stack header files. 01q,21feb05,ebh update manually (zippy merge was not running) 01p,21jan05,vvv osdep.h cleanup 01o,03jan05,bwa BUG FIX: connect() returning wrong errno (SPR #102896). 01n,11oct04,p_r fixed pathname for dsi header (SPR #102446) 01m,30aug04,bwa fixed shutdown() exception when done on closed socket. 01l,13jul04,p_r removed e-mail address from comment (SPR #99392) 01k,01jun04,bwa added flow control (SPR #97987). 01j,26may04,bwa added WV instrumentation; fixed 'netun' includes (SPR #97714). 01i,23apr04,bwa removed references to soXXX routines. 01h,12apr04,bwa added '#include '; #undef'ed DEBUG_SHOW. 01g,04mar04,bwa changed 'netinet' directory references to 'netun'; dropped max data queued from 64k to 64k-1; optimized uncomp_send() and uncomp_rcvd() for the reg case; BUG FIX: buffer leak when ENOTCONN and EINVAL error in send(). 01f,10feb04,bwa added express messages (MSG_EXP) support; BUG FIX: non-reclamation of buffers when EMSGSIZE error; 01e,06jan04,bwa BUG FIX: connect-send-close-accept sequence was not allowed. 01d,05dec03,xli change the include path; 01c,03dec03,bwa BUG FIX: peer's so_rcv.sb_cc was not updated properly; BUG FIX: shutdown(0) would still allow send from peer; BUG FIX: could send data larger than receiver's buffer. 01b,26nov03,bwa wrote explanation about DIAB warning when testing sblock() return value; cleanup of warnings generated by GNU. 01a,03nov03,bwa written. */ /* DESCRIPTION This library implements the major protocol-specific routines of the AF_LOCAL/COMP protocol for sockets. The routines present in here are not to be called directly; they have to be registered in the AF_LOCAL protocol switch entry for COMP. They are then called by the socket upper layers (sockLib, bsdSockLib, uipc_socket libraries) when needed. INTERNAL: SYSTEM VIEWER INSTRUMENTATION Network Critical instrumentation level. NOMANUAL */ /* include files */ /* VxWorks system includes */ #include #include /* for strlen() */ #include /* for (s)printf() */ #include /* for printErrno() */ #include /* for objNameSet() */ #include /* for malloc/free */ #include /* networking includes */ #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef _WRS_CONFIG_SV_INSTRUMENTATION #ifdef INCLUDE_WVNETD /* Set common fields of event identifiers for this module. */ LOCAL UCHAR wvNetDModuleId = WV_NETD_UNCOMP_USRREQ_MODULE; /* uncomp_usrreq.c */ LOCAL UCHAR wvNetDLocalFilter = WV_NETD_NONE; /* Available event filter */ LOCAL UCHAR wvNetDEntityId = WV_NETD_SOCKET; /* Entity ID */ #endif /* INCLUDE_WVNETD */ #endif /* _WRS_CONFIG_SV_INSTRUMENTATION */ /* set limits for largest message possible (64k-1 for COMP) */ /* XXX : NOTE - These limits should be the size of buffers allocated for UNCOMP * usage. HOWEVER, this is not implemented yet and what it does * is only enforcing limits on send() and recv() operations. * It is thus impossible to send a packet larger than 64kB and * impossible for the receiving socket to hold more than 64kB of * data at once. */ LOCAL uint32_t uncomp_sendspace = (64 * 1024) - 1; /* UNCOMP send buffers */ LOCAL uint32_t uncomp_recvspace = (64 * 1024) - 1; /* UNCOMP recv buffers */ /* * This is the list of AF_LOCAL/COMP sockets. It is a doubly-linked list in * which the sockets are queued in ascending order by their name. If sockets * a created following a connection and they are given the same name as the * listening socket, they are queued after the parent, in the reverse order * that they were created. One exception: socket that are not yet named, and * thus sport the invalid name '0x0', are queued at the end of the list. */ extern DL_LIST uncompSocketList; /* list of AF_LOCAL/COMP sockets */ /* local defines */ #define UNCOMP_NAME_PREFIX "/comp/socket/0x" #define UNCOMP_NAME_PREFIX_LEN 15 #define UNCOMP_NAME_SUFFIX_START 15 #define UNCOMP_NAME_LEN_MAX 19 #define UNCOMP_INVALID_NAME 0x0 #define UNCOMP_FIRST_VALID_NAME 0x1 #define UNCOMP_LAST_VALID_NAME 0xffff #define UNCOMP_OFFSET_ASCII_0 0x30 #define UNCOMP_OFFSET_ASCII_A 0x37 #define UNCOMP_OFFSET_ASCII_a 0x57 /* debugging definitions */ #define UNCOMP_DEBUG 0 #define UNCOMP_DEBUG_SHOW 0 #define UNCOMP_USE_OLD_BSD_STUFF 0 /* function prototypes */ LOCAL int uncomp_abort (struct socket *so); LOCAL int uncomp_accept (struct socket *so, struct sockaddr **nam); LOCAL int uncomp_attach (struct socket *so, int proto, struct proc *p); LOCAL int uncomp_bind (struct socket *so, struct sockaddr *nam, struct proc *p); LOCAL int uncomp_connect (struct socket *so, struct sockaddr *nam, struct proc *p); LOCAL int uncomp_connect2 (struct socket *so1, struct socket *so2); LOCAL int uncomp_disconnect (struct socket *so); LOCAL int uncomp_detach (struct socket *so); LOCAL int uncomp_listen (struct socket *so, struct proc *p); LOCAL int uncomp_peeraddr (struct socket *so, struct sockaddr **nam); LOCAL int uncomp_rcvd (struct socket * so, int flags); LOCAL int uncomp_send (struct socket *so, int flags, struct mbuf *m, struct sockaddr *nam, struct mbuf *control, struct proc *p); LOCAL int uncomp_shutdown (struct socket *so); LOCAL int uncomp_sockaddr (struct socket *so, struct sockaddr **nam); /* create and fill in the protocol switch entry function table */ struct pr_usrreqs uncomp_usrreqs = { uncomp_abort, uncomp_accept, uncomp_attach, uncomp_bind, uncomp_connect, uncomp_connect2, dsiControlNotSupp, uncomp_detach, uncomp_disconnect, uncomp_listen, uncomp_peeraddr, uncomp_rcvd, dsiRcvoobNotSupp, uncomp_send, dsiSenseNull, uncomp_shutdown, uncomp_sockaddr, NULL, NULL, NULL }; /* convenience routines */ /******************************************************************************* * * uncomp_firstAvailableNameGet - Returns first available COMP socket name * * RETURNS: A 16-bit number (the socket name), suffix of /comp/socket/0xXXXX, or * UNCOMP_INVALID_NAME if list of names is exhausted * * POSSIBLE ERROR VALUES: N/A * * NOMANUAL */ LOCAL uint16_t uncomp_firstAvailableNameGet ( UNCOMPCB ** ppSlot /* container, place where to insert the socket */ ) { UNCOMPCB * pUnComp = (UNCOMPCB *) DLL_FIRST (&uncompSocketList); UNCOMPCB * pUnCompPrev; /* is list empty ? */ if (pUnComp == NULL) { *ppSlot = NULL; return (UNCOMP_FIRST_VALID_NAME); } /* is slot before first node ? */ if (pUnComp->selfName != UNCOMP_FIRST_VALID_NAME) { *ppSlot = NULL; return (UNCOMP_FIRST_VALID_NAME); } FOREVER /* find a slot: exhaust the list */ { pUnCompPrev = pUnComp; FOREVER /* skip name duplicates */ { /* if there are duplicates up until the tail, tail is the slot */ if ((UNCOMPCB *) DLL_NEXT ((DL_NODE *)pUnComp) == NULL) { /* are all (2^16)-1 names already taken ? (HIGHLY unlikely) */ if (pUnComp->selfName == UNCOMP_LAST_VALID_NAME) return (UNCOMP_INVALID_NAME); /* if not, new socket will be inserted after the current one */ *ppSlot = pUnComp; return (uint16_t)(pUnComp->selfName + 1); } /* next node is not NULL, move current indicator */ pUnComp = (UNCOMPCB *) DLL_NEXT ((DL_NODE *)pUnComp); /* check if previous name == current name... */ if (pUnComp->selfName != pUnCompPrev->selfName) { /* ...it is not */ /* if (cur name != prev+1), there is a gap: this is the slot */ if (pUnComp->selfName != (pUnCompPrev->selfName + 1)) { *ppSlot = (UNCOMPCB *) DLL_PREVIOUS ((DL_NODE *)pUnComp); return (uint16_t)((*ppSlot)->selfName + 1); } else /* ...it is: no gap, continue searching */ break; } /* * if we get here, two contiguous sockets have the same name: * continue the loop to discard all duplicates */ } } } /******************************************************************************* * * uncomp_nameDecode - extract the suffix from an AF_LOCAL/COMP socket name * * RETURNS: the XXXX part of /comp/socket/0xXXXX or UNCOMP_INVALID_NAME if * illegal string * * POSSIBLE ERROR VALUES: N/A * * NOMANUAL */ LOCAL uint16_t uncomp_nameDecode ( char * pName /* UNCOMP name in the format /comp/socket/0xXXXX */ ) { uint16_t suffix = UNCOMP_INVALID_NAME; int index = UNCOMP_NAME_PREFIX_LEN; int len; pName[103] = '\0'; len = (int)strlen (pName); /* * only names in the format /comp/socket/0xXXXX are accepted, where * XXXX is a one-to-four digits hexadecimal number (i.e. 0x1 to 0xFFFF) */ /* verify lower and upper name len boundaries */ if (len < UNCOMP_NAME_PREFIX_LEN + 1) /* at least prefix + 1 */ return (UNCOMP_INVALID_NAME); if (len > UNCOMP_NAME_LEN_MAX) /* at max prefix + 0xFFFF */ return (UNCOMP_INVALID_NAME); /* verify the prefix passed is '/comp/socket/0x' */ if (strncmp (pName, UNCOMP_NAME_PREFIX, UNCOMP_NAME_PREFIX_LEN) != 0) return UNCOMP_INVALID_NAME; /* loop to numerize all digits in a XXXX hexadecimal character string */ len -= UNCOMP_NAME_PREFIX_LEN; for (; len > 0; len--,index++) { if (pName[index] >= '0' && pName[index] <= '9') suffix += (uint16_t)((pName[index] - UNCOMP_OFFSET_ASCII_0) << ((len-1) * 4)); else if (pName[index] >= 'A' && pName[index] <= 'F') suffix += (uint16_t)((pName[index] - UNCOMP_OFFSET_ASCII_A) << ((len-1) * 4)); else if (pName[index] >= 'a' && pName[index] <= 'f') suffix += (uint16_t)((pName[index] - UNCOMP_OFFSET_ASCII_a) << ((len-1) * 4)); else return UNCOMP_INVALID_NAME; } return (suffix); } /******************************************************************************* * * uncomp_acceptSocketFind - find the accepting socket by name * * RETURNS: the accepting socket structure, or NULL if not found * * POSSIBLE ERROR VALUES: N/A * * NOMANUAL */ LOCAL struct socket * uncomp_acceptSocketFind ( struct sockaddr_un * pName /* name of the socket to find, in format: */ /* /comp/socket/0xXXXX */ ) { /* numerize name */ uint16_t name = uncomp_nameDecode (pName->sun_path); /* grab first uncomp socket */ DL_NODE * pCur = (DL_NODE *) DLL_FIRST (&uncompSocketList); /* something's wrong ... */ if ((name == UNCOMP_INVALID_NAME) || (pCur == NULL)) return (NULL); /* * loop through sockets list to find the correct accepting socket; * the correct one is one with the correct name and free space on the * accepting queue; bail out early if we're passed the wanted name. */ do { if (((UNCOMPCB *)pCur)->selfName == name) if (((UNCOMPCB *)pCur)->pSelfSocket->so_qlen < ((UNCOMPCB *)pCur)->pSelfSocket->so_qlimit) break; if (((UNCOMPCB *)pCur)->selfName > name) { pCur = NULL; break; } } while ((pCur = (DL_NODE *) DLL_NEXT (pCur)) != NULL); if (pCur != NULL) return (((UNCOMPCB *)pCur)->pSelfSocket); else return (NULL); } /******************************************************************************* * * uncomp_dupsockaddr - duplicate the sockaddr_un of a socket * * Memory is allocated, DSI_ADDR_FREE() must be called on the return value later * if memory is to be reclaimed. * * RETURNS: the new instance of a sockaddr_un, or NULL if allocation failed * * POSSIBLE ERROR VALUES: N/A * * NOMANUAL */ LOCAL struct sockaddr_un * uncomp_dupsockaddr ( UNCOMPCB * pUnComp /* the AF_LOCAL block in which to fetch the address */ ) { struct sockaddr_un * pName; int inc = UNCOMP_NAME_SUFFIX_START; /* increment counter */ char temp; /* temp holder for char */ int nameSize, pos; pName = DSI_ADDR_ALLOC (struct sockaddr_un); /* allocate name structure */ if (pName == NULL) return NULL; pName->sun_len = UN_SOCKADDR_UN_SIZE; /* length of structure */ pName->sun_family = AF_LOCAL; /* address family */ strcpy (pName->sun_path, UNCOMP_NAME_PREFIX); /* prepend prefix */ /* go through all digits in the name and convert them to characters */ for (nameSize = 0x1000, pos = 4; pos > 0; nameSize >>= 4, pos--) { if (pUnComp->selfName >= nameSize) { temp = (char)((pUnComp->selfName >> ((pos - 1) * 4)) & 0xf); temp += (char)(temp > 9 ? UNCOMP_OFFSET_ASCII_a : UNCOMP_OFFSET_ASCII_0); pName->sun_path[inc++] = temp; } } if (pUnComp->selfName == 0x0) pName->sun_path[inc++] = '0'; pName->sun_path[inc] = '\0'; return (pName); } #if UNCOMP_DEBUG_SHOW /******************************************************************************* * * uncomp_socketDump - dumps information about an AF_LOCAL/COMP socket * * Debug-only routine * * Usage: * 'so' parameter: fd or socket pointer * 'type' parameter: 0 tells 'so' is an fd, 1 tells 'so' is a pointer * * RETURNS: N/A * * POSSIBLE ERROR VALUES: N/A * * NOMANUAL */ void uncomp_socketDump ( long so, /* socket to dump */ int type /* 0 = fd, 1 = socket ptr */ ) { char * uncomp_stateGet (uint16_t state, char * pBuffer); struct socket * pSocket = (type ? (struct socket *)so : (struct socket *)iosFdValue(so)); UNCOMPCB * pUnComp = (UNCOMPCB *)pSocket->so_pcb; struct mbuf * pNext; char pState[20]; printf ("--------------- %d ----------------\n", pSocket->so_fd); printf ("socket dump for so %d\n", so); printf ("socket structure: 0x%lx\n", (long)pSocket); printf ("pSocket->so_state: 0x%x\n", pSocket->so_state); printf ("pSocket->so_options: 0x%x\n", pSocket->so_options); printf ("pSocket->so_head: 0x%lx\n", (long)pSocket->so_head); printf ("pSocket->so_qlen: %d\n", pSocket->so_qlen); printf ("first unaccepted socket: 0x%lx\n", (long)pSocket->so_comp.tqh_first); printf ("pSocket->so_qlimit: %d\n", pSocket->so_qlimit); printf ("pSocket->so_fd: %d\n", pSocket->so_fd); printf ("&pSocket->so_snd: 0x%lx\n", (long)&pSocket->so_snd); printf ("pSocket->so_snd.sb_lowat:0x%lx\n", (long)pSocket->so_snd.sb_lowat); printf ("pSocket->so_snd.sb_hiwat:0x%lx\n", (long)pSocket->so_snd.sb_hiwat); printf ("pSocket->so_snd.sb_mb: 0x%lx\n", (long)pSocket->so_snd.sb_mb); printf ("&pSocket->so_rcv: 0x%lx\n", (long)&pSocket->so_rcv); printf ("pSocket->so_rcv.sb_lowat:0x%lx\n", (long)pSocket->so_rcv.sb_lowat); printf ("pSocket->so_rcv.sb_hiwat:0x%lx\n", (long)pSocket->so_rcv.sb_hiwat); printf ("pSocket->so_rcv.sb_cc: 0x%lx\n", (long)pSocket->so_rcv.sb_cc); printf ("pSocket->so_rcv.sb_mb: 0x%lx, len = %d\n", (long)pSocket->so_rcv.sb_mb, pSocket->so_rcv.sb_mb != NULL ? pSocket->so_rcv.sb_mb->m_len : 0); if (pSocket->so_rcv.sb_mb == NULL) goto uncomp_tag; pNext = pSocket->so_rcv.sb_mb->m_nextpkt; while (pNext) { printf (" 0x%lx, len = %d\n", (long)pNext, pNext->m_len); pNext = pNext->m_nextpkt; } uncomp_tag: printf ("COMP: 0x%lx\n", (long)pUnComp); printf ("COMP->pSelfSocket: 0x%lx\n", (long)pUnComp->pSelfSocket); printf ("COMP->pPeerSocket: 0x%lx\n", (long)pUnComp->pPeerSocket); printf ("COMP->selfName: 0x%x\n", pUnComp->selfName); printf ("COMP->selfState: %s\n", uncomp_stateGet (pUnComp->selfState, pState)); printf ("----------------------------------\n"); } #endif /* UNCOMP_DEBUG_SHOW */ /******************************************************************************* * * uncomp_stateGet - returns a string for an AF_LOCAL/COMP state number * * Note: pBuffer is modified. * * RETURNS: the state string * * POSSIBLE ERROR VALUES: N/A * * NOMANUAL */ char * uncomp_stateGet ( uint16_t state, /* state, UNCOMPS_XXX value */ char * pBuffer /* where to return the string */ ) { switch (state) { case UNCOMPS_CLOSED: bcopy ("CLOSED", pBuffer, strlen("CLOSED")+1 ); break; case UNCOMPS_LISTENING: bcopy ("LISTENING", pBuffer, strlen("LISTENING")+1 ); break; case UNCOMPS_EXCHANGING: bcopy ("EXCHANGING", pBuffer, strlen("EXCHANGING")+1); break; case UNCOMPS_DONE_SEND: bcopy ("DONE_SEND", pBuffer, strlen("DONE_SEND")+1 ); break; case UNCOMPS_DONE_RECV: bcopy ("DONE_RECV", pBuffer, strlen("DONE_RECV")+1 ); break; case UNCOMPS_REQUEST: bcopy ("REQUEST", pBuffer, strlen("REQUEST")+1 ); break; } return pBuffer; } #if UNCOMP_DEBUG_SHOW /******************************************************************************* * * uncomp_sdl - dumps the list of AF_LOCAL/COMP sockets and all their info * * Debug-only routine. 'sdl' stands for 'Socket Dump List'. * * RETURNS: N/A * * POSSIBLE ERROR VALUES: N/A * * NOMANUAL */ void uncomp_sdl (void) { DL_NODE * pCur = (DL_NODE *) DLL_FIRST (&uncompSocketList); if (pCur == NULL) { printf ("uncompSocketList is empty\n"); return; } do { uncomp_socketDump ((long)((UNCOMPCB *)pCur)->pSelfSocket, 1); } while ((pCur = (DL_NODE *) DLL_NEXT (pCur)) != NULL); } #endif /* UNCOMP_DEBUG_SHOW */ /* Local COMP protocol implementation */ /******************************************************************************* * * uncomp_abort - AF_LOCAL/COMP pru_abort routine * * Disconnect and detach a local socket and its peer; called when a close() * command is issued, only when sockets are pending on the listen socket. * * RETURNS: 0 on success or an error value * * POSSIBLE ERROR VALUES: * EINVAL * * NOMANUAL */ LOCAL int uncomp_abort ( struct socket *so /* socket on which to abort */ ) { UNCOMPCB * pUnComp = SO_TO_UNCOMPCB(so); if (pUnComp == NULL) return EINVAL; so->so_error = ECONNABORTED; uncomp_detach(so); return 0; } /******************************************************************************* * * uncomp_accept - AF_LOCAL/COMP pru_accept routine * * Pass back name of connected socket. This routine is actually called AFTER * the sockets are connected via a connect() call by the client. * * RETURNS: 0 on success or an error value * * POSSIBLE ERROR VALUES: * EINVAL * ENOBUFS * * NOMANUAL */ LOCAL int uncomp_accept ( struct socket *so, /* new socket created by connect2()(dsiSoNewConn3) */ struct sockaddr **nam /* container for the name of the peer socket */ ) { UNCOMPCB * pUnComp = SO_TO_UNCOMPCB(so); if (pUnComp == NULL) return EINVAL; /* * Pass back name of connected socket, if it was bound and we are still * connected (our peer may have closed already!). */ if ((pUnComp->selfState == UNCOMPS_EXCHANGING) && (pUnComp->pPeerSocket != NULL)) { /* returning peer's addr */ pUnComp = SO_TO_UNCOMPCB (pUnComp->pPeerSocket); *nam = (struct sockaddr *) uncomp_dupsockaddr (pUnComp); if (*nam == NULL) return ENOBUFS; } /* else do nothing, there is no peer, so no address to return */ /* * NOTE: the only case when this routine is called and there is no peer * is if a peer did a socket-connect-send-close series of calls, * and the peer has closed before the accept is done; in this case, * the accept() call should succeed, not fail. */ return (0); } /******************************************************************************* * * uncomp_attach - AF_LOCAL/COMP pru_attach routine * * Create the 'so_pcb' field of an AF_LOCAL/COMP socket. The data structure * created is of type 'UNCOMPCB'. * * RETURNS: 0 on success or an error value * * POSSIBLE ERROR VALUES: * EISCONN * ENOBUFS * * NOMANUAL */ LOCAL int uncomp_attach ( struct socket *so, /* socket that needs the new UNCOMPCB data struct */ int proto, /* protocol, unused for now */ struct proc *p /* always NULL */ ) { UNCOMPCB * pUnComp = SO_TO_UNCOMPCB(so); int error = 0; /* is socket already set up ? */ if (pUnComp != NULL) return EISCONN; /* set buffer limits */ if (so->so_snd.sb_hiwat == 0 || so->so_rcv.sb_hiwat == 0) error = dsiSoReserve (so, uncomp_sendspace, uncomp_recvspace); if (error) return (error); /* allocate space for uncompcb */ pUnComp = DSI_PCB_ALLOC(UNCOMPCB); if (pUnComp == NULL) return (ENOBUFS); /* initialize uncompcb part of socket */ bzero((char *)pUnComp, sizeof (UNCOMPCB)); /* ZERO everyting */ pUnComp->pSelfSocket = so; /* store back ptr to socket */ pUnComp->selfState = UNCOMPS_CLOSED; /* socket is in CLOSED state */ pUnComp->selfName = UNCOMP_INVALID_NAME; /* bogus name, not named yet */ dllAdd (&uncompSocketList, &pUnComp->node); /* add socket in uncomp list */ so->so_pcb = (caddr_t)pUnComp; /* store uncompcb in socket */ return (0); } /******************************************************************************* * * uncomp_bind - AF_LOCAL/COMP pru_bind routine * * Bind a user-provided name (AF_LOCAL address) to a socket. If the user didn't * provide a name, one is assigned by the system. * * RETURNS: 0 on success or an error value * * POSSIBLE ERROR VALUES: * EINVAL * EADDRINUSE * EMFILE * * NOMANUAL */ LOCAL int uncomp_bind ( struct socket *so, /* socket to bind name 'nam' on */ struct sockaddr *nam, /* name to be bound to socket 'so' */ struct proc *p /* always NULL */ ) { UNCOMPCB * pUnComp = SO_TO_UNCOMPCB(so); UNCOMPCB * pSlot; /* slot where to insert socket in list */ struct sockaddr_un * pName = (struct sockaddr_un *)nam; uint16_t selfName; /* name of socket */ /* is it a local COMP socket ? */ if (pUnComp == NULL) return EINVAL; /* did user provide a name ? */ if ((pName == NULL) || (pName->sun_path[0] == '\0')) { /* find available socket name and slot where to put it in the queue */ pUnComp->selfName = uncomp_firstAvailableNameGet (&pSlot); if (pUnComp->selfName == UNCOMP_INVALID_NAME) return (EMFILE); /* remove socket from tail of queue and put it after slot found */ dllRemove (&uncompSocketList, &pUnComp->node); dllInsert (&uncompSocketList, pSlot != NULL ? &pSlot->node : NULL, &pUnComp->node); return (0); } /* grab real name (suffix from /comp/socket/suffix) */ selfName = uncomp_nameDecode (pName->sun_path); /* is it a valid name ? */ if (selfName == UNCOMP_INVALID_NAME) return (EINVAL); /* traverse list to find insertion spot */ for (pSlot = (UNCOMPCB *)DLL_LAST (&uncompSocketList); pSlot != NULL; pSlot = (UNCOMPCB *)DLL_PREVIOUS (pSlot)) { if ((pSlot->selfName < selfName) && (pSlot->selfName != 0)) break; if (pSlot->selfName == selfName) return (EADDRINUSE); } /* store name in uncompcb part of socket */ pUnComp->selfName = selfName; dllRemove (&uncompSocketList, &pUnComp->node); dllInsert (&uncompSocketList, pSlot != NULL ? &pSlot->node : NULL, &pUnComp->node); return (0); } /******************************************************************************* * * uncomp_connect - AF_LOCAL/COMP pru_connect routine * * Connect the local socket 'so' to another socket at the address 'nam'. Both * sockets are local, this being the implementation of the AF_LOCAL/COMP * protocol. * * RETURNS: 0 on success or an error value * * POSSIBLE ERROR VALUES: * EINVAL * ECONNREFUSED * EMFILE * * NOMANUAL */ LOCAL int uncomp_connect ( struct socket *so, /* socket to connect */ struct sockaddr *nam, /* name of listening socket */ struct proc *p /* always NULL */ ) { UNCOMPCB * pUnComp = SO_TO_UNCOMPCB(so); /* PCB of socket */ UNCOMPCB * pSlot; /* slot where to insert socket */ struct sockaddr_un * pName = (struct sockaddr_un *) nam;/* nam conversion */ struct socket * pAcceptSocket; struct socket * pConnectedPeerSocket; /* is socket a local COMP socket ? */ if (pUnComp == NULL) return EINVAL; /* can we find accepting socket that is named 'nam' ? */ if ((pAcceptSocket = uncomp_acceptSocketFind (pName)) == NULL) return ECONNREFUSED; /* is socket found really an accepting socket ? */ if ((pAcceptSocket->so_options & SO_ACCEPTCONN) == 0) return ECONNREFUSED; /* create new peer socket and place it in the 'CONNECTED' state */ pConnectedPeerSocket = dsiSoNewConn3 (pAcceptSocket, SS_ISCONNECTED, p); if (pConnectedPeerSocket == NULL) return ECONNREFUSED; /* new connected socket's peer's name == same as the accepting socket's */ SO_TO_UNCOMPCB(pConnectedPeerSocket)->selfName = SO_TO_UNCOMPCB(pAcceptSocket)->selfName; /* * remove new socket from the tail of the AF_LOCAL/COMP sockets queue * and put it after the accepting socket in that queue */ dllRemove (&uncompSocketList, &(SO_TO_UNCOMPCB(pConnectedPeerSocket)->node)); dllInsert (&uncompSocketList, &(SO_TO_UNCOMPCB(pAcceptSocket)->node), &(SO_TO_UNCOMPCB(pConnectedPeerSocket)->node)); /* allocate a name for the socket we are connecting */ /* is socket already bound ? */ if (pUnComp->selfName == UNCOMP_INVALID_NAME) { /* find available socket name and slot where to put it in the list */ pUnComp->selfName = uncomp_firstAvailableNameGet (&pSlot); if (pUnComp->selfName == UNCOMP_INVALID_NAME) return (EMFILE); /* remove socket from tail of queue and put it after slot found */ dllRemove (&uncompSocketList, &pUnComp->node); dllInsert (&uncompSocketList, pSlot != NULL ? &pSlot->node : NULL, &pUnComp->node); } /* finally, connect the two sockets locally */ return (uncomp_connect2 (so, pConnectedPeerSocket)); } /******************************************************************************* * * uncomp_connect2 - AF_LOCAL/COMP pru_connect2 routine * * Connect two local sockets 'so1' and 'so2' together. * * RETURNS: 0 on success or an error value * * POSSIBLE ERROR VALUES: * EINVAL * * NOMANUAL */ LOCAL int uncomp_connect2 ( struct socket *so1, /* connecting socket */ struct socket *so2 /* socket created by connection */ ) { UNCOMPCB *pUnComp1 = SO_TO_UNCOMPCB(so1); UNCOMPCB *pUnComp2 = SO_TO_UNCOMPCB(so2); /* are the two sockets valid local COMP sockets ? */ if ((pUnComp1 == NULL) || (pUnComp2 == NULL)) return EINVAL; /* exchange local COMP information between peer sockets */ pUnComp2->pPeerSocket = pUnComp1->pSelfSocket; pUnComp1->pPeerSocket = pUnComp2->pSelfSocket; /* the two sockets are now connected */ dsiSoIsConnected (so1); dsiSoIsConnected (so2); pUnComp1->selfState = UNCOMPS_EXCHANGING; pUnComp2->selfState = UNCOMPS_EXCHANGING; return 0; } /* control is EOPNOTSUPP */ /******************************************************************************* * * uncomp_detach - AF_LOCAL/COMP pru_detach routine * * RETURNS: 0 on success or an error value * * POSSIBLE ERROR VALUES: * EINVAL * * NOMANUAL */ LOCAL int uncomp_detach ( struct socket *so /* socket that is closing */ ) { UNCOMPCB *pUnComp = SO_TO_UNCOMPCB(so); if (pUnComp == NULL) return EINVAL; /* remove socket from list of active AF_LOCAL/COMP socket */ DLL_REMOVE(&uncompSocketList, pUnComp); /* disconnect if connected */ if (pUnComp->pPeerSocket != NULL) uncomp_disconnect(pUnComp->pSelfSocket); DSI_PCB_FREE(pUnComp); so->so_pcb = NULL; return 0; } /******************************************************************************* * * uncomp_disconnect - AF_LOCAL/COMP pru_disconnect routine * * Disconnect socket 'so' from its peer, and vice-versa. Both sockets are placed * in the 'CLOSED' state. * * This routine is called by sodisconnect() (which is called by close() and * soconnect() (in the case an error occurs)) and uncomp_detach(). * * RETURNS: 0 on success or an error value * * POSSIBLE ERROR VALUES: * EINVAL * ENOTCONN * * NOMANUAL */ LOCAL int uncomp_disconnect ( struct socket *so /* socket that is disconnecting */ ) { UNCOMPCB * pUnCompPeer; UNCOMPCB * pUnCompSelf = SO_TO_UNCOMPCB(so); /* valid local COMP socket ? */ if (pUnCompSelf == NULL) return EINVAL; /* is socket connected ? */ if ((pUnCompPeer = SO_TO_UNCOMPCB(pUnCompSelf->pPeerSocket)) == NULL) return ENOTCONN; /* close both ends of the connection */ pUnCompSelf->pPeerSocket = pUnCompPeer->pPeerSocket = NULL; pUnCompSelf->selfState = UNCOMPS_CLOSED; pUnCompPeer->selfState = UNCOMPS_CLOSED; dsiSoIsDisconnected (pUnCompSelf->pSelfSocket); dsiSoIsDisconnected (pUnCompPeer->pSelfSocket); return 0; } /******************************************************************************* * * uncomp_listen - AF_LOCAL/COMP pru_listen routine * * Called by solisten. Does little besides setting the listening socket's state * to 'LISTENING'. * * bind()s a name if not already bound. * * RETURNS: 0 on success or an error value * * POSSIBLE ERROR VALUES: * EINVAL * * NOMANUAL */ LOCAL int uncomp_listen ( struct socket *so, /* socket that is listening */ struct proc *p /* always NULL */ ) { UNCOMPCB * pUnComp = SO_TO_UNCOMPCB(so); int error; if ((pUnComp == NULL) || (pUnComp->selfState != UNCOMPS_CLOSED)) return EINVAL; if (pUnComp->selfName == UNCOMP_INVALID_NAME) if ((error = uncomp_bind (so, NULL, p)) != 0) return (error); pUnComp->selfState = UNCOMPS_LISTENING; return (0); } /******************************************************************************* * * uncomp_peeraddr - AF_LOCAL/COMP pru_peeraddr routine * * Return the address of the socket connected to socket 'so' through 'nam'. * * RETURNS: 0 on success or an error value * * POSSIBLE ERROR VALUES: * ENOBUFS * ENOTCONN * * NOMANUAL */ LOCAL int uncomp_peeraddr ( struct socket *so, /* socket to get peer's address from */ struct sockaddr **nam /* container, name of peer */ ) { UNCOMPCB *pUnComp = SO_TO_UNCOMPCB(so); /* return peer's address if socket is a COMP local one and is connected */ if (pUnComp == NULL) return EINVAL; if ((pUnComp->selfState == UNCOMPS_EXCHANGING) || (pUnComp->selfState == UNCOMPS_DONE_RECV) || (pUnComp->selfState == UNCOMPS_DONE_SEND)) { pUnComp = SO_TO_UNCOMPCB(pUnComp->pPeerSocket); /* memory allocation done here, caller will free memory */ *nam = (struct sockaddr *) uncomp_dupsockaddr(pUnComp); if (*nam == NULL) return ENOBUFS; } else return ENOTCONN; return 0; } /******************************************************************************* * * uncomp_rcvd - AF_LOCAL/COMP pru_rcvd routine * * Work done on AF_LOCAL/COMP sockets at the end of the recv operation. * * RETURNS: 0 * * NOMANUAL */ LOCAL int uncomp_rcvd ( struct socket * so, /* socket receiving */ int flags /* recv flags */ ) { UNCOMPCB * pUnComp = SO_TO_UNCOMPCB(so); /* update pointers */ if (pUnComp->pLastReg == pUnComp->pRecvPacket) pUnComp->pLastReg = NULL; else if (pUnComp->pLastExp == pUnComp->pRecvPacket) pUnComp->pLastExp = NULL; pUnComp->pRecvPacket = so->so_rcv.sb_mb; /* only try to permit flow from peer if peer (still) exists */ if (pUnComp->pPeerSocket != NULL) DSI_SO_W_WAKEUP (pUnComp->pPeerSocket); return (0); } /* pru_rcvoob is EOPNOTSUPP */ /******************************************************************************* * * uncomp_send - AF_LOCAL/COMP pru_send routine * * Send data to peer socket. AF_LOCAL/COMP sockets MUST be connected to * pass data. * * Packets are treated as whole messages and can't be split. * * RETURNS: 0 on success or an error value * * POSSIBLE ERROR VALUES: * EINVAL * ENOTCONN * EMSGSIZE * * NOMANUAL */ LOCAL int uncomp_send ( struct socket *so, /* socket to send on */ int flags, /* send flags */ struct mbuf *m, /* the message itself */ struct sockaddr *nam, /* always NULL */ struct mbuf *control, /* always NULL */ struct proc *p /* always NULL */ ) { struct mbuf ** ppExpTail; /* tail of express q */ struct mbuf ** ppRegTail; /* tail of regular q */ struct mbuf ** ppRecvPacket; /* next received packet */ UNCOMPCB * pUnComp = SO_TO_UNCOMPCB(so); /* socket's PCB */ UNCOMPCB * pPeerUnComp; /* peer socket's PCB */ int pktSz = 0; /* size of sent packet */ long clCnt = 0; /* cluster cnt of sent pkt */ int error = 0; /* is socket an local COMP socket ? */ if (pUnComp == NULL) { netMblkClChainFree (m); return EINVAL; } /* * to send, socket must be connected, i.e. must meet these criteria: * 1. must have a peer socket; * 2. must be in a connected state, either * a) EXCHANGING (both sides can send) * or b) DONE_RECV (other side done sending) * 3. must NOT have a peer that can't receive. * * The third condition might seem a bit weird, but the reason is that * uncomp_shutdown() is not called by shutdown(0), and thus the COMP * state is not updated. */ if ((pUnComp->pPeerSocket == NULL) || ((pUnComp->selfState != UNCOMPS_EXCHANGING) && (pUnComp->selfState != UNCOMPS_DONE_RECV)) || (pUnComp->pPeerSocket->so_state & SS_CANTRCVMORE) != 0) { netMblkClChainFree (m); WV_NET_EVENT_4 (NETD_IP4_DATAPATH_EVENT, WV_NETD_CRITICAL, 1, 1, WV_NETDEVENT_CRITICAL, WV_NETD_SEND, uncomp_send, 0, ENOTCONN, so->so_fd) return ENOTCONN; } /* obtain size and cluster count of packet */ DSI_PKT_SZ (m, pktSz, clCnt); pPeerUnComp = SO_TO_UNCOMPCB(pUnComp->pPeerSocket); /* flow control: loop until there is enough space in peer's recv buffer */ do { /* verify the packet doesn't bust the receiver's limit */ if (pUnComp->pPeerSocket->so_rcv.sb_hiwat >= (pUnComp->pPeerSocket->so_rcv.sb_cc + pktSz)) { goto enqueuepacket; } /* does message even fit in the whole receiver's buffer ? */ if (pktSz > pUnComp->pPeerSocket->so_rcv.sb_hiwat) { error = EMSGSIZE; break; } if (so->so_state & SS_NBIO) /* non-blocking I/O: don't wait */ error = EWOULDBLOCK; else /* sender wants to wait based on T/O */ { if ((error = dsiSbWait (&so->so_snd)) == 0) { /* * was awakened: must check that the sockets are still * transmitting between each other. */ if (pUnComp->selfState != UNCOMPS_CLOSED) { if (!(pUnComp->pPeerSocket->so_state & SS_CANTRCVMORE)) continue; else { error = EPIPE; break; } } else { netMblkClChainFree (m); WV_NET_EVENT_4 (NETD_IP4_DATAPATH_EVENT, WV_NETD_CRITICAL, 1, 2, WV_NETDEVENT_CRITICAL, WV_NETD_SEND, uncomp_send, 0, EPIPE, so->so_fd) return EPIPE; } } } } while ((error != EWOULDBLOCK) && (error != EINTR)); /* this error signals a timeout */ pPeerUnComp->dropped++; /* update dropped packets stats */ netMblkClChainFree (m); WV_NET_EVENT_4 (NETD_IP4_DATAPATH_EVENT, WV_NETD_CRITICAL, 1, 3, WV_NETDEVENT_CRITICAL, WV_NETD_SEND, uncomp_send, 0, error, so->so_fd) return error; enqueuepacket: /* update allocated buffer information in socket structure */ DSI_SB_ALLOC (&pUnComp->pPeerSocket->so_rcv, pktSz, clCnt); /* update high watermark statistics on receiving socket */ if (pUnComp->pPeerSocket->so_rcv.sb_cc > pPeerUnComp->hiwatermark) pPeerUnComp->hiwatermark = (uint16_t)(pUnComp->pPeerSocket->so_rcv.sb_cc); /* * if sending an EXP message, put it at the end of the EXP queue; * if not, put it at the end of the 'regular message' queue */ ppExpTail = &pPeerUnComp->pLastExp; ppRegTail = &pPeerUnComp->pLastReg; ppRecvPacket = &pPeerUnComp->pRecvPacket; if (!(flags & PRUS_EXP)) { if (*ppRegTail == NULL) { if (*ppExpTail == NULL) /* msg q was completely empty */ { pUnComp->pPeerSocket->so_rcv.sb_mb = m; m->m_nextpkt = NULL; *ppRecvPacket = *ppRegTail = m; /* DSI_SO_R_WAKEUP_ONE(pUnComp->pPeerSocket); */ } else /* exp msgs present, put m after them */ { (*ppExpTail)->m_nextpkt = m; m->m_nextpkt = NULL; *ppExpTail = m; } } else /* reg msgs present, put m at tail */ { (*ppRegTail)->m_nextpkt = m; m->m_nextpkt = NULL; *ppRegTail = m; } } else { if (*ppExpTail == NULL) /* q is empty, m is the new head */ { m->m_nextpkt = pUnComp->pPeerSocket->so_rcv.sb_mb; pUnComp->pPeerSocket->so_rcv.sb_mb = m; *ppRecvPacket = *ppExpTail = m; #if 0 if (*ppRegTail == NULL) { DSI_SO_R_WAKEUP_ONE(pUnComp->pPeerSocket); } #endif /* 0 */ } else /* put m at tail of queue */ { m->m_nextpkt = (*ppExpTail)->m_nextpkt; (*ppExpTail)->m_nextpkt = m; *ppExpTail = m; } } /* always call to give one counting semaphore - not just when q was empty */ DSI_SO_R_WAKEUP_ONE(pUnComp->pPeerSocket); return 0; } /* pru_sense is EOPNOTSUPP */ /******************************************************************************* * * uncomp_shutdown - AF_LOCAL/COMP pru_shutdown routine. * * This routine is called when a user does a call to shutdown with the SD_WR * option. * * NOTE : this routine is actually only called to close the sending side * * RETURNS: 0 on success or an error value * * POSSIBLE ERROR VALUES: * EINVAL * * NOMANUAL */ LOCAL int uncomp_shutdown ( struct socket *so /* socket that is shutting down */ ) { UNCOMPCB * pUnComp = SO_TO_UNCOMPCB(so); /* is socket a local COMP socket ? */ if (pUnComp == NULL) return EINVAL; /* socket can't send anymore */ dsiSoCantSendMore(so); /* if connected, socket can't send more and peer can't receive more */ if (pUnComp->selfState == UNCOMPS_EXCHANGING) { SO_TO_UNCOMPCB(pUnComp->pPeerSocket)->selfState = UNCOMPS_DONE_RECV; pUnComp->selfState = UNCOMPS_DONE_SEND; dsiSoCantRcvMore (pUnComp->pPeerSocket); } else if (pUnComp->selfState == UNCOMPS_DONE_RECV) uncomp_disconnect (so); return 0; } /******************************************************************************* * * uncomp_sockaddr - AF_LOCAL/COMP pru_sockaddr routine * * Return the socket's address * * RETURNS: 0 on success or an error value * * POSSIBLE ERROR VALUES: * EINVAL * ENOMEM * * NOMANUAL */ LOCAL int uncomp_sockaddr ( struct socket *so, /* socket from which to get address */ struct sockaddr **nam /* container for the socket's name */ ) { UNCOMPCB * pUnComp = SO_TO_UNCOMPCB(so); if (pUnComp == NULL) return EINVAL; /* memory allocation done here, caller will free memory */ *nam = (struct sockaddr *) uncomp_dupsockaddr(pUnComp); if (*nam == NULL) return ENOBUFS; return 0; } /* XXX : below this point is kept for reference only (work-in-progress) */ int uncomp_ctloutput(so, sopt) struct socket *so; struct sockopt *sopt; { int error = 0; #if UNCOMP_USE_OLD_BSD_STUFF struct unpcb *unp = sotounpcb(so); switch (sopt->sopt_dir) { case SOPT_GET: switch (sopt->sopt_name) { case LOCAL_PEERCRED: if (unp->unp_flags & UNP_HAVEPC) error = sooptcopyout(sopt, &unp->unp_peercred, sizeof(unp->unp_peercred)); else { if (so->so_type == SOCK_STREAM) error = ENOTCONN; else error = EINVAL; } break; default: error = EOPNOTSUPP; break; } break; case SOPT_SET: default: error = EOPNOTSUPP; break; } #endif /* UNCOMP_USE_OLD_BSD_STUFF */ return (error); } #if 0 #if UNCOMP_USE_OLD_BSD_STUFF /* * Both send and receive buffers are allocated PIPSIZ bytes of buffering * for stream sockets, although the total for sender and receiver is * actually only PIPSIZ. * Datagram sockets really use the sendspace as the maximum datagram size, * and don't really want to reserve the sendspace. Their recvspace should * be large enough for at least one max-size datagram plus address. */ #ifndef PIPSIZ #define PIPSIZ 8192 #endif /* PIPSIZ */ static u_long unpst_sendspace = PIPSIZ; static u_long unpst_recvspace = PIPSIZ; static u_long unpdg_sendspace = 2*1024; /* really max datagram size */ static u_long unpdg_recvspace = 4*1024; static int unp_rights; /* file descriptors in flight */ SYSCTL_DECL(_net_local_stream); SYSCTL_INT(_net_local_stream, OID_AUTO, sendspace, CTLFLAG_RW, &unpst_sendspace, 0, ""); SYSCTL_INT(_net_local_stream, OID_AUTO, recvspace, CTLFLAG_RW, &unpst_recvspace, 0, ""); SYSCTL_DECL(_net_local_dgram); SYSCTL_INT(_net_local_dgram, OID_AUTO, maxdgram, CTLFLAG_RW, &unpdg_sendspace, 0, ""); SYSCTL_INT(_net_local_dgram, OID_AUTO, recvspace, CTLFLAG_RW, &unpdg_recvspace, 0, ""); SYSCTL_DECL(_net_local); SYSCTL_INT(_net_local, OID_AUTO, inflight, CTLFLAG_RD, &unp_rights, 0, ""); #endif /* UNCOMP_USE_OLD_BSD_STUFF */ static int prison_unpcb(struct proc *p, struct uncompcb *unp) { #if UNCOMP_USE_OLD_BSD_STUFF if (!p->p_prison) return (0); if (p->p_fd->fd_rdir == unp->unp_rvnode) return (0); #endif /* UNCOMP_USE_OLD_BSD_STUFF */ return (1); } static int unp_pcblist(SYSCTL_HANDLER_ARGS) { #if UNCOMP_USE_OLD_BSD_STUFF int error, i, n; struct unpcb *unp, **unp_list; unp_gen_t gencnt; struct xunpgen xug; struct unp_head *head; head = ((intptr_t)arg1 == SOCK_DGRAM ? &unp_dhead : &unp_shead); /* * The process of preparing the PCB list is too time-consuming and * resource-intensive to repeat twice on every request. */ if (req->oldptr == 0) { n = unp_count; req->oldidx = 2 * (sizeof xug) + (n + n/8) * sizeof(struct xunpcb); return 0; } if (req->newptr != 0) return EPERM; /* * OK, now we're committed to doing something. */ gencnt = unp_gencnt; n = unp_count; xug.xug_len = sizeof xug; xug.xug_count = n; xug.xug_gen = gencnt; xug.xug_sogen = so_gencnt; error = SYSCTL_OUT(req, &xug, sizeof xug); if (error) return error; unp_list = malloc(n * sizeof *unp_list, M_TEMP, M_WAITOK); if (unp_list == 0) return ENOMEM; for (unp = LIST_FIRST(head), i = 0; unp && i < n; unp = LIST_NEXT(unp, unp_link)) { if (unp->unp_gencnt <= gencnt && !prison_unpcb(req->p, unp)) unp_list[i++] = unp; } n = i; /* in case we lost some during malloc */ error = 0; for (i = 0; i < n; i++) { unp = unp_list[i]; if (unp->unp_gencnt <= gencnt) { struct xunpcb xu; xu.xu_len = sizeof xu; xu.xu_unpp = unp; /* * XXX - need more locking here to protect against * connect/disconnect races for SMP. */ if (unp->unp_addr) bcopy(unp->unp_addr, &xu.xu_addr, unp->unp_addr->sun_len); if (unp->unp_conn && unp->unp_conn->unp_addr) bcopy(unp->unp_conn->unp_addr, &xu.xu_caddr, unp->unp_conn->unp_addr->sun_len); bcopy(unp, &xu.xu_unp, sizeof *unp); sotoxsocket(unp->unp_socket, &xu.xu_socket); error = SYSCTL_OUT(req, &xu, sizeof xu); } } if (!error) { /* * Give the user an updated idea of our state. * If the generation differs from what we told * her before, she knows that something happened * while we were processing this request, and it * might be necessary to retry. */ xug.xug_gen = unp_gencnt; xug.xug_sogen = so_gencnt; xug.xug_count = unp_count; error = SYSCTL_OUT(req, &xug, sizeof xug); } free(unp_list, M_TEMP); #endif /* UNCOMP_USE_OLD_BSD_STUFF */ int error = 0; return error; } #if UNCOMP_USE_OLD_BSD_STUFF SYSCTL_PROC(_net_local_dgram, OID_AUTO, pcblist, CTLFLAG_RD, (caddr_t)(long)SOCK_DGRAM, 0, unp_pcblist, "S,xunpcb", "List of active local datagram sockets"); SYSCTL_PROC(_net_local_stream, OID_AUTO, pcblist, CTLFLAG_RD, (caddr_t)(long)SOCK_STREAM, 0, unp_pcblist, "S,xunpcb", "List of active local stream sockets"); #endif /* UNCOMP_USE_OLD_BSD_STUFF */ int unp_externalize(rights) struct mbuf *rights; { #if UNCOMP_USE_OLD_BSD_STUFF struct proc *p = curproc; /* XXX */ register int i; register struct cmsghdr *cm = mtod(rights, struct cmsghdr *); register int *fdp; register struct file **rp; register struct file *fp; int newfds = (cm->cmsg_len - (CMSG_DATA(cm) - (u_char *)cm)) / sizeof (struct file *); int f; /* * if the new FD's will not fit, then we free them all */ if (!fdavail(p, newfds)) { rp = (struct file **)CMSG_DATA(cm); for (i = 0; i < newfds; i++) { fp = *rp; /* * zero the pointer before calling unp_discard, * since it may end up in unp_gc().. */ *rp++ = 0; unp_discard(fp); } return (EMSGSIZE); } /* * now change each pointer to an fd in the global table to * an integer that is the index to the local fd table entry * that we set up to point to the global one we are transferring. * If sizeof (struct file *) is bigger than or equal to sizeof int, * then do it in forward order. In that case, an integer will * always come in the same place or before its corresponding * struct file pointer. * If sizeof (struct file *) is smaller than sizeof int, then * do it in reverse order. */ if (sizeof (struct file *) >= sizeof (int)) { fdp = (int *)(cm + 1); rp = (struct file **)CMSG_DATA(cm); for (i = 0; i < newfds; i++) { if (fdalloc(p, 0, &f)) panic("unp_externalize"); fp = *rp++; p->p_fd->fd_ofiles[f] = fp; fp->f_msgcount--; unp_rights--; *fdp++ = f; } } else { fdp = (int *)(cm + 1) + newfds - 1; rp = (struct file **)CMSG_DATA(cm) + newfds - 1; for (i = 0; i < newfds; i++) { if (fdalloc(p, 0, &f)) panic("unp_externalize"); fp = *rp--; p->p_fd->fd_ofiles[f] = fp; fp->f_msgcount--; unp_rights--; *fdp-- = f; } } /* * Adjust length, in case sizeof(struct file *) and sizeof(int) * differs. */ cm->cmsg_len = CMSG_LEN(newfds * sizeof(int)); rights->m_len = cm->cmsg_len; #endif /* UNCOMP_USE_OLD_BSD_STUFF */ return (0); } void unp_init(void) { #if UNCOMP_USE_OLD_BSD_STUFF unp_zone = zinit("unpcb", sizeof(struct unpcb), nmbclusters, 0, 0); if (unp_zone == 0) panic("unp_init"); LIST_INIT(&unp_dhead); LIST_INIT(&unp_shead); #endif /* UNCOMP_USE_OLD_BSD_STUFF */ } #ifndef MIN #define MIN(a,b) (((a)<(b))?(a):(b)) #endif /* MIN */ static int unp_internalize(control, p) struct mbuf *control; struct proc *p; { #if UNCOMP_USE_OLD_BSD_STUFF struct filedesc *fdescp = p->p_fd; register struct cmsghdr *cm = mtod(control, struct cmsghdr *); register struct file **rp; register struct file *fp; register int i, fd, *fdp; register struct cmsgcred *cmcred; int oldfds; u_int newlen; if ((cm->cmsg_type != SCM_RIGHTS && cm->cmsg_type != SCM_CREDS) || cm->cmsg_level != SOL_SOCKET || cm->cmsg_len != control->m_len) return (EINVAL); /* * Fill in credential information. */ if (cm->cmsg_type == SCM_CREDS) { cmcred = (struct cmsgcred *)(cm + 1); cmcred->cmcred_pid = p->p_pid; cmcred->cmcred_uid = p->p_cred->p_ruid; cmcred->cmcred_gid = p->p_cred->p_rgid; cmcred->cmcred_euid = p->p_ucred->cr_uid; cmcred->cmcred_ngroups = MIN(p->p_ucred->cr_ngroups, CMGROUP_MAX); for (i = 0; i < cmcred->cmcred_ngroups; i++) cmcred->cmcred_groups[i] = p->p_ucred->cr_groups[i]; return(0); } oldfds = (cm->cmsg_len - sizeof (*cm)) / sizeof (int); /* * check that all the FDs passed in refer to legal OPEN files * If not, reject the entire operation. */ fdp = (int *)(cm + 1); for (i = 0; i < oldfds; i++) { fd = *fdp++; if ((unsigned)fd >= fdescp->fd_nfiles || fdescp->fd_ofiles[fd] == NULL) return (EBADF); if (fdescp->fd_ofiles[fd]->f_type == DTYPE_KQUEUE) return (EOPNOTSUPP); } /* * Now replace the integer FDs with pointers to * the associated global file table entry.. * Allocate a bigger buffer as necessary. But if an cluster is not * enough, return E2BIG. */ newlen = CMSG_LEN(oldfds * sizeof(struct file *)); if (newlen > MCLBYTES) return (E2BIG); if (newlen - control->m_len > M_TRAILINGSPACE(control)) { if (control->m_flags & M_EXT) return (E2BIG); MCLGET(control, M_WAIT); if ((control->m_flags & M_EXT) == 0) return (ENOBUFS); /* copy the data to the cluster */ memcpy(mtod(control, char *), cm, cm->cmsg_len); cm = mtod(control, struct cmsghdr *); } /* * Adjust length, in case sizeof(struct file *) and sizeof(int) * differs. */ control->m_len = cm->cmsg_len = newlen; /* * Transform the file descriptors into struct file pointers. * If sizeof (struct file *) is bigger than or equal to sizeof int, * then do it in reverse order so that the int won't get until * we're done. * If sizeof (struct file *) is smaller than sizeof int, then * do it in forward order. */ if (sizeof (struct file *) >= sizeof (int)) { fdp = (int *)(cm + 1) + oldfds - 1; rp = (struct file **)CMSG_DATA(cm) + oldfds - 1; for (i = 0; i < oldfds; i++) { fp = fdescp->fd_ofiles[*fdp--]; *rp-- = fp; fp->f_count++; fp->f_msgcount++; unp_rights++; } } else { fdp = (int *)(cm + 1); rp = (struct file **)CMSG_DATA(cm); for (i = 0; i < oldfds; i++) { fp = fdescp->fd_ofiles[*fdp++]; *rp++ = fp; fp->f_count++; fp->f_msgcount++; unp_rights++; } } #endif /* UNCOMP_USE_OLD_BSD_STUFF */ return (0); } static int unp_defer, unp_gcing; static void unp_gc() { #if UNCOMP_USE_OLD_BSD_STUFF register struct file *fp, *nextfp; register struct socket *so; struct file **extra_ref, **fpp; int nunref, i; if (unp_gcing) return; unp_gcing = 1; unp_defer = 0; /* * before going through all this, set all FDs to * be NOT defered and NOT externally accessible */ LIST_FOREACH(fp, &filehead, f_list) fp->f_flag &= ~(FMARK|FDEFER); do { LIST_FOREACH(fp, &filehead, f_list) { /* * If the file is not open, skip it */ if (fp->f_count == 0) continue; /* * If we already marked it as 'defer' in a * previous pass, then try process it this time * and un-mark it */ if (fp->f_flag & FDEFER) { fp->f_flag &= ~FDEFER; unp_defer--; } else { /* * if it's not defered, then check if it's * already marked.. if so skip it */ if (fp->f_flag & FMARK) continue; /* * If all references are from messages * in transit, then skip it. it's not * externally accessible. */ if (fp->f_count == fp->f_msgcount) continue; /* * If it got this far then it must be * externally accessible. */ fp->f_flag |= FMARK; } /* * either it was defered, or it is externally * accessible and not already marked so. * Now check if it is possibly one of OUR sockets. */ if (fp->f_type != DTYPE_SOCKET || (so = (struct socket *)fp->f_data) == 0) continue; if (so->so_proto->pr_domain != &localdomain || (so->so_proto->pr_flags&PR_RIGHTS) == 0) continue; #ifdef notdef if (so->so_rcv.sb_flags & SB_LOCK) { /* * This is problematical; it's not clear * we need to wait for the sockbuf to be * unlocked (on a uniprocessor, at least), * and it's also not clear what to do * if sbwait returns an error due to receipt * of a signal. If sbwait does return * an error, we'll go into an infinite * loop. Delete all of this for now. */ (void) sbwait(&so->so_rcv); goto restart; } #endif /* notdef */ /* * So, Ok, it's one of our sockets and it IS externally * accessible (or was defered). Now we look * to see if we hold any file descriptors in its * message buffers. Follow those links and mark them * as accessible too. */ unp_scan(so->so_rcv.sb_mb, unp_mark); } } while (unp_defer); /* * We grab an extra reference to each of the file table entries * that are not otherwise accessible and then free the rights * that are stored in messages on them. * * The bug in the orginal code is a little tricky, so I'll describe * what's wrong with it here. * * It is incorrect to simply unp_discard each entry for f_msgcount * times -- consider the case of sockets A and B that contain * references to each other. On a last close of some other socket, * we trigger a gc since the number of outstanding rights (unp_rights) * is non-zero. If during the sweep phase the gc code un_discards, * we end up doing a (full) closef on the descriptor. A closef on A * results in the following chain. Closef calls soo_close, which * calls soclose. Soclose calls first (through the switch * uncomp_usrreq) unp_detach, which re-invokes unp_gc. Unp_gc simply * returns because the previous instance had set unp_gcing, and * we return all the way back to soclose, which marks the socket * with SS_NOFDREF, and then calls sofree. Sofree calls sorflush * to free up the rights that are queued in messages on the socket A, * i.e., the reference on B. The sorflush calls via the dom_dispose * switch unp_dispose, which unp_scans with unp_discard. This second * instance of unp_discard just calls closef on B. * * Well, a similar chain occurs on B, resulting in a sorflush on B, * which results in another closef on A. Unfortunately, A is already * being closed, and the descriptor has already been marked with * SS_NOFDREF, and soclose panics at this point. * * Here, we first take an extra reference to each inaccessible * descriptor. Then, we call sorflush ourself, since we know * it is a Unix domain socket anyhow. After we destroy all the * rights carried in messages, we do a last closef to get rid * of our extra reference. This is the last close, and the * unp_detach etc will shut down the socket. * */ extra_ref = malloc(nfiles * sizeof(struct file *), M_FILE, M_WAITOK); for (nunref = 0, fp = LIST_FIRST(&filehead), fpp = extra_ref; fp != 0; fp = nextfp) { nextfp = LIST_NEXT(fp, f_list); /* * If it's not open, skip it */ if (fp->f_count == 0) continue; /* * If all refs are from msgs, and it's not marked accessible * then it must be referenced from some unreachable cycle * of (shut-down) FDs, so include it in our * list of FDs to remove */ if (fp->f_count == fp->f_msgcount && !(fp->f_flag & FMARK)) { *fpp++ = fp; nunref++; fp->f_count++; } } /* * for each FD on our hit list, do the following two things */ for (i = nunref, fpp = extra_ref; --i >= 0; ++fpp) { struct file *tfp = *fpp; if (tfp->f_type == DTYPE_SOCKET && tfp->f_data != NULL) sorflush((struct socket *)(tfp->f_data)); } for (i = nunref, fpp = extra_ref; --i >= 0; ++fpp) closef(*fpp, (struct proc *) NULL); free((caddr_t)extra_ref, M_FILE); unp_gcing = 0; #endif /* UNCOMP_USE_OLD_BSD_STUFF */ } void unp_dispose(m) struct mbuf *m; { #if UNCOMP_USE_OLD_BSD_STUFF if (m) unp_scan(m, unp_discard); #endif /* UNCOMP_USE_OLD_BSD_STUFF */ } static void unp_scan(m0, op) register struct mbuf *m0; void (*op) __P((struct file *)); { #if UNCOMP_USE_OLD_BSD_STUFF register struct mbuf *m; register struct file **rp; register struct cmsghdr *cm; register int i; int qfds; while (m0) { for (m = m0; m; m = m->m_next) if (m->m_type == MT_CONTROL && m->m_len >= sizeof(*cm)) { cm = mtod(m, struct cmsghdr *); if (cm->cmsg_level != SOL_SOCKET || cm->cmsg_type != SCM_RIGHTS) continue; qfds = (cm->cmsg_len - (CMSG_DATA(cm) - (u_char *)cm)) / sizeof (struct file *); rp = (struct file **)CMSG_DATA(cm); for (i = 0; i < qfds; i++) (*op)(*rp++); break; /* XXX, but saves time */ } m0 = m0->m_act; } #endif /* UNCOMP_USE_OLD_BSD_STUFF */ } static void unp_mark(fp) struct file *fp; { #if UNCOMP_USE_OLD_BSD_STUFF if (fp->f_flag & FMARK) return; unp_defer++; fp->f_flag |= (FMARK|FDEFER); #endif /* UNCOMP_USE_OLD_BSD_STUFF */ } static void unp_discard(fp) struct file *fp; { #if UNCOMP_USE_OLD_BSD_STUFF fp->f_msgcount--; unp_rights--; (void) closef(fp, (struct proc *)NULL); #endif /* UNCOMP_USE_OLD_BSD_STUFF */ } #endif /* if 0 */