In this short overview of IPX/SPX protocol I'll give an description of these packets and some examples of usage. As a basis of this work the Rahner James' story from Doctor Dobbs Journal (May, 1992) "IPX - the Great Communicator" was used. The sources of this text can be downloaded from DDJ's FTP server.
On ISO (International Standards Organization) OSI (Open System Interconnection) model, the IPX stays on Network level, and SPX on Transport level, as it relates to Novell's NetWare.
|Table 1 - ISO/OSI model|
Internetwork Packet Exchange (IPX) is a protocol that allows for high-speed, peer-to-peer communication on Novell's NetWare. IPX is the lowest level of communication that can be performed on a network without resorting to direct access of the hardware. Novell refers to the functions that enable this type of connection as "IPX/SPX Communication Services". SPX - Sequenced Packet Exchange. These packets can be sent from one machine to another or sent to all machines (nodes). Sending a packet to all machines is called a BROADCAST. A broadcast will be confined to just the local network, however there are ways around this which I will not go into here. Both IPX and SPX are developed by Novell, Inc. For simplicity, I refer to both IPX and SPX as XPX.
Normal Novell Operating system accesses are through the MS-DOS INT 21h window. XPX however, uses a slightly different machanism. Before any function calls can be made, the application must call the MS-DOS multiplex (2Fh) and get the vector to the XPX entry point. Code sample in assembler shows how to do that.
mov ax, 7A00h ; function 7Ah, AL=0 int 2Fh ; MS-DOS Multiplex Interrupt ; Returns with AL==0FFh if xPX ; exists and ES:DI==xPX vector inc al ; Set ZERO if AL==-1 jnz outta_here ; Quit if xPX isn't around mov IPX_Vector, di ; Save the xPX entry vector mov IPX_Vector+2, es
Although one could call each function via multiplex, it is much more efficient to make call using the saved vector. XPX uses register BX to hold an XPX command number. An XPX call is not kind to unused registers and, in general, sensitive registers should be saved (especially BP). By using a far call (rather than MS-DOS int 21h), most XPX functions can be called from background process without the programmer worrying about trashing the system. All XPX functions return the status of their result in AL.
All network interfaces (Ethernet, Arcnet, and so on) require a unique 6-byte node ID to differentiate between them. The node ID only differentiates a network interface within a single network. Therefore, two network can have interfaces with identical node IDs. Because a single computer can have multiple network interfaces or a network interface can be placed on another computer, a node ID does not necessarily specify a single, distinct computer system.
Every Novell network has a 4-byte ID number that uniquely differentiates it from any other Novell networks to which it is connected. If no other networks are connected, any number is unique.
Every node connected to a network can open several different channels of communication, called "sockets". Sockets allow application to differentiate types of communication performed by a node. One socket can be used to broadcast one message to all nodes while another socket can be used to send and/or receive messages from specific nodes or group of nodes. By default, XPX can support up to 20 sockets on a single node. Through configuration, the number of sockets that may be used can be increated to 150. A 2-byte number is used as a socket ID.
Numerically, the network, node, and socket IDs are in Motorola (Big Endian) format which is opposite to the format used by Intel (Small Endian) products. The format is actually no consequence because the magnitude, order, and content are not relevant to the application or XPX. There are special cases of each type of ID, but all are palindromes, so byte order is still not important. The 4-byte network ID can be viewed as a 32-bit long integer with no significance associated with its magnitude. The 6-byte node ID can be viewed as a non-ASCII string, such as a filename, with no naming convension. The 2-byte socket ID can be a short integer that also has no significance associated with its magnitude.
In order to send a packet over a IPX network one needs to set up a basic control structure, called Event Control Block (ECB) which is vital for the driver. The ECB is passed to XPX to describe its associated channels and buffers. The ECB consists of several data fields and its structure is shown on Table 2. The first 34 bytes of the ECB contain control and addressing information. A list of "associated fragments" immediately follows this 34-byte header.
|Table 2 - ECB fields|
|Link||4||Pointer to next ECB|
|ESR||4||Pointer to Event Service Routine|
|InUse||1||Flag telling the ECB status|
|Complete||1||Flag telling the ECB completion code|
|Socket||2||Big endian socket number for ECB|
|IPXWork||4||Work space for IPX|
|DrvWork||12||Work space for driver|
|ImmedAddr||12||Address to send to|
|FragCount||2||Number of data fragments|
|FragData||4||Pointer to data fragment (first fragment)|
|FragSize||2||Size of data fragment (first fragment)|
|Optional||...||Last two fields repeated FragCount-1 times|
When initalizing ECB record, set all fields to zero first, then set necessary fields.
Link field is used internally by IPX driver and needs no attention from a programmer's side.
ESR field is a pointer to an Event Service Routine, which is called whenever a packet arrives or is sent. The ESR is a function provided by the application and called by XPX when the ECB has been processed. The ESR is called in the following contitions:
An ESR must return with RETF instruction and interrupts disabled and maintain the stack's integrity. It can call any XPX function except Close Socket; reschedule itself through an AES call; and enable interrupts during operation, as long as blocking is done against another event calling the same function.
An application could conceivably have a separate ESR for every ECB; however, that approach would be excessive. The ESR should be fairly general purpose and should be approached with the same mind-set as an Interrupt Service Routine (ISR).
An application can use ESR for speeding up the processing by allowing packets to come and go in the background; or it can leave it empty - then the application should constantly check the InUse flag to see if any packet has been arrived or sent.
InUse flag can have several values but only 0 indicates that application can use received data or send data to the socket. In table 3 are listed values that InUse flag can take
|Table 3 - InUse flag values|
|00h||Done||Indicates that the ECB has been processed and completion code is valid|
|F8h||Waiting||ECB has been put in a waiting queue bacause IPX was busy|
|FAh||Processing||ECB is being processed by IPX|
|FBh||Holding||Send/receive has occurred, but ECB is in holding queue waiting to be processed|
|FDh||Scheduled||Event has been scheduled and is waiting for time-out|
|FEh||Listening||IPX is listening for incoming packets at a socket|
|FFh||Sending||ECB is being sent or in the process of being sent|
Completion code flag contains the result of the latest action and is filled by XPX after the ECB has been processed. It is valid only when the InUse flag is 00h. This field can take following values
|Table 4 - Completion Code flag values|
|00h||Success||The packet has been sent successfully|
|FCh||Canceled||Send request for this ECB has been cancelled, just like Twin Peaks|
|FDh||Challenged||Packet was malformed (eg. IPX packet header < 30 bytes, SPX header < 42 bytes, or packet size > 576 bytes)|
|FEh||Undelivered||Packet couldn't be sent, no listener available|
|FFh||Failure||Network was physically unable to deliver the packet|
|Additional for reception|
|FDh||Overflow||The size of the fragment received is either 0 or too large (> 576 bytes)|
|FFh||Closed||The reception socket was not open|
|Additional for cancellation of an ECB|
|F9h||Cancel_Fail||The ECB could not be cancelled|
Socket field contains an big endian 2-byte number. In few words, the socket is a number that lets a node decide if it will act on a packet. It allows multiple programs on the computer to use IPX packets completely transparent to others. Packets can be received only if they are addressed to specific socket and that socket has been opened. So packets can be ignored by nodes that are not capable of accepting them.
Socket number can not always be arbitrarily used. Novell & Xerox have reserved the range of socket IDs from 1 to 0BB8h, inclusive. An application may use of socket IDs from 8000h to FFFFh as they see fit. If the application requests socket ID 0, Netware will dynamically allocate a socket ID in the range of 4000h to 7FFFh. Some of the documented socket IDs used by Novell are: 451h - used by Netware file servers for service requests (Netware Core Protocol - NCP); 452h - Service Advertising Protocol (SAP); 453h - Routing Information Packet (RIP); 455h - Used by NetBIOS; 456h - Diagnostic Packet; 457h - Server Serial Number (labeled "Copy Protection" by Lanalyzer); 4000h - 7FFFh - used for dynamic allocation; 4444h, 5555h, 8000h - FFFFh - assigned by Novell.
IPXWork & DrvWork are used internally by driver and must be initialized to 0.
Immediate Address contains the address of the node the packet should be sent to or nodes in case of broadcasting. If ECB is being sent (a talker) using IPX (rather than SPX), this field should be filled in by an application. If ECB is a receiver (a listener) or uses SPX, this field is set by driver.
FragCount is filled by appliaction to tell XPX (I use XPX to refer both the IPX and SPX) how many fragment descriptions are to follow. All ECBs must have at least one fragment descriptor to point to an XPX header. The cumulative size of fragments associated with an ECB can not exceed 576 bytes. If more than 1 fragment is used, the next two fields are repeated.
FragData is a pointer to the fragment data. Application must have at least one fragment that contains a complete IPX or SPX header. Any additional data buffers can be contiguous extension of the XPX header or segmented into unconnected memory locations. Noncontiguous memory fragments require more than one fragment descriptor as stated above.
FragSize defines the number of bytes in the fragment. If the size of the first fragment is not at least the size of neither IPX nor SPX header, XPX returns an error. Fragments that follow must contain at least 1 byte.
Optional Frag desctiptions are optional and do not need to be used or declared if the first fragment descriptor describes the packet entirely.
The next structure to be defined is an IPX (or SPX) header. The application needs to fill the IPX structure only if the packet is being transmitted. If the packet is being used as a listener, XPX fills in all the fields. IPX header is 30 bytes in size and is structured in the following manner:
|Table 5 - IPX Packet header|
|CheckSum||2||Big endian checksum. Set to FFFFh (-1) by XPX|
|Length||2||Big endian length of the entire XPX packet, including all other fragments|
|TC||1||Transport Control (age of packet). Set to 0 by XPX|
|PktType||1||Packet Type. Set by application to 4 for an IPX Packet or 5 for an SPX Packet, if the packet is being sent (talker). Detailed description follows|
|DestNetID||4||Destination network ID. Set by application if packet is being sent (talker). If set to 0, the current network is used regardless of its true ID|
|DestNodeID||6||Destination Node ID. Set by application if packet is being sent (talker). If set to all 0FFh's, the IPX packet will be sent to all IPX listeners on the network with an equal socket ID, including active listeners on transmitting node|
|DestSocketID||2||Destination Socket ID. Set by application if packet is being sent (talker). Socket ID must be opened before socket ID can be used.|
|SrcNetID||4||Source Network ID. Set by XPX to the network ID of the packet's source|
|SrcNodeID||6||Source Node ID. Set by XPX to the node ID of packet's source|
|SrcSocketID||2||Source Socket ID. Set by XPX to the socket ID of packet's source|
PktType filled values...
If using SPX instead of IPX, the header is as shown on Table 6. SPX header is actually a superset of IPX packet header. None of the SPX-specific fields need to be filled in by the application for either transmissions on reception. These fields must be available for SPX packets.
|Table 6 - SPX Packet header|
|IPX Header||30||IPX Header as defined in Table 5|
|CC||1||Connection Control. Used by SPX to control flow of data|
|DST||1||Data-stream Type. Information byte that can be used by application for any purpose. Created by SPX for use by application|
|SrcConnID||2||Source Connection ID. Connection number of source node for this SPX packet. Created by SPX for use by application|
|DestConnID||2||Destination Connection ID. Connection number of destination node for this SPX packet. Created by SPX for use by application|
|SeqNr||2||Sequence Number. Used by SPX to keep sequence of received and transmitted packets straight|
|AckNr||2||Acknowledge Number. Used by SPX to acknowledge receipt of a packet|
|AllocNr||2||Allocation Number. Used by SPX to keep track of packets sent but not acknowledged|
The size of an SPX packet header is 12 bytes larger than the header for IPX packets. Because the headers must be part of every XPX packet, the maximum size of data can be sent with an IPX packet is 546 bytes, and the maximum for SPX is 534 bytes.
As Rahner James wrote in his article, he tried to use ESRs with his IPX function set. Experimentations yielded 100% delivery rate for over 100'000 packet transmission. To find out reasonable limit, 4 16MHz 386SX computers were used to continuously send and receive 512-byte packets. There were no loss of packets until they reached about 70 packets/second/node (280 packets/second, overall).
Before going into details, lets look at core IPX function set and how they should be treated. First thing to do is to find out the presence of an IPX driver and get it's entry vector. The code example was shown on
mov bx, 9 ; BX = IPX Get Internetwork Address mov ax, ds ; ES:SI -> structure to fill with the network mov es, ax ; and node IDs mov si, offset reply_buffer call dword ptr IPX_Vector
This function will always return good status (0) in AX. The buffer to by ES:SI will be filled with 0-3 (Network ID of the application's computer) or 4-9 (Node ID of the application's side).
The application would need to open a socket ID in order to communicate with the other peers. The following code would open a socket ID:
mov bx, 0 ; BX = IPX Open Socket command mov dx, socket ; DX = socket ID to open mov al, life_time ; AL = socket longevity flag call dword ptr IPX_Vector
This function returns the socket number that was opened in DX and a completion status in AL. Valid returns from this function are 00h (Socket opened), 0FFh (Socket previously opened, no problem), and 0FEh (Socket table is full, bummer).
The socket longevity flag informs XPX of how long the application intends to keep the socket open. A 0 indicates that the socket will be closed explicitly by the application or XPX should close it when the application exits back to MS-DOS. A 0FFh tells XPX to close the socket only when explicitly told to do so by the application. A 0FFh life allows the application to TSR and still maintain an open socket ID.
When the application has opened a socket ID, it must setup listeners and/or talkers. To create a listener, the application must first create an ECB and fill in the fields that deal with listening. Next, the application needs an IPX header structure and a buffer in which the received data will be placed. The fragment descriptor(s) of the ECB should point to the IPX header and the destination data buffer. If the IPX header and data buffer are one contiguous memory block, only one fragment descriptor is required. When the ECB, IPX header and data buffer are setup, a far pointer to the ECB is sent to XPX. The ECB sits with XPX until a packet comes in from the network that matches the ECB's socket ID. When the received packet is transferred to the listeners structures, the In Use Flag is set to 0 and the Completion Code field is set appropriately. If an ESR was provided by the ECB, the ESR is called by XPX. The ECB is then free to be used by the application.
To send an ECB off to the listening post, the following code fragment could be used:
mov bx, 4 ; BX = IPX Listen For Packet les si, offset ECB_Packet ; ES:SI -> ECB for the packet call dword ptr IPX_Vector
The function returns immediately with the status in AL. A return of 0 indicates that the ECB has been placed in a listening queue, waiting for the next incoming packet. A return of 0FFh indicates that the socket ID set within the ECB has not been opened.
The application should either poll the In Use Flag field for completion code or wait for the ESR to be called by XPX.
The application can send any number of listening packets to XPX. If a packet is received by XPX and no listener is ready to receive it, the packet and its data are lost. If a packet is lost, IPX does not inform either the destination or the source node of this condition. IPX does not guarantee delivery, SPX does.
The requirements for sending a packet is similar to setting up a listening packet. The ECB, IPX header and data buffer are set up with pertinent information. XPX can then be called with the following code fragment:
mov bx, 3 ; BX = IPX Send Packet command les si, offset ECB_Packet ; ES:SI -> ECB for the packet call dword ptr IPX_Vector
The function returns immediately, but has no status in AL. As with listening, the application should poll the In Use Flag/Completion Code fields for any result of the transmission.
Using IPX, if the transmitted packet does not reach its target, the source node is not informed. SPX on the other hand has methods to acknowledge packet arrival.
The Immediate Address field of the ECB needs to be filled for talking packets. If the target node is on the same network as the source node, the Immediate Address is merely the destination's node ID. If multiple networks are connected, the application needs to query XPX as to what the Immediate Address is for a full node and network ID. That query is made with the following code fragment:
mov bx, 2 ; BX = IPX Get Local Target command mov ax, ds ; ES = data segment of the request and mov es, ax ; reply buffer mov si, offset request_buffer mov di, offset reply_buffer call dword ptr IPX_Vector
On entry ES:SI should point to the following structure 0-3 (Network ID of the destination node), 4-9 (Node ID of the destination node), or 10-11 (Socket ID of the destination node.
This function returns the status of the operation in AL. A status of 0 indicates that the network and node were found. A status of 0FAh indicates that xPX could not find a network path to the node. If the local target was found, CX will contain an estimate of the number of system ticks (1/18th of a second) that a packet would take to arrive at the destination node's socket. If the node was found, the six byte buffer pointed to by ES:DI will be filled with the Immediate Address of the target node. If the node is on another network, the local address is the node ID of the bridge to the other network.
When the application has completed its transmissions and receptions, it should disconnect from its target node(s), cancel any active events and close any open socket IDs. Informing peers and closing socket IDs are not as important as canceling any outstanding events. If an ECB is still in xPX's queue and the associated ESR vector is not NULL, xPX will still call that ESR without regard to whether the program is still operating. A call to an ESR of a program that has terminated will probably hang the computer system. If the peer is not informed of closure, packets will be ignored, but no catastrophic failure will occur. If the socket ID's longevity flag was 0 (eg. a mere mortal), the socket will be closed automatically when the program terminates.
The following code fragment will cancel an IPX event:
mov bx, 6 ; BX = IPX Cancel Event command les si, ECB ; ES:SI -> ECB to cancel call dword ptr IPX_Vector
Status is returned in AL. If AL is 0, the ECB was canceled successfully and an 0FCh is placed in the Completion Code field of the ECB. If AL is 0FFh, the ECB was not scheduled for use. If AL is 0F9h, the ECB cannot be canceled - possibly because it is at the moment of transfer. In most instances, the return status from a Cancel Event command is immaterial
A canceled ECB does not call its associated ESR.
The following code fragment can be used to disconnect from a target address:
mov bx, 0Bh ; BX = IPX Disconnect From Target cmd mov ax, ds ; ES:SI -> full address of target mov es, ax mov si, offset address call dword ptr IPX_Vector
On entry ES:SI should point to the following structure:
|0 - 3||Network ID of the target|
|4 - 9||Node ID of the target|
|10-11||Socket ID of the target|
No status is returned from this function.
Finally, in order to explicitly close a socket ID, the following code fragment can be used:
mov bx, 1 ; BX = IPX Close Socket command mov dx, socket_ID ; DX = socket ID to close call dword ptr IPX_Vector
There is no status returned by this function. If the socket was never open, IPX does not care. If the socket was open, all events that were associated with the socket by previous calls will be canceled. The closure of a socket ID and its associated events does not trigger a call to any ESRs.
IPX is a high performance protocol available on Novell NetWare networks. IPX adds a level of complexity and uncertanty to peer-to-peer communications, but can be fashioned into a useful resource. SPX is slightly slower, but guarantees data delivery.
|[My Home Page] [Library] [Net]||Last Updated:|