diff --git a/scripts/base/protocols/modbus/main.bro b/scripts/base/protocols/modbus/main.bro index 3c3d1db97e..3837df74ed 100644 --- a/scripts/base/protocols/modbus/main.bro +++ b/scripts/base/protocols/modbus/main.bro @@ -3,24 +3,71 @@ module Modbus; export { + redef enum Log::ID += { LOG }; + type Info: record { + ## Time of the request. + ts: time &log; + ## Unique identifier for the connnection. + uid: string &log; + ## Identifier for the connection. + id: conn_id &log; + ## The name of the function message that was sent. + func: string &log &optional; + ## The status of the response. + success: bool &log &default=T; + ## The exception if the response was a failure. + exception: string &log &optional; + }; + + ## Event that can be handled to access the Modbus record as it is sent on + ## to the logging framework. + global log_modbus: event(rec: Info); } +redef record connection += { + modbus: Info &optional; +}; + # Configure DPD and the packet filter. redef capture_filters += { ["modbus"] = "tcp port 502" }; redef dpd_config += { [ANALYZER_MODBUS] = [$ports = set(502/tcp)] }; redef likely_server_ports += { 502/tcp }; - -event modbus_exception(c: connection, header: ModbusHeaders, code: count) +event bro_init() &priority=5 { - print fmt("%.6f %s There was an exception: %s", network_time(), c$id, exception_codes[code]); + Log::create_stream(Modbus::LOG, [$columns=Info, $ev=log_modbus]); } -event modbus_message(c: connection, header: ModbusHeaders, is_orig: bool) +event modbus_message(c: connection, headers: ModbusHeaders, is_orig: bool) &priority=5 { - #if ( function_codes[header$function_code] in set("READ_MULTIPLE_REGISTERS", "READ_WRITE_REGISTERS", "WRITE_MULTIPLE_REGISTERS") ) - # return; + if ( ! c?$modbus ) + { + c$modbus = [$ts=network_time(), $uid=c$uid, $id=c$id]; + } - print fmt("%.6f %s %s: %s", network_time(), c$id, is_orig ? "request":"response", function_codes[header$function_code]); + c$modbus$ts = network_time(); + c$modbus$func = function_codes[headers$function_code]; + + if ( ! is_orig && + ( headers$function_code >= 0x81 || headers$function_code <= 0x98 ) ) + c$modbus$success = F; + else + c$modbus$success = T; } + +event modbus_message(c: connection, headers: ModbusHeaders, is_orig: bool) &priority=-5 + { + # Don't log now if this is an exception (log in the exception event handler) + if ( c$modbus$success ) + Log::write(LOG, c$modbus); + } + +event modbus_exception(c: connection, headers: ModbusHeaders, code: count) &priority=5 + { + c$modbus$exception = exception_codes[code]; + Log::write(LOG, c$modbus); + + delete c$modbus$exception; + } + diff --git a/scripts/policy/protocols/modbus/known-masters-slaves.bro b/scripts/policy/protocols/modbus/known-masters-slaves.bro new file mode 100644 index 0000000000..b4bf2f0577 --- /dev/null +++ b/scripts/policy/protocols/modbus/known-masters-slaves.bro @@ -0,0 +1,56 @@ +##! Script for tracking known Modbus masters and slaves. +##! +##! .. todo: This script needs a lot of work. What might be more interesting is to track +##! master/slave relationships based on commands sent and successful (non-exception) +##! responses. + +module Known; + +export { + redef enum Log::ID += { MODBUS_LOG }; + + type ModbusDeviceType: enum { + MODBUS_MASTER, + MODBUS_SLAVE, + }; + + type ModbusInfo: record { + ## The time the device was discovered. + ts: time &log; + ## The IP address of the host. + host: addr &log; + ## The type of device being tracked. + device_type: ModbusDeviceType &log; + }; + + ## The Modbus nodes being tracked. + global modbus_nodes: set[addr, ModbusDeviceType] &create_expire=1day &redef; + + ## Event that can be handled to access the loggable record as it is sent + ## on to the logging framework. + global log_known_modbus: event(rec: ModbusInfo); +} + +event bro_init() &priority=5 + { + Log::create_stream(Known::MODBUS_LOG, [$columns=ModbusInfo, $ev=log_known_modbus]); + } + +event modbus_message(c: connection, headers: ModbusHeaders, is_orig: bool) + { + local master = c$id$orig_h; + local slave = c$id$resp_h; + + if ( [master, MODBUS_MASTER] !in modbus_nodes ) + { + add modbus_nodes[master, MODBUS_MASTER]; + Log::write(MODBUS_LOG, [$ts=network_time(), $host=master, $device_type=MODBUS_MASTER]); + } + + if ( [slave, MODBUS_SLAVE] !in modbus_nodes ) + { + add modbus_nodes[slave, MODBUS_SLAVE]; + Log::write(MODBUS_LOG, [$ts=network_time(), $host=slave, $device_type=MODBUS_SLAVE]); + } + + } diff --git a/scripts/policy/protocols/modbus/track-memmap.bro b/scripts/policy/protocols/modbus/track-memmap.bro new file mode 100644 index 0000000000..d0df2a8e0d --- /dev/null +++ b/scripts/policy/protocols/modbus/track-memmap.bro @@ -0,0 +1,94 @@ +##! This script tracks the memory map of holding (read/write) registers and logs +##! changes as they are discovered. +##! +##! .. todo: Not all register reads and write functions are being supported yet. +module Modbus; + +export { + redef enum Log::ID += { Modbus::REGISTER_CHANGE_LOG }; + + ## The hosts that should have memory mapping enabled. + const track_memmap: Host = ALL_HOSTS &redef; + + type MemmapInfo: record { + ts: time &log; + uid: string &log; + id: conn_id &log; + register: count &log; + old_val: count &log; + new_val: count &log; + delta: interval &log; + }; + + type RegisterValue: record { + last_set: time; + value: count; + }; + + ## Indexed on the device register value and yielding the register value. + type Registers: table[count] of RegisterValue; + + ## The memory map of slaves is tracked with this variable. + global device_registers: table[addr] of Registers; + + ## This event is generated every time a register is seen to be different than + ## it was previously seen to be. + global changed_register: event(c: connection, register: count, old_val: count, new_val: count, delta: interval); +} + +redef record Modbus::Info += { + track_address: count &default=0; +}; + +event bro_init() &priority=5 + { + Log::create_stream(Modbus::REGISTER_CHANGE_LOG, [$columns=MemmapInfo]); + } + +event modbus_read_holding_registers_request(c: connection, headers: ModbusHeaders, start_address: count, quantity: count) + { + c$modbus$track_address = start_address+1; + } + +event modbus_read_holding_registers_response(c: connection, headers: ModbusHeaders, registers: ModbusRegisters) + { + local slave = c$id$resp_h; + + if ( ! addr_matches_host(slave, track_memmap ) ) + return; + + if ( slave !in device_registers ) + device_registers[slave] = table(); + + local slave_regs = device_registers[slave]; + for ( i in registers ) + { + if ( c$modbus$track_address in slave_regs ) + { + if ( slave_regs[c$modbus$track_address]$value != registers[i] ) + { + local delta = network_time() - slave_regs[c$modbus$track_address]$last_set; + event Modbus::changed_register(c, c$modbus$track_address, + slave_regs[c$modbus$track_address]$value, registers[i], + delta); + + slave_regs[c$modbus$track_address]$last_set = network_time(); + slave_regs[c$modbus$track_address]$value = registers[i]; + } + } + else + { + local tmp_reg: RegisterValue = [$last_set=network_time(), $value=registers[i]]; + slave_regs[c$modbus$track_address] = tmp_reg; + } + + ++c$modbus$track_address; + } + } + +event Modbus::changed_register(c: connection, register: count, old_val: count, new_val: count, delta: interval) + { + local rec: MemmapInfo = [$ts=network_time(), $uid=c$uid, $id=c$id, + $register=register, $old_val=old_val, $new_val=new_val, $delta=delta]; + Log::write(REGISTER_CHANGE_LOG, rec); + } diff --git a/src/event.bif b/src/event.bif index 480ffd21c5..199c95dbf2 100644 --- a/src/event.bif +++ b/src/event.bif @@ -6547,146 +6547,300 @@ event netflow_v5_header%(h: nf_v5_header%); ## .. bro:see:: netflow_v5_record event netflow_v5_record%(r: nf_v5_record%); -## Event for any function code whether or not it's supported. +## Generated for any modbus message regardless of if the particular function +## is further supported or not. ## -## TODO-Dina: Document event. -event modbus_message%(c: connection, header: ModbusHeaders, is_orig: bool%); - -## Event that parses modbus exception +## c: The connection. ## -## TODO-Dina: Document event. -event modbus_exception%(c: connection, header: ModbusHeaders, code: count%); - -## Event that passes modbus request function code=1 +## headers: The headers for the modbus function. ## -## TODO-Dina: Document event. -event modbus_read_coils_request%(c: connection, header: ModbusHeaders, start_address: count, quantity: count%); +## is_orig: True if the event is raised for the originator side. +event modbus_message%(c: connection, headers: ModbusHeaders, is_orig: bool%); -## Event that passes modbus response function code=1 +## Generated for any modbus exception message. ## -## TODO-Dina: Document event. -event modbus_read_coils_response%(c: connection, header: ModbusHeaders, coils: ModbusCoils%); - -## Event that passes modbus request function code=2 +## c: The connection. ## -## TODO-Dina: Document event. -event modbus_read_discrete_inputs_request%(c: connection, header: ModbusHeaders, start_address: count, quantity: count%); - -## Event that passes modbus response function code=2 +## headers: The headers for the modbus function. ## -## TODO-Dina: Document event. -event modbus_read_discrete_inputs_response%(c: connection, header: ModbusHeaders, coils: ModbusCoils%); +## code: The exception code. +event modbus_exception%(c: connection, headers: ModbusHeaders, code: count%); -## Event that passes modbus request function code=3 +## Generated for a Modbus read coils request. ## -## TODO-Dina: Document event. -event modbus_read_holding_registers_request%(c: connection, header: ModbusHeaders, start_address: count, quantity: count%); - -## Event that passes modbus response function code=3 +## c: The connection. ## -## TODO-Dina: Document event. -event modbus_read_holding_registers_response%(c: connection, header: ModbusHeaders, registers: ModbusRegisters%); - -## Event that passes modbus request function code =4 +## headers: The headers for the modbus function. ## -## TODO-Dina: Document event. -event modbus_read_input_registers_request%(c: connection, header: ModbusHeaders, start_address: count, quantity: count%); - -## Event that passes modbus response function code=4 +## start_address: The memory address where of the first coil to be read. ## -## TODO-Dina: Document event. -event modbus_read_input_registers_response%(c: connection, header: ModbusHeaders, registers: ModbusRegisters%); +## quantity: The number of coils to be read. +event modbus_read_coils_request%(c: connection, headers: ModbusHeaders, start_address: count, quantity: count%); -## Event that passes modbus request function code=5 +## Generated for a Modbus read coils response. ## -## TODO-Dina: Document event. -event modbus_write_single_coil_request%(c: connection, header: ModbusHeaders, start_address: count, on_off: count, other: count%); - -## Event that passes modbus request function code=5 +## c: The connection. ## -## TODO-Dina: Document event. -event modbus_write_single_coil_response%(c: connection, header: ModbusHeaders, start_address: count, on_off: count, other: count%); - -## Event that passes modbus request function code=6 +## headers: The headers for the modbus function. ## -## TODO-Dina: Document event. -event modbus_write_single_register_request%(c: connection, header: ModbusHeaders, start_address: count, value: count%); +## coils: The coil values returned from the device. +event modbus_read_coils_response%(c: connection, headers: ModbusHeaders, coils: ModbusCoils%); -## Event that passes modbus response function code=6 +## Generated for a Modbus read discrete inputs request. ## -## TODO-Dina: Document event. -event modbus_write_single_register_response%(c: connection, header: ModbusHeaders, start_address: count, value: count%); - - -## Event that passes modbus request function code=15 +## c: The connection. ## -## TODO-Dina: Document event. -event modbus_write_multiple_coils_request%(c: connection, header: ModbusHeaders, start_address: count, coils: ModbusCoils%); - -## Event that passes modbus response function code=15 +## headers: The headers for the modbus function. ## -## TODO-Dina: Document event. -event modbus_write_multiple_coils_response%(c: connection, header: ModbusHeaders, start_address: count, quantity: count%); - -## Event that passes modbus request function code=16 +## start_address: The memory address of the first coil to be read. ## -## TODO-Dina: Document event. -event modbus_write_multiple_registers_request%(c: connection, header: ModbusHeaders, start_address: count, registers: ModbusRegisters%); +## quantity: The number of coils to be read. +event modbus_read_discrete_inputs_request%(c: connection, headers: ModbusHeaders, start_address: count, quantity: count%); -## Event that passes modbus response function code=16 +## Generated for a Modbus read discrete inputs response. ## -## TODO-Dina: Document event. -event modbus_write_multiple_registers_response%(c: connection, header: ModbusHeaders, start_address: count, quantity: count%); +## c: The connection. +## +## headers: The headers for the modbus function. +## +## coils: The coil values returned from the device. +event modbus_read_discrete_inputs_response%(c: connection, headers: ModbusHeaders, coils: ModbusCoils%); -## Event that passes modbus request function code=20 +## Generated for a Modbus read holding registers request. ## -## TODO-Dina: Document event. -event modbus_read_file_record_request%(c: connection, header: ModbusHeaders%); +## c: The connection. +## +## headers: The headers for the modbus function. +## +## start_address: The memory address of the first register to be read. +## +## quantity: The number of registers to be read. +event modbus_read_holding_registers_request%(c: connection, headers: ModbusHeaders, start_address: count, quantity: count%); -## Event that passes modbus response function code=20 +## Generated for a Modbus read holding registers response. ## -## TODO-Dina: Document event. -event modbus_read_file_record_response%(c: connection, header: ModbusHeaders%); +## c: The connection. +## +## headers: The headers for the modbus function. +## +## registers: The register values returned from the device. +event modbus_read_holding_registers_response%(c: connection, headers: ModbusHeaders, registers: ModbusRegisters%); -## Event that passes modbus request function code=21 +## Generated for a Modbus read input registers request. ## -## TODO-Dina: Document event. -event modbus_write_file_record_request%(c: connection, header: ModbusHeaders%); +## c: The connection. +## +## headers: The headers for the modbus function. +## +## start_address: The memory address of the first register to be read. +## +## quantity: The number of registers to be read. +event modbus_read_input_registers_request%(c: connection, headers: ModbusHeaders, start_address: count, quantity: count%); -## Event that passes modbus response function code=21 +## Generated for a Modbus read input registers response. ## -## TODO-Dina: Document event. -event modbus_write_file_record_response%(c: connection, header: ModbusHeaders%); +## c: The connection. +## +## headers: The headers for the modbus function. +## +## registers: The register values returned from the device. +event modbus_read_input_registers_response%(c: connection, headers: ModbusHeaders, registers: ModbusRegisters%); -## Event that passes modbus request function code=22 +## Generated for a Modbus write single coil request. ## -## TODO-Dina: Document event. -event modbus_mask_write_register_request%(c: connection, header: ModbusHeaders, start_address: count, and_mask: count, or_mask: count%); +## c: The connection. +## +## headers: The headers for the modbus function. +## +## address: The memory address of the coil to be written. +## +## value: The value to be written to the coil. +event modbus_write_single_coil_request%(c: connection, headers: ModbusHeaders, address: count, value: bool%); -## Event that passes modbus response function code=22 +## Generated for a Modbus write single coil response. ## -## TODO-Dina: Document event. -event modbus_mask_write_register_response%(c: connection, header: ModbusHeaders, start_address: count, and_mask: count, or_mask: count%); +## c: The connection. +## +## headers: The headers for the modbus function. +## +## address: The memory address of the coil that was written. +## +## value: The value that was written to the coil. +event modbus_write_single_coil_response%(c: connection, headers: ModbusHeaders, address: count, value: bool%); -## Event that passes modbus request function code=23 +## Generated for a Modbus write single register request. ## -## TODO-Dina: Document event. -event modbus_read_write_multiple_registers_request%(c: connection, header: ModbusHeaders, read_start_address: count, read_quantity: count, write_start_address: count, write_registers: ModbusRegisters%); +## c: The connection. +## +## headers: The headers for the modbus function. +## +## address: The memory address of the register to be written. +## +## value: The value to be written to the register. +event modbus_write_single_register_request%(c: connection, headers: ModbusHeaders, address: count, value: count%); -## Event that passes modbus response function code=23 +## Generated for a Modbus write single register response. ## -## TODO-Dina: Document event. -event modbus_read_write_multiple_registers_response%(c: connection, header: ModbusHeaders, written_registers: ModbusRegisters%); +## c: The connection. +## +## headers: The headers for the modbus function. +## +## address: The memory address of the register that was written. +## +## value: The value that was written to the register. +event modbus_write_single_register_response%(c: connection, headers: ModbusHeaders, address: count, value: count%); -## Event that passes modbus request function code=24 +## Generated for a Modbus write multiple coils request. ## -## TODO-Dina: Document event. -event modbus_read_fifo_queue_request%(c: connection, header: ModbusHeaders, start_address: count%); +## c: The connection. +## +## headers: The headers for the modbus function. +## +## start_address: The memory address of the first coil to be written. +## +## value: The values to be written to the coils. +event modbus_write_multiple_coils_request%(c: connection, headers: ModbusHeaders, start_address: count, coils: ModbusCoils%); -## Event that passes modbus response function code=24 +## Generated for a Modbus write multiple coils response. ## -## TODO-Dina: Document event. -event modbus_read_fifo_queue_response%(c: connection, header: ModbusHeaders, fifos: ModbusRegisters%); +## c: The connection. +## +## headers: The headers for the modbus function. +## +## start_address: The memory address of the first coil that was written. +## +## quantity: The quantity of coils that were written. +event modbus_write_multiple_coils_response%(c: connection, headers: ModbusHeaders, start_address: count, quantity: count%); + +## Generated for a Modbus write multiple registers request. +## +## c: The connection. +## +## headers: The headers for the modbus function. +## +## start_address: The memory address of the first register to be written. +## +## registers: The values to be written to the registers. +event modbus_write_multiple_registers_request%(c: connection, headers: ModbusHeaders, start_address: count, registers: ModbusRegisters%); + +## Generated for a Modbus write multiple registers response. +## +## c: The connection. +## +## headers: The headers for the modbus function. +## +## start_address: The memory address of the first register that was written. +## +## quantity: The quantity of registers that were written. +event modbus_write_multiple_registers_response%(c: connection, headers: ModbusHeaders, start_address: count, quantity: count%); + +## Generated for a Modbus read file record request. +## +## c: The connection. +## +## headers: The headers for the modbus function. +## +## .. note: This event is incomplete. The information from the data structure is not +## yet passed through to the event. +event modbus_read_file_record_request%(c: connection, headers: ModbusHeaders%); + +## Generated for a Modbus read file record response. +## +## c: The connection. +## +## headers: The headers for the modbus function. +## +## .. note: This event is incomplete. The information from the data structure is not +## yet passed through to the event. +event modbus_read_file_record_response%(c: connection, headers: ModbusHeaders%); + +## Generated for a Modbus write file record request. +## +## c: The connection. +## +## headers: The headers for the modbus function. +## +## .. note: This event is incomplete. The information from the data structure is not +## yet passed through to the event. +event modbus_write_file_record_request%(c: connection, headers: ModbusHeaders%); + +## Generated for a Modbus write file record response. +## +## c: The connection. +## +## headers: The headers for the modbus function. +## +## .. note: This event is incomplete. The information from the data structure is not +## yet passed through to the event. +event modbus_write_file_record_response%(c: connection, headers: ModbusHeaders%); + +## Generated for a Modbus mask write register request. +## +## c: The connection. +## +## headers: The headers for the modbus function. +## +## address: The memory address of the register where the masks should be applied. +## +## and_mask: The value of the logical AND mask to apply to the register. +## +## or_mask: The value of the logical OR mask to apply to the register. +event modbus_mask_write_register_request%(c: connection, headers: ModbusHeaders, address: count, and_mask: count, or_mask: count%); + +## Generated for a Modbus mask write register request. +## +## c: The connection. +## +## headers: The headers for the modbus function. +## +## address: The memory address of the register where the masks were applied. +## +## and_mask: The value of the logical AND mask applied register. +## +## or_mask: The value of the logical OR mask applied to the register. +event modbus_mask_write_register_response%(c: connection, headers: ModbusHeaders, address: count, and_mask: count, or_mask: count%); + +## Generated for a Modbus read/write multiple registers request. +## +## c: The connection. +## +## headers: The headers for the modbus function. +## +## read_start_address: The memory address of the first register to be read. +## +## read_quantity: The number of registers to read. +## +## write_start_address: The memory address of the first register to be written. +## +## write_registers: The values to be written to the registers. +event modbus_read_write_multiple_registers_request%(c: connection, headers: ModbusHeaders, read_start_address: count, read_quantity: count, write_start_address: count, write_registers: ModbusRegisters%); + +## Generated for a Modbus read/write multiple registers response. +## +## c: The connection. +## +## headers: The headers for the modbus function. +## +## written_registers: The register values read from the registers specified in the request. +event modbus_read_write_multiple_registers_response%(c: connection, headers: ModbusHeaders, written_registers: ModbusRegisters%); + +## Generated for a Modbus read FIFO queue request. +## +## c: The connection. +## +## headers: The headers for the modbus function. +## +## start_address: The address of the FIFO queue to read. +event modbus_read_fifo_queue_request%(c: connection, headers: ModbusHeaders, start_address: count%); + +## Generated for a Modbus read FIFO queue response. +## +## c: The connection. +## +## headers: The headers for the modbus function. +## +## fifos: The register values read from the FIFO queue on the device. +event modbus_read_fifo_queue_response%(c: connection, headers: ModbusHeaders, fifos: ModbusRegisters%); ## Raised for informational messages reported via Bro's reporter framework. Such ## messages may be generated internally by the event engine and also by other diff --git a/src/modbus-analyzer.pac b/src/modbus-analyzer.pac index fd3e45486c..bf32cecddb 100644 --- a/src/modbus-analyzer.pac +++ b/src/modbus-analyzer.pac @@ -194,11 +194,23 @@ refine flow ModbusTCP_Flow += { %{ if ( ::modbus_write_single_coil_request ) { + int val; + if ( ${message.value} == 0x0000 ) + val = 0; + else if ( ${message.value} == 0xFF00 ) + val = 1; + else + { + connection()->bro_analyzer()->ProtocolViolation(fmt("invalid value for modbus write single coil request %d", + ${message.value})); + return false; + } + BifEvent::generate_modbus_write_single_coil_request(connection()->bro_analyzer(), connection()->bro_analyzer()->Conn(), HeaderToBro(header), - ${message.start_address}, - ${message.on_off}, ${message.other}); + ${message.address}, + val); } return true; @@ -209,11 +221,23 @@ refine flow ModbusTCP_Flow += { %{ if ( ::modbus_write_single_coil_response ) { + int val; + if ( ${message.value} == 0x0000 ) + val = 0; + else if ( ${message.value} == 0xFF00 ) + val = 1; + else + { + connection()->bro_analyzer()->ProtocolViolation(fmt("invalid value for modbus write single coil response %d", + ${message.value})); + return false; + } + BifEvent::generate_modbus_write_single_coil_response(connection()->bro_analyzer(), connection()->bro_analyzer()->Conn(), HeaderToBro(header), - ${message.start_address}, - ${message.on_off}, ${message.other}); + ${message.address}, + val); } return true; @@ -228,7 +252,7 @@ refine flow ModbusTCP_Flow += { BifEvent::generate_modbus_write_single_register_request(connection()->bro_analyzer(), connection()->bro_analyzer()->Conn(), HeaderToBro(header), - ${message.start_address}, ${message.value}); + ${message.address}, ${message.value}); } return true; @@ -242,7 +266,7 @@ refine flow ModbusTCP_Flow += { BifEvent::generate_modbus_write_single_register_response(connection()->bro_analyzer(), connection()->bro_analyzer()->Conn(), HeaderToBro(header), - ${message.start_address}, ${message.value}); + ${message.address}, ${message.value}); } return true; @@ -434,7 +458,7 @@ refine flow ModbusTCP_Flow += { BifEvent::generate_modbus_mask_write_register_request(connection()->bro_analyzer(), connection()->bro_analyzer()->Conn(), HeaderToBro(header), - ${message.start_address}, + ${message.address}, ${message.and_mask}, ${message.or_mask}); } @@ -449,7 +473,7 @@ refine flow ModbusTCP_Flow += { BifEvent::generate_modbus_mask_write_register_response(connection()->bro_analyzer(), connection()->bro_analyzer()->Conn(), HeaderToBro(header), - ${message.start_address}, + ${message.address}, ${message.and_mask}, ${message.or_mask}); } diff --git a/src/modbus-protocol.pac b/src/modbus-protocol.pac index e227357a8b..cef2626270 100644 --- a/src/modbus-protocol.pac +++ b/src/modbus-protocol.pac @@ -14,13 +14,13 @@ enum function_codes { READ_INPUT_REGISTERS = 0x04, WRITE_SINGLE_COIL = 0x05, WRITE_SINGLE_REGISTER = 0x06, - READ_EXCEPTION_STATUS = 0x07, - DIAGNOSTICS = 0x08, - GET_COMM_EVENT_COUNTER = 0x0B, - GET_COMM_EVENT_LOG = 0x0C, + # READ_EXCEPTION_STATUS = 0x07, + # DIAGNOSTICS = 0x08, + # GET_COMM_EVENT_COUNTER = 0x0B, + # GET_COMM_EVENT_LOG = 0x0C, WRITE_MULTIPLE_COILS = 0x0F, WRITE_MULTIPLE_REGISTERS = 0x10, - REPORT_SLAVE_ID = 0x11, + # REPORT_SLAVE_ID = 0x11, READ_FILE_RECORD = 0x14, WRITE_FILE_RECORD = 0x15, MASK_WRITE_REGISTER = 0x16, @@ -64,9 +64,7 @@ type ModbusTCP_PDU(is_orig: bool) = record { true -> request: ModbusTCP_Request(header); false -> response: ModbusTCP_Response(header); }; -} &length=header.len+6, &byteorder=bigendian, &let { - deliver: bool = $context.flow.deliver_message(header); -}; +} &length=header.len+6, &byteorder=bigendian; type ModbusTCP_TransportHeader = record { tid: uint16; # Transaction identifier @@ -74,6 +72,8 @@ type ModbusTCP_TransportHeader = record { len: uint16; # Length of everyting after this field uid: uint8; # Unit identifier (previously 'slave address') fc: uint8; # MODBUS function code (see function_codes enum) +} &byteorder=bigendian, &let { + deliver: bool = $context.flow.deliver_message(this); }; type ModbusTCP_Request(header: ModbusTCP_TransportHeader) = case header.fc of { @@ -98,7 +98,7 @@ type ModbusTCP_Request(header: ModbusTCP_TransportHeader) = case header.fc of { #ENCAP_INTERFACE_TRANSPORT # All the rest - default -> unknown: empty &restofdata; + default -> unknown: bytestring &restofdata; }; # Responses @@ -140,7 +140,7 @@ type ModbusTCP_Response(header: ModbusTCP_TransportHeader) = case header.fc of { READ_FIFO_QUEUE_EXCEPTION -> readFIFOQueueException: Exception(header); # All the rest - default -> unknown: empty &restofdata; + default -> unknown: bytestring &restofdata; }; type Exception(header: ModbusTCP_TransportHeader) = record { @@ -155,7 +155,7 @@ type ReadCoilsRequest(header: ModbusTCP_TransportHeader) = record { quantity: uint16 &check(quantity <= 2000); } &let { deliver: bool = $context.flow.deliver_ReadCoilsRequest(header, this); -}; +} &byteorder=bigendian; # RESPONSE FC=1 type ReadCoilsResponse(header: ModbusTCP_TransportHeader) = record { @@ -163,7 +163,7 @@ type ReadCoilsResponse(header: ModbusTCP_TransportHeader) = record { bits: bytestring &length=byte_count; } &let { deliver: bool = $context.flow.deliver_ReadCoilsResponse(header, this); -}; +} &byteorder=bigendian; # REQUEST FC=2 type ReadDiscreteInputsRequest(header: ModbusTCP_TransportHeader) = record { @@ -171,7 +171,7 @@ type ReadDiscreteInputsRequest(header: ModbusTCP_TransportHeader) = record { quantity: uint16 &check(quantity <= 2000); } &let { deliver: bool = $context.flow.deliver_ReadDiscreteInputsRequest(header, this); -}; +} &byteorder=bigendian; # RESPONSE FC=2 type ReadDiscreteInputsResponse(header: ModbusTCP_TransportHeader) = record { @@ -179,7 +179,7 @@ type ReadDiscreteInputsResponse(header: ModbusTCP_TransportHeader) = record { bits: bytestring &length=byte_count; } &let { deliver: bool = $context.flow.deliver_ReadDiscreteInputsResponse(header, this); -}; +} &byteorder=bigendian; # REQUEST FC=3 type ReadHoldingRegistersRequest(header: ModbusTCP_TransportHeader) = record { @@ -187,7 +187,7 @@ type ReadHoldingRegistersRequest(header: ModbusTCP_TransportHeader) = record { quantity: uint16 &check(1 <= quantity && quantity <= 125); } &let { deliver: bool = $context.flow.deliver_ReadHoldingRegistersRequest(header, this); -}; +} &byteorder=bigendian; # RESPONSE FC=3 type ReadHoldingRegistersResponse(header: ModbusTCP_TransportHeader) = record { @@ -195,7 +195,7 @@ type ReadHoldingRegistersResponse(header: ModbusTCP_TransportHeader) = record { registers: uint16[] &length=byte_count; } &let { deliver: bool = $context.flow.deliver_ReadHoldingRegistersResponse(header, this); -}; +} &byteorder=bigendian; # REQUEST FC=4 type ReadInputRegistersRequest(header: ModbusTCP_TransportHeader) = record { @@ -203,7 +203,7 @@ type ReadInputRegistersRequest(header: ModbusTCP_TransportHeader) = record { quantity: uint16 &check(1 <= quantity && quantity <= 125); } &let { deliver: bool = $context.flow.deliver_ReadInputRegistersRequest(header, this); -}; +} &byteorder=bigendian; # RESPONSE FC=4 type ReadInputRegistersResponse(header: ModbusTCP_TransportHeader) = record { @@ -211,41 +211,39 @@ type ReadInputRegistersResponse(header: ModbusTCP_TransportHeader) = record { registers: uint16[] &length=byte_count; } &let { deliver: bool = $context.flow.deliver_ReadInputRegistersResponse(header, this); -}; +} &byteorder=bigendian; # REQUEST FC=5 type WriteSingleCoilRequest(header: ModbusTCP_TransportHeader) = record { - start_address: uint16; - on_off: uint8 &check(on_off == 0x00 || on_off == 0xFF); - other: uint8 &check(other == 0x00); + address: uint16; + value: uint16 &check(value == 0x0000 || value == 0xFF00); } &let { deliver: bool = $context.flow.deliver_WriteSingleCoilRequest(header, this); -}; +} &byteorder=bigendian; # RESPONSE FC=5 type WriteSingleCoilResponse(header: ModbusTCP_TransportHeader) = record { - start_address: uint16; - on_off: uint8 &check(on_off == 0x00 || on_off == 0xFF); - other: uint8 &check(other == 0x00); + address: uint16; + value: uint16 &check(value == 0x0000 || value == 0xFF00); } &let { deliver: bool = $context.flow.deliver_WriteSingleCoilResponse(header, this); -}; +} &byteorder=bigendian; # REQUEST FC=6 type WriteSingleRegisterRequest(header: ModbusTCP_TransportHeader) = record { - start_address: uint16; - value: uint16; + address: uint16; + value: uint16; } &let { deliver: bool = $context.flow.deliver_WriteSingleRegisterRequest(header, this); -}; +} &byteorder=bigendian; # RESPONSE FC=6 type WriteSingleRegisterResponse(header: ModbusTCP_TransportHeader) = record { - start_address: uint16; - value: uint16; + address: uint16; + value: uint16; } &let { deliver: bool = $context.flow.deliver_WriteSingleRegisterResponse(header, this); -}; +} &byteorder=bigendian; # REQUEST FC=15 type WriteMultipleCoilsRequest(header: ModbusTCP_TransportHeader) = record { @@ -255,7 +253,7 @@ type WriteMultipleCoilsRequest(header: ModbusTCP_TransportHeader) = record { coils: bytestring &length=byte_count; } &let { deliver: bool = $context.flow.deliver_WriteMultipleCoilsRequest(header, this); -}; +} &byteorder=bigendian; # RESPONSE FC=15 type WriteMultipleCoilsResponse(header: ModbusTCP_TransportHeader) = record { @@ -263,7 +261,7 @@ type WriteMultipleCoilsResponse(header: ModbusTCP_TransportHeader) = record { quantity: uint16 &check(quantity <= 0x07B0); } &let { deliver: bool = $context.flow.deliver_WriteMultipleCoilsResponse(header, this); -}; +} &byteorder=bigendian; # REQUEST FC=16 type WriteMultipleRegistersRequest(header: ModbusTCP_TransportHeader) = record { @@ -275,7 +273,7 @@ type WriteMultipleRegistersRequest(header: ModbusTCP_TransportHeader) = record { registers: uint16[quantity] &length=byte_count; } &let { deliver: bool = $context.flow.deliver_WriteMultipleRegistersRequest(header, this); -}; +} &byteorder=bigendian; # RESPONSE FC=16 type WriteMultipleRegistersResponse(header: ModbusTCP_TransportHeader) = record { @@ -283,39 +281,38 @@ type WriteMultipleRegistersResponse(header: ModbusTCP_TransportHeader) = record quantity: uint16; } &let { deliver: bool = $context.flow.deliver_WriteMultipleRegistersResponse(header, this); -}; - +} &byteorder=bigendian; # Support data structure for following message type. type FileRecordRequest = record { - ref_type: uint8; - file_num: uint16; - record_num: uint16; + ref_type: uint8 &check(ref_type == 6); + file_num: uint16 &check(file_num > 0); + record_num: uint16 &check(record_num <= 0x270F); record_len: uint16; -}; +} &byteorder=bigendian; # REQUEST FC=20 type ReadFileRecordRequest(header: ModbusTCP_TransportHeader) = record { - byte_count: uint8; + byte_count: uint8 &check(byte_count >= 0x07 && byte_count <= 0xF5); references: FileRecordRequest[] &length=byte_count; } &let { deliver: bool = $context.flow.deliver_ReadFileRecordRequest(header, this); -}; +} &byteorder=bigendian; # Support data structure for the following message type. type FileRecordResponse = record { - file_len: uint8; - ref_type: uint8; + file_len: uint8 &check(file_len >= 0x07 && file_len <= 0xF5); + ref_type: uint8 &check(ref_type == 6); record_data: uint16[] &length=file_len; -}; +} &byteorder=bigendian; # RESPONSE FC=20 type ReadFileRecordResponse(header: ModbusTCP_TransportHeader) = record { - byte_count: uint8; + byte_count: uint8 &check(byte_count >= 0x07 && byte_count <= 0xF5); references: FileRecordResponse[] &length=byte_count; } &let { deliver: bool = $context.flow.deliver_ReadFileRecordResponse(header, this); -}; +} &byteorder=bigendian; # Support data structure for the two following message types. type ReferenceWithData = record { @@ -324,7 +321,7 @@ type ReferenceWithData = record { record_num: uint16; word_count: uint16; register_value: uint16[word_count]; -}; +} &byteorder=bigendian; # REQUEST FC=21 type WriteFileRecordRequest(header: ModbusTCP_TransportHeader) = record { @@ -332,7 +329,7 @@ type WriteFileRecordRequest(header: ModbusTCP_TransportHeader) = record { references: ReferenceWithData[] &length=byte_count; } &let { deliver: bool = $context.flow.deliver_WriteFileRecordRequest(header, this); -}; +} &byteorder=bigendian; # RESPONSE FC=21 type WriteFileRecordResponse(header: ModbusTCP_TransportHeader) = record { @@ -340,27 +337,25 @@ type WriteFileRecordResponse(header: ModbusTCP_TransportHeader) = record { references: ReferenceWithData[] &length=byte_count; } &let { deliver: bool = $context.flow.deliver_WriteFileRecordResponse(header, this); -}; - +} &byteorder=bigendian; # REQUEST FC=22 type MaskWriteRegisterRequest(header: ModbusTCP_TransportHeader) = record { - start_address: uint16; - and_mask: uint16; - or_mask: uint16; + address: uint16; + and_mask: uint16; + or_mask: uint16; } &let { deliver: bool = $context.flow.deliver_MaskWriteRegisterRequest(header, this); -}; +} &byteorder=bigendian; # RESPONSE FC=22 type MaskWriteRegisterResponse(header: ModbusTCP_TransportHeader) = record { - start_address: uint16; - and_mask: uint16; - or_mask: uint16; + address: uint16; + and_mask: uint16; + or_mask: uint16; } &let { deliver: bool = $context.flow.deliver_MaskWriteRegisterResponse(header, this); -}; - +} &byteorder=bigendian; # REQUEST FC=23 type ReadWriteMultipleRegistersRequest(header: ModbusTCP_TransportHeader) = record { @@ -372,7 +367,7 @@ type ReadWriteMultipleRegistersRequest(header: ModbusTCP_TransportHeader) = reco write_register_values: uint16[write_quantity] &length=write_byte_count; } &let { deliver: bool = $context.flow.deliver_ReadWriteMultipleRegistersRequest(header, this); -}; +} &byteorder=bigendian; # RESPONSE FC=23 type ReadWriteMultipleRegistersResponse(header: ModbusTCP_TransportHeader) = record { @@ -380,20 +375,20 @@ type ReadWriteMultipleRegistersResponse(header: ModbusTCP_TransportHeader) = rec registers: uint16[] &length=byte_count; } &let { deliver: bool = $context.flow.deliver_ReadWriteMultipleRegistersResponse(header, this); -}; +} &byteorder=bigendian; # REQUEST FC=24 type ReadFIFOQueueRequest(header: ModbusTCP_TransportHeader) = record { start_address: uint16; } &let{ deliver: bool = $context.flow.deliver_ReadFIFOQueueRequest(header, this); -}; +} &byteorder=bigendian; # RESPONSE FC=24 type ReadFIFOQueueResponse(header: ModbusTCP_TransportHeader) = record { byte_count: uint16 &check(byte_count <= 62); - fifo_count: uint16 &check(word_count <= 31); - register_data: uint16[fifo_count]; + fifo_count: uint16 &check(fifo_count <= 31); + register_data: uint16[fifo_count] &length=byte_count/2; } &let { deliver: bool = $context.flow.deliver_ReadFIFOQueueResponse(header, this); -}; +} &byteorder=bigendian;