Adds a new rust modbus app layer parser and detection module.
Moves the C module to rust but leaves the test cases in place to
regression test the new rust module.
flate2 = "~1.0.19"
brotli = "~3.3.0"
+sawp-modbus = "~0.4.0"
+sawp = "~0.4.0"
der-parser = "~4.0.2"
kerberos-parser = "~0.5.0"
ntp-parser = "~0.4.0"
digest = "~0.9.0"
sha-1 = "~0.9.2"
md-5 = "~0.9.1"
+regex = "~1.4.2"
+lazy_static = "~1.4.0"
[dev-dependencies]
test-case = "~1.1.0"
"AppLayerGetTxIterTuple",
"RdpState",
"SIPState",
+ "ModbusState",
"CMark",
]
pub mod smb;
pub mod krb;
pub mod dcerpc;
+pub mod modbus;
pub mod ike;
pub mod snmp;
--- /dev/null
+/* Copyright (C) 2021 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+use super::modbus::ModbusTransaction;
+use lazy_static::lazy_static;
+use regex::Regex;
+use sawp_modbus::{AccessType, CodeCategory, Data, Flags, FunctionCode, Message};
+use std::ffi::CStr;
+use std::ops::{Range, RangeInclusive};
+use std::os::raw::{c_char, c_void};
+use std::str::FromStr;
+
+lazy_static! {
+ static ref ACCESS_RE: Regex = Regex::new(
+ "^\\s*\"?\\s*access\\s*(read|write)\
+ \\s*(discretes|coils|input|holding)?\
+ (?:,\\s*address\\s+([<>]?\\d+)(?:<>(\\d+))?\
+ (?:,\\s*value\\s+([<>]?\\d+)(?:<>(\\d+))?)?)?\
+ \\s*\"?\\s*$"
+ )
+ .unwrap();
+ static ref FUNC_RE: Regex = Regex::new(
+ "^\\s*\"?\\s*function\\s*(!?[A-z0-9]+)\
+ (?:,\\s*subfunction\\s+(\\d+))?\\s*\"?\\s*$"
+ )
+ .unwrap();
+ static ref UNIT_RE: Regex = Regex::new(
+ "^\\s*\"?\\s*unit\\s+([<>]?\\d+)\
+ (?:<>(\\d+))?(?:,\\s*(.*))?\\s*\"?\\s*$"
+ )
+ .unwrap();
+}
+
+#[derive(Debug, PartialEq)]
+pub struct DetectModbusRust {
+ category: Option<Flags<CodeCategory>>,
+ function: Option<FunctionCode>,
+ subfunction: Option<u16>,
+ access_type: Option<Flags<AccessType>>,
+ unit_id: Option<Range<u16>>,
+ address: Option<Range<u16>>,
+ value: Option<Range<u16>>,
+}
+
+/// TODO: remove these after regression testing commit
+#[no_mangle]
+pub extern "C" fn rs_modbus_get_category(modbus: *const DetectModbusRust) -> u8 {
+ let modbus = unsafe { modbus.as_ref() }.unwrap();
+ modbus.category.map(|val| val.bits()).unwrap_or(0)
+}
+
+#[no_mangle]
+pub extern "C" fn rs_modbus_get_function(modbus: *const DetectModbusRust) -> u8 {
+ let modbus = unsafe { modbus.as_ref() }.unwrap();
+ modbus.function.map(|val| val as u8).unwrap_or(0)
+}
+
+#[no_mangle]
+pub extern "C" fn rs_modbus_get_subfunction(modbus: *const DetectModbusRust) -> u16 {
+ let modbus = unsafe { modbus.as_ref() }.unwrap();
+ modbus.subfunction.unwrap_or(0)
+}
+
+#[no_mangle]
+pub extern "C" fn rs_modbus_get_has_subfunction(modbus: *const DetectModbusRust) -> bool {
+ let modbus = unsafe { modbus.as_ref() }.unwrap();
+ modbus.subfunction.is_some()
+}
+
+#[no_mangle]
+pub extern "C" fn rs_modbus_get_access_type(modbus: *const DetectModbusRust) -> u8 {
+ let modbus = unsafe { modbus.as_ref() }.unwrap();
+ modbus.access_type.map(|val| val.bits()).unwrap_or(0)
+}
+
+#[no_mangle]
+pub extern "C" fn rs_modbus_get_unit_id_min(modbus: *const DetectModbusRust) -> u16 {
+ let modbus = unsafe { modbus.as_ref() }.unwrap();
+ modbus.unit_id.as_ref().map(|val| val.start).unwrap_or(0)
+}
+
+#[no_mangle]
+pub extern "C" fn rs_modbus_get_unit_id_max(modbus: *const DetectModbusRust) -> u16 {
+ let modbus = unsafe { modbus.as_ref() }.unwrap();
+ modbus.unit_id.as_ref().map(|val| val.end).unwrap_or(0)
+}
+
+#[no_mangle]
+pub extern "C" fn rs_modbus_get_address_min(modbus: *const DetectModbusRust) -> u16 {
+ let modbus = unsafe { modbus.as_ref() }.unwrap();
+ modbus.address.as_ref().map(|val| val.start).unwrap_or(0)
+}
+
+#[no_mangle]
+pub extern "C" fn rs_modbus_get_address_max(modbus: *const DetectModbusRust) -> u16 {
+ let modbus = unsafe { modbus.as_ref() }.unwrap();
+ modbus.address.as_ref().map(|val| val.end).unwrap_or(0)
+}
+
+#[no_mangle]
+pub extern "C" fn rs_modbus_get_data_min(modbus: *const DetectModbusRust) -> u16 {
+ let modbus = unsafe { modbus.as_ref() }.unwrap();
+ modbus.value.as_ref().map(|val| val.start).unwrap_or(0)
+}
+
+#[no_mangle]
+pub extern "C" fn rs_modbus_get_data_max(modbus: *const DetectModbusRust) -> u16 {
+ let modbus = unsafe { modbus.as_ref() }.unwrap();
+ modbus.value.as_ref().map(|val| val.end).unwrap_or(0)
+}
+
+impl Default for DetectModbusRust {
+ fn default() -> Self {
+ DetectModbusRust {
+ category: None,
+ function: None,
+ subfunction: None,
+ access_type: None,
+ unit_id: None,
+ address: None,
+ value: None,
+ }
+ }
+}
+
+/// Compares a range from the alert signature to the transaction's unit_id/address/value
+/// range. If the signature's range intersects with the transaction, it is a match and true is
+/// returned.
+fn check_match_range(sig_range: &Range<u16>, trans_range: RangeInclusive<u16>) -> bool {
+ if sig_range.start == sig_range.end {
+ sig_range.start >= *trans_range.start() && sig_range.start <= *trans_range.end()
+ } else if sig_range.start == std::u16::MIN {
+ sig_range.end > *trans_range.start()
+ } else if sig_range.end == std::u16::MAX {
+ sig_range.start < *trans_range.end()
+ } else {
+ sig_range.start < *trans_range.end() && *trans_range.start() < sig_range.end
+ }
+}
+
+/// Compares a range from the alert signature to the transaction's unit_id/address/value.
+/// If the signature's range intersects with the transaction, it is a match and true is
+/// returned.
+fn check_match(sig_range: &Range<u16>, value: u16) -> bool {
+ if sig_range.start == sig_range.end {
+ sig_range.start == value
+ } else if sig_range.start == std::u16::MIN {
+ sig_range.end > value
+ } else if sig_range.end == std::u16::MAX {
+ sig_range.start < value
+ } else {
+ sig_range.start < value && value < sig_range.end
+ }
+}
+
+/// Gets the min/max range of an alert signature from the respective capture groups.
+/// In the case where the max is not given, it is set based on the first char of the min str
+/// which indicates what range we are looking for:
+/// '<' = std::u16::MIN..min
+/// '>' = min..std::u16::MAX
+/// _ = min..min
+/// If the max is given, the range returned is min..max
+fn parse_range(min_str: &str, max_str: &str) -> Result<Range<u16>, ()> {
+ if max_str.is_empty() {
+ if let Some(sign) = min_str.chars().next() {
+ match min_str[!sign.is_ascii_digit() as usize..].parse::<u16>() {
+ Ok(num) => match sign {
+ '>' => Ok(num..std::u16::MAX),
+ '<' => Ok(std::u16::MIN..num),
+ _ => Ok(num..num),
+ },
+ Err(_) => {
+ SCLogError!("Invalid min number: {}", min_str);
+ Err(())
+ }
+ }
+ } else {
+ Err(())
+ }
+ } else {
+ let min = match min_str.parse::<u16>() {
+ Ok(num) => num,
+ Err(_) => {
+ SCLogError!("Invalid min number: {}", min_str);
+ return Err(());
+ }
+ };
+
+ let max = match max_str.parse::<u16>() {
+ Ok(num) => num,
+ Err(_) => {
+ SCLogError!("Invalid max number: {}", max_str);
+ return Err(());
+ }
+ };
+
+ Ok(min..max)
+ }
+}
+
+/// Intermediary function between the C code and the parsing functions.
+#[no_mangle]
+pub unsafe extern "C" fn rs_modbus_parse(c_arg: *const c_char) -> *mut c_void {
+ if c_arg.is_null() {
+ return std::ptr::null_mut();
+ }
+ if let Ok(arg) = CStr::from_ptr(c_arg).to_str() {
+ match parse_unit_id(&arg)
+ .or_else(|_| parse_function(&arg))
+ .or_else(|_| parse_access(&arg))
+ {
+ Ok(detect) => return Box::into_raw(Box::new(detect)) as *mut c_void,
+ Err(()) => return std::ptr::null_mut(),
+ }
+ }
+ std::ptr::null_mut()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_modbus_free(ptr: *mut c_void) {
+ if !ptr.is_null() {
+ let _ = Box::from_raw(ptr as *mut DetectModbusRust);
+ }
+}
+
+/// Compares a transaction to a signature to determine whether the transaction
+/// matches the signature. If it does, 1 is returned; otherwise 0 is returned.
+#[no_mangle]
+pub extern "C" fn rs_modbus_inspect(tx: &ModbusTransaction, modbus: &DetectModbusRust) -> u8 {
+ // All necessary information can be found in the request (value inspection currently
+ // only supports write functions, which hold the value in the request).
+ // Only inspect the response in the case where there is no request.
+ let msg = match &tx.request {
+ Some(r) => r,
+ None => match &tx.response {
+ Some(r) => r,
+ None => return 0,
+ },
+ };
+
+ if let Some(unit_id) = &modbus.unit_id {
+ if !check_match(unit_id, msg.unit_id.into()) {
+ return 0;
+ }
+ }
+
+ if let Some(access_type) = &modbus.access_type {
+ let rd_wr_access = *access_type & (AccessType::READ | AccessType::WRITE);
+ let access_func = *access_type & AccessType::FUNC_MASK;
+
+ if rd_wr_access.is_empty()
+ || !msg.access_type.intersects(rd_wr_access)
+ || (!access_func.is_empty() && !msg.access_type.intersects(access_func))
+ {
+ return 0;
+ }
+
+ return inspect_data(msg, modbus) as u8;
+ }
+
+ if let Some(category) = modbus.category {
+ return u8::from(msg.category.intersects(category));
+ }
+
+ match &modbus.function {
+ Some(func) if func == &msg.function.code => match modbus.subfunction {
+ Some(subfunc) => {
+ if let Data::Diagnostic { func, data: _ } = &msg.data {
+ u8::from(subfunc == func.raw)
+ } else {
+ 0
+ }
+ }
+ None => 1,
+ },
+ None => 1,
+ _ => 0,
+ }
+}
+
+/// Compares the transaction's data with the signature to determine whether or
+/// not it is a match
+fn inspect_data(msg: &Message, modbus: &DetectModbusRust) -> bool {
+ let sig_address = if let Some(sig_addr) = &modbus.address {
+ // Compare the transaction's address with the signature to determine whether or
+ // not it is a match
+ if let Some(req_addr) = msg.get_address_range() {
+ if !check_match_range(sig_addr, req_addr) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+
+ sig_addr.start
+ } else {
+ return true;
+ };
+
+ let sig_value = if let Some(value) = &modbus.value {
+ value
+ } else {
+ return true;
+ };
+
+ if let Some(value) = msg.get_write_value_at_address(&sig_address) {
+ check_match(sig_value, value)
+ } else {
+ false
+ }
+}
+
+/// Parses the access type for the signature
+fn parse_access(access_str: &str) -> Result<DetectModbusRust, ()> {
+ let re = if let Some(re) = ACCESS_RE.captures(access_str) {
+ re
+ } else {
+ return Err(());
+ };
+
+ // 1: Read | Write
+ let mut access_type: Flags<AccessType> = match re.get(1) {
+ Some(access) => match AccessType::from_str(access.as_str()) {
+ Ok(access_type) => access_type.into(),
+ Err(_) => {
+ SCLogError!("Unknown access keyword {}", access.as_str());
+ return Err(());
+ }
+ },
+ None => {
+ SCLogError!("No access keyword found");
+ return Err(());
+ }
+ };
+
+ // 2: Discretes | Coils | Input | Holding
+ access_type = match re.get(2) {
+ Some(x) if x.as_str() == "coils" => access_type | AccessType::COILS,
+ Some(x) if x.as_str() == "holding" => access_type | AccessType::HOLDING,
+ Some(x) if x.as_str() == "discretes" => {
+ if access_type == AccessType::WRITE {
+ SCLogError!("Discrete access is only read access");
+ return Err(());
+ }
+ access_type | AccessType::DISCRETES
+ }
+ Some(x) if x.as_str() == "input" => {
+ if access_type == AccessType::WRITE {
+ SCLogError!("Input access is only read access");
+ return Err(());
+ }
+ access_type | AccessType::INPUT
+ }
+ Some(unknown) => {
+ SCLogError!("Unknown access keyword {}", unknown.as_str());
+ return Err(());
+ }
+ None => access_type,
+ };
+
+ // 3: Address min
+ let address = if let Some(min) = re.get(3) {
+ // 4: Address max
+ let max_str = if let Some(max) = re.get(4) {
+ max.as_str()
+ } else {
+ ""
+ };
+ parse_range(min.as_str(), max_str)?
+ } else {
+ return Ok(DetectModbusRust {
+ access_type: Some(access_type),
+ ..Default::default()
+ });
+ };
+
+ // 5: Value min
+ let value = if let Some(min) = re.get(5) {
+ if address.start != address.end {
+ SCLogError!("rule contains conflicting keywords (address range and value).");
+ return Err(());
+ }
+
+ if access_type == AccessType::READ {
+ SCLogError!("Value keyword only works in write access");
+ return Err(());
+ }
+
+ // 6: Value max
+ let max_str = if let Some(max) = re.get(6) {
+ max.as_str()
+ } else {
+ ""
+ };
+
+ parse_range(min.as_str(), max_str)?
+ } else {
+ return Ok(DetectModbusRust {
+ access_type: Some(access_type),
+ address: Some(address),
+ ..Default::default()
+ });
+ };
+
+ Ok(DetectModbusRust {
+ access_type: Some(access_type),
+ address: Some(address),
+ value: Some(value),
+ ..Default::default()
+ })
+}
+
+fn parse_function(func_str: &str) -> Result<DetectModbusRust, ()> {
+ let re = if let Some(re) = FUNC_RE.captures(func_str) {
+ re
+ } else {
+ return Err(());
+ };
+
+ let mut modbus: DetectModbusRust = Default::default();
+
+ // 1: Function
+ if let Some(x) = re.get(1) {
+ let word = x.as_str();
+
+ // Digit
+ if let Ok(num) = word.parse::<u8>() {
+ if num == 0 {
+ SCLogError!("Invalid modbus function value");
+ return Err(());
+ }
+
+ modbus.function = Some(FunctionCode::from_raw(num));
+
+ // 2: Subfunction (optional)
+ match re.get(2) {
+ Some(x) => {
+ let subfunc = x.as_str();
+ match subfunc.parse::<u16>() {
+ Ok(num) => {
+ modbus.subfunction = Some(num);
+ }
+ Err(_) => {
+ SCLogError!("Invalid subfunction value: {}", subfunc);
+ return Err(());
+ }
+ }
+ }
+ None => return Ok(modbus),
+ }
+ }
+ // Non-digit
+ else {
+ let neg = word.starts_with('!');
+
+ let category = match &word[neg as usize..] {
+ "assigned" => CodeCategory::PUBLIC_ASSIGNED.into(),
+ "unassigned" => CodeCategory::PUBLIC_UNASSIGNED.into(),
+ "public" => CodeCategory::PUBLIC_ASSIGNED | CodeCategory::PUBLIC_UNASSIGNED,
+ "user" => CodeCategory::USER_DEFINED.into(),
+ "reserved" => CodeCategory::RESERVED.into(),
+ "all" => {
+ CodeCategory::PUBLIC_ASSIGNED
+ | CodeCategory::PUBLIC_UNASSIGNED
+ | CodeCategory::USER_DEFINED
+ | CodeCategory::RESERVED
+ }
+ _ => {
+ SCLogError!("Keyword unknown: {}", word);
+ return Err(());
+ }
+ };
+
+ if neg {
+ modbus.category = Some(!category);
+ } else {
+ modbus.category = Some(category);
+ }
+ }
+ } else {
+ return Err(());
+ }
+
+ Ok(modbus)
+}
+
+fn parse_unit_id(unit_str: &str) -> Result<DetectModbusRust, ()> {
+ let re = if let Some(re) = UNIT_RE.captures(unit_str) {
+ re
+ } else {
+ return Err(());
+ };
+
+ // 3: Either function or access string
+ let mut modbus = if let Some(x) = re.get(3) {
+ let extra = x.as_str();
+ if let Ok(mbus) = parse_function(extra) {
+ mbus
+ } else if let Ok(mbus) = parse_access(extra) {
+ mbus
+ } else {
+ SCLogError!("Invalid modbus option: {}", extra);
+ return Err(());
+ }
+ } else {
+ Default::default()
+ };
+
+ // 1: Unit ID min
+ if let Some(min) = re.get(1) {
+ // 2: Unit ID max
+ let max_str = if let Some(max) = re.get(2) {
+ max.as_str()
+ } else {
+ ""
+ };
+
+ modbus.unit_id = Some(parse_range(min.as_str(), max_str)?);
+ } else {
+ SCLogError!("Min modbus unit ID not found");
+ return Err(());
+ }
+
+ Ok(modbus)
+}
--- /dev/null
+/* Copyright (C) 2021 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+pub mod detect;
+pub mod modbus;
--- /dev/null
+/* Copyright (C) 2021 Open Information Security Foundation
+*
+* You can copy, redistribute or modify this Program under the terms of
+* the GNU General Public License version 2 as published by the Free
+* Software Foundation.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* version 2 along with this program; if not, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+* 02110-1301, USA.
+*/
+use crate::applayer::*;
+use crate::core::{self, AppProto, ALPROTO_FAILED, ALPROTO_UNKNOWN, IPPROTO_TCP};
+
+use std::ffi::CString;
+use std::str::FromStr;
+
+use sawp::error::Error as SawpError;
+use sawp::error::ErrorKind as SawpErrorKind;
+use sawp::parser::{Direction, Parse};
+use sawp::probe::{Probe, Status};
+use sawp_modbus::{self, AccessType, ErrorFlags, Flags, Message};
+
+pub const REQUEST_FLOOD: usize = 500; // Default unreplied Modbus requests are considered a flood
+pub const MODBUS_PARSER: sawp_modbus::Modbus = sawp_modbus::Modbus {};
+
+static mut ALPROTO_MODBUS: AppProto = ALPROTO_UNKNOWN;
+
+enum ModbusEvent {
+ UnsolicitedResponse = 0,
+ InvalidFunctionCode,
+ InvalidLength,
+ InvalidValue,
+ InvalidExceptionCode,
+ ValueMismatch,
+ Flooded,
+ InvalidProtocolId,
+}
+
+impl FromStr for ModbusEvent {
+ type Err = ();
+ fn from_str(name: &str) -> Result<ModbusEvent, Self::Err> {
+ match name {
+ "unsolicited_response" => Ok(ModbusEvent::UnsolicitedResponse),
+ "invalid_function_code" => Ok(ModbusEvent::InvalidFunctionCode),
+ "invalid_length" => Ok(ModbusEvent::InvalidLength),
+ "invalid_value" => Ok(ModbusEvent::InvalidValue),
+ "invalid_exception_code" => Ok(ModbusEvent::InvalidExceptionCode),
+ "value_mismatch" => Ok(ModbusEvent::ValueMismatch),
+ "flooded" => Ok(ModbusEvent::Flooded),
+ "invalid_protocol_id" => Ok(ModbusEvent::InvalidProtocolId),
+ _ => Err(()),
+ }
+ }
+}
+
+impl ModbusEvent {
+ pub fn to_str(&self) -> &str {
+ match *self {
+ ModbusEvent::UnsolicitedResponse => "unsolicited_response",
+ ModbusEvent::InvalidFunctionCode => "invalid_function_code",
+ ModbusEvent::InvalidLength => "invalid_length",
+ ModbusEvent::InvalidValue => "invalid_value",
+ ModbusEvent::InvalidExceptionCode => "invalid_exception_code",
+ ModbusEvent::ValueMismatch => "value_mismatch",
+ ModbusEvent::Flooded => "flooded",
+ ModbusEvent::InvalidProtocolId => "invalid_protocol_id",
+ }
+ }
+
+ pub fn from_id(id: u32) -> Option<Self> {
+ match id {
+ 0 => Some(ModbusEvent::UnsolicitedResponse),
+ 1 => Some(ModbusEvent::InvalidFunctionCode),
+ 2 => Some(ModbusEvent::InvalidLength),
+ 3 => Some(ModbusEvent::InvalidValue),
+ 4 => Some(ModbusEvent::InvalidExceptionCode),
+ 5 => Some(ModbusEvent::ValueMismatch),
+ 6 => Some(ModbusEvent::Flooded),
+ 7 => Some(ModbusEvent::InvalidProtocolId),
+ _ => None,
+ }
+ }
+}
+
+pub struct ModbusTransaction {
+ pub id: u64,
+
+ pub request: Option<Message>,
+ pub response: Option<Message>,
+
+ pub events: *mut core::AppLayerDecoderEvents,
+ pub de_state: Option<*mut core::DetectEngineState>,
+ pub tx_data: AppLayerTxData,
+}
+
+impl ModbusTransaction {
+ pub fn new(id: u64) -> Self {
+ Self {
+ id,
+ request: None,
+ response: None,
+ events: std::ptr::null_mut(),
+ de_state: None,
+ tx_data: AppLayerTxData::new(),
+ }
+ }
+
+ fn set_event(&mut self, event: ModbusEvent) {
+ core::sc_app_layer_decoder_events_set_event_raw(&mut self.events, event as u8);
+ }
+
+ fn set_events_from_flags(&mut self, flags: &Flags<ErrorFlags>) {
+ if flags.intersects(ErrorFlags::FUNC_CODE) {
+ self.set_event(ModbusEvent::InvalidFunctionCode);
+ }
+ if flags.intersects(ErrorFlags::DATA_VALUE) {
+ self.set_event(ModbusEvent::InvalidValue);
+ }
+ if flags.intersects(ErrorFlags::DATA_LENGTH) {
+ self.set_event(ModbusEvent::InvalidLength);
+ }
+ if flags.intersects(ErrorFlags::EXC_CODE) {
+ self.set_event(ModbusEvent::InvalidExceptionCode);
+ }
+ if flags.intersects(ErrorFlags::PROTO_ID) {
+ self.set_event(ModbusEvent::InvalidProtocolId);
+ }
+ }
+}
+
+impl Drop for ModbusTransaction {
+ fn drop(&mut self) {
+ if !self.events.is_null() {
+ core::sc_app_layer_decoder_events_free_events(&mut self.events);
+ }
+
+ if let Some(state) = self.de_state {
+ core::sc_detect_engine_state_free(state);
+ }
+ }
+}
+
+pub struct ModbusState {
+ pub transactions: Vec<ModbusTransaction>,
+ tx_id: u64,
+ givenup: bool, // Indicates flood
+}
+
+impl ModbusState {
+ pub fn new() -> Self {
+ Self {
+ transactions: Vec::new(),
+ tx_id: 0,
+ givenup: false,
+ }
+ }
+
+ pub fn get_tx(&mut self, tx_id: u64) -> Option<&mut ModbusTransaction> {
+ for tx in &mut self.transactions {
+ if tx.id == tx_id + 1 {
+ return Some(tx);
+ }
+ }
+ None
+ }
+
+ /// Searches the requests in order to find one matching the given response. Returns the matching
+ /// transaction, if it exists
+ pub fn find_request_and_validate(
+ &mut self, resp: &mut Message,
+ ) -> Option<&mut ModbusTransaction> {
+ for tx in &mut self.transactions {
+ if let Some(req) = &tx.request {
+ if tx.response.is_none() && resp.matches(req) {
+ return Some(tx);
+ }
+ }
+ }
+ None
+ }
+
+ /// Searches the responses in order to find one matching the given request. Returns the matching
+ /// transaction, if it exists
+ pub fn find_response_and_validate(
+ &mut self, req: &mut Message,
+ ) -> Option<&mut ModbusTransaction> {
+ for tx in &mut self.transactions {
+ if let Some(resp) = &tx.response {
+ if tx.request.is_none() && req.matches(resp) {
+ return Some(tx);
+ }
+ }
+ }
+ None
+ }
+
+ pub fn new_tx(&mut self) -> Option<ModbusTransaction> {
+ // Check flood limit
+ if self.givenup {
+ return None;
+ }
+
+ self.tx_id += 1;
+ let mut tx = ModbusTransaction::new(self.tx_id);
+
+ if REQUEST_FLOOD != 0 && self.transactions.len() >= REQUEST_FLOOD {
+ tx.set_event(ModbusEvent::Flooded);
+ self.givenup = true;
+ }
+
+ Some(tx)
+ }
+
+ pub fn free_tx(&mut self, tx_id: u64) {
+ if let Some(index) = self.transactions.iter().position(|tx| tx.id == tx_id + 1) {
+ self.transactions.remove(index);
+
+ // Check flood limit
+ if self.givenup && REQUEST_FLOOD != 0 && self.transactions.len() < REQUEST_FLOOD {
+ self.givenup = false;
+ }
+ }
+ }
+
+ pub fn parse(&mut self, input: &[u8], direction: Direction) -> AppLayerResult {
+ let mut rest = input;
+ while rest.len() > 0 {
+ match MODBUS_PARSER.parse(rest, direction.clone()) {
+ Ok((inner_rest, Some(mut msg))) => {
+ match direction {
+ Direction::ToServer | Direction::Unknown => {
+ match self.find_response_and_validate(&mut msg) {
+ Some(tx) => {
+ tx.set_events_from_flags(&msg.error_flags);
+ tx.request = Some(msg);
+ }
+ None => {
+ let mut tx = match self.new_tx() {
+ Some(tx) => tx,
+ None => return AppLayerResult::ok(),
+ };
+ tx.set_events_from_flags(&msg.error_flags);
+ tx.request = Some(msg);
+ self.transactions.push(tx);
+ }
+ }
+ }
+ Direction::ToClient => match self.find_request_and_validate(&mut msg) {
+ Some(tx) => {
+ if msg
+ .access_type
+ .intersects(AccessType::READ | AccessType::WRITE)
+ && msg.error_flags.intersects(
+ ErrorFlags::DATA_LENGTH | ErrorFlags::DATA_VALUE,
+ )
+ {
+ tx.set_event(ModbusEvent::ValueMismatch);
+ } else {
+ tx.set_events_from_flags(&msg.error_flags);
+ }
+ tx.response = Some(msg);
+ }
+ None => {
+ let mut tx = match self.new_tx() {
+ Some(tx) => tx,
+ None => return AppLayerResult::ok(),
+ };
+ if msg
+ .access_type
+ .intersects(AccessType::READ | AccessType::WRITE)
+ && msg.error_flags.intersects(
+ ErrorFlags::DATA_LENGTH | ErrorFlags::DATA_VALUE,
+ )
+ {
+ tx.set_event(ModbusEvent::ValueMismatch);
+ } else {
+ tx.set_events_from_flags(&msg.error_flags);
+ }
+ tx.response = Some(msg);
+ tx.set_event(ModbusEvent::UnsolicitedResponse);
+ self.transactions.push(tx);
+ }
+ },
+ }
+
+ if inner_rest.len() >= rest.len() {
+ return AppLayerResult::err();
+ }
+ rest = inner_rest;
+ }
+ Ok((inner_rest, None)) => {
+ return AppLayerResult::incomplete(
+ (input.len() - inner_rest.len()) as u32,
+ inner_rest.len() as u32 + 1,
+ );
+ }
+ Err(SawpError {
+ kind: SawpErrorKind::Incomplete(sawp::error::Needed::Size(needed)),
+ }) => {
+ return AppLayerResult::incomplete(
+ (input.len() - rest.len()) as u32,
+ (rest.len() + needed.get()) as u32,
+ );
+ }
+ Err(SawpError {
+ kind: SawpErrorKind::Incomplete(sawp::error::Needed::Unknown),
+ }) => {
+ return AppLayerResult::incomplete(
+ (input.len() - rest.len()) as u32,
+ rest.len() as u32 + 1,
+ );
+ }
+ Err(_) => return AppLayerResult::err(),
+ }
+ }
+ AppLayerResult::ok()
+ }
+}
+
+/// Probe input to see if it looks like Modbus.
+#[no_mangle]
+pub extern "C" fn rs_modbus_probe(
+ _flow: *const core::Flow, _direction: u8, input: *const u8, len: u32, _rdir: *mut u8,
+) -> AppProto {
+ let slice: &[u8] = unsafe { std::slice::from_raw_parts(input as *mut u8, len as usize) };
+ match MODBUS_PARSER.probe(slice, Direction::Unknown) {
+ Status::Recognized => unsafe { ALPROTO_MODBUS },
+ Status::Incomplete => ALPROTO_UNKNOWN,
+ Status::Unrecognized => unsafe { ALPROTO_FAILED },
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn rs_modbus_state_new(
+ _orig_state: *mut std::os::raw::c_void, _orig_proto: AppProto,
+) -> *mut std::os::raw::c_void {
+ Box::into_raw(Box::new(ModbusState::new())) as *mut std::os::raw::c_void
+}
+
+#[no_mangle]
+pub extern "C" fn rs_modbus_state_free(state: *mut std::os::raw::c_void) {
+ let _state: Box<ModbusState> = unsafe { Box::from_raw(state as *mut ModbusState) };
+}
+
+#[no_mangle]
+pub extern "C" fn rs_modbus_state_tx_free(state: *mut std::os::raw::c_void, tx_id: u64) {
+ let state = cast_pointer!(state, ModbusState);
+ state.free_tx(tx_id);
+}
+
+#[no_mangle]
+pub extern "C" fn rs_modbus_parse_request(
+ _flow: *const core::Flow, state: *mut std::os::raw::c_void, pstate: *mut std::os::raw::c_void,
+ input: *const u8, input_len: u32, _data: *const std::os::raw::c_void, _flags: u8,
+) -> AppLayerResult {
+ if input_len == 0 {
+ if unsafe { AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TS) } > 0 {
+ return AppLayerResult::ok();
+ } else {
+ return AppLayerResult::err();
+ }
+ }
+
+ let state = cast_pointer!(state, ModbusState);
+ let buf = unsafe { std::slice::from_raw_parts(input, input_len as usize) };
+
+ state.parse(buf, Direction::ToServer)
+}
+
+#[no_mangle]
+pub extern "C" fn rs_modbus_parse_response(
+ _flow: *const core::Flow, state: *mut std::os::raw::c_void, pstate: *mut std::os::raw::c_void,
+ input: *const u8, input_len: u32, _data: *const std::os::raw::c_void, _flags: u8,
+) -> AppLayerResult {
+ if input_len == 0 {
+ unsafe {
+ if AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TC) > 0 {
+ return AppLayerResult::ok();
+ } else {
+ return AppLayerResult::err();
+ }
+ }
+ }
+
+ let state = cast_pointer!(state, ModbusState);
+ let buf = unsafe { std::slice::from_raw_parts(input, input_len as usize) };
+
+ state.parse(buf, Direction::ToClient)
+}
+
+#[no_mangle]
+pub extern "C" fn rs_modbus_state_get_tx_count(state: *mut std::os::raw::c_void) -> u64 {
+ let state = cast_pointer!(state, ModbusState);
+ state.tx_id
+}
+
+#[no_mangle]
+pub extern "C" fn rs_modbus_state_get_tx(
+ state: *mut std::os::raw::c_void, tx_id: u64,
+) -> *mut std::os::raw::c_void {
+ let state = cast_pointer!(state, ModbusState);
+ match state.get_tx(tx_id) {
+ Some(tx) => (tx as *mut ModbusTransaction) as *mut std::os::raw::c_void,
+ None => std::ptr::null_mut(),
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn rs_modbus_tx_get_alstate_progress(
+ tx: *mut std::os::raw::c_void, _direction: u8,
+) -> std::os::raw::c_int {
+ let tx = cast_pointer!(tx, ModbusTransaction);
+ tx.response.is_some() as std::os::raw::c_int
+}
+
+#[no_mangle]
+pub extern "C" fn rs_modbus_state_get_events(
+ tx: *mut std::os::raw::c_void,
+) -> *mut core::AppLayerDecoderEvents {
+ let tx = cast_pointer!(tx, ModbusTransaction);
+ tx.events
+}
+
+#[no_mangle]
+pub extern "C" fn rs_modbus_state_get_event_info(
+ event_name: *const std::os::raw::c_char, event_id: *mut std::os::raw::c_int,
+ event_type: *mut core::AppLayerEventType,
+) -> std::os::raw::c_int {
+ if event_name.is_null() {
+ return -1;
+ }
+
+ let event_name = unsafe { std::ffi::CStr::from_ptr(event_name) };
+ if let Ok(event_name) = event_name.to_str() {
+ match ModbusEvent::from_str(event_name) {
+ Ok(event) => unsafe {
+ *event_id = event as std::os::raw::c_int;
+ *event_type = core::APP_LAYER_EVENT_TYPE_TRANSACTION;
+ 0
+ },
+ Err(_) => {
+ SCLogError!(
+ "event {} not present in modbus's enum map table.",
+ event_name
+ );
+ -1
+ }
+ }
+ } else {
+ -1
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn rs_modbus_state_get_event_info_by_id(
+ event_id: std::os::raw::c_int, event_name: *mut *const std::os::raw::c_char,
+ event_type: *mut core::AppLayerEventType,
+) -> i8 {
+ if let Some(e) = ModbusEvent::from_id(event_id as u32) {
+ unsafe {
+ *event_name = e.to_str().as_ptr() as *const std::os::raw::c_char;
+ *event_type = core::APP_LAYER_EVENT_TYPE_TRANSACTION;
+ }
+ 0
+ } else {
+ SCLogError!("event {} not present in modbus's enum map table.", event_id);
+ -1
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn rs_modbus_state_get_tx_detect_state(
+ tx: *mut std::os::raw::c_void,
+) -> *mut core::DetectEngineState {
+ let tx = cast_pointer!(tx, ModbusTransaction);
+ match tx.de_state {
+ Some(ds) => ds,
+ None => std::ptr::null_mut(),
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn rs_modbus_state_set_tx_detect_state(
+ tx: *mut std::os::raw::c_void, de_state: &mut core::DetectEngineState,
+) -> std::os::raw::c_int {
+ let tx = cast_pointer!(tx, ModbusTransaction);
+ tx.de_state = Some(de_state);
+ 0
+}
+
+#[no_mangle]
+pub extern "C" fn rs_modbus_state_get_tx_data(
+ tx: *mut std::os::raw::c_void,
+) -> *mut AppLayerTxData {
+ let tx = cast_pointer!(tx, ModbusTransaction);
+ &mut tx.tx_data
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_modbus_register_parser() {
+ let default_port = std::ffi::CString::new("[502]").unwrap();
+ let parser = RustParser {
+ name: b"modbus\0".as_ptr() as *const std::os::raw::c_char,
+ default_port: default_port.as_ptr(),
+ ipproto: IPPROTO_TCP,
+ probe_ts: Some(rs_modbus_probe),
+ probe_tc: Some(rs_modbus_probe),
+ min_depth: 0,
+ max_depth: 16,
+ state_new: rs_modbus_state_new,
+ state_free: rs_modbus_state_free,
+ tx_free: rs_modbus_state_tx_free,
+ parse_ts: rs_modbus_parse_request,
+ parse_tc: rs_modbus_parse_response,
+ get_tx_count: rs_modbus_state_get_tx_count,
+ get_tx: rs_modbus_state_get_tx,
+ tx_comp_st_ts: 1,
+ tx_comp_st_tc: 1,
+ tx_get_progress: rs_modbus_tx_get_alstate_progress,
+ get_events: Some(rs_modbus_state_get_events),
+ get_eventinfo: Some(rs_modbus_state_get_event_info),
+ get_eventinfo_byid: Some(rs_modbus_state_get_event_info_by_id),
+ localstorage_new: None,
+ localstorage_free: None,
+ get_files: None,
+ get_tx_iterator: None,
+ get_de_state: rs_modbus_state_get_tx_detect_state,
+ set_de_state: rs_modbus_state_set_tx_detect_state,
+ get_tx_data: rs_modbus_state_get_tx_data,
+ apply_tx_config: None,
+ flags: APP_LAYER_PARSER_OPT_ACCEPT_GAPS,
+ truncate: None,
+ };
+
+ let ip_proto_str = CString::new("tcp").unwrap();
+ if AppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 {
+ let alproto = AppLayerRegisterProtocolDetection(&parser, 1);
+ ALPROTO_MODBUS = alproto;
+ if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 {
+ let _ = AppLayerRegisterParser(&parser, alproto);
+ }
+ }
+}
+
+// This struct and accessor functions are used for app-layer-modbus.c tests.
+pub mod test {
+ use super::ModbusState;
+ use sawp_modbus::{Data, Message, Read, Write};
+ use std::ffi::c_void;
+ #[repr(C)]
+ pub struct ModbusMessage(*const c_void);
+
+ #[no_mangle]
+ pub unsafe extern "C" fn rs_modbus_message_get_function(msg: *const ModbusMessage) -> u8 {
+ let msg = msg.as_ref().unwrap().0 as *const Message;
+ let msg = msg.as_ref().unwrap();
+ msg.function.raw
+ }
+
+ #[no_mangle]
+ pub unsafe extern "C" fn rs_modbus_message_get_subfunction(msg: *const ModbusMessage) -> u16 {
+ let msg = msg.as_ref().unwrap().0 as *const Message;
+ let msg = msg.as_ref().unwrap();
+ if let Data::Diagnostic { func, data: _ } = &msg.data {
+ func.raw
+ } else {
+ panic!("wrong modbus message data type");
+ }
+ }
+
+ #[no_mangle]
+ pub unsafe extern "C" fn rs_modbus_message_get_read_request_address(
+ msg: *const ModbusMessage,
+ ) -> u16 {
+ let msg = msg.as_ref().unwrap().0 as *const Message;
+ let msg = msg.as_ref().unwrap();
+ if let Data::Read(Read::Request {
+ address,
+ quantity: _,
+ }) = &msg.data
+ {
+ *address
+ } else {
+ panic!("wrong modbus message data type");
+ }
+ }
+
+ #[no_mangle]
+ pub unsafe extern "C" fn rs_modbus_message_get_read_request_quantity(
+ msg: *const ModbusMessage,
+ ) -> u16 {
+ let msg = msg.as_ref().unwrap().0 as *const Message;
+ let msg = msg.as_ref().unwrap();
+ if let Data::Read(Read::Request {
+ address: _,
+ quantity,
+ }) = &msg.data
+ {
+ *quantity
+ } else {
+ panic!("wrong modbus message data type");
+ }
+ }
+
+ #[no_mangle]
+ pub unsafe extern "C" fn rs_modbus_message_get_rw_multreq_read_address(
+ msg: *const ModbusMessage,
+ ) -> u16 {
+ let msg = msg.as_ref().unwrap().0 as *const Message;
+ let msg = msg.as_ref().unwrap();
+ if let Data::ReadWrite {
+ read:
+ Read::Request {
+ address,
+ quantity: _,
+ },
+ write: _,
+ } = &msg.data
+ {
+ *address
+ } else {
+ panic!("wrong modbus message data type");
+ }
+ }
+
+ #[no_mangle]
+ pub unsafe extern "C" fn rs_modbus_message_get_rw_multreq_read_quantity(
+ msg: *const ModbusMessage,
+ ) -> u16 {
+ let msg = msg.as_ref().unwrap().0 as *const Message;
+ let msg = msg.as_ref().unwrap();
+ if let Data::ReadWrite {
+ read:
+ Read::Request {
+ address: _,
+ quantity,
+ },
+ write: _,
+ } = &msg.data
+ {
+ *quantity
+ } else {
+ panic!("wrong modbus message data type");
+ }
+ }
+
+ #[no_mangle]
+ pub unsafe extern "C" fn rs_modbus_message_get_rw_multreq_write_address(
+ msg: *const ModbusMessage,
+ ) -> u16 {
+ let msg = msg.as_ref().unwrap().0 as *const Message;
+ let msg = msg.as_ref().unwrap();
+ if let Data::ReadWrite {
+ read: _,
+ write:
+ Write::MultReq {
+ address,
+ quantity: _,
+ data: _,
+ },
+ } = &msg.data
+ {
+ *address
+ } else {
+ panic!("wrong modbus message data type");
+ }
+ }
+
+ #[no_mangle]
+ pub unsafe extern "C" fn rs_modbus_message_get_rw_multreq_write_quantity(
+ msg: *const ModbusMessage,
+ ) -> u16 {
+ let msg = msg.as_ref().unwrap().0 as *const Message;
+ let msg = msg.as_ref().unwrap();
+ if let Data::ReadWrite {
+ read: _,
+ write:
+ Write::MultReq {
+ address: _,
+ quantity,
+ data: _,
+ },
+ } = &msg.data
+ {
+ *quantity
+ } else {
+ panic!("wrong modbus message data type");
+ }
+ }
+
+ #[no_mangle]
+ pub unsafe extern "C" fn rs_modbus_message_get_rw_multreq_write_data(
+ msg: *const ModbusMessage, data_len: *mut usize,
+ ) -> *const u8 {
+ let msg = msg.as_ref().unwrap().0 as *const Message;
+ let msg = msg.as_ref().unwrap();
+ if let Data::ReadWrite {
+ read: _,
+ write:
+ Write::MultReq {
+ address: _,
+ quantity: _,
+ data,
+ },
+ } = &msg.data
+ {
+ *data_len = data.len();
+ data.as_slice().as_ptr()
+ } else {
+ panic!("wrong modbus message data type");
+ }
+ }
+
+ #[no_mangle]
+ pub unsafe extern "C" fn rs_modbus_message_get_write_multreq_address(
+ msg: *const ModbusMessage,
+ ) -> u16 {
+ let msg = msg.as_ref().unwrap().0 as *const Message;
+ let msg = msg.as_ref().unwrap();
+ if let Data::Write(Write::MultReq {
+ address,
+ quantity: _,
+ data: _,
+ }) = &msg.data
+ {
+ *address
+ } else {
+ panic!("wrong modbus message data type");
+ }
+ }
+
+ #[no_mangle]
+ pub unsafe extern "C" fn rs_modbus_message_get_write_multreq_quantity(
+ msg: *const ModbusMessage,
+ ) -> u16 {
+ let msg = msg.as_ref().unwrap().0 as *const Message;
+ let msg = msg.as_ref().unwrap();
+ if let Data::Write(Write::MultReq {
+ address: _,
+ quantity,
+ data: _,
+ }) = &msg.data
+ {
+ *quantity
+ } else {
+ panic!("wrong modbus message data type");
+ }
+ }
+
+ #[no_mangle]
+ pub unsafe extern "C" fn rs_modbus_message_get_write_multreq_data(
+ msg: *const ModbusMessage, data_len: *mut usize,
+ ) -> *const u8 {
+ let msg = msg.as_ref().unwrap().0 as *const Message;
+ let msg = msg.as_ref().unwrap();
+ if let Data::Write(Write::MultReq {
+ address: _,
+ quantity: _,
+ data,
+ }) = &msg.data
+ {
+ *data_len = data.len();
+ data.as_slice().as_ptr()
+ } else {
+ panic!("wrong modbus message data type");
+ }
+ }
+
+ #[no_mangle]
+ pub unsafe extern "C" fn rs_modbus_message_get_and_mask(msg: *const ModbusMessage) -> u16 {
+ let msg = msg.as_ref().unwrap().0 as *const Message;
+ let msg = msg.as_ref().unwrap();
+ if let Data::Write(Write::Mask {
+ address: _,
+ and_mask,
+ or_mask: _,
+ }) = &msg.data
+ {
+ *and_mask
+ } else {
+ panic!("wrong modbus message data type");
+ }
+ }
+
+ #[no_mangle]
+ pub unsafe extern "C" fn rs_modbus_message_get_or_mask(msg: *const ModbusMessage) -> u16 {
+ let msg = msg.as_ref().unwrap().0 as *const Message;
+ let msg = msg.as_ref().unwrap();
+ if let Data::Write(Write::Mask {
+ address: _,
+ and_mask: _,
+ or_mask,
+ }) = &msg.data
+ {
+ *or_mask
+ } else {
+ panic!("wrong modbus message data type");
+ }
+ }
+
+ #[no_mangle]
+ pub unsafe extern "C" fn rs_modbus_message_get_write_address(msg: *const ModbusMessage) -> u16 {
+ let msg = msg.as_ref().unwrap().0 as *const Message;
+ let msg = msg.as_ref().unwrap();
+ if let Data::Write(Write::Other { address, data: _ }) = &msg.data {
+ *address
+ } else {
+ panic!("wrong modbus message data type");
+ }
+ }
+
+ #[no_mangle]
+ pub unsafe extern "C" fn rs_modbus_message_get_write_data(msg: *const ModbusMessage) -> u16 {
+ let msg = msg.as_ref().unwrap().0 as *const Message;
+ let msg = msg.as_ref().unwrap();
+ if let Data::Write(Write::Other { address: _, data }) = &msg.data {
+ *data
+ } else {
+ panic!("wrong modbus message data type");
+ }
+ }
+
+ #[no_mangle]
+ pub unsafe extern "C" fn rs_modbus_message_get_bytevec_data(
+ msg: *const ModbusMessage, data_len: *mut usize,
+ ) -> *const u8 {
+ let msg = msg.as_ref().unwrap().0 as *const Message;
+ let msg = msg.as_ref().unwrap();
+ if let Data::ByteVec(data) = &msg.data {
+ *data_len = data.len();
+ data.as_slice().as_ptr()
+ } else {
+ panic!("wrong modbus message data type");
+ }
+ }
+
+ #[no_mangle]
+ pub extern "C" fn rs_modbus_state_get_tx_request(
+ state: *mut std::os::raw::c_void, tx_id: u64,
+ ) -> ModbusMessage {
+ let state = cast_pointer!(state, ModbusState);
+ if let Some(tx) = state.get_tx(tx_id) {
+ if let Some(request) = &tx.request {
+ ModbusMessage((request as *const Message) as *const c_void)
+ } else {
+ ModbusMessage(std::ptr::null())
+ }
+ } else {
+ ModbusMessage(std::ptr::null())
+ }
+ }
+
+ #[no_mangle]
+ pub extern "C" fn rs_modbus_state_get_tx_response(
+ state: *mut std::os::raw::c_void, tx_id: u64,
+ ) -> ModbusMessage {
+ let state = cast_pointer!(state, ModbusState);
+ if let Some(tx) = state.get_tx(tx_id) {
+ if let Some(response) = &tx.response {
+ ModbusMessage((response as *const Message) as *const c_void)
+ } else {
+ ModbusMessage(std::ptr::null())
+ }
+ } else {
+ ModbusMessage(std::ptr::null())
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use sawp_modbus::{
+ Data, Diagnostic, DiagnosticSubfunction, Exception, ExceptionCode, FunctionCode, Read,
+ Write,
+ };
+
+ const INVALID_FUNC_CODE: &[u8] = &[
+ 0x00, 0x00, // Transaction ID
+ 0x00, 0x00, // Protocol ID
+ 0x00, 0x02, // Length
+ 0x00, // Unit ID
+ 0x00, // Function code
+ ];
+
+ const RD_COILS_REQ: &[u8] = &[
+ 0x00, 0x00, // Transaction ID
+ 0x00, 0x00, // Protocol ID
+ 0x00, 0x06, // Length
+ 0x00, // Unit ID
+ 0x01, // Function code
+ 0x78, 0x90, // Starting Address
+ 0x00, 0x13, // Quantity of coils
+ ];
+
+ const RD_COILS_RESP: &[u8] = &[
+ 0x00, 0x00, // Transaction ID
+ 0x00, 0x00, // Protocol ID
+ 0x00, 0x06, // Length
+ 0x00, // Unit ID
+ 0x01, // Function code
+ 0x03, // Byte count
+ 0xCD, 0x6B, 0x05, // Coil Status
+ ];
+
+ const RD_COILS_ERR_RESP: &[u8] = &[
+ 0x00, 0x00, // Transaction ID
+ 0x00, 0x00, // Protocol ID
+ 0x00, 0x03, // Length
+ 0x00, // Unit ID
+ 0x81, // Function code
+ 0xFF, // Exception code
+ ];
+
+ const WR_SINGLE_REG_REQ: &[u8] = &[
+ 0x00, 0x0A, // Transaction ID
+ 0x00, 0x00, // Protocol ID
+ 0x00, 0x06, // Length
+ 0x00, // Unit ID
+ 0x06, // Function code
+ 0x00, 0x01, // Register Address
+ 0x00, 0x03, // Register Value
+ ];
+
+ const INVALID_WR_SINGLE_REG_REQ: &[u8] = &[
+ 0x00, 0x0A, // Transaction ID
+ 0x00, 0x00, // Protocol ID
+ 0x00, 0x04, // Length
+ 0x00, // Unit ID
+ 0x06, // Function code
+ 0x00, 0x01, // Register Address
+ ];
+
+ const WR_SINGLE_REG_RESP: &[u8] = &[
+ 0x00, 0x0A, // Transaction ID
+ 0x00, 0x00, // Protocol ID
+ 0x00, 0x06, // Length
+ 0x00, // Unit ID
+ 0x06, // Function code
+ 0x00, 0x01, // Register Address
+ 0x00, 0x03, // Register Value
+ ];
+
+ const WR_MULT_REG_REQ: &[u8] = &[
+ 0x00, 0x0A, // Transaction ID
+ 0x00, 0x00, // Protocol ID
+ 0x00, 0x0B, // Length
+ 0x00, // Unit ID
+ 0x10, // Function code
+ 0x00, 0x01, // Starting Address
+ 0x00, 0x02, // Quantity of Registers
+ 0x04, // Byte count
+ 0x00, 0x0A, // Registers Value
+ 0x01, 0x02,
+ ];
+
+ const INVALID_PDU_WR_MULT_REG_REQ: &[u8] = &[
+ 0x00, 0x0A, // Transaction ID
+ 0x00, 0x00, // Protocol ID
+ 0x00, 0x02, // Length
+ 0x00, // Unit ID
+ 0x10, // Function code
+ ];
+
+ const WR_MULT_REG_RESP: &[u8] = &[
+ 0x00, 0x0A, // Transaction ID
+ 0x00, 0x00, // Protocol ID
+ 0x00, 0x06, // Length
+ 0x00, // Unit ID
+ 0x10, // Function code
+ 0x00, 0x01, // Starting Address
+ 0x00, 0x02, // Quantity of Registers
+ ];
+
+ const MASK_WR_REG_REQ: &[u8] = &[
+ 0x00, 0x0A, // Transaction ID
+ 0x00, 0x00, // Protocol ID
+ 0x00, 0x08, // Length
+ 0x00, // Unit ID
+ 0x16, // Function code
+ 0x00, 0x04, // Reference Address
+ 0x00, 0xF2, // And_Mask
+ 0x00, 0x25, // Or_Mask
+ ];
+
+ const INVALID_MASK_WR_REG_REQ: &[u8] = &[
+ 0x00, 0x0A, // Transaction ID
+ 0x00, 0x00, // Protocol ID
+ 0x00, 0x06, // Length
+ 0x00, // Unit ID
+ 0x16, // Function code
+ 0x00, 0x04, // Reference Address
+ 0x00, 0xF2, // And_Mask
+ ];
+
+ const MASK_WR_REG_RESP: &[u8] = &[
+ 0x00, 0x0A, // Transaction ID
+ 0x00, 0x00, // Protocol ID
+ 0x00, 0x08, // Length
+ 0x00, // Unit ID
+ 0x16, // Function code
+ 0x00, 0x04, // Reference Address
+ 0x00, 0xF2, // And_Mask
+ 0x00, 0x25, // Or_Mask
+ ];
+
+ const RD_WR_MULT_REG_REQ: &[u8] = &[
+ 0x12, 0x34, // Transaction ID
+ 0x00, 0x00, // Protocol ID
+ 0x00, 0x11, // Length
+ 0x00, // Unit ID
+ 0x17, // Function code
+ 0x00, 0x03, // Read Starting Address
+ 0x00, 0x06, // Quantity to Read
+ 0x00, 0x0E, // Write Starting Address
+ 0x00, 0x03, // Quantity to Write
+ 0x06, // Write Byte count
+ 0x12, 0x34, // Write Registers Value
+ 0x56, 0x78, 0x9A, 0xBC,
+ ];
+
+ // Mismatch value in Byte count 0x0B instead of 0x0C
+ const RD_WR_MULT_REG_RESP: &[u8] = &[
+ 0x12, 0x34, // Transaction ID
+ 0x00, 0x00, // Protocol ID
+ 0x00, 0x0E, // Length
+ 0x00, // Unit ID
+ 0x17, // Function code
+ 0x0B, // Byte count
+ 0x00, 0xFE, // Read Registers Value
+ 0x0A, 0xCD, 0x00, 0x01, 0x00, 0x03, 0x00, 0x0D, 0x00,
+ ];
+
+ const FORCE_LISTEN_ONLY_MODE: &[u8] = &[
+ 0x0A, 0x00, // Transaction ID
+ 0x00, 0x00, // Protocol ID
+ 0x00, 0x06, // Length
+ 0x00, // Unit ID
+ 0x08, // Function code
+ 0x00, 0x04, // Sub-function code
+ 0x00, 0x00, // Data
+ ];
+
+ const INVALID_PROTO_REQ: &[u8] = &[
+ 0x00, 0x00, // Transaction ID
+ 0x00, 0x01, // Protocol ID
+ 0x00, 0x06, // Length
+ 0x00, // Unit ID
+ 0x01, // Function code
+ 0x78, 0x90, // Starting Address
+ 0x00, 0x13, // Quantity of coils
+ ];
+
+ const INVALID_LEN_WR_MULT_REG_REQ: &[u8] = &[
+ 0x00, 0x0A, // Transaction ID
+ 0x00, 0x00, // Protocol ID
+ 0x00, 0x09, // Length
+ 0x00, // Unit ID
+ 0x10, // Function code
+ 0x00, 0x01, // Starting Address
+ 0x00, 0x02, // Quantity of Registers
+ 0x04, // Byte count
+ 0x00, 0x0A, // Registers Value
+ 0x01, 0x02,
+ ];
+
+ const EXCEEDED_LEN_WR_MULT_REG_REQ: &[u8] = &[
+ 0x00, 0x0A, // Transaction ID
+ 0x00, 0x00, // Protocol ID
+ 0xff, 0xfa, // Length
+ 0x00, // Unit ID
+ 0x10, // Function code
+ 0x00, 0x01, // Starting Address
+ 0x7f, 0xf9, // Quantity of Registers
+ 0xff, // Byte count
+ ];
+
+ #[test]
+ fn read_coils() {
+ let mut state = ModbusState::new();
+ assert_eq!(
+ AppLayerResult::ok(),
+ state.parse(&RD_COILS_REQ, Direction::ToServer)
+ );
+ assert_eq!(state.transactions.len(), 1);
+
+ let tx = &state.transactions[0];
+ let msg = tx.request.as_ref().unwrap();
+ assert_eq!(msg.function.code, FunctionCode::RdCoils);
+ assert_eq!(
+ msg.data,
+ Data::Read(Read::Request {
+ address: 0x7890,
+ quantity: 0x0013
+ })
+ );
+
+ assert_eq!(
+ AppLayerResult::ok(),
+ state.parse(&RD_COILS_RESP, Direction::ToClient)
+ );
+ assert_eq!(state.transactions.len(), 1);
+
+ let tx = &state.transactions[0];
+ let msg = tx.response.as_ref().unwrap();
+ assert_eq!(msg.function.code, FunctionCode::RdCoils);
+ assert_eq!(msg.data, Data::Read(Read::Response(vec![0xCD, 0x6B, 0x05])));
+ }
+
+ #[test]
+ fn write_multiple_registers() {
+ let mut state = ModbusState::new();
+ assert_eq!(
+ AppLayerResult::ok(),
+ state.parse(&WR_MULT_REG_REQ, Direction::ToServer)
+ );
+ assert_eq!(state.transactions.len(), 1);
+
+ let tx = &state.transactions[0];
+ let msg = tx.request.as_ref().unwrap();
+ assert_eq!(msg.function.code, FunctionCode::WrMultRegs);
+ assert_eq!(
+ msg.data,
+ Data::Write(Write::MultReq {
+ address: 0x0001,
+ quantity: 0x0002,
+ data: vec![0x00, 0x0a, 0x01, 0x02],
+ })
+ );
+
+ assert_eq!(
+ AppLayerResult::ok(),
+ state.parse(&WR_MULT_REG_RESP, Direction::ToClient)
+ );
+ assert_eq!(state.transactions.len(), 1);
+
+ let tx = &state.transactions[0];
+ let msg = tx.response.as_ref().unwrap();
+ assert_eq!(msg.function.code, FunctionCode::WrMultRegs);
+ assert_eq!(
+ msg.data,
+ Data::Write(Write::Other {
+ address: 0x0001,
+ data: 0x0002
+ })
+ );
+ }
+
+ #[test]
+ fn read_write_multiple_registers() {
+ let mut state = ModbusState::new();
+ assert_eq!(
+ AppLayerResult::ok(),
+ state.parse(&RD_WR_MULT_REG_REQ, Direction::ToServer)
+ );
+ assert_eq!(state.transactions.len(), 1);
+
+ let tx = &state.transactions[0];
+ let msg = tx.request.as_ref().unwrap();
+ assert_eq!(msg.function.code, FunctionCode::RdWrMultRegs);
+ assert_eq!(
+ msg.data,
+ Data::ReadWrite {
+ read: Read::Request {
+ address: 0x0003,
+ quantity: 0x0006,
+ },
+ write: Write::MultReq {
+ address: 0x000e,
+ quantity: 0x0003,
+ data: vec![0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc]
+ }
+ }
+ );
+
+ assert_eq!(
+ AppLayerResult::ok(),
+ state.parse(&RD_WR_MULT_REG_RESP, Direction::ToClient)
+ );
+ assert_eq!(state.transactions.len(), 1);
+
+ let tx = &state.transactions[0];
+ let msg = tx.response.as_ref().unwrap();
+ assert_eq!(msg.function.code, FunctionCode::RdWrMultRegs);
+ assert_eq!(
+ msg.data,
+ Data::Read(Read::Response(vec![
+ 0x00, 0xFE, 0x0A, 0xCD, 0x00, 0x01, 0x00, 0x03, 0x00, 0x0D, 0x00,
+ ]))
+ );
+ }
+
+ #[test]
+ fn force_listen_only_mode() {
+ let mut state = ModbusState::new();
+ assert_eq!(
+ AppLayerResult::ok(),
+ state.parse(&FORCE_LISTEN_ONLY_MODE, Direction::ToServer)
+ );
+ assert_eq!(state.transactions.len(), 1);
+
+ let tx = &state.transactions[0];
+ let msg = tx.request.as_ref().unwrap();
+ assert_eq!(msg.function.code, FunctionCode::Diagnostic);
+ assert_eq!(
+ msg.data,
+ Data::Diagnostic {
+ func: Diagnostic {
+ raw: 4,
+ code: DiagnosticSubfunction::ForceListenOnlyMode
+ },
+ data: vec![0x00, 0x00]
+ }
+ );
+ }
+
+ #[test]
+ fn invalid_protocol_version() {
+ let mut state = ModbusState::new();
+ assert_eq!(
+ AppLayerResult::ok(),
+ state.parse(&INVALID_PROTO_REQ, Direction::ToServer)
+ );
+
+ assert_eq!(state.transactions.len(), 1);
+ let tx = &state.transactions[0];
+ let msg = tx.request.as_ref().unwrap();
+ assert_eq!(msg.error_flags, ErrorFlags::PROTO_ID);
+ }
+
+ #[test]
+ fn unsolicited_response() {
+ let mut state = ModbusState::new();
+ assert_eq!(
+ AppLayerResult::ok(),
+ state.parse(&RD_COILS_RESP, Direction::ToClient)
+ );
+ assert_eq!(state.transactions.len(), 1);
+
+ let tx = &state.transactions[0];
+ let msg = tx.response.as_ref().unwrap();
+ assert_eq!(msg.function.code, FunctionCode::RdCoils);
+ assert_eq!(msg.data, Data::Read(Read::Response(vec![0xCD, 0x6B, 0x05])));
+ }
+
+ #[test]
+ fn invalid_length_request() {
+ let mut state = ModbusState::new();
+ assert_eq!(
+ AppLayerResult::incomplete(15, 4),
+ state.parse(&INVALID_LEN_WR_MULT_REG_REQ, Direction::ToServer)
+ );
+ assert_eq!(state.transactions.len(), 1);
+
+ let tx = &state.transactions[0];
+ let msg = tx.request.as_ref().unwrap();
+ assert_eq!(msg.function.code, FunctionCode::WrMultRegs);
+ assert_eq!(
+ msg.data,
+ Data::Write(Write::MultReq {
+ address: 0x0001,
+ quantity: 0x0002,
+ data: vec![0x00, 0x0a]
+ })
+ );
+ assert_eq!(msg.error_flags, ErrorFlags::DATA_LENGTH);
+ }
+
+ #[test]
+ fn exception_code_invalid() {
+ let mut state = ModbusState::new();
+ assert_eq!(
+ AppLayerResult::ok(),
+ state.parse(&RD_COILS_REQ, Direction::ToServer)
+ );
+ assert_eq!(state.transactions.len(), 1);
+
+ let tx = &state.transactions[0];
+ let msg = tx.request.as_ref().unwrap();
+ assert_eq!(msg.function.code, FunctionCode::RdCoils);
+ assert_eq!(
+ msg.data,
+ Data::Read(Read::Request {
+ address: 0x7890,
+ quantity: 0x0013
+ })
+ );
+
+ assert_eq!(
+ AppLayerResult::ok(),
+ state.parse(&RD_COILS_ERR_RESP, Direction::ToClient)
+ );
+ assert_eq!(state.transactions.len(), 1);
+
+ let tx = &state.transactions[0];
+ let msg = tx.response.as_ref().unwrap();
+ assert_eq!(
+ msg.data,
+ Data::Exception(Exception {
+ raw: 255,
+ code: ExceptionCode::Unknown
+ })
+ );
+ assert_eq!(msg.error_flags, ErrorFlags::EXC_CODE);
+ }
+
+ #[test]
+ fn fragmentation_1_adu_in_2_tcp_packets() {
+ let mut state = ModbusState::new();
+ assert_eq!(
+ AppLayerResult::incomplete(0, 15),
+ state.parse(
+ &RD_COILS_REQ[0..(RD_COILS_REQ.len() - 3)],
+ Direction::ToServer
+ )
+ );
+ assert_eq!(state.transactions.len(), 0);
+ assert_eq!(
+ AppLayerResult::ok(),
+ state.parse(&RD_COILS_REQ, Direction::ToServer)
+ );
+ assert_eq!(state.transactions.len(), 1);
+
+ let tx = &state.transactions[0];
+ assert!(&tx.request.is_some());
+ let msg = tx.request.as_ref().unwrap();
+ assert_eq!(msg.function.code, FunctionCode::RdCoils);
+ assert_eq!(
+ msg.data,
+ Data::Read(Read::Request {
+ address: 0x7890,
+ quantity: 0x0013
+ })
+ );
+ }
+
+ #[test]
+ fn fragmentation_2_adu_in_1_tcp_packet() {
+ let req = [RD_COILS_REQ, WR_MULT_REG_REQ].concat();
+ let resp = [RD_COILS_RESP, WR_MULT_REG_RESP].concat();
+
+ let mut state = ModbusState::new();
+ assert_eq!(AppLayerResult::ok(), state.parse(&req, Direction::ToServer));
+ assert_eq!(state.transactions.len(), 2);
+
+ let tx = &state.transactions[0];
+ let msg = tx.request.as_ref().unwrap();
+ assert_eq!(msg.function.code, FunctionCode::RdCoils);
+ assert_eq!(
+ msg.data,
+ Data::Read(Read::Request {
+ address: 0x7890,
+ quantity: 0x0013
+ })
+ );
+
+ let tx = &state.transactions[1];
+ let msg = tx.request.as_ref().unwrap();
+ assert_eq!(msg.function.code, FunctionCode::WrMultRegs);
+ assert_eq!(
+ msg.data,
+ Data::Write(Write::MultReq {
+ address: 0x0001,
+ quantity: 0x0002,
+ data: vec![0x00, 0x0a, 0x01, 0x02]
+ })
+ );
+
+ assert_eq!(
+ AppLayerResult::ok(),
+ state.parse(&resp, Direction::ToClient)
+ );
+ assert_eq!(state.transactions.len(), 2);
+
+ let tx = &state.transactions[0];
+ let msg = tx.response.as_ref().unwrap();
+ assert_eq!(msg.function.code, FunctionCode::RdCoils);
+ assert_eq!(msg.data, Data::Read(Read::Response(vec![0xCD, 0x6B, 0x05])));
+
+ let tx = &state.transactions[1];
+ let msg = tx.response.as_ref().unwrap();
+ assert_eq!(msg.function.code, FunctionCode::WrMultRegs);
+ assert_eq!(
+ msg.data,
+ Data::Write(Write::Other {
+ address: 0x0001,
+ data: 0x0002
+ })
+ );
+ }
+
+ #[test]
+ fn exceeded_length_request() {
+ let mut state = ModbusState::new();
+ assert_eq!(
+ AppLayerResult::ok(),
+ state.parse(&EXCEEDED_LEN_WR_MULT_REG_REQ, Direction::ToServer)
+ );
+
+ assert_eq!(state.transactions.len(), 1);
+ let tx = &state.transactions[0];
+ let msg = tx.request.as_ref().unwrap();
+ assert_eq!(msg.error_flags, ErrorFlags::DATA_LENGTH);
+ }
+
+ #[test]
+ fn invalid_pdu_len_req() {
+ let mut state = ModbusState::new();
+ assert_eq!(
+ AppLayerResult::ok(),
+ state.parse(&INVALID_PDU_WR_MULT_REG_REQ, Direction::ToServer)
+ );
+
+ assert_eq!(state.transactions.len(), 1);
+
+ let tx = &state.transactions[0];
+ let msg = tx.request.as_ref().unwrap();
+ assert_eq!(msg.function.code, FunctionCode::WrMultRegs);
+ assert_eq!(msg.data, Data::ByteVec(vec![]));
+ }
+
+ #[test]
+ fn mask_write_register_request() {
+ let mut state = ModbusState::new();
+ assert_eq!(
+ AppLayerResult::ok(),
+ state.parse(&MASK_WR_REG_REQ, Direction::ToServer)
+ );
+ assert_eq!(state.transactions.len(), 1);
+
+ let tx = &state.transactions[0];
+ let msg = tx.request.as_ref().unwrap();
+ assert_eq!(msg.function.code, FunctionCode::MaskWrReg);
+ assert_eq!(
+ msg.data,
+ Data::Write(Write::Mask {
+ address: 0x0004,
+ and_mask: 0x00f2,
+ or_mask: 0x0025
+ })
+ );
+
+ assert_eq!(
+ AppLayerResult::ok(),
+ state.parse(&MASK_WR_REG_RESP, Direction::ToClient)
+ );
+ assert_eq!(state.transactions.len(), 1);
+
+ let tx = &state.transactions[0];
+ let msg = tx.response.as_ref().unwrap();
+ assert_eq!(msg.function.code, FunctionCode::MaskWrReg);
+ assert_eq!(
+ msg.data,
+ Data::Write(Write::Mask {
+ address: 0x0004,
+ and_mask: 0x00f2,
+ or_mask: 0x0025
+ })
+ );
+ }
+
+ #[test]
+ fn write_single_register_request() {
+ let mut state = ModbusState::new();
+ assert_eq!(
+ AppLayerResult::ok(),
+ state.parse(&WR_SINGLE_REG_REQ, Direction::ToServer)
+ );
+ assert_eq!(state.transactions.len(), 1);
+
+ let tx = &state.transactions[0];
+ let msg = tx.request.as_ref().unwrap();
+ assert_eq!(msg.function.code, FunctionCode::WrSingleReg);
+ assert_eq!(
+ msg.data,
+ Data::Write(Write::Other {
+ address: 0x0001,
+ data: 0x0003
+ })
+ );
+
+ assert_eq!(
+ AppLayerResult::ok(),
+ state.parse(&WR_SINGLE_REG_RESP, Direction::ToClient)
+ );
+ assert_eq!(state.transactions.len(), 1);
+
+ let tx = &state.transactions[0];
+ let msg = tx.response.as_ref().unwrap();
+ assert_eq!(msg.function.code, FunctionCode::WrSingleReg);
+ assert_eq!(
+ msg.data,
+ Data::Write(Write::Other {
+ address: 0x0001,
+ data: 0x0003
+ })
+ );
+ }
+
+ #[test]
+ fn invalid_mask_write_register_request() {
+ let mut state = ModbusState::new();
+ assert_eq!(
+ AppLayerResult::ok(),
+ state.parse(&INVALID_MASK_WR_REG_REQ, Direction::ToServer)
+ );
+ assert_eq!(state.transactions.len(), 1);
+
+ let tx = &state.transactions[0];
+ let msg = tx.request.as_ref().unwrap();
+ assert_eq!(msg.function.code, FunctionCode::MaskWrReg);
+ assert_eq!(msg.error_flags, ErrorFlags::DATA_LENGTH);
+ assert_eq!(msg.data, Data::ByteVec(vec![0x00, 0x04, 0x00, 0xF2]));
+
+ assert_eq!(
+ AppLayerResult::ok(),
+ state.parse(&MASK_WR_REG_RESP, Direction::ToClient)
+ );
+ assert_eq!(state.transactions.len(), 1);
+
+ let tx = &state.transactions[0];
+ let msg = tx.response.as_ref().unwrap();
+ assert_eq!(msg.function.code, FunctionCode::MaskWrReg);
+ assert_eq!(
+ msg.data,
+ Data::Write(Write::Mask {
+ address: 0x0004,
+ and_mask: 0x00f2,
+ or_mask: 0x0025
+ })
+ );
+ }
+
+ #[test]
+ fn invalid_write_single_register_request() {
+ let mut state = ModbusState::new();
+ assert_eq!(
+ AppLayerResult::ok(),
+ state.parse(&INVALID_WR_SINGLE_REG_REQ, Direction::ToServer)
+ );
+ assert_eq!(state.transactions.len(), 1);
+
+ let tx = &state.transactions[0];
+ let msg = tx.request.as_ref().unwrap();
+ assert_eq!(msg.function.code, FunctionCode::WrSingleReg);
+ assert_eq!(msg.error_flags, ErrorFlags::DATA_LENGTH);
+ assert_eq!(msg.data, Data::ByteVec(vec![0x00, 0x01]));
+
+ assert_eq!(
+ AppLayerResult::ok(),
+ state.parse(&WR_SINGLE_REG_RESP, Direction::ToClient)
+ );
+ assert_eq!(state.transactions.len(), 1);
+
+ let tx = &state.transactions[0];
+ let msg = tx.response.as_ref().unwrap();
+ assert_eq!(msg.function.code, FunctionCode::WrSingleReg);
+ assert_eq!(
+ msg.data,
+ Data::Write(Write::Other {
+ address: 0x0001,
+ data: 0x0003
+ })
+ );
+ }
+
+ #[test]
+ fn invalid_function_code() {
+ let mut state = ModbusState::new();
+ assert_eq!(
+ AppLayerResult::ok(),
+ state.parse(&INVALID_FUNC_CODE, Direction::ToServer)
+ );
+ assert_eq!(state.transactions.len(), 1);
+
+ let tx = &state.transactions[0];
+ let msg = tx.request.as_ref().unwrap();
+ assert_eq!(msg.function.code, FunctionCode::Unknown);
+ assert_eq!(msg.data, Data::ByteVec(vec![]));
+ }
+}
#include "suricata-common.h"
#include "util-debug.h"
-#include "util-byte.h"
-#include "util-enum.h"
-#include "util-mem.h"
-#include "util-misc.h"
-#include "stream.h"
-#include "stream-tcp.h"
-
-#include "app-layer-protos.h"
#include "app-layer-parser.h"
#include "app-layer-modbus.h"
-#include "app-layer-detect-proto.h"
-
-#include "conf.h"
-#include "conf-yaml-loader.h"
-#include "decode.h"
-
-SCEnumCharMap modbus_decoder_event_table[ ] = {
- /* Modbus Application Data Unit messages - ADU Modbus */
- { "INVALID_PROTOCOL_ID", MODBUS_DECODER_EVENT_INVALID_PROTOCOL_ID },
- { "UNSOLICITED_RESPONSE", MODBUS_DECODER_EVENT_UNSOLICITED_RESPONSE },
- { "INVALID_LENGTH", MODBUS_DECODER_EVENT_INVALID_LENGTH },
- { "INVALID_UNIT_IDENTIFIER", MODBUS_DECODER_EVENT_INVALID_UNIT_IDENTIFIER},
-
- /* Modbus Protocol Data Unit messages - PDU Modbus */
- { "INVALID_FUNCTION_CODE", MODBUS_DECODER_EVENT_INVALID_FUNCTION_CODE },
- { "INVALID_VALUE", MODBUS_DECODER_EVENT_INVALID_VALUE },
- { "INVALID_EXCEPTION_CODE", MODBUS_DECODER_EVENT_INVALID_EXCEPTION_CODE },
- { "VALUE_MISMATCH", MODBUS_DECODER_EVENT_VALUE_MISMATCH },
-
- /* Modbus Decoder event */
- { "FLOODED", MODBUS_DECODER_EVENT_FLOODED},
- { NULL, -1 },
-};
-
-/* Modbus Application Data Unit (ADU) length range. */
-#define MODBUS_MIN_ADU_LEN 2
-#define MODBUS_MAX_ADU_LEN 254
-
-/* Modbus Protocol version. */
-#define MODBUS_PROTOCOL_VER 0
-
-/* Modbus Unit Identifier range. */
-#define MODBUS_MIN_INVALID_UNIT_ID 247
-#define MODBUS_MAX_INVALID_UNIT_ID 255
-
-/* Modbus Quantity range. */
-#define MODBUS_MIN_QUANTITY 0
-#define MODBUS_MAX_QUANTITY_IN_BIT_ACCESS 2000
-#define MODBUS_MAX_QUANTITY_IN_WORD_ACCESS 125
-
-/* Modbus Count range. */
-#define MODBUS_MIN_COUNT 1
-#define MODBUS_MAX_COUNT 250
-
-/* Modbus Function Code. */
-#define MODBUS_FUNC_READCOILS 0x01
-#define MODBUS_FUNC_READDISCINPUTS 0x02
-#define MODBUS_FUNC_READHOLDREGS 0x03
-#define MODBUS_FUNC_READINPUTREGS 0x04
-#define MODBUS_FUNC_WRITESINGLECOIL 0x05
-#define MODBUS_FUNC_WRITESINGLEREG 0x06
-#define MODBUS_FUNC_READEXCSTATUS 0x07
-#define MODBUS_FUNC_DIAGNOSTIC 0x08
-#define MODBUS_FUNC_GETCOMEVTCOUNTER 0x0b
-#define MODBUS_FUNC_GETCOMEVTLOG 0x0c
-#define MODBUS_FUNC_WRITEMULTCOILS 0x0f
-#define MODBUS_FUNC_WRITEMULTREGS 0x10
-#define MODBUS_FUNC_REPORTSERVERID 0x11
-#define MODBUS_FUNC_READFILERECORD 0x14
-#define MODBUS_FUNC_WRITEFILERECORD 0x15
-#define MODBUS_FUNC_MASKWRITEREG 0x16
-#define MODBUS_FUNC_READWRITEMULTREGS 0x17
-#define MODBUS_FUNC_READFIFOQUEUE 0x18
-#define MODBUS_FUNC_ENCAPINTTRANS 0x2b
-#define MODBUS_FUNC_MASK 0x7f
-#define MODBUS_FUNC_ERRORMASK 0x80
-
-/* Modbus Diagnostic functions: Subfunction Code. */
-#define MODBUS_SUBFUNC_QUERY_DATA 0x00
-#define MODBUS_SUBFUNC_RESTART_COM 0x01
-#define MODBUS_SUBFUNC_DIAG_REGS 0x02
-#define MODBUS_SUBFUNC_CHANGE_DELIMITER 0x03
-#define MODBUS_SUBFUNC_LISTEN_MODE 0x04
-#define MODBUS_SUBFUNC_CLEAR_REGS 0x0a
-#define MODBUS_SUBFUNC_BUS_MSG_COUNT 0x0b
-#define MODBUS_SUBFUNC_COM_ERR_COUNT 0x0c
-#define MODBUS_SUBFUNC_EXCEPT_ERR_COUNT 0x0d
-#define MODBUS_SUBFUNC_SERVER_MSG_COUNT 0x0e
-#define MODBUS_SUBFUNC_SERVER_NO_RSP_COUNT 0x0f
-#define MODBUS_SUBFUNC_SERVER_NAK_COUNT 0x10
-#define MODBUS_SUBFUNC_SERVER_BUSY_COUNT 0x11
-#define MODBUS_SUBFUNC_SERVER_CHAR_COUNT 0x12
-#define MODBUS_SUBFUNC_CLEAR_COUNT 0x14
-
-/* Modbus Encapsulated Interface Transport function: MEI type. */
-#define MODBUS_MEI_ENCAPINTTRANS_CAN 0x0d
-#define MODBUS_MEI_ENCAPINTTRANS_READ 0x0e
-
-/* Modbus Exception Codes. */
-#define MODBUS_ERROR_CODE_ILLEGAL_FUNCTION 0x01
-#define MODBUS_ERROR_CODE_ILLEGAL_DATA_ADDRESS 0x02
-#define MODBUS_ERROR_CODE_ILLEGAL_DATA_VALUE 0x03
-#define MODBUS_ERROR_CODE_SERVER_DEVICE_FAILURE 0x04
-#define MODBUS_ERROR_CODE_MEMORY_PARITY_ERROR 0x08
-
-/* Modbus Application Protocol (MBAP) header. */
-struct ModbusHeader_ {
- uint16_t transactionId;
- uint16_t protocolId;
- uint16_t length;
- uint8_t unitId;
-} __attribute__((__packed__));
-typedef struct ModbusHeader_ ModbusHeader;
-
-/* Modbus Read/Write function and Access Types. */
-#define MODBUS_TYP_WRITE_SINGLE (MODBUS_TYP_WRITE | MODBUS_TYP_SINGLE)
-#define MODBUS_TYP_WRITE_MULTIPLE (MODBUS_TYP_WRITE | MODBUS_TYP_MULTIPLE)
-#define MODBUS_TYP_READ_WRITE_MULTIPLE (MODBUS_TYP_READ | MODBUS_TYP_WRITE | MODBUS_TYP_MULTIPLE)
-
-/* Macro to convert quantity value (in bit) into count value (in word): count = Ceil(quantity/8) */
-#define CEIL(quantity) (((quantity) + 7)>>3)
-
-/* Modbus Default unreplied Modbus requests are considered a flood */
-#define MODBUS_CONFIG_DEFAULT_REQUEST_FLOOD 500
-
-/* Modbus default stream reassembly depth */
-#define MODBUS_CONFIG_DEFAULT_STREAM_DEPTH 0
-
-static uint32_t request_flood = MODBUS_CONFIG_DEFAULT_REQUEST_FLOOD;
-static uint32_t stream_depth = MODBUS_CONFIG_DEFAULT_STREAM_DEPTH;
-
-static int ModbusStateGetEventInfo(const char *event_name, int *event_id, AppLayerEventType *event_type)
-{
- *event_id = SCMapEnumNameToValue(event_name, modbus_decoder_event_table);
-
- if (*event_id == -1) {
- SCLogError(SC_ERR_INVALID_ENUM_MAP, "event \"%s\" not present in "
- "modbus's enum map table.", event_name);
- /* yes this is fatal */
- return -1;
- }
-
- *event_type = APP_LAYER_EVENT_TYPE_TRANSACTION;
-
- return 0;
-}
-
-static int ModbusStateGetEventInfoById(int event_id, const char **event_name,
- AppLayerEventType *event_type)
-{
- *event_name = SCMapEnumValueToName(event_id, modbus_decoder_event_table);
-
- if (*event_name == NULL) {
- SCLogError(SC_ERR_INVALID_ENUM_MAP, "event \"%d\" not present in "
- "modbus's enum map table.", event_id);
- /* yes this is fatal */
- return -1;
- }
-
- *event_type = APP_LAYER_EVENT_TYPE_TRANSACTION;
-
- return 0;
-}
-
-static void ModbusSetEvent(ModbusState *modbus, uint8_t e)
-{
- if (modbus && modbus->curr) {
- SCLogDebug("modbus->curr->decoder_events %p", modbus->curr->decoder_events);
- AppLayerDecoderEventsSetEventRaw(&modbus->curr->decoder_events, e);
- SCLogDebug("modbus->curr->decoder_events %p", modbus->curr->decoder_events);
- modbus->events++;
- } else
- SCLogDebug("couldn't set event %u", e);
-}
-
-static AppLayerDecoderEvents *ModbusGetEvents(void *tx)
-{
- return ((ModbusTransaction *)tx)->decoder_events;
-}
-
-static int ModbusGetAlstateProgress(void *modbus_tx, uint8_t direction)
-{
- ModbusTransaction *tx = (ModbusTransaction *) modbus_tx;
- ModbusState *modbus = tx->modbus;
-
- if (tx->replied == 1)
- return 1;
-
- /* Check flood limit */
- if ((modbus->givenup == 1) &&
- ((modbus->transaction_max - tx->tx_num) > request_flood))
- return 1;
-
- return 0;
-}
-
-static void *ModbusGetTx(void *alstate, uint64_t tx_id)
-{
- ModbusState *modbus = (ModbusState *) alstate;
- ModbusTransaction *tx = NULL;
-
- if (modbus->curr && modbus->curr->tx_num == tx_id + 1)
- return modbus->curr;
-
- TAILQ_FOREACH(tx, &modbus->tx_list, next) {
- SCLogDebug("tx->tx_num %"PRIu64", tx_id %"PRIu64, tx->tx_num, (tx_id+1));
- if (tx->tx_num != (tx_id+1))
- continue;
-
- SCLogDebug("returning tx %p", tx);
- return tx;
- }
-
- return NULL;
-}
-
-static AppLayerTxData *ModbusGetTxData(void *vtx)
-{
- ModbusTransaction *tx = (ModbusTransaction *)vtx;
- return &tx->tx_data;
-}
-
-static uint64_t ModbusGetTxCnt(void *alstate)
-{
- return ((uint64_t) ((ModbusState *) alstate)->transaction_max);
-}
-
-/** \internal
- * \brief Find the Modbus Transaction in the state based on Transaction ID.
- *
- * \param modbus Pointer to Modbus state structure
- * \param transactionId Transaction ID of the transaction
- *
- * \retval tx or NULL if not found
- */
-static ModbusTransaction *ModbusTxFindByTransaction(const ModbusState *modbus,
- const uint16_t transactionId)
-{
- ModbusTransaction *tx = NULL;
-
- if (modbus->curr == NULL)
- return NULL;
-
- /* fast path */
- if ((modbus->curr->transactionId == transactionId) &&
- !(modbus->curr->replied)) {
- return modbus->curr;
- /* slow path, iterate list */
- } else {
- TAILQ_FOREACH(tx, &modbus->tx_list, next) {
- if ((tx->transactionId == transactionId) &&
- !(modbus->curr->replied))
- return tx;
- }
- }
- /* not found */
- return NULL;
-}
-
-/** \internal
- * \brief Allocate a Modbus Transaction and
- * add it into Transaction list of Modbus State
- *
- * \param modbus Pointer to Modbus state structure
- *
- * \retval Pointer to Transaction or NULL pointer
- */
-static ModbusTransaction *ModbusTxAlloc(ModbusState *modbus) {
- ModbusTransaction *tx;
-
- /* Check flood limit */
- if ((request_flood != 0) && (modbus->unreplied_cnt >= request_flood)) {
- ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_FLOODED);
- modbus->givenup = 1;
- return NULL;
- }
-
- tx = (ModbusTransaction *) SCCalloc(1, sizeof(ModbusTransaction));
- if (unlikely(tx == NULL))
- return NULL;
-
- modbus->transaction_max++;
- modbus->unreplied_cnt++;
-
- modbus->curr = tx;
-
- SCLogDebug("modbus->transaction_max updated to %"PRIu64, modbus->transaction_max);
-
- TAILQ_INSERT_TAIL(&modbus->tx_list, tx, next);
-
- tx->modbus = modbus;
- tx->tx_num = modbus->transaction_max;
-
- return tx;
-}
-
-/** \internal
- * \brief Free a Modbus Transaction
- *
- * \retval Pointer to Transaction or NULL pointer
- */
-static void ModbusTxFree(ModbusTransaction *tx) {
- SCEnter();
- if (tx->data != NULL)
- SCFree(tx->data);
-
- AppLayerDecoderEventsFreeEvents(&tx->decoder_events);
-
- if (tx->de_state != NULL)
- DetectEngineStateFree(tx->de_state);
-
- SCFree(tx);
- SCReturn;
-}
+void ModbusParserRegisterTests(void);
/**
- * \brief Modbus transaction cleanup callback
- */
-static void ModbusStateTxFree(void *state, uint64_t tx_id)
-{
- SCEnter();
- ModbusState *modbus = (ModbusState *) state;
- ModbusTransaction *tx = NULL, *ttx;
-
- SCLogDebug("state %p, id %"PRIu64, modbus, tx_id);
-
- TAILQ_FOREACH_SAFE(tx, &modbus->tx_list, next, ttx) {
- SCLogDebug("tx %p tx->tx_num %"PRIu64", tx_id %"PRIu64, tx, tx->tx_num, (tx_id+1));
-
- if (tx->tx_num != (tx_id+1))
- continue;
-
- if (tx == modbus->curr)
- modbus->curr = NULL;
-
- if (tx->decoder_events != NULL) {
- if (tx->decoder_events->cnt <= modbus->events)
- modbus->events -= tx->decoder_events->cnt;
- else
- modbus->events = 0;
- }
-
- modbus->unreplied_cnt--;
-
- /* Check flood limit */
- if ((modbus->givenup == 1) &&
- (request_flood != 0) &&
- (modbus->unreplied_cnt < request_flood) )
- modbus->givenup = 0;
-
- TAILQ_REMOVE(&modbus->tx_list, tx, next);
- ModbusTxFree(tx);
- break;
- }
- SCReturn;
-}
-
-/** \internal
- * \brief Extract 8bits data from pointer the received input data
- *
- * \param res Pointer to the result
- * \param input Pointer the received input data
- * \param input_len Length of the received input data
- * \param offset Offset of the received input data pointer
- */
-static int ModbusExtractUint8(ModbusState *modbus,
- uint8_t *res,
- const uint8_t *input,
- uint32_t input_len,
- uint16_t *offset) {
- SCEnter();
- if (input_len < (uint32_t) (*offset + sizeof(uint8_t))) {
- ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_LENGTH);
- SCReturnInt(-1);
- }
-
- *res = *(input + *offset);
- *offset += sizeof(uint8_t);
- SCReturnInt(0);
-}
-
-/** \internal
- * \brief Extract 16bits data from pointer the received input data
- *
- * \param res Pointer to the result
- * \param input Pointer the received input data
- * \param input_len Length of the received input data
- * \param offset Offset of the received input data pointer
- */
-static int ModbusExtractUint16(ModbusState *modbus,
- uint16_t *res,
- const uint8_t *input,
- uint32_t input_len,
- uint16_t *offset) {
- SCEnter();
- if (input_len < (uint32_t) (*offset + sizeof(uint16_t))) {
- ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_LENGTH);
- SCReturnInt(-1);
- }
-
- ByteExtractUint16(res, BYTE_BIG_ENDIAN, sizeof(uint16_t), (const uint8_t *) (input + *offset));
- *offset += sizeof(uint16_t);
- SCReturnInt(0);
-}
-
-/** \internal
- * \brief Check length field in Modbus header according to code function
- *
- * \param modbus Pointer to Modbus state structure
- * \param length Length field in Modbus Header
- * \param len Length according to code functio
- */
-static int ModbusCheckHeaderLength(ModbusState *modbus,
- uint16_t length,
- uint16_t len) {
- SCEnter();
- if (length != len) {
- ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_LENGTH);
- SCReturnInt(-1);
- }
- SCReturnInt(0);
-}
-
-/** \internal
- * \brief Check Modbus header
- *
- * \param tx Pointer to Modbus Transaction structure
- * \param modbus Pointer to Modbus state structure
- * \param header Pointer to Modbus header state in which the value to be stored
- */
-static void ModbusCheckHeader(ModbusState *modbus,
- ModbusHeader *header)
-{
- SCEnter();
- /* MODBUS protocol is identified by the value 0. */
- if (header->protocolId != MODBUS_PROTOCOL_VER)
- ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_PROTOCOL_ID);
-
- /* Check Length field that is a byte count of the following fields */
- if ((header->length < MODBUS_MIN_ADU_LEN) ||
- (header->length > MODBUS_MAX_ADU_LEN) )
- ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_LENGTH);
-
- /* Check Unit Identifier field that is not in invalid range */
- if ((header->unitId > MODBUS_MIN_INVALID_UNIT_ID) &&
- (header->unitId < MODBUS_MAX_INVALID_UNIT_ID) )
- ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_UNIT_IDENTIFIER);
-
- SCReturn;
-}
-
-/** \internal
- * \brief Parse Exception Response and verify protocol compliance.
- *
- * \param tx Pointer to Modbus Transaction structure
- * \param modbus Pointer to Modbus state structure
- * \param input Pointer the received input data
- * \param input_len Length of the received input data
- * \param offset Offset of the received input data pointer
- */
-static void ModbusExceptionResponse(ModbusTransaction *tx,
- ModbusState *modbus,
- const uint8_t *input,
- uint32_t input_len,
- uint16_t *offset)
-{
- SCEnter();
- uint8_t exception = 0;
-
- /* Exception code (1 byte) */
- if (ModbusExtractUint8(modbus, &exception, input, input_len, offset))
- SCReturn;
-
- switch (exception) {
- case MODBUS_ERROR_CODE_ILLEGAL_FUNCTION:
- case MODBUS_ERROR_CODE_SERVER_DEVICE_FAILURE:
- break;
- case MODBUS_ERROR_CODE_ILLEGAL_DATA_VALUE:
- if (tx->function == MODBUS_FUNC_DIAGNOSTIC) {
- break;
- }
- /* Fallthrough */
- case MODBUS_ERROR_CODE_ILLEGAL_DATA_ADDRESS:
- if ( (tx->type & MODBUS_TYP_ACCESS_FUNCTION_MASK) ||
- (tx->function == MODBUS_FUNC_READFIFOQUEUE) ||
- (tx->function == MODBUS_FUNC_ENCAPINTTRANS)) {
- break;
- }
- /* Fallthrough */
- case MODBUS_ERROR_CODE_MEMORY_PARITY_ERROR:
- if ( (tx->function == MODBUS_FUNC_READFILERECORD) ||
- (tx->function == MODBUS_FUNC_WRITEFILERECORD) ) {
- break;
- }
- /* Fallthrough */
- default:
- ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_EXCEPTION_CODE);
- break;
- }
-
- SCReturn;
-}
-
-/** \internal
- * \brief Parse Read data Request, complete Transaction structure
- * and verify protocol compliance.
- *
- * \param tx Pointer to Modbus Transaction structure
- * \param modbus Pointer to Modbus state structure
- * \param input Pointer the received input data
- * \param input_len Length of the received input data
- * \param offset Offset of the received input data pointer
- */
-static void ModbusParseReadRequest(ModbusTransaction *tx,
- ModbusState *modbus,
- const uint8_t *input,
- uint32_t input_len,
- uint16_t *offset)
-{
- SCEnter();
- uint16_t quantity;
- uint8_t type = tx->type;
-
- /* Starting Address (2 bytes) */
- if (ModbusExtractUint16(modbus, &(tx->read.address), input, input_len, offset))
- goto end;
-
- /* Quantity (2 bytes) */
- if (ModbusExtractUint16(modbus, &(tx->read.quantity), input, input_len, offset))
- goto end;
- quantity = tx->read.quantity;
-
- /* Check Quantity range */
- if (type & MODBUS_TYP_BIT_ACCESS_MASK) {
- if ((quantity == MODBUS_MIN_QUANTITY) ||
- (quantity > MODBUS_MAX_QUANTITY_IN_BIT_ACCESS))
- goto error;
- } else {
- if ((quantity == MODBUS_MIN_QUANTITY) ||
- (quantity > MODBUS_MAX_QUANTITY_IN_WORD_ACCESS))
- goto error;
- }
-
- if (~type & MODBUS_TYP_WRITE)
- /* Except from Read/Write Multiple Registers function (code 23) */
- /* The length of all Read Data function requests is 6 bytes */
- /* Modbus Application Protocol Specification V1.1b3 from 6.1 to 6.4 */
- ModbusCheckHeaderLength(modbus, tx->length, 6);
-
- goto end;
-
-error:
- ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_VALUE);
-end:
- SCReturn;
-}
-
-/** \internal
- * \brief Parse Read data Response and verify protocol compliance
- *
- * \param tx Pointer to Modbus Transaction structure
- * \param modbus Pointer to Modbus state structure
- * \param input Pointer the received input data
- * \param input_len Length of the received input data
- * \param offset Offset of the received input data pointer
- */
-static void ModbusParseReadResponse(ModbusTransaction *tx,
- ModbusState *modbus,
- const uint8_t *input,
- uint32_t input_len,
- uint16_t *offset)
-{
- SCEnter();
- uint8_t count = 0;
-
- /* Count (1 bytes) */
- if (ModbusExtractUint8(modbus, &count, input, input_len, offset))
- goto end;
-
- /* Check Count range and value according to the request */
- if ((tx->type) & MODBUS_TYP_BIT_ACCESS_MASK) {
- if ( (count < MODBUS_MIN_COUNT) ||
- (count > MODBUS_MAX_COUNT) ||
- (count != CEIL(tx->read.quantity)))
- goto error;
- } else {
- if ( (count == MODBUS_MIN_COUNT) ||
- (count > MODBUS_MAX_COUNT) ||
- (count != (2 * (tx->read.quantity))))
- goto error;
- }
-
- /* Except from Read/Write Multiple Registers function (code 23) */
- /* The length of all Read Data function responses is (3 bytes + count) */
- /* Modbus Application Protocol Specification V1.1b3 from 6.1 to 6.4 */
- ModbusCheckHeaderLength(modbus, tx->length, 3 + count);
- goto end;
-
-error:
- ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_VALUE_MISMATCH);
-end:
- SCReturn;
-}
-
-/** \internal
- * \brief Parse Write data Request, complete Transaction structure
- * and verify protocol compliance.
- *
- * \param tx Pointer to Modbus Transaction structure
- * \param modbus Pointer to Modbus state structure
- * \param input Pointer the received input data
- * \param input_len Length of the received input data
- * \param offset Offset of the received input data pointer
- *
- * \retval On success returns 0 or on failure returns -1.
- */
-static int ModbusParseWriteRequest(ModbusTransaction *tx,
- ModbusState *modbus,
- const uint8_t *input,
- uint32_t input_len,
- uint16_t *offset)
-{
- SCEnter();
- uint16_t quantity = 1, word = 0;
- uint8_t byte = 0, count = 1, type = tx->type;
-
- int i = 0;
-
- /* Starting/Output/Register Address (2 bytes) */
- if (ModbusExtractUint16(modbus, &(tx->write.address), input, input_len, offset))
- goto end;
-
- if (type & MODBUS_TYP_SINGLE) {
- /* The length of Write Single Coil (code 5) and */
- /* Write Single Register (code 6) requests is 6 bytes */
- /* Modbus Application Protocol Specification V1.1b3 6.5 and 6.6 */
- if (ModbusCheckHeaderLength(modbus, tx->length, 6))
- goto end;
- } else if (type & MODBUS_TYP_MULTIPLE) {
- /* Quantity (2 bytes) */
- if (ModbusExtractUint16(modbus, &quantity, input, input_len, offset))
- goto end;
- tx->write.quantity = quantity;
-
- /* Count (1 bytes) */
- if (ModbusExtractUint8(modbus, &count, input, input_len, offset))
- goto end;
- tx->write.count = count;
-
- if (type & MODBUS_TYP_BIT_ACCESS_MASK) {
- /* Check Quantity range and conversion in byte (count) */
- if ((quantity == MODBUS_MIN_QUANTITY) ||
- (quantity > MODBUS_MAX_QUANTITY_IN_BIT_ACCESS) ||
- (quantity != CEIL(count)))
- goto error;
-
- /* The length of Write Multiple Coils (code 15) request is (7 + count) */
- /* Modbus Application Protocol Specification V1.1b3 6.11 */
- if (ModbusCheckHeaderLength(modbus, tx->length, 7 + count))
- goto end;
- } else {
- /* Check Quantity range and conversion in byte (count) */
- if ((quantity == MODBUS_MIN_QUANTITY) ||
- (quantity > MODBUS_MAX_QUANTITY_IN_WORD_ACCESS) ||
- (count != (2 * quantity)))
- goto error;
-
- if (type & MODBUS_TYP_READ) {
- /* The length of Read/Write Multiple Registers function (code 23) */
- /* request is (11 bytes + count) */
- /* Modbus Application Protocol Specification V1.1b3 6.17 */
- if (ModbusCheckHeaderLength(modbus, tx->length, 11 + count))
- goto end;
- } else {
- /* The length of Write Multiple Coils (code 15) and */
- /* Write Multiple Registers (code 16) functions requests is (7 bytes + count) */
- /* Modbus Application Protocol Specification V1.1b3 from 6.11 and 6.12 */
- if (ModbusCheckHeaderLength(modbus, tx->length, 7 + count))
- goto end;
- }
- }
- } else {
- /* Mask Write Register function (And_Mask and Or_Mask) */
- quantity = 2;
-
- /* The length of Mask Write Register (code 22) function request is 8 */
- /* Modbus Application Protocol Specification V1.1b3 6.16 */
- if (ModbusCheckHeaderLength(modbus, tx->length, 8))
- goto end;
- }
-
- if (type & MODBUS_TYP_COILS) {
- /* Output value (data block) unit is count */
- tx->data = (uint16_t *) SCCalloc(1, count * sizeof(uint16_t));
- if (unlikely(tx->data == NULL))
- SCReturnInt(-1);
-
- if (type & MODBUS_TYP_SINGLE) {
- /* Outputs value (2 bytes) */
- if (ModbusExtractUint16(modbus, &word, input, input_len, offset))
- goto end;
- tx->data[i] = word;
-
- if ((word != 0x00) && (word != 0xFF00))
- goto error;
- } else {
- for (i = 0; i < count; i++) {
- /* Outputs value (1 byte) */
- if (ModbusExtractUint8(modbus, &byte, input, input_len, offset))
- goto end;
- tx->data[i] = (uint16_t) byte;
- }
- }
- } else {
- /* Registers value (data block) unit is quantity */
- tx->data = (uint16_t *) SCCalloc(1, quantity * sizeof(uint16_t));
- if (unlikely(tx->data == NULL))
- SCReturnInt(-1);
-
- for (i = 0; i < quantity; i++) {
- /* Outputs/Registers value (2 bytes) */
- if (ModbusExtractUint16(modbus, &word, input, input_len, offset))
- goto end;
- tx->data[i] = word;
- }
- }
- goto end;
-
-error:
- ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_VALUE);
-end:
- SCReturnInt(0);
-}
-
-/** \internal
- * \brief Parse Write data Response and verify protocol compliance
- *
- * \param tx Pointer to Modbus Transaction structure
- * \param modbus Pointer to Modbus state structure
- * \param input Pointer the received input data
- * \param input_len Length of the received input data
- * \param offset Offset of the received input data pointer
- */
-static void ModbusParseWriteResponse(ModbusTransaction *tx,
- ModbusState *modbus,
- const uint8_t *input,
- uint32_t input_len,
- uint16_t *offset)
-{
- SCEnter();
- uint16_t address = 0, quantity = 0, word = 0;
- uint8_t type = tx->type;
-
- /* Starting Address (2 bytes) */
- if (ModbusExtractUint16(modbus, &address, input, input_len, offset))
- goto end;
-
- if (address != tx->write.address)
- goto error;
-
- if (type & MODBUS_TYP_SINGLE) {
- /* Check if Outputs/Registers value has been stored */
- if (tx->data != NULL)
- {
- /* Outputs/Registers value (2 bytes) */
- if (ModbusExtractUint16(modbus, &word, input, input_len, offset))
- goto end;
-
- /* Check with Outputs/Registers from request */
- if (word != tx->data[0])
- goto error;
- }
- } else if (type & MODBUS_TYP_MULTIPLE) {
- /* Quantity (2 bytes) */
- if (ModbusExtractUint16(modbus, &quantity, input, input_len, offset))
- goto end;
-
- /* Check Quantity range */
- if (type & MODBUS_TYP_BIT_ACCESS_MASK) {
- if ((quantity == MODBUS_MIN_QUANTITY) ||
- (quantity > MODBUS_MAX_QUANTITY_IN_WORD_ACCESS))
- goto error;
- } else {
- if ((quantity == MODBUS_MIN_QUANTITY) ||
- (quantity > MODBUS_MAX_QUANTITY_IN_BIT_ACCESS))
- goto error;
- }
-
- /* Check Quantity value according to the request */
- if (quantity != tx->write.quantity)
- goto error;
- } else {
- /* Check if And_Mask and Or_Mask values have been stored */
- if (tx->data != NULL)
- {
- /* And_Mask value (2 bytes) */
- if (ModbusExtractUint16(modbus, &word, input, input_len, offset))
- goto end;
-
- /* Check And_Mask value according to the request */
- if (word != tx->data[0])
- goto error;
-
- /* And_Or_Mask value (2 bytes) */
- if (ModbusExtractUint16(modbus, &word, input, input_len, offset))
- goto end;
-
- /* Check Or_Mask value according to the request */
- if (word != tx->data[1])
- goto error;
- }
-
- /* The length of Mask Write Register (code 22) function response is 8 */
- /* Modbus Application Protocol Specification V1.1b3 6.16 */
- ModbusCheckHeaderLength(modbus, tx->length, 8);
- goto end;
- }
-
- /* Except from Mask Write Register (code 22) */
- /* The length of all Write Data function responses is 6 */
- /* Modbus Application Protocol Specification V1.1b3 6.5, 6.6, 6.11, 6.12 and 6.17 */
- ModbusCheckHeaderLength(modbus, tx->length, 6);
- goto end;
-
-error:
- ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_VALUE_MISMATCH);
-end:
- SCReturn;
-}
-
-/** \internal
- * \brief Parse Diagnostic Request, complete Transaction
- * structure (Category) and verify protocol compliance.
- *
- * \param tx Pointer to Modbus Transaction structure
- * \param modbus Pointer to Modbus state structure
- * \param input Pointer the received input data
- * \param input_len Length of the received input data
- * \param offset Offset of the received input data pointer
- *
- * \retval Reserved category function returns 1 otherwise returns 0.
- */
-static int ModbusParseDiagnosticRequest(ModbusTransaction *tx,
- ModbusState *modbus,
- const uint8_t *input,
- uint32_t input_len,
- uint16_t *offset)
-{
- SCEnter();
- uint16_t data = 0;
-
- /* Sub-function (2 bytes) */
- if (ModbusExtractUint16(modbus, &(tx->subFunction), input, input_len, offset))
- goto end;
-
- /* Data (2 bytes) */
- if (ModbusExtractUint16(modbus, &data, input, input_len, offset))
- goto end;
-
- if (tx->subFunction != MODBUS_SUBFUNC_QUERY_DATA) {
- switch (tx->subFunction) {
- case MODBUS_SUBFUNC_RESTART_COM:
- if ((data != 0x00) && (data != 0xFF00))
- goto error;
- break;
-
- case MODBUS_SUBFUNC_CHANGE_DELIMITER:
- if ((data & 0xFF) != 0x00)
- goto error;
- break;
-
- case MODBUS_SUBFUNC_LISTEN_MODE:
- /* No answer is expected then mark tx as completed. */
- tx->replied = 1;
- /* Fallthrough */
- case MODBUS_SUBFUNC_DIAG_REGS:
- case MODBUS_SUBFUNC_CLEAR_REGS:
- case MODBUS_SUBFUNC_BUS_MSG_COUNT:
- case MODBUS_SUBFUNC_COM_ERR_COUNT:
- case MODBUS_SUBFUNC_EXCEPT_ERR_COUNT:
- case MODBUS_SUBFUNC_SERVER_MSG_COUNT:
- case MODBUS_SUBFUNC_SERVER_NO_RSP_COUNT:
- case MODBUS_SUBFUNC_SERVER_NAK_COUNT:
- case MODBUS_SUBFUNC_SERVER_BUSY_COUNT:
- case MODBUS_SUBFUNC_SERVER_CHAR_COUNT:
- case MODBUS_SUBFUNC_CLEAR_COUNT:
- if (data != 0x00)
- goto error;
- break;
-
- default:
- /* Set function code category */
- tx->category = MODBUS_CAT_RESERVED;
- SCReturnInt(1);
- }
-
- /* The length of all Diagnostic Requests is 6 */
- /* Modbus Application Protocol Specification V1.1b3 6.8 */
- ModbusCheckHeaderLength(modbus, tx->length, 6);
- }
-
- goto end;
-
-error:
- ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_VALUE);
-end:
- SCReturnInt(0);
-}
-
-/* Modbus Function Code Categories structure. */
-typedef struct ModbusFunctionCodeRange_ {
- uint8_t function;
- uint8_t category;
-} ModbusFunctionCodeRange;
-
-/* Modbus Function Code Categories table. */
-static ModbusFunctionCodeRange modbusFunctionCodeRanges[] = {
- { 0, MODBUS_CAT_PUBLIC_UNASSIGNED},
- { 9, MODBUS_CAT_RESERVED },
- { 15, MODBUS_CAT_PUBLIC_UNASSIGNED},
- { 41, MODBUS_CAT_RESERVED },
- { 43, MODBUS_CAT_PUBLIC_UNASSIGNED},
- { 65, MODBUS_CAT_USER_DEFINED },
- { 73, MODBUS_CAT_PUBLIC_UNASSIGNED},
- { 90, MODBUS_CAT_RESERVED },
- { 92, MODBUS_CAT_PUBLIC_UNASSIGNED},
- { 100, MODBUS_CAT_USER_DEFINED },
- { 111, MODBUS_CAT_PUBLIC_UNASSIGNED},
- { 125, MODBUS_CAT_RESERVED },
- { 128, MODBUS_CAT_NONE }
-};
-
-/** \internal
- * \brief Parse the Modbus Protocol Data Unit (PDU) Request
- *
- * \param tx Pointer to Modbus Transaction structure
- * \param ModbusPdu Pointer the Modbus PDU state in which the value to be stored
- * \param input Pointer the received input data
- * \param input_len Length of the received input data
- */
-static void ModbusParseRequestPDU(ModbusTransaction *tx,
- ModbusState *modbus,
- const uint8_t *input,
- uint32_t input_len)
-{
- SCEnter();
- uint16_t offset = (uint16_t) sizeof(ModbusHeader);
- uint8_t count = 0;
-
- int i = 0;
-
- /* Standard function codes used on MODBUS application layer protocol (1 byte) */
- if (ModbusExtractUint8(modbus, &(tx->function), input, input_len, &offset))
- goto end;
-
- /* Set default function code category */
- tx->category = MODBUS_CAT_NONE;
-
- /* Set default function primary table */
- tx->type = MODBUS_TYP_NONE;
-
- switch (tx->function) {
- case MODBUS_FUNC_NONE:
- ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_FUNCTION_CODE);
- break;
-
- case MODBUS_FUNC_READCOILS:
- /* Set function type */
- tx->type = (MODBUS_TYP_COILS | MODBUS_TYP_READ);
- break;
-
- case MODBUS_FUNC_READDISCINPUTS:
- /* Set function type */
- tx->type = (MODBUS_TYP_DISCRETES | MODBUS_TYP_READ);
- break;
-
- case MODBUS_FUNC_READHOLDREGS:
- /* Set function type */
- tx->type = (MODBUS_TYP_HOLDING | MODBUS_TYP_READ);
- break;
-
- case MODBUS_FUNC_READINPUTREGS:
- /* Set function type */
- tx->type = (MODBUS_TYP_INPUT | MODBUS_TYP_READ);
- break;
-
- case MODBUS_FUNC_WRITESINGLECOIL:
- /* Set function type */
- tx->type = (MODBUS_TYP_COILS | MODBUS_TYP_WRITE_SINGLE);
- break;
-
- case MODBUS_FUNC_WRITESINGLEREG:
- /* Set function type */
- tx->type = (MODBUS_TYP_HOLDING | MODBUS_TYP_WRITE_SINGLE);
- break;
-
- case MODBUS_FUNC_WRITEMULTCOILS:
- /* Set function type */
- tx->type = (MODBUS_TYP_COILS | MODBUS_TYP_WRITE_MULTIPLE);
- break;
-
- case MODBUS_FUNC_WRITEMULTREGS:
- /* Set function type */
- tx->type = (MODBUS_TYP_HOLDING | MODBUS_TYP_WRITE_MULTIPLE);
- break;
-
- case MODBUS_FUNC_MASKWRITEREG:
- /* Set function type */
- tx->type = (MODBUS_TYP_HOLDING | MODBUS_TYP_WRITE);
- break;
-
- case MODBUS_FUNC_READWRITEMULTREGS:
- /* Set function type */
- tx->type = (MODBUS_TYP_HOLDING | MODBUS_TYP_READ_WRITE_MULTIPLE);
- break;
-
- case MODBUS_FUNC_READFILERECORD:
- case MODBUS_FUNC_WRITEFILERECORD:
- /* Count/length (1 bytes) */
- if (ModbusExtractUint8(modbus, &count, input, input_len, &offset))
- goto end;
-
- /* Modbus Application Protocol Specification V1.1b3 6.14 and 6.15 */
- ModbusCheckHeaderLength(modbus, tx->length, 2 + count);
- break;
-
- case MODBUS_FUNC_DIAGNOSTIC:
- if(ModbusParseDiagnosticRequest(tx, modbus, input, input_len, &offset))
- goto end;
- break;
-
- case MODBUS_FUNC_READEXCSTATUS:
- case MODBUS_FUNC_GETCOMEVTCOUNTER:
- case MODBUS_FUNC_GETCOMEVTLOG:
- case MODBUS_FUNC_REPORTSERVERID:
- /* Modbus Application Protocol Specification V1.1b3 6.7, 6.9, 6.10 and 6.13 */
- ModbusCheckHeaderLength(modbus, tx->length, 2);
- break;
-
- case MODBUS_FUNC_READFIFOQUEUE:
- /* Modbus Application Protocol Specification V1.1b3 6.18 */
- ModbusCheckHeaderLength(modbus, tx->length, 4);
- break;
-
- case MODBUS_FUNC_ENCAPINTTRANS:
- /* MEI type (1 byte) */
- if (ModbusExtractUint8(modbus, &(tx->mei), input, input_len, &offset))
- goto end;
-
- if (tx->mei == MODBUS_MEI_ENCAPINTTRANS_READ) {
- /* Modbus Application Protocol Specification V1.1b3 6.21 */
- ModbusCheckHeaderLength(modbus, tx->length, 5);
- } else if (tx->mei != MODBUS_MEI_ENCAPINTTRANS_CAN) {
- /* Set function code category */
- tx->category = MODBUS_CAT_RESERVED;
- goto end;
- }
- break;
-
- default:
- /* Check if request is error. */
- if (tx->function & MODBUS_FUNC_ERRORMASK) {
- ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_FUNCTION_CODE);
- goto end;
- }
-
- /* Get and store function code category */
- for (i = 0; modbusFunctionCodeRanges[i].category != MODBUS_CAT_NONE; i++) {
- if (tx->function <= modbusFunctionCodeRanges[i].function)
- break;
- tx->category = modbusFunctionCodeRanges[i].category;
- }
- goto end;
- }
-
- /* Set function code category */
- tx->category = MODBUS_CAT_PUBLIC_ASSIGNED;
-
- if (tx->type & MODBUS_TYP_READ)
- ModbusParseReadRequest(tx, modbus, input, input_len, &offset);
-
- if (tx->type & MODBUS_TYP_WRITE)
- ModbusParseWriteRequest(tx, modbus, input, input_len, &offset);
-
-end:
- SCReturn;
-}
-
-/** \internal
- * \brief Parse the Modbus Protocol Data Unit (PDU) Response
- *
- * \param tx Pointer to Modbus Transaction structure
- * \param modbus Pointer the Modbus PDU state in which the value to be stored
- * \param input Pointer the received input data
- * \param input_len Length of the received input data
- * \param offset Offset of the received input data pointer
- */
-static void ModbusParseResponsePDU(ModbusTransaction *tx,
- ModbusState *modbus,
- const uint8_t *input,
- uint32_t input_len)
-{
- SCEnter();
- uint16_t offset = (uint16_t) sizeof(ModbusHeader);
- uint8_t count = 0, function = 0, mei = 0;
- uint8_t error = false;
-
- /* Standard function codes used on MODBUS application layer protocol (1 byte) */
- if (ModbusExtractUint8(modbus, &function, input, input_len, &offset))
- goto end;
-
- /* Check if response is error */
- if(function & MODBUS_FUNC_ERRORMASK) {
- function &= MODBUS_FUNC_MASK;
- error = true;
- }
-
- if (tx->category == MODBUS_CAT_PUBLIC_ASSIGNED) {
- /* Check if response is error. */
- if (error) {
- ModbusExceptionResponse(tx, modbus, input, input_len, &offset);
- } else {
- switch(function) {
- case MODBUS_FUNC_READEXCSTATUS:
- /* Modbus Application Protocol Specification V1.1b3 6.7 */
- ModbusCheckHeaderLength(modbus, tx->length, 3);
- goto end;
-
- case MODBUS_FUNC_GETCOMEVTCOUNTER:
- /* Modbus Application Protocol Specification V1.1b3 6.9 */
- ModbusCheckHeaderLength(modbus, tx->length, 6);
- goto end;
-
- case MODBUS_FUNC_READFILERECORD:
- case MODBUS_FUNC_WRITEFILERECORD:
- /* Count/length (1 bytes) */
- if (ModbusExtractUint8(modbus, &count, input, input_len, &offset))
- goto end;
-
- /* Modbus Application Protocol Specification V1.1b3 6.14 and 6.15 */
- ModbusCheckHeaderLength(modbus, tx->length, 2 + count);
- goto end;
-
- case MODBUS_FUNC_ENCAPINTTRANS:
- /* MEI type (1 byte) */
- if (ModbusExtractUint8(modbus, &mei, input, input_len, &offset))
- goto end;
-
- if (mei != tx->mei)
- ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_VALUE_MISMATCH);
- goto end;
- }
-
- if (tx->type & MODBUS_TYP_READ)
- ModbusParseReadResponse(tx, modbus, input, input_len, &offset);
- /* Read/Write response contents none write response part */
- else if (tx->type & MODBUS_TYP_WRITE)
- ModbusParseWriteResponse(tx, modbus, input, input_len, &offset);
- }
- }
-
-end:
- SCReturn;
-}
-
-/** \internal
- * \brief Parse the Modbus Application Protocol (MBAP) header
- *
- * \param header Pointer the Modbus header state in which the value to be stored
- * \param input Pointer the received input data
- */
-static int ModbusParseHeader(ModbusState *modbus,
- ModbusHeader *header,
- const uint8_t *input,
- uint32_t input_len)
-{
- SCEnter();
- uint16_t offset = 0;
-
- int r = 0;
-
- /* can't pass the header fields directly due to alignment (Bug 2088) */
- uint16_t transaction_id = 0;
- uint16_t protocol_id = 0;
- uint16_t length = 0;
- uint8_t unit_id = 0;
-
- /* Transaction Identifier (2 bytes) */
- r = ModbusExtractUint16(modbus, &transaction_id, input, input_len, &offset);
- /* Protocol Identifier (2 bytes) */
- r |= ModbusExtractUint16(modbus, &protocol_id, input, input_len, &offset);
- /* Length (2 bytes) */
- r |= ModbusExtractUint16(modbus, &length, input, input_len, &offset);
- /* Unit Identifier (1 byte) */
- r |= ModbusExtractUint8(modbus, &unit_id, input, input_len, &offset);
-
- if (r != 0) {
- SCReturnInt(-1);
- }
- header->transactionId = transaction_id;
- header->protocolId = protocol_id;
- header->length = length;
- header->unitId = unit_id;
-
- SCReturnInt(0);
-}
-
-/** \internal
- *
- * \brief This function is called to retrieve a Modbus Request
- *
- * \param state Modbus state structure for the parser
- * \param input Input line of the command
- * \param input_len Length of the request
- *
- * \retval AppLayerResult APP_LAYER_OK or APP_LAYER_ERROR
- */
-static AppLayerResult ModbusParseRequest(Flow *f,
- void *state,
- AppLayerParserState *pstate,
- const uint8_t *input,
- uint32_t input_len,
- void *local_data,
- const uint8_t flags)
-{
- SCEnter();
- ModbusState *modbus = (ModbusState *) state;
- ModbusTransaction *tx;
- ModbusHeader header;
-
- if (input == NULL && AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TS)) {
- SCReturnStruct(APP_LAYER_OK);
- } else if (input == NULL || input_len == 0) {
- SCReturnStruct(APP_LAYER_ERROR);
- }
-
- while (input_len > 0) {
- uint32_t adu_len = input_len;
- const uint8_t *adu = input;
-
- /* Extract MODBUS Header */
- if (ModbusParseHeader(modbus, &header, adu, adu_len))
- SCReturnStruct(APP_LAYER_OK);
-
- /* Update ADU length with length in Modbus header. */
- adu_len = (uint32_t) sizeof(ModbusHeader) + (uint32_t) header.length - 1;
- if (adu_len > input_len)
- SCReturnStruct(APP_LAYER_OK);
-
- /* Allocate a Transaction Context and add it to Transaction list */
- tx = ModbusTxAlloc(modbus);
- if (tx == NULL)
- SCReturnStruct(APP_LAYER_OK);
-
- /* Check MODBUS Header */
- ModbusCheckHeader(modbus, &header);
-
- /* Store Unit ID, Transaction ID & PDU length */
- tx->unit_id = header.unitId;
- tx->transactionId = header.transactionId;
- tx->length = header.length;
-
- /* Extract MODBUS PDU and fill Transaction Context */
- ModbusParseRequestPDU(tx, modbus, adu, adu_len);
-
- /* Update input line and remaining input length of the command */
- input += adu_len;
- input_len -= adu_len;
- }
-
- SCReturnStruct(APP_LAYER_OK);
-}
-
-/** \internal
- * \brief This function is called to retrieve a Modbus response
- *
- * \param state Pointer to Modbus state structure for the parser
- * \param input Input line of the command
- * \param input_len Length of the request
- *
- * \retval AppLayerResult APP_LAYER_OK or APP_LAYER_ERROR
- */
-static AppLayerResult ModbusParseResponse(Flow *f,
- void *state,
- AppLayerParserState *pstate,
- const uint8_t *input,
- uint32_t input_len,
- void *local_data,
- const uint8_t flags)
-{
- SCEnter();
- ModbusHeader header;
- ModbusState *modbus = (ModbusState *) state;
- ModbusTransaction *tx;
-
- if (input == NULL && AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TC)) {
- SCReturnStruct(APP_LAYER_OK);
- } else if (input == NULL || input_len == 0) {
- SCReturnStruct(APP_LAYER_ERROR);
- }
-
- while (input_len > 0) {
- uint32_t adu_len = input_len;
- const uint8_t *adu = input;
-
- /* Extract MODBUS Header */
- if (ModbusParseHeader(modbus, &header, adu, adu_len))
- SCReturnStruct(APP_LAYER_OK);
-
- /* Update ADU length with length in Modbus header. */
- adu_len = (uint32_t) sizeof(ModbusHeader) + (uint32_t) header.length - 1;
- if (adu_len > input_len)
- SCReturnStruct(APP_LAYER_OK);
-
- /* Find the transaction context thanks to transaction ID (and function code) */
- tx = ModbusTxFindByTransaction(modbus, header.transactionId);
- if (tx == NULL) {
- /* Allocate a Transaction Context if not previous request */
- /* and add it to Transaction list */
- tx = ModbusTxAlloc(modbus);
- if (tx == NULL)
- SCReturnStruct(APP_LAYER_OK);
-
- SCLogDebug("MODBUS_DECODER_EVENT_UNSOLICITED_RESPONSE");
- ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_UNSOLICITED_RESPONSE);
- } else {
- /* Store PDU length */
- tx->length = header.length;
-
- /* Extract MODBUS PDU and fill Transaction Context */
- ModbusParseResponsePDU(tx, modbus, adu, adu_len);
- }
-
- /* Check and store MODBUS Header */
- ModbusCheckHeader(modbus, &header);
-
- /* Mark as completed */
- tx->replied = 1;
-
- /* Update input line and remaining input length of the command */
- input += adu_len;
- input_len -= adu_len;
- }
-
- SCReturnStruct(APP_LAYER_OK);
-}
-
-/** \internal
- * \brief Function to allocate the Modbus state memory
- */
-static void *ModbusStateAlloc(void *orig_state, AppProto proto_orig)
-{
- ModbusState *modbus;
-
- modbus = (ModbusState *) SCCalloc(1, sizeof(ModbusState));
- if (unlikely(modbus == NULL))
- return NULL;
-
- TAILQ_INIT(&modbus->tx_list);
-
- return (void *) modbus;
-}
-
-/** \internal
- * \brief Function to free the Modbus state memory
- */
-static void ModbusStateFree(void *state)
-{
- SCEnter();
- ModbusState *modbus = (ModbusState *) state;
- ModbusTransaction *tx = NULL, *ttx;
-
- if (state) {
- TAILQ_FOREACH_SAFE(tx, &modbus->tx_list, next, ttx) {
- ModbusTxFree(tx);
- }
-
- SCFree(state);
- }
- SCReturn;
-}
-
-static uint16_t ModbusProbingParser(Flow *f,
- uint8_t direction,
- const uint8_t *input,
- uint32_t input_len,
- uint8_t *rdir)
-{
- ModbusHeader *header = (ModbusHeader *) input;
-
- /* Modbus header is 7 bytes long */
- if (input_len < sizeof(ModbusHeader))
- return ALPROTO_UNKNOWN;
-
- /* MODBUS protocol is identified by the value 0. */
- if (header->protocolId != 0)
- return ALPROTO_FAILED;
-
- return ALPROTO_MODBUS;
-}
-
-static DetectEngineState *ModbusGetTxDetectState(void *vtx)
-{
- ModbusTransaction *tx = (ModbusTransaction *)vtx;
- return tx->de_state;
-}
-
-static int ModbusSetTxDetectState(void *vtx, DetectEngineState *s)
-{
- ModbusTransaction *tx = (ModbusTransaction *)vtx;
- tx->de_state = s;
- return 0;
-}
-
-/**
- * \brief Function to register the Modbus protocol parsers and other functions
+ * \brief Function to register the Modbus protocol parser
*/
void RegisterModbusParsers(void)
{
- SCEnter();
- const char *proto_name = "modbus";
-
- /* Modbus application protocol V1.1b3 */
- if (AppLayerProtoDetectConfProtoDetectionEnabled("tcp", proto_name)) {
- AppLayerProtoDetectRegisterProtocol(ALPROTO_MODBUS, proto_name);
-
- if (RunmodeIsUnittests()) {
- AppLayerProtoDetectPPRegister(IPPROTO_TCP,
- "502",
- ALPROTO_MODBUS,
- 0, sizeof(ModbusHeader),
- STREAM_TOSERVER,
- ModbusProbingParser, ModbusProbingParser);
- } else {
- /* If there is no app-layer section for Modbus, silently
- * leave it disabled. */
- if (!AppLayerProtoDetectPPParseConfPorts("tcp", IPPROTO_TCP,
- proto_name, ALPROTO_MODBUS,
- 0, sizeof(ModbusHeader),
- ModbusProbingParser, ModbusProbingParser)) {
- return;
- }
- }
-
- ConfNode *p = NULL;
- p = ConfGetNode("app-layer.protocols.modbus.request-flood");
- if (p != NULL) {
- uint32_t value;
- if (ParseSizeStringU32(p->val, &value) < 0) {
- SCLogError(SC_ERR_MODBUS_CONFIG, "invalid value for request-flood %s", p->val);
- } else {
- request_flood = value;
- }
- }
- SCLogConfig("Modbus request flood protection level: %u", request_flood);
-
- p = ConfGetNode("app-layer.protocols.modbus.stream-depth");
- if (p != NULL) {
- uint32_t value;
- if (ParseSizeStringU32(p->val, &value) < 0) {
- SCLogError(SC_ERR_MODBUS_CONFIG, "invalid value for stream-depth %s", p->val);
- } else {
- stream_depth = value;
- }
- }
- SCLogConfig("Modbus stream depth: %u", stream_depth);
- } else {
- SCLogConfig("Protocol detection and parser disabled for %s protocol.", proto_name);
- return;
- }
- if (AppLayerParserConfParserEnabled("tcp", proto_name)) {
- AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_MODBUS, STREAM_TOSERVER, ModbusParseRequest);
- AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_MODBUS, STREAM_TOCLIENT, ModbusParseResponse);
- AppLayerParserRegisterStateFuncs(IPPROTO_TCP, ALPROTO_MODBUS, ModbusStateAlloc, ModbusStateFree);
-
- AppLayerParserRegisterGetEventsFunc(IPPROTO_TCP, ALPROTO_MODBUS, ModbusGetEvents);
- AppLayerParserRegisterDetectStateFuncs(IPPROTO_TCP, ALPROTO_MODBUS,
- ModbusGetTxDetectState, ModbusSetTxDetectState);
-
- AppLayerParserRegisterGetTx(IPPROTO_TCP, ALPROTO_MODBUS, ModbusGetTx);
- AppLayerParserRegisterTxDataFunc(IPPROTO_TCP, ALPROTO_MODBUS, ModbusGetTxData);
- AppLayerParserRegisterGetTxCnt(IPPROTO_TCP, ALPROTO_MODBUS, ModbusGetTxCnt);
- AppLayerParserRegisterTxFreeFunc(IPPROTO_TCP, ALPROTO_MODBUS, ModbusStateTxFree);
-
- AppLayerParserRegisterGetStateProgressFunc(IPPROTO_TCP, ALPROTO_MODBUS, ModbusGetAlstateProgress);
- AppLayerParserRegisterStateProgressCompletionStatus(ALPROTO_MODBUS, 1, 1);
-
- AppLayerParserRegisterGetEventInfo(IPPROTO_TCP, ALPROTO_MODBUS, ModbusStateGetEventInfo);
- AppLayerParserRegisterGetEventInfoById(IPPROTO_TCP, ALPROTO_MODBUS, ModbusStateGetEventInfoById);
-
- AppLayerParserRegisterParserAcceptableDataDirection(IPPROTO_TCP, ALPROTO_MODBUS, STREAM_TOSERVER);
-
- AppLayerParserSetStreamDepth(IPPROTO_TCP, ALPROTO_MODBUS, stream_depth);
- } else {
- SCLogConfig("Parsed disabled for %s protocol. Protocol detection" "still on.", proto_name);
- }
+ rs_modbus_register_parser();
#ifdef UNITTESTS
AppLayerParserRegisterProtocolUnittests(IPPROTO_TCP, ALPROTO_MODBUS, ModbusParserRegisterTests);
#endif
#include "stream-tcp.h"
#include "stream-tcp-private.h"
+#include "rust.h"
+
+/* Modbus default stream reassembly depth */
+#define MODBUS_CONFIG_DEFAULT_STREAM_DEPTH 0
+
/* Modbus Application Protocol Specification V1.1b3 6.1: Read Coils */
static uint8_t invalidFunctionCode[] = {
/* Transaction ID */ 0x00, 0x00,
FAIL_IF_NOT(r == 0);
FLOWLOCK_UNLOCK(&f);
- ModbusState *modbus_state = f.alstate;
+ ModbusState *modbus_state = f.alstate;
FAIL_IF_NULL(modbus_state);
- ModbusTransaction *tx = ModbusGetTx(modbus_state, 0);
- FAIL_IF_NOT(tx->function == 1);
- FAIL_IF_NOT(tx->read.address == 0x7890);
- FAIL_IF_NOT(tx->read.quantity == 19);
+ ModbusMessage request = rs_modbus_state_get_tx_request(modbus_state, 0);
+ FAIL_IF_NULL(request._0);
+ FAIL_IF_NOT(rs_modbus_message_get_function(&request) == 1);
+ FAIL_IF_NOT(rs_modbus_message_get_read_request_address(&request) == 0x7890);
+ FAIL_IF_NOT(rs_modbus_message_get_read_request_quantity(&request) == 19);
FLOWLOCK_WRLOCK(&f);
r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
FAIL_IF_NOT(r == 0);
FLOWLOCK_UNLOCK(&f);
- FAIL_IF_NOT(modbus_state->transaction_max == 1);
+ FAIL_IF_NOT(rs_modbus_state_get_tx_count(modbus_state) == 1);
AppLayerParserThreadCtxFree(alp_tctx);
StreamTcpFreeConfig(true);
FAIL_IF_NOT(r == 0);
FLOWLOCK_UNLOCK(&f);
- ModbusState *modbus_state = f.alstate;
+ ModbusState *modbus_state = f.alstate;
FAIL_IF_NULL(modbus_state);
- ModbusTransaction *tx = ModbusGetTx(modbus_state, 0);
+ ModbusMessage request = rs_modbus_state_get_tx_request(modbus_state, 0);
+ FAIL_IF_NULL(request._0);
+ FAIL_IF_NOT(rs_modbus_message_get_function(&request) == 16);
+ FAIL_IF_NOT(rs_modbus_message_get_write_multreq_address(&request) == 0x01);
+ FAIL_IF_NOT(rs_modbus_message_get_write_multreq_quantity(&request) == 2);
- FAIL_IF_NOT(tx->function == 16);
- FAIL_IF_NOT(tx->write.address == 0x01);
- FAIL_IF_NOT(tx->write.quantity == 2);
- FAIL_IF_NOT(tx->write.count == 4);
- FAIL_IF_NOT(tx->data[0] == 0x000A);
- FAIL_IF_NOT(tx->data[1] == 0x0102);
+ size_t data_len;
+ const uint8_t *data = rs_modbus_message_get_write_multreq_data(&request, &data_len);
+ FAIL_IF_NOT(data_len == 4);
+ FAIL_IF_NOT(data[0] == 0x00);
+ FAIL_IF_NOT(data[1] == 0x0A);
+ FAIL_IF_NOT(data[2] == 0x01);
+ FAIL_IF_NOT(data[3] == 0x02);
FLOWLOCK_WRLOCK(&f);
r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
FAIL_IF_NOT(r == 0);
FLOWLOCK_UNLOCK(&f);
- FAIL_IF_NOT(modbus_state->transaction_max == 1);
+ FAIL_IF_NOT(rs_modbus_state_get_tx_count(modbus_state) == 1);
AppLayerParserThreadCtxFree(alp_tctx);
StreamTcpFreeConfig(true);
FAIL_IF_NOT(r == 0);
FLOWLOCK_UNLOCK(&f);
- ModbusState *modbus_state = f.alstate;
+ ModbusState *modbus_state = f.alstate;
FAIL_IF_NULL(modbus_state);
- ModbusTransaction *tx = ModbusGetTx(modbus_state, 0);
-
- FAIL_IF_NOT(tx->function == 23);
- FAIL_IF_NOT(tx->read.address == 0x03);
- FAIL_IF_NOT(tx->read.quantity == 6);
- FAIL_IF_NOT(tx->write.address == 0x0E);
- FAIL_IF_NOT(tx->write.quantity == 3);
- FAIL_IF_NOT(tx->write.count == 6);
- FAIL_IF_NOT(tx->data[0] == 0x1234);
- FAIL_IF_NOT(tx->data[1] == 0x5678);
- FAIL_IF_NOT(tx->data[2] == 0x9ABC);
+ ModbusMessage request = rs_modbus_state_get_tx_request(modbus_state, 0);
+ FAIL_IF_NULL(request._0);
+
+ FAIL_IF_NOT(rs_modbus_message_get_function(&request) == 23);
+ FAIL_IF_NOT(rs_modbus_message_get_rw_multreq_read_address(&request) == 0x03);
+ FAIL_IF_NOT(rs_modbus_message_get_rw_multreq_read_quantity(&request) == 6);
+ FAIL_IF_NOT(rs_modbus_message_get_rw_multreq_write_address(&request) == 0x0E);
+ FAIL_IF_NOT(rs_modbus_message_get_rw_multreq_write_quantity(&request) == 3);
+
+ size_t data_len;
+ uint8_t const *data = rs_modbus_message_get_rw_multreq_write_data(&request, &data_len);
+ FAIL_IF_NOT(data_len == 6);
+ FAIL_IF_NOT(data[0] == 0x12);
+ FAIL_IF_NOT(data[1] == 0x34);
+ FAIL_IF_NOT(data[2] == 0x56);
+ FAIL_IF_NOT(data[3] == 0x78);
+ FAIL_IF_NOT(data[4] == 0x9A);
+ FAIL_IF_NOT(data[5] == 0xBC);
FLOWLOCK_WRLOCK(&f);
r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
FAIL_IF_NOT(r == 0);
FLOWLOCK_UNLOCK(&f);
- FAIL_IF_NOT(modbus_state->transaction_max == 1);
+ FAIL_IF_NOT(rs_modbus_state_get_tx_count(modbus_state) == 1);
/* do detect */
SigMatchSignatures(&tv, de_ctx, det_ctx, p);
FAIL_IF_NOT(r == 0);
FLOWLOCK_UNLOCK(&f);
- ModbusState *modbus_state = f.alstate;
+ ModbusState *modbus_state = f.alstate;
FAIL_IF_NULL(modbus_state);
- ModbusTransaction *tx = ModbusGetTx(modbus_state, 0);
+ ModbusMessage request = rs_modbus_state_get_tx_request(modbus_state, 0);
+ FAIL_IF_NULL(request._0);
- FAIL_IF_NOT(tx->function == 8);
- FAIL_IF_NOT(tx->subFunction == 4);
+ FAIL_IF_NOT(rs_modbus_message_get_function(&request) == 8);
+ FAIL_IF_NOT(rs_modbus_message_get_subfunction(&request) == 4);
AppLayerParserThreadCtxFree(alp_tctx);
StreamTcpFreeConfig(true);
FAIL_IF_NOT(r == 0);
FLOWLOCK_UNLOCK(&f);
- ModbusState *modbus_state = f.alstate;
+ ModbusState *modbus_state = f.alstate;
FAIL_IF_NULL(modbus_state);
/* do detect */
FAIL_IF_NOT(r == 0);
FLOWLOCK_UNLOCK(&f);
- ModbusState *modbus_state = f.alstate;
+ ModbusState *modbus_state = f.alstate;
FAIL_IF_NULL(modbus_state);
/* do detect */
STREAM_TOSERVER,
invalidLengthWriteMultipleRegistersReq,
sizeof(invalidLengthWriteMultipleRegistersReq));
- FAIL_IF_NOT(r == 0);
+ FAIL_IF_NOT(r == 1);
FLOWLOCK_UNLOCK(&f);
- ModbusState *modbus_state = f.alstate;
+ ModbusState *modbus_state = f.alstate;
FAIL_IF_NULL(modbus_state);
/* do detect */
FAIL_IF_NOT(r == 0);
FLOWLOCK_UNLOCK(&f);
- ModbusState *modbus_state = f.alstate;
+ ModbusState *modbus_state = f.alstate;
FAIL_IF_NULL(modbus_state);
- ModbusTransaction *tx = ModbusGetTx(modbus_state, 0);
+ ModbusMessage request = rs_modbus_state_get_tx_request(modbus_state, 0);
+ FAIL_IF_NULL(request._0);
- FAIL_IF_NOT(tx->function == 1);
- FAIL_IF_NOT(tx->read.address == 0x7890);
- FAIL_IF_NOT(tx->read.quantity == 19);
+ FAIL_IF_NOT(rs_modbus_message_get_function(&request) == 1);
+ FAIL_IF_NOT(rs_modbus_message_get_read_request_address(&request) == 0x7890);
+ FAIL_IF_NOT(rs_modbus_message_get_read_request_quantity(&request) == 19);
FLOWLOCK_WRLOCK(&f);
r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
FAIL_IF_NOT(r == 0);
FLOWLOCK_UNLOCK(&f);
- FAIL_IF_NOT(modbus_state->transaction_max == 1);
+ FAIL_IF_NOT(rs_modbus_state_get_tx_count(modbus_state) == 1);
/* do detect */
SigMatchSignatures(&tv, de_ctx, det_ctx, p);
FLOWLOCK_WRLOCK(&f);
int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
STREAM_TOSERVER, input, input_len - part2_len);
- FAIL_IF_NOT(r == 0);
+ FAIL_IF_NOT(r == 1);
r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
STREAM_TOSERVER, input, input_len);
FAIL_IF_NOT(r == 0);
FLOWLOCK_UNLOCK(&f);
- ModbusState *modbus_state = f.alstate;
+ ModbusState *modbus_state = f.alstate;
FAIL_IF_NULL(modbus_state);
- ModbusTransaction *tx = ModbusGetTx(modbus_state, 0);
+ ModbusMessage request = rs_modbus_state_get_tx_request(modbus_state, 0);
+ FAIL_IF_NULL(request._0);
- FAIL_IF_NOT(tx->function == 1);
- FAIL_IF_NOT(tx->read.address == 0x7890);
- FAIL_IF_NOT(tx->read.quantity == 19);
+ FAIL_IF_NOT(rs_modbus_message_get_function(&request) == 1);
+ FAIL_IF_NOT(rs_modbus_message_get_read_request_address(&request) == 0x7890);
+ FAIL_IF_NOT(rs_modbus_message_get_read_request_quantity(&request) == 19);
input_len = sizeof(readCoilsRsp);
part2_len = 10;
FLOWLOCK_WRLOCK(&f);
r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
STREAM_TOCLIENT, input, input_len - part2_len);
- FAIL_IF_NOT(r == 0);
+ FAIL_IF_NOT(r == 1);
r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
STREAM_TOCLIENT, input, input_len);
FAIL_IF_NOT(r == 0);
FLOWLOCK_UNLOCK(&f);
- FAIL_IF_NOT(modbus_state->transaction_max ==1);
+ FAIL_IF_NOT(rs_modbus_state_get_tx_count(modbus_state) == 1);
AppLayerParserThreadCtxFree(alp_tctx);
StreamTcpFreeConfig(true);
FAIL_IF_NOT(r == 0);
FLOWLOCK_UNLOCK(&f);
- ModbusState *modbus_state = f.alstate;
+ ModbusState *modbus_state = f.alstate;
FAIL_IF_NULL(modbus_state);
- FAIL_IF_NOT(modbus_state->transaction_max == 2);
+ FAIL_IF_NOT(rs_modbus_state_get_tx_count(modbus_state) == 2);
+
+ ModbusMessage request = rs_modbus_state_get_tx_request(modbus_state, 1);
+ FAIL_IF_NULL(request._0);
- ModbusTransaction *tx = ModbusGetTx(modbus_state, 1);
+ FAIL_IF_NOT(rs_modbus_message_get_function(&request) == 16);
+ FAIL_IF_NOT(rs_modbus_message_get_write_multreq_address(&request) == 0x01);
+ FAIL_IF_NOT(rs_modbus_message_get_write_multreq_quantity(&request) == 2);
- FAIL_IF_NOT(tx->function == 16);
- FAIL_IF_NOT(tx->write.address == 0x01);
- FAIL_IF_NOT(tx->write.quantity == 2);
- FAIL_IF_NOT(tx->write.count == 4);
- FAIL_IF_NOT(tx->data[0] == 0x000A);
- FAIL_IF_NOT(tx->data[1] == 0x0102);
+ size_t data_len;
+ uint8_t const *data = rs_modbus_message_get_write_multreq_data(&request, &data_len);
+ FAIL_IF_NOT(data_len == 4);
+ FAIL_IF_NOT(data[0] == 0x00);
+ FAIL_IF_NOT(data[1] == 0x0A);
+ FAIL_IF_NOT(data[2] == 0x01);
+ FAIL_IF_NOT(data[3] == 0x02);
input_len = sizeof(readCoilsRsp) + sizeof(writeMultipleRegistersRsp);
FAIL_IF_NOT(r == 0);
FLOWLOCK_UNLOCK(&f);
- ModbusState *modbus_state = f.alstate;
+ ModbusState *modbus_state = f.alstate;
FAIL_IF_NULL(modbus_state);
/* do detect */
FAIL_IF_NOT(r == 0);
FLOWLOCK_UNLOCK(&f);
- ModbusState *modbus_state = f.alstate;
+ ModbusState *modbus_state = f.alstate;
FAIL_IF_NULL(modbus_state);
/* do detect */
FAIL_IF_NOT(r == 0);
FLOWLOCK_UNLOCK(&f);
- ModbusState *modbus_state = f.alstate;
+ ModbusState *modbus_state = f.alstate;
FAIL_IF_NULL(modbus_state);
- ModbusTransaction *tx = ModbusGetTx(modbus_state, 0);
+ ModbusMessage request = rs_modbus_state_get_tx_request(modbus_state, 0);
+ FAIL_IF_NULL(request._0);
- FAIL_IF_NOT(tx->function == 22);
- FAIL_IF_NOT(tx->data[0] == 0x00F2);
- FAIL_IF_NOT(tx->data[1] == 0x0025);
+ FAIL_IF_NOT(rs_modbus_message_get_function(&request) == 22);
+ FAIL_IF_NOT(rs_modbus_message_get_and_mask(&request) == 0x00F2);
+ FAIL_IF_NOT(rs_modbus_message_get_or_mask(&request) == 0x0025);
FLOWLOCK_WRLOCK(&f);
r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
FAIL_IF_NOT(r == 0);
FLOWLOCK_UNLOCK(&f);
- FAIL_IF_NOT(modbus_state->transaction_max == 1);
+ FAIL_IF_NOT(rs_modbus_state_get_tx_count(modbus_state) == 1);
AppLayerParserThreadCtxFree(alp_tctx);
StreamTcpFreeConfig(true);
FAIL_IF_NOT(r == 0);
FLOWLOCK_UNLOCK(&f);
- ModbusState *modbus_state = f.alstate;
+ ModbusState *modbus_state = f.alstate;
FAIL_IF_NULL(modbus_state);
- ModbusTransaction *tx = ModbusGetTx(modbus_state, 0);
+ ModbusMessage request = rs_modbus_state_get_tx_request(modbus_state, 0);
+ FAIL_IF_NULL(request._0);
- FAIL_IF_NOT(tx->function == 6);
- FAIL_IF_NOT(tx->write.address == 0x0001);
- FAIL_IF_NOT(tx->data[0] == 0x0003);
+ FAIL_IF_NOT(rs_modbus_message_get_function(&request) == 6);
+ FAIL_IF_NOT(rs_modbus_message_get_write_address(&request) == 0x0001);
+ FAIL_IF_NOT(rs_modbus_message_get_write_data(&request) == 0x0003);
FLOWLOCK_WRLOCK(&f);
r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
FAIL_IF_NOT(r == 0);
FLOWLOCK_UNLOCK(&f);
- FAIL_IF_NOT(modbus_state->transaction_max == 1);
+ FAIL_IF_NOT(rs_modbus_state_get_tx_count(modbus_state) == 1);
AppLayerParserThreadCtxFree(alp_tctx);
StreamTcpFreeConfig(true);
FAIL_IF_NOT(r == 0);
FLOWLOCK_UNLOCK(&f);
- ModbusState *modbus_state = f.alstate;
+ ModbusState *modbus_state = f.alstate;
FAIL_IF_NULL(modbus_state);
- ModbusTransaction *tx = ModbusGetTx(modbus_state, 0);
+ ModbusMessage request = rs_modbus_state_get_tx_request(modbus_state, 0);
+ FAIL_IF_NULL(request._0);
- FAIL_IF_NOT(tx->function == 22);
+ FAIL_IF_NOT(rs_modbus_message_get_function(&request) == 22);
/* do detect */
SigMatchSignatures(&tv, de_ctx, det_ctx, p);
FAIL_IF_NOT(r == 0);
FLOWLOCK_UNLOCK(&f);
- FAIL_IF_NOT(modbus_state->transaction_max == 1);
+ FAIL_IF_NOT(rs_modbus_state_get_tx_count(modbus_state) == 1);
+ ModbusMessage response = rs_modbus_state_get_tx_response(modbus_state, 0);
+ FAIL_IF_NULL(response._0);
+
+ FAIL_IF_NOT(rs_modbus_message_get_function(&response) == 22);
SigGroupCleanup(de_ctx);
SigCleanSignatures(de_ctx);
FAIL_IF_NOT(r == 0);
FLOWLOCK_UNLOCK(&f);
- ModbusState *modbus_state = f.alstate;
+ ModbusState *modbus_state = f.alstate;
FAIL_IF_NULL(modbus_state);
- ModbusTransaction *tx = ModbusGetTx(modbus_state, 0);
+ ModbusMessage request = rs_modbus_state_get_tx_request(modbus_state, 0);
+ FAIL_IF_NULL(request._0);
- FAIL_IF_NOT(tx->function == 6);
- FAIL_IF_NOT(tx->write.address == 0x0001);
+ FAIL_IF_NOT(rs_modbus_message_get_function(&request) == 6);
+ size_t data_len;
+ const uint8_t *data = rs_modbus_message_get_bytevec_data(&request, &data_len);
+ FAIL_IF_NOT(data_len == 2);
+ FAIL_IF_NOT(data[0] == 0x00);
+ FAIL_IF_NOT(data[1] == 0x01);
/* do detect */
SigMatchSignatures(&tv, de_ctx, det_ctx, p);
FAIL_IF_NOT(r == 0);
FLOWLOCK_UNLOCK(&f);
- FAIL_IF_NOT(modbus_state->transaction_max == 1);
+ FAIL_IF_NOT(rs_modbus_state_get_tx_count(modbus_state) == 1);
+ ModbusMessage response = rs_modbus_state_get_tx_response(modbus_state, 0);
+ FAIL_IF_NULL(response._0);
+
+ FAIL_IF_NOT(rs_modbus_message_get_function(&response) == 6);
+ FAIL_IF_NOT(rs_modbus_message_get_write_address(&response) == 0x0001);
SigGroupCleanup(de_ctx);
SigCleanSignatures(de_ctx);
FLOWLOCK_WRLOCK(&f);
int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOSERVER,
input, input_len - part2_len);
- FAIL_IF(r != 0);
+ FAIL_IF(r != 1);
FLOWLOCK_UNLOCK(&f);
FAIL_IF(((TcpSession *)(f.protoctx))->reassembly_depth != MODBUS_CONFIG_DEFAULT_STREAM_DEPTH);
FLOWLOCK_WRLOCK(&f);
r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOCLIENT,
input, input_len - part2_len);
- FAIL_IF(r != 0);
+ FAIL_IF(r != 1);
FLOWLOCK_UNLOCK(&f);
FAIL_IF(((TcpSession *)(f.protoctx))->reassembly_depth != MODBUS_CONFIG_DEFAULT_STREAM_DEPTH);
FAIL_IF_NOT(r == 0);
FLOWLOCK_UNLOCK(&f);
- ModbusState *modbus_state = f.alstate;
+ ModbusState *modbus_state = f.alstate;
FAIL_IF_NULL(modbus_state);
/* do detect */
#ifndef __APP_LAYER_MODBUS_H__
#define __APP_LAYER_MODBUS_H__
-#include "decode.h"
-#include "detect-engine-state.h"
-#include "queue.h"
-#include "rust.h"
-
-/* Modbus Application Data Unit (ADU)
- * and Protocol Data Unit (PDU) messages */
-enum {
- MODBUS_DECODER_EVENT_INVALID_PROTOCOL_ID,
- MODBUS_DECODER_EVENT_UNSOLICITED_RESPONSE,
- MODBUS_DECODER_EVENT_INVALID_LENGTH,
- MODBUS_DECODER_EVENT_INVALID_UNIT_IDENTIFIER,
- MODBUS_DECODER_EVENT_INVALID_FUNCTION_CODE,
- MODBUS_DECODER_EVENT_INVALID_VALUE,
- MODBUS_DECODER_EVENT_INVALID_EXCEPTION_CODE,
- MODBUS_DECODER_EVENT_VALUE_MISMATCH,
- MODBUS_DECODER_EVENT_FLOODED,
-};
-
/* Modbus Function Code Categories. */
#define MODBUS_CAT_NONE 0x0
#define MODBUS_CAT_PUBLIC_ASSIGNED (1<<0)
#define MODBUS_TYP_WRITE_MULTIPLE (MODBUS_TYP_WRITE | MODBUS_TYP_MULTIPLE)
#define MODBUS_TYP_READ_WRITE_MULTIPLE (MODBUS_TYP_READ | MODBUS_TYP_WRITE | MODBUS_TYP_MULTIPLE)
-/* Modbus Function Code. */
-#define MODBUS_FUNC_NONE 0x00
-
-/* Modbus Transaction Structure, request/response. */
-typedef struct ModbusTransaction_ {
- struct ModbusState_ *modbus;
-
- uint64_t tx_num; /**< internal: id */
- uint16_t transactionId;
- uint16_t length;
- uint8_t unit_id;
- uint8_t function;
- uint8_t category;
- uint8_t type;
- uint8_t replied; /**< bool indicating request is replied to. */
-
- union {
- uint16_t subFunction;
- uint8_t mei;
- struct {
- struct {
- uint16_t address;
- uint16_t quantity;
- } read;
- struct {
- uint16_t address;
- uint16_t quantity;
- uint8_t count;
- } write;
- };
- };
- uint16_t *data; /**< to store data to write, bit is converted in 16bits. */
-
- AppLayerDecoderEvents *decoder_events; /**< per tx events */
- DetectEngineState *de_state;
- AppLayerTxData tx_data;
-
- TAILQ_ENTRY(ModbusTransaction_) next;
-} ModbusTransaction;
-
-/* Modbus State Structure. */
-typedef struct ModbusState_ {
- TAILQ_HEAD(, ModbusTransaction_) tx_list; /**< transaction list */
- ModbusTransaction *curr; /**< ptr to current tx */
- uint64_t transaction_max;
- uint32_t unreplied_cnt; /**< number of unreplied requests */
- uint16_t events;
- uint8_t givenup; /**< bool indicating flood. */
-} ModbusState;
-
void RegisterModbusParsers(void);
-void ModbusParserRegisterTests(void);
#endif /* __APP_LAYER_MODBUS_H__ */
#include "suricata-common.h"
#include "app-layer.h"
-#include "app-layer-modbus.h"
#include "detect.h"
#include "detect-modbus.h"
#include "util-debug.h"
-/** \internal
- *
- * \brief Value match detection code
- *
- * \param value Modbus value context (min, max and mode)
- * \param min Minimum value to compare
- * \param inter Interval or maximum (min + inter) value to compare
- *
- * \retval 1 match or 0 no match
- */
-static int DetectEngineInspectModbusValueMatch(DetectModbusValue *value,
- uint16_t min,
- uint16_t inter)
-{
- SCEnter();
- uint16_t max = min + inter;
-
- int ret = 0;
-
- switch (value->mode) {
- case DETECT_MODBUS_EQ:
- if ((value->min >= min) && (value->min <= max))
- ret = 1;
- break;
-
- case DETECT_MODBUS_LT:
- if (value->min > min)
- ret = 1;
- break;
-
- case DETECT_MODBUS_GT:
- if (value->min < max)
- ret = 1;
- break;
-
- case DETECT_MODBUS_RA:
- if ((value->max > min) && (value->min < max))
- ret = 1;
- break;
- }
-
- SCReturnInt(ret);
-}
-
-/** \internal
- *
- * \brief Do data (and address) inspection & validation for a signature
- *
- * \param tx Pointer to Modbus Transaction
- * \param address Address inspection
- * \param data Pointer to data signature structure to match
- *
- * \retval 0 no match or 1 match
- */
-static int DetectEngineInspectModbusData(ModbusTransaction *tx,
- uint16_t address,
- DetectModbusValue *data)
-{
- SCEnter();
- uint16_t offset, value = 0, type = tx->type;
-
- if (type & MODBUS_TYP_SINGLE) {
- /* Output/Register(s) Value */
- if (type & MODBUS_TYP_COILS)
- value = (tx->data[0])? 1 : 0;
- else
- value = tx->data[0];
- } else if (type & MODBUS_TYP_MULTIPLE) {
- int i, size = (int) sizeof(tx->data);
-
- offset = address - (tx->write.address + 1);
-
- /* In case of Coils, offset is in bit (convert in byte) */
- if (type & MODBUS_TYP_COILS)
- offset >>= 3;
-
- for (i=0; i< size; i++) {
- /* Select the correct register/coils amongst the output value */
- if (!(offset--)) {
- value = tx->data[i];
- break;
- }
- }
-
- /* In case of Coils, offset is now in the bit is the rest of previous convert */
- if (type & MODBUS_TYP_COILS) {
- offset = (address - (tx->write.address + 1)) & 0x7;
- value = (value >> offset) & 0x1;
- }
- } else {
- /* It is not possible to define the value that is writing for Mask */
- /* Write Register function because the current content is not available.*/
- SCReturnInt(0);
- }
-
- SCReturnInt(DetectEngineInspectModbusValueMatch(data, value, 0));
-}
-
-/** \internal
- *
- * \brief Do address inspection & validation for a signature
- *
- * \param tx Pointer to Modbus Transaction
- * \param address Pointer to address signature structure to match
- * \param access Access mode (READ or WRITE)
- *
- * \retval 0 no match or 1 match
- */
-static int DetectEngineInspectModbusAddress(ModbusTransaction *tx,
- DetectModbusValue *address,
- uint8_t access)
-{
- SCEnter();
- int ret = 0;
-
- /* Check if read/write address of request is at/in the address range of signature */
- if (access == MODBUS_TYP_READ) {
- /* In the PDU Coils are addresses starting at zero */
- /* therefore Coils numbered 1-16 are addressed as 0-15 */
- ret = DetectEngineInspectModbusValueMatch(address,
- tx->read.address + 1,
- tx->read.quantity - 1);
- } else {
- /* In the PDU Registers are addresses starting at zero */
- /* therefore Registers numbered 1-16 are addressed as 0-15 */
- if (tx->type & MODBUS_TYP_SINGLE)
- ret = DetectEngineInspectModbusValueMatch(address,
- tx->write.address + 1,
- 0);
- else
- ret = DetectEngineInspectModbusValueMatch(address,
- tx->write.address + 1,
- tx->write.quantity - 1);
- }
-
- SCReturnInt(ret);
-}
-
-/** \brief Do the content inspection & validation for a signature
- *
- * \param de_ctx Detection engine context
- * \param det_ctx Detection engine thread context
- * \param s Signature to inspect ( and sm: SigMatch to inspect)
- * \param f Flow
- * \param flags App layer flags
- * \param alstate App layer state
- * \param txv Pointer to Modbus Transaction structure
- *
- * \retval 0 no match or 1 match
- */
-int DetectEngineInspectModbus(DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
- const struct DetectEngineAppInspectionEngine_ *engine, const Signature *s, Flow *f,
- uint8_t flags, void *alstate, void *txv, uint64_t tx_id)
-{
- SCEnter();
- ModbusTransaction *tx = (ModbusTransaction *)txv;
- DetectModbus *modbus = (DetectModbus *)engine->smd->ctx;
-
- int ret = 0;
-
- if (modbus == NULL) {
- SCLogDebug("no modbus state, no match");
- SCReturnInt(0);
- }
-
- if (modbus->unit_id != NULL) {
- if (DetectEngineInspectModbusValueMatch(modbus->unit_id, tx->unit_id, 0) == 0) {
- SCReturnInt(0);
- } else {
- ret = 1;
- }
- }
-
- if (modbus->type == MODBUS_TYP_NONE) {
- if (modbus->category == MODBUS_CAT_NONE) {
- if (modbus->function != MODBUS_FUNC_NONE) {
- if (modbus->function == tx->function) {
- if (modbus->has_subfunction) {
- SCLogDebug("looking for Modbus server function %d and subfunction %d",
- modbus->function, modbus->subfunction);
- ret = (modbus->subfunction == (tx->subFunction))? 1 : 0;
- } else {
- SCLogDebug("looking for Modbus server function %d", modbus->function);
- ret = 1;
- }
- } else {
- ret = 0;
- }
- }
- } else {
- SCLogDebug("looking for Modbus category function %d", modbus->category);
- ret = (tx->category & modbus->category)? 1 : 0;
- }
- } else {
- uint8_t access = modbus->type & MODBUS_TYP_ACCESS_MASK;
- uint8_t function = modbus->type & MODBUS_TYP_ACCESS_FUNCTION_MASK;
-
- if (access != MODBUS_TYP_NONE) {
- if ((access & tx->type) && ((function == MODBUS_TYP_NONE) || (function & tx->type))) {
- if (modbus->address != NULL) {
- ret = DetectEngineInspectModbusAddress(tx, modbus->address, access);
-
- if (ret && (modbus->data != NULL)) {
- ret = DetectEngineInspectModbusData(tx, modbus->address->min, modbus->data);
- }
- } else {
- SCLogDebug("looking for Modbus access type %d and function type %d", access, function);
- ret = 1;
- }
- } else {
- ret = 0;
- }
- }
- }
-
- SCReturnInt(ret);
-}
-
#ifdef UNITTESTS /* UNITTESTS */
#include "app-layer-parser.h"
FAIL_IF_NOT(r == 0);
FLOWLOCK_UNLOCK(&f);
- ModbusState *modbus_state = f.alstate;
- FAIL_IF_NULL(modbus_state);
+ FAIL_IF_NULL(f.alstate);
/* do detect */
SigMatchSignatures(&tv, de_ctx, det_ctx, p);
FAIL_IF_NOT(r == 0);
FLOWLOCK_UNLOCK(&f);
- ModbusState *modbus_state = f.alstate;
- FAIL_IF_NULL(modbus_state);
+ FAIL_IF_NULL(f.alstate);
/* do detect */
SigMatchSignatures(&tv, de_ctx, det_ctx, p);
FAIL_IF_NOT(r == 0);
FLOWLOCK_UNLOCK(&f);
- ModbusState *modbus_state = f.alstate;
- FAIL_IF_NULL(modbus_state);
+ FAIL_IF_NULL(f.alstate);
/* do detect */
SigMatchSignatures(&tv, de_ctx, det_ctx, p);
FAIL_IF_NOT(r == 0);
FLOWLOCK_UNLOCK(&f);
- ModbusState *modbus_state = f.alstate;
- FAIL_IF_NULL(modbus_state);
+ FAIL_IF_NULL(f.alstate);
/* do detect */
SigMatchSignatures(&tv, de_ctx, det_ctx, p);
FAIL_IF_NOT(r == 0);
FLOWLOCK_UNLOCK(&f);
- ModbusState *modbus_state = f.alstate;
- FAIL_IF_NULL(modbus_state);
+ FAIL_IF_NULL(f.alstate);
/* do detect */
SigMatchSignatures(&tv, de_ctx, det_ctx, p);
FAIL_IF_NOT(r == 0);
FLOWLOCK_UNLOCK(&f);
- ModbusState *modbus_state = f.alstate;
- FAIL_IF_NULL(modbus_state);
+ FAIL_IF_NULL(f.alstate);
/* do detect */
SigMatchSignatures(&tv, de_ctx, det_ctx, p);
FAIL_IF_NOT(r == 0);
FLOWLOCK_UNLOCK(&f);
- ModbusState *modbus_state = f.alstate;
- FAIL_IF_NULL(modbus_state);
+ FAIL_IF_NULL(f.alstate);
/* do detect */
SigMatchSignatures(&tv, de_ctx, det_ctx, p);
FAIL_IF_NOT(r == 0);
FLOWLOCK_UNLOCK(&f);
- ModbusState *modbus_state = f.alstate;
- FAIL_IF_NULL(modbus_state);
+ FAIL_IF_NULL(f.alstate);
/* do detect */
SigMatchSignatures(&tv, de_ctx, det_ctx, p);
FAIL_IF_NOT(r == 0);
FLOWLOCK_UNLOCK(&f);
- ModbusState *modbus_state = f.alstate;
- FAIL_IF_NULL(modbus_state);
+ FAIL_IF_NULL(f.alstate);
/* do detect */
SigMatchSignatures(&tv, de_ctx, det_ctx, p);
FAIL_IF_NOT(r == 0);
FLOWLOCK_UNLOCK(&f);
- ModbusState *modbus_state = f.alstate;
- FAIL_IF_NULL(modbus_state);
+ FAIL_IF_NULL(f.alstate);
/* do detect */
SigMatchSignatures(&tv, de_ctx, det_ctx, p);
FAIL_IF_NOT(r == 0);
FLOWLOCK_UNLOCK(&f);
- ModbusState *modbus_state = f.alstate;
- FAIL_IF_NULL(modbus_state);
+ FAIL_IF_NULL(f.alstate);
/* do detect */
SigMatchSignatures(&tv, de_ctx, det_ctx, p);
FAIL_IF_NOT(r == 0);
FLOWLOCK_UNLOCK(&f);
- ModbusState *modbus_state = f.alstate;
- FAIL_IF_NULL(modbus_state);
+ FAIL_IF_NULL(f.alstate);
/* do detect */
SigMatchSignatures(&tv, de_ctx, det_ctx, p);
#include "detect-engine.h"
#include "detect-modbus.h"
-#include "detect-engine-modbus.h"
#include "util-debug.h"
#include "util-byte.h"
#include "app-layer-modbus.h"
#include "stream-tcp.h"
-
-/**
- * \brief Regex for parsing the Modbus unit id string
- */
-#define PARSE_REGEX_UNIT_ID "^\\s*\"?\\s*unit\\s+([<>]?\\d+)(<>\\d+)?(,\\s*(.*))?\\s*\"?\\s*$"
-static DetectParseRegex unit_id_parse_regex;
-
-/**
- * \brief Regex for parsing the Modbus function string
- */
-#define PARSE_REGEX_FUNCTION "^\\s*\"?\\s*function\\s*(!?[A-z0-9]+)(,\\s*subfunction\\s+(\\d+))?\\s*\"?\\s*$"
-static DetectParseRegex function_parse_regex;
-
-/**
- * \brief Regex for parsing the Modbus access string
- */
-#define PARSE_REGEX_ACCESS "^\\s*\"?\\s*access\\s*(read|write)\\s*(discretes|coils|input|holding)?(,\\s*address\\s+([<>]?\\d+)(<>\\d+)?(,\\s*value\\s+([<>]?\\d+)(<>\\d+)?)?)?\\s*\"?\\s*$"
-static DetectParseRegex access_parse_regex;
+#include "rust.h"
static int g_modbus_buffer_id = 0;
#ifdef UNITTESTS
-void DetectModbusRegisterTests(void);
+static void DetectModbusRegisterTests(void);
#endif
/** \internal
*/
static void DetectModbusFree(DetectEngineCtx *de_ctx, void *ptr) {
SCEnter();
- DetectModbus *modbus = (DetectModbus *) ptr;
-
- if(modbus) {
- if (modbus->unit_id)
- SCFree(modbus->unit_id);
-
- if (modbus->address)
- SCFree(modbus->address);
-
- if (modbus->data)
- SCFree(modbus->data);
-
- SCFree(modbus);
- }
-}
-
-/** \internal
- *
- * \brief This function is used to parse Modbus parameters in access mode
- *
- * \param de_ctx Pointer to the detection engine context
- * \param str Pointer to the user provided id option
- *
- * \retval Pointer to DetectModbusData on success or NULL on failure
- */
-static DetectModbus *DetectModbusAccessParse(DetectEngineCtx *de_ctx, const char *str)
-{
- SCEnter();
- DetectModbus *modbus = NULL;
-
- char arg[MAX_SUBSTRINGS];
- int ov[MAX_SUBSTRINGS], ret, res;
-
- ret = DetectParsePcreExec(&access_parse_regex, str, 0, 0, ov, MAX_SUBSTRINGS);
- if (ret < 1)
- goto error;
-
- res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 1, arg, MAX_SUBSTRINGS);
- if (res < 0) {
- SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed");
- goto error;
- }
-
- /* We have a correct Modbus option */
- modbus = (DetectModbus *) SCCalloc(1, sizeof(DetectModbus));
- if (unlikely(modbus == NULL))
- goto error;
-
- if (strcmp(arg, "read") == 0)
- modbus->type = MODBUS_TYP_READ;
- else if (strcmp(arg, "write") == 0)
- modbus->type = MODBUS_TYP_WRITE;
- else
- goto error;
-
- if (ret > 2) {
- res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 2, arg, MAX_SUBSTRINGS);
- if (res < 0) {
- SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed");
- goto error;
- }
-
- if (*arg != '\0') {
- if (strcmp(arg, "discretes") == 0) {
- if (modbus->type == MODBUS_TYP_WRITE)
- /* Discrete access is only read access. */
- goto error;
-
- modbus->type |= MODBUS_TYP_DISCRETES;
- }
- else if (strcmp(arg, "coils") == 0) {
- modbus->type |= MODBUS_TYP_COILS;
- }
- else if (strcmp(arg, "input") == 0) {
- if (modbus->type == MODBUS_TYP_WRITE) {
- /* Input access is only read access. */
- goto error;
- }
-
- modbus->type |= MODBUS_TYP_INPUT;
- }
- else if (strcmp(arg, "holding") == 0) {
- modbus->type |= MODBUS_TYP_HOLDING;
- }
- else
- goto error;
- }
-
- if (ret > 4) {
- res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 4, arg, MAX_SUBSTRINGS);
- if (res < 0) {
- SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed");
- goto error;
- }
-
- /* We have a correct address option */
- modbus->address = (DetectModbusValue *) SCCalloc(1, sizeof(DetectModbusValue));
- if (unlikely(modbus->address == NULL))
- goto error;
-
- uint8_t idx;
- if (arg[0] == '>') {
- idx = 1;
- modbus->address->mode = DETECT_MODBUS_GT;
- } else if (arg[0] == '<') {
- idx = 1;
- modbus->address->mode = DETECT_MODBUS_LT;
- } else {
- idx = 0;
- }
- if (StringParseUint16(&modbus->address->min, 10, 0,
- (const char *) (arg + idx)) < 0) {
- SCLogError(SC_ERR_INVALID_VALUE, "Invalid value for min "
- "address: %s", (const char *)(arg + idx));
- goto error;
- }
- SCLogDebug("and min/equal address %d", modbus->address->min);
-
- if (ret > 5) {
- res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 5, arg, MAX_SUBSTRINGS);
- if (res < 0) {
- SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed");
- goto error;
- }
-
- if (*arg != '\0') {
- if (StringParseUint16(&modbus->address->max, 10, 0,
- (const char*) (arg + 2)) < 0) {
- SCLogError(SC_ERR_INVALID_VALUE, "Invalid value for max "
- "address: %s", (const char*)(arg + 2));
- goto error;
- }
- modbus->address->mode = DETECT_MODBUS_RA;
- SCLogDebug("and max address %d", modbus->address->max);
- }
-
- if (ret > 7) {
- res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 7, arg, MAX_SUBSTRINGS);
- if (res < 0) {
- SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed");
- goto error;
- }
-
- if (modbus->address->mode != DETECT_MODBUS_EQ) {
- SCLogError(SC_ERR_CONFLICTING_RULE_KEYWORDS, "rule contains conflicting keywords (address range and value).");
- goto error;
- }
-
- /* We have a correct address option */
- if (modbus->type == MODBUS_TYP_READ)
- /* Value access is only possible in write access. */
- goto error;
-
- modbus->data = (DetectModbusValue *) SCCalloc(1, sizeof(DetectModbusValue));
- if (unlikely(modbus->data == NULL))
- goto error;
-
-
- uint8_t idx_mode;
- if (arg[0] == '>') {
- idx_mode = 1;
- modbus->data->mode = DETECT_MODBUS_GT;
- } else if (arg[0] == '<') {
- idx_mode = 1;
- modbus->data->mode = DETECT_MODBUS_LT;
- } else {
- idx_mode = 0;
- }
- if (StringParseUint16(&modbus->data->min, 10, 0,
- (const char*) (arg + idx_mode)) < 0) {
- SCLogError(SC_ERR_INVALID_VALUE, "Invalid value for "
- "min data: %s", (const char*)(arg + idx_mode));
- goto error;
- }
- SCLogDebug("and min/equal value %d", modbus->data->min);
-
- if (ret > 8) {
- res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 8, arg, MAX_SUBSTRINGS);
- if (res < 0) {
- SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed");
- goto error;
- }
-
- if (*arg != '\0') {
- if (StringParseUint16(&modbus->data->max,
- 10, 0, (const char*) (arg + 2)) < 0) {
- SCLogError(SC_ERR_INVALID_VALUE,
- "Invalid value for max data: %s",
- (const char*)(arg + 2));
- goto error;
- }
- modbus->data->mode = DETECT_MODBUS_RA;
- SCLogDebug("and max value %d", modbus->data->max);
- }
- }
- }
- }
- }
- }
-
- SCReturnPtr(modbus, "DetectModbusAccess");
-
-error:
- if (modbus != NULL)
- DetectModbusFree(de_ctx, modbus);
-
- SCReturnPtr(NULL, "DetectModbus");
-}
-
-/** \internal
- *
- * \brief This function is used to parse Modbus parameters in function mode
- *
- * \param str Pointer to the user provided id option
- *
- * \retval id_d pointer to DetectModbusData on success
- * \retval NULL on failure
- */
-static DetectModbus *DetectModbusFunctionParse(DetectEngineCtx *de_ctx, const char *str)
-{
- SCEnter();
- DetectModbus *modbus = NULL;
-
- char arg[MAX_SUBSTRINGS], *ptr = arg;
- int ov[MAX_SUBSTRINGS], res, ret;
-
- ret = DetectParsePcreExec(&function_parse_regex, str, 0, 0, ov, MAX_SUBSTRINGS);
- if (ret < 1)
- goto error;
-
- res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 1, arg, MAX_SUBSTRINGS);
- if (res < 0) {
- SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed");
- goto error;
- }
-
- /* We have a correct Modbus function option */
- modbus = (DetectModbus *) SCCalloc(1, sizeof(DetectModbus));
- if (unlikely(modbus == NULL))
- goto error;
-
- if (isdigit((unsigned char)ptr[0])) {
- if (StringParseUint8(&modbus->function, 10, 0, (const char *)ptr) < 0) {
- SCLogError(SC_ERR_INVALID_VALUE, "Invalid value for "
- "modbus function: %s", (const char *)ptr);
- goto error;
- }
- /* Function code 0 is managed by decoder_event INVALID_FUNCTION_CODE */
- if (modbus->function == MODBUS_FUNC_NONE) {
- SCLogError(SC_ERR_INVALID_SIGNATURE,
- "Invalid argument \"%d\" supplied to modbus function keyword.",
- modbus->function);
- goto error;
- }
-
- SCLogDebug("will look for modbus function %d", modbus->function);
-
- if (ret > 2) {
- res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 3, arg, MAX_SUBSTRINGS);
- if (res < 0) {
- SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed");
- goto error;
- }
-
- if (StringParseUint16(&modbus->subfunction, 10, 0, (const char *)arg) < 0) {
- SCLogError(SC_ERR_INVALID_VALUE, "Invalid value for "
- "modbus subfunction: %s", (const char*)arg);
- goto error;
- }
- modbus->has_subfunction = true;
-
- SCLogDebug("and subfunction %d", modbus->subfunction);
- }
- } else {
- uint8_t neg = 0;
-
- if (ptr[0] == '!') {
- neg = 1;
- ptr++;
- }
-
- if (strcmp("assigned", ptr) == 0)
- modbus->category = MODBUS_CAT_PUBLIC_ASSIGNED;
- else if (strcmp("unassigned", ptr) == 0)
- modbus->category = MODBUS_CAT_PUBLIC_UNASSIGNED;
- else if (strcmp("public", ptr) == 0)
- modbus->category = MODBUS_CAT_PUBLIC_ASSIGNED | MODBUS_CAT_PUBLIC_UNASSIGNED;
- else if (strcmp("user", ptr) == 0)
- modbus->category = MODBUS_CAT_USER_DEFINED;
- else if (strcmp("reserved", ptr) == 0)
- modbus->category = MODBUS_CAT_RESERVED;
- else if (strcmp("all", ptr) == 0)
- modbus->category = MODBUS_CAT_ALL;
-
- if (neg)
- modbus->category = ~modbus->category;
- SCLogDebug("will look for modbus category function %d", modbus->category);
- }
-
- SCReturnPtr(modbus, "DetectModbusFunction");
-
-error:
- if (modbus != NULL)
- DetectModbusFree(de_ctx, modbus);
-
- SCReturnPtr(NULL, "DetectModbus");
-}
-
-/** \internal
- *
- * \brief This function is used to parse Modbus parameters in unit id mode
- *
- * \param str Pointer to the user provided id option
- *
- * \retval Pointer to DetectModbusUnit on success or NULL on failure
- */
-static DetectModbus *DetectModbusUnitIdParse(DetectEngineCtx *de_ctx, const char *str)
-{
- SCEnter();
- DetectModbus *modbus = NULL;
-
- char arg[MAX_SUBSTRINGS];
- int ov[MAX_SUBSTRINGS], ret, res;
-
- ret = DetectParsePcreExec(&unit_id_parse_regex, str, 0, 0, ov, MAX_SUBSTRINGS);
- if (ret < 1)
- goto error;
-
- res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 1, arg, MAX_SUBSTRINGS);
- if (res < 0) {
- SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed");
- goto error;
- }
-
- if (ret > 3) {
- /* We have more Modbus option */
- const char *str_ptr;
-
- res = pcre_get_substring((char *)str, ov, MAX_SUBSTRINGS, 4, &str_ptr);
- if (res < 0) {
- SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed");
- goto error;
- }
-
- if ((modbus = DetectModbusFunctionParse(de_ctx, str_ptr)) == NULL) {
- if ((modbus = DetectModbusAccessParse(de_ctx, str_ptr)) == NULL) {
- SCLogError(SC_ERR_PCRE_MATCH, "invalid modbus option");
- goto error;
- }
- }
- } else {
- /* We have only unit id Modbus option */
- modbus = (DetectModbus *) SCCalloc(1, sizeof(DetectModbus));
- if (unlikely(modbus == NULL))
- goto error;
- }
-
- /* We have a correct unit id option */
- modbus->unit_id = (DetectModbusValue *) SCCalloc(1, sizeof(DetectModbusValue));
- if (unlikely(modbus->unit_id == NULL))
- goto error;
-
- uint8_t idx;
- if (arg[0] == '>') {
- idx = 1;
- modbus->unit_id->mode = DETECT_MODBUS_GT;
- } else if (arg[0] == '<') {
- idx = 1;
- modbus->unit_id->mode = DETECT_MODBUS_LT;
- } else {
- idx = 0;
- }
- if (StringParseUint16(&modbus->unit_id->min, 10, 0, (const char *) (arg + idx)) < 0) {
- SCLogError(SC_ERR_INVALID_VALUE, "Invalid value for "
- "modbus min unit id: %s", (const char*)(arg + idx));
- goto error;
- }
- SCLogDebug("and min/equal unit id %d", modbus->unit_id->min);
-
- if (ret > 2) {
- res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 2, arg, MAX_SUBSTRINGS);
- if (res < 0) {
- SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed");
- goto error;
- }
-
- if (*arg != '\0') {
- if (StringParseUint16(&modbus->unit_id->max, 10, 0, (const char *) (arg + 2)) < 0) {
- SCLogError(SC_ERR_INVALID_VALUE, "Invalid value for "
- "modbus max unit id: %s", (const char*)(arg + 2));
- goto error;
- }
- modbus->unit_id->mode = DETECT_MODBUS_RA;
- SCLogDebug("and max unit id %d", modbus->unit_id->max);
- }
+ if (ptr != NULL) {
+ rs_modbus_free(ptr);
}
-
- SCReturnPtr(modbus, "DetectModbusUnitId");
-
-error:
- if (modbus != NULL)
- DetectModbusFree(de_ctx, modbus);
-
- SCReturnPtr(NULL, "DetectModbus");
+ SCReturn;
}
-
/** \internal
*
* \brief this function is used to add the parsed "id" option into the current signature
static int DetectModbusSetup(DetectEngineCtx *de_ctx, Signature *s, const char *str)
{
SCEnter();
- DetectModbus *modbus = NULL;
+ DetectModbusRust *modbus = NULL;
SigMatch *sm = NULL;
if (DetectSignatureSetAppProto(s, ALPROTO_MODBUS) != 0)
return -1;
- if ((modbus = DetectModbusUnitIdParse(de_ctx, str)) == NULL) {
- if ((modbus = DetectModbusFunctionParse(de_ctx, str)) == NULL) {
- if ((modbus = DetectModbusAccessParse(de_ctx, str)) == NULL) {
- SCLogError(SC_ERR_PCRE_MATCH, "invalid modbus option");
- goto error;
- }
- }
+ if ((modbus = rs_modbus_parse(str)) == NULL) {
+ SCLogError(SC_ERR_PCRE_MATCH, "invalid modbus option");
+ goto error;
}
/* Okay so far so good, lets get this into a SigMatch and put it in the Signature. */
SCReturnInt(-1);
}
+static int DetectModbusMatch(DetectEngineThreadCtx *det_ctx, Flow *f, uint8_t flags, void *state,
+ void *txv, const Signature *s, const SigMatchCtx *ctx)
+{
+ return rs_modbus_inspect(txv, (void *)ctx);
+}
+
+/** \brief Do the content inspection & validation for a signature
+ *
+ * \param de_ctx Detection engine context
+ * \param det_ctx Detection engine thread context
+ * \param s Signature to inspect ( and sm: SigMatch to inspect)
+ * \param f Flow
+ * \param flags App layer flags
+ * \param alstate App layer state
+ * \param txv Pointer to Modbus Transaction structure
+ *
+ * \retval 0 no match or 1 match
+ */
+static int DetectEngineInspectModbus(DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
+ const struct DetectEngineAppInspectionEngine_ *engine, const Signature *s, Flow *f,
+ uint8_t flags, void *alstate, void *txv, uint64_t tx_id)
+{
+ return DetectEngineInspectGenericList(
+ de_ctx, det_ctx, s, engine->smd, f, flags, alstate, txv, tx_id);
+}
+
/**
* \brief Registration function for Modbus keyword
*/
void DetectModbusRegister(void)
{
- SCEnter();
- sigmatch_table[DETECT_AL_MODBUS].name = "modbus";
- sigmatch_table[DETECT_AL_MODBUS].desc = "match on various properties of Modbus requests";
- sigmatch_table[DETECT_AL_MODBUS].url = "/rules/modbus-keyword.html#modbus-keyword";
- sigmatch_table[DETECT_AL_MODBUS].Match = NULL;
- sigmatch_table[DETECT_AL_MODBUS].Setup = DetectModbusSetup;
- sigmatch_table[DETECT_AL_MODBUS].Free = DetectModbusFree;
+ sigmatch_table[DETECT_AL_MODBUS].name = "modbus";
+ sigmatch_table[DETECT_AL_MODBUS].desc = "match on various properties of Modbus requests";
+ sigmatch_table[DETECT_AL_MODBUS].url = "/rules/modbus-keyword.html#modbus-keyword";
+ sigmatch_table[DETECT_AL_MODBUS].Match = NULL;
+ sigmatch_table[DETECT_AL_MODBUS].Setup = DetectModbusSetup;
+ sigmatch_table[DETECT_AL_MODBUS].Free = DetectModbusFree;
+ sigmatch_table[DETECT_AL_MODBUS].AppLayerTxMatch = DetectModbusMatch;
#ifdef UNITTESTS
sigmatch_table[DETECT_AL_MODBUS].RegisterTests = DetectModbusRegisterTests;
#endif
- DetectSetupParseRegexes(PARSE_REGEX_UNIT_ID, &unit_id_parse_regex);
- DetectSetupParseRegexes(PARSE_REGEX_FUNCTION, &function_parse_regex);
- DetectSetupParseRegexes(PARSE_REGEX_ACCESS, &access_parse_regex);
DetectAppLayerInspectEngineRegister2(
"modbus", ALPROTO_MODBUS, SIG_FLAG_TOSERVER, 0, DetectEngineInspectModbus, NULL);
#ifdef UNITTESTS /* UNITTESTS */
#include "util-unittest.h"
+/** Convert rust structure to C for regression tests.
+ *
+ * Note: Newly allocated `DetectModbus` structure must be freed.
+ *
+ * TODO: remove this after regression testing commit.
+ */
+static DetectModbusValue *DetectModbusValueRustToC(uint16_t min, uint16_t max)
+{
+ DetectModbusValue *value = SCMalloc(sizeof(*value));
+ FAIL_IF_NULL(value);
+
+ value->min = min;
+ value->max = max;
+
+ if (min == max) {
+ value->mode = DETECT_MODBUS_EQ;
+ } else if (min == 0) {
+ value->mode = DETECT_MODBUS_LT;
+ } else if (max == UINT16_MAX) {
+ value->mode = DETECT_MODBUS_GT;
+ } else {
+ value->mode = DETECT_MODBUS_RA;
+ }
+
+ return value;
+}
+
+static DetectModbus *DetectModbusRustToC(DetectModbusRust *ctx)
+{
+ DetectModbus *modbus = SCMalloc(sizeof(*modbus));
+ FAIL_IF_NULL(modbus);
+
+ modbus->category = rs_modbus_get_category(ctx);
+ modbus->function = rs_modbus_get_function(ctx);
+ modbus->subfunction = rs_modbus_get_subfunction(ctx);
+ modbus->has_subfunction = rs_modbus_get_has_subfunction(ctx);
+ modbus->type = rs_modbus_get_access_type(ctx);
+
+ modbus->unit_id = DetectModbusValueRustToC(
+ rs_modbus_get_unit_id_min(ctx), rs_modbus_get_unit_id_max(ctx));
+
+ modbus->address = DetectModbusValueRustToC(
+ rs_modbus_get_address_min(ctx), rs_modbus_get_address_max(ctx));
+
+ modbus->data =
+ DetectModbusValueRustToC(rs_modbus_get_data_min(ctx), rs_modbus_get_data_max(ctx));
+
+ return modbus;
+}
+
+static void DetectModbusCFree(DetectModbus *modbus)
+{
+ if (modbus) {
+ if (modbus->unit_id)
+ SCFree(modbus->unit_id);
+
+ if (modbus->address)
+ SCFree(modbus->address);
+
+ if (modbus->data)
+ SCFree(modbus->data);
+
+ SCFree(modbus);
+ }
+}
+
/** \test Signature containing a function. */
static int DetectModbusTest01(void)
{
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
- modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
+ modbus = DetectModbusRustToC(
+ (DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
FAIL_IF_NOT(modbus->function == 1);
+ DetectModbusCFree(modbus);
SigGroupCleanup(de_ctx);
SigCleanSignatures(de_ctx);
DetectEngineCtxFree(de_ctx);
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
- modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
+ modbus = DetectModbusRustToC(
+ (DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
FAIL_IF_NOT(modbus->function == 8);
FAIL_IF_NOT(modbus->subfunction == 4);
+ DetectModbusCFree(modbus);
SigGroupCleanup(de_ctx);
SigCleanSignatures(de_ctx);
DetectEngineCtxFree(de_ctx);
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
- modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
+ modbus = DetectModbusRustToC(
+ (DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
FAIL_IF_NOT(modbus->category == MODBUS_CAT_RESERVED);
+ DetectModbusCFree(modbus);
SigGroupCleanup(de_ctx);
SigCleanSignatures(de_ctx);
DetectEngineCtxFree(de_ctx);
DetectEngineCtx *de_ctx = NULL;
DetectModbus *modbus = NULL;
- uint8_t category = ~MODBUS_CAT_PUBLIC_ASSIGNED;
-
de_ctx = DetectEngineCtxInit();
FAIL_IF_NULL(de_ctx);
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
- modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
+ modbus = DetectModbusRustToC(
+ (DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
- FAIL_IF_NOT(modbus->category == category);
+ FAIL_IF(modbus->category & MODBUS_CAT_PUBLIC_ASSIGNED);
+ FAIL_IF_NOT(modbus->category & MODBUS_CAT_PUBLIC_UNASSIGNED);
+ FAIL_IF_NOT(modbus->category & MODBUS_CAT_USER_DEFINED);
+ FAIL_IF_NOT(modbus->category & MODBUS_CAT_RESERVED);
+ DetectModbusCFree(modbus);
SigGroupCleanup(de_ctx);
SigCleanSignatures(de_ctx);
DetectEngineCtxFree(de_ctx);
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
- modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
+ modbus = DetectModbusRustToC(
+ (DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
FAIL_IF_NOT(modbus->type == MODBUS_TYP_READ);
+ DetectModbusCFree(modbus);
SigGroupCleanup(de_ctx);
SigCleanSignatures(de_ctx);
DetectEngineCtxFree(de_ctx);
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
- modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
+ modbus = DetectModbusRustToC(
+ (DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
FAIL_IF_NOT(modbus->type == type);
+ DetectModbusCFree(modbus);
SigGroupCleanup(de_ctx);
SigCleanSignatures(de_ctx);
DetectEngineCtxFree(de_ctx);
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
- modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
+ modbus = DetectModbusRustToC(
+ (DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
FAIL_IF_NOT(modbus->type == type);
FAIL_IF_NOT((*modbus->address).mode == mode);
FAIL_IF_NOT((*modbus->address).min == 1000);
+ DetectModbusCFree(modbus);
SigGroupCleanup(de_ctx);
SigCleanSignatures(de_ctx);
DetectEngineCtxFree(de_ctx);
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
- modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
+ modbus = DetectModbusRustToC(
+ (DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
FAIL_IF_NOT(modbus->type == type);
FAIL_IF_NOT((*modbus->address).mode == mode);
FAIL_IF_NOT((*modbus->address).min == 500);
+ DetectModbusCFree(modbus);
SigGroupCleanup(de_ctx);
SigCleanSignatures(de_ctx);
DetectEngineCtxFree(de_ctx);
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
- modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
+ modbus = DetectModbusRustToC(
+ (DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
FAIL_IF_NOT(modbus->type == type);
FAIL_IF_NOT((*modbus->address).mode == addressMode);
FAIL_IF_NOT((*modbus->data).min == 500);
FAIL_IF_NOT((*modbus->data).max == 1000);
+ DetectModbusCFree(modbus);
SigGroupCleanup(de_ctx);
SigCleanSignatures(de_ctx);
DetectEngineCtxFree(de_ctx);
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
- modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
+ modbus = DetectModbusRustToC(
+ (DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
FAIL_IF_NOT((*modbus->unit_id).min == 10);
FAIL_IF_NOT((*modbus->unit_id).mode == mode);
+ DetectModbusCFree(modbus);
SigGroupCleanup(de_ctx);
SigCleanSignatures(de_ctx);
DetectEngineCtxFree(de_ctx);
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
- modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
+ modbus = DetectModbusRustToC(
+ (DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
FAIL_IF_NOT((*modbus->unit_id).min == 10);
FAIL_IF_NOT((*modbus->unit_id).mode == mode);
FAIL_IF_NOT(modbus->function == 8);
FAIL_IF_NOT(modbus->subfunction == 4);
+ DetectModbusCFree(modbus);
SigGroupCleanup(de_ctx);
SigCleanSignatures(de_ctx);
DetectEngineCtxFree(de_ctx);
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
- modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
+ modbus = DetectModbusRustToC(
+ (DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
FAIL_IF_NOT((*modbus->unit_id).min == 10);
FAIL_IF_NOT((*modbus->unit_id).mode == mode);
FAIL_IF_NOT((*modbus->address).mode == mode);
FAIL_IF_NOT((*modbus->address).min == 1000);
+ DetectModbusCFree(modbus);
SigGroupCleanup(de_ctx);
SigCleanSignatures(de_ctx);
DetectEngineCtxFree(de_ctx);
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
- modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
+ modbus = DetectModbusRustToC(
+ (DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
FAIL_IF_NOT((*modbus->unit_id).min == 10);
FAIL_IF_NOT((*modbus->unit_id).max == 500);
FAIL_IF_NOT((*modbus->unit_id).mode == mode);
+ DetectModbusCFree(modbus);
SigGroupCleanup(de_ctx);
SigCleanSignatures(de_ctx);
DetectEngineCtxFree(de_ctx);
#include "app-layer-ssh.h"
#include "app-layer-ftp.h"
#include "app-layer-smtp.h"
-#include "app-layer-modbus.h"
#include "app-layer-enip.h"
#include "app-layer-dnp3.h"
#include "app-layer-smb.h"