/*++ Copyright (c) 1992 Microsoft Corporation Module Name: ddp.c Abstract: This module implements the ddp protocol. Author: Jameel Hyder (jameelh@microsoft.com) Nikhil Kamkolkar (nikhilk@microsoft.com) Revision History: 19 Jun 1992 Initial Version Notes: Tab stop: 4 --*/ #define DDP_LOCALS #define FILENUM DDP #include #pragma hdrstop #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGEINIT, AtalkDdpInitCloseAddress) #pragma alloc_text(PAGEINIT, atalkDdpInitCloseComplete) #pragma alloc_text(PAGEINIT, AtalkInitDdpOpenStaticSockets) #endif // // AtalkDdpOpenAddress() // This opens a DDP address object and returns a pointer to it in // DdpAddrObject. The AppletalkSocket is created and will be the // address of this object. // ATALK_ERROR AtalkDdpOpenAddress( IN PPORT_DESCRIPTOR pPortDesc, IN BYTE Socket, IN OUT PATALK_NODEADDR pDesiredNode OPTIONAL, IN DDPAO_HANDLER pSktHandler OPTIONAL, IN PVOID pSktCtx OPTIONAL, IN BYTE Protocol OPTIONAL, IN PATALK_DEV_CTX pDevCtx, OUT PDDP_ADDROBJ * ppDdpAddr ) /*++ Routine Description: Arguments: Return Value: --*/ { PATALK_NODE pAtalkNode, pNextNode; PDDP_ADDROBJ pDdpAddr = NULL; ATALK_ERROR error = ATALK_NO_ERROR; DBGPRINT(DBG_COMP_DDP, DBG_LEVEL_INFO, ("AtalkDdpOpenAddress: Opening DDP socket %d on port %lx\n", Socket, pPortDesc)); do { // Verify the Appletalk socket number if (!IS_VALID_SOCKET(Socket)) { error = ATALK_SOCKET_INVALID; break; } // Allocate space for the address object if ((pDdpAddr = AtalkAllocZeroedMemory(sizeof(DDP_ADDROBJ))) == NULL) { error = ATALK_RESR_MEM; break; } if (pDesiredNode != NULL) { AtalkNodeReferenceByAddr(pPortDesc, pDesiredNode, &pAtalkNode, &error); if (ATALK_SUCCESS(error)) { ASSERT(VALID_ATALK_NODE(pAtalkNode)); // try to allocate the socket on this node. error = atalkDdpAllocSocketOnNode(pPortDesc, Socket, pAtalkNode, pSktHandler, pSktCtx, Protocol, pDevCtx, pDdpAddr); // Remove the reference on the node. AtalkNodeDereference(pAtalkNode); } break; } else { KIRQL OldIrql; // We can open the socket on any one of our // nodes. // We first get the port lock // Then we go through all the nodes on the port // reference a node, let go of the port lock // acquire the node lock, try to open the socket // on it. If we succeed, we return, else we fail. ACQUIRE_SPIN_LOCK(&pPortDesc->pd_Lock, &OldIrql); do { // Try to get a referenced node. null if no non-closing node found. AtalkNodeReferenceNextNc(pPortDesc->pd_Nodes, &pAtalkNode, &error); while (ATALK_SUCCESS(error)) { // We do not use this node if it is orphaned or if // it is a router node and we are trying to open a // user socket (dynamic or non-reserved). if (((pAtalkNode->an_Flags & (AN_ORPHAN_NODE | AN_ROUTER_NODE)) == 0) || ((Socket != UNKNOWN_SOCKET) && (Socket <= LAST_APPLE_RESD_SOCKET))) { RELEASE_SPIN_LOCK(&pPortDesc->pd_Lock, OldIrql); // try to allocate the socket on this node. PortLock held! error = atalkDdpAllocSocketOnNode(pPortDesc, Socket, pAtalkNode, pSktHandler, pSktCtx, Protocol, pDevCtx, pDdpAddr); if (ATALK_SUCCESS(error)) { // Done! Break out of the loop. Remove the ref we added. AtalkNodeDereference(pAtalkNode); ACQUIRE_SPIN_LOCK(&pPortDesc->pd_Lock, &OldIrql); break; } ACQUIRE_SPIN_LOCK(&pPortDesc->pd_Lock, &OldIrql); } // Gotta get to the next node. AtalkNodeReferenceNextNc(pAtalkNode->an_Next, &pNextNode, &error); RELEASE_SPIN_LOCK(&pPortDesc->pd_Lock, OldIrql); AtalkNodeDereference(pAtalkNode); ACQUIRE_SPIN_LOCK(&pPortDesc->pd_Lock, &OldIrql); pAtalkNode = pNextNode; } } while (FALSE); RELEASE_SPIN_LOCK(&pPortDesc->pd_Lock, OldIrql); } } while (FALSE); if (ATALK_SUCCESS(error)) { if (ppDdpAddr != NULL) *ppDdpAddr = pDdpAddr; } else { DBGPRINT(DBG_COMP_DDP, DBG_LEVEL_ERR, ("AtalkDdpOpenAddress: failed with error %lx\n", error)); ASSERTMSG("AtalkDdpOpenAddress: failed\n", 0); if (pDdpAddr) AtalkFreeMemory(pDdpAddr); } return error; } ATALK_ERROR AtalkDdpCleanupAddress( IN PDDP_ADDROBJ pDdpAddr ) /*++ Routine Description: Releases any pending requests on the address. Arguments: Return Value: --*/ { KIRQL OldIrql; // Free all pending ddp reads. ACQUIRE_SPIN_LOCK(&pDdpAddr->ddpao_Lock, &OldIrql); while (!IsListEmpty(&pDdpAddr->ddpao_ReadLinkage)) { PLIST_ENTRY p; PDDP_READ pRead; p = RemoveHeadList(&pDdpAddr->ddpao_ReadLinkage); RELEASE_SPIN_LOCK(&pDdpAddr->ddpao_Lock, OldIrql); pRead = CONTAINING_RECORD(p, DDP_READ, dr_Linkage); (*pRead->dr_RcvCmp)(ATALK_FAILURE, pRead->dr_OpBuf, 0, NULL, pRead->dr_RcvCtx); AtalkDdpDereference(pDdpAddr); AtalkFreeMemory(pRead); ACQUIRE_SPIN_LOCK(&pDdpAddr->ddpao_Lock, &OldIrql); } RELEASE_SPIN_LOCK(&pDdpAddr->ddpao_Lock, OldIrql); return ATALK_NO_ERROR; } ATALK_ERROR AtalkDdpCloseAddress( IN PDDP_ADDROBJ pDdpAddr, IN GENERIC_COMPLETION pCloseCmp OPTIONAL, IN PVOID pCloseCtx OPTIONAL ) /*++ Routine Description: Called to close an open ddp address object. This will complete after all requests on the object are done/cancelled, and the Appletalk Socket is closed. Arguments: Return Value: --*/ { KIRQL OldIrql; BOOLEAN closing; ASSERT (VALID_DDP_ADDROBJ(pDdpAddr)); ACQUIRE_SPIN_LOCK(&pDdpAddr->ddpao_Lock, &OldIrql); closing = ((pDdpAddr->ddpao_Flags & DDPAO_CLOSING) != 0) ? TRUE : FALSE; ASSERTMSG("DdpAddr is already closing!\n", !closing); if (!closing) { // Set the closing flag and remember the completion routines. pDdpAddr->ddpao_Flags |= DDPAO_CLOSING; pDdpAddr->ddpao_CloseComp = pCloseCmp; pDdpAddr->ddpao_CloseCtx = pCloseCtx; } RELEASE_SPIN_LOCK(&pDdpAddr->ddpao_Lock, OldIrql); if (!closing) { // Release any pending reads AtalkDdpCleanupAddress(pDdpAddr); AtalkNbpCloseSocket(pDdpAddr); // Remove reference for the creation AtalkDdpDereference(pDdpAddr); } return ATALK_PENDING; } ATALK_ERROR AtalkDdpInitCloseAddress( IN PPORT_DESCRIPTOR pPortDesc, IN PATALK_ADDR pAtalkAddr ) /*++ Routine Description: Arguments: Return Value: --*/ { ATALK_ERROR error; PDDP_ADDROBJ pDdpAddr; // !!!This should only be called during initialization!!! KEVENT Event = {0}; // Try to see if the socket exists. AtalkDdpRefByAddr(pPortDesc, pAtalkAddr, &pDdpAddr, &error); if (ATALK_SUCCESS(error)) { ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL); KeInitializeEvent(&Event, NotificationEvent, FALSE); // Call close with the appropriate completion routine. error = AtalkDdpCloseAddress(pDdpAddr, atalkDdpInitCloseComplete, (PVOID)&Event); // Remove the reference we added. AtalkDdpDereference(pDdpAddr); if (error == ATALK_PENDING) { // Wait on event, completion routine will set NdisRequestEvent KeWaitForSingleObject(&Event, Executive, KernelMode, TRUE, NULL); // Assume socket closed successfully. error = ATALK_NO_ERROR; } } return error; } VOID atalkDdpInitCloseComplete( ATALK_ERROR Error, PVOID Ctx ) /*++ Routine Description: Arguments: Return Value: --*/ { PKEVENT pEvent = (PKEVENT)Ctx; if (!ATALK_SUCCESS(Error)) { DBGPRINT(DBG_COMP_DDP, DBG_LEVEL_INFO, ("atalkDdpInitCloseComplete: Closed with error %lx\n", Error)); } KeSetEvent(pEvent, 0L, FALSE); } ATALK_ERROR AtalkInitDdpOpenStaticSockets( IN PPORT_DESCRIPTOR pPortDesc, IN OUT PATALK_NODE pNode ) /*++ Routine Description: Arguments: Return Value: --*/ { PDDP_ADDROBJ pDdpAddr, pDdpAddr1, pDdpAddr2, pDdpAddr3; ATALK_ERROR error = ATALK_NO_ERROR; // This is called whenever a new node is created. do { error = AtalkDdpOpenAddress(pPortDesc, NAMESINFORMATION_SOCKET, &pNode->an_NodeAddr, AtalkNbpPacketIn, NULL, DDPPROTO_ANY, NULL, &pDdpAddr); if (!ATALK_SUCCESS(error)) break; // A lot of devices today work around the fact that a macintosh uses socket 254 // for lookups from chooser. Agfa is one such beast. To make this work, we reserve // this socket for Nbp lookups ourselves. error = AtalkDdpOpenAddress(pPortDesc, LAST_DYNAMIC_SOCKET, &pNode->an_NodeAddr, AtalkNbpPacketIn, NULL, DDPPROTO_ANY, NULL, &pDdpAddr1); if (!ATALK_SUCCESS(error)) { AtalkDdpCloseAddress(pDdpAddr, NULL, NULL); break; } error = AtalkDdpOpenAddress(pPortDesc, ECHOER_SOCKET, &pNode->an_NodeAddr, AtalkAepPacketIn, NULL, DDPPROTO_ANY, NULL, &pDdpAddr2); if (!ATALK_SUCCESS(error)) { AtalkDdpCloseAddress(pDdpAddr, NULL, NULL); AtalkDdpCloseAddress(pDdpAddr1, NULL, NULL); break; } // NOTE: RTMP uses two protocol types. error = AtalkDdpOpenAddress(pPortDesc, RTMP_SOCKET, &pNode->an_NodeAddr, AtalkRtmpPacketIn, NULL, DDPPROTO_ANY, NULL, &pDdpAddr3); if (!ATALK_SUCCESS(error)) { AtalkDdpCloseAddress(pDdpAddr, NULL, NULL); AtalkDdpCloseAddress(pDdpAddr1, NULL, NULL); AtalkDdpCloseAddress(pDdpAddr2, NULL, NULL); } } while (FALSE); return error; } // // AtalkDdpReceive() // Called by an external caller to the stack. // PAMDL is an Appletalk Memory Descriptor List. On NT, it will be an MDL. // ATALK_ERROR AtalkDdpReceive( IN PDDP_ADDROBJ pDdpAddr, IN PAMDL pAmdl, IN USHORT AmdlLen, IN ULONG RecvFlags, IN RECEIVE_COMPLETION pRcvCmp, IN PVOID pRcvCtx OPTIONAL ) /*++ Routine Description: Arguments: Return Value: --*/ { ATALK_ERROR error; PDDP_READ pRead; NTSTATUS status; ULONG bytesCopied; ATALK_ADDR remoteAddr; KIRQL OldIrql; BOOLEAN completeRecv = FALSE, DerefAddr = FALSE; BOOLEAN pendingDgram = FALSE; do { if (pRcvCmp == NULL) { error = ATALK_DDP_INVALID_PARAM; break; } AtalkDdpReferenceByPtr(pDdpAddr, &error); if (!ATALK_SUCCESS(error)) { break; } DerefAddr = TRUE; error = ATALK_NO_ERROR; ACQUIRE_SPIN_LOCK(&pDdpAddr->ddpao_Lock, &OldIrql); if (pDdpAddr->ddpao_Flags & DDPAO_DGRAM_PENDING) { if (AmdlLen < pDdpAddr->ddpao_EventInfo->ev_IndDgramLen) { error = ATALK_BUFFER_TOO_SMALL; } AmdlLen = MIN(AmdlLen, pDdpAddr->ddpao_EventInfo->ev_IndDgramLen); status = TdiCopyBufferToMdl( pDdpAddr->ddpao_EventInfo->ev_IndDgram, 0, AmdlLen, pAmdl, 0, &bytesCopied); remoteAddr = pDdpAddr->ddpao_EventInfo->ev_IndSrc; pDdpAddr->ddpao_Flags &= ~DDPAO_DGRAM_PENDING; completeRecv = TRUE; } else { // This case never really will be executed for non-blocking sockets. // Dont bother about this alloc with spinlock held for now. // RACE CONDITION is with a packet coming in and setting DGRAM_PENDING. if ((pRead = AtalkAllocMemory(sizeof(DDP_READ))) == NULL) { RELEASE_SPIN_LOCK(&pDdpAddr->ddpao_Lock, OldIrql); error = ATALK_RESR_MEM; break; } InsertTailList(&pDdpAddr->ddpao_ReadLinkage, &pRead->dr_Linkage); DerefAddr = FALSE; pRead->dr_OpBuf = pAmdl; pRead->dr_OpBufLen = AmdlLen; pRead->dr_RcvCmp = pRcvCmp; pRead->dr_RcvCtx = pRcvCtx; error = ATALK_PENDING; } RELEASE_SPIN_LOCK(&pDdpAddr->ddpao_Lock, OldIrql); } while (FALSE); if (completeRecv) { ASSERT((error == ATALK_NO_ERROR) || (error == ATALK_BUFFER_TOO_SMALL)); (*pRcvCmp)(error, pAmdl, AmdlLen, &remoteAddr, pRcvCtx); // And return pending for sure! error = ATALK_PENDING; DerefAddr = TRUE; } if (DerefAddr) { AtalkDdpDereference(pDdpAddr); } return error; } // // DdpSend() // This function is used to deliver packets submitted by the ddp clients. // The packets are assummed to either be destined for one of the nodes on // the port, or need to be routed to another port (if router is on), or to // be transmitted onto the physical medium. // // This takes a buffer descriptor as an input. This can contain either a // PAMDL or a PBYTE depending on where the data is coming from (user space // or router code respectively). In addition, it will take an optional header // buffer that will be appended to the ddp header. The buffer descriptor is // optional, that if NULL, it will be construed as a zero-length send. // ATALK_ERROR AtalkDdpSend( IN PDDP_ADDROBJ pDdpAddr, IN PATALK_ADDR pDestAddr, IN BYTE Protocol, IN BOOLEAN DefinitelyRemoteAddr, IN PBUFFER_DESC pBuffDesc OPTIONAL, IN PBYTE pOptHdr OPTIONAL, IN USHORT OptHdrLen OPTIONAL, IN PBYTE pMcastAddr OPTIONAL, IN PSEND_COMPL_INFO pSendInfo OPTIONAL ) /*++ Routine Description: Arguments: Return Value: --*/ { ATALK_ERROR error; BOOLEAN shouldBeRouted; PPORT_DESCRIPTOR pPortDesc; ATALK_ADDR srcAddr; KIRQL OldIrql; BOOLEAN delivered = FALSE; // NULL buffer descriptor => 0-length send. ASSERT((pBuffDesc == NULL) || (pBuffDesc->bd_Length > 0)); #ifdef DDP_STRICT // Check destination address if (INVALID_ADDRESS(pDestAddr)) { return ATALK_DDP_INVALID_ADDR; } // Check the datagram length. if (pBuffDesc) { USHORT dgramLen; AtalkSizeOfBuffDescData(pBuffDesc, &dgramLen); if (dgramLen > MAX_DGRAM_SIZE) { return ATALK_BUFFER_TOO_BIG; } } #endif // Get a pointer to the port on which the socket exists. pPortDesc = pDdpAddr->ddpao_Node->an_Port; // Get the source address srcAddr = pDdpAddr->ddpao_Addr; if (!DefinitelyRemoteAddr) { // All socket handlers assume that they are called at DISPACTH. Make it so. KeRaiseIrql(DISPATCH_LEVEL, &OldIrql); AtalkDdpOutBufToNodesOnPort(pPortDesc, &srcAddr, pDestAddr, Protocol, pBuffDesc, pOptHdr, OptHdrLen, &delivered); KeLowerIrql(OldIrql); if (delivered) { // Ok, packet meant for one of our own nodes on this port, // and we delivered it. Call the completion routine. if (pSendInfo != NULL) { (*pSendInfo->sc_TransmitCompletion)(NDIS_STATUS_SUCCESS, pSendInfo); } return ATALK_PENDING; } } ASSERT (!delivered); // Can our router handle it? shouldBeRouted = ((pPortDesc->pd_Flags & PD_ROUTER_RUNNING) && (pDestAddr->ata_Network != CABLEWIDE_BROADCAST_NETWORK) && !(WITHIN_NETWORK_RANGE(pDestAddr->ata_Network, &pPortDesc->pd_NetworkRange)) && !(WITHIN_NETWORK_RANGE(pDestAddr->ata_Network, &AtalkStartupNetworkRange))); DBGPRINT(DBG_COMP_DDP, DBG_LEVEL_INFO, ("AtalkDdpSend: destNet %lx shouldBeRouted %s\n", pDestAddr->ata_Network, shouldBeRouted ? "Yes" : "No")); if (shouldBeRouted) { ASSERT (!((WITHIN_NETWORK_RANGE(pDestAddr->ata_Network, &pPortDesc->pd_NetworkRange)) && (pDestAddr->ata_Node == ATALK_BROADCAST_NODE))); // If we're a router and the packet isn't destined for the target ports // local network, let our router handle it -- rather than sending to // whatever the "best router" is or to "a router". do { // This algorithm is taken from the "Appletalk Phase 2 Specification". // If the destination network number is within the range of the reception // port's network range and the destination node number is broadcast, then // we can drop the packet on the floor -- it is a network specific broadcast // not for this router. Note that we've already delivered the packet, and // thus not gotten here, if it was really addressed to the network of any // node owned by the reception port (in AtalkDdpPacketIn). // Also: // Try to find an entry in the routing table that contains the target // network. If not found, discard the packet. PDDP_ADDROBJ pRouteDdpAddr; PRTE pRte; PPORT_DESCRIPTOR pDestPortDesc; PATALK_NODE pRouterNode; ATALK_ADDR actualDest; if ((pRte = AtalkRtmpReferenceRte(pDestAddr->ata_Network)) == NULL) { DBGPRINT(DBG_COMP_ROUTER, DBG_LEVEL_FATAL, ("AtalkDdpRouter: %lx RtmpRte/Not in ThisCableRange\n", pDestAddr->ata_Network)); error = ATALK_RESR_MEM; break; } do { // Get the port descriptor corres. to the RTE pDestPortDesc = pRte->rte_PortDesc; ASSERT(VALID_PORT(pDestPortDesc)); // If the target network's hop count is non-zero, we really need to send // the beast, so, just do it! if (pRte->rte_NumHops != 0) { // Too many hops? error = AtalkDdpTransmit(pDestPortDesc, &srcAddr, pDestAddr, Protocol, pBuffDesc, pOptHdr, OptHdrLen, 1, // HopCount NULL, // pZoneMcastAddr &pRte->rte_NextRouter, pSendInfo); break; } // If the destination node is zero, the packet is really destined for the // router's node on this port. if (pDestAddr->ata_Node == ANY_ROUTER_NODE) { // Try to reference this port, if not successful, its probably // closing down. Grab the port lock and read the router node address. // No need to reference, just ensure its not null. ACQUIRE_SPIN_LOCK(&pDestPortDesc->pd_Lock, &OldIrql); if ((pDestPortDesc->pd_Flags & PD_CLOSING) == 0) { ASSERT(pDestPortDesc->pd_RefCount > 0); pDestPortDesc->pd_RefCount++; } else { ASSERTMSG("AtalkDdpRouter: Could not ref port!\n", 0); error = ATALK_PORT_CLOSING; RELEASE_SPIN_LOCK(&pDestPortDesc->pd_Lock, OldIrql); break; } pRouterNode = pDestPortDesc->pd_RouterNode; if (pRouterNode != NULL) { actualDest.ata_Network = pRouterNode->an_NodeAddr.atn_Network; actualDest.ata_Node = pRouterNode->an_NodeAddr.atn_Node; // Set the actual destination socket. actualDest.ata_Socket = pDestAddr->ata_Socket; } else { ASSERTMSG("AtalkDdpRouter: pRouter node is null!\n", 0); error = ATALK_DDP_NOTFOUND; } if (ATALK_SUCCESS(error)) { AtalkDdpRefByAddrNode(pDestPortDesc, &actualDest, pRouterNode, &pRouteDdpAddr, &error); } RELEASE_SPIN_LOCK(&pDestPortDesc->pd_Lock, OldIrql); if (ATALK_SUCCESS(error)) { KIRQL OldIrql; // Socket handlers assume that they are called at DISPATCH. Make it so. KeRaiseIrql(DISPATCH_LEVEL, &OldIrql); AtalkDdpInvokeHandlerBufDesc(pDestPortDesc, pRouteDdpAddr, &srcAddr, pDestAddr, // Pass in the actual destination Protocol, pBuffDesc, pOptHdr, OptHdrLen); // Remove the reference on the socket AtalkDdpDereferenceDpc(pRouteDdpAddr); KeLowerIrql(OldIrql); } else { ASSERTMSG("AtalkDdpRouter: pSocket on router node is null!\n", 0); } break; } // Okay, now walk through the nodes on the target port, looking for a // home for this packet. if (!DefinitelyRemoteAddr) { // All socket handlers assume that they are called at DISPACTH. Make it so. KeRaiseIrql(DISPATCH_LEVEL, &OldIrql); AtalkDdpOutBufToNodesOnPort(pDestPortDesc, &srcAddr, pDestAddr, Protocol, pBuffDesc, pOptHdr, OptHdrLen, &delivered); KeLowerIrql(OldIrql); if (delivered) { if (pSendInfo != NULL) { (*pSendInfo->sc_TransmitCompletion)(NDIS_STATUS_SUCCESS, pSendInfo); } error = ATALK_NO_ERROR; break; } } // We need to deliver this packet to a local ports network. error = AtalkDdpTransmit(pDestPortDesc, &srcAddr, pDestAddr, Protocol, pBuffDesc, pOptHdr, OptHdrLen, 1, // HopCount NULL, // pZoneMcastAddr, NULL, pSendInfo); } while (FALSE); INTERLOCKED_INCREMENT_LONG_DPC( &pDestPortDesc->pd_PortStats.prtst_NumPktRoutedOut, &AtalkStatsLock.SpinLock); AtalkRtmpDereferenceRte(pRte, FALSE); // Lock held? } while (FALSE); INTERLOCKED_INCREMENT_LONG_DPC( &pPortDesc->pd_PortStats.prtst_NumPktRoutedIn, &AtalkStatsLock.SpinLock); } else { error = AtalkDdpTransmit(pPortDesc, &srcAddr, pDestAddr, Protocol, pBuffDesc, pOptHdr, OptHdrLen, 0, // HopCnt, pMcastAddr, NULL, // pXmitDestNode, pSendInfo); } return error; } // // DdpTransmit() // This function is called to build the headers for the packet and send it // out via the depend level functions. It is assumed at this point that the // packet is destined for nodes not currently controlled by this stack. // // KnownMulticastAddress: Although the DDP destination is encoded using // 'Destination', if this parameter is non-null, the packet is actually // sent to this address. // // TransmitDestination: Again, as above, the router uses this to pass on the // packet to the next router it needs to go to, if 'Destination' is still one // or more hops away. // // This is only called from within ddp send or by the router code (rtmp/zip/router). // ATALK_ERROR AtalkDdpTransmit( IN PPORT_DESCRIPTOR pPortDesc, IN PATALK_ADDR pSrcAddr, IN PATALK_ADDR pDestAddr, IN BYTE Protocol, IN PBUFFER_DESC pBuffDesc OPTIONAL, IN PBYTE pOptHdr OPTIONAL, IN USHORT OptHdrLen OPTIONAL, IN USHORT HopCnt, IN PBYTE pMcastAddr OPTIONAL, IN PATALK_NODEADDR pXmitDestNode OPTIONAL, IN PSEND_COMPL_INFO pSendInfo OPTIONAL ) /*++ Routine Description: Arguments: Return Value: --*/ { PBYTE pDgram, pDgramStart, pLinkDdpOptHdr; PBUFFER_DESC pPktDesc; USHORT linkLen; ATALK_NODEADDR srcNode; ATALK_NODEADDR destNode; USHORT actualLength; ATALK_NODEADDR actualDest; PBUFFER_DESC probe; PBRE routerNode; USHORT bufLen = 0; USHORT checksum = 0; PBYTE knownAddress = NULL; PBYTE knownRouteInfo = NULL; USHORT knownRouteInfoLen = 0; BOOLEAN broadcast = FALSE; ATALK_ERROR error = ATALK_NO_ERROR; BOOLEAN shortDdpHeader = FALSE; BOOLEAN errorFreePkt = FALSE; // // The basic transmit algorithum is: // // if (non-extended-network) // { // if ((destination-network is 0 or // destination-network is NetworkRange.firstNetwork) and // (source-network is 0 or // source-network is NetworkRange.firstNetwork)) // { // // return-okay // } // } // if (destination-network is CableWideBroadcastNetworkNumber or // destination-network in NetworkRange or // destination-network in SartupRange or // { // // return-okay // } // if (destination-network-and-node in best-router-cache) // { // // return-okay // } // if (seen-a-router-recently) // { // // return-okay // } // return-error destNode.atn_Network = pDestAddr->ata_Network; destNode.atn_Node = pDestAddr->ata_Node; actualDest.atn_Network = UNKNOWN_NETWORK; actualDest.atn_Node = UNKNOWN_NODE; do { if (pBuffDesc != NULL) { // Get the buffer length. Check the datagram length. AtalkSizeOfBuffDescData(pBuffDesc, &bufLen); ASSERT(bufLen > 0); } #ifdef DDP_STRICT // Check destination address if (INVALID_ADDRESS(pDestAddr) || INVALID_ADDRESS(pSrcAddr)) { error = ATALK_DDP_INVALID_ADDR; break; } if (pBuffDesc != NULL) { // Ensure we do not have a chained datagram. if (pBuffDesc->bd_Next != NULL) { KeBugCheck(0); } if (bufLen > MAX_DGRAM_SIZE) { error = ATALK_BUFFER_TOO_BIG; break; } } if (OptHdrLen > MAX_OPTHDR_LEN) { error = ATALK_BUFFER_TOO_BIG; break; } #endif // For non-extended networks, we may want to send a short DDP header. if (!(EXT_NET(pPortDesc)) && ((pDestAddr->ata_Network == UNKNOWN_NETWORK) || (pDestAddr->ata_Network == pPortDesc->pd_NetworkRange.anr_FirstNetwork)) && ((pSrcAddr->ata_Network == UNKNOWN_NETWORK) || (pSrcAddr->ata_Network == pPortDesc->pd_NetworkRange.anr_FirstNetwork))) { // Use a short ddp header. Call the port handler to first alloc // the buffer that will hold both the link and ddp hdrs. shortDdpHeader = TRUE; AtalkNdisAllocBuf(&pPktDesc); if (pPktDesc == NULL) { error = ATALK_FAILURE; break; } // In cases of error, free the allocated packet. errorFreePkt = TRUE; // pPkt will be the beginning of the packet and pDgram is where // we fill in the ddp header. actualLength = bufLen + SDDP_HDR_LEN + OptHdrLen; pLinkDdpOptHdr = pPktDesc->bd_CharBuffer; linkLen = 0; ASSERT (pPortDesc->pd_NdisPortType == NdisMediumLocalTalk); // Build the LAP header. This will build it from pDgram backwards, // and set the pPkt pointer as the packet to be freed in the // built buffer descriptor. linkLen = AtalkNdisBuildLTHdr(pLinkDdpOptHdr, &pDestAddr->ata_Node, pSrcAddr->ata_Node, ALAP_SDDP_HDR_TYPE); DBGPRINT(DBG_COMP_DDP, DBG_LEVEL_INFO, ("AtalkDdpTransmit: Sending short hdr on non-ext net! %ld\n", pDestAddr->ata_Node, pDestAddr->ata_Network)); break; } // LONG DDP HEADER // Compute the extended AppleTalk node number that we'll really need to // send the packet to. DBGPRINT(DBG_COMP_DDP, DBG_LEVEL_INFO, ("AtalkDdpTransmit: Building a long ddp header for bufdesc %lx on port %lx\n", pBuffDesc, pPortDesc)); do { if (pMcastAddr != NULL) { knownAddress = pMcastAddr ; break; } if (pXmitDestNode != NULL) { actualDest = *pXmitDestNode; break; } if ((WITHIN_NETWORK_RANGE(pDestAddr->ata_Network, &pPortDesc->pd_NetworkRange)) || (pDestAddr->ata_Network == CABLEWIDE_BROADCAST_NETWORK) || (WITHIN_NETWORK_RANGE(pDestAddr->ata_Network, &AtalkStartupNetworkRange))) { actualDest.atn_Node = pDestAddr->ata_Node; actualDest.atn_Network = pDestAddr->ata_Network; broadcast = (pDestAddr->ata_Node == ATALK_BROADCAST_NODE); break; } atalkDdpFindInBrc(pPortDesc, destNode.atn_Network, &routerNode); if (routerNode != NULL) { // Okay, we know where to go. knownAddress = routerNode->bre_RouterAddr; knownRouteInfo = (PBYTE)routerNode + sizeof(BRE); knownRouteInfoLen = routerNode->bre_RouteInfoLen; break; } if (pPortDesc->pd_Flags & PD_SEEN_ROUTER_RECENTLY) { actualDest = pPortDesc->pd_ARouter; break; } // No router known. What do we do ? If its not an extended net, // just send it - else return error. if (EXT_NET(pPortDesc)) { error = ATALK_DDP_NO_ROUTER; break; } actualDest.atn_Node = pDestAddr->ata_Node; actualDest.atn_Network = pDestAddr->ata_Network; broadcast = (pDestAddr->ata_Node == ATALK_BROADCAST_NODE); } while (FALSE); if (error != ATALK_NO_ERROR) { break; } AtalkNdisAllocBuf(&pPktDesc); if (pPktDesc == NULL) { error = ATALK_FAILURE; break; } // In cases of error, free the allocated packet. errorFreePkt = TRUE; pLinkDdpOptHdr = pPktDesc->bd_CharBuffer; linkLen = 0; actualLength = bufLen + LDDP_HDR_LEN + OptHdrLen; // If we already know where we're headed, just blast it out. Also, // if we're broadcasting, just do it. "knownAddress" will be NULL // if we're broadcasting and that will cause the BuildHeader to make // a broadcast packet. if (EXT_NET(pPortDesc) && ((knownAddress != NULL) || broadcast || (actualDest.atn_Network == CABLEWIDE_BROADCAST_NETWORK))) { // Build the LAP header. AtalkNdisBuildHdr(pPortDesc, pLinkDdpOptHdr, linkLen, actualLength, knownAddress, knownRouteInfo, knownRouteInfoLen, APPLETALK_PROTOCOL); break; } // On non-extended networks, just send the packet to the desired node -- // no AARP games here. if (!EXT_NET(pPortDesc)) { DBGPRINT(DBG_COMP_DDP, DBG_LEVEL_INFO, ("AtalkDdpTransmit: Sending long hdr on non-ext net! %ld\n", actualDest.atn_Network, actualDest.atn_Node)); ASSERT (pPortDesc->pd_NdisPortType == NdisMediumLocalTalk); linkLen = AtalkNdisBuildLTHdr(pLinkDdpOptHdr, &actualDest.atn_Node, pSrcAddr->ata_Node, ALAP_LDDP_HDR_TYPE); break; } // We're sending to a particular node on an extended network. // Do we know its hardware address ? If so, send it out. { KIRQL OldIrql; USHORT index; PAMT pAmt; // Go through the AMT and find the entry for the destination // address if present. index = HASH_ATALK_NODE(&actualDest) % PORT_AMT_HASH_SIZE; ACQUIRE_SPIN_LOCK(&pPortDesc->pd_Lock, &OldIrql); for (pAmt = pPortDesc->pd_Amt[index]; pAmt != NULL; pAmt = pAmt->amt_Next) { if (ATALK_NODES_EQUAL(&pAmt->amt_Target, &actualDest)) { ASSERT(EXT_NET(pPortDesc)); AtalkNdisBuildHdr(pPortDesc, pLinkDdpOptHdr, linkLen, actualLength, pAmt->amt_HardwareAddr, (PBYTE)pAmt+sizeof(AMT), pAmt->amt_RouteInfoLen, APPLETALK_PROTOCOL); error = ATALK_NO_ERROR; break; } } RELEASE_SPIN_LOCK(&pPortDesc->pd_Lock, OldIrql); if (pAmt == NULL) { DBGPRINT(DBG_COMP_AARP, DBG_LEVEL_WARN, ("atalkDdpFindInAmt: Could not find %lx.%lx\n", actualDest.atn_Network, actualDest.atn_Node)); error = ATALK_DDP_NO_AMT_ENTRY; } else break; // Found the actual h/w address we want to go to. } // Free up the allocated header buffer. errorFreePkt = TRUE; ASSERT(!ATALK_SUCCESS(error)); // We dont have the hardware address for the logical address that we // need to send the packet to. Send out aarp requests and drop this packet. // The higher layers can retry later if they have to. srcNode.atn_Network = pSrcAddr->ata_Network; srcNode.atn_Node = pSrcAddr->ata_Node; probe = BUILD_AARPREQUEST(pPortDesc, MAX_HW_ADDR_LEN, srcNode, actualDest); if (probe != NULL) { #ifdef PROFILING INTERLOCKED_INCREMENT_LONG( &pPortDesc->pd_PortStats.prtst_NumAarpProbesOut, &AtalkStatsLock.SpinLock); #endif // Send the aarp packet. error = AtalkNdisSendPacket(pPortDesc, probe, AtalkAarpSendComplete, NULL); if (!ATALK_SUCCESS(error)) { TMPLOGERR() AtalkAarpSendComplete(NDIS_STATUS_FAILURE, probe, NULL); } } DBGPRINT(DBG_COMP_DDP, DBG_LEVEL_WARN, ("AMT Entry not found for %lx.%lx\n", pDestAddr->ata_Network, pDestAddr->ata_Node)); error = ATALK_DDP_NO_AMT_ENTRY; break; } while (FALSE); // Do we need to send the packet? if (ATALK_SUCCESS(error)) { ASSERT(HopCnt <= RTMP_MAX_HOPS); // Remember the beginning of the dgram pDgramStart = pDgram = pLinkDdpOptHdr + linkLen; if (!shortDdpHeader) { *pDgram++ = (DDP_HOP_COUNT(HopCnt) + DDP_MSB_LEN(actualLength)); PUTSHORT2BYTE(pDgram, actualLength); pDgram++; ASSERT(checksum == 0); PUTSHORT2SHORT(pDgram, checksum); pDgram += sizeof(USHORT); PUTSHORT2SHORT(pDgram, pDestAddr->ata_Network); pDgram += sizeof(USHORT); PUTSHORT2SHORT(pDgram, pSrcAddr->ata_Network); pDgram += sizeof(USHORT); *pDgram++ = pDestAddr->ata_Node; *pDgram++ = pSrcAddr->ata_Node; *pDgram++ = pDestAddr->ata_Socket; *pDgram++ = pSrcAddr->ata_Socket; *pDgram++ = Protocol; // Copy the optional header if present if (OptHdrLen > 0) { ASSERT(pOptHdr != NULL); RtlCopyMemory(pDgram, pOptHdr, OptHdrLen); } // Set length in the buffer descriptor. AtalkSetSizeOfBuffDescData(pPktDesc, linkLen + LDDP_HDR_LEN + OptHdrLen); } else { *pDgram++ = DDP_MSB_LEN(actualLength); PUTSHORT2BYTE(pDgram, actualLength); pDgram++; *pDgram++ = pDestAddr->ata_Socket; *pDgram++ = pSrcAddr->ata_Socket; *pDgram++ = Protocol; // Copy the optional header if present if (OptHdrLen > 0) { ASSERT(pOptHdr != NULL); RtlCopyMemory(pDgram, pOptHdr, OptHdrLen); } // Set length in the buffer descriptor. AtalkSetSizeOfBuffDescData(pPktDesc, linkLen + SDDP_HDR_LEN + OptHdrLen); } // Chain the passed in buffer desc onto the tail of the one // returned above. AtalkPrependBuffDesc(pPktDesc, pBuffDesc); // Okay, set checksum if needed. if (pPortDesc->pd_Flags & PD_SEND_CHECKSUMS) { // Temporary skip over the leading unchecksumed bytes. checksum = AtalkDdpCheckSumBufferDesc(pPktDesc, (USHORT)(linkLen + LEADING_UNCHECKSUMED_BYTES)); DBGPRINT(DBG_COMP_DDP, DBG_LEVEL_INFO, ("AtalkDdpTransmit: checksum %lx\n", checksum)); PUTSHORT2SHORT(&pDgramStart[LDDP_CHECKSUM_OFFSET], checksum); } INTERLOCKED_ADD_STATISTICS(&pPortDesc->pd_PortStats.prtst_DataOut, AtalkSizeBuffDesc(pPktDesc), &AtalkStatsLock.SpinLock); // Send the packet. The completion routine will handle freeing the buffer chain. error = AtalkNdisSendPacket(pPortDesc, pPktDesc, AtalkDdpSendComplete, pSendInfo); if (!ATALK_SUCCESS(error)) { AtalkDdpSendComplete(NDIS_STATUS_FAILURE, pPktDesc, pSendInfo); // Return pending. We've alredy called the completion // routine here, which will have called the callers // completion routine. error = ATALK_PENDING; } } // Do we need to free the allocated header packet? if (!ATALK_SUCCESS(error) && (errorFreePkt)) { AtalkNdisFreeBuf(pPktDesc); } return error; } VOID AtalkDdpSendComplete( NDIS_STATUS Status, PBUFFER_DESC pBuffDesc, PSEND_COMPL_INFO pInfo ) /*++ Routine Description: Arguments: Return Value: --*/ { // Free up the buffer descriptor for the first part // and call the specified completion. One of the contexts // should be the remaining part of the buffer descriptor // chain. // There will always be atleast the ddp header, although the next // part could be null. Thats upto the completion routine to care // about. ASSERT(pBuffDesc != NULL); pBuffDesc->bd_Next = NULL; ASSERT(pBuffDesc->bd_Flags & BD_CHAR_BUFFER); AtalkNdisFreeBuf(pBuffDesc); // If null, just return. if (pInfo != NULL) { // Call the completion routine for the transmit if present if (pInfo->sc_TransmitCompletion) (pInfo->sc_TransmitCompletion)(Status, pInfo); } } VOID AtalkDdpInvokeHandlerBufDesc( IN PPORT_DESCRIPTOR pPortDesc, IN PDDP_ADDROBJ pDdpAddr, IN PATALK_ADDR pSrc, IN PATALK_ADDR pDest, IN BYTE Protocol, IN PBUFFER_DESC pBuffDesc OPTIONAL, IN PBYTE pOptHdr OPTIONAL, IN USHORT OptHdrLen OPTIONAL ) /*++ Routine Description: Arguments: Return Value: --*/ { USHORT pktLen = 0; PBYTE pPkt = NULL; BOOLEAN freePkt = FALSE; // This is only called from directly or indirectly throught // the router in AtalkDdpSend. Both of these cases indicate // that we have completion routines to deal with. We just make // a copy and assume caller will deal with its buffer descriptor. // Alloc and copy the buffer descriptor data into pPkt. // optimization: If the buffer descriptor is not a chain // and contains a PBYTE and OptHdrLen = 0, // then pass that directly. // Or if buffer descriptor is NULL indicating 0-length // sends. do { if ((pBuffDesc != NULL) && (pBuffDesc->bd_Next == NULL) && (pBuffDesc->bd_Flags & BD_CHAR_BUFFER) && (OptHdrLen == 0)) { DBGPRINT(DBG_COMP_DDP, DBG_LEVEL_INFO, ("AtalkDdpInvokeHandlerBufDesc: one element, opt hdr null %ld\n", pBuffDesc->bd_Length)); pPkt = pBuffDesc->bd_CharBuffer; pktLen = pBuffDesc->bd_Length; } else if ((pBuffDesc != NULL) || (OptHdrLen != 0)) { // Make a copy! Either the buffer descriptor of the Optional Header // is non null. Or both or non-null. if (pBuffDesc != NULL) { AtalkSizeOfBuffDescData(pBuffDesc, &pktLen); ASSERT(pktLen > 0); } // Add the optHdrLen pktLen += OptHdrLen; DBGPRINT(DBG_COMP_DDP, DBG_LEVEL_INFO, ("AtalkDdpInvokeHandlerBufDesc: Size (incl opt hdr len) %ld\n", pktLen)); if ((pPkt = AtalkAllocMemory(pktLen)) != NULL) { // First copy the OptHdr if present if (pOptHdr != NULL) { RtlCopyMemory(pPkt, pOptHdr, OptHdrLen); } if (pBuffDesc != NULL) { AtalkCopyBuffDescToBuffer(pBuffDesc, 0, // SrcOff pktLen - OptHdrLen, pPkt + OptHdrLen); } freePkt = TRUE; } else { break; } } else { ASSERT((pBuffDesc == NULL) && (OptHdrLen == 0)); ASSERT(pPkt == NULL); ASSERT(pktLen == 0); } AtalkDdpInvokeHandler(pPortDesc, pDdpAddr, pSrc, pDest, Protocol, pPkt, pktLen); } while (FALSE); if (freePkt) { AtalkFreeMemory(pPkt); } } VOID AtalkDdpInvokeHandler( IN PPORT_DESCRIPTOR pPortDesc, IN PDDP_ADDROBJ pDdpAddr, IN PATALK_ADDR pSrc, IN PATALK_ADDR pDest, IN BYTE Protocol, IN PBYTE pPkt OPTIONAL, IN USHORT PktLen OPTIONAL ) /*++ Routine Description: Arguments: Return Value: --*/ { PLIST_ENTRY p; PDDP_READ pRead; NTSTATUS status; ATALK_ERROR error; ULONG bytesCopied; BOOLEAN eventDone = FALSE; ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL); // The address object should be referenced, and we just assume // it will be valid during the lifetime of this call. // Check if protocol type is valid. if ((pDdpAddr->ddpao_Protocol != Protocol) && (pDdpAddr->ddpao_Protocol != DDPPROTO_ANY)) { return; } // First check for queued ddp reads ACQUIRE_SPIN_LOCK_DPC(&pDdpAddr->ddpao_Lock); if (!IsListEmpty(&pDdpAddr->ddpao_ReadLinkage)) { p = RemoveHeadList(&pDdpAddr->ddpao_ReadLinkage); RELEASE_SPIN_LOCK_DPC(&pDdpAddr->ddpao_Lock); error = ATALK_NO_ERROR; pRead = CONTAINING_RECORD(p, DDP_READ, dr_Linkage); // Do copy if > 0 bytes if (PktLen > 0) { if (PktLen > pRead->dr_OpBufLen) { error = ATALK_BUFFER_TOO_SMALL; } PktLen = MIN(PktLen, pRead->dr_OpBufLen); status = TdiCopyBufferToMdl(pPkt, 0, PktLen, GET_MDL_FROM_OPAQUE(pRead->dr_OpBuf), 0, &bytesCopied); ASSERT(status == STATUS_SUCCESS); } (*pRead->dr_RcvCmp)(error, pRead->dr_OpBuf, PktLen, pSrc, pRead->dr_RcvCtx); AtalkFreeMemory(pRead); return; } // If a handler was set on this socket,call it. else if (pDdpAddr->ddpao_Handler != NULL) { RELEASE_SPIN_LOCK_DPC(&pDdpAddr->ddpao_Lock); (*pDdpAddr->ddpao_Handler)(pPortDesc, pDdpAddr, pPkt, PktLen, pSrc, pDest, ATALK_NO_ERROR, Protocol, pDdpAddr->ddpao_HandlerCtx, FALSE, NULL); } else { // if there is an event handler on this address object call it. // If there is already a buffered datagram, drop this packet. // If not, save this datagram as the buffered one, and then // indicate, if (pDdpAddr->ddpao_Flags & DDPAO_DGRAM_EVENT) { do { // We have datagram event handler set on this AO. if (pDdpAddr->ddpao_Flags & (DDPAO_DGRAM_ACTIVE | DDPAO_DGRAM_PENDING)) { // We are already indicating an event. Or we // have a buffered datagram. Drop this pkt. break; } else { PTDI_IND_RECEIVE_DATAGRAM RcvHandler; PVOID RcvCtx; ULONG bytesTaken; PIRP rcvDgramIrp; TA_APPLETALK_ADDRESS srcTdiAddr; NTSTATUS status; ASSERT(pDdpAddr->ddpao_EventInfo != NULL); pDdpAddr->ddpao_Flags |= (DDPAO_DGRAM_ACTIVE | DDPAO_DGRAM_PENDING); RcvHandler = pDdpAddr->ddpao_EventInfo->ev_RcvDgramHandler; RcvCtx = pDdpAddr->ddpao_EventInfo->ev_RcvDgramCtx; ATALKADDR_TO_TDI(&srcTdiAddr, pSrc); // Save the dgram in the event info. RtlCopyMemory(pDdpAddr->ddpao_EventInfo->ev_IndDgram, pPkt, PktLen); pDdpAddr->ddpao_EventInfo->ev_IndDgramLen = PktLen; pDdpAddr->ddpao_EventInfo->ev_IndSrc = *pSrc; pDdpAddr->ddpao_EventInfo->ev_IndProto = Protocol; RELEASE_SPIN_LOCK_DPC(&pDdpAddr->ddpao_Lock); status = (*RcvHandler)(RcvCtx, sizeof(TA_APPLETALK_ADDRESS), &srcTdiAddr, 0, // Options length NULL, // Options 0, // Datagram flags (ULONG)PktLen, // Bytes indicated (ULONG)PktLen, // Bytes available (ULONG *)&bytesTaken, pPkt, &rcvDgramIrp); ASSERT((bytesTaken == 0) || (bytesTaken == PktLen)); if (status == STATUS_MORE_PROCESSING_REQUIRED) { if (rcvDgramIrp != NULL) { // Post the receive as if it came from the io system status= AtalkDispatchInternalDeviceControl( (PDEVICE_OBJECT)AtalkDeviceObject[ATALK_DEV_DDP], rcvDgramIrp); ASSERT(status == STATUS_PENDING); } } else if (status == STATUS_SUCCESS) { if (bytesTaken != 0) { // Assume all of the data was read. pDdpAddr->ddpao_Flags &= ~DDPAO_DGRAM_PENDING; } } else if (status == STATUS_DATA_NOT_ACCEPTED) { // Client may have posted a receive in the indication. Or // it will post a receive later on. Do nothing here. DBGPRINT(DBG_COMP_DDP, DBG_LEVEL_ERR, ("atalkDdpRecvData: Indication status %lx\n", status)); } ACQUIRE_SPIN_LOCK_DPC(&pDdpAddr->ddpao_Lock); } } while (FALSE); // reset the event flags pDdpAddr->ddpao_Flags &= ~DDPAO_DGRAM_ACTIVE; } RELEASE_SPIN_LOCK_DPC(&pDdpAddr->ddpao_Lock); } } VOID AtalkDdpPacketIn( IN PPORT_DESCRIPTOR pPortDesc, IN PBYTE pLinkHdr, IN PBYTE pPkt, IN USHORT PktLen ) /*++ Routine Description: Arguments: Return Value: --*/ { USHORT dgramLen, ddpHdrLen; USHORT hopCnt, checksum; BYTE Protocol; ATALK_ADDR destAddr, srcAddr; PBYTE pDdpHdr; // Only for localtalk BYTE alapSrcNode; BYTE alapDestNode; USHORT srcOffset; BOOLEAN extHdr = TRUE; PBYTE pRouteInfo; USHORT routeLen = 0; BOOLEAN delivered = FALSE; BOOLEAN broadcast = FALSE; BOOLEAN shouldBeRouted = FALSE; ATALK_ERROR error = ATALK_NO_ERROR; TIME TimeS, TimeE, TimeD; TimeS = KeQueryPerformanceCounter(NULL); if (PORT_CLOSING(pPortDesc)) { // If we are not active, return! return; } do { ASSERT((PktLen > 0) || ((PktLen == 0) && (pPkt == NULL))); if (PktLen > (MAX_DGRAM_SIZE + LDDP_HDR_LEN)) { DBGPRINT(DBG_COMP_DDP, DBG_LEVEL_WARN, ("AtalkDdpPacketIn: Invalid size %lx\n", PktLen)); error = ATALK_DDP_INVALID_LEN; break; } // Get to the ddp header pDdpHdr = pPkt; // Short and long header formats have the length in the same place, dgramLen = DDP_GET_LEN(pDdpHdr); hopCnt = DDP_GET_HOP_COUNT(pDdpHdr); // Is the packet too long? if ((hopCnt > RTMP_MAX_HOPS) || (dgramLen > PktLen)) { error = ATALK_DDP_INVALID_LEN; break; } // First glean the information. Check for route info if // tokenring network. switch (pPortDesc->pd_NdisPortType) { case NdisMedium802_5: if (pLinkHdr[TLAP_SRC_OFFSET] & TLAP_SRC_ROUTING_MASK) { routeLen = (pLinkHdr[TLAP_ROUTE_INFO_OFFSET] & TLAP_ROUTE_INFO_SIZE_MASK); // First, glean any AARP information that we can, then handle the DDP // packet. This guy also makes sure we have a good 802.2 header... // // Need to make a localcopy of the source address and then turn // the source routing bit off before calling GleanAarpInfo // pLinkHdr[TLAP_SRC_OFFSET] &= ~TLAP_SRC_ROUTING_MASK; pRouteInfo = pLinkHdr + TLAP_ROUTE_INFO_OFFSET; } ddpHdrLen = LDDP_HDR_LEN; srcOffset = TLAP_SRC_OFFSET; break; case NdisMedium802_3: // Check the length. if ((dgramLen < LDDP_HDR_LEN) || (dgramLen > MAX_DGRAM_SIZE + LDDP_HDR_LEN)) { error = ATALK_DDP_INVALID_LEN; break; } ddpHdrLen = LDDP_HDR_LEN; srcOffset = ELAP_SRC_OFFSET; break; case NdisMediumFddi: // Check the length. if ((dgramLen < LDDP_HDR_LEN) || (dgramLen > MAX_DGRAM_SIZE + LDDP_HDR_LEN)) { error = ATALK_DDP_INVALID_LEN; break; } ddpHdrLen = LDDP_HDR_LEN; srcOffset = FDDI_SRC_OFFSET; break; case NdisMediumLocalTalk: // Do we have an extended header? extHdr = (BOOLEAN)(pLinkHdr[ALAP_TYPE_OFFSET] == ALAP_LDDP_HDR_TYPE); if (extHdr) { ddpHdrLen = LDDP_HDR_LEN; } else { alapDestNode = *(pLinkHdr + ALAP_DEST_OFFSET); alapSrcNode = *(pLinkHdr + ALAP_SRC_OFFSET); if ((dgramLen < SDDP_HDR_LEN) || (dgramLen > (MAX_DGRAM_SIZE + SDDP_HDR_LEN))) { error = ATALK_DDP_INVALID_LEN; break; } ddpHdrLen = SDDP_HDR_LEN; } break; default: // Should never happen! DBGPRINT(DBG_COMP_DDP, DBG_LEVEL_FATAL, ("AtalkDdpPacketIn: Unknown media\n")); KeBugCheck(0); break; } if (!ATALK_SUCCESS(error)) { break; } // Advance packet to point to the data. Caller frees up packet. pPkt += ddpHdrLen; // Glean aarp information for non-localtalk ports if (pPortDesc->pd_NdisPortType != NdisMediumLocalTalk) { AtalkAarpGleanInfo(pPortDesc, pLinkHdr + srcOffset, TLAP_ADDR_LEN, pRouteInfo, (USHORT)routeLen, pDdpHdr, (USHORT)ddpHdrLen); } pDdpHdr += 2; // Past off-cable & len if (extHdr) // Long DDP header { // Get checksum, verification, if needed. GETSHORT2SHORT(&checksum, pDdpHdr); pDdpHdr += 2; if (checksum != 0) { USHORT calcCheckSum; // pDdpHdr has already moved passed LEADING_UNCHECKSUMED_BYTES. // So we just need to decrease the header length field. Use // dgramLen, NOT PktLen! calcCheckSum = AtalkDdpCheckSumPacket(pDdpHdr, (USHORT)(ddpHdrLen - LEADING_UNCHECKSUMED_BYTES), pPkt, (USHORT)(dgramLen - ddpHdrLen)); if (checksum != calcCheckSum) { DBGPRINT(DBG_COMP_DDP, DBG_LEVEL_ERR, ("AtalkDdpPacketIn: Checksums dont match! %lx.%lx\n", checksum, calcCheckSum)); AtalkLogBadPacket(pPortDesc, &srcAddr, &destAddr, pDdpHdr, (USHORT)(ddpHdrLen - LEADING_UNCHECKSUMED_BYTES)); error = ATALK_DDP_PKT_DROPPED; break; } } // Build full source and destination AppleTalk address structures // from our DDP header. GETSHORT2SHORT(&destAddr.ata_Network, pDdpHdr); pDdpHdr += 2; GETSHORT2SHORT(&srcAddr.ata_Network, pDdpHdr); pDdpHdr += 2; destAddr.ata_Node = *pDdpHdr++; srcAddr.ata_Node = *pDdpHdr++; destAddr.ata_Socket = *pDdpHdr++; srcAddr.ata_Socket = *pDdpHdr++; // Get the protocol type. Protocol = *pDdpHdr; broadcast = (destAddr.ata_Node == ATALK_BROADCAST_NODE); // Do we like what we see? Note "nnnn00" is now allowed and used by // NBP. if ((srcAddr.ata_Network > LAST_VALID_NETWORK) || (srcAddr.ata_Network < FIRST_VALID_NETWORK) || (srcAddr.ata_Node < MIN_USABLE_ATALKNODE) || (srcAddr.ata_Node > MAX_USABLE_ATALKNODE)) { error = ATALK_DDP_INVALID_SRC; break; } if ((destAddr.ata_Network > LAST_VALID_NETWORK) || ((destAddr.ata_Node > MAX_USABLE_ATALKNODE) && !broadcast)) { error = ATALK_DDP_INVALID_DEST; break; } // Loop through all nodes that are on the reception port and see if // anybody wants this packet. The algorithm is from the "AppleTalk // Phase 2 Protocol Specification" with enhacements to support ports // that have multiple nodes. // "0000xx" (where "xx" isnt "FF") should not be accepted on an // extended port... For some unknown reason, the spec would like // us to pass this case onto the router (which will, no doubt, // drop it on the floor because it won't find network zero in its // routing table)... you know, bug-for-bug compatible! if ((destAddr.ata_Network == UNKNOWN_NETWORK) && (pPortDesc->pd_Flags & PD_EXT_NET) && (!broadcast)) { DBGPRINT(DBG_COMP_DDP, DBG_LEVEL_WARN, ("DdpPacketIn: Received pkt with net/node %lx.%lx on ext\n", destAddr.ata_Network, destAddr.ata_Node)); shouldBeRouted = TRUE; } else { // Now, on the packet in path, we either deliver the packet // to one of our nodes on this port, or we pass it on to the // router. Even if the packet is a broadcast, the delivered // flag will be set to true. shouldBeRouter will be set to // true, only if the packet *DOES NOT* seem to be destined for // this port. We route the packet *ONLY IF* both shouldBeRouter // is true and delivered is false. AtalkDdpInPktToNodesOnPort(pPortDesc, &destAddr, &srcAddr, Protocol, pPkt, (USHORT)(dgramLen - LDDP_HDR_LEN), &delivered, &shouldBeRouted); } // If we're a router and we think that might help, give her a crack at it. if ((pPortDesc->pd_Flags & PD_ROUTER_RUNNING) && !delivered && shouldBeRouted) { AtalkDdpRouteInPkt(pPortDesc, &srcAddr, &destAddr, Protocol, pPkt, (USHORT)(dgramLen - LDDP_HDR_LEN), hopCnt); } } else // Short DDP header! { BYTE ThisNode; ASSERT(!EXT_NET(pPortDesc)); if (pPortDesc->pd_Flags & PD_EXT_NET) { error = ATALK_DDP_SHORT_HDR; break; } // Use network number for the node on this port for source/destination // network numbers. When we search for the socket/address // object, the concept net = 0, matches anything will come // into play. srcAddr.ata_Network = destAddr.ata_Network = NET_ON_NONEXTPORT(pPortDesc); srcAddr.ata_Node = alapSrcNode; ThisNode = NODE_ON_NONEXTPORT(pPortDesc); if (alapDestNode == ATALK_BROADCAST_NODE) { destAddr.ata_Node = ThisNode; } else if (alapDestNode != ThisNode) { error = ATALK_DDP_INVALID_DEST; break; } else { destAddr.ata_Node = alapDestNode; } DBGPRINT(DBG_COMP_DDP, DBG_LEVEL_WARN, ("AtalkDdpPacketIn: NonExtended Dest Net.Node %lx.%lx\n", destAddr.ata_Network, destAddr.ata_Node)); // Get the socket numbers from the ddp header. destAddr.ata_Socket = *pDdpHdr++; srcAddr.ata_Socket = *pDdpHdr++; // Get the protocol type Protocol = *pDdpHdr; // If the protocol type is 0, we have an error. if (Protocol == 0) { error = ATALK_DDP_INVALID_PROTO; break; } // Now the destination node address could be // ALAP_BROADCAST_NODE (0xFF). if ((srcAddr.ata_Node < MIN_USABLE_ATALKNODE) || (srcAddr.ata_Node > MAX_USABLE_ATALKNODE)) { error = ATALK_DDP_INVALID_SRC; break; } if (((destAddr.ata_Node < MIN_USABLE_ATALKNODE) || (destAddr.ata_Node > MAX_USABLE_ATALKNODE)) && (destAddr.ata_Node != ATALK_BROADCAST_NODE)) { error = ATALK_DDP_INVALID_DEST; break; } // On a non-extended port, there will be only one node. AtalkDdpInPktToNodesOnPort(pPortDesc, &destAddr, &srcAddr, Protocol, pPkt, (USHORT)(dgramLen - SDDP_HDR_LEN), &delivered, &shouldBeRouted); // This is a dud parameter // for non-ext nets } } while (FALSE); if (!ATALK_SUCCESS(error)) { DBGPRINT(DBG_COMP_DDP, DBG_LEVEL_WARN, ("AtalkDdpPacketIn: Dropping packet %lx\n", error) ); } TimeE = KeQueryPerformanceCounter(NULL); TimeD.QuadPart = TimeE.QuadPart - TimeS.QuadPart; INTERLOCKED_ADD_LARGE_INTGR_DPC( &pPortDesc->pd_PortStats.prtst_DdpPacketInProcessTime, TimeD, &AtalkStatsLock.SpinLock); INTERLOCKED_INCREMENT_LONG_DPC( &pPortDesc->pd_PortStats.prtst_NumDdpPacketsIn, &AtalkStatsLock.SpinLock); } VOID AtalkDdpQuery( IN PDDP_ADDROBJ pDdpAddr, IN PAMDL pAmdl, OUT PULONG BytesWritten ) /*++ Routine Description: Arguments: Return Value: --*/ { TDI_ADDRESS_INFO tdiInfo; PTA_APPLETALK_ADDRESS pTaAddr; ASSERT (VALID_DDP_ADDROBJ(pDdpAddr)); pTaAddr = (PTA_APPLETALK_ADDRESS)&tdiInfo.Address; ATALKADDR_TO_TDI(pTaAddr, &pDdpAddr->ddpao_Addr); TdiCopyBufferToMdl ((PBYTE)&tdiInfo, 0L, sizeof(tdiInfo), pAmdl, 0, BytesWritten); } VOID AtalkDdpOutBufToNodesOnPort( IN PPORT_DESCRIPTOR pPortDesc, IN PATALK_ADDR pSrc, IN PATALK_ADDR pDest, IN BYTE Protocol, IN PBUFFER_DESC pBuffDesc OPTIONAL, IN PBYTE pOptHdr OPTIONAL, IN USHORT OptHdrLen OPTIONAL, OUT PBOOLEAN Delivered ) /*++ Routine Description: Arguments: Return Value: --*/ { ATALK_ERROR error; PATALK_NODE pAtalkNode, pNextNode; PDDP_ADDROBJ pDdpAddr; BOOLEAN fDeliver, fSpecific, needToRef; BOOLEAN lockHeld = FALSE; ASSERT (KeGetCurrentIrql() == DISPATCH_LEVEL); // Do not internally loopback broadcast frames, these should come // back to us from the mac. if (pDest->ata_Node == ATALK_BROADCAST_NODE) { *Delivered = FALSE; return; } fSpecific = (pDest->ata_Network != CABLEWIDE_BROADCAST_NETWORK); // Walk through our nodes to see if we can deliver this packet. // OPTIMIZATIONS: // In most cases, this will not be true. Optimize for returning false. // Also, a node closing is a rare occurence. If we run into one that is // closing, we abort trying to deliver this packet to a node on our port, // and instead return delivered = FALSE. DDP - unreliable, and node closing // should be a transient state. We avoid the acquire/release code. ACQUIRE_SPIN_LOCK_DPC(&pPortDesc->pd_Lock); lockHeld = TRUE; pNextNode = pPortDesc->pd_Nodes; needToRef = TRUE; for (; (pAtalkNode = pNextNode) != NULL; ) { fDeliver = FALSE; error = ATALK_NO_ERROR; if (((pAtalkNode->an_NodeAddr.atn_Network == pDest->ata_Network) || !fSpecific) && (pAtalkNode->an_NodeAddr.atn_Node == pDest->ata_Node)) { // Reference node. If we fail, we abort. if (needToRef) { AtalkNodeRefByPtr(pAtalkNode, &error); } if (ATALK_SUCCESS(error)) { fDeliver = TRUE; // Set up for next node. if (fSpecific) { pNextNode = NULL; } else { // Get next eligible node. pNextNode = pAtalkNode->an_Next; while (pNextNode != NULL) { if (pNextNode->an_NodeAddr.atn_Node == pDest->ata_Node) { AtalkNodeRefByPtr(pNextNode, &error); if (!ATALK_SUCCESS(error)) { pNextNode = NULL; } needToRef = FALSE; break; } else { pNextNode = pNextNode->an_Next; } } } } else { // Break out of the for loop. break; } } else { pNextNode = pAtalkNode->an_Next; needToRef = TRUE; } if (fDeliver) { // Release port lock, deliver packet, and Deref the node. // Find the ddp address object on this node corresponding // to this address. This will get the node lock. AtalkDdpRefByAddrNode(pPortDesc, pDest, pAtalkNode, &pDdpAddr, &error); RELEASE_SPIN_LOCK_DPC(&pPortDesc->pd_Lock); lockHeld = FALSE; if (ATALK_SUCCESS(error)) { // Invoke socket handler on this address object. AtalkDdpInvokeHandlerBufDesc(pPortDesc, pDdpAddr, pSrc, pDest, Protocol, pBuffDesc, pOptHdr, OptHdrLen); // Remove the reference on the socket AtalkDdpDereferenceDpc(pDdpAddr); } // Remove the reference on the node AtalkNodeDereference(pAtalkNode); // If we had to deliver to a specific node, we are done. if (fSpecific) { break; } ACQUIRE_SPIN_LOCK_DPC(&pPortDesc->pd_Lock); lockHeld = TRUE; } } if (lockHeld) { RELEASE_SPIN_LOCK_DPC(&pPortDesc->pd_Lock); } *Delivered = (fSpecific && fDeliver); } VOID AtalkDdpInPktToNodesOnPort( IN PPORT_DESCRIPTOR pPortDesc, IN PATALK_ADDR pDest, IN PATALK_ADDR pSrc, IN BYTE Protocol, IN PBYTE pPkt OPTIONAL, IN USHORT PktLen OPTIONAL, OUT PBOOLEAN Delivered, OUT PBOOLEAN ShouldBeRouted ) /*++ Routine Description: Arguments: Return Value: --*/ { PATALK_NODE pAtalkNode, pNextNode; PDDP_ADDROBJ pDdpAddr; BOOLEAN broadcast; BOOLEAN fSpecific, fDeliver, needToRef; ATALK_ERROR error = ATALK_NO_ERROR; BOOLEAN lockHeld = FALSE; BOOLEAN delivered = FALSE; BOOLEAN shouldBeRouted = FALSE; ASSERT (KeGetCurrentIrql() == DISPATCH_LEVEL); broadcast = (pDest->ata_Node == ATALK_BROADCAST_NODE); // is a directed packet to a socket on a particular node...? fSpecific = (!broadcast && (pDest->ata_Network != UNKNOWN_NETWORK)); // OPTIMIZATIONS: // In most cases, this will not be true. Optimize for returning false. // Also, a node closing is a rare occurence. If we run into one that is // closing, we abort trying to deliver this packet to a node on our port, // and instead return delivered = FALSE. DDP - unreliable, and node closing // should be a transient state. We avoid the acquire/release code. ACQUIRE_SPIN_LOCK_DPC(&pPortDesc->pd_Lock); lockHeld = TRUE; pNextNode = pPortDesc->pd_Nodes; needToRef = TRUE; while ((pAtalkNode = pNextNode) != NULL) { fDeliver = FALSE; error = ATALK_NO_ERROR; // For incoming packet, we check to see if the destination // net is 0, or destination net is our node's net, or we are // non-extended and our node's net is zero. i.e. is the packet // destined for a node on this port. If not, route it. Continue // checking all nodes though, as a single port can have nodes with // different network numbers. if (((pAtalkNode->an_NodeAddr.atn_Network == pDest->ata_Network) || (pDest->ata_Network == UNKNOWN_NETWORK) || (!EXT_NET(pPortDesc) && (pAtalkNode->an_NodeAddr.atn_Network == UNKNOWN_NETWORK))) && (broadcast || (pAtalkNode->an_NodeAddr.atn_Node == pDest->ata_Node))) { // Reference node if we need to. Only happens for the first // time we enter the loop. If we fail, we abort. if (needToRef) { AtalkNodeRefByPtr(pAtalkNode, &error); if (!ATALK_SUCCESS(error)) { break; } } fDeliver = TRUE; // Set up for next node. if (fSpecific) { // Only one node on a non-extended port. So set next to NULL. pNextNode = NULL; } else { // Get next eligible node. pNextNode = pAtalkNode->an_Next; while (pNextNode != NULL) { if (((pNextNode->an_NodeAddr.atn_Network == pDest->ata_Network) || (pDest->ata_Network == UNKNOWN_NETWORK) || (!EXT_NET(pPortDesc) && (pNextNode->an_NodeAddr.atn_Network == UNKNOWN_NETWORK))) && (broadcast || (pNextNode->an_NodeAddr.atn_Node == pDest->ata_Node))) { AtalkNodeRefByPtr(pNextNode, &error); if (!ATALK_SUCCESS(error)) { pNextNode = NULL; } needToRef = FALSE; break; } pNextNode = pNextNode->an_Next; } } } else { // The packet probably could be meant to be routed. // This could be set multiple times - idempotent. shouldBeRouted = TRUE; needToRef = TRUE; pNextNode = pAtalkNode->an_Next; } if (fDeliver) { // Release port lock, deliver packet, and Deref the node. // Find the ddp address object on this node corresponding // to this address. This will get the node lock. if (broadcast) pDest->ata_Node = pAtalkNode->an_NodeAddr.atn_Node; AtalkDdpRefByAddrNode(pPortDesc, pDest, pAtalkNode, &pDdpAddr, &error); // If we had changed the destination node, change it back. if (broadcast) pDest->ata_Node = ATALK_BROADCAST_NODE; RELEASE_SPIN_LOCK_DPC(&pPortDesc->pd_Lock); lockHeld = FALSE; if (ATALK_SUCCESS(error)) { // Invoke socket handler on this address object. // Use the packet pointer directly! AtalkDdpInvokeHandler(pPortDesc, pDdpAddr, pSrc, pDest, Protocol, pPkt, PktLen); // Remove the reference on the socket AtalkDdpDereferenceDpc(pDdpAddr); } // Remove the reference on the node AtalkNodeDereference(pAtalkNode); // If we had to deliver to a specific node, we are done. if (fSpecific) { shouldBeRouted = FALSE; break; } ACQUIRE_SPIN_LOCK_DPC(&pPortDesc->pd_Lock); lockHeld = TRUE; } } if (lockHeld) { RELEASE_SPIN_LOCK_DPC(&pPortDesc->pd_Lock); } *Delivered = delivered; *ShouldBeRouted = shouldBeRouted; } USHORT AtalkDdpCheckSumBuffer( IN PBYTE Buffer, IN USHORT BufLen, IN USHORT CurrentCheckSum ) /*++ Routine Description: Calculate the DDP checksum of a byte array Arguments: Return Value: --*/ { USHORT CheckSum = CurrentCheckSum; ULONG i; // The following algorithm is from Inside AppleTalk, Second Edition // page 4-17 for (i = 0; i < BufLen; i++) { CheckSum += Buffer[i]; if (CheckSum & 0x8000) // 16-bit rotate left one bit { CheckSum <<= 1; CheckSum ++; } else CheckSum <<= 1; } if (CheckSum == 0) CheckSum = 0xFFFF; return CheckSum; } USHORT AtalkDdpCheckSumPacket( IN PBYTE pHdr, IN USHORT HdrLen, IN PBYTE pPkt, IN USHORT PktLen ) /*++ Routine Description: Arguments: Return Value: --*/ { USHORT checksum = 0; // MAX_LDDP_PKT_SIZE is 600, so we use < instead of <= ASSERT(HdrLen + PktLen < MAX_LDDP_PKT_SIZE); if ((HdrLen + PktLen) < MAX_LDDP_PKT_SIZE) { if (HdrLen > 0) { checksum = AtalkDdpCheckSumBuffer(pHdr, HdrLen, 0); } if (PktLen > 0) { checksum = AtalkDdpCheckSumBuffer(pPkt, PktLen, checksum); } } return checksum; } // Calculate the DDP checksum of the passed in buffer. The buffer is described // by the buffer descriptor USHORT AtalkDdpCheckSumBufferDesc( IN PBUFFER_DESC pBuffDesc, IN USHORT Offset ) /*++ Routine Description: Arguments: Return Value: --*/ { PBYTE pBuf; USHORT checksum = 0; while (pBuffDesc != NULL) { if (pBuffDesc->bd_Flags & BD_CHAR_BUFFER) { pBuf = pBuffDesc->bd_CharBuffer; } else { pBuf = MmGetSystemAddressForMdl(pBuffDesc->bd_OpaqueBuffer); } checksum = AtalkDdpCheckSumBuffer(pBuf, pBuffDesc->bd_Length, checksum); pBuffDesc = pBuffDesc->bd_Next; } return checksum; } // This routine needs to verify that the socket does not already // exist on the node. If it doesnt it will alloc the ddp address // object and link it into the node and do all the required initialization. // The node is guaranteed to be referenced. ATALK_ERROR atalkDdpAllocSocketOnNode( IN PPORT_DESCRIPTOR pPortDesc, IN BYTE Socket, IN PATALK_NODE pAtalkNode, IN DDPAO_HANDLER pSktHandler OPTIONAL, IN PVOID pSktCtx OPTIONAL, IN BYTE Protocol OPTIONAL, IN PATALK_DEV_CTX pDevCtx, OUT PDDP_ADDROBJ pDdpAddr ) /*++ Routine Description: Arguments: Return Value: --*/ { ATALK_ADDR addr; PDDP_ADDROBJ pDdpAddrx; KIRQL OldIrql; int i, j, index; BOOLEAN found = TRUE; ATALK_ERROR error = ATALK_NO_ERROR; // See if the socket exists else, link our new socket into // the node linkage. All within a critical section. addr.ata_Network = pAtalkNode->an_NodeAddr.atn_Network; addr.ata_Node = pAtalkNode->an_NodeAddr.atn_Node; addr.ata_Socket = Socket; // Now reference the node on which this socket will reside. // This will go away when the socket is closed. AtalkNodeReferenceByPtr(pAtalkNode, &error); if (!ATALK_SUCCESS(error)) { TMPLOGERR(); return error; } ACQUIRE_SPIN_LOCK(&pAtalkNode->an_Lock, &OldIrql); if (Socket == DYNAMIC_SOCKET) { // Two attempts if we are at the end of the range and restart from // the beginning. for (j = 0; (j < NUM_USER_NODES) && found; j++) { for (i = pAtalkNode->an_NextDynSkt; i <= LAST_DYNAMIC_SOCKET; i++) { addr.ata_Socket = (BYTE)i; index = HASH_ATALK_ADDR(&addr) % NODE_DDPAO_HASH_SIZE; found = atalkDdpFindAddrOnList(pAtalkNode, index, (BYTE)i, &pDdpAddrx); if (found) continue; Socket = (BYTE)i; break; } // Now if still havent found the socket id, set NextDynSkt to // beginning of the range and try again. if (found) { pAtalkNode->an_NextDynSkt = FIRST_DYNAMIC_SOCKET; continue; } // Not found. Increment next id to be used. if (++(pAtalkNode->an_NextDynSkt) == 0) { // We wrapped! Set the value to the lowest dynamic // socket. Thats what it should have been initialized // to. pAtalkNode->an_NextDynSkt = FIRST_DYNAMIC_SOCKET; } DBGPRINT(DBG_COMP_DDP, DBG_LEVEL_INFO, ("atalkDdpAllocSocketOnNode: Created dynamic socket %x\n", Socket)); // Done. break; } if (found) { error = ATALK_SOCKET_NODEFULL; } } else { index = HASH_ATALK_ADDR(&addr) % NODE_DDPAO_HASH_SIZE; found = atalkDdpFindAddrOnList(pAtalkNode, index, (BYTE)Socket, &pDdpAddrx); if (found) { error = ATALK_SOCKET_EXISTS; } } if (ATALK_SUCCESS(error)) { // Initialize and thread in the structure #if DBG pDdpAddr->ddpao_Signature = DDPAO_SIGNATURE; #endif pDdpAddr->ddpao_RefCount = 1; // Creation pDdpAddr->ddpao_DevCtx = pDevCtx; pDdpAddr->ddpao_Node = pAtalkNode; pDdpAddr->ddpao_Addr.ata_Network = pAtalkNode->an_NodeAddr.atn_Network; pDdpAddr->ddpao_Addr.ata_Node = pAtalkNode->an_NodeAddr.atn_Node; pDdpAddr->ddpao_Addr.ata_Socket = Socket; pDdpAddr->ddpao_Protocol = Protocol; pDdpAddr->ddpao_Handler = pSktHandler; pDdpAddr->ddpao_HandlerCtx = pSktCtx; INITIALIZE_SPIN_LOCK(&pDdpAddr->ddpao_Lock); InitializeListHead(&pDdpAddr->ddpao_ReadLinkage); // We use 'index' to link this in. pDdpAddr->ddpao_Next = pAtalkNode->an_DdpAoHash[index]; pAtalkNode->an_DdpAoHash[index] = pDdpAddr; } RELEASE_SPIN_LOCK(&pAtalkNode->an_Lock, OldIrql); // If we failed, Dereference the node if (!ATALK_SUCCESS(error)) AtalkNodeDereference(pAtalkNode); return error; } BOOLEAN atalkDdpFindAddrOnList( IN PATALK_NODE pAtalkNode, IN ULONG Index, IN BYTE Socket, OUT PDDP_ADDROBJ * ppDdpAddr ) /*++ Routine Description: Arguments: Return Value: --*/ { PDDP_ADDROBJ pDdpAddr; BOOLEAN found = FALSE; for (pDdpAddr = pAtalkNode->an_DdpAoHash[Index]; pDdpAddr != NULL; pDdpAddr = pDdpAddr->ddpao_Next) { if (pDdpAddr->ddpao_Addr.ata_Socket == Socket) { *ppDdpAddr = pDdpAddr; found = TRUE; break; } } return found; } VOID AtalkDdpRefByAddr( IN PPORT_DESCRIPTOR pPortDesc, IN PATALK_ADDR pAtalkAddr, OUT PDDP_ADDROBJ * ppDdpAddr, OUT PATALK_ERROR pErr ) /*++ Routine Description: Arguments: Return Value: --*/ { ULONG index; ATALK_NODEADDR node; PATALK_NODE pAtalkNode; PDDP_ADDROBJ pDdpAddr; KIRQL OldIrql; ATALK_ERROR ErrorCode; node.atn_Network = pAtalkAddr->ata_Network; node.atn_Node = pAtalkAddr->ata_Node; // First find the node on this port given its address AtalkNodeReferenceByAddr(pPortDesc, &node, &pAtalkNode, &ErrorCode); if (ATALK_SUCCESS(ErrorCode)) { ASSERT(VALID_ATALK_NODE(pAtalkNode)); index = HASH_ATALK_ADDR(pAtalkAddr) % NODE_DDPAO_HASH_SIZE; ACQUIRE_SPIN_LOCK(&pAtalkNode->an_Lock, &OldIrql); if(atalkDdpFindAddrOnList(pAtalkNode, index, pAtalkAddr->ata_Socket, &pDdpAddr)) { AtalkDdpReferenceByPtr(pDdpAddr, &ErrorCode); if (ATALK_SUCCESS(ErrorCode)) { ASSERT (VALID_DDP_ADDROBJ(pDdpAddr)); *ppDdpAddr = pDdpAddr; } } else { ErrorCode = ATALK_DDP_NOTFOUND; } RELEASE_SPIN_LOCK(&pAtalkNode->an_Lock, OldIrql); // Remove the node reference ASSERT(VALID_ATALK_NODE(pAtalkNode)); AtalkNodeDereference(pAtalkNode); } *pErr = ErrorCode; } VOID AtalkDdpRefByAddrNode( IN PPORT_DESCRIPTOR pPortDesc, IN PATALK_ADDR pAtalkAddr, IN PATALK_NODE pAtalkNode, OUT PDDP_ADDROBJ * ppDdpAddr, OUT PATALK_ERROR pErr ) /*++ Routine Description: Arguments: Return Value: --*/ { ULONG index; KIRQL OldIrql; PDDP_ADDROBJ pDdpAddr; ASSERT(VALID_ATALK_NODE(pAtalkNode)); index = HASH_ATALK_ADDR(pAtalkAddr) % NODE_DDPAO_HASH_SIZE; ACQUIRE_SPIN_LOCK(&pAtalkNode->an_Lock, &OldIrql); if(atalkDdpFindAddrOnList(pAtalkNode, index, pAtalkAddr->ata_Socket, &pDdpAddr)) { AtalkDdpReferenceByPtr(pDdpAddr, pErr); if (ATALK_SUCCESS(*pErr)) { ASSERT (VALID_DDP_ADDROBJ(pDdpAddr)); *ppDdpAddr = pDdpAddr; } } else { *pErr = ATALK_DDP_NOTFOUND; } RELEASE_SPIN_LOCK(&pAtalkNode->an_Lock, OldIrql); } VOID AtalkDdpRefNextNc( IN PDDP_ADDROBJ pDdpAddr, IN PDDP_ADDROBJ * ppDdpAddr, OUT PATALK_ERROR pErr ) /*++ Routine Description: MUST BE CALLED WITH THE NODE LOCK HELD! Arguments: Return Value: --*/ { *pErr = ATALK_FAILURE; *ppDdpAddr = NULL; for (; pDdpAddr != NULL; pDdpAddr = pDdpAddr->ddpao_Next) { AtalkDdpReferenceByPtrDpc(pDdpAddr, pErr); if (ATALK_SUCCESS(*pErr)) { // Ok, this address is referenced! ASSERT (VALID_DDP_ADDROBJ(pDdpAddr)); *ppDdpAddr = pDdpAddr; break; } } } VOID FASTCALL AtalkDdpDeref( IN OUT PDDP_ADDROBJ pDdpAddr, IN BOOLEAN AtDpc ) /*++ Routine Description: Arguments: Return Value: --*/ { ATALK_ERROR error = ATALK_NO_ERROR; PATALK_NODE pNode = pDdpAddr->ddpao_Node; BOOLEAN done = FALSE; KIRQL OldIrql; ASSERT (VALID_DDP_ADDROBJ(pDdpAddr)); if (AtDpc) { ACQUIRE_SPIN_LOCK_DPC(&pDdpAddr->ddpao_Lock); } else { ACQUIRE_SPIN_LOCK(&pDdpAddr->ddpao_Lock, &OldIrql); } ASSERT(pDdpAddr->ddpao_RefCount > 0); if (--(pDdpAddr->ddpao_RefCount) == 0) { done = TRUE; } if (AtDpc) { RELEASE_SPIN_LOCK_DPC(&pDdpAddr->ddpao_Lock); } else { RELEASE_SPIN_LOCK(&pDdpAddr->ddpao_Lock, OldIrql); } if (done) { PDDP_ADDROBJ * ppDdpAddr; int index; ASSERT((pDdpAddr->ddpao_Flags & DDPAO_CLOSING) != 0); if ((pDdpAddr->ddpao_Flags & DDPAO_CLOSING) == 0) { KeBugCheck(0); } // Remove this guy from the node linkage if (AtDpc) { ACQUIRE_SPIN_LOCK_DPC(&pNode->an_Lock); } else { ACQUIRE_SPIN_LOCK(&pNode->an_Lock, &OldIrql); } index = HASH_ATALK_ADDR(&pDdpAddr->ddpao_Addr) % NODE_DDPAO_HASH_SIZE; for (ppDdpAddr = &pNode->an_DdpAoHash[index]; *ppDdpAddr != NULL; ppDdpAddr = &((*ppDdpAddr)->ddpao_Next)) { if (*ppDdpAddr == pDdpAddr) { *ppDdpAddr = pDdpAddr->ddpao_Next; break; } } if (AtDpc) { RELEASE_SPIN_LOCK_DPC(&pNode->an_Lock); } else { RELEASE_SPIN_LOCK(&pNode->an_Lock, OldIrql); } DBGPRINT(DBG_COMP_DDP, DBG_LEVEL_INFO, ("AtalkDdpDeref: Closing ddp socket %lx\n", pDdpAddr->ddpao_Addr.ata_Socket)); if (pDdpAddr->ddpao_EventInfo != NULL) { AtalkFreeMemory(pDdpAddr->ddpao_EventInfo); } // Call the completion routines if (*pDdpAddr->ddpao_CloseComp != NULL) { (*pDdpAddr->ddpao_CloseComp)(ATALK_NO_ERROR, pDdpAddr->ddpao_CloseCtx); } // Free the address structure AtalkFreeMemory(pDdpAddr); // Dereference the node for this address AtalkNodeDereference(pNode); } } VOID AtalkDdpNewHandlerForSocket( IN PDDP_ADDROBJ pDdpAddr, IN DDPAO_HANDLER pSktHandler, IN PVOID pSktHandlerCtx ) /*++ Routine Description: Arguments: Return Value: --*/ { KIRQL OldIrql; ASSERT (VALID_DDP_ADDROBJ(pDdpAddr)); ACQUIRE_SPIN_LOCK(&pDdpAddr->ddpao_Lock, &OldIrql); pDdpAddr->ddpao_Handler = pSktHandler; pDdpAddr->ddpao_HandlerCtx = pSktHandlerCtx; RELEASE_SPIN_LOCK(&pDdpAddr->ddpao_Lock, OldIrql); }