/*++ Copyright(c) 1999-2000 Microsoft Corporation Module Name: bridge.c Abstract: Ethernet MAC level bridge. SAMPLE program illustrating how to interface with the bridge driver via IOCTLs Author: Mark Aiken Environment: User mode Revision History: Apr 2000 - Original version --*/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bioctl.h" // =========================================================================== // // GLOBALS // // =========================================================================== HANDLE gThreadQuitEvent = NULL; HANDLE gBridgeDevice = NULL; // =========================================================================== // // FUNCTIONS // // =========================================================================== VOID PrintError( IN DWORD Error ) /*++ Routine Description: Prints a description of a system error Arguments: Error The error code --*/ { TCHAR msg[200]; printf( "(%08x): ", Error ); if( FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM, NULL, Error, 0, msg, sizeof(msg), NULL ) > 0 ) { printf( "%s", msg ); } } VOID PrintNotification( IN PBRIDGE_NOTIFY_HEADER pNotify, IN DWORD DataBytes ) /*++ Routine Description: Prints a notification from the bridge driver Arguments: pNotify The notification block DataBytes Total size of the notification block --*/ { BOOLEAN bPrintAdapterInfo = TRUE; printf( "\n\nNotification on Adapter %p: ", pNotify->Handle ); switch( pNotify->NotifyType ) { case BrdgNotifyEnumerateAdapters: printf( "Enumeration was requested\n" ); break; case BrdgNotifyAddAdapter: printf( "Adapter was added\n" ); break; case BrdgNotifyRemoveAdapter: printf( "Adapter was removed\n" ); bPrintAdapterInfo = FALSE; break; case BrdgNotifyLinkSpeedChange: printf( "Link speed changed\n" ); break; case BrdgNotifyMediaStateChange: printf( "Media state changed\n" ); break; case BrdgNotifyAdapterStateChange: printf( "Forwarding state changed\n" ); break; default: printf( "UNKNOWN NOTIFICATION\n" ); bPrintAdapterInfo = FALSE; break; } if( bPrintAdapterInfo ) { PBRIDGE_ADAPTER_INFO pInfo = (PBRIDGE_ADAPTER_INFO)((PUCHAR)pNotify + sizeof(BRIDGE_NOTIFY_HEADER)); printf("Adapter information:\n\n"); printf("Link Speed: %iMbps\n", pInfo->LinkSpeed / 10000); printf("Media State: %s\n", pInfo->MediaState == NdisMediaStateConnected ? "CONNECTED" : "DISCONNECTED" ); printf("Physical Medium: %08x\n", pInfo->PhysicalMedium); printf("Forwarding state: "); switch( pInfo->State ) { case Disabled: printf("** DISABLED **\n"); break; case Blocking: printf("BLOCKING\n"); break; case Listening: printf("Listening\n"); break; case Learning: printf("Learning\n"); break; case Forwarding: printf("Forwarding\n"); break; } printf("MAC Address: %02x-%02x-%02x-%02x-%02x-%02x\n", pInfo->MACAddress[0], pInfo->MACAddress[1], pInfo->MACAddress[2], pInfo->MACAddress[3], pInfo->MACAddress[4], pInfo->MACAddress[5]); } } VOID __cdecl NotificationThread( PVOID pv ) /*++ Routine Description: Notification-listening thread Pends an IOCTL call and waits for notifications from the bridge driver --*/ { BOOLEAN bQuit = FALSE; HANDLE Handles[2]; DWORD WaitResult, WrittenBytes; UCHAR OutBuffer[sizeof(BRIDGE_NOTIFY_HEADER) + 1514]; OVERLAPPED Overlap; Handles[0] = gThreadQuitEvent; // Create an event to sleep against for the request Handles[1] = CreateEvent( NULL, FALSE/*auto-reset*/, FALSE/*Start unsignalled*/, NULL ); if( Handles[1] == NULL ) { printf( "Couldn't create an event: " ); PrintError( GetLastError() ); _endthread(); } Overlap.Offset = Overlap.OffsetHigh = 0L; Overlap.hEvent = Handles[1]; while( ! bQuit ) { // Ask for a notification if( ! DeviceIoControl( gBridgeDevice, BRIDGE_IOCTL_REQUEST_NOTIFY, NULL, 0L, OutBuffer, sizeof(OutBuffer), &WrittenBytes, &Overlap ) ) { DWORD Error = GetLastError(); if( Error != ERROR_IO_PENDING ) { printf( "DeviceIoControl returned an error: " ); PrintError( Error ); _endthread(); } } // Wait against the completion event and the kill event WaitResult = WaitForMultipleObjects( 2, Handles, FALSE, INFINITE ); if( WaitResult == WAIT_OBJECT_0 ) { // The quit event was signaled bQuit = TRUE; } else if( WaitResult != WAIT_OBJECT_0 + 1 ) { printf( "Error waiting: " ); PrintError( GetLastError() ); _endthread(); } else { // The completion event was signaled. // Try to retrieve the number of bytes read if( !GetOverlappedResult(gBridgeDevice, &Overlap, &WrittenBytes, FALSE) ) { printf( "Couldn't get the device call result: " ); PrintError( GetLastError() ); _endthread(); } PrintNotification( (PBRIDGE_NOTIFY_HEADER)OutBuffer, WrittenBytes ); } } printf("Notification thread exiting.\n"); _endthread(); } HANDLE OpenDevice( CHAR *pDeviceName ) /*++ Routine Description: Opens a device Arguments: pDeviceName The device's name Return Value: A handle to the opened device or INVALID_HANDLE_VALUE on failure; --*/ { DWORD DesiredAccess; DWORD ShareMode; LPSECURITY_ATTRIBUTES lpSecurityAttributes = NULL; DWORD CreationDistribution; DWORD FlagsAndAttributes; HANDLE TemplateFile; HANDLE Handle = NULL; DesiredAccess = GENERIC_READ|GENERIC_WRITE; ShareMode = 0; CreationDistribution = OPEN_EXISTING; FlagsAndAttributes = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED; TemplateFile = (HANDLE)INVALID_HANDLE_VALUE; Handle = CreateFile( pDeviceName, DesiredAccess, ShareMode, lpSecurityAttributes, CreationDistribution, FlagsAndAttributes, TemplateFile ); return (Handle); } BOOL DoBlockingRequest( IN DWORD IOCTL, IN PVOID inBuff, IN DWORD inSize, IN PVOID outBuff, IN DWORD outSize, OUT OPTIONAL PDWORD pWrittenBytes ) /*++ Routine Description: Makes an IOCTL call to a driver and blocks until the request completes Arguments: IOCTL The IOCTL code inBuff The input data buffer inSize The size of the input buffer outBuff The output buffer outSize The size of the output buffer pWrittenBytes (optional) returns the number of bytes written to outBuff Return Value: TRUE for success, FALSE for failure --*/ { DWORD WrittenBytes; OVERLAPPED Overlap; if( pWrittenBytes == NULL ) { pWrittenBytes = &WrittenBytes; } // Create an event to sleep against for the request Overlap.hEvent = CreateEvent( NULL, FALSE/*auto-reset*/, FALSE/*Start unsignalled*/, NULL ); if( Overlap.hEvent == NULL ) { printf( "Couldn't create an event: " ); PrintError( GetLastError() ); return FALSE; } Overlap.Offset = Overlap.OffsetHigh = 0L; // Make the request if( ! DeviceIoControl( gBridgeDevice, IOCTL, inBuff, inSize, outBuff, outSize,pWrittenBytes, &Overlap ) ) { DWORD Error = GetLastError(); if( Error != ERROR_IO_PENDING ) { return FALSE; } } // Wait for the result if( !GetOverlappedResult(gBridgeDevice, &Overlap,pWrittenBytes, TRUE) ) { return FALSE; } return TRUE; } PVOID GetVariableData( IN ULONG IOCTL, IN PVOID inBuff, IN ULONG inBuffSize, OUT PULONG pNumBytes, IN ULONG safetyBuffer ) /*++ Routine Description: Makes an IOCTL call that returns variable-sized data (for which two calls must be made, one to determine the size of the data and the other to retrieve it) Arguments: IOCTL The IOCTL code inBuff The input data buffer inBuffSize The size of the input buffer pNumBytes The size of the allocated buffer holding the result safetyBuffer A count of bytes to allocate beyond the claimed size of the data (a safeguard against dynamically changing data) Return Value: A freshly allocated buffer, *pNumBytes large, with the return data NULL on failure --*/ { // First make a request to discover the number of necessary bytes if( ! DoBlockingRequest( IOCTL, inBuff, inBuffSize, NULL, 0L, pNumBytes ) ) { DWORD Error = GetLastError(); // Expect the error to be ERROR_MORE_DATA since we didn't provide an output buffer! if( Error == ERROR_MORE_DATA ) { if( *pNumBytes > 0L ) { // Allocate safetyBuffer extra bytes in case the data is dynamic PUCHAR pData = (PUCHAR)HeapAlloc(GetProcessHeap(), 0, *pNumBytes + safetyBuffer); ULONG i; if( pData == NULL ) { printf( "Failed to allocate %i bytes of memory: ", *pNumBytes + safetyBuffer ); PrintError( GetLastError() ); return NULL; } // Make the request again to actually retrieve the addresses if( ! DoBlockingRequest( IOCTL, inBuff, inBuffSize, pData, *pNumBytes + safetyBuffer, pNumBytes ) ) { HeapFree( GetProcessHeap(), 0, pData ); printf( "Failed to read variable-length data: " ); PrintError( GetLastError() ); return NULL; } // Success. Hand back the data buffer. return pData; } else { printf( "Driver returned zero bytes: " ); PrintError( GetLastError() ); return NULL; } } else { printf( "Failed to get the number of necessary bytes: " ); PrintError( GetLastError() ); return NULL; } } return NULL; } _inline VOID PrintByteString( IN PUCHAR pData, IN UINT numChars ) { UINT i; for( i = 0; i < numChars; i++ ) { printf( "%02x", pData[i] ); if( i != numChars - 1 ) { printf( "-" ); } } printf( "\n" ); } VOID PrintAdapterSTAInfo( IN BRIDGE_ADAPTER_HANDLE Handle ) { BRIDGE_STA_ADAPTER_INFO info; if(! DoBlockingRequest(BRIDGE_IOCTL_GET_ADAPTER_STA_INFO, &Handle, sizeof(Handle), &info, sizeof(info), NULL) ) { printf( "Couldn't request STA information for adapter %p : ", Handle ); PrintError( GetLastError() ); } else { printf( "\nSTA Information for adapter %p:\n\n", Handle ); printf( "Port unique ID : %i\n", info.ID ); printf( "Path Cost : %i\n", info.PathCost ); printf( "Designated Root : " ); PrintByteString( info.DesignatedRootID, BRIDGE_ID_LEN ); printf( "Designated Cost : %i\n", info.DesignatedCost ); printf( "Designated Bridge : " ); PrintByteString( info.DesignatedBridgeID, BRIDGE_ID_LEN ); printf( "Designated Port : %i\n\n", info.DesignatedPort ); } } VOID PrintSTAInfo( ) { BRIDGE_STA_GLOBAL_INFO info; if(! DoBlockingRequest(BRIDGE_IOCTL_GET_GLOBAL_STA_INFO, NULL, 0L, &info, sizeof(info), NULL) ) { printf( "Couldn't request global STA information : " ); PrintError( GetLastError() ); } else { printf( "This bridge's ID : " ); PrintByteString( info.OurID, BRIDGE_ID_LEN ); printf( "Designated Root : " ); PrintByteString( info.DesignatedRootID, BRIDGE_ID_LEN ); printf( "Cost to root : %i\n", info.RootCost ); printf( "Root adapter : %p\n", info.RootAdapter ); printf( "MaxAge : %i\n", info.MaxAge ); printf( "HelloTime : %i\n", info.HelloTime ); printf( "ForwardDelay : %i\n", info.ForwardDelay ); printf( "TopologyChangeDetected : " ); if( info.bTopologyChangeDetected ) { printf( "TRUE\n" ); } else { printf( "FALSE\n" ); } printf( "TopologyChange : " ); if( info.bTopologyChange ) { printf( "TRUE\n\n" ); } else { printf( "FALSE\n\n" ); } } } VOID PrintTableEntries( IN BRIDGE_ADAPTER_HANDLE Handle ) /*++ Routine Description: Retrieves and prints the MAC table entries for a particular adapter Arguments: Handle The adapter --*/ { PUCHAR pAddresses; ULONG i, numBytes; pAddresses = GetVariableData( BRIDGE_IOCTL_GET_TABLE_ENTRIES, &Handle, sizeof(Handle), &numBytes, 60L ); if( pAddresses == NULL ) { printf( "Failed to read table entries: " ); PrintError( GetLastError() ); return; } printf( "Forwarding table entries for adapter %x: \n", Handle ); for( i = 0L; i < numBytes / ETH_LENGTH_OF_ADDRESS; i++ ) { PrintByteString( pAddresses, ETH_LENGTH_OF_ADDRESS ); pAddresses += ETH_LENGTH_OF_ADDRESS; } HeapFree( GetProcessHeap(), 0, pAddresses ); } VOID PrintPacketStats( IN PBRIDGE_PACKET_STATISTICS pStats ) /*++ Routine Description: Prints a BRIDGE_PACKET_STATISTICS structure in a friendly way Arguments: pStats The structure --*/ { printf("Bridge packet statistics:\n\n"); printf("Transmitted Frames: %16I64u\n", pStats->TransmittedFrames); printf("Transmitted Frames w/Errors: %16I64u\n", pStats->TransmittedErrorFrames); printf("Transmitted Directed Frames: %16I64u\n", pStats->DirectedTransmittedFrames); printf("Transmitted Multicast Frames: %16I64u\n", pStats->MulticastTransmittedFrames); printf("Transmitted Broadcast Frames: %16I64u\n\n", pStats->BroadcastTransmittedFrames); printf("Transmitted Bytes: %16I64u\n", pStats->TransmittedBytes); printf("Transmitted Directed Bytes %16I64u\n", pStats->DirectedTransmittedBytes); printf("Transmitted Multicast Bytes: %16I64u\n", pStats->MulticastTransmittedBytes); printf("Transmitted Broadcast Bytes: %16I64u\n\n", pStats->BroadcastTransmittedBytes); printf("Indicated Frames: %16I64u\n", pStats->IndicatedFrames); printf("Indicated Frames w/Errors: %16I64u\n\n", pStats->IndicatedDroppedFrames); printf("Indicated Directed Frames: %16I64u\n", pStats->DirectedIndicatedFrames); printf("Indicated Multicast Frames: %16I64u\n", pStats->MulticastIndicatedFrames); printf("Indicated Broadcast Frames: %16I64u\n\n", pStats->BroadcastIndicatedFrames); printf("Indicated Bytes: %16I64u\n", pStats->IndicatedBytes); printf("Indicated Directed Bytes: %16I64u\n", pStats->DirectedIndicatedBytes); printf("Indicated Multicast Bytes: %16I64u\n", pStats->MulticastIndicatedBytes); printf("Indicated Broadcast Bytes: %16I64u\n\n", pStats->BroadcastIndicatedBytes); printf("Received Frames (incl. relay): %16I64u\n", pStats->ReceivedFrames); printf("Received Bytes (incl. relay): %16I64u\n", pStats->ReceivedBytes); printf("Received Frames w/Copy: %16I64u\n", pStats->ReceivedCopyFrames); printf("Received Bytes w/Copy: %16I64u\n", pStats->ReceivedCopyBytes); printf("Received Frames w/No Copy: %16I64u\n", pStats->ReceivedNoCopyFrames); printf("Received Bytes w/No Copy: %16I64u\n", pStats->ReceivedNoCopyBytes); } VOID PrintAdapterPacketStats( IN PBRIDGE_ADAPTER_PACKET_STATISTICS pStats ) /*++ Routine Description: Prints a BRIDGE_ADAPTER_PACKET_STATISTICS structure in a friendly way Arguments: pStats The structure --*/ { PUCHAR pc = (PUCHAR)pStats; printf("Bridge per-adapter packet statistics:\n\n"); printf("Transmitted Frames: %16I64u\n", pStats->SentFrames); printf("Transmitted Bytes: %16I64u\n", pStats->SentBytes); printf("Transmitted Local-Source Frames: %16I64u\n", pStats->SentLocalFrames); printf("Transmitted Local-Source Bytes: %16I64u\n", pStats->SentLocalBytes); printf("Received Frames: %16I64u\n", pStats->ReceivedFrames); printf("Received Bytes: %16I64u\n\n", pStats->ReceivedBytes); } VOID PrintBufferStats( IN PBRIDGE_BUFFER_STATISTICS pStats ) /*++ Routine Description: Prints a BRIDGE_BUFFER_STATISTICS structure in a friendly way Arguments: pStats The structure --*/ { printf("Bridge buffer statistics:\n\n"); printf("Copy Packets In Use: %4lu\n", pStats->UsedCopyPackets); printf("Total Copy Packets Available: %4lu\n", pStats->MaxCopyPackets); printf("Safety Copy Packets: %4lu\n", pStats->SafetyCopyPackets); printf("Copy Pool Overflows: %10I64u\n\n", pStats->CopyPoolOverflows); printf("Wrapper Packets In Use: %4lu\n", pStats->UsedWrapperPackets); printf("Total Wrapper Packets Available: %4lu\n", pStats->MaxWrapperPackets); printf("Safety Wrapper Packets: %4lu\n", pStats->SafetyWrapperPackets); printf("Wrapper Pool Overflows: %10I64u\n\n", pStats->WrapperPoolOverflows); printf("Surprise Alloc Failures: %10I64u\n", pStats->AllocFailures); } BOOLEAN ReadUlongArg( IN PUCHAR inbuf, OUT PULONG arg ) /*++ Routine Description: Reads an unsigned decimal value from a string and returns it The value must occur as the second word of the string Arguments: inbuf The input string arg The resulting number Return Value: TRUE for success, FALSE for failure --*/ { UCHAR scratch[100]; if( sscanf(inbuf, "%s %lu", scratch, arg) < 2 ) { return FALSE; } return TRUE; } BOOLEAN ReadHexPtrArg( IN PUCHAR inbuf, OUT PULONG_PTR arg ) /*++ Routine Description: Reads an unsigned hexidecimal value from a string and returns it The value must occur as the second word of the string Arguments: inbuf The input string arg The resulting number Return Value: TRUE for success, FALSE for failure --*/ { UCHAR scratch[100]; INT read; if( sizeof(*arg) <= sizeof(ULONG) ) { read = sscanf(inbuf, "%s %lx", scratch, arg); } else { read = sscanf(inbuf, "%s %I64x", scratch, arg); } if( (read != EOF) && (read < 2) ) { return FALSE; } return TRUE; } VOID __cdecl main( INT argc, CHAR *argv[] ) { CHAR inbuf[100], command[100]; BOOLEAN bQuit = FALSE; printf("\nSAMPLE MAC Bridge control program\n"); // Open the bridge device gBridgeDevice = OpenDevice( BRIDGE_DOS_DEVICE_NAME ); if( gBridgeDevice == INVALID_HANDLE_VALUE ) { printf( "Couldn't open bridge device: " ); PrintError( GetLastError() ); return; } // Create the thread-quit notification event gThreadQuitEvent = CreateEvent( NULL, FALSE/*auto-reset*/, FALSE/*Start unsignalled*/, NULL ); if( gThreadQuitEvent == NULL ) { printf( "Couldn't create an event: " ); PrintError( GetLastError() ); return; } // Spin up a thread to handle notifications _beginthread( NotificationThread, 0, NULL ); while( ! bQuit ) { PCHAR pSpace; printf( "> " ); // Get a line of input gets( inbuf ); // Find the first word delimiter pSpace = strchr( inbuf, ' ' ); // Copy over the first word if( pSpace != NULL ) { strncpy( command, inbuf, pSpace - inbuf ); command[pSpace-inbuf] = '\0'; } else { strcpy( command, inbuf ); } if( _stricmp(command, "enum") == 0 ) { if(! DoBlockingRequest(BRIDGE_IOCTL_GET_ADAPTERS, NULL, 0L, NULL, 0L, NULL) ) { printf( "Couldn't request an adapter re-enumeration : " ); PrintError( GetLastError() ); } else { printf("Requested an adapter re-enumeration.\n" ); } } else if( (_stricmp(command, "devicename") == 0) || (_stricmp(command, "friendlyname") == 0) ) { ULONG IOCTL = (_stricmp(command, "devicename") == 0) ? BRIDGE_IOCTL_GET_ADAPT_DEVICE_NAME : BRIDGE_IOCTL_GET_ADAPT_FRIENDLY_NAME; BRIDGE_ADAPTER_HANDLE Handle; PWCHAR pName; ULONG numBytes; if( ! ReadHexPtrArg(inbuf, &Handle) ) { printf("Must supply an adapter handle for this command.\n"); } else { pName = (PWCHAR)GetVariableData( IOCTL, &Handle, sizeof(Handle), &numBytes, 0L ); if( pName == NULL ) { printf("Couldn't get name for adapter %p: ", Handle); PrintError( GetLastError() ); } else { printf("The name is: %S\n", pName); } HeapFree( GetProcessHeap(), 0, pName ); } } else if( _stricmp(command, "table") == 0 ) { BRIDGE_ADAPTER_HANDLE Handle; if( ! ReadHexPtrArg(inbuf, &Handle) ) { printf("Must supply an adapter handle for this command.\n"); } else { PrintTableEntries(Handle); } } else if( _stricmp(command, "mac") == 0 ) { UCHAR addr[ETH_LENGTH_OF_ADDRESS]; if(! DoBlockingRequest(BRIDGE_IOCTL_GET_MAC_ADDRESS, NULL, 0L, addr, sizeof(addr), NULL) ) { printf("Attempt to query MAC address failed: "); PrintError( GetLastError() ); } else { printf( "Bridge MAC address is %02x-%02x-%02x-%02x-%02x-%02x\n", addr[0], addr[1], addr[2], addr[3], addr[4], addr[5] ); } } else if( _stricmp(command, "onretain") == 0 ) { if(! DoBlockingRequest(BRIDGE_IOCTL_RETAIN_PACKETS, NULL, 0L, NULL, 0L, NULL) ) { printf("Attempt to turn ON packet retention failed: "); PrintError( GetLastError() ); } else { printf("Packet retention ENABLED.\n"); } } else if( _stricmp(command, "offretain") == 0 ) { if(! DoBlockingRequest(BRIDGE_IOCTL_NO_RETAIN_PACKETS, NULL, 0L, NULL, 0L, NULL) ) { printf("Attempt to turn OFF packet retention failed: "); PrintError( GetLastError() ); } else { printf("Packet retention DISABLED.\n"); } } else if( _stricmp(command, "packetstats") == 0 ) { BRIDGE_ADAPTER_HANDLE Handle; if( ! ReadHexPtrArg(inbuf, &Handle) ) { BRIDGE_PACKET_STATISTICS Stats; if(! DoBlockingRequest(BRIDGE_IOCTL_GET_PACKET_STATS, NULL, 0L, &Stats, sizeof(Stats), NULL) ) { printf("Attempt to retrieve global packet statistics failed: "); PrintError( GetLastError() ); } else { PrintPacketStats(&Stats); } } else { BRIDGE_ADAPTER_PACKET_STATISTICS Stats; if(! DoBlockingRequest(BRIDGE_IOCTL_GET_ADAPTER_PACKET_STATS, &Handle, sizeof(Handle), &Stats, sizeof(Stats), NULL) ) { printf("Attempt to retrieve packet statistics for adapter %p failed: ", Handle); PrintError( GetLastError() ); } else { PrintAdapterPacketStats(&Stats); } } } else if( _stricmp(command, "bufferstats") == 0 ) { BRIDGE_BUFFER_STATISTICS Stats; if(! DoBlockingRequest(BRIDGE_IOCTL_GET_BUFFER_STATS, NULL, 0L, &Stats, sizeof(Stats), NULL) ) { printf("Attempt to retrieve buffer statistics failed: "); PrintError( GetLastError() ); } else { PrintBufferStats(&Stats); } } else if( _stricmp(command, "stainfo") == 0 ) { BRIDGE_ADAPTER_HANDLE Handle; if( ! ReadHexPtrArg(inbuf, &Handle) ) { PrintSTAInfo(); } else { PrintAdapterSTAInfo( Handle ); } } else if( _stricmp( command, "quit" ) == 0 ) { printf( "Signalling an exit...\n" ); if( ! SetEvent( gThreadQuitEvent ) ) { printf( "Couldn't signal an event: " ); PrintError( GetLastError() ); return; } bQuit = TRUE; } else { // Print a list of commands to help the user printf( "\n\nSupported commands:\n\n" ); printf( "ENUM - Enumerates adapters\n" ); printf( "DEVICENAME - Retrieves the device name of the indicated adapter\n" ); printf( "FRIENDLYNAME - Retrieves the friendly name of the indicated adapter\n" ); printf( "TABLE - Prints the forwarding table for the indicated adapter\n" ); printf( "MAC - Prints the bridge's MAC address\n" ); printf( "ONRETAIN - Turns ON NIC packet retention\n" ); printf( "OFFRETAIN - Turns OFF NIC packet retention\n" ); printf( "PACKETSTATS [] - Retrieves packet-handling statistics for a particular\n" ); printf( " adapter (or global data if no adapter handle is\n" ); printf( " provided)\n" ); printf( "BUFFERSTATS - Retrieves buffer-management statistics\n" ); printf( "STAINFO [] - Retrieves STA info for a particular adapter (or global\n" ); printf( " info if no adapter handle is provided)\n" ); printf( "QUIT - Exits\n\n" ); } } CloseHandle( gBridgeDevice ); }