! IP module for INet process, GDMR, Feb/March 1987, Oct 1988
! Now includes ICMP module.
! Ignores options on input, never sends any options.
! Should do fragmentation/reassembly

!%option "-NonStandard-NoCheck-NoTrace-NoDiag-NoStack-NoLine"
%option "-NonStandard-NoCheck"

%constinteger ARP lives = 4

%include "INet:Formats.Inc"
%include "INet:Utility.Inc"
%include "INet:Stats.Inc"
%include "INet:Control.Inc"
%include "INet:INet.Inc"

%externalintegerspec control

%externalinteger  slow IP address   = 0
%externalinteger  slow network      = 0
%externalinteger  ether IP address  = 0

%routinespec ICMP inbound(%record(buffer fm)%name b)

%externalroutinespec TCP  inbound(%record(buffer fm)%name b)
%externalroutinespec UDP  inbound(%record(buffer fm)%name b)

%externalroutinespec slow ether outbound(%record(buffer fm)%name b)
%externalroutinespec      ether outbound(%record(buffer fm)%name b)

%externalrecord(INet statistics fm)%spec stats

%routine dump IP header(%record(IP header fm) h, %string(31) text)
   printstring(text)
   printstring(" IP header version ");  write(h_IHL >> 4, 0)
   printstring(", size ");  write(h_IHL & 15, 0)
   printstring(", TOS ");  write(h_type of service, 0)
   printstring(", claimed length ");  write(h_total length, 0)
   newline
   printstring("ID ");  write(h_identification, 0)
   printstring(", fragment ");  write(h_fragment offset, 0)
   printstring(", TTL ");  write(h_TTL, 0)
   printstring(", prot ");  write(h_protocol, 0)
   newline
   printstring("Source ");  print inet address(h_source)
   printstring(", destination ");  print inet address(h_destination)
   newline
%end

%externalroutinespec find route(%integer target, %integername gate, via)

%externalintegerfn IP source for(%integer d)
   %result = slow IP address %if d & 16_FFFFFF00 = slow network
   %result = slow IP address %if ether IP address = 0;  ! No 10Meg, use slow
   %result = ether IP address
%end

%routine IP send(%record(buffer fm)%name b)
   %integer c, via
      !! printstring("IP send to ");  print INet address(b_IP peer);  newline
      %if control & trace IP out # 0 %start
         dump IP header(b_IP header, "Outbound")
      %finish
      b_IP header_TTL = b_IP header_TTL - 1
      release buffer(b) %and %return %if b_IP header_TTL = 0;  ! Expired
      stats_IP packets out = stats_IP packets out + 1
      stats_IP bytes out = stats_IP bytes out + b_IP bytes
      b_IP header_checksum = 0;  ! Meantime
!N!   net order short(b_IP header_total length)
!N!   ! fragment offset assumed 0
!N!   net order long(b_IP header_source)
!N!   net order long(b_IP header_destination)
      c = calculate checksum(b_IP header, 20);  ! Header checksum only
      b_IP header_checksum <- \c
      b_IP header_checksum <- 16_FFFF %if b_IP header_checksum = 0;  ! Use -0
      find route(b_IP peer, b_IP target, via)
      b_IP target = b_IP peer %if b_IP target = 0;  ! Direct
      !! %if b_IP target # b_IP peer %start
      !!    printstring("Route for ");  print INet address(b_IP peer)
      !!    printstring(" via ");  print INet address(b_IP target);  newline
      !! %finish
      %if b_IP target & 16_FFFFFF00 = slow network %start
         slow ether outbound(b)
      %else
         ether outbound(b)
      %finish
%end

%externalroutine IP inbound(%record(buffer fm)%name b)
   %integer checksum, header size, host, net, class
!L!   lights or A(IP light)
      !! printstring("Inbound IP packet, size ")
      !! write(b_bytes, 0);  newline
      !! dump(byteinteger(addr(b_IP header)), b_bytes)
      header size = (b_IP header_IHL & 15) * 4
      !! printstring("Header size is ");  write(header size, 0);  newline
      ! check the checksum.
      checksum = calculate checksum(b_IP header, header size)
      %if checksum # 0 %and checksum # 16_FFFF %start
         ! Dud checksum (beware ONES-complement)
!N!      net order long(b_IP header_source)
         !! pdate
         !! printstring("Dud IP checksum ");  write(checksum, 0)
         !! printstring(" from ");  print inet address(b_IP header_source)
         !! newline
         ! If the checksum was wrong then maybe the source address is wrong
         ! too, so we log it to the non-peer area.
       ! sp_IP checksum errors = sp_IP checksum errors + 1
         release buffer(b)
         -> out
      %finish
      ! Convert things from net order
!N!   net order short(b_IP header_total length)
!N!   net order short(b_IP header_fragment offset)
!N!   net order long(b_IP header_source)
!N!   net order long(b_IP header_destination)
      %if control & trace IP in # 0 %start
      !! printstring("IP from ");  print INet address(b_IP header_source)
      !! printstring(" for ");  print INet address(b_IP header_destination)
      !! printstring(" TTL ");  write(b_IP header_TTL, 0);  newline
         dump IP header(b_IP header, "Inbound")
      %finish
      %if b_IP header_source & 16_FF000000 = 0 %start
         ! A Sun booting -- throw it away
         !! printstring("Sun booting (probably)");  newline
         stats_IP other errors = stats_IP other errors + 1
         release buffer(b)
         -> out
      %finish
      b_IP peer = b_IP header_source
      %if b_IP header_fragment offset & 2_0011 1111 1111 1111 # 0 %start
         ! Fragmented -- dump it (*meantime*)
         !! pdate
         !! printstring("Fragmented IP packet received from ")
         !! print inet address(b_IP peer)
         !! printstring(" -- dumping");  newline
         stats_IP fragmented packets = stats_IP fragmented packets + 1
         stats_IP fragmented bytes = stats_IP fragmented bytes + b_bytes
         ! Drop it unconditionally, for now
         stats_IP fragments dropped = stats_IP fragments dropped + 1
         stats_IP fragmented bytes dropped = %c
            stats_IP fragmented bytes dropped + b_bytes
         release buffer(b)
         -> out
      %finish
    ! %if 46 <= b_IP header_total length # b_bytes %start
    !    ! Received length wrong
    !    !! pdate
    !    !! printstring("Length mismatch from ")
    !    !! print inet address(b_IP peer)
    !    !! printstring(": claimed ");  write(b_IP header_total length, 0)
    !    !! printstring(", actual ");  write(b_bytes, 0);  newline
    !    stats_IP dud lengths = stats_IP dud lengths + 1
    !    ! Carry on -- assume that the IP length is the correct one.
    !    ! Some systems, notably cheops, appear to be padding packets to
    !    ! an even boundary....
    ! %finish
      ! Validate the destination.  >> SHOULD BOMB-PROOF THIS <<
      split INet address(b_IP header_destination, host, net, class)
      %if host = 0 %start
         ! Old-style ...0.0 broadcast
         !! printstring("Old-style IP broadcast from ")
         !! print inet address(b_IP peer)
         !! printstring(", protocol ");  write(b_IP header_protocol, 0)
         !! newline
         stats_IP old broadcasts = stats_IP old broadcasts + 1
      %else %if host = -1 %or host = new broadcast mask(class)
         ! New-style ...255.255 broadcast
         !! printstring("New-style IP broadcast from ")
         !! print inet address(b_IP peer)
         !! printstring(", protocol ");  write(b_IP header_protocol, 0)
         !! newline
         stats_IP new broadcasts = stats_IP new broadcasts + 1
      %else %if b_IP header_destination # ether IP address %c
            %and b_IP header_destination # slow IP address
         ! Not for us.  Forward it. (??!)
         !! pdate
         !! printstring("Forwarding IP: destination ")
         !! print inet address(b_IP header_destination)
         !! printstring(", source ");  print inet address(b_IP header_source)
         !! newline
         stats_IP packets routed = stats_IP packets routed + 1
         stats_IP bytes routed = stats_IP bytes routed + b_bytes
         b_IP peer = b_IP header_destination
         IP send(b)
         -> out
      %finish
      ! Now forward the validated header...
      b_header2 == record(addr(b_IP header) + header size)
      b_IP bytes = b_IP header_total length - header size
      stats_IP packets in = stats_IP packets in + 1
      stats_IP bytes in = stats_IP bytes in + b_IP bytes
      %if b_IP header_protocol = IP TCP protocol %start
         !! printstring("IP TCP");  newline
         TCP inbound(b)
      %else %if b_IP header_protocol = IP UDP protocol
         !! printstring("IP UDP");  newline
         UDP inbound(b)
      %else %if b_IP header_protocol = IP ICMP protocol
         !! printstring("IP ICMP");  newline
         ICMP inbound(b)
      %else
         ! Something unknown
         !! pdate
         !! printstring("Unknown protocol type ");  write(b_IP header_protocol, 0)
         !! printstring(" from ");  print inet address(b_IP peer)
         !! newline
         stats_IP dud protocols = stats_IP dud protocols + 1
         release buffer(b)
      %finish
out:
!L!   lights and A(\IP light)
%end

%externalroutine IP outbound(%record(buffer fm)%name b)
   %owninteger outbound identification = 0
!L!   lights or B(IP light)
      !! printstring("IP outbound for ")
      !! print inet address(b_IP peer);  newline
      !! dump(byteinteger(addr(b_header 2)), b_IP bytes)
      %if b_IP tag = 0 %start
         outbound identification = (outbound identification + 1) & 16_FFFF
         outbound identification = 1 %if outbound identification = 0
         b_IP tag = outbound identification
      %finish
      b_bytes = b_IP bytes + 20
      b_IP header == record(addr(b_header 2) - 20)
      b_IP header_IHL = 16_45
      b_IP header_type of service = 0
      b_IP header_total length = b_bytes
      b_IP header_identification <- b_IP tag
      b_IP header_fragment offset = 0
      b_IP header_TTL = 255
      b_IP header_protocol = b_protocol
      b_IP header_source = IP source for(b_IP peer)
      b_IP header_destination = b_IP peer
      !! dump IP header(b_IP header)
      ! No options
      IP send(b)
!L!   lights and B(\IP light)
%end


! ICMP follows....

%constinteger ICMP echo reply               = 0
%constinteger ICMP destination unreachable  = 3
%constinteger ICMP source quench            = 4
%constinteger ICMP redirect                 = 5
%constinteger ICMP echo                     = 8
%constinteger ICMP time exceeded            = 11
%constinteger ICMP parameter problem        = 12
%constinteger ICMP timestamp                = 13
%constinteger ICMP timestamp reply          = 14
%constinteger ICMP information request      = 15
%constinteger ICMP information reply        = 16

%constinteger ICMP protocol unreachable     = 2
%constinteger ICMP port unreachable         = 3

%conststring(31)%array ICMP message text(0 : 16) =
   { 0}  "Echo reply",
   { 1}  "?? 1",
   { 2}  "?? 2",
   { 3}  "Destination unreachable",
   { 4}  "Source quench",
   { 5}  "Redirect",
   { 6}  "?? 6",
   { 7}  "?? 7",
   { 8}  "Echo",
   { 9}  "?? 9",
   {10}  "?? 10",
   {11}  "Time exceeded",
   {12}  "Parameter problem",
   {13}  "Timestamp",
   {14}  "Timestamp reply",
   {15}  "Information request",
   {16}  "Information reply"

%owninteger ICMP echo sequence = 0

%externalroutine ICMP inbound(%record(buffer fm)%name b)
   %string(127) message, source
   %integer c, x, host, net, class
!L!   lights or A(ICMP light)
      !! printstring("ICMP inbound from ")
      !! print inet address(b_IP header_source);  newline
      !! dump(byteinteger(addr(b_ICMP header)), b_IP bytes)
      message = "ICMP: from " . inet address to S(b_IP header_source)
      !source = INet address to name(b_IP header_source)
      !message = message . " (" . source . ")" %if source # ""
      split INet address(b_IP header_destination, host, net, class)
      message = message . " (broadcast??)" %if b_flags & broadcast flag # 0
      message = message . " -- "
      %if b_IP bytes < 8 %start
         pdate
         printstring("Short ICMP message from ")
         print inet address(b_IP header_source);  newline
         stats_ICMP errors in = stats_ICMP errors in + 1
         release buffer(b)
         -> out
      %finish
      stats_ICMP in = stats_ICMP in + 1
      %if b_ICMP header_type = ICMP echo %start
         ! Send echo reply
         %if b_flags & broadcast flag = 0 %start
            ! Don't reply to broadcasts
            b_ICMP header_type = ICMP echo reply
            b_ICMP header_checksum = 0
            c = calculate checksum(b_ICMP header, b_IP bytes)
            b_ICMP header_checksum <- \c
            stats_ICMP out = stats_ICMP out + 1
            !! printstring("Sending echo reply to ")
            !! print inet address(b_IP peer);  newline
            b_protocol = IP ICMP protocol
            b_next queue == nil
            IP outbound(b)
            -> out
         %finish
      %else %if b_ICMP header_type = ICMP echo reply
         printstring(message);  printstring("echo reply")
         newline
      %else %if b_ICMP header_type = ICMP timestamp
         ! Send timestamp reply
         %if b_flags & broadcast flag = 0 %start
            ! Don't reply to broadcasts
            x = b_ICMP header_originate timestamp
!N!         net order long(x)
            b_ICMP header_type = ICMP timestamp reply
            b_ICMP header_receive timestamp = generate timestamp
!N!         net order long(b_ICMP header_receive timestamp)
            b_ICMP header_transmit timestamp = b_ICMP header_receive timestamp
            b_ICMP header_checksum = 0
            c = calculate checksum(b_ICMP header, b_IP bytes)
            b_ICMP header_checksum <- \c
            stats_ICMP out = stats_ICMP out + 1
            !! printstring("Sending timestamp reply to ")
            !! print inet address(b_IP peer);  newline
            b_protocol = IP ICMP protocol
            b_next queue == nil
            IP outbound(b)
            -> out
         %finish
      %else %if b_ICMP header_type = ICMP timestamp reply
!N!      net order long(b_ICMP header_originate timestamp)
!N!      net order long(b_ICMP header_receive timestamp)
!N!      net order long(b_ICMP header_transmit timestamp)
         message = message . "timestamp reply: " . %c
                   convert timestamp(b_ICMP header_originate timestamp) . ", " . %c
                   convert timestamp(b_ICMP header_receive   timestamp) . ", " . %c
                   convert timestamp(b_ICMP header_transmit  timestamp)
         printstring(message);  newline
      %finish
      release buffer(b)
out:
!L!   lights and A(\ICMP light)
%end


! ICMP outbound (user requests)

%recordformat ICMP request fm(%integer code, target)

%externalroutine ICMP outbound(%record(buffer fm)%name b)
   %record(ICMP request fm)%name request
   %integer c
!L!   lights or B(ICMP light)
      request == record(addr(b_data start))
      !! printstring("ICMP outbound for ")
      !! print inet address(request_target);  newline
      b_ICMP header == record(addr(b_data(32)))
      b_ICMP header = 0
      b_IP peer = request_target
      %if request_code = ICMP ping request %start
         b_ICMP header_type = ICMP echo
         b_ICMP header_code = 0
         ICMP echo sequence = ICMP echo sequence + 1
         b_ICMP header_sequence number = ICMP echo sequence
         c = calculate checksum(b_ICMP header, 8)
         b_ICMP header_checksum <- \c
         b_data bytes = 0;  b_IP bytes = 8;  b_protocol = IP ICMP protocol
         b_next queue == nil
         IP outbound(b)
         stats_ICMP out = stats_ICMP out + 1
      %else %if request_code = ICMP timestamp request
         b_ICMP header_type = ICMP timestamp
         b_ICMP header_code = 0
         ICMP echo sequence = ICMP echo sequence + 1
         b_ICMP header_sequence number = ICMP echo sequence
         b_ICMP header_originate timestamp = generate timestamp
!N!      net order long(b_ICMP header_originate timestamp)
         c = calculate checksum(b_ICMP header, 20)
         b_ICMP header_checksum <- \c
         b_data bytes = 0;  b_IP bytes = 20;  b_protocol = IP ICMP protocol
         b_next queue == nil
         IP outbound(b)
         stats_ICMP out = stats_ICMP out + 1
      %else
         !! printstring("Unknown ICMP request code ")
         !! phex(request_code);  newline
         stats_ICMP errors out = stats_ICMP errors out + 1
         release buffer(b)
      %finish
!L!   lights and B(\ICMP light)
%end

%end %of %file
