SNMP/UDP trap handler

SNMP/UDP trap handler controller provides easy-to-use way to process SNMP v1/v2c and EVA ICS native UDP traps.

SNMP Traps

Processing

A trap data is parsed and processed later with a custom lmacro with the following keyword arguments:

Name

Type

Content

trap_source

String

SNMP trap sender IP address

trap_community

String

SNMP trap community

trap_vars

Map<String, Any>

Trap variables map as SNMP_OID=VAL

Supported SNMP data types

The following SNMP data types are parsed and sent in trap_vars:

Type

Processed as

BER (X.690)

u64/i64/bool/String

Counter32

u32

Gauge32

u32

UInteger32

u32

Counter64

u64

String

String

Unsupported data types are ignored.

Native traps

EVA ICS native traps is a native UDP signaling trap protocol, which replaces v3 UC UDP API.

The native trap structure is a string with new-line (LF) separated commands, e.g.:

u sensor:env/temp 1 25.57
a unit:tests/motor1 1 34.22
a unit:tests/relay1 toggle

Note

Native traps are processed by the service directly, the process macro is not required to be set.

Updates

Update commands have the following format:

u[pdate] <OID> <status> [value]

Example:

u sensor:env/temp 1 25.57

Actions

Action commands have the following format:

a[action] <OID> <status> [value]

Example:

a unit:tests/motor1 1 34.22

If a toggle-action is required to be executed, use “t” (or “toggle”) as the status.

Encrypted/compressed traps

The service supports encrypted and/or compressed trap payloads. The frame format is the same as for replication bulk events.

For encrypted frames, key_svc parameter in the service config is mandatory.

A Python example, which sends bzip2-compressed plus AES-256-GCM-encrypted native traps:

import socket
import bz2
from Cryptodome import Random
from Cryptodome.Cipher import AES
from hashlib import sha256

sender = ''
key_id = 'mykey'
key_val = 'SECRET'

payload = """
u sensor:env/temp 1 25.57
a unit:tests/door t
a unit:tests/u2 1
"""

flags = 2 + (1 << 4)  # 2 = AES-256-GCM, 5th bit = 1 - bzip2
nonce = Random.new().read(12)
hasher = sha256()
hasher.update(key_val.encode())
cipher = AES.new(hasher.digest(), AES.MODE_GCM, nonce)
frame, digest = cipher.encrypt_and_digest(bz2.compress(payload.encode()))
binary_payload = b'\x00\x01' + flags.to_bytes(
    1, 'little') + b'\x00\x00' + sender.encode() + b'\x00' + key_id.encode(
    ) + b'\x00' + frame + digest + nonce
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.sendto(binary_payload, ('127.0.0.1', 1162))

Setup

Use the template EVA_DIR/share/svc-tpl/svc-tpl-controller-trap.yml:

# SNMP trap handler controller
command: svc/eva-controller-trap
bus:
  path: var/bus.ipc
config:
  listen: 0.0.0.0:1162
  # valid values
  # snmp_v1 - SNMP v1
  # snmp_v2c - SNMP v2c (default)
  # eva_v1 - EVA ICS native UDP traps v1
  protocol: snmp_v2c
  # accepted communities
  communities:
    - public
  # accepted hosts/networks
  hosts_allow:
     - 127.0.0.1
     - 10.0.0.0/8
  # process lmacro OID, required for SNMP traps
  process: lmacro:tests/trap_handler
  # key service, required for encrypted traps (native)
  key_svc: eva.aaa.localauth
  # allow encrypted traps only (native)
  require_auth: false
  # verbose info logging
  verbose: false
  mibs:
    # if SNMP mibs are enabled, the process macro receives MIB-converted var
    # names. MIB conversion uses net-snmp (built-in) library, which may be
    # considered as unsafe. DO NOT USE MIBs FROM UNVERIFIED SOURCES.
    #
    # Usually requires installing base MIBs (libsnmp-base, snmp-mibs-downloader
    # and/or related system packages)
    enabled: false
    #files:
       #- /path/to/file.mib
    dirs:
      - /usr/share/snmp/mibs
      - /usr/share/snmp/mibs/iana
      - /usr/share/snmp/mibs/ietf
user: nobody

Create the service using eva-shell:

eva svc create eva.controller.trap1 /opt/eva4/share/svc-tpl/svc-tpl-controller-trap.yml

or using the bus CLI client:

cd /opt/eva4
cat DEPLOY.yml | ./bin/yml2mp | \
    ./sbin/bus ./var/bus.ipc rpc call eva.core svc.deploy -

(see eva.core::svc.deploy for more info)