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

说明

本系统模拟实现了一个路由器与两个主机节点。该路由器将接收原始以太网帧,并像真正的路由器一样处理它们:将它们转发到正确的传出接口,处理以太网帧,处理 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):
    # 初始化总合为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()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/445742.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Crow 编译和环境搭建

Crow与其说是编译&#xff0c;倒不如说是环境搭建。Crow只需要包含头文件&#xff0c;所以不用编译生成lib。 Crow环境搭建 boost&#xff08;可以不编译boost&#xff0c;只需要boost头文件即可&#xff09;asio &#xff08;可以不编译&#xff0c;直接包含头文件。不能直接…

事务【MySQL】

稍等更新图片。。。。 事务的概念 引入 在 A 转账 100 元给 B 的过程中&#xff0c;如果在 A 的账户已经减去了 100 元&#xff0c;B 的账户还未加上 100 元之前断网&#xff0c;那么这 100 元将会凭空消失。对于转账这件事&#xff0c;转出和转入这两件事应该是绑定在一起的…

C语言——函数指针——函数指针变量(详解)

函数指针变量 函数指针变量的作用 函数指针变量是指向函数的指针&#xff0c;它可以用来存储函数的地址&#xff0c;并且可以通过该指针调用相应的函数。函数指针变量的作用主要有以下几个方面&#xff1a; 回调函数&#xff1a;函数指针变量可以作为参数传递给其他函数&…

Docker基础教程 - 10 常用容器部署-Redis

更好的阅读体验&#xff1a;点这里 &#xff08; www.doubibiji.com &#xff09; 10 常用容器部署-Redis 下面介绍一下常用容器的部署。可以先简单了解下&#xff0c;用到再来详细查看。 在 Docker 中部署 Redis 容器。 10.1 搜索镜像 首先搜索镜像&#xff0c;命令&…

强大的项目管理软件:OmniPlan Pro 4 mac中文版

OmniPlan Pro 4 mac中文版是由The Omni Group为macOS和iOS操作系统开发的一款专业级项目管理软件。它允许用户创建和管理复杂的项目&#xff0c;从定义任务、分配资源到跟踪进度和生成报告&#xff0c;一应俱全。 这款软件提供了一系列强大的工具&#xff0c;帮助用户进行高效…

集合框架(一)Set系列集合

Set<E>是一个接口 特点 无序&#xff1a;添加数据的顺序和获取出的数据顺序不一致&#xff1b;不重复&#xff0c;无索引 注意&#xff1a;Set要用到的常用方法&#xff0c;基本上就是collection提供的!自己几乎没有额外新增一些常用功能! HashSet集合的底层原理 前置知…

GPU 和并行计算

还是那句话&#xff0c;互联网领域遇到的大多数问题&#xff0c;在现实世界早就有了解法&#xff0c;今天再分享一个。 视频来自安阳市最后的朋克&#xff0c;张教练的实拍&#xff0c;视频中展示的是血糕&#xff0c;安阳市特产&#xff0c;不了解的可以将其等同于 “一种必须…

【JavaScript】JavaScript 变量 ① ( JavaScript 变量概念 | 变量声明 | 变量类型 | 变量初始化 | ES6 简介 )

文章目录 一、JavaScript 变量1、变量概念2、变量声明3、ES6 简介4、变量类型5、变量初始化 二、JavaScript 变量示例1、代码示例2、展示效果 一、JavaScript 变量 1、变量概念 JavaScript 变量 是用于 存储数据 的 容器 , 通过 变量名称 , 可以 获取 / 修改 变量 中的数据 ; …

Util工具类功能设计与类设计(http模块一)

目录 类功能 类定义 类实现 编译测试 Split分割字符串测试 ReadFile读取测试 WriteFile写入测试 UrlEncode编码测试 UrlDecode编码测试 StatuDesc状态码信息获取测试 ExtMime后缀名获取文件mime测试 IsDirectory&IsRegular测试 VaildPath请求路径有效性判断测…

Day33-计算机基础3

Day33-计算机基础3 1.根据TCP/IP进行Linux内核参数优化1.1 例1&#xff1a;调整访问服务端的【客户端】的动态端口范围 &#xff0c;LVS&#xff08;10-50万并发&#xff09;&#xff0c;NGINX负载&#xff0c;SQUID缓存服务,1.2 企业案例&#xff1a;DOS攻击的案例&#xff1a…

工资低适合下班做的6大副业,每一个都值得尝试!

2024年是最适合发展个人副业的时候&#xff01;无论你是否有全职工作&#xff0c;如果你的主业还不能满足你的成就感&#xff0c;还不能满足你的生活需求&#xff0c;这6个下班可以做的副业都很值得尝试&#xff01; 千金宝库做简单的网络任务 近年来&#xff0c;随着互联网技…

算法详解——leetcode150(逆波兰表达式)

欢迎来看博主的算法讲解 博主ID&#xff1a;代码小豪 文章目录 逆波兰表达式逆波兰表达式的作用代码将中缀表达式转换成后缀表达式文末代码 逆波兰表达式 先来看看leetcode当中的原题 大多数人初见逆波兰表达式的时候大都一脸懵逼&#xff0c;因为与平时常见的表达式不同&am…

C语言学习笔记,学懂C语言,看这篇就够了!(中)

附上视频链接&#xff1a;X站的C语言教程 目录 第8章、函数8.1 函数是什么8.2 函数的分类8.2.1 库函数8.2.1.1 如何使用库函数 8.2.2 自定义函数 8.3 函数参数8.3.1 实际参数(实参)8.3.2 形式参数(形参) 8.4 函数调用8.4.1 传值调用8.4.2 传址调用8.4.3 练习 8.5 函数的嵌套调…

如何使用ArcGIS Pro进行坡度分析

坡度分析是地理信息系统中一种常见的空间分析方法&#xff0c;用于计算地表或地形的坡度&#xff0c;这里为大家介绍一下如何使用ArcGIS Pro进行坡度分析&#xff0c;希望能对你有所帮助。 数据来源 教程所使用的数据是从水经微图中下载的DEM数据&#xff0c;除了DEM数据&…

Python爬虫:http和https介绍及请求

HTTP和HTTPS 学习目标&#xff1a; 记忆 http、https的概念和区别记忆 浏览器发送http请求的过程记忆 http请求头的形式记忆 http响应头的形式了解 http响应状态码 1 为什么要复习http和https 在发送请求&#xff0c;获取响应的过程中 就是发送http或https的请求&#xff0c…

自然语言发展历程

一、基础知识 自然语言处理&#xff1a;能够让计算理解人类的语言。 检测计算机是否智能化的方法&#xff1a;图灵测试 自然语言处理相关基础点&#xff1a; 基础点1——词表示问题&#xff1a; 1、词表示&#xff1a;把自然语言中最基本的语言单位——词&#xff0c;将它转…

中国电子学会2021年9月份青少年软件编程Sc ratch图形化等级考试试卷四级真题

【 单选题 】 1.下面哪个选项程序可以交换下图列表中第2项和第3项的位置&#xff1f; A&#xff1a; B&#xff1a; C&#xff1a; D&#xff1a; 2.雷峰塔景区的门票价格政策是&#xff1a;成人40元/人&#xff1b;6周岁&#xff08;含6周岁&#xff09;以下的实行免票&#…

常用MII接口详解

开放式系统互连 (OSI) 模型 七层开放系统互连 (OSI) 模型中&#xff0c;以太网层 位于最底部两层 - 物理层和数据链路层。 从百兆以太网接口开始 首先是百兆以太网规定的两种接口 介质无关接口 (MII) Media Independent Interface 介质相关接口 (MDI) Medium Depen…

manjaro 安装 wps 教程

内核: Linux 6.6.16.2 wps-office版本&#xff1a; 11.10.11719-1 本文仅作为参考使用, 如果以上版本差别较大不建议参考 安装wps主体 yay -S wps-office 安装wps字体 &#xff08;如果下载未成功看下面的方法&#xff09; yay -S ttf-waps-fonts 安装wps中文语言 yay …

如何用YOLOv8实现图像分割

1. 介绍 在之前的文章中,介绍了如何使用 YOLOv8 在不同的编程语言来检测图片中的对象。然而,YOLOv8 还可以把检测到的目标图像分割出来,本篇文章将介绍如何使用YOLOv8做图片分割。 对象检测的结果是所有检测到的对象的边界框。图像分割的结果是所有检测到的对象的蒙版。它是…