! BSD-style RIP process for Mouse INet, GDMR, Feb 1988.
! Revised Sept 1988 to only advertise particular routes to the "other" side.

! Although we're started up by the INet "main" module and are
! called by its clock handler, we're interface to UDP through the
! standard user interface.

%option "-NonStandard-NoCheck-NoDiag-NoLine"

%constinteger listener priority = 6
%constinteger listener size = 10240
%constinteger advertiser priority = 5
%constinteger advertiser size = 10240

%constinteger routed port = 520

%constinteger route slots = 32
%constinteger RIP limit = 250;  !?

%include "Moose:Mouse.Inc"
%include "INet:INet.Inc"
%include "INet:Stats.Inc"
%include "INet:Control.Inc"
%externalintegerspec control

%externalrecord(INet statistics fm)%spec stats

%include "INet:Lights.Inc"
%constinteger routed light = 16_80000000

%constinteger infinity = 16_7FFFFFFF

%externalintegerspec slow IP address
%externalintegerspec ether IP address

%externalroutinespec set ether IP address(%integer example)

%externalpredicatespec FS lookup(%string(31) what, %integername value)
%externalroutinespec FS insert(%string(31) what, %integer value)

%systemroutinespec phex(%integer x)

%recordformat RIP entry fm(%short type,
                           %short spare1,
                           %integer network,
                           %integer spare2, spare3,
                           %integer metric)
%constinteger RIP entry size = 2 + 2 + 4 + 8 + 4

%recordformat RIP packet fm(%byte op, version,
                            %short spare,
                            %record(RIP entry fm)%array e(1 : RIP limit))


%routine print INet address(%integer addr)
   write(addr >> 24 & 255, 0);  print symbol('.')
   write(addr >> 16 & 255, 0);  print symbol('.')
   write(addr >>  8 & 255, 0);  print symbol('.')
   write(addr       & 255, 0)
%end

%include "INet:Dump.Inc"


! Route tables & manipulation thereof

%constinteger unreachable = 16
%constinteger dead = 16_10000000

%constinteger alive interval = 6;  ! Ticks
%constinteger forget interval = 12;  ! Ticks

%owninteger stamp = 0

%recordformat route fm(%integer network, mask, gateway, metric, stamp, via)
%ownrecord(route fm)%array route table(1 : route slots) = 0(*)
%owninteger routes = 0
%owninteger local routes = 0
%ownrecord(semaphore fm) route semaphore = 0

%externalroutine find route(%integer target, %integername gate, via)
   %record(route fm)%name r
   %integer i
      !! printstring("Find route for ");  print INet address(target);  newline
      semaphore wait(route semaphore)
      ! We know there must always be one route, as otherwise we wouldn't
      ! be able to speak to anyone....
      %for i = 1, 1, routes %cycle
         r == route table(i)
         %if target & r_mask = r_network %and r_metric < unreachable %start
            ! Found a route
            gate = r_gateway
            via = r_via
            signal semaphore(route semaphore)
            !! printstring("Found: -> ");  print INet address(r_gateway);  newline
            %return
         %finish
      %repeat
      signal semaphore(route semaphore)
      !! printstring("Unknown");  newline
      gate = -1;  ! Unknown
%end

%externalroutine add local route(%integer address, metric, via)
   %record(route fm)%name r
   %integer i
      !! printstring("Add local route: ");  print INet address(address)
      !! printstring("  metric ");  write(metric, 0);  newline
      semaphore wait(route semaphore)
      ! Add the route at the end of our tables
      r == route table(routes + 1)
      %if address & 16_80000000 = 0 %start
         ! Class A
         r_mask = 16_FF000000
      %else %if address & 16_C0000000 = 16_80000000
         ! Class B
         r_mask = 16_FFFF0000
      %else %if address & 16_E0000000 = 16_C0000000
         ! Class C
         r_mask = 16_FFFFFF00
      %else
         ! Unknown -- ignore it
         signal semaphore(route semaphore)
         %return
      %finish
      r_network = address & r_mask
      r_gateway = 0;  ! -> direct
      r_metric = metric
      r_stamp = infinity
      r_via = via
      routes = routes + 1
      local routes = local routes + 1
      signal semaphore(route semaphore)
      stats_RIP updated = stats_RIP updated + 1
%end

%routine add route(%integer network, gateway, metric, via)
   %record(route fm)%name r
   %integer i, mask
      %if control & trace RIP in # 0 %start
         printstring("   To: ");  print INet address(network)
         printstring(" via ");  print INet address(gateway)
         printstring("  metric ");  write(metric, 0)
         printstring(" from ")
         %if via = 1 %then printstring("slow") %else printstring("fast")
         newline
      %finish
      %return %if network = 0 %or metric >= unreachable
      %if network & 16_80000000 = 0 %start
         ! Class A
         mask = 16_FF000000
      %else %if network & 16_C0000000 = 16_80000000
         ! Class B
         mask = 16_FFFF0000
      %else %if network & 16_E0000000 = 16_C0000000
         ! Class C
         mask = 16_FFFFFF00
      %else
         ! Unknown -- ignore it
         %return
      %finish
      network = network & mask
      semaphore wait(route semaphore)
      ! First find out if it's there already
      %for i = 1, 1, routes %cycle
         r == route table(i)
         %if r_network = network %start
            ! Found it
            !! printstring("Slot ");  write(i, 0)
            !! printstring(" -> ");  print INet address(r_gateway)
            !! printstring("  metric ");  write(r_metric, 0);  newline
            %if gateway = r_gateway %or metric < r_metric %start
               ! Metric update from the gateway, or a better route.
               ! Track it, and tally it if it's a genuine update.
               stats_RIP updated = stats_RIP updated + 1 %if metric # r_metric
               r_gateway = gateway
               r_metric = metric
               r_stamp = stamp
               r_via = via
            %finish
            ! Nothing (more) to do
            signal semaphore(route semaphore)
            %return
         %finish
      %repeat
      ! If we get here we haven't found the entry.  For convenience
      ! we'll add it at the end of our tables meantime....
      r == route table(routes + 1)
      r_network = network
      r_mask = mask
      r_gateway = gateway
      r_metric = metric
      r_stamp = stamp
      r_via = via
      routes = routes + 1
      signal semaphore(route semaphore)
      stats_RIP updated = stats_RIP updated + 1
%end


! UDP comms stuff

%ownrecord(mailbox fm)%name request box

%ownrecord(semaphore fm) RX semaphore = 0
%ownrecord(mailbox fm) RX mailbox = 0
%ownrecord(INet user request fm) RX request = 0
%owninteger RX unit = 0

%ownrecord(semaphore fm) TX semaphore = 0
%ownrecord(mailbox fm) TX mailbox = 0
%ownrecord(INet user request fm) TX request = 0
%owninteger TX unit = 0

%ownrecord(semaphore fm) advertiser semaphore = 0

%recordformat connect fm(%integer ra, rp, lp)


! Peer daemon listener

%routine listener process
   %owninteger first time = 0
   %record(connect fm) c
   %record(RIP packet fm) p
   %record(INet user request fm)%name reply
   %integer i, n
      open input(2, ":N");  select input(2)
      open output(2, ":T");  select output(2)
      ! Attach ourselves to the routed UDP port
      RX request_key = addr(RX request)
      RX request_code = UDP allocate unit request
      send message(RX request, request box, RX mailbox)
      reply == receive message(RX mailbox)
      %if reply ## RX request %start
         printstring("Dubious UDP reply (allocate)");  newline
         %stop
      %else %if reply_status # 0
         printstring("UDP reply (allocate): ");  write(reply_status, 0)
         newline
         %stop
      %finish
      !! printstring("Routed RX unit ");  write(reply_unit, 0);  newline
      RX unit = reply_unit
      ! Define UDP context
      c_ra = 0;  c_rp = 0
      c_lp = routed port
      RX request_unit = RX unit
      RX request_key = addr(RX request)
      RX request_code = UDP define context request
      RX request_buffer == byteinteger(addr(c))
      RX request_bytes = 12
      send message(RX request, request box, RX mailbox)
      reply == receive message(RX mailbox)
      %if reply ## RX request %start
         printstring("Dubious UDP reply (allocate)");  newline
         %stop
      %else %if reply_status # 0
         printstring("UDP reply (allocate): ");  write(reply_status, 0)
         newline
         %stop
      %finish
      !! printstring("Context defined: ");  print INet address(c_ra)
      !! space;  write(c_rp, 0);  space;  write(c_lp, 0);  newline

      %cycle
         ! Loop, reading RIP packets.
         RX request_unit = RX unit
         RX request_key = addr(RX request)
         RX request_code = UDP receive request
         RX request_timeout = 0;  ! None
         RX request_buffer == byteinteger(addr(p))
         send message(RX request, request box, RX mailbox)
         reply == receive message(RX mailbox)
!L!      lights or A(routed light)
         %if reply ## RX request %start
            printstring("Dubious UDP reply (receive)");  newline
            %stop
         %else %if reply_status # 0
            printstring("UDP reply (receive): ");  write(reply_status, 0)
            newline
            %stop
         %else %if p_op = 2 %and p_version = 1
            stats_RIP packets in = stats_RIP packets in + 1
            %if first time = 0 %and reply_interface = 2 %start
               ! First message from the fast ether, so set our IP address
               set ether IP address(reply_peer)
               first time = 1
            %finish
            n = (reply_bytes - 4) // RIP entry size
            %if control & trace RIP in # 0 %start
               printstring("RIP from ");  print INet address(reply_peer)
               write(n, 3);  printstring(" entries via ")
               %if reply_interface = 1 %then printstring("slow") %c
                                       %else printstring("fast")
               newline
            %finish
            %for i = 1, 1, n %cycle
               add route(p_e(i)_network, reply_peer, %c
                         p_e(i)_metric + 1, reply_interface) %c
                  %if p_e(i)_type = 2
            %repeat
         %finish
!L!      lights and A(\ routed light)
      %repeat
%end


! Route advertiser

%routine advertise to(%integer address, n, %record(RIP packet fm)%name p)
   %record(INet user request fm)%name reply
   %record(connect fm) c
      %return %if address = 0
      ! Define UDP context
      c_ra = address
      c_rp = routed port ! 16_20000;  ! Outbound only
      c_lp = routed port
      !! printstring("Defining context: ");  phex(c_ra)
      !! space;  phex(c_rp);  space;  phex(c_lp);  newline
      TX request_unit = TX unit
      TX request_key = addr(TX request)
      TX request_code = UDP define context request
      TX request_buffer == byteinteger(addr(c))
      TX request_bytes = 12
      send message(TX request, request box, TX mailbox)
      reply == receive message(TX mailbox)
      %if reply ## TX request %start
         printstring("Dubious UDP reply (define)");  newline
         %return
      %else %if reply_status # 0
         printstring("UDP reply (define): ");  write(reply_status, 0)
         newline
         %return
      %finish
      !! printstring("Context defined: ");  print INet address(c_ra)
      !! space;  write(c_rp, 0);  space;  write(c_lp, 0);  newline
      TX request_unit = TX unit
      TX request_key = addr(TX request)
      TX request_code = UDP data request
      TX request_buffer == byteinteger(addr(p))
      TX request_bytes = n * RIP entry size + 4
      send message(TX request, request box, TX mailbox)
      reply == receive message(TX mailbox)
      %if reply ## TX request %start
         printstring("Dubious UDP reply (send)");  newline
         %return
      %else %if reply_status # 0
         printstring("UDP reply (send): ");  write(reply_status, 0)
         newline
         %return
      %finish
      !! printstring("Sent RIP to ");  print INet address(address);  newline
      stats_RIP packets out = stats_RIP packets out + 1
%end

%routine advertiser process
   %record(RIP packet fm) p
   %record(RIP entry fm)%name e
   %record(route fm)%name r
   %record(INet user request fm)%name reply
   %integer i, n, x
      open input(2, ":N");  select input(2)
      open output(2, ":T");  select output(2)
      ! Get ourselves a UDP unit
      TX request_key = addr(TX request)
      TX request_code = UDP allocate unit request
      send message(TX request, request box, TX mailbox)
      reply == receive message(TX mailbox)
      %if reply ## TX request %start
         printstring("Dubious UDP reply (allocate)");  newline
         %stop
      %else %if reply_status # 0
         printstring("UDP reply (allocate): ");  write(reply_status, 0)
         newline
         %stop
      %finish
      !! printstring("Routed TX unit ");  write(reply_unit, 0);  newline
      TX unit = reply_unit

      %cycle
         semaphore wait(advertiser semaphore)
!L!      lights or B(routed light)
         %for x = 1, 1, local routes %cycle
            ! Create a RIP packet
            p = 0;  n = 0
            p_op = 2;  p_version = 1
            semaphore wait(route semaphore)
            %for i = 1, 1, routes %cycle
               r == route table(i)
               %if r_via # x %and r_metric <= unreachable %start
                  n = n + 1
                  e == p_e(n)
                  e_type = 2;  e_network = r_network
                  e_metric = r_metric
                  %if control & trace RIP out # 0 %start
                     printstring("Advertising ");  print INet address(e_network)
                     printstring(" metric ");  write(e_metric, 0)
                     printstring(" to ")
                     %if x = 1 %then printstring("slow") %else printstring("fast")
                     newline
                  %finish
               %finish
            %repeat
            signal semaphore(route semaphore)
            ! Now send it on its way
            %if n > 0 %start
               %if x = 1 %start
                  advertise to((slow IP address & 16_FFFFFF00) ! 255, n, p)
               %else
                  advertise to(ether IP address & 16_FFFF0000, n, p)
               %finish
            %finish
         %repeat
!L!      lights and B(\ routed light)
      %repeat
%end


! Clock, initialisation

%externalroutine routed clock
   %record(route fm)%name r
   %integer i, d
      stamp = stamp + 1
      semaphore wait(route semaphore)
      %for i = 1, 1, routes %cycle
         r == route table(i)
         d = stamp - r_stamp
         %if d >= forget interval %start
            r_metric = dead
            !stats_RIP dropped = stats_RIP dropped + 1
         %else %if d >= alive interval
            r_metric = unreachable
            stats_RIP dropped = stats_RIP dropped + 1
         %finish
      %repeat
      signal semaphore(route semaphore)
      signal semaphore(advertiser semaphore) %c
         %if stamp & 1 # 0 %and local routes > 1 %and control & hush RIP = 0
%end

%externalroutine start routed
   %record(process fm)%name p
   %integer x
   %label listener, advertiser
      %if FS lookup(INet mailbox name, x) %start
         request box == record(x)
      %else
         printstring("Routed: no INet mailbox??");  newline
         %stop
      %finish
      setup semaphore(RX semaphore)
      setup mailbox(RX mailbox, RX semaphore)
      setup message(RX request, size of(RX request))
      setup semaphore(TX semaphore)
      setup mailbox(TX mailbox, TX semaphore)
      setup message(TX request, size of(TX request))
      setup semaphore(advertiser semaphore)
      setup semaphore(route semaphore)
      signal semaphore(route semaphore)
      FS insert("INET_ROUTE_TABLE", addr(route table(1)))
      p == create process(listener size, addr(listener), listener priority, nil)
      p == create process(advertiser size, addr(advertiser), advertiser priority, nil)
      !! printstring("Routed started");  newline
      %return

listener:
      listener process
advertiser:
      advertiser process
%end

%end %of %file
