├── lab-1 ├── README.md ├── myhub.py ├── report │ ├── lab_1.pcapng │ └── lab_1.pdf ├── start_mininet.py └── testcases │ ├── myhub_testscenario.py │ └── test_lab1.py ├── lab-2 ├── README.md ├── myswitch.py ├── myswitch_lru.py ├── myswitch_to.py ├── myswitch_traffic.py ├── report │ ├── lab_2.pdf │ ├── lab_2_task2_server1.pcapng │ ├── lab_2_task2_server2.pcapng │ ├── lab_2_task3_server1.pcapng │ ├── lab_2_task3_server2.pcapng │ ├── lab_2_task4_client.pcapng │ ├── lab_2_task4_server1.pcapng │ ├── lab_2_task4_server2.pcapng │ ├── lab_2_task5_client.pcapng │ ├── lab_2_task5_server1.pcapng │ └── lab_2_task5_server2.pcapng ├── start_mininet.py └── testcases │ ├── myswitch_lru_testscenario.srpy │ ├── myswitch_to_testscenario.srpy │ ├── myswitch_traffic_testscenario.srpy │ └── mytestscenario.py ├── lab-3 ├── README.md ├── forwarding_table.txt ├── myrouter.py ├── report │ ├── lab_3.pdf │ └── lab_3_task2_server1.pcapng ├── start_mininet.py └── testcases │ ├── myrouter1_testscenario.srpy │ └── test_submit.py ├── lab-4 ├── README.md ├── forwarding_table.txt ├── myrouter.py ├── rebulid_pkt.py ├── report │ ├── lab_4.pcapng │ └── lab_4.pdf ├── start_mininet.py └── testcases │ ├── myrouter2_testscenario.srpy │ └── test_submit.py ├── lab-5 ├── README.md ├── forwarding_table.txt ├── myrouter.py ├── rebulid_pkt.py ├── report │ ├── lab_5.pcapng │ └── lab_5.pdf ├── start_mininet.py └── testcases │ ├── router3_testscenario.srpy │ ├── router3_testscenario_template.py │ └── test_submit.py ├── lab-6 ├── README.md ├── blastee.py ├── blaster.py ├── middlebox.py ├── report │ ├── lab_6.pdf │ ├── lab_6_blastee.pcapng │ ├── lab_6_blaster.pcapng │ └── lab_6_middlebox.pcapng ├── start_mininet.py └── testcases │ └── test_submit.py └── lab-7 ├── README.md ├── cachingServer ├── cacheTable.py └── cachingServer.py ├── dnsServer ├── dns_server.py └── dns_table.txt ├── mainServer ├── doc │ ├── index.html │ └── success.jpg └── mainServer.py ├── report ├── 185220001_cache.log ├── 185220001_client.log ├── 185220001_dns.log └── lab_7.pdf ├── requirements.txt ├── runCachingServer.py ├── runDNSServer.py ├── success.jpg ├── testCache_output.txt ├── testDNS.py ├── testDNS_output.txt ├── test_entry.py ├── testcases ├── baseTestcase.py ├── test_all.py ├── test_cache.py ├── test_dns.py └── test_submit.py └── utils ├── dns_utils.py ├── ip_utils.py ├── manageservice.py ├── network.py ├── rpcServer.py └── tracer.py /lab-1/README.md: -------------------------------------------------------------------------------- 1 | # Lab-1 assignment 2 | 3 | Refer to [Lab-1 manual](https://pavinberg.gitbook.io/nju-network-lab/lab-1) 4 | -------------------------------------------------------------------------------- /lab-1/myhub.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | ''' 4 | Ethernet hub in Switchyard. 5 | ''' 6 | import switchyard 7 | from switchyard.lib.userlib import * 8 | 9 | 10 | def main(net: switchyard.llnetbase.LLNetBase): 11 | my_interfaces = net.interfaces() 12 | mymacs = [intf.ethaddr for intf in my_interfaces] 13 | ingress_pkt_num = 0 14 | egress_pkt_num = 0 15 | 16 | while True: 17 | try: 18 | _, fromIface, packet = net.recv_packet() 19 | except NoPackets: 20 | continue 21 | except Shutdown: 22 | break 23 | 24 | ingress_pkt_num += 1 25 | log_debug (f"In {net.name} received packet {packet} on {fromIface}") 26 | eth = packet.get_header(Ethernet) 27 | if eth is None: 28 | log_info("Received a non-Ethernet packet?!") 29 | log_info(f"in:{ingress_pkt_num} out:{egress_pkt_num}") 30 | return 31 | if eth.dst in mymacs: 32 | log_info("Received a packet intended for me") 33 | log_info(f"in:{ingress_pkt_num} out:{egress_pkt_num}") 34 | else: 35 | for intf in my_interfaces: 36 | if fromIface!= intf.name: 37 | egress_pkt_num += 1 38 | log_info (f"Flooding packet {packet} to {intf.name}") 39 | log_info(f"in:{ingress_pkt_num} out:{egress_pkt_num}") 40 | net.send_packet(intf, packet) 41 | 42 | net.shutdown() 43 | -------------------------------------------------------------------------------- /lab-1/report/lab_1.pcapng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vectormoon/Computer-Network-Lab/48868ca1b2e8bbfca8921846844574b840dc111f/lab-1/report/lab_1.pcapng -------------------------------------------------------------------------------- /lab-1/report/lab_1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vectormoon/Computer-Network-Lab/48868ca1b2e8bbfca8921846844574b840dc111f/lab-1/report/lab_1.pdf -------------------------------------------------------------------------------- /lab-1/start_mininet.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import sys 4 | 5 | from mininet.topo import Topo 6 | from mininet.net import Mininet 7 | from mininet.log import lg 8 | from mininet.node import CPULimitedHost 9 | from mininet.link import TCLink 10 | from mininet.util import irange, custom, quietRun, dumpNetConnections 11 | from mininet.cli import CLI 12 | 13 | from time import sleep, time 14 | from subprocess import Popen, PIPE 15 | import subprocess 16 | import argparse 17 | import os 18 | 19 | parser = argparse.ArgumentParser(description="Mininet pyswitch topology") 20 | # no arguments needed as yet :-) 21 | args = parser.parse_args() 22 | lg.setLogLevel('info') 23 | 24 | 25 | nodes = { 26 | "server1": { 27 | "mac": "10:00:00:00:00:{:02x}", 28 | "ip": "192.168.100.1/24" 29 | }, 30 | # "server2": { 31 | # "mac": "20:00:00:00:00:{:02x}", 32 | # "ip": "192.168.100.2/24" 33 | # }, 34 | "client": { 35 | "mac": "30:00:00:00:00:{:02x}", 36 | "ip": "192.168.100.3/24" 37 | }, 38 | "hub": { 39 | "mac": "40:00:00:00:00:{:02x}", 40 | } 41 | } 42 | 43 | 44 | class PySwitchTopo(Topo): 45 | 46 | def __init__(self, args): 47 | # Add default members to class. 48 | super(PySwitchTopo, self).__init__() 49 | 50 | # Host and link configuration 51 | # 52 | # 53 | # server1 54 | # \ 55 | # hub----client 56 | # / 57 | # server2 58 | # 59 | 60 | nodeconfig = {"cpu": -1} 61 | 62 | for node in nodes.keys(): 63 | self.addHost(node, **nodeconfig) 64 | for node in nodes.keys(): 65 | # all links are 10Mb/s, 100 millisecond prop delay 66 | if node != "hub": 67 | self.addLink(node, "hub", bw=10, delay="100ms") 68 | 69 | 70 | def set_ip(net, node1, node2, ip): 71 | node1 = net.get(node1) 72 | ilist = node1.connectionsTo(net.get(node2)) # returns list of tuples 73 | intf = ilist[0] 74 | intf[0].setIP(ip) 75 | 76 | 77 | def reset_macs(net, node, macbase): 78 | ifnum = 1 79 | node_object = net.get(node) 80 | for intf in node_object.intfList(): 81 | node_object.setMAC(macbase.format(ifnum), intf) 82 | ifnum += 1 83 | 84 | for intf in node_object.intfList(): 85 | print(node, intf, node_object.MAC(intf)) 86 | 87 | 88 | def set_route(net, fromnode, prefix, nextnode): 89 | node_object = net.get(fromnode) 90 | ilist = node_object.connectionsTo(net.get(nextnode)) 91 | node_object.setDefaultRoute(ilist[0][0]) 92 | 93 | 94 | def setup_addressing(net): 95 | for node, config in nodes.items(): 96 | reset_macs(net, node, config["mac"]) 97 | if node != "hub": 98 | set_ip(net, node, "hub", config["ip"]) 99 | 100 | 101 | def disable_ipv6(net): 102 | for v in net.values(): 103 | v.cmdPrint('sysctl -w net.ipv6.conf.all.disable_ipv6=1') 104 | v.cmdPrint('sysctl -w net.ipv6.conf.default.disable_ipv6=1') 105 | 106 | 107 | def main(): 108 | topo = PySwitchTopo(args) 109 | net = Mininet(controller=None, topo=topo, link=TCLink, cleanup=True) 110 | setup_addressing(net) 111 | disable_ipv6(net) 112 | net.interact() 113 | 114 | 115 | if __name__ == '__main__': 116 | main() 117 | -------------------------------------------------------------------------------- /lab-1/testcases/myhub_testscenario.py: -------------------------------------------------------------------------------- 1 | from switchyard.lib.userlib import * 2 | 3 | 4 | def new_packet(hwsrc, hwdst, ipsrc, ipdst, reply=False): 5 | ether = Ethernet(src=hwsrc, dst=hwdst, ethertype=EtherType.IP) 6 | ippkt = IPv4(src=ipsrc, dst=ipdst, protocol=IPProtocol.ICMP, ttl=32) 7 | icmppkt = ICMP() 8 | if reply: 9 | icmppkt.icmptype = ICMPType.EchoReply 10 | else: 11 | icmppkt.icmptype = ICMPType.EchoRequest 12 | return ether + ippkt + icmppkt 13 | 14 | 15 | def test_hub(): 16 | s = TestScenario("hub tests") 17 | s.add_interface('eth0', '10:00:00:00:00:01') 18 | s.add_interface('eth1', '10:00:00:00:00:02') 19 | s.add_interface('eth2', '10:00:00:00:00:03') 20 | 21 | # test case 1: a frame with broadcast destination should get sent out 22 | # all ports except ingress 23 | testpkt = new_packet( 24 | "30:00:00:00:00:02", 25 | "ff:ff:ff:ff:ff:ff", 26 | "172.16.42.2", 27 | "255.255.255.255" 28 | ) 29 | s.expect( 30 | PacketInputEvent("eth1", testpkt, display=Ethernet), 31 | ("An Ethernet frame with a broadcast destination address " 32 | "should arrive on eth1") 33 | ) 34 | s.expect( 35 | PacketOutputEvent("eth0", testpkt, "eth2", testpkt, display=Ethernet), 36 | ("The Ethernet frame with a broadcast destination address should be " 37 | "forwarded out ports eth0 and eth2") 38 | ) 39 | 40 | # test case 2: a frame with any unicast address except one assigned to hub 41 | # interface should be sent out all ports except ingress 42 | reqpkt = new_packet( 43 | "20:00:00:00:00:01", 44 | "30:00:00:00:00:02", 45 | '192.168.1.100', 46 | '172.16.42.2' 47 | ) 48 | s.expect( 49 | PacketInputEvent("eth0", reqpkt, display=Ethernet), 50 | ("An Ethernet frame from 20:00:00:00:00:01 to 30:00:00:00:00:02 " 51 | "should arrive on eth0") 52 | ) 53 | s.expect( 54 | PacketOutputEvent("eth1", reqpkt, "eth2", reqpkt, display=Ethernet), 55 | ("Ethernet frame destined for 30:00:00:00:00:02 should be flooded out" 56 | " eth1 and eth2") 57 | ) 58 | 59 | resppkt = new_packet( 60 | "30:00:00:00:00:02", 61 | "20:00:00:00:00:01", 62 | '172.16.42.2', 63 | '192.168.1.100', 64 | reply=True 65 | ) 66 | s.expect( 67 | PacketInputEvent("eth1", resppkt, display=Ethernet), 68 | ("An Ethernet frame from 30:00:00:00:00:02 to 20:00:00:00:00:01 " 69 | "should arrive on eth1") 70 | ) 71 | s.expect( 72 | PacketOutputEvent("eth0", resppkt, "eth2", resppkt, display=Ethernet), 73 | ("Ethernet frame destined to 20:00:00:00:00:01 should be flooded out" 74 | "eth0 and eth2") 75 | ) 76 | 77 | # test case 3: a frame with dest address of one of the interfaces should 78 | # result in nothing happening 79 | reqpkt = new_packet( 80 | "20:00:00:00:00:01", 81 | "10:00:00:00:00:03", 82 | '192.168.1.100', 83 | '172.16.42.2' 84 | ) 85 | s.expect( 86 | PacketInputEvent("eth2", reqpkt, display=Ethernet), 87 | ("An Ethernet frame should arrive on eth2 with destination address " 88 | "the same as eth2's MAC address") 89 | ) 90 | s.expect( 91 | PacketInputTimeoutEvent(1.0), 92 | ("The hub should not do anything in response to a frame arriving with" 93 | " a destination address referring to the hub itself.") 94 | ) 95 | 96 | mypkt = new_packet( 97 | "20:00:00:00:00:01", 98 | "ff:ff:ff:ff:ff:ff", 99 | "192.168.1.100", 100 | "255.255.255.255" 101 | ) 102 | s.expect( 103 | PacketInputEvent("eth2", mypkt, display=Ethernet), 104 | ("An Ethernet frame with a broadcast destination address should arrive on eth2") 105 | ) 106 | s.expect( 107 | PacketOutputEvent("eth0", mypkt, "eth1", mypkt, display=Ethernet), 108 | ("The Ethernet frame with a broadcast destination address should be forwarded out ports eth0 and eth1") 109 | ) 110 | 111 | return s 112 | 113 | 114 | scenario = test_hub() 115 | -------------------------------------------------------------------------------- /lab-1/testcases/test_lab1.py: -------------------------------------------------------------------------------- 1 | from subprocess import Popen, PIPE 2 | from unittest import TestCase 3 | 4 | 5 | class Lab1Test(TestCase): 6 | def test_myhub(self): 7 | p = Popen( 8 | ["swyard", "-t", "testcases/myhub_testscenario.py", "myhub.py"], 9 | stdout=PIPE, stderr=PIPE 10 | ) 11 | output, error = p.communicate() 12 | assert p.returncode == 0 13 | -------------------------------------------------------------------------------- /lab-2/README.md: -------------------------------------------------------------------------------- 1 | # Lab-2 assignment 2 | 3 | Refer to [Lab-2 manual](https://pavinberg.gitbook.io/nju-network-lab/lab-2) 4 | -------------------------------------------------------------------------------- /lab-2/myswitch.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Ethernet learning switch in Python. 3 | 4 | Note that this file currently has the code to implement a "hub" 5 | in it, not a learning switch. (I.e., it's currently a switch 6 | that doesn't learn.) 7 | ''' 8 | import switchyard 9 | from switchyard.lib.userlib import * 10 | 11 | 12 | def main(net: switchyard.llnetbase.LLNetBase): 13 | my_interfaces = net.interfaces() 14 | mymacs = [intf.ethaddr for intf in my_interfaces] 15 | 16 | table = {} 17 | 18 | while True: 19 | try: 20 | _, fromIface, packet = net.recv_packet() 21 | except NoPackets: 22 | continue 23 | except Shutdown: 24 | break 25 | 26 | log_debug (f"In {net.name} received packet {packet} on {fromIface}") 27 | eth = packet.get_header(Ethernet) 28 | # log_info (f"--------------------------{eth.src}-----------------------") 29 | if eth is None: 30 | log_info("Received a non-Ethernet packet?!") 31 | return 32 | if eth.dst in mymacs: 33 | log_info("Received a packet intended for me") 34 | if (table.get(eth.src, -1) == -1): 35 | table[eth.src] = fromIface 36 | log_info(f"Record mac_adress: {eth.src}, interface: {fromIface}") 37 | if eth.dst not in mymacs: 38 | if (table.get(eth.dst, -1) != -1): 39 | net.send_packet(table[eth.dst], packet) 40 | log_info (f"Sending packet {packet} to {table[eth.dst]}") 41 | else: 42 | for intf in my_interfaces: 43 | if fromIface!= intf.name: 44 | log_info (f"Flooding packet {packet} to {intf.name}") 45 | net.send_packet(intf, packet) 46 | 47 | # for k,v in table.items(): 48 | # print("key:", k, "value:", v) 49 | 50 | 51 | net.shutdown() 52 | -------------------------------------------------------------------------------- /lab-2/myswitch_lru.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Ethernet learning switch in Python. 3 | 4 | Note that this file currently has the code to implement a "hub" 5 | in it, not a learning switch. (I.e., it's currently a switch 6 | that doesn't learn.) 7 | ''' 8 | import switchyard 9 | from switchyard.lib.userlib import * 10 | 11 | 12 | def main(net: switchyard.llnetbase.LLNetBase): 13 | my_interfaces = net.interfaces() 14 | mymacs = [intf.ethaddr for intf in my_interfaces] 15 | 16 | table = {} 17 | age = 0 18 | 19 | while True: 20 | try: 21 | _, fromIface, packet = net.recv_packet() 22 | except NoPackets: 23 | continue 24 | except Shutdown: 25 | break 26 | 27 | log_debug (f"In {net.name} received packet {packet} on {fromIface}") 28 | eth = packet.get_header(Ethernet) 29 | # log_info (f"--------------------------{eth.src}-----------------------") 30 | if eth is None: 31 | log_info("Received a non-Ethernet packet?!") 32 | return 33 | if eth.dst in mymacs: 34 | log_info("Received a packet intended for me") 35 | if (table.get(eth.src, -1) == -1): 36 | if (len(table) == 5): 37 | sorted_table = sorted(table.items(), key=lambda x:x[1][1], reverse=True) 38 | del table[sorted_table[0][0]] 39 | for macAddress in list(table.keys()): 40 | table[macAddress][1] += 1 41 | table[eth.src] = [fromIface, age] 42 | log_info(f"Record mac_address: {eth.src}, interface: {fromIface}, age: {age}") 43 | 44 | # log_info("----------test info-----------") 45 | # for k,v in table.items(): 46 | # log_info(f"key: {k}, value: {v}") 47 | # log_info("----------test info-----------") 48 | 49 | elif (table.get(eth.src, -1) != -1): 50 | if table[eth.src][0] != fromIface: 51 | table[eth.src][0] = fromIface 52 | log_info(f"Update port mac_address: {table[eth.src][0]}, interface: {fromIface}") 53 | # log_info("----------test info-----------") 54 | # for k,v in table.items(): 55 | # log_info(f"key: {k}, value: {v}") 56 | # log_info("----------test info-----------") 57 | else: 58 | # log_info("----------test info-----------") 59 | # log_info(f"mac_address: {table[eth.src]}, port: {fromIface} not change") 60 | # log_info("----------test info-----------") 61 | pass 62 | if eth.dst not in mymacs: 63 | if (table.get(eth.dst, -1) != -1): 64 | for macAddress in list(table.keys()): 65 | if (table[macAddress][1] < table[eth.dst][1]): 66 | table[macAddress][1] += 1 67 | table[eth.dst][1] = 0 68 | net.send_packet(table[eth.dst][0], packet) 69 | log_info (f"Sending packet {packet} to {table[eth.dst]}") 70 | # log_info("----------test info-----------") 71 | # for k,v in table.items(): 72 | # log_info(f"key: {k}, value: {v}") 73 | # log_info("----------test info-----------") 74 | else: 75 | for intf in my_interfaces: 76 | if fromIface!= intf.name: 77 | log_info (f"Flooding packet {packet} to {intf.name}") 78 | net.send_packet(intf, packet) 79 | 80 | 81 | 82 | 83 | net.shutdown() 84 | -------------------------------------------------------------------------------- /lab-2/myswitch_to.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Ethernet learning switch in Python. 3 | 4 | Note that this file currently has the code to implement a "hub" 5 | in it, not a learning switch. (I.e., it's currently a switch 6 | that doesn't learn.) 7 | ''' 8 | import switchyard 9 | from switchyard.lib.userlib import * 10 | import time 11 | 12 | 13 | def main(net: switchyard.llnetbase.LLNetBase): 14 | my_interfaces = net.interfaces() 15 | mymacs = [intf.ethaddr for intf in my_interfaces] 16 | 17 | table = {} 18 | 19 | while True: 20 | try: 21 | _, fromIface, packet = net.recv_packet() 22 | except NoPackets: 23 | continue 24 | except Shutdown: 25 | break 26 | 27 | for macAddress in list(table.keys()): 28 | if (time.time() - table[macAddress][1]) > 10.0: 29 | del table[macAddress] 30 | 31 | log_debug (f"In {net.name} received packet {packet} on {fromIface}") 32 | eth = packet.get_header(Ethernet) 33 | # log_info (f"--------------------------{eth.src}-----------------------") 34 | # log_info (f"--------------------------{_}-----------------------") 35 | if eth is None: 36 | log_info("Received a non-Ethernet packet?!") 37 | return 38 | if eth.dst in mymacs: 39 | log_info("Received a packet intended for me") 40 | elif (table.get(eth.src, -1) == -1): 41 | table[eth.src] = [fromIface, time.time()] 42 | log_info(f"Record mac_adress: {eth.src}, interface: {fromIface}, timestamp: {time.time()}") 43 | else: 44 | if (fromIface == table[eth.src][0]): 45 | table[eth.src][1] = time.time() 46 | else: 47 | table[eth.src] = [fromIface, time.time()] 48 | log_info(f"Update mac_adress: {eth.src}, interface: {fromIface}, timestamp: {time.time()}") 49 | 50 | if eth.dst not in mymacs: 51 | if (table.get(eth.dst, -1) != -1): 52 | net.send_packet(table[eth.dst][0], packet) 53 | log_info (f"Sending packet {packet} to {table[eth.dst][0]}") 54 | else: 55 | for intf in my_interfaces: 56 | if fromIface!= intf.name: 57 | log_info (f"Flooding packet {packet} to {intf.name}") 58 | net.send_packet(intf, packet) 59 | 60 | # for k,v in table.items(): 61 | # print("key:", k, "value:", v) 62 | 63 | 64 | net.shutdown() 65 | -------------------------------------------------------------------------------- /lab-2/myswitch_traffic.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Ethernet learning switch in Python. 3 | 4 | Note that this file currently has the code to implement a "hub" 5 | in it, not a learning switch. (I.e., it's currently a switch 6 | that doesn't learn.) 7 | ''' 8 | import switchyard 9 | from switchyard.lib.userlib import * 10 | 11 | 12 | def main(net: switchyard.llnetbase.LLNetBase): 13 | my_interfaces = net.interfaces() 14 | mymacs = [intf.ethaddr for intf in my_interfaces] 15 | 16 | table = {} 17 | traffic = 0 18 | 19 | while True: 20 | try: 21 | _, fromIface, packet = net.recv_packet() 22 | except NoPackets: 23 | continue 24 | except Shutdown: 25 | break 26 | 27 | log_debug (f"In {net.name} received packet {packet} on {fromIface}") 28 | eth = packet.get_header(Ethernet) 29 | if eth is None: 30 | log_info("Received a non-Ethernet packet?!") 31 | return 32 | if eth.dst in mymacs: 33 | log_info("Received a packet intended for me") 34 | if (table.get(eth.src, -1) == -1): 35 | if (len(table) == 5): 36 | sorted_table = sorted(table.items(), key=lambda x:x[1][1], reverse=False) 37 | del table[sorted_table[0][0]] 38 | table[eth.src] = [fromIface, traffic] 39 | log_info(f"Record mac_address: {eth.src}, interface: {fromIface}, traffic: {traffic}") 40 | elif (table.get(eth.src, -1) != -1): 41 | if table[eth.src][0] != fromIface: 42 | table[eth.src][0] = fromIface 43 | log_info(f"Update port mac_address: {table[eth.src][0]}, interface: {fromIface}") 44 | if eth.dst not in mymacs: 45 | if (table.get(eth.dst, -1) != -1): 46 | table[eth.dst][1] += 1 47 | net.send_packet(table[eth.dst][0], packet) 48 | log_info (f"Sending packet {packet} to {table[eth.dst]}") 49 | else: 50 | for intf in my_interfaces: 51 | if fromIface!= intf.name: 52 | log_info (f"Flooding packet {packet} to {intf.name}") 53 | net.send_packet(intf, packet) 54 | 55 | 56 | 57 | 58 | net.shutdown() 59 | -------------------------------------------------------------------------------- /lab-2/report/lab_2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vectormoon/Computer-Network-Lab/48868ca1b2e8bbfca8921846844574b840dc111f/lab-2/report/lab_2.pdf -------------------------------------------------------------------------------- /lab-2/report/lab_2_task2_server1.pcapng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vectormoon/Computer-Network-Lab/48868ca1b2e8bbfca8921846844574b840dc111f/lab-2/report/lab_2_task2_server1.pcapng -------------------------------------------------------------------------------- /lab-2/report/lab_2_task2_server2.pcapng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vectormoon/Computer-Network-Lab/48868ca1b2e8bbfca8921846844574b840dc111f/lab-2/report/lab_2_task2_server2.pcapng -------------------------------------------------------------------------------- /lab-2/report/lab_2_task3_server1.pcapng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vectormoon/Computer-Network-Lab/48868ca1b2e8bbfca8921846844574b840dc111f/lab-2/report/lab_2_task3_server1.pcapng -------------------------------------------------------------------------------- /lab-2/report/lab_2_task3_server2.pcapng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vectormoon/Computer-Network-Lab/48868ca1b2e8bbfca8921846844574b840dc111f/lab-2/report/lab_2_task3_server2.pcapng -------------------------------------------------------------------------------- /lab-2/report/lab_2_task4_client.pcapng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vectormoon/Computer-Network-Lab/48868ca1b2e8bbfca8921846844574b840dc111f/lab-2/report/lab_2_task4_client.pcapng -------------------------------------------------------------------------------- /lab-2/report/lab_2_task4_server1.pcapng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vectormoon/Computer-Network-Lab/48868ca1b2e8bbfca8921846844574b840dc111f/lab-2/report/lab_2_task4_server1.pcapng -------------------------------------------------------------------------------- /lab-2/report/lab_2_task4_server2.pcapng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vectormoon/Computer-Network-Lab/48868ca1b2e8bbfca8921846844574b840dc111f/lab-2/report/lab_2_task4_server2.pcapng -------------------------------------------------------------------------------- /lab-2/report/lab_2_task5_client.pcapng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vectormoon/Computer-Network-Lab/48868ca1b2e8bbfca8921846844574b840dc111f/lab-2/report/lab_2_task5_client.pcapng -------------------------------------------------------------------------------- /lab-2/report/lab_2_task5_server1.pcapng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vectormoon/Computer-Network-Lab/48868ca1b2e8bbfca8921846844574b840dc111f/lab-2/report/lab_2_task5_server1.pcapng -------------------------------------------------------------------------------- /lab-2/report/lab_2_task5_server2.pcapng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vectormoon/Computer-Network-Lab/48868ca1b2e8bbfca8921846844574b840dc111f/lab-2/report/lab_2_task5_server2.pcapng -------------------------------------------------------------------------------- /lab-2/start_mininet.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import sys 4 | 5 | from mininet.topo import Topo 6 | from mininet.net import Mininet 7 | from mininet.log import lg 8 | from mininet.node import CPULimitedHost 9 | from mininet.link import TCLink 10 | from mininet.util import irange, custom, quietRun, dumpNetConnections 11 | from mininet.cli import CLI 12 | 13 | from time import sleep, time 14 | from subprocess import Popen, PIPE 15 | import subprocess 16 | import argparse 17 | import os 18 | 19 | parser = argparse.ArgumentParser(description="Mininet pyswitch topology") 20 | # no arguments needed as yet :-) 21 | args = parser.parse_args() 22 | lg.setLogLevel('info') 23 | 24 | class PySwitchTopo(Topo): 25 | 26 | def __init__(self, args): 27 | # Add default members to class. 28 | super(PySwitchTopo, self).__init__() 29 | 30 | # Host and link configuration 31 | # 32 | # 33 | # server1 34 | # \ 35 | # switch----client 36 | # / 37 | # server2 38 | # 39 | 40 | nodeconfig = {'cpu':-1} 41 | self.addHost('server1', **nodeconfig) 42 | self.addHost('server2', **nodeconfig) 43 | self.addHost('switch', **nodeconfig) 44 | self.addHost('client', **nodeconfig) 45 | 46 | for node in ['server1','server2','client']: 47 | # all links are 10Mb/s, 100 millisecond prop delay 48 | self.addLink(node, 'switch', bw=10, delay='100ms') 49 | 50 | def set_ip(net, node1, node2, ip): 51 | node1 = net.get(node1) 52 | ilist = node1.connectionsTo(net.get(node2)) # returns list of tuples 53 | intf = ilist[0] 54 | intf[0].setIP(ip) 55 | 56 | def reset_macs(net, node, macbase): 57 | ifnum = 1 58 | node_object = net.get(node) 59 | for intf in node_object.intfList(): 60 | node_object.setMAC(macbase.format(ifnum), intf) 61 | ifnum += 1 62 | 63 | for intf in node_object.intfList(): 64 | print(node,intf,node_object.MAC(intf)) 65 | 66 | def set_route(net, fromnode, prefix, nextnode): 67 | node_object = net.get(fromnode) 68 | ilist = node_object.connectionsTo(net.get(nextnode)) 69 | node_object.setDefaultRoute(ilist[0][0]) 70 | 71 | def setup_addressing(net): 72 | reset_macs(net, 'server1', '10:00:00:00:00:{:02x}') 73 | reset_macs(net, 'server2', '20:00:00:00:00:{:02x}') 74 | reset_macs(net, 'client', '30:00:00:00:00:{:02x}') 75 | reset_macs(net, 'switch', '40:00:00:00:00:{:02x}') 76 | set_ip(net, 'server1','switch','192.168.100.1/24') 77 | set_ip(net, 'server2','switch','192.168.100.2/24') 78 | set_ip(net, 'client','switch','192.168.100.3/24') 79 | 80 | def disable_ipv6(net): 81 | for v in net.values(): 82 | v.cmdPrint('sysctl -w net.ipv6.conf.all.disable_ipv6=1') 83 | v.cmdPrint('sysctl -w net.ipv6.conf.default.disable_ipv6=1') 84 | 85 | def main(): 86 | topo = PySwitchTopo(args) 87 | net = Mininet(controller=None, topo=topo, link=TCLink, cleanup=True) 88 | setup_addressing(net) 89 | disable_ipv6(net) 90 | net.interact() 91 | 92 | if __name__ == '__main__': 93 | main() 94 | -------------------------------------------------------------------------------- /lab-2/testcases/myswitch_lru_testscenario.srpy: -------------------------------------------------------------------------------- 1 | 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 -------------------------------------------------------------------------------- /lab-2/testcases/myswitch_to_testscenario.srpy: -------------------------------------------------------------------------------- 1 | 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 -------------------------------------------------------------------------------- /lab-2/testcases/myswitch_traffic_testscenario.srpy: -------------------------------------------------------------------------------- 1 | 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 -------------------------------------------------------------------------------- /lab-2/testcases/mytestscenario.py: -------------------------------------------------------------------------------- 1 | from switchyard.lib.userlib import * 2 | 3 | 4 | def new_packet(hwsrc, hwdst, ipsrc, ipdst, reply=False): 5 | ether = Ethernet(src=hwsrc, dst=hwdst, ethertype=EtherType.IP) 6 | ippkt = IPv4(src=ipsrc, dst=ipdst, protocol=IPProtocol.ICMP, ttl=32) 7 | icmppkt = ICMP() 8 | if reply: 9 | icmppkt.icmptype = ICMPType.EchoReply 10 | else: 11 | icmppkt.icmptype = ICMPType.EchoRequest 12 | return ether + ippkt + icmppkt 13 | 14 | 15 | def test_switch(): 16 | s = TestScenario("switch tests") 17 | s.add_interface('eth0', '10:00:00:00:00:01') 18 | s.add_interface('eth1', '10:00:00:00:00:02') 19 | s.add_interface('eth2', '10:00:00:00:00:03') 20 | 21 | # test case 1: a frame with broadcast destination should get sent out 22 | # all ports except ingress 23 | testpkt = new_packet( 24 | "30:00:00:00:00:02", 25 | "ff:ff:ff:ff:ff:ff", 26 | "172.16.42.2", 27 | "255.255.255.255" 28 | ) 29 | s.expect( 30 | PacketInputEvent("eth1", testpkt, display=Ethernet), 31 | ("An Ethernet frame with a broadcast destination address " 32 | "should arrive on eth1") 33 | ) 34 | s.expect( 35 | PacketOutputEvent("eth0", testpkt, "eth2", testpkt, display=Ethernet), 36 | ("The Ethernet frame with a broadcast destination address should be " 37 | "forwarded out ports eth0 and eth2") 38 | ) 39 | 40 | # test case 1: a frame with broadcast destination should get sent out 41 | # all ports except ingress 42 | testpkt = new_packet( 43 | "30:00:00:00:00:01", 44 | "20:00:00:00:00:01", 45 | "192.168.100.3", 46 | "192.168.100.1" 47 | ) 48 | s.expect( 49 | PacketInputEvent("eth2", testpkt, display=Ethernet), 50 | ("An Ethernet frame from 30:00:00:00:00:01 to 20:00:00:00:00:01 " 51 | "should arrive on eth2") 52 | ) 53 | s.expect( 54 | PacketOutputEvent("eth1", testpkt, "eth0", testpkt, display=Ethernet), 55 | ("Ethernet frame destined to 20:00:00:00:00:01 should be flooded out" 56 | "eth0 and eth2") 57 | ) 58 | 59 | return s 60 | 61 | 62 | scenario = test_switch() -------------------------------------------------------------------------------- /lab-3/README.md: -------------------------------------------------------------------------------- 1 | # Lab-3 assignment 2 | 3 | Refer to [Lab-3 manual](https://pavinberg.gitbook.io/nju-network-lab/ipv4-router/lab-3) 4 | -------------------------------------------------------------------------------- /lab-3/forwarding_table.txt: -------------------------------------------------------------------------------- 1 | 192.168.100.0 255.255.255.0 192.168.100.1 router-eth0 2 | 192.168.200.0 255.255.255.0 192.168.200.1 router-eth1 3 | 10.1.0.0 255.255.0.0 10.1.1.1 router-eth2 -------------------------------------------------------------------------------- /lab-3/myrouter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | ''' 4 | Basic IPv4 router (static routing) in Python. 5 | ''' 6 | 7 | import time 8 | import switchyard 9 | from switchyard.lib.userlib import * 10 | 11 | 12 | class Router(object): 13 | def __init__(self, net: switchyard.llnetbase.LLNetBase): 14 | self.net = net 15 | # other initialization stuff here 16 | self.interfaces = net.interfaces() 17 | self.ip_list = [] 18 | self.arp_table = {} 19 | for i in self.interfaces: 20 | self.ip_list.append(i.ipaddr) 21 | 22 | def handle_packet(self, recv: switchyard.llnetbase.ReceivedPacket): 23 | timestamp, ifaceName, packet = recv 24 | # TODO: your logic here 25 | arp = packet.get_header(Arp) 26 | 27 | input_port = self.net.interface_by_name(ifaceName) 28 | # log_info (f"-------------{input_port.ethaddr}----------------") 29 | 30 | if arp is None: 31 | log_info ("No ARP packet") 32 | else: 33 | self.arp_table[arp.senderprotoaddr] = arp.senderhwaddr 34 | log_info ("------------------------arp_table_info------------------------") 35 | for k,v in self.arp_table.items(): 36 | log_info (f" IP: {k}; MAC Address: {v}") 37 | log_info ("--------------------------------------------------------------") 38 | 39 | targetip_exist_flag = -1 40 | for i in range(len(self.ip_list)): 41 | if (self.ip_list[i] == arp.targetprotoaddr): 42 | targetip_exist_flag = i 43 | # log_info (f"-------------{self.ip_list[targetip_exist_flag]}----------------") 44 | 45 | if (targetip_exist_flag != -1): 46 | arp_reply_pkt = create_ip_arp_reply(input_port.ethaddr, arp.senderhwaddr, arp.targetprotoaddr, arp.senderprotoaddr) 47 | self.net.send_packet(ifaceName, arp_reply_pkt) 48 | log_info (f"Sending packet {arp_reply_pkt} to {ifaceName}") 49 | 50 | # def my_timeout(self, time_to_delete): 51 | # pass 52 | 53 | def start(self): 54 | '''A running daemon of the router. 55 | Receive packets until the end of time. 56 | ''' 57 | while True: 58 | try: 59 | recv = self.net.recv_packet(timeout=1.0) 60 | except NoPackets: 61 | continue 62 | except Shutdown: 63 | break 64 | 65 | self.handle_packet(recv) 66 | 67 | self.stop() 68 | 69 | def stop(self): 70 | self.net.shutdown() 71 | 72 | 73 | def main(net): 74 | ''' 75 | Main entry point for router. Just create Router 76 | object and get it going. 77 | ''' 78 | router = Router(net) 79 | router.start() 80 | -------------------------------------------------------------------------------- /lab-3/report/lab_3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vectormoon/Computer-Network-Lab/48868ca1b2e8bbfca8921846844574b840dc111f/lab-3/report/lab_3.pdf -------------------------------------------------------------------------------- /lab-3/report/lab_3_task2_server1.pcapng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vectormoon/Computer-Network-Lab/48868ca1b2e8bbfca8921846844574b840dc111f/lab-3/report/lab_3_task2_server1.pcapng -------------------------------------------------------------------------------- /lab-3/start_mininet.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import sys 4 | 5 | from mininet.topo import Topo 6 | from mininet.net import Mininet 7 | from mininet.log import lg 8 | from mininet.node import CPULimitedHost 9 | from mininet.link import TCLink 10 | from mininet.util import irange, custom, quietRun, dumpNetConnections 11 | from mininet.cli import CLI 12 | 13 | from time import sleep, time 14 | from subprocess import Popen, PIPE 15 | import subprocess 16 | import argparse 17 | import os 18 | 19 | parser = argparse.ArgumentParser(description="Mininet portion of pyrouter") 20 | # no arguments needed as yet :-) 21 | args = parser.parse_args() 22 | lg.setLogLevel('info') 23 | 24 | class PyRouterTopo(Topo): 25 | 26 | def __init__(self, args): 27 | # Add default members to class. 28 | super(PyRouterTopo, self).__init__() 29 | 30 | # Host and link configuration 31 | # 32 | # 33 | # server1 34 | # \ 35 | # router----client 36 | # / 37 | # server2 38 | # 39 | 40 | nodeconfig = {'cpu':-1} 41 | self.addHost('server1', **nodeconfig) 42 | self.addHost('server2', **nodeconfig) 43 | self.addHost('router', **nodeconfig) 44 | self.addHost('client', **nodeconfig) 45 | 46 | linkconfig = { 47 | 'bw': 10, 48 | 'delay': 0.010, 49 | 'loss': 0.0 50 | } 51 | 52 | for node in ['server1','server2','client']: 53 | self.addLink(node, 'router', **linkconfig) 54 | 55 | def set_ip_pair(net, node1, node2, ip1, ip2): 56 | node1 = net.get(node1) 57 | ilist = node1.connectionsTo(net.get(node2)) # returns list of tuples 58 | intf = ilist[0] 59 | intf[0].setIP(ip1) 60 | intf[1].setIP(ip2) 61 | 62 | def reset_macs(net, node, macbase): 63 | ifnum = 1 64 | node_object = net.get(node) 65 | for intf in node_object.intfList(): 66 | node_object.setMAC(macbase.format(ifnum), intf) 67 | ifnum += 1 68 | 69 | for intf in node_object.intfList(): 70 | print(node,intf,node_object.MAC(intf)) 71 | 72 | def set_route(net, fromnode, prefix, gw): 73 | node_object = net.get(fromnode) 74 | node_object.cmdPrint("route add -net {} gw {}".format(prefix, gw)) 75 | 76 | def setup_addressing(net): 77 | reset_macs(net, 'server1', '10:00:00:00:00:{:02x}') 78 | reset_macs(net, 'server2', '20:00:00:00:00:{:02x}') 79 | reset_macs(net, 'client', '30:00:00:00:00:{:02x}') 80 | reset_macs(net, 'router', '40:00:00:00:00:{:02x}') 81 | set_ip_pair(net, 'server1', 'router', '192.168.100.1/30', '192.168.100.2/30') 82 | set_ip_pair(net, 'server2', 'router', '192.168.200.1/30', '192.168.200.2/30') 83 | set_ip_pair(net, 'client', 'router', '10.1.1.1/30', '10.1.1.2/30') 84 | set_route(net, 'server1', '10.1.0.0/16', '192.168.100.2') 85 | set_route(net, 'server1', '192.168.200.0/24', '192.168.100.2') 86 | set_route(net, 'server2', '10.1.0.0/16', '192.168.200.2') 87 | set_route(net, 'server2', '192.168.100.0/24', '192.168.200.2') 88 | set_route(net, 'client', '192.168.100.0/24', '10.1.1.2') 89 | set_route(net, 'client', '192.168.200.0/24', '10.1.1.2') 90 | set_route(net, 'client', '172.16.0.0/16', '10.1.1.2') 91 | 92 | table = ( 93 | "192.168.100.0 255.255.255.0 192.168.100.1 router-eth0\n" 94 | "192.168.200.0 255.255.255.0 192.168.200.1 router-eth1\n" 95 | "10.1.0.0 255.255.0.0 10.1.1.1 router-eth2" 96 | ) 97 | with open('forwarding_table.txt', 'w') as fp: 98 | fp.write(table) 99 | 100 | def disable_ipv6(net): 101 | for v in net.values(): 102 | v.cmdPrint('sysctl -w net.ipv6.conf.all.disable_ipv6=1') 103 | v.cmdPrint('sysctl -w net.ipv6.conf.default.disable_ipv6=1') 104 | 105 | def main(): 106 | topo = PyRouterTopo(args) 107 | net = Mininet(topo=topo, link=TCLink, cleanup=True, controller=None) 108 | setup_addressing(net) 109 | disable_ipv6(net) 110 | net.interact() 111 | 112 | if __name__ == '__main__': 113 | main() 114 | -------------------------------------------------------------------------------- /lab-3/testcases/myrouter1_testscenario.srpy: -------------------------------------------------------------------------------- 1 | QlpoOTFBWSZTWVhrYlAABKV///////Pad/3l6+//uv/v///j1l6AmmsQHUFTBEMBANQx0AS+dSmOtzuOzqruToMlGkwRppNTZTZT0maTT1NN6iMmajEyBk0B6mI0AaMjQNNDQxD1NGg0BoNA02mUGhBT00RT9U/aUzTUj1DQADQaNAA9QAAAAAANNNBoNAAAAAAJTRRMiGgjJkGgDQHqAMho0BoADIAAA0NAaAANBoAABoQYBDRiaAyaYTJpkAGEaNMAmAJhBoZAxMgDAACYmTATATQJFImFTGJNPU2k9INBo0A0ANNDQAAANAAAAAABpoAAD1ALqzbTGGPiEYevZUK1KTPO8aEbh12CNGy0QwwpUbglAYixQ07t34X3EDcnmJyZBasiJIDZFTgEkOf4QiVONCe47rQVrDv2KRFqVrErGuaQCuCjYizTqV4YyUUuogGUoioEzmP1HMSpQYiA0MwYBSsNZRoqCnQacTHATVjXLyyl1A5Ds5g8PwwSI4/RG5Ll8g6SuyTfq/ZLrqWG2qneJ5i/TTImmGoE23WbmqwZeURYALXsC5uCF8PfLMyxBPLdF61e0qKD2RKJjvGcevcZA6vL6qErEZwSla2ymZK+RsqJIZ20KgEmYsy0Q417dwKVsKXHVN61VRmwHmq1gWKOJ6aLGNVm2acoL33rX4moBnpVZdfWUEEgaW1nckJmp2zkxd5iYFmJ8Rslq48z+Vi0HFohGHt3cZESIR2UNg3lU+/htDilABCvmSefF38MyQhyopL3cxIgEx4NEOXeBpVGADGtVgeWhdzLREsOiofLH5j+QJd/VEbKNrG18taTeWckaxIbaarFL4MrA+8rVA9UUj9tsdhTgghZLcEhvsFaeicYMx8ESy+qiMUHHXaYEIOO3YF8Ut3XJsiF5wdBqcOOODjUL2XvMyp3Q8NXFihnYLKsHkxMlKKJTYIFCGdlAcRCYkcViqTS1M5HbmE3V+VgpVEDsJ7ZmItZCKZEwVYhVEKbFQEWdiNt0EWwdsmkiI4U5PAl2CAr2reEImigT+M3Sg1x1riWAQ0NDOMELlwxVkFdIwGXYHsPOWnIQsM7GtQoTRmBYQWOaJ7oYrJqkBC4LxeLwiqMoKEBG9G2CShRAvQ07Dl3Tq8xDWhl9dj4KYeICwU/WswMgsV2Fo+v2e2TuAbJ0RxGNvbvmexbR1JbKVCmsOy3AVwJBAlKlEqWjBUpKrGit0edRO1AckFLpIZt5Vvopy2YhE6lC3sm2iBSAjBhoE63srlFNOhvIrC0MNsNa8EyCyeQBjXAyAqyB93djmOoKBNFNWGOITzmS10urCXJwVJUkqkpaCjE0K3dajIBWpUZKaCYspYxDG1YBAlqtQqmQMtMZGGutGBh45WWIwSGa2QDxPqZrwLIDTzhwer1gVGU06LJO52e7vO9HdrDE89gy3Fi6tOFK1kUQ1IStLaUUOEQJxAogQulxEBgg6DQ0OuI2ViQKXEqU8vG6hcWc0jIy6hMTKFd+QNCMwRTxmNpoJ53cFyRoWA1ozEY+fBZSTJlgPSjFWFXoriuRqMDqMjYMAxEhgciZRVSQyhZNaoB0iqr4hSmGsEaBkwYblurAyTK456zyN1QwlbonenDPhbaGiRwJ4yZoJNbI4b5SK5k5UBEO4jaPOu6jiCgigadTIqMEHToEBCgCINyDXzh0BXXmcLPiNWlg39QUsvsAo/cFdiBp98xNBccoJctEQTMJyF2NiGLRlxsxkIkGaD4gsK57FDZ1b11LZpjqKsJcHDb21MfCJ72BUHsXLukeYaF+utGTJXj7v2qi/yk3SIgY9WmNd7qUa/W1g3YoMvPAvdW8A1vJc1epqyQGQtg1KYpPw9fZy8ynUA1wQLputNx/xdyRThQkFhrYlA= -------------------------------------------------------------------------------- /lab-3/testcases/test_submit.py: -------------------------------------------------------------------------------- 1 | import re 2 | import pathlib 3 | from unittest import TestCase 4 | 5 | 6 | labNum = 3 # 1-7 7 | reportPattern = f"\\d{{9}}[\\u4e00-\\u9fa5]+_lab_{labNum}" 8 | 9 | 10 | class TestDir(TestCase): 11 | def test_check_report(self): 12 | workingDir = pathlib.Path(".") 13 | reportDir = workingDir / "report" 14 | self.assertTrue( 15 | reportDir.exists(), 16 | "Report directory `report/` doesn't exist" 17 | ) 18 | reports = list(reportDir.glob("*.pdf")) 19 | self.assertNotEqual(len(reports), 0, "No report PDF found") 20 | self.assertEqual(len(reports), 1, "More than one report PDF found") 21 | for report in reports: 22 | self.assertTrue( 23 | re.match(reportPattern, report.stem) is not None, 24 | "Wrong name of report PDF" 25 | ) 26 | for capfile in reportDir.glob("*.pcap*"): 27 | self.assertTrue( 28 | capfile.stem.startswith(f"lab_{labNum}"), 29 | f"Wrong name of capture file '{capfile}'. " 30 | f"Should start with 'lab_{labNum}'" 31 | ) 32 | 33 | def test_check_src(self): 34 | workingDir = pathlib.Path(".") 35 | srcNames = { 36 | "myrouter.py", 37 | } 38 | pyfiles = set(map(lambda f: f.name, workingDir.glob("*.py"))) 39 | for srcfile in srcNames: 40 | self.assertTrue( 41 | srcfile in pyfiles, 42 | f"'{srcfile}' cannot be found" 43 | ) 44 | -------------------------------------------------------------------------------- /lab-4/README.md: -------------------------------------------------------------------------------- 1 | # Lab-4 assignment 2 | 3 | Refer to [Lab-4 manual](https://pavinberg.gitbook.io/nju-network-lab/ipv4-router/lab-4) 4 | -------------------------------------------------------------------------------- /lab-4/forwarding_table.txt: -------------------------------------------------------------------------------- 1 | 172.16.0.0 255.255.0.0 192.168.1.2 router-eth0 2 | 172.16.128.0 255.255.192.0 10.10.0.254 router-eth1 3 | 172.16.64.0 255.255.192.0 10.10.1.254 router-eth1 4 | 10.100.0.0 255.255.0.0 172.16.42.2 router-eth2 -------------------------------------------------------------------------------- /lab-4/myrouter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | ''' 4 | Basic IPv4 router (static routing) in Python. 5 | ''' 6 | 7 | import time 8 | import switchyard 9 | from switchyard.lib.userlib import * 10 | from switchyard.lib.logging import * 11 | import rebulid_pkt 12 | import queue 13 | 14 | 15 | class Router(object): 16 | def __init__(self, net: switchyard.llnetbase.LLNetBase): 17 | self.net = net 18 | # other initialization stuff here 19 | self.interfaces = net.interfaces() 20 | self.arp_table = {} 21 | 22 | self.ip_list = [] 23 | self.eth_list = [] 24 | for i in self.interfaces: 25 | self.ip_list.append(i.ipaddr) 26 | self.eth_list.append(i.ethaddr) 27 | 28 | self.forwarding_table = {} 29 | # init by net.interfaces() 30 | for i in self.interfaces: 31 | my_netmask = i.netmask 32 | my_ipaddr = i.ipaddr 33 | log_debug (f"type of my_ipaddr: {type(my_ipaddr)}; type of my_netmask: {type(my_netmask)}") 34 | log_debug (f"value of my_ipaddr: {my_ipaddr}; value of my_netmask: {my_netmask}; value of i.netmask: {i.netmask}") 35 | sub_network_address = IPv4Address(ip_address((int(my_ipaddr) & int(my_netmask)))) 36 | log_debug (f"value of subnet address: {sub_network_address}\n") 37 | self.forwarding_table[sub_network_address] = [my_netmask, '0.0.0.0', i.name] 38 | self.print_forwarding_table() 39 | # init by forwarding_table.txt 40 | with open('forwarding_table.txt') as f: 41 | while True: 42 | line = f.readline() 43 | if not line: 44 | break 45 | else: 46 | table_info = line.split() 47 | self.forwarding_table[IPv4Address(table_info[0])] = [IPv4Address(table_info[1]), IPv4Address(table_info[2]), table_info[3]] 48 | self.print_forwarding_table() 49 | 50 | self.packet_queue = [] 51 | 52 | 53 | def print_forwarding_table(self): 54 | log_info ("------------------------forwarding_table_info------------------------") 55 | for k,v in self.forwarding_table.items(): 56 | log_info (f" address: {k}; other: {v}. ") 57 | log_info ("----------------------------------------------------------------------") 58 | 59 | 60 | def handle_packet(self, recv: switchyard.llnetbase.ReceivedPacket): 61 | timestamp, ifaceName, packet = recv 62 | # TODO: your logic here 63 | arp = packet.get_header(Arp) 64 | ipv4 = packet.get_header(IPv4) 65 | 66 | input_port = self.net.interface_by_name(ifaceName) 67 | 68 | if (arp is not None): 69 | self.arp_table[arp.senderprotoaddr] = arp.senderhwaddr 70 | 71 | log_info ("------------------------arp_table_info------------------------") 72 | for k,v in self.arp_table.items(): 73 | log_info (f" IP: {k}; MAC Address: {v}") 74 | log_info ("--------------------------------------------------------------") 75 | if arp.operation == ArpOperation.Request: 76 | targetip_exist_flag = -1 77 | for i in range(len(self.ip_list)): 78 | if (self.ip_list[i] == arp.targetprotoaddr): 79 | targetip_exist_flag = i 80 | if (targetip_exist_flag != -1): 81 | arp_reply_pkt = create_ip_arp_reply(input_port.ethaddr, arp.senderhwaddr, arp.targetprotoaddr, arp.senderprotoaddr) 82 | self.net.send_packet(ifaceName, arp_reply_pkt) 83 | log_info (f"Sending arp reply {arp_reply_pkt} to {ifaceName}") 84 | elif (ipv4 is not None): 85 | ipv4.ttl = ipv4.ttl - 1 86 | # ------------Task2: IP Forwarding Table Lookup ------------ 87 | drop_flag = 0 88 | match_subnet = '0.0.0.0' 89 | match = 0 90 | # match router port, True->drop it 91 | for address in self.interfaces: 92 | if (ipv4.dst == address.ipaddr): 93 | # drop 94 | drop_flag = 1 95 | # longest prefix matching rule 96 | if (drop_flag == 0): 97 | prefix_len = 0 98 | for address in self.forwarding_table.keys(): 99 | prefix = IPv4Address(address) 100 | destaddr = ipv4.dst 101 | matches = (int(prefix) & int(destaddr)) == int(prefix) 102 | if (matches): 103 | netaddr = IPv4Network(str(address) + '/' + str(self.forwarding_table[address][0])) 104 | if (netaddr.prefixlen > prefix_len): 105 | prefix_len = netaddr.prefixlen 106 | match_subnet = IPv4Address(address) 107 | match = 1 108 | # no match in table 109 | if (match == 0): 110 | drop_flag = 1 111 | # ---------------------------------------------------------- 112 | # rebulid packet 113 | if (drop_flag == 0): 114 | # put rebulid packet in queue 115 | if self.forwarding_table[match_subnet][1] == '0.0.0.0': 116 | dstip = IPv4Address(ipv4.dst) 117 | else: 118 | dstip = IPv4Address(self.forwarding_table[match_subnet][1]) 119 | pkt = rebulid_pkt.rebulid_pkt(packet, match_subnet, self.forwarding_table[match_subnet][2], dstip) 120 | self.packet_queue.append(pkt) 121 | 122 | # ------------Task3: Forwarding the Packet and ARP ------------ 123 | def forwarding(self): 124 | if (len(self.packet_queue) == 0): 125 | return 126 | handle_pkt = self.packet_queue[0] 127 | targetipaddr = handle_pkt.get_targetipaddress() 128 | router_send_to_host_port_name = handle_pkt.get_send_out_port() 129 | my_packet = handle_pkt.get_packet() 130 | router_forwarding_port_info = self.net.interface_by_name(router_send_to_host_port_name) 131 | if (targetipaddr in self.arp_table.keys()): 132 | # search arp table 133 | self.forwarding_packet(my_packet, router_send_to_host_port_name, targetipaddr, router_forwarding_port_info) 134 | elif (handle_pkt.get_num_of_retries() < 5): 135 | # send arp request 136 | self.send_arp_request(handle_pkt, router_forwarding_port_info, targetipaddr, router_send_to_host_port_name) 137 | elif (handle_pkt.get_num_of_retries() >= 5): 138 | # delete 139 | log_info (f"Delete packet {self.packet_queue[0].get_packet()}") 140 | del(self.packet_queue[0]) 141 | # ---------------------------------------------------------- 142 | 143 | def forwarding_packet(self, my_packet, router_send_to_host_port_name, targetipaddr, router_forwarding_port_info): 144 | my_packet[Ethernet].src = router_forwarding_port_info.ethaddr 145 | my_packet[Ethernet].dst = self.arp_table[targetipaddr] 146 | self.net.send_packet(router_send_to_host_port_name, my_packet) 147 | log_info (f"Forwarding packet {my_packet} to {router_send_to_host_port_name}") 148 | del(self.packet_queue[0]) 149 | 150 | def send_arp_request(self, handle_pkt, router_forwarding_port_info, targetipaddr, router_send_to_host_port_name): 151 | retry_flag = 0 152 | if ((time.time() - handle_pkt.get_recent_time()) > 1.0): 153 | retry_flag = 1 154 | if (retry_flag): 155 | arppacket = create_ip_arp_request(router_forwarding_port_info.ethaddr,router_forwarding_port_info.ipaddr,targetipaddr) 156 | handle_pkt.try_to_send() 157 | handle_pkt.update_time() 158 | self.net.send_packet(router_send_to_host_port_name, arppacket) 159 | log_info (f"Sending arp request {arppacket} to {router_send_to_host_port_name}") 160 | 161 | def start(self): 162 | '''A running daemon of the router. 163 | Receive packets until the end of time. 164 | ''' 165 | while True: 166 | self.forwarding() 167 | 168 | try: 169 | recv = self.net.recv_packet(timeout=1.0) 170 | except NoPackets: 171 | continue 172 | except Shutdown: 173 | break 174 | log_info("handle_packet") 175 | # debugger() 176 | self.handle_packet(recv) 177 | 178 | self.stop() 179 | 180 | def stop(self): 181 | self.net.shutdown() 182 | 183 | 184 | def main(net): 185 | ''' 186 | Main entry point for router. Just create Router 187 | object and get it going. 188 | ''' 189 | router = Router(net) 190 | router.start() 191 | -------------------------------------------------------------------------------- /lab-4/rebulid_pkt.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | class rebulid_pkt: 4 | def __init__(self, pkt, subnet, port, targetip): 5 | self.packet = pkt 6 | self.recent_time = time.time() 7 | self.num_of_retries = 0 8 | self.match_subnet = subnet 9 | self.send_out_port = port 10 | self.targetipaddress = targetip 11 | 12 | def try_to_send(self): 13 | self.num_of_retries = self.num_of_retries + 1 14 | 15 | def update_time(self): 16 | self.recent_time = time.time() 17 | 18 | def get_num_of_retries(self): 19 | return self.num_of_retries 20 | 21 | def get_recent_time(self): 22 | return self.recent_time 23 | 24 | def get_targetipaddress(self): 25 | return self.targetipaddress 26 | 27 | def get_send_out_port(self): 28 | return self.send_out_port 29 | 30 | def get_packet(self): 31 | return self.packet 32 | 33 | def get_subnet(self): 34 | return self.match_subnet -------------------------------------------------------------------------------- /lab-4/report/lab_4.pcapng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vectormoon/Computer-Network-Lab/48868ca1b2e8bbfca8921846844574b840dc111f/lab-4/report/lab_4.pcapng -------------------------------------------------------------------------------- /lab-4/report/lab_4.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vectormoon/Computer-Network-Lab/48868ca1b2e8bbfca8921846844574b840dc111f/lab-4/report/lab_4.pdf -------------------------------------------------------------------------------- /lab-4/start_mininet.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import sys 4 | 5 | from mininet.topo import Topo 6 | from mininet.net import Mininet 7 | from mininet.log import lg 8 | from mininet.node import CPULimitedHost 9 | from mininet.link import TCLink 10 | from mininet.util import irange, custom, quietRun, dumpNetConnections 11 | from mininet.cli import CLI 12 | 13 | from time import sleep, time 14 | from subprocess import Popen, PIPE 15 | import subprocess 16 | import argparse 17 | import os 18 | 19 | parser = argparse.ArgumentParser(description="Mininet portion of pyrouter") 20 | # no arguments needed as yet :-) 21 | args = parser.parse_args() 22 | lg.setLogLevel('info') 23 | 24 | class PyRouterTopo(Topo): 25 | 26 | def __init__(self, args): 27 | # Add default members to class. 28 | super(PyRouterTopo, self).__init__() 29 | 30 | # Host and link configuration 31 | # 32 | # 33 | # server1 34 | # \ 35 | # router----client 36 | # / 37 | # server2 38 | # 39 | 40 | nodeconfig = {'cpu':-1} 41 | self.addHost('server1', **nodeconfig) 42 | self.addHost('server2', **nodeconfig) 43 | self.addHost('router', **nodeconfig) 44 | self.addHost('client', **nodeconfig) 45 | 46 | linkconfig = { 47 | 'bw': 10, 48 | 'delay': 0.010, 49 | 'loss': 0.0 50 | } 51 | 52 | for node in ['server1','server2','client']: 53 | self.addLink(node, 'router', **linkconfig) 54 | 55 | def set_ip_pair(net, node1, node2, ip1, ip2): 56 | node1 = net.get(node1) 57 | ilist = node1.connectionsTo(net.get(node2)) # returns list of tuples 58 | intf = ilist[0] 59 | intf[0].setIP(ip1) 60 | intf[1].setIP(ip2) 61 | 62 | def reset_macs(net, node, macbase): 63 | ifnum = 1 64 | node_object = net.get(node) 65 | for intf in node_object.intfList(): 66 | node_object.setMAC(macbase.format(ifnum), intf) 67 | ifnum += 1 68 | 69 | for intf in node_object.intfList(): 70 | print(node,intf,node_object.MAC(intf)) 71 | 72 | def set_route(net, fromnode, prefix, gw): 73 | node_object = net.get(fromnode) 74 | node_object.cmdPrint("route add -net {} gw {}".format(prefix, gw)) 75 | 76 | def setup_addressing(net): 77 | reset_macs(net, 'server1', '10:00:00:00:00:{:02x}') 78 | reset_macs(net, 'server2', '20:00:00:00:00:{:02x}') 79 | reset_macs(net, 'client', '30:00:00:00:00:{:02x}') 80 | reset_macs(net, 'router', '40:00:00:00:00:{:02x}') 81 | set_ip_pair(net, 'server1', 'router', '192.168.100.1/30', '192.168.100.2/30') 82 | set_ip_pair(net, 'server2', 'router', '192.168.200.1/30', '192.168.200.2/30') 83 | set_ip_pair(net, 'client', 'router', '10.1.1.1/30', '10.1.1.2/30') 84 | set_route(net, 'server1', '10.1.0.0/16', '192.168.100.2') 85 | set_route(net, 'server1', '192.168.200.0/24', '192.168.100.2') 86 | set_route(net, 'server2', '10.1.0.0/16', '192.168.200.2') 87 | set_route(net, 'server2', '192.168.100.0/24', '192.168.200.2') 88 | set_route(net, 'client', '192.168.100.0/24', '10.1.1.2') 89 | set_route(net, 'client', '192.168.200.0/24', '10.1.1.2') 90 | set_route(net, 'client', '172.16.0.0/16', '10.1.1.2') 91 | 92 | table = ( 93 | "192.168.100.0 255.255.255.0 192.168.100.1 router-eth0\n" 94 | "192.168.200.0 255.255.255.0 192.168.200.1 router-eth1\n" 95 | "10.1.0.0 255.255.0.0 10.1.1.1 router-eth2" 96 | ) 97 | with open('forwarding_table.txt', 'w') as fp: 98 | fp.write(table) 99 | 100 | def disable_ipv6(net): 101 | for v in net.values(): 102 | v.cmdPrint('sysctl -w net.ipv6.conf.all.disable_ipv6=1') 103 | v.cmdPrint('sysctl -w net.ipv6.conf.default.disable_ipv6=1') 104 | 105 | def main(): 106 | topo = PyRouterTopo(args) 107 | net = Mininet(topo=topo, link=TCLink, cleanup=True, controller=None) 108 | setup_addressing(net) 109 | disable_ipv6(net) 110 | net.interact() 111 | 112 | if __name__ == '__main__': 113 | main() 114 | -------------------------------------------------------------------------------- /lab-4/testcases/myrouter2_testscenario.srpy: -------------------------------------------------------------------------------- 1 | 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 -------------------------------------------------------------------------------- /lab-4/testcases/test_submit.py: -------------------------------------------------------------------------------- 1 | import re 2 | import pathlib 3 | from unittest import TestCase, main 4 | 5 | 6 | labNum = 4 # 1-7 7 | reportPattern = f"\\d{{9}}[\\u4e00-\\u9fa5]+_lab_{labNum}" 8 | 9 | 10 | class TestDir(TestCase): 11 | def test_check_report(self): 12 | workingDir = pathlib.Path(".") 13 | reportDir = workingDir / "report" 14 | self.assertTrue( 15 | reportDir.exists(), 16 | "Report directory `report/` doesn't exist" 17 | ) 18 | reports = list(reportDir.glob("*.pdf")) 19 | self.assertNotEqual(len(reports), 0, "No report PDF found") 20 | self.assertEqual(len(reports), 1, "More than one report PDF found") 21 | for report in reports: 22 | self.assertTrue( 23 | re.match(reportPattern, report.stem) is not None, 24 | "Wrong name of report PDF" 25 | ) 26 | for capfile in reportDir.glob("*.pcap*"): 27 | self.assertTrue( 28 | capfile.stem.startswith(f"lab_{labNum}"), 29 | f"Wrong name of capture file '{capfile}'. " 30 | f"Should start with 'lab_{labNum}'" 31 | ) 32 | 33 | def test_check_src(self): 34 | workingDir = pathlib.Path(".") 35 | srcNames = { 36 | "myrouter.py", 37 | } 38 | pyfiles = set(map(lambda f: f.name, workingDir.glob("*.py"))) 39 | for srcfile in srcNames: 40 | self.assertTrue( 41 | srcfile in pyfiles, 42 | f"'{srcfile}' cannot be found" 43 | ) 44 | 45 | 46 | if __name__ == '__main__': 47 | main() 48 | -------------------------------------------------------------------------------- /lab-5/README.md: -------------------------------------------------------------------------------- 1 | # Lab-5 assignment 2 | 3 | Refer to [Lab-5 manual](https://pavinberg.gitbook.io/nju-network-lab/ipv4-router/lab-5) 4 | -------------------------------------------------------------------------------- /lab-5/forwarding_table.txt: -------------------------------------------------------------------------------- 1 | 192.168.100.0 255.255.255.0 192.168.100.1 router-eth0 2 | 192.168.200.0 255.255.255.0 192.168.200.1 router-eth1 3 | 10.1.0.0 255.255.0.0 10.1.1.1 router-eth2 -------------------------------------------------------------------------------- /lab-5/myrouter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | ''' 4 | Basic IPv4 router (static routing) in Python. 5 | ''' 6 | 7 | import time 8 | import switchyard 9 | from switchyard.lib.userlib import * 10 | from switchyard.lib.logging import * 11 | import rebulid_pkt 12 | import queue 13 | 14 | 15 | class Router(object): 16 | def __init__(self, net: switchyard.llnetbase.LLNetBase): 17 | self.net = net 18 | # other initialization stuff here 19 | self.interfaces = net.interfaces() 20 | self.arp_table = {} 21 | 22 | self.ip_list = [] 23 | self.eth_list = [] 24 | for i in self.interfaces: 25 | self.ip_list.append(i.ipaddr) 26 | self.eth_list.append(i.ethaddr) 27 | 28 | self.forwarding_table = {} 29 | # init by net.interfaces() 30 | for i in self.interfaces: 31 | my_netmask = i.netmask 32 | my_ipaddr = i.ipaddr 33 | log_debug (f"type of my_ipaddr: {type(my_ipaddr)}; type of my_netmask: {type(my_netmask)}") 34 | log_debug (f"value of my_ipaddr: {my_ipaddr}; value of my_netmask: {my_netmask}; value of i.netmask: {i.netmask}") 35 | sub_network_address = IPv4Address(ip_address((int(my_ipaddr) & int(my_netmask)))) 36 | log_debug (f"value of subnet address: {sub_network_address}\n") 37 | self.forwarding_table[sub_network_address] = [my_netmask, '0.0.0.0', i.name] 38 | self.print_forwarding_table() 39 | # init by forwarding_table.txt 40 | with open('forwarding_table.txt') as f: 41 | while True: 42 | line = f.readline() 43 | if not line: 44 | break 45 | else: 46 | table_info = line.split() 47 | self.forwarding_table[IPv4Address(table_info[0])] = [IPv4Address(table_info[1]), IPv4Address(table_info[2]), table_info[3]] 48 | self.print_forwarding_table() 49 | 50 | self.packet_queue = [] 51 | 52 | self.icmperr = 0 53 | 54 | 55 | def print_forwarding_table(self): 56 | log_info ("------------------------forwarding_table_info------------------------") 57 | for k,v in self.forwarding_table.items(): 58 | log_info (f" address: {k}; other: {v}. ") 59 | log_info ("----------------------------------------------------------------------") 60 | 61 | 62 | def icmp_error(self, origpkt, type_of_error, icmp_code, srcip, dstip): 63 | # origpkt = Ethernet() + IPv4() + ICMP() 64 | i = origpkt.get_header_index(Ethernet) 65 | del origpkt[i] 66 | 67 | eth = Ethernet() 68 | 69 | icmp = ICMP() 70 | icmp.icmptype = type_of_error 71 | icmp.icmpcode = icmp_code 72 | icmp.icmpdata.data = origpkt.to_bytes()[:28] 73 | 74 | ip = IPv4() 75 | ip.protocol = IPProtocol.ICMP 76 | ip.ttl = 64 77 | ip.src = srcip 78 | ip.dst = dstip 79 | 80 | pkt = eth + ip + icmp 81 | return pkt 82 | 83 | def prefix_match(self, dst): 84 | prefix_len = 0 85 | match_subnet = '0.0.0.0' 86 | for address in self.forwarding_table.keys(): 87 | destaddr = dst 88 | prefixnet = IPv4Network(str(address) + '/' + str(self.forwarding_table[address][0])) 89 | matches = destaddr in prefixnet 90 | if (matches): 91 | if (prefixnet.prefixlen > prefix_len): 92 | prefix_len = prefixnet.prefixlen 93 | match_subnet = IPv4Address(address) 94 | match = 1 95 | return match_subnet 96 | 97 | 98 | def handle_packet(self, recv: switchyard.llnetbase.ReceivedPacket): 99 | timestamp, ifaceName, packet = recv 100 | # TODO: your logic here 101 | arp = packet.get_header(Arp) 102 | ipv4 = packet.get_header(IPv4) 103 | icmp = packet.get_header(ICMP) 104 | 105 | input_port_info = self.net.interface_by_name(ifaceName) 106 | 107 | if (arp is not None): 108 | self.arp_table[arp.senderprotoaddr] = arp.senderhwaddr 109 | 110 | log_info ("------------------------arp_table_info------------------------") 111 | for k,v in self.arp_table.items(): 112 | log_info (f" IP: {k}; MAC Address: {v}") 113 | log_info ("--------------------------------------------------------------") 114 | if arp.operation == ArpOperation.Request: 115 | targetip_exist_flag = -1 116 | for i in range(len(self.ip_list)): 117 | if (self.ip_list[i] == arp.targetprotoaddr): 118 | targetip_exist_flag = i 119 | if (targetip_exist_flag != -1): 120 | arp_reply_pkt = create_ip_arp_reply(input_port_info.ethaddr, arp.senderhwaddr, arp.targetprotoaddr, arp.senderprotoaddr) 121 | self.net.send_packet(ifaceName, arp_reply_pkt) 122 | log_info (f"Sending arp reply {arp_reply_pkt} to {ifaceName}") 123 | elif (ipv4 is not None): 124 | targetip_exist_flag = -1 125 | for i in range(len(self.ip_list)): 126 | if (self.ip_list[i] == ipv4.dst): 127 | targetip_exist_flag = i 128 | if (icmp is not None): 129 | # ------------Task2: Responding to ICMP echo requests ------------ 130 | log_debug(f"icmp: {icmp}\nicmpcode: {icmp.icmpcode}\n icmpdata: {icmp.icmpdata}\n icmptype: {icmp.icmptype}") 131 | if (targetip_exist_flag != -1) and (icmp.icmptype == 8 and icmp.icmpcode == 0): 132 | icmp_reply = ICMP() 133 | # echo reply(ping) 134 | icmp_reply.icmptype = 0 135 | icmp_reply.icmpcode = 0 136 | # copy info from icmp request to icmp reply 137 | icmp_reply.icmpdata.sequence = icmp.icmpdata.sequence 138 | icmp_reply.icmpdata.identifier = icmp.icmpdata.identifier 139 | icmp_reply.icmpdata.data = icmp.icmpdata.data 140 | # 2=ICMP 141 | packet[2] = icmp_reply 142 | # construct IP header 143 | ipv4.dst = ipv4.src 144 | ipv4.src = self.ip_list[targetip_exist_flag] 145 | # ---------------------------------------------------------------- 146 | # ICMP time exceeded 147 | ipv4.ttl = ipv4.ttl - 1 148 | if ipv4.ttl <= 0: 149 | packet = self.icmp_error(packet, 11, 0, input_port_info.ipaddr, ipv4.src) 150 | ipv4.dst = ipv4.src 151 | ipv4.src = self.ip_list[targetip_exist_flag] 152 | self.icmperr = 1 153 | # ------------Task2: IP Forwarding Table Lookup ------------ 154 | drop_flag = 0 155 | match_subnet = '0.0.0.0' 156 | match = 0 157 | # ICMP destination port unreachable 158 | for address in self.interfaces: 159 | if (ipv4.dst == address.ipaddr): 160 | # drop 161 | drop_flag = 1 162 | packet = self.icmp_error(packet, 3, 3, input_port_info.ipaddr, ipv4.src) 163 | ipv4.dst = ipv4.src 164 | ipv4.src = self.ip_list[targetip_exist_flag] 165 | match_subnet = self.prefix_match(ipv4.dst) 166 | self.icmperr = 1 167 | # longest prefix matching rule 168 | if (drop_flag == 0): 169 | prefix_len = 0 170 | for address in self.forwarding_table.keys(): 171 | destaddr = ipv4.dst 172 | prefixnet = IPv4Network(str(address) + '/' + str(self.forwarding_table[address][0])) 173 | matches = destaddr in prefixnet 174 | if (matches): 175 | if (prefixnet.prefixlen > prefix_len): 176 | prefix_len = prefixnet.prefixlen 177 | match_subnet = IPv4Address(address) 178 | match = 1 179 | # ICMP destination network unreachable 180 | if (match == 0): 181 | drop_flag = 1 182 | packet = self.icmp_error(packet, 3, 0, input_port_info.ipaddr, ipv4.src) 183 | ipv4.dst = ipv4.src 184 | ipv4.src = self.ip_list[targetip_exist_flag] 185 | match_subnet = self.prefix_match(ipv4.dst) 186 | self.icmperr = 1 187 | # ---------------------------------------------------------- 188 | # rebulid packet 189 | if self.forwarding_table[match_subnet][1] == '0.0.0.0': 190 | dstip = IPv4Address(ipv4.dst) 191 | else: 192 | dstip = IPv4Address(self.forwarding_table[match_subnet][1]) 193 | pkt = rebulid_pkt.rebulid_pkt(packet, match_subnet, self.forwarding_table[match_subnet][2], dstip, input_port_info) 194 | self.packet_queue.append(pkt) 195 | 196 | 197 | # ------------Task3: Forwarding the Packet and ARP ------------ 198 | def forwarding(self): 199 | if (len(self.packet_queue) == 0): 200 | return 201 | handle_pkt = self.packet_queue[0] 202 | targetipaddr = handle_pkt.get_targetipaddress() 203 | router_send_to_host_port_name = handle_pkt.get_send_out_port() 204 | my_packet = handle_pkt.get_packet() 205 | router_forwarding_port_info = self.net.interface_by_name(router_send_to_host_port_name) 206 | input_port_info = handle_pkt.get_input_port_info() 207 | # if (my_packet[1].src == IPv4Address('192.168.1.239')): 208 | # debugger() 209 | if (targetipaddr in self.arp_table.keys()): 210 | # search arp table 211 | self.forwarding_packet(my_packet, router_send_to_host_port_name, targetipaddr, router_forwarding_port_info) 212 | elif (handle_pkt.get_num_of_retries() < 5): 213 | # send arp request 214 | self.send_arp_request(handle_pkt, router_forwarding_port_info, targetipaddr, router_send_to_host_port_name, input_port_info) 215 | elif (handle_pkt.get_num_of_retries() >= 5): 216 | # ICMP destination host unreachable 217 | targetip_exist_flag = -1 218 | for i in range(len(self.ip_list)): 219 | if (self.ip_list[i] == my_packet[IPv4].dst): 220 | targetip_exist_flag = i 221 | # debugger() 222 | packet = self.icmp_error(my_packet, 3, 1, input_port_info.ipaddr, my_packet[IPv4].src) 223 | my_packet[IPv4].dst = my_packet[IPv4].src 224 | my_packet[IPv4].src = self.ip_list[targetip_exist_flag] 225 | match_subnet = self.prefix_match(my_packet[IPv4].dst) 226 | # self.net.send_packet(input_port_info.name, packet) 227 | log_info (f"Delete packet {self.packet_queue[0].get_packet()}") 228 | del(self.packet_queue[0]) 229 | self.icmperr = 1 230 | if self.forwarding_table[match_subnet][1] == '0.0.0.0': 231 | dstip = IPv4Address(my_packet[IPv4].dst) 232 | else: 233 | dstip = IPv4Address(self.forwarding_table[match_subnet][1]) 234 | pkt = rebulid_pkt.rebulid_pkt(packet, match_subnet, self.forwarding_table[match_subnet][2], dstip, input_port_info) 235 | self.packet_queue.append(pkt) 236 | # ---------------------------------------------------------- 237 | 238 | def forwarding_packet(self, my_packet, router_send_to_host_port_name, targetipaddr, router_forwarding_port_info): 239 | if (self.icmperr): 240 | my_packet[Ethernet].src = router_forwarding_port_info.ethaddr 241 | my_packet[Ethernet].dst = self.arp_table[targetipaddr] 242 | self.net.send_packet(router_send_to_host_port_name, my_packet) 243 | log_info (f"Forwarding packet {my_packet} to {router_send_to_host_port_name}") 244 | del(self.packet_queue[0]) 245 | self.icmperr = 0 246 | else: 247 | my_packet[Ethernet].src = router_forwarding_port_info.ethaddr 248 | my_packet[Ethernet].dst = self.arp_table[targetipaddr] 249 | self.net.send_packet(router_send_to_host_port_name, my_packet) 250 | log_info (f"Forwarding packet {my_packet} to {router_send_to_host_port_name}") 251 | del(self.packet_queue[0]) 252 | 253 | def send_arp_request(self, handle_pkt, router_forwarding_port_info, targetipaddr, router_send_to_host_port_name, input_port_info): 254 | retry_flag = 0 255 | if ((time.time() - handle_pkt.get_recent_time()) > 1.0): 256 | retry_flag = 1 257 | if (retry_flag): 258 | if (self.icmperr): 259 | arppacket = create_ip_arp_request(input_port_info.ethaddr,input_port_info.ipaddr,targetipaddr) 260 | handle_pkt.try_to_send() 261 | handle_pkt.update_time() 262 | self.net.send_packet(input_port_info.name, arppacket) 263 | log_info (f"Sending arp request {arppacket} to {input_port_info.name}") 264 | else: 265 | arppacket = create_ip_arp_request(router_forwarding_port_info.ethaddr,router_forwarding_port_info.ipaddr,targetipaddr) 266 | handle_pkt.try_to_send() 267 | handle_pkt.update_time() 268 | self.net.send_packet(router_send_to_host_port_name, arppacket) 269 | log_info (f"Sending arp request {arppacket} to {router_send_to_host_port_name}") 270 | 271 | 272 | def start(self): 273 | '''A running daemon of the router. 274 | Receive packets until the end of time. 275 | ''' 276 | while True: 277 | self.forwarding() 278 | 279 | try: 280 | recv = self.net.recv_packet(timeout=1.0) 281 | except NoPackets: 282 | continue 283 | except Shutdown: 284 | break 285 | 286 | log_info("handle_packet") 287 | self.handle_packet(recv) 288 | 289 | self.stop() 290 | 291 | def stop(self): 292 | self.net.shutdown() 293 | 294 | 295 | def main(net): 296 | ''' 297 | Main entry point for router. Just create Router 298 | object and get it going. 299 | ''' 300 | router = Router(net) 301 | router.start() 302 | -------------------------------------------------------------------------------- /lab-5/rebulid_pkt.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | class rebulid_pkt: 4 | def __init__(self, pkt, subnet, port, targetip, inputport_info): 5 | self.packet = pkt 6 | self.recent_time = time.time() 7 | self.num_of_retries = 0 8 | self.match_subnet = subnet 9 | self.send_out_port = port 10 | self.targetipaddress = targetip 11 | self.input_port_info = inputport_info 12 | 13 | def try_to_send(self): 14 | self.num_of_retries = self.num_of_retries + 1 15 | 16 | def update_time(self): 17 | self.recent_time = time.time() 18 | 19 | def get_num_of_retries(self): 20 | return self.num_of_retries 21 | 22 | def get_recent_time(self): 23 | return self.recent_time 24 | 25 | def get_targetipaddress(self): 26 | return self.targetipaddress 27 | 28 | def get_send_out_port(self): 29 | return self.send_out_port 30 | 31 | def get_packet(self): 32 | return self.packet 33 | 34 | def get_subnet(self): 35 | return self.match_subnet 36 | 37 | def get_input_port_info(self): 38 | return self.input_port_info -------------------------------------------------------------------------------- /lab-5/report/lab_5.pcapng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vectormoon/Computer-Network-Lab/48868ca1b2e8bbfca8921846844574b840dc111f/lab-5/report/lab_5.pcapng -------------------------------------------------------------------------------- /lab-5/report/lab_5.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vectormoon/Computer-Network-Lab/48868ca1b2e8bbfca8921846844574b840dc111f/lab-5/report/lab_5.pdf -------------------------------------------------------------------------------- /lab-5/start_mininet.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import sys 4 | 5 | from mininet.topo import Topo 6 | from mininet.net import Mininet 7 | from mininet.log import lg 8 | from mininet.node import CPULimitedHost 9 | from mininet.link import TCLink 10 | from mininet.util import irange, custom, quietRun, dumpNetConnections 11 | from mininet.cli import CLI 12 | 13 | from time import sleep, time 14 | from subprocess import Popen, PIPE 15 | import subprocess 16 | import argparse 17 | import os 18 | 19 | parser = argparse.ArgumentParser(description="Mininet portion of pyrouter") 20 | # no arguments needed as yet :-) 21 | args = parser.parse_args() 22 | lg.setLogLevel('info') 23 | 24 | class PyRouterTopo(Topo): 25 | 26 | def __init__(self, args): 27 | # Add default members to class. 28 | super(PyRouterTopo, self).__init__() 29 | 30 | # Host and link configuration 31 | # 32 | # 33 | # server1 34 | # \ 35 | # router----client 36 | # / 37 | # server2 38 | # 39 | 40 | nodeconfig = {'cpu':-1} 41 | self.addHost('server1', **nodeconfig) 42 | self.addHost('server2', **nodeconfig) 43 | self.addHost('router', **nodeconfig) 44 | self.addHost('client', **nodeconfig) 45 | 46 | self.addHost('server3', **nodeconfig) 47 | 48 | linkconfig = { 49 | 'bw': 10, 50 | 'delay': 0.010, 51 | 'loss': 0.0 52 | } 53 | 54 | for node in ['server1','server2','client', 'server3']: 55 | self.addLink(node, 'router', **linkconfig) 56 | 57 | def set_ip_pair(net, node1, node2, ip1, ip2): 58 | node1 = net.get(node1) 59 | ilist = node1.connectionsTo(net.get(node2)) # returns list of tuples 60 | intf = ilist[0] 61 | intf[0].setIP(ip1) 62 | intf[1].setIP(ip2) 63 | 64 | def reset_macs(net, node, macbase): 65 | ifnum = 1 66 | node_object = net.get(node) 67 | for intf in node_object.intfList(): 68 | node_object.setMAC(macbase.format(ifnum), intf) 69 | ifnum += 1 70 | 71 | for intf in node_object.intfList(): 72 | print(node,intf,node_object.MAC(intf)) 73 | 74 | def set_route(net, fromnode, prefix, gw): 75 | node_object = net.get(fromnode) 76 | node_object.cmdPrint("route add -net {} gw {}".format(prefix, gw)) 77 | 78 | def setup_addressing(net): 79 | reset_macs(net, 'server1', '10:00:00:00:00:{:02x}') 80 | reset_macs(net, 'server2', '20:00:00:00:00:{:02x}') 81 | reset_macs(net, 'client', '30:00:00:00:00:{:02x}') 82 | reset_macs(net, 'router', '40:00:00:00:00:{:02x}') 83 | 84 | # reset_macs(net, 'server3', '50:00:00:00:00:{:02x}') 85 | 86 | set_ip_pair(net, 'server1', 'router', '192.168.100.1/30', '192.168.100.2/30') 87 | set_ip_pair(net, 'server2', 'router', '192.168.200.1/30', '192.168.200.2/30') 88 | set_ip_pair(net, 'client', 'router', '10.1.1.1/30', '10.1.1.2/30') 89 | set_route(net, 'server1', '10.1.0.0/16', '192.168.100.2') 90 | set_route(net, 'server1', '192.168.200.0/24', '192.168.100.2') 91 | set_route(net, 'server2', '10.1.0.0/16', '192.168.200.2') 92 | set_route(net, 'server2', '192.168.100.0/24', '192.168.200.2') 93 | set_route(net, 'client', '192.168.100.0/24', '10.1.1.2') 94 | set_route(net, 'client', '192.168.200.0/24', '10.1.1.2') 95 | set_route(net, 'client', '172.16.0.0/16', '10.1.1.2') 96 | 97 | # set_route(net, 'server1', '172.16.0.0/16', '192.168.100.2') 98 | 99 | table = ( 100 | "192.168.100.0 255.255.255.0 192.168.100.1 router-eth0\n" 101 | "192.168.200.0 255.255.255.0 192.168.200.1 router-eth1\n" 102 | "10.1.0.0 255.255.0.0 10.1.1.1 router-eth2" 103 | ) 104 | with open('forwarding_table.txt', 'w') as fp: 105 | fp.write(table) 106 | 107 | def disable_ipv6(net): 108 | for v in net.values(): 109 | v.cmdPrint('sysctl -w net.ipv6.conf.all.disable_ipv6=1') 110 | v.cmdPrint('sysctl -w net.ipv6.conf.default.disable_ipv6=1') 111 | 112 | def main(): 113 | topo = PyRouterTopo(args) 114 | net = Mininet(topo=topo, link=TCLink, cleanup=True, controller=None) 115 | setup_addressing(net) 116 | disable_ipv6(net) 117 | net.interact() 118 | 119 | if __name__ == '__main__': 120 | main() 121 | -------------------------------------------------------------------------------- /lab-5/testcases/router3_testscenario.srpy: -------------------------------------------------------------------------------- 1 | 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 -------------------------------------------------------------------------------- /lab-5/testcases/router3_testscenario_template.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from switchyard.lib.userlib import * 4 | from copy import deepcopy 5 | 6 | def get_raw_pkt(pkt, xlen): 7 | pkt = deepcopy(pkt) 8 | i = pkt.get_header_index(Ethernet) 9 | if i >= 0: 10 | del pkt[i] 11 | b = pkt.to_bytes()[:xlen] 12 | return b 13 | 14 | def mk_arpreq(hwsrc, ipsrc, ipdst): 15 | arp_req = Arp() 16 | arp_req.operation = ArpOperation.Request 17 | arp_req.senderprotoaddr = IPAddr(ipsrc) 18 | arp_req.targetprotoaddr = IPAddr(ipdst) 19 | arp_req.senderhwaddr = EthAddr(hwsrc) 20 | arp_req.targethwaddr = EthAddr("ff:ff:ff:ff:ff:ff") 21 | ether = Ethernet() 22 | ether.src = EthAddr(hwsrc) 23 | ether.dst = EthAddr("ff:ff:ff:ff:ff:ff") 24 | ether.ethertype = EtherType.ARP 25 | return ether + arp_req 26 | 27 | def mk_arpresp(arpreqpkt, hwsrc, arphwsrc=None, arphwdst=None): 28 | if arphwsrc is None: 29 | arphwsrc = hwsrc 30 | if arphwdst is None: 31 | arphwdst = arpreqpkt.get_header(Arp).senderhwaddr 32 | ether = Ethernet() 33 | ether.src = EthAddr(hwsrc) 34 | ether.dst = arpreqpkt.get_header(Arp).senderhwaddr 35 | ether.ethertype = EtherType.ARP 36 | arp_reply = Arp() 37 | arp_reply.operation = ArpOperation.Reply 38 | arp_reply.senderprotoaddr = IPAddr(arpreqpkt.get_header(Arp).targetprotoaddr) 39 | arp_reply.targetprotoaddr = IPAddr(arpreqpkt.get_header(Arp).senderprotoaddr) 40 | arp_reply.senderhwaddr = EthAddr(arphwsrc) 41 | arp_reply.targethwaddr = EthAddr(arphwdst) 42 | return ether + arp_reply 43 | 44 | def mk_ping(hwsrc, hwdst, ipsrc, ipdst, reply=False, ttl=64, payload=''): 45 | ether = Ethernet() 46 | ether.src = EthAddr(hwsrc) 47 | ether.dst = EthAddr(hwdst) 48 | ether.ethertype = EtherType.IP 49 | ippkt = IPv4() 50 | ippkt.src = IPAddr(ipsrc) 51 | ippkt.dst = IPAddr(ipdst) 52 | ippkt.protocol = IPProtocol.ICMP 53 | ippkt.ttl = ttl 54 | ippkt.ipid = 0 55 | if reply: 56 | icmppkt = ICMP() 57 | icmppkt.icmptype = ICMPType.EchoReply 58 | icmppkt.icmpcode = ICMPCodeEchoReply.EchoReply 59 | else: 60 | icmppkt = ICMP() 61 | icmppkt.icmptype = ICMPType.EchoRequest 62 | icmppkt.icmpcode = ICMPCodeEchoRequest.EchoRequest 63 | icmppkt.icmpdata.sequence = 42 64 | icmppkt.icmpdata.data = payload 65 | return ether + ippkt + icmppkt 66 | 67 | def mk_icmperr(hwsrc, hwdst, ipsrc, ipdst, xtype, xcode=0, origpkt=None, ttl=64): 68 | ether = Ethernet() 69 | ether.src = EthAddr(hwsrc) 70 | ether.dst = EthAddr(hwdst) 71 | ether.ethertype = EtherType.IP 72 | ippkt = IPv4() 73 | ippkt.src = IPAddr(ipsrc) 74 | ippkt.dst = IPAddr(ipdst) 75 | ippkt.protocol = IPProtocol.ICMP 76 | ippkt.ttl = ttl 77 | ippkt.ipid = 0 78 | icmppkt = ICMP() 79 | icmppkt.icmptype = xtype 80 | icmppkt.icmpcode = xcode 81 | if origpkt is not None: 82 | xpkt = deepcopy(origpkt) 83 | i = xpkt.get_header_index(Ethernet) 84 | if i >= 0: 85 | del xpkt[i] 86 | icmppkt.icmpdata.data = xpkt.to_bytes()[:28] 87 | icmppkt.icmpdata.origdgramlen = len(xpkt) 88 | 89 | return ether + ippkt + icmppkt 90 | 91 | def mk_udp(hwsrc, hwdst, ipsrc, ipdst, ttl=64, srcport=10000, dstport=10000, payload=''): 92 | ether = Ethernet() 93 | ether.src = EthAddr(hwsrc) 94 | ether.dst = EthAddr(hwdst) 95 | ether.ethertype = EtherType.IP 96 | ippkt = IPv4() 97 | ippkt.src = IPAddr(ipsrc) 98 | ippkt.dst = IPAddr(ipdst) 99 | ippkt.protocol = IPProtocol.UDP 100 | ippkt.ttl = ttl 101 | ippkt.ipid = 0 102 | udppkt = UDP() 103 | udppkt.src = srcport 104 | udppkt.dst = dstport 105 | return ether + ippkt + udppkt + RawPacketContents(payload) 106 | 107 | def icmp_tests(): 108 | s = TestScenario("IP forwarding and ARP requester tests") 109 | s.add_interface('router-eth0', '10:00:00:00:00:01', '192.168.1.1', '255.255.255.0') 110 | s.add_interface('router-eth1', '10:00:00:00:00:02', '10.10.0.1', '255.255.0.0') 111 | s.add_interface('router-eth2', '10:00:00:00:00:03', '172.16.42.1', '255.255.255.252') 112 | s.add_file('forwarding_table.txt', '''172.16.0.0 255.255.0.0 192.168.1.2 router-eth0 113 | 172.16.128.0 255.255.192.0 10.10.0.254 router-eth1 114 | 172.16.64.0 255.255.192.0 10.10.1.254 router-eth1 115 | 10.100.0.0 255.255.0.0 172.16.42.2 router-eth2 116 | ''') 117 | 118 | nottinyttl = '''lambda pkt: pkt.get_header(IPv4).ttl >= 8''' 119 | 120 | # Your tests here 121 | 122 | return s 123 | 124 | scenario = icmp_tests() 125 | -------------------------------------------------------------------------------- /lab-5/testcases/test_submit.py: -------------------------------------------------------------------------------- 1 | import re 2 | import pathlib 3 | from unittest import TestCase, main 4 | 5 | 6 | labNum = 5 # 1-7 7 | reportPattern = f"\\d{{9}}[\\u4e00-\\u9fa5]+_lab_{labNum}" 8 | 9 | 10 | class TestDir(TestCase): 11 | def test_check_report(self): 12 | workingDir = pathlib.Path(".") 13 | reportDir = workingDir / "report" 14 | self.assertTrue( 15 | reportDir.exists(), 16 | "Report directory `report/` doesn't exist" 17 | ) 18 | reports = list(reportDir.glob("*.pdf")) 19 | self.assertNotEqual(len(reports), 0, "No report PDF found") 20 | self.assertEqual(len(reports), 1, "More than one report PDF found") 21 | for report in reports: 22 | self.assertTrue( 23 | re.match(reportPattern, report.stem) is not None, 24 | "Wrong name of report PDF" 25 | ) 26 | for capfile in reportDir.glob("*.pcap*"): 27 | self.assertTrue( 28 | capfile.stem.startswith(f"lab_{labNum}"), 29 | f"Wrong name of capture file '{capfile}'. " 30 | f"Should start with 'lab_{labNum}'" 31 | ) 32 | 33 | def test_check_src(self): 34 | workingDir = pathlib.Path(".") 35 | srcNames = { 36 | "myrouter.py", 37 | } 38 | pyfiles = set(map(lambda f: f.name, workingDir.glob("*.py"))) 39 | for srcfile in srcNames: 40 | self.assertTrue( 41 | srcfile in pyfiles, 42 | f"'{srcfile}' cannot be found" 43 | ) 44 | 45 | 46 | if __name__ == '__main__': 47 | main() 48 | -------------------------------------------------------------------------------- /lab-6/README.md: -------------------------------------------------------------------------------- 1 | # Lab-6 assignment 2 | 3 | Refer to [Lab-6 manual](https://pavinberg.gitbook.io/nju-network-lab/lab-6) 4 | -------------------------------------------------------------------------------- /lab-6/blastee.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import time 4 | import threading 5 | from struct import pack 6 | import switchyard 7 | from switchyard.lib.address import * 8 | from switchyard.lib.packet import * 9 | from switchyard.lib.userlib import * 10 | 11 | 12 | class Blastee: 13 | def __init__( 14 | self, 15 | net: switchyard.llnetbase.LLNetBase, 16 | blasterIp, 17 | num 18 | ): 19 | self.net = net 20 | # TODO: store the parameters 21 | self.blasterIpAddr = blasterIp 22 | self.blasteeIpAddr = "192.168.100.1" 23 | self.blasterEthAddr = "10:00:00:00:00:01" 24 | self.blasteeEthAddr = "20:00:00:00:00:01" 25 | self.myNum = num 26 | 27 | 28 | def handle_packet(self, recv: switchyard.llnetbase.ReceivedPacket): 29 | _, fromIface, packet = recv 30 | log_debug(f"I got a packet from {fromIface}") 31 | log_debug(f"Pkt: {packet}") 32 | 33 | my_pkt = Ethernet() + IPv4(protocol=IPProtocol.UDP) + UDP() 34 | my_pkt[UDP].src = 4444 35 | my_pkt[UDP].dst = 5555 36 | # my_pkt += b'These are some application data bytes' 37 | 38 | my_pkt[Ethernet].src = self.blasteeEthAddr 39 | my_pkt[Ethernet].dst = self.blasterEthAddr 40 | 41 | my_pkt[IPv4].src = self.blasteeIpAddr 42 | my_pkt[IPv4].dst = self.blasterIpAddr 43 | # blaster packet format 44 | # <------- Switchyard headers -----> <----- Your packet header(raw bytes) ------> <-- Payload in raw bytes ---> 45 | # ------------------------------------------------------------------------------------------------------------- 46 | # | ETH Hdr | IP Hdr | UDP Hdr | Sequence number(32 bits) | Length(16 bits) | Variable length payload | 47 | # ------------------------------------------------------------------------------------------------------------- 48 | # debugger() 49 | sequence = struct.pack(">4s", packet[3].to_bytes()[0:4]) 50 | payload = struct.pack(">8s", packet[3].to_bytes()[6:14]) 51 | 52 | my_pkt += sequence 53 | my_pkt += payload 54 | 55 | log_info(f"Sending packet from blastee to blaster, pkt info {my_pkt}") 56 | self.net.send_packet(fromIface, my_pkt) 57 | 58 | 59 | def start(self): 60 | '''A running daemon of the blastee. 61 | Receive packets until the end of time. 62 | ''' 63 | while True: 64 | try: 65 | recv = self.net.recv_packet(timeout=1.0) 66 | except NoPackets: 67 | continue 68 | except Shutdown: 69 | break 70 | 71 | self.handle_packet(recv) 72 | 73 | self.shutdown() 74 | 75 | def shutdown(self): 76 | self.net.shutdown() 77 | 78 | 79 | def main(net, **kwargs): 80 | blastee = Blastee(net, **kwargs) 81 | blastee.start() 82 | -------------------------------------------------------------------------------- /lab-6/blaster.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import time 4 | from random import randint 5 | import switchyard 6 | from switchyard.lib.address import * 7 | from switchyard.lib.packet import * 8 | from switchyard.lib.userlib import * 9 | 10 | from struct import pack 11 | 12 | 13 | class Blaster: 14 | def __init__( 15 | self, 16 | net: switchyard.llnetbase.LLNetBase, 17 | blasteeIp, 18 | num, 19 | length="100", 20 | senderWindow="5", 21 | timeout="300", 22 | recvTimeout="100" 23 | ): 24 | self.net = net 25 | # TODO: store the parameters 26 | self.LHS = 1 27 | self.RHS = 1 28 | self.senderWindow = int(senderWindow) 29 | self.length = int(length) 30 | self.num = int(num) 31 | self.timeout = (int(timeout) / 1000) 32 | self.recvTimeout = (int(recvTimeout) / 1000) 33 | # host info 34 | self.blasterIpAddr = "192.168.200.1" 35 | self.blasteeIpAddr = "192.168.100.1" 36 | self.blasterEthAddr = "10:00:00:00:00:01" 37 | self.blasteeEthAddr = "20:00:00:00:00:01" 38 | # statistics about the transmission 39 | self.Total_TX_time = 0 40 | self.Number_of_reTX = 0 41 | self.Number_of_coarse_TOs = 0 42 | self.Throughput = 0.0 43 | self.Goodput = 0.0 44 | # assistant data 45 | self.ACKd = [0] * (self.num+1) 46 | self.sent_pkt_flag = [0] * (self.num+1) 47 | self.frist_packet_sent_time = time.time() 48 | self.last_packet_sent_time = time.time() 49 | self.LHS_timer = time.time() 50 | self.coarse_TO_flag = 0 51 | 52 | 53 | def handle_packet(self, recv: switchyard.llnetbase.ReceivedPacket): 54 | _, fromIface, packet = recv 55 | log_debug("I got a packet") 56 | # TODO: handle packet and send ack 57 | # blastee packet format 58 | # <------- Switchyard headers -----> <----- Your packet header(raw bytes) ------> <-- Payload in raw bytes ---> 59 | # ------------------------------------------------------------------------------------------------------------- 60 | # | ETH Hdr | IP Hdr | UDP Hdr | Sequence number(32 bits) | Payload (8 bytes) | 61 | # ------------------------------------------------------------------------------------------------------------- 62 | # debugger() 63 | sequence = struct.unpack(">i", packet[3].to_bytes()[0:4]) 64 | self.ACKd[sequence[0]] = 1 65 | log_info (f"Receiving ack pakcet, packet info {packet}") 66 | if (self.LHS == self.num): 67 | return 68 | while (self.ACKd[self.LHS] == 1): 69 | self.LHS += 1 70 | self.LHS_timer = time.time() 71 | 72 | 73 | def handle_no_packet(self): 74 | log_debug("Didn't receive anything") 75 | # Creating the headers for the packet 76 | pkt = Ethernet() + IPv4() + UDP() 77 | pkt[1].protocol = IPProtocol.UDP 78 | pkt[Ethernet].src = self.blasterEthAddr 79 | pkt[Ethernet].dst = self.blasteeEthAddr 80 | pkt[IPv4].src = self.blasterIpAddr 81 | pkt[IPv4].dst = self.blasteeIpAddr 82 | # handle coarse timeout 83 | max_ack_number = 0 84 | for i in range(len(self.ACKd)): 85 | if (self.ACKd[i] == 1): 86 | max_ack_number = i 87 | if (max_ack_number > self.LHS): 88 | self.coarse_TO_flag = 1 89 | # Do other things here and send packet 90 | if (time.time() - self.LHS_timer) > self.timeout: 91 | # debugger() 92 | self.Number_of_reTX += 1 93 | self.Throughput += self.length 94 | Sequence_number = self.LHS.to_bytes(4, "big") 95 | Length = self.length.to_bytes(2, "big") 96 | Variable_length_payload = struct.pack(">13s", bytes("hello, world!".encode('utf-8'))) 97 | pkt += Sequence_number 98 | pkt += Length 99 | pkt += Variable_length_payload 100 | log_info (f"Retransmitting pakcet from blaster to blastee, sequence {self.LHS}, packet info {pkt}") 101 | self.net.send_packet("blaster-eth0", pkt) 102 | elif (self.coarse_TO_flag): 103 | self.coarse_TO_flag = 0 104 | self.Number_of_coarse_TOs += 1 105 | elif (self.RHS - self.LHS + 1 <= self.senderWindow) and (self.sent_pkt_flag[self.num-1] == 0): 106 | # <------- Switchyard headers -----> <----- Your packet header(raw bytes) ------> <-- Payload in raw bytes ---> 107 | # ------------------------------------------------------------------------------------------------------------- 108 | # | ETH Hdr | IP Hdr | UDP Hdr | Sequence number(32 bits) | Length(16 bits) | Variable length payload | 109 | # ------------------------------------------------------------------------------------------------------------- 110 | Sequence_number = self.RHS.to_bytes(4, "big") 111 | Length = self.length.to_bytes(2, "big") 112 | Variable_length_payload = struct.pack(">13s", bytes("Hello, world!".encode('utf-8'))) 113 | pkt += Sequence_number 114 | pkt += Length 115 | pkt += Variable_length_payload 116 | log_info(f"Sending packet from blaster to blastee, sequence {self.RHS}, pkt info {pkt}") 117 | self.net.send_packet("blaster-eth0", pkt) 118 | self.sent_pkt_flag[self.RHS] = 1 119 | if (self.RHS - self.LHS + 1 < self.senderWindow) and (self.RHS < self.num): 120 | self.RHS += 1 121 | self.Throughput += self.length 122 | self.Goodput += self.length 123 | 124 | 125 | def calc_stats(self): 126 | # debugger() 127 | self.Total_TX_time = float(self.last_packet_sent_time - self.frist_packet_sent_time) 128 | self.Throughput = int(self.Throughput*8 / self.Total_TX_time) 129 | self.Goodput = int(self.Goodput*8 / self.Total_TX_time) 130 | 131 | 132 | def printing_stats(self): 133 | fmt = "{:^20}\t{:^20}" 134 | print("-------------------printing stats-------------------") 135 | print(fmt.format("Total_TX_time", self.Total_TX_time)) 136 | print(fmt.format("Number_of_reTX", self.Number_of_reTX)) 137 | print(fmt.format("Number_of_coarse_TOs", self.Number_of_coarse_TOs)) 138 | print(fmt.format("Throughput", self.Throughput)) 139 | print(fmt.format("Goodput", self.Goodput)) 140 | print("----------------------------------------------------") 141 | 142 | 143 | def start(self): 144 | '''A running daemon of the blaster. 145 | Receive packets until the end of time. 146 | ''' 147 | while True: 148 | try: 149 | recv = self.net.recv_packet(timeout=self.recvTimeout) 150 | except NoPackets: 151 | if (self.LHS == self.num): 152 | self.last_packet_sent_time = time.time() 153 | break 154 | self.handle_no_packet() 155 | continue 156 | except Shutdown: 157 | break 158 | 159 | self.handle_packet(recv) 160 | 161 | # debugger() 162 | self.calc_stats() 163 | self.printing_stats() 164 | self.shutdown() 165 | 166 | 167 | def shutdown(self): 168 | self.net.shutdown() 169 | 170 | 171 | def main(net, **kwargs): 172 | blaster = Blaster(net, **kwargs) 173 | blaster.start() 174 | -------------------------------------------------------------------------------- /lab-6/middlebox.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import time 4 | import threading 5 | from random import randint 6 | 7 | import switchyard 8 | from switchyard.lib.address import * 9 | from switchyard.lib.packet import * 10 | from switchyard.lib.userlib import * 11 | 12 | 13 | class Middlebox: 14 | def __init__( 15 | self, 16 | net: switchyard.llnetbase.LLNetBase, 17 | dropRate="0.19" 18 | ): 19 | self.net = net 20 | self.dropRate = float(dropRate) 21 | # TODO 22 | self.interfaces = net.interfaces() 23 | self.ip_list = [] 24 | self.eth_list = [] 25 | self.name_list = [] 26 | for i in self.interfaces: 27 | self.ip_list.append(i.ipaddr) 28 | self.eth_list.append(i.ethaddr) 29 | self.name_list.append(i.name) 30 | 31 | def handle_packet(self, recv: switchyard.llnetbase.ReceivedPacket): 32 | _, fromIface, packet = recv 33 | if fromIface == "middlebox-eth0": 34 | log_debug("Received from blaster") 35 | ''' 36 | Received data packet 37 | Should I drop it? 38 | If not, modify headers & send to blastee 39 | ''' 40 | randnum = randint(1, 100) 41 | # drop 42 | if (randnum < 20): 43 | log_info ("middlebox drop packet") 44 | # modify and send 45 | else: 46 | middlebox_eth1_num = 0 47 | for i in range(len(self.name_list)): 48 | if self.name_list[i] == "middlebox-eth1": 49 | middlebox_eth1_num = i 50 | 51 | packet[Ethernet].src = self.eth_list[middlebox_eth1_num] 52 | packet[Ethernet].dst = "20:00:00:00:00:01" 53 | log_info(f"Sending packet {packet} to blastee") 54 | self.net.send_packet("middlebox-eth1", packet) 55 | elif fromIface == "middlebox-eth1": 56 | log_debug("Received from blastee") 57 | ''' 58 | Received ACK 59 | Modify headers & send to blaster. Not dropping ACK packets! 60 | net.send_packet("middlebox-eth0", pkt) 61 | ''' 62 | randnum = randint(1, 100) 63 | # drop 64 | if (randnum < 20): 65 | log_info ("middlebox drop packet") 66 | # modify and send 67 | else: 68 | middlebox_eth0_num = 0 69 | for i in range(len(self.name_list)): 70 | if self.name_list[i] == "middlebox-eth0": 71 | middlebox_eth0_num = i 72 | 73 | packet[Ethernet].src = self.eth_list[middlebox_eth0_num] 74 | packet[Ethernet].dst = "10:00:00:00:00:01" 75 | log_info(f"Sending packet {packet} to blaster") 76 | self.net.send_packet("middlebox-eth0", packet) 77 | else: 78 | log_debug("Oops :))") 79 | 80 | def start(self): 81 | '''A running daemon of the router. 82 | Receive packets until the end of time. 83 | ''' 84 | while True: 85 | try: 86 | recv = self.net.recv_packet(timeout=1.0) 87 | except NoPackets: 88 | continue 89 | except Shutdown: 90 | break 91 | 92 | self.handle_packet(recv) 93 | 94 | self.shutdown() 95 | 96 | def shutdown(self): 97 | self.net.shutdown() 98 | 99 | 100 | def main(net, **kwargs): 101 | middlebox = Middlebox(net, **kwargs) 102 | middlebox.start() 103 | -------------------------------------------------------------------------------- /lab-6/report/lab_6.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vectormoon/Computer-Network-Lab/48868ca1b2e8bbfca8921846844574b840dc111f/lab-6/report/lab_6.pdf -------------------------------------------------------------------------------- /lab-6/report/lab_6_blastee.pcapng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vectormoon/Computer-Network-Lab/48868ca1b2e8bbfca8921846844574b840dc111f/lab-6/report/lab_6_blastee.pcapng -------------------------------------------------------------------------------- /lab-6/report/lab_6_blaster.pcapng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vectormoon/Computer-Network-Lab/48868ca1b2e8bbfca8921846844574b840dc111f/lab-6/report/lab_6_blaster.pcapng -------------------------------------------------------------------------------- /lab-6/report/lab_6_middlebox.pcapng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vectormoon/Computer-Network-Lab/48868ca1b2e8bbfca8921846844574b840dc111f/lab-6/report/lab_6_middlebox.pcapng -------------------------------------------------------------------------------- /lab-6/start_mininet.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import sys 4 | 5 | from mininet.topo import Topo 6 | from mininet.net import Mininet 7 | from mininet.log import lg 8 | from mininet.node import CPULimitedHost 9 | from mininet.link import TCLink 10 | from mininet.util import irange, custom, quietRun, dumpNetConnections 11 | from mininet.cli import CLI 12 | 13 | from time import sleep, time 14 | from subprocess import Popen, PIPE 15 | import subprocess 16 | import argparse 17 | import os 18 | 19 | parser = argparse.ArgumentParser(description="Mininet portion of pyrouter") 20 | # no arguments needed as yet :-) 21 | args = parser.parse_args() 22 | lg.setLogLevel('info') 23 | 24 | class PyRouterTopo(Topo): 25 | 26 | def __init__(self, args): 27 | # Add default members to class. 28 | super(PyRouterTopo, self).__init__() 29 | 30 | # Host and link configuration 31 | # 32 | # 33 | # blaster 34 | # \ 35 | # middlebox 36 | # / 37 | # blastee 38 | # 39 | 40 | nodeconfig = {'cpu':-1} 41 | self.addHost('blaster', **nodeconfig) 42 | self.addHost('blastee', **nodeconfig) 43 | self.addHost('middlebox', **nodeconfig) 44 | 45 | linkconfig = { 46 | 'bw': 10, 47 | 'delay': '37.5ms', # Feel free to play with this value to see how it affects the communication 48 | 'loss': 0.0 49 | } 50 | 51 | for node in ['blaster','blastee']: 52 | self.addLink(node, 'middlebox', **linkconfig) 53 | 54 | def set_ip_pair(net, node1, node2, ip1, ip2): 55 | node1 = net.get(node1) 56 | ilist = node1.connectionsTo(net.get(node2)) # returns list of tuples 57 | intf = ilist[0] 58 | intf[0].setIP(ip1) 59 | intf[1].setIP(ip2) 60 | 61 | def reset_macs(net, node, macbase): 62 | ifnum = 1 63 | node_object = net.get(node) 64 | for intf in node_object.intfList(): 65 | node_object.setMAC(macbase.format(ifnum), intf) 66 | ifnum += 1 67 | 68 | for intf in node_object.intfList(): 69 | print(node,intf,node_object.MAC(intf)) 70 | 71 | def set_route(net, fromnode, prefix, gw): 72 | node_object = net.get(fromnode) 73 | node_object.cmdPrint("route add -net {} gw {}".format(prefix, gw)) 74 | 75 | def setup_addressing(net): 76 | ''' 77 | * reset_macs call sets the MAC address of the nodes in the network 78 | * blaster and blastee has a single port, hence the MAC address ends with :01 79 | * middlebox has two ports, MAC address ends with :01 and :02 respectively, that are connected to the blaster and blastee. 80 | ''' 81 | reset_macs(net, 'blaster', '10:00:00:00:00:{:02x}') 82 | reset_macs(net, 'blastee', '20:00:00:00:00:{:02x}') 83 | reset_macs(net, 'middlebox', '40:00:00:00:00:{:02x}') 84 | ''' 85 | * set_ip_pair call assigns IP addresses of the interfaces 86 | * convention is same as MAC address 87 | * middlebox has two IP addresses: 192.168.100.2 and 192.168.200.2 - connected to blaster and blastee respectively 88 | ''' 89 | set_ip_pair(net, 'blaster','middlebox','192.168.100.1/30','192.168.100.2/30') 90 | set_ip_pair(net, 'blastee','middlebox','192.168.200.1/30','192.168.200.2/30') 91 | set_route(net, 'blaster', '192.168.200.0/24', '192.168.100.2') 92 | set_route(net, 'blastee', '192.168.100.0/24', '192.168.200.2') 93 | 94 | def disable_ipv6(net): 95 | for v in net.values(): 96 | v.cmdPrint('sysctl -w net.ipv6.conf.all.disable_ipv6=1') 97 | v.cmdPrint('sysctl -w net.ipv6.conf.default.disable_ipv6=1') 98 | 99 | def main(): 100 | topo = PyRouterTopo(args) 101 | net = Mininet(topo=topo, link=TCLink, cleanup=True, controller=None) 102 | setup_addressing(net) 103 | disable_ipv6(net) 104 | net.interact() 105 | 106 | if __name__ == '__main__': 107 | main() 108 | -------------------------------------------------------------------------------- /lab-6/testcases/test_submit.py: -------------------------------------------------------------------------------- 1 | import re 2 | import pathlib 3 | from unittest import TestCase, main 4 | 5 | 6 | labNum = 6 # 1-7 7 | reportPattern = f"\\d{{9}}[\\u4e00-\\u9fa5]+_lab_{labNum}" 8 | 9 | 10 | class TestDir(TestCase): 11 | def test_check_report(self): 12 | workingDir = pathlib.Path(".") 13 | reportDir = workingDir / "report" 14 | self.assertTrue( 15 | reportDir.exists(), 16 | "Report directory `report/` doesn't exist" 17 | ) 18 | reports = list(reportDir.glob("*.pdf")) 19 | self.assertNotEqual(len(reports), 0, "No report PDF found") 20 | self.assertEqual(len(reports), 1, "More than one report PDF found") 21 | for report in reports: 22 | self.assertTrue( 23 | re.match(reportPattern, report.stem) is not None, 24 | "Wrong name of report PDF" 25 | ) 26 | for capfile in reportDir.glob("*.pcap*"): 27 | self.assertTrue( 28 | capfile.stem.startswith(f"lab_{labNum}"), 29 | f"Wrong name of capture file '{capfile}'. " 30 | f"Should start with 'lab_{labNum}'" 31 | ) 32 | 33 | def test_check_src(self): 34 | workingDir = pathlib.Path(".") 35 | srcNames = { 36 | "blastee.py", 37 | "blaster.py", 38 | "middlebox.py" 39 | } 40 | pyfiles = set(map(lambda f: f.name, workingDir.glob("*.py"))) 41 | for srcfile in srcNames: 42 | self.assertTrue( 43 | srcfile in pyfiles, 44 | f"'{srcfile}' cannot be found" 45 | ) 46 | 47 | if __name__ == '__main__': 48 | main() 49 | -------------------------------------------------------------------------------- /lab-7/README.md: -------------------------------------------------------------------------------- 1 | # Lab-7 2 | 3 | Refer to [Lab-7 manual](https://pavinberg.gitbook.io/nju-network-lab/lab-7) 4 | -------------------------------------------------------------------------------- /lab-7/cachingServer/cacheTable.py: -------------------------------------------------------------------------------- 1 | ''' CacheTable used by CachingServer. 2 | 3 | !NOTICE! You should NOT change this file! 4 | 5 | CacheTable is a dict-like table storing . 6 | 7 | CacheTable is stored in memory, but may be implemented to use disk in the 8 | future. 9 | ''' 10 | import time 11 | from collections import UserDict 12 | 13 | from utils.tracer import trace 14 | 15 | 16 | __all__ = ["HTTPCacheItem", "CacheTable"] 17 | 18 | 19 | class HTTPCacheItem: 20 | ''' The value of the CacheTable ''' 21 | def __init__(self, headers: list, body: bytearray): 22 | ''' Initiate an item that stores info of an HTTP response. 23 | headers: HTTP headers 24 | body: HTTP body 25 | timestamp: created time of this item. Used for expiration. 26 | ''' 27 | self.headers = headers # list of pairs 28 | self.body = body 29 | self.timestamp = time.time() # to see if it's expired 30 | 31 | 32 | class CacheTable(UserDict): 33 | ''' A dict-based cache table storing . 34 | 35 | Example: 36 | >>> ct = CacheTable() 37 | >>> ct.setHeaders(path, headers) 38 | >>> ct.appendBody(path, body) 39 | >>> headers = ct.getHeaders(path) 40 | >>> body = ct.getBody(path) 41 | ''' 42 | def __init__(self, timeout=-1): 43 | ''' Initiate a CacheTable. 44 | Params: 45 | timeout: seconds for a item to live. Negative for forever. 46 | ''' 47 | self.timeout = timeout # seconds. None for no timeout 48 | super().__init__() 49 | 50 | @trace 51 | def setHeaders(self, key: str, headers): 52 | ''' Set the headers of `key`. Create a item if `key` doesn't exist. 53 | Params: 54 | key: str, key to visit 55 | headers: List[Tuple[str, str]], headers to store 56 | ''' 57 | if key in self.data: 58 | self.data[key].headers = headers 59 | else: 60 | self.data[key] = HTTPCacheItem(headers, bytearray()) 61 | 62 | @trace 63 | def getHeaders(self, key: str): 64 | ''' Get headers of `key` item. 65 | Returns: 66 | List[Tuple[str, str]] headers. 67 | ''' 68 | if key in self.data: 69 | return self.data[key].headers 70 | else: 71 | return None 72 | 73 | # return self.data[key].headers 74 | 75 | def appendBody(self, key: str, body: bytearray): 76 | ''' Append the body to the CacheItem corresponding to key. 77 | `key` should already in self.data, which means this should be called 78 | after calling self.setHeaders(). 79 | ''' 80 | self.data[key].body.extend(body) 81 | 82 | def getBody(self, key: str) -> bytearray: 83 | return self.data[key].body 84 | 85 | def expired(self, key): 86 | ''' Check if the item of `key` expired. Return True if expired. ''' 87 | if self.timeout > 0: 88 | if time.time() - self.data[key].timestamp > self.timeout: 89 | del self.data[key] # remove key 90 | return True 91 | return False 92 | -------------------------------------------------------------------------------- /lab-7/cachingServer/cachingServer.py: -------------------------------------------------------------------------------- 1 | ''' Caching Server for Content Delivery Network (CDN) 2 | 3 | CachingServer is a subclass of TCPServer that runs a server. 4 | CachingServerHttpHandler is a subclass of BaseHTTPRequestHandler that handles 5 | HTTP reqeust. 6 | 7 | There is a CacheTable in CachingServer to store CDN caches. 8 | CachingServer is also responsible for fetching content from remote main server. 9 | 10 | If the target content does not exist in the server cache, the server should 11 | fetch it from remote main server and store it in local cache for future usage. 12 | Else the server shall just response with the cache content. 13 | 14 | For optional task 2, we need to consider large content delivery. When we fetch 15 | a large content from remote, storing it locally before replying back to client 16 | is not acceptable, since the client will wait for a long time. So the server 17 | shall store and response to client simultaneously. 18 | 19 | ''' 20 | 21 | import io 22 | import sys 23 | from datetime import datetime 24 | from typing import Type, Optional, Tuple, List 25 | from http import HTTPStatus 26 | from http.server import BaseHTTPRequestHandler 27 | from http.client import HTTPConnection, HTTPResponse 28 | from socketserver import TCPServer 29 | 30 | from .cacheTable import CacheTable 31 | from utils.tracer import trace 32 | 33 | import copy 34 | import shutil 35 | import mimetypes 36 | import os 37 | 38 | __all__ = ["CachingServer", "CachingServerHttpHandler"] 39 | 40 | __version__ = "0.1" 41 | 42 | CACHE_TIMEOUT = 10 43 | 44 | BUFFER_SIZE = 64 * 1024 # bytes. 64 KB 45 | 46 | 47 | class CachingServer(TCPServer): 48 | ''' The caching server for CDN ''' 49 | def __init__(self, 50 | serverAddress: Tuple[str, str], 51 | serverRequestHandler: Type[BaseHTTPRequestHandler], 52 | mainServerAddress: str, 53 | ): 54 | ''' Construct a server. 55 | Params: 56 | serverAddresss: the server's address 57 | serverRequestHandler: handler for http request. Subclass of 58 | BaseHTTPRequestHandler. 59 | mainServerAddress: the address(include port) to main server, 60 | e.g. 172.0.10.1:8080 61 | ''' 62 | # TODO 63 | self.targetFiles = { 64 | "/doc/": "/doc/index.html", 65 | "/doc/success.jpg": "/doc/success.jpg" 66 | } 67 | # 68 | self.mainServerAddress = mainServerAddress 69 | self.cacheTable = CacheTable(timeout=CACHE_TIMEOUT) 70 | self.allow_reuse_address = True 71 | super().__init__(serverAddress, serverRequestHandler, True) 72 | 73 | def _filterHeaders(self, headers: List[Tuple[str, str]]): 74 | ''' discard some headers and return the left ''' 75 | discardHeaders = {"server", "date", "connection"} 76 | return [header for header in headers 77 | if header[0].lower() not in discardHeaders] 78 | 79 | @trace 80 | def requestMainServer(self, path: str) -> Optional[HTTPResponse]: 81 | ''' GET `path` from main server. 82 | Called by self.touchItem(). 83 | Params: 84 | path: path of target 85 | Return: 86 | HTTPResponse if successfully requested. 87 | None if failed (server is down or file not found). 88 | ''' 89 | conn = HTTPConnection(self.mainServerAddress) 90 | try: 91 | conn.request("GET", path) 92 | except ConnectionRefusedError: 93 | self.log_error(f"Cannot connect to main server '{self.mainServerAddress}'") 94 | return None 95 | response: HTTPResponse = conn.getresponse() 96 | if response.status == HTTPStatus.OK: 97 | self.log_info(f"Fetched '{path}' from main server " 98 | f"'{self.mainServerAddress}'") 99 | return response 100 | 101 | # else: status isn't ok 102 | self.log_error(f"File not found on main server '{self.mainServerAddress}'") 103 | return None 104 | 105 | def touchItem(self, path: str): 106 | ''' Touch the item of path. 107 | This method, called by HttpHandler, serves as a bridge of server and 108 | handler. 109 | If the target doesn't exsit or expires, fetch from main server. 110 | Write the headers to local cache and return the body. 111 | ''' 112 | # TODO: implement the logic described in doc-string 113 | ct = CacheTable() 114 | if (ct.getHeaders(path) is not None): 115 | headers = ct.getHeaders(path) 116 | body = ct.getBody(path) 117 | 118 | # with open('testCache_output.txt', 'a') as writer: 119 | # writer.write("touchItem\n") 120 | # writer.write(f"path\n") 121 | # writer.writelines(str(ct.getHeaders(path))) 122 | 123 | return headers, body 124 | elif (ct.expired(path) or ct.getHeaders(path) is None): 125 | response = self.requestMainServer(path) 126 | # 127 | # with open('testCache_output.txt', 'a') as writer: 128 | # writer.write("touchItem ") 129 | # # writer.write(f"{path} {self.targetFiles[path]}") 130 | # writer.writelines(str(response)) 131 | # writer.write("\n") 132 | # 133 | if (response is None): 134 | return None, None 135 | else: 136 | headers = response.getheaders() 137 | body = response.read() 138 | ct.setHeaders(path, headers) 139 | ct.appendBody(path, body) 140 | # 141 | # with open('testCache_output.txt', 'a') as writer: 142 | # writer.write("touchItemRespond ") 143 | # writer.write(f"headers: {headers}\n read: {body}") 144 | # writer.write("\n") 145 | # 146 | return headers, body 147 | 148 | 149 | def log_info(self, msg): 150 | self._logMsg("Info", msg) 151 | 152 | def log_error(self, msg): 153 | self._logMsg("Error", msg) 154 | 155 | def log_warning(self, msg): 156 | self._logMsg("Warning", msg) 157 | 158 | def _logMsg(self, info, msg): 159 | ''' Log an arbitrary message. 160 | Used by log_info, log_warning, log_error. 161 | ''' 162 | info = f"[{info}]" 163 | now = datetime.now().strftime("%Y/%m/%d-%H:%M:%S") 164 | sys.stdout.write(f"{now}| {info} {msg}\n") 165 | 166 | 167 | class CachingServerHttpHandler(BaseHTTPRequestHandler): 168 | ''' A caching server for CDN network. 169 | An HTTP request or response should have a head and an optional body. 170 | 171 | The request head will be parsed automatically in BaseHTTPRequestHandler. 172 | The path in URL will be stored in self.path. It will call self.do_GET() or 173 | self.do_HEAD() according to the request's method. You can simply consider 174 | one of them the entry of the handler. 175 | 176 | The response head is consist of status, version and multiple headers. At 177 | least it should have headers "Content-Type" and "Content-Length". The 178 | former is the type of the content to send and the latter is how many bytes 179 | the content has. Also the BaseHTTPRequestHandler provides some useful 180 | methods to create the headers. 181 | 182 | There are two io.BufferedIOBase readable and writable objects, self.rfile 183 | and self.wfile. self.rfile is used to read bytes from the client and 184 | self.wfile is used to write bytes to the client. 185 | ''' 186 | 187 | server_version = "CachingServerHTTP/" + __version__ 188 | 189 | @trace 190 | def sendHeaders(self): 191 | ''' Send HTTP headers to client''' 192 | # TODO: implement the logic of sending headers 193 | headers, body = self.server.touchItem(self.path) 194 | 195 | # with open('testCache_output.txt', 'a') as writer: 196 | # writer.write("sendHeaders ") 197 | # writer.write(str(headers)) 198 | # writer.writelines(str(body)) 199 | # writer.write("\n") 200 | 201 | if (headers is None and body is None): 202 | self.send_response(HTTPStatus.NOT_FOUND) 203 | self.end_headers() 204 | return None 205 | else: 206 | self.send_response(HTTPStatus.OK) 207 | for header in headers: 208 | self.send_header(header[0], header[1]) 209 | self.end_headers() 210 | return body 211 | 212 | def sendBody(self, body): 213 | ''' Send HTTP body to client. 214 | Should be called after calling self.sendHeaders(). Else you may get 215 | broken pipe error. 216 | ''' 217 | if (body is not None): 218 | self.wfile.write(body) 219 | 220 | @trace 221 | def do_GET(self): 222 | ''' Logic when receive a HTTP GET. 223 | Notice that the URL is automatically parsed and the path is stored in 224 | self.path. 225 | ''' 226 | # TODO: implement the logic to response a GET. 227 | # Remember to leverage the methods in CachingServer. 228 | 229 | with open('testCache_output.txt', 'a') as writer: 230 | writer.write("do_GET ") 231 | writer.write(self.path) 232 | writer.write("\n") 233 | 234 | body = self.sendHeaders() 235 | self.sendBody(body) 236 | 237 | 238 | @trace 239 | def do_HEAD(self): 240 | ''' Logic when receive a HTTP HEAD. 241 | The difference from self.do_GET() is that do_HEAD() only send HTTP 242 | headers. 243 | ''' 244 | # TODO: implement the logic to response a HEAD. 245 | # Similar to do_GET() 246 | body = self.sendHeaders() 247 | 248 | 249 | def version_string(self): 250 | ''' Return the server software version string. ''' 251 | return self.server_version 252 | 253 | def log_message(self, fmt, *args): 254 | ''' Override the method of base class ''' 255 | info = f"[From {self.client_address[0]}:{self.client_address[1]}]" 256 | now = datetime.now().strftime("%Y/%m/%d-%H:%M:%S") 257 | sys.stdout.write(f"{now}| {info} {fmt % args}\n") 258 | -------------------------------------------------------------------------------- /lab-7/dnsServer/dns_server.py: -------------------------------------------------------------------------------- 1 | '''DNS Server for Content Delivery Network (CDN) 2 | ''' 3 | 4 | import sys 5 | from socketserver import UDPServer, BaseRequestHandler 6 | from utils.dns_utils import DNS_Request, DNS_Rcode 7 | from utils.ip_utils import IP_Utils 8 | from datetime import datetime 9 | import math 10 | 11 | import re 12 | from collections import namedtuple 13 | 14 | import copy 15 | import random 16 | 17 | __all__ = ["DNSServer", "DNSHandler"] 18 | 19 | 20 | class DNSServer(UDPServer): 21 | def __init__(self, server_address, dns_file, RequestHandlerClass, bind_and_activate=True): 22 | super().__init__(server_address, RequestHandlerClass, bind_and_activate=True) 23 | self._dns_table = {} 24 | self.parse_dns_file(dns_file) 25 | 26 | def parse_dns_file(self, dns_file): 27 | # --------------------------------------------------- 28 | # TODO: your codes here. Parse the dns_table.txt file 29 | # and load the data into self._dns_table. 30 | # -------------------------------------------------- 31 | with open('dnsServer/dns_table.txt', 'r') as reader: 32 | while True: 33 | line = reader.readline() 34 | if not line: 35 | break 36 | else: 37 | table_info = line.split() 38 | self._dns_table[table_info[0]] = copy.deepcopy(table_info) 39 | 40 | 41 | @property 42 | def table(self): 43 | return self._dns_table 44 | 45 | 46 | class DNSHandler(BaseRequestHandler): 47 | """ 48 | This class receives clients' udp packet with socket handler and request data. 49 | ---------------------------------------------------------------------------- 50 | There are several objects you need to mention: 51 | - udp_data : the payload of udp protocol. 52 | - socket: connection handler to send or receive message with the client. 53 | - client_ip: the client's ip (ip source address). 54 | - client_port: the client's udp port (udp source port). 55 | - DNS_Request: a dns protocl tool class. 56 | We have written the skeleton of the dns server, all you need to do is to select 57 | the best response ip based on user's infomation (i.e., location). 58 | 59 | NOTE: This module is a very simple version of dns server, called global load ba- 60 | lance dns server. We suppose that this server knows all the ip addresses of 61 | cache servers for any given domain_name (or cname). 62 | """ 63 | 64 | def __init__(self, request, client_address, server): 65 | self.table = server.table 66 | super().__init__(request, client_address, server) 67 | # test server table 68 | # with open('testDNS_output.txt', 'w') as writer: 69 | # for key, values in self.table.items(): 70 | # writer.write(key) 71 | # writer.write(" -> ") 72 | # for value in values: 73 | # writer.write(f"{value} ") 74 | # writer.write("\n") 75 | 76 | def calc_distance(self, pointA, pointB): 77 | ''' TODO: calculate distance between two points ''' 78 | x = abs(pointA[0] - pointB[0]) 79 | y = abs(pointA[1] - pointB[1]) 80 | x = math.pow(x, 2) 81 | y = math.pow(y, 2) 82 | res = math.sqrt(x + y) 83 | return res 84 | 85 | def get_response(self, request_domain_name): 86 | response_type, response_val = (None, None) 87 | # ------------------------------------------------ 88 | # TODO: your codes here. 89 | # Determine an IP to response according to the client's IP address. 90 | # set "response_ip" to "the best IP address". 91 | client_ip, _ = self.client_address 92 | # match *, such as 93 | # request_domain_name = test.cncourse.org. 94 | # server_table_domain_name = *.cncourse.org. 95 | regex_flag = 1 96 | if (request_domain_name in self.table): 97 | regex_flag = 0 98 | if (regex_flag): 99 | for key in self.table.keys(): 100 | if "*" in key: 101 | key = key[1:] 102 | if (re.search(key, request_domain_name) is not None): 103 | request_domain_name = "*" + key 104 | if (request_domain_name in self.table): 105 | if (self.table[request_domain_name][1] == "CNAME"): 106 | response_type = "CNAME" 107 | response_val = self.table[request_domain_name][2] 108 | elif (self.table[request_domain_name][1] == "A"): 109 | response_type = "A" 110 | # only one record in the list 111 | table_len = len(self.table[request_domain_name]) 112 | min_distant = 0x3f3f3f3f 113 | if (table_len == 3): 114 | response_val = self.table[request_domain_name][2] 115 | else: 116 | # need to adopt a random load balance policy for multiple servers 117 | if (IP_Utils.getIpLocation(client_ip) is None): 118 | random_load_flag = random.randint(2, table_len - 1) 119 | response_val = self.table[request_domain_name][random_load_flag] 120 | # need to select the nearest Cache Node 121 | else: 122 | client_ip_location = IP_Utils.getIpLocation(client_ip) 123 | i = 2 124 | min_distant_flag = 0 125 | while (i < table_len): 126 | server_ip_location = IP_Utils.getIpLocation(self.table[request_domain_name][i]) 127 | if (server_ip_location is not None): 128 | res = self.calc_distance(server_ip_location, client_ip_location) 129 | if (res < min_distant): 130 | min_distant = res 131 | min_distant_flag = i 132 | i += 1 133 | if min_distant_flag == 0: 134 | response_val = None 135 | else: 136 | response_val = self.table[request_domain_name][min_distant_flag] 137 | # ------------------------------------------------- 138 | 139 | # test result 140 | # with open('testDNS_output.txt', 'w') as writer: 141 | # writer.write(f"request_domain_name: {request_domain_name}\n") 142 | # writer.write(f"response_type: {response_type}\n") 143 | # writer.write(f"response_value: {response_val}\n") 144 | # writer.write(f"response_value: {self.table[request_domain_name][2]}\n") 145 | # writer.write(f"distant: {min_distant}\n") 146 | 147 | return (response_type, response_val) 148 | 149 | 150 | def handle(self): 151 | """ 152 | This function is called once there is a dns request. 153 | """ 154 | ## init udp data and socket. 155 | udp_data, socket = self.request 156 | 157 | ## read client-side ip address and udp port. 158 | client_ip, client_port = self.client_address 159 | 160 | ## check dns format. 161 | valid = DNS_Request.check_valid_format(udp_data) 162 | if valid: 163 | ## decode request into dns object and read domain_name property. 164 | dns_request = DNS_Request(udp_data) 165 | request_domain_name = str(dns_request.domain_name) 166 | self.log_info(f"Receving DNS request from '{client_ip}' asking for " 167 | f"'{request_domain_name}'") 168 | 169 | # get caching server address 170 | response = self.get_response(request_domain_name) 171 | 172 | # response to client with response_ip 173 | if None not in response: 174 | dns_response = dns_request.generate_response(response) 175 | else: 176 | dns_response = DNS_Request.generate_error_response( 177 | error_code=DNS_Rcode.NXDomain) 178 | else: 179 | self.log_error(f"Receiving invalid dns request from " 180 | f"'{client_ip}:{client_port}'") 181 | dns_response = DNS_Request.generate_error_response( 182 | error_code=DNS_Rcode.FormErr) 183 | 184 | socket.sendto(dns_response.raw_data, self.client_address) 185 | 186 | def log_info(self, msg): 187 | self._logMsg("Info", msg) 188 | 189 | def log_error(self, msg): 190 | self._logMsg("Error", msg) 191 | 192 | def log_warning(self, msg): 193 | self._logMsg("Warning", msg) 194 | 195 | def _logMsg(self, info, msg): 196 | ''' Log an arbitrary message. 197 | Used by log_info, log_warning, log_error. 198 | ''' 199 | info = f"[{info}]" 200 | now = datetime.now().strftime("%Y/%m/%d-%H:%M:%S") 201 | sys.stdout.write(f"{now}| {info} {msg}\n") 202 | -------------------------------------------------------------------------------- /lab-7/dnsServer/dns_table.txt: -------------------------------------------------------------------------------- 1 | homepage.cncourse.org. CNAME home.cncourse.org. 2 | *.cncourse.org. CNAME home.nasa.org. 3 | *.netlab.org. CNAME home.nasa.org. 4 | home.nasa.org. A 10.0.0.1 10.0.0.2 10.0.0.3 5 | lab.nasa.org. A 10.0.0.4 10.0.0.5 6 | *.localhost.computer A 127.0.0.1 7 | -------------------------------------------------------------------------------- /lab-7/mainServer/doc/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Directory listing for /doc/ 6 | 7 | 8 |

Directory listing for /doc/

9 |
10 | 13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /lab-7/mainServer/doc/success.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vectormoon/Computer-Network-Lab/48868ca1b2e8bbfca8921846844574b840dc111f/lab-7/mainServer/doc/success.jpg -------------------------------------------------------------------------------- /lab-7/mainServer/mainServer.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import http.server 4 | from http.server import SimpleHTTPRequestHandler 5 | from functools import partial 6 | 7 | 8 | def main(argv): 9 | import argparse 10 | 11 | parser = argparse.ArgumentParser(argv) 12 | parser.add_argument('--bind', '-b', default='', metavar='ADDRESS', 13 | help='Specify alternate bind address ' 14 | '[default: all interfaces]') 15 | parser.add_argument('--directory', '-d', default=os.getcwd(), 16 | help='Specify alternative directory ' 17 | '[default:current directory]') 18 | parser.add_argument('port', action='store', 19 | default=8000, type=int, 20 | nargs='?', 21 | help='Specify alternate port [default: 8000]') 22 | args = parser.parse_args() 23 | os.chdir(args.directory) 24 | http.server.test(HandlerClass=SimpleHTTPRequestHandler, port=args.port, bind=args.bind) 25 | 26 | 27 | if __name__ == '__main__': 28 | main(sys.argv[1:]) 29 | -------------------------------------------------------------------------------- /lab-7/report/185220001_cache.log: -------------------------------------------------------------------------------- 1 | ---------------------------------------- 2 | Exception happened during processing of request from ('10.0.0.24', 51230) 3 | Traceback (most recent call last): 4 | File "/usr/local/lib/python3.6/socketserver.py", line 320, in _handle_request_noblock 5 | self.process_request(request, client_address) 6 | File "/usr/local/lib/python3.6/socketserver.py", line 351, in process_request 7 | self.finish_request(request, client_address) 8 | File "/usr/local/lib/python3.6/socketserver.py", line 364, in finish_request 9 | self.RequestHandlerClass(request, client_address, self) 10 | File "/usr/local/lib/python3.6/socketserver.py", line 724, in __init__ 11 | self.handle() 12 | File "/usr/local/lib/python3.6/http/server.py", line 418, in handle 13 | self.handle_one_request() 14 | File "/usr/local/lib/python3.6/http/server.py", line 406, in handle_one_request 15 | method() 16 | File "/NJU/utils/tracer.py", line 25, in wrapper 17 | ret = method(*args, **kwargs) 18 | File "/NJU/cachingServer/cachingServer.py", line 229, in do_GET 19 | with open('testCache_output.txt', 'a') as writer: 20 | OSError: [Errno 30] Read-only file system: 'testCache_output.txt' 21 | ---------------------------------------- 22 | ---------------------------------------- 23 | Exception happened during processing of request from ('10.0.0.24', 51232) 24 | Traceback (most recent call last): 25 | File "/usr/local/lib/python3.6/socketserver.py", line 320, in _handle_request_noblock 26 | self.process_request(request, client_address) 27 | File "/usr/local/lib/python3.6/socketserver.py", line 351, in process_request 28 | self.finish_request(request, client_address) 29 | File "/usr/local/lib/python3.6/socketserver.py", line 364, in finish_request 30 | self.RequestHandlerClass(request, client_address, self) 31 | File "/usr/local/lib/python3.6/socketserver.py", line 724, in __init__ 32 | self.handle() 33 | File "/usr/local/lib/python3.6/http/server.py", line 418, in handle 34 | self.handle_one_request() 35 | File "/usr/local/lib/python3.6/http/server.py", line 406, in handle_one_request 36 | method() 37 | File "/NJU/utils/tracer.py", line 25, in wrapper 38 | ret = method(*args, **kwargs) 39 | File "/NJU/cachingServer/cachingServer.py", line 229, in do_GET 40 | with open('testCache_output.txt', 'a') as writer: 41 | OSError: [Errno 30] Read-only file system: 'testCache_output.txt' 42 | ---------------------------------------- 43 | ---------------------------------------- 44 | Exception happened during processing of request from ('10.0.0.24', 51234) 45 | Traceback (most recent call last): 46 | File "/usr/local/lib/python3.6/socketserver.py", line 320, in _handle_request_noblock 47 | self.process_request(request, client_address) 48 | File "/usr/local/lib/python3.6/socketserver.py", line 351, in process_request 49 | self.finish_request(request, client_address) 50 | File "/usr/local/lib/python3.6/socketserver.py", line 364, in finish_request 51 | self.RequestHandlerClass(request, client_address, self) 52 | File "/usr/local/lib/python3.6/socketserver.py", line 724, in __init__ 53 | self.handle() 54 | File "/usr/local/lib/python3.6/http/server.py", line 418, in handle 55 | self.handle_one_request() 56 | File "/usr/local/lib/python3.6/http/server.py", line 406, in handle_one_request 57 | method() 58 | File "/NJU/utils/tracer.py", line 25, in wrapper 59 | ret = method(*args, **kwargs) 60 | File "/NJU/cachingServer/cachingServer.py", line 229, in do_GET 61 | with open('testCache_output.txt', 'a') as writer: 62 | OSError: [Errno 30] Read-only file system: 'testCache_output.txt' 63 | ---------------------------------------- 64 | 2021/06/16-03:55:41| [INFO] Caching server started 65 | Caching server serving on http://0.0.0.0:8036 66 | -------------------------------------------------------------------------------- /lab-7/report/185220001_client.log: -------------------------------------------------------------------------------- 1 | test_01_cache_missed_1 (testcases.test_all.TestAll) ... ERROR 2 | test_02_cache_hit_1 (testcases.test_all.TestAll) ... ERROR 3 | test_03_not_found (testcases.test_all.TestAll) ... ERROR 4 | 5 | ====================================================================== 6 | ERROR: test_01_cache_missed_1 (testcases.test_all.TestAll) 7 | ---------------------------------------------------------------------- 8 | Traceback (most recent call last): 9 | File "/home/onl/NJU_experiment/frame/testcases/test_all.py", line 29, in test_01_cache_missed_1 10 | self.cache_missed_template(targetDomain, cachePort, 11 | File "/home/onl/NJU_experiment/frame/testcases/baseTestcase.py", line 80, in cache_missed_template 12 | self.request_template(expectProcedures, visitIP, visitPort, target, dnsIP, dnsPort) 13 | File "/home/onl/NJU_experiment/frame/testcases/baseTestcase.py", line 58, in request_template 14 | status, body = curl(url, dnsIP, dnsPort, method=method) 15 | File "/home/onl/NJU_experiment/frame/utils/network.py", line 79, in curl 16 | return _request(req) 17 | File "/home/onl/NJU_experiment/frame/utils/network.py", line 18, in wrapper 18 | ret = func(*args, **kwargs) 19 | File "/home/onl/NJU_experiment/frame/utils/network.py", line 54, in _request 20 | with urllib.request.urlopen(req) as f: 21 | File "/usr/lib/python3.8/urllib/request.py", line 222, in urlopen 22 | return opener.open(url, data, timeout) 23 | File "/usr/lib/python3.8/urllib/request.py", line 525, in open 24 | response = self._open(req, data) 25 | File "/usr/lib/python3.8/urllib/request.py", line 542, in _open 26 | result = self._call_chain(self.handle_open, protocol, protocol + 27 | File "/usr/lib/python3.8/urllib/request.py", line 502, in _call_chain 28 | result = func(*args) 29 | File "/usr/lib/python3.8/urllib/request.py", line 1379, in http_open 30 | return self.do_open(http.client.HTTPConnection, req) 31 | File "/usr/lib/python3.8/urllib/request.py", line 1354, in do_open 32 | r = h.getresponse() 33 | File "/usr/lib/python3.8/http/client.py", line 1347, in getresponse 34 | response.begin() 35 | File "/usr/lib/python3.8/http/client.py", line 307, in begin 36 | version, status, reason = self._read_status() 37 | File "/usr/lib/python3.8/http/client.py", line 276, in _read_status 38 | raise RemoteDisconnected("Remote end closed connection without" 39 | http.client.RemoteDisconnected: Remote end closed connection without response 40 | 41 | ====================================================================== 42 | ERROR: test_02_cache_hit_1 (testcases.test_all.TestAll) 43 | ---------------------------------------------------------------------- 44 | Traceback (most recent call last): 45 | File "/home/onl/NJU_experiment/frame/testcases/test_all.py", line 34, in test_02_cache_hit_1 46 | self.cache_hit_template(targetDomain, cachePort, 47 | File "/home/onl/NJU_experiment/frame/testcases/baseTestcase.py", line 90, in cache_hit_template 48 | self.request_template(expectProcedures, visitIP, visitPort, target, dnsIP, dnsPort) 49 | File "/home/onl/NJU_experiment/frame/testcases/baseTestcase.py", line 58, in request_template 50 | status, body = curl(url, dnsIP, dnsPort, method=method) 51 | File "/home/onl/NJU_experiment/frame/utils/network.py", line 79, in curl 52 | return _request(req) 53 | File "/home/onl/NJU_experiment/frame/utils/network.py", line 18, in wrapper 54 | ret = func(*args, **kwargs) 55 | File "/home/onl/NJU_experiment/frame/utils/network.py", line 54, in _request 56 | with urllib.request.urlopen(req) as f: 57 | File "/usr/lib/python3.8/urllib/request.py", line 222, in urlopen 58 | return opener.open(url, data, timeout) 59 | File "/usr/lib/python3.8/urllib/request.py", line 525, in open 60 | response = self._open(req, data) 61 | File "/usr/lib/python3.8/urllib/request.py", line 542, in _open 62 | result = self._call_chain(self.handle_open, protocol, protocol + 63 | File "/usr/lib/python3.8/urllib/request.py", line 502, in _call_chain 64 | result = func(*args) 65 | File "/usr/lib/python3.8/urllib/request.py", line 1379, in http_open 66 | return self.do_open(http.client.HTTPConnection, req) 67 | File "/usr/lib/python3.8/urllib/request.py", line 1354, in do_open 68 | r = h.getresponse() 69 | File "/usr/lib/python3.8/http/client.py", line 1347, in getresponse 70 | response.begin() 71 | File "/usr/lib/python3.8/http/client.py", line 307, in begin 72 | version, status, reason = self._read_status() 73 | File "/usr/lib/python3.8/http/client.py", line 276, in _read_status 74 | raise RemoteDisconnected("Remote end closed connection without" 75 | http.client.RemoteDisconnected: Remote end closed connection without response 76 | 77 | ====================================================================== 78 | ERROR: test_03_not_found (testcases.test_all.TestAll) 79 | ---------------------------------------------------------------------- 80 | Traceback (most recent call last): 81 | File "/home/onl/NJU_experiment/frame/testcases/test_all.py", line 39, in test_03_not_found 82 | self.not_found_template(targetDomain, cachePort, 83 | File "/home/onl/NJU_experiment/frame/testcases/baseTestcase.py", line 118, in not_found_template 84 | status, body = curl(url, dnsIP, dnsPort) 85 | File "/home/onl/NJU_experiment/frame/utils/network.py", line 79, in curl 86 | return _request(req) 87 | File "/home/onl/NJU_experiment/frame/utils/network.py", line 18, in wrapper 88 | ret = func(*args, **kwargs) 89 | File "/home/onl/NJU_experiment/frame/utils/network.py", line 54, in _request 90 | with urllib.request.urlopen(req) as f: 91 | File "/usr/lib/python3.8/urllib/request.py", line 222, in urlopen 92 | return opener.open(url, data, timeout) 93 | File "/usr/lib/python3.8/urllib/request.py", line 525, in open 94 | response = self._open(req, data) 95 | File "/usr/lib/python3.8/urllib/request.py", line 542, in _open 96 | result = self._call_chain(self.handle_open, protocol, protocol + 97 | File "/usr/lib/python3.8/urllib/request.py", line 502, in _call_chain 98 | result = func(*args) 99 | File "/usr/lib/python3.8/urllib/request.py", line 1379, in http_open 100 | return self.do_open(http.client.HTTPConnection, req) 101 | File "/usr/lib/python3.8/urllib/request.py", line 1354, in do_open 102 | r = h.getresponse() 103 | File "/usr/lib/python3.8/http/client.py", line 1347, in getresponse 104 | response.begin() 105 | File "/usr/lib/python3.8/http/client.py", line 307, in begin 106 | version, status, reason = self._read_status() 107 | File "/usr/lib/python3.8/http/client.py", line 276, in _read_status 108 | raise RemoteDisconnected("Remote end closed connection without" 109 | http.client.RemoteDisconnected: Remote end closed connection without response 110 | 111 | ---------------------------------------------------------------------- 112 | Ran 3 tests in 0.919s 113 | 114 | FAILED (errors=3) 115 | -------------------------------------------------------------------------------- /lab-7/report/185220001_dns.log: -------------------------------------------------------------------------------- 1 | 2021/06/16-03:55:40| [INFO] DNS server started 2 | DNS server serving on 0.0.0.0:8036 3 | 2021/06/16-03:55:43| [Info] Receving DNS request from '10.0.0.24' asking for 'stfw.localhost.computer.' 4 | 2021/06/16-03:55:43| [Info] Receving DNS request from '10.0.0.24' asking for 'stfw.localhost.computer.' 5 | 2021/06/16-03:55:43| [Info] Receving DNS request from '10.0.0.24' asking for 'stfw.localhost.computer.' 6 | -------------------------------------------------------------------------------- /lab-7/report/lab_7.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vectormoon/Computer-Network-Lab/48868ca1b2e8bbfca8921846844574b840dc111f/lab-7/report/lab_7.pdf -------------------------------------------------------------------------------- /lab-7/requirements.txt: -------------------------------------------------------------------------------- 1 | dnslib==0.9.14 2 | rpyc==5.0.1 3 | -------------------------------------------------------------------------------- /lab-7/runCachingServer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | '''Run a Caching Server 3 | 4 | !NOTICE! You should NOT change this file! 5 | 6 | Command line arguments: 7 | mainserver: main server adress. 8 | [port]: port number, default: 1222. 9 | Notice that if you run the program not in root, the port number 10 | should be greater than 1024. 11 | Usage: 12 | $ ./runCachingServer.py [port] 13 | Example: 14 | $ ./runCachingServer.py localhost:8000 1222 15 | ''' 16 | 17 | import sys 18 | import argparse 19 | import pathlib 20 | import shutil 21 | from cachingServer.cachingServer import CachingServer, CachingServerHttpHandler 22 | 23 | 24 | def parse_args(argv): 25 | ''' Parse arguments of the program ''' 26 | parser = argparse.ArgumentParser(description="Run HTTP caching server") 27 | parser.add_argument("mainserver", type=str, help="remote main server to fetch file") 28 | parser.add_argument("port", action='store', default=1222, type=int, nargs='?', 29 | help="port to start the http service (default: 1222)") 30 | parser.add_argument("--rpcserver", "-r", type=str, help="rpc server for tracing (used in test mode)") 31 | args = parser.parse_args(argv) 32 | return args.mainserver, args.port, args.rpcserver 33 | 34 | 35 | def connectRPC(rpcAddr): 36 | import utils.tracer 37 | if rpcAddr is not None: 38 | try: 39 | addr, port = rpcAddr.split(":") 40 | except ValueError: 41 | print(f"wrong format of rpc server address '{rpcAddr}'", file=sys.stderr) 42 | sys.exit() 43 | utils.tracer.initateRPCServerProxy(addr, port) 44 | 45 | 46 | def main(argv): 47 | ''' Entry of the program ''' 48 | mainserver, port, rpcAddr = parse_args(argv) 49 | connectRPC(rpcAddr) 50 | 51 | # start a server 52 | # if you run locally, this will start a http service at 53 | # http://localhost: 54 | with CachingServer(("", port), CachingServerHttpHandler, mainserver) as httpd: 55 | print(f"Caching server serving on http://{httpd.server_address[0]}:" 56 | f"{httpd.server_address[1]}") 57 | try: 58 | httpd.serve_forever() 59 | except KeyboardInterrupt: 60 | httpd.shutdown() 61 | httpd.server_close() 62 | 63 | 64 | if __name__ == '__main__': 65 | # main("localhost:8000", 1222) # for development 66 | main(sys.argv[1:]) 67 | -------------------------------------------------------------------------------- /lab-7/runDNSServer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | '''Run a DNS Server 3 | 4 | !NOTICE! You should NOT change this file! 5 | 6 | Command line arguments: 7 | [port]: port number, default: 9999. 8 | Notice that if you run the program not in root, the port number 9 | should be greater than 1024. 10 | Usage: 11 | $ ./runDNSServer.py [port] 12 | ''' 13 | 14 | import sys 15 | import argparse 16 | import pathlib 17 | import shutil 18 | from dnsServer.dns_server import DNSServer, DNSHandler 19 | 20 | 21 | def parse_args(argv): 22 | ''' Parse arguments of the program ''' 23 | parser = argparse.ArgumentParser(description="Run DNS server") 24 | parser.add_argument("port", action='store', default=9999, type=int, nargs='?', 25 | help="port to start the dns service (default: 9999)") 26 | parser.add_argument("file", action='store', default="./dnsServer/dns_table.txt", 27 | type=str, nargs='?', help="file used to load dns table (default: ./dnsServer/dns_table.txt)") 28 | args = parser.parse_args(argv) 29 | return (args.port, args.file) 30 | 31 | 32 | def main(argv): 33 | ''' Entry of the program ''' 34 | port,dns_file = parse_args(argv) 35 | 36 | # start a server 37 | # if you run locally, this will start a dns service at 38 | # localhost: 39 | with DNSServer(("", port), dns_file, DNSHandler) as dnsd: 40 | # print(f"Start a dns server listening on {HOST}:{PORT}") 41 | print(f"DNS server serving on {dnsd.server_address[0]}:" 42 | f"{dnsd.server_address[1]}") 43 | try: 44 | dnsd.serve_forever() 45 | except KeyboardInterrupt: 46 | dnsd.shutdown() 47 | dnsd.server_close() 48 | 49 | if __name__ == '__main__': 50 | main(sys.argv[1:]) 51 | -------------------------------------------------------------------------------- /lab-7/success.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vectormoon/Computer-Network-Lab/48868ca1b2e8bbfca8921846844574b840dc111f/lab-7/success.jpg -------------------------------------------------------------------------------- /lab-7/testDNS.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from utils.network import resolve_domain_name 4 | 5 | dnsIP, dnsPort = "localhost", 9999 6 | 7 | def resolveDomain(domain): 8 | resp = resolve_domain_name(domain, dnsIP, dnsPort) 9 | if resp: 10 | return resp.response_val 11 | return None 12 | 13 | result = resolveDomain("home.nasa.org") 14 | print(result) 15 | 16 | result = resolveDomain("test.nasa.org") 17 | print(result) 18 | -------------------------------------------------------------------------------- /lab-7/testDNS_output.txt: -------------------------------------------------------------------------------- 1 | domain.non.exists. 2 | -------------------------------------------------------------------------------- /lab-7/test_entry.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | '''Testcases entry 3 | 4 | !NOTICE! You should NOT change this file! 5 | 6 | Usage: 7 | - Test DNS server: 8 | $ python3 test_entry.py dns 9 | - Test caching server: 10 | $ python3 test_entry.py cache 11 | - Test all: 12 | $ python3 test_entry.py all 13 | ''' 14 | 15 | import sys 16 | import argparse 17 | import time 18 | import rpyc 19 | import unittest 20 | from testcases import test_cache, test_dns, test_all 21 | 22 | from utils.manageservice import startForCaching, startForDNS, startAll, terminateAll 23 | 24 | 25 | VERBOSITY = 2 26 | 27 | 28 | def parse_args(argv): 29 | ''' Parse arguments of the program ''' 30 | parser = argparse.ArgumentParser(description="Run tests") 31 | parser.add_argument("case", choices=['all', 'dns', 'cache'], 32 | help="Choose a test case to run") 33 | parser.add_argument("--dnsip", default="127.0.0.1", type=str, 34 | help="DNS server IP address (default '127.0.0.1')") 35 | parser.add_argument("--mainip", default="127.0.0.1", type=str, 36 | help="Main server IP address (default '127.0.0.1')") 37 | parser.add_argument("--rpcip", default="127.0.0.1", type=str, 38 | help="RPC server IP address (default '127.0.0.1')") 39 | parser.add_argument("--cacheip", default="127.0.0.1", type=str, 40 | help="Caching server IP address (default '127.0.0.1')") 41 | parser.add_argument("--dnsport", "-n", default=9999, type=int, 42 | help="Port for DNS server service (default: 9999)") 43 | parser.add_argument("--mainport", "-m", default=8000, type=int, 44 | help="Port for main server service (default: 8000)") 45 | parser.add_argument("--cacheport", "-c", default=1222, type=int, 46 | help="Port for caching server service (default: 1222)") 47 | parser.add_argument("--rpcport", "-r", default=3322, type=int, 48 | help="Port for RPC tracing server service (default: 3322)") 49 | parser.add_argument("--maindir", "-d", default="mainServer/", type=str, 50 | help="Working directory for main server (default: 'mainServer/'") 51 | 52 | args = parser.parse_args(argv) 53 | return args 54 | 55 | 56 | def testCache(args): 57 | test_cache.cacheIP = args.cacheip 58 | test_cache.cachePort = args.cacheport 59 | test_cache.mainWorkDir = args.maindir 60 | try: 61 | success = startForCaching(mainPort=args.mainport, mainWorkDir=args.maindir, 62 | cachePort=args.cacheport, rpcPort=args.rpcport) 63 | if success: 64 | time.sleep(1) 65 | test_cache.rpcserver = rpyc.connect(args.rpcip, port=args.rpcport) 66 | suite = unittest.TestLoader().loadTestsFromModule(test_cache) 67 | unittest.TextTestRunner(verbosity=VERBOSITY).run(suite) 68 | finally: 69 | terminateAll() 70 | 71 | 72 | def testDNS(args): 73 | test_dns.dnsIP = args.dnsip 74 | test_dns.dnsPort = args.dnsport 75 | try: 76 | success = startForDNS(port=args.dnsport) 77 | if success: 78 | time.sleep(1) 79 | suite = unittest.TestLoader().loadTestsFromModule(test_dns) 80 | unittest.TextTestRunner(verbosity=VERBOSITY).run(suite) 81 | finally: 82 | terminateAll() 83 | 84 | 85 | def testAll(args): 86 | test_all.dnsIP = args.dnsip 87 | test_all.dnsPort = args.dnsport 88 | test_all.cachePort = args.cacheport 89 | test_all.mainWorkDir = args.maindir 90 | try: 91 | success = startAll(dnsPort=args.dnsport, 92 | mainPort=args.mainport, mainWorkDir=args.maindir, 93 | cachePort=args.cacheport, rpcPort=args.rpcport) 94 | if success: 95 | time.sleep(1) 96 | test_all.rpcserver = rpyc.connect(args.rpcip, port=args.rpcport) 97 | suite = unittest.TestLoader().loadTestsFromModule(test_all) 98 | unittest.TextTestRunner(verbosity=VERBOSITY).run(suite) 99 | finally: 100 | terminateAll() 101 | 102 | 103 | def main(argv): 104 | args = parse_args(argv) 105 | if args.case == 'dns': 106 | testDNS(args) 107 | elif args.case == 'cache': 108 | testCache(args) 109 | elif args.case == 'all': 110 | testAll(args) 111 | 112 | 113 | if __name__ == '__main__': 114 | main(sys.argv[1:]) 115 | -------------------------------------------------------------------------------- /lab-7/testcases/baseTestcase.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import unittest 4 | import time 5 | import pathlib 6 | from http import HTTPStatus 7 | from utils.rpcServer import Calls 8 | from utils.network import curl, createUrl 9 | 10 | __all__ = ['BaseTestcase'] 11 | 12 | 13 | class BaseTestcase(unittest.TestCase): 14 | def __init__(self, rpcserver, checkDir, targetFiles, 15 | *args, **kwargs): 16 | self.rpcserver = rpcserver 17 | self.checkDir = checkDir 18 | self.targetFiles = targetFiles 19 | super().__init__(*args, **kwargs) 20 | 21 | def compareProcedures(self, procedures, expectProcedures): 22 | eproIter = iter(expectProcedures) 23 | for i, pro in enumerate(procedures): 24 | try: 25 | epro = next(eproIter) 26 | except StopIteration: 27 | break 28 | if pro not in Calls.all(): 29 | continue 30 | while epro.optional and pro != epro.method: 31 | # skip the optional epro 32 | epro = next(eproIter) 33 | self.assertEqual(pro, epro.method, 34 | "After calling methods:\n - {}"\ 35 | .format('\n - '.join(procedures[:i])) 36 | + f"\nExpecting `{epro.method}` but you called `{pro}`") 37 | else: 38 | try: 39 | epro = next(eproIter) 40 | while epro.optional: 41 | epro = next(eproIter) 42 | except StopIteration: 43 | return 44 | # expectProcedures is longer that procedures 45 | self.assertTrue(False, f"Expecting `{epro.method}` but you call nothing") 46 | 47 | def checkResponse(self, body, target): 48 | if self.checkDir is not None: 49 | filepath = pathlib.Path(self.checkDir) / self.targetFiles[target] 50 | else: 51 | filepath = self.targetFiles[target] 52 | with open(filepath, "rb") as fp: 53 | self.assertEqual(body, fp.read(), "File damaged") 54 | 55 | def request_template(self, expectProcedures, visitIP, visitPort, target, 56 | dnsIP=None, dnsPort=None, method="GET"): 57 | url = createUrl(netloc=visitIP, port=visitPort, path=target) 58 | status, body = curl(url, dnsIP, dnsPort, method=method) 59 | self.assertEqual(status, HTTPStatus.OK, 60 | f"request to caching server failed with status '{status}'") 61 | if method == "GET": 62 | self.checkResponse(body, target) 63 | 64 | time.sleep(0.3) # wait for asynchronous RPC calls arrive at RPC server 65 | success, procedures = self.rpcserver.root.getProcedures() 66 | if success: 67 | self.compareProcedures(procedures, expectProcedures) 68 | else: 69 | print(f"RPC calling raises exceptions: {procedures}", file=sys.stderr) 70 | 71 | def cache_missed_template(self, visitIP, visitPort, target, 72 | dnsIP=None, dnsPort=None): 73 | expectProcedures = [ 74 | Calls.do_GET(), 75 | Calls.fetch(), 76 | Calls.send(optional=True), 77 | Calls.storeInCache(), 78 | Calls.send(optional=True), 79 | Calls.loadCache(optional=True) 80 | ] 81 | self.request_template(expectProcedures, visitIP, visitPort, target, dnsIP, dnsPort) 82 | 83 | def cache_hit_template(self, visitIP, visitPort, target, 84 | dnsIP=None, dnsPort=None): 85 | expectProcedures = [ 86 | Calls.do_GET(), 87 | Calls.loadCache(optional=True), 88 | Calls.send(), 89 | Calls.loadCache(optional=True) 90 | ] 91 | self.request_template(expectProcedures, visitIP, visitPort, target, dnsIP, dnsPort) 92 | 93 | def cache_missed_HEAD_template(self, visitIP, visitPort, target, 94 | dnsIP=None, dnsPort=None): 95 | expectProcedures = [ 96 | Calls.do_HEAD(), 97 | Calls.fetch(), 98 | Calls.storeInCache(), 99 | Calls.send(), 100 | Calls.loadCache(optional=True) 101 | ] 102 | self.request_template(expectProcedures, visitIP, visitPort, 103 | target, dnsIP, dnsPort, method="HEAD") 104 | 105 | def cache_hit_HEAD_template(self, visitIP, visitPort, target, 106 | dnsIP=None, dnsPort=None): 107 | expectProcedures = [ 108 | Calls.do_HEAD(), 109 | Calls.loadCache(optional=True), 110 | Calls.send(), 111 | Calls.loadCache(optional=True) 112 | ] 113 | self.request_template(expectProcedures, visitIP, visitPort, 114 | target, dnsIP, dnsPort, method="HEAD") 115 | 116 | def not_found_template(self, visitIP, visitPort, target, 117 | dnsIP=None, dnsPort=None): 118 | url = createUrl(netloc=visitIP, port=visitPort, path=target) 119 | status, body = curl(url, dnsIP, dnsPort) 120 | self.assertEqual(status, HTTPStatus.NOT_FOUND, 121 | "should get a HTTP NOT FOUND response") 122 | 123 | success, procedures = self.rpcserver.root.getProcedures() 124 | expectProcedures = [ 125 | Calls.do_GET(), 126 | Calls.fetch() 127 | ] 128 | if success: 129 | self.compareProcedures(procedures, expectProcedures) 130 | else: 131 | print(f"RPC calling raises exceptions: {procedures}", file=sys.stderr) 132 | 133 | def tearDown(self): 134 | self.rpcserver.root.clearProcedures() 135 | time.sleep(0.3) 136 | -------------------------------------------------------------------------------- /lab-7/testcases/test_all.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from time import time 4 | from .baseTestcase import BaseTestcase 5 | 6 | 7 | # should be set by test_entry 8 | dnsIP = None 9 | dnsPort = None 10 | rpcserver = None 11 | mainWorkDir = None 12 | cachePort = None 13 | 14 | targetDomain = "stfw.localhost.computer" 15 | 16 | targetFiles = { 17 | "doc/": "doc/index.html", 18 | "doc/success.jpg": "doc/success.jpg" 19 | } 20 | 21 | 22 | class TestAll(BaseTestcase): 23 | def __init__(self, *args, **kwargs): 24 | super().__init__(rpcserver, mainWorkDir, targetFiles, 25 | *args, **kwargs) 26 | 27 | def test_01_cache_missed_1(self): 28 | target = "doc/success.jpg" 29 | self.cache_missed_template(targetDomain, cachePort, 30 | target, dnsIP, dnsPort) 31 | 32 | def test_02_cache_hit_1(self): 33 | target = "doc/success.jpg" 34 | self.cache_hit_template(targetDomain, cachePort, 35 | target, dnsIP, dnsPort) 36 | 37 | def test_03_not_found(self): 38 | target = "noneexist" 39 | self.not_found_template(targetDomain, cachePort, 40 | target, dnsIP, dnsPort) 41 | -------------------------------------------------------------------------------- /lab-7/testcases/test_cache.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | '''Testcases for caching server 3 | 4 | !NOTICE! You should NOT change this file! 5 | 6 | You should run the test by running "test_entry.py" 7 | ''' 8 | 9 | from .baseTestcase import BaseTestcase 10 | 11 | 12 | # should be set by test_entry 13 | rpcserver = None 14 | mainWorkDir = None 15 | cacheIP = None 16 | cachePort = None 17 | 18 | targetFiles = { 19 | "doc/": "doc/index.html", 20 | "doc/success.jpg": "doc/success.jpg" 21 | } 22 | 23 | 24 | class TestCache(BaseTestcase): 25 | def __init__(self, *args, **kwargs): 26 | super().__init__(rpcserver, mainWorkDir, targetFiles, 27 | *args, **kwargs) 28 | 29 | def test_01_cache_missed_1(self): 30 | target = "doc/" 31 | 32 | with open('testCache_output.txt', 'a') as writer: 33 | writer.write("\ntest_01_cache_missed_1\n") 34 | writer.write(f"cacheIP: {cacheIP}, cachePort: {cachePort}, target: {target}\n") 35 | 36 | self.cache_missed_template(cacheIP, cachePort, target) 37 | 38 | def test_02_cache_hit_1(self): 39 | target = "doc/" 40 | 41 | with open('testCache_output.txt', 'a') as writer: 42 | writer.write("\ntest_02_cache_hit_1\n") 43 | writer.write(f"cacheIP: {cacheIP}, cachePort: {cachePort}, target: {target}\n") 44 | 45 | self.cache_hit_template(cacheIP, cachePort, target) 46 | 47 | def test_03_cache_missed_2(self): 48 | target = "doc/success.jpg" 49 | self.cache_missed_template(cacheIP, cachePort, target) 50 | 51 | def test_04_cache_hit_2(self): 52 | target = "doc/success.jpg" 53 | self.cache_hit_template(cacheIP, cachePort, target) 54 | 55 | def test_05_HEAD(self): 56 | target = "doc/success.jpg" 57 | self.cache_hit_HEAD_template(cacheIP, cachePort, target) 58 | 59 | def test_06_not_found(self): 60 | target = "noneexist" 61 | 62 | with open('testCache_output.txt', 'a') as writer: 63 | writer.write("\n\n\n\n\ntest_06_not_found\n") 64 | writer.write(f"cacheIP: {cacheIP}, cachePort: {cachePort}, target: {target}\n") 65 | 66 | self.not_found_template(cacheIP, cachePort, target) 67 | -------------------------------------------------------------------------------- /lab-7/testcases/test_dns.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import socket 5 | import unittest 6 | from utils.dns_utils import DNS_Request, DNS_Response 7 | from utils.network import resolve_domain_name 8 | 9 | 10 | # should be set by test_entry 11 | dnsIP = None 12 | dnsPort = None 13 | 14 | 15 | class TestDNS(unittest.TestCase): 16 | 17 | def resolveDomain(self, domain_name): 18 | global dnsIP, dnsPort 19 | return resolve_domain_name(domain_name, dnsIP, dnsPort) 20 | 21 | def test_non_exist(self): 22 | res = self.resolveDomain("domain.non.exists") 23 | self.assertEqual(res, None) 24 | 25 | def test_cname1(self): 26 | res = self.resolveDomain("test.cncourse.org.") 27 | self.assertEqual(res.response_type, 5) 28 | self.assertEqual(str(res.response_val), "home.nasa.org.") 29 | 30 | def test_cname2(self): 31 | res = self.resolveDomain("home.cncourse.org") 32 | self.assertEqual(res.response_type, 5) 33 | self.assertEqual(str(res.response_val), "home.nasa.org.") 34 | 35 | def test_location1(self): 36 | res = self.resolveDomain("home.nasa.org.") 37 | self.assertEqual(res.response_type, 1) 38 | self.assertEqual(str(res.response_val), "10.0.0.1") 39 | 40 | def test_location2(self): 41 | res = self.resolveDomain("lab.nasa.org") 42 | self.assertEqual(res.response_type, 1) 43 | self.assertEqual(str(res.response_val), "10.0.0.5") 44 | 45 | 46 | if __name__ == '__main__': 47 | unittest.main() 48 | -------------------------------------------------------------------------------- /lab-7/testcases/test_submit.py: -------------------------------------------------------------------------------- 1 | import re 2 | import pathlib 3 | from unittest import TestCase, main 4 | 5 | 6 | labNum = 7 # 1-7 7 | reportPattern = f"\\d{{9}}[\\u4e00-\\u9fa5]+_lab_{labNum}" 8 | 9 | 10 | class TestDir(TestCase): 11 | def test_check_report(self): 12 | workingDir = pathlib.Path(".") 13 | reportDir = workingDir / "report" 14 | self.assertTrue( 15 | reportDir.exists(), 16 | "Report directory `report/` doesn't exist" 17 | ) 18 | reports = list(reportDir.glob("*.pdf")) 19 | self.assertNotEqual(len(reports), 0, "No report PDF found") 20 | self.assertEqual(len(reports), 1, "More than one report PDF found") 21 | for report in reports: 22 | self.assertTrue( 23 | re.match(reportPattern, report.stem) is not None, 24 | "Wrong name of report PDF" 25 | ) 26 | for capfile in reportDir.glob("*.pcap*"): 27 | self.assertTrue( 28 | capfile.stem.startswith(f"lab_{labNum}"), 29 | f"Wrong name of capture file '{capfile}'. " 30 | f"Should start with 'lab_{labNum}'" 31 | ) 32 | 33 | 34 | if __name__ == '__main__': 35 | main() 36 | -------------------------------------------------------------------------------- /lab-7/utils/dns_utils.py: -------------------------------------------------------------------------------- 1 | from dnslib import * 2 | from enum import Enum 3 | import struct 4 | 5 | class DNS_Rcode(Enum): 6 | NoError = 0 # No Error 7 | FormErr = 1 # Format Error 8 | ServFail = 2 # Server Failure 9 | NXDomain = 3 # Non-Existent Domain 10 | # ... 11 | # ignore other coeds. 12 | 13 | 14 | class DNS_Request: 15 | """ 16 | A simple dns toolkit wraped from dnslib. 17 | ------------------------------------------------------------------------ 18 | NOTE: This module is only a partial of DNS protocol. As is mentioned, 19 | it's just a wrapper from dnslib. The purpose of this module is to make 20 | it easier for you to finish the lab, without caring many details on the 21 | protocol itself. We also do not consider about the performance issue. 22 | """ 23 | def __init__(self, raw_data): 24 | self._raw_data = raw_data 25 | # Only support one question now. 26 | self._domain_name = DNSRecord.parse(raw_data).questions[0].get_qname() 27 | 28 | @property 29 | def domain_name(self): 30 | return self._domain_name 31 | 32 | @property 33 | def raw_data(self): 34 | return self._raw_data 35 | 36 | def to_bytes(self): 37 | return self._raw_data 38 | 39 | def generate_response(self, response): 40 | """ 41 | This function generates a dns response for the current request. 42 | return: DNS_Response object 43 | qr : Query 44 | aa : Authoritative Answer 45 | ra : Recursion Available 46 | rd : Recursion Desired 47 | """ 48 | if response[0] == "CNAME": 49 | return DNS_Response(DNSRecord(header=DNSHeader(qr=1,aa=1,ra=1, rcode=DNS_Rcode.NoError.value), 50 | q=DNSQuestion(self._domain_name), 51 | a=RR(self._domain_name, rtype=QTYPE.CNAME, rdata=CNAME(response[1]))).pack()) 52 | elif response[0] == "A": 53 | return DNS_Response(DNSRecord(header=DNSHeader(qr=1,aa=1,ra=1, rcode=DNS_Rcode.NoError.value), 54 | q=DNSQuestion(self._domain_name), 55 | a=RR(self._domain_name, rdata=A(response[1]))).pack()) 56 | 57 | @classmethod 58 | def generate_error_response(cls, error_code): 59 | """ 60 | This function generates a dns response with specific error code. 61 | return: DNS_Response object 62 | usage: req_obj.generate_error_response(DNS_Rcode.FormErr) 63 | """ 64 | return DNS_Response(DNSRecord(header=DNSHeader(qr=1,aa=1,ra=1, rcode=error_code.value)).pack()) 65 | 66 | @classmethod 67 | def construct_dns_request(cls, domain_name): 68 | """ 69 | This function constructs a dns request. 70 | return: DNS_Request object 71 | usage: DNS_Request.construct_dns_request("example.com") 72 | """ 73 | raw_data = DNSRecord.question(domain_name).pack() 74 | return cls(raw_data) 75 | 76 | @staticmethod 77 | def check_valid_format(raw_data): 78 | try: 79 | d= DNSRecord.parse(raw_data) 80 | if len(d.questions) < 1: 81 | return False 82 | except DNSError as e: 83 | return False 84 | return True 85 | 86 | class DNS_Response: 87 | def __init__(self, raw_data): 88 | self._raw_data = raw_data 89 | d = DNSRecord.parse(raw_data) 90 | self._rcode = d.header.get_rcode() 91 | self._response_type = None 92 | self._response_val = None 93 | if self._rcode == DNS_Rcode.NoError.value: 94 | self._response_type = d.a.rtype 95 | self._response_val = d.a.rdata 96 | self._domain_name = d.questions[0].get_qname() 97 | 98 | @property 99 | def domain_name(self): 100 | return self._domain_name 101 | 102 | @property 103 | def response_type(self): 104 | return self._response_type 105 | 106 | @property 107 | def response_val(self): 108 | return self._response_val 109 | 110 | @property 111 | def raw_data(self): 112 | return self._raw_data -------------------------------------------------------------------------------- /lab-7/utils/ip_utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pathlib 3 | # import geoip2.webservice 4 | # import geoip2.database 5 | 6 | class IP_Utils: 7 | test_cases = { 8 | "127.0.0.1": (0,0), 9 | "10.0.0.1" : (20,0), 10 | "10.0.0.2" : (20,-10), 11 | "10.0.0.3" : (-30,118), 12 | "10.0.0.4" : (28.5,120), 13 | "10.0.0.5" : (28.5,50), 14 | } 15 | 16 | @staticmethod 17 | def getIpLocation(ip_str): 18 | """ Read the latitude and Longitude of an ip address. 19 | --------------------------------------------------------------- 20 | Args: 21 | ip_str: ip address in string format. 22 | Returns: 23 | - (latitude, longitude) : location tuple 24 | - None : the ip address do not exist in the database. 25 | If the function returns None, You should randomly select an ip address. 26 | """ 27 | if ip_str in IP_Utils.test_cases.keys(): 28 | return IP_Utils.test_cases[ip_str] 29 | raise ValueError(f"IP address {ip_str} is not in location databse") 30 | # database = pathlib.Path(__file__).parent / 'data/GeoLite2-City.mmdb' 31 | # reader = geoip2.database.Reader(str(database)) 32 | # try: 33 | # response = reader.city(ip_str) 34 | # latitude = response.location.latitude 35 | # longitude = response.location.longitude 36 | # except geoip2.errors.AddressNotFoundError: 37 | # return None 38 | # return (latitude, longitude) 39 | -------------------------------------------------------------------------------- /lab-7/utils/manageservice.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from datetime import datetime 3 | from urllib.parse import urlunparse 4 | from subprocess import Popen, PIPE 5 | 6 | 7 | __all__ = ['startForCaching', 'startForDNS', 'startAll', 'terminateAll'] 8 | 9 | 10 | dsP, msP, rsP, csP = None, None, None, None 11 | 12 | 13 | def log_info(msg): 14 | now = datetime.now().strftime("%Y/%m/%d-%H:%M:%S") 15 | print(f"{now}| [INFO] {msg}") 16 | 17 | 18 | def log_error(msg): 19 | now = datetime.now().strftime("%Y/%m/%d-%H:%M:%S") 20 | print(f"{now}| [ERROR] {msg}", file=sys.stderr) 21 | 22 | 23 | def startDNSServer(port=9999): 24 | cmd = ["python3", "runDNSServer.py", str(port)] 25 | p = Popen(cmd, stdout=PIPE, stderr=PIPE) 26 | if p.poll() is None: 27 | log_info("DNS server started") 28 | return p 29 | log_error("Cannot start DNS server") 30 | return None 31 | 32 | 33 | def startMainServer(workdir, bind=None, port=8000): 34 | cmd = ["python3", "mainServer/mainServer.py", str(port), "-d", workdir] 35 | if bind is not None: 36 | cmd += ["-b", bind] 37 | p = Popen(cmd, stdout=PIPE, stderr=PIPE) 38 | if p.poll() is None: 39 | log_info("Main server started") 40 | return p 41 | log_error("Cannot start main server") 42 | return None 43 | 44 | 45 | def startRPCServer(port=3322): 46 | cmd = ["python3", "utils/rpcServer.py", str(port)] 47 | p = Popen(cmd, stdout=PIPE, stderr=PIPE) 48 | if p.poll() is None: 49 | log_info("RPC server started") 50 | return p 51 | log_error("Cannot start RPC server") 52 | return None 53 | 54 | 55 | def startCachingServer(mainServerAddr, port=1222, rpcAddr=None): 56 | cmd = ["python3", "runCachingServer.py", mainServerAddr, str(port)] 57 | if rpcAddr is not None: 58 | cmd += ["-r", rpcAddr] 59 | p = Popen(cmd, stdout=PIPE, stderr=PIPE) 60 | if p.poll() is None: 61 | log_info("Caching server started") 62 | return p 63 | log_error("Cannot start caching server") 64 | return None 65 | 66 | 67 | def startForCaching( 68 | mainIP="127.0.0.1", mainPort=8000, mainWorkDir="mainServer/", 69 | cacheIP="127.0.0.1", cachePort=1222, 70 | rpcIP="127.0.0.1", rpcPort=3322 71 | ): 72 | '''Start main server, RPC server and caching server''' 73 | global msP, rsP, csP 74 | msP = startMainServer(workdir=mainWorkDir, bind=mainIP, port=mainPort) 75 | rsP = startRPCServer(port=rpcPort) 76 | mainAddr = f"{mainIP}:{mainPort}" 77 | rpcAddr = f"{rpcIP}:{rpcPort}" 78 | csP = startCachingServer(mainAddr, port=cachePort, rpcAddr=rpcAddr) 79 | return msP is not None and rsP is not None and csP is not None 80 | 81 | 82 | def startForDNS(dnsIP="127.0.0.1", port=9999): 83 | '''Start DNS server''' 84 | global dsP 85 | dsP = startDNSServer(port=port) 86 | return dsP is not None 87 | 88 | 89 | def startAll( 90 | dnsIP="127.0.0.1", dnsPort=9999, 91 | mainIP="127.0.0.1", mainPort=8000, mainWorkDir="mainServer/", 92 | cacheIP="127.0.0.1", cachePort=1222, 93 | rpcIP="127.0.0.1", rpcPort=3322 94 | ): 95 | '''Start DNS server, main server, RPC server and caching server''' 96 | global dsP, msP, rsP, csP 97 | dsP = startDNSServer(dnsPort) 98 | msP = startMainServer(workdir=mainWorkDir, bind=mainIP, port=mainPort) 99 | rsP = startRPCServer(port=rpcPort) 100 | mainAddr = f"{mainIP}:{mainPort}" 101 | rpcAddr = f"{rpcIP}:{rpcPort}" 102 | csP = startCachingServer(mainAddr, port=cachePort, rpcAddr=rpcAddr) 103 | return dsP is not None \ 104 | and msP is not None \ 105 | and rsP is not None \ 106 | and csP is not None 107 | 108 | 109 | def terminateAll(): 110 | global dsP, msP, rsP, csP 111 | if dsP is not None: 112 | dsP.terminate() 113 | log_info("DNS server terminated") 114 | if csP is not None: 115 | csP.terminate() 116 | log_info("Caching server terminated") 117 | if rsP is not None: 118 | rsP.terminate() 119 | log_info("PRC server terminated") 120 | if msP is not None: 121 | msP.terminate() 122 | log_info("Main server terminated") 123 | -------------------------------------------------------------------------------- /lab-7/utils/network.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import re 4 | import time 5 | import socket 6 | import functools 7 | import urllib.request 8 | import urllib.error 9 | from urllib.parse import urlparse, urlunparse 10 | from utils.dns_utils import DNS_Request, DNS_Response 11 | 12 | 13 | def timer(func): 14 | '''Decorator: a timer ''' 15 | @functools.wraps(func) 16 | def wrapper(*args, **kwargs): 17 | tick = time.time() 18 | ret = func(*args, **kwargs) 19 | toc = time.time() 20 | print(f"\n[Request time] {(toc - tick) * 1000:.2f} ms") 21 | return ret 22 | return wrapper 23 | 24 | 25 | def isIpv4(text): 26 | if not re.match(r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}", text): 27 | return False 28 | splitted = text.split(".") 29 | for num in splitted: 30 | if int(num) < 0 or int(num) > 255: 31 | return False 32 | return True 33 | 34 | 35 | def resolve_domain_name(url, dnsIP, dnsPort): 36 | # SOCK_DGRAM is the socket type to use for UDP sockets 37 | with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock: 38 | sock.settimeout(10) 39 | # Send a request to dns server. 40 | dns_request = DNS_Request.construct_dns_request(url) 41 | if dns_request is None: 42 | raise ValueError(f"cannot create DNS request from URL '{url}'") 43 | sock.sendto(dns_request.to_bytes(), (dnsIP, dnsPort)) 44 | received = sock.recv(1024) 45 | dns_response = DNS_Response(received) 46 | if dns_response.response_type is not None: 47 | return dns_response 48 | return None 49 | 50 | 51 | @timer 52 | def _request(req): 53 | try: 54 | with urllib.request.urlopen(req) as f: 55 | return f.status, f.read() 56 | except urllib.error.HTTPError as e: 57 | return e.getcode(), None 58 | 59 | 60 | def curl(url, dnsIP=None, dnsPort=None, method="GET"): 61 | o = urlparse(url) 62 | if not isIpv4(o.hostname): 63 | # url has a domain name 64 | resp = resolve_domain_name(o.hostname, dnsIP, dnsPort) 65 | if resp is None: 66 | # cannot resolve domain name 67 | raise ValueError("could not resolve domain name") 68 | while resp.response_type == 5: 69 | nd = resp.response_val 70 | resp = resolve_domain_name(nd, dnsIP, dnsPort) 71 | if resp.response_type != 1: 72 | # cannot resolve domain name 73 | raise ValueError("could not resolve domain name") 74 | url = url.replace(o.hostname, str(resp.response_val)) 75 | 76 | # now url is an IP address 77 | req = urllib.request.Request(url) 78 | req.get_method = lambda: method 79 | return _request(req) 80 | 81 | 82 | def createUrl(scheme="http", netloc="127.0.0.1", port=None, 83 | path="", params="", query="", fragment=""): 84 | addr = f"{netloc}:{port}" if port else netloc 85 | urlParts = (scheme, addr, path, params, query, fragment) 86 | return urlunparse(urlParts) 87 | -------------------------------------------------------------------------------- /lab-7/utils/rpcServer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | import argparse 5 | from collections import namedtuple 6 | import rpyc 7 | from rpyc.utils.server import ThreadedServer 8 | 9 | __all__ = ['Calls'] 10 | 11 | 12 | OneCall = namedtuple("OneCall", ("method", "optional")) 13 | 14 | class Calls: 15 | @staticmethod 16 | def all(): 17 | return { 18 | "do_GET", "do_HEAD", "requestMainServer", "setHeaders", 19 | "sendHeaders", "getHeaders" 20 | } 21 | 22 | @staticmethod 23 | def do_GET(optional=False): 24 | return OneCall("do_GET", optional) 25 | 26 | @staticmethod 27 | def do_HEAD(optional=False): 28 | return OneCall("do_HEAD", optional) 29 | 30 | @staticmethod 31 | def fetch(optional=False): 32 | return OneCall("requestMainServer", optional) 33 | 34 | @staticmethod 35 | def storeInCache(optional=False): 36 | return OneCall("setHeaders", optional) 37 | 38 | @staticmethod 39 | def send(optional=False): 40 | return OneCall("sendHeaders", optional) 41 | 42 | @staticmethod 43 | def loadCache(optional=False): 44 | return OneCall("getHeaders", optional) 45 | 46 | 47 | def parse_args(argv): 48 | ''' Parse arguments of the program ''' 49 | parser = argparse.ArgumentParser(description="Run RPC tracing server") 50 | parser.add_argument("port", action='store', default=3322, type=int, nargs='?', 51 | help="port to start the http service (default: 3322)") 52 | args = parser.parse_args(argv) 53 | return args.port 54 | 55 | class RPCService(rpyc.Service): 56 | procedures = [] 57 | errors = [] 58 | 59 | def called(self, method: str): 60 | self.procedures.append(method) 61 | # print(f"Called {method}") 62 | 63 | def echoError(self, method, message): 64 | ''' echo error message to RPC server ''' 65 | print(f"Error when calling `{method}`: {message}", file=sys.stderr) 66 | self.errors.append(message) 67 | 68 | def getProcedures(self): 69 | if len(self.errors) > 0: 70 | return False, self.errors 71 | return True, self.procedures 72 | 73 | def clearProcedures(self): 74 | self.procedures.clear() 75 | 76 | 77 | if __name__ == "__main__": 78 | port = parse_args(sys.argv[1:]) 79 | 80 | t = ThreadedServer( 81 | RPCService, port=port, 82 | protocol_config={'allow_public_attrs': True} 83 | ) 84 | print(f"Starting RPC server at port: {port}") 85 | t.start() 86 | -------------------------------------------------------------------------------- /lab-7/utils/tracer.py: -------------------------------------------------------------------------------- 1 | import functools 2 | import rpyc 3 | 4 | 5 | rpcserver = None 6 | 7 | 8 | def initateRPCServerProxy(addr=None, port=None): 9 | global rpcserver 10 | if addr is not None: 11 | if port is None: 12 | port = 3322 13 | rpcserver = rpyc.connect(addr, port, config={"allow_all_attrs" : True}) 14 | 15 | 16 | def trace(method): 17 | ''' Trace methods calling ''' 18 | @functools.wraps(method) 19 | def wrapper(*args, **kwargs): 20 | if rpcserver is not None: 21 | try: 22 | rpyc.async_(rpcserver.root.called)(method.__name__) 23 | except Exception as e: 24 | rpcserver.root.echoError(method.__name__, e) 25 | ret = method(*args, **kwargs) 26 | return ret 27 | return wrapper 28 | --------------------------------------------------------------------------------