Getting IP data from numerous sources ===================================== Contents -------- Introduction Getting the local machine's name RAS machines using some sort of dialup Network machines Using the DHCP VxD to get the IP address and the DNS server data Using WsControl() to get the IP address Author Legalese Copyright Introduction ------------ This document describes several ways to get TCP/IP specific data under Windows 95+. Information was provided by George Foot, Jacob Verhoeks, Arthur Hoogervorst and several whacky programs by Alfons Hoogervorst. Basic information required to get for TCP/IP: o The local machine's name o The local machine's IP address(es) o The IP addresses of DNS servers Sample source code (in C), that demonstrate the techniques described in this document, are available on request. This document refers seevral times to the Windows registry and to VxDs. If you want to use the registry and to call VxDs from within DOS programs (running under Windows), also check out (delete the usual no.spam from the URL). Getting the local machine's name -------------------------------- You can get the computer's name by getting the ASCIIZ string from the following key: HKEY_LOCAL_MACHINE\ System\ CurrentControlSet\ Control\ ComputerName\ ComputerName\ ComputerName = ASCIIZ string of max. 64 characters. Several Windows TCP/IP programs use the computer name in host names, for example Eudora. However, the returned ASCIIZ string doesn't have to be "DNS compliant", i.e. it may have white space and other characters that are invalid in host names. Alternatively, you can find the "real" machine name by getting the current IP address, and using gethostbyaddr(). RAS machines using some sort of dialup -------------------------------------- The RAS dialup adapter stores TCP/IP settings in a "phone book entry". For Windows 95, this phone book is stored in the registry. To get the relevant addresses, you need to follow these steps: 1. Check if there's a RAS connection active (optional): Read DWORD value of the following key: HKEY_LOCAL_MACHINE\ System\ CurrentControlSet\ Services\ RemoteAccess\ Remote Connection = DWORD If the returned DWORD has value 1, there's a RAS connection 2. Get the name of the current RAS phone book: Read an ASCIIZ string from the following key: HKEY_CURRENT_USER\ RemoteAccess\ Default = ASCIIZ string for the active phone book The returned string should be used in the next step. 3. Get the RAS settings using the "phone book name". Note that you should first get the size for the settings buffer (using the Registry API). Read binary data from the following key: HKEY_CURRENT_USER\ RemoteAccess\ Profile\ "phone book name" = BINARY data of max. 50 bytes "phone book name" is the name of the phone book retrieved in step 3. 4. The byte at offset 0x04 in the retrieved data buffer has several flags set for available TCP/IP data. If bit one (0x01) is set, the user set a fixed IP address for the current RAS connection. This fixed IP address (in host order) can be found at offset 0x08. If bit two (0x02) is set, the user specified one or two DNS addresses. The DNS addresses are in host order, and can be found at offset 0x0C and offset 0x10. Note: 0.0.0.0 (0x0UL) is an invalid (unspecified) DNS address. History: I had most of the registry functions working (for DOS programs running under Windows 3.1 and 95), and also found the appropriate RAS connection key. I didn't find the necessary IP data, and told this to Jacob Verhoeks. The next day he came up with a full and detailed description of the binary data stored under the RemoteAccess\Profile tree. Network machines ---------------- For network machines too the data is stored in the registry. Here are the steps to retrieve the current IP address and the DNS IP addresses. 1. The current active adapter's IP address can be found in the following key: HKEY_LOCAL_MACHINE\ System\ CurrentControlSet\ Services\ Class\ NetTrans\ 0000\ IPAddress = ASCIIZ string This ASCIIZ string has the dotted name IP address. Note: Presumably Windows 95 stores multiple interfaces in the NetTrans key. Ideally you should enumerate each of them, and check whether any of them has an "IPAddress" key. 2. The current IP addresses of DNS servers can be found in the following key: HKEY_LOCAL_MACHINE\ System\ CurrentControlSet\ Services\ VxD\ MSTCP\ NameServer = ASCIIZ string This key stores DNS IP addresses in a comma-separated list. History: I hadn't worked for a long time on DosSock95 when George Foot referred me to a .FIX file and sent me the info about the IP address. Both George Foot and Jacob Verhoeks provided the key which stores the name server information. Using the DHCP VxD to get the IP address and the DNS server data ---------------------------------------------------------------- For RAS connections, there's an alternate way to get the IP address and DNS server addresses. This only works with RAS connections. First get the entry point of the DHCP VxD (which has ID 0x49A). Call the entry point with (E)AX set to 1, ES:BX set to a buffer receiving DHCP data, and (E)CX with the size of the buffer. If the buffer is smaller than the data available, AX will be set to 111 (Win32 error: ERROR_BUFFER_OVERFLOW) and the first DWORD in the buffer will contain the size of the DHCP data. Here's how the information looks like: Offset Type What 0x0000 WORD Number of IP addresses 0x000C DWORD IP addresses in host order 0x0020 WORD Total number of bytes in server addresses. Divide by four to get the number of server addresses. 0x0024 WORD Offset (from begin of data) to server addresses. History: This information was found while stepping through the WinSock code. Don't try this at home (in a sense: do it actually at home). Using WsControl() to get the IP address --------------------------------------- WsControl() is an undocumented function found in both the 16 bit and the 32 bit WinSock DLLs of Microsoft. Many people already suspected that WsControl() returns very useful data, because it's used by the Windows 95 WINIPCFG tool. The problem is that it's an undocumented function: you won't hear anything of it from Microsoft... In short: the following information may be highly platform dependent, and extremely unportable. Food for the hacking minded proper. Here's what I found out (and I'd appreciate any comments if you find other things about WsControl). The WsControl() function looks like this: DWORD WsControl(DWORD Protocol, DWORD Action, LPVOID CommandBuffer, LPDWORD CommandBufferSize, LPVOID ResultBuffer, LPDWORD ResultBufferSize); For TCP/IP Protocol should be set to IPPROTO_TCP and/or IPPROTO_UDP. Other protocol values result in the expected WSA error "unsupported protocol". The only value of Action I encountered was 0, which seems to mean something like "Get Information". I believe that passing a value of 1 would send something like "Set Information" to WinSock; but I must admit that I didn't try this. (For obvious reasons ofcourse. If you happen to have a lot of money, consider donating money to me, so I can buy a test machine. :-) CommandBuffer has a buffer with a command that's sent to WinSock, CommandBufferSize points to a DWORD with the size of the CommandBuffer. On return of the function, ResultBuffer will have data returned by WsControl for the command in CommandBuffer. The DWORD pointed to by ResultBufferSize should have the size of ResultBuffer on function call, and has the number of bytes written to ResultBuffer on function return. CommandBuffer ------------- The Command buffer is a structure of 36 bytes. It may look like this; names are mine, yours may be better. #pragma pack(1) typedef struct { DWORD Number; /* Interface number, WS_INTERFACE_TCPIP for TCPIP */ DWORD Unknown; /* Seems to be used to differentiate between * multiple TCPIP interfaces. */ } WS_INTERFACE, FAR* LPWS_INTERFACE, NEAR* NPWS_INTERFACE, * PWS_INTERFACE; typedef struct { WS_INTERFACE Interface; /* Interfaces to query??? */ DWORD What; /* Changes for each request */ DWORD Unknown; /* Seems to be always 0x100??? */ DWORD Command; /* Obviously a command */ BYTE Unknown1[16]; /* Unknown (Always 0???) */ } WS_IN_PARAMS, FAR* LPWS_IN_PARAMS, NEAR* NPWS_IN_PARAMS, * PWS_IN_PARAMS; #pragma pack() The first two DWORDs in the WS_IN_PARAMS structure (the CommandBuffer) have a number for what I call an "interface". In Windows 95 lingo you can call it an adapter. For the TCP/IP interface, the Interface should have a value of 0x0301, with possibly some other value for the most significant DWORD (little endian: xxxx xxxx 0000 0301). Just to clarify the above phrase: when Windows searches for an TCP/IP interface, it loops while checking for the DWORD 0x00000301. The What member seems to change for each WsControl request, and probably "browses" deeper into the hierarchy of data available through WsControl. The Command member has a number which seem to correspond with a command. Available Commands ------------------ Here's what I found about commands. Note that the naming of the commands are mine; yours may be better. Get List Of Interfaces ---------------------- Interface = 0 What = 0x100 Unknown = 0x100 Command = 0 Unknown1[] = 0 Returns several quad words (4 words, 2 dwords) with all the interfaces available for use in WsControl. The interface number for TCP/IP is 0x0301 (with the Unknown dword possibly set to differentiate between multiple TCP/IP interfaces). Example output: 0000: 00 04 00 00 00 00 00 00 - 01 04 00 00 00 00 00 00 0010: 01 03 00 00 00 00 00 00 - 80 03 00 00 00 00 00 00 0020: 80 02 00 00 00 00 00 00 - 00 02 00 00 00 00 00 00 0030: 00 02 00 00 01 00 00 00 The quadword at offset 0x10 has the first (and only) TCP/IP interface. Incidently, the quadword at 0x30 is related to the loopback adapter, the quadword at 0x28 to the PPP adapter. Acknowledge Valid Interface (Get Version??? Is Current???) ---------------------------------------------------------- Interface = Valid Interface What = 0x100 Unknown = 0x100 Command = 0x1 Unknown1[] = 0 To acknowledge the interface in Interface, send the above command. The result buffer should return a DWORD with a special value. For example, the interface number for TCP/IP is 0x0301. If you set Interface to 0000 0000 0000 0301, the returned DWORD will contain 0x0303. Presumably, the special value returned is related to the HIBYTE of the interface number. Note that the ResultBufferSize will NOT have the number of bytes written to the buffer. This seems to be a bug - or perhaps it's just the way this command is supposed to work. Your guess may be better. Example output: 0000: 03 03 00 00 The above output is for the TCP/IP interface. Get Interface Information ------------------------- Interface = Valid Interface What = 0x200 Unknown = 0x100 Command = 0x1 Unknown1[] = 0 This command returns several data which may or may not be useful at all. For TCP/IP the DWORD at offset 0x54 has the number of IP addresses active for the TCP/IP interface. To get specific IP address information, use the Get Active Address Information. For TCP/IP the DWORD at offset 0x58 has the number of IP address related information structures. To get the information, use the Get Extended Active Address Information. Example Output: For TCP/IP: 0000: 02 00 00 00 80 00 00 00 - EA 01 00 00 00 00 00 00 0010: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 0020: EA 01 00 00 66 01 00 00 - 00 00 00 00 00 00 00 00 0030: 00 00 00 00 3C 00 00 00 - 00 00 00 00 00 00 00 00 0040: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 0050: 02 00 00 00 02 00 00 00 - 07 00 00 00 The DWORD at offset 0x54 has the number of structures returned by the Get Active Address Information command. The DWORD at offset 0x58 has the number of structures returned by the Get Extended Active Address command. Get Extended Active Address Information --------------------------------------- Interface = Valid Interface What = 0x200 Unknown = 0x100 Command = 0x101 Unknown1[] = 0 For TCP/IP this command returns data with an unknown structure. It seems to list network masks. Example output: For TCP/IP see output below. Note that the size of each structure is 0x150 / 0x07. 0000: E0 00 00 00 02 00 00 00 - 01 00 00 00 FF FF FF FF 0010: FF FF FF FF FF FF FF FF - C3 AD E4 8E 03 00 00 00 0020: 02 00 00 00 38 00 00 00 - E0 00 00 00 FF FF FF FF 0030: 00 00 00 00 02 00 00 00 - 01 00 00 00 FF FF FF FF 0040: FF FF FF FF FF FF FF FF - C3 AD E4 8E 03 00 00 00 0050: 02 00 00 00 38 00 00 00 - 00 00 00 00 FF FF FF FF 0060: C3 AD E4 8E 01 00 00 00 - 01 00 00 00 FF FF FF FF 0070: FF FF FF FF FF FF FF FF - 7F 00 00 01 03 00 00 00 0080: 02 00 00 00 38 00 00 00 - FF FF FF FF FF FF FF FF 0090: C3 AD E4 FF 02 00 00 00 - 01 00 00 00 FF FF FF FF 00A0: FF FF FF FF FF FF FF FF - C3 AD E4 8E 03 00 00 00 00B0: 02 00 00 00 38 00 00 00 - FF FF FF FF FF FF FF FF 00C0: C3 AD E4 00 02 00 00 00 - 01 00 00 00 FF FF FF FF 00D0: FF FF FF FF FF FF FF FF - C3 AD E4 8E 03 00 00 00 00E0: 02 00 00 00 38 00 00 00 - FF FF FF 00 FF FF FF FF 00F0: FF FF FF FF 02 00 00 00 - 01 00 00 00 FF FF FF FF 0100: FF FF FF FF FF FF FF FF - C3 AD E4 8E 03 00 00 00 0110: 02 00 00 00 BB 2B 00 00 - FF FF FF FF FF FF FF FF 0120: 7F 00 00 00 01 00 00 00 - 01 00 00 00 FF FF FF FF 0130: FF FF FF FF FF FF FF FF - 7F 00 00 01 03 00 00 00 0140: 02 00 00 00 BC 2B 00 00 - FF 00 00 00 FF FF FF FF Get Active Address Information ------------------------------ Interface = Valid Interface What = 0x200 Unknown = 0x100 Command = 0x102 Unknown1[] = 0 For TCP/IP this command returns data which is partly understood. The first four bytes in each structure have active IP addresses. Example output: For TCP/IP see output below. Note that the size of each structure is 0x30 / 0x02. 0000: C3 AD E4 8E 02 00 00 00 - FF FF FF 00 01 00 00 00 0010: FF FF 00 00 01 00 00 C0 - 7F 00 00 01 01 00 00 00 0020: FF 00 00 00 01 00 00 00 - FF FF 00 00 00 00 00 C0 As you can see, the first DWORD has the current IP address (195.173.228.142). The first DWORD in the second structure has the loopback address of 127.0.0.1, which was active at that time. Author ------ Written by Alfons Hoogervorst. He can be contacted at . If you have additional information about any of the topics in this document, especially about WsControl(), send me a note. Comments, suggestions, and any useful hints are welcome too. Alfons works as a freelance developer, specializing in low-level programming. Legalese -------- You're allowed to distribute this file for free, without paying me any fee. However, the following sections should remain unmodified in this document: Introduction, Author, Legalese, Copyright. The information in this document is provided AS IS, without any warranties or guarantees. As a human being, he expressedly reserves his rights to err: "If its meaning doesn't manifest, then: put it to rest!" Copyright --------- Copyright (C) Alfons Hoogervorst / The Programming with Puns Facility, 1998.