Python实现一个简单的主机-路由器结构(计算机网络)

说明

本系统模拟实现了一个路由器与两个主机节点。该路由器将接收原始以太网帧,并像真正的路由器一样处理它们:将它们转发到正确的传出接口,处理以太网帧,处理 IPv4 分组,处理 ARP分组,处理 ICMP 分组,创建新帧等。这个路由器将模拟实现路由器处理主机节点发送的以太网帧的操作。

运行展示

源代码

python 复制代码
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):
    # 初始化总合为0
    total = 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_destination
        self.mac_source = mac_source
        self.protocol_type = protocol_type    # 为IPv4
        self.data = data
     
     
# ICMP差错报文格式
class ICMPError:
    def __init__(self, message_type, checksum, data):
        self.type = message_type   # 1-主机不可达
        self.checksum = checksum
        self.data = data
        

# ICMP回送请求或回答报文格式
class ICMPMessage:
    def __init__(self, message_type, checksum, data):
        self.type = message_type    # 类型字段,8-请求,0-回答
        self.checksum = checksum
        self.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_ip
        self.mac = my_mac
        self.arp_table = arp_table
        self.routing_table = route_table
        self.frame_queue = queue.Queue()

    def route(self):
        while True:
            # 拆开frame获取IP数据报
            frame_message = self.frame_queue.get()
            if frame_message == "":
                continue
            ip_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 += rest
            if not checksum == validate_ip_checksum(ip_message_t):
                print("路由器:校验和验证失败")
                # 发送一个ICMP响应报文
                print("路由器:发送一个ICMP响应报文告知主机")
                continue
            print("路由器:校验和验证成功")

            # 验证TTL合法性
            ttl = int(bin_to_dec_str(ip_header[64:72]))
            if ttl <= 0:
                print("TTL值不合法")
                # 发送一个ICMP响应报文
                print("路由器:发送一个ICMP响应报文告知主机")
                continue
            else:
                # 自减
                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)

            # 转发的frame
            new_frame_message = ""

            # 根据路由表查目的IP地址
            for ip, move in route_table.items():
                if ip == ip_destination:
                    # 修改IP头部,源地址改变但目的地址不变
                    if move == "direct":
                        print("路由器:目标可直达,直接转发")
                    elif move == "upper":
                        print("路由器:目标为本身,交付上层")
                    break
            else:
                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.mac
                for m_ip, mac in arp_table.items():
                    if ip_header[-32:] == ipv4_to_binary(m_ip):
                        mac_destination = mac
                        new_frame_message += mac_destination + "@" + mac_source + "@IPv4@" + ip_message
                        print("路由器:路由器已转发,下一跳MAC为:", mac)


# 主机节点类
class Host:
    def __init__(self, ip, mac):
        self.ip = ip
        self.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_mac
        print("主机:目标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_header
        total = 0
        for i in range(0, len(data), 16):
            word = int(data[i:i + 16], 2)
            total += word

        total = (total & 0xffff) + (total >> 16)
        total = (total + (total >> 16)) & 0xffff
        checksum = '{:016b}'.format(total ^ 0xffff)
        print("主机:生成的校验和为:" + checksum)
        return checksum

    # 发送数据帧
    def send_frame(self, dest_ip, data):
        # 组装IP数据报
        ip_header = "0100"                                      # 版本号为4
        ip_header += "0101"                                     # 首部长度20B
        ip_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("主机已关闭")
        break
    if message == "shift":
        flag = not flag
        continue
    if flag:
        my_host1.send_frame(my_ip, message)
    else:
        my_host2.send_frame("172.26.21.12", message)
    time.sleep(1)

# 释放资源,关闭线程
router_thread.join()
相关推荐
算法小白(真小白)2 小时前
低代码软件搭建自学第二天——构建拖拽功能
python·低代码·pyqt
唐小旭2 小时前
服务器建立-错误:pyenv环境建立后python版本不对
运维·服务器·python
007php0073 小时前
Go语言zero项目部署后启动失败问题分析与解决
java·服务器·网络·python·golang·php·ai编程
Chinese Red Guest3 小时前
python
开发语言·python·pygame
骑个小蜗牛3 小时前
Python 标准库:string——字符串操作
python
背着黄油面包的猫5 小时前
计算机网络基础知识
计算机网络
黄公子学安全6 小时前
Java的基础概念(一)
java·开发语言·python
程序员一诺6 小时前
【Python使用】嘿马python高级进阶全体系教程第10篇:静态Web服务器-返回固定页面数据,1. 开发自己的静态Web服务器【附代码文档】
后端·python
小木_.6 小时前
【Python 图片下载器】一款专门为爬虫制作的图片下载器,多线程下载,速度快,支持续传/图片缩放/图片压缩/图片转换
爬虫·python·学习·分享·批量下载·图片下载器