mirror of
https://github.com/AR2000AR/openComputers_codes.git
synced 2025-09-08 14:41:14 +02:00
[network] rework file structure and udp
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
local arp = require("layers.arp")
|
||||
local ethernetType = require("layers.ethernet").TYPE
|
||||
local ipv4 = require("layers.ipv4")
|
||||
local arp = require("network.arp")
|
||||
local ethernetType = require("network.ethernet").TYPE
|
||||
local ipv4 = require("network.ipv4")
|
||||
|
||||
for k, v in pairs(arp.list(arp.HARDWARE_TYPE.ETHERNET, ethernetType.IPv4)) do
|
||||
print(ipv4.address.tostring(v[1]), v[2])
|
||||
|
@@ -1,6 +1,6 @@
|
||||
local networklib = require("network")
|
||||
local ethernet = require("layers.ethernet")
|
||||
local ipv4 = require("layers.ipv4")
|
||||
local ethernet = require("network.ethernet")
|
||||
local ipv4 = require("network.ipv4")
|
||||
local shell = require("shell")
|
||||
|
||||
local args, opts = shell.parse(...)
|
||||
|
@@ -1,28 +1,29 @@
|
||||
local network = require("network")
|
||||
local modem = require("component").modem
|
||||
local ipv4Address = require("layers").ipv4.address
|
||||
local shell = require("shell")
|
||||
local event = require("event")
|
||||
local thread = require("thread")
|
||||
local term = require("term")
|
||||
local os = require("os")
|
||||
local shell = require("shell")
|
||||
local event = require("event")
|
||||
local thread = require("thread")
|
||||
local term = require("term")
|
||||
local os = require("os")
|
||||
local socket = require("socket")
|
||||
|
||||
local args, opts = shell.parse(...)
|
||||
local udpInterface = network.interfaces[modem.address].udp
|
||||
local socket, reason
|
||||
|
||||
local args, opts = shell.parse(...)
|
||||
local udpSocket, reason = socket.udp()
|
||||
local listenerThread
|
||||
|
||||
|
||||
opts.p = tonumber(opts.p)
|
||||
opts.p = tonumber(opts.p) or 0
|
||||
opts.b = opts.b or "0.0.0.0"
|
||||
|
||||
---@param listenedSocket UDPSocket
|
||||
local function listenSocket(listenedSocket)
|
||||
repeat
|
||||
local msg = listenedSocket:reciveString()
|
||||
if (msg) then
|
||||
term.write(msg)
|
||||
checkArg(1, listenedSocket, 'table')
|
||||
while true do
|
||||
local datagram = listenedSocket:recieve()
|
||||
if (datagram) then
|
||||
term.write(datagram)
|
||||
end
|
||||
os.sleep()
|
||||
until not listenedSocket:isOpen()
|
||||
end
|
||||
end
|
||||
|
||||
local function help()
|
||||
@@ -37,7 +38,7 @@ local function help()
|
||||
end
|
||||
|
||||
event.listen("interrupted", function(...)
|
||||
if (socket) then socket:close() end
|
||||
if (udpSocket) then udpSocket:close() end
|
||||
if (listenerThread) then
|
||||
if (not listenerThread:join(3)) then
|
||||
listenerThread:kill()
|
||||
@@ -49,30 +50,28 @@ end
|
||||
if (opts.h or opts.help) then
|
||||
help()
|
||||
os.exit()
|
||||
elseif (opts.l and opts.u and (opts.p or tonumber(arg[1]))) then --listen UDP
|
||||
socket = udpInterface:open(opts.p or tonumber(arg[1]))
|
||||
assert(socket)
|
||||
print(string.format("Listening on port %d", socket:getLocalPort()))
|
||||
listenerThread = thread.create(listenSocket, socket)
|
||||
while socket:isOpen() do
|
||||
elseif (opts.l and opts.u and (tonumber(args[1]) or opts.p)) then --listen UDP
|
||||
assert(udpSocket:setsockname("*", tonumber(args[1]) or opts.p))
|
||||
--udpSocket:setCallback(listenSocket)
|
||||
print(string.format("Listening on %s:%d", udpSocket:getsockname()))
|
||||
listenerThread = thread.create(listenSocket, udpSocket)
|
||||
while true do
|
||||
--no remote addr/port. We cannot send msgs
|
||||
os.sleep()
|
||||
end
|
||||
socket:close()
|
||||
udpSocket:close()
|
||||
elseif (opts.u) then --connect UDP
|
||||
socket, reason = udpInterface:open(opts.p or tonumber(args[2]), ipv4Address.fromString(args[1]), tonumber(args[2]))
|
||||
if (not socket) then
|
||||
print("Could not open socket : " .. reason)
|
||||
os.exit(1)
|
||||
end
|
||||
assert(socket)
|
||||
print(string.format("Listening on port %d", socket:getLocalPort()))
|
||||
listenerThread = thread.create(listenSocket, socket)
|
||||
assert(udpSocket:setsockname(opts.b, opts.p))
|
||||
args[2] = assert(tonumber(args[2]), "Invalid port number")
|
||||
assert(udpSocket:setpeername(args[1], args[2]))
|
||||
--udpSocket:setCallback(listenSocket)
|
||||
print(string.format("Listening on %s:%d", udpSocket:getsockname()))
|
||||
listenerThread = thread.create(listenSocket, udpSocket)
|
||||
repeat
|
||||
local msg = term.read()
|
||||
if (msg) then socket:send(msg .. "\n") end
|
||||
until not msg or not socket:isOpen()
|
||||
socket:close()
|
||||
if (msg) then udpSocket:send(msg .. "\n") end
|
||||
until not msg
|
||||
udpSocket:close()
|
||||
else
|
||||
help()
|
||||
end
|
||||
|
8
network/bin/netstat.lua
Normal file
8
network/bin/netstat.lua
Normal file
@@ -0,0 +1,8 @@
|
||||
local network = require("network")
|
||||
local ipv4Address = require("network.ipv4").address
|
||||
|
||||
--=============================================================================
|
||||
|
||||
for _, info in pairs(network.udp:getInterface():getOpenPorts()) do
|
||||
print(string.format("UDP\t%s:%d\t%s:%d", ipv4Address.tostring(info.loc.address), info.loc.port, ipv4Address.tostring(info.rem.address), info.rem.port))
|
||||
end
|
@@ -4,21 +4,21 @@ local computer = require("computer")
|
||||
local os = require("os")
|
||||
local bit32 = require("bit32")
|
||||
local network = require("network")
|
||||
local ethernetType = require("layers.ethernet").TYPE
|
||||
local icmp = require("layers.icmp")
|
||||
local ipv4 = require("layers.ipv4")
|
||||
local arp = require("layers.arp")
|
||||
local ethernetType = require("network.ethernet").TYPE
|
||||
local icmp = require("network.icmp")
|
||||
local ipv4 = require("network.ipv4")
|
||||
local arp = require("network.arp")
|
||||
|
||||
local args, opts = shell.parse(...)
|
||||
|
||||
---=============================================================================
|
||||
if (opts["help"] or opts["h"] or #args == 0) then
|
||||
print("ping [-W timeout] [-s packetsize] ip")
|
||||
print("ping [-W timeout] [-s packetsize] [-p padding] ip")
|
||||
os.exit()
|
||||
end
|
||||
---=============================================================================
|
||||
for k, v in pairs(opts) do print(k, v) end
|
||||
opts.W = tonumber(opts.W) or 10
|
||||
opts.W = tonumber(opts.W) or 5
|
||||
opts.s = tonumber(opts.s) or 56
|
||||
opts.p = opts.p or "A"
|
||||
|
||||
@@ -34,8 +34,7 @@ if (not localMac) then
|
||||
os.exit(1)
|
||||
end
|
||||
|
||||
|
||||
local icmpInterface = network.interfaces[localMac].icmp
|
||||
local icmpInterface = network.icmp.getInterface()
|
||||
assert(icmpInterface)
|
||||
|
||||
|
||||
@@ -49,6 +48,7 @@ local function ping()
|
||||
local param = bit32.lshift(i, 8) + 0
|
||||
local icmpEcho = icmp.ICMPPacket(icmp.TYPE.ECHO_REQUEST, icmp.CODE.ECHO_REQUEST.Echo_request, param, string.rep(opts.p, math.floor(opts.s / #opts.p)))
|
||||
local sent, reason = pcall(icmpInterface.send, icmpInterface, targetIP, icmpEcho)
|
||||
--local sent, reason = icmpInterface.send(icmpInterface, targetIP, icmpEcho)
|
||||
local t = computer.uptime()
|
||||
if (sent) then
|
||||
sentICMP[i] = t
|
||||
@@ -63,15 +63,15 @@ end
|
||||
--=============================================================================
|
||||
|
||||
local timeoutTimer = event.timer(0.1, function()
|
||||
for seq, v in pairs(sentICMP) do
|
||||
if v and computer.uptime() - v > opts["W"] then
|
||||
print(string.format("Timeout\ticmp_seq=%d\t(%d s)", seq, opts["W"]))
|
||||
sentICMP[seq] = false
|
||||
ping()
|
||||
break
|
||||
end
|
||||
end
|
||||
end, math.huge)
|
||||
for seq, v in pairs(sentICMP) do
|
||||
if v and computer.uptime() - v > opts["W"] then
|
||||
print(string.format("Timeout\ticmp_seq=%d\t(%d s)", seq, opts["W"]))
|
||||
sentICMP[seq] = false
|
||||
ping()
|
||||
break
|
||||
end
|
||||
end
|
||||
end, math.huge)
|
||||
--=============================================================================
|
||||
local icmpListener = event.listen("ICMP", function(eName, from, to, type, code, param, payload)
|
||||
local seq = bit32.extract(param, 8, 8)
|
||||
@@ -89,7 +89,7 @@ event.listen("interrupted", function(...)
|
||||
run = false
|
||||
return false
|
||||
end)
|
||||
---Main loop===================================================================
|
||||
--Main loop====================================================================
|
||||
print(string.format("Ping %s from %s with %d bytes of data.", args[1], ipv4.address.tostring(route.interface:getAddr()), opts.s))
|
||||
ping()
|
||||
while run do
|
||||
|
@@ -46,6 +46,10 @@ end
|
||||
|
||||
--=============================================================================
|
||||
|
||||
---@class OSITransportLayer : OSILayer
|
||||
|
||||
--=============================================================================
|
||||
|
||||
---@class Payload
|
||||
---@field payloadType number
|
||||
local Payload = {}
|
||||
@@ -60,3 +64,11 @@ end
|
||||
---@return Payload
|
||||
function Payload.unpack(...)
|
||||
end
|
||||
|
||||
---@return string
|
||||
function Payload:getPayload()
|
||||
end
|
||||
|
||||
---@param payload string
|
||||
function Payload:setPayload(payload)
|
||||
end
|
||||
|
@@ -3,7 +3,10 @@ local fs = require("filesystem")
|
||||
local component = require("component")
|
||||
local computer = require("computer")
|
||||
local network = require("network")
|
||||
local layers = require("layers")
|
||||
local icmp = require("network.icmp")
|
||||
local ipv4 = require("network.ipv4")
|
||||
local udp = require("network.udp")
|
||||
local ethernet = require("network.ethernet")
|
||||
|
||||
|
||||
--=============================================================================
|
||||
@@ -134,13 +137,13 @@ function ifconfig.loadInterfaces(file)
|
||||
if (iName and iType and iMode) then --check valid interface
|
||||
flog("Found interface : %s", 1, iName)
|
||||
autoInterface[iName] = false or autoInterface[iName]
|
||||
line = fileHandler:read("l") --read next line
|
||||
if (not line) then break end -- reached eof
|
||||
while line and line:match("^%s+") do --while in the interface paragraph
|
||||
line = fileHandler:read("l") --read next line
|
||||
if (not line) then break end -- reached eof
|
||||
while line and line:match("^%s+") do --while in the interface paragraph
|
||||
flog("\tCurrent line : %q", 2, line)
|
||||
local opt, arg = line:match("^%s+(%w+)%s+(%g+)$") --get the option name and argument
|
||||
if (VALID_PARAM[iType] ~= nil) then --known iType
|
||||
if (VALID_PARAM[iType][iMode] ~= nil) then --known iMode
|
||||
if (VALID_PARAM[iType] ~= nil) then --known iType
|
||||
if (VALID_PARAM[iType][iMode] ~= nil) then --known iMode
|
||||
readInterfaces[iName] = readInterfaces[iName] or {iName = iName, iType = iType, iMode = iMode}
|
||||
if (VALID_PARAM[iType][iMode][opt] == true) then
|
||||
flog("\tFound option %q with argument %q", 1, opt, arg)
|
||||
@@ -192,6 +195,14 @@ end
|
||||
|
||||
---@param iName string
|
||||
function ifconfig.ifup(iName)
|
||||
if (not network.internal.icmp) then
|
||||
network.internal.icmp = icmp.ICMPLayer(network.router)
|
||||
network.router:setProtocol(network.internal.icmp)
|
||||
end
|
||||
if (not network.interfaces.udp) then
|
||||
network.internal.udp = udp.UDPLayer(network.router)
|
||||
network.router:setProtocol(network.internal.udp)
|
||||
end
|
||||
local interface = ifconfig.findInterface(iName)
|
||||
if (not interface) then
|
||||
flog("No such interface : %s", -1, iName)
|
||||
@@ -216,17 +227,13 @@ function ifconfig.ifup(iName)
|
||||
---@cast iName - nil
|
||||
network.interfaces[iName] = {}
|
||||
--ethernet
|
||||
network.interfaces[iName].ethernet = layers.ethernet.EthernetInterface(component.proxy(iName))
|
||||
network.interfaces[iName].ethernet = ethernet.EthernetInterface(component.proxy(iName))
|
||||
--ip
|
||||
local address, mask = layers.ipv4.address.fromCIDR(interface.address)
|
||||
network.interfaces[iName].ip = layers.ipv4.IPv4Layer(network.interfaces[iName].ethernet, network.router, address, mask)
|
||||
--icmp
|
||||
network.interfaces[iName].icmp = layers.icmp.ICMPLayer(network.interfaces[iName].ip)
|
||||
--udp
|
||||
network.interfaces[iName].udp = layers.udp.UDPLayer(network.interfaces[iName].ip)
|
||||
local address, mask = ipv4.address.fromCIDR(interface.address)
|
||||
network.interfaces[iName].ip = ipv4.IPv4Layer(network.interfaces[iName].ethernet, network.router, address, mask)
|
||||
--router
|
||||
if (interface.gateway) then
|
||||
network.router:addRoute({interface = network.interfaces[iName].ip, network = 0, mask = 0, gateway = layers.ipv4.address.fromString(interface.gateway), metric = tonumber(interface.metric) or 100})
|
||||
network.router:addRoute({interface = network.interfaces[iName].ip, network = 0, mask = 0, gateway = ipv4.address.fromString(interface.gateway), metric = tonumber(interface.metric) or 100})
|
||||
end
|
||||
|
||||
return true
|
||||
@@ -272,7 +279,7 @@ function ifconfig.ifdown(iName)
|
||||
|
||||
if (network.interfaces[iName]) then
|
||||
---@cast iName - nil
|
||||
network.router:removeLayer(network.interfaces[iName].ip)
|
||||
network.router:removeByInterface(network.interfaces[iName].ip)
|
||||
network.interfaces[iName] = nil
|
||||
else
|
||||
flog("Interface %q is not up", 0, name)
|
||||
|
@@ -1,310 +0,0 @@
|
||||
local ethernet = require("layers.ethernet")
|
||||
local event = require("event")
|
||||
|
||||
---@class arplib
|
||||
---@field private internal table
|
||||
local arp = {}
|
||||
arp.internal = {}
|
||||
arp.internal.cache = {}
|
||||
arp.internal.localAddress = {}
|
||||
|
||||
---@enum arpOperation
|
||||
arp.OPERATION = {
|
||||
REQUEST = 1,
|
||||
REPLY = 2
|
||||
}
|
||||
---@enum arpHardwareType
|
||||
arp.HARDWARE_TYPE = {
|
||||
ETHERNET = 1
|
||||
}
|
||||
---@enum arpProtocoleType
|
||||
arp.PROTOCOLE_TYPE = ethernet.TYPE
|
||||
|
||||
---============================================================================
|
||||
--#region ARPFrame
|
||||
|
||||
---@class ARPFrame:Payload
|
||||
---@field private _htype number
|
||||
---@field private _ptype number
|
||||
---@field private _oper arpOperation
|
||||
---@field private _sha number|string
|
||||
---@field private _spa number|string
|
||||
---@field private _tha number|string
|
||||
---@field private _tpa number|string
|
||||
---@operator call:ARPFrame
|
||||
---@overload fun(htype:number,ptype:number,oper:arpOperation,sha:number|string,spa:number|string,tha:number|string,tpa:number|string):ARPFrame
|
||||
local ARPFrame = {}
|
||||
ARPFrame.payloadType = ethernet.TYPE.ARP
|
||||
ARPFrame.OPERATION = {
|
||||
REQUEST = 1,
|
||||
REPLY = 2
|
||||
}
|
||||
|
||||
|
||||
setmetatable(ARPFrame, {
|
||||
---@param htype number
|
||||
---@param ptype number
|
||||
---@param oper arpOperation
|
||||
---@param sha number|string
|
||||
---@param spa number|string
|
||||
---@param tha number|string
|
||||
---@param tpa number|string
|
||||
---@return ARPFrame
|
||||
__call = function(self, htype, ptype, oper, sha, spa, tha, tpa)
|
||||
checkArg(1, htype, "number")
|
||||
checkArg(2, ptype, "number")
|
||||
checkArg(3, oper, "number")
|
||||
checkArg(4, sha, "string", "number")
|
||||
checkArg(5, spa, "string", "number")
|
||||
checkArg(6, tha, "string", "number")
|
||||
checkArg(7, tpa, "string", "number")
|
||||
|
||||
local o = {
|
||||
_htype = htype,
|
||||
_ptype = ptype,
|
||||
_oper = oper,
|
||||
_sha = sha,
|
||||
_spa = spa,
|
||||
_tha = tha,
|
||||
_tpa = tpa,
|
||||
}
|
||||
|
||||
setmetatable(o, {__index = self})
|
||||
|
||||
return o
|
||||
end
|
||||
})
|
||||
|
||||
---@return string
|
||||
function ARPFrame:pack()
|
||||
return string.format("%x\0%x\0%x\0%s\0%s\0%s\0%s", self:getHtype(), self:getPtype(), self:getOper(), self:getSha(), self:getSpa(), self:getTha(), self:getTpa())
|
||||
end
|
||||
|
||||
---@param arpString string
|
||||
---@return ARPFrame
|
||||
function ARPFrame.unpack(arpString)
|
||||
local htype, ptype, oper, sha, spa, tha, tpa = arpString:match("^(%x+)\0(%x+)\0(%x+)\0([^\0]+)\0([^\0]+)\0([^\0]+)\0([^\0]+)$")
|
||||
htype = tonumber(htype, 16)
|
||||
ptype = tonumber(ptype, 16)
|
||||
oper = tonumber(oper, 16)
|
||||
if (tonumber(sha)) then sha = tonumber(sha) end
|
||||
if (tonumber(spa)) then spa = tonumber(spa) end
|
||||
if (tonumber(tha)) then tha = tonumber(tha) end
|
||||
if (tonumber(tpa)) then tpa = tonumber(tpa) end
|
||||
return ARPFrame(htype, ptype, oper, sha, spa, tha, tpa)
|
||||
end
|
||||
|
||||
--#region getter/setter
|
||||
|
||||
---Get htype
|
||||
---@return number
|
||||
function ARPFrame:getHtype() return self._htype end
|
||||
|
||||
---@param val number
|
||||
function ARPFrame:setHtype(val)
|
||||
checkArg(1, val, "number")
|
||||
self._htype = val
|
||||
end
|
||||
|
||||
---Get ptype
|
||||
---@return number
|
||||
function ARPFrame:getPtype() return self._ptype end
|
||||
|
||||
---@param val number
|
||||
function ARPFrame:setPtype(val)
|
||||
checkArg(1, val, "number")
|
||||
self._ptype = val
|
||||
end
|
||||
|
||||
---Get oper
|
||||
---@return number
|
||||
function ARPFrame:getOper() return self._oper end
|
||||
|
||||
---@param val number
|
||||
function ARPFrame:setOper(val)
|
||||
checkArg(1, val, "number")
|
||||
self._oper = val
|
||||
end
|
||||
|
||||
---Get sha
|
||||
---@return number|string
|
||||
function ARPFrame:getSha() return self._sha end
|
||||
|
||||
---@param val number|string
|
||||
function ARPFrame:setSha(val)
|
||||
checkArg(1, val, 'string', 'number')
|
||||
self._sha = val
|
||||
end
|
||||
|
||||
---Get spa
|
||||
---@return number|string
|
||||
function ARPFrame:getSpa() return self._spa end
|
||||
|
||||
---@param val number|string
|
||||
function ARPFrame:setSpa(val)
|
||||
checkArg(1, val, 'string', 'number')
|
||||
self._spa = val
|
||||
end
|
||||
|
||||
---Get tha
|
||||
---@return number|string
|
||||
function ARPFrame:getTha() return self._tha end
|
||||
|
||||
---@param val number|string
|
||||
function ARPFrame:setTha(val)
|
||||
checkArg(1, val, 'string', 'number')
|
||||
self._tha = val
|
||||
end
|
||||
|
||||
---Get tpa
|
||||
---@return number|string
|
||||
function ARPFrame:getTpa() return self._tpa end
|
||||
|
||||
---@param val number|string
|
||||
function ARPFrame:setTpa(val)
|
||||
checkArg(1, val, 'string', 'number')
|
||||
self._tpa = val
|
||||
end
|
||||
|
||||
--#endregion
|
||||
--#endregion
|
||||
--=============================================================================
|
||||
--#region ARPLayer
|
||||
|
||||
---@class ARPLayer : OSINetworkLayer
|
||||
local ARPLayer = {}
|
||||
---@type ethernetType
|
||||
ARPLayer.layerType = ethernet.TYPE.ARP
|
||||
|
||||
setmetatable(ARPLayer, {
|
||||
---@param osiLayer OSIDataLayer
|
||||
---@return ARPLayer
|
||||
__call = function(self, osiLayer)
|
||||
local o = {
|
||||
_layer = osiLayer
|
||||
}
|
||||
setmetatable(o, {__index = self})
|
||||
osiLayer:setLayer(o)
|
||||
return o
|
||||
end
|
||||
})
|
||||
|
||||
function ARPLayer:getAddr()
|
||||
return self._layer:getAddr()
|
||||
end
|
||||
|
||||
function ARPLayer:getMTU()
|
||||
return 0
|
||||
end
|
||||
|
||||
---Handle the payload from the layer under
|
||||
---@param from string
|
||||
---@param to string
|
||||
---@param payload string
|
||||
function ARPLayer:payloadHandler(from, to, payload)
|
||||
local arpFrame = ARPFrame.unpack(payload)
|
||||
if (arpFrame:getOper() == arp.OPERATION.REQUEST) then
|
||||
local protocolAddress = arp.getLocalAddress(arpFrame:getHtype(), arpFrame:getPtype(), self._layer:getAddr())
|
||||
if (protocolAddress == arpFrame:getTpa()) then
|
||||
self:send(from, ARPFrame(arpFrame:getHtype(), arpFrame:getPtype(), ARPFrame.OPERATION.REPLY, arpFrame:getSha(), arpFrame:getSpa(), to, arpFrame:getTpa()))
|
||||
end
|
||||
elseif (arpFrame:getOper() == arp.OPERATION.REPLY) then
|
||||
arp.addCached(arpFrame:getHtype(), arpFrame:getPtype(), arpFrame:getTha(), arpFrame:getTpa())
|
||||
end
|
||||
end
|
||||
|
||||
---Send the arp frame
|
||||
---@param payload ARPFrame
|
||||
function ARPLayer:send(dst, payload)
|
||||
if (dst == ethernet.MAC_NIL) then dst = ethernet.MAC_BROADCAST end
|
||||
local eFrame = ethernet.EthernetFrame(self._layer:getAddr(), dst, nil, self.layerType, payload:pack())
|
||||
self._layer:send(dst, eFrame)
|
||||
end
|
||||
|
||||
--#endregion
|
||||
--=============================================================================
|
||||
|
||||
---Add the address to the cache
|
||||
---@param htype number
|
||||
---@param ptype number
|
||||
---@param ha any
|
||||
---@param pa any
|
||||
function arp.addCached(htype, ptype, ha, pa)
|
||||
local cache = arp.internal.cache --alias
|
||||
if (not cache[ptype]) then cache[ptype] = {} end
|
||||
if (not cache[ptype][pa]) then cache[ptype][pa] = {} end
|
||||
cache[ptype][pa][htype] = ha
|
||||
--push a signal to let other know a new address got cached
|
||||
event.push("arp", htype, ptype, ha, pa)
|
||||
end
|
||||
|
||||
---Register the local address to answer ARP request
|
||||
---@param htype number
|
||||
---@param ptype number
|
||||
---@param ha any
|
||||
---@param pa any
|
||||
function arp.setLocalAddress(htype, ptype, ha, pa)
|
||||
local localAddress = arp.internal.localAddress --alias
|
||||
if (not localAddress[htype]) then localAddress[htype] = {} end
|
||||
if (not localAddress[htype][ha]) then localAddress[htype][ha] = {} end
|
||||
localAddress[htype][ha][ptype] = pa
|
||||
end
|
||||
|
||||
---Resolve local harware address into protocol address
|
||||
---@param htype number
|
||||
---@param ptype number
|
||||
---@param ha any Requested hardware address
|
||||
---@return string|number?
|
||||
function arp.getLocalAddress(htype, ptype, ha)
|
||||
local localAddress = arp.internal.localAddress --alias
|
||||
if (localAddress[htype] and localAddress[htype][ha] and localAddress[htype][ha][ptype]) then
|
||||
return localAddress[htype][ha][ptype]
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
---Resolve local protocol address into hardware address
|
||||
---@param htype number
|
||||
---@param ptype number
|
||||
---@param pa any Requested protocol address
|
||||
---@return string|number?
|
||||
function arp.getLocalHardwareAddress(htype, ptype, pa)
|
||||
local localAddress = arp.internal.localAddress --alias
|
||||
for ha, v in pairs(localAddress[htype]) do
|
||||
if (v[ptype] == pa) then return ha end
|
||||
end
|
||||
end
|
||||
|
||||
---Resolve harware address
|
||||
---@param interface ARPLayer
|
||||
---@param htype number requested address type
|
||||
---@param ptype number provided address type
|
||||
---@param pa any Requested protocol address
|
||||
---@param spa any Local protocol address
|
||||
---@return any? tpa target protocol address
|
||||
function arp.getAddress(interface, htype, ptype, pa, spa)
|
||||
local cache = arp.internal.cache --alias
|
||||
if (cache[ptype] and cache[ptype][pa] and cache[ptype][pa][htype]) then
|
||||
return cache[ptype][pa][htype]
|
||||
end
|
||||
-- cache miss
|
||||
local arpMessage = ARPFrame(htype, ptype, ARPFrame.OPERATION.REQUEST, interface:getAddr(), spa, ethernet.MAC_NIL, pa)
|
||||
interface:send(ethernet.MAC_BROADCAST, arpMessage)
|
||||
|
||||
local tpa = select(4, event.pull(1, "arp", htype, ptype, nil, pa))
|
||||
return tpa
|
||||
end
|
||||
|
||||
function arp.list(htype, ptype)
|
||||
local l = {}
|
||||
if (arp.internal.cache[ptype]) then
|
||||
for k, v in pairs(arp.internal.cache[ptype]) do
|
||||
table.insert(l, {k, v[htype]})
|
||||
end
|
||||
end
|
||||
return l
|
||||
end
|
||||
|
||||
arp.ARPLayer = ARPLayer
|
||||
arp.ARPFrame = ARPFrame
|
||||
return arp
|
@@ -1,240 +0,0 @@
|
||||
local ipv4 = require("layers.ipv4")
|
||||
local event = require("event")
|
||||
--=============================================================================
|
||||
|
||||
|
||||
---@class icmplib
|
||||
local icmp = {}
|
||||
---@enum icmpType
|
||||
icmp.TYPE = {
|
||||
ECHO_REPLY = 0,
|
||||
DESTINATION_UNREACHABLE = 3,
|
||||
REDIRECT_MESSAGE = 5,
|
||||
ECHO_REQUEST = 8,
|
||||
ROUTER_ADVERTISEMENT = 9,
|
||||
ROUTER_SOLICITATION = 10,
|
||||
TIME_EXCEEDED = 11,
|
||||
PARAMETER_PROBELM_BAD_IP_HEADER = 12,
|
||||
TIMESTAMP = 13,
|
||||
TIMESTAMP_REPLY = 14,
|
||||
EXTENDED_ECHO_REQUEST = 42,
|
||||
EXTENDED_ECHO_REPLY = 43
|
||||
}
|
||||
icmp.CODE = {
|
||||
ECHO_REPLY = {
|
||||
ECHO_REPLY = 0
|
||||
},
|
||||
DESTINATION_UNREACHABLE = {
|
||||
DESTINATION_NETWORK_UNREACHABLE = 0,
|
||||
DESTINATION_HOST_UNREACHABLE = 1,
|
||||
DESTINATION_PROTOCOL_UNREACHABLE = 2,
|
||||
DESTINATION_PORT_UNREACHABLE = 3,
|
||||
FRAGMENTATION_REQUIRED_AND_DF_FLAG_SET = 4,
|
||||
SOURCE_ROUTE_FAILED = 5,
|
||||
DESTINATION_NETWORK_UNKNOWN = 6,
|
||||
DESTINATION_HOST_UNKNOWN = 7,
|
||||
SOURCE_HOST_ISOLATED = 8,
|
||||
NETWORK_ADMINISTRATIVELY_PROHIBITED = 9,
|
||||
HOST_ADMINISTRATIVELY_PROHIBITED = 10,
|
||||
NETWORK_UNREACHABLE_FOR_TOS = 11,
|
||||
HOST_UNREACHABLE_FOR_TOS = 12,
|
||||
COMMUNICATION_ADMINISTRATIVELY_PROHIBITED = 13,
|
||||
HOST_PRECEDENCE_VIOLATION = 14,
|
||||
PRECEDENCE_CUTOFF_IN_EFFECT = 15,
|
||||
},
|
||||
REDIRECT_MESSAGE = {
|
||||
REDIRECT_DATAGRAM_FOR_THE_NETWORK = 0,
|
||||
REDIRECT_DATAGRAM_FOR_THE_HOST = 1,
|
||||
REDIRECT_DATAGRAM_FOR_THE_TOS_NETWORK = 2,
|
||||
REDIRECT_DATAGRAM_FOR_THE_TOS_HOST = 3,
|
||||
},
|
||||
ECHO_REQUEST = {
|
||||
Echo_request = 0,
|
||||
},
|
||||
ROUTER_ADVERTISEMENT = {
|
||||
ROUTER_ADVERTISEMENt = 0,
|
||||
},
|
||||
ROUTER_SOLICITATION = {
|
||||
Router_discovery_selection_solicitation = 0,
|
||||
},
|
||||
TIME_EXCEEDED = {
|
||||
TTL_expired_in_transit = 0,
|
||||
Fragment_reassembly_time_exceeded = 1,
|
||||
},
|
||||
PARAMETER_PROBELM_BAD_IP_HEADER = {
|
||||
Pointer_indicates_the_error = 0,
|
||||
Missing_a_required_option = 1,
|
||||
Bad_length = 2,
|
||||
},
|
||||
TIMESTAMP = {
|
||||
Timestamp = 0,
|
||||
},
|
||||
TIMESTAMP_REPLY = {
|
||||
Timestamp_reply = 0,
|
||||
},
|
||||
EXTENDED_ECHO_REQUEST = {
|
||||
Request_Extended_Echo = 0,
|
||||
},
|
||||
EXTENDED_ECHO_REPLY = {
|
||||
No_Error = 0,
|
||||
Malformed_Query = 1,
|
||||
No_Such_Interface = 2,
|
||||
No_Such_Table_Entry = 3,
|
||||
Multiple_Interfaces_Satisfy_Query = 4,
|
||||
}
|
||||
}
|
||||
|
||||
--=============================================================================
|
||||
--#region ICMPPacket
|
||||
|
||||
---@class ICMPPacket:Payload
|
||||
---@field private _type number
|
||||
---@field private _code number
|
||||
---@field private _param number
|
||||
---@field private _payload string
|
||||
---@operator call:ICMPPacket
|
||||
---@overload fun(type:icmpType,code:number,param:number,paylaod:string):ICMPPacket
|
||||
---@overload fun(type:icmpType,code:number,param:number):ICMPPacket
|
||||
---@overload fun(type:icmpType,code:number):ICMPPacket
|
||||
local ICMPPacket = {}
|
||||
ICMPPacket.payloadType = ipv4.PROTOCOLS.ICMP
|
||||
|
||||
setmetatable(ICMPPacket, {
|
||||
---@param type icmpType
|
||||
---@param code number
|
||||
---@param param? number
|
||||
---@param payload? string
|
||||
---@return ICMPPacket
|
||||
__call = function(self, type, code, param, payload)
|
||||
local o = {
|
||||
_type = type,
|
||||
_code = code,
|
||||
_param = param or 0,
|
||||
_payload = payload or ""
|
||||
}
|
||||
setmetatable(o, {__index = self})
|
||||
return o
|
||||
end
|
||||
})
|
||||
|
||||
--#region getter/setter
|
||||
|
||||
---@return number
|
||||
function ICMPPacket:getType() return self._type end
|
||||
|
||||
---@param val number
|
||||
function ICMPPacket:setType(val)
|
||||
checkArg(1, val, "number")
|
||||
self._type = val
|
||||
end
|
||||
|
||||
---@return number
|
||||
function ICMPPacket:getCode() return self._code end
|
||||
|
||||
---@param val number
|
||||
function ICMPPacket:setCode(val)
|
||||
checkArg(1, val, "number")
|
||||
self._code = val
|
||||
end
|
||||
|
||||
---@return number
|
||||
function ICMPPacket:getParam() return self._param end
|
||||
|
||||
---@param val number
|
||||
function ICMPPacket:setParam(val)
|
||||
checkArg(1, val, "number")
|
||||
self._param = val
|
||||
end
|
||||
|
||||
---@return string
|
||||
function ICMPPacket:getPayload() return self._payload end
|
||||
|
||||
---@param val string
|
||||
function ICMPPacket:setPayload(val)
|
||||
checkArg(1, val, "string")
|
||||
self._payload = val
|
||||
end
|
||||
|
||||
--#endregion
|
||||
|
||||
function ICMPPacket:pack()
|
||||
return string.format("%.2x%.2x%.8x%s", self._type, self._code, self._param, self._payload)
|
||||
end
|
||||
|
||||
---@return ICMPPacket
|
||||
function ICMPPacket.unpack(val)
|
||||
local o = "%x%x"
|
||||
local patern = string.format("(%s)(%s)(%s)(%s)", o, o, o:rep(4), ".*")
|
||||
local a, b, c, d = val:match(patern)
|
||||
a = tonumber(a, 16);
|
||||
assert(a)
|
||||
b = tonumber(b, 16);
|
||||
assert(b)
|
||||
c = tonumber(c, 16);
|
||||
assert(c)
|
||||
return ICMPPacket(a, b, c, d)
|
||||
end
|
||||
|
||||
--#endregion
|
||||
---=============================================================================
|
||||
--#region ICMPLayer
|
||||
|
||||
---@class ICMPLayer:OSINetworkLayer
|
||||
---@field private _layer IPv4Layer
|
||||
---@operator call:ICMPLayer
|
||||
---@overload fun(layer:IPv4Layer):ICMPLayer
|
||||
local ICMPLayer = {}
|
||||
ICMPLayer.layerType = ipv4.PROTOCOLS.ICMP
|
||||
|
||||
setmetatable(ICMPLayer, {
|
||||
---@param layer IPv4Layer
|
||||
---@return ICMPLayer
|
||||
__call = function(self, layer)
|
||||
local o = {
|
||||
_layer = layer
|
||||
}
|
||||
setmetatable(o, {__index = self})
|
||||
layer:setLayer(o)
|
||||
return o
|
||||
end,
|
||||
})
|
||||
|
||||
---@param payload ICMPPacket
|
||||
function ICMPLayer:send(dst, payload)
|
||||
local ipDatagram = ipv4.IPv4Packet(self._layer:getAddr(), dst, payload)
|
||||
self._layer:getRouter():send(ipDatagram)
|
||||
end
|
||||
|
||||
---@param payload string
|
||||
function ICMPLayer:payloadHandler(from, to, payload)
|
||||
local icmpPacket = ICMPPacket.unpack(payload)
|
||||
if (icmpPacket:getType() == icmp.TYPE.ECHO_REQUEST) then
|
||||
local reply = ICMPPacket(icmp.TYPE.ECHO_REPLY, icmp.CODE.ECHO_REPLY.ECHO_REPLY, icmpPacket:getParam(), icmpPacket:getPayload())
|
||||
self:send(from, reply)
|
||||
elseif (icmpPacket:getType() == icmp.TYPE.ECHO_REPLY) then
|
||||
event.push("ICMP", from, to, icmpPacket:getType(), icmpPacket:getCode(), icmpPacket:getParam(), icmpPacket:getPayload())
|
||||
end
|
||||
end
|
||||
|
||||
function ICMPLayer:getAddr()
|
||||
return self._layer:getAddr()
|
||||
end
|
||||
|
||||
---Send a timeout icmp message
|
||||
---@param packet IPv4Packet
|
||||
---@param code number
|
||||
function ICMPLayer:sendTimeout(packet, code)
|
||||
local icmpPacket = ICMPPacket(icmp.TYPE.TIME_EXCEEDED, icmp.CODE.TIME_EXCEEDED.TTL_expired_in_transit, nil, string.format("%.2x%.2x%.4x%.4x%.2x%.4x%.2x%.2x%.8x%.8x%s",
|
||||
packet:getDscp(), packet:getEcn(), packet:getLen(), packet:getId(), packet:getFlags(),
|
||||
packet:getFragmentOffset(), packet:getTtl(), packet:getProtocol(),
|
||||
packet:getSrc(), packet:getDst(), packet:getPayload():sub(1, 8)))
|
||||
self:send(packet:getSrc(), icmpPacket)
|
||||
end
|
||||
|
||||
--#endregion
|
||||
--=============================================================================
|
||||
|
||||
--=============================================================================
|
||||
icmp.ICMPPacket = ICMPPacket
|
||||
icmp.ICMPLayer = ICMPLayer
|
||||
return icmp
|
@@ -1,9 +0,0 @@
|
||||
---@class layersLib
|
||||
local layers = {}
|
||||
layers.ipv4 = require("layers.ipv4")
|
||||
layers.arp = require("layers.arp")
|
||||
layers.ethernet = require("layers.ethernet")
|
||||
layers.icmp = require("layers.icmp")
|
||||
layers.udp = require("layers.udp")
|
||||
|
||||
return layers
|
@@ -1,272 +0,0 @@
|
||||
local IPv4Packet = require("layers.ipv4").IPv4Packet
|
||||
|
||||
---@class udpLib
|
||||
local udp = {}
|
||||
|
||||
--#region UDPPacket
|
||||
|
||||
---@class UDPPacket : Payload
|
||||
---@field private _srcPort number
|
||||
---@field private _dstPort number
|
||||
---@field private _payload string
|
||||
---@operator call:UDPPacket
|
||||
---@overload fun(srcPort:number,dstPort:number,payload:string):UDPPacket
|
||||
local UDPPacket = {}
|
||||
UDPPacket.payloadType = require("layers.ipv4").PROTOCOLS.UDP
|
||||
|
||||
---@return UDPPacket
|
||||
setmetatable(UDPPacket, {
|
||||
---@param self UDPPacket
|
||||
---@param srcPort number
|
||||
---@param dstPort number
|
||||
---@param payload string
|
||||
---@return table
|
||||
__call = function(self, srcPort, dstPort, payload)
|
||||
checkArg(1, srcPort, "number")
|
||||
checkArg(2, dstPort, "number")
|
||||
checkArg(3, payload, "string")
|
||||
local o = {
|
||||
_scrPort = 0,
|
||||
_dstPort = 0,
|
||||
_payload = ""
|
||||
}
|
||||
setmetatable(o, {__index = self})
|
||||
|
||||
o:setDstPort(dstPort)
|
||||
o:setSrcPort(srcPort)
|
||||
o:setPayload(payload)
|
||||
|
||||
return o
|
||||
end
|
||||
})
|
||||
|
||||
function UDPPacket:getDstPort() return self._dstPort end
|
||||
|
||||
function UDPPacket:getSrcPort() return self._srcPort end
|
||||
|
||||
function UDPPacket:getPayload() return self._payload end
|
||||
|
||||
function UDPPacket:setDstPort(port)
|
||||
checkArg(1, port, "number")
|
||||
assert(port >= 0 and port <= 2 ^ 16 - 1, "Port outside valid range")
|
||||
self._dstPort = port
|
||||
end
|
||||
|
||||
function UDPPacket:setSrcPort(port)
|
||||
checkArg(1, port, "number")
|
||||
assert(port >= 0 and port <= 2 ^ 16 - 1, "Port outside valid range")
|
||||
self._srcPort = port
|
||||
end
|
||||
|
||||
function UDPPacket:setPayload(value)
|
||||
checkArg(1, value, "string")
|
||||
self._payload = value
|
||||
end
|
||||
|
||||
---Prepare the packet for the next layer
|
||||
---@return string
|
||||
function UDPPacket:pack()
|
||||
return string.format("%.4x%.4x%s", self:getSrcPort(), self:getDstPort(), self:getPayload())
|
||||
end
|
||||
|
||||
---Get a udp packet from the string
|
||||
---@param value string
|
||||
---@return UDPPacket
|
||||
function UDPPacket.unpack(value)
|
||||
local o = "%x%x"
|
||||
local src, dst, payload = value:match(string.format("(%s)(%s)(%s)", o:rep(2), o:rep(2), ".*"))
|
||||
src = tonumber(src, 16)
|
||||
dst = tonumber(dst, 16)
|
||||
return UDPPacket(src, dst, payload)
|
||||
end
|
||||
|
||||
--#endregion
|
||||
--=============================================================================
|
||||
|
||||
--#region UDP socket
|
||||
|
||||
---@class UDPSocket
|
||||
---@field package _lPort number
|
||||
---@field package _rAddresse number
|
||||
---@field package _rPort number
|
||||
---@field private _buffer table<UDPPacket>
|
||||
---@field private _layer UDPLayer
|
||||
---@operator call:UDPSocket
|
||||
---@overload fun(layer:UDPLayer,localPort:number):UDPSocket
|
||||
---@overload fun(layer:UDPLayer,localPort:number,remoteAddress:number,remotePort:number):UDPSocket
|
||||
local UDPSocket = {}
|
||||
|
||||
---@return UDPSocket
|
||||
setmetatable(UDPSocket, {
|
||||
__call = function(self, layer, localPort, remoteAddress, remotePort)
|
||||
checkArg(1, layer, "table")
|
||||
checkArg(2, localPort, "number")
|
||||
checkArg(3, remoteAddress, "number", "nil")
|
||||
checkArg(4, remotePort, "number", "nil")
|
||||
local o = {
|
||||
_lPort = localPort,
|
||||
_rAddresse = remoteAddress or 0,
|
||||
_rPort = remotePort or 0,
|
||||
_buffer = {},
|
||||
_layer = layer
|
||||
}
|
||||
setmetatable(o, {__index = self})
|
||||
return o
|
||||
end
|
||||
})
|
||||
|
||||
---Recive one packet
|
||||
---@return UDPPacket?
|
||||
---@nodiscard
|
||||
function UDPSocket:recive()
|
||||
return table.remove(self._buffer, 1)
|
||||
end
|
||||
|
||||
---Recive one payload
|
||||
---@return string?
|
||||
---@nodiscard
|
||||
function UDPSocket:reciveString()
|
||||
local packet = self:recive()
|
||||
if (packet) then
|
||||
return packet:getPayload()
|
||||
end
|
||||
end
|
||||
|
||||
---Send a udpPacket or string.
|
||||
---A string can only be sent if the socket's remote address and port are set
|
||||
---@param payload UDPPacket|string
|
||||
---@return boolean packetSent
|
||||
function UDPSocket:send(payload)
|
||||
if (type(payload) == "string") then
|
||||
---@cast payload string
|
||||
if (self._rAddresse == 0 or self._rPort == 0) then return false end
|
||||
self._layer:send(self._rAddresse, UDPPacket(self._lPort, self._rPort, payload))
|
||||
return true
|
||||
else
|
||||
---@cast payload UDPPacket
|
||||
self._layer:send(self._rAddresse, payload)
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
---Handle the payload recived by UDPLayer
|
||||
---@package
|
||||
---@param udpPacket UDPPacket
|
||||
function UDPSocket:payloadHandler(udpPacket)
|
||||
table.insert(self._buffer, udpPacket)
|
||||
end
|
||||
|
||||
---close the socket
|
||||
function UDPSocket:close()
|
||||
self._layer:close(self)
|
||||
end
|
||||
|
||||
---Check if the socket is still open
|
||||
---@return boolean
|
||||
function UDPSocket:isOpen()
|
||||
return self._layer:isOpen(self)
|
||||
end
|
||||
|
||||
function UDPSocket:getLocalPort()
|
||||
return self._lPort
|
||||
end
|
||||
|
||||
function UDPSocket:getRemotePort()
|
||||
return self._rPort
|
||||
end
|
||||
|
||||
--#endregion
|
||||
--=============================================================================
|
||||
|
||||
--#region UPD layer
|
||||
|
||||
---@class UDPLayer : OSILayer
|
||||
---@field private _sockets table<number,UDPSocket>
|
||||
---@field private _layer IPv4Layer
|
||||
---@operator call:UDPLayer
|
||||
---@overload fun(layer:IPv4Layer):UDPLayer
|
||||
local UDPLayer = {}
|
||||
UDPLayer.layerType = require("layers.ipv4").PROTOCOLS.UDP
|
||||
|
||||
---@return UDPLayer
|
||||
setmetatable(UDPLayer, {
|
||||
---@param layer IPv4Layer
|
||||
---@return UDPLayer
|
||||
__call = function(self, layer)
|
||||
local o = {
|
||||
_sockets = {},
|
||||
_layer = layer
|
||||
}
|
||||
setmetatable(o, {__index = self})
|
||||
layer:setLayer(o) --tell the IPv4Layer that we exists
|
||||
return o
|
||||
end
|
||||
})
|
||||
|
||||
function UDPLayer:payloadHandler(from, to, payload)
|
||||
local udpPacket = UDPPacket.unpack(payload)
|
||||
if (not self._sockets[udpPacket:getDstPort()]) then return end
|
||||
self._sockets[udpPacket:getDstPort()]:payloadHandler(udpPacket)
|
||||
end
|
||||
|
||||
---Open a new UDP socket.
|
||||
---@param port? number
|
||||
---@param remoteAd? number
|
||||
---@param remotePort? number
|
||||
---@return UDPSocket? socket, string? reason
|
||||
function UDPLayer:open(port, remoteAd, remotePort)
|
||||
--#region checkArg
|
||||
checkArg(1, port, "number", "nil")
|
||||
if (port) then
|
||||
checkArg(2, remoteAd, "number", "nil")
|
||||
if (remoteAd) then
|
||||
checkArg(3, remotePort, "number")
|
||||
else
|
||||
checkArg(3, remotePort, "number", "nil")
|
||||
end
|
||||
else
|
||||
checkArg(2, remoteAd, "nil")
|
||||
checkArg(3, remotePort, "nil")
|
||||
end
|
||||
--#endregion
|
||||
if (port == nil) then
|
||||
repeat
|
||||
port = math.random(1025, (2 ^ 16) - 1)
|
||||
until not self._sockets[port]
|
||||
end
|
||||
if (self._sockets[port]) then return nil, "port already used" end
|
||||
local socket = UDPSocket(self, port, remoteAd, remotePort)
|
||||
self._sockets[port] = socket
|
||||
return socket
|
||||
end
|
||||
|
||||
---@package
|
||||
---@param socket UDPSocket
|
||||
function UDPLayer:close(socket)
|
||||
self._sockets[socket._lPort] = nil
|
||||
end
|
||||
|
||||
---Check if the given socket is open on this layer
|
||||
---@param socket UDPSocket
|
||||
---@return boolean
|
||||
function UDPLayer:isOpen(socket)
|
||||
if (self._sockets[socket._lPort]) then return true else return false end
|
||||
end
|
||||
|
||||
---Send a udp paylaod
|
||||
---@param to number
|
||||
---@param payload UDPPacket
|
||||
function UDPLayer:send(to, payload)
|
||||
self._layer:send(IPv4Packet(self._layer:getAddr(), to, payload))
|
||||
end
|
||||
|
||||
function UDPLayer:getAddr() return self._layer:getAddr() end
|
||||
|
||||
function UDPLayer:getMTU() return self._layer:getMTU() - 8 end
|
||||
|
||||
--#endregion
|
||||
|
||||
udp.UDPLayer = UDPLayer
|
||||
udp.UDPPacket = UDPPacket
|
||||
udp.UDPSocket = UDPSocket
|
||||
return udp
|
147
network/lib/network/arp/ARPFrame.lua
Normal file
147
network/lib/network/arp/ARPFrame.lua
Normal file
@@ -0,0 +1,147 @@
|
||||
local ethernet = require("network.ethernet")
|
||||
|
||||
|
||||
---@class ARPFrame:Payload
|
||||
---@field private _htype number
|
||||
---@field private _ptype number
|
||||
---@field private _oper arpOperation
|
||||
---@field private _sha number|string
|
||||
---@field private _spa number|string
|
||||
---@field private _tha number|string
|
||||
---@field private _tpa number|string
|
||||
---@operator call:ARPFrame
|
||||
---@overload fun(htype:number,ptype:number,oper:arpOperation,sha:number|string,spa:number|string,tha:number|string,tpa:number|string):ARPFrame
|
||||
local ARPFrame = {}
|
||||
ARPFrame.payloadType = ethernet.TYPE.ARP
|
||||
ARPFrame.OPERATION = {
|
||||
REQUEST = 1,
|
||||
REPLY = 2
|
||||
}
|
||||
|
||||
|
||||
setmetatable(ARPFrame, {
|
||||
---@param htype number
|
||||
---@param ptype number
|
||||
---@param oper arpOperation
|
||||
---@param sha number|string
|
||||
---@param spa number|string
|
||||
---@param tha number|string
|
||||
---@param tpa number|string
|
||||
---@return ARPFrame
|
||||
__call = function(self, htype, ptype, oper, sha, spa, tha, tpa)
|
||||
checkArg(1, htype, "number")
|
||||
checkArg(2, ptype, "number")
|
||||
checkArg(3, oper, "number")
|
||||
checkArg(4, sha, "string", "number")
|
||||
checkArg(5, spa, "string", "number")
|
||||
checkArg(6, tha, "string", "number")
|
||||
checkArg(7, tpa, "string", "number")
|
||||
|
||||
local o = {
|
||||
_htype = htype,
|
||||
_ptype = ptype,
|
||||
_oper = oper,
|
||||
_sha = sha,
|
||||
_spa = spa,
|
||||
_tha = tha,
|
||||
_tpa = tpa,
|
||||
}
|
||||
|
||||
setmetatable(o, {__index = self})
|
||||
|
||||
return o
|
||||
end
|
||||
})
|
||||
|
||||
---@return string
|
||||
function ARPFrame:pack()
|
||||
return string.format("%x\0%x\0%x\0%s\0%s\0%s\0%s", self:getHtype(), self:getPtype(), self:getOper(), self:getSha(), self:getSpa(), self:getTha(), self:getTpa())
|
||||
end
|
||||
|
||||
---@param arpString string
|
||||
---@return ARPFrame
|
||||
function ARPFrame.unpack(arpString)
|
||||
local htype, ptype, oper, sha, spa, tha, tpa = arpString:match("^(%x+)\0(%x+)\0(%x+)\0([^\0]+)\0([^\0]+)\0([^\0]+)\0([^\0]+)$")
|
||||
htype = tonumber(htype, 16)
|
||||
ptype = tonumber(ptype, 16)
|
||||
oper = tonumber(oper, 16)
|
||||
if (tonumber(sha)) then sha = tonumber(sha) end
|
||||
if (tonumber(spa)) then spa = tonumber(spa) end
|
||||
if (tonumber(tha)) then tha = tonumber(tha) end
|
||||
if (tonumber(tpa)) then tpa = tonumber(tpa) end
|
||||
return ARPFrame(htype, ptype, oper, sha, spa, tha, tpa)
|
||||
end
|
||||
|
||||
--#region getter/setter
|
||||
|
||||
---Get htype
|
||||
---@return number
|
||||
function ARPFrame:getHtype() return self._htype end
|
||||
|
||||
---@param val number
|
||||
function ARPFrame:setHtype(val)
|
||||
checkArg(1, val, "number")
|
||||
self._htype = val
|
||||
end
|
||||
|
||||
---Get ptype
|
||||
---@return number
|
||||
function ARPFrame:getPtype() return self._ptype end
|
||||
|
||||
---@param val number
|
||||
function ARPFrame:setPtype(val)
|
||||
checkArg(1, val, "number")
|
||||
self._ptype = val
|
||||
end
|
||||
|
||||
---Get oper
|
||||
---@return number
|
||||
function ARPFrame:getOper() return self._oper end
|
||||
|
||||
---@param val number
|
||||
function ARPFrame:setOper(val)
|
||||
checkArg(1, val, "number")
|
||||
self._oper = val
|
||||
end
|
||||
|
||||
---Get sha
|
||||
---@return number|string
|
||||
function ARPFrame:getSha() return self._sha end
|
||||
|
||||
---@param val number|string
|
||||
function ARPFrame:setSha(val)
|
||||
checkArg(1, val, 'string', 'number')
|
||||
self._sha = val
|
||||
end
|
||||
|
||||
---Get spa
|
||||
---@return number|string
|
||||
function ARPFrame:getSpa() return self._spa end
|
||||
|
||||
---@param val number|string
|
||||
function ARPFrame:setSpa(val)
|
||||
checkArg(1, val, 'string', 'number')
|
||||
self._spa = val
|
||||
end
|
||||
|
||||
---Get tha
|
||||
---@return number|string
|
||||
function ARPFrame:getTha() return self._tha end
|
||||
|
||||
---@param val number|string
|
||||
function ARPFrame:setTha(val)
|
||||
checkArg(1, val, 'string', 'number')
|
||||
self._tha = val
|
||||
end
|
||||
|
||||
---Get tpa
|
||||
---@return number|string
|
||||
function ARPFrame:getTpa() return self._tpa end
|
||||
|
||||
---@param val number|string
|
||||
function ARPFrame:setTpa(val)
|
||||
checkArg(1, val, 'string', 'number')
|
||||
self._tpa = val
|
||||
end
|
||||
|
||||
return ARPFrame
|
57
network/lib/network/arp/ARPLayer.lua
Normal file
57
network/lib/network/arp/ARPLayer.lua
Normal file
@@ -0,0 +1,57 @@
|
||||
local ethernet = require("network.ethernet")
|
||||
local ARPFrame = require("network.arp.ARPFrame")
|
||||
local arpConst = require("network.arp.constantes")
|
||||
local arpAPI = require("network.arp.api")
|
||||
|
||||
|
||||
---@class ARPLayer : OSINetworkLayer
|
||||
local ARPLayer = {}
|
||||
---@type ethernetType
|
||||
ARPLayer.layerType = ethernet.TYPE.ARP
|
||||
|
||||
setmetatable(ARPLayer, {
|
||||
---@param osiLayer OSIDataLayer
|
||||
---@return ARPLayer
|
||||
__call = function(self, osiLayer)
|
||||
local o = {
|
||||
_layer = osiLayer
|
||||
}
|
||||
setmetatable(o, {__index = self})
|
||||
osiLayer:setLayer(o)
|
||||
return o
|
||||
end
|
||||
})
|
||||
|
||||
function ARPLayer:getAddr()
|
||||
return self._layer:getAddr()
|
||||
end
|
||||
|
||||
function ARPLayer:getMTU()
|
||||
return 0
|
||||
end
|
||||
|
||||
---Handle the payload from the layer under
|
||||
---@param from string
|
||||
---@param to string
|
||||
---@param payload string
|
||||
function ARPLayer:payloadHandler(from, to, payload)
|
||||
local arpFrame = ARPFrame.unpack(payload)
|
||||
if (arpFrame:getOper() == arpConst.OPERATION.REQUEST) then
|
||||
local protocolAddress = arpAPI.getLocalAddress(arpFrame:getHtype(), arpFrame:getPtype(), self._layer:getAddr())
|
||||
if (protocolAddress == arpFrame:getTpa()) then
|
||||
self:send(from, ARPFrame(arpFrame:getHtype(), arpFrame:getPtype(), ARPFrame.OPERATION.REPLY, arpFrame:getSha(), arpFrame:getSpa(), to, arpFrame:getTpa()))
|
||||
end
|
||||
elseif (arpFrame:getOper() == arpConst.OPERATION.REPLY) then
|
||||
arpAPI.addCached(arpFrame:getHtype(), arpFrame:getPtype(), arpFrame:getTha(), arpFrame:getTpa())
|
||||
end
|
||||
end
|
||||
|
||||
---Send the arp frame
|
||||
---@param payload ARPFrame
|
||||
function ARPLayer:send(dst, payload)
|
||||
if (dst == ethernet.MAC_NIL) then dst = ethernet.MAC_BROADCAST end
|
||||
local eFrame = ethernet.EthernetFrame(self._layer:getAddr(), dst, nil, self.layerType, payload:pack())
|
||||
self._layer:send(dst, eFrame)
|
||||
end
|
||||
|
||||
return ARPLayer
|
94
network/lib/network/arp/api.lua
Normal file
94
network/lib/network/arp/api.lua
Normal file
@@ -0,0 +1,94 @@
|
||||
local ethernet = require("network.ethernet")
|
||||
local event = require("event")
|
||||
local ARPFrame = require("network.arp.ARPFrame")
|
||||
|
||||
---@class arplib
|
||||
---@field private internal table
|
||||
local arpAPI = {}
|
||||
arpAPI.internal = {}
|
||||
arpAPI.internal.cache = {}
|
||||
arpAPI.internal.localAddress = {}
|
||||
|
||||
|
||||
---Add the address to the cache
|
||||
---@param htype number
|
||||
---@param ptype number
|
||||
---@param ha any
|
||||
---@param pa any
|
||||
function arpAPI.addCached(htype, ptype, ha, pa)
|
||||
local cache = arpAPI.internal.cache --alias
|
||||
if (not cache[ptype]) then cache[ptype] = {} end
|
||||
if (not cache[ptype][pa]) then cache[ptype][pa] = {} end
|
||||
cache[ptype][pa][htype] = ha
|
||||
--push a signal to let other know a new address got cached
|
||||
event.push("arp", htype, ptype, ha, pa)
|
||||
end
|
||||
|
||||
---Register the local address to answer ARP request
|
||||
---@param htype number
|
||||
---@param ptype number
|
||||
---@param ha any
|
||||
---@param pa any
|
||||
function arpAPI.setLocalAddress(htype, ptype, ha, pa)
|
||||
local localAddress = arpAPI.internal.localAddress --alias
|
||||
if (not localAddress[htype]) then localAddress[htype] = {} end
|
||||
if (not localAddress[htype][ha]) then localAddress[htype][ha] = {} end
|
||||
localAddress[htype][ha][ptype] = pa
|
||||
end
|
||||
|
||||
---Resolve local harware address into protocol address
|
||||
---@param htype number
|
||||
---@param ptype number
|
||||
---@param ha any Requested hardware address
|
||||
---@return string|number?
|
||||
function arpAPI.getLocalAddress(htype, ptype, ha)
|
||||
local localAddress = arpAPI.internal.localAddress --alias
|
||||
if (localAddress[htype] and localAddress[htype][ha] and localAddress[htype][ha][ptype]) then
|
||||
return localAddress[htype][ha][ptype]
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
---Resolve local protocol address into hardware address
|
||||
---@param htype number
|
||||
---@param ptype number
|
||||
---@param pa any Requested protocol address
|
||||
---@return string|number?
|
||||
function arpAPI.getLocalHardwareAddress(htype, ptype, pa)
|
||||
local localAddress = arpAPI.internal.localAddress --alias
|
||||
for ha, v in pairs(localAddress[htype]) do
|
||||
if (v[ptype] == pa) then return ha end
|
||||
end
|
||||
end
|
||||
|
||||
---Resolve harware address
|
||||
---@param interface ARPLayer
|
||||
---@param htype number requested address type
|
||||
---@param ptype number provided address type
|
||||
---@param pa any Requested protocol address
|
||||
---@param spa any Local protocol address
|
||||
---@return any? tpa target protocol address
|
||||
function arpAPI.getAddress(interface, htype, ptype, pa, spa)
|
||||
local cache = arpAPI.internal.cache --alias
|
||||
if (cache[ptype] and cache[ptype][pa] and cache[ptype][pa][htype]) then
|
||||
return cache[ptype][pa][htype]
|
||||
end
|
||||
-- cache miss
|
||||
local arpMessage = ARPFrame(htype, ptype, ARPFrame.OPERATION.REQUEST, interface:getAddr(), spa, ethernet.MAC_NIL, pa)
|
||||
interface:send(ethernet.MAC_BROADCAST, arpMessage)
|
||||
|
||||
local tpa = select(4, event.pull(1, "arp", htype, ptype, nil, pa))
|
||||
return tpa
|
||||
end
|
||||
|
||||
function arpAPI.list(htype, ptype)
|
||||
local l = {}
|
||||
if (arpAPI.internal.cache[ptype]) then
|
||||
for k, v in pairs(arpAPI.internal.cache[ptype]) do
|
||||
table.insert(l, {k, v[htype]})
|
||||
end
|
||||
end
|
||||
return l
|
||||
end
|
||||
|
||||
return arpAPI
|
18
network/lib/network/arp/constantes.lua
Normal file
18
network/lib/network/arp/constantes.lua
Normal file
@@ -0,0 +1,18 @@
|
||||
local ethernet = require("network.ethernet")
|
||||
|
||||
---@class arpConstantes
|
||||
local arpConstantes = {
|
||||
---@enum arpOperation
|
||||
OPERATION = {
|
||||
REQUEST = 1,
|
||||
REPLY = 2
|
||||
},
|
||||
---@enum arpHardwareType
|
||||
HARDWARE_TYPE = {
|
||||
ETHERNET = 1
|
||||
},
|
||||
---@enum arpProtocoleType
|
||||
PROTOCOLE_TYPE = ethernet.TYPE
|
||||
}
|
||||
|
||||
return arpConstantes
|
19
network/lib/network/arp/init.lua
Normal file
19
network/lib/network/arp/init.lua
Normal file
@@ -0,0 +1,19 @@
|
||||
local arpAPI = require("network.arp.api")
|
||||
local arpConst = require("network.arp.constantes")
|
||||
|
||||
---@class libARP
|
||||
local arp = {
|
||||
ARPFrame = require("network.arp.ARPFrame"),
|
||||
ARPLayer = require("network.arp.ARPLayer"),
|
||||
addCached = arpAPI.addCached,
|
||||
getAddress = arpAPI.getAddress,
|
||||
getLocalAddress = arpAPI.getLocalAddress,
|
||||
getLocalHardwareAddress = arpAPI.getLocalHardwareAddress,
|
||||
list = arpAPI.list,
|
||||
setLocalAddress = arpAPI.setLocalAddress,
|
||||
HARDWARE_TYPE = arpConst.HARDWARE_TYPE,
|
||||
OPERATION = arpConst.OPERATION,
|
||||
PROTOCOLE_TYPE = arpConst.PROTOCOLE_TYPE
|
||||
}
|
||||
|
||||
return arp
|
@@ -216,14 +216,6 @@ function EthernetInterface:getAddr()
|
||||
return self._modem.address
|
||||
end
|
||||
|
||||
local function time(f, ...)
|
||||
local t, c = computer.uptime(), os.clock()
|
||||
local r = table.pack(f(...))
|
||||
local t2, c2 = computer.uptime() - t, os.clock() - c
|
||||
require("event").onError(string.format("%s:%s\t", debug.getinfo(3, "S").short_src, debug.getinfo(3, "l").currentline) .. t2 .. " " .. c2)
|
||||
return table.unpack(r)
|
||||
end
|
||||
|
||||
---Send a ethernet frame
|
||||
---@param dst string
|
||||
---@param eFrame EthernetFrame
|
||||
@@ -232,9 +224,9 @@ function EthernetInterface:send(dst, eFrame)
|
||||
dst = dst or eFrame:getDst()
|
||||
checkArg(2, eFrame, "table")
|
||||
if (dst == ethernet.MAC_BROADCAST) then
|
||||
time(self._modem.broadcast, self._port, eFrame:pack())
|
||||
self._modem.broadcast(self._port, eFrame:pack())
|
||||
else
|
||||
time(self._modem.send, dst, self._port, eFrame:pack())
|
||||
self._modem.send(dst, self._port, eFrame:pack())
|
||||
end
|
||||
end
|
||||
|
62
network/lib/network/icmp/ICMPLayer.lua
Normal file
62
network/lib/network/icmp/ICMPLayer.lua
Normal file
@@ -0,0 +1,62 @@
|
||||
local network = require("network")
|
||||
local ipv4 = require("network.ipv4")
|
||||
local ICMPPacket = require("network.icmp.ICMPPacket")
|
||||
local icmpConst = require("network.icmp.constantes")
|
||||
local event = require("event")
|
||||
|
||||
|
||||
---@class ICMPLayer:OSINetworkLayer
|
||||
---@field private _layer OSINetworkLayer
|
||||
---@operator call:ICMPLayer
|
||||
---@overload fun(layer:IPv4Layer):ICMPLayer
|
||||
local ICMPLayer = {}
|
||||
ICMPLayer.layerType = ipv4.PROTOCOLS.ICMP
|
||||
|
||||
setmetatable(ICMPLayer, {
|
||||
---@param layer OSINetworkLayer
|
||||
---@return ICMPLayer
|
||||
__call = function(self, layer)
|
||||
local o = {
|
||||
_layer = layer
|
||||
}
|
||||
setmetatable(o, {__index = self})
|
||||
layer:setLayer(o)
|
||||
return o
|
||||
end,
|
||||
})
|
||||
|
||||
---@param payload ICMPPacket
|
||||
function ICMPLayer:send(dst, payload)
|
||||
local localIP = network.router:getRoute(dst).interface:getAddr()
|
||||
assert(localIP, "[ICMP] : no local IP. Cannot send packet")
|
||||
local ipDatagram = ipv4.IPv4Packet(localIP, dst, payload)
|
||||
network.router:send(ipDatagram)
|
||||
end
|
||||
|
||||
---@param payload string
|
||||
function ICMPLayer:payloadHandler(from, to, payload)
|
||||
local icmpPacket = ICMPPacket.unpack(payload)
|
||||
if (icmpPacket:getType() == icmpConst.TYPE.ECHO_REQUEST) then
|
||||
local reply = ICMPPacket(icmpConst.TYPE.ECHO_REPLY, icmpConst.CODE.ECHO_REPLY.ECHO_REPLY, icmpPacket:getParam(), icmpPacket:getPayload())
|
||||
self:send(from, reply)
|
||||
elseif (icmpPacket:getType() == icmpConst.TYPE.ECHO_REPLY) then
|
||||
event.push("ICMP", from, to, icmpPacket:getType(), icmpPacket:getCode(), icmpPacket:getParam(), icmpPacket:getPayload())
|
||||
end
|
||||
end
|
||||
|
||||
function ICMPLayer:getAddr()
|
||||
return self._layer:getAddr()
|
||||
end
|
||||
|
||||
---Send a timeout icmp message
|
||||
---@param packet IPv4Packet
|
||||
---@param code number
|
||||
function ICMPLayer:sendTimeout(packet, code)
|
||||
local icmpPacket = ICMPPacket(icmpConst.TYPE.TIME_EXCEEDED, icmpConst.CODE.TIME_EXCEEDED.TTL_expired_in_transit, nil, string.format("%.2x%.2x%.4x%.4x%.2x%.4x%.2x%.2x%.8x%.8x%s",
|
||||
packet:getDscp(), packet:getEcn(), packet:getLen(), packet:getId(), packet:getFlags(),
|
||||
packet:getFragmentOffset(), packet:getTtl(), packet:getProtocol(),
|
||||
packet:getSrc(), packet:getDst(), packet:getPayload():sub(1, 8)))
|
||||
self:send(packet:getSrc(), icmpPacket)
|
||||
end
|
||||
|
||||
return ICMPLayer
|
92
network/lib/network/icmp/ICMPPacket.lua
Normal file
92
network/lib/network/icmp/ICMPPacket.lua
Normal file
@@ -0,0 +1,92 @@
|
||||
local ipv4 = require("network.ipv4")
|
||||
|
||||
|
||||
---@class ICMPPacket:Payload
|
||||
---@field private _type number
|
||||
---@field private _code number
|
||||
---@field private _param number
|
||||
---@field private _payload string
|
||||
---@operator call:ICMPPacket
|
||||
---@overload fun(type:icmpType,code:number,param:number,paylaod:string):ICMPPacket
|
||||
---@overload fun(type:icmpType,code:number,param:number):ICMPPacket
|
||||
---@overload fun(type:icmpType,code:number):ICMPPacket
|
||||
local ICMPPacket = {}
|
||||
ICMPPacket.payloadType = ipv4.PROTOCOLS.ICMP
|
||||
|
||||
setmetatable(ICMPPacket, {
|
||||
---@param type icmpType
|
||||
---@param code number
|
||||
---@param param? number
|
||||
---@param payload? string
|
||||
---@return ICMPPacket
|
||||
__call = function(self, type, code, param, payload)
|
||||
local o = {
|
||||
_type = type,
|
||||
_code = code,
|
||||
_param = param or 0,
|
||||
_payload = payload or ""
|
||||
}
|
||||
setmetatable(o, {__index = self})
|
||||
return o
|
||||
end
|
||||
})
|
||||
|
||||
--#region getter/setter
|
||||
|
||||
---@return number
|
||||
function ICMPPacket:getType() return self._type end
|
||||
|
||||
---@param val number
|
||||
function ICMPPacket:setType(val)
|
||||
checkArg(1, val, "number")
|
||||
self._type = val
|
||||
end
|
||||
|
||||
---@return number
|
||||
function ICMPPacket:getCode() return self._code end
|
||||
|
||||
---@param val number
|
||||
function ICMPPacket:setCode(val)
|
||||
checkArg(1, val, "number")
|
||||
self._code = val
|
||||
end
|
||||
|
||||
---@return number
|
||||
function ICMPPacket:getParam() return self._param end
|
||||
|
||||
---@param val number
|
||||
function ICMPPacket:setParam(val)
|
||||
checkArg(1, val, "number")
|
||||
self._param = val
|
||||
end
|
||||
|
||||
---@return string
|
||||
function ICMPPacket:getPayload() return self._payload end
|
||||
|
||||
---@param val string
|
||||
function ICMPPacket:setPayload(val)
|
||||
checkArg(1, val, "string")
|
||||
self._payload = val
|
||||
end
|
||||
|
||||
--#endregion
|
||||
|
||||
function ICMPPacket:pack()
|
||||
return string.format("%.2x%.2x%.8x%s", self._type, self._code, self._param, self._payload)
|
||||
end
|
||||
|
||||
---@return ICMPPacket
|
||||
function ICMPPacket.unpack(val)
|
||||
local o = "%x%x"
|
||||
local patern = string.format("(%s)(%s)(%s)(%s)", o, o, o:rep(4), ".*")
|
||||
local a, b, c, d = val:match(patern)
|
||||
a = tonumber(a, 16);
|
||||
assert(a)
|
||||
b = tonumber(b, 16);
|
||||
assert(b)
|
||||
c = tonumber(c, 16);
|
||||
assert(c)
|
||||
return ICMPPacket(a, b, c, d)
|
||||
end
|
||||
|
||||
return ICMPPacket
|
82
network/lib/network/icmp/constantes.lua
Normal file
82
network/lib/network/icmp/constantes.lua
Normal file
@@ -0,0 +1,82 @@
|
||||
---@class icmpConstantes
|
||||
local icmpConst = {}
|
||||
---@enum icmpType
|
||||
icmpConst.TYPE = {
|
||||
ECHO_REPLY = 0,
|
||||
DESTINATION_UNREACHABLE = 3,
|
||||
REDIRECT_MESSAGE = 5,
|
||||
ECHO_REQUEST = 8,
|
||||
ROUTER_ADVERTISEMENT = 9,
|
||||
ROUTER_SOLICITATION = 10,
|
||||
TIME_EXCEEDED = 11,
|
||||
PARAMETER_PROBELM_BAD_IP_HEADER = 12,
|
||||
TIMESTAMP = 13,
|
||||
TIMESTAMP_REPLY = 14,
|
||||
EXTENDED_ECHO_REQUEST = 42,
|
||||
EXTENDED_ECHO_REPLY = 43
|
||||
}
|
||||
icmpConst.CODE = {
|
||||
ECHO_REPLY = {
|
||||
ECHO_REPLY = 0
|
||||
},
|
||||
DESTINATION_UNREACHABLE = {
|
||||
DESTINATION_NETWORK_UNREACHABLE = 0,
|
||||
DESTINATION_HOST_UNREACHABLE = 1,
|
||||
DESTINATION_PROTOCOL_UNREACHABLE = 2,
|
||||
DESTINATION_PORT_UNREACHABLE = 3,
|
||||
FRAGMENTATION_REQUIRED_AND_DF_FLAG_SET = 4,
|
||||
SOURCE_ROUTE_FAILED = 5,
|
||||
DESTINATION_NETWORK_UNKNOWN = 6,
|
||||
DESTINATION_HOST_UNKNOWN = 7,
|
||||
SOURCE_HOST_ISOLATED = 8,
|
||||
NETWORK_ADMINISTRATIVELY_PROHIBITED = 9,
|
||||
HOST_ADMINISTRATIVELY_PROHIBITED = 10,
|
||||
NETWORK_UNREACHABLE_FOR_TOS = 11,
|
||||
HOST_UNREACHABLE_FOR_TOS = 12,
|
||||
COMMUNICATION_ADMINISTRATIVELY_PROHIBITED = 13,
|
||||
HOST_PRECEDENCE_VIOLATION = 14,
|
||||
PRECEDENCE_CUTOFF_IN_EFFECT = 15,
|
||||
},
|
||||
REDIRECT_MESSAGE = {
|
||||
REDIRECT_DATAGRAM_FOR_THE_NETWORK = 0,
|
||||
REDIRECT_DATAGRAM_FOR_THE_HOST = 1,
|
||||
REDIRECT_DATAGRAM_FOR_THE_TOS_NETWORK = 2,
|
||||
REDIRECT_DATAGRAM_FOR_THE_TOS_HOST = 3,
|
||||
},
|
||||
ECHO_REQUEST = {
|
||||
Echo_request = 0,
|
||||
},
|
||||
ROUTER_ADVERTISEMENT = {
|
||||
ROUTER_ADVERTISEMENt = 0,
|
||||
},
|
||||
ROUTER_SOLICITATION = {
|
||||
Router_discovery_selection_solicitation = 0,
|
||||
},
|
||||
TIME_EXCEEDED = {
|
||||
TTL_expired_in_transit = 0,
|
||||
Fragment_reassembly_time_exceeded = 1,
|
||||
},
|
||||
PARAMETER_PROBELM_BAD_IP_HEADER = {
|
||||
Pointer_indicates_the_error = 0,
|
||||
Missing_a_required_option = 1,
|
||||
Bad_length = 2,
|
||||
},
|
||||
TIMESTAMP = {
|
||||
Timestamp = 0,
|
||||
},
|
||||
TIMESTAMP_REPLY = {
|
||||
Timestamp_reply = 0,
|
||||
},
|
||||
EXTENDED_ECHO_REQUEST = {
|
||||
Request_Extended_Echo = 0,
|
||||
},
|
||||
EXTENDED_ECHO_REPLY = {
|
||||
No_Error = 0,
|
||||
Malformed_Query = 1,
|
||||
No_Such_Interface = 2,
|
||||
No_Such_Table_Entry = 3,
|
||||
Multiple_Interfaces_Satisfy_Query = 4,
|
||||
}
|
||||
}
|
||||
|
||||
return icmpConst
|
13
network/lib/network/icmp/init.lua
Normal file
13
network/lib/network/icmp/init.lua
Normal file
@@ -0,0 +1,13 @@
|
||||
local icmpConst = require("network.icmp.constantes")
|
||||
local ICMPLayer = require("network.icmp.ICMPLayer")
|
||||
local ICMPPacket = require("network.icmp.ICMPPacket")
|
||||
|
||||
---@class icmpLib
|
||||
local icmp = {
|
||||
ICMPLayer = ICMPLayer,
|
||||
ICMPPacket = ICMPPacket,
|
||||
CODE = icmpConst.CODE,
|
||||
TYPE = icmpConst.TYPE
|
||||
}
|
||||
|
||||
return icmp
|
@@ -4,12 +4,15 @@ local routing = require("routing")
|
||||
---@class InterfaceTypes
|
||||
---@field ethernet EthernetInterface
|
||||
---@field ip IPv4Layer
|
||||
---@field icmp ICMPLayer
|
||||
---@field udp UDPLayer
|
||||
|
||||
---@class networklib
|
||||
local networklib = {}
|
||||
networklib.internal = {}
|
||||
networklib.internal = {
|
||||
---@type UDPLayer
|
||||
udp = nil,
|
||||
---@type ICMPLayer
|
||||
icmp = nil
|
||||
}
|
||||
---@type table<string,InterfaceTypes>
|
||||
networklib.interfaces = {}
|
||||
---@type IPv4Router
|
||||
@@ -21,14 +24,36 @@ networklib.router = routing.IPv4Router()
|
||||
---@overload fun():table<string,InterfaceTypes>
|
||||
function networklib.getInterface(filter)
|
||||
checkArg(1, filter, "string", "nil")
|
||||
if (filter) then return {networklib.interfaces.ethernet[component.get(filter, "modem")] or nil} end
|
||||
if (filter) then return {networklib.interfaces[component.get(filter, "modem")] or nil} end
|
||||
return networklib.interfaces
|
||||
end
|
||||
|
||||
---Get the primary interface if set
|
||||
---@return InterfaceTypes
|
||||
function networklib.getPrimaryInterface()
|
||||
return networklib.getInterface(component.getPrimary("modem").address)
|
||||
return networklib.getInterface(component.getPrimary("modem").address)[1]
|
||||
end
|
||||
|
||||
--=============================================================================
|
||||
--#region udp
|
||||
|
||||
networklib.udp = {}
|
||||
|
||||
function networklib.udp.getInterface()
|
||||
return networklib.internal.udp
|
||||
end
|
||||
|
||||
--#endregion
|
||||
--=============================================================================
|
||||
--#region icmp
|
||||
|
||||
networklib.icmp = {}
|
||||
|
||||
---@return ICMPLayer
|
||||
function networklib.icmp.getInterface()
|
||||
return networklib.internal.icmp
|
||||
end
|
||||
|
||||
--#endregion
|
||||
|
||||
return networklib
|
172
network/lib/network/ipv4/IPv4Layer.lua
Normal file
172
network/lib/network/ipv4/IPv4Layer.lua
Normal file
@@ -0,0 +1,172 @@
|
||||
local ethernet = require("network.ethernet")
|
||||
local arp = require("network.arp")
|
||||
local ipv4Address = require("network.ipv4.address")
|
||||
local IPv4Packet = require("network.ipv4.IPv4Packet")
|
||||
local ipv4Consts = require("network.ipv4.constantes")
|
||||
local bit32 = require("bit32")
|
||||
|
||||
|
||||
---@class IPv4Layer : OSINetworkLayer
|
||||
---@field private _addr number
|
||||
---@field private _mask number
|
||||
---@field private _router IPv4Router
|
||||
---@field package _layer OSIDataLayer
|
||||
---@field package _arp ARPLayer
|
||||
---@field private _buffer table<number,table<number,table<number,IPv4Packet>>>
|
||||
---@operator call:IPv4Layer
|
||||
---@overload fun(dataLayer:OSIDataLayer,router:IPv4Router,addr:number|string,mask:number|string):IPv4Layer
|
||||
local IPv4Layer = {}
|
||||
IPv4Layer.layerType = ethernet.TYPE.IPv4
|
||||
|
||||
|
||||
setmetatable(IPv4Layer, {
|
||||
---@param dataLayer OSIDataLayer
|
||||
---@param router IPv4Router
|
||||
---@param addr number|string
|
||||
---@param mask number|string
|
||||
---@return IPv4Layer
|
||||
__call = function(self, dataLayer, router, addr, mask)
|
||||
checkArg(1, dataLayer, "table")
|
||||
checkArg(2, addr, "number", "string")
|
||||
checkArg(3, mask, "number", "string")
|
||||
local o = {
|
||||
_addr = 0,
|
||||
_mask = 0,
|
||||
_layer = dataLayer,
|
||||
_arp = nil,
|
||||
_router = nil,
|
||||
_buffer = {}
|
||||
}
|
||||
setmetatable(o, {__index = self})
|
||||
o:setAddr(addr)
|
||||
o:setMask(mask)
|
||||
dataLayer:setLayer(o)
|
||||
o:setRouter(router)
|
||||
--arp
|
||||
o._arp = arp.ARPLayer(dataLayer)
|
||||
arp.setLocalAddress(arp.HARDWARE_TYPE.ETHERNET, arp.PROTOCOLE_TYPE.IPv4, dataLayer:getAddr(), o:getAddr())
|
||||
return o
|
||||
end,
|
||||
})
|
||||
|
||||
---Set the interfaces's address
|
||||
---@param val string|number
|
||||
function IPv4Layer:setAddr(val)
|
||||
if (type(val) == "number" and val > 0 and val < 0xffffffff) then
|
||||
self._addr = val
|
||||
else
|
||||
self._addr = ipv4Address.fromString(val)
|
||||
end
|
||||
end
|
||||
|
||||
---Get the local IPv4
|
||||
---@return number
|
||||
function IPv4Layer:getAddr() return self._addr end
|
||||
|
||||
---Set the interfaces's address mask
|
||||
---@param val number
|
||||
function IPv4Layer:setMask(val)
|
||||
checkArg(1, val, "number")
|
||||
local found0 = false
|
||||
for i = 31, 0, -1 do
|
||||
if (not found0) then
|
||||
found0 = 0 == bit32.extract(val, i)
|
||||
else
|
||||
if (bit32.extract(val, i) == 1) then
|
||||
error("Invalid mask", 2)
|
||||
end
|
||||
end
|
||||
end
|
||||
if (type(val) == "number" and val > 0 and val < 0xffffffff) then
|
||||
self._mask = val
|
||||
else
|
||||
self._mask = ipv4Address.fromString(val)
|
||||
end
|
||||
end
|
||||
|
||||
---Get the local mask
|
||||
---@return number
|
||||
function IPv4Layer:getMask() return self._mask end
|
||||
|
||||
function IPv4Layer:getMTU() return self._layer:getMTU() - 38 end
|
||||
|
||||
---@param layer OSILayer
|
||||
function IPv4Layer:setLayer(layer)
|
||||
self._router:setProtocol(layer)
|
||||
end
|
||||
|
||||
---Set the layer's router. Used for rerouting packets
|
||||
---@param router IPv4Router
|
||||
function IPv4Layer:setRouter(router)
|
||||
self._router = router
|
||||
self._router:setLayer(self)
|
||||
self._router:addRoute({network = bit32.band(self:getAddr(), self:getMask()), mask = self:getMask(), gateway = self:getAddr(), metric = 0, interface = self})
|
||||
end
|
||||
|
||||
---Get the router.
|
||||
---@return IPv4Router
|
||||
function IPv4Layer:getRouter()
|
||||
return self._router
|
||||
end
|
||||
|
||||
---Send a IPv4Packet
|
||||
---@param self IPv4Layer
|
||||
---@param to number
|
||||
---@param payload IPv4Packet
|
||||
---@overload fun(self:IPv4Layer,payload:IPv4Packet)
|
||||
function IPv4Layer:send(to, payload)
|
||||
if (not payload) then
|
||||
---@diagnostic disable-next-line: cast-local-type
|
||||
payload = to
|
||||
to = payload:getDst()
|
||||
end
|
||||
---@cast payload IPv4Packet
|
||||
if (to == self:getAddr()) then self:payloadHandler(self:getAddr(), to, payload) end
|
||||
|
||||
local dst = arp.getAddress(self._arp, arp.HARDWARE_TYPE.ETHERNET, self.layerType, to, self:getAddr())
|
||||
if (not dst) then error("Cannot resolve IP", 2) end
|
||||
for _, payloadFragment in pairs(payload:getFragments(self:getMTU())) do
|
||||
local eFrame = ethernet.EthernetFrame(self._layer:getAddr(), dst, nil, self.layerType, payloadFragment:pack())
|
||||
self._layer:send(dst, eFrame)
|
||||
end
|
||||
end
|
||||
|
||||
function IPv4Layer:payloadHandler(from, to, payload)
|
||||
local pl = IPv4Packet.unpack(payload)
|
||||
if (pl:getDst()) == self:getAddr() then --if the packet destination is here
|
||||
if (pl:getLen() > 1) then --merge framents
|
||||
local bufferID = string.format("%d%d%d%d", from, to, pl:getProtocol(), pl:getId())
|
||||
self._buffer[bufferID] = self._buffer[bufferID] or {}
|
||||
|
||||
--place the packet in a buffer
|
||||
table.insert(self._buffer[bufferID], math.max(#self._buffer[bufferID], pl:getFragmentOffset()), pl)
|
||||
|
||||
--if the buffer hold all the packets merge them
|
||||
if (#self._buffer[bufferID] == pl:getLen()) then
|
||||
local fullPayload, proto = {}, pl:getProtocol()
|
||||
for i, fragment in ipairs(self._buffer[pl:getProtocol()][pl:getSrc()]) do
|
||||
table.insert(fullPayload, math.max(#fullPayload, fragment:getFragmentOffset()), fragment:getPayload())
|
||||
end
|
||||
pl = IPv4Packet(pl:getSrc(), pl:getDst(), table.concat(fullPayload), pl:getProtocol())
|
||||
pl:setProtocol(proto)
|
||||
self._buffer[pl:getProtocol()][pl:getSrc()] = nil
|
||||
end
|
||||
--TODO : handle merge timeout
|
||||
end
|
||||
--if the packet is complete, send it to the router to be handed to the destination program
|
||||
self._router:payloadHandler(pl:getSrc(), pl:getDst(), pl:pack())
|
||||
elseif (self._router) then
|
||||
--reduce ttl
|
||||
pl:setTtl(pl:getTtl() - 1)
|
||||
if (pl:getTtl() >= 1) then
|
||||
self._router:send(pl)
|
||||
else
|
||||
--TODO : refactor using router
|
||||
if (self._layers[ipv4Consts.PROTOCOLS.ICMP]) then
|
||||
self._layers[ipv4Consts.PROTOCOLS.ICMP] --[[@as ICMPLayer]]:sendTimeout(pl, 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return IPv4Layer
|
@@ -1,17 +1,7 @@
|
||||
local bit32 = require("bit32")
|
||||
local arp = require("layers.arp")
|
||||
local ethernet = require("layers.ethernet")
|
||||
local ethernet = require("network.ethernet")
|
||||
|
||||
|
||||
---@class ipv4lib
|
||||
local ipv4lib = {}
|
||||
---@enum ipv4Protocol
|
||||
ipv4lib.PROTOCOLS = {
|
||||
ICMP = 1, -- Internet Control Message Protocol
|
||||
TCP = 6, -- Transmission Control Protocol
|
||||
UDP = 17, -- User Datagram Protocol
|
||||
OSPF = 89, -- Open Shortest Path First
|
||||
}
|
||||
---@class IPv4Header
|
||||
---@field dscp number Differentiated Services Code Point
|
||||
---@field ecn number Explicit Congestion Notification
|
||||
@@ -30,7 +20,6 @@ ipv4lib.PROTOCOLS = {
|
||||
---@field protocol ipv4Protocol
|
||||
|
||||
--=============================================================================
|
||||
|
||||
--#region IPv4Packet
|
||||
|
||||
---@class IPv4Packet : Payload
|
||||
@@ -260,17 +249,17 @@ end
|
||||
function IPv4Packet.unpack(val)
|
||||
local o = "%x%x"
|
||||
local patern = string.format("(%s)(%s)(%s)(%s)(%s)(%s)(%s)(%s)(%s)(%s)(%s)",
|
||||
o, --dscp
|
||||
o, --ecn
|
||||
o, --dscp
|
||||
o, --ecn
|
||||
o:rep(2), --len
|
||||
o:rep(2), --id
|
||||
o, --flags
|
||||
o, --flags
|
||||
o:rep(2), --fragmentOffset
|
||||
o, --ttl
|
||||
o, --protocol
|
||||
o, --ttl
|
||||
o, --protocol
|
||||
o:rep(4), --src
|
||||
o:rep(4), --dst
|
||||
".*" --payload
|
||||
".*" --payload
|
||||
)
|
||||
local dscp, ecn, len, id, flags, fragmentOffset, ttl, protocol, src, dst, payload = val:match(patern)
|
||||
|
||||
@@ -311,214 +300,4 @@ function IPv4Packet.unpack(val)
|
||||
return packet
|
||||
end
|
||||
|
||||
--#endregion
|
||||
--=============================================================================
|
||||
|
||||
--#region IPv4Layer
|
||||
|
||||
---@class IPv4Layer : OSINetworkLayer
|
||||
---@field private _addr number
|
||||
---@field private _mask number
|
||||
---@field private _router IPv4Router
|
||||
---@field package _layer OSIDataLayer
|
||||
---@field package _layers table<ipv4Protocol,OSINetworkLayer>
|
||||
---@field package _arp ARPLayer
|
||||
---@field private _buffer table<number,table<number,table<number,IPv4Packet>>>
|
||||
---@operator call:IPv4Layer
|
||||
---@overload fun(dataLayer:OSIDataLayer,router:IPv4Router,addr:number|string,mask:number|string):IPv4Layer
|
||||
local IPv4Layer = {}
|
||||
|
||||
IPv4Layer.layerType = ethernet.TYPE.IPv4
|
||||
|
||||
|
||||
setmetatable(IPv4Layer, {
|
||||
---@param dataLayer OSIDataLayer
|
||||
---@param router IPv4Router
|
||||
---@param addr number|string
|
||||
---@param mask number|string
|
||||
---@return IPv4Layer
|
||||
__call = function(self, dataLayer, router, addr, mask)
|
||||
checkArg(1, dataLayer, "table")
|
||||
checkArg(2, addr, "number", "string")
|
||||
checkArg(3, mask, "number", "string")
|
||||
local o = {
|
||||
_addr = 0,
|
||||
_mask = 0,
|
||||
_layer = dataLayer,
|
||||
_layers = {},
|
||||
_arp = nil,
|
||||
_router = nil,
|
||||
_buffer = {}
|
||||
}
|
||||
setmetatable(o, {__index = self})
|
||||
o:setAddr(addr)
|
||||
o:setMask(mask)
|
||||
dataLayer:setLayer(o)
|
||||
o:setRouter(router)
|
||||
--arp
|
||||
o._arp = arp.ARPLayer(dataLayer)
|
||||
arp.setLocalAddress(arp.HARDWARE_TYPE.ETHERNET, arp.PROTOCOLE_TYPE.IPv4, dataLayer:getAddr(), o:getAddr())
|
||||
return o
|
||||
end,
|
||||
})
|
||||
|
||||
---Set the interfaces's address
|
||||
---@param val string|number
|
||||
function IPv4Layer:setAddr(val)
|
||||
if (type(val) == "number" and val > 0 and val < 0xffffffff) then
|
||||
self._addr = val
|
||||
else
|
||||
self._addr = ipv4lib.address.fromString(val)
|
||||
end
|
||||
end
|
||||
|
||||
---Get the local IPv4
|
||||
---@return number
|
||||
function IPv4Layer:getAddr() return self._addr end
|
||||
|
||||
---Set the interfaces's address mask
|
||||
---@param val string|number
|
||||
function IPv4Layer:setMask(val)
|
||||
local found0 = false
|
||||
for i = 31, 0, -1 do
|
||||
if (not found0) then
|
||||
found0 = 0 == bit32.extract(val, i)
|
||||
else
|
||||
if (bit32.extract(val, i) == 1) then
|
||||
error("Invalid mask", 2)
|
||||
end
|
||||
end
|
||||
end
|
||||
if (type(val) == "number" and val > 0 and val < 0xffffffff) then
|
||||
self._mask = val
|
||||
else
|
||||
self._mask = ipv4lib.address.fromString(val)
|
||||
end
|
||||
end
|
||||
|
||||
---Get the local mask
|
||||
---@return number
|
||||
function IPv4Layer:getMask() return self._mask end
|
||||
|
||||
function IPv4Layer:getMTU() return self._layer:getMTU() - 38 end
|
||||
|
||||
---@param layer OSINetworkLayer
|
||||
function IPv4Layer:setLayer(layer)
|
||||
self._layers[layer.layerType] = layer
|
||||
end
|
||||
|
||||
---Set the layer's router. Used for rerouting packets
|
||||
---@param router IPv4Router
|
||||
function IPv4Layer:setRouter(router)
|
||||
self._router = router
|
||||
self._router:setLayer(self)
|
||||
self._router:addRoute({network = bit32.band(self:getAddr(), self:getMask()), mask = self:getMask(), gateway = self:getAddr(), metric = 0, interface = self})
|
||||
end
|
||||
|
||||
---Get the router.
|
||||
---@return IPv4Router
|
||||
function IPv4Layer:getRouter()
|
||||
return self._router
|
||||
end
|
||||
|
||||
---Send a IPv4Packet
|
||||
---@param self IPv4Layer
|
||||
---@param to number
|
||||
---@param payload IPv4Packet
|
||||
---@overload fun(self:IPv4Layer,payload:IPv4Packet)
|
||||
function IPv4Layer:send(to, payload)
|
||||
if (not payload) then
|
||||
---@diagnostic disable-next-line: cast-local-type
|
||||
payload = to
|
||||
to = payload:getDst()
|
||||
end
|
||||
local dst = arp.getAddress(self._arp, arp.HARDWARE_TYPE.ETHERNET, self.layerType, to, self:getAddr())
|
||||
if (not dst) then error("Cannot resolve IP", 2) end
|
||||
for _, payloadFragment in pairs(payload:getFragments(self:getMTU())) do
|
||||
local eFrame = ethernet.EthernetFrame(self._layer:getAddr(), dst, nil, self.layerType, payloadFragment:pack())
|
||||
self._layer:send(dst, eFrame)
|
||||
end
|
||||
end
|
||||
|
||||
function IPv4Layer:payloadHandler(from, to, payload)
|
||||
local pl = IPv4Packet.unpack(payload)
|
||||
if (pl:getDst()) == self:getAddr() then --if the packet destination is here
|
||||
if (pl:getLen() > 1) then --merge framents
|
||||
self._buffer[pl:getProtocol()] = self._buffer[pl:getProtocol()] or {}
|
||||
self._buffer[pl:getProtocol()][pl:getSrc()] = self._buffer[pl:getProtocol()][pl:getSrc()] or {}
|
||||
|
||||
--place the packet in a buffer
|
||||
table.insert(self._buffer[pl:getProtocol()][pl:getSrc()], math.max(#self._buffer[pl:getProtocol()][pl:getSrc()], pl:getFragmentOffset()), pl)
|
||||
|
||||
--if the buffer hold all the packets merge them
|
||||
if (#self._buffer[pl:getProtocol()][pl:getSrc()] == pl:getLen()) then
|
||||
local fullPayload, proto = {}, pl:getProtocol()
|
||||
for i, fragment in ipairs(self._buffer[pl:getProtocol()][pl:getSrc()]) do
|
||||
require("event").onError(string.format("%d %d", #fullPayload, fragment:getFragmentOffset()))
|
||||
table.insert(fullPayload, math.max(#fullPayload, fragment:getFragmentOffset()), fragment:getPayload())
|
||||
end
|
||||
pl = IPv4Packet(pl:getSrc(), pl:getDst(), table.concat(fullPayload), pl:getProtocol())
|
||||
pl:setProtocol(proto)
|
||||
self._buffer[pl:getProtocol()][pl:getSrc()] = nil
|
||||
end
|
||||
--TODO : handle merge timeout
|
||||
end
|
||||
--if the packet is complete, send it to the arporiate layer
|
||||
if (self._layers[pl:getProtocol()] and pl:getLen() == 1) then
|
||||
self._layers[pl:getProtocol()]:payloadHandler(pl:getSrc(), pl:getDst(), pl:getPayload())
|
||||
end
|
||||
elseif (self._router) then
|
||||
--reduce ttl
|
||||
pl:setTtl(pl:getTtl() - 1)
|
||||
if (pl:getTtl() >= 1) then
|
||||
self._router:send(pl)
|
||||
else
|
||||
if (self._layers[ipv4lib.PROTOCOLS.ICMP]) then
|
||||
self._layers[ipv4lib.PROTOCOLS.ICMP] --[[@as ICMPLayer]]:sendTimeout(pl, 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--#endregion
|
||||
--=============================================================================
|
||||
|
||||
ipv4lib.address = {}
|
||||
|
||||
function ipv4lib.address.fromString(val)
|
||||
local a, b, c, d = val:match("^(%d+)%.(%d+)%.(%d+)%.(%d+)$")
|
||||
a = tonumber(a)
|
||||
b = tonumber(b)
|
||||
c = tonumber(c)
|
||||
d = tonumber(d)
|
||||
if (not (0 <= a and a <= 255)) then error("#1 Not a valid IPv4", 2) end
|
||||
if (not (0 <= b and b <= 255)) then error("#1 Not a valid IPv4", 2) end
|
||||
if (not (0 <= c and c <= 255)) then error("#1 Not a valid IPv4", 2) end
|
||||
if (not (0 <= d and d <= 255)) then error("#1 Not a valid IPv4", 2) end
|
||||
return bit32.lshift(a, 8 * 3) + bit32.lshift(b, 8 * 2) + bit32.lshift(c, 8 * 1) + d
|
||||
end
|
||||
|
||||
function ipv4lib.address.tostring(val)
|
||||
local a = bit32.extract(val, 24, 8)
|
||||
local b = bit32.extract(val, 16, 8)
|
||||
local c = bit32.extract(val, 8, 8)
|
||||
local d = bit32.extract(val, 0, 8)
|
||||
return string.format("%d.%d.%d.%d", a, b, c, d)
|
||||
end
|
||||
|
||||
---Get the address and mask from the CIDR notation
|
||||
---@param cidr string
|
||||
---@return number address, number mask
|
||||
function ipv4lib.address.fromCIDR(cidr)
|
||||
local address, mask = cidr:match("^(%d+%.%d+%.%d+%.%d+)/(%d+)$")
|
||||
mask = tonumber(mask)
|
||||
assert(mask >= 0, "Invalid mask")
|
||||
assert(mask <= 32, "Invalid mask")
|
||||
return ipv4lib.address.fromString(address), bit32.lshift(2 ^ mask - 1, 32 - mask)
|
||||
end
|
||||
|
||||
--=============================================================================
|
||||
|
||||
ipv4lib.IPv4Layer = IPv4Layer
|
||||
ipv4lib.IPv4Packet = IPv4Packet
|
||||
return ipv4lib
|
||||
return IPv4Packet
|
42
network/lib/network/ipv4/address.lua
Normal file
42
network/lib/network/ipv4/address.lua
Normal file
@@ -0,0 +1,42 @@
|
||||
local bit32 = require("bit32")
|
||||
|
||||
---@class ipv4AdressLib
|
||||
local ipv4Adress = {}
|
||||
|
||||
function ipv4Adress.fromString(val)
|
||||
local a, b, c, d = val:match("^(%d+)%.(%d+)%.(%d+)%.(%d+)$")
|
||||
a = tonumber(a)
|
||||
b = tonumber(b)
|
||||
c = tonumber(c)
|
||||
d = tonumber(d)
|
||||
if (not (0 <= a and a <= 255)) then error("#1 Not a valid IPv4", 2) end
|
||||
if (not (0 <= b and b <= 255)) then error("#1 Not a valid IPv4", 2) end
|
||||
if (not (0 <= c and c <= 255)) then error("#1 Not a valid IPv4", 2) end
|
||||
if (not (0 <= d and d <= 255)) then error("#1 Not a valid IPv4", 2) end
|
||||
assert(a)
|
||||
assert(b)
|
||||
assert(c)
|
||||
assert(d)
|
||||
return bit32.lshift(a, 8 * 3) + bit32.lshift(b, 8 * 2) + bit32.lshift(c, 8 * 1) + d
|
||||
end
|
||||
|
||||
function ipv4Adress.tostring(val)
|
||||
local a = bit32.extract(val, 24, 8)
|
||||
local b = bit32.extract(val, 16, 8)
|
||||
local c = bit32.extract(val, 8, 8)
|
||||
local d = bit32.extract(val, 0, 8)
|
||||
return string.format("%d.%d.%d.%d", a, b, c, d)
|
||||
end
|
||||
|
||||
---Get the address and mask from the CIDR notation
|
||||
---@param cidr string
|
||||
---@return number address, number mask
|
||||
function ipv4Adress.fromCIDR(cidr)
|
||||
local address, mask = cidr:match("^(%d+%.%d+%.%d+%.%d+)/(%d+)$")
|
||||
mask = tonumber(mask)
|
||||
assert(mask >= 0, "Invalid mask")
|
||||
assert(mask <= 32, "Invalid mask")
|
||||
return ipv4Adress.fromString(address), bit32.lshift(2 ^ mask - 1, 32 - mask)
|
||||
end
|
||||
|
||||
return ipv4Adress
|
12
network/lib/network/ipv4/constantes.lua
Normal file
12
network/lib/network/ipv4/constantes.lua
Normal file
@@ -0,0 +1,12 @@
|
||||
---@class ipv4ConstsLib
|
||||
local ipv4Consts = {}
|
||||
|
||||
---@enum ipv4Protocol
|
||||
ipv4Consts.PROTOCOLS = {
|
||||
ICMP = 1, -- Internet Control Message Protocol
|
||||
TCP = 6, -- Transmission Control Protocol
|
||||
UDP = 17, -- User Datagram Protocol
|
||||
OSPF = 89, -- Open Shortest Path First
|
||||
}
|
||||
|
||||
return ipv4Consts
|
14
network/lib/network/ipv4/init.lua
Normal file
14
network/lib/network/ipv4/init.lua
Normal file
@@ -0,0 +1,14 @@
|
||||
local consts = require("network.ipv4.constantes")
|
||||
|
||||
---@class ipv4lib
|
||||
local ipv4lib = {
|
||||
IPv4Layer = require("network.ipv4.IPv4Layer"),
|
||||
IPv4Packet = require("network.ipv4.IPv4Packet"),
|
||||
address = require("network.ipv4.address"),
|
||||
PROTOCOLS = consts.PROTOCOLS
|
||||
}
|
||||
|
||||
|
||||
--=============================================================================
|
||||
|
||||
return ipv4lib
|
76
network/lib/network/udp/UDPDatagram.lua
Normal file
76
network/lib/network/udp/UDPDatagram.lua
Normal file
@@ -0,0 +1,76 @@
|
||||
---@class UDPDatagram : Payload
|
||||
---@field private _srcPort number
|
||||
---@field private _dstPort number
|
||||
---@field private _payload string
|
||||
---@operator call:UDPDatagram
|
||||
---@overload fun(srcPort:number,dstPort:number,payload:string):UDPDatagram
|
||||
local UDPDatagram = {}
|
||||
UDPDatagram.payloadType = require("network.ipv4").PROTOCOLS.UDP
|
||||
|
||||
---@return UDPDatagram
|
||||
setmetatable(UDPDatagram, {
|
||||
---@param self UDPDatagram
|
||||
---@param srcPort number
|
||||
---@param dstPort number
|
||||
---@param payload string
|
||||
---@return table
|
||||
__call = function(self, srcPort, dstPort, payload)
|
||||
checkArg(1, srcPort, "number")
|
||||
checkArg(2, dstPort, "number")
|
||||
checkArg(3, payload, "string")
|
||||
local o = {
|
||||
_scrPort = 0,
|
||||
_dstPort = 0,
|
||||
_payload = ""
|
||||
}
|
||||
setmetatable(o, {__index = self})
|
||||
|
||||
o:setDstPort(dstPort)
|
||||
o:setSrcPort(srcPort)
|
||||
o:setPayload(payload)
|
||||
|
||||
return o
|
||||
end
|
||||
})
|
||||
|
||||
function UDPDatagram:getDstPort() return self._dstPort end
|
||||
|
||||
function UDPDatagram:getSrcPort() return self._srcPort end
|
||||
|
||||
function UDPDatagram:getPayload() return self._payload end
|
||||
|
||||
function UDPDatagram:setDstPort(port)
|
||||
checkArg(1, port, "number")
|
||||
assert(port >= 0 and port <= 2 ^ 16 - 1, "Port outside valid range")
|
||||
self._dstPort = port
|
||||
end
|
||||
|
||||
function UDPDatagram:setSrcPort(port)
|
||||
checkArg(1, port, "number")
|
||||
assert(port >= 0 and port <= 2 ^ 16 - 1, "Port outside valid range")
|
||||
self._srcPort = port
|
||||
end
|
||||
|
||||
function UDPDatagram:setPayload(value)
|
||||
checkArg(1, value, "string")
|
||||
self._payload = value
|
||||
end
|
||||
|
||||
---Prepare the packet for the next layer
|
||||
---@return string
|
||||
function UDPDatagram:pack()
|
||||
return string.format("%.4x%.4x%s", self:getSrcPort(), self:getDstPort(), self:getPayload())
|
||||
end
|
||||
|
||||
---Get a udp packet from the string
|
||||
---@param value string
|
||||
---@return UDPDatagram
|
||||
function UDPDatagram.unpack(value)
|
||||
local o = "%x%x"
|
||||
local src, dst, payload = value:match(string.format("(%s)(%s)(%s)", o:rep(2), o:rep(2), ".*"))
|
||||
src = tonumber(src, 16)
|
||||
dst = tonumber(dst, 16)
|
||||
return UDPDatagram(src, dst, payload)
|
||||
end
|
||||
|
||||
return UDPDatagram
|
324
network/lib/network/udp/UDPLayer.lua
Normal file
324
network/lib/network/udp/UDPLayer.lua
Normal file
@@ -0,0 +1,324 @@
|
||||
--local UDPSocket = require("network.udp.UDPSocket")
|
||||
local UDPDatagram = require("network.udp.UDPDatagram")
|
||||
local IPv4Packet = require("network.ipv4.IPv4Packet")
|
||||
local ipv4Address = require("network.ipv4.address")
|
||||
local network = require("network")
|
||||
|
||||
|
||||
---@class UDPLayer : OSITransportLayer
|
||||
---@field private _sockets table<number,table<number,table<number,table<number,UDPSocket>>>>
|
||||
---@field private _layer OSINetworkLayer
|
||||
---@operator call:UDPLayer
|
||||
---@overload fun(layer:IPv4Layer):UDPLayer
|
||||
local UDPLayer = {}
|
||||
UDPLayer.layerType = require("network.ipv4").PROTOCOLS.UDP
|
||||
|
||||
---@return UDPLayer
|
||||
setmetatable(UDPLayer, {
|
||||
---@param layer IPv4Layer
|
||||
---@return UDPLayer
|
||||
__call = function(self, layer)
|
||||
local o = {
|
||||
_sockets = {},
|
||||
_layer = layer
|
||||
}
|
||||
setmetatable(o, {__index = self})
|
||||
layer:setLayer(o) --tell the IPv4Layer that we exists
|
||||
return o
|
||||
end
|
||||
})
|
||||
|
||||
function UDPLayer:payloadHandler(from, to, payload)
|
||||
local udpPacket = UDPDatagram.unpack(payload)
|
||||
local socket = self:getSocket(to, udpPacket:getDstPort(), from, udpPacket:getSrcPort())
|
||||
if (not socket) then
|
||||
return
|
||||
end
|
||||
assert(socket)
|
||||
socket:payloadHandler(from, to, udpPacket)
|
||||
end
|
||||
|
||||
--#region
|
||||
-- ---Open a new UDP socket.
|
||||
-- ---@param address number
|
||||
-- ---@param port number
|
||||
-- ---@param remoteAddress number
|
||||
-- ---@param remotePort number
|
||||
-- ---@overload fun(self:UDPLayer):UDPSocket
|
||||
-- ---@overload fun(self:UDPLayer,address:number):UDPSocket
|
||||
-- ---@overload fun(self:UDPLayer,address:nil,port:number):UDPSocket?, string?
|
||||
-- ---@overload fun(self:UDPLayer,address:number,port:number):UDPSocket?, string?
|
||||
-- ---@overload fun(self:UDPLayer,address:nil,port:nil,remoteAddress:number,remotePort:number):UDPSocket
|
||||
-- ---@overload fun(self:UDPLayer,address:number,port:nil,remoteAddress:number,remotePort:number):UDPSocket
|
||||
-- ---@return UDPSocket? socket, string? reason
|
||||
-- function UDPLayer:open(address, port, remoteAddress, remotePort)
|
||||
-- --#region checkArg
|
||||
-- if (remoteAddress) then
|
||||
-- checkArg(3, remoteAddress, 'number')
|
||||
-- checkArg(4, remotePort, 'number')
|
||||
-- else
|
||||
-- checkArg(3, remoteAddress, 'nil')
|
||||
-- checkArg(4, remotePort, 'nil')
|
||||
-- end
|
||||
-- --[[Truth table
|
||||
-- address 0 1 1 0 1 0 1 0
|
||||
-- port 0 1 0 1 1 0 0 1
|
||||
-- remoteAddres 0 1 0 0 0 1 1 1
|
||||
-- remotePort 0 1 0 0 0 1 1 1
|
||||
-- 0 15 1 2 3 12 13 14
|
||||
-- ]]
|
||||
-- local validTypes = {[0] = true,[15] = true,[1] = true,[2] = true,[3] = true,[12] = true,[13] = true,[14] = true}
|
||||
-- local currentTypes = 0
|
||||
-- if (address) then currentTypes = currentTypes + 1 end
|
||||
-- if (port) then currentTypes = currentTypes + 2 end
|
||||
-- if (remoteAddress) then currentTypes = currentTypes + 4 end
|
||||
-- if (remotePort) then currentTypes = currentTypes + 8 end
|
||||
-- if (not validTypes[currentTypes]) then
|
||||
-- error("Invalid arguments types. Found : " .. currentTypes .. ". Please check that all required arguments are present", 2)
|
||||
-- end
|
||||
-- --#endregion
|
||||
|
||||
-- --TODO : default to default route interface address
|
||||
-- if (not address) then address = 0 end
|
||||
|
||||
-- if (port == nil or port == 0) then
|
||||
-- repeat
|
||||
-- port = math.random(1025, (2 ^ 16) - 1)
|
||||
-- until not self:getSocket(address, port, remoteAddress, remotePort)
|
||||
-- end
|
||||
-- local socket = UDPSocket(self, address, port, remoteAddress, remotePort)
|
||||
-- if (self:addSocket(socket)) then
|
||||
-- return socket
|
||||
-- else
|
||||
-- return nil, "Could not create socket"
|
||||
-- end
|
||||
-- end
|
||||
--#endregion
|
||||
|
||||
---comment
|
||||
---@param socket UDPSocket
|
||||
---@param address number
|
||||
---@param port number
|
||||
---@return number? port, string? reason
|
||||
function UDPLayer:bindSocket(socket, address, port)
|
||||
self:close(socket) --delete the socket from the internal data
|
||||
local rIPString, rPort = socket:getpeername()
|
||||
local rIP = ipv4Address.fromString(rIPString)
|
||||
self._sockets[address] = self._sockets[address] or {}
|
||||
if (port == 0) then
|
||||
repeat
|
||||
repeat
|
||||
port = math.random(49152, 65535)
|
||||
until not self._sockets[address][port]
|
||||
until not (self._sockets[address][port] and self._sockets[address][port][rIP] and self._sockets[address][port][rIP][rPort])
|
||||
end
|
||||
if (not self:addSocket(socket, address, port, rIP, rPort)) then
|
||||
return nil, "Port busy"
|
||||
end
|
||||
return port
|
||||
end
|
||||
|
||||
---@param socket UDPSocket
|
||||
---@param address number
|
||||
---@param port number
|
||||
---@return number? port, string? reason
|
||||
function UDPLayer:connectSocket(socket, address, port)
|
||||
self:close(socket) --delete the socket from the internal data
|
||||
local lIPString, lPort = socket:getsockname()
|
||||
local lIP = ipv4Address.fromString(lIPString)
|
||||
if (lIPString ~= 0 and lPort == 0) then
|
||||
self._sockets[lIP] = self._sockets[lIP] or {}
|
||||
if (port == 0) then
|
||||
repeat
|
||||
repeat
|
||||
port = math.random(49152, 65535)
|
||||
until not self._sockets[lIP][lPort]
|
||||
until not (self._sockets[lIP][lPort][address] and self._sockets[lIP][lPort][address][port])
|
||||
end
|
||||
if (not self:addSocket(socket, lIP, lPort, address, port)) then
|
||||
return nil, "Port busy"
|
||||
end
|
||||
else
|
||||
if (not self:addSocket(socket, lIP, lPort, address, port)) then
|
||||
return nil, "Port busy"
|
||||
end
|
||||
end
|
||||
return 1
|
||||
end
|
||||
|
||||
---@package
|
||||
---@param socket UDPSocket
|
||||
function UDPLayer:close(socket)
|
||||
local function tableEmpty(tbl)
|
||||
for _, _ in pairs(tbl) do
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
if (not self:isOpen(socket)) then return end
|
||||
local lIPString, lPort = socket:getsockname()
|
||||
local lIP = ipv4Address.fromString(lIPString)
|
||||
local peerIPString, peerPort = socket:getpeername()
|
||||
local peerIP = ipv4Address.fromString(peerIPString)
|
||||
|
||||
self._sockets[lIP][lPort][peerIP][peerPort] = nil
|
||||
|
||||
if (tableEmpty(self._sockets[lIP][lPort][peerIP])) then
|
||||
self._sockets[lIP][lPort][peerIP] = nil
|
||||
end
|
||||
if (tableEmpty(self._sockets[lIP][lPort])) then
|
||||
self._sockets[lIP][lPort] = nil
|
||||
end
|
||||
if (tableEmpty(self._sockets[lIP])) then
|
||||
self._sockets[lIP] = nil
|
||||
end
|
||||
end
|
||||
|
||||
---Check if the given socket is open on this layer
|
||||
---@param socket UDPSocket
|
||||
---@return boolean
|
||||
function UDPLayer:isOpen(socket)
|
||||
local lIPString, lPort = socket:getsockname()
|
||||
local lIP = ipv4Address.fromString(lIPString)
|
||||
local peerIPString, peerPort = socket:getpeername()
|
||||
local peerIP = ipv4Address.fromString(peerIPString)
|
||||
|
||||
if (self:getSocket(lIP, lPort, peerIP, peerPort)) then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
---Get the matching open socket
|
||||
---@private
|
||||
---@param localAddress? number
|
||||
---@param localPort number
|
||||
---@param remoteAddress? number
|
||||
---@param remotePort? number
|
||||
---@return UDPSocket?
|
||||
function UDPLayer:getSocket(localAddress, localPort, remoteAddress, remotePort)
|
||||
checkArg(1, localAddress, 'nil', 'number')
|
||||
localAddress = localAddress or 0
|
||||
checkArg(2, localPort, 'number')
|
||||
checkArg(3, remoteAddress, 'nil', 'number')
|
||||
remoteAddress = remoteAddress or 0
|
||||
checkArg(4, remotePort, 'nil', 'number')
|
||||
remotePort = remotePort or 0
|
||||
if (not self._sockets[localAddress]) then
|
||||
if (self._sockets[0]) then
|
||||
localAddress = 0
|
||||
else
|
||||
return
|
||||
end
|
||||
end
|
||||
local tmp = self._sockets[localAddress]
|
||||
|
||||
if (not tmp[localPort]) then return end
|
||||
tmp = tmp[localPort]
|
||||
|
||||
if (not tmp[remoteAddress]) then
|
||||
if (tmp[0]) then
|
||||
remoteAddress = 0
|
||||
else
|
||||
return
|
||||
end
|
||||
end
|
||||
tmp = tmp[remoteAddress]
|
||||
|
||||
if (not tmp[remotePort]) then
|
||||
if (tmp[0]) then
|
||||
remotePort = 0
|
||||
else
|
||||
return
|
||||
end
|
||||
end
|
||||
return tmp[remotePort]
|
||||
end
|
||||
|
||||
---Send a udp paylaod
|
||||
---@param from number
|
||||
---@param to number
|
||||
---@param payload UDPDatagram
|
||||
function UDPLayer:send(from, to, payload)
|
||||
checkArg(1, from, 'number')
|
||||
checkArg(2, to, 'number')
|
||||
checkArg(3, payload, 'table')
|
||||
network.router:send(IPv4Packet(from, to, payload))
|
||||
end
|
||||
|
||||
function UDPLayer:getAddr() return self._layer:getAddr() end
|
||||
|
||||
function UDPLayer:getMTU() return self._layer:getMTU() - 8 end
|
||||
|
||||
---add a socket to the internal list. Return false if could not be added (addrress / port already in use)
|
||||
---@private
|
||||
---@param socket UDPSocket
|
||||
---@param localAddress number
|
||||
---@param localPort number
|
||||
---@param remoteAddress number
|
||||
---@param remotePort number
|
||||
---@overload fun(self,socket:UDPSocket)
|
||||
---@return boolean added
|
||||
function UDPLayer:addSocket(socket, localAddress, localPort, remoteAddress, remotePort)
|
||||
checkArg(1, socket, 'table')
|
||||
if (localAddress ~= nil) then
|
||||
checkArg(2, localAddress, 'number')
|
||||
checkArg(3, localPort, 'number')
|
||||
checkArg(4, remoteAddress, 'number')
|
||||
checkArg(5, remotePort, 'number')
|
||||
else
|
||||
local lIPString, peerIPString
|
||||
lIPString, localPort = socket:getsockname()
|
||||
localAddress = ipv4Address.fromString(lIPString)
|
||||
peerIPString, remotePort = socket:getpeername()
|
||||
remoteAddress = ipv4Address.fromString(peerIPString)
|
||||
end
|
||||
|
||||
self._sockets[localAddress] = self._sockets[localAddress] or {}
|
||||
local tmp = self._sockets[localAddress]
|
||||
tmp[localPort] = tmp[localPort] or {}
|
||||
tmp = tmp[localPort]
|
||||
tmp[remoteAddress] = tmp[remoteAddress] or {}
|
||||
tmp = tmp[remoteAddress]
|
||||
|
||||
if (not tmp[remotePort]) then
|
||||
tmp[remotePort] = socket
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
---@return table<table>
|
||||
function UDPLayer:getOpenPorts()
|
||||
local r = {}
|
||||
local function getTreeBottomValues(tree)
|
||||
local vals = {}
|
||||
for _, v1 in pairs(tree) do
|
||||
for _, v2 in pairs(v1) do
|
||||
for _, v3 in pairs(v2) do
|
||||
for _, socket in pairs(v3) do
|
||||
table.insert(vals, socket)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return vals
|
||||
end
|
||||
|
||||
for _, socket in pairs(getTreeBottomValues(self._sockets)) do
|
||||
local lIPString, lPort = socket:getsockname()
|
||||
local lIP = ipv4Address.fromString(lIPString)
|
||||
local peerIPString, peerPort = socket:getpeername()
|
||||
local peerIP = ipv4Address.fromString(peerIPString)
|
||||
table.insert(r, {
|
||||
loc = {address = lIP, port = lPort},
|
||||
rem = {address = peerIP, port = peerPort}
|
||||
})
|
||||
end
|
||||
|
||||
return r
|
||||
end
|
||||
|
||||
return UDPLayer
|
6
network/lib/network/udp/init.lua
Normal file
6
network/lib/network/udp/init.lua
Normal file
@@ -0,0 +1,6 @@
|
||||
---@class udpLib
|
||||
local udp = {
|
||||
UDPLayer = require("network.udp.UDPLayer"),
|
||||
--UDPSocket = require("network.udp.UDPSocket"),
|
||||
}
|
||||
return udp
|
@@ -1,5 +1,6 @@
|
||||
local bit32 = require("bit32")
|
||||
local ipv4Address = require("layers.ipv4").address
|
||||
local ipv4Address = require("network.ipv4").address
|
||||
local ipv4 = require("network.ipv4")
|
||||
|
||||
---@class routingLib
|
||||
local routing = {}
|
||||
@@ -15,18 +16,18 @@ local routing = {}
|
||||
|
||||
---@class IPv4Router:OSINetworkLayer
|
||||
---@field private _routes table<Route>
|
||||
---@field private _layers table<IPv4Layer>
|
||||
---@field private _protocols table<ipv4Protocol,OSILayer>
|
||||
---@operator call:IPv4Router
|
||||
---@overload fun():IPv4Router
|
||||
local IPv4Router = {}
|
||||
IPv4Router.layerType = require("layers.ethernet").TYPE.IPv4
|
||||
IPv4Router.layerType = require("network.ethernet").TYPE.IPv4
|
||||
|
||||
---@return IPv4Router
|
||||
setmetatable(IPv4Router, {
|
||||
__call = function(self)
|
||||
local o = {
|
||||
_routes = {},
|
||||
_layers = {}
|
||||
_protocols = {}
|
||||
}
|
||||
setmetatable(o, {__index = self})
|
||||
return o
|
||||
@@ -37,24 +38,54 @@ setmetatable(IPv4Router, {
|
||||
---@param route Route
|
||||
function IPv4Router:addRoute(route)
|
||||
if (not route.network) then return end
|
||||
--TODO : sort by metrics
|
||||
if (not (route.network == 0 and route.mask == 0)) then
|
||||
table.insert(self._routes, 1, route)
|
||||
else
|
||||
table.insert(self._routes, route)
|
||||
end
|
||||
---@param a Route
|
||||
---@param b Route
|
||||
---@return boolean
|
||||
table.sort(self._routes, function(a, b)
|
||||
if (a.network == 0 and b.network == 0) then
|
||||
return a.metric > b.metric
|
||||
elseif (a.network ~= 0 and b.network ~= 0) then
|
||||
if (a.metric == b.metric) then
|
||||
if (a.network == b.network) then
|
||||
return a.mask > b.mask
|
||||
else
|
||||
return a.network < b.network
|
||||
end
|
||||
else
|
||||
return a.metric > b.metric
|
||||
end
|
||||
elseif (a.network == 0) then
|
||||
return false
|
||||
else
|
||||
return true
|
||||
end
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
---Get the route for the prodvided network address / mask
|
||||
---@param address number
|
||||
---@param address? number
|
||||
---@return Route
|
||||
function IPv4Router:getRoute(address)
|
||||
for id, route in ipairs(self._routes) do
|
||||
checkArg(1, address, 'nil', 'number')
|
||||
if (not address) then
|
||||
for id, route in ipairs(self:listRoutes()) do
|
||||
---@cast route Route
|
||||
if (route.network == 0 and route.mask == 0) then return route end
|
||||
end
|
||||
end
|
||||
for id, route in ipairs(self:listRoutes()) do
|
||||
---@cast route Route
|
||||
local address1 = bit32.band(address, route.mask)
|
||||
local address2 = bit32.band(route.network, route.mask)
|
||||
if (address1 == address2) then return route end
|
||||
end
|
||||
error("No route found. This is not normal. Make sure a default route is set", 2)
|
||||
error(string.format("No route found to %s. This is not normal. Make sure a default route is set", ipv4.address.tostring(address)), 2)
|
||||
end
|
||||
|
||||
---Remove a route
|
||||
@@ -73,38 +104,6 @@ function IPv4Router:listRoutes(id)
|
||||
return self._routes
|
||||
end
|
||||
|
||||
---Add a gateway (IPv4Lyer)
|
||||
---@param interface IPv4Layer
|
||||
function IPv4Router:setLayer(interface)
|
||||
table.insert(self._layers, interface)
|
||||
end
|
||||
|
||||
---Get the interface with the given address
|
||||
---@param address number
|
||||
---@return IPv4Layer?
|
||||
---@overload fun(self:IPv4Router):table<IPv4Layer>
|
||||
function IPv4Router:getLayer(address)
|
||||
if (not address) then return self._layers end
|
||||
for _, layer in ipairs(self._layers) do
|
||||
if (layer:getAddr() == address) then
|
||||
return layer
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---remove the interface
|
||||
---@param layer IPv4Layer
|
||||
function IPv4Router:removeLayer(layer)
|
||||
for i, v in ipairs(self._layers) do
|
||||
if (v:getAddr() == layer:getAddr()) then
|
||||
table.remove(self._layers, i)
|
||||
break
|
||||
end
|
||||
end
|
||||
self:removeGateway(layer:getAddr())
|
||||
self:removeByInterface(layer)
|
||||
end
|
||||
|
||||
---Remove a gateway from the routing table. Useful to remove default route for a interface.
|
||||
---@param gateway number
|
||||
function IPv4Router:removeGateway(gateway)
|
||||
@@ -136,12 +135,19 @@ function IPv4Router:removeByInterface(interface)
|
||||
for v in pairs(rmRoutes) do
|
||||
table.remove(self._routes, v)
|
||||
end
|
||||
self:removeGateway(interface:getAddr())
|
||||
end
|
||||
|
||||
---send the IPv4 packet
|
||||
---@param packet IPv4Packet
|
||||
function IPv4Router:send(packet)
|
||||
if (self:getRoute(packet:getDst()).interface:getAddr() == packet:getDst()) then
|
||||
local layer = self:getRoute(packet:getDst()).interface
|
||||
assert(layer)
|
||||
self:getRoute(packet:getDst()).interface:payloadHandler(layer:getAddr(), packet:getDst(), packet)
|
||||
end
|
||||
local route = self:getRoute(packet:getDst())
|
||||
if (packet:getSrc()) then packet:setSrc(route.interface:getAddr()) end
|
||||
if (route.gateway == route.interface:getAddr()) then
|
||||
route.interface:send(packet)
|
||||
else
|
||||
@@ -149,6 +155,37 @@ function IPv4Router:send(packet)
|
||||
end
|
||||
end
|
||||
|
||||
---@param protocolHandler OSILayer
|
||||
function IPv4Router:setProtocol(protocolHandler)
|
||||
self._protocols[protocolHandler.layerType] = protocolHandler
|
||||
end
|
||||
|
||||
IPv4Router.setLayer = IPv4Router.setProtocol
|
||||
|
||||
---@param protocolID ipv4Protocol
|
||||
---@return OSILayer
|
||||
function IPv4Router:getProtocol(protocolID)
|
||||
return self._protocols[protocolID]
|
||||
end
|
||||
|
||||
---@param from number
|
||||
---@param to number
|
||||
---@param payload string
|
||||
function IPv4Router:payloadHandler(from, to, payload)
|
||||
local datagram = ipv4.IPv4Packet.unpack(payload)
|
||||
if (self._protocols[datagram:getProtocol()]) then
|
||||
self._protocols[datagram:getProtocol()]:payloadHandler(from, to, datagram:getPayload())
|
||||
end
|
||||
end
|
||||
|
||||
function IPv4Router:getAddr()
|
||||
return self:getRoute().interface
|
||||
end
|
||||
|
||||
function IPv4Router:getMTU()
|
||||
return self:getRoute().interface:getMTU()
|
||||
end
|
||||
|
||||
--=============================================================================
|
||||
|
||||
routing.IPv4Router = IPv4Router
|
||||
|
3
network/lib/socket/init.lua
Normal file
3
network/lib/socket/init.lua
Normal file
@@ -0,0 +1,3 @@
|
||||
return {
|
||||
udp = require("socket.udp")
|
||||
}
|
180
network/lib/socket/udp.lua
Normal file
180
network/lib/socket/udp.lua
Normal file
@@ -0,0 +1,180 @@
|
||||
local ipv4Address = require("network.ipv4.address")
|
||||
local UDPDatagram = require("network.udp.UDPDatagram")
|
||||
local network = require("network")
|
||||
|
||||
|
||||
---@class UDPSocket
|
||||
---@field public kind UDPSocketKind
|
||||
---@field private _sockname table
|
||||
---@field private _peername table
|
||||
---@field private _buffer table
|
||||
---@field private _timeout number
|
||||
---@operator call:UDPSocket
|
||||
---@overload fun(self):UDPSocket
|
||||
local UDPSocket = {}
|
||||
|
||||
---@alias UDPSocketKind
|
||||
--- | "unconnected"
|
||||
--- | "connected"
|
||||
|
||||
---@return UDPSocket
|
||||
setmetatable(UDPSocket, {
|
||||
__call = function(self)
|
||||
local o = {
|
||||
_sockname = {"0.0.0.0", 0},
|
||||
_peername = {"0.0.0.0", 0},
|
||||
kind = "unconnected",
|
||||
_buffer = {},
|
||||
_timeout = 0
|
||||
}
|
||||
setmetatable(o, {__index = self})
|
||||
return o
|
||||
end
|
||||
})
|
||||
|
||||
function UDPSocket:close()
|
||||
network.udp.getInterface():close(self)
|
||||
end
|
||||
|
||||
---Retrieves information about the peer associated with a connected UDP object.\
|
||||
---Returns the IP address and port number of the peer.\
|
||||
---Note: It makes no sense to call this method on unconnected objects.
|
||||
---@return string address,number port
|
||||
function UDPSocket:getpeername()
|
||||
return table.unpack(self._peername)
|
||||
end
|
||||
|
||||
---Returns the local address information associated to the object.\
|
||||
---The method returns a string with local IP address and a number with the port. In case of error, the method returns nil.\
|
||||
---Note: UDP sockets are not bound to any address until the setsockname or the sendto method is called for the first time (in which case it is bound to an ephemeral port and the wild-card address).
|
||||
---@return string address,number port
|
||||
function UDPSocket:getsockname()
|
||||
return table.unpack(self._sockname)
|
||||
end
|
||||
|
||||
---@param size? number
|
||||
---@return string?
|
||||
function UDPSocket:recieve(size)
|
||||
local datagram = self:receivefrom(size)
|
||||
return datagram
|
||||
end
|
||||
|
||||
---@return string? datagram, string? fromAddress, number? fromPort
|
||||
function UDPSocket:receivefrom(size)
|
||||
if (select(2, self:getsockname()) == 0) then
|
||||
error("Reciving object before binding to a address/port", 2)
|
||||
end
|
||||
local t1 = os.time()
|
||||
repeat
|
||||
os.sleep()
|
||||
until #self._buffer > 0 or (self._timeout > 0 and os.time() - t1 > self._timeout)
|
||||
if (#self._buffer > 0) then
|
||||
return table.unpack(table.remove(self._buffer, 1))
|
||||
end
|
||||
end
|
||||
|
||||
---Sends a datagram to the UDP peer of a connected object.\
|
||||
---Datagram is a string with the datagram contents. The maximum datagram size for UDP is 64K minus IP layer overhead. However datagrams larger than the link layer packet size will be fragmented, which may deteriorate performance and/or reliability.\
|
||||
---If successful, the method returns 1. In case of error, the method returns nil followed by an error message.\
|
||||
---Note: In UDP, the send method never blocks and the only way it can fail is if the underlying transport layer refuses to send a message to the specified address (i.e. no interface accepts the address).
|
||||
---@param datagram string
|
||||
---@return number?,string? reason
|
||||
function UDPSocket:send(datagram)
|
||||
if (self.kind == "unconnected") then return nil, "Not a connected udp socket" end
|
||||
return self:sendto(datagram, self:getpeername())
|
||||
end
|
||||
|
||||
---comment
|
||||
---@param datagram string
|
||||
---@param ip string
|
||||
---@param port number
|
||||
---@return number?,string? reason
|
||||
function UDPSocket:sendto(datagram, ip, port)
|
||||
if (select(2, self:getsockname()) == 0) then
|
||||
self:setsockname('*', 0)
|
||||
end
|
||||
local lIP, srcPort = self:getsockname()
|
||||
local dstIP = ipv4Address.fromString(ip)
|
||||
local srcIP = ipv4Address.fromString(lIP)
|
||||
local datagramObject = UDPDatagram(srcPort, port, datagram)
|
||||
network.udp.getInterface():send(srcIP, dstIP, datagramObject)
|
||||
return 1
|
||||
end
|
||||
|
||||
---@param address number
|
||||
---@param port number
|
||||
---@overload fun(self,address:string)
|
||||
---@return number? success, string? reason
|
||||
function UDPSocket:setpeername(address, port)
|
||||
checkArg(1, address, 'string')
|
||||
if (self.kind == "connected") then
|
||||
if (address ~= '*') then
|
||||
error("Address must be '*'", 2)
|
||||
end
|
||||
network.udp.getInterface():connectSocket(self, 0, 0)
|
||||
self._peername = {"0.0.0.0", 0}
|
||||
self.kind = "unconnected"
|
||||
return 1
|
||||
else
|
||||
checkArg(2, port, 'number')
|
||||
local success, reason = network.udp.getInterface():connectSocket(self, ipv4Address.fromString(address), port)
|
||||
if (success) then
|
||||
self._peername = {address, port}
|
||||
self.kind = "connected"
|
||||
return 1
|
||||
else
|
||||
return nil, reason
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param address string
|
||||
---@param port number
|
||||
---@return number? success, string? reason
|
||||
function UDPSocket:setsockname(address, port)
|
||||
checkArg(1, address, 'string')
|
||||
checkArg(2, port, 'number')
|
||||
local reason
|
||||
if (not self.kind == "unconnected") then return nil, "Not a unconnected udp socket" end
|
||||
if (address == '*') then address = "0.0.0.0" end
|
||||
local _, sockP = self:getsockname()
|
||||
if (sockP == 0) then
|
||||
---@diagnostic disable-next-line: cast-local-type
|
||||
port, reason = network.udp.getInterface():bindSocket(self, ipv4Address.fromString(address), port)
|
||||
if (port) then
|
||||
self._sockname = {address, port}
|
||||
end
|
||||
return 1, reason
|
||||
else
|
||||
return nil, "Socket already bound"
|
||||
end
|
||||
end
|
||||
|
||||
---Sets options for the UDP object. Options are only needed by low-level or time-critical applications. You should only modify an option if you are sure you need it.
|
||||
---@param option 'dontroute'|'broadcast'
|
||||
---@param value? boolean
|
||||
function UDPSocket:setoption(option, value)
|
||||
error("NOT IMPLEMENTED", 2)
|
||||
end
|
||||
|
||||
function UDPSocket:settimeout(value)
|
||||
checkArg(1, value, 'number')
|
||||
self._timeout = value
|
||||
end
|
||||
|
||||
---Handle the payload recived by UDPLayer
|
||||
---@package
|
||||
---@param from number
|
||||
---@param to number
|
||||
---@param udpPacket UDPDatagram
|
||||
function UDPSocket:payloadHandler(from, to, udpPacket)
|
||||
table.insert(self._buffer, {udpPacket:getPayload(), ipv4Address.tostring(from), udpPacket:getSrcPort()})
|
||||
end
|
||||
|
||||
---Create and returns an unconnected UDP obeject.
|
||||
---@return UDPSocket
|
||||
local function udp()
|
||||
return UDPSocket()
|
||||
end
|
||||
|
||||
return udp
|
Reference in New Issue
Block a user