From f34936c10cda74d68299763066f51316979ef4b5 Mon Sep 17 00:00:00 2001 From: Izaya Date: Fri, 16 Feb 2018 21:36:00 +1100 Subject: [PATCH] added a L3 implementation and updated the documentation --- OpenOS/etc/rc.d/minitel.lua | 213 ++++++++++++++++++++++++++++++++++++ protocol.md | 43 ++++++++ 2 files changed, 256 insertions(+) create mode 100644 OpenOS/etc/rc.d/minitel.lua create mode 100644 protocol.md diff --git a/OpenOS/etc/rc.d/minitel.lua b/OpenOS/etc/rc.d/minitel.lua new file mode 100644 index 0000000..e1e2772 --- /dev/null +++ b/OpenOS/etc/rc.d/minitel.lua @@ -0,0 +1,213 @@ +--[[ +packet format: +packetID: random string to differentiate +packetType: + - 0: unreliable + - 1: reliable, requires ack + - 2: ack packet +destination: end destination hostname +sender: original sender of packet +data: the actual packet data, duh. +]]-- + +local listeners = {} +local timers = {} + +local event = require "event" +local component = require "component" +local computer = require "computer" +local hostname = computer.address() +local listener = false +local dbug = false +local modems = {} +local port = 4096 +local retry = 30 + +--[[ +LKR format: +address { + local hardware address + remote hardware address + time last received +} +]]-- +local rcache = {} +local rctime = 30 + +--[[ +packet queue format: +{ + packetID, + packetType + destination, + data, + timestamp, + attempts +} +]]-- +local pqueue = {} + +-- packet cache: [packet ID]=uptime +local pcache = {} +local pctime = 30 + +local function dprint(...) + if dbug then + print(...) + end +end + +function start() + local f=io.open("/etc/hostname","rb") + if f then + hostname = f:read() + f:close() + end + print("Hostname: "..hostname) + if listener then return end + for a,t in component.list("modem") do + modems[#modems+1] = component.proxy(a) + end + for k,v in ipairs(modems) do + v.open(port) + print("Opened port "..port.." on "..v.address) + end + + local function genPacketID() + local npID = "" + for i = 1, 16 do + npID = npID .. string.char(math.random(32,126)) + end + return npID + end + + local function sendPacket(packetID,packetType,dest,sender,vport,data) + if rcache[dest] then + dprint("Cached", rcache[dest][1],"send",rcache[dest][2],port,packetID,packetType,dest,sender,vport,data) + component.invoke(rcache[dest][1],"send",rcache[dest][2],port,packetID,packetType,dest,sender,vport,data) + else + dprint("Not cached", port,packetID,packetType,dest,sender,vport,data) + for k,v in pairs(modems) do + v.broadcast(port,packetID,packetType,dest,sender,vport,data) + end + end + end + + local function pruneCache() + for k,v in pairs(rcache) do + dprint(k,v[3],computer.uptime()) + if v[3] < computer.uptime() then + rcache[k] = nil + dprint("pruned "..k.." from routing cache") + end + end + for k,v in pairs(pcache) do + if v < computer.uptime() then + pcache[k] = nil + dprint("pruned "..k.." from packet cache") + end + end + end + + local function checkPCache(packetID) + dprint(packetID) + for k,v in pairs(pcache) do + dprint(k) + if k == packetID then return true end + end + return false + end + + local function processPacket(_,localModem,from,pport,_,packetID,packetType,dest,sender,vport,data) + pruneCache() + if pport == port then + dprint(port,vport,packetType,dest) + if checkPCache(packetID) then return end + if dest == hostname then + if packetType == 1 then + sendPacket(genPacketID(),2,sender,hostname,vport,packetID) + end + if packetType == 2 then + dprint("Dropping "..data.." from queue") + pqueue[data] = nil + computer.pushSignal("net_ack",data) + end + if packetType ~= 2 then + computer.pushSignal("net_msg",sender,vport,data) + end + end + if not rcache[sender] then + dprint("rcache: "..sender..":", localModem,from,computer.uptime()) + rcache[sender] = {localModem,from,computer.uptime()+rctime} + end + if not pcache[packetID] then + pcache[packetID] = computer.uptime()+pctime + end + end + end + + listeners["modem_message"]=processPacket + event.listen("modem_message",processPacket) + print("Started packet listening daemon: "..tostring(processPacket)) + + local function queuePacket(_,ptype,to,vport,data) + npID = genPacketID() + pqueue[npID] = {ptype,to,vport,data,0,0} + dprint(npID,table.unpack(pqueue[npID])) + end + + listeners["net_send"]=queuePacket + event.listen("net_send",queuePacket) + print("Started packet queueing daemon: "..tostring(queuePacket)) + + local function packetPusher() + for k,v in pairs(pqueue) do + if v[5] < computer.uptime() then + dprint(k,v[1],v[2],hostname,v[3],v[4]) + sendPacket(k,v[1],v[2],hostname,v[3],v[4]) + if v[1] ~= 1 or v[6] == 255 then + pqueue[k] = nil + else + pqueue[k][5]=computer.uptime()+retry + pqueue[k][6]=pqueue[k][6]+1 + end + end + end + end + + timers[#timers+1]=event.timer(0,packetPusher,math.huge) + print("Started packet pusher: "..tostring(timers[#timers])) + + event.listen("net_ack",dprint) +end + +function stop() + for k,v in pairs(listeners) do + event.ignore(k,v) + print("Stopped listener: "..tostring(v)) + end + for k,v in pairs(timers) do + event.cancel(v) + print("Stopped timer: "..tostring(v)) + end +end + +function debug() + dbug = not dbug +end +function set_retry(sn) + retry = tonumber(sn) or 30 + print("retry = "..tostring(retry)) +end +function set_pctime(sn) + pctime = tonumber(sn) or 30 + print("pctime = "..tostring(pctime)) +end +function set_rctime(sn) + rctime = tonumber(sn) or 30 + print("rctime = "..tostring(rctime)) +end +function set_port(sn) + port = tonumber(sn) or 4096 + print("port = "..tostring(port)) +end diff --git a/protocol.md b/protocol.md new file mode 100644 index 0000000..c8dd4a6 --- /dev/null +++ b/protocol.md @@ -0,0 +1,43 @@ +# Minitel - An easy to implement LAN protocol + +## Language +Node - a device of some description on a Minitel network, ie a computer, server, microcontroller or drone. + +## Behavior + +### Receiving +Upon a node receiving a message addressed to itself, it should: + +1. Check if the packet ID has been seen before, and if so, drop the packet +2. Check if the packet is addressed to the node, and if so, queue it as a net_msg event +3. If the packet is indeed addressed to this node, and the packet type is 1 (reliable), send an acknowledgement packet. +4. Optional: Add the sender to the address cache if it isn't already in the cache +5. Optional: If the packet is addressed to a different node, repeat the packet, preferably respecting the address cache + +### Optional: Meshing +If a message is not addressed to a node, and the node has not seen the packet ID before, the node should repeat it. Whether via the address in the cache or by broadcast, it should be passed on, and the hardware address added to the cache as the sender. + +### Optional: Address caching +Each machine should keep a last known route cache. The exact format is up to the implementation, but it will need: + +- hostname +- hardware address +- time when added to cache + +It is recommended to keep the data in the cache for 30 seconds or so, then drop it, though being user-configurable is ideal. + +When sending a message, check the cache for the given destination. If there is a hardware address in the cache for the destination, send the message straight to that address. Otherwise, broadcast the message. + +## Packet Format +Packets are made up of separated parts, as allowed by OpenComputers modems. + +- packetID: random string to differentiate packets from each other +- packetType, number: + 0. unreliable + 1. reliable, requires acknowledgement + 2. acknowledgement packet +- destination: end destination hostname, string +- sender: original sender of packet, string +- port: virtual port, number \< 65536 +- data: the actual packet data, or in the case of an acknowledgement packet, the original packet ID, string +