MODULE UDP;
IMPORT Modules, Machine, Objects, Network, IP, ICMP;
CONST
Ok* = 0;
PortInUse* = 3501;
Timeout* = 3502;
BufferOverflow* = 3503;
NoInterface* = 3504;
Closed* = 3505;
NilPort* = 0;
IPTypeUDP = 17;
UDPHdrLen = 8;
MaxPseudoHdrLen = 40;
MaxUDPDataLen = 10000H-UDPHdrLen;
MinEphemeralPort = 1024;
MaxEphemeralPort = 5000;
QueueSize = 40;
HashTableSize = 128;
TYPE
Socket* = OBJECT
VAR
next: Socket;
lport: LONGINT;
hdr: ARRAY UDPHdrLen OF CHAR;
pseudoHdr: ARRAY MaxPseudoHdrLen OF CHAR;
queue: ARRAY QueueSize OF Network.Buffer;
queueFirst: LONGINT;
queueLast: LONGINT;
timer: Objects.Timer;
timeout, open: BOOLEAN;
PROCEDURE &Open*(lport: LONGINT; VAR res: LONGINT);
BEGIN
open := TRUE;
ASSERT((lport >= 0) & (lport < 10000H));
SELF.lport := lport;
IF pool.AddSocket(SELF) THEN
Network.PutNet2(hdr, 0, SELF.lport);
queueFirst := 0;
queueLast := 0;
NEW(timer);
res := Ok;
ELSE
res := PortInUse;
END
END Open;
PROCEDURE Send*(fip: IP.Adr; fport: LONGINT; VAR data: ARRAY OF CHAR; ofs, len: LONGINT; VAR res: LONGINT);
VAR
int: IP.Interface;
BEGIN {EXCLUSIVE}
ASSERT((fport >= 0) & (fport < 10000H));
ASSERT((len >= 0) & (len <= MaxUDPDataLen));
int := IP.InterfaceByDstIP(fip);
IF int # NIL THEN
DoSend(int, fip, fport, data, ofs, len);
res := Ok;
ELSE
res := NoInterface;
END;
END Send;
PROCEDURE SendBroadcast*(int: IP.Interface; fport: LONGINT; VAR data: ARRAY OF CHAR; ofs, len: LONGINT);
BEGIN {EXCLUSIVE}
ASSERT((fport >= 0) & (fport < 10000H));
ASSERT((len >= 0) & (len <= MaxUDPDataLen));
DoSend(int, int.broadAdr, fport, data, ofs, len);
END SendBroadcast;
PROCEDURE Receive*(VAR data: ARRAY OF CHAR; ofs, size, ms: LONGINT; VAR fip: IP.Adr; VAR fport, len, res: LONGINT);
VAR
buffer: Network.Buffer;
fragmentBuffer: Network.Buffer;
fragmentOffset: LONGINT;
BEGIN {EXCLUSIVE}
IF ~open THEN res := Closed; RETURN END;
IF queueFirst = queueLast THEN
IF ms > 0 THEN
timeout := FALSE;
Objects.SetTimeout(timer, DoTimeout, ms);
AWAIT((queueFirst # queueLast) OR timeout OR ~open);
IF ~open THEN res := Closed; RETURN END;
IF timeout THEN
res := Timeout;
RETURN;
ELSE
Objects.CancelTimeout(timer)
END;
ELSIF ms = -1 THEN
AWAIT((queueFirst # queueLast) OR ~ open);
IF ~open THEN res := Closed; RETURN END;
ELSE
res := Timeout;
RETURN;
END;
END;
buffer := queue[queueLast];
queueLast := (queueLast + 1) MOD QueueSize;
fip := IP.SrcAdrFromBuffer(buffer);
fport := Network.GetNet2(buffer.data, buffer.ofs);
fragmentBuffer := buffer;
len := 0;
WHILE fragmentBuffer # NIL DO
INC(len, fragmentBuffer.len);
fragmentBuffer := fragmentBuffer.nextFragment;
END;
DEC(len, UDPHdrLen);
IF len > size THEN
res := BufferOverflow;
ELSE
Network.Copy(buffer.data, data, buffer.ofs+UDPHdrLen, ofs, buffer.len - UDPHdrLen);
fragmentOffset := ofs + buffer.len - UDPHdrLen;
fragmentBuffer := buffer.next;
WHILE fragmentBuffer # NIL DO
Network.Copy(fragmentBuffer.data, data, buffer.ofs, fragmentOffset, buffer.len);
INC(fragmentOffset, buffer.len);
fragmentBuffer := fragmentBuffer.nextFragment;
END;
res := Ok;
END;
Network.ReturnBuffer(buffer);
END Receive;
PROCEDURE DoSend(int: IP.Interface; fip: IP.Adr; fport: LONGINT; VAR data: ARRAY OF CHAR; ofs, len: LONGINT);
VAR
sum: LONGINT;
pseudoHdrLen: LONGINT;
BEGIN
Network.PutNet2(hdr, 2, fport);
Network.PutNet2(hdr, 4, len+UDPHdrLen);
Network.Put2(hdr, 6, 0);
IF ~(Network.ChecksumUDP IN int.dev.calcChecksum) THEN
pseudoHdrLen := int.WritePseudoHeader(pseudoHdr, int.localAdr, fip, IPTypeUDP, len+UDPHdrLen);
sum := IP.Checksum1(pseudoHdr, 0, pseudoHdrLen, 0);
sum := IP.Checksum1(hdr, 0, UDPHdrLen, sum);
sum := IP.Checksum2(data, ofs, len, sum);
Network.Put2(hdr, 6, sum);
END;
int.Send(IPTypeUDP, fip, hdr, data, UDPHdrLen, ofs, len, IP.MaxTTL);
END DoSend;
PROCEDURE DoTimeout;
BEGIN {EXCLUSIVE}
timeout := TRUE;
END DoTimeout;
PROCEDURE Input(fip: IP.Adr; buffer: Network.Buffer);
BEGIN {EXCLUSIVE}
IF (queueLast - queueFirst) MOD QueueSize = 1 THEN
Machine.AtomicInc(NUDPQueueOverflow);
Network.ReturnBuffer(buffer);
ELSE
queue[queueFirst] := buffer;
queueFirst := (queueFirst + 1) MOD QueueSize;
Machine.AtomicInc(NUDPQueued);
END;
END Input;
PROCEDURE Close*;
BEGIN {EXCLUSIVE}
pool.RemoveSocket(SELF);
Objects.CancelTimeout(timer);
open := FALSE;
WHILE queueFirst # queueLast DO
Network.ReturnBuffer(queue[queueLast]);
queueLast := (queueLast + 1) MOD QueueSize;
END;
END Close;
END Socket;
SocketPool = OBJECT
VAR
table: ARRAY HashTableSize OF Socket;
eport: LONGINT;
PROCEDURE &Init*;
VAR i: LONGINT;
BEGIN
FOR i := 0 TO HashTableSize-1 DO
table[i] := NIL;
END;
eport := MinEphemeralPort;
END Init;
PROCEDURE Lookup(lport: LONGINT): Socket;
VAR item: Socket;
BEGIN
item := table[lport MOD HashTableSize];
WHILE (item # NIL) & (item.lport # lport) DO
item := item.next;
END;
RETURN item;
END Lookup;
PROCEDURE AddSocket(p: Socket): BOOLEAN;
VAR
ok: BOOLEAN;
i, sport: LONGINT;
BEGIN {EXCLUSIVE}
IF p.lport = NilPort THEN
sport := eport;
REPEAT
p.lport := eport;
ok := (Lookup(eport) = NIL);
INC(eport);
IF eport > MaxEphemeralPort THEN
eport := MinEphemeralPort;
END;
UNTIL ok OR (eport = sport);
ELSE
ok := (Lookup(p.lport) = NIL);
END;
IF ok THEN
i := p.lport MOD HashTableSize;
p.next := table[i];
table[i] := p;
END;
RETURN ok;
END AddSocket;
PROCEDURE RemoveSocket(p: Socket);
VAR
i: LONGINT;
item: Socket;
BEGIN {EXCLUSIVE}
i := p.lport MOD HashTableSize;
IF table[i] = NIL THEN
ELSIF table[i] = p THEN
table[i] := table[i].next;
ELSE
item := table[i];
WHILE (item.next # NIL) & (item.next # p) DO
item := item.next;
END;
IF item.next # NIL THEN
item.next := item.next.next;
END;
END;
END RemoveSocket;
PROCEDURE CloseAll;
VAR i: LONGINT;
BEGIN
FOR i := 0 TO HashTableSize-1 DO
WHILE table[i] # NIL DO
table[i].Close();
END;
END;
END CloseAll;
END SocketPool;
VAR
pool: SocketPool;
NUDPRcvTotal-, NUDPTooSmall-, NUDPBadChecksum-, NUDPRcvBroadcast-, NUDPUnknownPort-,
NUDPQueued-, NUDPQueueOverflow-, NUDPTrim-, NUDPBadHdrLen-: LONGINT;
PROCEDURE Input(int: IP.Interface; type: LONGINT; fip, lip: IP.Adr; buffer: Network.Buffer);
VAR
pseudoHdr: ARRAY MaxPseudoHdrLen OF CHAR;
pseudoHdrLen: LONGINT;
sum, tlen: LONGINT;
s: Socket;
reassembledLength: LONGINT;
fragmentBuffer: Network.Buffer;
BEGIN
Machine.AtomicInc(NUDPRcvTotal);
IF buffer.len >= UDPHdrLen THEN
tlen := Network.GetNet2(buffer.data, buffer.ofs+4);
IF (tlen >= UDPHdrLen) & (tlen <= buffer.len) THEN
IF tlen < buffer.len THEN
Machine.AtomicInc(NUDPTrim);
buffer.len := tlen;
END;
IF Network.ChecksumUDP IN buffer.calcChecksum THEN
sum := 0;
ELSE
sum := Network.Get2(buffer.data, buffer.ofs+6);
END;
IF sum # 0 THEN
reassembledLength := 0;
fragmentBuffer := buffer;
WHILE fragmentBuffer # NIL DO
INC(reassembledLength, fragmentBuffer.len);
fragmentBuffer := fragmentBuffer.nextFragment;
END;
pseudoHdrLen := int.WritePseudoHeader(pseudoHdr, fip, lip, IPTypeUDP, reassembledLength);
sum := IP.Checksum1(pseudoHdr, 0, pseudoHdrLen, 0);
IF buffer.nextFragment # NIL THEN
fragmentBuffer := buffer;
WHILE fragmentBuffer.nextFragment # NIL DO
sum := IP.Checksum1(fragmentBuffer.data, fragmentBuffer.ofs, fragmentBuffer.len, sum);
fragmentBuffer := fragmentBuffer.nextFragment;
END;
sum := IP.Checksum2(fragmentBuffer.data, fragmentBuffer.ofs, fragmentBuffer.len, sum);
ELSE
sum := IP.Checksum2(buffer.data, buffer.ofs, buffer.len, sum);
END;
END;
IF sum = 0 THEN
s := pool.Lookup(Network.GetNet2(buffer.data, buffer.ofs+2));
IF s # NIL THEN
s.Input(fip, buffer);
RETURN;
ELSIF ~int.IsBroadcast(lip) THEN
Machine.AtomicInc(NUDPUnknownPort);
ICMP.SendICMP (ICMP.ICMPDstUnreachable, fip, buffer);
END;
ELSE
Machine.AtomicInc(NUDPBadChecksum);
END;
ELSE
Machine.AtomicInc(NUDPBadHdrLen);
END;
ELSE
Machine.AtomicInc(NUDPTooSmall);
END;
Network.ReturnBuffer(buffer);
END Input;
PROCEDURE Cleanup;
BEGIN
IP.RemoveReceiver(IPTypeUDP);
pool.CloseAll();
END Cleanup;
BEGIN
NEW(pool);
IP.InstallReceiver(IPTypeUDP, Input);
Modules.InstallTermHandler(Cleanup);
END UDP.
(*
History:
27.10.2003 mvt Complete internal redesign for new interfaces of Network and IP.
22.11.2003 mvt Changed SocketPool to work with a hash table.
02.05.2005 eb Works with fragmented packets & IPv6 ready (WritePseudoHdr)
*)