From 2de30ef06448e8f81af7bdacba4bcf86c79ebc05 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 29 Jun 2024 14:10:58 -0400 Subject: [PATCH] #488 fixes to sequence number changes and auth packet data --- coordinator/coordinator.lua | 13 +++++++++---- coordinator/session/pocket.lua | 9 +++++---- pocket/pocket.lua | 22 ++++++++++++++++------ reactor-plc/plc.lua | 10 +++++++--- rtu/rtu.lua | 10 +++++++--- scada-common/comms.lua | 2 +- scada-common/network.lua | 10 +++++----- supervisor/session/coordinator.lua | 9 +++++---- supervisor/session/plc.lua | 9 +++++---- supervisor/session/pocket.lua | 9 +++++---- supervisor/session/rtu.lua | 9 +++++---- supervisor/session/svsessions.lua | 8 ++++---- supervisor/supervisor.lua | 2 +- 13 files changed, 75 insertions(+), 47 deletions(-) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index 9e98a33..27543ec 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -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 diff --git a/coordinator/session/pocket.lua b/coordinator/session/pocket.lua index f2f59e4..c554c64 100644 --- a/coordinator/session/pocket.lua +++ b/coordinator/session/pocket.lua @@ -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 diff --git a/pocket/pocket.lua b/pocket/pocket.lua index fb4f41a..82b87b5 100644 --- a/pocket/pocket.lua +++ b/pocket/pocket.lua @@ -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 diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 3eead7e..3a61e37 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -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 :) diff --git a/rtu/rtu.lua b/rtu/rtu.lua index 0278f42..231c261 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -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 diff --git a/scada-common/comms.lua b/scada-common/comms.lua index e31c60b..bfedd2c 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -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 diff --git a/scada-common/network.lua b/scada-common/network.lua index 4dce34e..c34a1d5 100644 --- a/scada-common/network.lua +++ b/scada-common/network.lua @@ -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 diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index f449650..a135a44 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -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 diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index fb5ca1b..4aad6d3 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -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 diff --git a/supervisor/session/pocket.lua b/supervisor/session/pocket.lua index 145add2..94a4467 100644 --- a/supervisor/session/pocket.lua +++ b/supervisor/session/pocket.lua @@ -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 diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index 789e649..756ee01 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -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 diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index c7c24fc..002dd56 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -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) diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index 2d69b7c..69a98f5 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -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)