Version 0.2.3, Copyright 1999, 2001, 2004 by Richard Dawe
Last updated 2004-07-03
This document can be distributed freely, so long as it is
unmodified.
[Introduction] [How to Call the VxD] [SOCK.VXD Functions] [Bugs in SOCK.VXD] [Useful and Relevant Links] [Credits] [Version History]
This document covers how to use the virtual device driver (VxD) SOCK.VXD from the Win95 port of the Coda network filesystem project. It was written to allow DJGPP programs to use Windows's Winsock networking services. I have nothing to do with the Coda project - I maintain a networking library for DJGPP called libsocket that uses the VxD.
The VxD is distributed under the terms of the GNU General Public License.
SOCK.VXD uses the Transport Driver Interface (TDI) provided by Windows to device drivers. This means that it will provide networking services irrespective of the underlying drivers. Thus, it will work with Winsock 1.x, Winsock 2 and perhaps proprietary networking stacks. SOCK.VXD only supports Internet address family sockets of the (type, protocol) combinations of (SOCK_STREAM, IPPROTO_TCP) and (SOCK_DGRAM, IPPROTO_UDP).
There are multiple versions of SOCK.VXD, all of which are documented here. Versions prior to version 1 did not have a "get version" function and will be referred to as "version 0". The version is important, because there are bugfixes between releases.
SOCK.VXD outputs debugging messages. To view these requires a debug version of Windows. The debug messages, generated by Out_Debug_String() (which is the same as DebugOutputString()?), can be viewed using remote debugging by serial port or using a utility such as Dbwin from the Windows Developers' Journal. Unfortunately, I do not have a debug build of Windows, so I don't know whether this works (or how useful it may be).
The VxD has been packaged into a nice installer. The installer and the source are available from ftp://www.coda.cs.cmu.edu/pub/tools/95. The sources contain all the defines, etc., that one needs, when interfacing with the VxD.
Because SOCK.VXD is GPL'd, one can look at the source code and find out how the VxD works, fix bugs, etc. Microsoft's Windows Winsock driver WSOCK.VXD is not distributed with source, so using it is a hit-and-miss affair. This is a great advantage when using SOCK.VXD. IMHO SOCK.VXD has a much cleaner interface than WSOCK.VXD. WSOCK.VXD has been empircally documented in the WSOCK.VXD Pseudo-Documentation.
SOCK.VXD does not have a real-mode entry point. Perhaps this is because it was written to be used with DJGPP, which uses protected-mode.
SOCK.VXD will work with 16-bit DPMI programs.
Please mail me with any additional information.
The first stage is to actually load the VxD into memory. This can be performed using the VXDLDR virtual device. The entry point for VXDLDR has to be obtained using a DOS function call (table below). VXDLDR has an ID of 0x27.
Interrupt 0x2F, Sub-function 0x1684: Get VxD entry point | |
---|---|
In | AX = 0x1684 BX = VxD ID number |
Out | ES:DI = VxD entry point |
Once a valid entry point (i.e. not 0:0) has been obtained for VXDLDR, the VxD should be called as follows:
VXLDR function 0x1: Load VxD | |
---|---|
In | AX = 0x1 DS:DX = Buffer containing VxD's filename |
Out | Carry flag clear on success |
Function 0x2, unload VxD, works in the same way.
SOCK.VXD has a VxD ID number of 0x1235. The entry point can be obtained using interrupt 0x2F sub-function 0x1684 as above.
The VxD is called with the function number in EAX. If a function takes a socket file descriptor, it is placed in EDI. The return code is placed in EAX. If the return value is an error, the carry flag is set. All SOCK.VXD errors are returned as negative integers. Exception: If an invalid function number is called, 0x1234 is returned as the error. If SOCK.VXD has a TDI error that it cannot cope with, it is return to the caller. TDI errors can be distinguished from the SOCK.VXD errors, because they are between 0x0 and 0xff - the SOCK.VXD errors are > 0x100. The TDI errors are also usually returned as positive codes - I will indicate where this isn't the case.
All IP addresses and port numbers are passed in network order, i.e. big endian. The host order on Intel platforms is little endian, so a conversion is necessary. DJGPP provides the functions htons(), htonl(), ntohs() and ntohl() to convert between host and network order and vice-versa.
All buffers in 32-bit mode are referenced by a selector and offset. The data selector for DJGPP programs is given by the function _my_ds(). The offset is then the pointer to the data cast to a long integer.
Function Number | Description | BSD Function | Documented Here |
---|---|---|---|
0 | Get version information | None equivalent | Yes |
1 | Create socket | socket() | Yes |
2 | Close | close() [1] | Yes |
3 | Bind | bind() | Yes |
4 | Send to | sendto() | Yes |
5 | Receive from | recvfrom() | Yes |
6 | Select | select() | Yes |
7 | Connect | connect() | Yes |
8 | Send | send() | Yes |
9 | Receive | recv() | Yes |
10 | Listen | listen() | Yes |
11 | Asynchronous connect? | ? | No |
12 | Accept | accept() | Yes |
13 | Get socket name | getsockname() | Yes |
14 | Get peer name | getpeername() | Yes |
15 | Get/set non-blocking mode | None equivalent [2] | Yes |
16 | Get allocated file descriptors | None equivalent | Yes |
100 | Get TDI version | None equivalent | No |
101 | Get TDI information about "MSTCP" |
None equivalent | No |
Key | |
---|---|
Documented | Undocumented |
[1] close() operates on file descriptors. SOCK.VXD allocates its own "virtual" file descriptors. These may not match up with the ones used by e.g. DJGPP, etc.
[2] This is equivalent to using fcntl() with the flag O_NONBLOCK or ioctl() with the request FIONBIO.
As mentioned in the introduction, versions of SOCK.VXD prior to version 1 did not support this function. In fact, if you call this function for "version 0", its behaviour is unpredictable. This is because it tries to find the socket referred to by EDI. This will almost certainly fail and return an error.
To obtain consistent behaviour between all versions of SOCK.VXD, call this function will an invalid file descriptor (currently greater than 31) in EDI, so that it will fail consistently with version 0 or return the correct version with version 1 (or later).
In | EAX = 0 EDI = Bad file descriptor, e.g. 0xFFFF |
---|---|
Out | EAX = Version number Carry set on error |
Note I:The available file descriptors are 0 to 31. I think these are shared between all processes using SOCK.VXD, but I am unsure. It is up to the client program to find a free file descriptor. This can be achieved by repeatedly calling this function until a free file descriptor is found - SOCK.VXD returns an error if the specified file descriptor is being used. Using the file descriptor 0 may be a little dangerous, unless care is taken.
Note II: UDP sockets are created in non-blocking mode by default. TCP sockets are created in blocking mode.
In | EAX = 1 EBX = Protocol (TCP = 6, UDP = 17) EDI = Desired file descriptor |
---|---|
Out | EAX = Return code Carry set on error |
BUG: The close call forcibly terminates TCP connections, i.e. it sends a RST instead of using a graceful disconnect. This is OK for protocols such as HTTP, but I have found that it makes writing an FTP client with SOCK.VXD impossible - the FTP server thinks transfers fail, because of the forcible close.
In | EAX = 2 EDI = File descriptor |
---|---|
Out | EAX = Return code Carry set on error |
This re-uses the local address by default. With most TCP/IP stacks, one usually has to explicitly enable this with the SO_REUSEADDR option of setsockopt(). So, caution is needed, because there is no way to disable this, due to the lack of a setsockopt() call.
In | EAX = 3 EDI = File descriptor EBX = IP address EDX = Port number |
---|---|
Out | EAX = Return code Carry set on error |
This function is specialised to datagrams only, e.g. UDP/IP connections, unlike the normal BSD function. Also contrary to BSD behaviour, it doesn't bind to a local address as necessary. So, a local address must explicitly be specified by using the bind function.
The TDI errors are returned as negative numbers for this function.
In | EAX = 4 EDI = File descriptor EBX = IP address of peer EDX = Port number of peer DS = Selector of send buffer ESI = Offset of send buffer ECX = Send buffer length |
---|---|
Out | EAX = Return code Carry set on error ECX = Sent length |
This function is specialised to datagrams only, e.g. UDP/IP connections, unlike the normal BSD function.
In versions prior to version 1, there was a bug in this function call. If the socket was marked as blocking then the operation would be carried out in non-blocking mode, and vice-versa. This bug can be circumvented by flipping the socket's mode using function 15. This bug was fixed in version 1.
In | EAX = 5 EDI = File descriptor DS = Selector of receive buffer ESI = Offset of receive buffer ECX = Length of receive buffer |
---|---|
Out | EAX = Return code Carry set on error ECX = Received length EBX = IP address of peer EDX = Port number of peer |
Select can be used to test all the available file descriptors for readiness for reading, writing and exceptions (i.e. errors). It takes a bitfield of all the file descriptors, so one cannot just pass the file descriptor to it. The bitfield is constructed by logically OR'ing together 2^fd for each file descriptor fd, like so:
bitfield = 2^fd1 | 2^fd2 | ...;
2^fd1 can also be written as 1 << fd1. On exit from the call, the bitfield contains the file descriptors that were ready.
A timeout for the select operation can be given. 0xFFFFFFFF is taken to mean "wait forever". The elapsed time before finding a ready socket is returned.
In | EAX = 6 EDI = Timeout EBX = Read bitfield ECX = Write bitfield EDX = Exception bitfield |
---|---|
Out | EAX = Return code Carry set on error EDI = Elapsed time EBX = Read bitfield ECX = Write bitfield EDX = Exception bitfield |
This function is specialised to streams only, e.g. TCP/IP connections, unlike the normal BSD function. One must simulate the BSD connect() call's behaviour for datagrams, which is to associate a default address for sendto() calls. This can be done by storing the address and passing it to send to later.
In | EAX = 7 EDI = File descriptor EBX = IP address EDX = Port number |
---|---|
Out | EAX = Return code Carry set on error |
This function is specialised to streams only, e.g. TCP/IP connections, unlike the normal BSD function.
Looking at the source code after the send completes, it appears that ECX holds a combination of some status word (content unknown) bitwise OR'd with the sent length, while EAX contains just the sent length.
ECX contains the total number of bytes queued to send bitwise OR'd with the TDI status for the last data that was sent from the queue:
(tdi_error << 16) | send_queue_length
The TDI errors are returned as negative numbers for this function.
In | EAX = 8 EDI = File descriptor DS = Selector of send buffer ESI = Offset of send buffer ECX = Length of send buffer |
---|---|
Out | EAX = TDI error code or sent length Carry set on error ECX = Queued send length & TDI status of last send |
This function is specialised to streams only, e.g. TCP/IP connections, unlike the normal BSD function.
In | EAX = 9 EDI = File descriptor DS = Selector of receive buffer ESI = Offset of send buffer ECX = Length of receive buffer |
---|---|
Out | EAX = Return code Carry set on error ECX = Received length |
In | EAX = 10 EDI = File descriptor ECX = Backlog |
---|---|
Out | EAX = Return code Carry set on error |
This call requires a file descriptor to be passed to the function. This must be chosen beforehand. Like creating a socket this file descriptor must be unused. If it is in use, the connection will be dropped.
In | EAX = 12 EDI = File descriptor EDX = File descriptor for accepted connection |
---|---|
Out | EAX = Return code Carry set on error EBX = Port number of peer ECX = IP address of peer |
There currently seems to be a bug in this function. The IP address does not seem to be filled. Thus, it has to be obtained by other means.
In | EAX = 13 EDI = File descriptor |
---|---|
Out | EAX = Return code Carry set on error EBX = Port number ECX = IP address |
In | EAX = 14 EDI = File descriptor |
---|---|
Out | EAX = Return code Carry set on error EBX = Port number of peer ECX = IP address |
In | EAX = 15 EDI = File descriptor ECX = 0 for get, 1 for set EDX = 0 for blocking, non-zero for non-blocking |
---|---|
Out | EAX = Return code Carry set on error EDX = 0 for blocking, non-zero for non-blocking (get only) |
This function was added in version 1.
In | EAX = 16 |
---|---|
Out | EAX = File descriptor bitfield
(see function 6)
or -1 if no file descriptors are in use Carry set on error |
Unfortunately there are many bugs in SOCK.VXD. These severely limit its usefulness. Here's a list of known bugs:
SOCK.VXD's sockets always behave as though the socket option SO_REUSEADDR is set, so local addresses are reused on the bind call.
SOCK.VXD always closes stream sockets by sending a RST. This causes protocols that require graceful closures to fail, e.g. FTP data transfers.
SOCK.VXD seems to have problems receiving/sending more than 32K through a stream socket. The symptoms are that the DOS box blocks in SOCK.VXD (i.e. Ctrl+C will not kill the program and the DOS box has to be forcefully closed).
SOCK.VXD's getsockname call does not always seem to work.
SOCK.VXD has no out-of-band (OOB) support.
There are problems using firewall software:
There may be problems with other firewall software, but no other programs have been reported yet.
TDI Frequently Asked Questions - TDI is the Transport Driver Interface, which SOCK.VXD uses to access Windows's TCP/IP stack. The FAQ includes links to Microsoft's documentation and sample code.
Open System Resources, Inc. (OSR) has much information on Windows driver programming, but mostly for Windows NT/2000. Since TDI is portable between Windows '9x and NT/2000, this may also be of interest. See NT Insider Online, the ntdev mailing list and a sample TDI driver for Windows NT that uses UDP.
Alfons Hoogervorst has compiled some information about finding out various IP details under Windows into ipdata.txt.
I have compiled a similar, but complementary, document called "Where IP Data is Stored for Network Cards under Windows".
Winsock API issues are discussed in the comp.os.ms-windows.programmer.tools.winsock newsgroup. The Winsock Programmers FAQ is the newsgroup's FAQ. These may be useful when programming for SOCK.VXD, because the Winsock issues may also affect the VxD.
A mailing list has been set up to discuss the use of Windows networking from a DOS box - the dossock mailing list.
libsocket home page - I wrote this document while adding an interface to SOCK.VXD to libsocket.
I have partially documented the Windows Winsock virtual device driver, WSOCK.VXD, in the WSOCK.VXD Pseudo-Documentation.
Version 0.2.3 (2004-07-03) - Update the URL for the dossock mailing list. Update my e-mail address.
Version 0.2.2 (2000-12-16) - More information on how send works.
Version 0.2.1 (2000-10-01) - Added some TDI links, a link to the Winsock Programmers FAQ.
Version 0.2.0 (2000-09-09) - SOCK.VXD works with 16-bit DPMI. Added a list of known bugs, discovered while developing libsocket.
Version 0.1.9 (1999-12-26) - Corrected mistake in input parameters for connect.
Version 0.1.8 (1999-12-24) - No real-mode entry point! Added info about bug in close. Added info about state of sockets after creation.
Version 0.1.7 (1999-09-19) - Added new findings for the bind, send to, connect and send calls. Also added a link to Dbwin, for viewing the VxD's debugging output. Updated the info on TDI errors.
Version 0.1.6 - Finally added a link to the Coda home page (I can't believe I didn't do that before) and a snippet about SOCK.VXD's debugging output.
Version 0.1.5 - Added notes on the specialisation of data transfer functions to datagrams or streams, unlike the normal BSD functions.
Version 0.1.4 - Updated information on the "get allocated file descriptors" function.
Version 0.1.3 - Update to cope with version 1 of SOCK.VXD.
Version 0.1.2 - Same as original with a new section for useful links. Revision a - Corrected error in socket creation - protocol should go in EBX!
Copyright 1999-2001 by Richard Dawe. Any comments are welcome - please send them to <webmaster@phekda.org>.