1
0
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:
2023-03-30 12:02:50 +02:00
parent ed91e1e493
commit 314a38d222
33 changed files with 1630 additions and 1189 deletions

View File

@@ -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])

View File

@@ -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(...)

View File

@@ -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
View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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

View 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

View 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

View 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

View 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

View File

@@ -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

View 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

View 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

View 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

View 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

View File

@@ -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

View 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

View File

@@ -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

View 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

View 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

View 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

View 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

View 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

View File

@@ -0,0 +1,6 @@
---@class udpLib
local udp = {
UDPLayer = require("network.udp.UDPLayer"),
--UDPSocket = require("network.udp.UDPSocket"),
}
return udp

View File

@@ -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

View File

@@ -0,0 +1,3 @@
return {
udp = require("socket.udp")
}

180
network/lib/socket/udp.lua Normal file
View 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