Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Deploy and configure Tofino industrial firewalls from Belden/Hirschmann to protect SCADA systems and PLCs using deep packet inspection for OT protocols including Modbus, EtherNet/IP, OPC, and S7comm, enforcing granular access control between ICS security zones.
.claude/skills/implementing-ics-firewall-with-tofino/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-03 | ✗→✓ | ▲ Improved | — | — |
| case-20 | ✗→✓ | ▲ Improved | — | — |
| case-06 | ✗→✓ | ▲ Improved | — | — |
| case-04 | ✗→✓ | ▲ Improved | — | — |
| case-16 | ✗→✓ | ▲ Improved | — | — |
Do not use for enterprise IT firewall deployment, for perimeter firewall between IT and OT (use Palo Alto/Fortinet at the DMZ), or for environments using only IP-based protocols without OT-specific DPI needs.
yaml# Tofino ICS Firewall Deployment Architecture # Zone-level protection using deep packet inspection deployment_zones: zone_1_reactor_control: tofino_appliance: "TOFINO-XN-001" deployment_mode: "inline_bridge" protected_assets: - name: "PLC-REACTOR-01" ip: "10.10.1.10" vendor: "Siemens S7-1500" protocols: ["S7comm/102", "Profinet"] - name: "PLC-REACTOR-02" ip: "10.10.1.11" vendor: "Siemens S7-1500" protocols: ["S7comm/102", "Profinet"] authorized_communications: - source: "10.10.2.50" # Engineering workstation dest: "10.10.1.0/24" protocols: ["S7comm"] access_type: "engineering" - source: "10.10.2.10" # HMI server dest: "10.10.1.0/24" protocols: ["S7comm"] access_type: "operational" zone_2_packaging: tofino_appliance: "TOFINO-XN-002" deployment_mode: "inline_bridge" protected_assets: - name: "PLC-PACK-01" ip: "10.10.3.10" vendor: "Rockwell ControlLogix" protocols: ["EtherNet-IP/44818", "CIP"] authorized_communications: - source: "10.10.2.20" # HMI dest: "10.10.3.0/24" protocols: ["EtherNet-IP"] access_type: "operational" zone_3_utilities: tofino_appliance: "TOFINO-XN-003" deployment_mode: "inline_bridge" protected_assets: - name: "RTU-BOILER-01" ip: "10.10.4.10" vendor: "Schneider M340" protocols: ["Modbus-TCP/502"] authorized_communications: - source: "10.10.2.30" # SCADA server dest: "10.10.4.0/24" protocols: ["Modbus-TCP"] allowed_function_codes: [1, 2, 3, 4] # Read only from SCADA
python#!/usr/bin/env python3 """Tofino ICS Firewall Rule Generator. Generates Tofino firewall rules with deep packet inspection for industrial protocols based on communication baseline analysis. """ import json import sys from datetime import datetime from typing import Dict, List class TofinoRuleGenerator: """Generates Tofino ICS firewall DPI rules.""" def __init__(self): self.rules = [] self.rule_id = 1000 def add_modbus_rule(self, src: str, dst: str, allowed_funcs: List[int], allowed_registers: List[dict] = None, description: str = ""): """Generate Modbus DPI rule.""" func_names = { 1: "read_coils", 2: "read_discrete_inputs", 3: "read_holding_registers", 4: "read_input_registers", 5: "write_single_coil", 6: "write_single_register", 15: "write_multiple_coils", 16: "write_multiple_registers", } rule = { "rule_id": self.rule_id, "protocol": "Modbus-TCP", "action": "ALLOW", "source": src, "destination": dst, "port": 502, "dpi_policy": { "allowed_function_codes": [ {"code": fc, "name": func_names.get(fc, f"FC{fc}")} for fc in allowed_funcs ], "blocked_function_codes": [ fc for fc in range(1, 128) if fc not in allowed_funcs ], }, "description": description, "log": True, } if allowed_registers: rule["dpi_policy"]["allowed_register_ranges"] = allowed_registers self.rules.append(rule) self.rule_id += 1 return rule def add_s7comm_rule(self, src: str, dst: str, allowed_operations: List[str], description: str = ""): """Generate S7comm DPI rule.""" operation_map = { "read": {"function": 0x04, "name": "Read Variable"}, "write": {"function": 0x05, "name": "Write Variable"}, "setup": {"function": 0xF0, "name": "Setup Communication"}, "download": {"function": 0x1A, "name": "Request Download"}, "upload": {"function": 0x1D, "name": "Start Upload"}, "cpu_stop": {"function": 0x29, "name": "PLC Stop"}, "cpu_start": {"function": 0x28, "name": "PI Service (Start)"}, } rule = { "rule_id": self.rule_id, "protocol": "S7comm", "action": "ALLOW", "source": src, "destination": dst, "port": 102, "dpi_policy": { "allowed_operations": [ operation_map[op] for op in allowed_operations if op in operation_map ], "block_cpu_stop": "cpu_stop" not in allowed_operations, "block_program_download": "download" not in allowed_operations, }, "description": description, "log": True, } self.rules.append(rule) self.rule_id += 1 return rule def add_ethernet_ip_rule(self, src: str, dst: str, allowed_services: List[str], description: str = ""): """Generate EtherNet/IP CIP DPI rule.""" rule = { "rule_id": self.rule_id, "protocol": "EtherNet-IP", "action": "ALLOW", "source": src, "destination": dst, "port": 44818, "dpi_policy": { "allowed_cip_services": allowed_services, "block_firmware_flash": True, "block_program_download": "program_download" not in allowed_services, }, "description": description, "log": True, } self.rules.append(rule) self.rule_id += 1 return rule def add_default_deny(self): """Add default deny rule at the end.""" self.rules.append({ "rule_id": 9999, "protocol": "ANY", "action": "DENY", "source": "ANY", "destination": "ANY", "port": "ANY", "description": "Default deny - block all unmatched traffic", "log": True, }) def generate_config(self) -> str: """Generate complete Tofino firewall configuration.""" config = { "tofino_configuration": { "generated": datetime.now().isoformat(), "appliance_model": "Tofino Xenon", "firmware_version": "4.2", "mode": "inline_bridge", "failsafe": "fail_open", "rules": self.rules, } } return json.dumps(config, indent=2) def print_summary(self): """Print rule summary.""" print(f"\n{'='*65}") print("TOFINO ICS FIREWALL RULE SUMMARY") print(f"{'='*65}") print(f"Generated: {datetime.now().isoformat()}") print(f"Total Rules: {len(self.rules)}") for rule in self.rules: action_icon = "+" if rule["action"] == "ALLOW" else "X" print(f"\n [{action_icon}] Rule {rule['rule_id']}: {rule.get('description', '')}") print(f" {rule['source']} -> {rule['destination']}:{rule['port']}") print(f" Protocol: {rule['protocol']}") if "dpi_policy" in rule: dpi = rule["dpi_policy"] if "allowed_function_codes" in dpi: funcs = [f["name"] for f in dpi["allowed_function_codes"]] print(f" DPI - Allowed Modbus FCs: {', '.join(funcs)}") if "allowed_operations" in dpi: ops = [o["name"] for o in dpi["allowed_operations"]] print(f" DPI - Allowed S7 Ops: {', '.join(ops)}") if __name__ == "__main__": gen = TofinoRuleGenerator() # SCADA server to Modbus RTUs: read-only gen.add_modbus_rule( src="10.10.2.30", dst="10.10.4.0/24", allowed_funcs=[1, 2, 3, 4], description="SCADA to utilities RTUs - read only", ) # Engineering workstation to Siemens PLCs: full access gen.add_s7comm_rule( src="10.10.2.50", dst="10.10.1.0/24", allowed_operations=["read", "write", "setup", "download", "upload"], description="Engineering WS to reactor PLCs - full engineering access", ) # HMI to Siemens PLCs: read + write only (no program download) gen.add_s7comm_rule( src="10.10.2.10", dst="10.10.1.0/24", allowed_operations=["read", "write", "setup"], description="HMI to reactor PLCs - operational access only", ) # HMI to Rockwell PLCs: operational access gen.add_ethernet_ip_rule( src="10.10.2.20", dst="10.10.3.0/24", allowed_services=["read_tag", "write_tag", "get_attribute"], description="HMI to packaging PLCs - operational access", ) gen.add_default_deny() gen.print_summary()
| Term | Definition | |------|------------| | Tofino Xenon | Belden/Hirschmann industrial firewall appliance with deep packet inspection for OT protocols | | Deep Packet Inspection (DPI) | Examining message payload content beyond headers to enforce fine-grained rules on industrial protocol operations | | Inline Bridge Mode | Transparent deployment mode where the firewall sits between network segments without requiring IP changes | | Fail-Open | Safety mode where firewall passes all traffic if the appliance fails, maintaining process availability | | Loadable Security Module (LSM) | Tofino plugin module providing protocol-specific DPI for Modbus, EtherNet/IP, OPC, or other protocols | | Central Management Platform (CMP) | Tofino centralized management server for deploying and managing policies across multiple Tofino appliances |
TOFINO DEPLOYMENT REPORT
===========================
Date: YYYY-MM-DD
Appliances Deployed: [count]
PER-APPLIANCE SUMMARY:
[Appliance ID]:
Mode: Inline Bridge
Failsafe: Fail-Open
Protected Assets: [count]
Rules: [count]
DPI Protocols: [list]
RULE SUMMARY:
Allow Rules: [count]
Deny Rules: [count]
DPI-Enforced Rules: [count]
MONITORING:
Blocked Packets (24h): [count]
DPI Violations (24h): [count]| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-03 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted, and 21 counted toward the lift figure. The other 1 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +45 percentage points is the difference between those two pass rates over the 21 comparable cases.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.