Coda SOCK.VXD Pseudo-Documentation

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.

Contents

[Introduction] [How to Call the VxD] [SOCK.VXD Functions] [Bugs in SOCK.VXD] [Useful and Relevant Links] [Credits] [Version History]


Introduction

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).

Getting Started

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.

Accuracy of the Information

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.

[Back to the Contents]


How to Call the VxD

Loading the VxD

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.

The VxD's Entry Point and Parameters

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.

[Back to the Contents]


SOCK.VXD Functions

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.

Function 0 - Get version information

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

[Back to the Function Table]

Function 1 - Create Socket

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

[Back to the Function Table]

Function 2 - Close

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

[Back to the Function Table]

Function 3 - Bind

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

[Back to the Function Table]

Function 4 - Send To

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

[Back to the Function Table]

Function 5 - Receive From

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

[Back to the Function Table]

Function 6 - Select

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

[Back to the Function Table]

Function 7 - Connect

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

[Back to the Function Table]

Function 8 - Send

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

[Back to the Function Table]

Function 9 - Receive

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

[Back to the Function Table]

Function 10 - Listen

In EAX = 10
EDI = File descriptor
ECX = Backlog
Out EAX = Return code
Carry set on error

[Back to the Function Table]

Function 12 - Accept

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

[Back to the Function Table]

Function 13 - Get Socket Name

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

[Back to the Function Table]

Function 14 - Get Peer Name

In EAX = 14
EDI = File descriptor
Out EAX = Return code
Carry set on error
EBX = Port number of peer
ECX = IP address

[Back to the Function Table]

Function 15 - Get/set Non-blocking Mode

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)

[Back to the Function Table]

Function 16 - Get allocated file descriptors

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

[Back to the Function Table]

[Back to the Contents]


Bugs in SOCK.VXD

Unfortunately there are many bugs in SOCK.VXD. These severely limit its usefulness. Here's a list of known bugs:

[Back to the Contents]


Useful and Relevant Links

[Back to the Contents]


Credits

[Back to the Contents]


Version History

[Back to the Contents]


Copyright 1999-2001 by Richard Dawe. Any comments are welcome - please send them to <webmaster@phekda.org>.