说明
本系统模拟实现了一个路由器与两个主机节点。该路由器将接收原始以太网帧,并像真正的路由器一样处理它们:将它们转发到正确的传出接口,处理以太网帧,处理 IPv4 分组,处理 ARP分组,处理 ICMP 分组,创建新帧等。这个路由器将模拟实现路由器处理主机节点发送的以太网帧的操作。
运行展示
源代码
import queue
import threading
import time
import socket
import uuid# 十进制转二进制
def dec_to_bin_str(num):return str(bin(num))[2:]# 二进制转十进制
def bin_to_dec_str(binary):return str(int(binary, 2))# IP地址转二进制
def ipv4_to_binary(ipv4):binary_list = ['{0:08b}'.format(int(num)) for num in ipv4.split('.')]return ''.join(binary_list)# 二进制地址转点分十进制
def binary_to_ipv4(binary_str):if len(binary_str) != 32:return "Invalid binary string"segments = [binary_str[i:i+8] for i in range(0, 32, 8)]ipv4_address = ".".join(str(int(segment, 2)) for segment in segments)return ipv4_address# 验证校验和
def validate_ip_checksum(data):# 初始化总合为0total = 0# 每16位为单位进行遍历for i in range(0, len(data), 16):word = int(data[i:i + 16], 2)total += word# 按位与操作和右移操作,在总和的低16位中和高16位中分别加上结果total = (total & 0xffff) + (total >> 16)# 再次按位与操作和右移操作,对结果再次进行相加total = (total + (total >> 16)) & 0xffff# 将总和与0xffff进行异或运算,并将结果转换为16位二进制数checksum = '{:016b}'.format(total ^ 0xffff)return checksum# IP数据报的格式(IPv4格式)
class IPv4Message:def __init__(self, version, header_length, service, total_length, identification, flags, fragment_offset, ttl, protocol, checksum, source_address, destination_address, data):self.version = version # 版本self.header_length = header_length # 首部长度self.service = service # 区分服务self.total_length = total_length # 总长度self.identification = identification # 标识self.flags = flags # 标志self.fragment_offset = fragment_offset # 片偏移self.ttl = ttl # 生存时间self.protocol = protocol # 协议self.checksum = checksum # 校验和self.source_address = source_address # 源地址self.destination_address = destination_address # 目的地址self.data = data # 数据# 以太网MAC帧的格式
class MACMessage:def __init__(self, mac_destination, mac_source, protocol_type, data):self.mac_destination = mac_destinationself.mac_source = mac_sourceself.protocol_type = protocol_type # 为IPv4self.data = data# ICMP差错报文格式
class ICMPError:def __init__(self, message_type, checksum, data):self.type = message_type # 1-主机不可达self.checksum = checksumself.data = data# ICMP回送请求或回答报文格式
class ICMPMessage:def __init__(self, message_type, checksum, data):self.type = message_type # 类型字段,8-请求,0-回答self.checksum = checksumself.data = data# ARP广播格式
class ARPBroadcast:def __init__(self):self.mac = "FF:FF:FF:FF:FF:FF"# 获取本机IP与本机MAC
my_ip = socket.gethostbyname(socket.gethostname())
my_mac = ':'.join(['{:02x}'.format((uuid.getnode() >> elements) & 0xff) for elements in range(0, 2 * 6, 2)][::-1])# ARP表
arp_table = {my_ip: my_mac, '172.26.213.121': '00:11:22:33:44:55', '172.26.213.64': '00:aa:bb:cc:dd:ee'}# 路由表
route_table = {my_ip: 'direct', '172.26.213.121': 'direct', '0.0.0.0': '172.26.213.64'}# 路由器类
class Router:def __init__(self):self.ip = my_ipself.mac = my_macself.arp_table = arp_tableself.routing_table = route_tableself.frame_queue = queue.Queue()def route(self):while True:# 拆开frame获取IP数据报frame_message = self.frame_queue.get()if frame_message == "":continueip_message = frame_message.split("@")[-1]ip_header = ip_message[0:160]# 计算校验和checksum = ip_header[80:96]rest = ip_header[-64:]print("路由器:计算的校验和为:" + checksum)ip_message_t = ip_header[0:80]ip_message_t += "0000000000000000"ip_message_t += restif not checksum == validate_ip_checksum(ip_message_t):print("路由器:校验和验证失败")# 发送一个ICMP响应报文print("路由器:发送一个ICMP响应报文告知主机")continueprint("路由器:校验和验证成功")# 验证TTL合法性ttl = int(bin_to_dec_str(ip_header[64:72]))if ttl <= 0:print("TTL值不合法")# 发送一个ICMP响应报文print("路由器:发送一个ICMP响应报文告知主机")continueelse:# 自减ttl -= 1# 拆解IP首部ip_source = binary_to_ipv4((ip_message[0:160])[-64:-32])ip_destination = binary_to_ipv4((ip_message[0:160])[-32:])print("路由器:源IP:" + ip_source + ",目的IP:" + ip_destination)# 转发的framenew_frame_message = ""# 根据路由表查目的IP地址for ip, move in route_table.items():if ip == ip_destination:# 修改IP头部,源地址改变但目的地址不变if move == "direct":print("路由器:目标可直达,直接转发")elif move == "upper":print("路由器:目标为本身,交付上层")breakelse:ip_header = ip_header[0:-32]ip_header += ipv4_to_binary(route_table["0.0.0.0"])print("路由器:新的IP数据报已生成,下一跳IP地址为:" + route_table["0.0.0.0"])# 根据ARP表获取下一跳MAC,封装成帧,转发mac_source = self.macfor m_ip, mac in arp_table.items():if ip_header[-32:] == ipv4_to_binary(m_ip):mac_destination = macnew_frame_message += mac_destination + "@" + mac_source + "@IPv4@" + ip_messageprint("路由器:路由器已转发,下一跳MAC为:", mac)# 主机节点类
class Host:def __init__(self, ip, mac):self.ip = ipself.mac = mac# 获取目的MAC地址(广播ARP请求分组并接收ARP响应分组)def get_dest_mac(self, dest_ip):print("主机:正在进行ARP广播")dest_mac = "FF:FF:FF:FF:FF:FF"for ip, mac in arp_table.items():if ip == dest_ip:dest_mac = my_macprint("主机:目标MAC已获取" + dest_mac)return dest_mac# 初始化校验和def make_checksum(self, ip_header, dest_ip):ip_header += "0000000000000000"ip_header += ipv4_to_binary(self.ip) + ipv4_to_binary(dest_ip)data = ip_headertotal = 0for i in range(0, len(data), 16):word = int(data[i:i + 16], 2)total += wordtotal = (total & 0xffff) + (total >> 16)total = (total + (total >> 16)) & 0xffffchecksum = '{:016b}'.format(total ^ 0xffff)print("主机:生成的校验和为:" + checksum)return checksum# 发送数据帧def send_frame(self, dest_ip, data):# 组装IP数据报ip_header = "0100" # 版本号为4ip_header += "0101" # 首部长度20Bip_header += "00000000" # 区分服务ip_header += dec_to_bin_str(len(data) + 20).zfill(16) # 总长度ip_header += "0000000000000000" # 标识ip_header += "000" # 标志ip_header += "0000000000000" # 片偏移ip_header += "00000000" # 生存时间ip_header += "00000000" # 协议ip_header += self.make_checksum(ip_header, dest_ip) # 校验和ip_header += ipv4_to_binary(self.ip) # 源地址ip_header += ipv4_to_binary(dest_ip) # 目的地址ip_message = ip_header + data # 组装成IP数据报# 组装数据帧frame_head = self.get_dest_mac(dest_ip)frame_head += "@" + self.mac + "@IPv4@"frame_message = frame_head + ip_message# 发送给路由器print("主机:数据帧发送完毕")my_router.frame_queue.put(frame_message)# 路由器与主机节点
my_router = Router()
my_host1 = Host("172.26.213.121", "00:11:22:33:44:55")
my_host2 = Host("172.26.213.122", "00:11:22:33:44:55")# 打开线程
router_thread = threading.Thread(target=my_router.route)
router_thread.start()# 标志位
flag = True# 打印本机IP与MAC,即路由器IP、MAC
print("本机IP:" + my_ip + " 本机MAC:" + my_mac)# 轮询输入
while True:message = input("主机:请输入要发送的消息:")if message == "exit":print("主机已关闭")breakif message == "shift":flag = not flagcontinueif flag:my_host1.send_frame(my_ip, message)else:my_host2.send_frame("172.26.21.12", message)time.sleep(1)# 释放资源,关闭线程
router_thread.join()