/*++ Copyright (c) 1992 Microsoft Corporation Module Name: node.c Abstract: This module contains the Appletalk Node management code. Author: Jameel Hyder (jameelh@microsoft.com) Nikhil Kamkolkar (nikhilk@microsoft.com) Revision History: 19 Jun 1992 Initial Version Notes: Tab stop: 4 --*/ #include #pragma hdrstop #define FILENUM NODE #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGEINIT, AtalkInitNodeCreateOnPort) #pragma alloc_text(PAGEINIT, AtalkInitNodeAllocate) #pragma alloc_text(PAGEINIT, AtalkInitNodeGetPramAddr) #pragma alloc_text(PAGEINIT, AtalkInitNodeSavePramAddr) #endif ATALK_ERROR AtalkInitNodeCreateOnPort( PPORT_DESCRIPTOR pPortDesc, BOOLEAN AllowStartupRange, BOOLEAN RouterNode, PATALK_NODEADDR NodeAddr ) /*++ Routine Description: Arguments: Return Value: --*/ { PATALK_NODE pNode; ATALK_ERROR error = ATALK_NO_ERROR; ATALK_NODEADDR desiredNode = { UNKNOWN_NETWORK, UNKNOWN_NODE}; PWSTR NodeName; KIRQL OldIrql; ACQUIRE_SPIN_LOCK(&pPortDesc->pd_Lock, &OldIrql); do { if ((pPortDesc->pd_Flags & PD_FINDING_NODE) == 0) { pPortDesc->pd_Flags |= PD_FINDING_NODE; } else { // Return if we are already trying to find a node error = ATALK_NODE_FINDING; break; } // We should not be here if we have already allocated a router node and the user nodes ASSERT(!RouterNode || ((pPortDesc->pd_Flags & PD_ROUTER_NODE) == 0)); ASSERT ((pPortDesc->pd_Flags & (PD_ROUTER_NODE | PD_USER_NODE_1 | PD_USER_NODE_2)) != (PD_ROUTER_NODE | PD_USER_NODE_1 | PD_USER_NODE_2)); // On non-extended ports we only allow one node! The theory being that some // LocalTalk cards are too smart for their own good and have a concept of // their "source node number" and thus only support one node, also on // non-extended ports, nodes are scarse. if (!EXT_NET(pPortDesc)) { // For a localtalk node we do things differently. // During initialization time, we would have obtained // the address from the mac, that will be the node // address. ASSERT(pPortDesc->pd_Flags & PD_BOUND); ASSERT(pPortDesc->pd_AlapNode != 0); // This needs to be initialized to UNKNOWN_NETWORK or obtained // from the net during initialization. ASSERT(pPortDesc->pd_NetworkRange.anr_FirstNetwork == UNKNOWN_NETWORK); if (!ATALK_SUCCESS((error = AtalkInitNodeAllocate(pPortDesc, &pNode)))) { LOG_ERRORONPORT(pPortDesc, EVENT_ATALK_INIT_COULDNOTGETNODE, 0, NULL, 0); break; } // Use the allocated structure to set the info. // Thread this into the port structure. pPortDesc->pd_LtNetwork = pNode->an_NodeAddr.atn_Network = pPortDesc->pd_NetworkRange.anr_FirstNetwork; pNode->an_NodeAddr.atn_Node = (BYTE)pPortDesc->pd_AlapNode; // Reference the port for this node. AtalkPortReferenceByPtrNonInterlock(pPortDesc, &error); if (!ATALK_SUCCESS(error)) { AtalkFreeMemory(pNode); break; } // Now put it in the port descriptor pNode->an_Next = pPortDesc->pd_Nodes; pPortDesc->pd_Nodes = pNode; } else { // Use PRAM values if we have them if (RouterNode) { NodeName = ROUTER_NODE_VALUE; if (pPortDesc->pd_RoutersPramNode.atn_Network != UNKNOWN_NETWORK) { desiredNode = pPortDesc->pd_RoutersPramNode; } } else { if ((pPortDesc->pd_Flags & PD_USER_NODE_1) == 0) { NodeName = USER_NODE1_VALUE; if (pPortDesc->pd_UsersPramNode1.atn_Network != UNKNOWN_NETWORK) { // If we are not a router node, and the first user node // has not been allocated... desiredNode = pPortDesc->pd_UsersPramNode1; } } else if ((pPortDesc->pd_Flags & PD_USER_NODE_2) == 0) { NodeName = USER_NODE2_VALUE; if (pPortDesc->pd_UsersPramNode2.atn_Network != UNKNOWN_NETWORK) { // If we are not a router node, and the second user node // has not been allocated... desiredNode = pPortDesc->pd_UsersPramNode2; } } } // Flags should be set so future get node requests return failure // until we are done with this attempt. We need to call // the aarp routines without the lock held - they will // block. ASSERT(pPortDesc->pd_Flags & PD_FINDING_NODE); RELEASE_SPIN_LOCK(&pPortDesc->pd_Lock, OldIrql); // If this routine succeeds in finding the node, it // will chain in the atalkNode into the port. It also // returns with the proper flags set/reset in the // pPortDesc structure. It will also have referenced the port // and inserted the node into the port's node list. error = AtalkInitAarpForNodeOnPort(pPortDesc, AllowStartupRange, desiredNode, &pNode); ACQUIRE_SPIN_LOCK(&pPortDesc->pd_Lock, &OldIrql); if (!ATALK_SUCCESS(error)) { // AARP for node failed. LOG_ERRORONPORT(pPortDesc, EVENT_ATALK_INIT_COULDNOTGETNODE, 0, NULL, 0); } } } while (FALSE); // Ok, done finding node. No need for a crit section. pPortDesc->pd_Flags &= ~PD_FINDING_NODE; if (ATALK_SUCCESS(error)) { // If router node, remember it in port descriptor // Do this before setting up the rtmp/nbp listeners. // In anycase, clients must check this value for null, // not guaranteed as zip socket could already be open. if (RouterNode) pPortDesc->pd_RouterNode = pNode; RELEASE_SPIN_LOCK(&pPortDesc->pd_Lock, OldIrql); // Setup the RTMP, NBP and EP listeners on this node. // These will be the non-router versions. StartRouting // calls will then switch them to be the router versions // at the appropriate time. error = AtalkInitDdpOpenStaticSockets(pPortDesc, pNode); if (ATALK_SUCCESS(error)) { if (EXT_NET(pPortDesc)) { // We always save this address. AtalkInitNodeSavePramAddr(pPortDesc, NodeName, &pNode->an_NodeAddr); } // Return the address of the node opened. if (NodeAddr != NULL) *NodeAddr = pNode->an_NodeAddr; } else { // Error opening sockets. Release node, return failure LOG_ERRORONPORT(pPortDesc, EVENT_ATALK_NODE_OPENSOCKETS, 0, NULL, 0); AtalkNodeReleaseOnPort(pPortDesc, pNode); } } else { RELEASE_SPIN_LOCK(&pPortDesc->pd_Lock, OldIrql); } if (!ATALK_SUCCESS(error)) { DBGPRINT(DBG_COMP_NODE, DBG_LEVEL_INFO, ("Creation node on port %lx failed! %lx\n", pPortDesc, error)); } else { DBGPRINT(DBG_COMP_NODE, DBG_LEVEL_INFO, ("Creation node on port %lx with addr %lx.%lx and p%lx\n", pPortDesc, pNode->an_NodeAddr.atn_Network, pNode->an_NodeAddr.atn_Node, pNode)); } return(error); } ATALK_ERROR AtalkNodeReleaseOnPort( PPORT_DESCRIPTOR pPortDesc, PATALK_NODE pNode ) /*++ Routine Description: Arguments: Return Value: --*/ { PDDP_ADDROBJ pDdpAddr, pNextAddr; ATALK_ERROR error; KIRQL OldIrql; SHORT i; DBGPRINT(DBG_COMP_NODE, DBG_LEVEL_WARN, ("AtalkNodeReleaseOnPort: Releasing node %lx on port %lx!\n", pNode, pPortDesc)); ACQUIRE_SPIN_LOCK(&pNode->an_Lock, &OldIrql); if ((pNode->an_NodeAddr.atn_Network == AtalkUserNode1.atn_Network) && (pNode->an_NodeAddr.atn_Node == AtalkUserNode1.atn_Node)) { pPortDesc->pd_Flags &= ~PD_USER_NODE_1; AtalkUserNode1.atn_Network = 0; AtalkUserNode1.atn_Node = 0; } else if ((pNode->an_NodeAddr.atn_Network == AtalkUserNode2.atn_Network) && (pNode->an_NodeAddr.atn_Node == AtalkUserNode2.atn_Node)) { pPortDesc->pd_Flags &= ~PD_USER_NODE_2; AtalkUserNode2.atn_Network = 0; AtalkUserNode2.atn_Node = 0; } if ((pNode->an_Flags & AN_CLOSING) == 0) { // Set the closing flag. pNode->an_Flags |= AN_CLOSING; // First close all the sockets on the node for (i = 0; i < NODE_DDPAO_HASH_SIZE; i++) { pNextAddr = NULL; AtalkDdpReferenceNextNc(pNode->an_DdpAoHash[i], &pDdpAddr, &error); if (!ATALK_SUCCESS(error)) { // Check the other hash table entries. No non-closing // sockets on this list. continue; } while (TRUE) { // Get the next non-closing node using our referenced node before // closing it. Note we use pDdpAddr->...Flink. AtalkDdpReferenceNextNc(pDdpAddr->ddpao_Next, &pNextAddr, &error); // Close the referenced ddp addr after releasing the node lock. RELEASE_SPIN_LOCK(&pNode->an_Lock, OldIrql); if (pDdpAddr->ddpao_Flags & DDPAO_SOCK_INTERNAL) { AtalkDdpCloseAddress(pDdpAddr, NULL, NULL); } else { AtalkDdpPnPSuspendAddress(pDdpAddr); } // Dereference the address. AtalkDdpDereference(pDdpAddr); ACQUIRE_SPIN_LOCK(&pNode->an_Lock, &OldIrql); if (pNextAddr != NULL) pDdpAddr = pNextAddr; else break; } } RELEASE_SPIN_LOCK(&pNode->an_Lock, OldIrql); // Remove the creation reference for this node. AtalkNodeDereference(pNode); } else { // We are already closing. RELEASE_SPIN_LOCK(&pNode->an_Lock, OldIrql); } return(ATALK_NO_ERROR); } BOOLEAN AtalkNodeExistsOnPort( PPORT_DESCRIPTOR pPortDesc, PATALK_NODEADDR pNodeAddr ) /*++ Routine Description: Arguments: Return Value: --*/ { PATALK_NODE pCheckNode; BOOLEAN exists = FALSE; ACQUIRE_SPIN_LOCK_DPC(&pPortDesc->pd_Lock); for (pCheckNode = pPortDesc->pd_Nodes; pCheckNode != NULL; pCheckNode = pCheckNode->an_Next) { if (ATALK_NODES_EQUAL(&pCheckNode->an_NodeAddr, pNodeAddr)) { exists = TRUE; break; } } RELEASE_SPIN_LOCK_DPC(&pPortDesc->pd_Lock); return(exists); } VOID AtalkInitNodeSavePramAddr( IN PPORT_DESCRIPTOR pPortDesc, IN PWSTR RegValue, OUT PATALK_NODEADDR pNode ) /*++ Routine Description: Arguments: Return Value: --*/ { UNICODE_STRING valueName; ULONG bytesWritten; ULONG ValueToSave; // Save the node value as xxxx00yy where xxxx is network, yy is node ValueToSave = (pNode->atn_Network << 16) + pNode->atn_Node; RtlInitUnicodeString (&valueName, RegValue); ZwSetValueKey(pPortDesc->pd_AdapterInfoHandle, &valueName, 0, REG_DWORD, &ValueToSave, sizeof(ULONG)); } VOID AtalkInitNodeGetPramAddr( IN PPORT_DESCRIPTOR pPortDesc, IN PWSTR RegValue, OUT PATALK_NODEADDR pNode ) /*++ Routine Description: Arguments: Return Value: --*/ { NTSTATUS Status; UNICODE_STRING valueName; ULONG bytesWritten; ULONG ValueRead; BYTE buffer[sizeof(KEY_VALUE_FULL_INFORMATION) + 32]; PKEY_VALUE_FULL_INFORMATION nodeValue = (PKEY_VALUE_FULL_INFORMATION)buffer; RtlInitUnicodeString (&valueName, RegValue); Status = ZwQueryValueKey(pPortDesc->pd_AdapterInfoHandle, &valueName, KeyValueFullInformation, buffer, sizeof(buffer), &bytesWritten); if (NT_SUCCESS(Status)) { ValueRead = *(PULONG)(buffer + nodeValue->DataOffset); } else { ValueRead = 0; ASSERT (UNKNOWN_NETWORK == 0); ASSERT (UNKNOWN_NODE == 0); } pNode->atn_Node = (BYTE)(ValueRead & 0xFF); pNode->atn_Network = (USHORT)(ValueRead >> 16); if ((pNode->atn_Network == UNKNOWN_NETWORK) || (pNode->atn_Node == UNKNOWN_NODE)) { pNode->atn_Node = UNKNOWN_NODE; pNode->atn_Network = UNKNOWN_NETWORK; } } VOID AtalkZapPramValue( IN PPORT_DESCRIPTOR pPortDesc, IN PWSTR RegValue ) { UNICODE_STRING valueName; ULONG bytesWritten; ULONG ValueToSave; // Write 0 to the value to zap it for now. ValueToSave = 0; RtlInitUnicodeString (&valueName, RegValue); ZwSetValueKey(pPortDesc->pd_AdapterInfoHandle, &valueName, 0, REG_DWORD, &ValueToSave, sizeof(ULONG)); } ATALK_ERROR AtalkInitNodeAllocate( IN PPORT_DESCRIPTOR pPortDesc, OUT PATALK_NODE *ppNode ) /*++ Routine Description: Arguments: Return Value: --*/ { PATALK_NODE pNode; // Allocate a new active Node structure if ((pNode = (PATALK_NODE)AtalkAllocZeroedMemory(sizeof(ATALK_NODE))) == NULL) { return(ATALK_RESR_MEM); } // Initialize some elements of the structure. Remaining stuff // done when the node is actually being inserted into the port // hash table. #if DBG pNode->an_Signature = AN_SIGNATURE; #endif // Initialize the Nbp Id & Enumerator pNode->an_NextNbpId = 0; pNode->an_NextNbpEnum = 0; pNode->an_NextDynSkt = FIRST_DYNAMIC_SOCKET; INITIALIZE_SPIN_LOCK(&pNode->an_Lock); pNode->an_Port = pPortDesc; // Port on which node exists pNode->an_RefCount = 1; // Reference for creation. // Return pointer to allocated node *ppNode = pNode; return(ATALK_NO_ERROR); } VOID AtalkNodeRefByAddr( IN PPORT_DESCRIPTOR pPortDesc, IN PATALK_NODEADDR NodeAddr, OUT PATALK_NODE * ppNode, OUT PATALK_ERROR pErr ) /*++ Routine Description: Arguments: Return Value: --*/ { PATALK_NODE pNode; KIRQL OldIrql; BOOLEAN foundNode = FALSE; *pErr = ATALK_NODE_NONEXISTENT; ACQUIRE_SPIN_LOCK(&pPortDesc->pd_Lock, &OldIrql); for (pNode = pPortDesc->pd_Nodes; pNode != NULL; pNode = pNode->an_Next) { ASSERT(VALID_ATALK_NODE(pNode)); // Note: On non-extended ports, there should be only one pNode. if (((NodeAddr->atn_Network == CABLEWIDE_BROADCAST_NETWORK) || (pNode->an_NodeAddr.atn_Network == NodeAddr->atn_Network) || (!EXT_NET(pPortDesc) && (pNode->an_NodeAddr.atn_Network == UNKNOWN_NETWORK))) && ((NodeAddr->atn_Node == ATALK_BROADCAST_NODE) || (pNode->an_NodeAddr.atn_Node == NodeAddr->atn_Node))) { DBGPRINT(DBG_COMP_NODE, DBG_LEVEL_INFO, ("AtalkNodeRefByAddr: Found: %lx.%lx for Lookup: %lx.%lx\n", pNode->an_NodeAddr.atn_Network, pNode->an_NodeAddr.atn_Node, NodeAddr->atn_Network, NodeAddr->atn_Node)); foundNode = TRUE; break; } } if (foundNode) { AtalkNodeRefByPtr(pNode, pErr); // Return a pointer to the referenced node. if (ATALK_SUCCESS(*pErr)) { ASSERT(ppNode != NULL); ASSERT(pNode != NULL); *ppNode = pNode; } } RELEASE_SPIN_LOCK(&pPortDesc->pd_Lock, OldIrql); } VOID AtalkNodeRefNextNc( IN PATALK_NODE pNode, IN PATALK_NODE * ppNode, OUT PATALK_ERROR pErr ) /*++ Routine Description: MUST BE CALLED WITH THE PORTLOCK HELD! Arguments: Return Value: --*/ { *pErr = ATALK_FAILURE; *ppNode = NULL; for (; pNode != NULL; pNode = pNode->an_Next) { ASSERT(VALID_ATALK_NODE(pNode)); AtalkNodeRefByPtr(pNode, pErr); if (ATALK_SUCCESS(*pErr)) { // Ok, this node is referenced! *ppNode = pNode; break; } } } VOID AtalkNodeDeref( IN OUT PATALK_NODE pNode ) /*++ Routine Description: Arguments: Return Value: --*/ { PPORT_DESCRIPTOR pPortDesc = pNode->an_Port; KIRQL OldIrql; BOOLEAN done = FALSE; ASSERT(VALID_ATALK_NODE(pNode)); ACQUIRE_SPIN_LOCK(&pNode->an_Lock, &OldIrql); ASSERT(pNode->an_RefCount > 0); if (--pNode->an_RefCount == 0) { done = TRUE; } RELEASE_SPIN_LOCK(&pNode->an_Lock, OldIrql); if (done) { PATALK_NODE *ppNode; ASSERT((pNode->an_Flags & AN_CLOSING) != 0); DBGPRINT(DBG_COMP_NODE, DBG_LEVEL_WARN, ("AtalkNodeDeref: Freeing node %lx\n", pNode)); ACQUIRE_SPIN_LOCK(&pPortDesc->pd_Lock, &OldIrql); // Remove this guy from the port linkage for (ppNode = &pNode->an_Port->pd_Nodes; *ppNode != NULL; ppNode = &((*ppNode)->an_Next)) { if (*ppNode == pNode) { *ppNode = pNode->an_Next; break; } } RELEASE_SPIN_LOCK(&pPortDesc->pd_Lock, OldIrql); // Dereference the port for this node AtalkPortDereference(pPortDesc); // Free the node structure AtalkFreeMemory(pNode); } }