Skip to content

Commit 1d3c6b8

Browse files
committed
Initial works into making a more general architecture of the digital twin. For now, the PLCs now can use the "BasePLC" class to implement some generic functions. This same approach will be followed by other classes. The good news, is that most of our "automatic_xx" scripts are generalized enough to be used by any ICS topology, we just need to refractor and decide the path in which they will be stored.
We put this work on a pause for now, to generate the 9months dataset for the enhanced c-town topology Former-commit-id: d32ba17
1 parent d74ddd9 commit 1d3c6b8

File tree

17 files changed

+106
-9
lines changed

17 files changed

+106
-9
lines changed

Demand_patterns/ctown_months_pattern__alternative.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@
336336
"name": "python",
337337
"nbconvert_exporter": "python",
338338
"pygments_lexer": "ipython3",
339-
"version": "3.7.7"
339+
"version": "3.7.4"
340340
}
341341
},
342342
"nbformat": 4,

ICS_topologies/Makefile

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,18 @@ MININET = sudo mn
88
PYTHON = sudo python
99
PYTHON_OPTS =
1010

11-
# regex testMatch: (?:^|[b_.-])[Tt]est)
12-
# --exe: include also executable files
13-
# -s: don't capture std output
14-
# nosetests -s tests/devices_tests.py:fun_name
11+
general-minitown:
12+
if [ ! -d general_topology/logs ]; then\
13+
mkdir general_topology/logs;\
14+
fi
15+
if [ ! -d general_topology/output ]; then\
16+
mkdir general_topology/output;\
17+
fi
18+
cd general_topology; rm -rf minitown_db.sqlite; $(PYTHON) $(PYTHON_OPTS) init.py; sudo chown mininet:mininet minitown_db.sqlite
19+
sudo pkill -f -u root "python -m cpppo.server.enip"
20+
sudo mn -c
21+
cd general_topology; sudo $(PYTHON) $(PYTHON_OPTS) automatic_run.py
22+
1523
single-minitown:
1624
if [ ! -d minitown_topology/logs ]; then\
1725
mkdir minitown_topology/logs;\

ICS_topologies/enhanced_ctown_topology/automatic_run.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ def setup_network(self):
5151
self.do_forward(net.get('attacker'))
5252

5353
def __init__(self, name, net):
54-
5554
signal.signal(signal.SIGINT, self.interrupt)
5655
signal.signal(signal.SIGTERM, self.interrupt)
5756
net.start()

ICS_topologies/enhanced_ctown_topology/plc1.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717

1818
class PLC1(PLC):
19-
2019
def write_output(self):
2120
print 'DEBUG plc1 shutdown'
2221
with open('output/plc1_saved_tank_levels_received.csv', 'w') as f:
@@ -44,8 +43,6 @@ def pre_loop(self):
4443

4544

4645
def main_loop(self):
47-
48-
4946
while True:
5047
try:
5148
self.local_time += 1

ICS_topologies/general_topology/automatic_plant.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import subprocessimport argparseimport signalimport sysclass SimulationControl(): def main(self): args = self.get_arguments() self.process_arguments(args) signal.signal(signal.SIGINT, self.interrupt) signal.signal(signal.SIGTERM, self.interrupt) self.simulation = self.start_simulation() while self.simulation.poll() is None: pass def interrupt(self, sig, frame): self.finish() sys.exit(0) def finish(self): self.simulation.send_signal(signal.SIGINT) self.simulation.wait() if self.simulation.poll() is None: self.simulation.terminate() if self.simulation.poll() is None: self.simulation.kill() def start_simulation(self): simulation = subprocess.Popen(["../../../wntr-experiments/bin/python", 'physical_process.py', self.simulator, self.topology, self.output]) return simulation def process_arguments(self,arg_parser): if arg_parser.simulator: self.simulator = arg_parser.simulator else: self.simulator = 'pdd' if arg_parser.topology: self.topology = arg_parser.topology else: self.topology = "minitown" if arg_parser.output: self.output = arg_parser.output else: self.output = 'default.csv' def get_arguments(self): parser = argparse.ArgumentParser(description='Master Script that launches the WNTR simulation') parser.add_argument("--simulator", "-s",help="Type of simulation used, can be Pressure Driven (pdd) or Demand Driven (dd)") parser.add_argument("--topology", "-t",help="Water network topology to simulate") parser.add_argument("--output", "-o", help="Output file name") return parser.parse_args()if __name__=="__main__": simulation_control = SimulationControl() simulation_control.main()

ICS_topologies/general_topology/automatic_plc.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import subprocessimport timeimport sysimport argparseimport signalclass NodeControl(): def sigint_handler(self, sig, frame): self.terminate() sys.exit(0) def terminate(self): print "Stopping Tcp dump process on PLC..." self.process_tcp_dump.kill() print "Stopping PLC..." self.plc_process.send_signal(signal.SIGINT) self.plc_process.wait() if self.plc_process.poll() is None: self.plc_process.terminate() if self.plc_process.poll() is None: self.plc_process.kill() def main(self): args = self.get_arguments() self.process_arguments(args) signal.signal(signal.SIGINT, self.sigint_handler) signal.signal(signal.SIGTERM, self.sigint_handler) self.configure_routing() self.delete_log() self.process_tcp_dump = self.start_tcpdump_capture() self.plc_process = self.start_plc() while self.plc_process.poll() is None: pass self.terminate() def process_arguments(self,arg_parser): if arg_parser.name: self.name = arg_parser.name print self.name else: self.name = 'plc1' def delete_log(self): subprocess.call(['rm', '-rf', self.name + '.log']) def configure_routing(self): self.interface_name = self.name + '-eth0' if self.name == 'scada': routing = subprocess.call(['route', 'add', 'default', 'gw', '192.168.2.254', self.interface_name],shell=False) else: routing = subprocess.call(['route','add','default', 'gw' ,'192.168.1.254', self.interface_name], shell=False) return routing def start_tcpdump_capture(self): pcap = self.interface_name+'.pcap' tcp_dump = subprocess.Popen(['tcpdump', '-i', self.interface_name, '-w', 'output/' + pcap], shell = False) return tcp_dump def start_plc(self): plc_process = subprocess.Popen(['python', self.name + '.py'], shell=False) return plc_process def get_arguments(self): parser = argparse.ArgumentParser(description='Master Script of a node in Minicps') parser.add_argument("--name", "-n",help="Name of the mininet node and script to run") return parser.parse_args()if __name__=="__main__": node_control = NodeControl() node_control.main()

ICS_topologies/general_topology/automatic_run.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from mininet.net import Mininetfrom mininet.cli import CLIfrom minicps.mcps import MiniCPSfrom topo import ScadaTopoimport sysimport timeimport shleximport subprocessimport signalautomatic = 0class Minitown(MiniCPS): """ Script to run the Minitown SCADA topology """ def __init__(self, name, net): signal.signal(signal.SIGINT, self.interrupt) signal.signal(signal.SIGTERM, self.interrupt) net.start() r0 = net.get('r0') # Pre experiment configuration, prepare routing path r0.cmd('sysctl net.ipv4.ip_forward=1') if automatic: self.automatic_start() else: CLI(net) net.stop() def interrupt(self, sig, frame): self.finish() sys.exit(0) def automatic_start(self): plc1 = net.get('plc1') plc2 = net.get('plc2') scada = net.get('scada') self.create_log_files() plc1_output = open("output/plc1.log", 'r+') plc2_output = open("output/plc2.log", 'r+') scada_output = open("output/scada.log", 'r+') physical_output = open("output/physical.log", 'r+') self.plc1_process = plc1.popen(sys.executable, "automatic_plc.py", "-n", "plc1", stderr=sys.stdout, stdout=plc1_output ) time.sleep(0.2) self.plc2_process = plc2.popen(sys.executable, "automatic_plc.py", "-n", "plc2", stderr=sys.stdout, stdout=plc2_output ) self.scada_process = scada.popen(sys.executable, "automatic_plc.py", "-n", "scada", stderr=sys.stdout, stdout=scada_output ) print "[*] Launched the PLCs and SCADA process, launching simulation..." plant = net.get('plant') simulation_cmd = shlex.split("python automatic_plant.py -s pdd -t minitown -o physical_process.csv") self.simulation = plant.popen(simulation_cmd, stderr=sys.stdout, stdout=physical_output) print "[] Simulating..." while self.simulation.poll() is None: pass self.finish() def create_log_files(self): subprocess.call("./create_log_files.sh") def end_plc_process(self, plc_process): plc_process.send_signal(signal.SIGINT) plc_process.wait() if plc_process.poll() is None: print("[*] Forcing termination of PLC process") plc_process.terminate() if plc_process.poll() is None: print("[*] Killing process") plc_process.kill() def finish(self): print "[*] Simulation finished" self.end_plc_process(self.scada_process) self.end_plc_process(self.plc2_process) self.end_plc_process(self.plc1_process) if self.simulation: self.simulation.terminate() cmd = shlex.split("./kill_cppo.sh") subprocess.call(cmd) net.stop() sys.exit(0)if __name__ == "__main__": topo = ScadaTopo() net = Mininet(topo=topo) minitown_cps = Minitown(name='minitown', net=net)
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from utils import PLC1_DATA, STATE, PLC1_PROTOCOL
2+
from minicps.devices import PLC
3+
import csv
4+
import signal
5+
import sys
6+
7+
8+
class BasePLC(PLC):
9+
10+
def set_parameters(self, path, result_list):
11+
self.result_list = result_list
12+
self.path = path
13+
14+
def write_output(self):
15+
with open('output/' + self.path, 'w') as f:
16+
writer = csv.writer(f)
17+
writer.writerows(self.result_list)
18+
19+
def sigint_handler(self, sig, frame):
20+
print 'DEBUG plc shutdown'
21+
self.write_output()
22+
sys.exit(0)
23+
24+
def startup(self):
25+
signal.signal(signal.SIGINT, self.sigint_handler)
26+
signal.signal(signal.SIGTERM, self.sigint_handler)
27+

ICS_topologies/general_topology/create_log_files.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
#!/bin/bashfiles=(plc1 plc2 scada physical)rm -rf output/*for file in "${files[@]}" do touch output/$file".log"done

ICS_topologies/general_topology/init.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from minicps.states import SQLiteStatefrom utils import PATH, SCHEMA, SCHEMA_INITfrom sqlite3 import OperationalErrorif __name__ == "__main__": try: SQLiteState._create(PATH, SCHEMA) SQLiteState._init(PATH, SCHEMA_INIT) print "{} successfully created.".format(PATH) except OperationalError: print "{} already exists.".format(PATH)

0 commit comments

Comments
 (0)