Hvordan kjøre realistisk replay av IEC 60870-5-104-trafikk mot en SCADA-server i et virtuelt testlab, uten å røre SCADA-en.
- Kort om verktøyet
- Hva det gjør i praksis
- Nettverksutfordringen — hvorfor replay er vanskelig
- Anbefalt oppsett i VMware / Proxmox / virtuelt miljø
- Steg for steg: sette opp labben
- Steg for steg: kjøre en replay
- Forstå resultatene
- Feilsøking
- Begreper og kortliste
outstation er et verktøy for å spille av fanget nettverkstrafikk — typisk IEC 60870-5-104 fra reelle RTU-er — mot et testmål, på en slik måte at du kan:
- Benchmarke en SCADA-server ved å slippe løs et realistisk antall RTU-sesjoner mot den samtidig, og måle latens, gjennomstrømning og om noen meldinger mistes.
- Regresjonsteste en SCADA-oppgradering ved å spille av den samme trafikken før og etter, og sammenligne responsen.
- Simulere last — for eksempel 200 RTU-er som sender 50 meldinger i sekundet hver — fra én enkelt Linux-VM.
- Fôre IDS-/logger-/historian-systemer med realistisk trafikk uten å måtte ha de ekte RTU-ene tilkoblet.
Verktøyet er styrt fullt ut fra et nettgrensesnitt i nettleseren (http://<replay-vm>:8080). Det er ingen kommandolinje-arbeidsflyt — du laster opp pcap-filer, konfigurerer en kjøring, trykker start, og ser på resultatene i samme grensesnitt.
| Modus | Hva skjer | Bruksområde |
|---|---|---|
| Raw replay | Pakkene fra pcap-filen blir skrevet rått ut på nettverket via AF_PACKET, med opprinnelsesadresser uendret, men med destinasjonen omskrevet til ditt mål. Ingen TCP-tilstand, ingen ack. | Fôre IDS-er, wire-tap-analyse, logger-regresjon. |
| Benchmark / stateful session replay | Verktøyet åpner en reell TCP-sesjon per RTU i pcap-en, kjører en protokoll-bevisst replay (IEC 104), venter på ack-er, måler latens. | Lasttesting av SCADA-server, session-regresjon, respons-måling. |
Denne veiledningen fokuserer på benchmark-modus mot en SCADA-server, siden det er her SCADA-ingeniøren typisk har bruk for verktøyet.
La oss si du har en pcap fanget fra produksjon, med trafikk fra 200 RTU-er som alle kommuniserer med en SCADA-master over IEC 60870-5-104. Pcap-en er på 3 GB og dekker en time.
Du peker outstation på et SCADA-testsystem, velger "benchmark"-modus og trykker start. Det som skjer:
- Pcap-analyse. Verktøyet leser filen, identifiserer alle TCP-flows, grupperer pakkene per kilde-IP (per RTU), og plukker ut de meldingene som er relevante for IEC 104.
- Sesjonsoppsett. For hver RTU i pcap-en opprettes et TCP-socket som binder seg til den RTU-ens originale kilde-IP (via automatiske IP-aliaser på lokalt nettgrensesnitt), og kobler seg til SCADA-testserveren på port 2404.
- IEC 104-handshake. Hver sesjon sender
STARTDT actog venter påSTARTDT confra SCADA. K-vinduet settes opp (standardk=12,w=8). - Meldingsstrøm. Hver sesjon spiller av sine I-rammer mot SCADA med ønsket pacing (maksimal hastighet eller original pcap-timing), venter på S-rammer / ack-er, måler latens send→ack for hver enkelt melding.
- Live rapportering. I nettgrensesnittet ser du et live nettverksdiagram med animerte datapakker (ekte retning og hastighet), progress-barer per RTU, gjennomstrømning i pakker per sekund, og en latenstidshistogram som oppdateres underveis.
- Avslutning. Når alle sesjoner er ferdige, lagres rapporten i en SQLite-database, pakkene som ble sendt på wire lagres som en egen pcap (kan lastes ned for verifisering), og du ser aggregerte statistikker: p50/p90/p99-latens, meldinger sendt/mottatt, feil, per-sesjon-detaljer.
Dette er kanskje den viktigste delen å forstå.
Pcap-en inneholder trafikk fra 200 RTU-er som er spredt over mange forskjellige subnett — for eksempel 192.168.10.0/24, 172.16.5.0/24, 10.50.0.0/16, osv. Disse IP-ene er de ekte adressene til RTU-ene i produksjon.
SCADA-testserveren har sannsynligvis en whitelist: den godtar bare tilkoblinger fra adresser den kjenner — som er de samme RTU-adressene fra produksjon.
Så langt, alt vel: outstation sender pakker med de ekte kilde-IP-ene, SCADA godtar dem fordi de matcher whitelisten, og TCP-SYN-en kommer fram.
Men — SCADA må svare tilbake på SYN-en. SCADA-ens kjerne slår opp i rutetabellen sin: "Hvor skal jeg sende pakker til 192.168.10.42?". Hvis SCADA-en står på 10.0.0.0/24, har den ingen direkte rute til 192.168.10.0/24. Den sender svaret til sin default gateway. Default gateway har heller ingen rute dit. Svaret dør.
TCP-handshaken fullføres aldri. Ingen IEC 104-sesjon opprettes. Benchmarken feiler før den har startet.
Den åpenbare løsningen — "bare legg inn statiske ruter på SCADA som peker RTU-subnettene tilbake til outstation" — er ikke alltid akseptabelt:
- SCADA-serveren kan være en produksjonslignende test som skal være "uten endring".
- Du har ikke root-tilgang på SCADA.
- Endringene må dokumenteres, godkjennes, og rulles tilbake — alt er overhead.
- Testen skal være non-invasive: SCADA-en skal oppføre seg nøyaktig som i produksjon.
Vi trenger en løsning der vi manipulerer SCADAens nettverksmiljø, ikke SCADA-en selv.
Hvis både outstation og SCADA kjører som virtuelle maskiner — noe de gjør i dette oppsettet — kan vi sette dem begge på en isolert virtuell switch der outstation er den eneste naboen SCADA kan se på lag 2.
Da skjer følgende, helt automatisk:
- SCADA prøver å sende en TCP SYN-ACK til
192.168.10.42. - Rutetabellen sier: "off-subnet, send til default gateway
10.0.0.1". - SCADA gjør en ARP: "Hvem har
10.0.0.1?". - På en isolert switch er outstation den eneste som hører ARP-spørsmålet. Outstation har fått
10.0.0.1lagt til som et lokalt /32-alias på sitt indre nettgrensesnitt, og svarer: "Det er meg." - SCADA sender SYN-ACK-en til outstation sin MAC-adresse.
- Outstation tar imot pakken på kjernenivå, og fordi
192.168.10.42også er et lokalt alias, ruter kjernen pakken opp til brukerrommet, der benchmark-sesjonen bundet til192.168.10.42:0venter på den. Handshaken fullføres.
SCADA har ikke blitt konfigurert om. Den tror fortsatt den snakker med sin default gateway. Whitelisten stemmer fortsatt fordi vi ikke har rørt kilde-IP-en. Alt er som i produksjon — bortsett fra at det fysiske laget har blitt skjøvet til en isolert virtuell switch.
┌─────────────────────┐ ┌──────────────────────────┐
│ │ │ │
│ SCADA (VM) │ │ outstation (VM) │
│ │ │ │
│ eth0 │ │ eth0 (indre) │
│ 10.0.0.50/24 ├────────┤ 10.0.0.1/24 (*) │
│ gw: 10.0.0.1 │ │ + 192.168.10.0/24 alias │
│ │ │ + 172.16.5.0/24 alias │
│ │ │ + ... │
│ │ │ │
└─────────────────────┘ │ eth1 (ytre) │
│ 10.20.30.40/24 │
isolert vSwitch │ gw: 10.20.30.1 │
"vswitch_test" └───────────┬──────────────┘
│
│
ekte lab-nettverk
(internett, admin, NTP,
oppdateringer for SCADA)
(*) 10.0.0.1 er et eksempel — det skal være den samme IP-en som SCADA allerede har konfigurert som sin default gateway. Vi bruker det SCADA allerede tror er gatewayen, vi skaper ingen ny konfigurasjon på SCADA-siden.
- SCADA-VM: din eksisterende SCADA-testserver. Ingen endringer. Flyttes bare over på den nye isolerte switchen.
- outstation-VM: en ren Ubuntu / Debian / RHEL-installasjon med
outstation-binæren, to vNIC-er. - Isolert virtuell switch (kalt
vswitch_testher): i VMware ESXi: "Port Group" uten uplink; i vSphere: "Private VLAN"; i Proxmox: en Linux Bridge uten fysisk interface; i VirtualBox: "Internal Network"; i libvirt/KVM:<forward mode='none'/>.
- eth0 (indre): eneste kontakt med SCADA. Her legges alle RTU-aliaser og gateway-aliaset.
- eth1 (ytre): outstation-VM-en selv trenger tilgang til det ekte lab-nettet for admin/SSH/oppdateringer. I tillegg brukes eth1 som NAT-utgang slik at SCADA fortsatt kan hente NTP, oppdateringer osv. via outstation. Dette er valgfritt — hvis SCADA skal være helt isolert under testen, kan du droppe eth1.
Dette gjør du én gang per testlab. Etterpå er det kun pcap-opplasting og knappetrykk i nettgrensesnittet.
VMware vSphere / ESXi:
- Gå til Host → Networking → Virtual switches → Add standard virtual switch.
- Kall den
vswitch_test. - Ikke legg til noen physical uplink. Denne switchen skal ikke være tilkoblet noen NIC.
- Gå til Port groups → Add port group, navngi den
pg_scada_test, velgvswitch_testsom uplink. - VLAN-ID kan stå som 0.
Proxmox VE:
- Datacenter → Node → Network → Create → Linux Bridge, navngi den
vmbr_test. - La "Bridge ports" stå tom.
- Ikke gi den en IP.
libvirt / virt-manager:
<network>
<name>isolated-test</name>
<forward mode='none'/>
<bridge name='virbr-test' stp='on'/>
</network>Last inn med sudo virsh net-define isolated.xml && sudo virsh net-start isolated-test && sudo virsh net-autostart isolated-test.
- Slå av SCADA-VM-en, eller — om den støtter det — hot-swap vNIC-en.
- I VM-innstillingene: endre network adapter fra dagens switch til
pg_scada_test/vmbr_test/isolated-test. - Ikke endre noe inne i SCADA-gjesten. IP-adresse, netmask, default gateway, DNS — alt skal være uendret.
- Start SCADA-en igjen.
Viktig: etter flyttingen vil SCADA-en miste all nettverkskontakt inntil outstation-VM-en også er koblet til den samme isolerte switchen. Dette er forventet.
- Opprett en ny VM med 4 vCPU, 8 GB RAM (for 200 RTU-er), 40 GB disk.
- Installer Ubuntu Server 22.04 LTS eller lignende.
- Legg til to vNIC-er:
eth0→vswitch_test/pg_scada_test(indre — mot SCADA).eth1→ din vanlige lab-switch (ytre — mot det ekte nettet).
- Sett en statisk IP på
eth0. Bruk samme IP som SCADA-en har som default gateway — for eksempel10.0.0.1/24. Ikke sett gateway på denne. - Sett en IP på
eth1som passer det ekte lab-nettet, og sett default gateway på den. - Installer outstation og systemd-tjenesten (følg
systemd/install.shi repoet).
Du kan verifisere ved å pinge SCADA fra outstation: ping 10.0.0.50. Hvis det svarer, er det fysiske laget i orden.
Fra arbeidsstasjonen din, åpne http://<outstation-eth1-ip>:8080 i nettleser. Du skal se oversikten med seksjoner for "Pcap Library", "Run Configuration", "Runs" og "Network Diagram".
- Gå til Pcap Library-seksjonen.
- Dra-og-slipp pcap-en eller pcapng-en din inn i opplastingsfeltet.
- Vent til analysen er ferdig — du ser antall pakker, RTU-er, TCP-flows, lengde og en "viability"-vurdering av om pcap-en egner seg for benchmark-modus.
I Run Configuration-seksjonen:
- Velg pcap: klikk på pcap-en du nettopp lastet opp.
- Target IP: SCADA-testserverens IP, f.eks.
10.0.0.50. - Target port:
2404for IEC 104 (standard). - Egress NIC: velg
eth0(indre, mot SCADA). Dette er nettgrensesnittet der AF_PACKET-injeksjonen og TCP-sesjonene skal sendes ut. - Flags → haken på "benchmark mode".
- Role: "target is server · tool connects out as master" (dette er standard).
- Protocol:
iec104. - Pacing: velg "as fast as possible" for maksbelastning, eller "original pcap timing" for realisme.
- Iterations: antall ganger scriptet skal løpe.
1for et enkelt pass,0for uendelig løkke.
Dette er det nye, kritiske trinnet. Haken du har ventet på:
- Huk av "act as scada gateway" nederst i benchmark-panelet.
- SCADA-side gateway IP: den IP-en SCADA har som default gateway — i vårt eksempel
10.0.0.1. Dette er IP-en outstation vil hekte på som /32-alias under kjøringen. - Inner NIC: velg
eth0(samme som egress NIC — det er den som peker mot SCADA). - Upstream NAT NIC (valgfritt): velg
eth1hvis SCADA skal ha fortsatt tilgang til det ekte nettet (oppdateringer, NTP, admin) mens testen kjører. outstation vil da slå på IP-forwarding og legge til en MASQUERADE-regel foreth1. La være tom hvis SCADA skal være helt isolert under testen.
Trykk START RUN. Det som skjer bak kulissene:
- outstation installerer
10.0.0.1/32som alias påeth0. - IP-forwarding slås på (hvis NAT er valgt).
- MASQUERADE-regelen settes inn i
iptables -t nat -A POSTROUTING -o eth1 -j MASQUERADE. - For hver RTU i pcap-en legges RTU-IP-en til som /32-alias på
eth0via bestående auto-alias-mekanismen. - pcr_br0-broen opprettes, veth-interfaces per kilde etableres.
- Sesjonene kobler seg til SCADA-en én etter én (eller samtidig, avhengig av concurrency-valg).
- SCADA svarer på ARP-spørsmål for
10.0.0.1(via oss) og192.168.10.42(via oss direkte på lag 2 — selv om det ikke er nødvendig siden det går via default gateway). - Sesjonene strømmer I-rammer, måler latens, logger til SQLite.
Nettgrensesnittet viser live nettverksdiagram med animerte datastrømmer, progress-bar per RTU, og latens-sparkline.
outstation rydder opp alt automatisk:
- /32-aliaset på
eth0fjernes. - IP-forwarding tilbakestilles til opprinnelig verdi.
- MASQUERADE-regelen slettes.
- Veth-interfacene og pcr_br0-broen rives.
- Alle endringer er reversible.
Hvis outstation-prosessen krasjer midt i en kjøring, blir aliasene og MASQUERADE-regelen liggende. Ved oppstart ser outstation etter dette i statusfilen /var/lib/outstation/state-aliases.txt og fjerner spor etter forrige kjøring automatisk. Du får en advarsel i loggen: "reclaimed N orphaned ip alias(es) from a previous run".
Når kjøringen er ferdig ser du en "Run card" i RUNS-seksjonen. Klikk DETAILS for å utvide.
- Total messages sent / received: skal matche antall I-rammer i pcap-en (sendt) og det SCADA-en responderte med (mottatt).
- Aggregate latency p50/p90/p99/max: send→ack-tid målt per melding, aggregert over alle RTU-er. Dette er den viktigste tallet for SCADA-benchmarking.
- Aggregate throughput (msgs/sec): summen av alle sesjoners meldinger per sekund.
En rad per RTU, med:
- connected: om TCP-handshaken faktisk kom gjennom (dette er din første sanitetssjekk).
- messages sent / received / bytes
- latency p50/p90/p99/max: per-sesjons latens.
- window stalls: antall ganger sesjonen måtte vente på et
w-ack fra SCADA fordi k-vinduet var fullt. Høyt tall her = SCADA henger etter på ack-sendingen. - unacked at end: meldinger som ble sendt men ikke fikk ack før sesjonen stengte. Bør være 0.
- error: hvis sesjonen feilet, står feilmeldingen her.
outstation lagrer alt som faktisk ble sendt på wire under kjøringen til /tmp/outstation-captures/run_<id>.pcap. Du kan laste denne ned via DOWNLOAD CAPTURE-knappen og åpne den i Wireshark for å verifisere at det som gikk ut matcher forventningen.
Under DETAILS kan du også se en histogramsammenligning av inter-frame-gaps i original pcap vs det som faktisk ble sendt. Dette lar deg vurdere hvor nøyaktig pacing-en var. Typisk forskjell: millisekund-nivå på p99-halen, noe som er normalt for userspace-scheduling på Linux.
Alle kjøringer lagres i /var/lib/outstation/runs.sqlite. Etter en server-restart ligger alle historiske kjøringer der fortsatt, med rapporter og alt, og du kan slette dem individuelt via DELETE-knappen på hver run card.
Sjekk fra outstation-VM-en:
ping 10.0.0.50 # SCADA-ens IP
ip addr show eth0 # skal vise 10.0.0.1/24
ip neigh | grep 10.0.0.50 # skal vise SCADA-ens MACHvis pingen feiler, sjekk at begge VM-ene faktisk står på samme isolerte switch i hypervisoren.
I nettgrensesnittets per-sesjon-rad er connected fortsatt false etter 10 sekunder.
- Verifiser at gateway-aliaset er installert:
ip addr show eth0 | grep "10.0.0.1". Skal være der under kjøringen. - Verifiser at RTU-IP-aliaset er installert:
ip addr show eth0 | grep "192.168.10.42"(eller hvilken RTU du tester). - Sjekk at SCADA-en faktisk har
10.0.0.1som default gateway:ip routepå SCADA — selv om vi ikke skal endre noe på SCADA, kan du fritt lese konfigurasjonen. - Sjekk at kilde-IP-en matcher SCADA-ens whitelist. Dette er den vanligste feilen: du bruker en pcap fra et annet miljø, og SCADA-testserveren godtar ikke de kilde-IP-ene.
Hvis du i Wireshark (på outstation-siden) ser at outstation-kjernen svarer med RST på SYN-ACK-er fra SCADA, betyr det at RTU-IP-en ikke er lagt til som lokalt alias, og kjernen vet ikke at det er en "lokal" adresse. Dette skjer kun hvis den pre-emptive auto-alias-mekanismen har sviktet. Sjekk /var/log/syslog for feil fra netctl::add_ip_alias.
Du glemte å huke på "upstream NAT NIC". SCADA-en står isolert med kun outstation som nabo, og kommer seg ikke videre. Enten aktiver NAT-modus i neste kjøring, eller aksepter at SCADA er isolert under testen (ofte det beste uansett).
Benchmark-modus har et valgfritt warmup-intervall (standard 0 sekunder). Hvis du har satt en høy verdi, er det forventet at sesjonene ikke begynner å sende før warmup er ferdig. Warmup-tiden brukes for å la deg attache Wireshark / tcpdump på eth0 før trafikken starter.
Start outstation på nytt: sudo systemctl restart outstation (eller manuell start av binæren). Den rydder opp automatisk ved oppstart og logger reclaimed N orphaned ip alias(es).
Hvis du vil rydde manuelt:
sudo cat /var/lib/outstation/state-aliases.txt
sudo ip addr del 10.0.0.1/32 dev eth0
sudo iptables -t nat -D POSTROUTING -o eth1 -j MASQUERADE
sudo sysctl net.ipv4.ip_forward=0| Begrep | Forklaring |
|---|---|
| IEC 60870-5-104 | Standard for SCADA-kommunikasjon, TCP-basert, port 2404. Sender I-, S- og U-rammer; bruker et glidende k-vindu for flytkontroll. |
| I-frame | "Information frame" — den faktiske nyttelasten med ASDU-data (målinger, kommandoer, hendelser). |
| S-frame | "Supervisory frame" — ack-ramme som bekrefter mottak av N I-rammer. |
| U-frame | "Unnumbered frame" — styrings-rammer (STARTDT, STOPDT, TESTFR). |
| k-vindu | Maksimalt antall u-bekreftede I-rammer en sender kan ha ute. Standard k=12. |
| w-vindu | Mottaker må sende S-frame ack senest etter w mottatte I-rammer. Standard w=8. |
| ASDU | "Application Service Data Unit" — innholdet i en I-frame: type-ID, COT (cause of transmission), common address, IOA (information object address), verdier. |
| RTU | "Remote Terminal Unit" — fjernstasjon som rapporterer til SCADA. I denne labben er hver RTU representert av én TCP-sesjon fra outstation. |
| SCADA master | Systemet som samler inn data fra RTU-er. Når vi kjører benchmark i "master"-rolle, er outstation klienten og SCADA er serveren. |
| pcap / pcapng | Pakkefangstformater. outstation støtter begge. |
| AF_PACKET | Linux-mekanisme for å sende/motta rå Ethernet-rammer direkte, uten å gå via TCP/IP-stakken. Brukt av outstation for raw replay-modus. |
| MASQUERADE | NAT-regel i iptables som rewriter kildeadressen til utgangs-NICens adresse. Brukt her for å gi SCADA upstream-tilgang. |
| Isolert vSwitch | Virtuell switch som ikke er tilkoblet noen fysisk NIC. Kun VM-er som er koblet til samme switch kan snakke med hverandre. |
| /32-alias | En IP-adresse lagt til på et nettgrensesnitt med subnet-maske 32, som betyr "bare denne enkle adressen, ingen subnet-rute". Brukt for å hekte gateway-IP-en og RTU-IP-ene på eth0 uten å rote med rutetabellen. |
- Prosjektets README:
README.mdi rotmappen. - Protokollkode:
crates/proto_iec104/. - SCADA-gateway-implementasjon:
crates/netctl/src/lib.rs(søk etterGatewayGuard).
God testing.