From f6387530a422a335f5fceb4c12c16aca2be57b52 Mon Sep 17 00:00:00 2001 From: gamemanj Date: Tue, 28 Mar 2017 14:23:30 +0100 Subject: [PATCH] OC Internet Card transport and a LuaSocket server for it. --- oc/tcpdial.lua | 78 ++++++++++++++++++++++++++++++ tcprouter.lua | 129 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 207 insertions(+) create mode 100644 oc/tcpdial.lua create mode 100644 tcprouter.lua diff --git a/oc/tcpdial.lua b/oc/tcpdial.lua new file mode 100644 index 0000000..591aeff --- /dev/null +++ b/oc/tcpdial.lua @@ -0,0 +1,78 @@ +-- I, 20kdc, release this into the public domain. +-- No warranty is provided, implied or otherwise. + +-- Light Copper Base node that communicates to a server via an +-- Internet Card. +-- Only handles one modem for now. + +local args = {...} +if #args ~= 3 then error("name, tcphost, tcpport") end + +local component = require("component") +local cdlib = require("cdlib") +local event = require("event") + +-- Adjust to taste. +local tcp = require("internet").open(tostring(args[2]), tonumber(args[3])) + +local md = component.modem +md.open(4957) + +tcp:setTimeout(0.05) +tcp:write(string.char((#(args[1])) - 1) .. args[1]) +tcp:flush() + +print("TCP up") + +local function verify(d) + local hops, src, dst, data = cdlib.decode(d) + if not data then return end + -- Just a bit of filtering + if dst:sub(1, 1) ~= "<" then return end + if d:len() > 2021 then return end + return true +end + +local function readByte() + while true do + local ok, err = pcall(tcp.read, tcp, 1) + if ok then return err end + -- not nice :( + if err:find("timeout") then + coroutine.yield() + else + error(err) + end + end +end +local function readerRoutine() + while true do + local h = readByte() + local l = readByte() + local sz = string.byte(l) + (string.byte(h) * 256) + print("Incoming packet size " .. sz) + local dat = "" + for i = 1, sz do + dat = dat .. readByte() + end + md.broadcast(4957, "copper", dat) + end +end + +local primary = coroutine.create(readerRoutine) +while true do + local et, _, _, p, _, m, d = event.pull(0.1) + local ok, o = coroutine.resume(primary) + if not ok then error(o) end + if et == "modem_message" and p == 4957 then + if m == "copper" then + -- Incoming Copper packet. + if verify(d) then + local h = math.floor(d:len() / 256) + local l = d:len() % 256 + tcp:write(string.char(h, l) .. d) + tcp:flush() + end + end + end +end diff --git a/tcprouter.lua b/tcprouter.lua new file mode 100644 index 0000000..5248921 --- /dev/null +++ b/tcprouter.lua @@ -0,0 +1,129 @@ +-- TCP-based 'absolute root' node +-- Used to connect servers together. +-- A smart routing node which includes hierarchial gateways. +-- (Note: Multiple "absolute root" nodes can be used at once, +-- and things should work as long as there are no conflicts and +-- everybody who wants to communicate has a common root node. +-- A less efficient but potentially better approach would be to just +-- have TCP-based Copper meshnet nodes +-- and run the gateways on the MC servers.) + +local socket = require("socket") +local t = socket.bind("*", 4957) + +local cdlib = require("cdlib") + +local sockets = {} + +local function getbyte(s) + s:settimeout(0.05) + while true do + local d, e = s:receive(1) + if not d then + if e ~= "timeout" then + return + end + coroutine.yield() + else + return d + end + end +end +local function getpacket(s) + local h = getbyte(s) + if not h then error("connection failed") end + local l = getbyte(s) + if not l then error("framing bad") end + local sz = string.byte(l) + (string.byte(h) * 256) + if sz > 2021 then error("packet too large") end + local data = "" + for i = 1, sz do + local dbt = getbyte(s) + if not dbt then error("terminated early") end + data = data .. dbt + end + return cdlib.decode(data) +end + +local function checkLen(name) + if name:len() == 0 then return end + if name:len() > 256 then return end + return name +end + +local function translateSend(hops, src, dst, data, srname, tgsock, tgname) + if src:sub(1, 1) == "<" then return end + if dst:sub(1, tgname:len() + 2) ~= "<" .. tgname .. "/" then return end + -- Ok, all rejection rules have been handled + src = "<" .. srname .. "/" .. src + dst = dst:sub(tgname:len() + 3) + src, dst = checkLen(src), checkLen(dst) + if src and dst then + local enc = cdlib.encode(hops, src, dst, data) + local h = math.floor(enc:len() / 256) + local l = enc:len() % 256 + local frame = string.char(h, l) + tgsock:send(frame .. enc) + end +end + +-- The main coroutine. +-- Moves messages around the system. +local function messageroutine(tbl) + local b = getbyte(tbl[2]) + if not b then error("Didn't even send name") end + local name = "" + -- 0 is not a typo, this follows Copper name format. + for i = 0, string.byte(b) do + local b2 = getbyte(tbl[2]) + if not b2 then error("Didn't even complete name") end + name = name .. b2 + end + print("confirmed name " .. name) + tbl[3] = name + while true do + local hops, src, dst, data = getpacket(tbl[2]) + if not data then + error("Bad Copper packet") + end + print("packet", src, dst) + if hops ~= 255 then + for _, v in ipairs(sockets) do + if v[3] then + if v ~= tbl then + translateSend(hops + 1, src, dst, data, name, v[2], v[3]) + end + end + end + end + end +end +while true do + t:settimeout(0.05) + local ns = t:accept() + if ns then + -- Note: packets are ~500 bytes, Copper data can be ~2KB + -- but it isn't USUALLY that way. + -- ns:setoption("tcp-nodelay", true) + ns:setoption("keepalive", true) + print("incoming") + local co = coroutine.create(messageroutine) + local tbl = {co, ns} + table.insert(sockets, tbl) + local ok, r = coroutine.resume(co, tbl) + if not ok then + -- do cleanup later + print("connection died quick", r) + end + end + local i = 1 + while i <= #sockets do + local ok, r = coroutine.resume(sockets[i][1]) + if not ok then + print("dropping conn", r) + table.remove(sockets, i)[2]:close() + else + i = i + 1 + end + end +end