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.
1 |
2 |
3 |
4 |
5 |
6 |
1 |
2 |
3 |
4 |
5 |
6 |
1 |
2 |
--------data--------- |
1 |
2 |
3 |
4 |
|-----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.