MODULE Network;
IMPORT SYSTEM, WSock32, Machine, KernelLog, Plugins, Kernel, Objects, Modules;
VAR
CONST
MaxLinkAdrSize* = 8;
MaxPacketSize* = 1600;
MaxNofBuffers = 10000;
TypePointToPoint* = 0;
TypeEthernet* = 1;
LinkNotLinked* = 0;
LinkLinked* = 1;
LinkUnknown* = 2;
ChecksumIP* = 0;
ChecksumUDP* = 1;
ChecksumTCP* = 2;
MaxLoopbackPacketsPerMS = 500;
TYPE
LinkAdr* = ARRAY MaxLinkAdrSize OF CHAR;
Buffer* = POINTER TO RECORD
data*: ARRAY MaxPacketSize OF CHAR;
ofs*: LONGINT;
len*: LONGINT;
l3ofs*: LONGINT;
l4ofs*: LONGINT;
src*: LinkAdr;
calcChecksum*: SET;
int*: LONGINT;
set*: SET;
next*, prev*: Buffer;
END;
ReceiverList = POINTER TO RECORD
next: ReceiverList;
type: LONGINT;
receiver: Receiver;
END;
SendSnifferList = POINTER TO RECORD
next: SendSnifferList;
sniffer: SendSniffer;
END;
RecvSnifferList = POINTER TO RECORD
next: RecvSnifferList;
sniffer: ReceiveSniffer;
END;
LinkDevice* = OBJECT (Plugins.Plugin)
VAR
type-: LONGINT;
local-: LinkAdr;
broadcast-: LinkAdr;
mtu-: LONGINT;
adrSize-: LONGINT;
sendCount-, recvCount-: HUGEINT;
calcChecksum-: SET;
recList: ReceiverList;
sendSnifferList: SendSnifferList;
recvSnifferList: RecvSnifferList;
item: ReceiverList;
sniffer: RecvSnifferList;
discard: BOOLEAN;
finalized: BOOLEAN;
upBufFirst, upBufLast: Buffer;
buf: Buffer;
timer: Kernel.MilliTimer;
packetCount: LONGINT;
PROCEDURE &Constr*(type, mtu, adrSize: LONGINT);
VAR res: LONGINT;
BEGIN
ASSERT((mtu >= 0) & (mtu <= MaxPacketSize));
ASSERT((adrSize >= 0) & (adrSize <= MaxLinkAdrSize));
IF type = TypeEthernet THEN
ASSERT(adrSize = 6);
END;
SELF.type := type;
SELF.mtu := mtu;
SELF.adrSize := adrSize;
SELF.sendCount := 0;
SELF.recvCount := 0;
SELF.calcChecksum := {};
recList := NIL;
upBufFirst := NIL;
Kernel.SetTimer(timer, 2);
packetCount := 0;
finalized := FALSE;
sendSnifferList := NIL;
recvSnifferList := NIL;
END Constr;
PROCEDURE Finalize*(connected: BOOLEAN);
BEGIN {EXCLUSIVE}
ASSERT(~finalized);
finalized := TRUE;
END Finalize;
PROCEDURE Linked*(): LONGINT;
BEGIN
RETURN LinkUnknown;
END Linked;
PROCEDURE Send*(dst: LinkAdr; type: LONGINT; VAR l3hdr, l4hdr, data: ARRAY OF CHAR; h3len, h4len, dofs, dlen: LONGINT; loopback: BOOLEAN);
VAR
sniffer: SendSnifferList;
discard: BOOLEAN;
BEGIN
ASSERT(~finalized);
discard := FALSE;
sniffer := sendSnifferList;
WHILE sniffer # NIL DO
discard := discard OR sniffer^.sniffer(SELF, dst, type, l3hdr, l4hdr, data, h3len, h4len, dofs, dlen);
sniffer := sniffer^.next;
END;
IF ~discard THEN
IF loopback THEN
Loopback(dst, type, l3hdr, l4hdr, data, h3len, h4len, dofs, dlen);
ELSE
DoSend(dst, type, l3hdr, l4hdr, data, h3len, h4len, dofs, dlen);
END;
INC(sendCount, dlen + h3len + h4len);
END;
END Send;
PROCEDURE DoSend*(dst: LinkAdr; type: LONGINT; VAR l3hdr, l4hdr, data: ARRAY OF CHAR; h3len, h4len, dofs, dlen: LONGINT);
BEGIN
HALT(301);
END DoSend;
PROCEDURE Loopback(dst: LinkAdr; type: LONGINT; VAR l3hdr, l4hdr, data: ARRAY OF CHAR; h3len, h4len, dofs, dlen: LONGINT);
VAR buf: Buffer;
BEGIN
IF packetCount >= MaxLoopbackPacketsPerMS THEN
WHILE ~Kernel.Expired(timer) DO
Objects.Yield();
END;
Kernel.SetTimer(timer, 2);
packetCount := 0;
END;
buf := GetNewBuffer();
IF buf # NIL THEN
buf.l3ofs := 0;
buf.l4ofs := 0;
buf.ofs := 0;
buf.len := 0;
buf.src := dst;
buf.calcChecksum := {ChecksumIP, ChecksumUDP, ChecksumTCP};
Copy(l3hdr, buf.data, 0, buf.len, h3len);
INC(buf.len, h3len);
Copy(l4hdr, buf.data, 0, buf.len, h4len);
INC(buf.len, h4len);
Copy(data, buf.data, dofs, buf.len, dlen);
INC(buf.len, dlen);
QueueBuffer(buf, type);
Machine.AtomicInc(packetCount)
ELSE
END
END Loopback;
PROCEDURE InstallReceiver*(type: LONGINT; r: Receiver);
VAR item: ReceiverList;
BEGIN {EXCLUSIVE}
ASSERT(~finalized);
ASSERT(r # NIL);
item := recList;
WHILE item # NIL DO
ASSERT(item^.type # type);
item := item^.next;
END;
NEW(item);
item^.type := type;
item^.receiver := r;
item^.next := recList;
recList := item;
END InstallReceiver;
PROCEDURE RemoveReceiver*(type: LONGINT);
VAR item: ReceiverList;
BEGIN {EXCLUSIVE}
ASSERT(~finalized);
IF recList = NIL THEN
ELSIF recList^.type = type THEN
recList := recList^.next;
ELSE
item := recList;
WHILE (item^.next # NIL) & (item^.next^.type # type) DO
item := item^.next;
END;
IF item^.next # NIL THEN
item^.next := item^.next^.next;
ELSE
END;
END;
END RemoveReceiver;
PROCEDURE InstallSendSniffer*(s: SendSniffer);
VAR item: SendSnifferList;
BEGIN {EXCLUSIVE}
ASSERT(~finalized);
item := sendSnifferList;
WHILE (item # NIL) & (item^.sniffer # s) DO
item := item^.next;
END;
IF item # NIL THEN
ELSE
NEW(item);
item^.sniffer := s;
item^.next := sendSnifferList;
sendSnifferList := item;
END;
END InstallSendSniffer;
PROCEDURE RemoveSendSniffer*(s: SendSniffer);
VAR item: SendSnifferList;
BEGIN {EXCLUSIVE}
ASSERT(~finalized);
IF sendSnifferList = NIL THEN
ELSIF sendSnifferList^.sniffer = s THEN
sendSnifferList := sendSnifferList^.next;
ELSE
item := sendSnifferList;
WHILE (item^.next # NIL) & (item^.next^.sniffer # s) DO
item := item^.next;
END;
IF item^.next # NIL THEN
item^.next := item^.next^.next;
ELSE
END;
END;
END RemoveSendSniffer;
PROCEDURE InstallReceiveSniffer*(s: ReceiveSniffer);
VAR item: RecvSnifferList;
BEGIN {EXCLUSIVE}
ASSERT(~finalized);
item := recvSnifferList;
WHILE (item # NIL) & (item^.sniffer # s) DO
item := item^.next;
END;
IF item # NIL THEN
ELSE
NEW(item);
item^.sniffer := s;
item^.next := recvSnifferList;
recvSnifferList := item;
END;
END InstallReceiveSniffer;
PROCEDURE RemoveReceiveSniffer*(s: ReceiveSniffer);
VAR item: RecvSnifferList;
BEGIN {EXCLUSIVE}
ASSERT(~finalized);
IF recvSnifferList = NIL THEN
ELSIF recvSnifferList^.sniffer = s THEN
recvSnifferList := recvSnifferList^.next;
ELSE
item := recvSnifferList;
WHILE (item^.next # NIL) & (item^.next^.sniffer # s) DO
item := item^.next;
END;
IF item^.next # NIL THEN
item^.next := item^.next^.next;
ELSE
END;
END;
END RemoveReceiveSniffer;
PROCEDURE QueueBuffer*(buf: Buffer; type: LONGINT);
BEGIN {EXCLUSIVE}
ASSERT(buf # NIL);
buf.int := type;
buf.next := NIL;
IF upBufFirst = NIL THEN
upBufFirst := buf;
ELSE
upBufLast.next := buf;
END;
upBufLast := buf;
END QueueBuffer;
BEGIN {ACTIVE, PRIORITY(Objects.High)}
(* can run concurrently with SetReceiver, QueueBuffer, InstallReceiverSniffer and RemoveReceiverSniffer *)
LOOP
BEGIN {EXCLUSIVE}
AWAIT((upBufFirst # NIL) OR finalized);
IF (upBufFirst = NIL) & finalized THEN
(* terminate process after all buffer upcalls are done *)
EXIT;
END;
buf := upBufFirst;
upBufFirst := upBufFirst.next;
END;
INC(recvCount, buf.len);
discard := FALSE;
sniffer := recvSnifferList;
WHILE sniffer # NIL DO
(* call sniffer *)
discard := discard OR sniffer^.sniffer(SELF, buf.int, buf);
sniffer := sniffer^.next;
END;
IF ~discard THEN
(* search for receiver *)
item := recList;
WHILE (item # NIL) & (item^.type # buf.int) DO
item := item^.next;
END;
discard := (item = NIL);
END;
IF discard THEN
(* discard packet and return buffer *)
ReturnBuffer(buf);
ELSE
(* do upcall *)
item^.receiver(SELF, item^.type, buf);
END;
END;
END LinkDevice;
TYPE
Receiver* = PROCEDURE {DELEGATE} (dev: LinkDevice; type: LONGINT; buffer: Buffer);
SendSniffer* = PROCEDURE {DELEGATE} (dev: LinkDevice; VAR dst: LinkAdr; VAR type: LONGINT; VAR l3hdr, l4hdr, data: ARRAY OF CHAR; VAR h3len, h4len, dofs, dlen: LONGINT): BOOLEAN;
ReceiveSniffer* = PROCEDURE {DELEGATE} (dev: LinkDevice; VAR type: LONGINT; buffer: Buffer): BOOLEAN;
VAR
registry*: Plugins.Registry;
nofBuf: LONGINT;
nofFreeBuf: LONGINT;
freeBufList: Buffer;
PROCEDURE GetNewBuffer*(): Buffer;
VAR item: Buffer;
BEGIN {EXCLUSIVE}
IF freeBufList # NIL THEN
item := freeBufList;
freeBufList := freeBufList.next;
Machine.AtomicAdd(nofFreeBuf, -1);
ELSIF nofBuf < MaxNofBuffers THEN
NEW(item);
Machine.AtomicInc(nofBuf);
ELSE
item := NIL;
END;
RETURN item;
END GetNewBuffer;
PROCEDURE ReturnBuffer*(buf: Buffer);
BEGIN {EXCLUSIVE}
ASSERT(buf # NIL);
buf.next := freeBufList;
freeBufList := buf;
Machine.AtomicInc(nofFreeBuf);
END ReturnBuffer;
PROCEDURE Finalize(p: Plugins.Plugin);
BEGIN
p(LinkDevice).Finalize(TRUE);
END Finalize;
PROCEDURE Equal*(VAR buf1, buf2: ARRAY OF CHAR; ofs1, ofs2, n: LONGINT): BOOLEAN;
BEGIN
WHILE (n > 0) & (buf1[ofs1] = buf2[ofs2]) DO INC(ofs1); INC(ofs2); DEC(n) END;
RETURN n <= 0
END Equal;
PROCEDURE Put4*(VAR buf: ARRAY OF CHAR; ofs, val: LONGINT);
CODE {SYSTEM.i386}
MOV EAX, [EBP+val]
MOV EBX, [EBP+ofs]
MOV ECX, [EBP+buf]
MOV [ECX+EBX], EAX
END Put4;
PROCEDURE Put2*(VAR buf: ARRAY OF CHAR; ofs, val: LONGINT);
CODE {SYSTEM.i386}
MOV EAX, [EBP+val]
MOV EBX, [EBP+ofs]
MOV ECX, [EBP+buf]
MOV [ECX+EBX], AX
END Put2;
PROCEDURE Get4*(VAR buf: ARRAY OF CHAR; ofs: LONGINT): LONGINT;
CODE {SYSTEM.i386}
MOV EBX, [EBP+ofs]
MOV ECX, [EBP+buf]
MOV EAX, [ECX+EBX]
END Get4;
PROCEDURE Get2*(VAR buf: ARRAY OF CHAR; ofs: LONGINT): LONGINT;
CODE {SYSTEM.i386}
MOV EBX, [EBP+ofs]
MOV ECX, [EBP+buf]
XOR EAX, EAX
MOV AX, [ECX+EBX]
END Get2;
PROCEDURE PutNet4*(VAR buf: ARRAY OF CHAR; ofs, val: LONGINT);
CODE {SYSTEM.i386}
MOV EAX, [EBP+val]
XCHG AL, AH
ROL EAX, 16
XCHG AL, AH
MOV EBX, [EBP+ofs]
MOV ECX, [EBP+buf]
MOV [ECX+EBX], EAX
END PutNet4;
PROCEDURE PutNet2*(VAR buf: ARRAY OF CHAR; ofs, val: LONGINT);
CODE {SYSTEM.i386}
MOV EAX, [EBP+val]
XCHG AL, AH
MOV EBX, [EBP+ofs]
MOV ECX, [EBP+buf]
MOV [ECX+EBX], AX
END PutNet2;
PROCEDURE GetNet4*(VAR buf: ARRAY OF CHAR; ofs: LONGINT): LONGINT;
CODE {SYSTEM.i386}
MOV EBX, [EBP+ofs]
MOV ECX, [EBP+buf]
MOV EAX, [ECX+EBX]
XCHG AL, AH
ROL EAX, 16
XCHG AL, AH
END GetNet4;
PROCEDURE GetNet2*(VAR buf: ARRAY OF CHAR; ofs: LONGINT): LONGINT;
CODE {SYSTEM.i386}
MOV EBX, [EBP+ofs]
MOV ECX, [EBP+buf]
XOR EAX, EAX
MOV AX, [ECX+EBX]
XCHG AL, AH
END GetNet2;
PROCEDURE LinkAdrToStr*(VAR adr: LinkAdr; size: LONGINT; VAR s: ARRAY OF CHAR);
VAR
i, j: LONGINT;
hex: ARRAY 17 OF CHAR;
BEGIN
ASSERT(LEN(s) >= size*3);
hex := "0123456789ABCDEF";
i := 0;
FOR j := 0 TO size-1 DO
s[i] := hex[ORD(adr[j]) DIV 10H MOD 10H]; INC(i);
s[i] := hex[ORD(adr[j]) MOD 10H]; INC(i);
IF j = size-1 THEN s[i] := 0X ELSE s[i] := ":" END;
INC(i);
END;
END LinkAdrToStr;
PROCEDURE OutLinkAdr*(VAR adr: LinkAdr; size: LONGINT);
VAR s: ARRAY MaxLinkAdrSize*3 OF CHAR;
BEGIN
LinkAdrToStr(adr, size, s);
KernelLog.String(s);
END OutLinkAdr;
PROCEDURE Copy*(VAR from, to: ARRAY OF CHAR; fofs, tofs, len: LONGINT);
VAR res: LONGINT;
BEGIN
IF len > 0 THEN
ASSERT((fofs+len <= LEN(from)) & (tofs+len <= LEN(to)));
SYSTEM.MOVE(SYSTEM.ADR(from[fofs]), SYSTEM.ADR(to[tofs]), len);
END;
END Copy;
PROCEDURE Cleanup;
BEGIN
registry.Enumerate(Finalize);
Plugins.main.Remove(registry);
WSock32.CleanUp;
END Cleanup;
BEGIN
nofBuf := 0;
nofFreeBuf := 0;
NEW(registry, "Network", "Network interface drivers");
WSock32.Startup;
Modules.InstallTermHandler(Cleanup);
END Network.
(*
History:
10.10.2003 mvt Complete redesign and additional implementation of buffer handling and upcall mechanism
17.10.2003 mvt Changed the way of initialization and finalization (now only Constr/Finalize)
21.10.2003 mvt Changed SetReceiver to InstallReceiver and RemoveReceiver
15.11.2003 mvt Changed buffering to work with EXCLUSIVE sections instead of using locking and a semaphore.
16.11.2003 mvt Added support for checksum calclulation by the device.
25.11.2003 mvt Added l3ofs and l4ofs to Buffer type.
17.12.2003 mvt Changed variable "linked" to method "Linked".
*)
(**
How to use the module:
The module is loaded as soon as it is used first. It needn't to be loaded explicitly at startup. It can also be unloaded an reloaded without reboot.
How to use a driver:
Network driver objects in Bluebottle are extensions of the Network.LinkDevice object. All loaded instances of network driver objects are registered in the registry Network.registry. To obtain access to a network driver, use the Get, Await or GetAll methods of this registry.
Example:
VAR dev: Network.LinkDevice;
dev := Network.registry.Get(""); (* if no name is specified, first device (or NIL) is returned *)
The Send method of LinkDevice is used to send a packet. The dst parameter specifies the link destination address (e.g., a 6-byte ethernet MAC address for an ethernet device). The type parameter specifies the link-layer protocol type (e.g. 800H when sending IP over ethernet). The source address of the packet is automatically generated by the device, if necessary.
For reasons of reducing buffer copying between network layers, the method allows 3 buffers to be passed:
The l3hdr, l4hdr, data, h3len, h4len, dofs and dlen fields specify 3 buffers:
One buffer for a layer 3 header, one for a layer 4 header and one for the payload of the packet. The buffers don't have to be filled like this. They are simply concatenated to one frame by the device driver. Therefore, each of them is allowed to be empty.
The layer 3 header is stored in: l3hdr[0..h3len-1]
The layer 4 header is stored in: l4hdr[0..h4len-1]
The payload is stored in data[dofs..dofs+dlen-1]
Example:
CONST
Type = 05555H; (* link-layer protocol type *)
VAR
dlen: LONGINT;
l3hdr: ARRAY HdrLen OF CHAR;
data: ARRAY MaxDataLen OF CHAR;
(* - l3hdr[0..HdrLen-1] contains the layer 3 packet header.
- data[0..dlen-1] contains the packet data.
- there is no layer 4 header in this example, i.e. an empty buffer is passed (len=0)
*)
dev.Send(dev.broadcast, Type, l3hdr, l3hdr, data, HdrLen, 0, 0, dlen); (* send a broadcast packet *)
Packet receiving is driven by the driver object. A receiver interested in a specific type of packet registers itself using the InstallReceiver method. The type parameter specifies the link-layer protocol type, which must be unique. There can only be one receiver installed per type. When a packet arrives, the driver object looks at the protocol type and calls the specific receiver (if any is installed for this type).
Example:
PROCEDURE Receiver(dev: Network.LinkDevice; type: LONGINT; buffer: Network.Buffer);
BEGIN
ASSERT(type = Type);
CheckAdr(buffer.src); (* some link layer source address checks *)
IF ~(ChecksumForThisProtocol IN buffer.calcChecksum) THEN
VerifyChecksum(buffer);
END;
ExamineLayer3Header(buffer.data, buffer.ofs, Layer3HeaderSize);
ProcessPayload(buffer.data, buffer.ofs+Layer3HeaderSize, buffer.len-Header3LayerSize);
(* MANDATORY!!
Buffer must be returned here! - or at higher layers, if the buffer is passed there! *)
Network.ReturnBuffer(buffer);
END Receiver;
dev.InstallReceiver(Type, Receiver); (* install the receiver *)
When passing the buffer to a higher layer (e.g. layer 4), the field "l3ofs" should be set in order to enable the higher layer protocol to access this layer's header (required by ICMP).
The same is valid for the field "l4ofs" when passing the buffer to a higher layer than 4.
The "type" field of a LinkDevice specifies what kind of device it is. Currently, two options (constants) are available:
- TypePointToPoint: dev is a point-to-point link (device), e.g. PPP
- TypeEthernet: dev is an ethernet device
For point-to-point links, the following rules are met:
In constructor, the "local" and "broadcast" parameters are ignored.
If it is not possible to transmit the layer 3 type of packet, the type is set to 0 for the received packet.
If the field "adrSize" is 0, the dst address parameter in Send() is ignored and the src address parameter in the Receiver is not defined.
If "adrSize" is > 0, the dst address passed in Send() is transmitted and presented as src address for the received packet.
The "local" and "broadcast" fields of the object specify the link-level address of the device, and the broadcast address, respectively. If needed, they have to be set during device initialization.
The mtu field specifies the largest allowed packet size in bytes, excluding layer 2 headers and trailers (e.g. 1500 for ethernet).
The "sendCount" and "recvCount" fields show the number of data bytes sent and received by the device since the driver object was loaded.
How to implement a driver:
IMPORTANT:
- Read first "How to use a driver"!
- Read comments of methods you override!
A network driver object is implemented by extending the LinkDevice object. At least the "DoSend" method has to be overridden with a concrete implementation. Normally, you will also override the constructor, the destructor (method "Finalize") and the method "Linked".
CAUTION:
If you override the constructor or destructor:
- At the beginning of the overriding constructor, the overridden constructor has to be called!
- At the end of the overriding destructor, the overridden destructor has to be called!
NOTE:
The device has to be registered and deregistered as a Plugin in Network.registry by the device driver as follows:
Add the device to the registry after it is ready to send/receive data!
Remove the device from the registry before you begin to deinitialize the device!
Normally, the device driver will have to install an interrupt handler for receiving packets. This handler must be installed in Objects. Interrupts registered in Machine are not allowed! (because they are not allowed to call QueueBuffer due to its EXCLUSIVE section)
If you use interrupts:
- do the minimum operations required for receiving and queueing a packet!
- return immediately after having done the operations!
Receiving and queueing packets is done like this:
When you are notified of an incomming network packet (normally by an interrupt), get a new buffer by calling:
buf := Network.GetNewBuffer();
If buf = NIL, the packet has to be discarded because all buffers are currently in use and the maximum amount of buffers (MaxNofBuffers) is exceeded.
If buf # NIL (the normal case), read the packet data into buf.data and set buf.ofs and buf.len accordingly.
The buffer is not initialized in any way.
If the device supports DMA, you could try to get the buffers earlier and pass their physical addesses to the device. With this you can save one packet copying operation!
In addition to the fields "data", "ofs", "len" of "buffer", it is mandatory to set the fields "src" and "calcChecksum" correctly. "src" is the link layer source address and "calcChecksum" is the set of checksums already verified by the device (normally {}).
As soon as the buffer contains the whole packet data, it is passed to the upper layers by calling:
QueueBuffer(buf, type);
Where "type" is the layer 3 type of the packet.
See TestNet.Mod (SendBroadcast, SendTest and Receiver) and IP.Mod (ARPRequest and ARPInput) for an example of packet sending and receiving. See Ethernet3Com90x.Mod for an example of an ethernet driver object and Loopback.Mod for a simple point-to-point link implementation.
*)