#488 fixes to sequence number changes and auth packet data

This commit is contained in:
Mikayla Fischler 2024-06-29 14:10:58 -04:00
parent 8dedb092e7
commit 2de30ef064
13 changed files with 75 additions and 47 deletions

View File

@ -233,6 +233,7 @@ function coordinator.comms(version, nic, sv_watchdog)
sv_linked = false,
sv_addr = comms.BROADCAST,
sv_seq_num = util.time_ms() * 10, -- unique per peer, restarting will not re-use seq nums due to message rate
sv_r_seq_num = nil, ---@type nil|integer
sv_config_err = false,
last_est_ack = ESTABLISH_ACK.ALLOW,
last_api_est_acks = {},
@ -369,6 +370,7 @@ function coordinator.comms(version, nic, sv_watchdog)
sv_watchdog.cancel()
self.sv_addr = comms.BROADCAST
self.sv_linked = false
self.sv_r_seq_num = nil
iocontrol.fp_link_state(types.PANEL_LINK_STATE.DISCONNECTED)
_send_sv(PROTOCOL.SCADA_MGMT, MGMT_TYPE.CLOSE, {})
end
@ -490,7 +492,7 @@ function coordinator.comms(version, nic, sv_watchdog)
_send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.BAD_API_VERSION)
elseif dev_type == DEVICE_TYPE.PKT then
-- pocket linking request
local id = apisessions.establish_session(src_addr, packet.scada_frame.seq_num() + 1, firmware_v)
local id = apisessions.establish_session(src_addr, packet.scada_frame.seq_num(), firmware_v)
coordinator.log_comms(util.c("API_ESTABLISH: pocket (", firmware_v, ") [@", src_addr, "] connected with session ID ", id))
local conf = iocontrol.get_db().facility.conf
@ -512,14 +514,16 @@ function coordinator.comms(version, nic, sv_watchdog)
end
elseif r_chan == config.SVR_Channel then
-- check sequence number
if self.sv_seq_num ~= packet.scada_frame.seq_num() then
log.warning("sequence out-of-order: last = " .. self.sv_seq_num .. ", new = " .. packet.scada_frame.seq_num())
if self.sv_r_seq_num == nil then
self.sv_r_seq_num = packet.scada_frame.seq_num() + 1
elseif self.sv_r_seq_num ~= packet.scada_frame.seq_num() then
log.warning("sequence out-of-order: last = " .. self.sv_r_seq_num .. ", new = " .. packet.scada_frame.seq_num())
return false
elseif self.sv_linked and src_addr ~= self.sv_addr then
log.debug("received packet from unknown computer " .. src_addr .. " while linked; channel in use by another system?")
return false
else
self.sv_seq_num = packet.scada_frame.seq_num() + 1
self.sv_r_seq_num = packet.scada_frame.seq_num() + 1
end
-- feed watchdog on valid sequence number
@ -671,6 +675,7 @@ function coordinator.comms(version, nic, sv_watchdog)
sv_watchdog.cancel()
self.sv_addr = comms.BROADCAST
self.sv_linked = false
self.sv_r_seq_num = nil
iocontrol.fp_link_state(types.PANEL_LINK_STATE.DISCONNECTED)
log.info("server connection closed by remote host")
else

View File

@ -41,7 +41,8 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout)
local self = {
-- connection properties
seq_num = i_seq_num,
seq_num = i_seq_num + 2, -- next after the establish approval was sent
r_seq_num = i_seq_num + 1,
connected = true,
conn_watchdog = util.new_watchdog(timeout),
last_rtt = 0,
@ -104,11 +105,11 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout)
---@param pkt mgmt_frame|crdn_frame
local function _handle_packet(pkt)
-- check sequence number
if self.seq_num ~= pkt.scada_frame.seq_num() then
log.warning(log_header .. "sequence out-of-order: last = " .. self.seq_num .. ", new = " .. pkt.scada_frame.seq_num())
if self.r_seq_num ~= pkt.scada_frame.seq_num() then
log.warning(log_header .. "sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. pkt.scada_frame.seq_num())
return
else
self.seq_num = pkt.scada_frame.seq_num() + 1
self.r_seq_num = pkt.scada_frame.seq_num() + 1
end
-- feed watchdog

View File

@ -371,12 +371,14 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav)
linked = false,
addr = comms.BROADCAST,
seq_num = util.time_ms() * 10, -- unique per peer, restarting will not re-use seq nums due to message rate
r_seq_num = nil, ---@type nil|integer
last_est_ack = ESTABLISH_ACK.ALLOW
},
api = {
linked = false,
addr = comms.BROADCAST,
seq_num = util.time_ms() * 10, -- unique per peer, restarting will not re-use seq nums due to message rate
r_seq_num = nil, ---@type nil|integer
last_est_ack = ESTABLISH_ACK.ALLOW
},
establish_delay_counter = 0
@ -465,6 +467,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav)
nav.unload_sv()
self.sv.linked = false
self.sv.addr = comms.BROADCAST
self.sv.r_seq_num = nil
_send_sv(MGMT_TYPE.CLOSE, {})
end
@ -474,6 +477,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav)
nav.unload_api()
self.api.linked = false
self.api.addr = comms.BROADCAST
self.api.r_seq_num = nil
_send_crd(MGMT_TYPE.CLOSE, {})
end
@ -599,15 +603,17 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav)
log.debug("received packet on unconfigured channel " .. l_chan, true)
elseif r_chan == config.CRD_Channel then
-- check sequence number
if self.api.seq_num ~= packet.scada_frame.seq_num() then
log.warning("sequence out-of-order (API): last = " .. self.api.seq_num .. ", new = " .. packet.scada_frame.seq_num())
if self.api.r_seq_num == nil then
self.api.r_seq_num = packet.scada_frame.seq_num() + 1
elseif self.api.r_seq_num ~= packet.scada_frame.seq_num() then
log.warning("sequence out-of-order (API): last = " .. self.api.r_seq_num .. ", new = " .. packet.scada_frame.seq_num())
return
elseif self.api.linked and (src_addr ~= self.api.addr) then
log.debug("received packet from unknown computer " .. src_addr .. " while linked (API expected " .. self.api.addr ..
"); channel in use by another system?")
return
else
self.api.seq_num = packet.scada_frame.seq_num() + 1
self.api.r_seq_num = packet.scada_frame.seq_num() + 1
end
-- feed watchdog on valid sequence number
@ -653,6 +659,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav)
nav.unload_api()
self.api.linked = false
self.api.addr = comms.BROADCAST
self.api.r_seq_num = nil
log.info("coordinator server connection closed by remote host")
else _fail_type(packet) end
elseif packet.type == MGMT_TYPE.ESTABLISH then
@ -716,15 +723,17 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav)
end
elseif r_chan == config.SVR_Channel then
-- check sequence number
if self.sv.seq_num ~= packet.scada_frame.seq_num() then
log.warning("sequence out-of-order (SVR): last = " .. self.sv.seq_num .. ", new = " .. packet.scada_frame.seq_num())
if self.sv.r_seq_num == nil then
self.sv.r_seq_num = packet.scada_frame.seq_num() + 1
elseif self.sv.r_seq_num ~= packet.scada_frame.seq_num() then
log.warning("sequence out-of-order (SVR): last = " .. self.sv.r_seq_num .. ", new = " .. packet.scada_frame.seq_num())
return
elseif self.sv.linked and (src_addr ~= self.sv.addr) then
log.debug("received packet from unknown computer " .. src_addr .. " while linked (SVR expected " .. self.sv.addr ..
"); channel in use by another system?")
return
else
self.sv.seq_num = packet.scada_frame.seq_num() + 1
self.sv.r_seq_num = packet.scada_frame.seq_num() + 1
end
-- feed watchdog on valid sequence number
@ -756,6 +765,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav)
nav.unload_sv()
self.sv.linked = false
self.sv.addr = comms.BROADCAST
self.sv.r_seq_num = nil
log.info("supervisor server connection closed by remote host")
elseif packet.type == MGMT_TYPE.DIAG_TONE_GET then
if _check_length(packet, 8) then

View File

@ -525,6 +525,7 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog)
local self = {
sv_addr = comms.BROADCAST,
seq_num = util.time_ms() * 10, -- unique per peer, restarting will not re-use seq nums due to message rate
r_seq_num = nil, ---@type nil|integer
scrammed = false,
linked = false,
last_est_ack = ESTABLISH_ACK.ALLOW,
@ -715,6 +716,7 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog)
self.sv_addr = comms.BROADCAST
self.linked = false
self.status_cache = nil
self.r_seq_num = nil
databus.tx_link_state(types.PANEL_LINK_STATE.DISCONNECTED)
end
@ -822,15 +824,17 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog)
-- handle packets now that we have prints setup
if l_chan == config.PLC_Channel then
-- check sequence number
if self.seq_num ~= packet.scada_frame.seq_num() then
log.warning("sequence out-of-order: last = " .. self.seq_num .. ", new = " .. packet.scada_frame.seq_num())
if self.r_seq_num == nil then
self.r_seq_num = packet.scada_frame.seq_num() + 1
elseif self.r_seq_num ~= packet.scada_frame.seq_num() then
log.warning("sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. packet.scada_frame.seq_num())
return
elseif self.linked and (src_addr ~= self.sv_addr) then
log.debug("received packet from unknown computer " .. src_addr .. " while linked (expected " .. self.sv_addr ..
"); channel in use by another system?")
return
else
self.seq_num = packet.scada_frame.seq_num() + 1
self.r_seq_num = packet.scada_frame.seq_num() + 1
end
-- feed the watchdog first so it doesn't uhh...eat our packets :)

View File

@ -285,6 +285,7 @@ function rtu.comms(version, nic, conn_watchdog)
local self = {
sv_addr = comms.BROADCAST,
seq_num = util.time_ms() * 10, -- unique per peer, restarting will not re-use seq nums due to message rate
r_seq_num = nil, ---@type nil|integer
txn_id = 0,
last_est_ack = ESTABLISH_ACK.ALLOW
}
@ -362,6 +363,7 @@ function rtu.comms(version, nic, conn_watchdog)
function public.unlink(rtu_state)
rtu_state.linked = false
self.sv_addr = comms.BROADCAST
self.r_seq_num = nil
databus.tx_link_state(types.PANEL_LINK_STATE.DISCONNECTED)
end
@ -439,15 +441,17 @@ function rtu.comms(version, nic, conn_watchdog)
if l_chan == config.RTU_Channel then
-- check sequence number
if self.seq_num ~= packet.scada_frame.seq_num() then
log.warning("sequence out-of-order: last = " .. self.seq_num .. ", new = " .. packet.scada_frame.seq_num())
if self.r_seq_num == nil then
self.r_seq_num = packet.scada_frame.seq_num() + 1
elseif self.r_seq_num ~= packet.scada_frame.seq_num() then
log.warning("sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. packet.scada_frame.seq_num())
return
elseif rtu_state.linked and (src_addr ~= self.sv_addr) then
log.debug("received packet from unknown computer " .. src_addr .. " while linked (expected " .. self.sv_addr ..
"); channel in use by another system?")
return
else
self.seq_num = packet.scada_frame.seq_num() + 1
self.r_seq_num = packet.scada_frame.seq_num() + 1
end
-- feed watchdog on valid sequence number

View File

@ -294,7 +294,7 @@ function comms.authd_packet()
self.src_addr = s_packet.src_addr()
self.dest_addr = s_packet.dest_addr()
self.mac = mac(textutils.serialize(s_packet.raw_header(), { allow_repetitions = true, compact = true }))
self.raw = { self.src_addr, self.dest_addr, self.mac, s_packet.data() }
self.raw = { self.src_addr, self.dest_addr, self.mac, s_packet.raw_sendable() }
end
-- parse in a modem message as an authenticated SCADA packet

View File

@ -80,7 +80,7 @@ end
---@param modem table modem to use
function network.nic(modem)
local self = {
connected = true, -- used to avoid costly MAC calculations if modem isn't even present
connected = true, -- used to avoid costly MAC calculations if modem isn't even present
channels = {}
}
@ -175,7 +175,7 @@ function network.nic(modem)
---@param packet scada_packet packet
function public.transmit(dest_channel, local_channel, packet)
if self.connected then
local tx_packet = packet ---@type authd_packet|scada_packet
local tx_packet = packet ---@type authd_packet|scada_packet
if c_eng.hmac ~= nil then
-- local start = util.time_ms()
@ -214,13 +214,13 @@ function network.nic(modem)
s_packet.receive(side, sender, reply_to, a_packet.data(), distance)
if s_packet.is_valid() then
-- local start = util.time_ms()
-- local start = util.time_ms()
local computed_hmac = compute_hmac(textutils.serialize(s_packet.raw_header(), { allow_repetitions = true, compact = true }))
if a_packet.mac() == computed_hmac then
-- log.debug("network.modem.receive: HMAC verified in " .. (util.time_ms() - start) .. "ms")
s_packet.stamp_authenticated()
else
s_packet.stamp_authenticated()
else
-- log.debug("network.modem.receive: HMAC failed verification in " .. (util.time_ms() - start) .. "ms")
end
end

View File

@ -58,7 +58,8 @@ function coordinator.new_session(id, s_addr, i_seq_num, in_queue, out_queue, tim
local self = {
units = facility.get_units(),
-- connection properties
seq_num = i_seq_num,
seq_num = i_seq_num + 2, -- next after the establish approval was sent
r_seq_num = i_seq_num + 1,
connected = true,
conn_watchdog = util.new_watchdog(timeout),
establish_time = util.time_s(),
@ -182,11 +183,11 @@ function coordinator.new_session(id, s_addr, i_seq_num, in_queue, out_queue, tim
---@param pkt mgmt_frame|crdn_frame
local function _handle_packet(pkt)
-- check sequence number
if self.seq_num ~= pkt.scada_frame.seq_num() then
log.warning(log_header .. "sequence out-of-order: last = " .. self.seq_num .. ", new = " .. pkt.scada_frame.seq_num())
if self.r_seq_num ~= pkt.scada_frame.seq_num() then
log.warning(log_header .. "sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. pkt.scada_frame.seq_num())
return
else
self.seq_num = pkt.scada_frame.seq_num() + 1
self.r_seq_num = pkt.scada_frame.seq_num() + 1
end
-- feed watchdog

View File

@ -67,7 +67,8 @@ function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue,
ramping_rate = false,
auto_lock = false,
-- connection properties
seq_num = i_seq_num,
seq_num = i_seq_num + 2, -- next after the establish approval was sent
r_seq_num = i_seq_num + 1,
connected = true,
received_struct = false,
received_status_cache = false,
@ -349,11 +350,11 @@ function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue,
---@param pkt mgmt_frame|rplc_frame
local function _handle_packet(pkt)
-- check sequence number
if self.seq_num ~= pkt.scada_frame.seq_num() then
log.warning(log_header .. "sequence out-of-order: last = " .. self.seq_num .. ", new = " .. pkt.scada_frame.seq_num())
if self.r_seq_num ~= pkt.scada_frame.seq_num() then
log.warning(log_header .. "sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. pkt.scada_frame.seq_num())
return
else
self.seq_num = pkt.scada_frame.seq_num() + 1
self.r_seq_num = pkt.scada_frame.seq_num() + 1
end
-- process packet

View File

@ -44,7 +44,8 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout,
local self = {
-- connection properties
seq_num = i_seq_num,
seq_num = i_seq_num + 2, -- next after the establish approval was sent
r_seq_num = i_seq_num + 1,
connected = true,
conn_watchdog = util.new_watchdog(timeout),
last_rtt = 0,
@ -93,11 +94,11 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout,
---@param pkt mgmt_frame
local function _handle_packet(pkt)
-- check sequence number
if self.seq_num ~= pkt.scada_frame.seq_num() then
log.warning(log_header .. "sequence out-of-order: last = " .. self.seq_num .. ", new = " .. pkt.scada_frame.seq_num())
if self.r_seq_num ~= pkt.scada_frame.seq_num() then
log.warning(log_header .. "sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. pkt.scada_frame.seq_num())
return
else
self.seq_num = pkt.scada_frame.seq_num() + 1
self.r_seq_num = pkt.scada_frame.seq_num() + 1
end
-- feed watchdog

View File

@ -52,7 +52,8 @@ function rtu.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout, ad
advert = advertisement,
fac_units = facility.get_units(),
-- connection properties
seq_num = i_seq_num,
seq_num = i_seq_num + 2, -- next after the establish approval was sent
r_seq_num = i_seq_num + 1,
connected = true,
conn_watchdog = util.new_watchdog(timeout),
last_rtt = 0,
@ -240,11 +241,11 @@ function rtu.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout, ad
---@param pkt modbus_frame|mgmt_frame
local function _handle_packet(pkt)
-- check sequence number
if self.seq_num ~= pkt.scada_frame.seq_num() then
log.warning(log_header .. "sequence out-of-order: last = " .. self.seq_num .. ", new = " .. pkt.scada_frame.seq_num())
if self.r_seq_num ~= pkt.scada_frame.seq_num() then
log.warning(log_header .. "sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. pkt.scada_frame.seq_num())
return
else
self.seq_num = pkt.scada_frame.seq_num() + 1
self.r_seq_num = pkt.scada_frame.seq_num() + 1
end
-- feed watchdog

View File

@ -274,7 +274,7 @@ end
-- establish a new PLC session
---@nodiscard
---@param source_addr integer PLC computer ID
---@param i_seq_num integer initial sequence number to use next
---@param i_seq_num integer initial (most recent) sequence number
---@param for_reactor integer unit ID
---@param version string PLC version
---@return integer|false session_id
@ -324,7 +324,7 @@ end
-- establish a new RTU gateway session
---@nodiscard
---@param source_addr integer RTU gateway computer ID
---@param i_seq_num integer initial sequence number to use next
---@param i_seq_num integer initial (most recent) sequence number
---@param advertisement table RTU capability advertisement
---@param version string RTU gateway version
---@return integer session_id
@ -365,7 +365,7 @@ end
-- establish a new coordinator session
---@nodiscard
---@param source_addr integer coordinator computer ID
---@param i_seq_num integer initial sequence number to use next
---@param i_seq_num integer initial (most recent) sequence number
---@param version string coordinator version
---@return integer|false session_id
function svsessions.establish_crd_session(source_addr, i_seq_num, version)
@ -410,7 +410,7 @@ end
-- establish a new pocket diagnostics session
---@nodiscard
---@param source_addr integer pocket computer ID
---@param i_seq_num integer initial sequence number to use next
---@param i_seq_num integer initial (most recent) sequence number
---@param version string pocket version
---@return integer|false session_id
function svsessions.establish_pdg_session(source_addr, i_seq_num, version)

View File

@ -197,7 +197,7 @@ function supervisor.comms(_version, nic, fp_ok)
local r_chan = packet.scada_frame.remote_channel()
local src_addr = packet.scada_frame.src_addr()
local protocol = packet.scada_frame.protocol()
local i_seq_num = packet.scada_frame.seq_num() + 1
local i_seq_num = packet.scada_frame.seq_num()
if l_chan ~= config.SVR_Channel then
log.debug("received packet on unconfigured channel " .. l_chan, true)