├── .gitignore ├── CMakeLists.txt ├── LICENSE.txt ├── Makefile ├── NOTICE.txt ├── README.md ├── configure ├── scripts ├── __load__.zeek ├── __preload__.zeek ├── consts.zeek └── icsnpp │ └── ethercat │ ├── __load__.zeek │ └── main.zeek ├── src ├── ECAT.cc ├── ECAT.h ├── ECAT_ENUMS.h ├── Plugin.cc ├── Plugin.h └── events.bif ├── tests ├── .gitignore ├── analyzer │ ├── availability.zeek │ └── basic.zeek ├── baseline │ └── analyzer.basic │ │ ├── conn.log │ │ ├── ecat_aoe_info.log │ │ ├── ecat_arp_info.log │ │ ├── ecat_coe_info.log │ │ ├── ecat_dev_info.log │ │ ├── ecat_log_address.log │ │ └── ecat_registers.log ├── btest.cfg ├── files │ └── random.seed ├── scripts │ ├── diff-remove-timestamps │ └── get-zeek-env └── traces │ └── ethercat_example.pcap └── zkg.meta /.gitignore: -------------------------------------------------------------------------------- 1 | build/ -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15 FATAL_ERROR) 2 | 3 | project(Plugin) 4 | 5 | include(ZeekPlugin) 6 | 7 | zeek_plugin_begin(ICSNPP ETHERCAT) 8 | zeek_plugin_cc(src/ECAT.cc src/Plugin.cc) 9 | zeek_plugin_bif(src/events.bif) 10 | zeek_plugin_dist_files(README CHANGES COPYING VERSION) 11 | zeek_plugin_end() -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2023, Battelle Energy Alliance, LLC 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Convenience Makefile providing a few common top-level targets. 3 | # 4 | # Copyright (c) 2020 Battelle Energy Alliance, LLC. All rights reserved. 5 | 6 | cmake_build_dir=build 7 | arch=`uname -s | tr A-Z a-z`-`uname -m` 8 | 9 | all: build-it 10 | 11 | build-it: 12 | @test -e $(cmake_build_dir)/config.status || ./configure 13 | -@test -e $(cmake_build_dir)/CMakeCache.txt && \ 14 | test $(cmake_build_dir)/CMakeCache.txt -ot `cat $(cmake_build_dir)/CMakeCache.txt | grep ZEEK_DIST | cut -d '=' -f 2`/build/CMakeCache.txt && \ 15 | echo Updating stale CMake cache && \ 16 | touch $(cmake_build_dir)/CMakeCache.txt 17 | 18 | ( cd $(cmake_build_dir) && make ) 19 | 20 | install: 21 | ( cd $(cmake_build_dir) && make install ) 22 | 23 | clean: 24 | ( cd $(cmake_build_dir) && make clean ) 25 | 26 | distclean: 27 | rm -rf $(cmake_build_dir) 28 | 29 | test: 30 | make -C tests -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | This project contains code from Idaho National Laboratory's ICSNPP Project 2 | Github URL: https://github.com/cisagov/ICSNPP 3 | Licensed under BSD 3-Part License. 4 | 5 | 6 | © 2023 Battelle Energy Alliance, LLC 7 | ALL RIGHTS RESERVED 8 | 9 | Prepared by Battelle Energy Alliance, LLC 10 | Under Contract No. DE-AC07-05ID14517 11 | With the U. S. Department of Energy 12 | 13 | NOTICE: This computer software was prepared by Battelle Energy 14 | Alliance, LLC, hereinafter the Contractor, under Contract 15 | No. AC07-05ID14517 with the United States (U. S.) Department of 16 | Energy (DOE). The Government is granted for itself and others acting on 17 | its behalf a nonexclusive, paid-up, irrevocable worldwide license in this 18 | data to reproduce, prepare derivative works, and perform publicly and 19 | display publicly, by or on behalf of the Government. There is provision for 20 | the possible extension of the term of this license. Subsequent to that 21 | period or any extension granted, the Government is granted for itself and 22 | others acting on its behalf a nonexclusive, paid-up, irrevocable worldwide 23 | license in this data to reproduce, prepare derivative works, distribute 24 | copies to the public, perform publicly and display publicly, and to permit 25 | others to do so. The specific term of the license can be identified by 26 | inquiry made to Contractor or DOE. NEITHER THE UNITED STATES NOR THE UNITED 27 | STATES DEPARTMENT OF ENERGY, NOR CONTRACTOR MAKES ANY WARRANTY, EXPRESS OR 28 | IMPLIED, OR ASSUMES ANY LIABILITY OR RESPONSIBILITY FOR THE USE, ACCURACY, 29 | COMPLETENESS, OR USEFULNESS OR ANY INFORMATION, APPARATUS, PRODUCT, OR 30 | PROCESS DISCLOSED, OR REPRESENTS THAT ITS USE WOULD NOT INFRINGE PRIVATELY 31 | OWNED RIGHTS. 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ICSNPP-ETHERCAT 2 | 3 | Industrial Control Systems Network Protocol Parsers (ICSNPP) - Ethercat. 4 | 5 | ## Overview 6 | 7 | ICSNPP-Ethercat is a Zeek plugin for parsing and logging fields within the Ethercat protocol. 8 | 9 | This plugin was developed to be fully customizable. To drill down into specific Ethercat packets and log certain variables, users can add the logging functionality to [scripts/icsnpp/ethercat/main.zeek](scripts/icsnpp/ethercat/main.zeek). The functions within [scripts/icsnpp/ethercat/main.zeek](scripts/icsnpp/ethercat/main.zeek) and [src/events.bif](src/events.bif) are good guides for adding new logging functionality. 10 | 11 | This parser produces eight log files. These log files are defined in [scripts/icsnpp/ethercat/main.zeek](scripts/icsnpp/ethercat/main.zeek). 12 | * ecat_registers.log 13 | * ecat_log_address.log 14 | * ecat_dev_info.log 15 | * ecat_aoe_info.log 16 | * ecat_coe_info.log 17 | * ecat_foe_info.log 18 | * ecat_soe_info.log 19 | * ecat_arp_info.log 20 | 21 | For additional information on these log files, see the *Logging Capabilities* section below. 22 | 23 | ## Installation 24 | 25 | ### Installing Zeek 26 | 27 | ### Package Manager 28 | 29 | This script is available as a package for [Zeek Package Manger](https://docs.zeek.org/projects/package-manager/en/stable/index.html) 30 | 31 | ```bash 32 | zkg refresh 33 | zkg install icsnpp-ethercat 34 | ``` 35 | 36 | If this package is installed from ZKG, it will be added to the available plugins. This can be tested by running `zeek -N`. If installed correctly, users will see `ICSNPP::ETHERCAT`. 37 | 38 | If ZKG is configured to load packages (see @load packages in quickstart guide), this plugin and these scripts will automatically be loaded and ready to go. 39 | [ZKG Quickstart Guide](https://docs.zeek.org/projects/package-manager/en/stable/quickstart.html) 40 | 41 | If users are not using site/local.zeek or another site installation of Zeek and want to run this package on a packet capture, they can add `icsnpp/ethercat` to the command to run this plugin's scripts on the packet capture: 42 | 43 | ```bash 44 | git clone https://github.com/cisagov/icsnpp-ethercat.git 45 | zeek -Cr icsnpp-ethercat/tests/traces/ethercat_example.pcap icsnpp/ethercat 46 | ``` 47 | 48 | 49 | ## Manual Install 50 | To install this package manually, clone this repository and run the configure and make commands as shown below. 51 | 52 | ```bash 53 | git clone https://github.com/cisagov/icsnpp-ethercat.git 54 | cd icsnpp-ethercat/ 55 | ./configure 56 | make 57 | ``` 58 | If these commands succeed, users will end up with a newly created build directory that contains all the files needed to run/test this plugin. The easiest way to test the parser is to point the ZEEK_PLUGIN_PATH environment variable to this build directory. 59 | 60 | ```bash 61 | export ZEEK_PLUGIN_PATH=$PWD/build/ 62 | zeek -N # Ensure everything compiled correctly and you are able to see ICSNPP::ETHERCAT 63 | ``` 64 | Once users have tested the functionality locally and it appears to have compiled correctly, they can install it system-wide: 65 | 66 | ```bash 67 | sudo make install 68 | unset ZEEK_PLUGIN_PATH 69 | zeek -N # Ensure everything installed correctly and you are able to see ICSNPP::ETHERCAT 70 | ``` 71 | To run this plugin in a site deployment, users will need to add the line @load icsnpp/ethercat to the site/local.zeek file to load this plugin's scripts. 72 | 73 | If users are not using site/local.zeek or another site installation of Zeek and want to run this package on a packet capture, they can add icsnpp/ethercat to the command to run this plugin's scripts on the packet capture: 74 | 75 | ```bash 76 | zeek -Cr icsnpp-ethercat/tests/traces/ethercat_example.pcap icsnpp/ethercat 77 | ``` 78 | If users want to deploy this plugin on an already existing Zeek implementation and don't want to build the plugin on the machine, they can extract the ICSNPP_ETHERCAT.tgz file to the directory of the established ZEEK_PLUGIN_PATH (default is ${ZEEK_INSTALLATION_DIR}/lib/zeek/plugins/). 79 | 80 | ```bash 81 | tar xvzf build/ICSNPP_Ethercat.tgz -C $ZEEK_PLUGIN_PATH 82 | ``` 83 | ## Logging Capabilities 84 | 85 | ### ECAT Registers (ecat_registers.log) 86 | 87 | #### Overview 88 | 89 | This log captures register memory address read and writes **ecat_registers.log**. 90 | 91 | This log is also the catch all. Before it gets to this point it is sent through more parsing routines to pull out any other information (i.e., CoE, AoE, FoE, EoE, and SoE mailbox data). 92 | 93 | #### Fields Captured 94 | 95 | | Field | Type | Description | 96 | | ----------------- |-----------|-----------------------------------------------------------| 97 | | ts | time | Timestamp | 98 | | srcmac | string | Source MAC address | 99 | | dstmac | string | Destination MAC address | 100 | | Command | string | EtherCAT command | 101 | | Slave_Addr | string | EtherCAT slave address | 102 | | Register_Type | string | Register information | 103 | | Register_Addr | string | Memory address being accessed | 104 | | data | string | Data to be read or wrote to memory address | 105 | 106 | 107 | ### ECAT Address read write (ecat_log_address.log) 108 | 109 | #### Overview 110 | 111 | This log captures Logical Read and writes to addresses and logs them to **ecat_log_address.log**. 112 | 113 | #### Fields Captured 114 | 115 | | Field | Type | Description | 116 | | ----------------- |-----------|-----------------------------------------------------------| 117 | | ts | time | Timestamp | 118 | | srcmac | string | Source MAC address | 119 | | dstmac | string | Destination MAC address | 120 | | Log_Addr | string | Address data is being accessed from | 121 | | Length | count | Length of data | 122 | | Command | string | EtherCAT command | 123 | | data | string | Data read or write | 124 | 125 | ### ECAT Device Info (ecat_dev_info.log) 126 | 127 | #### Overview 128 | 129 | This log captures ECAT Device info and logs it to **ecat_dev_info.log**. 130 | 131 | #### Fields Captured 132 | 133 | | Field | Type | Description | 134 | | ----------------- |-----------|-----------------------------------------------------------| 135 | | ts | time | Timestamp | 136 | | slave_id | string | Ethercat slave address | 137 | | revision | string | Revision of EtherCAT controller | 138 | | dev_type | string | Type of EtherCAT controller | 139 | | build | string | Build version | 140 | | fmmucnt | string | Fieldbus memory management unit supported channel count | 141 | | smcount | string | Sync manager count | 142 | | ports | string | Port descriptor | 143 | | dpram | string | Ram size | 144 | | features | string | Features supported | 145 | 146 | ### ECAT AoE Info (ecat_aoe_info.log) 147 | 148 | #### Overview 149 | 150 | This log captures AoE (ADS over Ethercat, Automation Device Specification) information 151 | and logs it to **ecat_aoe_info.log**. 152 | 153 | #### Fields Captured 154 | 155 | | Field | Type | Description | 156 | | ----------------- |-----------|-----------------------------------------------------------| 157 | | ts | time | Timestamp | 158 | | targetid | string | Target network ID | 159 | | targetport | string | Target port | 160 | | senderid | string | Sender network ID | 161 | | senderport | string | Sender port | 162 | | cmd | string | Command | 163 | | stateflags | string | State flags | 164 | | data | string | Command data | 165 | 166 | ### ECAT CoE Info (ecat_coe_info.log) 167 | 168 | #### Overview 169 | 170 | This log captures CoE (CAN over Ethercat) and logs it to **ecat_coe_info.log**. 171 | 172 | #### Fields Captured 173 | 174 | | Field | Type | Description | 175 | | ----------------- |-----------|-----------------------------------------------------------| 176 | | ts | time | Timestamp | 177 | | number | string | Message number | 178 | | Type | string | Message type | 179 | | req_resp | string | Request or response type | 180 | | index | string | Index | 181 | | subindex | string | Sub index | 182 | | dataoffset | string | Data offset | 183 | 184 | ### ECAT FoE Info (ecat_foe_info.log) 185 | 186 | #### Overview 187 | 188 | This log captures FoE (File Over Ethercat) information and logs it to **ecat_foe_info.log**. 189 | 190 | #### Fields Captured 191 | 192 | | Field | Type | Description | 193 | | ----------------- |-----------|-----------------------------------------------------------| 194 | | ts | time | Timestamp | 195 | | opCode | string | Operation code | 196 | | reserved | string | Reserved | 197 | | packet_num | string | Packet number | 198 | | error_code | string | Error code | 199 | | filename | string | Filename | 200 | | data | string | Transferred data | 201 | 202 | ### ECAT SoE Info (ecat_soe_info.log) 203 | 204 | #### Overview 205 | 206 | This log captures SoE (Servo over Ethercat) and logs it to **ecat_soe_info.log**. 207 | 208 | #### Fields Captured 209 | 210 | | Field | Type | Description | 211 | | ----------------- |-----------|-----------------------------------------------------------| 212 | | ts | time | Timestamp | 213 | | opCode | string | Command sent for controller | 214 | | incomplete | string | Function check to determine if it has been processed | 215 | | error | string | Error message | 216 | | drive_num | string | Drive number for command | 217 | | element_flags | string | Element flags | 218 | | index | string | Message index | 219 | 220 | ### ECAT ARP Info (ecat_arp_info.log) 221 | 222 | #### Overview 223 | 224 | This log captures ARP info that is passed through EoE (Ethernet over Ethercat) 225 | and logs it to **ecat_arp_info.log**. 226 | 227 | #### Fields Captured 228 | 229 | | Field | Type | Description | 230 | | ----------------- |-----------|-----------------------------------------------------------| 231 | | ts | time | Timestamp | 232 | | arp_type | string | ARP command | 233 | | mac_src | string | Source MAC address | 234 | | mac_dst | string | Destination MAC address | 235 | | SPA | addr | Sender protocol address | 236 | | SHA | string | Sender hardware address | 237 | | TPA | addr | Target protocol address | 238 | | THA | string | Target hardware address | 239 | 240 | ## ICSNPP Packages 241 | 242 | All ICSNPP Packages: 243 | * [ICSNPP](https://github.com/cisagov/icsnpp) 244 | 245 | Full ICS Protocol Parsers: 246 | * [BACnet](https://github.com/cisagov/icsnpp-bacnet) 247 | * Full Zeek protocol parser for BACnet (Building Control and Automation) 248 | * [BSAP](https://github.com/cisagov/icsnpp-bsap) 249 | * Full Zeek protocol parser for BSAP (Bristol Standard Asynchronous Protocol) over IP 250 | * Full Zeek protocol parser for BSAP Serial comm converted using serial tap device 251 | * [Ethercat](https://github.com/cisagov/icsnpp-ethercat) 252 | * Full Zeek protocol parser for Ethercat 253 | * [Ethernet/IP and CIP](https://github.com/cisagov/icsnpp-enip) 254 | * Full Zeek protocol parser for Ethernet/IP and CIP 255 | * [GE SRTP](https://github.com/cisagov/icsnpp-ge-srtp) 256 | * Full Zeek protocol parser for GE SRTP 257 | * [Genisys](https://github.com/cisagov/icsnpp-genisys) 258 | * Full Zeek protocol parser for Genisys 259 | * [OPCUA-Binary](https://github.com/cisagov/icsnpp-opcua-binary) 260 | * Full Zeek protocol parser for OPC UA (OPC Unified Architecture) - Binary 261 | * [S7Comm](https://github.com/cisagov/icsnpp-s7comm) 262 | * Full Zeek protocol parser for S7comm, S7comm-plus, and COTP 263 | * [Synchrophasor](https://github.com/cisagov/icsnpp-synchrophasor) 264 | * Full Zeek protocol parser for Synchrophasor Data Transfer for Power Systems (C37.118) 265 | * [Profinet IO CM](https://github.com/cisagov/icsnpp-profinet-io-cm) 266 | * Full Zeek protocol parser for Profinet I/O Context Manager 267 | 268 | Updates to Zeek ICS Protocol Parsers: 269 | * [DNP3](https://github.com/cisagov/icsnpp-dnp3) 270 | * DNP3 Zeek script extending logging capabilities of Zeek's default DNP3 protocol parser 271 | * [Modbus](https://github.com/cisagov/icsnpp-modbus) 272 | * Modbus Zeek script extending logging capabilities of Zeek's default Modbus protocol parser 273 | 274 | ### License 275 | 276 | Copyright 2023 Battelle Energy Alliance, LLC. Released under the terms of the 3-Clause BSD License (see [`LICENSE.txt`](./LICENSE.txt)). 277 | -------------------------------------------------------------------------------- /configure: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Wrapper for viewing/setting options that the plugin's CMake 4 | # scripts will recognize. 5 | # 6 | # Don't edit this. Edit configure.plugin to add plugin-specific options. 7 | # 8 | 9 | set -e 10 | command="$0 $*" 11 | 12 | if [ -e `dirname $0`/configure.plugin ]; then 13 | # Include custom additions. 14 | . `dirname $0`/configure.plugin 15 | fi 16 | 17 | # Check for `cmake` command. 18 | type cmake > /dev/null 2>&1 || { 19 | echo "\ 20 | This package requires CMake, please install it first, then you may 21 | use this configure script to access CMake equivalent functionality.\ 22 | " >&2; 23 | exit 1; 24 | } 25 | 26 | usage() { 27 | 28 | cat 1>&2 </dev/null 2>&1; then 41 | plugin_usage 1>&2 42 | fi 43 | 44 | echo 45 | 46 | exit 1 47 | } 48 | 49 | # Function to append a CMake cache entry definition to the 50 | # CMakeCacheEntries variable 51 | # $1 is the cache entry variable name 52 | # $2 is the cache entry variable type 53 | # $3 is the cache entry variable value 54 | append_cache_entry () { 55 | CMakeCacheEntries="$CMakeCacheEntries -D $1:$2=$3" 56 | } 57 | 58 | # set defaults 59 | builddir=build 60 | zeekdist="" 61 | installroot="default" 62 | CMakeCacheEntries="" 63 | 64 | while [ $# -ne 0 ]; do 65 | case "$1" in 66 | -*=*) optarg=`echo "$1" | sed 's/[-_a-zA-Z0-9]*=//'` ;; 67 | *) optarg= ;; 68 | esac 69 | 70 | case "$1" in 71 | --help|-h) 72 | usage 73 | ;; 74 | 75 | --zeek-dist=*) 76 | zeekdist=`cd $optarg && pwd` 77 | ;; 78 | 79 | --bro-dist=*) # Legacy option for backwards compability 80 | zeekdist=`cd $optarg && pwd` 81 | ;; 82 | 83 | --install-root=*) 84 | installroot=$optarg 85 | ;; 86 | 87 | --with-binpac=*) 88 | append_cache_entry BinPAC_ROOT_DIR PATH $optarg 89 | binpac_root=$optarg 90 | ;; 91 | 92 | --with-broker=*) 93 | append_cache_entry BROKER_ROOT_DIR PATH $optarg 94 | broker_root=$optarg 95 | ;; 96 | 97 | --with-caf=*) 98 | append_cache_entry CAF_ROOT_DIR PATH $optarg 99 | caf_root=$optarg 100 | ;; 101 | 102 | --with-bifcl=*) 103 | append_cache_entry BifCl_EXE PATH $optarg 104 | ;; 105 | 106 | --enable-debug) 107 | append_cache_entry BRO_PLUGIN_ENABLE_DEBUG BOOL true 108 | ;; 109 | 110 | *) 111 | if type plugin_option >/dev/null 2>&1; then 112 | plugin_option $1 && shift && continue; 113 | fi 114 | 115 | echo "Invalid option '$1'. Try $0 --help to see available options." 116 | exit 1 117 | ;; 118 | esac 119 | shift 120 | done 121 | 122 | if [ -z "$zeekdist" ]; then 123 | if type zeek-config >/dev/null 2>&1; then 124 | zeek_config="zeek-config" 125 | elif type bro-config >/dev/null 2>&1; then 126 | zeek_config="bro-config" 127 | fi 128 | 129 | if [ -n "${zeek_config}" ]; then 130 | if ${zeek_config} --cmake_dir >/dev/null 2>&1; then 131 | # Have a newer version of zeek-config that has needed flags 132 | append_cache_entry BRO_CONFIG_PREFIX PATH `${zeek_config} --prefix` 133 | append_cache_entry BRO_CONFIG_INCLUDE_DIR PATH `${zeek_config} --include_dir` 134 | append_cache_entry BRO_CONFIG_PLUGIN_DIR PATH `${zeek_config} --plugin_dir` 135 | append_cache_entry BRO_CONFIG_CMAKE_DIR PATH `${zeek_config} --cmake_dir` 136 | append_cache_entry CMAKE_MODULE_PATH PATH `${zeek_config} --cmake_dir` 137 | 138 | build_type=`${zeek_config} --build_type` 139 | 140 | if [ "$build_type" = "debug" ]; then 141 | append_cache_entry BRO_PLUGIN_ENABLE_DEBUG BOOL true 142 | fi 143 | 144 | if [ -z "$binpac_root" ]; then 145 | append_cache_entry BinPAC_ROOT_DIR PATH `${zeek_config} --binpac_root` 146 | fi 147 | 148 | if [ -z "$broker_root" ]; then 149 | append_cache_entry BROKER_ROOT_DIR PATH `${zeek_config} --broker_root` 150 | fi 151 | 152 | if [ -z "$caf_root" ]; then 153 | append_cache_entry CAF_ROOT_DIR PATH `${zeek_config} --caf_root` 154 | fi 155 | else 156 | # Using legacy bro-config, so we must use the "--bro_dist" option. 157 | zeekdist=`${zeek_config} --bro_dist 2> /dev/null` 158 | 159 | if [ ! -e "$zeekdist/zeek-path-dev.in" ]; then 160 | echo "$zeekdist does not appear to be a valid Zeek source tree." 161 | exit 1 162 | fi 163 | 164 | # BRO_DIST is needed to support legacy Bro plugins 165 | append_cache_entry BRO_DIST PATH $zeekdist 166 | append_cache_entry ZEEK_DIST PATH $zeekdist 167 | append_cache_entry CMAKE_MODULE_PATH PATH $zeekdist/cmake 168 | fi 169 | else 170 | echo "Either 'zeek-config' must be in PATH or '--zeek-dist=' used" 171 | exit 1 172 | fi 173 | else 174 | if [ ! -e "$zeekdist/zeek-path-dev.in" -a ! -e "$zeekdist/bro-path-dev.in" ]; then 175 | echo "$zeekdist does not appear to be a valid Zeek source tree." 176 | exit 1 177 | fi 178 | 179 | # BRO_DIST is needed to support legacy Bro plugins 180 | append_cache_entry BRO_DIST PATH $zeekdist 181 | append_cache_entry ZEEK_DIST PATH $zeekdist 182 | append_cache_entry CMAKE_MODULE_PATH PATH $zeekdist/cmake 183 | fi 184 | 185 | if [ "$installroot" != "default" ]; then 186 | mkdir -p $installroot 187 | append_cache_entry BRO_PLUGIN_INSTALL_ROOT PATH $installroot 188 | fi 189 | 190 | echo "Build Directory : $builddir" 191 | echo "Zeek Source Directory : $zeekdist" 192 | 193 | mkdir -p $builddir 194 | cd $builddir 195 | 196 | cmake $CMakeCacheEntries .. 197 | 198 | echo "# This is the command used to configure this build" > config.status 199 | echo $command >> config.status 200 | chmod u+x config.status -------------------------------------------------------------------------------- /scripts/__load__.zeek: -------------------------------------------------------------------------------- 1 | ##! Copyright (c) 2020 Battelle Energy Alliance, LLC. All rights reserved." -------------------------------------------------------------------------------- /scripts/__preload__.zeek: -------------------------------------------------------------------------------- 1 | @load ./consts -------------------------------------------------------------------------------- /scripts/consts.zeek: -------------------------------------------------------------------------------- 1 | ##! Copyright (c) 2020 Battelle Energy Alliance, LLC. All rights reserved. 2 | module PacketAnalyzer::ECAT; 3 | 4 | export{ 5 | 6 | ############################################################# 7 | ######### Ethercat commands ######### 8 | ############################################################# 9 | const Ecat_Cmd = { 10 | [0x00] = "NOP", 11 | [0x01] = "Auto Increment Physical Read", 12 | [0x02] = "Auto Increment Physical Write", 13 | [0x03] = "Auto Increment Physical ReadWrite", 14 | [0x04] = "Configured Addr Physical Read", 15 | [0x05] = "Configured Addr Physical Write", 16 | [0x06] = "Configured Addr Physical ReadWrite", 17 | [0x07] = "Broadcast Read", 18 | [0x08] = "Broadcast Write", 19 | [0x09] = "Broadcast ReadWrite", 20 | [0x0A] = "Logical Read", 21 | [0x0B] = "Logical Write", 22 | [0x0C] = "Logical ReadWrite", 23 | [0x0D] = "Auto Increment Physical Read Mult Write", 24 | [0x0E] = "Configured Addr Physical Read Mult Write", 25 | 26 | 27 | } &default = function(n: count): string {return fmt("Unknown CMD Cmd-0x%02x", n); }; 28 | 29 | ############################################################# 30 | ######### AoE commands ######### 31 | ############################################################# 32 | const Ecat_AoE_Cmd = { 33 | [0x0000] = "NOP", 34 | [0x0001] = "ADS Read Device Info", 35 | [0x0002] = "ADS Read", 36 | [0x0003] = "ADS Write", 37 | [0x0004] = "ADS Read State", 38 | [0x0005] = "ADS Write Control", 39 | [0x0006] = "ADS Add Device Notification", 40 | [0x0007] = "ADS Delete Device Notification", 41 | [0x0008] = "ADS Device Notification", 42 | [0x0009] = "ADS Read Write", 43 | 44 | 45 | } &default = function(n: count): string {return fmt("Unknown CMD Cmd-0x%02x", n); }; 46 | 47 | ############################################################################ 48 | ######### Ecat_Registers: ######### 49 | ######### Description: Register address for reading and writing ######### 50 | ############################################################################ 51 | const Ecat_Registers = { 52 | [0x0000] = "Type", 53 | [0x0001] = "Revision", 54 | [0x0002] = "Build", 55 | [0x0004] = "FMMUSPT", 56 | [0x0005] = "SyncManagers", 57 | [0x0006] = "RAMSize", 58 | [0x0007] = "PortDescriptor", 59 | [0x0008] = "ESC_Features", 60 | [0x0010] = "CSAddr", 61 | [0x0012] = "CSAlias", 62 | [0x0020] = "Reg_Write_En", 63 | [0x0021] = "Reg_Write_Prot", 64 | [0x0030] = "ESC_Write_En", 65 | [0x0031] = "ESC_Write_Prot", 66 | [0x0040] = "ESC_Rst_Ecat", 67 | [0x0041] = "ESC_Rst_Pdi", 68 | [0x0100] = "ESC_DL_Ctl", 69 | [0x0108] = "Phy_RD_WR_Offs", 70 | [0x0110] = "ESC_DL_Stat", 71 | [0x0120] = "AL_Ctl", 72 | [0x0130] = "AL_Stat", 73 | [0x0134] = "AL_Stat_Code", 74 | [0x0138] = "RUN_Led_Ovrd", 75 | [0x0139] = "ERR_Led_Ovrd", 76 | [0x0140] = "PDI_Ctl", 77 | [0x0141] = "ESC_Conf", 78 | [0x014E] = "PDI_Info", 79 | [0x0150] = "PDI_Conf", 80 | [0x0152] = "PDI_Onchip_Conf", 81 | [0x0151] = "Sync_Latc_Pdi", 82 | [0x0200] = "ECAT_Ev_Msk", 83 | [0x0204] = "PDI AL Event Mask", 84 | [0x0210] = "ECAT Event Request", 85 | [0x0220] = "AL Event Request", 86 | [0x0300] = "RX_Err_Cnt", 87 | [0x0308] = "Fwd_Rx_Err_Cnt", 88 | [0x030C] = "ECAT_Proc_Err_Cnt", 89 | [0x030D] = "PDI_Err_Cnt", 90 | [0x030E] = "PDI_Err_Code", 91 | [0x0310] = "LLC", 92 | [0x0400] = "WTD_Div", 93 | [0x0410] = "WTD_Time_PDI", 94 | [0x0420] = "WTD_Time_Proc_Data", 95 | [0x0440] = "WTD_Stat_Proc_Data", 96 | [0x0442] = "WTD_Cnt_Proc_Data", 97 | [0x0443] = "WTD_Cnt_PDI", 98 | [0x0500] = "SII_EEPROM_Intr", 99 | [0x0510] = "MII_Mang_Intr", 100 | [0x0600] = "FMMU", 101 | [0x0800] = "SyncManager", 102 | [0x0900] = "Dist_Clk", 103 | [0x0E00] = "ESC_Specf-Reg ", 104 | [0x0F00] = "Dig_IO_Data", 105 | [0x0F10] = "GP_Out_Data", 106 | [0x0F18] = "GP_In", 107 | [0x0F80] = "USR_Ram", 108 | [0x1000] = "PDI_Dig_IO_Data", 109 | [0x1004] = "PD_Ram", 110 | }&default = function(n: count): string {return fmt("Unknown Register Reg-0x%02x", n); }; 111 | 112 | } 113 | -------------------------------------------------------------------------------- /scripts/icsnpp/ethercat/__load__.zeek: -------------------------------------------------------------------------------- 1 | @load ./main -------------------------------------------------------------------------------- /scripts/icsnpp/ethercat/main.zeek: -------------------------------------------------------------------------------- 1 | ##! Copyright (c) 2020 Battelle Energy Alliance, LLC. All rights reserved." 2 | ##! main.zeek 3 | ##! 4 | ##! Packet Analyzer Ethercat Analyzer - Contains the base script-layer 5 | ##! functionality for processing events 6 | ##! emitted from the analyzer. 7 | ##! 8 | ##! Author: Devin Vollmer 9 | ##! Contact: devin.vollmer@inl.gov 10 | 11 | module PacketAnalyzer::ECAT; 12 | 13 | export { 14 | redef enum Log::ID += { LOG_ECAT_REGISTERS, LOG_ECAT_ADDRESS, LOG_ECAT_DEV_INFO, LOG_ECAT_AOE_INFO, 15 | LOG_ECAT_COE_INFO, LOG_ECAT_FOE_INFO, LOG_ECAT_SOE_INFO, LOG_ECAT_ARP_INFO}; 16 | 17 | ############################################################################################### 18 | ############################# ECAT_REGISTER -> ecat_registers.log ############################ 19 | ############################################################################################### 20 | type ECAT_REGISTER: record { 21 | ts : time &log; ## Timestamp for when the event happened 22 | srcmac : string &log; ## Source Mac Address 23 | dstmac : string &log; ## Destination Mac Address 24 | Command : string &log; ## Ethercat Command 25 | Slave_Addr : string &log; ## Ethercat Slave Address 26 | Register_Type : string &log; ## Register Information 27 | Register_Addr : string &log; ## Memory Address being accessed 28 | data : string &log; ## Data to be read or wrote to memory address 29 | # ## TODO: Add other fields here that you'd like to log. 30 | }; 31 | global log_ecat_registers: event(rec: ECAT_REGISTER); 32 | global log_policy_ecat_registers: Log::PolicyHook; 33 | 34 | ############################################################################################### 35 | ############################ ECAT_LOG_ADDR -> ecat_log_address.log ########################### 36 | ############################################################################################### 37 | type ECAT_LOG_ADDR: record { 38 | ts : time &log; ## Timestamp for when the event happened 39 | srcmac : string &log; ## Source Mac Address 40 | dstmac : string &log; ## Destination Mac Address 41 | Log_Addr : string &log; ## Address data is being accessed from 42 | Length : count &log; ## Length of data 43 | Command : string &log; ## Ethercat Command 44 | data : string &log; ## Data read or write 45 | # ## TODO: Add other fields here that you'd like to log. 46 | }; 47 | global log_ecat_addr: event(rec: ECAT_LOG_ADDR); 48 | global log_policy_ecat_addr: Log::PolicyHook; 49 | 50 | ############################################################################################### 51 | ############################# ECAT_DEV_INFO -> ecat_dev_info.log ############################ 52 | ############################################################################################### 53 | type ECAT_DEV_INFO: record { 54 | ts : time &log; ## Timestamp for when the event happened 55 | slave_id : string &log; ## Ethercat Slave Address 56 | revision : string &log; ## Default Zeek connection info (IP addresses, ports) 57 | dev_type : string &log; ## Number of functions per message 58 | build : string &log; ## Build version 59 | fmmucnt : string &log; ## Fieldbus Memory Management Unit supported channel count 60 | smcount : string &log; ## Sync Manager count 61 | ports : string &log; ## Port Descriptor 62 | dpram : string &log; ## Ram size 63 | features : string &log; ## Features supported 64 | # ## TODO: Add other fields here that you'd like to log. 65 | }; 66 | global log_ecat_dev: event(rec: ECAT_DEV_INFO); 67 | global log_policy_ecat_dev: Log::PolicyHook; 68 | 69 | ############################################################################################### 70 | ############################# ECAT_AOE_INFO -> ecat_aoe_info.log ############################ 71 | ############################################################################################### 72 | type ECAT_AOE_INFO: record { 73 | ts : time &log; ## Timestamp for when the event happened 74 | targetid : string &log; ## Target Network ID 75 | targetport : string &log; ## Target Port 76 | senderid : string &log; ## Sender Network ID 77 | senderport : string &log; ## Sender Port 78 | cmd : string &log; ## Command 79 | stateflags : string &log; ## State Flags 80 | data : string &log; ## Command Data 81 | # ## TODO: Add other fields here that you'd like to log. 82 | }; 83 | global log_ecat_aoe: event(rec: ECAT_AOE_INFO); 84 | global log_policy_ecat_aoe: Log::PolicyHook; 85 | 86 | ############################################################################################### 87 | ############################# ECAT_COE_INFO -> ecat_coe_info.log ############################ 88 | ############################################################################################### 89 | type ECAT_COE_INFO: record { 90 | ts : time &log; ## Timestamp for when the event happened 91 | number : string &log; ## Message number 92 | Type : string &log; ## Message Type 93 | req_resp : string &log; ## Request or Response type 94 | index : string &log; ## Index 95 | subindex : string &log; ## Sub Index 96 | dataoffset : string &log; ## Data Offset 97 | # ## TODO: Add other fields here that you'd like to log. 98 | }; 99 | global log_ecat_coe: event(rec: ECAT_COE_INFO); 100 | global log_policy_ecat_coe: Log::PolicyHook; 101 | 102 | ############################################################################################### 103 | ############################# ECAT_FOE_INFO -> ecat_foe_info.log ############################ 104 | ############################################################################################### 105 | type ECAT_FOE_INFO: record { 106 | ts : time &log; ## Timestamp for when the event happened 107 | opCode : string &log; ## Operation Code 108 | reserved : string &log; ## Reserved 109 | packet_num : string &log; ## Packet number 110 | error_code : string &log; ## Error Code 111 | filename : string &log; ## Filename 112 | data : string &log; ## Transferred Data 113 | # ## TODO: Add other fields here that you'd like to log. 114 | }; 115 | global log_ecat_foe: event(rec: ECAT_FOE_INFO); 116 | global log_policy_ecat_foe: Log::PolicyHook; 117 | 118 | ############################################################################################### 119 | ############################# ECAT_SOE_INFO -> ecat_soe_info.log ############################ 120 | ############################################################################################### 121 | type ECAT_SOE_INFO: record { 122 | ts : time &log; ## Timestamp for when the event happened 123 | opCode : string &log; ## Command sent for controller 124 | incomplete : string &log; ## Function check to determine if it has been processed 125 | error : string &log; ## Error message 126 | drive_num : string &log; ## Drive number for command 127 | element_flags : string &log; ## Element Flags 128 | index : string &log; ## Message Index 129 | # ## TODO: Add other fields here that you'd like to log. 130 | }; 131 | global log_ecat_soe: event(rec: ECAT_SOE_INFO); 132 | global log_policy_ecat_soe: Log::PolicyHook; 133 | 134 | ############################################################################################### 135 | ############################# ECAT_ARP_INFO -> ecat_arp_info.log ############################ 136 | ############################################################################################### 137 | type ECAT_ARP_INFO: record { 138 | ts : time &log; ## Timestamp for when the event happened 139 | arp_type : string &log; ## Arp command 140 | mac_src : string &log; ## Source Mac address 141 | mac_dst : string &log; ## Destination Mac address 142 | SPA : addr &log; ## Sender protocol address 143 | SHA : string &log; ## Sender hardware address 144 | TPA : addr &log; ## Target protocol address 145 | THA : string &log; ## Target hardware address 146 | # ## TODO: Add other fields here that you'd like to log. 147 | }; 148 | global log_ecat_arp: event(rec: ECAT_ARP_INFO); 149 | global log_policy_ecat_arp: Log::PolicyHook; 150 | } 151 | 152 | 153 | ################################################################################################### 154 | ########## Defines Ethercat logs, ecat_log_address, ecat_dev_info, ecat_aoe_info ############ 155 | ########## ecat_dev_info, ecat_coe_info, ecat_foe_info, ecat_soe_info, ecat_arp_info ############ 156 | ########## ############ 157 | ########## Registers Ethercat Packet Analyzer, IP analyzer, and ARP analyzer ############ 158 | ################################################################################################### 159 | event zeek_init() &priority=20 { 160 | 161 | Log::create_stream(PacketAnalyzer::ECAT::LOG_ECAT_REGISTERS, [$columns=ECAT_REGISTER, $ev=log_ecat_registers, $path="ecat_registers", $policy=log_policy_ecat_registers]); 162 | Log::create_stream(PacketAnalyzer::ECAT::LOG_ECAT_ADDRESS, [$columns=ECAT_LOG_ADDR, $ev=log_ecat_addr, $path="ecat_log_address", $policy=log_policy_ecat_addr]); 163 | Log::create_stream(PacketAnalyzer::ECAT::LOG_ECAT_DEV_INFO, [$columns=ECAT_DEV_INFO, $ev=log_ecat_dev, $path="ecat_dev_info", $policy=log_policy_ecat_dev]); 164 | Log::create_stream(PacketAnalyzer::ECAT::LOG_ECAT_AOE_INFO, [$columns=ECAT_AOE_INFO, $ev=log_ecat_aoe, $path="ecat_aoe_info", $policy=log_policy_ecat_aoe]); 165 | Log::create_stream(PacketAnalyzer::ECAT::LOG_ECAT_COE_INFO, [$columns=ECAT_COE_INFO, $ev=log_ecat_coe, $path="ecat_coe_info", $policy=log_policy_ecat_coe]); 166 | Log::create_stream(PacketAnalyzer::ECAT::LOG_ECAT_FOE_INFO, [$columns=ECAT_FOE_INFO, $ev=log_ecat_foe, $path="ecat_foe_info", $policy=log_policy_ecat_foe]); 167 | Log::create_stream(PacketAnalyzer::ECAT::LOG_ECAT_SOE_INFO, [$columns=ECAT_SOE_INFO, $ev=log_ecat_soe, $path="ecat_soe_info", $policy=log_policy_ecat_soe]); 168 | Log::create_stream(PacketAnalyzer::ECAT::LOG_ECAT_ARP_INFO, [$columns=ECAT_ARP_INFO, $ev=log_ecat_arp, $path="ecat_arp_info", $policy=log_policy_ecat_arp]); 169 | PacketAnalyzer::register_packet_analyzer(PacketAnalyzer::ANALYZER_ETHERNET, 0x88a4, PacketAnalyzer::ANALYZER_ETHERCAT); 170 | PacketAnalyzer::register_packet_analyzer(PacketAnalyzer::ANALYZER_ETHERCAT, 0x0800, PacketAnalyzer::ANALYZER_IP); 171 | PacketAnalyzer::register_packet_analyzer(PacketAnalyzer::ANALYZER_ETHERCAT, 0x0806, PacketAnalyzer::ANALYZER_ARP); 172 | 173 | } 174 | 175 | ############################################################################################### 176 | ############### Defines logging of ecat_registers event -> ecat_registers.log ################ 177 | ############################################################################################### 178 | event ecat_registers(mac_src: string, mac_dst: string, slave_addr: count, reg_type: count, 179 | reg_addr: count, data_cmd: count, data: string) 180 | { 181 | local info: ECAT_REGISTER; 182 | info$ts = network_time(); 183 | info$srcmac = mac_src; 184 | info$dstmac = mac_dst; 185 | info$Slave_Addr = fmt("0x%02x", slave_addr); 186 | info$Register_Type = Ecat_Registers[reg_type]; 187 | info$Register_Addr = fmt("0x%02x", reg_addr); 188 | info$Command = Ecat_Cmd[data_cmd]; 189 | info$data = data; 190 | 191 | Log::write(PacketAnalyzer::ECAT::LOG_ECAT_REGISTERS, info); 192 | } 193 | 194 | ############################################################################################### 195 | ############# Defines logging of ecat_log_address event -> ecat_log_address.log ############## 196 | ############################################################################################### 197 | event ecat_log_address(mac_src: string, mac_dst: string, data_len: count, data_cmd: count, 198 | data_addr: string, data: string) 199 | { 200 | local info: ECAT_LOG_ADDR; 201 | info$ts = network_time(); 202 | info$srcmac = mac_src; 203 | info$dstmac = mac_dst; 204 | info$Log_Addr = data_addr; 205 | info$Length = data_len; 206 | info$Command = Ecat_Cmd[data_cmd]; 207 | info$data = data; 208 | 209 | Log::write(PacketAnalyzer::ECAT::LOG_ECAT_ADDRESS, info); 210 | } 211 | 212 | ############################################################################################### 213 | ################# Defines logging of ecat_device event -> ecat_dev_info.log ################## 214 | ############################################################################################### 215 | event ecat_device(SlaveId: count, Revision: count, Type: count, Build: count, FmmuCnt: count, 216 | SmCnt: count, Ports: count, Dpram: count, Features: count) 217 | { 218 | local info: ECAT_DEV_INFO; 219 | info$ts = network_time(); 220 | info$slave_id = fmt("0x%02x", SlaveId); 221 | info$revision = fmt("0x%02x", Revision); 222 | info$dev_type = fmt("0x%02x", Type); 223 | info$build = fmt("0x%02x", Build); 224 | info$fmmucnt = fmt("0x%02x", FmmuCnt); 225 | info$smcount = fmt("0x%02x", SmCnt); 226 | info$ports = fmt("0x%02x", Ports); 227 | info$dpram = fmt("0x%02x", Dpram); 228 | info$features = fmt("0x%02x", Features); 229 | Log::write(PacketAnalyzer::ECAT::LOG_ECAT_DEV_INFO, info); 230 | } 231 | 232 | ############################################################################################### 233 | ################## Defines logging of ecat_AoE event -> ecat_aoe_info.log #################### 234 | ############################################################################################### 235 | event ecat_AoE(targetid: string, senderid: string, targetport: count, senderport: count, 236 | cmd: count, stateflags: count, req_res: string) 237 | { 238 | local info: ECAT_AOE_INFO; 239 | info$ts = network_time(); 240 | info$targetid = targetid; 241 | info$targetport = fmt("0x%02x", targetport); 242 | info$senderid = senderid; 243 | info$senderport = fmt("0x%02x", senderport); 244 | info$cmd = Ecat_AoE_Cmd[cmd]; 245 | info$stateflags = fmt("0x%02x", stateflags); 246 | info$data = req_res; 247 | 248 | Log::write(PacketAnalyzer::ECAT::LOG_ECAT_AOE_INFO, info); 249 | } 250 | 251 | ############################################################################################### 252 | ################## Defines logging of ecat_CoE event -> ecat_coe_info.log #################### 253 | ############################################################################################### 254 | event ecat_CoE(number: count, Type: count, req_resp: count, index: count, subindex: count, 255 | dataoffset: count) 256 | { 257 | local info: ECAT_COE_INFO; 258 | info$ts = network_time(); 259 | 260 | info$number = fmt("0x%02x", number); 261 | info$Type = fmt("0x%02x", Type); 262 | info$req_resp = fmt("0x%02x", req_resp); 263 | info$index = fmt("0x%02x", index); 264 | info$subindex = fmt("0x%02x", subindex); 265 | info$dataoffset = fmt("0x%02x", dataoffset); 266 | 267 | Log::write(PacketAnalyzer::ECAT::LOG_ECAT_COE_INFO, info); 268 | } 269 | 270 | ############################################################################################### 271 | ################## Defines logging of ecat_FoE event -> ecat_foe_info.log #################### 272 | ############################################################################################### 273 | event ecat_FoE(opCode: count, reserved: count, packet_num: count, error_code: count, filename: 274 | string, data: string) 275 | { 276 | local info: ECAT_FOE_INFO; 277 | info$ts = network_time(); 278 | 279 | info$opCode = fmt("0x%02x", opCode); 280 | info$reserved = fmt("0x%02x", reserved); 281 | info$packet_num = fmt("0x%02x", packet_num); 282 | info$error_code = fmt("0x%02x", error_code); 283 | info$filename = filename; 284 | info$data = data; 285 | 286 | Log::write(PacketAnalyzer::ECAT::LOG_ECAT_FOE_INFO, info); 287 | } 288 | 289 | ############################################################################################### 290 | ################## Defines logging of ecat_SoE event -> ecat_soe_info.log #################### 291 | ############################################################################################### 292 | event ecat_SoE(opCode: count, incomplete: count, error: count, drive_num: count, 293 | element_flags: count, index: count) 294 | { 295 | local info: ECAT_SOE_INFO; 296 | info$ts = network_time(); 297 | 298 | info$opCode = fmt("0x%02x", opCode); 299 | info$incomplete = fmt("0x%02x", incomplete); 300 | info$error = fmt("0x%02x", error); 301 | info$drive_num = fmt("0x%02x", drive_num); 302 | info$element_flags = fmt("0x%02x", element_flags); 303 | info$index = fmt("0x%02x", index); 304 | 305 | Log::write(PacketAnalyzer::ECAT::LOG_ECAT_SOE_INFO, info); 306 | } 307 | 308 | ############################################################################################### 309 | ################ Defines logging of ecat_arp_info event -> ecat_arp_info.log ################# 310 | ############################################################################################### 311 | event arp_request(mac_src: string, mac_dst: string, SPA: addr, SHA: string, TPA: addr, THA: string) 312 | { 313 | local info: ECAT_ARP_INFO; 314 | info$ts = network_time(); 315 | info$arp_type = "Request"; 316 | info$mac_src = mac_src; 317 | info$mac_dst = mac_dst; 318 | info$SPA = SPA; 319 | info$SHA = SHA; 320 | info$TPA = TPA; 321 | info$THA = THA; 322 | 323 | Log::write(PacketAnalyzer::ECAT::LOG_ECAT_ARP_INFO, info); 324 | } 325 | 326 | ############################################################################################### 327 | ################ Defines logging of ecat_arp_info event -> ecat_arp_info.log ################# 328 | ############################################################################################### 329 | event arp_reply(mac_src: string, mac_dst: string, SPA: addr, SHA: string, TPA: addr, THA: string) 330 | { 331 | local info: ECAT_ARP_INFO; 332 | info$ts = network_time(); 333 | info$arp_type = "Reply"; 334 | info$mac_src = mac_src; 335 | info$mac_dst = mac_dst; 336 | info$SPA = SPA; 337 | info$SHA = SHA; 338 | info$TPA = TPA; 339 | info$THA = THA; 340 | 341 | Log::write(PacketAnalyzer::ECAT::LOG_ECAT_ARP_INFO, info); 342 | } -------------------------------------------------------------------------------- /src/ECAT.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Battelle Energy Alliance, LLC. All rights reserved. 2 | // ECAT.cc 3 | // 4 | // ECAT - Defines packet analysis functions for parsing ethercat packets. 5 | // 6 | // Author: Devin Vollmer 7 | // Contact: devin.vollmer@inl.gov 8 | 9 | 10 | #include "ECAT.h" 11 | #include "ECAT_ENUMS.h" 12 | #include "zeek/Event.h" 13 | #include 14 | #include 15 | #include "events.bif.h" 16 | 17 | #include "zeek/zeek-config.h" 18 | #ifdef HAVE_NET_ETHERNET_H 19 | #include 20 | #elif defined(HAVE_SYS_ETHERNET_H) 21 | #include 22 | #elif defined(HAVE_NETINET_IF_ETHER_H) 23 | #include 24 | #elif defined(HAVE_NET_ETHERTYPES_H) 25 | #include 26 | #endif 27 | 28 | using namespace zeek::packet_analysis::ETHERCAT; 29 | 30 | ecat_datagram ec_datagram[MAX_DATAGRAM_COUNT]; 31 | ecat_device_info ec_devinfo; 32 | ecat_mailbox ec_mailbox; 33 | 34 | ECATAnalyzer::ECATAnalyzer() 35 | : zeek::packet_analysis::Analyzer("ETHERCAT") 36 | { 37 | } 38 | 39 | void ECATAnalyzer::Initialize() 40 | { 41 | Analyzer::Initialize(); 42 | } 43 | 44 | // Assumption: there is 2 bytes available starting at "data" 45 | uint16_t inline readLittleEndianShort(const uint8_t *data) 46 | { 47 | return (data[1] << 8) + data[0]; 48 | } 49 | 50 | // Assumption: there is 2 bytes available starting at "data" 51 | uint16_t inline readBigEndianShort(const uint8_t *data) 52 | { 53 | return (data[0] << 8) + data[1]; 54 | } 55 | 56 | // Assumption: there is 4 bytes available starting at "data" 57 | uint32_t inline readLittleEndianInt(const uint8_t *data) 58 | { 59 | return (data[3] << 24) + (data[2] << 16) + (data[1] << 8) + data[0]; 60 | } 61 | 62 | void inline setAddresses(uint8_t currentIndex, uint32_t addressValue, uint16_t slaveAddress, uint16_t offsetValue) 63 | { 64 | ec_datagram[currentIndex].address[0] = addressValue & 0xFF; 65 | ec_datagram[currentIndex].address[1] = (addressValue & 0xFF00) >> 8; 66 | ec_datagram[currentIndex].address[2] = (addressValue & 0xFF0000) >> 16; 67 | ec_datagram[currentIndex].address[3] = (addressValue & 0xFF000000) >> 24; 68 | ec_datagram[currentIndex].slave_addr = slaveAddress; 69 | ec_datagram[currentIndex].off_addr = offsetValue; 70 | } 71 | 72 | // Returns true if currentRegisterAddress <= offsetAddress < nextRegisterAddress 73 | bool inline inDesiredRegisterRange(uint16_t offsetAddress, uint16_t currentRegisterAddress, uint16_t nextRegisterAddress) 74 | { 75 | return (offsetAddress >= currentRegisterAddress) && (offsetAddress < nextRegisterAddress); 76 | } 77 | 78 | // ----------------------------------ECATAnalyzer AnalyzePacket------------------------------------ 79 | // Message Description: 80 | // Main Packet Analyzer for Ecat traffic 81 | // Arguments: 82 | // - len: Length of data passed to analyzer 83 | // - data: Data to be analyzed 84 | // - packet: Packet information from parent analyzer(ie. Ethernet analyzer) 85 | // Protocol Parsing: 86 | // Parses data according to pack 87 | // 88 | // ------------------------------------------------------------------------------------------------ 89 | bool ECATAnalyzer::AnalyzePacket(size_t len, const uint8_t* data, Packet* packet) 90 | { 91 | const u_char* src = packet->l2_src; 92 | const u_char* dst = packet->l2_dst; 93 | 94 | uint16_t messageLength; 95 | bool reservedBit; 96 | uint8_t messageType; 97 | // Verify we have enough data to extract a header 98 | if(!parseMessageHeader(data, len, messageLength, reservedBit, messageType)) 99 | { 100 | Weird("Short EtherCAT Packet", packet); 101 | return false; 102 | } 103 | 104 | if(messageLength + HEADER_LENGTH > len) 105 | { 106 | Weird("Message Length and Packet Length Mismatch", packet); 107 | return false; 108 | } 109 | 110 | uint8_t datagramCount = 0; 111 | uint16_t dataProcessed = 0; 112 | const uint8_t *currentDatagramStart = data + HEADER_LENGTH; 113 | size_t remainingLength = messageLength; 114 | memset(ec_datagram, 0, sizeof(ec_datagram)); 115 | while(0 < remainingLength) 116 | { 117 | uint16_t tempDataProcessed; 118 | if(!parseDatagram(currentDatagramStart, remainingLength, 119 | datagramCount, tempDataProcessed)) 120 | { 121 | Weird("Bad Packet Length", packet); 122 | return false; 123 | } 124 | currentDatagramStart += tempDataProcessed; 125 | remainingLength -= tempDataProcessed; 126 | 127 | // Log the information 128 | if(0xFF != ec_datagram[datagramCount].address[0] && 129 | 0xFF != ec_datagram[datagramCount].address[2]) 130 | { 131 | if(ecat_log_address) 132 | { 133 | event_mgr.Enqueue(ecat_log_address, 134 | ToEthAddrStr(src), 135 | ToEthAddrStr(dst), 136 | val_mgr->Count(ec_datagram[datagramCount].length), 137 | val_mgr->Count(ec_datagram[datagramCount].cmd), 138 | HexToString(ec_datagram[datagramCount].address, 4), 139 | HexToString(ec_datagram[datagramCount].data, ec_datagram[datagramCount].length)); 140 | } 141 | } 142 | else if(0xFF != ec_datagram[datagramCount].off_addr) 143 | { 144 | if(ecat_registers) 145 | { 146 | event_mgr.Enqueue(ecat_registers, 147 | ToEthAddrStr(src), 148 | ToEthAddrStr(dst), 149 | val_mgr->Count(ec_datagram[datagramCount].slave_addr), 150 | val_mgr->Count(ec_datagram[datagramCount].off_addr_name), 151 | val_mgr->Count(ec_datagram[datagramCount].off_addr), 152 | val_mgr->Count(ec_datagram[datagramCount].cmd), 153 | HexToString(ec_datagram[datagramCount].data, ec_datagram[datagramCount].length)); 154 | } 155 | } 156 | 157 | if(0 == ec_datagram[datagramCount++].last_indicator) 158 | { 159 | // Last datagram has been parsed 160 | break; 161 | } 162 | 163 | if(MAX_DATAGRAM_COUNT <= datagramCount) 164 | { 165 | Weird("Too many datagrams", packet); 166 | return false; 167 | } 168 | } 169 | 170 | if(0 < remainingLength) 171 | { 172 | Weird("Packet data remaining", packet); 173 | return false; 174 | } 175 | 176 | return true; 177 | } 178 | 179 | // -------------------------------ECATAnalyzer parseMessageHeader---------------------------------- 180 | // Message Description: 181 | // Parses the two byte EtherCAT Header 182 | // Arguments: 183 | // - data: Data to be analyzed 184 | // - length: Length of data 185 | // - messageLength (out): Parsed message length 186 | // - reserveBit (out): Value of the reserved bit 187 | // - messageType (out): Parsed message type 188 | // Return: 189 | // Returns whether or not there were enough bytes to parse the message header 190 | // Protocol Parsing: 191 | // Parses data according to pack (note, packed as Little Endian) 192 | // 193 | // Ethercat length and message type sent in 2 bytes 194 | // Message Length .... .xxx xxxx xxxx 195 | // Reserved .... 0... .... .... 196 | // Message Type xxxx .... .... .... 197 | // ------------------------------------------------------------------------------------------------ 198 | bool ECATAnalyzer::parseMessageHeader(const uint8_t* data, size_t length, uint16_t &messageLength, bool &reserveBit, uint8_t &messageType) 199 | { 200 | if(HEADER_LENGTH > length) 201 | { 202 | return false; 203 | } 204 | uint16_t messageHeader = readLittleEndianShort(data); 205 | messageLength = messageHeader & 0x07FF; 206 | reserveBit = (messageHeader & 0x0800) >> 11; 207 | messageType = (messageHeader & 0xF000) >> 12; 208 | return true; 209 | } 210 | 211 | // -------------------------------ECATAnalyzer parseDatagram---------------------------------- 212 | // Message Description: 213 | // Parses the two byte EtherCAT Header 214 | // Arguments: 215 | // - data: Data to be analyzed 216 | // - length: Length of data 217 | // - currentIndex: Datagram array index to use for storing data 218 | // - dataProcessed (out): The number of bytes parsed for the datagram 219 | // Return: 220 | // Returns whether or not the datagram was parsed correctly 221 | // Protocol Parsing: 222 | // Parses data according to pack 223 | // ------------------------------------------------------------------------------------------------ 224 | bool ECATAnalyzer::parseDatagram(const uint8_t* data, size_t length, uint8_t currentIndex, uint16_t &dataProcessed) 225 | { 226 | memset(&ec_devinfo, 0, sizeof(ec_devinfo)); 227 | memset(&ec_mailbox, 0, sizeof(ec_mailbox)); 228 | dataProcessed = 0; 229 | if(!parseDatagramHeader(data, length, currentIndex)) 230 | { 231 | return false; 232 | } 233 | dataProcessed = DATAGRAM_HEADER_LENGTH; 234 | uint16_t newDataProcessed; 235 | bool failedInFurtherProcessing; 236 | if(!parseDatagramBody(data + dataProcessed, length - dataProcessed, currentIndex, newDataProcessed, failedInFurtherProcessing)) 237 | { 238 | return false; 239 | } 240 | if(failedInFurtherProcessing) 241 | { 242 | // TODO: What do we do? 243 | } 244 | dataProcessed += newDataProcessed; 245 | if(!parseDatagramTrailer(data + dataProcessed, length - dataProcessed, currentIndex)) 246 | { 247 | return false; 248 | } 249 | dataProcessed += DATAGRAM_TRAILER_LENGTH; 250 | return true; 251 | } 252 | 253 | // -------------------------------ECATAnalyzer parseMessageHeader---------------------------------- 254 | // Message Description: 255 | // Parses the EtherCAT Datagram Header 256 | // Arguments: 257 | // - data: Data to be analyzed 258 | // - length: Length of data 259 | // - currentIndex: Datagram array index to use for storing data 260 | // Return: 261 | // Returns whether or not there were enough bytes to parse the message header 262 | // Protocol Parsing: 263 | // Parses data according to pack (note, packed as Little Endian) 264 | // 265 | // Ethercat length and message type sent in 2 bytes 266 | // Message Length .... .xxx xxxx xxxx 267 | // Reserved ..00 0... .... .... 268 | // Circulating .x.. .... .... .... 269 | // Next x... .... .... .... 270 | // ------------------------------------------------------------------------------------------------ 271 | bool ECATAnalyzer::parseDatagramHeader(const uint8_t* data, size_t length, uint8_t currentIndex) 272 | { 273 | if(length < DATAGRAM_HEADER_LENGTH) 274 | { 275 | return false; 276 | } 277 | // 8-bit Command 278 | ec_datagram[currentIndex].cmd = data[0]; 279 | // 8-bit Index 280 | ec_datagram[currentIndex].index = data[1]; 281 | // 32-bit Address/(Slave Address + Offset) 282 | uint32_t tempAddress = readLittleEndianInt(data+2); 283 | if(commandUsesRegularAddress(ec_datagram[currentIndex].cmd)) 284 | { 285 | // 0xFF is used in the old code as a marker for the offset 286 | setAddresses(currentIndex, tempAddress, 0x00, 0xFF); 287 | } 288 | else 289 | { 290 | uint16_t slaveAddress = tempAddress & 0xFFFF; 291 | uint16_t offset = (tempAddress & 0xFFFF0000) >> 16; 292 | setAddresses(currentIndex, 0xFFFFFFFF, slaveAddress, offset); 293 | ec_datagram[currentIndex].off_addr_name = determineOffsetName(offset); 294 | } 295 | // 16-bit Bitfield 296 | uint16_t tempBits = readLittleEndianShort(data+6); 297 | ec_datagram[currentIndex].length = tempBits & 0x07FF; 298 | uint8_t reservedBits = (tempBits & 0x3800) >> 11; 299 | uint8_t circulatingBit = (tempBits & 0x4000) >> 14; 300 | uint8_t hasNextBit = (tempBits & 0x8000) >> 15; 301 | // TODO: Double check this...this seems to be a bug in the old version where 302 | // it uses both circulating bits and hasNextBit 303 | ec_datagram[currentIndex].last_indicator = hasNextBit; 304 | // 16-bit IRQ 305 | ec_datagram[currentIndex].interrupt = readLittleEndianShort(data+8); 306 | return true; 307 | } 308 | 309 | // -------------------------------ECATAnalyzer parseDatagramBody---------------------------------- 310 | // Message Description: 311 | // Parses the EtherCAT Datagram Body 312 | // Arguments: 313 | // - data: Data to be analyzed 314 | // - length: Length of data 315 | // - currentIndex: Datagram array index to use for storing data 316 | // - dataProcessed (out): The number of bytes parsed for the datagram 317 | // Return: 318 | // Returns whether or not there were enough bytes to parse the message trailer 319 | // Protocol Parsing: 320 | // Parses data according to pack (note, packed as Little Endian) 321 | // ------------------------------------------------------------------------------------------------ 322 | bool ECATAnalyzer::parseDatagramBody(const uint8_t* data, size_t length, uint8_t currentIndex, uint16_t &dataProcessed, bool &failedInFurtherProcessing) 323 | { 324 | failedInFurtherProcessing = false; 325 | if(ec_datagram[currentIndex].length > length || 326 | ec_datagram[currentIndex].length > MAX_DATA_SIZE) 327 | { 328 | return false; 329 | } 330 | 331 | // First copy the data 332 | memcpy(ec_datagram[currentIndex].data, data, ec_datagram[currentIndex].length); 333 | dataProcessed = ec_datagram[currentIndex].length; 334 | 335 | // Attempt further processing if possible 336 | switch(ec_datagram[currentIndex].cmd) 337 | { 338 | case ecat_datagram_aprd: 339 | // Intentional Fallthrough 340 | case ecat_datagram_apwr: 341 | // Intentional Fallthrough 342 | case ecat_datagram_aprw: 343 | // Intentional Fallthrough 344 | case ecat_datagram_fprd: 345 | // Intentional Fallthrough 346 | case ecat_datagram_fpwr: 347 | // Intentional Fallthrough 348 | case ecat_datagram_fprw: 349 | // Intentional Fallthrough 350 | case ecat_datagram_brd: 351 | // Intentional Fallthrough 352 | case ecat_datagram_bwr: 353 | // Intentional Fallthrough 354 | case ecat_datagram_brw: 355 | // Intentional Fallthrough 356 | case ecat_datagram_armw: 357 | // Intentional Fallthrough 358 | case ecat_datagram_frmw: 359 | failedInFurtherProcessing = !furtherProcessDatagramBody(currentIndex); 360 | break; 361 | default: 362 | // Currently don't know how to process...so leave it as data 363 | break; 364 | } 365 | return true; 366 | } 367 | 368 | // -------------------------------ECATAnalyzer furtherProcessDatagramBody---------------------------------- 369 | // Message Description: 370 | // Further process the datagram body if we know how to 371 | // Arguments: 372 | // - currentIndex: Datagram array index to use for storing data 373 | // Return: 374 | // False if there was a problem parsing known data, otherwise true 375 | // ------------------------------------------------------------------------------------------------ 376 | bool ECATAnalyzer::furtherProcessDatagramBody(uint8_t currentIndex) 377 | { 378 | switch(ec_datagram[currentIndex].off_addr_name) 379 | { 380 | case dev_type: 381 | return processDevTypeData(currentIndex); 382 | case pd_ram: 383 | return processPDRAMData(currentIndex); 384 | default: 385 | // Don't know how to parse further 386 | return true; 387 | } 388 | } 389 | 390 | bool ECATAnalyzer::processDevTypeData(uint8_t currentIndex) 391 | { 392 | if(ec_datagram[currentIndex].length < DEVICE_INFO_SIZE) 393 | { 394 | return false; 395 | } 396 | 397 | ec_devinfo.revision = ec_datagram[currentIndex].data[0]; 398 | ec_devinfo.type = ec_datagram[currentIndex].data[1]; 399 | ec_devinfo.build = readLittleEndianShort(ec_datagram[currentIndex].data + 2); 400 | ec_devinfo.fmmu_cnt = ec_datagram[currentIndex].data[4]; 401 | ec_devinfo.sm_cnt = ec_datagram[currentIndex].data[5]; 402 | ec_devinfo.dpram = ec_datagram[currentIndex].data[6]; 403 | ec_devinfo.ports = ec_datagram[currentIndex].data[7]; 404 | ec_devinfo.features = readLittleEndianShort(ec_datagram[currentIndex].data + 8); 405 | 406 | if(ecat_device) 407 | { 408 | event_mgr.Enqueue(ecat_device, 409 | val_mgr->Count(ec_datagram[currentIndex].slave_addr), 410 | val_mgr->Count(ec_devinfo.revision), 411 | val_mgr->Count(ec_devinfo.type), 412 | val_mgr->Count(ec_devinfo.build), 413 | val_mgr->Count(ec_devinfo.fmmu_cnt), 414 | val_mgr->Count(ec_devinfo.sm_cnt), 415 | val_mgr->Count(ec_devinfo.ports), 416 | val_mgr->Count(ec_devinfo.dpram), 417 | val_mgr->Count(ec_devinfo.features)); 418 | } 419 | 420 | return true; 421 | } 422 | 423 | bool ECATAnalyzer::processPDRAMData(uint8_t currentIndex) 424 | { 425 | if(ec_datagram[currentIndex].length < MAILBOX_HEADER_SIZE) 426 | { 427 | return false; 428 | } 429 | ec_mailbox.header.length = readLittleEndianShort(ec_datagram[currentIndex].data); 430 | ec_mailbox.header.address = readLittleEndianShort(ec_datagram[currentIndex].data + 2); 431 | uint16_t otherBits = readBigEndianShort(ec_datagram[currentIndex].data + 4); 432 | uint8_t reserved = (otherBits & 0xFC) >> 10; 433 | ec_mailbox.header.priority = (otherBits & 0x0300) >> 8; 434 | ec_mailbox.header.counter = (otherBits & 0xF0) >> 4; 435 | ec_mailbox.header.type = otherBits & 0x0F; 436 | 437 | if(ec_datagram[currentIndex].length < MAILBOX_HEADER_SIZE + ec_mailbox.header.length) 438 | { 439 | return false; 440 | } 441 | 442 | // Try to parse the rest of the mailbox 443 | return parseMailbox(currentIndex); 444 | } 445 | 446 | bool ECATAnalyzer::parseMailbox(uint8_t currentIndex) 447 | { 448 | switch(ec_mailbox.header.type) 449 | { 450 | case ecat_mbx_aoe: 451 | return parseAOE(currentIndex); 452 | case ecat_mbx_eoe: 453 | return parseEOE(currentIndex); 454 | case ecat_mbx_coe: 455 | return parseCOE(currentIndex); 456 | case ecat_mbx_foe: 457 | return parseFOE(currentIndex); 458 | case ecat_mbx_soe: 459 | return parseSOE(currentIndex); 460 | default: 461 | // Unknown, no parsing 462 | break; 463 | } 464 | return true; 465 | } 466 | 467 | bool ECATAnalyzer::parseAOE(uint8_t currentIndex) 468 | { 469 | if(ec_mailbox.header.length < AOE_HEADER_SIZE) 470 | { 471 | return false; 472 | } 473 | 474 | const uint8_t* mailboxStart = ec_datagram[currentIndex].data + MAILBOX_HEADER_SIZE; 475 | 476 | for(int16_t i = AOE_ID_LENGTH-1; i >= 0; i--) 477 | { 478 | ec_mailbox.aoe.targetid[i] = mailboxStart[AOE_ID_LENGTH - i - 1]; 479 | } 480 | uint16_t currentOffset = AOE_ID_LENGTH; 481 | 482 | ec_mailbox.aoe.targetport = readLittleEndianShort(mailboxStart + currentOffset); 483 | currentOffset += AOE_PORT_LENGTH; 484 | 485 | for(int16_t i = AOE_ID_LENGTH-1; i >= 0; i--) 486 | { 487 | ec_mailbox.aoe.senderid[i] = mailboxStart[currentOffset + (AOE_ID_LENGTH - i - 1)]; 488 | } 489 | currentOffset += AOE_ID_LENGTH; 490 | 491 | ec_mailbox.aoe.senderport = readLittleEndianShort(mailboxStart + currentOffset); 492 | currentOffset += AOE_PORT_LENGTH; 493 | 494 | ec_mailbox.aoe.cmd = readLittleEndianShort(mailboxStart + currentOffset); 495 | currentOffset += 2; 496 | 497 | ec_mailbox.aoe.stateflags = readLittleEndianShort(mailboxStart + currentOffset); 498 | currentOffset += 2; 499 | 500 | ec_mailbox.aoe.cbdata = readLittleEndianInt(mailboxStart + currentOffset); 501 | currentOffset += 4; 502 | 503 | ec_mailbox.aoe.errorcode = readLittleEndianInt(mailboxStart + currentOffset); 504 | currentOffset += 4; 505 | 506 | ec_mailbox.aoe.invokeid = readLittleEndianInt(mailboxStart + currentOffset); 507 | currentOffset += 4; 508 | 509 | uint16_t dataSize = ec_mailbox.header.length - AOE_HEADER_SIZE; 510 | if(dataSize > 0) 511 | { 512 | memcpy(ec_mailbox.aoe.req_res, mailboxStart + AOE_HEADER_SIZE, dataSize); 513 | } 514 | 515 | if(ecat_AoE) 516 | { 517 | event_mgr.Enqueue(ecat_AoE, 518 | ToEthAddrStr(ec_mailbox.aoe.targetid), 519 | ToEthAddrStr(ec_mailbox.aoe.senderid), 520 | val_mgr->Count(ec_mailbox.aoe.targetport), 521 | val_mgr->Count(ec_mailbox.aoe.senderport), 522 | val_mgr->Count(ec_mailbox.aoe.cmd), 523 | val_mgr->Count(ec_mailbox.aoe.stateflags), 524 | HexToString(ec_mailbox.aoe.req_res, dataSize)); 525 | } 526 | 527 | return true; 528 | } 529 | 530 | bool ECATAnalyzer::parseEOE(uint8_t currentIndex) 531 | { 532 | if(ec_mailbox.header.length < EOE_HEADER_SIZE) 533 | { 534 | return false; 535 | } 536 | 537 | const uint8_t* mailboxStart = ec_datagram[currentIndex].data + MAILBOX_HEADER_SIZE; 538 | 539 | Packet tempPacket; 540 | 541 | // Really confused by these values... 542 | // EoE Header is the first 4 bytes 543 | tempPacket.eth_type = readBigEndianShort(mailboxStart + 16); 544 | tempPacket.l2_dst = mailboxStart + 4; 545 | tempPacket.l2_src = mailboxStart + 10; 546 | tempPacket.len = ec_mailbox.header.length - 4; 547 | tempPacket.data = mailboxStart + 4; 548 | 549 | if(tempPacket.eth_type < 0x0800) 550 | { 551 | return false; 552 | } 553 | ForwardPacket(tempPacket.len, mailboxStart + 18, &tempPacket, tempPacket.eth_type); 554 | return true; 555 | } 556 | 557 | bool ECATAnalyzer::parseCOE(uint8_t currentIndex) 558 | { 559 | if(ec_mailbox.header.length < COE_HEADER_SIZE) 560 | { 561 | return false; 562 | } 563 | 564 | const uint8_t* mailboxStart = ec_datagram[currentIndex].data + MAILBOX_HEADER_SIZE; 565 | 566 | ec_mailbox.coe.number = mailboxStart[0]; 567 | ec_mailbox.coe.type = (mailboxStart[1] & 0xF0) >> 4; 568 | ec_mailbox.coe.req_resp = mailboxStart[2]; 569 | ec_mailbox.coe.index = readLittleEndianShort(mailboxStart + 3); 570 | ec_mailbox.coe.subindex = mailboxStart[5]; 571 | ec_mailbox.coe.data_offset = readLittleEndianInt(mailboxStart + 6); 572 | 573 | if(ecat_CoE) 574 | { 575 | event_mgr.Enqueue(ecat_CoE, 576 | val_mgr->Count(ec_mailbox.coe.number), 577 | val_mgr->Count(ec_mailbox.coe.type), 578 | val_mgr->Count(ec_mailbox.coe.req_resp), 579 | val_mgr->Count(ec_mailbox.coe.index), 580 | val_mgr->Count(ec_mailbox.coe.subindex), 581 | val_mgr->Count(ec_mailbox.coe.data_offset)); 582 | } 583 | return true; 584 | } 585 | 586 | bool ECATAnalyzer::parseFOE(uint8_t currentIndex) 587 | { 588 | if(ec_mailbox.header.length < FOE_HEADER_SIZE) 589 | { 590 | return false; 591 | } 592 | 593 | const uint8_t* mailboxStart = ec_datagram[currentIndex].data + MAILBOX_HEADER_SIZE; 594 | 595 | ec_mailbox.foe.opCode = mailboxStart[0]; 596 | ec_mailbox.foe.reserved = mailboxStart[1]; 597 | ec_mailbox.foe.password = readLittleEndianInt(mailboxStart + 2); 598 | ec_mailbox.foe.packet_num = readLittleEndianInt(mailboxStart + 6); 599 | ec_mailbox.foe.error_code = readLittleEndianInt(mailboxStart + 10); 600 | 601 | // See if optional fields exist 602 | if(ec_mailbox.header.length >= FOE_HEADER_SIZE + MAX_FOE_DATA_SIZE) 603 | { 604 | memcpy(ec_mailbox.foe.filename, 605 | mailboxStart + FOE_HEADER_SIZE, 606 | MAX_FOE_DATA_SIZE); 607 | } 608 | 609 | if(ec_mailbox.header.length >= FOE_HEADER_SIZE + 2 * MAX_FOE_DATA_SIZE) 610 | { 611 | memcpy(ec_mailbox.foe.data, 612 | mailboxStart + FOE_HEADER_SIZE + MAX_FOE_DATA_SIZE, 613 | MAX_FOE_DATA_SIZE); 614 | } 615 | 616 | if(ec_mailbox.header.length >= FOE_HEADER_SIZE + 3 * MAX_FOE_DATA_SIZE) 617 | { 618 | memcpy(ec_mailbox.foe.error_txt, 619 | mailboxStart + FOE_HEADER_SIZE + 2 * MAX_FOE_DATA_SIZE, 620 | MAX_FOE_DATA_SIZE); 621 | } 622 | 623 | if(ecat_FoE) 624 | { 625 | event_mgr.Enqueue(ecat_FoE, 626 | val_mgr->Count(ec_mailbox.foe.opCode), 627 | val_mgr->Count(ec_mailbox.foe.reserved), 628 | val_mgr->Count(ec_mailbox.foe.packet_num), 629 | val_mgr->Count(ec_mailbox.foe.error_code), 630 | HexToString(ec_mailbox.foe.filename, MAX_FOE_DATA_SIZE), 631 | HexToString(ec_mailbox.foe.data, MAX_FOE_DATA_SIZE)); 632 | } 633 | 634 | return true; 635 | } 636 | 637 | bool ECATAnalyzer::parseSOE(uint8_t currentIndex) 638 | { 639 | if(ec_mailbox.header.length < SOE_HEADER_SIZE) 640 | { 641 | return false; 642 | } 643 | 644 | const uint8_t* mailboxStart = ec_datagram[currentIndex].data + MAILBOX_HEADER_SIZE; 645 | 646 | ec_mailbox.soe.opCode = mailboxStart[0]; 647 | ec_mailbox.soe.incomplete = mailboxStart[1]; 648 | ec_mailbox.soe.error = mailboxStart[2]; 649 | ec_mailbox.soe.drive_num = mailboxStart[3]; 650 | ec_mailbox.soe.element_flags = mailboxStart[4]; 651 | ec_mailbox.soe.index = readLittleEndianShort(mailboxStart + 5); 652 | 653 | if(ecat_SoE) 654 | { 655 | event_mgr.Enqueue(ecat_SoE, 656 | val_mgr->Count(ec_mailbox.soe.opCode), 657 | val_mgr->Count(ec_mailbox.soe.incomplete), 658 | val_mgr->Count(ec_mailbox.soe.error), 659 | val_mgr->Count(ec_mailbox.soe.drive_num), 660 | val_mgr->Count(ec_mailbox.soe.element_flags), 661 | val_mgr->Count(ec_mailbox.soe.index)); 662 | } 663 | 664 | return true; 665 | } 666 | 667 | // -------------------------------ECATAnalyzer parseDatagramTrailer---------------------------------- 668 | // Message Description: 669 | // Parses the two byte EtherCAT Datagram Trailer 670 | // Arguments: 671 | // - data: Data to be analyzed 672 | // - length: Length of data 673 | // - currentIndex: Datagram array index to use for storing data 674 | // Return: 675 | // Returns whether or not there were enough bytes to parse the message trailer 676 | // Protocol Parsing: 677 | // Parses data according to pack (note, packed as Little Endian) 678 | // ------------------------------------------------------------------------------------------------ 679 | bool ECATAnalyzer::parseDatagramTrailer(const uint8_t* data, size_t length, uint8_t currentIndex) 680 | { 681 | if(length < DATAGRAM_TRAILER_LENGTH) 682 | { 683 | return false; 684 | } 685 | ec_datagram[currentIndex].working_counter = readLittleEndianShort(data); 686 | return true; 687 | } 688 | 689 | // -------------------------------ECATAnalyzer commandUsesRegularAddress---------------------------------- 690 | // Message Description: 691 | // Checks if the command uses a regular address or a Slave Address + Offset scheme 692 | // Arguments: 693 | // - command: Datagram Command 694 | // Return: 695 | // Returns true if the command uses a regular address, otherwise false 696 | // ------------------------------------------------------------------------------------------------ 697 | bool ECATAnalyzer::commandUsesRegularAddress(uint8_t command) 698 | { 699 | switch(command) 700 | { 701 | case ecat_datagram_lrd: 702 | // Intentional Fallthrough 703 | case ecat_datagram_lwr: 704 | // Intentional Fallthrough 705 | case ecat_datagram_lrw: 706 | return true; 707 | default: 708 | return false; 709 | } 710 | } 711 | 712 | // -------------------------------ECATAnalyzer determineOffsetName---------------------------------- 713 | // Message Description: 714 | // Checks if the command uses a regular address or a Slave Address + Offset scheme 715 | // Arguments: 716 | // - offsetAddress: Datagram Offset Address 717 | // Return: 718 | // The normalized Datagram Offset Address Name 719 | // ------------------------------------------------------------------------------------------------ 720 | uint16_t ECATAnalyzer::determineOffsetName(uint16_t offsetAddress) 721 | { 722 | // Just handle the ones with breaks: 723 | if(inDesiredRegisterRange(offsetAddress, build, fmmuspt)) 724 | { 725 | return build; 726 | } 727 | else if(inDesiredRegisterRange(offsetAddress, esc_features, cs_addr)) 728 | { 729 | return esc_features; 730 | } 731 | else if(inDesiredRegisterRange(offsetAddress, cs_addr, cs_alias)) 732 | { 733 | return cs_addr; 734 | } 735 | else if(inDesiredRegisterRange(offsetAddress, cs_alias, reg_write_en)) 736 | { 737 | return cs_alias; 738 | } 739 | else if(inDesiredRegisterRange(offsetAddress, reg_write_prot, esc_write_en)) 740 | { 741 | return reg_write_prot; 742 | } 743 | else if(inDesiredRegisterRange(offsetAddress, esc_write_prot, esc_rst_ecat)) 744 | { 745 | return esc_write_prot; 746 | } 747 | else if(inDesiredRegisterRange(offsetAddress, esc_rst_pdi, esc_dl_ctl)) 748 | { 749 | return esc_rst_pdi; 750 | } 751 | else if(inDesiredRegisterRange(offsetAddress, esc_dl_ctl, phy_rd_wr_offs)) 752 | { 753 | return esc_dl_ctl; 754 | } 755 | else if(inDesiredRegisterRange(offsetAddress, phy_rd_wr_offs, esc_dl_stat)) 756 | { 757 | return phy_rd_wr_offs; 758 | } 759 | else if(inDesiredRegisterRange(offsetAddress, esc_dl_stat, al_ctrl)) 760 | { 761 | return esc_dl_stat; 762 | } 763 | else if(inDesiredRegisterRange(offsetAddress, al_ctrl, al_stat)) 764 | { 765 | return al_ctrl; 766 | } 767 | else if(inDesiredRegisterRange(offsetAddress, al_stat, al_stat_code)) 768 | { 769 | return al_stat; 770 | } 771 | else if(inDesiredRegisterRange(offsetAddress, al_stat_code, run_led_ovrd)) 772 | { 773 | return al_stat_code; 774 | } 775 | else if(inDesiredRegisterRange(offsetAddress, esc_conf, pdi_info)) 776 | { 777 | return esc_conf; 778 | } 779 | else if(inDesiredRegisterRange(offsetAddress, pdi_info, pdi_conf)) 780 | { 781 | return pdi_info; 782 | } 783 | else if(inDesiredRegisterRange(offsetAddress, pdi_onchip_conf, ecat_ev_msk)) 784 | { 785 | return pdi_onchip_conf; 786 | } 787 | else if(inDesiredRegisterRange(offsetAddress, ecat_ev_msk, pdi_al_ev_msk)) 788 | { 789 | return ecat_ev_msk; 790 | } 791 | else if(inDesiredRegisterRange(offsetAddress, pdi_al_ev_msk, ecat_ev_req)) 792 | { 793 | return pdi_al_ev_msk; 794 | } 795 | else if(inDesiredRegisterRange(offsetAddress, ecat_ev_req, al_ev_req)) 796 | { 797 | return ecat_ev_req; 798 | } 799 | else if(inDesiredRegisterRange(offsetAddress, al_ev_req, rx_err_cnt)) 800 | { 801 | return al_ev_req; 802 | } 803 | else if(inDesiredRegisterRange(offsetAddress, rx_err_cnt, fwd_rx_err_cnt)) 804 | { 805 | return rx_err_cnt; 806 | } 807 | else if(inDesiredRegisterRange(offsetAddress, fwd_rx_err_cnt, ecat_proc_err_cnt)) 808 | { 809 | return fwd_rx_err_cnt; 810 | } 811 | else if(inDesiredRegisterRange(offsetAddress, pdi_err_code, llc)) 812 | { 813 | return pdi_err_code; 814 | } 815 | else if(inDesiredRegisterRange(offsetAddress, llc, wtd_div)) 816 | { 817 | return llc; 818 | } 819 | else if(inDesiredRegisterRange(offsetAddress, wtd_div, wtd_time_pdi)) 820 | { 821 | return wtd_div; 822 | } 823 | else if(inDesiredRegisterRange(offsetAddress, wtd_time_pdi, wtd_time_proc_data)) 824 | { 825 | return wtd_time_pdi; 826 | } 827 | else if(inDesiredRegisterRange(offsetAddress, wtd_time_proc_data, wtd_stat_proc_data)) 828 | { 829 | return wtd_time_proc_data; 830 | } 831 | else if(inDesiredRegisterRange(offsetAddress, wtd_stat_proc_data, wtd_cnt_proc_data)) 832 | { 833 | return wtd_stat_proc_data; 834 | } 835 | else if(inDesiredRegisterRange(offsetAddress, wtd_cnt_pdi, sii_eeprom_intr)) 836 | { 837 | return wtd_cnt_pdi; 838 | } 839 | else if(inDesiredRegisterRange(offsetAddress, sii_eeprom_intr, mii_mang_intr)) 840 | { 841 | return sii_eeprom_intr; 842 | } 843 | else if(inDesiredRegisterRange(offsetAddress, mii_mang_intr, fmmu)) 844 | { 845 | return mii_mang_intr; 846 | } 847 | else if(inDesiredRegisterRange(offsetAddress, fmmu, sync_manager)) 848 | { 849 | return fmmu; 850 | } 851 | else if(inDesiredRegisterRange(offsetAddress, sync_manager, dist_clk)) 852 | { 853 | return sync_manager; 854 | } 855 | else if(inDesiredRegisterRange(offsetAddress, dist_clk, esc_specf_reg)) 856 | { 857 | return dist_clk; 858 | } 859 | else if(inDesiredRegisterRange(offsetAddress, esc_specf_reg, dig_io_data)) 860 | { 861 | return esc_specf_reg; 862 | } 863 | else if(inDesiredRegisterRange(offsetAddress, dig_io_data, gp_out_data)) 864 | { 865 | return dig_io_data; 866 | } 867 | else if(inDesiredRegisterRange(offsetAddress, gp_out_data, gp_in)) 868 | { 869 | return gp_out_data; 870 | } 871 | else if(inDesiredRegisterRange(offsetAddress, gp_in, usr_ram)) 872 | { 873 | return gp_in; 874 | } 875 | else if(inDesiredRegisterRange(offsetAddress, usr_ram, pd_ram)) 876 | { 877 | return usr_ram; 878 | } 879 | else if(offsetAddress >= pd_ram) 880 | { 881 | return pd_ram; 882 | } 883 | else 884 | { 885 | // No range, just return itself 886 | return offsetAddress; 887 | } 888 | } 889 | 890 | zeek::AddrValPtr ECATAnalyzer::ToAddrVal(const void* addr) 891 | { 892 | //Note: We only handle IPv4 addresses. 893 | return zeek::make_intrusive(*(const uint32_t*) addr); 894 | } 895 | 896 | zeek::StringValPtr ECATAnalyzer::ToEthAddrStr(const u_char* addr) 897 | { 898 | char buf[1024]; 899 | snprintf(buf, sizeof(buf), "%02x:%02x:%02x:%02x:%02x:%02x", 900 | addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); 901 | return zeek::make_intrusive(buf); 902 | } 903 | 904 | zeek::StringValPtr ECATAnalyzer::HexToString(const u_char* data, uint16_t len) 905 | { 906 | char buf[0x2000]; 907 | int offset = 0; 908 | int count = 0; 909 | if( len) 910 | { 911 | for(int i = 0; i < len; i++) 912 | { 913 | if( ((data[i] & 0xF0) >> 4) > 0x09) 914 | { 915 | buf[count] = (((data[i] & 0xF0) >> 4) - 0x0A) + 0x41; 916 | } 917 | else 918 | { 919 | buf[count] = ((data[i] & 0xF0) >> 4) + 0x30; 920 | } 921 | 922 | if( ((data[i] & 0x0F)) > 0x09) 923 | { 924 | buf[count+1] = ((data[i] & 0x0F) - 0x0A) + 0x41; 925 | } 926 | else 927 | { 928 | buf[count+1] = (data[i] & 0x0F) + 0x30; 929 | } 930 | 931 | count += 2; 932 | } 933 | 934 | buf[count] = 0x00; 935 | return zeek::make_intrusive(buf); 936 | } 937 | return NULL; 938 | } 939 | -------------------------------------------------------------------------------- /src/ECAT.h: -------------------------------------------------------------------------------- 1 | //"Copyright (c) 2021 Battelle Energy Alliance, LLC. All rights reserved." 2 | // ECAT.h 3 | // 4 | // ECAT - Defines packet ECATAnalyzer class for ethercat packet analysis through 5 | // zeek. 6 | // 7 | // Author: Devin Vollmer 8 | // Contact: devin.vollmer@inl.gov 9 | 10 | 11 | #pragma once 12 | 13 | #if __has_include() 14 | #include 15 | #else 16 | #include 17 | #endif 18 | 19 | #include "zeek/packet_analysis/Analyzer.h" 20 | #include "zeek/packet_analysis/Component.h" 21 | #include 22 | #pragma once 23 | 24 | #include 25 | #define HEADER_LENGTH 2 26 | #define MAX_DATAGRAM_COUNT 15 27 | #define DATAGRAM_HEADER_LENGTH 10 28 | #define DATAGRAM_TRAILER_LENGTH 2 29 | #define MAX_DATA_SIZE 2047 // 16-bit field in the spec, but limited to 11 bits by the protocol itself...going with 11-bit size 30 | #define MAX_FOE_DATA_SIZE 512 31 | #define DEVICE_INFO_SIZE 10 32 | #define MAILBOX_HEADER_SIZE 6 33 | #define AOE_HEADER_SIZE 32 34 | #define AOE_ID_LENGTH 6 35 | #define AOE_PORT_LENGTH 2 36 | #define EOE_HEADER_SIZE 18 37 | #define COE_HEADER_SIZE 10 38 | #define FOE_HEADER_SIZE 14 39 | #define SOE_HEADER_SIZE 7 40 | 41 | namespace zeek::packet_analysis::ETHERCAT { 42 | 43 | typedef struct { 44 | uint8_t cmd; 45 | uint8_t index; /**< Index (set by master */ 46 | uint8_t mailbox_type; 47 | uint8_t last_indicator; 48 | uint8_t data[MAX_DATA_SIZE]; /**< Datagram payload. */ 49 | uint16_t length; 50 | uint16_t interrupt; 51 | uint16_t working_counter; /**< Working counter. */ 52 | uint16_t slave_addr; 53 | uint16_t off_addr; // actual location of register address 54 | uint16_t off_addr_name; 55 | uint8_t address[4]; /**< Recipient address. */ 56 | } ecat_datagram; 57 | 58 | typedef struct { 59 | uint8_t revision; 60 | uint8_t type; 61 | uint8_t fmmu_cnt; 62 | uint8_t sm_cnt; 63 | uint8_t ports; 64 | uint8_t dpram; 65 | uint16_t features; 66 | uint16_t build; 67 | } ecat_device_info; 68 | 69 | typedef struct { 70 | uint16_t length; 71 | uint16_t address; 72 | uint8_t priority; 73 | uint8_t type; 74 | uint8_t counter; 75 | } ecat_mailbox_header; 76 | 77 | typedef struct { 78 | uint8_t targetid[6]; 79 | uint8_t senderid[6]; 80 | uint8_t req_res[MAX_DATA_SIZE]; 81 | uint8_t counter; 82 | uint16_t cmd; 83 | uint16_t targetport; 84 | uint16_t senderport; 85 | uint16_t stateflags; 86 | uint32_t cbdata; 87 | uint32_t errorcode; 88 | uint32_t invokeid; 89 | } ecat_mailbox_aoe; 90 | 91 | typedef struct { 92 | uint16_t length; 93 | uint16_t address; 94 | uint8_t priority; 95 | uint8_t type; 96 | uint8_t counter; 97 | } ecat_mailbox_eoe; 98 | 99 | typedef struct { 100 | uint32_t data_offset; 101 | uint16_t number; 102 | uint16_t type; 103 | uint16_t index; 104 | uint8_t subindex; 105 | uint8_t req_resp; 106 | } ecat_mailbox_coe; 107 | 108 | // Need more information on these last three mailbox's 109 | // FOE, SOE, VOE 110 | typedef struct { 111 | uint32_t password; 112 | uint32_t packet_num; 113 | uint32_t error_code; 114 | uint8_t opCode; 115 | uint8_t reserved; 116 | uint8_t filename[MAX_FOE_DATA_SIZE]; 117 | uint8_t data[MAX_FOE_DATA_SIZE]; 118 | uint8_t error_txt[MAX_FOE_DATA_SIZE]; 119 | } ecat_mailbox_foe; 120 | 121 | // header structure found in open source documents ros.org 122 | // no pcap found to verify how to parse this information 123 | typedef struct { 124 | uint16_t index; 125 | uint8_t opCode; 126 | uint8_t incomplete; 127 | uint8_t error; 128 | uint8_t drive_num; 129 | uint8_t element_flags; 130 | } ecat_mailbox_soe; 131 | 132 | // VoE is Vendor specific data 133 | // typedef struct { 134 | // uint16_t length; 135 | // uint16_t address; 136 | // uint8_t priority; 137 | // uint8_t type; 138 | // uint8_t counter; 139 | // } Ecat_Mailbox_VOE; 140 | 141 | typedef struct 142 | { 143 | ecat_mailbox_header header; 144 | ecat_mailbox_aoe aoe; 145 | ecat_mailbox_eoe eoe; 146 | ecat_mailbox_coe coe; 147 | ecat_mailbox_foe foe; 148 | ecat_mailbox_soe soe; 149 | // Ecat_Mailbox_VOE VOE; 150 | } ecat_mailbox; 151 | 152 | class ECATAnalyzer : public Analyzer { 153 | public: 154 | ECATAnalyzer(); 155 | ~ECATAnalyzer() override = default; 156 | 157 | void Initialize() override; 158 | 159 | bool AnalyzePacket(size_t len, const uint8_t* data, Packet* packet) override; 160 | 161 | static zeek::packet_analysis::AnalyzerPtr Instantiate() 162 | { 163 | return std::make_shared(); 164 | } 165 | 166 | private: 167 | bool parseMessageHeader(const uint8_t* data, size_t length, uint16_t &messageLength, bool &reserveBit, uint8_t &messageType); 168 | bool parseDatagram(const uint8_t* data, size_t length, uint8_t currentIndex, uint16_t &dataProcessed); 169 | bool parseDatagramHeader(const uint8_t* data, size_t length, uint8_t currentIndex); 170 | bool parseDatagramBody(const uint8_t* data, size_t length, uint8_t currentIndex, uint16_t &dataProcessed, bool &failedInFurtherProcessing); 171 | bool furtherProcessDatagramBody(uint8_t currentIndex); 172 | bool processDevTypeData(uint8_t currentIndex); 173 | bool processPDRAMData(uint8_t currentIndex); 174 | bool parseMailbox(uint8_t currentIndex); 175 | bool parseAOE(uint8_t currentIndex); 176 | bool parseEOE(uint8_t currentIndex); 177 | bool parseCOE(uint8_t currentIndex); 178 | bool parseFOE(uint8_t currentIndex); 179 | bool parseSOE(uint8_t currentIndex); 180 | bool parseDatagramTrailer(const uint8_t* data, size_t length, uint8_t currentIndex); 181 | bool commandUsesRegularAddress(uint8_t command); 182 | uint16_t determineOffsetName(uint16_t offsetAddress); 183 | zeek::AddrValPtr ToAddrVal(const void* addr); 184 | zeek::StringValPtr ToEthAddrStr(const u_char* addr); 185 | zeek::StringValPtr HexToString(const u_char* data, uint16_t len); 186 | }; 187 | 188 | } 189 | -------------------------------------------------------------------------------- /src/ECAT_ENUMS.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Battelle Energy Alliance, LLC. All rights reserved. 2 | // ECAT_ENUMS.h 3 | // 4 | // Ethercat Enums - Defines constants for Ethercat devices 5 | // 6 | // Author: Devin Vollmer 7 | // Contact: devin.vollmer@inl.gov 8 | 9 | #pragma once 10 | // -----------------------------------Ethercat Mailbox----------------------------------------- 11 | // Description: Ethercat mailbox types 12 | // -------------------------------------------------------------------------------------------- 13 | enum { 14 | ecat_mbx_none = 0x00, 15 | ecat_mbx_aoe = 0x01, // ADS over Ethernet 16 | ecat_mbx_eoe = 0x02, // Ethernet over EtherCAT 17 | ecat_mbx_coe = 0x03, // CANopen over EtherCAT 18 | ecat_mbx_foe = 0x04, // File-Access over EtherCAT 19 | ecat_mbx_soe = 0x05, // Servo-Profile over EtherCAT 20 | ecat_mbx_voe = 0x0F // Vendor specific 21 | }; 22 | 23 | 24 | // ---------------------------------Ethercat datagram Type------------------------------------- 25 | // Description: Function type of ethercat datagram 26 | // -------------------------------------------------------------------------------------------- 27 | enum { 28 | ecat_datagram_none = 0x00, //Dummy. 29 | ecat_datagram_aprd = 0x01, //Auto Increment Physical Read. 30 | ecat_datagram_apwr = 0x02, //Auto Increment Physical Write. 31 | ecat_datagram_aprw = 0x03, //Auto Increment Physical ReadWrite. 32 | ecat_datagram_fprd = 0x04, //Configured Address Physical Read. 33 | ecat_datagram_fpwr = 0x05, //Configured Address Physical Write. 34 | ecat_datagram_fprw = 0x06, //Configured Address Physical ReadWrite. 35 | ecat_datagram_brd = 0x07, //Broadcast Read. 36 | ecat_datagram_bwr = 0x08, //Broadcast Write. 37 | ecat_datagram_brw = 0x09, //Broadcast ReadWrite. 38 | ecat_datagram_lrd = 0x0A, //Logical Read. 39 | ecat_datagram_lwr = 0x0B, //Logical Write. 40 | ecat_datagram_lrw = 0x0C, //Logical ReadWrite. 41 | ecat_datagram_armw = 0x0D, //Auto Increment Physical Read Multiple 42 | //Write. 43 | ecat_datagram_frmw = 0x0E, //Configured Address Physical Read Multiple 44 | //Write. 45 | }; 46 | 47 | // -----------------------------------Ethercat Register---------------------------------------- 48 | // Description: Register address for reading and writing 49 | // -------------------------------------------------------------------------------------------- 50 | enum { 51 | dev_type = 0x0000, 52 | revision = 0x0001, 53 | build = 0x0002, // Build Rev, two byte register length 54 | fmmuspt = 0x0004, // FMMUs supported 55 | sync_managers = 0x0005, // SyncManagers supported 56 | ram_size = 0x0006, // Size of physical ram 57 | port_descriptor = 0x0007, 58 | esc_features = 0x0008, //ESC Features supported 2 byte register length 59 | cs_addr = 0x0010, // Configured Station Address 2 byte register value 60 | cs_alias = 0x0012, // Configured Station Alias 2 byte register value 61 | reg_write_en = 0x0020, // Register Write Enable 62 | reg_write_prot = 0x0021, // Register Write Protection 63 | esc_write_en = 0x0030, // ESC Write Enable 64 | esc_write_prot = 0x0031, // ESC Write Protection 65 | esc_rst_ecat = 0x0040, // ESC Reset ECAT 66 | esc_rst_pdi = 0x0041, // ESC Reset PDI 67 | esc_dl_ctl = 0x0100, // 4 bytes ESC DL Control 68 | phy_rd_wr_offs = 0x0108, // 2 bytes Physical Read/Write Offset 69 | esc_dl_stat = 0x0110, // 2bytes ESC DL Status 70 | al_ctrl = 0x0120, // 2bytes AL Control 71 | al_stat = 0x0130, // 2bytes AL Status 72 | al_stat_code = 0x0134, // 2bytes AL Status Code 73 | run_led_ovrd = 0x0138, // RUN LED Override 74 | err_led_ovrd = 0x0139, // ERR LED Override 75 | pdi_ctrl = 0x0140, // PDI Control 76 | esc_conf = 0x0141, // ESC Configuration 77 | pdi_info = 0x014E, // 2bytes PDI Information 78 | // TODO: Check the next few values... 79 | pdi_conf = 0x0150, // 4 bytes PDI Configuration 80 | pdi_onchip_conf = 0x0152, // 2 byte PDI On-chip bus extended configuration 81 | sync_latc_pdi = 0x0151, // Bit [1:0] Sync/Latch[1:0] PDI Configuration 82 | ecat_ev_msk = 0x0200, // 2 byte ECAT Event Mask 83 | pdi_al_ev_msk = 0x0204, // 4 byte PDI AL Event Mask 84 | ecat_ev_req = 0x0210, // 2 byte ECAT Event Request 85 | al_ev_req = 0x0220, // 4 byte AL Event Request 86 | rx_err_cnt = 0x0300, // 8 byte RX Error Counter 87 | fwd_rx_err_cnt = 0x0308, // 4 byte Forwarded RX Error Counter 88 | ecat_proc_err_cnt = 0x030C, // ECAT Processing Unit Error Counter 89 | pdi_err_cnt = 0x030D, // PDI Error Counter 90 | pdi_err_code = 0x030E, // PDI Error Code 91 | llc = 0x0310, // 4 byte Lost Link Counter 92 | wtd_div = 0x0400, // 2 byte Watchdog Divider 93 | wtd_time_pdi = 0x0410, // 2 byte Watchdog Time PDI 94 | wtd_time_proc_data = 0x0420, // 2 byte Watchdog Time Process Data 95 | wtd_stat_proc_data = 0x0440, // 2 byte Watchdog Status Process Data 96 | wtd_cnt_proc_data = 0x0442, // Watchdog Counter Process Data 97 | wtd_cnt_pdi = 0x0443, // Watchdog Counter PDI 98 | sii_eeprom_intr = 0x0500, // 16 byte SII EEPROM Interface 99 | mii_mang_intr = 0x0510, // 6 byte MII Management Interface 100 | fmmu = 0x0600, // 0xff bytes FMMU 101 | sync_manager = 0x0800, // 0x7f bytes SyncManager 102 | dist_clk = 0x0900, // 0xff bytes Distributed Clocks 103 | esc_specf_reg = 0x0E00, // 0xff ESC specific registers 104 | dig_io_data = 0x0F00, // 4 byte Digital I/O Output Data 105 | gp_out_data = 0x0F10, // 8 byte General Purpose Outputs 106 | gp_in = 0x0F18, // 8 byte rGeneral Purpose Inputs 107 | usr_ram = 0x0F80, // 80 bytes User RAM 108 | pd_ram = 0x1000, // 4 byte PDI Digital I/O Input Data 109 | 110 | }; 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /src/Plugin.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Battelle Energy Alliance, LLC. All rights reserved." 2 | 3 | #include "Plugin.h" 4 | #include "zeek/packet_analysis/Component.h" 5 | 6 | namespace plugin 7 | { 8 | namespace ICSNPP_ETHERCAT 9 | { 10 | Plugin plugin; 11 | } 12 | } 13 | 14 | using namespace plugin::ICSNPP_ETHERCAT; 15 | 16 | zeek::plugin::Configuration Plugin::Configure() 17 | { 18 | AddComponent(new zeek::packet_analysis::Component("ETHERCAT", 19 | zeek::packet_analysis::ETHERCAT::ECATAnalyzer::Instantiate)); 20 | 21 | zeek::plugin::Configuration config; 22 | config.name = "ICSNPP::ETHERCAT"; 23 | config.description = "Ethercat packet analyzer"; 24 | config.version.major = 1; 25 | config.version.minor = 0; 26 | 27 | return config; 28 | } 29 | 30 | -------------------------------------------------------------------------------- /src/Plugin.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Battelle Energy Alliance, LLC. All rights reserved. 2 | #pragma once 3 | 4 | #include 5 | #include "ECAT.h" 6 | 7 | namespace plugin 8 | { 9 | namespace ICSNPP_ETHERCAT 10 | { 11 | class Plugin : public zeek::plugin::Plugin 12 | { 13 | protected: 14 | virtual zeek::plugin::Configuration Configure(); 15 | }; 16 | 17 | extern Plugin plugin; 18 | } 19 | } -------------------------------------------------------------------------------- /src/events.bif: -------------------------------------------------------------------------------- 1 | ## events.bif 2 | ## 3 | ## Ethercat Packet Analyzer - Defines events the analyzer will generate 4 | ## 5 | ## Author: Devin Vollmer 6 | ## Contact: devin.vollmer@inl.gov 7 | ## 8 | ## Copyright (c) 2020 Battelle Energy Alliance, LLC. All rights reserved. 9 | 10 | event ecat_log_address%(mac_src: string, 11 | mac_dst: string, 12 | data_len: count, 13 | data_cmd: count, 14 | data_addr: string, 15 | data: string%); 16 | 17 | event ecat_registers%(mac_src: string, 18 | mac_dst: string, 19 | slave_addr: count, 20 | reg_type: count, 21 | reg_addr: count, 22 | data_cmd: count, 23 | data: string%); 24 | 25 | event ecat_device%(SlaveId: count, 26 | Revision: count, 27 | Type: count, 28 | Build: count, 29 | FmmuCnt: count, 30 | SmCnt: count, 31 | Ports: count, 32 | Dpram: count, 33 | Features: count%); 34 | 35 | event ecat_AoE%(targetid: string, 36 | senderid: string, 37 | targetport: count, 38 | senderport: count, 39 | cmd: count, 40 | stateflags: count, 41 | req_res: string%); 42 | 43 | event ecat_CoE%(number: count, 44 | Type: count, 45 | req_resp: count, 46 | index: count, 47 | subindex: count, 48 | dataoffset: count%); 49 | 50 | event ecat_FoE%(opCode: count, 51 | reserved: count, 52 | packet_num: count, 53 | error_code: count, 54 | filename: string, 55 | data: string%); 56 | 57 | event ecat_SoE%(opCode: count, 58 | incomplete: count, 59 | error: count, 60 | drive_num: count, 61 | element_flags: count, 62 | index: count%); 63 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | .tmp 2 | .btest.failed.dat 3 | -------------------------------------------------------------------------------- /tests/analyzer/availability.zeek: -------------------------------------------------------------------------------- 1 | # @TEST-EXEC: zeek -NN | grep -i -q ANALYZER_ETHERCAT 2 | # 3 | # @TEST-DOC: Check that ECAT analyzer is available. 4 | -------------------------------------------------------------------------------- /tests/analyzer/basic.zeek: -------------------------------------------------------------------------------- 1 | # @TEST-EXEC: zeek -C -r ${TRACES}/ethercat_example.pcap %INPUT 2 | # @TEST-EXEC: zeek-cut id.orig_h id.orig_p id.resp_h id.resp_p proto service < conn.log > conn.tmp && mv conn.tmp conn.log 3 | # @TEST-EXEC: btest-diff conn.log 4 | # @TEST-EXEC: btest-diff ecat_aoe_info.log 5 | # @TEST-EXEC: btest-diff ecat_arp_info.log 6 | # @TEST-EXEC: btest-diff ecat_coe_info.log 7 | # @TEST-EXEC: btest-diff ecat_dev_info.log 8 | # @TEST-EXEC: btest-diff ecat_log_address.log 9 | # @TEST-EXEC: btest-diff ecat_registers.log 10 | # 11 | # @TEST-DOC: Test ECAT analyzer with small trace. 12 | 13 | @load icsnpp/ethercat 14 | -------------------------------------------------------------------------------- /tests/baseline/analyzer.basic/conn.log: -------------------------------------------------------------------------------- 1 | ### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. 2 | 169.254.93.65 137 169.254.255.255 137 udp dns 3 | 0.0.0.0 68 255.255.255.255 67 udp dhcp 4 | fe80::7d2a:c135:4ab4:4d90 546 ff02::1:2 547 udp - 5 | -------------------------------------------------------------------------------- /tests/baseline/analyzer.basic/ecat_aoe_info.log: -------------------------------------------------------------------------------- 1 | ### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. 2 | #separator \x09 3 | #set_separator , 4 | #empty_field (empty) 5 | #unset_field - 6 | #path ecat_aoe_info 7 | #open XXXX-XX-XX-XX-XX-XX 8 | #fields ts targetid targetport senderid senderport cmd stateflags data 9 | #types time string string string string string string string 10 | XXXXXXXXXX.XXXXXX 01:02:c6:c9:52:05 0x3ec 01:02:c6:c9:52:05 0x3ec ADS Write 0x04 0100000003000000060000000550354B0501 11 | XXXXXXXXXX.XXXXXX 01:02:c6:c9:52:05 0x3ec 01:02:c6:c9:52:05 0x3ec ADS Write 0x04 0100000003000000060000000550354B0501 12 | XXXXXXXXXX.XXXXXX 01:02:c6:c9:52:05 0x3ec 01:02:c6:c9:52:05 0x3ec ADS Write 0x05 0100000003000000060000000550354B0501 13 | XXXXXXXXXX.XXXXXX 01:02:c6:c9:52:05 0x3ec 01:02:c6:c9:52:05 0x3ec ADS Write 0x04 0100000003000000060000000550354B0501 14 | XXXXXXXXXX.XXXXXX 01:02:c6:c9:52:05 0x3ec 01:02:c6:c9:52:05 0x3ec ADS Write 0x04 0100000003000000060000000550354B0501 15 | XXXXXXXXXX.XXXXXX 01:02:c6:c9:52:05 0x3ec 01:02:c6:c9:52:05 0x3ec ADS Write 0x05 0100000003000000060000000550354B0501 16 | #close XXXX-XX-XX-XX-XX-XX 17 | -------------------------------------------------------------------------------- /tests/baseline/analyzer.basic/ecat_arp_info.log: -------------------------------------------------------------------------------- 1 | ### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. 2 | #separator \x09 3 | #set_separator , 4 | #empty_field (empty) 5 | #unset_field - 6 | #path ecat_arp_info 7 | #open XXXX-XX-XX-XX-XX-XX 8 | #fields ts arp_type mac_src mac_dst SPA SHA TPA THA 9 | #types time string string string addr string addr string 10 | XXXXXXXXXX.XXXXXX Request 00:01:05:4c:b8:af ff:ff:ff:ff:ff:ff 0.0.0.0 00:01:05:4c:b8:af 169.254.93.65 00:00:00:00:00:00 11 | XXXXXXXXXX.XXXXXX Request 00:01:05:4c:b8:af ff:ff:ff:ff:ff:ff 0.0.0.0 00:01:05:4c:b8:af 169.254.93.65 00:00:00:00:00:00 12 | XXXXXXXXXX.XXXXXX Request 00:01:05:4c:b8:af ff:ff:ff:ff:ff:ff 0.0.0.0 00:01:05:4c:b8:af 169.254.93.65 00:00:00:00:00:00 13 | XXXXXXXXXX.XXXXXX Request 00:01:05:4c:b8:af ff:ff:ff:ff:ff:ff 0.0.0.0 00:01:05:4c:b8:af 169.254.93.65 00:00:00:00:00:00 14 | XXXXXXXXXX.XXXXXX Request 00:01:05:4c:b8:af ff:ff:ff:ff:ff:ff 0.0.0.0 00:01:05:4c:b8:af 169.254.93.65 00:00:00:00:00:00 15 | XXXXXXXXXX.XXXXXX Request 00:01:05:4c:b8:af ff:ff:ff:ff:ff:ff 0.0.0.0 00:01:05:4c:b8:af 169.254.93.65 00:00:00:00:00:00 16 | XXXXXXXXXX.XXXXXX Request 00:01:05:4c:b8:af ff:ff:ff:ff:ff:ff 169.254.93.65 00:01:05:4c:b8:af 169.254.93.65 00:00:00:00:00:00 17 | XXXXXXXXXX.XXXXXX Request 00:01:05:4c:b8:af ff:ff:ff:ff:ff:ff 169.254.93.65 00:01:05:4c:b8:af 169.254.93.65 00:00:00:00:00:00 18 | #close XXXX-XX-XX-XX-XX-XX 19 | -------------------------------------------------------------------------------- /tests/baseline/analyzer.basic/ecat_coe_info.log: -------------------------------------------------------------------------------- 1 | ### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. 2 | #separator \x09 3 | #set_separator , 4 | #empty_field (empty) 5 | #unset_field - 6 | #path ecat_coe_info 7 | #open XXXX-XX-XX-XX-XX-XX 8 | #fields ts number Type req_resp index subindex dataoffset 9 | #types time string string string string string string 10 | XXXXXXXXXX.XXXXXX 0x00 0x02 0x33 0x1c12 0x00 0x16000001 11 | XXXXXXXXXX.XXXXXX 0x00 0x02 0x33 0x1c12 0x00 0x16000001 12 | XXXXXXXXXX.XXXXXX 0x00 0x03 0x60 0x1c12 0x00 0x16000001 13 | XXXXXXXXXX.XXXXXX 0x00 0x02 0x33 0x1c13 0x00 0x1a000001 14 | XXXXXXXXXX.XXXXXX 0x00 0x02 0x33 0x1c13 0x00 0x1a000001 15 | XXXXXXXXXX.XXXXXX 0x00 0x03 0x60 0x1c13 0x00 0x1a000001 16 | #close XXXX-XX-XX-XX-XX-XX 17 | -------------------------------------------------------------------------------- /tests/baseline/analyzer.basic/ecat_dev_info.log: -------------------------------------------------------------------------------- 1 | ### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. 2 | #separator \x09 3 | #set_separator , 4 | #empty_field (empty) 5 | #unset_field - 6 | #path ecat_dev_info 7 | #open XXXX-XX-XX-XX-XX-XX 8 | #fields ts slave_id revision dev_type build fmmucnt smcount ports dpram features 9 | #types time string string string string string string string string string 10 | XXXXXXXXXX.XXXXXX 0x03 0x12 0x01 0x01 0x03 0x04 0x4a 0x01 0x1fc 11 | XXXXXXXXXX.XXXXXX 0x04 0x12 0x01 0x01 0x03 0x04 0x4a 0x01 0x1fc 12 | XXXXXXXXXX.XXXXXX 0x02 0x12 0x01 0x01 0x03 0x04 0x4a 0x01 0x1fc 13 | XXXXXXXXXX.XXXXXX 0x03 0x12 0x01 0x01 0x03 0x04 0x4a 0x01 0x1fc 14 | XXXXXXXXXX.XXXXXX 0x01 0x12 0x01 0x01 0x03 0x04 0x4e 0x01 0x1fc 15 | XXXXXXXXXX.XXXXXX 0x02 0x12 0x01 0x01 0x03 0x04 0x4e 0x01 0x1fc 16 | XXXXXXXXXX.XXXXXX 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 17 | XXXXXXXXXX.XXXXXX 0x01 0x05 0x03 0x420 0x03 0x04 0x0f 0x04 0x18c 18 | #close XXXX-XX-XX-XX-XX-XX 19 | -------------------------------------------------------------------------------- /tests/btest.cfg: -------------------------------------------------------------------------------- 1 | [btest] 2 | TestDirs = analyzer 3 | TmpDir = %(testbase)s/.tmp 4 | BaselineDir = %(testbase)s/baseline 5 | IgnoreDirs = .tmp 6 | IgnoreFiles = *.tmp *.swp #* *.trace .DS_Store 7 | 8 | [environment] 9 | ZEEKPATH=`%(testbase)s/scripts/get-zeek-env zeekpath` 10 | ZEEK_PLUGIN_PATH=`%(testbase)s/scripts/get-zeek-env zeek_plugin_path` 11 | ZEEK_SEED_FILE=%(testbase)s/files/random.seed 12 | PATH=`%(testbase)s/scripts/get-zeek-env path` 13 | PACKAGE=%(testbase)s/../scripts 14 | TZ=UTC 15 | LC_ALL=C 16 | TRACES=%(testbase)s/traces 17 | TMPDIR=%(testbase)s/.tmp 18 | TEST_DIFF_CANONIFIER=%(testbase)s/scripts/diff-remove-timestamps 19 | -------------------------------------------------------------------------------- /tests/files/random.seed: -------------------------------------------------------------------------------- 1 | 2983378351 2 | 1299727368 3 | 0 4 | 310447 5 | 0 6 | 1409073626 7 | 3975311262 8 | 34130240 9 | 1450515018 10 | 1466150520 11 | 1342286698 12 | 1193956778 13 | 2188527278 14 | 3361989254 15 | 3912865238 16 | 3596260151 17 | 517973768 18 | 1462428821 19 | 0 20 | 2278350848 21 | 32767 22 | -------------------------------------------------------------------------------- /tests/scripts/diff-remove-timestamps: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | # 3 | # Replace anything which looks like timestamps with XXXs (including the #start/end markers in logs). 4 | 5 | # Get us "modern" regexps with sed. 6 | if [ `uname` == "Linux" ]; then 7 | sed="sed -r" 8 | else 9 | sed="sed -E" 10 | fi 11 | 12 | $sed 's/(0\.000000)|([0-9]{9,10}\.[0-9]{2,8})/XXXXXXXXXX.XXXXXX/g' | \ 13 | $sed 's/^ *#(open|close).(19|20)..-..-..-..-..-..$/#\1 XXXX-XX-XX-XX-XX-XX/g' 14 | -------------------------------------------------------------------------------- /tests/scripts/get-zeek-env: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # 3 | # BTest helper for getting values for Zeek-related environment variables. 4 | 5 | base=$(dirname $0) 6 | zeek_dist=$(cat ${base}/../../build/CMakeCache.txt 2>/dev/null | grep ZEEK_DIST | cut -d = -f 2) 7 | 8 | if [ -n "${zeek_dist}" ]; then 9 | if [ "$1" = "zeekpath" ]; then 10 | ${zeek_dist}/build/zeek-path-dev 11 | elif [ "$1" = "zeek_plugin_path" ]; then 12 | ( cd ${base}/../.. && pwd ) 13 | elif [ "$1" = "path" ]; then 14 | echo ${zeek_dist}/build/src:${zeek_dist}/aux/btest:${base}/:${zeek_dist}/aux/zeek-cut:$PATH 15 | else 16 | echo "usage: $(basename $0) " >&2 17 | exit 1 18 | fi 19 | else 20 | # Use Zeek installation for testing. In this case zeek-config must be in PATH. 21 | if ! which zeek-config >/dev/null 2>&1; then 22 | echo "zeek-config not found" >&2 23 | exit 1 24 | fi 25 | 26 | if [ "$1" = "zeekpath" ]; then 27 | zeek-config --zeekpath 28 | elif [ "$1" = "zeek_plugin_path" ]; then 29 | # Combine the local tree and the system-wide path. This allows 30 | # us to test on a local build or an installation made via zkg, 31 | # which squirrels away the build. --cpk 32 | echo "$(cd ${base}/../.. && pwd):$(zeek-config --plugin_dir)" 33 | elif [ "$1" = "path" ]; then 34 | echo ${PATH} 35 | else 36 | echo "usage: $(basename $0) " >&2 37 | exit 1 38 | fi 39 | fi 40 | -------------------------------------------------------------------------------- /tests/traces/ethercat_example.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cisagov/icsnpp-ethercat/50b9c38357241cdb04a5fc0602e77f51a4b2fe7e/tests/traces/ethercat_example.pcap -------------------------------------------------------------------------------- /zkg.meta: -------------------------------------------------------------------------------- 1 | [package] 2 | build_dir = build/ICSNPP_Ethercat.tgz 3 | script_dir = build/scripts/icsnpp/ethercat 4 | build_command = ./configure && make 5 | test_command = cd tests && btest -c btest.cfg 6 | description = Ethercat plugin for parsing and logging of the Ethercat protocol - CISA ICSNPP 7 | credits = Devin Vollmer 8 | tags = ecat, ECAT, ethercat, Ethercat, ics, ICS, CISA, INL, ICSNPP, icsnpp, zeek plugin, log writer, packet analyzer 9 | depends = 10 | zkg >=2.0 11 | zeek >=4.0.0 --------------------------------------------------------------------------------