2017-03-19 02:31:25 +11:00
|
|
|
-- I, 20kdc, release this into the public domain.
|
|
|
|
-- No warranty is provided, implied or otherwise.
|
|
|
|
|
2017-03-20 11:14:56 +11:00
|
|
|
-- 'Copper' networking - "The Routing Library".
|
2017-03-18 20:34:13 +11:00
|
|
|
-- This is meant as a portable (even into OC) library for networking.
|
|
|
|
-- This 'outer function' is the instantiator.
|
|
|
|
|
|
|
|
-- Note that it is probably possible to cause this code to run out of
|
|
|
|
-- memory in several hilarious ways.
|
2017-03-20 11:14:56 +11:00
|
|
|
-- I've taken the approach that reduction of code .
|
2017-03-18 20:34:13 +11:00
|
|
|
|
|
|
|
-- Interfaces have no meaning, since addresses are names.
|
|
|
|
-- Which "side" a system is on is irrelevant.
|
2017-03-20 11:14:56 +11:00
|
|
|
-- (Unless you're developing a hierarchial gateway, in which case this library isn't for you
|
|
|
|
-- as it follows a default set of routing rules. Switch to cdlib for fine-grained control.)
|
|
|
|
|
2017-03-18 20:34:13 +11:00
|
|
|
-- For sending, the following function is used:
|
|
|
|
-- transmit(nodeId, message)
|
|
|
|
-- The nodeId is a string or number that has been given via culib.input,
|
|
|
|
-- or nil for broadcast.
|
|
|
|
-- It's more of a suggestion than a requirement to check nodeId.
|
|
|
|
|
|
|
|
-- The message is always a string.
|
|
|
|
-- This mirrors the message format usable by sendPacket and onReceive.
|
|
|
|
|
|
|
|
-- "onReceive" is a function which is called when a packet is decoded.
|
|
|
|
-- onReceive(namefrom, nameto, data)
|
|
|
|
|
|
|
|
-- "time" is a function which returns the real time, in seconds.
|
|
|
|
-- It need not be precise.
|
|
|
|
-- (This is used for caches.)
|
2017-03-20 11:14:56 +11:00
|
|
|
|
|
|
|
local cdlib = require("cdlib")
|
|
|
|
|
2017-03-18 20:34:13 +11:00
|
|
|
return function (hostname, transmit, onReceive, time)
|
|
|
|
|
|
|
|
-- How many packets need to be stored in seenBefore's keyspace
|
|
|
|
-- before 'panic' is the best response?
|
2017-03-18 23:28:46 +11:00
|
|
|
local tuningMaxSeenBeforeCountBeforeEmergencyFlush = 0x300
|
2017-03-18 20:34:13 +11:00
|
|
|
|
2017-03-20 11:14:56 +11:00
|
|
|
-- Prevents OOM by LKR cache flooding - how many entries can the LKR have, max?
|
|
|
|
-- (Though spamming packets from many sources is now a viable method for dropping LKR,
|
|
|
|
-- it used to be a viable OOM method.)
|
|
|
|
-- Note that setting this to 0 or less will effectively result in a value of 1.
|
|
|
|
local tuningMaxLKREntries = 0x400
|
|
|
|
|
2017-03-19 00:33:26 +11:00
|
|
|
-- Expect another packet after this amount of time,
|
|
|
|
-- or else clear the known receivers cache entry.
|
2017-03-20 08:54:57 +11:00
|
|
|
-- Minimum should be less or equal to tuningAttempts *
|
|
|
|
-- tuningAttemptTime in relib.
|
|
|
|
local tuningExpectContinue = 15 + math.random(15)
|
2017-03-18 20:34:13 +11:00
|
|
|
|
|
|
|
-- Flush the loop detector every so often.
|
|
|
|
-- This is not a complete clear.
|
2017-03-19 00:33:26 +11:00
|
|
|
local tuningFlushLoopDetector = 60
|
|
|
|
|
2017-03-18 23:28:46 +11:00
|
|
|
-- Do not change this value unless protocol has changed accordingly.
|
|
|
|
local tuningAutorejectLen = 1506
|
2017-03-18 20:34:13 +11:00
|
|
|
|
|
|
|
local loopDetectorNext = time() + tuningFlushLoopDetector
|
|
|
|
|
|
|
|
-- Packets that have been seen before.
|
|
|
|
-- The values are the amount of times a packet has been seen.
|
|
|
|
-- This is flushed every tuningFlushLoopDetector seconds -
|
|
|
|
-- the flushing decrements the value until it reaches 0,
|
|
|
|
-- so packets which have looped before get a longer timeout.
|
|
|
|
local seenBefore = {}
|
|
|
|
local seenBeforeCount = 0
|
|
|
|
|
|
|
|
-- [address] = {
|
2017-03-19 00:33:26 +11:00
|
|
|
-- node, -- the node that a message was received from
|
|
|
|
-- expiry
|
2017-03-18 20:34:13 +11:00
|
|
|
-- }
|
2017-03-18 22:32:14 +11:00
|
|
|
local lastKnownReceiver = {}
|
2017-03-20 11:14:56 +11:00
|
|
|
-- How many LKR entries are there?
|
|
|
|
local lkrCacheCount = 0
|
2017-03-19 00:33:26 +11:00
|
|
|
|
2017-03-18 20:34:13 +11:00
|
|
|
local function refresh()
|
|
|
|
local t = time()
|
|
|
|
if t >= loopDetectorNext then
|
|
|
|
for k, v in pairs(seenBefore) do
|
|
|
|
local n = v - 1
|
|
|
|
if n > 0 then
|
|
|
|
seenBefore[k] = n
|
|
|
|
else
|
|
|
|
seenBefore[k] = nil
|
|
|
|
seenBeforeCount = seenBeforeCount - 1
|
|
|
|
end
|
|
|
|
end
|
2017-03-18 23:28:46 +11:00
|
|
|
loopDetectorNext = t + tuningFlushLoopDetector
|
2017-03-18 20:34:13 +11:00
|
|
|
end
|
2017-03-18 22:32:14 +11:00
|
|
|
for k, v in pairs(lastKnownReceiver) do
|
|
|
|
if t >= v[2] then
|
2017-03-19 00:33:26 +11:00
|
|
|
--print("It was decided LKV[" .. k .. "] was out of date @ " .. v[2] .. " by " .. hostname)
|
|
|
|
lastKnownReceiver[k] = nil
|
2017-03-18 22:32:14 +11:00
|
|
|
end
|
|
|
|
end
|
2017-03-18 20:34:13 +11:00
|
|
|
end
|
|
|
|
|
2017-03-20 11:14:56 +11:00
|
|
|
-- Used to clean up LKR entries to prevent OOM.
|
|
|
|
local function removeOldestLKR()
|
|
|
|
local lowest = nil
|
|
|
|
local lowestExpiry = math.huge
|
|
|
|
for k, v in pairs(lastKnownReceiver) do
|
|
|
|
if v[2] < lowestExpiry then
|
|
|
|
lowest = k
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if lowest then
|
|
|
|
lastKnownReceiver[lowest] = nil
|
|
|
|
lkrCacheCount = lkrCacheCount - 1
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-03-18 20:34:13 +11:00
|
|
|
local culib = {}
|
|
|
|
|
|
|
|
-- Can be changed.
|
|
|
|
culib.hostname = hostname
|
2017-03-19 00:33:26 +11:00
|
|
|
|
|
|
|
-- Stats.
|
|
|
|
culib.lkrCacheMisses = 0
|
|
|
|
culib.lkrCacheHits = 0
|
|
|
|
|
2017-03-18 20:34:13 +11:00
|
|
|
culib.input = function (node, message)
|
2017-03-18 22:32:14 +11:00
|
|
|
local t = time()
|
2017-03-18 23:28:46 +11:00
|
|
|
|
|
|
|
-- Eliminate the hops value first of all.
|
|
|
|
local hops = message:byte(1)
|
|
|
|
message = message:sub(2)
|
|
|
|
|
2017-03-18 20:34:13 +11:00
|
|
|
if seenBefore[message] then
|
|
|
|
seenBefore[message] = seenBefore[message] + 1
|
|
|
|
return
|
|
|
|
else
|
2017-03-18 23:28:46 +11:00
|
|
|
seenBefore[message] = 2
|
2017-03-18 20:34:13 +11:00
|
|
|
seenBeforeCount = seenBeforeCount + 1
|
|
|
|
if seenBeforeCount > tuningMaxSeenBeforeCountBeforeEmergencyFlush then
|
|
|
|
-- Panic
|
|
|
|
seenBeforeCount = 0
|
|
|
|
seenBefore = {}
|
|
|
|
end
|
|
|
|
end
|
2017-03-18 23:28:46 +11:00
|
|
|
|
2017-03-20 11:14:56 +11:00
|
|
|
-- Begin parsing.
|
2017-03-18 22:32:14 +11:00
|
|
|
|
2017-03-20 11:14:56 +11:00
|
|
|
local fnam, tnam, data = cdlib.decodeNoHops(message)
|
|
|
|
if not data then
|
|
|
|
return
|
|
|
|
end
|
2017-03-18 22:32:14 +11:00
|
|
|
|
2017-03-20 11:14:56 +11:00
|
|
|
if data:len() > tuningAutorejectLen then
|
2017-03-18 23:28:46 +11:00
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2017-03-19 03:12:31 +11:00
|
|
|
if fnam ~= "*" then
|
2017-03-20 11:14:56 +11:00
|
|
|
if not lastKnownReceiver[fnam] then
|
|
|
|
-- if, not while, because if someone ignores my note above
|
|
|
|
-- and sets the tuning to 0 it would crash otherwise. *sigh*
|
|
|
|
if lkrCacheCount >= tuningMaxLKREntries then
|
|
|
|
removeOldestLKR()
|
|
|
|
end
|
|
|
|
lkrCacheCount = lkrCacheCount + 1
|
|
|
|
end
|
2017-03-19 03:12:31 +11:00
|
|
|
lastKnownReceiver[fnam] = {node, t + tuningExpectContinue}
|
|
|
|
end
|
2017-03-18 22:32:14 +11:00
|
|
|
|
2017-03-20 11:14:56 +11:00
|
|
|
onReceive(fnam, tnam, data)
|
2017-03-18 20:34:13 +11:00
|
|
|
if culib.hostname == tnam then return end
|
2017-03-18 22:32:14 +11:00
|
|
|
|
2017-03-18 20:34:13 +11:00
|
|
|
-- Redistribution of messages not aimed here
|
2017-03-18 23:28:46 +11:00
|
|
|
if hops == 255 then
|
2017-03-20 11:14:56 +11:00
|
|
|
-- Don't redistribute
|
2017-03-18 23:28:46 +11:00
|
|
|
return
|
|
|
|
else
|
2017-03-20 11:14:56 +11:00
|
|
|
-- Prepend the hops byte that got removed earlier
|
|
|
|
message = string.char(hops + 1) .. message
|
2017-03-18 23:28:46 +11:00
|
|
|
end
|
|
|
|
|
2017-03-18 22:32:14 +11:00
|
|
|
local lkr = lastKnownReceiver[tnam]
|
|
|
|
if lkr then
|
2017-03-19 00:33:26 +11:00
|
|
|
culib.lkrCacheHits = culib.lkrCacheHits + 1
|
2017-03-20 11:14:56 +11:00
|
|
|
transmit(lkr[1], message)
|
2017-03-18 22:32:14 +11:00
|
|
|
else
|
2017-03-19 00:33:26 +11:00
|
|
|
culib.lkrCacheMisses = culib.lkrCacheMisses + 1
|
2017-03-20 11:14:56 +11:00
|
|
|
transmit(nil, message)
|
2017-03-18 22:32:14 +11:00
|
|
|
end
|
|
|
|
end
|
|
|
|
culib.refresh = refresh
|
2017-03-18 20:34:13 +11:00
|
|
|
culib.output = function (fnam, tnam, message)
|
|
|
|
onReceive(fnam, tnam, message)
|
|
|
|
if tnam == culib.hostname then return end
|
2017-03-20 11:14:56 +11:00
|
|
|
local m = cdlib.encode(0, fnam, tnam, message)
|
2017-03-18 22:32:14 +11:00
|
|
|
transmit(nil, m)
|
2017-03-18 20:34:13 +11:00
|
|
|
end
|
|
|
|
return culib
|
|
|
|
end
|