How to program IPX on Netware systems Written By Kenneth Johnson July 29th 1996 Updated Dec 5th 1997 Table of contents Section 0. General Info Section 1. Network addresses and socket numbers Section 2. Data types and packet format. Section 3. The information in Section 2 in Pascal record types. Section 4. The routines. Section 5. Program flow Section 6. Common questions. Section 7. Other stuff. Section 8. About Kenneth Johnson. Section 9. Future works. Hello! I wrote this document to give programmers a basic understanding of the use of IPX, and what it can do. The following information may work well, or may not work at all. If I didn't think it would work then I probably wouldn't write this. I can't test my IPX stuff because I am currently not on a network. But it worked ok when I was in school so I am assuming it works now. If you are a fairly good programmer, and understand networks and interrupts then you should not have much trouble writing your own routines from this text. You should have a good idea of record types, and very basic assembly language. I've included the code you need for the routines. Another thing I should explain is that some data fields have (hi-low) beside them. That means that the most significant byte should be switched with the least significant byte. If the variable X must be switched in this manner it would look like this (in pascal): X := swap(X); Section 0. General info What does IPX stand for? IPX stands for (I)nternetwork (P)acket E(x)change Protocol, it is used by networks to send and receive information quickly to and from the server and other computers. Why use IPX and not SPX? The reason I have wrote IPX routines and not SPX is that IPX is faster. The problem with IPX is that it just sends the packet to the destination computer. It may not necessarily get there, and if you send multiple packets sequentially, the third packet may get there before the first, whereas SPX (S)equenced (P)acket E(x)change makes sure that they are delivered in the proper order. These problems are easily overcome. Section 1. Network addresses. þ What is an address þ A network address is comprised of a Network Number, and a Node Number within a Network. This is how Netware recognizes you on the network. The format of a netware address: 1. Network number (hi-low) : =long integer= 2. node address (hi-low) : =array of six bytes= 3. Socket number (hi-low) : =word= A socket is a "highway" or connection that this information should be sent on. You can pick any number for the socket, but Netware has some reserved. Also games like DOOM send packets via IPX during a net game. DOOM sends on socket number $869C. If you really want to ruin a net DOOM game, start sending packets on this socket. (And see how unpopular you'll become!) Getting your address and others is fairly simple. Section 2. Data types and packet format þ Inside IPX þ I will now discuss some of the parts of IPX. This is the hardest part in understanding IPX. To send information from one computer to another, or to all computers that are on the network IPX uses PACKETS. Packets are "pieces" of information. To send a packet, you must set up something called an ECB or (E)vent (C)ontrol (B)lock with an IPX header and datagram. This is what IPX uses as information to send data. What are all these things? Keep reading and I'll explain. The numbers beside each data field correspond to the information that follows the format listing. þ Format of IPX header þ (1) Checksum (hi-low) : =word= (2) Length of packet (hi-low) : =word= (3) Transport control : =byte= (4) packet type (hi-low) : =byte= (5) Destination address : (address record type discussed above) (6) The source address : (address record type discussed above) 1. Just set this to zero (don't worry about the hi-low stuff) 2. The length of the packet you are going to send. Just Sizeof(IPXHEADER). (the size of the ipx header) 3. Just set to zero. 4. The type of packet you are going to send. I just set to zero, but different values have different uses. You can also use four. 5. The filled destination address, the computer(s) you want to send this packet to. You must also fill in the socket number, a value you choose for the socket that you are sending on, and the other computer(s) are listening on. 6. The filled source address (your address) with the socket to send on filled. þ Format of ECB þ Here's the format for an IPX Event Control Block. (1) Link : =long integer= (2) Pointer To Event Service Routine : =long integer= (3) In-Use-Flag : =byte= (4) Completion Code : =byte= (5) Socket Number (hi-low) : =word= (6) workspace : =four bytes= (7) driver workspace : =twelve bytes= (8) Immediate Local Node Address : =six bytes= (9) Fragment Count : =word= (10) Data Fragments : =array[1..2] of fragments= (see below) þ Format of a fragment record (Data Fragments) þ Format for the fragments of data found in the ECB. This does not necessarily have to be an array of two fragments, it could be more, but this is what I use and I can guarantee that it works. Pointer to Fragment : =pointer= Size of fragment : =word= Explanation of the ECB data fields: 1.When setting up an ECB I don't even bother with the "link". I'm not even sure about it's purpose. 2.You don't have to fill this field either. This is an address to the event service routine. When the ECB you have given to IPX has been handled, it calls this procedure. You don't really need it, but I guess it would make life easier. 3.The In-Use-Flag is used to determine whether or not IPX has gotten around to actually sending the data off to the other computer(s). If it has sent the packet then this is set as $0, if IPX did not send it, it will be $ff. The in-use-flag could have other values which will be discussed later. These two are the ones that are important right now. 4.If the completion code is 0h then IPX delivered the packet with no problems, if it is anything else then it did not send it. 5. The socket that you are going to send the packet on. 6. Just fill with zeros. 7. Just fill with zeros. 8. Your local node address. We will be looking at routines that get your network address (including node address) 9. Your fragment count. (In this document, we'll use 2) 10. You're information that you want IPX to send. It is contained in DATAGRAMS. Datagrams is the actual information you want to send, whether it's part of a file, for a chatting program, or any other type of format. A datagram can be maximum 546 bytes long. I assume that the minimum would be 1 or 0, but that doesn't really matter. I have the datagram as an array of bytes: Datagram : array[1..546] of byte; In the fragment pointer of TFRAG, you give it the datagram segment and offset and in the Size field you give it the size of the datagram, in this case 546 bytes. What is interesting about IPX is that instead of giving IPX variables to put information in, you give it their addresses, and IPX will take care of everything. In the Fragment array you must include the IPX header in the first fragment. So you copy the stuff in the IPX header into the datagram, and then your other info into the second datagram and IPX handles it. Section 3. The information in Section 2 in Pascal record types. Here's what I babbled on about in section two in Pascal Record Types. These are the actual things you send IPX so that it can work: type {The datagram record type} datagramRec = array[1..546] of Byte; {where you put the data you want to send} {IPX record type} tipxheader = record {IPX uses this to determine where } checksum : word; {it's going, from who and on} Len : word; {which socket} control : byte; packettype : byte; dest, source : tinternetworkaddress; end; {network address record type without socket number} networkaddressREC = record network : longint; nodeaddr : array[1..6] of byte; end; {network address record type with socket number} tinternetworkaddressREC = record {full network address and socket} network : longint; nodeaddr : array[1..6] of byte; socket : word; end; {the data fragment record type} tfrag = record {where the data pointers goes} FragPtr : pointer; Size : Word; end; {Event control block record type} Tecb = record {what you send to IPX} Link : pointer; ESR : pointer; inuse : byte; code : byte; socketnum : word; ipxworkspace : array[1..4] of byte; driver : array[1..12] of byte; localnode : array[1..6] of byte; fragcount : word; fragdata : array[1..2] of tfrag;{the first TFRAG should have} end; {the IPX header} Section 4. The routines Here's all the routines that you will be using to send packets, listen for packets, get your address and others. þ essential routines þ {------------------------------------------------------------------------} {sending a packet: ecbpointer is an ecb record type} procedure sendpacket(var ecbpointer);assembler; asm push bp {if this isn't saved then the program will crash} mov bx,0003h les si,ecbpointer int $7a pop bp end; {------------------------------------------------------------------------} {listen for a packet: ecb is an ecb record type} procedure listenforpacket(Var ecb);assembler; asm push bp Push si mov bx,$0004 les si,ecb int $7a mov [byte ptr result],al Pop si pop bp end; {------------------------------------------------------------------------} {check to see if IPX is installed} function IPXinstalled : Boolean; var stat : byte; begin asm mov ax,7a00h int $2f mov stat,al end; if stat = $ff then ipxinstalled := true else ipxinstalled := false; end; {------------------------------------------------------------------------} {closes an open socket} procedure closesocket(socketnum : word); var sock : word; begin sock := swap(Socketnum); asm mov bx,0001h mov dx,sock int $7a end; end; {------------------------------------------------------------------------} {Opens a socket, (SocketType is the type of socket you want to open: 00h = open until close or terminate FFh = open until close} procedure opensocket(socketnum : word); var i : word; begin i := swap(socketnum); asm mov bx,0000h mov dx,i mov al,sockettype int $7a mov [byte ptr result],al end; end; {------------------------------------------------------------------------} {tells IPX that the program is idle and it can do some work} Procedure RelinquishControl;assembler; asm mov bx,$000a int $7a end; {------------------------------------------------------------------------} þ Other routines þ {------------------------------------------------------------------------} {some of these routines use this.} procedure Callint(Ahreg : byte;Var bufferin,bufferout; Var error : Byte);assembler; asm push ds mov ah,ahreg lds si,bufferin les di,bufferout int 21h mov [byte ptr error],al pop ds end; {------------------------------------------------------------------------} procedure getinternetaddress(Connect : byte; Var net : tinternetworkaddressrec); type request = record len : word; sub : byte; c: byte; end; reply = record len : word; n : tinternetworkaddressREC; end; var bufferin : request; bufferout : reply; i : byte; begin fillchar(Bufferout,sizeof(Bufferout),0); bufferin.len := 2; bufferin.sub := $13; bufferin.c := connect; bufferout.len := sizeof(Bufferout)-2; callint($e3,bufferin,bufferout,error); net.networkaddr := bufferout.n.networkaddr; for i := 1 to 6 do net.nodeaddr[i] := bufferout.n.nodeaddr[i]; net.socket := bufferout.n.socket; end; {------------------------------------------------------------------------} procedure myaddrASM(var bufferout);assembler; asm mov bx,0009h les si,bufferout int $7a end; {------------------------------------------------------------------------} {get your own address} Procedure myaddress(Var n : tinternetworkaddressrec); type reply = record net : longint; node : array[1..6] of byte; end; var bufferout : reply; begin fillchar(Bufferout,sizeof(Bufferout),0); myaddrasm(Bufferout); move(bufferout.node,n.nodeaddr,sizeof(N.nodeaddr)); n.networkaddr := bufferout.net; end; {------------------------------------------------------------------------} Other routines that you should write are things that set up the ECB for sending and receiving packets. I'll show you what you should be doing for those routines. Section 5. Program flow In this section I will show you the basic program flow for sending and receiving an IPX packet. For the socket numbers I just use the same socket for sending/receiving data. A better way to do this would be to have one socket for receiving and one for sending packets. (that gets a bit complicated) SEND PACKET The program flow is this: variables used: ecb : tecb; IPX : tipxheader; D : datagram; 1. Open socket your going to send on. 2. Setup your IPX header. (The fields listed here MUST be filled) ==> Get destination address & put it in IPX.dest If you want to send a broadcast packet (to all computers) then just leave the ipx.dest filled with zeros and the ipx.dest.nodeaddr filled with $FF. ==> IPX.checksum must equal zero (hi-low) ==> IPX.len = Sizeof(ipx) (size of the ipx header) (hi-low) ==> IPX.dest.socket equals socket number that destination (hi-low) computer is using to receive data. (I think) ==> the rest of the IPX header should be all zeros. The fields with "hi-low" must have their bytes SWAPED. 3. Put whatever data you want to send into the datagram. as an example: S := 'This is a test!' Move(S,D,Sizeof(S)); Now the datagram (D) contains the string "This is a test!". When we set up the ECB and actually send the packet, this is what we would be sending. You can put ANY type of data in the datagram. 4. Setup your Event Control Block (ECB) These fields must be filled: ==> ecb.socketnum equals socket your sending on (hi-low) ==> Fragcount equals the amount of data fragments you have. In this document we are using two. ==> Fragdata[1].size := sizeof(IPX) ==> Fragdata[1].fragptr := @(IPX) (pointer to IPX header) N.B The first fragment must always contain the ipx header data. ==> fragdata[2].size := sizeof(D) ==> fragdata[2].fragptr := @(D) The second fragment contains the datagram. (the stuff your actually sending) 5. Send the packet. You are now ready to send the packet on it's speedy little voyage through the network to reach it's destination computer(s). This is just a call to the routine Sendpacket. You give the routine the filled ecb and it takes care of the rest. SendPakcet(ecb); repeat relinquishcontrol; until ecb.inuse = $00; What is that "relinquishcontrol" stuff? That's a routine to tell IPX that it can go ahead and send the packet. Usually you repeat that routine until the ecb.inuse flag is set to zero, which means that the packet was sent. If ECB.code is something other than zero then the packet did not send successfully. That's how an IPX packet is sent. You should probably write some procedures that will set up the IPX header and ECB header by just sending up a connection number and the header. In my IPXSETUP procedure, a connection number that is zero tells it to set up the header's dest address as a broadcast packet. Note that setting up ipx for receiving a packet is a bit different. (A lot easier!) RECEIVE PACKET (same variables used) Basic program flow: 1. Fill IPX header with zeros 2. Setup ECB. ==> ecb.socketnum := swap(listensocket) ==> ecb.fragcount = 2 ==> fragdata[1].size := sizeof(ipx); fragdata[1].fragprt := @ipx; ==> fragdata[2].size := sizeof(d); ==> fragdata[2].fragptr := @d; 3. Wait for a packet to arrive. Listenforpacket(ecB); repeat relinquishcontrol; until ecb.inuse = 00; 4. data is now in Datagram Notice in step 3 that the routine "listenforpacket" does not wait for a packet to be send. Rather, you call that routine and then repeat "relinquishcontrol" until a packet does arrive. When a packet is received then IPX puts the packet information into the pointers specified. In this case "IPX" and "D". Now you know who send the packet (you have their address) and what they sent. Section 6. Common questions Q. Why isn't my program working? A. Could be a number of reasons. Step through your program and see exactly what you are putting in the IPX header and ECB. (If your using Pascal press ctrl-f4) Also, if you are setting up an ECB or header, any fields that do not have to be filled with a value should be set to zero. Make sure the socket you are sending on is open. (check the error code) If you e-mail me with a question then it will probably end up in this section. Section 7. Other stuff Here's a list of the inuse flag codes,completion codes and values for IPX packet types: Values for ECB in-use flag: 00h available E0h AES temporary F6h \special IPX/SPX processing for v3.02+ F7h / F8h IPX in critical section F9h SPX listening FAh processing FBh holding FCh AES waiting FDh AES counting down delay time FEh awaiting packet reception FFh senfing packet Values for ECB completion code: 00h success ECh remote terminated connection without acknowledging packet EDh abnormal connection termination EEh invalid connection ID EFh SPX connection table full F9h event should not be canceled FAh cannot establish connection with specified destination FCh cancelled FDh malformed packet FEh packet undeliverable FFh physical error Values for IPX packet type: 00h unknown packet type 01h routing information packet 02h echo packet 03h error packet 04h packet exchange packet 05h SPX packet 11h Netware Core Protocol 14h Propagated Packet (for Netware), NetBIOS name packet 15h-1Eh experimental protocols Section 8 About Ken Johnson Hello! My name is Ken Johnson and I am a student at Carleton University in Ottawa Ont. Canada. Currently I am in my first year of a Computer Mathmatics degree. You can visit my website at: wabakimi.carleton.ca/~kjohnso3 or email me at kjohnso3@chat.carleton.ca Section 9 Future works Hopefully somemore TCP/IP stuff but I haven't had the time to do so. If you are interested in IPX/SPX code for C++ then send me some mail.