MS-DOS TCP/IP Programming

(Note: this page is a bit stale. Not sure if WATTCP has an official home anymore.)

Packet Drivers

Many DOS TCP/IP packages rely on these. You can pick them up from Crynwr Software, the home of the Packet Driver spec. Alternatives to packet drivers, along with some WATTCP debugging info for end-users, are mentioned in an FAQ I wrote for a game that uses WATTCP and KLOS PPP.


Wattcp is very popular. Printed programmer's guide available. Statically linked library; uses Packet Driver interface. A few Usenet posts with bugfixes are archived below.

Other TCP/IP packages for DOS programmers

DOS Trumpet

DOS Trumpet is a TSR with a fairly well defined API (called ABI to emphasise that it's a binary application interface, not just a source level interface).

The official home page for DOS Trumpet ABI is Version 3.01 (aka ntcpdrv) is referred to as "using less memory" on the official home page, but it is the first version to properly implement several features, for example, the ability to find the address of sender of a UDP packet.

Mike Durkin has written a great C wrapper for the trumpet ABI; you can download it from Tsoft's site. I have ported his C wrapper to 32 bit Watcom C. (It still compiles under 16 bit Borland, too.) You can download my port from here until Mike picks it up. (Note: I only ported the DNS and UDP parts-- the TCP part still needs porting, but it should be very easy.)
A few Usenet posts with bugfixes are archived below.

The wrapper comes with working DNS routines and an example finger client, but no UDP examples. To remedy that, I have written ports of a perl UDP example:

We can be fairly sure that this ABI works, because Tsoft's NFS based on it. (See

The trick to receiving UDP packets is revealed in the DNS code: you can use the same handle to get DNS responses as you used to send them! That means that the 'srcport' and 'destport' parameters to udp_open are really 'localport' and 'remoteport'.

Pages about RARP

Other pages of interest to TCP/IP programmers

DOS TCP/IP integrated software suites (no TSR's)

Related Usenet postings

Subject: TCPDRV C ABI bug fixed --- tcpabi_udp_status
Date: Fri, 03 Apr 1998 20:19:25 -0600
Newsgroups: comp.protocols.tcp-ip.ibmpc, comp.protocols.tcp-ip

I found a bug in Trumpet TCP ABI in C interface TABI9605, and
fixed it. The bug is caused by an omitted line in specification
file TCP201.SPE. To fix this bug, add the following line in
udp_status segment just after line "ah = 24H" in file TCP201.SPE

    bx = handle

Then rewrite C function tcpabi_udp_status as following:

int tcpabi_udp_status(unsigned handle, unsigned char flags,
        unsigned *size_next, tcpabi_session_info_t far **info )
   union REGS r;
   struct SREGS s;

   r.h.ah = 0x24; = flags;
   r.x.bx = handle;     /* Added by, 98.03.24 */
   int86x( _tcpabi_vector, &r, &r, &s );
   _tcpabi_errno = r.h.dl;
   if( size_next ) *size_next =;
   if( info ) *info = MK_FP(, r.x.di );
   return ( _tcpabi_errno ? -1 : );
         /* return number of packets avail */
         /* or -1 if error */

Subject: WATTCP UDP passive socket bug fixed
Date: Fri, 03 Apr 1998 20:22:17 -0600
Newsgroups: comp.protocols.tcp-ip.ibmpc, comp.protocols.tcp-ip

I found a bug in WATTCP9609 UDP part, and fixed it. The bug is, if
you open a passive UDP socket for a UDP server(such as TFTP server),
the server can gets first datagram from a client, and then discards
all successive datagrams from the same client. If you set debug on
(using tcp_set_debug_state), you can see "discarding..." when a
datagram arrives.

The bug is due to the cross effect of function udp_handler in file
PCTCP.C and function _arp_resolve in file PCARP.C, and some more.
When first datagram reaches the server, the server records client's
IP address and port, then sends an ARP request to find client's MAC
address(really need here?). Unfortunately, the ARP answer packet
overwrites the head of the first datagram buffer, myaddr CHANGES.
So successive datagrams are discarded.

There are several ways to remedy this error. I simply move the
_arp_resolve function call to the end of function udp_handler,
the datagram buffer is useless there.

By the way, it's fortune that we can get first datagram's content,
because ARP packet length is just the same as normal IP head length
plus UDP head length.

For your convenience, I list the corrected function here.

Dashui Zhou, Dept. of CS, Shandong Univ., China.

 * Handler for incoming packets.
static void udp_handler( in_Header *ip )
    udp_Header *up;
    tcp_PseudoHeader ph;
    word len;
    byte *dp;
    longword temp;
    udp_Socket *s;
    int need_arp = 0;   /* must be auto, 98.04.03 */

    temp = intel( ip->destination );

    // temp = ip number
    //     or
    //     or sin_mask.255.255

    if ( ((~temp & ~sin_mask) != 0) &&  /* not a broadcast packet*/
        ((( temp - my_ip_addr) > multihomes )   /* not my address */
        && my_ip_addr))                 /* and I know my address */

    len = in_GetHdrlenBytes(ip);
    up = (udp_Header *)((byte *)ip + len);    /* udp segment pointer */
    len = intel16( up->length );

    /* demux to active sockets */
    for ( s = udp_allsocs; s; s = s->next ) {
        if ( s->safetysig != SAFETYUDP ) {
            if (debug_on) outs("chain error in udp\r\n");
        if ( (s->hisport != 0) &&
             (intel16( up->dstPort ) == s->myport) &&
             (intel16( up->srcPort ) == s->hisport) &&
             ((temp & sin_mask)  == (s->myaddr & sin_mask)) &&
             (intel( ip->source ) == s->hisaddr )) break;
    if (_dbugrecv) (*_dbugrecv)( (sock_type*)s, ip, up, 0);
    if ( !s ) {
        /* demux to passive sockets */
        for ( s = udp_allsocs; s; s = s->next )
            if ( ((s->hisaddr == 0) || (s->hisaddr == 0xffffffffuL))
              && intel16( up->dstPort ) == s->myport ) {

                // do we record this information ???
                if ( s->hisaddr == 0 ) {
                    s->hisaddr = intel( ip->source );
                    s->hisport = intel16( up->srcPort );

/* found a bug and fixed here, Apr. 3, 1998 */
                    need_arp = 1;       /* remember we need arp */
                                        /* and postpone arp action */

                    // take on value of expected destination unless
                    // it is broadcast
                    if ( (~temp & ~sin_mask) != 0 )
                        s->myaddr = temp;
    if ( !s ) {
        /* demux to broadcast sockets */
        for ( s = udp_allsocs; s; s = s->next )
            if ( (s->hisaddr == 0xffffffffuL) &&
                 (intel16( up->dstPort ) == s->myport )) break;

    if ( !s ) {
        if (debug_on) outs("discarding...");

    // these parameters are used for things other than just checksums
    ph.src = ip->source;    /* already INTELled */
    ph.dst = ip->destination;
    ph.mbz = 0;
    ph.protocol = UDP_PROTO;
    ph.length = up->length;
    if ( up->checksum ) {
        ph.checksum =  checksum(up, len);
        if (checksum(&ph, sizeof( tcp_PseudoHeader)) != 0xffff)

    /* process user data */
    if ( (len -= UDP_LENGTH ) > 0) {
        dp = (byte *)( up );
        if (s->dataHandler)
            s->dataHandler( s, &dp[ UDP_LENGTH ], len , &ph, up);
        else {
            if (len > s->maxrdatalen ) len = s->maxrdatalen;
            movmem( &dp[ UDP_LENGTH ], s->rdata, len );
            s->rdatalen = len;

    if( need_arp )      /* get his MAC address before return,
                           98.04.03 */
        _arp_resolve(intel(ip->source), &s->hisethaddr, 0);

} // End of udp_handler

Subject: WATTCP UDP Client/Server Example
Date: 8 Apr 1998 20:01:37 GMT
From: "Michael Temari"
Newsgroups: comp.protocols.tcp-ip.ibmpc

I've been seeing some questions regarding WATTCP UDP programming lately and
thought I would share my UDP client/server test program.

Basically run the program with no arguments and it is a server that listens
on UDP port 812 and echoes back whatever it receives.  Pass the argument of
the IP address of the server and the program becomes the client allowing
you the enter data that will be sent to the server and get bounced back.

Hope this helps those who are trying to do UDP programming with WATTCP.

Michael Temari

attached file: Wtest.c

Dan Kegel