From 669246815585ba709aeaafb54b928db0bb47d2f4 Mon Sep 17 00:00:00 2001 From: Rafael Date: Thu, 30 Apr 2026 09:42:21 +0200 Subject: [PATCH 01/42] implemented network topo --- lab0/network_topo.py | 18 ++++++++++++++++-- lab1/run_network.py | 17 ++++++++++++++--- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/lab0/network_topo.py b/lab0/network_topo.py index 0790ece..77c9ad6 100644 --- a/lab0/network_topo.py +++ b/lab0/network_topo.py @@ -22,13 +22,27 @@ #!/usr/bin/python from mininet.topo import Topo +from mininet.link import TCLink +from mininet.node import OVSBridge class BridgeTopo(Topo): "Creat a bridge-like customized network topology according to Figure 1 in the lab0 description." - def __init__(self): - Topo.__init__(self) + def build(self): + hosts = [] + for i in range(1, 5): + hosts.append(self.addHost(f"h{i}")) + + s1 = self.addSwitch("s1", cls=OVSBridge) + s2 = self.addSwitch("s2", cls=OVSBridge) + + links = [] + e1 = self.addLink(hosts[0], s1, cls=TCLink, bw=15, delay="10ms") + e2 = self.addLink(hosts[1], s1, cls=TCLink, bw=15, delay="10ms") + e3 = self.addLink(hosts[2], s2, cls=TCLink, bw=15, delay="10ms") + e4 = self.addLink(hosts[3], s2, cls=TCLink, bw=15, delay="10ms") + e5 = self.addLink(s1, s2, cls=TCLink, bw=20, delay="45ms") # TODO: add nodes and links to construct the topology; remember to specify the link properties diff --git a/lab1/run_network.py b/lab1/run_network.py index bafb44e..295e4c8 100644 --- a/lab1/run_network.py +++ b/lab1/run_network.py @@ -31,11 +31,22 @@ class NetworkTopo(Topo): - def __init__(self): + def build(self): + self.addHost("ext", ip="192.168.1.123/24") + self.addHost("h1", ip="10.0.1.2/24") + self.addHost("h2", ip="10.0.1.3/24") + self.addHost("ser", ip="10.0.2.2/24") - Topo.__init__(self) + self.addSwitch("s1") + self.addSwitch("s2") + self.addSwitch("s3") - # Build the specified network topology here + self.addLink("h1", "s1", bw=15, delay="10ms") + self.addLink("h2", "s1", bw=15, delay="10ms") + self.addLink("s1", "s3", bw=15, delay="10ms") + self.addLink("s3", "ext", bw=15, delay="10ms") + self.addLink("s3", "s2", bw=15, delay="10ms") + self.addLink("s2", "ser", bw=15, delay="10ms") def run(): topo = NetworkTopo() From f92b683200e0c065b331c47e3f735e7b48b43d4c Mon Sep 17 00:00:00 2001 From: Rafael Date: Fri, 1 May 2026 14:52:14 +0200 Subject: [PATCH 02/42] implemented switches --- lab1/ans_controller.py | 28 +++++++++++++++++++++++----- lab1/run_network.py | 6 +++--- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/lab1/ans_controller.py b/lab1/ans_controller.py index 128b456..e26cf95 100644 --- a/lab1/ans_controller.py +++ b/lab1/ans_controller.py @@ -18,13 +18,17 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ - +from logging import getLogger from ryu.base import app_manager from ryu.controller import ofp_event from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER from ryu.controller.handler import set_ev_cls -from ryu.ofproto import ofproto_v1_3 +from ryu.lib.packet import packet, ethernet +from ryu.ofproto import ofproto_v1_3, ofproto_v1_3_parser +from pprint import pprint + +logger = getLogger(__name__) class LearningSwitch(app_manager.RyuApp): OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION] @@ -33,7 +37,7 @@ def __init__(self, *args, **kwargs): super(LearningSwitch, self).__init__(*args, **kwargs) # Here you can initialize the data structures you want to keep at the controller - + self.packets_received = 0 @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER) def switch_features_handler(self, ev): @@ -62,8 +66,22 @@ def add_flow(self, datapath, priority, match, actions): # Handle the packet_in event @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) def _packet_in_handler(self, ev): - + self.packets_received += 1 msg = ev.msg datapath = msg.datapath + in_port = msg.match["in_port"] + pkt = packet.Packet(msg.data) + eth = pkt.get_protocol(ethernet.ethernet) + logger.info(f"seq={self.packets_received}: dpid={datapath.id}: in_port={in_port}, eth_src={eth.src}, eth_dst={eth.dst};") - # Your controller implementation should start here \ No newline at end of file + self.add_flow(datapath=datapath, priority=0, + match=ofproto_v1_3_parser.OFPMatch(eth_dst=eth.src), + actions=[ofproto_v1_3_parser.OFPActionOutput(port=in_port)]) + logger.info(f"Added rule: match(eth_dst={eth.src}), action(port={in_port}) on dpid={datapath.id};") + out = ofproto_v1_3_parser.OFPPacketOut(datapath=datapath, + buffer_id=msg.buffer_id, + in_port=in_port, + actions=[ofproto_v1_3_parser.OFPActionOutput(ofproto_v1_3.OFPP_FLOOD)], + data=msg.data) + datapath.send_msg(out) + logger.info(f"Instruction to dpid={datapath.id}: broadcast") diff --git a/lab1/run_network.py b/lab1/run_network.py index 295e4c8..a7774ff 100644 --- a/lab1/run_network.py +++ b/lab1/run_network.py @@ -37,9 +37,9 @@ def build(self): self.addHost("h2", ip="10.0.1.3/24") self.addHost("ser", ip="10.0.2.2/24") - self.addSwitch("s1") - self.addSwitch("s2") - self.addSwitch("s3") + self.addSwitch("s1", dpid=f"{1:016d}") + self.addSwitch("s2", dpid=f"{2:016d}") + self.addSwitch("s3", dpid=f"{3:016d}") self.addLink("h1", "s1", bw=15, delay="10ms") self.addLink("h2", "s1", bw=15, delay="10ms") From c5370c6925e760faf2a723f34d17e7b2a5bddd0a Mon Sep 17 00:00:00 2001 From: Arya-55 Date: Mon, 4 May 2026 16:26:37 +0200 Subject: [PATCH 03/42] added .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0dc7b4b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/.vscode \ No newline at end of file From 4529882ea40ca3bd01c1e08f4d5ff9f97ea046d8 Mon Sep 17 00:00:00 2001 From: Rafael Date: Sat, 9 May 2026 20:51:26 +0200 Subject: [PATCH 04/42] implemented tests for switches --- lab1/run_network.py | 97 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 93 insertions(+), 4 deletions(-) diff --git a/lab1/run_network.py b/lab1/run_network.py index a7774ff..d528c18 100644 --- a/lab1/run_network.py +++ b/lab1/run_network.py @@ -20,18 +20,24 @@ """ #!/bin/env python3 +from collections import defaultdict +from pprint import pprint -from mininet.topo import Topo +from mininet.cli import CLI from mininet.net import Mininet -from mininet.node import RemoteController, OVSKernelSwitch +from mininet.node import OVSKernelSwitch, RemoteController, Switch from mininet.link import TCLink -from mininet.cli import CLI +from mininet.topo import Topo from mininet.log import setLogLevel +import requests +from ryu.topology import switches class NetworkTopo(Topo): - def build(self): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.addHost("ext", ip="192.168.1.123/24") self.addHost("h1", ip="10.0.1.2/24") self.addHost("h2", ip="10.0.1.3/24") @@ -48,6 +54,86 @@ def build(self): self.addLink("s3", "s2", bw=15, delay="10ms") self.addLink("s2", "ser", bw=15, delay="10ms") + +class SwitchFlowTester: + + def __init__(self, net: Mininet): + self.net = net + + self.test_data_switch_flows = defaultdict(dict) + switchname = "s1" + port_to_hop = self.get_port_to_hop(switchname) + self.test_data_switch_flows[switchname][net.get("h1").MAC()] = port_to_hop["h1"] + self.test_data_switch_flows[switchname][net.get("h2").MAC()] = port_to_hop["h2"] + self.test_data_switch_flows[switchname][net.get("ext").MAC()] = port_to_hop["s3"] + self.test_data_switch_flows[switchname][net.get("ser").MAC()] = port_to_hop["s3"] + + switchname = "s2" + port_to_hop = self.get_port_to_hop(switchname) + self.test_data_switch_flows[switchname][net.get("h1").MAC()] = port_to_hop["s3"] + self.test_data_switch_flows[switchname][net.get("h2").MAC()] = port_to_hop["s3"] + self.test_data_switch_flows[switchname][net.get("ext").MAC()] = port_to_hop["s3"] + self.test_data_switch_flows[switchname][net.get("ser").MAC()] = port_to_hop["ser"] + + switchname = "s3" + port_to_hop = self.get_port_to_hop(switchname) + self.test_data_switch_flows[switchname][net.get("h1").MAC()] = port_to_hop["s1"] + self.test_data_switch_flows[switchname][net.get("h2").MAC()] = port_to_hop["s1"] + self.test_data_switch_flows[switchname][net.get("ext").MAC()] = port_to_hop["ext"] + self.test_data_switch_flows[switchname][net.get("ser").MAC()] = port_to_hop["s2"] + + for switchname in self.test_data_switch_flows: + for hostmac in self.test_data_switch_flows[switchname]: + print(f"Switch={switchname}: {hostmac} --> {self.test_data_switch_flows[switchname][hostmac]}") + + def get_port_to_hop(self, switchname): + switch = self.net.get(switchname) + ret = {} + for port_no, intf in switch.intfs.items(): + if port_no == 0: # except lo + continue + link = intf.link + if link.intf1.node.name != switchname: + ret[link.intf1.node.name] = port_no + else: + ret[link.intf2.node.name] = port_no + return ret + + @staticmethod + def cast_to_int_if_possible(arg): + try: + return int(arg) + except ValueError: + return arg + + def parse_action_str(self, action_str: str): + action, target = action_str.split(":") + return action, self.cast_to_int_if_possible(target) + + def test(self): + flows = defaultdict(dict) + for switch in self.net.switches: + sdpid = int(switch.dpid) + resp = requests.get(f"http://localhost:8080/stats/flow/{sdpid}") + flows_json = resp.json()[str(sdpid)] + + for d in flows_json: + match = d["match"].get("dl_dst") + flows[switch.name][match] = [d["actions"]] + for actions in flows[switch.name][match]: + for action_str in actions: + _, port = self.parse_action_str(action_str) + flows[switch.name][match] = port + + for switchname in self.test_data_switch_flows: + for hostmac, port_no in self.test_data_switch_flows[switchname].items(): + test = flows[switchname].get(hostmac) == port_no + if test: + print(f"Success: Switch={switchname}: Rule: {hostmac} --> {port_no} == {flows[switchname].get(hostmac)}") + else: + print(f"Failure: Switch={switchname}: Rule: {hostmac} --> {port_no} != {flows[switchname].get(hostmac)}") + + def run(): topo = NetworkTopo() net = Mininet(topo=topo, @@ -60,6 +146,9 @@ def run(): ip="127.0.0.1", port=6653) net.start() + net.pingAllFull() + switch_flow_tester = SwitchFlowTester(net) + switch_flow_tester.test() CLI(net) net.stop() From 0ccb14a59fdccff2e4505d74976e654b54ad259a Mon Sep 17 00:00:00 2001 From: Arya-55 Date: Sun, 10 May 2026 21:04:40 +0200 Subject: [PATCH 05/42] added network topo --- lab1/run_network.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/lab1/run_network.py b/lab1/run_network.py index bafb44e..dcdac42 100644 --- a/lab1/run_network.py +++ b/lab1/run_network.py @@ -35,7 +35,26 @@ def __init__(self): Topo.__init__(self) - # Build the specified network topology here + # hosts + h1 = self.addHost("h1", ip = "10.0.1.2/24") + h2 = self.addHost("h2", ip = "10.0.1.3/24") + ser = self.addHost("ser", ip = "10.0.2.2/24") + ext = self.addHost("ext", ip = "192.168.1.123/24") + + # switches + s1 = self.addSwitch("s1", dpid = f"{1:016d}") + s2 = self.addSwitch("s2", dpid = f"{2:016d}") + + # router + s3 = self.addSwitch("s3", dpid = f"{3:016d}") + + # links + self.addLink(h1, s1, bw = 15, delay = "10ms") + self.addLink(h2, s1, bw = 15, delay = "10ms") + self.addLink(s1, s3, bw = 15, delay = "10ms") + self.addLink(s3, ext, bw = 15, delay = "10ms") + self.addLink(s3, s2, bw = 15, delay = "10ms") + self.addLink(s2, ser, bw = 15, delay = "10ms") def run(): topo = NetworkTopo() From a9aac3ba79b23a23f3da9f440882202a4a744280 Mon Sep 17 00:00:00 2001 From: Arya-55 Date: Mon, 11 May 2026 13:43:30 +0200 Subject: [PATCH 06/42] added entry point and data structures for the router implementation --- lab1/ans_controller.py | 27 +++++++++++++++++++++++++++ lab1/run_network.py | 4 ++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/lab1/ans_controller.py b/lab1/ans_controller.py index e26cf95..61ea65e 100644 --- a/lab1/ans_controller.py +++ b/lab1/ans_controller.py @@ -39,6 +39,20 @@ def __init__(self, *args, **kwargs): # Here you can initialize the data structures you want to keep at the controller self.packets_received = 0 + # Router port MACs assumed by the controller + self.port_to_own_mac = { + 1: "00:00:00:00:01:01", + 2: "00:00:00:00:01:02", + 3: "00:00:00:00:01:03" + } + + # Router port (gateways) IP addresses assumed by the controller + self.port_to_own_ip = { + 1: "10.0.1.1/24", + 2: "10.0.2.1/24", + 3: "192.168.1.1/24" + } + @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER) def switch_features_handler(self, ev): @@ -69,6 +83,14 @@ def _packet_in_handler(self, ev): self.packets_received += 1 msg = ev.msg datapath = msg.datapath + + if datapath.id == 3: + # handle router request + self.handle_router_request(ev) + return + + # handle switch requests + in_port = msg.match["in_port"] pkt = packet.Packet(msg.data) eth = pkt.get_protocol(ethernet.ethernet) @@ -85,3 +107,8 @@ def _packet_in_handler(self, ev): data=msg.data) datapath.send_msg(out) logger.info(f"Instruction to dpid={datapath.id}: broadcast") + + def handle_router_request(self, ev): + msg = ev.msg + datapath = msg.datapath + pass \ No newline at end of file diff --git a/lab1/run_network.py b/lab1/run_network.py index d528c18..01e3510 100644 --- a/lab1/run_network.py +++ b/lab1/run_network.py @@ -147,8 +147,8 @@ def run(): port=6653) net.start() net.pingAllFull() - switch_flow_tester = SwitchFlowTester(net) - switch_flow_tester.test() + #switch_flow_tester = SwitchFlowTester(net) + #switch_flow_tester.test() CLI(net) net.stop() From eb59531032fee11d395d216d516dbf83a116171a Mon Sep 17 00:00:00 2001 From: Arya-55 Date: Mon, 11 May 2026 16:10:32 +0200 Subject: [PATCH 07/42] added logging and notes on what needs to be done --- .gitignore | 3 ++- lab1/ans_controller.py | 37 +++++++++++++++++++++++++++++++------ lab1/run_network.py | 2 +- 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 0dc7b4b..9da3a30 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -/.vscode \ No newline at end of file +/.vscode +__pycache__ \ No newline at end of file diff --git a/lab1/ans_controller.py b/lab1/ans_controller.py index 61ea65e..51070b0 100644 --- a/lab1/ans_controller.py +++ b/lab1/ans_controller.py @@ -48,9 +48,9 @@ def __init__(self, *args, **kwargs): # Router port (gateways) IP addresses assumed by the controller self.port_to_own_ip = { - 1: "10.0.1.1/24", - 2: "10.0.2.1/24", - 3: "192.168.1.1/24" + 1: "10.0.1.1", + 2: "10.0.2.1", + 3: "192.168.1.1" } @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER) @@ -85,7 +85,7 @@ def _packet_in_handler(self, ev): datapath = msg.datapath if datapath.id == 3: - # handle router request + # handle router (s3) request self.handle_router_request(ev) return @@ -93,10 +93,15 @@ def _packet_in_handler(self, ev): in_port = msg.match["in_port"] pkt = packet.Packet(msg.data) + + logger.info("Switch Packets:") + for p in pkt.protocols: + logger.info(f"\t- {p}") + eth = pkt.get_protocol(ethernet.ethernet) logger.info(f"seq={self.packets_received}: dpid={datapath.id}: in_port={in_port}, eth_src={eth.src}, eth_dst={eth.dst};") - self.add_flow(datapath=datapath, priority=0, + self.add_flow(datapath=datapath, priority=1, match=ofproto_v1_3_parser.OFPMatch(eth_dst=eth.src), actions=[ofproto_v1_3_parser.OFPActionOutput(port=in_port)]) logger.info(f"Added rule: match(eth_dst={eth.src}), action(port={in_port}) on dpid={datapath.id};") @@ -111,4 +116,24 @@ def _packet_in_handler(self, ev): def handle_router_request(self, ev): msg = ev.msg datapath = msg.datapath - pass \ No newline at end of file + in_port = msg.match["in_port"] + pkt = packet.Packet(msg.data) + + logger.info(f"Packet comes from router and was received on port {in_port}! Protocols:") + for p in pkt.protocols: + logger.info(f"\t- {p}") + + # ping packets have ipv6 and icmpv6 -> Ping uses icmp + # iperf generates arp packages + + # must answer ARP-Packets as hosts will ARP for IP Gateways + # "10.0.1.1" ? => "00:00:00:00:01:01" + # "10.0.2.1" ? => "00:00:00:00:01:02", + # "192.168.1.1" ? => "00:00:00:00:01:03" + + # router needs to rewrite ethernet headers when forwarding its packets (correkt?) + + # ext may not ping internal hosts => drop ICMP packets from ext + # what about from internal hosts to extern, according to the given result they should also not be able to ping ext + # no TCP/UDP allowed between ext and ser => drop TCP/UDP packets with respective src/dst-pairs + # hosts may only ping their own gateway => drop ICMP packages if src-ip != dst-ip \ No newline at end of file diff --git a/lab1/run_network.py b/lab1/run_network.py index 01e3510..25a318d 100644 --- a/lab1/run_network.py +++ b/lab1/run_network.py @@ -146,7 +146,7 @@ def run(): ip="127.0.0.1", port=6653) net.start() - net.pingAllFull() + #net.pingAllFull() #switch_flow_tester = SwitchFlowTester(net) #switch_flow_tester.test() CLI(net) From 5027f5e8872e18963f7563346518bf9662dc22d5 Mon Sep 17 00:00:00 2001 From: alexmupb Date: Mon, 11 May 2026 19:32:13 +0200 Subject: [PATCH 08/42] add mac on router links, add readme --- lab1/Readme.md | 17 +++++++++++++++++ lab1/ans_controller.py | 12 ++++++------ lab1/run_network.py | 15 ++++++++------- 3 files changed, 31 insertions(+), 13 deletions(-) create mode 100644 lab1/Readme.md diff --git a/lab1/Readme.md b/lab1/Readme.md new file mode 100644 index 0000000..14578a7 --- /dev/null +++ b/lab1/Readme.md @@ -0,0 +1,17 @@ +# Readme +First thing in any window: `cd share/ans-ss26-labs/lab1` + +In first window: `ryu-manager ans_controller.py` + +In second window: `sudo python3 run_network.py` + +From second window once network runs: `xterm h1` (h2, etc) to open host terminals + +In second window to restart network: `exit`, then `sudo mn -c`, which also kills the controller + +## Common troubleshooting commands +Ping: `h1 ping h2 -c5` , `pingall` + +Check TCP/UDP connectivity: `iperf h1 h2 (-u)` , add -u for UDP + +See flow tables on node s1: `sudo ovs-ofctl dump-flows s1` (in separate window, not mininet) \ No newline at end of file diff --git a/lab1/ans_controller.py b/lab1/ans_controller.py index 51070b0..bf1c7a0 100644 --- a/lab1/ans_controller.py +++ b/lab1/ans_controller.py @@ -41,16 +41,16 @@ def __init__(self, *args, **kwargs): # Router port MACs assumed by the controller self.port_to_own_mac = { - 1: "00:00:00:00:01:01", - 2: "00:00:00:00:01:02", - 3: "00:00:00:00:01:03" + 1: "00:00:00:00:01:01", + 2: "00:00:00:00:01:02", + 3: "00:00:00:00:01:03" } # Router port (gateways) IP addresses assumed by the controller self.port_to_own_ip = { - 1: "10.0.1.1", - 2: "10.0.2.1", - 3: "192.168.1.1" + 1: "10.0.1.1", + 2: "10.0.2.1", + 3: "192.168.1.1" } @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER) diff --git a/lab1/run_network.py b/lab1/run_network.py index 25a318d..1409de3 100644 --- a/lab1/run_network.py +++ b/lab1/run_network.py @@ -39,19 +39,20 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.addHost("ext", ip="192.168.1.123/24") - self.addHost("h1", ip="10.0.1.2/24") - self.addHost("h2", ip="10.0.1.3/24") + self.addHost("h1" , ip="10.0.1.2/24") + self.addHost("h2" , ip="10.0.1.3/24") self.addHost("ser", ip="10.0.2.2/24") self.addSwitch("s1", dpid=f"{1:016d}") self.addSwitch("s2", dpid=f"{2:016d}") self.addSwitch("s3", dpid=f"{3:016d}") - self.addLink("h1", "s1", bw=15, delay="10ms") - self.addLink("h2", "s1", bw=15, delay="10ms") - self.addLink("s1", "s3", bw=15, delay="10ms") - self.addLink("s3", "ext", bw=15, delay="10ms") - self.addLink("s3", "s2", bw=15, delay="10ms") + # Assign mac address on links for s3 as per given topology + self.addLink("h1", "s1" , bw=15, delay="10ms") + self.addLink("h2", "s1" , bw=15, delay="10ms") + self.addLink("s1", "s3" , bw=15, delay="10ms", addr2='00:00:00:00:01:01') + self.addLink("s3", "ext", bw=15, delay="10ms", addr1='00:00:00:00:01:03') + self.addLink("s3", "s2" , bw=15, delay="10ms", addr1='00:00:00:00:01:02') self.addLink("s2", "ser", bw=15, delay="10ms") From 006d6110dae268747137fcb631a5b73b6b4b1128 Mon Sep 17 00:00:00 2001 From: RafaelSche <47508551+RafaelSche@users.noreply.github.com> Date: Tue, 12 May 2026 13:41:18 +0200 Subject: [PATCH 09/42] Update Readme.md: use SwitchFlowTester --- lab1/Readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lab1/Readme.md b/lab1/Readme.md index 14578a7..9a22bcc 100644 --- a/lab1/Readme.md +++ b/lab1/Readme.md @@ -1,7 +1,7 @@ # Readme First thing in any window: `cd share/ans-ss26-labs/lab1` -In first window: `ryu-manager ans_controller.py` +In first window: `ryu-manager ans_controller.py` or `ryu-manager ryu.app.ofctl_rest ans_controller.py` to use the `SwitchFlowTester` In second window: `sudo python3 run_network.py` @@ -14,4 +14,4 @@ Ping: `h1 ping h2 -c5` , `pingall` Check TCP/UDP connectivity: `iperf h1 h2 (-u)` , add -u for UDP -See flow tables on node s1: `sudo ovs-ofctl dump-flows s1` (in separate window, not mininet) \ No newline at end of file +See flow tables on node s1: `sudo ovs-ofctl dump-flows s1` (in separate window, not mininet) From 15a9cfa89a779e7017a94871b8c6068e8e5b2ba8 Mon Sep 17 00:00:00 2001 From: Rafael Date: Tue, 12 May 2026 19:05:12 +0200 Subject: [PATCH 10/42] disable and drop IPv6 --- lab1/ans_controller.py | 7 ++++++- lab1/run_network.py | 6 ++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/lab1/ans_controller.py b/lab1/ans_controller.py index bf1c7a0..67b2bbb 100644 --- a/lab1/ans_controller.py +++ b/lab1/ans_controller.py @@ -23,7 +23,7 @@ from ryu.controller import ofp_event from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER from ryu.controller.handler import set_ev_cls -from ryu.lib.packet import packet, ethernet +from ryu.lib.packet import packet, ethernet, ether_types from ryu.ofproto import ofproto_v1_3, ofproto_v1_3_parser from pprint import pprint @@ -66,6 +66,11 @@ def switch_features_handler(self, ev): ofproto.OFPCML_NO_BUFFER)] self.add_flow(datapath, 0, match, actions) + # drop IPv6 for now + match = parser.OFPMatch(eth_type=ether_types.ETH_TYPE_IPV6) + actions = [] + self.add_flow(datapath, 3, match, actions) + # Add a flow entry to the flow-table def add_flow(self, datapath, priority, match, actions): ofproto = datapath.ofproto diff --git a/lab1/run_network.py b/lab1/run_network.py index 1409de3..771b315 100644 --- a/lab1/run_network.py +++ b/lab1/run_network.py @@ -146,6 +146,12 @@ def run(): controller=RemoteController, ip="127.0.0.1", port=6653) + + for host in net.hosts: + host.cmd("sysctl -w net.ipv6.conf.all.disable_ipv6=1") + host.cmd("sysctl -w net.ipv6.conf.default.disable_ipv6=1") + host.cmd("sysctl -w net.ipv6.conf.lo.disable_ipv6=1") + net.start() #net.pingAllFull() #switch_flow_tester = SwitchFlowTester(net) From c6a8656d430c19b1ea09943318cc87e6efed0eaa Mon Sep 17 00:00:00 2001 From: Rafael Date: Tue, 12 May 2026 19:07:39 +0200 Subject: [PATCH 11/42] log failed rule matches --- lab1/ans_controller.py | 88 +++++++++++++++++++++++++++--------------- 1 file changed, 57 insertions(+), 31 deletions(-) diff --git a/lab1/ans_controller.py b/lab1/ans_controller.py index 67b2bbb..901ce97 100644 --- a/lab1/ans_controller.py +++ b/lab1/ans_controller.py @@ -18,6 +18,7 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ +from collections import defaultdict from logging import getLogger from ryu.base import app_manager from ryu.controller import ofp_event @@ -30,6 +31,7 @@ logger = getLogger(__name__) + class LearningSwitch(app_manager.RyuApp): OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION] @@ -37,7 +39,7 @@ def __init__(self, *args, **kwargs): super(LearningSwitch, self).__init__(*args, **kwargs) # Here you can initialize the data structures you want to keep at the controller - self.packets_received = 0 + self.packet_counter = 0 # Router port MACs assumed by the controller self.port_to_own_mac = { @@ -53,9 +55,11 @@ def __init__(self, *args, **kwargs): 3: "192.168.1.1" } + self.mac_to_port = defaultdict(dict) + @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER) def switch_features_handler(self, ev): - + datapath = ev.msg.datapath ofproto = datapath.ofproto parser = datapath.ofproto_parser @@ -82,43 +86,64 @@ def add_flow(self, datapath, priority, match, actions): match=match, instructions=inst) datapath.send_msg(mod) + @staticmethod + def packet_out_to_port(msg, datapath, parser, in_port, port): + return parser.OFPPacketOut(datapath=datapath, + buffer_id=msg.buffer_id, + in_port=in_port, + actions=[parser.OFPActionOutput(port)], + data=msg.data) + + def flood_packet_out(self, *, ofproto, **kwargs): + return self.packet_out_to_port(port=ofproto.OFPP_FLOOD, **kwargs) + # Handle the packet_in event @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) def _packet_in_handler(self, ev): - self.packets_received += 1 + num_minus = 10 + print(num_minus * "-" + "_packet_in_handler start" + num_minus * "-") + # print(f"self.mac_to_port={self.mac_to_port}") + + self.packet_counter += 1 msg = ev.msg datapath = msg.datapath + ofproto = datapath.ofproto + parser = datapath.ofproto_parser if datapath.id == 3: # handle router (s3) request self.handle_router_request(ev) - return - # handle switch requests - - in_port = msg.match["in_port"] - pkt = packet.Packet(msg.data) - - logger.info("Switch Packets:") - for p in pkt.protocols: - logger.info(f"\t- {p}") - - eth = pkt.get_protocol(ethernet.ethernet) - logger.info(f"seq={self.packets_received}: dpid={datapath.id}: in_port={in_port}, eth_src={eth.src}, eth_dst={eth.dst};") - - self.add_flow(datapath=datapath, priority=1, - match=ofproto_v1_3_parser.OFPMatch(eth_dst=eth.src), - actions=[ofproto_v1_3_parser.OFPActionOutput(port=in_port)]) - logger.info(f"Added rule: match(eth_dst={eth.src}), action(port={in_port}) on dpid={datapath.id};") - out = ofproto_v1_3_parser.OFPPacketOut(datapath=datapath, - buffer_id=msg.buffer_id, - in_port=in_port, - actions=[ofproto_v1_3_parser.OFPActionOutput(ofproto_v1_3.OFPP_FLOOD)], - data=msg.data) - datapath.send_msg(out) - logger.info(f"Instruction to dpid={datapath.id}: broadcast") + else: + # handle switch requests + in_port = msg.match["in_port"] + pkt = packet.Packet(msg.data) + + logger.info("Switch Packets:") + for p in pkt.protocols: + logger.info(f"\t- {p}") + + eth = pkt.get_protocol(ethernet.ethernet) + logger.info(f"seq={self.packet_counter}: dpid={datapath.id}: in_port={in_port}, eth_src={eth.src}, eth_dst={eth.dst};") + + if self.mac_to_port.get(datapath.id, {}).get(eth.dst): + logger.critical(f"Existing rule did not match: match(eth_dst={eth.src}), action(port={in_port}) on dpid={datapath.id};") + out = self.packet_out_to_port(msg, datapath, parser, in_port, port=self.mac_to_port[datapath.id][eth.dst]) + else: + self.mac_to_port[datapath.id][eth.src] = in_port + self.add_flow(datapath=datapath, priority=2, + match=parser.OFPMatch(eth_dst=eth.src), + actions=[parser.OFPActionOutput(port=in_port)]) + logger.info(f"Added rule: match(eth_dst={eth.src}), action(port={in_port}) on dpid={datapath.id};") + out = self.flood_packet_out(msg=msg, datapath=datapath, parser=parser, in_port=in_port, ofproto=ofproto) + datapath.send_msg(out) + logger.info(f"Instruction to dpid={datapath.id}: broadcast") + print(num_minus * "-" + "_packet_in_handler end" + num_minus * "-") def handle_router_request(self, ev): + num_minus = 10 + print(num_minus * "-" + "handle_router_request start" + num_minus * "-") + msg = ev.msg datapath = msg.datapath in_port = msg.match["in_port"] @@ -127,7 +152,7 @@ def handle_router_request(self, ev): logger.info(f"Packet comes from router and was received on port {in_port}! Protocols:") for p in pkt.protocols: logger.info(f"\t- {p}") - + # ping packets have ipv6 and icmpv6 -> Ping uses icmp # iperf generates arp packages @@ -135,10 +160,11 @@ def handle_router_request(self, ev): # "10.0.1.1" ? => "00:00:00:00:01:01" # "10.0.2.1" ? => "00:00:00:00:01:02", # "192.168.1.1" ? => "00:00:00:00:01:03" - + # router needs to rewrite ethernet headers when forwarding its packets (correkt?) - + # ext may not ping internal hosts => drop ICMP packets from ext # what about from internal hosts to extern, according to the given result they should also not be able to ping ext # no TCP/UDP allowed between ext and ser => drop TCP/UDP packets with respective src/dst-pairs - # hosts may only ping their own gateway => drop ICMP packages if src-ip != dst-ip \ No newline at end of file + # hosts may only ping their own gateway => drop ICMP packages if src-ip != dst-ip + print(num_minus * "-" + "handle_router_request end" + num_minus * "-") \ No newline at end of file From 2202c22429ad26c14c1790e7ff7925236eea3383 Mon Sep 17 00:00:00 2001 From: Rafael Date: Tue, 12 May 2026 19:08:08 +0200 Subject: [PATCH 12/42] switche to mininet logging --- lab1/run_network.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/lab1/run_network.py b/lab1/run_network.py index 771b315..fa026e0 100644 --- a/lab1/run_network.py +++ b/lab1/run_network.py @@ -28,9 +28,8 @@ from mininet.node import OVSKernelSwitch, RemoteController, Switch from mininet.link import TCLink from mininet.topo import Topo -from mininet.log import setLogLevel +import mininet.log import requests -from ryu.topology import switches class NetworkTopo(Topo): @@ -85,7 +84,7 @@ def __init__(self, net: Mininet): for switchname in self.test_data_switch_flows: for hostmac in self.test_data_switch_flows[switchname]: - print(f"Switch={switchname}: {hostmac} --> {self.test_data_switch_flows[switchname][hostmac]}") + mininet.log.info(f"Switch={switchname}: {hostmac} --> {self.test_data_switch_flows[switchname][hostmac]}") def get_port_to_hop(self, switchname): switch = self.net.get(switchname) @@ -130,9 +129,9 @@ def test(self): for hostmac, port_no in self.test_data_switch_flows[switchname].items(): test = flows[switchname].get(hostmac) == port_no if test: - print(f"Success: Switch={switchname}: Rule: {hostmac} --> {port_no} == {flows[switchname].get(hostmac)}") + mininet.log.info(f"Success: Switch={switchname}: Rule: {hostmac} --> {port_no} == {flows[switchname].get(hostmac)}") else: - print(f"Failure: Switch={switchname}: Rule: {hostmac} --> {port_no} != {flows[switchname].get(hostmac)}") + mininet.log.info(f"Failure: Switch={switchname}: Rule: {hostmac} --> {port_no} != {flows[switchname].get(hostmac)}") def run(): @@ -153,12 +152,14 @@ def run(): host.cmd("sysctl -w net.ipv6.conf.lo.disable_ipv6=1") net.start() - #net.pingAllFull() - #switch_flow_tester = SwitchFlowTester(net) - #switch_flow_tester.test() + net.pingAll() + + switch_flow_tester = SwitchFlowTester(net) + switch_flow_tester.test() + CLI(net) net.stop() if __name__ == '__main__': - setLogLevel('info') + mininet.log.setLogLevel('info') run() \ No newline at end of file From 79ba176bc34f97389924dbd4a393aab10219eef2 Mon Sep 17 00:00:00 2001 From: Arya-55 Date: Wed, 13 May 2026 10:55:03 +0200 Subject: [PATCH 13/42] added comments --- lab1/ans_controller.py | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/lab1/ans_controller.py b/lab1/ans_controller.py index 901ce97..513c64b 100644 --- a/lab1/ans_controller.py +++ b/lab1/ans_controller.py @@ -24,7 +24,7 @@ from ryu.controller import ofp_event from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER from ryu.controller.handler import set_ev_cls -from ryu.lib.packet import packet, ethernet, ether_types +from ryu.lib.packet import packet, ethernet, ether_types, ipv4 from ryu.ofproto import ofproto_v1_3, ofproto_v1_3_parser from pprint import pprint @@ -149,6 +149,39 @@ def handle_router_request(self, ev): in_port = msg.match["in_port"] pkt = packet.Packet(msg.data) + eth = pkt.get_protocol(ethernet.ethernet) + ip = pkt.get_protocol(ipv4.ipv4) + icmp = pkt.get_protocol(icmp.icmp) + arp = pkt.get_protocol(arp.arp) + tcp = pkt.get_protocol(tcp.tcp) + udp = pkt.get_protocol(udp.udp) + + if udp or tcp: + # do udp/tcp stuff + # no connection between ser and ext, otherwise ok + pass + + if icmp: + # do icmp stuff + # internal all allowed (concrete Ip-adresses) + # gateway pings only to own (subnet) gateway (wenn subnetzte unterschiedlich, dann droppen, sonst icmp reply nach source) + # none to external + # none from external + pass + + if arp: + # do arp stuff + # answer to in-port with MAC of in-port-gateway (arp reply) + pass + + if ip: + # do ip stuff + # prefix matching, next hop (Ethernet-Header Rewriting: MAC-adresse der Source muss MAC adresse des input-ports sein (siehe actions)) + pass + + # do ethernet stuff? + + logger.info(f"Packet comes from router and was received on port {in_port}! Protocols:") for p in pkt.protocols: logger.info(f"\t- {p}") From 2701217043f370135a424bb0780cd4d6c2f587af Mon Sep 17 00:00:00 2001 From: Rafael Date: Wed, 13 May 2026 12:55:42 +0200 Subject: [PATCH 14/42] import protocols --- lab1/ans_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lab1/ans_controller.py b/lab1/ans_controller.py index 513c64b..d57ac65 100644 --- a/lab1/ans_controller.py +++ b/lab1/ans_controller.py @@ -24,7 +24,7 @@ from ryu.controller import ofp_event from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER from ryu.controller.handler import set_ev_cls -from ryu.lib.packet import packet, ethernet, ether_types, ipv4 +from ryu.lib.packet import packet, ethernet, ether_types, ipv4, icmp, arp, tcp, udp from ryu.ofproto import ofproto_v1_3, ofproto_v1_3_parser from pprint import pprint From d4a84163a858afd23beff8ddeeb0c4901b6df97a Mon Sep 17 00:00:00 2001 From: Rafael Date: Wed, 13 May 2026 12:57:19 +0200 Subject: [PATCH 15/42] function replay packet to in_port --- lab1/ans_controller.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lab1/ans_controller.py b/lab1/ans_controller.py index d57ac65..d7e0b1a 100644 --- a/lab1/ans_controller.py +++ b/lab1/ans_controller.py @@ -94,6 +94,9 @@ def packet_out_to_port(msg, datapath, parser, in_port, port): actions=[parser.OFPActionOutput(port)], data=msg.data) + def reply_packet_to_in_port(self, *, in_port, **kwargs): + return self.packet_out_to_port(port=in_port, **kwargs) + def flood_packet_out(self, *, ofproto, **kwargs): return self.packet_out_to_port(port=ofproto.OFPP_FLOOD, **kwargs) From db86b4dc4f83e4f60bf37cb1f4fd4c72cf4eaae9 Mon Sep 17 00:00:00 2001 From: Rafael Date: Wed, 13 May 2026 12:58:34 +0200 Subject: [PATCH 16/42] prepare packet processing --- lab1/ans_controller.py | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/lab1/ans_controller.py b/lab1/ans_controller.py index d7e0b1a..9df7754 100644 --- a/lab1/ans_controller.py +++ b/lab1/ans_controller.py @@ -149,22 +149,29 @@ def handle_router_request(self, ev): msg = ev.msg datapath = msg.datapath + ofproto = datapath.ofproto + parser = datapath.ofproto_parser + in_port = msg.match["in_port"] pkt = packet.Packet(msg.data) - eth = pkt.get_protocol(ethernet.ethernet) - ip = pkt.get_protocol(ipv4.ipv4) - icmp = pkt.get_protocol(icmp.icmp) - arp = pkt.get_protocol(arp.arp) - tcp = pkt.get_protocol(tcp.tcp) - udp = pkt.get_protocol(udp.udp) + logger.info(f"Packet comes from router and was received on port {in_port}! Protocols:") + for p in pkt.protocols: + logger.info(f"\t- {p}") + + eth_packet = pkt.get_protocol(ethernet.ethernet) + ipv4_packet = pkt.get_protocol(ipv4.ipv4) + icmp_packet = pkt.get_protocol(icmp.icmp) + arp_packet = pkt.get_protocol(arp.arp) + tcp_packet = pkt.get_protocol(tcp.tcp) + udp_packet = pkt.get_protocol(udp.udp) - if udp or tcp: + if udp_packet or tcp_packet: # do udp/tcp stuff # no connection between ser and ext, otherwise ok pass - if icmp: + if icmp_packet: # do icmp stuff # internal all allowed (concrete Ip-adresses) # gateway pings only to own (subnet) gateway (wenn subnetzte unterschiedlich, dann droppen, sonst icmp reply nach source) @@ -172,7 +179,7 @@ def handle_router_request(self, ev): # none from external pass - if arp: + if arp_packet: # do arp stuff # answer to in-port with MAC of in-port-gateway (arp reply) pass @@ -181,13 +188,9 @@ def handle_router_request(self, ev): # do ip stuff # prefix matching, next hop (Ethernet-Header Rewriting: MAC-adresse der Source muss MAC adresse des input-ports sein (siehe actions)) pass - - # do ethernet stuff? - - logger.info(f"Packet comes from router and was received on port {in_port}! Protocols:") - for p in pkt.protocols: - logger.info(f"\t- {p}") + datapath.send_msg(out) + # do ethernet stuff? # ping packets have ipv6 and icmpv6 -> Ping uses icmp # iperf generates arp packages From fbd7f38e6f150d0576286def983958c0a6922e2b Mon Sep 17 00:00:00 2001 From: Rafael Date: Wed, 13 May 2026 12:58:51 +0200 Subject: [PATCH 17/42] process arp packets --- lab1/ans_controller.py | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/lab1/ans_controller.py b/lab1/ans_controller.py index 9df7754..fee23c1 100644 --- a/lab1/ans_controller.py +++ b/lab1/ans_controller.py @@ -182,9 +182,28 @@ def handle_router_request(self, ev): if arp_packet: # do arp stuff # answer to in-port with MAC of in-port-gateway (arp reply) - pass - - if ip: + # rule: send arp reply with gateway mac + match = parser.OFPMatch(in_port=in_port, arp_sha=arp_packet.src_mac, arp_spa=arp_packet.src_ip, arp_op=2) + actions = [parser.OFPActionSetfield(opcode=2, + src_mac=self.port_to_own_mac[in_port], dst_mac=arp_packet.src_mac, + src_ip=self.port_to_own_ip[in_port], dst_ip=arp_packet.src_ip), + parser.OFPActionOutput(port=in_port)] + self.add_flow(datapath=datapath, priority=2, match=match, actions=actions) + logger.info(f"Added rule: match(in_port={in_port}, arp_sha={arp_packet.src_mac}, arp_spa={arp_packet.src_ip}, arp_op=2)," + f"action(port={in_port}) on router;") + + # send arp reply manually the first time + eth_packet.src, eth_packet.dst = self.port_to_own_mac[in_port], eth_packet.src + arp_packet.src_mac, arp_packet.dst_mac = self.port_to_own_mac[in_port], arp_packet.src_mac + arp_packet.src_ip, arp_packet.dst_ip = self.port_to_own_ip[in_port], arp_packet.src_ip + pkt = packet.Packet() + pkt.add_protocol(eth_packet) + pkt.add_protocol(arp_packet) + pkt.serialize() + out = self.reply_packet_to_in_port(data=msg.data, datapath=datapath, parser=parser, in_port=in_port) + logger.info(f"Instruction to router: send arp reply") + + if ipv4_packet: # do ip stuff # prefix matching, next hop (Ethernet-Header Rewriting: MAC-adresse der Source muss MAC adresse des input-ports sein (siehe actions)) pass From 4f90ff1e52e46cb9d88b9bc7fb455e68697ede15 Mon Sep 17 00:00:00 2001 From: Rafael Date: Wed, 13 May 2026 15:41:59 +0200 Subject: [PATCH 18/42] changed param of function packet_out_to_port() --- lab1/ans_controller.py | 75 ++++++++++++++++++++++++++---------------- 1 file changed, 46 insertions(+), 29 deletions(-) diff --git a/lab1/ans_controller.py b/lab1/ans_controller.py index fee23c1..31c962a 100644 --- a/lab1/ans_controller.py +++ b/lab1/ans_controller.py @@ -87,18 +87,18 @@ def add_flow(self, datapath, priority, match, actions): datapath.send_msg(mod) @staticmethod - def packet_out_to_port(msg, datapath, parser, in_port, port): + def packet_out_to_port(data, datapath, parser, in_port, port, ofproto): return parser.OFPPacketOut(datapath=datapath, - buffer_id=msg.buffer_id, in_port=in_port, + buffer_id=ofproto.OFP_NO_BUFFER, actions=[parser.OFPActionOutput(port)], - data=msg.data) + data=data) - def reply_packet_to_in_port(self, *, in_port, **kwargs): - return self.packet_out_to_port(port=in_port, **kwargs) + def reply_packet_to_in_port(self, *, in_port, ofproto, **kwargs): + return self.packet_out_to_port(port=in_port, in_port=ofproto.OFPP_CONTROLLER, ofproto=ofproto, **kwargs) def flood_packet_out(self, *, ofproto, **kwargs): - return self.packet_out_to_port(port=ofproto.OFPP_FLOOD, **kwargs) + return self.packet_out_to_port(port=ofproto.OFPP_FLOOD, ofproto=ofproto, **kwargs) # Handle the packet_in event @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) @@ -131,14 +131,14 @@ def _packet_in_handler(self, ev): if self.mac_to_port.get(datapath.id, {}).get(eth.dst): logger.critical(f"Existing rule did not match: match(eth_dst={eth.src}), action(port={in_port}) on dpid={datapath.id};") - out = self.packet_out_to_port(msg, datapath, parser, in_port, port=self.mac_to_port[datapath.id][eth.dst]) + out = self.packet_out_to_port(msg.data, datapath, parser, in_port, port=self.mac_to_port[datapath.id][eth.dst], ofproto=ofproto) else: self.mac_to_port[datapath.id][eth.src] = in_port self.add_flow(datapath=datapath, priority=2, match=parser.OFPMatch(eth_dst=eth.src), actions=[parser.OFPActionOutput(port=in_port)]) logger.info(f"Added rule: match(eth_dst={eth.src}), action(port={in_port}) on dpid={datapath.id};") - out = self.flood_packet_out(msg=msg, datapath=datapath, parser=parser, in_port=in_port, ofproto=ofproto) + out = self.flood_packet_out(data=msg.data, datapath=datapath, parser=parser, in_port=in_port, ofproto=ofproto) datapath.send_msg(out) logger.info(f"Instruction to dpid={datapath.id}: broadcast") print(num_minus * "-" + "_packet_in_handler end" + num_minus * "-") @@ -166,6 +166,7 @@ def handle_router_request(self, ev): tcp_packet = pkt.get_protocol(tcp.tcp) udp_packet = pkt.get_protocol(udp.udp) + outs = [] if udp_packet or tcp_packet: # do udp/tcp stuff # no connection between ser and ext, otherwise ok @@ -182,33 +183,49 @@ def handle_router_request(self, ev): if arp_packet: # do arp stuff # answer to in-port with MAC of in-port-gateway (arp reply) - # rule: send arp reply with gateway mac - match = parser.OFPMatch(in_port=in_port, arp_sha=arp_packet.src_mac, arp_spa=arp_packet.src_ip, arp_op=2) - actions = [parser.OFPActionSetfield(opcode=2, - src_mac=self.port_to_own_mac[in_port], dst_mac=arp_packet.src_mac, - src_ip=self.port_to_own_ip[in_port], dst_ip=arp_packet.src_ip), - parser.OFPActionOutput(port=in_port)] - self.add_flow(datapath=datapath, priority=2, match=match, actions=actions) - logger.info(f"Added rule: match(in_port={in_port}, arp_sha={arp_packet.src_mac}, arp_spa={arp_packet.src_ip}, arp_op=2)," - f"action(port={in_port}) on router;") - - # send arp reply manually the first time - eth_packet.src, eth_packet.dst = self.port_to_own_mac[in_port], eth_packet.src - arp_packet.src_mac, arp_packet.dst_mac = self.port_to_own_mac[in_port], arp_packet.src_mac - arp_packet.src_ip, arp_packet.dst_ip = self.port_to_own_ip[in_port], arp_packet.src_ip - pkt = packet.Packet() - pkt.add_protocol(eth_packet) - pkt.add_protocol(arp_packet) - pkt.serialize() - out = self.reply_packet_to_in_port(data=msg.data, datapath=datapath, parser=parser, in_port=in_port) - logger.info(f"Instruction to router: send arp reply") + if arp_packet.opcode == arp.ARP_REPLY: # process arp reply + self.ip_to_mac[arp_packet.src_ip] = arp_packet.src_mac + if self.buffered_ipv4_packets[arp_packet.src_ip]: + buffered_packet = self.buffered_ipv4_packets[arp_packet.src_ip].pop(0) + outs.append(self.forward_ipv4_packet(ipv4_packet=buffered_packet, + eth_dst=arp_packet.src_mac,# + eth_packet=eth_packet, + datapath=datapath, + parser=parser, + in_port=ofproto.OFPP_CONTROLLER, + ofproto=ofproto)) + else: # reply to arp request + match = parser.OFPMatch(in_port=in_port, arp_op=arp.ARP_REQUEST, eth_type=ether_types.ETH_TYPE_ARP) + actions = [parser.OFPActionSetField(arp_op=arp.ARP_REPLY), + parser.OFPActionSetField(eth_src=self.port_to_own_mac[in_port]), + parser.OFPActionSetField(eth_dst=arp_packet.src_mac), + parser.OFPActionSetField(arp_sha=self.port_to_own_mac[in_port]), + parser.OFPActionSetField(arp_tha=arp_packet.src_mac), + parser.OFPActionSetField(arp_spa=self.port_to_own_ip[in_port]), + parser.OFPActionSetField(arp_tpa=arp_packet.src_ip), + parser.OFPActionOutput(port=in_port)] + # rule: send arp reply with gateway mac + self.add_flow(datapath=datapath, priority=2, match=match, actions=actions) + logger.info(f"Added rule: match={match}, action={actions} on router;") + + # send arp reply manually the first time + eth_packet.src, eth_packet.dst = self.port_to_own_mac[in_port], eth_packet.src + arp_packet.src_mac, arp_packet.dst_mac = self.port_to_own_mac[in_port], arp_packet.src_mac + arp_packet.src_ip, arp_packet.dst_ip = self.port_to_own_ip[in_port], arp_packet.src_ip + arp_packet.opcode = 2 + pkt = packet.Packet() + pkt.add_protocol(eth_packet) + pkt.add_protocol(arp_packet) + pkt.serialize() + outs.append(self.reply_packet_to_in_port(data=pkt.data, datapath=datapath, parser=parser, in_port=in_port, ofproto=ofproto)) + logger.info(f"Instruction to router: send arp reply") if ipv4_packet: # do ip stuff # prefix matching, next hop (Ethernet-Header Rewriting: MAC-adresse der Source muss MAC adresse des input-ports sein (siehe actions)) pass - datapath.send_msg(out) + [datapath.send_msg(out) for out in outs] # do ethernet stuff? # ping packets have ipv6 and icmpv6 -> Ping uses icmp From ed3db420780b85c4b9a181f0245024d4cd922d58 Mon Sep 17 00:00:00 2001 From: Rafael Date: Wed, 13 May 2026 19:44:56 +0200 Subject: [PATCH 19/42] handle ipv4 --- lab1/ans_controller.py | 53 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/lab1/ans_controller.py b/lab1/ans_controller.py index 31c962a..c0d294d 100644 --- a/lab1/ans_controller.py +++ b/lab1/ans_controller.py @@ -19,6 +19,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ from collections import defaultdict +from ipaddress import ip_network from logging import getLogger from ryu.base import app_manager from ryu.controller import ofp_event @@ -54,8 +55,11 @@ def __init__(self, *args, **kwargs): 2: "10.0.2.1", 3: "192.168.1.1" } - + self.netmask = 24 + self.network_to_port = {ip_network((ip, self.netmask), strict=False).network_address: port for port, ip in self.port_to_own_ip.items()} self.mac_to_port = defaultdict(dict) + self.ip_to_mac = {} + self.buffered_ipv4_packets = defaultdict(list) @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER) def switch_features_handler(self, ev): @@ -143,6 +147,36 @@ def _packet_in_handler(self, ev): logger.info(f"Instruction to dpid={datapath.id}: broadcast") print(num_minus * "-" + "_packet_in_handler end" + num_minus * "-") + def forward_ipv4_packet(self, ipv4_packet, eth_dst, eth_packet, datapath, parser, in_port, ofproto): + match = parser.OFPMatch(eth_type=ether_types.ETH_TYPE_IP, ipv4_dst=(ipv4_packet.dst, 24)) + dst_network = ip_network((ipv4_packet.dst, self.netmask), strict=False) + out_port = self.network_to_port[dst_network.network_address] + actions = [parser.OFPActionSetField(eth_src=self.port_to_own_mac[out_port]), + parser.OFPActionSetField(eth_dst=eth_dst), + parser.OFPActionOutput(out_port)] + self.add_flow(datapath=datapath, priority=2, match=match, actions=actions) + logger.info(f"Added rule: match={match}, action={actions} on router;") + + eth_packet.src = self.network_to_port[dst_network.network_address] + eth_packet.dst = eth_dst + pkt = packet.Packet() + pkt.add_protocol(eth_packet) + pkt.add_protocol(ipv4_packet) + pkt.serialize() + + logger.info(f"Instruction to router: forward to ip={ipv4_packet.dst}, mac={eth_dst}") + return self.packet_out_to_port(data=pkt.data, datapath=datapath, parser=parser, in_port=in_port, port=out_port, ofproto=ofproto) + + def construct_arp_request(self, port, dst_ip, datapath, parser, ofproto): + eth_packet = ethernet.ethernet(src=self.port_to_own_mac[port], ethertype=ether_types.ETH_TYPE_ARP) + arp_packet = arp.arp(opcode=arp.ARP_REQUEST, src_mac=self.port_to_own_mac[port], src_ip=self.port_to_own_ip[port], dst_ip=dst_ip) + pkt = packet.Packet() + pkt.add_protocol(eth_packet) + pkt.add_protocol(arp_packet) + pkt.serialize() + logger.info(f"Instruction to router: arp request for {dst_ip}") + return self.packet_out_to_port(data=pkt.data, datapath=datapath, parser=parser, in_port=ofproto.OFPP_CONTROLLER, port=port, ofproto=ofproto) + def handle_router_request(self, ev): num_minus = 10 print(num_minus * "-" + "handle_router_request start" + num_minus * "-") @@ -223,7 +257,22 @@ def handle_router_request(self, ev): if ipv4_packet: # do ip stuff # prefix matching, next hop (Ethernet-Header Rewriting: MAC-adresse der Source muss MAC adresse des input-ports sein (siehe actions)) - pass + if self.ip_to_mac.get(ipv4_packet.dst): + logger.info(f"For IP-Address={ipv4_packet.dst}, found dst_mac={self.ip_to_mac.get(ipv4_packet.dst)}") + outs.append(self.forward_ipv4_packet(ipv4_packet=ipv4_packet, + eth_dst=self.ip_to_mac[ipv4_packet.dst], + eth_packet=eth_packet, + datapath=datapath, + parser=parser, + in_port=in_port, + ofproto=ofproto)) + else: + self.buffered_ipv4_packets[ipv4_packet.dst].append(ipv4_packet) + outs.append(self.construct_arp_request(port=self.network_to_port[ip_network((ipv4_packet.dst, self.netmask), strict=False).network_address], + dst_ip=ipv4_packet.dst, + datapath=datapath, + parser=parser, + ofproto=ofproto)) [datapath.send_msg(out) for out in outs] # do ethernet stuff? From 32656f388e41df0f6e7f073de0aa97dd56c805b4 Mon Sep 17 00:00:00 2001 From: Arya-55 Date: Thu, 14 May 2026 15:23:29 +0200 Subject: [PATCH 20/42] Added firewall --- lab1/ans_controller.py | 234 +++++++++++++++++++++++++++-------------- lab1/run_network.py | 4 +- 2 files changed, 157 insertions(+), 81 deletions(-) diff --git a/lab1/ans_controller.py b/lab1/ans_controller.py index c0d294d..7a3bfb3 100644 --- a/lab1/ans_controller.py +++ b/lab1/ans_controller.py @@ -19,19 +19,21 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ from collections import defaultdict -from ipaddress import ip_network +from ipaddress import ip_address, ip_network, IPv4Network from logging import getLogger from ryu.base import app_manager from ryu.controller import ofp_event from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER from ryu.controller.handler import set_ev_cls -from ryu.lib.packet import packet, ethernet, ether_types, ipv4, icmp, arp, tcp, udp +from ryu.lib.packet import packet, ethernet, ether_types, ipv4, in_proto, icmp, arp #, tcp, udp from ryu.ofproto import ofproto_v1_3, ofproto_v1_3_parser from pprint import pprint logger = getLogger(__name__) - +PRIO_FIREWALL = 5 +PRIO_STANDARD = 2 +PRIO_CATCHALL = 0 class LearningSwitch(app_manager.RyuApp): OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION] @@ -51,16 +53,56 @@ def __init__(self, *args, **kwargs): # Router port (gateways) IP addresses assumed by the controller self.port_to_own_ip = { - 1: "10.0.1.1", - 2: "10.0.2.1", - 3: "192.168.1.1" + 1: "10.0.1.1", # internal host gateway (s1 subnet) + 2: "10.0.2.1", # internal server gateway (ser) + 3: "192.168.1.1" # external server gateway (ext) } + self.netmask = 24 self.network_to_port = {ip_network((ip, self.netmask), strict=False).network_address: port for port, ip in self.port_to_own_ip.items()} self.mac_to_port = defaultdict(dict) self.ip_to_mac = {} self.buffered_ipv4_packets = defaultdict(list) + self.firewall = [ + { # no ICMP from ext to any intern (only own gateway) + "proto": in_proto.IPPROTO_ICMP, + "src": ip_network("192.168.1.0/24"), + "dst": ip_network("10.0.0.0/16") + }, + { + # no ICMP from intern to extern + "proto": in_proto.IPPROTO_ICMP, + "src": ip_network("10.0.0.0/16"), + "dst": ip_network("192.168.1.0/24") + }, + { + # no TCP from ser to ext + "proto": in_proto.IPPROTO_TCP, + "src": ip_network("10.0.2.0/24"), + "dst": ip_network("192.168.1.0/24") + }, + { + # no TCP from ext to ser + "proto": in_proto.IPPROTO_TCP, + "src": ip_network("192.168.1.0/24"), + "dst": ip_network("10.0.2.0/24") + }, + { + # no UDP from ser to ext + "proto": in_proto.IPPROTO_UDP, + "src": ip_network("10.0.2.0/24"), + "dst": ip_network("192.168.1.0/24") + }, + { + # no UDP from ext to ser + "proto": in_proto.IPPROTO_UDP, + "src": ip_network("192.168.1.0/24"), + "dst": ip_network("10.0.2.0/24") + } + ] + + @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER) def switch_features_handler(self, ev): @@ -72,12 +114,12 @@ def switch_features_handler(self, ev): match = parser.OFPMatch() actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER, ofproto.OFPCML_NO_BUFFER)] - self.add_flow(datapath, 0, match, actions) + self.add_flow(datapath, PRIO_CATCHALL, match, actions) # drop IPv6 for now match = parser.OFPMatch(eth_type=ether_types.ETH_TYPE_IPV6) actions = [] - self.add_flow(datapath, 3, match, actions) + self.add_flow(datapath, PRIO_FIREWALL, match, actions) # Add a flow entry to the flow-table def add_flow(self, datapath, priority, match, actions): @@ -90,25 +132,25 @@ def add_flow(self, datapath, priority, match, actions): match=match, instructions=inst) datapath.send_msg(mod) - @staticmethod - def packet_out_to_port(data, datapath, parser, in_port, port, ofproto): - return parser.OFPPacketOut(datapath=datapath, - in_port=in_port, - buffer_id=ofproto.OFP_NO_BUFFER, - actions=[parser.OFPActionOutput(port)], - data=data) + # @staticmethod + # def packet_out_to_port(data, datapath, parser, in_port, port, ofproto): + # return parser.OFPPacketOut(datapath=datapath, + # in_port=in_port, + # buffer_id=ofproto.OFP_NO_BUFFER, + # actions=[parser.OFPActionOutput(port)], + # data=data) - def reply_packet_to_in_port(self, *, in_port, ofproto, **kwargs): - return self.packet_out_to_port(port=in_port, in_port=ofproto.OFPP_CONTROLLER, ofproto=ofproto, **kwargs) + # def reply_packet_to_in_port(self, *, in_port, ofproto, **kwargs): + # return self.packet_out_to_port(port=in_port, in_port=ofproto.OFPP_CONTROLLER, ofproto=ofproto, **kwargs) - def flood_packet_out(self, *, ofproto, **kwargs): - return self.packet_out_to_port(port=ofproto.OFPP_FLOOD, ofproto=ofproto, **kwargs) + # def flood_packet_out(self, *, ofproto, **kwargs): + # return self.packet_out_to_port(port=ofproto.OFPP_FLOOD, ofproto=ofproto, **kwargs) # Handle the packet_in event @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) def _packet_in_handler(self, ev): - num_minus = 10 - print(num_minus * "-" + "_packet_in_handler start" + num_minus * "-") + #num_minus = 10 + #print(num_minus * "-" + "_packet_in_handler start" + num_minus * "-") # print(f"self.mac_to_port={self.mac_to_port}") self.packet_counter += 1 @@ -122,13 +164,15 @@ def _packet_in_handler(self, ev): self.handle_router_request(ev) else: + num_minus = 10 + print("\n" + num_minus * "-" + "Switch Request start (_packet_in_handler)" + num_minus * "-") # handle switch requests in_port = msg.match["in_port"] pkt = packet.Packet(msg.data) - logger.info("Switch Packets:") - for p in pkt.protocols: - logger.info(f"\t- {p}") + #logger.info("Switch Packets:") + #for p in pkt.protocols: + # logger.info(f"\t- {p}") eth = pkt.get_protocol(ethernet.ethernet) logger.info(f"seq={self.packet_counter}: dpid={datapath.id}: in_port={in_port}, eth_src={eth.src}, eth_dst={eth.dst};") @@ -138,14 +182,32 @@ def _packet_in_handler(self, ev): out = self.packet_out_to_port(msg.data, datapath, parser, in_port, port=self.mac_to_port[datapath.id][eth.dst], ofproto=ofproto) else: self.mac_to_port[datapath.id][eth.src] = in_port - self.add_flow(datapath=datapath, priority=2, + self.add_flow(datapath=datapath, priority=PRIO_STANDARD, match=parser.OFPMatch(eth_dst=eth.src), actions=[parser.OFPActionOutput(port=in_port)]) logger.info(f"Added rule: match(eth_dst={eth.src}), action(port={in_port}) on dpid={datapath.id};") out = self.flood_packet_out(data=msg.data, datapath=datapath, parser=parser, in_port=in_port, ofproto=ofproto) datapath.send_msg(out) logger.info(f"Instruction to dpid={datapath.id}: broadcast") - print(num_minus * "-" + "_packet_in_handler end" + num_minus * "-") + #print(num_minus * "-" + "Switch Request end (_packet_in_handler)" + num_minus * "-") + + + @staticmethod + def packet_out_to_port(data, datapath, parser, in_port, port, ofproto): + return parser.OFPPacketOut(datapath=datapath, + in_port=in_port, + buffer_id=ofproto.OFP_NO_BUFFER, + actions=[parser.OFPActionOutput(port)], + data=data) + + + def reply_packet_to_in_port(self, *, in_port, ofproto, **kwargs): + return self.packet_out_to_port(port=in_port, in_port=ofproto.OFPP_CONTROLLER, ofproto=ofproto, **kwargs) + + + def flood_packet_out(self, *, ofproto, **kwargs): + return self.packet_out_to_port(port=ofproto.OFPP_FLOOD, ofproto=ofproto, **kwargs) + def forward_ipv4_packet(self, ipv4_packet, eth_dst, eth_packet, datapath, parser, in_port, ofproto): match = parser.OFPMatch(eth_type=ether_types.ETH_TYPE_IP, ipv4_dst=(ipv4_packet.dst, 24)) @@ -154,7 +216,7 @@ def forward_ipv4_packet(self, ipv4_packet, eth_dst, eth_packet, datapath, parser actions = [parser.OFPActionSetField(eth_src=self.port_to_own_mac[out_port]), parser.OFPActionSetField(eth_dst=eth_dst), parser.OFPActionOutput(out_port)] - self.add_flow(datapath=datapath, priority=2, match=match, actions=actions) + self.add_flow(datapath=datapath, priority=PRIO_STANDARD, match=match, actions=actions) logger.info(f"Added rule: match={match}, action={actions} on router;") eth_packet.src = self.network_to_port[dst_network.network_address] @@ -167,6 +229,7 @@ def forward_ipv4_packet(self, ipv4_packet, eth_dst, eth_packet, datapath, parser logger.info(f"Instruction to router: forward to ip={ipv4_packet.dst}, mac={eth_dst}") return self.packet_out_to_port(data=pkt.data, datapath=datapath, parser=parser, in_port=in_port, port=out_port, ofproto=ofproto) + def construct_arp_request(self, port, dst_ip, datapath, parser, ofproto): eth_packet = ethernet.ethernet(src=self.port_to_own_mac[port], ethertype=ether_types.ETH_TYPE_ARP) arp_packet = arp.arp(opcode=arp.ARP_REQUEST, src_mac=self.port_to_own_mac[port], src_ip=self.port_to_own_ip[port], dst_ip=dst_ip) @@ -177,9 +240,21 @@ def construct_arp_request(self, port, dst_ip, datapath, parser, ofproto): logger.info(f"Instruction to router: arp request for {dst_ip}") return self.packet_out_to_port(data=pkt.data, datapath=datapath, parser=parser, in_port=ofproto.OFPP_CONTROLLER, port=port, ofproto=ofproto) + + def check_for_firewall_entry(self, ipv4_packet): + logger.info(f"Checking IPv4 packet against firewall:\n{ipv4_packet}") + for entry in self.firewall: + if all(getattr(ipv4_packet, key) == value if isinstance(value, int) else ip_address(getattr(ipv4_packet, key)) + in value for key, value in entry.items()): + logger.info(f"Found firewall entry: {entry}") + return {"ip_proto": entry["proto"], "ipv4_src": ipv4_packet.src, "ipv4_dst": ipv4_packet.dst} + logger.info(f"No matching firewall entry found") + return None + + def handle_router_request(self, ev): num_minus = 10 - print(num_minus * "-" + "handle_router_request start" + num_minus * "-") + print("\n" + num_minus * "-" + "Router Request start (handle_router_request)" + num_minus * "-") msg = ev.msg datapath = msg.datapath @@ -189,35 +264,37 @@ def handle_router_request(self, ev): in_port = msg.match["in_port"] pkt = packet.Packet(msg.data) - logger.info(f"Packet comes from router and was received on port {in_port}! Protocols:") - for p in pkt.protocols: - logger.info(f"\t- {p}") + #logger.info(f"Packet comes from router and was received on port {in_port}! Protocols:") + #for p in pkt.protocols: + # logger.info(f"\t- {p}") eth_packet = pkt.get_protocol(ethernet.ethernet) ipv4_packet = pkt.get_protocol(ipv4.ipv4) - icmp_packet = pkt.get_protocol(icmp.icmp) + # icmp_packet = pkt.get_protocol(icmp.icmp) arp_packet = pkt.get_protocol(arp.arp) - tcp_packet = pkt.get_protocol(tcp.tcp) - udp_packet = pkt.get_protocol(udp.udp) + # tcp_packet = pkt.get_protocol(tcp.tcp) + # udp_packet = pkt.get_protocol(udp.udp) outs = [] - if udp_packet or tcp_packet: - # do udp/tcp stuff - # no connection between ser and ext, otherwise ok - pass - - if icmp_packet: - # do icmp stuff - # internal all allowed (concrete Ip-adresses) - # gateway pings only to own (subnet) gateway (wenn subnetzte unterschiedlich, dann droppen, sonst icmp reply nach source) - # none to external - # none from external - pass + # if udp_packet or tcp_packet: + # # do udp/tcp stuff + # # no connection between ser and ext, otherwise ok + # pass + + # if icmp_packet: + # # do icmp stuff + # # internal all allowed (concrete Ip-adresses) + # # gateway pings only to own (subnet) gateway (wenn subnetzte unterschiedlich, dann droppen, sonst icmp reply nach source) + # # none to external + # # none from external + # pass if arp_packet: + logger.info("Got ARP packet") # do arp stuff # answer to in-port with MAC of in-port-gateway (arp reply) if arp_packet.opcode == arp.ARP_REPLY: # process arp reply + logger.info("Processing ARP REPLY message") self.ip_to_mac[arp_packet.src_ip] = arp_packet.src_mac if self.buffered_ipv4_packets[arp_packet.src_ip]: buffered_packet = self.buffered_ipv4_packets[arp_packet.src_ip].pop(0) @@ -229,6 +306,7 @@ def handle_router_request(self, ev): in_port=ofproto.OFPP_CONTROLLER, ofproto=ofproto)) else: # reply to arp request + logger.info("Processing ARP REQUEST message") match = parser.OFPMatch(in_port=in_port, arp_op=arp.ARP_REQUEST, eth_type=ether_types.ETH_TYPE_ARP) actions = [parser.OFPActionSetField(arp_op=arp.ARP_REPLY), parser.OFPActionSetField(eth_src=self.port_to_own_mac[in_port]), @@ -239,7 +317,7 @@ def handle_router_request(self, ev): parser.OFPActionSetField(arp_tpa=arp_packet.src_ip), parser.OFPActionOutput(port=in_port)] # rule: send arp reply with gateway mac - self.add_flow(datapath=datapath, priority=2, match=match, actions=actions) + self.add_flow(datapath=datapath, priority=PRIO_STANDARD, match=match, actions=actions) logger.info(f"Added rule: match={match}, action={actions} on router;") # send arp reply manually the first time @@ -255,40 +333,38 @@ def handle_router_request(self, ev): logger.info(f"Instruction to router: send arp reply") if ipv4_packet: + logger.info("Got IPv4 packet") # do ip stuff # prefix matching, next hop (Ethernet-Header Rewriting: MAC-adresse der Source muss MAC adresse des input-ports sein (siehe actions)) - if self.ip_to_mac.get(ipv4_packet.dst): - logger.info(f"For IP-Address={ipv4_packet.dst}, found dst_mac={self.ip_to_mac.get(ipv4_packet.dst)}") - outs.append(self.forward_ipv4_packet(ipv4_packet=ipv4_packet, - eth_dst=self.ip_to_mac[ipv4_packet.dst], - eth_packet=eth_packet, - datapath=datapath, - parser=parser, - in_port=in_port, - ofproto=ofproto)) + firewall_entry = self.check_for_firewall_entry(ipv4_packet) + + if firewall_entry: + # There is an entry in the firewall-table fitting this packet => add dropping rule + match = parser.OFPMatch(eth_type=0x0800, **firewall_entry) + self.add_flow(datapath=datapath, + priority=PRIO_FIREWALL, + match=match, + actions=[]) + logger.info(f"Added firewall rule on router: match={match}, action=[]") else: - self.buffered_ipv4_packets[ipv4_packet.dst].append(ipv4_packet) - outs.append(self.construct_arp_request(port=self.network_to_port[ip_network((ipv4_packet.dst, self.netmask), strict=False).network_address], - dst_ip=ipv4_packet.dst, - datapath=datapath, - parser=parser, - ofproto=ofproto)) + # IP forwarding + if self.ip_to_mac.get(ipv4_packet.dst): + logger.info(f"For IP-Address={ipv4_packet.dst}, found dst_mac={self.ip_to_mac.get(ipv4_packet.dst)}") + outs.append(self.forward_ipv4_packet(ipv4_packet=ipv4_packet, + eth_dst=self.ip_to_mac[ipv4_packet.dst], + eth_packet=eth_packet, + datapath=datapath, + parser=parser, + in_port=in_port, + ofproto=ofproto)) + else: + logger.info(f"For IP-Address={ipv4_packet.dst}, found no dst_mac. Buffering packet and sending ARP REQUEST") + self.buffered_ipv4_packets[ipv4_packet.dst].append(ipv4_packet) + outs.append(self.construct_arp_request(port=self.network_to_port[ip_network((ipv4_packet.dst, self.netmask), strict=False).network_address], + dst_ip=ipv4_packet.dst, + datapath=datapath, + parser=parser, + ofproto=ofproto)) [datapath.send_msg(out) for out in outs] - # do ethernet stuff? - - # ping packets have ipv6 and icmpv6 -> Ping uses icmp - # iperf generates arp packages - - # must answer ARP-Packets as hosts will ARP for IP Gateways - # "10.0.1.1" ? => "00:00:00:00:01:01" - # "10.0.2.1" ? => "00:00:00:00:01:02", - # "192.168.1.1" ? => "00:00:00:00:01:03" - - # router needs to rewrite ethernet headers when forwarding its packets (correkt?) - - # ext may not ping internal hosts => drop ICMP packets from ext - # what about from internal hosts to extern, according to the given result they should also not be able to ping ext - # no TCP/UDP allowed between ext and ser => drop TCP/UDP packets with respective src/dst-pairs - # hosts may only ping their own gateway => drop ICMP packages if src-ip != dst-ip - print(num_minus * "-" + "handle_router_request end" + num_minus * "-") \ No newline at end of file + #print(num_minus * "-" + "Router Request end (handle_router_request)" + num_minus * "-") \ No newline at end of file diff --git a/lab1/run_network.py b/lab1/run_network.py index fa026e0..f55b5a2 100644 --- a/lab1/run_network.py +++ b/lab1/run_network.py @@ -154,8 +154,8 @@ def run(): net.start() net.pingAll() - switch_flow_tester = SwitchFlowTester(net) - switch_flow_tester.test() + #switch_flow_tester = SwitchFlowTester(net) + #gitswitch_flow_tester.test() CLI(net) net.stop() From efc6f730e13d93c9c777e648dd3f1a6a097eff67 Mon Sep 17 00:00:00 2001 From: Arya-55 Date: Thu, 14 May 2026 16:32:22 +0200 Subject: [PATCH 21/42] Added RFC Info to lab1 readme --- lab1/Readme.md | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/lab1/Readme.md b/lab1/Readme.md index 9a22bcc..8e36746 100644 --- a/lab1/Readme.md +++ b/lab1/Readme.md @@ -15,3 +15,65 @@ Ping: `h1 ping h2 -c5` , `pingall` Check TCP/UDP connectivity: `iperf h1 h2 (-u)` , add -u for UDP See flow tables on node s1: `sudo ovs-ofctl dump-flows s1` (in separate window, not mininet) + +# RFC things to look at + +[RFC Link](https://datatracker.ietf.org/doc/html/rfc1812) + +### Page 39: Packet Drop Action +In the following, the action specified in certain cases is to +silently discard a received datagram. This means that the datagram +will be discarded without further processing **and that the router will +not send any ICMP error message** (see Section [4.3]) as a result. +However, for diagnosis of problems a router SHOULD provide the +capability of logging the error (see Section [1.3.3]), including the +contents of the silently discarded datagram, and SHOULD count +datagrams discarded. + +Das heißt: packet drop action = keine extra ICMP error message action + +### Page 46: TTL +Note in particular that a router MUST NOT check the TTL of a packet +except when forwarding it. + +A router MUST NOT originate or forward a datagram with a Time-to-Live +(TTL) value of zero. + +A router MUST NOT discard a datagram just because it was received +with TTL equal to zero or one; if it is to the router and otherwise +valid, the router MUST attempt to receive it. + +Auf Page 65 steht auch für IP Forwarding: The forwarder decrements (by at least one) + +Das heißt: Jedes packet was der router (controller in unserem fall) forwarded (d.h. nicht selber schreibt) muss ttl-1 haben, aber neue pakete (z.b. ICMP echo) nicht. +Ich glaube aber dass der router die packets schon selber dropped wenn ttl 0 ist, das müssen wir nicht extra als match action rule machen, aber kann man evtl. testen + +### Page 49: Router IP origin + broadcast +When a router originates any datagram, the IP source address MUST be +one of its own IP addresses (but not a broadcast or multicast +address). The only exception is during initialization. + +For most purposes, a datagram addressed to a broadcast or multicast +destination is processed as if it had been addressed to one of the +router's IP addresses; + +### Page 58: ICMP Echo +A router MUST implement an ICMP Echo server function that receives +Echo Requests sent to the router, and sends corresponding Echo +Replies. + +The IP source address in an ICMP Echo Reply MUST be the same as the +specific-destination address of the corresponding ICMP Echo Request +message. + +Data received in an ICMP Echo Request MUST be entirely included in +the resulting Echo Reply. + +### Page 71: Router to Router +Das müssen wir NICHT machen, weil nur 1 router: + +When a router is going to forward a packet, it must determine whether +it can send it directly to its destination, or whether it needs to +pass it through another router. If the latter, it needs to determine +which router to use. This section explains how these determinations +are made. From 98d3da15e58f17ef7e739b6a6e5f59d5c78a679f Mon Sep 17 00:00:00 2001 From: Rafael Date: Thu, 14 May 2026 18:49:47 +0200 Subject: [PATCH 22/42] bugfixes --- lab1/ans_controller.py | 116 ++++++++++++++++++++++++++++------------- lab1/run_network.py | 16 +++--- 2 files changed, 87 insertions(+), 45 deletions(-) diff --git a/lab1/ans_controller.py b/lab1/ans_controller.py index c0d294d..505289b 100644 --- a/lab1/ans_controller.py +++ b/lab1/ans_controller.py @@ -26,13 +26,20 @@ from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER from ryu.controller.handler import set_ev_cls from ryu.lib.packet import packet, ethernet, ether_types, ipv4, icmp, arp, tcp, udp +from ryu.lib.packet.in_proto import IPPROTO_ICMP from ryu.ofproto import ofproto_v1_3, ofproto_v1_3_parser from pprint import pprint +from ryu.ofproto.ofproto_v1_3_parser import OFPActionSetField logger = getLogger(__name__) +PRIO_FORWARD = 2 +PRIO_REPLY = 3 +PRIO_DROP = 4 + + class LearningSwitch(app_manager.RyuApp): OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION] @@ -55,11 +62,13 @@ def __init__(self, *args, **kwargs): 2: "10.0.2.1", 3: "192.168.1.1" } - self.netmask = 24 + self.netmask = "255.255.255.0" self.network_to_port = {ip_network((ip, self.netmask), strict=False).network_address: port for port, ip in self.port_to_own_ip.items()} self.mac_to_port = defaultdict(dict) self.ip_to_mac = {} self.buffered_ipv4_packets = defaultdict(list) + self.same_network_arp_drop_rules = defaultdict(list) + self.same_network_ip_drop_rules = defaultdict(list) @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER) def switch_features_handler(self, ev): @@ -77,7 +86,7 @@ def switch_features_handler(self, ev): # drop IPv6 for now match = parser.OFPMatch(eth_type=ether_types.ETH_TYPE_IPV6) actions = [] - self.add_flow(datapath, 3, match, actions) + self.add_flow(datapath, PRIO_DROP, match, actions) # Add a flow entry to the flow-table def add_flow(self, datapath, priority, match, actions): @@ -91,7 +100,7 @@ def add_flow(self, datapath, priority, match, actions): datapath.send_msg(mod) @staticmethod - def packet_out_to_port(data, datapath, parser, in_port, port, ofproto): + def packet_out_to_port(*, data, datapath, parser, in_port, port, ofproto): return parser.OFPPacketOut(datapath=datapath, in_port=in_port, buffer_id=ofproto.OFP_NO_BUFFER, @@ -107,10 +116,8 @@ def flood_packet_out(self, *, ofproto, **kwargs): # Handle the packet_in event @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) def _packet_in_handler(self, ev): - num_minus = 10 + num_minus = 20 print(num_minus * "-" + "_packet_in_handler start" + num_minus * "-") - # print(f"self.mac_to_port={self.mac_to_port}") - self.packet_counter += 1 msg = ev.msg datapath = msg.datapath @@ -120,7 +127,6 @@ def _packet_in_handler(self, ev): if datapath.id == 3: # handle router (s3) request self.handle_router_request(ev) - else: # handle switch requests in_port = msg.match["in_port"] @@ -133,32 +139,35 @@ def _packet_in_handler(self, ev): eth = pkt.get_protocol(ethernet.ethernet) logger.info(f"seq={self.packet_counter}: dpid={datapath.id}: in_port={in_port}, eth_src={eth.src}, eth_dst={eth.dst};") + if eth.src not in self.mac_to_port[datapath.id]: + self.mac_to_port[datapath.id][eth.src] = in_port + logger.info(f"New MAC for dpid={datapath.id}: {eth.src}") if self.mac_to_port.get(datapath.id, {}).get(eth.dst): - logger.critical(f"Existing rule did not match: match(eth_dst={eth.src}), action(port={in_port}) on dpid={datapath.id};") - out = self.packet_out_to_port(msg.data, datapath, parser, in_port, port=self.mac_to_port[datapath.id][eth.dst], ofproto=ofproto) + match = parser.OFPMatch(eth_dst=eth.dst, in_port=in_port) + actions = [parser.OFPActionOutput(port=self.mac_to_port[datapath.id][eth.dst])] + self.add_flow(datapath=datapath, priority=PRIO_FORWARD, match=match, actions=actions) + logger.info(f"Added rule: match={match}, actions={actions} on dpid={datapath.id};") + out = self.packet_out_to_port(data=msg.data, datapath=datapath, parser=parser, in_port=in_port, port=self.mac_to_port[datapath.id][eth.dst], ofproto=ofproto) else: - self.mac_to_port[datapath.id][eth.src] = in_port - self.add_flow(datapath=datapath, priority=2, - match=parser.OFPMatch(eth_dst=eth.src), - actions=[parser.OFPActionOutput(port=in_port)]) - logger.info(f"Added rule: match(eth_dst={eth.src}), action(port={in_port}) on dpid={datapath.id};") out = self.flood_packet_out(data=msg.data, datapath=datapath, parser=parser, in_port=in_port, ofproto=ofproto) datapath.send_msg(out) logger.info(f"Instruction to dpid={datapath.id}: broadcast") print(num_minus * "-" + "_packet_in_handler end" + num_minus * "-") def forward_ipv4_packet(self, ipv4_packet, eth_dst, eth_packet, datapath, parser, in_port, ofproto): - match = parser.OFPMatch(eth_type=ether_types.ETH_TYPE_IP, ipv4_dst=(ipv4_packet.dst, 24)) + match = parser.OFPMatch(eth_type=ether_types.ETH_TYPE_IP, ipv4_dst=(ipv4_packet.dst, self.netmask)) dst_network = ip_network((ipv4_packet.dst, self.netmask), strict=False) out_port = self.network_to_port[dst_network.network_address] actions = [parser.OFPActionSetField(eth_src=self.port_to_own_mac[out_port]), parser.OFPActionSetField(eth_dst=eth_dst), parser.OFPActionOutput(out_port)] - self.add_flow(datapath=datapath, priority=2, match=match, actions=actions) - logger.info(f"Added rule: match={match}, action={actions} on router;") + self.add_flow(datapath=datapath, priority=PRIO_FORWARD, match=match, actions=actions) + logger.info(f"Added rule: match={match}, actions={actions} on router;") - eth_packet.src = self.network_to_port[dst_network.network_address] + eth_packet.src = self.port_to_own_mac[out_port] eth_packet.dst = eth_dst + eth_packet.ethertype = ether_types.ETH_TYPE_IP + print(f"prepared fwd: {ipv4_packet}, eth_dst={eth_dst}, eth_packet.src={eth_packet.src}, eth_packet={eth_packet}, dpid={datapath.id}, in_port={in_port}") pkt = packet.Packet() pkt.add_protocol(eth_packet) pkt.add_protocol(ipv4_packet) @@ -178,7 +187,7 @@ def construct_arp_request(self, port, dst_ip, datapath, parser, ofproto): return self.packet_out_to_port(data=pkt.data, datapath=datapath, parser=parser, in_port=ofproto.OFPP_CONTROLLER, port=port, ofproto=ofproto) def handle_router_request(self, ev): - num_minus = 10 + num_minus = 20 print(num_minus * "-" + "handle_router_request start" + num_minus * "-") msg = ev.msg @@ -217,32 +226,31 @@ def handle_router_request(self, ev): if arp_packet: # do arp stuff # answer to in-port with MAC of in-port-gateway (arp reply) - if arp_packet.opcode == arp.ARP_REPLY: # process arp reply + if arp_packet.dst_ip != self.port_to_own_ip[in_port]: + if arp_packet.dst_ip not in self.same_network_arp_drop_rules[in_port]: + logger.info("Router: got foreign arp request. Dropping.") + match = parser.OFPMatch(in_port=in_port, arp_tpa=arp_packet.dst_ip, eth_type=ether_types.ETH_TYPE_ARP) + self.add_flow(datapath=datapath, priority=PRIO_DROP, match=match, actions=[]) + logger.info(f"Added rule: match={match}, actions={[]} on router;") + self.same_network_arp_drop_rules[in_port].append(arp_packet.dst_ip) + else: + logger.critical(f"Existing arp drop rule did not match: in_port={in_port} dst_ip={arp_packet.dst_ip}") + elif arp_packet.opcode == arp.ARP_REPLY: # process arp reply + logger.info("Router: got ARP Reply") self.ip_to_mac[arp_packet.src_ip] = arp_packet.src_mac if self.buffered_ipv4_packets[arp_packet.src_ip]: + logger.info("Router: found buffered IP packets, forwarding...") buffered_packet = self.buffered_ipv4_packets[arp_packet.src_ip].pop(0) outs.append(self.forward_ipv4_packet(ipv4_packet=buffered_packet, - eth_dst=arp_packet.src_mac,# + eth_dst=arp_packet.src_mac, eth_packet=eth_packet, datapath=datapath, parser=parser, in_port=ofproto.OFPP_CONTROLLER, ofproto=ofproto)) else: # reply to arp request - match = parser.OFPMatch(in_port=in_port, arp_op=arp.ARP_REQUEST, eth_type=ether_types.ETH_TYPE_ARP) - actions = [parser.OFPActionSetField(arp_op=arp.ARP_REPLY), - parser.OFPActionSetField(eth_src=self.port_to_own_mac[in_port]), - parser.OFPActionSetField(eth_dst=arp_packet.src_mac), - parser.OFPActionSetField(arp_sha=self.port_to_own_mac[in_port]), - parser.OFPActionSetField(arp_tha=arp_packet.src_mac), - parser.OFPActionSetField(arp_spa=self.port_to_own_ip[in_port]), - parser.OFPActionSetField(arp_tpa=arp_packet.src_ip), - parser.OFPActionOutput(port=in_port)] - # rule: send arp reply with gateway mac - self.add_flow(datapath=datapath, priority=2, match=match, actions=actions) - logger.info(f"Added rule: match={match}, action={actions} on router;") - - # send arp reply manually the first time + logger.info("Router: Found ARP Request") + # send arp reply manually eth_packet.src, eth_packet.dst = self.port_to_own_mac[in_port], eth_packet.src arp_packet.src_mac, arp_packet.dst_mac = self.port_to_own_mac[in_port], arp_packet.src_mac arp_packet.src_ip, arp_packet.dst_ip = self.port_to_own_ip[in_port], arp_packet.src_ip @@ -257,7 +265,40 @@ def handle_router_request(self, ev): if ipv4_packet: # do ip stuff # prefix matching, next hop (Ethernet-Header Rewriting: MAC-adresse der Source muss MAC adresse des input-ports sein (siehe actions)) - if self.ip_to_mac.get(ipv4_packet.dst): + if ipv4_packet.dst in self.port_to_own_ip.values(): + if ipv4_packet.proto == IPPROTO_ICMP: + if ipv4_packet.dst != self.port_to_own_ip[in_port]: + match = parser.OFPMatch(in_port=in_port, ipv4_dst=ipv4_packet.dst) + actions = [] + self.add_flow(datapath=datapath, priority=PRIO_DROP, match=match, actions=actions) + logger.info(f"Router: pinged wrong Gateway: src={ipv4_packet.src}; dst={ipv4_packet.dst};") + logger.info(f"Added drop rule: match={match}; actions={actions};") + else: + logger.info(f"Router: ping Gateway: src={ipv4_packet.src}; dst={ipv4_packet.dst};") + eth_packet.src, eth_packet.dst = self.port_to_own_mac[in_port], eth_packet.src + ipv4_packet.src, ipv4_packet.dst = self.port_to_own_ip[in_port], ipv4_packet.src + icmp_packet.type = icmp.ICMP_ECHO_REPLY + pkt = packet.Packet() + pkt.add_protocol(eth_packet) + pkt.add_protocol(ipv4_packet) + pkt.add_protocol(icmp_packet) + pkt.serialize() + outs.append(self.reply_packet_to_in_port(data=pkt.data, datapath=datapath, parser=parser, in_port=in_port, ofproto=ofproto)) + logger.info(f"Instruction to router: send icmp echo reply") + else: + logger.critical(f"Router: unknown IP-Protocol: {ipv4_packet.proto}") + + elif (ip_network((ipv4_packet.src, self.netmask), strict=False) == ip_network((ipv4_packet.dst, self.netmask), strict=False) and + ipv4_packet.dst != self.port_to_own_ip[in_port]): + if ipv4_packet.dst not in self.same_network_ip_drop_rules[in_port]: + logger.info("Router: got in network ip broadcast. Dropping.") + match = parser.OFPMatch(in_port=in_port, ipv4_dst=ipv4_packet.dst, eth_type=ether_types.ETH_TYPE_IP) + self.add_flow(datapath=datapath, priority=PRIO_DROP, match=match, actions=[]) + logger.info(f"Added rule: match={match}, actions={[]} on router;") + self.same_network_ip_drop_rules[in_port].append(ipv4_packet.dst) + else: + logger.critical(f"Existing IP drop rule did not match: in_port={in_port} dst_ip={ipv4_packet.dst}") + elif self.ip_to_mac.get(ipv4_packet.dst): logger.info(f"For IP-Address={ipv4_packet.dst}, found dst_mac={self.ip_to_mac.get(ipv4_packet.dst)}") outs.append(self.forward_ipv4_packet(ipv4_packet=ipv4_packet, eth_dst=self.ip_to_mac[ipv4_packet.dst], @@ -274,7 +315,8 @@ def handle_router_request(self, ev): parser=parser, ofproto=ofproto)) - [datapath.send_msg(out) for out in outs] + for out in outs: + logger.info(f"result={datapath.send_msg(out)}: {out}") # do ethernet stuff? # ping packets have ipv6 and icmpv6 -> Ping uses icmp diff --git a/lab1/run_network.py b/lab1/run_network.py index fa026e0..2e6c50a 100644 --- a/lab1/run_network.py +++ b/lab1/run_network.py @@ -37,10 +37,10 @@ class NetworkTopo(Topo): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.addHost("ext", ip="192.168.1.123/24") - self.addHost("h1" , ip="10.0.1.2/24") - self.addHost("h2" , ip="10.0.1.3/24") - self.addHost("ser", ip="10.0.2.2/24") + self.addHost("ext", ip="192.168.1.123/24", defaultRoute="via 192.168.1.1") + self.addHost("h1" , ip="10.0.1.2/24", defaultRoute="via 10.0.1.1") + self.addHost("h2" , ip="10.0.1.3/24", defaultRoute="via 10.0.1.1") + self.addHost("ser", ip="10.0.2.2/24", defaultRoute="via 10.0.2.1") self.addSwitch("s1", dpid=f"{1:016d}") self.addSwitch("s2", dpid=f"{2:016d}") @@ -50,8 +50,8 @@ def __init__(self, *args, **kwargs): self.addLink("h1", "s1" , bw=15, delay="10ms") self.addLink("h2", "s1" , bw=15, delay="10ms") self.addLink("s1", "s3" , bw=15, delay="10ms", addr2='00:00:00:00:01:01') - self.addLink("s3", "ext", bw=15, delay="10ms", addr1='00:00:00:00:01:03') self.addLink("s3", "s2" , bw=15, delay="10ms", addr1='00:00:00:00:01:02') + self.addLink("s3", "ext", bw=15, delay="10ms", addr1='00:00:00:00:01:03') self.addLink("s2", "ser", bw=15, delay="10ms") @@ -152,10 +152,10 @@ def run(): host.cmd("sysctl -w net.ipv6.conf.lo.disable_ipv6=1") net.start() - net.pingAll() + #net.pingAll() - switch_flow_tester = SwitchFlowTester(net) - switch_flow_tester.test() + # switch_flow_tester = SwitchFlowTester(net) + # switch_flow_tester.test() CLI(net) net.stop() From 082dcb0e3b5254ba1f7c226e5501e6cfa43c184b Mon Sep 17 00:00:00 2001 From: Rafael Date: Thu, 14 May 2026 19:55:12 +0200 Subject: [PATCH 23/42] pingall is working mostly --- lab1/ans_controller.py | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/lab1/ans_controller.py b/lab1/ans_controller.py index 505289b..f82d59f 100644 --- a/lab1/ans_controller.py +++ b/lab1/ans_controller.py @@ -66,7 +66,7 @@ def __init__(self, *args, **kwargs): self.network_to_port = {ip_network((ip, self.netmask), strict=False).network_address: port for port, ip in self.port_to_own_ip.items()} self.mac_to_port = defaultdict(dict) self.ip_to_mac = {} - self.buffered_ipv4_packets = defaultdict(list) + self.buffered_msgs = defaultdict(list) self.same_network_arp_drop_rules = defaultdict(list) self.same_network_ip_drop_rules = defaultdict(list) @@ -154,8 +154,18 @@ def _packet_in_handler(self, ev): logger.info(f"Instruction to dpid={datapath.id}: broadcast") print(num_minus * "-" + "_packet_in_handler end" + num_minus * "-") - def forward_ipv4_packet(self, ipv4_packet, eth_dst, eth_packet, datapath, parser, in_port, ofproto): - match = parser.OFPMatch(eth_type=ether_types.ETH_TYPE_IP, ipv4_dst=(ipv4_packet.dst, self.netmask)) + def forward_ipv4_packet(self, msg, eth_dst): + datapath = msg.datapath + ofproto = datapath.ofproto + parser = datapath.ofproto_parser + + in_port = msg.match["in_port"] + pkt = packet.Packet(msg.data) + + eth_packet = pkt.get_protocol(ethernet.ethernet) + ipv4_packet = pkt.get_protocol(ipv4.ipv4) + + match = parser.OFPMatch(eth_type=ether_types.ETH_TYPE_IP, ipv4_dst=ipv4_packet.dst) dst_network = ip_network((ipv4_packet.dst, self.netmask), strict=False) out_port = self.network_to_port[dst_network.network_address] actions = [parser.OFPActionSetField(eth_src=self.port_to_own_mac[out_port]), @@ -238,16 +248,10 @@ def handle_router_request(self, ev): elif arp_packet.opcode == arp.ARP_REPLY: # process arp reply logger.info("Router: got ARP Reply") self.ip_to_mac[arp_packet.src_ip] = arp_packet.src_mac - if self.buffered_ipv4_packets[arp_packet.src_ip]: + if self.buffered_msgs[arp_packet.src_ip]: logger.info("Router: found buffered IP packets, forwarding...") - buffered_packet = self.buffered_ipv4_packets[arp_packet.src_ip].pop(0) - outs.append(self.forward_ipv4_packet(ipv4_packet=buffered_packet, - eth_dst=arp_packet.src_mac, - eth_packet=eth_packet, - datapath=datapath, - parser=parser, - in_port=ofproto.OFPP_CONTROLLER, - ofproto=ofproto)) + buffered_msg = self.buffered_msgs[arp_packet.src_ip].pop(0) + outs.append(self.forward_ipv4_packet(buffered_msg, arp_packet.src_mac)) else: # reply to arp request logger.info("Router: Found ARP Request") # send arp reply manually @@ -300,15 +304,9 @@ def handle_router_request(self, ev): logger.critical(f"Existing IP drop rule did not match: in_port={in_port} dst_ip={ipv4_packet.dst}") elif self.ip_to_mac.get(ipv4_packet.dst): logger.info(f"For IP-Address={ipv4_packet.dst}, found dst_mac={self.ip_to_mac.get(ipv4_packet.dst)}") - outs.append(self.forward_ipv4_packet(ipv4_packet=ipv4_packet, - eth_dst=self.ip_to_mac[ipv4_packet.dst], - eth_packet=eth_packet, - datapath=datapath, - parser=parser, - in_port=in_port, - ofproto=ofproto)) + outs.append(self.forward_ipv4_packet(msg, self.ip_to_mac[ipv4_packet.dst])) else: - self.buffered_ipv4_packets[ipv4_packet.dst].append(ipv4_packet) + self.buffered_msgs[ipv4_packet.dst].append(msg) outs.append(self.construct_arp_request(port=self.network_to_port[ip_network((ipv4_packet.dst, self.netmask), strict=False).network_address], dst_ip=ipv4_packet.dst, datapath=datapath, From ac210f6e10143bd09dfb2a8666bd073fec12bfca Mon Sep 17 00:00:00 2001 From: Arya-55 Date: Thu, 14 May 2026 20:05:26 +0200 Subject: [PATCH 24/42] Fixed firewall and ports, reworked switch logic --- lab1/ans_controller.py | 56 ++++++++++++++++++++++++++++-------------- lab1/run_network.py | 12 ++++----- 2 files changed, 43 insertions(+), 25 deletions(-) diff --git a/lab1/ans_controller.py b/lab1/ans_controller.py index 7a3bfb3..4555e37 100644 --- a/lab1/ans_controller.py +++ b/lab1/ans_controller.py @@ -159,13 +159,17 @@ def _packet_in_handler(self, ev): ofproto = datapath.ofproto parser = datapath.ofproto_parser + logger.info(f"\n###### NEW PACKET ######") + pkt = packet.Packet(msg.data) + for p in pkt.protocols: + logger.info(f"{type(p)}") + if datapath.id == 3: # handle router (s3) request self.handle_router_request(ev) - else: num_minus = 10 - print("\n" + num_minus * "-" + "Switch Request start (_packet_in_handler)" + num_minus * "-") + print(num_minus * "-" + "Switch Request start (_packet_in_handler)" + num_minus * "-") # handle switch requests in_port = msg.match["in_port"] pkt = packet.Packet(msg.data) @@ -177,18 +181,30 @@ def _packet_in_handler(self, ev): eth = pkt.get_protocol(ethernet.ethernet) logger.info(f"seq={self.packet_counter}: dpid={datapath.id}: in_port={in_port}, eth_src={eth.src}, eth_dst={eth.dst};") - if self.mac_to_port.get(datapath.id, {}).get(eth.dst): - logger.critical(f"Existing rule did not match: match(eth_dst={eth.src}), action(port={in_port}) on dpid={datapath.id};") - out = self.packet_out_to_port(msg.data, datapath, parser, in_port, port=self.mac_to_port[datapath.id][eth.dst], ofproto=ofproto) - else: + if not self.mac_to_port.get(datapath.id, {}).get(eth.src): + # learn mapping between input-port and its MAC address (eth.src) self.mac_to_port[datapath.id][eth.src] = in_port - self.add_flow(datapath=datapath, priority=PRIO_STANDARD, - match=parser.OFPMatch(eth_dst=eth.src), - actions=[parser.OFPActionOutput(port=in_port)]) - logger.info(f"Added rule: match(eth_dst={eth.src}), action(port={in_port}) on dpid={datapath.id};") + logger.info(f"Updated mac_to_port for s{datapath.id}, {self.mac_to_port[datapath.id]}") + else: + logger.info("Already know in_port <-> MAC-address mapping") + + out_port = self.mac_to_port.get(datapath.id, {}).get(eth.dst) + if out_port: + # controller knows port of non-broadcast destination MAC -> add flow rule matching on in_port and dst-MAC + match = parser.OFPMatch(eth_dst = eth.dst, in_port = in_port) + actions = [parser.OFPActionOutput(port=out_port)] + self.add_flow(datapath=datapath, priority=PRIO_STANDARD, match=match, actions=actions) + logger.info(f"Added rule on s{datapath.id}: match={match}, action={actions}") + + # send packet out to output port + out = self.packet_out_to_port(data=msg.data, datapath=datapath, parser=parser, in_port=in_port, port=out_port, ofproto=ofproto) + logger.info(f"Instruction to dpid={datapath.id}: Send out to port {out_port}") + else: + # Flood packet out out = self.flood_packet_out(data=msg.data, datapath=datapath, parser=parser, in_port=in_port, ofproto=ofproto) + logger.info(f"Instruction to dpid={datapath.id}: broadcast") + datapath.send_msg(out) - logger.info(f"Instruction to dpid={datapath.id}: broadcast") #print(num_minus * "-" + "Switch Request end (_packet_in_handler)" + num_minus * "-") @@ -210,7 +226,7 @@ def flood_packet_out(self, *, ofproto, **kwargs): def forward_ipv4_packet(self, ipv4_packet, eth_dst, eth_packet, datapath, parser, in_port, ofproto): - match = parser.OFPMatch(eth_type=ether_types.ETH_TYPE_IP, ipv4_dst=(ipv4_packet.dst, 24)) + match = parser.OFPMatch(eth_type=ether_types.ETH_TYPE_IP, ipv4_dst = ipv4_packet.dst + "/24") dst_network = ip_network((ipv4_packet.dst, self.netmask), strict=False) out_port = self.network_to_port[dst_network.network_address] actions = [parser.OFPActionSetField(eth_src=self.port_to_own_mac[out_port]), @@ -219,7 +235,7 @@ def forward_ipv4_packet(self, ipv4_packet, eth_dst, eth_packet, datapath, parser self.add_flow(datapath=datapath, priority=PRIO_STANDARD, match=match, actions=actions) logger.info(f"Added rule: match={match}, action={actions} on router;") - eth_packet.src = self.network_to_port[dst_network.network_address] + eth_packet.src = self.port_to_own_mac[out_port] eth_packet.dst = eth_dst pkt = packet.Packet() pkt.add_protocol(eth_packet) @@ -243,18 +259,20 @@ def construct_arp_request(self, port, dst_ip, datapath, parser, ofproto): def check_for_firewall_entry(self, ipv4_packet): logger.info(f"Checking IPv4 packet against firewall:\n{ipv4_packet}") + for entry in self.firewall: if all(getattr(ipv4_packet, key) == value if isinstance(value, int) else ip_address(getattr(ipv4_packet, key)) in value for key, value in entry.items()): logger.info(f"Found firewall entry: {entry}") - return {"ip_proto": entry["proto"], "ipv4_src": ipv4_packet.src, "ipv4_dst": ipv4_packet.dst} - logger.info(f"No matching firewall entry found") - return None + return {"ip_proto": entry["proto"], "ipv4_src": ipv4_packet.src + "/24", "ipv4_dst": ipv4_packet.dst + "/24"} + + logger.info(f"No matching firewall entry found") + return None def handle_router_request(self, ev): num_minus = 10 - print("\n" + num_minus * "-" + "Router Request start (handle_router_request)" + num_minus * "-") + print(num_minus * "-" + "Router Request start (handle_router_request)" + num_minus * "-") msg = ev.msg datapath = msg.datapath @@ -290,7 +308,7 @@ def handle_router_request(self, ev): # pass if arp_packet: - logger.info("Got ARP packet") + logger.info(f"seq={self.packet_counter}: Got ARP packet:\n{arp_packet}") # do arp stuff # answer to in-port with MAC of in-port-gateway (arp reply) if arp_packet.opcode == arp.ARP_REPLY: # process arp reply @@ -333,7 +351,7 @@ def handle_router_request(self, ev): logger.info(f"Instruction to router: send arp reply") if ipv4_packet: - logger.info("Got IPv4 packet") + logger.info(f"seq={self.packet_counter}: Got IPv4 packet") # do ip stuff # prefix matching, next hop (Ethernet-Header Rewriting: MAC-adresse der Source muss MAC adresse des input-ports sein (siehe actions)) firewall_entry = self.check_for_firewall_entry(ipv4_packet) diff --git a/lab1/run_network.py b/lab1/run_network.py index f55b5a2..463af58 100644 --- a/lab1/run_network.py +++ b/lab1/run_network.py @@ -37,10 +37,10 @@ class NetworkTopo(Topo): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.addHost("ext", ip="192.168.1.123/24") - self.addHost("h1" , ip="10.0.1.2/24") - self.addHost("h2" , ip="10.0.1.3/24") - self.addHost("ser", ip="10.0.2.2/24") + self.addHost("ext", ip="192.168.1.123/24", defaultRoute="via 192.168.1.1") + self.addHost("h1" , ip="10.0.1.2/24", defaultRoute="via 10.0.1.1") + self.addHost("h2" , ip="10.0.1.3/24", defaultRoute="via 10.0.1.1") + self.addHost("ser", ip="10.0.2.2/24", defaultRoute="via 10.0.2.1") self.addSwitch("s1", dpid=f"{1:016d}") self.addSwitch("s2", dpid=f"{2:016d}") @@ -49,9 +49,9 @@ def __init__(self, *args, **kwargs): # Assign mac address on links for s3 as per given topology self.addLink("h1", "s1" , bw=15, delay="10ms") self.addLink("h2", "s1" , bw=15, delay="10ms") - self.addLink("s1", "s3" , bw=15, delay="10ms", addr2='00:00:00:00:01:01') - self.addLink("s3", "ext", bw=15, delay="10ms", addr1='00:00:00:00:01:03') + self.addLink("s3", "s1" , bw=15, delay="10ms", addr1='00:00:00:00:01:01') self.addLink("s3", "s2" , bw=15, delay="10ms", addr1='00:00:00:00:01:02') + self.addLink("s3", "ext", bw=15, delay="10ms", addr1='00:00:00:00:01:03') self.addLink("s2", "ser", bw=15, delay="10ms") From 06edc8244a0d557ff17b05555132918d8c64ff27 Mon Sep 17 00:00:00 2001 From: Arya-55 Date: Thu, 14 May 2026 22:25:04 +0200 Subject: [PATCH 25/42] TODO: Rework Firewall --- lab1/ans_controller.py | 56 ++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 32 deletions(-) diff --git a/lab1/ans_controller.py b/lab1/ans_controller.py index aa0894d..56cdfce 100644 --- a/lab1/ans_controller.py +++ b/lab1/ans_controller.py @@ -19,26 +19,23 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ from collections import defaultdict -from ipaddress import ip_address, ip_network, IPv4Network +from ipaddress import ip_address, ip_network from logging import getLogger from ryu.base import app_manager from ryu.controller import ofp_event from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER from ryu.controller.handler import set_ev_cls from ryu.lib.packet import packet, ethernet, ether_types, ipv4, in_proto, icmp, arp -from ryu.ofproto import ofproto_v1_3, ofproto_v1_3_parser +from ryu.ofproto import ofproto_v1_3 from pprint import pprint -from ryu.ofproto.ofproto_v1_3_parser import OFPActionSetField logger = getLogger(__name__) PRIO_FIREWALL = 5 -PRIO_STANDARD = 2 -PRIO_CATCHALL = 0 - -PRIO_FORWARD = 2 -PRIO_REPLY = 3 PRIO_DROP = 4 +PRIO_REPLY = 3 +PRIO_FORWARD = 2 +PRIO_CATCHALL = 0 class LearningSwitch(app_manager.RyuApp): @@ -49,6 +46,11 @@ def __init__(self, *args, **kwargs): # Here you can initialize the data structures you want to keep at the controller self.packet_counter = 0 + self.mac_to_port = defaultdict(dict) + self.ip_to_mac = {} + self.buffered_msgs = defaultdict(list) + self.same_network_arp_drop_rules = defaultdict(list) + self.same_network_ip_drop_rules = defaultdict(list) # Router port MACs assumed by the controller self.port_to_own_mac = { @@ -63,13 +65,9 @@ def __init__(self, *args, **kwargs): 2: "10.0.2.1", # internal server gateway (ser) 3: "192.168.1.1" # external server gateway (ext) } + self.netmask = "255.255.255.0" self.network_to_port = {ip_network((ip, self.netmask), strict=False).network_address: port for port, ip in self.port_to_own_ip.items()} - self.mac_to_port = defaultdict(dict) - self.ip_to_mac = {} - self.buffered_msgs = defaultdict(list) - self.same_network_arp_drop_rules = defaultdict(list) - self.same_network_ip_drop_rules = defaultdict(list) self.firewall = [ { # no ICMP from ext to any intern (only own gateway) @@ -112,7 +110,6 @@ def __init__(self, *args, **kwargs): @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER) def switch_features_handler(self, ev): - datapath = ev.msg.datapath ofproto = datapath.ofproto parser = datapath.ofproto_parser @@ -128,6 +125,7 @@ def switch_features_handler(self, ev): actions = [] self.add_flow(datapath, PRIO_DROP, match, actions) + # Add a flow entry to the flow-table def add_flow(self, datapath, priority, match, actions): ofproto = datapath.ofproto @@ -143,10 +141,6 @@ def add_flow(self, datapath, priority, match, actions): # Handle the packet_in event @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) def _packet_in_handler(self, ev): - #num_minus = 10 - #print(num_minus * "-" + "_packet_in_handler start" + num_minus * "-") - # print(f"self.mac_to_port={self.mac_to_port}") - self.packet_counter += 1 msg = ev.msg datapath = msg.datapath @@ -156,22 +150,18 @@ def _packet_in_handler(self, ev): logger.info(f"\n###### NEW PACKET ######") pkt = packet.Packet(msg.data) for p in pkt.protocols: - logger.info(f"{type(p)}") + logger.info(f" - {p}") if datapath.id == 3: # handle router (s3) request self.handle_router_request(ev) else: + # handle switch requests num_minus = 10 print(num_minus * "-" + "Switch Request start (_packet_in_handler)" + num_minus * "-") - # handle switch requests + in_port = msg.match["in_port"] pkt = packet.Packet(msg.data) - - #logger.info("Switch Packets:") - #for p in pkt.protocols: - # logger.info(f"\t- {p}") - eth = pkt.get_protocol(ethernet.ethernet) logger.info(f"seq={self.packet_counter}: dpid={datapath.id}: in_port={in_port}, eth_src={eth.src}, eth_dst={eth.dst};") @@ -187,7 +177,7 @@ def _packet_in_handler(self, ev): # controller knows port of non-broadcast destination MAC -> add flow rule matching on in_port and dst-MAC match = parser.OFPMatch(eth_dst = eth.dst, in_port = in_port) actions = [parser.OFPActionOutput(port=out_port)] - self.add_flow(datapath=datapath, priority=PRIO_STANDARD, match=match, actions=actions) + self.add_flow(datapath=datapath, priority=PRIO_FORWARD, match=match, actions=actions) logger.info(f"Added rule on s{datapath.id}: match={match}, action={actions}") # send packet out to output port @@ -199,7 +189,6 @@ def _packet_in_handler(self, ev): logger.info(f"Instruction to dpid={datapath.id}: broadcast") datapath.send_msg(out) - #print(num_minus * "-" + "Switch Request end (_packet_in_handler)" + num_minus * "-") @staticmethod @@ -296,8 +285,7 @@ def handle_router_request(self, ev): if arp_packet: logger.info(f"seq={self.packet_counter}: Got ARP packet:\n{arp_packet}") - # do arp stuff - # answer to in-port with MAC of in-port-gateway (arp reply) + if arp_packet.dst_ip != self.port_to_own_ip[in_port]: if arp_packet.dst_ip not in self.same_network_arp_drop_rules[in_port]: logger.info("Router: got foreign arp request. Dropping.") @@ -325,18 +313,22 @@ def handle_router_request(self, ev): pkt.add_protocol(eth_packet) pkt.add_protocol(arp_packet) pkt.serialize() + + for p in pkt.protocols: + logger.info(f"> {p}") + outs.append(self.reply_packet_to_in_port(data=pkt.data, datapath=datapath, parser=parser, in_port=in_port, ofproto=ofproto)) logger.info(f"Instruction to router: send arp reply") if ipv4_packet: logger.info(f"seq={self.packet_counter}: Got IPv4 packet") - # do ip stuff - # prefix matching, next hop (Ethernet-Header Rewriting: MAC-adresse der Source muss MAC adresse des input-ports sein (siehe actions)) firewall_entry = self.check_for_firewall_entry(ipv4_packet) if firewall_entry: # There is an entry in the firewall-table fitting this packet => add dropping rule match = parser.OFPMatch(eth_type=0x0800, **firewall_entry) + # TODO: Instead of installing a dropping rule, the controller should probably send out an ICMP package with code 3 and type 13 + # to get "Destination Unreachable — Communication Administratively Prohibited" self.add_flow(datapath=datapath, priority=PRIO_FIREWALL, match=match, @@ -383,4 +375,4 @@ def handle_router_request(self, ev): ofproto=ofproto)) for out in outs: - logger.info(f"result={datapath.send_msg(out)}: {out}") \ No newline at end of file + logger.info(f"result={datapath.send_msg(out)}")#: {out}") \ No newline at end of file From e31fc201ce567d783fcd32491856663bd012f8b9 Mon Sep 17 00:00:00 2001 From: alexmupb Date: Fri, 15 May 2026 14:49:14 +0200 Subject: [PATCH 26/42] add timestamp to logging --- lab1/ans_controller.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lab1/ans_controller.py b/lab1/ans_controller.py index 56cdfce..8b10147 100644 --- a/lab1/ans_controller.py +++ b/lab1/ans_controller.py @@ -18,6 +18,7 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ +import datetime from collections import defaultdict from ipaddress import ip_address, ip_network from logging import getLogger @@ -147,7 +148,9 @@ def _packet_in_handler(self, ev): ofproto = datapath.ofproto parser = datapath.ofproto_parser - logger.info(f"\n###### NEW PACKET ######") + t = datetime.datetime.now() + timestamp = f"{t.minute}:{t.second}.{str(t.microsecond)[:3]}" + logger.info(f"\n{timestamp} ###### NEW PACKET ######") pkt = packet.Packet(msg.data) for p in pkt.protocols: logger.info(f" - {p}") From bc82a38d0595845bb9365765253d25efea426f24 Mon Sep 17 00:00:00 2001 From: alexmupb Date: Fri, 15 May 2026 14:50:18 +0200 Subject: [PATCH 27/42] fix ping fail from missing icmp in fwd --- lab1/ans_controller.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lab1/ans_controller.py b/lab1/ans_controller.py index 8b10147..76a30f6 100644 --- a/lab1/ans_controller.py +++ b/lab1/ans_controller.py @@ -221,6 +221,9 @@ def forward_ipv4_packet(self, msg, eth_dst): eth_packet = pkt.get_protocol(ethernet.ethernet) ipv4_packet = pkt.get_protocol(ipv4.ipv4) + ipv4_packet.ttl = ipv4_packet.ttl - 1; + + icmp_packet = pkt.get_protocol(icmp.icmp) match = parser.OFPMatch(eth_type=ether_types.ETH_TYPE_IP, ipv4_dst=ipv4_packet.dst) dst_network = ip_network((ipv4_packet.dst, self.netmask), strict=False) @@ -238,6 +241,8 @@ def forward_ipv4_packet(self, msg, eth_dst): pkt = packet.Packet() pkt.add_protocol(eth_packet) pkt.add_protocol(ipv4_packet) + if icmp_packet: + pkt.add_protocol(icmp_packet) pkt.serialize() logger.info(f"Instruction to router: forward to ip={ipv4_packet.dst}, mac={eth_dst}") From 1303e80ce8578073b999e8ca1ded014ed99e7ee2 Mon Sep 17 00:00:00 2001 From: alexmupb Date: Fri, 15 May 2026 14:51:13 +0200 Subject: [PATCH 28/42] explicit 0 MAC for arp request, extra logging, document error --- lab1/ans_controller.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lab1/ans_controller.py b/lab1/ans_controller.py index 76a30f6..bfaea83 100644 --- a/lab1/ans_controller.py +++ b/lab1/ans_controller.py @@ -251,11 +251,16 @@ def forward_ipv4_packet(self, msg, eth_dst): def construct_arp_request(self, port, dst_ip, datapath, parser, ofproto): eth_packet = ethernet.ethernet(src=self.port_to_own_mac[port], ethertype=ether_types.ETH_TYPE_ARP) - arp_packet = arp.arp(opcode=arp.ARP_REQUEST, src_mac=self.port_to_own_mac[port], src_ip=self.port_to_own_ip[port], dst_ip=dst_ip) + arp_packet = arp.arp(opcode=arp.ARP_REQUEST, src_mac=self.port_to_own_mac[port], src_ip=self.port_to_own_ip[port], dst_mac="00:00:00:00:00:00", dst_ip=dst_ip) pkt = packet.Packet() pkt.add_protocol(eth_packet) pkt.add_protocol(arp_packet) pkt.serialize() + + logger.info(f"construct arp request, contents:") + for p in pkt.protocols: + logger.info(f"> {p}") + logger.info(f"Instruction to router: arp request for {dst_ip}") return self.packet_out_to_port(data=pkt.data, datapath=datapath, parser=parser, in_port=ofproto.OFPP_CONTROLLER, port=port, ofproto=ofproto) @@ -294,7 +299,7 @@ def handle_router_request(self, ev): if arp_packet: logger.info(f"seq={self.packet_counter}: Got ARP packet:\n{arp_packet}") - if arp_packet.dst_ip != self.port_to_own_ip[in_port]: + if arp_packet.dst_ip != self.port_to_own_ip[in_port]: # not directed to gateway: internal ARP, drop if arp_packet.dst_ip not in self.same_network_arp_drop_rules[in_port]: logger.info("Router: got foreign arp request. Dropping.") match = parser.OFPMatch(in_port=in_port, arp_tpa=arp_packet.dst_ip, eth_type=ether_types.ETH_TYPE_ARP) @@ -322,6 +327,7 @@ def handle_router_request(self, ev): pkt.add_protocol(arp_packet) pkt.serialize() + logger.info(f"construct arp reply, contents:") for p in pkt.protocols: logger.info(f"> {p}") @@ -376,6 +382,7 @@ def handle_router_request(self, ev): outs.append(self.forward_ipv4_packet(msg, self.ip_to_mac[ipv4_packet.dst])) else: self.buffered_msgs[ipv4_packet.dst].append(msg) + # TODO if pinging+ARPing an IP that is unknown (i.e. 10.1.2.3), the dict lookup errors outs.append(self.construct_arp_request(port=self.network_to_port[ip_network((ipv4_packet.dst, self.netmask), strict=False).network_address], dst_ip=ipv4_packet.dst, datapath=datapath, From 3fc0834c96c385aaac4dadd17daf1c0e2311342f Mon Sep 17 00:00:00 2001 From: alexmupb Date: Fri, 15 May 2026 14:51:29 +0200 Subject: [PATCH 29/42] add ping procedure steps --- lab1/ping.md | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 lab1/ping.md diff --git a/lab1/ping.md b/lab1/ping.md new file mode 100644 index 0000000..daf4b69 --- /dev/null +++ b/lab1/ping.md @@ -0,0 +1,68 @@ +# How ping works across subnets + +Based on this helpful guide of ARP: https://learningnetwork.cisco.com/s/article/fundamentals-of-arp-address-resolution-protocol Starting from Situation 4 + +Example: `h1 ping ser` + +s1, s2 = switch + +s3 = router + +Assumption: Host knows its GW (gateway) so that router knows it is being addressed + +Errors in the current implementation will be marked later in _italic_. + +## Step 1 (ARP h1 -> s3) +h1 sends ARP request to the port towards s1 with ETH dst "FF" (broadcast) as it needs to know destination mac of GW. IP dst is the GW + +## Step 2 +controller receives packet at s1, stores mac to port mapping, forwards it to port at s3 + +## Step 3 +controller receives packet at s3, detects it's ARP and that the IP dst matches the GW, so it needs to respond with ARP reply (opcode 2): +1. set ETH src as own MAC, and ETH dst as the h1 MAC (gotten from the incoming header) +2. set ARP IP/MAC dst to the incoming packet's data (h1), and ARP IP/MAC to own MAC/IP +3. Send back to port it came from (to s1) + +## Step 4 +controller receives packet at s1, stores mac to port mapping, detects it already has the mac from h1 (the ETH dst MAC) from Step 2, and creates a flow rule telling it to output to the port towards h1 if matching that MAC, forwards it to port at h1 + +Now h1 knows the MAC of the router, and it can send the ICMP echo + +## Step 5 (Create Echo) +h1 sends ICMP echo to the port towards s1 with ETH dst of the s3 and IP dst of ser + +## Step 6 +controller receives packet at s1, detects it already has the mac from s3 (the ETH dst MAC) from Step 4, and creates a flow rule telling it to output to the port towards h1 if matching that MAC, forwards it to port at h1. + +## Step 7 +controller receives packet at s3, as ETH dst MAC matches router's port MAC, it should process it. router sees the IP dst matches a gateway on one of its ports, meaning the target is reachable in that subnet: Need to create a new packet with the IP src of h1 and IP dst of ser, ETC src of outgoing port, but the ETH dst is unknown. + +**Issue**: _In this step it also needs to store the incoming ip + mac combo and add it to the router's flow rule, otherwise it will do an ARP again when the echo reply travels back to h1_. It does so in Step 11 for the other side but not here + +So for that, it postpones the packet creation and first does an ARP: + +## Step 8 (ARP s3 -> ser) +s3 sends ARP request to the port towards ser with ETH dst FF (broadcast) as it needs to know destination mac of ser. IP dst is ser + +## Step 9 +controller receives packet at s2, stores mac to port mapping, forwards it to port at ser. ser sends an ARP reply. + +## Step 10 +controller receives packet at s2, stores mac to port mapping, detects it already has the mac from ser (the ETH dst MAC) from Step 9, and creates a flow rule telling it to output to the port towards s3 if matching that MAC, forwards it to port at s3 + +## Step 11 (Forward Echo) +controller receives packet at s3, sees it is an ARP reply that matches the same IP it used the intended echo packet for in Step 7, and then takes that postponed packet and adds the ETH dst MAC to it, and the controller forwards it to port towards ser. + +In addition, as it forwards it creates a flow rule telling it to output on that port if the same IP dst is used. + +## Step 12 +controller receives packet at s2, stores mac to port mapping, detects it already has the mac from s3 (the ETH dst MAC) from Step 9, and creates a flow rule telling it to output to the port towards ser if matching that MAC, forwards it to port at ser + +ser now received the ICMP echo from h1 + +## Similar steps now in the other direction, except no ARPing, so not listed here + +That the route was already traversed once in both directions during ARP, so most packets will not enter the controller anymore. If they do, it's an issue that needs to be looked at. + +**Issue:** _when the ser -> h1 echo reaches s3, it still does another ARP which it shouldn't, see Step 7_ \ No newline at end of file From 5900acf33fb549d492005fccb323431ee47ab463 Mon Sep 17 00:00:00 2001 From: Rafael Date: Fri, 15 May 2026 15:02:23 +0200 Subject: [PATCH 30/42] fixed first packet loss on ping --- lab1/ans_controller.py | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/lab1/ans_controller.py b/lab1/ans_controller.py index f82d59f..b15f151 100644 --- a/lab1/ans_controller.py +++ b/lab1/ans_controller.py @@ -19,6 +19,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ from collections import defaultdict +from copy import deepcopy from ipaddress import ip_network from logging import getLogger from ryu.base import app_manager @@ -154,14 +155,14 @@ def _packet_in_handler(self, ev): logger.info(f"Instruction to dpid={datapath.id}: broadcast") print(num_minus * "-" + "_packet_in_handler end" + num_minus * "-") - def forward_ipv4_packet(self, msg, eth_dst): - datapath = msg.datapath + def forward_ipv4_packet(self, datapath, data, in_port, eth_dst): ofproto = datapath.ofproto parser = datapath.ofproto_parser - in_port = msg.match["in_port"] - pkt = packet.Packet(msg.data) - + pkt = packet.Packet(data) + logger.debug(f"Function forward_ipv4_packet(): Protocols:") + for p in pkt.protocols: + logger.debug(f"\t- {p}") eth_packet = pkt.get_protocol(ethernet.ethernet) ipv4_packet = pkt.get_protocol(ipv4.ipv4) @@ -177,10 +178,7 @@ def forward_ipv4_packet(self, msg, eth_dst): eth_packet.src = self.port_to_own_mac[out_port] eth_packet.dst = eth_dst eth_packet.ethertype = ether_types.ETH_TYPE_IP - print(f"prepared fwd: {ipv4_packet}, eth_dst={eth_dst}, eth_packet.src={eth_packet.src}, eth_packet={eth_packet}, dpid={datapath.id}, in_port={in_port}") - pkt = packet.Packet() - pkt.add_protocol(eth_packet) - pkt.add_protocol(ipv4_packet) + logger.debug(f"prepared fwd: {ipv4_packet}, eth_dst={eth_dst}, eth_packet.src={eth_packet.src}, eth_packet={eth_packet}, dpid={datapath.id}, in_port={in_port}") pkt.serialize() logger.info(f"Instruction to router: forward to ip={ipv4_packet.dst}, mac={eth_dst}") @@ -214,13 +212,13 @@ def handle_router_request(self, ev): eth_packet = pkt.get_protocol(ethernet.ethernet) ipv4_packet = pkt.get_protocol(ipv4.ipv4) - icmp_packet = pkt.get_protocol(icmp.icmp) + #icmp_packet = pkt.get_protocol(icmp.icmp) arp_packet = pkt.get_protocol(arp.arp) - tcp_packet = pkt.get_protocol(tcp.tcp) - udp_packet = pkt.get_protocol(udp.udp) + #tcp_packet = pkt.get_protocol(tcp.tcp) + #udp_packet = pkt.get_protocol(udp.udp) outs = [] - if udp_packet or tcp_packet: + """if udp_packet or tcp_packet: # do udp/tcp stuff # no connection between ser and ext, otherwise ok pass @@ -231,7 +229,7 @@ def handle_router_request(self, ev): # gateway pings only to own (subnet) gateway (wenn subnetzte unterschiedlich, dann droppen, sonst icmp reply nach source) # none to external # none from external - pass + pass""" if arp_packet: # do arp stuff @@ -278,7 +276,8 @@ def handle_router_request(self, ev): logger.info(f"Router: pinged wrong Gateway: src={ipv4_packet.src}; dst={ipv4_packet.dst};") logger.info(f"Added drop rule: match={match}; actions={actions};") else: - logger.info(f"Router: ping Gateway: src={ipv4_packet.src}; dst={ipv4_packet.dst};") + icmp_packet = pkt.get_protocol(icmp.icmp) + logger.info(f"ping Gateway: src={ipv4_packet.src}; dst={ipv4_packet.dst};") eth_packet.src, eth_packet.dst = self.port_to_own_mac[in_port], eth_packet.src ipv4_packet.src, ipv4_packet.dst = self.port_to_own_ip[in_port], ipv4_packet.src icmp_packet.type = icmp.ICMP_ECHO_REPLY @@ -304,9 +303,12 @@ def handle_router_request(self, ev): logger.critical(f"Existing IP drop rule did not match: in_port={in_port} dst_ip={ipv4_packet.dst}") elif self.ip_to_mac.get(ipv4_packet.dst): logger.info(f"For IP-Address={ipv4_packet.dst}, found dst_mac={self.ip_to_mac.get(ipv4_packet.dst)}") - outs.append(self.forward_ipv4_packet(msg, self.ip_to_mac[ipv4_packet.dst])) + outs.append(self.forward_ipv4_packet(datapath=datapath, data=msg.data, in_port=in_port, eth_dst=self.ip_to_mac[ipv4_packet.dst])) else: - self.buffered_msgs[ipv4_packet.dst].append(msg) + buffered = {"datapath": datapath, "data": deepcopy(msg.data), "in_port": in_port} + self.buffered_msgs[ipv4_packet.dst].append(buffered) + logger.info(f"MAC for IP={ipv4_packet.dst} not found. Buffer msg...") + logger.debug(f"put in buffer: {pformat(buffered)}") outs.append(self.construct_arp_request(port=self.network_to_port[ip_network((ipv4_packet.dst, self.netmask), strict=False).network_address], dst_ip=ipv4_packet.dst, datapath=datapath, From 518ab30913fdb744353436f82037783aed7ed0ba Mon Sep 17 00:00:00 2001 From: Rafael Date: Fri, 15 May 2026 15:05:18 +0200 Subject: [PATCH 31/42] more logging --- lab1/ans_controller.py | 80 ++++++++++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 31 deletions(-) diff --git a/lab1/ans_controller.py b/lab1/ans_controller.py index b15f151..d254523 100644 --- a/lab1/ans_controller.py +++ b/lab1/ans_controller.py @@ -21,7 +21,7 @@ from collections import defaultdict from copy import deepcopy from ipaddress import ip_network -from logging import getLogger +from logging import getLogger, StreamHandler, Formatter from ryu.base import app_manager from ryu.controller import ofp_event from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER @@ -29,11 +29,19 @@ from ryu.lib.packet import packet, ethernet, ether_types, ipv4, icmp, arp, tcp, udp from ryu.lib.packet.in_proto import IPPROTO_ICMP from ryu.ofproto import ofproto_v1_3, ofproto_v1_3_parser -from pprint import pprint +from pprint import pprint, pformat -from ryu.ofproto.ofproto_v1_3_parser import OFPActionSetField -logger = getLogger(__name__) +loglevel = "DEBUG" +logger, switch_logger = getLogger(f"{__name__}_Router"), getLogger(f"{__name__}_Switch") +logger.propagate, switch_logger.propagate = False, False +logger.setLevel(loglevel), switch_logger.setLevel("INFO") +logger.handlers.clear(), switch_logger.handlers.clear() +handler = StreamHandler() +switch_log_handler = StreamHandler() +handler.setFormatter(Formatter(fmt="%(asctime)s, %(name)s, %(levelname)s, %(lineno)d: %(message)s")) +switch_log_handler.setFormatter(Formatter(fmt="%(asctime)s, %(name)s, %(levelname)s, %(lineno)d: %(message)s")) +logger.addHandler(handler), switch_logger.addHandler(switch_log_handler) PRIO_FORWARD = 2 @@ -133,26 +141,26 @@ def _packet_in_handler(self, ev): in_port = msg.match["in_port"] pkt = packet.Packet(msg.data) - logger.info("Switch Packets:") + switch_logger.info("Switch Packets:") for p in pkt.protocols: - logger.info(f"\t- {p}") + switch_logger.info(f"\t- {p}") eth = pkt.get_protocol(ethernet.ethernet) - logger.info(f"seq={self.packet_counter}: dpid={datapath.id}: in_port={in_port}, eth_src={eth.src}, eth_dst={eth.dst};") + switch_logger.info(f"seq={self.packet_counter}: dpid={datapath.id}: in_port={in_port}, eth_src={eth.src}, eth_dst={eth.dst};") if eth.src not in self.mac_to_port[datapath.id]: self.mac_to_port[datapath.id][eth.src] = in_port - logger.info(f"New MAC for dpid={datapath.id}: {eth.src}") + switch_logger.info(f"New MAC for dpid={datapath.id}: {eth.src}") if self.mac_to_port.get(datapath.id, {}).get(eth.dst): match = parser.OFPMatch(eth_dst=eth.dst, in_port=in_port) actions = [parser.OFPActionOutput(port=self.mac_to_port[datapath.id][eth.dst])] self.add_flow(datapath=datapath, priority=PRIO_FORWARD, match=match, actions=actions) - logger.info(f"Added rule: match={match}, actions={actions} on dpid={datapath.id};") + switch_logger.info(f"Added rule: match={match}, actions={actions} on dpid={datapath.id};") out = self.packet_out_to_port(data=msg.data, datapath=datapath, parser=parser, in_port=in_port, port=self.mac_to_port[datapath.id][eth.dst], ofproto=ofproto) else: out = self.flood_packet_out(data=msg.data, datapath=datapath, parser=parser, in_port=in_port, ofproto=ofproto) datapath.send_msg(out) - logger.info(f"Instruction to dpid={datapath.id}: broadcast") + switch_logger.info(f"Instruction to dpid={datapath.id}: broadcast") print(num_minus * "-" + "_packet_in_handler end" + num_minus * "-") def forward_ipv4_packet(self, datapath, data, in_port, eth_dst): @@ -173,7 +181,7 @@ def forward_ipv4_packet(self, datapath, data, in_port, eth_dst): parser.OFPActionSetField(eth_dst=eth_dst), parser.OFPActionOutput(out_port)] self.add_flow(datapath=datapath, priority=PRIO_FORWARD, match=match, actions=actions) - logger.info(f"Added rule: match={match}, actions={actions} on router;") + logger.debug(f"Added rule: match={match}, actions={actions} on router;") eth_packet.src = self.port_to_own_mac[out_port] eth_packet.dst = eth_dst @@ -181,7 +189,7 @@ def forward_ipv4_packet(self, datapath, data, in_port, eth_dst): logger.debug(f"prepared fwd: {ipv4_packet}, eth_dst={eth_dst}, eth_packet.src={eth_packet.src}, eth_packet={eth_packet}, dpid={datapath.id}, in_port={in_port}") pkt.serialize() - logger.info(f"Instruction to router: forward to ip={ipv4_packet.dst}, mac={eth_dst}") + logger.info(f"forward to ip={ipv4_packet.dst}, mac={eth_dst}") return self.packet_out_to_port(data=pkt.data, datapath=datapath, parser=parser, in_port=in_port, port=out_port, ofproto=ofproto) def construct_arp_request(self, port, dst_ip, datapath, parser, ofproto): @@ -191,7 +199,7 @@ def construct_arp_request(self, port, dst_ip, datapath, parser, ofproto): pkt.add_protocol(eth_packet) pkt.add_protocol(arp_packet) pkt.serialize() - logger.info(f"Instruction to router: arp request for {dst_ip}") + logger.info(f"arp request for {dst_ip}") return self.packet_out_to_port(data=pkt.data, datapath=datapath, parser=parser, in_port=ofproto.OFPP_CONTROLLER, port=port, ofproto=ofproto) def handle_router_request(self, ev): @@ -206,9 +214,9 @@ def handle_router_request(self, ev): in_port = msg.match["in_port"] pkt = packet.Packet(msg.data) - logger.info(f"Packet comes from router and was received on port {in_port}! Protocols:") + logger.debug(f"Packet comes from router and was received on port {in_port}! Protocols:") for p in pkt.protocols: - logger.info(f"\t- {p}") + logger.debug(f"\t- {p}") eth_packet = pkt.get_protocol(ethernet.ethernet) ipv4_packet = pkt.get_protocol(ipv4.ipv4) @@ -236,22 +244,27 @@ def handle_router_request(self, ev): # answer to in-port with MAC of in-port-gateway (arp reply) if arp_packet.dst_ip != self.port_to_own_ip[in_port]: if arp_packet.dst_ip not in self.same_network_arp_drop_rules[in_port]: - logger.info("Router: got foreign arp request. Dropping.") + logger.info("got foreign arp request. Dropping.") match = parser.OFPMatch(in_port=in_port, arp_tpa=arp_packet.dst_ip, eth_type=ether_types.ETH_TYPE_ARP) self.add_flow(datapath=datapath, priority=PRIO_DROP, match=match, actions=[]) - logger.info(f"Added rule: match={match}, actions={[]} on router;") + logger.debug(f"Added rule: match={match}, actions={[]} on router;") self.same_network_arp_drop_rules[in_port].append(arp_packet.dst_ip) else: - logger.critical(f"Existing arp drop rule did not match: in_port={in_port} dst_ip={arp_packet.dst_ip}") + logger.error(f"Existing arp drop rule did not match: in_port={in_port} dst_ip={arp_packet.dst_ip}") elif arp_packet.opcode == arp.ARP_REPLY: # process arp reply - logger.info("Router: got ARP Reply") + logger.info("got ARP Reply") self.ip_to_mac[arp_packet.src_ip] = arp_packet.src_mac if self.buffered_msgs[arp_packet.src_ip]: - logger.info("Router: found buffered IP packets, forwarding...") - buffered_msg = self.buffered_msgs[arp_packet.src_ip].pop(0) - outs.append(self.forward_ipv4_packet(buffered_msg, arp_packet.src_mac)) + logger.info("found buffered IP packets, forwarding...") + buffered = self.buffered_msgs[arp_packet.src_ip].pop(0) + logger.debug(f"popped from buffer: {pformat(buffered)}") + outs.append(self.forward_ipv4_packet(eth_dst=arp_packet.src_mac, **buffered)) else: # reply to arp request - logger.info("Router: Found ARP Request") + logger.info("Found ARP Request") + if eth_packet.src != self.ip_to_mac.get(arp_packet.src_ip): + logger.info(f"Found new IP-MAC pair: {arp_packet.src_ip} -> {eth_packet.src}") + self.ip_to_mac[arp_packet.src_ip] = eth_packet.src + # send arp reply manually eth_packet.src, eth_packet.dst = self.port_to_own_mac[in_port], eth_packet.src arp_packet.src_mac, arp_packet.dst_mac = self.port_to_own_mac[in_port], arp_packet.src_mac @@ -262,19 +275,24 @@ def handle_router_request(self, ev): pkt.add_protocol(arp_packet) pkt.serialize() outs.append(self.reply_packet_to_in_port(data=pkt.data, datapath=datapath, parser=parser, in_port=in_port, ofproto=ofproto)) - logger.info(f"Instruction to router: send arp reply") + logger.info(f"Instruction: send arp reply") if ipv4_packet: # do ip stuff # prefix matching, next hop (Ethernet-Header Rewriting: MAC-adresse der Source muss MAC adresse des input-ports sein (siehe actions)) + logger.debug(f"ipv4_packet: {ipv4_packet.src} -> {ipv4_packet.dst}; eth_packet: {eth_packet.src} -> {eth_packet.dst};") + logger.debug(f"self.ip_to_mac={self.ip_to_mac}") + if eth_packet.src != self.ip_to_mac.get(ipv4_packet.src): + logger.info(f"Found new IP-MAC pair: {ipv4_packet.src} -> {eth_packet.src}") + self.ip_to_mac[ipv4_packet.src] = eth_packet.src if ipv4_packet.dst in self.port_to_own_ip.values(): if ipv4_packet.proto == IPPROTO_ICMP: if ipv4_packet.dst != self.port_to_own_ip[in_port]: match = parser.OFPMatch(in_port=in_port, ipv4_dst=ipv4_packet.dst) actions = [] self.add_flow(datapath=datapath, priority=PRIO_DROP, match=match, actions=actions) - logger.info(f"Router: pinged wrong Gateway: src={ipv4_packet.src}; dst={ipv4_packet.dst};") - logger.info(f"Added drop rule: match={match}; actions={actions};") + logger.info(f"pinged wrong Gateway: src={ipv4_packet.src}; dst={ipv4_packet.dst};") + logger.debug(f"Added drop rule: match={match}; actions={actions};") else: icmp_packet = pkt.get_protocol(icmp.icmp) logger.info(f"ping Gateway: src={ipv4_packet.src}; dst={ipv4_packet.dst};") @@ -287,17 +305,17 @@ def handle_router_request(self, ev): pkt.add_protocol(icmp_packet) pkt.serialize() outs.append(self.reply_packet_to_in_port(data=pkt.data, datapath=datapath, parser=parser, in_port=in_port, ofproto=ofproto)) - logger.info(f"Instruction to router: send icmp echo reply") + logger.info(f"Instruction: send icmp echo reply") else: - logger.critical(f"Router: unknown IP-Protocol: {ipv4_packet.proto}") + logger.error(f"unknown IP-Protocol: {ipv4_packet.proto}") elif (ip_network((ipv4_packet.src, self.netmask), strict=False) == ip_network((ipv4_packet.dst, self.netmask), strict=False) and ipv4_packet.dst != self.port_to_own_ip[in_port]): if ipv4_packet.dst not in self.same_network_ip_drop_rules[in_port]: - logger.info("Router: got in network ip broadcast. Dropping.") + logger.info("got in network ip broadcast. Dropping.") match = parser.OFPMatch(in_port=in_port, ipv4_dst=ipv4_packet.dst, eth_type=ether_types.ETH_TYPE_IP) self.add_flow(datapath=datapath, priority=PRIO_DROP, match=match, actions=[]) - logger.info(f"Added rule: match={match}, actions={[]} on router;") + logger.debug(f"Added rule: match={match}, actions={[]} on router;") self.same_network_ip_drop_rules[in_port].append(ipv4_packet.dst) else: logger.critical(f"Existing IP drop rule did not match: in_port={in_port} dst_ip={ipv4_packet.dst}") @@ -316,7 +334,7 @@ def handle_router_request(self, ev): ofproto=ofproto)) for out in outs: - logger.info(f"result={datapath.send_msg(out)}: {out}") + logger.debug(f"result={datapath.send_msg(out)}: {out}") # do ethernet stuff? # ping packets have ipv6 and icmpv6 -> Ping uses icmp From 49e397584a545701ecb4e8dc7a8a935a95d75604 Mon Sep 17 00:00:00 2001 From: alexmupb Date: Fri, 15 May 2026 15:07:22 +0200 Subject: [PATCH 32/42] fix typos --- lab1/ping.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lab1/ping.md b/lab1/ping.md index daf4b69..441c189 100644 --- a/lab1/ping.md +++ b/lab1/ping.md @@ -36,7 +36,7 @@ h1 sends ICMP echo to the port towards s1 with ETH dst of the s3 and IP dst of s controller receives packet at s1, detects it already has the mac from s3 (the ETH dst MAC) from Step 4, and creates a flow rule telling it to output to the port towards h1 if matching that MAC, forwards it to port at h1. ## Step 7 -controller receives packet at s3, as ETH dst MAC matches router's port MAC, it should process it. router sees the IP dst matches a gateway on one of its ports, meaning the target is reachable in that subnet: Need to create a new packet with the IP src of h1 and IP dst of ser, ETC src of outgoing port, but the ETH dst is unknown. +controller receives packet at s3, as ETH dst MAC matches router's port MAC, it should process it. router sees the IP dst matches a gateway on one of its ports, meaning the target is reachable in that subnet: Need to create a new packet with the IP src of h1 and IP dst of ser, ETH src of outgoing port, but the ETH dst is unknown. **Issue**: _In this step it also needs to store the incoming ip + mac combo and add it to the router's flow rule, otherwise it will do an ARP again when the echo reply travels back to h1_. It does so in Step 11 for the other side but not here @@ -63,6 +63,6 @@ ser now received the ICMP echo from h1 ## Similar steps now in the other direction, except no ARPing, so not listed here -That the route was already traversed once in both directions during ARP, so most packets will not enter the controller anymore. If they do, it's an issue that needs to be looked at. +The route was already traversed once in both directions during ARP, so most packets will not enter the controller anymore. If they do, it's an issue that needs to be looked at. **Issue:** _when the ser -> h1 echo reaches s3, it still does another ARP which it shouldn't, see Step 7_ \ No newline at end of file From cbe5b981c0588a6fd487c4cefbf08f9ac829e60e Mon Sep 17 00:00:00 2001 From: alexmupb Date: Fri, 15 May 2026 15:22:37 +0200 Subject: [PATCH 33/42] add OFPActionDecNwTtl action --- lab1/ans_controller.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lab1/ans_controller.py b/lab1/ans_controller.py index bfaea83..5555203 100644 --- a/lab1/ans_controller.py +++ b/lab1/ans_controller.py @@ -230,6 +230,7 @@ def forward_ipv4_packet(self, msg, eth_dst): out_port = self.network_to_port[dst_network.network_address] actions = [parser.OFPActionSetField(eth_src=self.port_to_own_mac[out_port]), parser.OFPActionSetField(eth_dst=eth_dst), + parser.OFPActionDecNwTtl(), parser.OFPActionOutput(out_port)] self.add_flow(datapath=datapath, priority=PRIO_FORWARD, match=match, actions=actions) logger.info(f"Added rule: match={match}, actions={actions} on router;") From 674e365fece64c2a0d2fe49ede2c619d6731c25f Mon Sep 17 00:00:00 2001 From: alexmupb Date: Fri, 15 May 2026 15:56:23 +0200 Subject: [PATCH 34/42] fix unneeded lines in merge --- lab1/ans_controller.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/lab1/ans_controller.py b/lab1/ans_controller.py index 242ecdb..b57231a 100644 --- a/lab1/ans_controller.py +++ b/lab1/ans_controller.py @@ -303,30 +303,11 @@ def handle_router_request(self, ev): in_port = msg.match["in_port"] pkt = packet.Packet(msg.data) - logger.debug(f"Packet comes from router and was received on port {in_port}! Protocols:") - for p in pkt.protocols: - logger.debug(f"\t- {p}") - eth_packet = pkt.get_protocol(ethernet.ethernet) ipv4_packet = pkt.get_protocol(ipv4.ipv4) - #icmp_packet = pkt.get_protocol(icmp.icmp) arp_packet = pkt.get_protocol(arp.arp) - #tcp_packet = pkt.get_protocol(tcp.tcp) - #udp_packet = pkt.get_protocol(udp.udp) outs = [] - """if udp_packet or tcp_packet: - # do udp/tcp stuff - # no connection between ser and ext, otherwise ok - pass - - if icmp_packet: - # do icmp stuff - # internal all allowed (concrete Ip-adresses) - # gateway pings only to own (subnet) gateway (wenn subnetzte unterschiedlich, dann droppen, sonst icmp reply nach source) - # none to external - # none from external - pass""" if arp_packet: logger.info(f"seq={self.packet_counter}: Got ARP packet:\n{arp_packet}") From d1149a8cb5e8644beef7508b2af2076d6e9a3d9f Mon Sep 17 00:00:00 2001 From: Arya-55 Date: Fri, 15 May 2026 20:24:58 +0200 Subject: [PATCH 35/42] firewall now only blocks ICMP Pings --- lab1/ans_controller.py | 94 ++++++++++++++++++++++++++++++------------ 1 file changed, 67 insertions(+), 27 deletions(-) diff --git a/lab1/ans_controller.py b/lab1/ans_controller.py index b57231a..60a90d7 100644 --- a/lab1/ans_controller.py +++ b/lab1/ans_controller.py @@ -21,7 +21,7 @@ import datetime from collections import defaultdict from copy import deepcopy -from ipaddress import ip_address, ip_network +from ipaddress import ip_address, ip_network, IPv4Network, IPv4Address from logging import getLogger, StreamHandler, Formatter from ryu.base import app_manager from ryu.controller import ofp_event @@ -32,16 +32,17 @@ from pprint import pprint, pformat -loglevel = "DEBUG" -logger, switch_logger = getLogger(f"{__name__}_Router"), getLogger(f"{__name__}_Switch") -logger.propagate, switch_logger.propagate = False, False -logger.setLevel(loglevel), switch_logger.setLevel("INFO") -logger.handlers.clear(), switch_logger.handlers.clear() -handler = StreamHandler() -switch_log_handler = StreamHandler() -handler.setFormatter(Formatter(fmt="%(asctime)s, %(name)s, %(levelname)s, %(lineno)d: %(message)s")) -switch_log_handler.setFormatter(Formatter(fmt="%(asctime)s, %(name)s, %(levelname)s, %(lineno)d: %(message)s")) -logger.addHandler(handler), switch_logger.addHandler(switch_log_handler) +logger, switch_logger = getLogger(__name__), getLogger(__name__) +# loglevel = "DEBUG" +# logger, switch_logger = getLogger(f"{__name__}_Router"), getLogger(f"{__name__}_Switch") +# logger.propagate, switch_logger.propagate = False, False +# logger.setLevel(loglevel), switch_logger.setLevel("INFO") +# logger.handlers.clear(), switch_logger.handlers.clear() +# handler = StreamHandler() +# switch_log_handler = StreamHandler() +# handler.setFormatter(Formatter(fmt="%(asctime)s, %(name)s, %(levelname)s, %(lineno)d: %(message)s")) +# switch_log_handler.setFormatter(Formatter(fmt="%(asctime)s, %(name)s, %(levelname)s, %(lineno)d: %(message)s")) +# logger.addHandler(handler), switch_logger.addHandler(switch_log_handler) PRIO_FIREWALL = 5 @@ -83,17 +84,33 @@ def __init__(self, *args, **kwargs): self.network_to_port = {ip_network((ip, self.netmask), strict=False).network_address: port for port, ip in self.port_to_own_ip.items()} self.firewall = [ - { # no ICMP from ext to any intern (only own gateway) + { # no ICMP pings from ext to any intern (only own gateway) "proto": in_proto.IPPROTO_ICMP, + "type": [icmp.ICMP_ECHO_REQUEST, icmp.ICMP_ECHO_REPLY], "src": ip_network("192.168.1.0/24"), "dst": ip_network("10.0.0.0/16") }, { - # no ICMP from intern to extern + # no ICMP pings from intern to extern "proto": in_proto.IPPROTO_ICMP, + "type": [icmp.ICMP_ECHO_REQUEST, icmp.ICMP_ECHO_REPLY], "src": ip_network("10.0.0.0/16"), "dst": ip_network("192.168.1.0/24") }, + { + # no ICMP pings from s1 subnet gateway to s2 + "proto": in_proto.IPPROTO_ICMP, + "type": [icmp.ICMP_ECHO_REQUEST, icmp.ICMP_ECHO_REPLY], + "src": ip_network("10.0.1.0/24"), + "dst": ip_address("10.0.2.1") + }, + { + # no ICMP pings from s2 subnet gateway to s1 + "proto": in_proto.IPPROTO_ICMP, + "type": [icmp.ICMP_ECHO_REQUEST, icmp.ICMP_ECHO_REPLY], + "src": ip_network("10.0.2.0/24"), + "dst": ip_address("10.0.1.1") + }, { # no TCP from ser to ext "proto": in_proto.IPPROTO_TCP, @@ -242,7 +259,7 @@ def forward_ipv4_packet(self, datapath, data, in_port, eth_dst): ipv4_packet = pkt.get_protocol(ipv4.ipv4) ipv4_packet.ttl = ipv4_packet.ttl - 1; - match = parser.OFPMatch(eth_type=ether_types.ETH_TYPE_IP, ipv4_dst=ipv4_packet.dst) + match = parser.OFPMatch(eth_type=ether_types.ETH_TYPE_IP, ip_proto = ipv4_packet.proto, ipv4_src=ipv4_packet.src, ipv4_dst=ipv4_packet.dst) dst_network = ip_network((ipv4_packet.dst, self.netmask), strict=False) out_port = self.network_to_port[dst_network.network_address] actions = [parser.OFPActionSetField(eth_src=self.port_to_own_mac[out_port]), @@ -278,17 +295,42 @@ def construct_arp_request(self, port, dst_ip, datapath, parser, ofproto): return self.packet_out_to_port(data=pkt.data, datapath=datapath, parser=parser, in_port=ofproto.OFPP_CONTROLLER, port=port, ofproto=ofproto) - def check_for_firewall_entry(self, ipv4_packet): - logger.info(f"Checking IPv4 packet against firewall:\n{ipv4_packet}") + def check_for_firewall_entry(self, ipv4_packet, icmp_packet = None): + logger.info(f"Checking IPv4 packet against firewall:\n{ipv4_packet}\n{icmp_packet}") - for entry in self.firewall: - if all(getattr(ipv4_packet, key) == value if isinstance(value, int) else ip_address(getattr(ipv4_packet, key)) - in value for key, value in entry.items()): - logger.info(f"Found firewall entry: {entry}") - return {"ip_proto": entry["proto"], "ipv4_src": ipv4_packet.src + "/24", "ipv4_dst": ipv4_packet.dst + "/24"} + if icmp_packet: + for entry in self.firewall: + match = [] + for key, value in entry.items(): + + # protocol + if isinstance(value, int): + match.append(getattr(ipv4_packet, key) == value) + + # type + if key == "type": + match.append(icmp_packet.type in value) + + # dst + if isinstance(value, IPv4Address): + match.append(ip_address(getattr(ipv4_packet, key)) == value) + + # src / dst + if isinstance(value, IPv4Network): + match.append(ip_address(getattr(ipv4_packet, key)) in value) + + if all(match): + logger.info(f"Found firewall entry: {entry}") + return {"ip_proto": entry["proto"], "icmpv4_type": icmp_packet.type, "ipv4_src": str(entry["src"]), "ipv4_dst": str(entry["dst"])} + else: + for entry in self.firewall: + if all(getattr(ipv4_packet, key) == value if isinstance(value, int) else ip_address(getattr(ipv4_packet, key)) + in value for key, value in entry.items()): + logger.info(f"Found firewall entry: {entry}") + return {"ip_proto": entry["proto"], "ipv4_src": str(entry["src"]), "ipv4_dst": str(entry["dst"])} logger.info(f"No matching firewall entry found") - return None + return None def handle_router_request(self, ev): @@ -305,6 +347,7 @@ def handle_router_request(self, ev): eth_packet = pkt.get_protocol(ethernet.ethernet) ipv4_packet = pkt.get_protocol(ipv4.ipv4) + icmp_packet = pkt.get_protocol(icmp.icmp) arp_packet = pkt.get_protocol(arp.arp) outs = [] @@ -356,13 +399,11 @@ def handle_router_request(self, ev): logger.info(f"seq={self.packet_counter}: Got IPv4 packet") logger.debug(f"ipv4_packet: {ipv4_packet.src} -> {ipv4_packet.dst}; eth_packet: {eth_packet.src} -> {eth_packet.dst};") - firewall_entry = self.check_for_firewall_entry(ipv4_packet) + firewall_entry = self.check_for_firewall_entry(ipv4_packet, icmp_packet=icmp_packet) if firewall_entry: - # There is an entry in the firewall-table fitting this packet => add dropping rule + # There is an entry in the firewall-table fitting this packet match = parser.OFPMatch(eth_type=0x0800, **firewall_entry) - # TODO: Instead of installing a dropping rule, the controller should probably send out an ICMP package with code 3 and type 13 - # to get "Destination Unreachable — Communication Administratively Prohibited" self.add_flow(datapath=datapath, priority=PRIO_FIREWALL, match=match, @@ -376,7 +417,6 @@ def handle_router_request(self, ev): # IP forwarding if ipv4_packet.dst in self.port_to_own_ip.values(): if ipv4_packet.proto == in_proto.IPPROTO_ICMP: - icmp_packet = pkt.get_protocol(icmp.icmp) logger.info(f"ping Gateway: src={ipv4_packet.src}; dst={ipv4_packet.dst};") eth_packet.src, eth_packet.dst = self.port_to_own_mac[in_port], eth_packet.src ipv4_packet.src, ipv4_packet.dst = self.port_to_own_ip[in_port], ipv4_packet.src From ca3990adf4889de94a8d7192313584e6ec6e4aa5 Mon Sep 17 00:00:00 2001 From: alexmupb Date: Fri, 15 May 2026 20:41:40 +0200 Subject: [PATCH 36/42] remove more logging --- lab1/ans_controller.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/lab1/ans_controller.py b/lab1/ans_controller.py index 60a90d7..a9062f7 100644 --- a/lab1/ans_controller.py +++ b/lab1/ans_controller.py @@ -18,7 +18,6 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -import datetime from collections import defaultdict from copy import deepcopy from ipaddress import ip_address, ip_network, IPv4Network, IPv4Address @@ -177,18 +176,17 @@ def _packet_in_handler(self, ev): ofproto = datapath.ofproto parser = datapath.ofproto_parser - t = datetime.datetime.now() - timestamp = f"{t.minute}:{t.second}.{str(t.microsecond)[:3]}" - logger.info(f"\n{timestamp} ###### NEW PACKET ######") pkt = packet.Packet(msg.data) for p in pkt.protocols: logger.info(f" - {p}") if datapath.id == 3: # handle router (s3) request + logger.info(f"\n###### NEW PACKET (Router) ######") self.handle_router_request(ev) else: # handle switch requests + switch_logger.info(f"\n###### NEW PACKET (Switch) ######") num_minus = 10 print(num_minus * "-" + "Switch Request start (_packet_in_handler)" + num_minus * "-") @@ -223,12 +221,10 @@ def _packet_in_handler(self, ev): else: # Flood packet out out = self.flood_packet_out(data=msg.data, datapath=datapath, parser=parser, in_port=in_port, ofproto=ofproto) - logger.info(f"Instruction to dpid={datapath.id}: broadcast") + switch_logger.info(f"Instruction to dpid={datapath.id}: broadcast") datapath.send_msg(out) - switch_logger.info(f"Instruction to dpid={datapath.id}: broadcast") - @staticmethod def packet_out_to_port(*, data, datapath, parser, in_port, port, ofproto): From ce1ee9a9b3d44a47063c1ce0926a0c4b92f25011 Mon Sep 17 00:00:00 2001 From: alexmupb Date: Fri, 15 May 2026 21:22:00 +0200 Subject: [PATCH 37/42] send ICMP unreachable for ICMP messages --- lab1/ans_controller.py | 61 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 53 insertions(+), 8 deletions(-) diff --git a/lab1/ans_controller.py b/lab1/ans_controller.py index a9062f7..35bce38 100644 --- a/lab1/ans_controller.py +++ b/lab1/ans_controller.py @@ -27,7 +27,7 @@ from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER from ryu.controller.handler import set_ev_cls from ryu.lib.packet import packet, ethernet, ether_types, ipv4, in_proto, icmp, arp -from ryu.ofproto import ofproto_v1_3 +from ryu.ofproto import ofproto_v1_3, ether from pprint import pprint, pformat @@ -136,6 +136,8 @@ def __init__(self, *args, **kwargs): } ] + self.firewall_tracked = [] + @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER) def switch_features_handler(self, ev): @@ -327,6 +329,33 @@ def check_for_firewall_entry(self, ipv4_packet, icmp_packet = None): logger.info(f"No matching firewall entry found") return None + + + def send_destination_unreachable(self, pkt, eth_packet, ipv4_packet, datapath, parser, in_port, ofproto): + router_mac = self.port_to_own_mac[in_port] + router_ip = self.port_to_own_ip[in_port] + + # extra stuff for ICMP unreachable that is not natively supported by the library + eth_offset = 14 + if eth_packet.ethertype == ether.ETH_TYPE_8021Q: + eth_offset = eth_offset + 4 + + orig_data = pkt.data[eth_offset:eth_offset + ipv4_packet.header_length * 4 + 8] + # helper for icmp payload + icmp_data = icmp.dest_unreach(data_len=len(orig_data), data=orig_data) + code = 13 # Prohibited + + un_pkt = packet.Packet() + un_pkt.add_protocol(ethernet.ethernet(src=router_mac, dst=eth_packet.src, ethertype=ether.ETH_TYPE_IP)) + un_pkt.add_protocol(ipv4.ipv4(src=router_ip, dst=ipv4_packet.src, proto=in_proto.IPPROTO_ICMP)) + un_pkt.add_protocol(icmp.icmp(type_=icmp.ICMP_DEST_UNREACH, code=code, csum=0, data=icmp_data)) + un_pkt.serialize() + logger.info(f"\n\nSEND ICMP UNREACHABLE:\n {str(un_pkt)}") + logger.info(f"\nin_port: {in_port}") # Easier to notice, before it was 2, then 1 after pingall + # outdated with the controller redirect actions taking priority: TODO after pingall (router populated with flow rules), "ext ping h1 -c1" no longer instantly breaks, however, + # "h1 ping ext -c1" works (hosts swapped). The reason is that since the ping goes back and forth, it gets blocked on the h1 side, meaning the ext one hangs + # so this means the rules for ext in the router let it through somehow after pingall, even if it should go through the firewall check here + return self.reply_packet_to_in_port(data=un_pkt.data, datapath=datapath, parser=parser, in_port=in_port, ofproto=ofproto) def handle_router_request(self, ev): @@ -398,13 +427,29 @@ def handle_router_request(self, ev): firewall_entry = self.check_for_firewall_entry(ipv4_packet, icmp_packet=icmp_packet) if firewall_entry: - # There is an entry in the firewall-table fitting this packet - match = parser.OFPMatch(eth_type=0x0800, **firewall_entry) - self.add_flow(datapath=datapath, - priority=PRIO_FIREWALL, - match=match, - actions=[]) - logger.info(f"Added firewall rule on router: match={match}, action=[]") + if firewall_entry not in self.firewall_tracked: + self.firewall_tracked.append(firewall_entry) + + # There is an entry in the firewall-table fitting this packet + match = parser.OFPMatch(eth_type=0x0800, **firewall_entry) + + # if icmp, redirect back to controller so it can send icmp unreachable + actions = [] + if icmp_packet: + actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER, + ofproto.OFPCML_NO_BUFFER)] + + self.add_flow(datapath=datapath, + priority=PRIO_FIREWALL, + match=match, + actions=actions) + logger.info(f"Added firewall rule on router: match={match}, action={actions}") + + # Send a "Destination Unreachable - Communication Administratively Prohibited" ICMP + # If it was an ICMP packet + if icmp_packet: + outs.append(self.send_destination_unreachable(pkt, eth_packet, ipv4_packet, datapath, parser, in_port, ofproto)) + else: logger.debug(f"self.ip_to_mac={self.ip_to_mac}") if eth_packet.src != self.ip_to_mac.get(ipv4_packet.src): From 0fffc6f7bdb61f3a3ff86114519eddf2b56c2c69 Mon Sep 17 00:00:00 2001 From: Rafael Date: Sat, 16 May 2026 13:43:08 +0200 Subject: [PATCH 38/42] logs and style corrections --- lab1/ans_controller.py | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/lab1/ans_controller.py b/lab1/ans_controller.py index 35bce38..f959bc5 100644 --- a/lab1/ans_controller.py +++ b/lab1/ans_controller.py @@ -32,16 +32,17 @@ logger, switch_logger = getLogger(__name__), getLogger(__name__) -# loglevel = "DEBUG" -# logger, switch_logger = getLogger(f"{__name__}_Router"), getLogger(f"{__name__}_Switch") -# logger.propagate, switch_logger.propagate = False, False -# logger.setLevel(loglevel), switch_logger.setLevel("INFO") -# logger.handlers.clear(), switch_logger.handlers.clear() -# handler = StreamHandler() -# switch_log_handler = StreamHandler() -# handler.setFormatter(Formatter(fmt="%(asctime)s, %(name)s, %(levelname)s, %(lineno)d: %(message)s")) -# switch_log_handler.setFormatter(Formatter(fmt="%(asctime)s, %(name)s, %(levelname)s, %(lineno)d: %(message)s")) -# logger.addHandler(handler), switch_logger.addHandler(switch_log_handler) +loglevel = "DEBUG" +logger, switch_logger = getLogger(f"{__name__}_Router"), getLogger(f"{__name__}_Switch") +logger.propagate, switch_logger.propagate = False, False +logger.setLevel(loglevel), switch_logger.setLevel("INFO") +logger.handlers.clear(), switch_logger.handlers.clear() +handler = StreamHandler() +switch_log_handler = StreamHandler() +handler.setFormatter(Formatter(fmt="%(asctime)s, %(name)s, %(levelname)s, %(lineno)d: %(message)s")) +switch_log_handler.setFormatter(Formatter(fmt="%(asctime)s, %(name)s, %(levelname)s, %(lineno)d: %(message)s")) +logger.addHandler(handler) +# switch_logger.addHandler(switch_log_handler) PRIO_FIREWALL = 5 @@ -180,7 +181,7 @@ def _packet_in_handler(self, ev): pkt = packet.Packet(msg.data) for p in pkt.protocols: - logger.info(f" - {p}") + switch_logger.info(f" - {p}") if datapath.id == 3: # handle router (s3) request @@ -255,9 +256,9 @@ def forward_ipv4_packet(self, datapath, data, in_port, eth_dst): logger.debug(f"\t- {p}") eth_packet = pkt.get_protocol(ethernet.ethernet) ipv4_packet = pkt.get_protocol(ipv4.ipv4) - ipv4_packet.ttl = ipv4_packet.ttl - 1; + ipv4_packet.ttl = ipv4_packet.ttl - 1 - match = parser.OFPMatch(eth_type=ether_types.ETH_TYPE_IP, ip_proto = ipv4_packet.proto, ipv4_src=ipv4_packet.src, ipv4_dst=ipv4_packet.dst) + match = parser.OFPMatch(eth_type=ether_types.ETH_TYPE_IP, ip_proto=ipv4_packet.proto, ipv4_src=ipv4_packet.src, ipv4_dst=ipv4_packet.dst) dst_network = ip_network((ipv4_packet.dst, self.netmask), strict=False) out_port = self.network_to_port[dst_network.network_address] actions = [parser.OFPActionSetField(eth_src=self.port_to_own_mac[out_port]), @@ -270,7 +271,7 @@ def forward_ipv4_packet(self, datapath, data, in_port, eth_dst): eth_packet.src = self.port_to_own_mac[out_port] eth_packet.dst = eth_dst eth_packet.ethertype = ether_types.ETH_TYPE_IP - logger.debug(f"prepared fwd: {ipv4_packet}, eth_dst={eth_dst}, eth_packet.src={eth_packet.src}, eth_packet={eth_packet}, dpid={datapath.id}, in_port={in_port}") + logger.debug(f"prepared fwd: {ipv4_packet}, eth_dst={eth_dst}, eth_packet.src={eth_packet.src}, eth_packet={eth_packet}, dpid={datapath.id}, in_port={in_port}, data={pkt.data}") pkt.serialize() logger.info(f"forward to ip={ipv4_packet.dst}, mac={eth_dst}") @@ -431,7 +432,7 @@ def handle_router_request(self, ev): self.firewall_tracked.append(firewall_entry) # There is an entry in the firewall-table fitting this packet - match = parser.OFPMatch(eth_type=0x0800, **firewall_entry) + match = parser.OFPMatch(eth_type=ether_types.ETH_TYPE_IP, **firewall_entry) # if icmp, redirect back to controller so it can send icmp unreachable actions = [] From 59a5585b781dfaf8e8b2f2cd1436c2253acaff17 Mon Sep 17 00:00:00 2001 From: Rafael Date: Sat, 16 May 2026 13:43:44 +0200 Subject: [PATCH 39/42] send icmp network unreachable if network does not exist --- lab1/ans_controller.py | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/lab1/ans_controller.py b/lab1/ans_controller.py index f959bc5..369fd0a 100644 --- a/lab1/ans_controller.py +++ b/lab1/ans_controller.py @@ -332,7 +332,7 @@ def check_for_firewall_entry(self, ipv4_packet, icmp_packet = None): return None - def send_destination_unreachable(self, pkt, eth_packet, ipv4_packet, datapath, parser, in_port, ofproto): + def send_destination_unreachable(self, pkt, eth_packet, ipv4_packet, datapath, parser, in_port, ofproto, type_=icmp.ICMP_DEST_UNREACH, code=13): router_mac = self.port_to_own_mac[in_port] router_ip = self.port_to_own_ip[in_port] @@ -344,12 +344,11 @@ def send_destination_unreachable(self, pkt, eth_packet, ipv4_packet, datapath, p orig_data = pkt.data[eth_offset:eth_offset + ipv4_packet.header_length * 4 + 8] # helper for icmp payload icmp_data = icmp.dest_unreach(data_len=len(orig_data), data=orig_data) - code = 13 # Prohibited un_pkt = packet.Packet() un_pkt.add_protocol(ethernet.ethernet(src=router_mac, dst=eth_packet.src, ethertype=ether.ETH_TYPE_IP)) un_pkt.add_protocol(ipv4.ipv4(src=router_ip, dst=ipv4_packet.src, proto=in_proto.IPPROTO_ICMP)) - un_pkt.add_protocol(icmp.icmp(type_=icmp.ICMP_DEST_UNREACH, code=code, csum=0, data=icmp_data)) + un_pkt.add_protocol(icmp.icmp(type_=type_, code=code, csum=0, data=icmp_data)) un_pkt.serialize() logger.info(f"\n\nSEND ICMP UNREACHABLE:\n {str(un_pkt)}") logger.info(f"\nin_port: {in_port}") # Easier to notice, before it was 2, then 1 after pingall @@ -487,16 +486,23 @@ def handle_router_request(self, ev): logger.info(f"For IP-Address={ipv4_packet.dst}, found dst_mac={self.ip_to_mac.get(ipv4_packet.dst)}") outs.append(self.forward_ipv4_packet(datapath=datapath, data=msg.data, in_port=in_port, eth_dst=self.ip_to_mac[ipv4_packet.dst])) else: - buffered = {"datapath": datapath, "data": deepcopy(msg.data), "in_port": in_port} - self.buffered_msgs[ipv4_packet.dst].append(buffered) - logger.info(f"MAC for IP={ipv4_packet.dst} not found. Buffer msg...") - logger.debug(f"put in buffer: {pformat(buffered)}") - # TODO if pinging+ARPing an IP that is unknown (i.e. 10.1.2.3), the dict lookup errors - outs.append(self.construct_arp_request(port=self.network_to_port[ip_network((ipv4_packet.dst, self.netmask), strict=False).network_address], - dst_ip=ipv4_packet.dst, - datapath=datapath, - parser=parser, - ofproto=ofproto)) + target_network = ip_network((ipv4_packet.dst, self.netmask), strict=False).network_address + port = self.network_to_port.get(target_network) + if port: + buffered = {"datapath": datapath, "data": deepcopy(msg.data), "in_port": in_port} + self.buffered_msgs[ipv4_packet.dst].append(buffered) + logger.info(f"MAC for IP={ipv4_packet.dst} not found. Buffer msg...") + logger.debug(f"put in buffer: {pformat(buffered)}") + outs.append(self.construct_arp_request(port=port, + dst_ip=ipv4_packet.dst, + datapath=datapath, + parser=parser, + ofproto=ofproto)) + else: + logger.error(f"Target network unknown: {ipv4_packet.dst} from network {target_network}") + self.send_destination_unreachable(pkt=pkt, eth_packet=eth_packet, ipv4_packet=ipv4_packet, + datapath=datapath, parser=parser, in_port=in_port, + ofproto=ofproto, code=0) for out in outs: - logger.debug(f"result={datapath.send_msg(out)}")#: {out}") \ No newline at end of file + logger.debug(f"result={datapath.send_msg(out)}") \ No newline at end of file From 183668f6132201a66775fd9db6e3dfd89954d37d Mon Sep 17 00:00:00 2001 From: Rafael Date: Sat, 16 May 2026 14:59:49 +0200 Subject: [PATCH 40/42] more style correcions --- lab1/ans_controller.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/lab1/ans_controller.py b/lab1/ans_controller.py index 369fd0a..80ba07f 100644 --- a/lab1/ans_controller.py +++ b/lab1/ans_controller.py @@ -407,15 +407,15 @@ def handle_router_request(self, ev): eth_packet.src, eth_packet.dst = self.port_to_own_mac[in_port], eth_packet.src arp_packet.src_mac, arp_packet.dst_mac = self.port_to_own_mac[in_port], arp_packet.src_mac arp_packet.src_ip, arp_packet.dst_ip = self.port_to_own_ip[in_port], arp_packet.src_ip - arp_packet.opcode = 2 + arp_packet.opcode = arp.ARP_REPLY pkt = packet.Packet() pkt.add_protocol(eth_packet) pkt.add_protocol(arp_packet) pkt.serialize() - logger.info(f"construct arp reply, contents:") + logger.debug(f"construct arp reply, contents:") for p in pkt.protocols: - logger.info(f"> {p}") + logger.debug(f"> {p}") outs.append(self.reply_packet_to_in_port(data=pkt.data, datapath=datapath, parser=parser, in_port=in_port, ofproto=ofproto)) logger.info(f"Instruction: send arp reply") @@ -436,13 +436,8 @@ def handle_router_request(self, ev): # if icmp, redirect back to controller so it can send icmp unreachable actions = [] if icmp_packet: - actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER, - ofproto.OFPCML_NO_BUFFER)] - - self.add_flow(datapath=datapath, - priority=PRIO_FIREWALL, - match=match, - actions=actions) + actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER, ofproto.OFPCML_NO_BUFFER)] + self.add_flow(datapath=datapath, priority=PRIO_FIREWALL, match=match, actions=actions) logger.info(f"Added firewall rule on router: match={match}, action={actions}") # Send a "Destination Unreachable - Communication Administratively Prohibited" ICMP From d6b28d8b25086574c91c468a4bac7f5310525238 Mon Sep 17 00:00:00 2001 From: Rafael Date: Sat, 16 May 2026 16:33:56 +0200 Subject: [PATCH 41/42] new tests in run_network.py --- lab1/run_network.py | 95 +++++++-------------------------------------- 1 file changed, 13 insertions(+), 82 deletions(-) diff --git a/lab1/run_network.py b/lab1/run_network.py index 857098e..57fd532 100644 --- a/lab1/run_network.py +++ b/lab1/run_network.py @@ -55,85 +55,6 @@ def __init__(self, *args, **kwargs): self.addLink("s2", "ser", bw=15, delay="10ms") -class SwitchFlowTester: - - def __init__(self, net: Mininet): - self.net = net - - self.test_data_switch_flows = defaultdict(dict) - switchname = "s1" - port_to_hop = self.get_port_to_hop(switchname) - self.test_data_switch_flows[switchname][net.get("h1").MAC()] = port_to_hop["h1"] - self.test_data_switch_flows[switchname][net.get("h2").MAC()] = port_to_hop["h2"] - self.test_data_switch_flows[switchname][net.get("ext").MAC()] = port_to_hop["s3"] - self.test_data_switch_flows[switchname][net.get("ser").MAC()] = port_to_hop["s3"] - - switchname = "s2" - port_to_hop = self.get_port_to_hop(switchname) - self.test_data_switch_flows[switchname][net.get("h1").MAC()] = port_to_hop["s3"] - self.test_data_switch_flows[switchname][net.get("h2").MAC()] = port_to_hop["s3"] - self.test_data_switch_flows[switchname][net.get("ext").MAC()] = port_to_hop["s3"] - self.test_data_switch_flows[switchname][net.get("ser").MAC()] = port_to_hop["ser"] - - switchname = "s3" - port_to_hop = self.get_port_to_hop(switchname) - self.test_data_switch_flows[switchname][net.get("h1").MAC()] = port_to_hop["s1"] - self.test_data_switch_flows[switchname][net.get("h2").MAC()] = port_to_hop["s1"] - self.test_data_switch_flows[switchname][net.get("ext").MAC()] = port_to_hop["ext"] - self.test_data_switch_flows[switchname][net.get("ser").MAC()] = port_to_hop["s2"] - - for switchname in self.test_data_switch_flows: - for hostmac in self.test_data_switch_flows[switchname]: - mininet.log.info(f"Switch={switchname}: {hostmac} --> {self.test_data_switch_flows[switchname][hostmac]}") - - def get_port_to_hop(self, switchname): - switch = self.net.get(switchname) - ret = {} - for port_no, intf in switch.intfs.items(): - if port_no == 0: # except lo - continue - link = intf.link - if link.intf1.node.name != switchname: - ret[link.intf1.node.name] = port_no - else: - ret[link.intf2.node.name] = port_no - return ret - - @staticmethod - def cast_to_int_if_possible(arg): - try: - return int(arg) - except ValueError: - return arg - - def parse_action_str(self, action_str: str): - action, target = action_str.split(":") - return action, self.cast_to_int_if_possible(target) - - def test(self): - flows = defaultdict(dict) - for switch in self.net.switches: - sdpid = int(switch.dpid) - resp = requests.get(f"http://localhost:8080/stats/flow/{sdpid}") - flows_json = resp.json()[str(sdpid)] - - for d in flows_json: - match = d["match"].get("dl_dst") - flows[switch.name][match] = [d["actions"]] - for actions in flows[switch.name][match]: - for action_str in actions: - _, port = self.parse_action_str(action_str) - flows[switch.name][match] = port - - for switchname in self.test_data_switch_flows: - for hostmac, port_no in self.test_data_switch_flows[switchname].items(): - test = flows[switchname].get(hostmac) == port_no - if test: - mininet.log.info(f"Success: Switch={switchname}: Rule: {hostmac} --> {port_no} == {flows[switchname].get(hostmac)}") - else: - mininet.log.info(f"Failure: Switch={switchname}: Rule: {hostmac} --> {port_no} != {flows[switchname].get(hostmac)}") - - def run(): topo = NetworkTopo() net = Mininet(topo=topo, @@ -152,10 +73,20 @@ def run(): host.cmd("sysctl -w net.ipv6.conf.lo.disable_ipv6=1") net.start() - #net.pingAll() + net.pingAll() + net.iperf((net.get("h1"), net.get("h2"))) + net.iperf((net.get("h1"), net.get("ser"))) + net.iperf((net.get("h1"), net.get("ext"))) + + net.iperf((net.get("h2"), net.get("h1"))) + net.iperf((net.get("h2"), net.get("ser"))) + net.iperf((net.get("h2"), net.get("ext"))) + + net.iperf((net.get("ser"), net.get("h1"))) + net.iperf((net.get("ser"), net.get("h2"))) - # switch_flow_tester = SwitchFlowTester(net) - # gitswitch_flow_tester.test() + net.iperf((net.get("ext"), net.get("h1"))) + net.iperf((net.get("ext"), net.get("h2"))) CLI(net) net.stop() From 0f27fcb7a7307195dcd724e8741b53d1f7356fe9 Mon Sep 17 00:00:00 2001 From: Rafael Date: Sat, 16 May 2026 20:31:58 +0200 Subject: [PATCH 42/42] last fixes --- lab1/ans_controller.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/lab1/ans_controller.py b/lab1/ans_controller.py index 80ba07f..9f17890 100644 --- a/lab1/ans_controller.py +++ b/lab1/ans_controller.py @@ -32,7 +32,7 @@ logger, switch_logger = getLogger(__name__), getLogger(__name__) -loglevel = "DEBUG" +loglevel = "INFO" logger, switch_logger = getLogger(f"{__name__}_Router"), getLogger(f"{__name__}_Switch") logger.propagate, switch_logger.propagate = False, False logger.setLevel(loglevel), switch_logger.setLevel("INFO") @@ -350,11 +350,7 @@ def send_destination_unreachable(self, pkt, eth_packet, ipv4_packet, datapath, p un_pkt.add_protocol(ipv4.ipv4(src=router_ip, dst=ipv4_packet.src, proto=in_proto.IPPROTO_ICMP)) un_pkt.add_protocol(icmp.icmp(type_=type_, code=code, csum=0, data=icmp_data)) un_pkt.serialize() - logger.info(f"\n\nSEND ICMP UNREACHABLE:\n {str(un_pkt)}") - logger.info(f"\nin_port: {in_port}") # Easier to notice, before it was 2, then 1 after pingall - # outdated with the controller redirect actions taking priority: TODO after pingall (router populated with flow rules), "ext ping h1 -c1" no longer instantly breaks, however, - # "h1 ping ext -c1" works (hosts swapped). The reason is that since the ping goes back and forth, it gets blocked on the h1 side, meaning the ext one hangs - # so this means the rules for ext in the router let it through somehow after pingall, even if it should go through the firewall check here + logger.info(f"SEND ICMP UNREACHABLE: {str(un_pkt)}, in_port: {in_port}") return self.reply_packet_to_in_port(data=un_pkt.data, datapath=datapath, parser=parser, in_port=in_port, ofproto=ofproto) @@ -427,7 +423,17 @@ def handle_router_request(self, ev): firewall_entry = self.check_for_firewall_entry(ipv4_packet, icmp_packet=icmp_packet) if firewall_entry: - if firewall_entry not in self.firewall_tracked: + # There is an entry in the firewall-table fitting this packet + match = parser.OFPMatch(eth_type=ether_types.ETH_TYPE_IP, **firewall_entry) + self.add_flow(datapath=datapath, + priority=PRIO_FIREWALL, + match=match, + actions=[]) + logger.info(f"Added firewall rule on router: match={match}, action=[]") + # here, it is possible to send an ICMP "Network Unreachable Communication prohibited", instead of a hard drop + # but the exercise seems to require a hard drop + # for it to take effect: remove "False: #" from the next two if statements and comment out the add_flow above + if False: # firewall_entry not in self.firewall_tracked: self.firewall_tracked.append(firewall_entry) # There is an entry in the firewall-table fitting this packet @@ -442,8 +448,8 @@ def handle_router_request(self, ev): # Send a "Destination Unreachable - Communication Administratively Prohibited" ICMP # If it was an ICMP packet - if icmp_packet: - outs.append(self.send_destination_unreachable(pkt, eth_packet, ipv4_packet, datapath, parser, in_port, ofproto)) + if False: # icmp_packet: + outs.append(self.send_destination_unreachable(pkt, eth_packet, ipv4_packet, datapath, parser, in_port, ofproto)) else: logger.debug(f"self.ip_to_mac={self.ip_to_mac}")