Ethernet Programming


Most people who surf the Internet don't use PPP, instead they use Ethernet for all their needs. PPP has its advantages and it's disadvantages. The biggest advantage of PPP is that it's cheap. All that you need is a modem and a phone line and you're in business. The problem with PPP is that since it works over normal telephone lines with modems at either end, it is extremely slow and unreliable. We don't mean to imply that the PPP protocol is unreliable, rather it is the telephones lines which are slow and undependable. There will always be a some loss of packets from you to the PPP server. Most of the time, the Internet by itself is very reliable as most of the lines are made of fiber. The weak link in the chain is the telephone wire, which forms the connection between you and your ISP.

When a large number of people want to surf the 'net, the employees of a large firm for example, you really can't use PPP to serve them all. Since you don't want to have all the lines in the building tied up by modems, most organisations prefer to use a LAN to connect themselves to the Internet and to help organise their business on the side. The most popular way to implement a Large Area Network is to use Ethernet.

In Ethernet there is only one cable that passes through the NIC's (Network Interface Cards) of all the computers on the LAN. There is no master and no slave, no server and no client (at the data link layer). All the machines have equal priority. Each card on each and every computer has it's own 48 bit Ethernet address which, like an IP address, is unique. To send a TCP/IP packet over an Ethernet LAN is simplicity itself. All you have to do is place the 40 byte TCP/IP packet in-between the Ethernet frame as shown below.

12345612345612--------data---------1234
|-----Destn---------| |-------Source------| |Type| | --------TCP/IP----------------| | ---FCS-----|

The Destination Ethernet address is the address of the computer this packet has to reach. The source Ethernet address is the address of our computer. The type is 0x08 0x00 which mean the data carried is IP encapsulated. The FCS is the frame check sequence or the checksum and it's calculated by the card, not you. So assuming you are working with Windows Sockets and you have a dial-up connection (using PPP), then the WinSock will generate the relevant Ethernet headers and the TCP/IP packets remain the same. If PPP is being used, the WinSock will create PPP headers.

Now with this information up our collective sleeves, lets get on with writing the simplest Ethernet program we could find. But first, a hardware and software checklist. You must have a computer, an Ethernet card and an Ethernet LAN. You don't need to be on the Internet.

In Ethernet programming, you don't need to talk directly to the NIC. Since most cards differ from each other, we need some driver software to be loaded in memory to provide you with a standard interface. So, FTP Software Inc. came out with what is called the Packet Driver Specification. The packet driver is just simple a TSR, which is first loaded into memory and from then on, we communicate with TSR which then talks to the card. The advantage of this approach is that you can have any card from any company, just as long as the TSR supports that card. This is definitely much easier than reading/writing hardware ports.

We have used ne1000 (the packet driver) on both 8 and 16 bit cards without any problems. Another thing to keep in mind about these programs is that they only work under DOS. Since we're dealing with TSR's here, I'll assume you know how to write one. Most Packet drivers capture interrupts between 60h and 80h. So what we do is use getvect() to ferret out the location of the packetdriver in memory and to double check, search for the string PKT DRVR at intervals of 3.

The reason we search in intervals of 3 is because the first instruction code in the driver is a jmp instruction and after that do we get the packet driver signature. To find the string signature, we use strncmp() and check for the above mentioned string. Since we have no packet driver loaded at present, we have to type in the following.

c:\>ne1000 0x60 3 0x300 {Enter}

This tells ne1000 to capture INT 0x60, with the IRQ set to 3 and 0x300 as the hardware port. If you have a 16 bit card, run ne2000. If this doesn't work then you have some wacky non-standard card in your box. Contact your network administrator.

Program 1

#include <stdio.h>
#include <dos.h>
main()
{
char *t="PKT DRVR";
int ok=1;
char *p;
unsigned char i=0x60;
while ( (ok) && (i<=0x80))
{
p=(char *)getvect(i);
p = p+3;
if ( strncmp(p,t,8) == 0)
ok=0;
else
i++;
}
if ( ok)
printf("No Packet found\n");
else
printf("Packet Found...%d\n",i);
}

This simple program simply searches your computers memory for the packet driver and informs you if it finds one.

On to number two.

Program 2

#include <stdio.h>
#include <dos.h>
union REGS a,b;
struct SREGS s;
char *p;
main()
{
a.h.ah=1;
a.h.al=255;
int86x(0x60,&a,&b,&s);
printf("Version %d\n",b.x.bx);
printf("Driver Class %d\n",b.h.ch);
printf("Type %d\n",b.x.dx);
printf("Number %d\n",b.h.cl);
printf("Basic functionality %d\n",b.h.al);
p=MK_FP(s.ds,b.x.si);
printf("Name %s\n",p);
}

These packet drivers can work with many conflicting standards. For example the DIX (Digital, Intel and Xerox) Internet, The IEEE 802.3, ARCnet, etc. So FTP software invented a label called class where 1 stands for DIX, 11 for IEEE 802.3 and so on..

Burnt into the card is a unique number which is called the type. These type numbers are handed to you by FTP and if the class and the type are the same, then a third label called the number will help distinguish between the two. In addition to this, every card also has a version number and a name. Now we've written a very small program, where we put 1 in the AH register and 255 in the AL register and call INT 60h. The registers will now give you the class, type, number, version number and the name of the packet driver.

One of the registers (b.h.ch) will return the class. The value is 1 which means that our card supports the DIX Ethernet standard, the most common form of Ethernet. We'll be concentrating on DIX for this very reason. The type (b.x.dx) is the manufacturer's number within the main class. FTP has given you these numbers, and if the type and the class are the same, then you have the number (b.h.cl) that differentiates between the two. In our case if the class and the type are unique.

In other words , the type and the class really don't matter. The version number (b.x.bx) is really not important. All of this has more to do with the packet driver than the card.

Then in the registers (s.ds,b.x.si), we have the name of the packet driver, which in our case it is ne1000. The name depends on whether you are using an 8-bit or 16-bit card as mentioned before.

Now, as we've already mentioned, every Ethernet card has a 48-bit Ethernet Address stored in it. In these 48 bits, the first 24-bits are given to the Ethernet manufacturer, who then decides what to do with the last 24 bits. The first 24-bits are called the Organisational Unique Identifier. If you really want to go into details, you can check out the first two which are pregnant with meaning. What you have to understand is that every Ethernet card you buy has an Ethernet Address hard-coded into it. Because 2 raised to 48 is an extremely large number, as of today there is be no shortage of Ethernet Address, quite unlike IP addresses which we are rapidly running out of.

Program 3

#include <stdio.h>
#include <dos.h>
union REGS a,b;
struct SREGS s;
char *p;
unsigned char aa[6];
main()
{
a.h.ah=6;
a.x.cx=6;
a.x.di=FP_OFF(aa);
s.es=FP_SEG(aa);
int86x(0x60,&a,&b,&s);
printf("Ethernet Address  %d\n",b.x.cx);
printf("%x:%x:%x:%x:%x:%x \n",aa[0],aa[1],aa[2],aa[3],aa[4],aa[5]);
}
This program displays your Ethernet address.

Now the question is that we require from the packet driver, and not the card, a handle, A handle is a number. If you want to send and receive data, you require a handle so that the packet driver knows who is asking for the information and where has the information has come from. This is because your packet driver can receive multiple packets, who in turn will talk to the card

In an Ethernet packet, the first 12 bytes are the destination and source Ethernet address. The next two are what we call the accesstype or the protocol. All IP packets will have the protocol 0x08 0x00. ARP Packets will have the type set to 0x08 0x06. So whenever we are talking to the packet driver, we need to tell it that we are interested in these specific type of packets.

The length of the type must be 0 and not 2, cause you are asking for the all packets. The callback function or the function to be called must be given . Once the values are placed in the relevant registers and the interrrupt generated, the bx register will hold the number which is called the handle.

Now, all that we need to do is send the packets. For this you will need 2 machines, Run the identical program on both, you send the packet with a certain packet type say 0x01 0x01, the other machine will receive the packet as you send it.

Bear in mind that 60 bytes is the minimum size because the entire Ethernet packet should be 64 bytes minimum, the 4 bytes at the end are the checksum. If you want to write a packet sniffer, all you need to do is make the typelen 0 and your card will now pick up each and every packet that passes on the cable. You can display them and see what everybody is doing on your Ethernet network.

Program 4

#include <dos.h>
#include <stdio.h>
FILE *fp;
void abc(unsigned char p)
{
fprintf(fp,"..%x..%d..%c\n",p,p,p);
}
union REGS a,b;
struct SREGS s;
unsigned char ad[6];
unsigned char c[2];
int handle,i,y,ii;
unsigned char d[60],e[600];
void interrupt zzz(bp, di, si, ds, es, dx, cx, bx, ax, ip, cs, flags)
unsigned short bp, di, si, ds, es, dx, cx, bx, ax, ip, cs, flags;
{
printf("Reciever ax=%d Packet Size %d\n",ax,cx);
if ( ax == 0)
{
es=FP_SEG(e);
di=FP_OFF(e);
ii=cx;
}
if ( ax==1)
{
for ( i=0;i<=ii;i++)
abc(e[i]);
}
}
main()
{
fp=fopen("c:\\z.txt","a+");
segread(&s);
a.h.ah=1;
a.h.al=255;
int86x(0x60,&a,&b,&s);
printf("Carry Flag Driver Info %d\n",b.x.cflag);
printf("Class %d Type %d\n",b.h.ch,b.x.dx);

a.h.ah=6;
a.x.cx=6;
s.es=FP_SEG(ad);
a.x.di=FP_OFF(ad);
int86x(0x60,&a,&b,&s);
printf("Carry Flag Ethernet Address %d\n",b.x.cflag);
printf("Ethernet Address %x:%x:%x:%x:%x:%x\n",ad[0],ad[1],ad[2],ad[3],ad[4],ad[5]);
a.h.al=1;
a.x.bx=0xff;
a.h.dl=0;
a.x.cx=0;
a.h.ah=2;
s.es=FP_SEG(zzz);
a.x.di=FP_OFF(zzz);
c[0]=0xff;c[1]=0xff;
s.ds=FP_SEG(c);
a.x.si=FP_OFF(c);
int86x(0x60,&a,&b,&s);
printf("Carry Flag Access Type %d\n",b.x.cflag);
printf("Handle %d\n",b.x.ax);
handle=b.x.ax;
a.h.ah=21;
a.x.bx=handle;
int86x(0x60,&a,&b,&s);
printf("Carry Flag Get Recieve Mode %d\n",b.x.cflag);
printf("Recieve Mode %d\n",b.x.ax);
a.h.ah=20;
a.x.bx=handle;
a.x.cx=6;
int86x(0x60,&a,&b,&s);
printf("Carry Flag Set Recieve Mode %d\n",b.x.cflag);
getch();
getch();
a.h.ah=3;
a.x.bx=handle;
int86x(0x60,&a,&b,&s);
printf("Carry Flag Release Type %d\n",b.x.cflag);
printf("All Over\n");
fclose(fp);
}

An Ethernet is just a simple cable connection, a way to communicate and transfer information across short distances. In Ethernet, there is no master and no slave; no computer is more important than the other. Even the file server carries the same priority as the lowliest diskless workstation on the network.

When a packet is sent across the network, it will travel from one end of the wire to the other end and every NIC along the way will pick up the packet to check it's destination address. If the address on the packet matches the address on the NIC, then it will make a copy of the packet and then send it on to the next NIX down the line. If the destination address on a packet is 0xff 0xff 0xff 0xff 0xff 0xff, which means it is a broadcast message, then every NIC on the network pick it up and process it.

Ethernet LANs are also called CSMA/CD networks. The CS stands for Carrier Detect which means that the cards will check whether information is already flowing on the wire before attempting to send data across it. If the line is occupied, they will wait for a break in communication before taking over the cable. MA is Multiple Access, which means that many cards and computers can be connected to one cable at the same time. CD stands for Collision Detection. If two Ethernet cards attempt to use the cable at the same time, then their data packets will 'collide' and confuse the cards. So whenever two cards discover that they are interfering with each other, they both back off and wait for a random interval of time. The card which waited for less time starts sending first and usually there is no clash this time. To reduce collisions, the cable should be of manageable length and the packet size should be small and fixed. In our case it is 60 plus 4 bytes of the checksum which adds up to a total of 64 bytes. The checksum is added by the card and not by us or the packet driver and this makes the operation extremely fast.

An Ethernet packet 'Sniffer'

Here's a complete sniffer program along with a systematic, step by step explanation of how exactly it all works. It's really not that difficult.

sniffer.c

#include <dos.h>
#include <stdio.h>
FILE *fp;
void abc(unsigned char p) {
fprintf(fp,"..%x..%d..%c\n",p,p,p);
}
union REGS a,b;
struct SREGS s;
unsigned char ad[6];
unsigned char c[2];
int handle,i,y,ii;
unsigned char d[60],e[600];

void interrupt zzz(bp, di, si, ds, es, dx, cx, bx, ax, ip, cs, flags)
unsigned short bp, di, si, ds, es, dx, cx, bx, ax, ip, cs, flags;
{
printf("Reciever ax=%d Packet Size %d\n",ax,cx);
if ( ax == 0) {
es=FP_SEG(e);
di=FP_OFF(e);
ii=cx;
	}
if ( ax==1) {
for ( i=0;i<=ii;i++)
abc(e[i]);
}
}

main( ) {
fp=fopen("c:\\z.txt","a+");
segread(&s);
a.h.ah=1;
a.h.al=255;
int86x(0x60,&a,&b,&s);
printf("Carry Flag Driver Info %d\n",b.x.cflag);			
printf("Class %d Type %d\n",b.h.ch,b.x.dx);
a.h.ah=6;
a.x.cx=6;
s.es=FP_SEG(ad);
a.x.di=FP_OFF(ad);
int86x(0x60,&a,&b,&s);
printf("Carry Flag Ethernet Address %d\n",b.x.cflag);		
printf("Ethernet Address is %x:%x:%x:%x:%x:%x\n", ad[0], ad[1], ad[2], ad[3], ad[4], ad[5] );
a.h.al=1;					
a.x.bx=-1;
a.h.dl=0;
a.x.cx=0;
a.h.ah=2;				
s.es=FP_SEG(zzz);
a.x.di=FP_OFF(zzz);
c[0]=0xff;				
c[1]=0xff;              			
s.ds=FP_SEG(c);
a.x.si=FP_OFF(c);
int86x(0x60,&a,&b,&s);		
printf("Carry Flag Access Type %d\n",b.x.cflag);
printf("Handle %d\n",b.x.ax);
handle=b.x.ax;			
a.h.ah=21;
a.x.bx=handle;
int86x(0x60,&a,&b,&s);
printf("Carry Flag Get Recieve Mode %d\n",b.x.cflag);
printf("Recieve Mode %d\n",b.x.ax);
a.h.ah=20;		
a.x.bx=handle;
a.x.cx=6;		
int86x(0x60,&a,&b,&s);
printf("Carry Flag Set Recieve Mode %d\n",b.x.cflag);
getch();
getch();                    	
a.h.ah=3;		
a.x.bx=handle;
int86x(0x60,&a,&b,&s);
printf("Carry Flag Release Type %d\n",b.x.cflag);		
printf("All Over\n");
fclose(fp);
}

This is the sniffer program we were just discussing. Like a good snoop, it eavesdrops on all the data transfers on the LAN, even if they're not meant for you.

The first part of the program should be quite familiar to you by now. We're first displaying some driver information and then we're printing out the Ethernet address of the card we're working on. The only reason we've added all that old stuff is 'cause otherwise the program would seem too small!

It's only now that we reach the really juicy bits. We put a whole bunch of nonsensical values in various registers and then stuff the address of the interrupt function zzz() into the registers ES:DI. This function is our call-back i.e. whenever the Ethernet card has any data for us, the Packet Driver will call the function zzz(). Now because zzz() is an interrupt function, it's passed all the values in the registers as parameters. It can then read the interpret the values and act on them. Jump over to the function zzz() and check it out.

As you can see, when zzz() is called, it checks the state of the AX register. If AX is equal to 0, then that means that the packet driver has information to pass on. Our function obediently places the address of an array e in the register pair ES:DI and copies the value stored in CX into a variable. CX held the exact number of bytes about to be sent to us.

The function zzz() is called once again and this time the value in AX is 1. This means that its OK for our function to access the bytes now stored in the array e. In this way the packet driver passes information between us and the Ethernet card. The function abc() is used to write the incoming data to disk.

Before we call the interrupt and set all this up however, we have to first pass the packet driver the filter value. By cramming the value 0xff 0xff into the array c, we tell the Ethernet card to give us all that bytes that it receives. The address of the array is placed in the register pair DS:SI.

The interrupt is now called.

After calling the interrupt we check the value in the Carry Flag. If it's one, then an error occurred and we're supposed to check AH for the error value. If it's zero, then all's well.

We also print out the value of the handle stored in AX, just before storing it in the variable handle. So by calling the function we not only assigned zzz() as the call-back function, we also got ourselves a handle.

By placing 21 in the AH register and the handle in the BX register, we can check up on our default receive mode, which is then displayed.

The Receive Mode specifies whose packets we'll receive. By default, we're only supposed to get our packets. By placing 20 in the AH register and the handle in the BX register and by setting the CX register to the desired mode, we can change the Receive Mode. We're going to change our mode to 6, which means we want to see all the packets, regardless of their destination address. 'Sniffer Mode' in other words.

The two getch()'s after the interrupt call halt program execution until someone clicks a key twice.

Once control passes the two getch()'s, we call another service in interrupt 0x60 and close the handle. This is a bit like doing an fclose() and it's a nice, clean way to mop up after the program.


The above tutorial is a joint effort of

Mr. Vijay Mukhi
Ms. Sonal Kotecha
Mr. Arsalan Zaidi
Mr. Rajkumar Ganesan


Back to the main page


Vijay Mukhi's Computer Institute
VMCI, B-13, Everest Building, Tardeo, Mumbai 400 034, India
Tel : 91-22-496 4335 /6/7/8/9     Fax : 91-22-307 28 59
e-mail : vmukhi@giasbm01.vsnl.net.in
http://www.vijaymukhi.com