123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459 |
- (* Aos, Copyright 2001, Pieter Muller, ETH Zurich *)
- MODULE UDP; (** AUTHOR "pjm, mvt"; PURPOSE "UDP protocol"; *)
- (*
- UDP Header
- 00 16 source port
- 02 16 destination port
- 04 16 UDP length (header and data)
- 06 16 UDP checksum (pseudo-header, header and data)
- 08 -- optional data
- UDP Pseudo-header (for checksum calculation)
- 00 32 source address
- 04 32 destination address
- 08 08 zero = 0
- 09 08 protocol = 17
- 10 16 UDP length (duplicate)
- Notes:
- o Bit numbers above are Intel bit order.
- o Avoid use of SET because of PPC bit numbering issues.
- o Always access fields as 8-, 16- or 32-bit values and use DIV, MOD, ASH, ODD for bit access.
- *)
- IMPORT Modules, Machine, Objects, Network, IP, ICMP;
- CONST
- (** Error codes *)
- Ok* = 0;
- PortInUse* = 3501;
- Timeout* = 3502;
- BufferOverflow* = 3503;
- NoInterface* = 3504;
- Closed* = 3505;
- NilPort* = 0;
- IPTypeUDP = 17; (* UDP type code for IP packets *)
- UDPHdrLen = 8;
- MaxPseudoHdrLen = 40; (* IPv4: 12, IPv6: 40 *)
- MaxUDPDataLen = 10000H-UDPHdrLen;
- MinEphemeralPort = 1024;
- MaxEphemeralPort = 5000;
- QueueSize = 40; (* size (number of packets) of receive queue per socket *)
- HashTableSize = 128; (* size of connection lookup hash table *)
- TYPE
- (** Socket. Stores the state of a UDP communication endpoint. *)
- Socket* = OBJECT
- VAR
- next: Socket; (* link for socket pool *)
- lport: LONGINT; (* local port *)
- hdr: ARRAY UDPHdrLen OF CHAR; (* UDP prototype header for sending *)
- pseudoHdr: ARRAY MaxPseudoHdrLen OF CHAR; (* pseudo header for calculating checksum *)
- (* Receive queue (ring buffer) *)
- queue: ARRAY QueueSize OF Network.Buffer;
- queueFirst: LONGINT; (* index where the new items are queued *)
- queueLast: LONGINT; (* index where the items are removed from the queued *)
- (* Variables for handling timeout *)
- timer: Objects.Timer;
- timeout, open: BOOLEAN;
- (** Constructor *)
- PROCEDURE &Open*(lport: LONGINT; VAR res: WORD);
- BEGIN
- open := TRUE;
- ASSERT((lport >= 0) & (lport < 10000H));
- SELF.lport := lport;
- IF pool.AddSocket(SELF) THEN
- (* set first part of UDP header *)
- Network.PutNet2(hdr, 0, SELF.lport);
- (* set up buffering and blocking *)
- queueFirst := 0;
- queueLast := 0;
- NEW(timer);
- res := Ok;
- ELSE
- res := PortInUse;
- END
- END Open;
- (** Send a UDP datagram to the foreign address specified by "fip" and "fport".
- The data is in "data[ofs..ofs+len-1]". In case of concurrent sends the datagrams are serialized. *)
- PROCEDURE Send*(fip: IP.Adr; fport: LONGINT; CONST data: ARRAY OF CHAR; ofs, len: LONGINT; VAR res: WORD);
- 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;
- (** Send a broadcast UDP datagram via interface "int" to port "lport". Normally only used by DHCP.
- The data is in "data[ofs..ofs+len-1]". In case of concurrent sends the datagrams are serialized. *)
- PROCEDURE SendBroadcast*(int: IP.Interface; fport: LONGINT; CONST 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;
- (** Receive a UDP datagram. If none is available, wait up to the specified timeout for one to arrive.
- "data[ofs..ofs+size-1]" is the data buffer to hold the returned datagram.
- "ms" is a wait timeout value in milliseconds, 0 means "don't wait", -1 means "infinite wait".
- On return, "fip" and "fport" hold the foreign address and port.
- "len" returns the actual datagram size and "data[ofs..ofs+len-1]" returns the data.
- "res" returns "Timeout" in case of a timeout and "BufferOverflow" if the received datagram was too big.
- *)
- PROCEDURE Receive*(VAR data: ARRAY OF CHAR; ofs, size, ms: LONGINT; VAR fip: IP.Adr; VAR fport, len: LONGINT; VAR res: WORD);
- VAR
- buffer: Network.Buffer;
- fragmentBuffer: Network.Buffer;
- fragmentOffset: LONGINT;
- BEGIN {EXCLUSIVE}
- IF ~open THEN res := Closed; RETURN END;
- IF queueFirst = queueLast THEN
- (* queue empty *)
- 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)
- (* now we can continue *)
- END;
- ELSIF ms = -1 THEN
- (* infinite wait *)
- AWAIT((queueFirst # queueLast) OR ~ open);
- IF ~open THEN res := Closed; RETURN END;
- ELSE
- res := Timeout;
- RETURN;
- END;
- END;
- (* Here we can get a packet from the queue *)
- 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
- (* packet too big for receive buffer *)
- 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;
- (* Internal send operation. Called from "Send" and "SendBroadcast". *)
- PROCEDURE DoSend(int: IP.Interface; fip: IP.Adr; fport: LONGINT; CONST data: ARRAY OF CHAR; ofs, len: LONGINT);
- VAR
- sum: LONGINT;
- pseudoHdrLen: LONGINT;
- BEGIN
- (* set UDP header *)
- Network.PutNet2(hdr, 2, fport); (* foreign port *)
- Network.PutNet2(hdr, 4, len+UDPHdrLen); (* UPD length *)
- Network.Put2(hdr, 6, 0); (* checksum := 0 *)
- IF ~(Network.ChecksumUDP IN int.dev.calcChecksum) THEN
- (* set pseudo header *)
- 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); (* checksum := sum *)
- END;
- int.Send(IPTypeUDP, fip, hdr, data, UDPHdrLen, ofs, len, IP.MaxTTL);
- END DoSend;
- (* Handle timeout call from Objects *)
- PROCEDURE DoTimeout;
- BEGIN {EXCLUSIVE}
- timeout := TRUE;
- END DoTimeout;
- (* Input a datagram on this socket. *)
- PROCEDURE Input(fip: IP.Adr; buffer: Network.Buffer);
- BEGIN {EXCLUSIVE}
- IF (queueLast - queueFirst) MOD QueueSize = 1 THEN
- (* queue full - discard packet and return buffer *)
- Machine.AtomicInc(NUDPQueueOverflow);
- Network.ReturnBuffer(buffer);
- ELSE
- queue[queueFirst] := buffer;
- queueFirst := (queueFirst + 1) MOD QueueSize;
- Machine.AtomicInc(NUDPQueued);
- END;
- END Input;
- (** Close the Socket, freeing its address for re-use. *)
- PROCEDURE Close*;
- BEGIN {EXCLUSIVE}
- pool.RemoveSocket(SELF);
- Objects.CancelTimeout(timer);
- open := FALSE;
- (* return all queued buffers *)
- WHILE queueFirst # queueLast DO
- Network.ReturnBuffer(queue[queueLast]);
- queueLast := (queueLast + 1) MOD QueueSize;
- END;
- (* do not touch any other fields, as instance may still be in use via pool.Lookup. *)
- END Close;
- END Socket;
- (* Socket pool *)
- SocketPool = OBJECT
- VAR
- table: ARRAY HashTableSize OF Socket;
- eport: LONGINT;
- (* Initialize the pool. *)
- PROCEDURE &Init*;
- VAR i: LONGINT;
- BEGIN
- FOR i := 0 TO HashTableSize-1 DO
- table[i] := NIL;
- END;
- eport := MinEphemeralPort;
- END Init;
- (* Look for the specified Socket *)
- 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;
- (* Add a socket to the pool. If lport is NilPort, an ephemeral port is assigned. *)
- PROCEDURE AddSocket(p: Socket): BOOLEAN;
- VAR
- ok: BOOLEAN;
- i, sport: LONGINT;
- BEGIN {EXCLUSIVE}
- IF p.lport = NilPort THEN
- (* find an unused ephemeral port *)
- sport := eport; (* store port where the search started *)
- REPEAT
- p.lport := eport;
- (* check if port is in use *)
- ok := (Lookup(eport) = NIL);
- INC(eport);
- IF eport > MaxEphemeralPort THEN
- eport := MinEphemeralPort;
- END;
- UNTIL ok OR (eport = sport);
- (* ok is TRUE here if the port is not used yet *)
- ELSE
- (* ensure port is not in use *)
- 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;
- (* Remove the Socket from the pool, making its address re-usable. *)
- PROCEDURE RemoveSocket(p: Socket);
- VAR
- i: LONGINT;
- item: Socket;
- BEGIN {EXCLUSIVE}
- i := p.lport MOD HashTableSize;
- IF table[i] = NIL THEN
- (* not found *)
- 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;
- (* do not clear p.next, because Lookup may be looking at it *)
- END RemoveSocket;
- (* Close all sockets that are registered in pool *)
- 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
- (* Module variables *)
- pool: SocketPool;
- (* Statistic variables *)
- NUDPRcvTotal-, NUDPTooSmall-, NUDPBadChecksum-, NUDPRcvBroadcast-, NUDPUnknownPort-,
- NUDPQueued-, NUDPQueueOverflow-, NUDPTrim-, NUDPBadHdrLen-: LONGINT;
- (* Receive a UDP datagram. *)
- PROCEDURE Input(int: IP.Interface; type: LONGINT; fip, lip: IP.Adr; buffer: Network.Buffer);
- VAR
- (* pseudo header for calculating checksum *)
- 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
- (* size not used *)
- 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); (* get checksum from header *)
- END;
- IF sum # 0 THEN
- (* calculate checksum *)
- (* set pseudo header *)
- 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
- (* fragmented packets *)
- 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);
- (* Exit here w/o returning buffer because it is passed to Socket.Input *)
- 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;
- (* Exit and return buffer here because it is no longer used *)
- 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)
- *)
|