cat ~/notes/behind-the-plc.md
nyari device l0 di balik plc modbus: waktu semua unit id bohong
[en]nyari device l0 di balik plc modbus: waktu semua unit id bohong
Di postingan sebelumnya, gw bikin lab OT kecil buat nguji scanner ke protokol dan I/O fisik beneran.
Scanner-nya nemu esp32 sama raspi. Modbus, S7, dan BACnet juga jawab sesuai harapan.
Terus topologinya gw ubah. Raspi jadi soft-PLC pakai openplc, lalu satu device RTU beneran gw sambung lewat USB-RS485. Device-nya lampu sinyal kecil warna merah sama ijo.
Harusnya scanner bisa nemuin lampu di belakang PLC. Yg kejadian malah PLC-nya kedetect sebagai Siemens dan device di belakangnya hilang total.
Dua hasil salah ini datang dari asumsi yg sama: endpoint Modbus TCP belum tentu transparent gateway.
Apa itu device L0?
Gateway Modbus TCP di port 502 sering punya bus serial RS-485 di belakangnya.
Bus itu diisi slave RTU kayak sensor, actuator, sama meteran.
Tiap slave punya alamat yg disebut unit id.
Request ke gateway dengan unit id 3 harusnya diterusin ke slave nomor 3 di bus serial.
Slave di belakang gateway ini yg gw sebut device L0. Scan network biasa nemuin gateway lalu berhenti. Discovery L0 harus nembus gateway, enumerate alamat slave, lalu klasifikasiin apa pun yg jawab.
Mental model itu cocok buat transparent gateway. Openplc punya kelakuan lain.
Kenapa raspi kedetect sebagai Siemens?
Hasil aneh pertama ada di identity PLC.
Openplc bisa expose S7 server di port 102 lewat snap7.
Handshake dari snap7 bawa identity hardcoded:
system: SNAP7-SERVER
module: CPU 315-2 PN/DP
copyright: Original Siemens Equipment
Scanner baca response S7 yg valid dan ngelakuin persis apa yg diperintahin. Masalahnya, identity protokol itu ngejelasin layer emulator, bukan raspi yg jalanin servicenya.
Ini konteks yg berguna, tapi belum ngejelasin lampu yg hilang.
Buat itu, gw perlu liat cara openplc nge-handle unit id.
Gateway yg jawab buat semua alamat
Gw mulai dari scan paling masuk akal: sweep unit id 1 sampai 247 lalu fingerprint tiap response.
Openplc jawab semuanya. Unit 1, unit 50, sama unit 200 balikin process image yg sama persis sampai ke byte-nya.
Ini reflection. Tanpa guard, satu soft-PLC bisa berubah jadi ratusan phantom device.
Transparent gateway kayak moxa bakal nerusin tiap unit id ke slave RTU yg beda.
Openplc ngegabungin data Modbus RTU ke satu process image, lalu jawab semua unit id dari image yg sama.
Sekarang scanner probe tiga titik sebelum jalanin sweep penuh:
- satu ID valid di bagian bawah
- satu ID valid di tengah range
- satu reserved canary ID di 250
Kalo canary ikut jawab dengan fingerprint yg sama, berarti endpoint itu mantulin satu image ke seluruh address space.
Scanner kasih flag modbus_reflection lalu berhenti enumerate ID.
Ga ada lagi phantom device.
Nyari lampu lewat address map
Berhenti sweep memang ngilangin false positive, tapi lampunya masih terpasang secara fisik.
Kalo bukan direpresentasiin sebagai unit id, dia pergi ke mana?
Openplc naro I/O logic-nya sendiri di alamat bawah, coil 0 sampai 799.
Output dari downstream slave dimulai dari %QX100.0, alias coil 800.
Lampunya di-flatten ke process image itu:
- output ijo di coil 800
- output merah di coil 807
Gw scan address band downstream pakai request read-only. I/O aktif muncul di 800 sama 807.
Ketemu…
{
"modbus_reflection": true,
"l0_devices": [{
"type": "actuator",
"method": "slave_offset_band",
"io_profile": {"coils": true, "input_registers": false},
"mapped_range": [800, 807]
}]
}
Ga ada text identity di data itu. Nama device, model, sama serial number cuma ada di konfigurasi PLC.
Scanner masih bisa nebak tipe kasar dari I/O profile. Coil tanpa analog register biasanya nunjuk ke digital output kayak relay, lampu, atau actuator lain.
Batas discovery read-only
Detection-nya jalan selama lampu nyala. Begitu gw matiin, seluruh address band baca nol.
Openplc balikin nol buat setiap alamat di dalam process image, mau ada device yg ke-map atau engga. Dari scan read-only, dua kondisi ini identik:
actuator ke-map di coil 800, tapi lagi mati
ga ada apa-apa di coil 800
Sensor aktif biasanya tetap punya value di register, jadi masih bisa keliatan waktu idle. Actuator coil-only yg mati ga punya sinyal sejenis itu.
Gw bisa nulis ke coil lalu nunggu response fisik. Masalahnya, scanner network yg nulis coil juga bisa nyalain motor, buka valve, atau trigger output beneran. Write probe tetap jadi teknik karakterisasi di lab doang.
Di soft-PLC yg reflection, discovery Modbus read-only masih bisa dapetin presence, I/O profile, tipe kasar, sama mapped range. Dia ga bisa baca identity asli atau buktiin actuator yg lagi mati itu ada.
Itu batas protokolnya.
Ngetes transparent gateway tanpa gateway
Scanner-nya masih perlu coverage buat transparent gateway normal, sementara lab gw cuma punya openplc.
Awalnya gw coba pakai pymodbus server.
Server itu otomatis jawab FC 0x11 sama MEI, lalu route slave 0 ke semua unit id.
Bukannya niru transparent gateway, hasilnya malah reflection lagi.
Akhirnya gw bikin mock paling kecil yg cukup pakai raw socket dan standard library. Tiap slave cuma jawab ID-nya sendiri, sementara canary 250 diem.
unit 0 Light coil doang -> actuator
unit 1 TempSensor input register doang -> sensor
unit 2 FlowMeter holding register -> sensor
Scanner berhasil enumerate tiga slave dan klasifikasiin semuanya dari I/O profile. Sekitar 200 baris socket code cukup buat nutup jalur yg ga bisa diuji pakai soft-PLC beneran.
Dua endpoint, dua cara discovery
Transparent gateway sama soft-PLC bisa sama-sama listen di port 502 dan punya serial device di belakangnya.
Kelakuannya cukup beda sampai satu strategi discovery ga bisa aman buat keduanya.
Sekarang scanner cek reflection dulu. Kalo tiap ID tetap unik, scanner enumerate slave. Kalo satu image dipantulin, scanner pindah ke mapped address band dan jelasin batas hasilnya.
Pembeda pentingnya bukan port mana yg kebuka. Pembeda pentingnya adalah endpoint itu nerusin alamat atau jawab atas nama alamat tersebut.