From fc24f39991966fca7ac2e100a5eae8c2f9aca137 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 19 Oct 2025 17:30:05 -0400 Subject: [PATCH] #580 supervisor wired/wireless dual networking --- scada-common/comms.lua | 25 +- scada-common/network.lua | 15 +- supervisor/backplane.lua | 2 +- supervisor/supervisor.lua | 476 +++++++++++++++++++++----------------- 4 files changed, 300 insertions(+), 218 deletions(-) diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 5de3103..04563b8 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -17,7 +17,7 @@ local max_distance = nil local comms = {} -- protocol/data versions (protocol/data independent changes tracked by util.lua version) -comms.version = "3.0.8" +comms.version = "3.0.9" comms.api_version = "0.0.10" ---@enum PROTOCOL @@ -49,13 +49,14 @@ local MGMT_TYPE = { ESTABLISH = 0, -- establish new connection KEEP_ALIVE = 1, -- keep alive packet w/ RTT CLOSE = 2, -- close a connection - RTU_ADVERT = 3, -- RTU capability advertisement - RTU_DEV_REMOUNT = 4, -- RTU multiblock possbily changed (formed, unformed) due to PPM remount - RTU_TONE_ALARM = 5, -- instruct RTUs to play specified alarm tones - DIAG_TONE_GET = 6, -- (API) diagnostic: get alarm tones - DIAG_TONE_SET = 7, -- (API) diagnostic: set alarm tones - DIAG_ALARM_SET = 8, -- (API) diagnostic: set alarm to simulate audio for - INFO_LIST_CMP = 9 -- (API) info: list all computers on the network + PROBE = 3, + RTU_ADVERT = 4, -- RTU capability advertisement + RTU_DEV_REMOUNT = 5, -- RTU multiblock possbily changed (formed, unformed) due to PPM remount + RTU_TONE_ALARM = 6, -- instruct RTUs to play specified alarm tones + DIAG_TONE_GET = 7, -- (API) diagnostic: get alarm tones + DIAG_TONE_SET = 8, -- (API) diagnostic: set alarm tones + DIAG_ALARM_SET = 9, -- (API) diagnostic: set alarm to simulate audio for + INFO_LIST_CMP = 10 -- (API) info: list all computers on the network } ---@enum CRDN_TYPE @@ -89,6 +90,12 @@ local ESTABLISH_ACK = { ---@enum DEVICE_TYPE device types for establish messages local DEVICE_TYPE = { PLC = 0, RTU = 1, SVR = 2, CRD = 3, PKT = 4 } +---@enum PROBE_ACK +local PROBE_ACK = { + OPEN = 0, + CONFLICT = 1 +} + ---@enum PLC_AUTO_ACK local PLC_AUTO_ACK = { FAIL = 0, -- failed to set burn rate/burn rate invalid @@ -130,6 +137,8 @@ comms.CRDN_TYPE = CRDN_TYPE comms.ESTABLISH_ACK = ESTABLISH_ACK comms.DEVICE_TYPE = DEVICE_TYPE +comms.PROBE_ACK = PROBE_ACK + comms.PLC_AUTO_ACK = PLC_AUTO_ACK comms.UNIT_COMMAND = UNIT_COMMAND diff --git a/scada-common/network.lua b/scada-common/network.lua index 7080553..8066adb 100644 --- a/scada-common/network.lua +++ b/scada-common/network.lua @@ -82,7 +82,9 @@ end function network.nic(modem) local self = { -- modem interface name - iface = ppm.get_iface(modem), + iface = "?", + -- phy name + name = "?", -- used to quickly return out of tx/rx functions if there is nothing to do connected = true, -- used to avoid costly MAC calculations if not required @@ -94,6 +96,10 @@ function network.nic(modem) ---@class nic:Modem local public = {} + -- get the phy name + ---@nodiscard + function public.phy_name() return self.name end + -- check if this NIC has a connected modem ---@nodiscard function public.is_connected() return self.connected end @@ -102,11 +108,14 @@ function network.nic(modem) ---@param reconnected_modem Modem function public.connect(reconnected_modem) modem = reconnected_modem + + self.iface = ppm.get_iface(modem) + self.name = util.c(util.trinary(modem.isWireless(), "WLAN_PHY", "ETH_PHY"), "{", self.iface, "}") self.connected = true + self.use_hash = c_eng.hmac and modem.isWireless() + -- open only previously opened channels modem.closeAll() - - -- open previously opened channels for _, channel in ipairs(self.channels) do modem.open(channel) end diff --git a/supervisor/backplane.lua b/supervisor/backplane.lua index 7736290..9974085 100644 --- a/supervisor/backplane.lua +++ b/supervisor/backplane.lua @@ -18,7 +18,7 @@ local _bp = { wd_nic = nil, ---@type nic|nil wired nic wl_nic = nil, ---@type nic|nil wireless nic - nic_map = {} + nic_map = {} ---@type nic[] connected nics } backplane.nics = _bp.nic_map diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index 5388d0a..bbdcad7 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -10,10 +10,11 @@ local svsessions = require("supervisor.session.svsessions") local supervisor = {} -local PROTOCOL = comms.PROTOCOL -local DEVICE_TYPE = comms.DEVICE_TYPE +local PROTOCOL = comms.PROTOCOL +local DEVICE_TYPE = comms.DEVICE_TYPE local ESTABLISH_ACK = comms.ESTABLISH_ACK -local MGMT_TYPE = comms.MGMT_TYPE +local PROBE_ACK = comms.PROBE_ACK +local MGMT_TYPE = comms.MGMT_TYPE ---@type svr_config ---@diagnostic disable-next-line: missing-fields @@ -156,14 +157,11 @@ function supervisor.comms(_version, fp_ok, facility) local function println(message) if not fp_ok then util.println_ts(message) end end local self = { - last_est_acks = {} + last_est_acks = {} ---@type ESTABLISH_ACK[] } comms.set_trusted_range(config.TrustedRange) - -- configure network channels - pcie.nic.reset_open(config) - -- pass system data and objects to svsessions svsessions.init(fp_ok, config, facility) @@ -175,8 +173,7 @@ function supervisor.comms(_version, fp_ok, facility) ---@param ack ESTABLISH_ACK ---@param data? any optional data local function _send_establish(nic, packet, ack, data) - local s_pkt = comms.scada_packet() - local m_pkt = comms.mgmt_packet() + local s_pkt, m_pkt = comms.scada_packet(), comms.mgmt_packet() m_pkt.make(MGMT_TYPE.ESTABLISH, { ack, data }) s_pkt.make(packet.src_addr(), packet.seq_num() + 1, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable()) @@ -185,6 +182,188 @@ function supervisor.comms(_version, fp_ok, facility) self.last_est_acks[packet.src_addr()] = ack end + -- send a probe response + ---@param nic nic + ---@param packet scada_packet + ---@param ack PROBE_ACK + local function _send_probe(nic, packet, ack) + local s_pkt, m_pkt = comms.scada_packet(), comms.mgmt_packet() + + m_pkt.make(MGMT_TYPE.PROBE, { ack }) + s_pkt.make(packet.src_addr(), packet.seq_num() + 1, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable()) + + nic.transmit(packet.remote_channel(), config.SVR_Channel, s_pkt) + end + + --#region Establish Handlers + + -- handle a PLC establish + ---@param nic nic + ---@param packet mgmt_frame + ---@param src_addr integer + ---@param i_seq_num integer + ---@param last_ack ESTABLISH_ACK + local function _establish_plc(nic, packet, src_addr, i_seq_num, last_ack) + local comms_v = packet.data[1] + local firmware_v = packet.data[2] + local dev_type = packet.data[3] + + if comms_v ~= comms.version then + if last_ack ~= ESTABLISH_ACK.BAD_VERSION then + log.info(util.c("dropping PLC establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) + end + + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) + elseif dev_type == DEVICE_TYPE.PLC then + -- PLC linking request + if packet.length == 4 and type(packet.data[4]) == "number" then + local reactor_id = packet.data[4] + + -- check ID validity + if reactor_id < 1 or reactor_id > config.UnitCount then + -- reactor index out of range + if last_ack ~= ESTABLISH_ACK.DENY then + log.warning(util.c("PLC_ESTABLISH: denied assignment ", reactor_id, " outside of configured unit count ", config.UnitCount)) + end + + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) + else + -- try to establish the session + local plc_id = svsessions.establish_plc_session(nic, src_addr, i_seq_num, reactor_id, firmware_v) + + if plc_id == false then + -- reactor already has a PLC assigned + if last_ack ~= ESTABLISH_ACK.COLLISION then + log.warning(util.c("PLC_ESTABLISH: assignment collision with reactor ", reactor_id)) + end + + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.COLLISION) + else + -- got an ID; assigned to a reactor successfully + println(util.c("PLC (", firmware_v, ") [@", src_addr, "] \xbb reactor ", reactor_id, " connected")) + log.info(util.c("PLC_ESTABLISH: PLC (", firmware_v, ") [@", src_addr, "] reactor unit ", reactor_id, " PLC connected with session ID ", plc_id, " on ", nic.phy_name())) + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.ALLOW) + end + end + else + log.debug("PLC_ESTABLISH: packet length mismatch/bad parameter type") + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) + end + else + log.debug(util.c("illegal establish packet for device ", dev_type, " on PLC channel")) + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) + end + end + + -- handle an RTU gateway establish + ---@param nic nic + ---@param packet mgmt_frame + ---@param src_addr integer + ---@param i_seq_num integer + ---@param last_ack ESTABLISH_ACK + local function _establish_rtu_gw(nic, packet, src_addr, i_seq_num, last_ack) + local comms_v = packet.data[1] + local firmware_v = packet.data[2] + local dev_type = packet.data[3] + + if comms_v ~= comms.version then + if last_ack ~= ESTABLISH_ACK.BAD_VERSION then + log.info(util.c("dropping RTU_GW establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) + end + + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) + elseif dev_type == DEVICE_TYPE.RTU then + if packet.length == 4 then + -- this is an RTU advertisement for a new session + local rtu_advert = packet.data[4] + local s_id = svsessions.establish_rtu_session(nic, src_addr, i_seq_num, rtu_advert, firmware_v) + + println(util.c("RTU (", firmware_v, ") [@", src_addr, "] \xbb connected")) + log.info(util.c("RTU_GW_ESTABLISH: RTU_GW (",firmware_v, ") [@", src_addr, "] connected with session ID ", s_id, " on ", nic.phy_name())) + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.ALLOW) + else + log.debug("RTU_GW_ESTABLISH: packet length mismatch") + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) + end + else + log.debug(util.c("illegal establish packet for device ", dev_type, " on RTU channel")) + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) + end + end + + -- handle a coordinator establish + ---@param nic nic + ---@param packet mgmt_frame + ---@param src_addr integer + ---@param i_seq_num integer + ---@param last_ack ESTABLISH_ACK + local function _establish_crd(nic, packet, src_addr, i_seq_num, last_ack) + local comms_v = packet.data[1] + local firmware_v = packet.data[2] + local dev_type = packet.data[3] + + if comms_v ~= comms.version then + if last_ack ~= ESTABLISH_ACK.BAD_VERSION then + log.info(util.c("dropping coordinator establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) + end + + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) + elseif dev_type == DEVICE_TYPE.CRD then + -- this is an attempt to establish a new coordinator session + local s_id = svsessions.establish_crd_session(nic, src_addr, i_seq_num, firmware_v) + + if s_id ~= false then + println(util.c("CRD (", firmware_v, ") [@", src_addr, "] \xbb connected")) + log.info(util.c("CRD_ESTABLISH: CRD (", firmware_v, ") [@", src_addr, "] connected with session ID ", s_id, " on ", nic.phy_name())) + + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.ALLOW, { config.UnitCount, facility.get_cooling_conf() }) + else + if last_ack ~= ESTABLISH_ACK.COLLISION then + log.info("CRD_ESTABLISH: denied new coordinator [@" .. src_addr .. "] due to already being connected to another coordinator") + end + + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.COLLISION) + end + else + log.debug(util.c("illegal establish packet for device ", dev_type, " on CRD channel")) + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) + end + end + + -- handle a pocket debug establish + ---@param nic nic + ---@param packet mgmt_frame + ---@param src_addr integer + ---@param i_seq_num integer + ---@param last_ack ESTABLISH_ACK + local function _establish_pdg(nic, packet, src_addr, i_seq_num, last_ack) + local comms_v = packet.data[1] + local firmware_v = packet.data[2] + local dev_type = packet.data[3] + + if comms_v ~= comms.version then + if last_ack ~= ESTABLISH_ACK.BAD_VERSION then + log.info(util.c("dropping PKT establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) + end + + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) + elseif dev_type == DEVICE_TYPE.PKT then + -- this is an attempt to establish a new pocket diagnostic session + local s_id = svsessions.establish_pdg_session(nic, src_addr, i_seq_num, firmware_v) + + println(util.c("PKT (", firmware_v, ") [@", src_addr, "] \xbb connected")) + log.info(util.c("PDG_ESTABLISH: pocket (", firmware_v, ") [@", src_addr, "] connected with session ID ", s_id, " on ", nic.phy_name())) + + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.ALLOW) + else + log.debug(util.c("illegal establish packet for device ", dev_type, " on PKT channel")) + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) + end + + end + + --#endregion + -- PUBLIC FUNCTIONS -- ---@class superv_comms @@ -240,89 +419,45 @@ function supervisor.comms(_version, fp_ok, facility) local protocol = packet.scada_frame.protocol() local i_seq_num = packet.scada_frame.seq_num() - if not nic then - log.error("received packet from unconfigured interface " .. packet.scada_frame.interface(), true) - elseif l_chan ~= config.SVR_Channel then + if l_chan ~= config.SVR_Channel then log.debug("received packet on unconfigured channel " .. l_chan, true) elseif r_chan == config.PLC_Channel then -- look for an associated session local session = svsessions.find_plc_session(src_addr) - if protocol == PROTOCOL.RPLC then - ---@cast packet rplc_frame - -- reactor PLC packet - if session ~= nil then + if session then + if nic ~= session.nic then + -- this is from the same device but on a different interface + -- drop unless it is a connection probe + if (protocol == PROTOCOL.SCADA_MGMT) and (packet.type == MGMT_TYPE.PROBE) then + ---@cast packet mgmt_frame + log.debug(util.c("PROBE_ACK: conflict with PLC @", src_addr, " on ", session.nic.phy_name(), " probed on ", nic.phy_name())) + _send_probe(nic, packet.scada_frame, PROBE_ACK.CONFLICT) + else + log.debug(util.c("unexpected packet for PLC @ ", src_addr, " received on ", nic.phy_name())) + end + else -- pass the packet onto the session handler session.in_queue.push_packet(packet) - else - -- any other packet should be session related, discard it - log.debug("discarding RPLC packet without a known session") end + elseif protocol == PROTOCOL.RPLC then + -- reactor PLC packet should be session related, discard it + log.debug("discarding RPLC packet without a known session") elseif protocol == PROTOCOL.SCADA_MGMT then ---@cast packet mgmt_frame -- SCADA management packet - if session ~= nil then - -- pass the packet onto the session handler - session.in_queue.push_packet(packet) - elseif packet.type == MGMT_TYPE.ESTABLISH then - -- establish a new session - local last_ack = self.last_est_acks[src_addr] - - -- validate packet and continue + if packet.type == MGMT_TYPE.ESTABLISH then + -- establish a new session: validate packet and continue if packet.length >= 3 and type(packet.data[1]) == "string" and type(packet.data[2]) == "string" then - local comms_v = packet.data[1] - local firmware_v = packet.data[2] - local dev_type = packet.data[3] - - if comms_v ~= comms.version then - if last_ack ~= ESTABLISH_ACK.BAD_VERSION then - log.info(util.c("dropping PLC establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) - end - - _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) - elseif dev_type == DEVICE_TYPE.PLC then - -- PLC linking request - if packet.length == 4 and type(packet.data[4]) == "number" then - local reactor_id = packet.data[4] - - -- check ID validity - if reactor_id < 1 or reactor_id > config.UnitCount then - -- reactor index out of range - if last_ack ~= ESTABLISH_ACK.DENY then - log.warning(util.c("PLC_ESTABLISH: denied assignment ", reactor_id, " outside of configured unit count ", config.UnitCount)) - end - - _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) - else - -- try to establish the session - local plc_id = svsessions.establish_plc_session(nic, src_addr, i_seq_num, reactor_id, firmware_v) - - if plc_id == false then - -- reactor already has a PLC assigned - if last_ack ~= ESTABLISH_ACK.COLLISION then - log.warning(util.c("PLC_ESTABLISH: assignment collision with reactor ", reactor_id)) - end - - _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.COLLISION) - else - -- got an ID; assigned to a reactor successfully - println(util.c("PLC (", firmware_v, ") [@", src_addr, "] \xbb reactor ", reactor_id, " connected")) - log.info(util.c("PLC_ESTABLISH: PLC (", firmware_v, ") [@", src_addr, "] reactor unit ", reactor_id, " PLC connected with session ID ", plc_id)) - _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.ALLOW) - end - end - else - log.debug("PLC_ESTABLISH: packet length mismatch/bad parameter type") - _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) - end - else - log.debug(util.c("illegal establish packet for device ", dev_type, " on PLC channel")) - _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) - end + _establish_plc(nic, packet, src_addr, i_seq_num, self.last_est_acks[src_addr]) else log.debug("invalid establish packet (on PLC channel)") _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) end + elseif packet.type == MGMT_TYPE.PROBE then + -- connection probing + log.debug(util.c("PROBE_ACK: reporting open to PLC @", src_addr, " probed on ", nic.phy_name())) + _send_probe(nic, packet.scada_frame, PROBE_ACK.OPEN) else -- any other packet should be session related, discard it log.debug(util.c("discarding PLC SCADA_MGMT packet without a known session from computer ", src_addr)) @@ -334,62 +469,43 @@ function supervisor.comms(_version, fp_ok, facility) -- look for an associated session local session = svsessions.find_rtu_session(src_addr) - if protocol == PROTOCOL.MODBUS_TCP then - ---@cast packet modbus_frame - -- MODBUS response - if session ~= nil then + if session then + if nic ~= session.nic then + -- this is from the same device but on a different interface + -- drop unless it is a connection probe + if (protocol == PROTOCOL.SCADA_MGMT) and (packet.type == MGMT_TYPE.PROBE) then + ---@cast packet mgmt_frame + log.debug(util.c("PROBE_ACK: conflict with RTU_GW @", src_addr, " on ", session.nic.phy_name(), " probed on ", nic.phy_name())) + _send_probe(nic, packet.scada_frame, PROBE_ACK.CONFLICT) + else + log.debug(util.c("unexpected packet for RTU_GW @ ", src_addr, " received on ", nic.phy_name())) + end + else -- pass the packet onto the session handler session.in_queue.push_packet(packet) - else - -- any other packet should be session related, discard it - log.debug("discarding MODBUS_TCP packet without a known session") end + elseif protocol == PROTOCOL.MODBUS_TCP then + ---@cast packet modbus_frame + -- MODBUS response, should be session related, discard it + log.debug("discarding MODBUS_TCP packet without a known session") elseif protocol == PROTOCOL.SCADA_MGMT then ---@cast packet mgmt_frame -- SCADA management packet - if session ~= nil then - -- pass the packet onto the session handler - session.in_queue.push_packet(packet) - elseif packet.type == MGMT_TYPE.ESTABLISH then - -- establish a new session - local last_ack = self.last_est_acks[src_addr] - - -- validate packet and continue + if packet.type == MGMT_TYPE.ESTABLISH then + -- establish a new session: validate packet and continue if packet.length >= 3 and type(packet.data[1]) == "string" and type(packet.data[2]) == "string" then - local comms_v = packet.data[1] - local firmware_v = packet.data[2] - local dev_type = packet.data[3] - - if comms_v ~= comms.version then - if last_ack ~= ESTABLISH_ACK.BAD_VERSION then - log.info(util.c("dropping RTU establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) - end - - _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) - elseif dev_type == DEVICE_TYPE.RTU then - if packet.length == 4 then - -- this is an RTU advertisement for a new session - local rtu_advert = packet.data[4] - local s_id = svsessions.establish_rtu_session(nic, src_addr, i_seq_num, rtu_advert, firmware_v) - - println(util.c("RTU (", firmware_v, ") [@", src_addr, "] \xbb connected")) - log.info(util.c("RTU_ESTABLISH: RTU (",firmware_v, ") [@", src_addr, "] connected with session ID ", s_id)) - _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.ALLOW) - else - log.debug("RTU_ESTABLISH: packet length mismatch") - _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) - end - else - log.debug(util.c("illegal establish packet for device ", dev_type, " on RTU channel")) - _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) - end + _establish_rtu_gw(nic, packet, src_addr, i_seq_num, self.last_est_acks[src_addr]) else log.debug("invalid establish packet (on RTU channel)") _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) end + elseif packet.type == MGMT_TYPE.PROBE then + -- connection probing + log.debug(util.c("PROBE_ACK: reporting open to RTU_GW @", src_addr, " probed on ", nic.phy_name())) + _send_probe(nic, packet.scada_frame, PROBE_ACK.OPEN) else -- any other packet should be session related, discard it - log.debug(util.c("discarding RTU SCADA_MGMT packet without a known session from computer ", src_addr)) + log.debug(util.c("discarding RTU gateway SCADA_MGMT packet without a known session from computer ", src_addr)) end else log.debug(util.c("illegal packet type ", protocol, " on RTU channel")) @@ -398,107 +514,61 @@ function supervisor.comms(_version, fp_ok, facility) -- look for an associated session local session = svsessions.find_crd_session(src_addr) - if protocol == PROTOCOL.SCADA_MGMT then - ---@cast packet mgmt_frame - -- SCADA management packet - if session ~= nil then + if session then + if nic ~= session.nic then + -- this is from the same device but on a different interface + -- drop unless it is a connection probe + if (protocol == PROTOCOL.SCADA_MGMT) and (packet.type == MGMT_TYPE.PROBE) then + ---@cast packet mgmt_frame + log.debug(util.c("PROBE_ACK: conflict with CRD @", src_addr, " on ", session.nic.phy_name(), " probed on ", nic.phy_name())) + _send_probe(nic, packet.scada_frame, PROBE_ACK.CONFLICT) + else + log.debug(util.c("unexpected packet for CRD @ ", src_addr, " received on ", nic.phy_name())) + end + else -- pass the packet onto the session handler session.in_queue.push_packet(packet) - elseif packet.type == MGMT_TYPE.ESTABLISH then - -- establish a new session - local last_ack = self.last_est_acks[src_addr] - - -- validate packet and continue + end + elseif protocol == PROTOCOL.SCADA_MGMT then + ---@cast packet mgmt_frame + -- SCADA management packet + if packet.type == MGMT_TYPE.ESTABLISH then + -- establish a new session: validate packet and continue if packet.length >= 3 and type(packet.data[1]) == "string" and type(packet.data[2]) == "string" then - local comms_v = packet.data[1] - local firmware_v = packet.data[2] - local dev_type = packet.data[3] - - if comms_v ~= comms.version then - if last_ack ~= ESTABLISH_ACK.BAD_VERSION then - log.info(util.c("dropping coordinator establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) - end - - _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) - elseif dev_type == DEVICE_TYPE.CRD then - -- this is an attempt to establish a new coordinator session - local s_id = svsessions.establish_crd_session(nic, src_addr, i_seq_num, firmware_v) - - if s_id ~= false then - println(util.c("CRD (", firmware_v, ") [@", src_addr, "] \xbb connected")) - log.info(util.c("CRD_ESTABLISH: coordinator (", firmware_v, ") [@", src_addr, "] connected with session ID ", s_id)) - - _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.ALLOW, { config.UnitCount, facility.get_cooling_conf() }) - else - if last_ack ~= ESTABLISH_ACK.COLLISION then - log.info("CRD_ESTABLISH: denied new coordinator [@" .. src_addr .. "] due to already being connected to another coordinator") - end - - _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.COLLISION) - end - else - log.debug(util.c("illegal establish packet for device ", dev_type, " on coordinator channel")) - _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) - end + _establish_crd(nic, packet, src_addr, i_seq_num, self.last_est_acks[src_addr]) else log.debug("CRD_ESTABLISH: establish packet length mismatch") _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) end + elseif packet.type == MGMT_TYPE.PROBE then + -- connection probing + log.debug(util.c("PROBE_ACK: reporting open to CRD @", src_addr, " probed on ", nic.phy_name())) + _send_probe(nic, packet.scada_frame, PROBE_ACK.OPEN) else -- any other packet should be session related, discard it log.debug(util.c("discarding coordinator SCADA_MGMT packet without a known session from computer ", src_addr)) end elseif protocol == PROTOCOL.SCADA_CRDN then ---@cast packet crdn_frame - -- coordinator packet - if session ~= nil then - -- pass the packet onto the session handler - session.in_queue.push_packet(packet) - else - -- any other packet should be session related, discard it - log.debug(util.c("discarding coordinator SCADA_CRDN packet without a known session from computer ", src_addr)) - end + -- coordinator packet, should be session related, discard it + log.debug(util.c("discarding coordinator SCADA_CRDN packet without a known session from computer ", src_addr)) else - log.debug(util.c("illegal packet type ", protocol, " on coordinator channel")) + log.debug(util.c("illegal packet type ", protocol, " on CRD channel")) end elseif r_chan == config.PKT_Channel then -- look for an associated session local session = svsessions.find_pdg_session(src_addr) - if protocol == PROTOCOL.SCADA_MGMT then + if session then + -- pass the packet onto the session handler + session.in_queue.push_packet(packet) + elseif protocol == PROTOCOL.SCADA_MGMT then ---@cast packet mgmt_frame -- SCADA management packet - if session ~= nil then - -- pass the packet onto the session handler - session.in_queue.push_packet(packet) - elseif packet.type == MGMT_TYPE.ESTABLISH then - -- establish a new session - local last_ack = self.last_est_acks[src_addr] - - -- validate packet and continue + if packet.type == MGMT_TYPE.ESTABLISH then + -- establish a new session: validate packet and continue if packet.length >= 3 and type(packet.data[1]) == "string" and type(packet.data[2]) == "string" then - local comms_v = packet.data[1] - local firmware_v = packet.data[2] - local dev_type = packet.data[3] - - if comms_v ~= comms.version then - if last_ack ~= ESTABLISH_ACK.BAD_VERSION then - log.info(util.c("dropping PDG establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) - end - - _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) - elseif dev_type == DEVICE_TYPE.PKT then - -- this is an attempt to establish a new pocket diagnostic session - local s_id = svsessions.establish_pdg_session(nic, src_addr, i_seq_num, firmware_v) - - println(util.c("PKT (", firmware_v, ") [@", src_addr, "] \xbb connected")) - log.info(util.c("PDG_ESTABLISH: pocket (", firmware_v, ") [@", src_addr, "] connected with session ID ", s_id)) - - _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.ALLOW) - else - log.debug(util.c("illegal establish packet for device ", dev_type, " on pocket channel")) - _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) - end + _establish_pdg(nic, packet, src_addr, i_seq_num, self.last_est_acks[src_addr]) else log.debug("PDG_ESTABLISH: establish packet length mismatch") _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) @@ -509,14 +579,8 @@ function supervisor.comms(_version, fp_ok, facility) end elseif protocol == PROTOCOL.SCADA_CRDN then ---@cast packet crdn_frame - -- coordinator packet - if session ~= nil then - -- pass the packet onto the session handler - session.in_queue.push_packet(packet) - else - -- any other packet should be session related, discard it - log.debug(util.c("discarding pocket SCADA_CRDN packet without a known session from computer ", src_addr)) - end + -- coordinator packet, should be session related, discard it + log.debug(util.c("discarding pocket SCADA_CRDN packet without a known session from computer ", src_addr)) else log.debug(util.c("illegal packet type ", protocol, " on pocket channel")) end