/* * TAP-Windows -- A kernel driver to provide virtual tap * device functionality on Windows. * * This code was inspired by the CIPE-Win32 driver by Damion K. Wilson. * * This source code is Copyright (C) 2002-2014 OpenVPN Technologies, Inc., * and is released under the GPL version 2 (see below). * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program (see the file COPYING included with this * distribution); if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ // // Include files. // #include "tap.h" //====================================================================== // TAP Send Path Support //====================================================================== #ifdef ALLOC_PRAGMA #pragma alloc_text( PAGE, TapDeviceRead) #endif // ALLOC_PRAGMA // checksum code for ICMPv6 packet, taken from dhcp.c / udp_checksum // see RFC 4443, 2.3, and RFC 2460, 8.1 USHORT icmpv6_checksum( __in const UCHAR *buf, __in const int len_icmpv6, __in const UCHAR *saddr6, __in const UCHAR *daddr6 ) { USHORT word16; ULONG sum = 0; int i; // make 16 bit words out of every two adjacent 8 bit words and // calculate the sum of all 16 bit words for (i = 0; i < len_icmpv6; i += 2) { word16 = ((buf[i] << 8) & 0xFF00) + ((i + 1 < len_icmpv6) ? (buf[i+1] & 0xFF) : 0); sum += word16; } // add the IPv6 pseudo header which contains the IP source and destination addresses for (i = 0; i < 16; i += 2) { word16 =((saddr6[i] << 8) & 0xFF00) + (saddr6[i+1] & 0xFF); sum += word16; } for (i = 0; i < 16; i += 2) { word16 =((daddr6[i] << 8) & 0xFF00) + (daddr6[i+1] & 0xFF); sum += word16; } // the next-header number and the length of the ICMPv6 packet sum += (USHORT) IPPROTO_ICMPV6 + (USHORT) len_icmpv6; // keep only the last 16 bits of the 32 bit calculated sum and add the carries while (sum >> 16) sum = (sum & 0xFFFF) + (sum >> 16); // Take the one's complement of sum return ((USHORT) ~sum); } /* // check IPv6 packet for "is this an IPv6 Neighbor Solicitation that // the tap driver needs to answer?" // see RFC 4861 4.3 for the different cases static IPV6ADDR IPV6_NS_TARGET_MCAST = { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x00, 0x00, 0x08 }; static IPV6ADDR IPV6_NS_TARGET_UNICAST = { 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08 }; BOOLEAN HandleIPv6NeighborDiscovery( __in PTAP_ADAPTER_CONTEXT Adapter, __in UCHAR * m_Data ) { const ETH_HEADER * e = (ETH_HEADER *) m_Data; const IPV6HDR *ipv6 = (IPV6HDR *) (m_Data + sizeof (ETH_HEADER)); const ICMPV6_NS * icmpv6_ns = (ICMPV6_NS *) (m_Data + sizeof (ETH_HEADER) + sizeof (IPV6HDR)); ICMPV6_NA_PKT *na; USHORT icmpv6_len, icmpv6_csum; // we don't really care about the destination MAC address here // - it's either a multicast MAC, or the userland destination MAC // but since the TAP driver is point-to-point, all packets are "for us" // IPv6 target address must be ff02::1::ff00:8 (multicast for // initial NS) or fe80::1 (unicast for recurrent NUD) if ( memcmp( ipv6->daddr, IPV6_NS_TARGET_MCAST, sizeof(IPV6ADDR) ) != 0 && memcmp( ipv6->daddr, IPV6_NS_TARGET_UNICAST, sizeof(IPV6ADDR) ) != 0 ) { return FALSE; // wrong target address } // IPv6 Next-Header must be ICMPv6 if ( ipv6->nexthdr != IPPROTO_ICMPV6 ) { return FALSE; // wrong next-header } // ICMPv6 type+code must be 135/0 for NS if ( icmpv6_ns->type != ICMPV6_TYPE_NS || icmpv6_ns->code != ICMPV6_CODE_0 ) { return FALSE; // wrong ICMPv6 type } // ICMPv6 target address must be fe80::8 (magic) if ( memcmp( icmpv6_ns->target_addr, IPV6_NS_TARGET_UNICAST, sizeof(IPV6ADDR) ) != 0 ) { return FALSE; // not for us } // packet identified, build magic response packet na = (ICMPV6_NA_PKT *) MemAlloc (sizeof (ICMPV6_NA_PKT), TRUE); if ( !na ) return FALSE; //------------------------------------------------ // Initialize Neighbour Advertisement reply packet //------------------------------------------------ // ethernet header na->eth.proto = htons(NDIS_ETH_TYPE_IPV6); ETH_COPY_NETWORK_ADDRESS(na->eth.dest, Adapter->PermanentAddress); ETH_COPY_NETWORK_ADDRESS(na->eth.src, Adapter->m_TapToUser.dest); // IPv6 header na->ipv6.version_prio = ipv6->version_prio; NdisMoveMemory( na->ipv6.flow_lbl, ipv6->flow_lbl, sizeof(na->ipv6.flow_lbl) ); icmpv6_len = sizeof(ICMPV6_NA_PKT) - sizeof(ETH_HEADER) - sizeof(IPV6HDR); na->ipv6.payload_len = htons(icmpv6_len); na->ipv6.nexthdr = IPPROTO_ICMPV6; na->ipv6.hop_limit = 255; NdisMoveMemory( na->ipv6.saddr, IPV6_NS_TARGET_UNICAST, sizeof(IPV6ADDR) ); NdisMoveMemory( na->ipv6.daddr, ipv6->saddr, sizeof(IPV6ADDR) ); // ICMPv6 na->icmpv6.type = ICMPV6_TYPE_NA; na->icmpv6.code = ICMPV6_CODE_0; na->icmpv6.checksum = 0; na->icmpv6.rso_bits = 0x60; // Solicited + Override NdisZeroMemory( na->icmpv6.reserved, sizeof(na->icmpv6.reserved) ); NdisMoveMemory( na->icmpv6.target_addr, IPV6_NS_TARGET_UNICAST, sizeof(IPV6ADDR) ); // ICMPv6 option "Target Link Layer Address" na->icmpv6.opt_type = ICMPV6_OPTION_TLLA; na->icmpv6.opt_length = ICMPV6_LENGTH_TLLA; ETH_COPY_NETWORK_ADDRESS( na->icmpv6.target_macaddr, Adapter->m_TapToUser.dest ); // calculate and set checksum icmpv6_csum = icmpv6_checksum ( (UCHAR*) &(na->icmpv6), icmpv6_len, na->ipv6.saddr, na->ipv6.daddr ); na->icmpv6.checksum = htons( icmpv6_csum ); DUMP_PACKET ("HandleIPv6NeighborDiscovery", (unsigned char *) na, sizeof (ICMPV6_NA_PKT)); IndicateReceivePacket (Adapter, (UCHAR *) na, sizeof (ICMPV6_NA_PKT)); MemFree (na, sizeof (ICMPV6_NA_PKT)); return TRUE; // all fine } //=================================================== // Generate an ARP reply message for specific kinds // ARP queries. //=================================================== BOOLEAN ProcessARP( __in PTAP_ADAPTER_CONTEXT Adapter, __in const PARP_PACKET src, __in const IPADDR adapter_ip, __in const IPADDR ip_network, __in const IPADDR ip_netmask, __in const MACADDR mac ) { //----------------------------------------------- // Is this the kind of packet we are looking for? //----------------------------------------------- if (src->m_Proto == htons (NDIS_ETH_TYPE_ARP) && MAC_EQUAL (src->m_MAC_Source, Adapter->PermanentAddress) && MAC_EQUAL (src->m_ARP_MAC_Source, Adapter->PermanentAddress) && ETH_IS_BROADCAST(src->m_MAC_Destination) && src->m_ARP_Operation == htons (ARP_REQUEST) && src->m_MAC_AddressType == htons (MAC_ADDR_TYPE) && src->m_MAC_AddressSize == sizeof (MACADDR) && src->m_PROTO_AddressType == htons (NDIS_ETH_TYPE_IPV4) && src->m_PROTO_AddressSize == sizeof (IPADDR) && src->m_ARP_IP_Source == adapter_ip && (src->m_ARP_IP_Destination & ip_netmask) == ip_network && src->m_ARP_IP_Destination != adapter_ip) { ARP_PACKET *arp = (ARP_PACKET *) MemAlloc (sizeof (ARP_PACKET), TRUE); if (arp) { //---------------------------------------------- // Initialize ARP reply fields //---------------------------------------------- arp->m_Proto = htons (NDIS_ETH_TYPE_ARP); arp->m_MAC_AddressType = htons (MAC_ADDR_TYPE); arp->m_PROTO_AddressType = htons (NDIS_ETH_TYPE_IPV4); arp->m_MAC_AddressSize = sizeof (MACADDR); arp->m_PROTO_AddressSize = sizeof (IPADDR); arp->m_ARP_Operation = htons (ARP_REPLY); //---------------------------------------------- // ARP addresses //---------------------------------------------- ETH_COPY_NETWORK_ADDRESS (arp->m_MAC_Source, mac); ETH_COPY_NETWORK_ADDRESS (arp->m_MAC_Destination, Adapter->PermanentAddress); ETH_COPY_NETWORK_ADDRESS (arp->m_ARP_MAC_Source, mac); ETH_COPY_NETWORK_ADDRESS (arp->m_ARP_MAC_Destination, Adapter->PermanentAddress); arp->m_ARP_IP_Source = src->m_ARP_IP_Destination; arp->m_ARP_IP_Destination = adapter_ip; DUMP_PACKET ("ProcessARP", (unsigned char *) arp, sizeof (ARP_PACKET)); IndicateReceivePacket (Adapter, (UCHAR *) arp, sizeof (ARP_PACKET)); MemFree (arp, sizeof (ARP_PACKET)); } return TRUE; } else return FALSE; } */ //============================================================= // CompleteIRP is normally called with an adapter -> userspace // network packet and an IRP (Pending I/O request) from userspace. // // The IRP will normally represent a queued overlapped read // operation from userspace that is in a wait state. // // Use the ethernet packet to satisfy the IRP. //============================================================= VOID tapCompletePendingReadIrp( __in PIRP Irp, __in PTAP_PACKET TapPacket ) { int offset; int len; NTSTATUS status = STATUS_UNSUCCESSFUL; ASSERT(Irp); ASSERT(TapPacket); //------------------------------------------- // While TapPacket always contains a // full ethernet packet, including the // ethernet header, in point-to-point mode, // we only want to return the IPv4 // component. //------------------------------------------- if (TapPacket->m_SizeFlags & TP_TUN) { offset = ETHERNET_HEADER_SIZE; len = (int) (TapPacket->m_SizeFlags & TP_SIZE_MASK) - ETHERNET_HEADER_SIZE; } else { offset = 0; len = (TapPacket->m_SizeFlags & TP_SIZE_MASK); } if (len < 0 || (int) Irp->IoStatus.Information < len) { Irp->IoStatus.Information = 0; Irp->IoStatus.Status = status = STATUS_BUFFER_OVERFLOW; NOTE_ERROR (); } else { Irp->IoStatus.Information = len; Irp->IoStatus.Status = status = STATUS_SUCCESS; // Copy packet data NdisMoveMemory( Irp->AssociatedIrp.SystemBuffer, TapPacket->m_Data + offset, len ); } // Free the TAP packet NdisFreeMemory(TapPacket,0,0); // Complete the IRP IoCompleteRequest (Irp, IO_NETWORK_INCREMENT); } VOID tapProcessSendPacketQueue( __in PTAP_ADAPTER_CONTEXT Adapter ) { KIRQL irql; // Process the send packet queue KeAcquireSpinLock(&Adapter->SendPacketQueue.QueueLock,&irql); while(Adapter->SendPacketQueue.Count > 0 ) { PIRP irp; PTAP_PACKET tapPacket; // Fetch a read IRP irp = IoCsqRemoveNextIrp( &Adapter->PendingReadIrpQueue.CsqQueue, NULL ); if( irp == NULL ) { // No IRP to satisfy break; } // Fetch a queued TAP send packet tapPacket = tapPacketRemoveHeadLocked( &Adapter->SendPacketQueue ); ASSERT(tapPacket); // BUGBUG!!! Investigate whether release/reacquire can cause // out-of-order IRP completion. Also, whether user-mode can // tolerate out-of-order packets. // Release packet queue lock while completing the IRP //KeReleaseSpinLock(&Adapter->SendPacketQueue.QueueLock,irql); // Complete the read IRP from queued TAP send packet. tapCompletePendingReadIrp(irp,tapPacket); // Reqcquire packet queue lock after completing the IRP //KeAcquireSpinLock(&Adapter->SendPacketQueue.QueueLock,&irql); } KeReleaseSpinLock(&Adapter->SendPacketQueue.QueueLock,irql); } // Flush the pending send TAP packet queue. VOID tapFlushSendPacketQueue( __in PTAP_ADAPTER_CONTEXT Adapter ) { KIRQL irql; // Process the send packet queue KeAcquireSpinLock(&Adapter->SendPacketQueue.QueueLock,&irql); DEBUGP (("[TAP] tapFlushSendPacketQueue: Flushing %d TAP packets\n", Adapter->SendPacketQueue.Count)); while(Adapter->SendPacketQueue.Count > 0 ) { PTAP_PACKET tapPacket; // Fetch a queued TAP send packet tapPacket = tapPacketRemoveHeadLocked( &Adapter->SendPacketQueue ); ASSERT(tapPacket); // Free the TAP packet NdisFreeMemory(tapPacket,0,0); } KeReleaseSpinLock(&Adapter->SendPacketQueue.QueueLock,irql); } VOID tapAdapterTransmit( __in PTAP_ADAPTER_CONTEXT Adapter, __in PNET_BUFFER NetBuffer, __in BOOLEAN DispatchLevel ) /*++ Routine Description: This routine is called to transmit an individual net buffer using a style similar to the previous NDIS 5 AdapterTransmit function. In this implementation adapter state and NB length checks have already been done before this function has been called. The net buffer will be completed by the calling routine after this routine exits. So, under this design it is necessary to make a deep copy of frame data in the net buffer. This routine creates a flat buffer copy of NB frame data. This is an unnecessary performance bottleneck. However, the bottleneck is probably not significant or measurable except for adapters running at 1Gbps or greater speeds. Since this adapter is currently running at 100Mbps this defect can be ignored. Runs at IRQL <= DISPATCH_LEVEL Arguments: Adapter Pointer to our adapter context NetBuffer Pointer to the net buffer to transmit DispatchLevel TRUE if called at IRQL == DISPATCH_LEVEL Return Value: None. In the Microsoft NDIS 6 architecture there is no per-packet status. --*/ { NDIS_STATUS status; ULONG packetLength; PTAP_PACKET tapPacket; PVOID packetData; packetLength = NET_BUFFER_DATA_LENGTH(NetBuffer); // Allocate TAP packet memory tapPacket = (PTAP_PACKET )NdisAllocateMemoryWithTagPriority( Adapter->MiniportAdapterHandle, TAP_PACKET_SIZE (packetLength), TAP_PACKET_TAG, NormalPoolPriority ); if(tapPacket == NULL) { DEBUGP (("[TAP] tapAdapterTransmit: TAP packet allocation failed\n")); return; } tapPacket->m_SizeFlags = (packetLength & TP_SIZE_MASK); // // Reassemble packet contents // -------------------------- // NdisGetDataBuffer does most of the work. There are two cases: // // 1.) If the NB data was not contiguous it will copy the entire // NB's data to m_data and return pointer to m_data. // 2.) If the NB data was contiguous it returns a pointer to the // first byte of the contiguous data instead of a pointer to m_Data. // In this case the data will not have been copied to m_Data. Copy // to m_Data will need to be done in an extra step. // // Case 1.) is the most likely in normal operation. // packetData = NdisGetDataBuffer(NetBuffer,packetLength,tapPacket->m_Data,1,0); if(packetData == NULL) { DEBUGP (("[TAP] tapAdapterTransmit: Could not get packet data\n")); NdisFreeMemory(tapPacket,0,0); return; } if(packetData != tapPacket->m_Data) { // Packet data was contiguous and not yet copied to m_Data. NdisMoveMemory(tapPacket->m_Data,packetData,packetLength); } DUMP_PACKET ("AdapterTransmit", tapPacket->m_Data, packetLength); //===================================================== // If IPv4 packet, check whether or not packet // was truncated. //===================================================== #if PACKET_TRUNCATION_CHECK IPv4PacketSizeVerify( tapPacket->m_Data, packetLength, FALSE, "TX", &Adapter->m_TxTrunc ); #endif //===================================================== // Are we running in DHCP server masquerade mode? // // If so, catch both DHCP requests and ARP queries // to resolve the address of our virtual DHCP server. //===================================================== #if 0 if (Adapter->m_dhcp_enabled) { const ETH_HEADER *eth = (ETH_HEADER *) tapPacket->m_Data; const IPHDR *ip = (IPHDR *) (tapPacket->m_Data + sizeof (ETH_HEADER)); const UDPHDR *udp = (UDPHDR *) (tapPacket->m_Data + sizeof (ETH_HEADER) + sizeof (IPHDR)); // ARP packet? if (packetLength == sizeof (ARP_PACKET) && eth->proto == htons (NDIS_ETH_TYPE_ARP) && Adapter->m_dhcp_server_arp ) { if (ProcessARP( Adapter, (PARP_PACKET) tapPacket->m_Data, Adapter->m_dhcp_addr, Adapter->m_dhcp_server_ip, ~0, Adapter->m_dhcp_server_mac) ) { goto no_queue; } } // DHCP packet? else if (packetLength >= sizeof (ETH_HEADER) + sizeof (IPHDR) + sizeof (UDPHDR) + sizeof (DHCP) && eth->proto == htons (NDIS_ETH_TYPE_IPV4) && ip->version_len == 0x45 // IPv4, 20 byte header && ip->protocol == IPPROTO_UDP && udp->dest == htons (BOOTPS_PORT) ) { const DHCP *dhcp = (DHCP *) (tapPacket->m_Data + sizeof (ETH_HEADER) + sizeof (IPHDR) + sizeof (UDPHDR)); const int optlen = packetLength - sizeof (ETH_HEADER) - sizeof (IPHDR) - sizeof (UDPHDR) - sizeof (DHCP); if (optlen > 0) // we must have at least one DHCP option { if (ProcessDHCP (Adapter, eth, ip, udp, dhcp, optlen)) { goto no_queue; } } else { goto no_queue; } } } #endif //=============================================== // In Point-To-Point mode, check to see whether // packet is ARP (handled) or IPv4 (sent to app). // IPv6 packets are inspected for neighbour discovery // (to be handled locally), and the rest is forwarded // all other protocols are dropped //=============================================== #if 0 if (Adapter->m_tun) { ETH_HEADER *e; e = (ETH_HEADER *) tapPacket->m_Data; switch (ntohs (e->proto)) { case NDIS_ETH_TYPE_ARP: // Make sure that packet is the right size for ARP. if (packetLength != sizeof (ARP_PACKET)) { goto no_queue; } ProcessARP ( Adapter, (PARP_PACKET) tapPacket->m_Data, Adapter->m_localIP, Adapter->m_remoteNetwork, Adapter->m_remoteNetmask, Adapter->m_TapToUser.dest ); default: goto no_queue; case NDIS_ETH_TYPE_IPV4: // Make sure that packet is large enough to be IPv4. if (packetLength < (ETHERNET_HEADER_SIZE + IP_HEADER_SIZE)) { goto no_queue; } // Only accept directed packets, not broadcasts. if (memcmp (e, &Adapter->m_TapToUser, ETHERNET_HEADER_SIZE)) { goto no_queue; } // Packet looks like IPv4, queue it. :-) tapPacket->m_SizeFlags |= TP_TUN; break; case NDIS_ETH_TYPE_IPV6: // Make sure that packet is large enough to be IPv6. if (packetLength < (ETHERNET_HEADER_SIZE + IPV6_HEADER_SIZE)) { goto no_queue; } // Broadcasts and multicasts are handled specially // (to be implemented) // Neighbor discovery packets to fe80::8 are special // OpenVPN sets this next-hop to signal "handled by tapdrv" if ( HandleIPv6NeighborDiscovery(Adapter,tapPacket->m_Data) ) { goto no_queue; } // Packet looks like IPv6, queue it. :-) tapPacket->m_SizeFlags |= TP_TUN; } } #endif //=============================================== // Push packet onto queue to wait for read from // userspace. //=============================================== if(tapAdapterReadAndWriteReady(Adapter)) { tapPacketQueueInsertTail(&Adapter->SendPacketQueue,tapPacket); } else { // // Tragedy. All this work and the packet is of no use... // NdisFreeMemory(tapPacket,0,0); } // Return after queuing or freeing TAP packet. return; // Free TAP packet without queuing. no_queue: if(tapPacket != NULL ) { NdisFreeMemory(tapPacket,0,0); } exit_success: return; } VOID tapSendNetBufferListsComplete( __in PTAP_ADAPTER_CONTEXT Adapter, __in PNET_BUFFER_LIST NetBufferLists, __in NDIS_STATUS SendCompletionStatus, __in BOOLEAN DispatchLevel ) { PNET_BUFFER_LIST currentNbl; PNET_BUFFER_LIST nextNbl = NULL; ULONG sendCompleteFlags = 0; for ( currentNbl = NetBufferLists; currentNbl != NULL; currentNbl = nextNbl ) { ULONG frameType; ULONG netBufferCount; ULONG byteCount; nextNbl = NET_BUFFER_LIST_NEXT_NBL(currentNbl); // Set NBL completion status. NET_BUFFER_LIST_STATUS(currentNbl) = SendCompletionStatus; // Fetch first NBs frame type. All linked NBs will have same type. frameType = tapGetNetBufferFrameType(NET_BUFFER_LIST_FIRST_NB(currentNbl)); // Fetch statistics for all NBs linked to the NB. netBufferCount = tapGetNetBufferCountsFromNetBufferList( currentNbl, &byteCount ); // Update statistics by frame type if(SendCompletionStatus == NDIS_STATUS_SUCCESS) { switch(frameType) { case NDIS_PACKET_TYPE_DIRECTED: Adapter->FramesTxDirected += netBufferCount; Adapter->BytesTxDirected += byteCount; break; case NDIS_PACKET_TYPE_BROADCAST: Adapter->FramesTxBroadcast += netBufferCount; Adapter->BytesTxBroadcast += byteCount; break; case NDIS_PACKET_TYPE_MULTICAST: Adapter->FramesTxMulticast += netBufferCount; Adapter->BytesTxMulticast += byteCount; break; default: ASSERT(FALSE); break; } } else { // Transmit error. Adapter->TransmitFailuresOther += netBufferCount; } currentNbl = nextNbl; } if(DispatchLevel) { sendCompleteFlags |= NDIS_SEND_COMPLETE_FLAGS_DISPATCH_LEVEL; } // Complete the NBLs NdisMSendNetBufferListsComplete( Adapter->MiniportAdapterHandle, NetBufferLists, sendCompleteFlags ); } BOOLEAN tapNetBufferListNetBufferLengthsValid( __in PTAP_ADAPTER_CONTEXT Adapter, __in PNET_BUFFER_LIST NetBufferLists ) /*++ Routine Description: Scan all NBLs and their linked NBs for valid lengths. Fairly absurd to find and packets with bogus lengths, but wise to check anyway. If ANY packet has a bogus length, then abort the entire send. The only time that one might see this check fail might be during HCK driver testing. The HKC test might send oversize packets to determine if the miniport can gracefully deal with them. This check is fairly fast. Unlike NDIS 5 packets, fetching NDIS 6 packets lengths do not require any computation. Arguments: Adapter Pointer to our adapter context NetBufferLists Head of a list of NBLs to examine Return Value: Returns TRUE if all NBs have reasonable lengths. Otherwise, returns FALSE. --*/ { PNET_BUFFER_LIST currentNbl; currentNbl = NetBufferLists; while (currentNbl) { PNET_BUFFER_LIST nextNbl; PNET_BUFFER currentNb; // Locate next NBL nextNbl = NET_BUFFER_LIST_NEXT_NBL(currentNbl); // Locate first NB (aka "packet") currentNb = NET_BUFFER_LIST_FIRST_NB(currentNbl); // // Process all NBs linked to this NBL // while(currentNb) { PNET_BUFFER nextNb; ULONG packetLength; // Locate next NB nextNb = NET_BUFFER_NEXT_NB(currentNb); packetLength = NET_BUFFER_DATA_LENGTH(currentNb); // Minimum packet size is size of Ethernet plus IPv4 headers. ASSERT(packetLength >= (ETHERNET_HEADER_SIZE + IP_HEADER_SIZE)); if(packetLength < (ETHERNET_HEADER_SIZE + IP_HEADER_SIZE)) { return FALSE; } // Maximum size should be Ethernet header size plus MTU plus modest pad for // VLAN tag. ASSERT( packetLength <= (ETHERNET_HEADER_SIZE + VLAN_TAG_SIZE + Adapter->MtuSize)); if(packetLength > (ETHERNET_HEADER_SIZE + VLAN_TAG_SIZE + Adapter->MtuSize)) { return FALSE; } // Move to next NB currentNb = nextNb; } // Move to next NBL currentNbl = nextNbl; } return TRUE; } VOID AdapterSendNetBufferLists( __in NDIS_HANDLE MiniportAdapterContext, __in PNET_BUFFER_LIST NetBufferLists, __in NDIS_PORT_NUMBER PortNumber, __in ULONG SendFlags ) /*++ Routine Description: Send Packet Array handler. Called by NDIS whenever a protocol bound to our miniport sends one or more packets. The input packet descriptor pointers have been ordered according to the order in which the packets should be sent over the network by the protocol driver that set up the packet array. The NDIS library preserves the protocol-determined ordering when it submits each packet array to MiniportSendPackets As a deserialized driver, we are responsible for holding incoming send packets in our internal queue until they can be transmitted over the network and for preserving the protocol-determined ordering of packet descriptors incoming to its MiniportSendPackets function. A deserialized miniport driver must complete each incoming send packet with NdisMSendComplete, and it cannot call NdisMSendResourcesAvailable. Runs at IRQL <= DISPATCH_LEVEL Arguments: MiniportAdapterContext Pointer to our adapter NetBufferLists Head of a list of NBLs to send PortNumber A miniport adapter port. Default is 0. SendFlags Additional flags for the send operation Return Value: None. Write status directly into each NBL with the NET_BUFFER_LIST_STATUS macro. --*/ { NDIS_STATUS status; PTAP_ADAPTER_CONTEXT adapter = (PTAP_ADAPTER_CONTEXT )MiniportAdapterContext; BOOLEAN DispatchLevel = (SendFlags & NDIS_SEND_FLAGS_DISPATCH_LEVEL); PNET_BUFFER_LIST currentNbl; BOOLEAN validNbLengths; UNREFERENCED_PARAMETER(NetBufferLists); UNREFERENCED_PARAMETER(PortNumber); UNREFERENCED_PARAMETER(SendFlags); ASSERT(PortNumber == 0); // Only the default port is supported // // Can't process sends if TAP device is not open. // ---------------------------------------------- // Just perform a "lying send" and return packets as if they // were successfully sent. // if(adapter->TapFileObject == NULL) { // // Complete all NBLs and return if adapter not ready. // tapSendNetBufferListsComplete( adapter, NetBufferLists, NDIS_STATUS_SUCCESS, DispatchLevel ); return; } // // Check Adapter send/receive ready state. // status = tapAdapterSendAndReceiveReady(adapter); if(status != NDIS_STATUS_SUCCESS) { // // Complete all NBLs and return if adapter not ready. // tapSendNetBufferListsComplete( adapter, NetBufferLists, status, DispatchLevel ); return; } // // Scan all NBLs and linked packets for valid lengths. // --------------------------------------------------- // If _ANY_ NB length is invalid, then fail the entire send operation. // // BUGBUG!!! Perhaps this should be less agressive. Fail only individual // NBLs... // // If length check is valid, then TAP_PACKETS can be safely allocated // and processed for all NBs being sent. // validNbLengths = tapNetBufferListNetBufferLengthsValid( adapter, NetBufferLists ); if(!validNbLengths) { // // Complete all NBLs and return if and NB length is invalid. // tapSendNetBufferListsComplete( adapter, NetBufferLists, NDIS_STATUS_INVALID_LENGTH, DispatchLevel ); return; } // // Process each NBL individually // currentNbl = NetBufferLists; while (currentNbl) { PNET_BUFFER_LIST nextNbl; PNET_BUFFER currentNb; // Locate next NBL nextNbl = NET_BUFFER_LIST_NEXT_NBL(currentNbl); // Locate first NB (aka "packet") currentNb = NET_BUFFER_LIST_FIRST_NB(currentNbl); // Transmit all NBs linked to this NBL while(currentNb) { PNET_BUFFER nextNb; // Locate next NB nextNb = NET_BUFFER_NEXT_NB(currentNb); // Transmit the NB tapAdapterTransmit(adapter,currentNb,DispatchLevel); // Move to next NB currentNb = nextNb; } // Move to next NBL currentNbl = nextNbl; } // Complete all NBLs tapSendNetBufferListsComplete( adapter, NetBufferLists, NDIS_STATUS_SUCCESS, DispatchLevel ); // Attempt to complete pending read IRPs from pending TAP // send packet queue. tapProcessSendPacketQueue(adapter); } VOID AdapterCancelSend( __in NDIS_HANDLE MiniportAdapterContext, __in PVOID CancelId ) { PTAP_ADAPTER_CONTEXT adapter = (PTAP_ADAPTER_CONTEXT )MiniportAdapterContext; // // This miniport completes its sends quickly, so it isn't strictly // neccessary to implement MiniportCancelSend. // // If we did implement it, we'd have to walk the Adapter->SendWaitList // and look for any NB that points to a NBL where the CancelId matches // NDIS_GET_NET_BUFFER_LIST_CANCEL_ID(Nbl). For any NB that so matches, // we'd remove the NB from the SendWaitList and set the NBL's status to // NDIS_STATUS_SEND_ABORTED, then complete the NBL. // } // IRP_MJ_READ callback. NTSTATUS TapDeviceRead( PDEVICE_OBJECT DeviceObject, PIRP Irp ) { NTSTATUS ntStatus = STATUS_SUCCESS;// Assume success PIO_STACK_LOCATION irpSp;// Pointer to current stack location PTAP_ADAPTER_CONTEXT adapter = NULL; PAGED_CODE(); irpSp = IoGetCurrentIrpStackLocation( Irp ); // // Fetch adapter context for this device. // -------------------------------------- // Adapter pointer was stashed in FsContext when handle was opened. // adapter = (PTAP_ADAPTER_CONTEXT )(irpSp->FileObject)->FsContext; ASSERT(adapter); // // Sanity checks on state variables // if (!tapAdapterReadAndWriteReady(adapter)) { //DEBUGP (("[%s] Interface is down in IRP_MJ_READ\n", // MINIPORT_INSTANCE_ID (adapter))); //NOTE_ERROR(); Irp->IoStatus.Status = ntStatus = STATUS_CANCELLED; Irp->IoStatus.Information = 0; IoCompleteRequest (Irp, IO_NO_INCREMENT); return ntStatus; } // Save IRP-accessible copy of buffer length Irp->IoStatus.Information = irpSp->Parameters.Read.Length; if (Irp->MdlAddress == NULL) { DEBUGP (("[%s] MdlAddress is NULL for IRP_MJ_READ\n", MINIPORT_INSTANCE_ID (adapter))); NOTE_ERROR(); Irp->IoStatus.Status = ntStatus = STATUS_INVALID_PARAMETER; Irp->IoStatus.Information = 0; IoCompleteRequest (Irp, IO_NO_INCREMENT); return ntStatus; } if ((Irp->AssociatedIrp.SystemBuffer = MmGetSystemAddressForMdlSafe( Irp->MdlAddress, NormalPagePriority ) ) == NULL ) { DEBUGP (("[%s] Could not map address in IRP_MJ_READ\n", MINIPORT_INSTANCE_ID (adapter))); NOTE_ERROR(); Irp->IoStatus.Status = ntStatus = STATUS_INSUFFICIENT_RESOURCES; Irp->IoStatus.Information = 0; IoCompleteRequest (Irp, IO_NO_INCREMENT); return ntStatus; } // BUGBUG!!! Use RemoveLock??? // // Queue the IRP and return STATUS_PENDING. // ---------------------------------------- // Note: IoCsqInsertIrp marks the IRP pending. // // BUGBUG!!! NDIS 5 implementation has IRP_QUEUE_SIZE of 16 and // does not queue IRP if this capacity is exceeded. // // Is this needed??? // IoCsqInsertIrp(&adapter->PendingReadIrpQueue.CsqQueue, Irp, NULL); // Attempt to complete pending read IRPs from pending TAP // send packet queue. tapProcessSendPacketQueue(adapter); ntStatus = STATUS_PENDING; return ntStatus; }