Building a Network Router Simulation in Python
Explore our comprehensive guide on building a network router simulation in Python. This hands-on guide will help you understand network communication and packet handling, making it a valuable resource to help your Python assignment. Whether you're a student learning about networking or a curious programmer, this guide will equip you with practical skills for real-world network administration and programming scenarios. Additionally, you'll gain insights into the core principles of network routing, allowing you to take on more advanced networking challenges with confidence.
Block 1: Imports and Constants
```python
import sys, socket, ipaddress, time, threading
LOCAL_HOST = "127.0.0.1"
BUFFER_SIZE = 1024
RESERVED_BITS = 0
PACKET_SIZE = 1500
```
- The code starts by importing necessary modules: `sys`, `socket`, `ipaddress`, `time`, and `threading`.
- It defines some constants such as `LOCAL_HOST`, `BUFFER_SIZE`, `RESERVED_BITS`, and `PACKET_SIZE` for configuration.
Block 2: Packet Modes
```python
# Modes
DISCOVERY_01 = 0x01
OFFER_02 = 0x02
REQUEST_03 = 0x03
ACK_04 = 0x04
ASK_06 = 0x06
DATA_05 = 0x05
READY_07 = 0x07
LOCATION_08 = 0x08
FRAGMENT_0A = 0x0a
FRAGMENT_END_0B = 0x0b
```
- This block defines packet modes as hexadecimal values. These modes represent different types of network packets, such as discovery, request, data, and more.
Block 3: Connection Class
```python
class Connection():
'''
Connection to a single switch
'''
def __init__(self, ip, port_num):
self.ip = ip
self.port_num = port_num
self._recv_packets = []
def __eq__(self, other):
if not isinstance(other, Connection):
return False
return self.ip == other.ip and self.port_num == other.port_num
```
- This block defines a `Connection` class. Each instance of this class represents a connection to a single switch.
- The class has an initializer that sets the IP address and port number for the connection.
- It also defines an `__eq__` method to compare two `Connection` objects for equality.
Block 4: Adapter Class
```python
class Adapter():
def __init__(self):
self.switch_num = int(sys.argv[1])
self.adapter = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
self.send_packets = []
self.recv_packets = []
self.ip = None # should contain 4 numbers
self.switch_ips = []
self.message = ''
self.within_5_sec = 0
self.connections = []
self.ready = False
self.lock = threading.Lock()
self.ack = False
self.switch_message = dict() # key: source_ip, value = [message]
def create_packet(self, mode, source_ip='0.0.0.0', dest_ip='0.0.0.0', data='0.0.0.0'):
packet = bytearray()
# append source ip
for elem in socket.inet_aton(source_ip):
packet.append(elem)
# append dest ip
for elem in socket.inet_aton(dest_ip):
packet.append(elem)
# append reserve
for _ in range(3):
packet.append(RESERVED_BITS)
# append mode
packet.append(mode)
try:
socket.inet_aton(data)
except socket.error:
for char in data:
packet.append(ord(char))
else:
# append assigned address
for elem in socket.inet_aton(data):
packet.append(elem)
self.send_packets.append(packet)
return packet
def greeting(self):
# send discovery
discovery_packet = self.create_packet(DISCOVERY_01)
send_address = (LOCAL_HOST, self.switch_num)
self.adapter.sendto(discovery_packet, send_address)
# receive offer
offer = self.adapter.recvfrom(PACKET_SIZE)
offerMessage = offer[0]
self.recv_packets.append(offerMessage)
switch_ip = ipaddress.IPv4Address(offerMessage[:4])
self.switch_ips.append(switch_ip)
self.ip = ipaddress.IPv4Address(int.from_bytes(offerMessage[12:16], byteorder='big'))
# send request
request_packet = self.create_packet(REQUEST_03, dest_ip=str(switch_ip), data=str(self.ip))
self.adapter.sendto(request_packet, send_address)
# receive ack
ack = self.adapter.recvfrom(PACKET_SIZE)
ackMessage = ack[0]
self.recv_packets.append(ackMessage)
def take_input(self):
'''
Take command from stdin. Acceptable command is 'send'
'''
while True:
print('> ', end='', flush=True)
try:
user_input = input()
except EOFError as e:
return
else:
self.send_command(user_input)
def send_command(self, user_input):
user_input_split = user_input.split(maxsplit=2) # prevent splitting data
if len(user_input_split) != 3:
return
command = user_input_split[0]
dest = user_input_split[1]
data = user_input_split[2]
data_packet = self.create_packet(mode=DATA_05, source_ip=str(self.ip), dest_ip=dest, data=data)
self.adapter.sendto(data_packet, (LOCAL_HOST, self.switch_num))
def run(self):
self.greeting()
thread_stdin = threading.Thread(target=self.take_input)
thread_stdin.start()
while True:
message, _ = self.adapter.recvfrom(PACKET_SIZE)
mode = message[11]
dest_ip = message[:4]
self.recv_packets.append(message)
if mode == ASK_06:
dest = ipaddress.ip_address(dest_ip)
mode_7_packet = self.create_packet(READY_07, source_ip=str(self.ip), dest_ip=str(dest), data='')
self.adapter.sendto(mode_7_packet, (LOCAL_HOST, self.switch_num))
self.ready = True
elif mode == DATA_05:
source_ip = message[:4]
packet_data = message[12:].decode()
source_ip = ipaddress.ip_address(int.from_bytes(source_ip, byteorder='big'))
print(f'Received from {str(source_ip)}: {packet_data}', flush=True)
print('> ', end='', flush=True)
elif mode == FRAGMENT_0A:
source_ip = ipaddress.ip_address(message[:4])
if source_ip not in self.switch_message:
self.switch_message[source_ip] = [""]
self.switch_message[source_ip][0] += message[12:].decode()
elif mode == FRAGMENT_END_0B:
source_ip = ipaddress.ip_address(message[:4])
self.switch_message[source_ip][0] += message[12:].decode()
print(f'Received from {str(source_ip)}: {self.switch_message[source_ip][0]}', flush=True)
self.switch_message[source_ip][0] = ""
print('> ', end='', flush=True)
```
- This block defines the `Adapter` class, which represents the network adapter.
- The `Adapter` class contains methods and properties for managing connections, sending and receiving packets, and handling user input.
Block 5: create_packet Method
```python
def create_packet(self, mode, source_ip='0.0.0.0', dest_ip='0.0.0.0', data='0.0.0.0'):
packet = bytearray()
# Packet creation code...
```
- The `create_packet` method is responsible for creating network packets. It takes the packet mode, source IP, destination IP, and data as input and constructs a packet accordingly.
Block 6: greeting Method
```python
def greeting(self):
# send discovery
discovery_packet = self.create_packet(DISCOVERY_01)
send_address = (LOCAL_HOST, self.switch_num)
self.adapter.sendto(discovery_packet, send_address)
# receive offer
offer = self.adapter.recvfrom(PACKET_SIZE)
offerMessage = offer[0]
self.recv_packets.append(offerMessage)
switch_ip = ipaddress.IPv4Address(offerMessage[:4])
self.switch_ips.append(switch_ip)
self.ip = ipaddress.IPv4Address(int.from_bytes(offerMessage[12:16], byteorder='big'))
# send request
request_packet = self create_packet(REQUEST_03, dest_ip=str(switch_ip), data=str(self.ip))
self.adapter.sendto(request_packet, send_address)
# receive ack
ack = self.adapter.recvfrom(PACKET_SIZE)
ackMessage = ack[0]
self.recv_packets.append(ackMessage)
```
- The `greeting` method is used to establish a connection with a switch, including sending discovery, request, and receiving an acknowledgment.
Block 7: take_input Method
```python
def take_input(self):
'''
Take command from stdin. Acceptable command is 'send'
'''
while True:
print('> ', end='', flush=True)
try:
user_input = input()
except EOFError as e:
return
else:
self.send_command(user_input)
```
- The `send_command` method processes and sends 'send' commands to send data to a specified destination.
Block 8: send_command Method
```python
def send_command(self, user_input):
user_input_split = user_input.split(maxsplit=2) # prevent splitting data
if len(user_input_split) != 3:
return
command = user_input_split[0]
dest = user_input_split[1]
data = user_input_split[2]
data_packet = self.create_packet(mode=DATA_05, source_ip=str(self.ip), dest_ip=dest, data=data)
self.adapter.sendto(data_packet, (LOCAL_HOST, self.switch_num))
```
- The `send_command` method processes and sends 'send' commands to send data to a specified destination.
Block 9: run Method
```python
def run(self):
self.greeting()
thread_stdin = threading.Thread(target=self.take_input)
thread_stdin.start()
while True:
message, _ = self.adapter.recvfrom(PACKET_SIZE)
mode = message[11]
dest_ip = message[:4]
self.recv_packets.append(message)
if mode == ASK_06:
dest = ipaddress.ip_address(dest_ip)
mode_7_packet = self.create_packet(READY_07, source_ip=str(self.ip), dest_ip=str(dest), data='')
self.adapter.sendto(mode_7_packet, (LOCAL_HOST, self.switch_num))
self.ready = True
elif mode == DATA_05:
source_ip = message[:4]
packet_data = message[12:].decode()
source_ip = ipaddress.ip_address(int.from_bytes(source_ip, byteorder='big'))
print(f'Received from {str(source_ip)}: {packet_data}', flush=True)
print('> ', end='', flush=True)
elif mode == FRAGMENT_0A:
source_ip = ipaddress.ip_address(message[:4])
if source_ip is not in self.switch_message:
self.switch_message[source_ip] = [""]
self.switch_message[source_ip][0] += message[12:].decode()
elif mode == FRAGMENT_END_0B:
source_ip = ipaddress.ip_address(message[:4])
self.switch_message[source_ip][0] += message[12:].decode()
print(f'Received from {str(source_ip)}: {self.switch_message[source_ip][0]}', flush=True)
self.switch_message[source_ip][0] = ""
print('> ', end='', flush=True)
```
- The `run` method represents the main execution loop of the adapter, including greeting the switch and handling incoming packets.
Block 10: main Function
```python
def main():
adapter = Adapter()
adapter.run()
if __name__ == "__main__":
main()
```
- The `main` function is the entry point of the script. It creates an `Adapter` instance and starts its execution by calling the `run` method. This part ensures that the code runs when the script is executed.
Conclusion
By exploring this code, you gain insight into the basic functioning of a network router. This simulation is a valuable educational resource for understanding the core concepts of network communication, packet handling, and user interaction. Whether you're a student learning about networking or a curious programmer, this Python router simulation is a great starting point. It equips you with practical skills that can be applied to real-world network administration and programming scenarios, making it a beneficial addition to your networking knowledge. So, don't hesitate to dive into this simulation, experiment with the code, and enhance your understanding of network routing in a hands-on and engaging way.