实验四:IPv4分组收发实验
完整代码在最后
实验要求:
IPv4 分组的基本接收处理,能够检测出接收到的IP 在如下错误:校验和错、TTL错、版本号错、头部长度错、错误 目标地址,当存在多种错误时,其他错误类型优先于错误目标地址。
注:不要求实现IPv4协议中的选项和分片处理功能,目标地址 错误为IP分组的目的地址非本机IP地址。本机IP地址:192.168. 214.138
基本上就是处理pcap包,解析其中的ipv4头部信息,对头部信息进行检测是否有错。整体来说比较简单,处理解析pcap包部分需要一些处理,最后需要一个txt文本文件,提交到系统上进行验证。
实验过程:
解析pcap包:
pcap包结构:
PcapHeader:24个字节
PacketHeader:16个字节,里面有数据部分长度,可以用来检测数据包是否完整,获得数据帧的位置
PacketData:数据包部分
我们要解析的IPV4头部信息就在PacketData中
此处我们先跳过PcapHeader部分(24个字节),然后读取PacketHeader中存储数据包长度的部分,检测是否完整,同时可以获得数据帧的位置:
pcap_file = f"./propacket/pro{i}.pcap"
# print("Processing:", pcap_file)
with open(pcap_file, 'rb') as f:
# 跳过pcapHeader部分
f.seek(24)
packet_header = f.read(16) # 读取packetHeader部分
incl_len = struct.unpack('<I', packet_header[8:12])[0] # 解析获取数据包长度
data = f.read(incl_len) # 读取数据包内容——packetData部分
if len(data) < 14: #以太网帧头部一般有14个字节长
print("数据包太短")
continue
之后得到数据包data后,首先可以对以太网头部进行解析,其中可以获取上层(网络层)使用的协议,也可以在IPV4头部中获得协议版本。
eth_header = data[:14] # 获得以太网帧头部分
eth_type = struct.unpack("!H", eth_header[12:14])[0] # 头部14个字节中最后两个存储上层协议类型
提取和判断:
首先附上头部信息:
如果是ipv4协议开始对ipv4头部进行解析,版本号,首都长度,ttl等信息。
if eth_type == 0x0800: # 如果是IPv4数据包
ip_header_offset = 14 # IP头部在以太网数据包中的偏移量
ip_header_len = (data[ip_header_offset] & 0x0F) * 4#首部长度
if ip_header_len ==20:
print("首部长度正确")
# file.write("首部长度正确\n")
else:
print("首部长度错误")
file.write("头部长度错\n")
istrue=False
continue
if len(data) < ip_header_offset + ip_header_len:
print("ip头部有缺失")
continue
ip_header = data[ip_header_offset:ip_header_offset + ip_header_len]
ip_version = (ip_header[0])>>4#版本号
ip_ttl=ip_header[8]#寿命
dst_ip=socket.inet_ntoa(ip_header[16:20])#目的ip
下面具体说一下校验和的计算,在接收端(发送端下面会说)的时候只需要将首部20个字节,每两个字节(两个两位十六进制数合为一个四位十六进制数)加起来进行计算,如果有溢出的情况(进位),则加1到原数中,进的位抹除,如此重复,知道加完这10个数,如果是0xffff,则校验和计算正确。
# 计算校验和
checksum = 0
tempsum=0
for i in range(0, len(ip_header), 2):
tempsum=(ip_header[i] << 8) + ip_header[i+1]#第一个字节的数左移8位加上第二个字节的数,实现“合成”
checksum=checksum+tempsum
# 处理溢出
while checksum>0xffff:
temp=checksum>>16
checksum=checksum&0xffff
checksum=checksum+temp
calculated_checksum=checksum
之后就是一些简单的判断
if ip_version !=4:
print("版本号错误")
file.write("版本号错\n")
istrue=False
continue
# continue
else:
print("版本号正确")
# file.write("版本号正确\n")
if ip_ttl <=0:
print("TTL错误")
file.write("TTL错\n")
istrue=False
continue
else:
print("TTL正确")
# file.write("TTL正确\n")
剩余的就是对写入文件的处理,比较简单就不单说了。
实验五:IPv4分组转发实验
实验要求:
在前面IPv4分组收发实验的基础上,增加分组转发功能。具体来说, 对于每一个到达本机的IPv4 分组,根据其目的IPv4地址 决定分组的处理行为: 1) “接收”目的地址为本机地址的分组; 2) 根据路由查找结果,“丢弃”查不到路由的分组; 3) 根据路由查找结果,“转发”不是本机接收的分组。
实验过程:
实验五其实就是在实验四的基础上多加了对目的地址的处理,其中需要设置一个简单的路由表,如果目的ip在路由表的设置中,则转发,如果是本地ip,则接收,否则丢弃。
设置路由表
因为路由条目比较简单,所以直接用一个列表就可以了
forward_ips = ['192.168.214.2', '192.168.214.1', '192.168.214.3', '43.168.142.77', '192.168.213.2', '192.168.142.2', '192.168.142.77', '43.168.142.78', '43.168.142.65', '43.168.142.66', '97.43.142.226', '97.43.142.225', '97.43.142.229']
目的ip处理
目的ip处理上面已经说过,如果是需要转发的ip需要对ip头部进行修改:ttl-1,校验和需要重新计算,具体计算方法如下:
发送端:将校验和字段先赋值位零,然后依然是每两个字节“合成”为一个数,求和处理,中间溢出(进位)的情况处理相同,都是加一然后进的位抹除,最后的和按位取反。
if dst_ip==localIp:
print("正确 接收")
file.write("正确 接收\n")
elif dst_ip in forward_ips:
newttl=ip_ttl-1
newchecksum=0
# 重新计算校验和
for i in range(0,len(ip_header),2):
if i==10:
tempsum=0
elif i==8:
tempsum=((ip_header[i]-1)<<8)+ip_header[i+1]
else:
tempsum=(ip_header[i]<<8)+ip_header[i+1]
newchecksum=newchecksum+tempsum
# 处理溢出
while newchecksum>0xffff:
temp=newchecksum>>16
newchecksum=newchecksum&0xffff
newchecksum=newchecksum+temp
newchecksum=~newchecksum
newchecksum=0xffff&newchecksum
file.write(f"正确 转发 {newttl} {hex(newchecksum)}\n")
else:
print("目的ip错误")
file.write("错误目标地址 丢弃\n")
istrue=False
continue
完整代码
实验四
import socket
import struct
# 设置好的本机ip
localIp='192.168.110.138'
count = 0
with open("./output/output.txt","w",encoding="utf-8") as file:
for i in range(100):
istrue= True
pcap_file = f"./propacket/pro{i}.pcap"
# print("Processing:", pcap_file)
with open(pcap_file, 'rb') as f:
# 跳过pcapHeader部分
f.seek(24)
packet_header = f.read(16) # 读取packetHeader部分
incl_len = struct.unpack('<I', packet_header[8:12])[0] # 解析获取数据包长度
data = f.read(incl_len) # 读取数据包内容——packetData部分
if len(data) < 14: #以太网帧头部一般有14个字节长
print("数据包太短")
continue
eth_header = data[:14] # 获得以太网帧头部分
eth_type = struct.unpack("!H", eth_header[12:14])[0] # 头部14个字节中最后两个存储上层协议类型
if eth_type == 0x0800: # 如果是IPv4数据包
ip_header_offset = 14 # IP头部在以太网数据包中的偏移量
ip_header_len = (data[ip_header_offset] & 0x0F) * 4#首部长度
if ip_header_len ==20:
print("首部长度正确")
# file.write("首部长度正确\n")
else:
print("首部长度错误")
file.write("头部长度错\n")
istrue=False
continue
if len(data) < ip_header_offset + ip_header_len:
print("ip头部有缺失")
continue
ip_header = data[ip_header_offset:ip_header_offset + ip_header_len]
ip_version = (ip_header[0])>>4#版本号
ip_ttl=ip_header[8]#寿命
dst_ip=socket.inet_ntoa(ip_header[16:20])#目的ip
# 解析 IPv4 头部的校验和字段
ip_checksum = struct.unpack('!H', ip_header[10:12])[0]
# 计算校验和
checksum = 0
tempsum=0
for i in range(0, len(ip_header), 2):
tempsum=(ip_header[i] << 8) + ip_header[i+1]#第一个字节的数左移8位加上第二个字节的数,实现“合成”
checksum=checksum+tempsum
# 处理溢出
while checksum>0xffff:
temp=checksum>>16
checksum=checksum&0xffff
checksum=checksum+temp
calculated_checksum=checksum
print("--------下面是IPv4头部信息--------")
print("Header Checksum:", hex(ip_checksum)) # 打印原始数据包中的校验和
print("Calculated Checksum:", hex(calculated_checksum)) # 打印计算得到的校验和
print("length of header is ",ip_header_len)
print("ttl is ",ip_ttl)
print("目的ip:",dst_ip)
print("------------------输出:---------------")
if ip_version !=4:
print("版本号错误")
file.write("版本号错\n")
istrue=False
continue
# continue
else:
print("版本号正确")
# file.write("版本号正确\n")
if ip_ttl <=0:
print("TTL错误")
file.write("TTL错\n")
istrue=False
continue
else:
print("TTL正确")
# file.write("TTL正确\n")
if calculated_checksum==0xffff:
count=count+1
print("校验和正确")
# file.write("校验和正确\n")
else:
print("校验和错误")
file.write("校验和错\n")
istrue=False
continue
if dst_ip==localIp:
print("目的ip正确")
# file.write("目的ip正确\n")
else:
print("目的ip错误")
file.write("错误目标地址\n")
istrue=False
continue
if istrue==True:
file.write("正确\n")
else:
print("不是ipv4协议,版本号错误")
print("-" * 33)
print("count is ",count)
实验五
import socket
import struct
localIp='192.168.110.138'
forward_ips = ['192.168.214.2', '192.168.214.1', '192.168.214.3', '43.168.142.77', '192.168.213.2', '192.168.142.2', '192.168.142.77', '43.168.142.78', '43.168.142.65', '43.168.142.66', '97.43.142.226', '97.43.142.225', '97.43.142.229']
count = 0
with open("./output/output2.txt","w",encoding="utf-8") as file:
for i in range(100):
istrue= True
pcap_file = f"./propacket/pro{i}.pcap"
# print("Processing:", pcap_file)
with open(pcap_file, 'rb') as f:
# 跳过pcapHeader部分
f.seek(24)
pcap_header = f.read(16) # 读取packetHeader部分
incl_len = struct.unpack('<I', pcap_header[8:12])[0] # 解析获取数据包长度
data = f.read(incl_len) # 读取数据包内容——packetData部分
if len(data) < 14: #以太网帧头部一般有14个字节长
print("数据包太短")
continue
eth_header = data[:14] # 获得以太网帧头部分
eth_type = struct.unpack("!H", eth_header[12:14])[0] # 头部14个字节中最后两个存储上层协议类型
if eth_type == 0x0800: # 如果是IPv4数据包
ip_header_offset = 14 # IP头部在以太网数据包中的偏移量
ip_header_len = (data[ip_header_offset] & 0x0F) * 4#首部长度
if ip_header_len ==20:
print("首部长度正确")
# file.write("首部长度正确\n")
else:
print("首部长度错误")
file.write("头部长度错 丢弃\n")
istrue=False
continue
if len(data) < ip_header_offset + ip_header_len:
print("头部信息有缺失")
continue
ip_header = data[ip_header_offset:ip_header_offset + ip_header_len]
ip_version = (ip_header[0])>>4#版本号
ip_ttl=ip_header[8]#寿命
dst_ip=socket.inet_ntoa(ip_header[16:20])#目的ip
# 解析 IPv4 头部的校验和字段
ip_checksum = struct.unpack('!H', ip_header[10:12])[0]
# print("原校验和为 ",hex(ip_checksum))
# print("ip_checksum is ",ip_checksum)
# 计算 IPv4 头部的校验和
checksum = 0
tempsum=0
for i in range(0, len(ip_header), 2):
# print("每加:",hex((ip_header[i] << 8) + ip_header[i+1]))
tempsum=(ip_header[i] << 8) + ip_header[i+1]
checksum=checksum+tempsum
# 处理溢出,一般两次就可以
while checksum>0xffff:
temp=checksum>>16
checksum=checksum&0xffff
checksum=checksum+temp
# checksum=checksum&0xffff
calculated_checksum=checksum
print("--------下面是IPv4头部信息--------")
print("Header Checksum:", hex(ip_checksum)) # 打印原始数据包中的校验和
print("Calculated Checksum:", hex(calculated_checksum)) # 打印计算得到的校验和
print("length of header is ",ip_header_len)
print("ttl is ",ip_ttl)
print("目的ip:",dst_ip)
print("------------------输出:---------------")
if ip_version !=4:
print("版本号错误")
file.write("版本号错 丢弃\n")
istrue=False
continue
# continue
else:
print("版本号正确")
# file.write("版本号正确\n")
if ip_ttl <=0:
print("TTL错误")
file.write("TTL错 丢弃\n")
istrue=False
continue
else:
print("TTL正确")
# file.write("TTL正确\n")
if calculated_checksum==0xffff:
count=count+1
print("校验和正确")
# file.write("校验和正确\n")
else:
print("校验和错误")
file.write("校验和错 丢弃\n")
istrue=False
continue
if dst_ip==localIp:
print("正确 接收")
file.write("正确 接收\n")
elif dst_ip in forward_ips:
newttl=ip_ttl-1
newchecksum=0
# 重新计算校验和
for i in range(0,len(ip_header),2):
if i==10:
tempsum=0
elif i==8:
tempsum=((ip_header[i]-1)<<8)+ip_header[i+1]
else:
tempsum=(ip_header[i]<<8)+ip_header[i+1]
newchecksum=newchecksum+tempsum
# 处理溢出
while newchecksum>0xffff:
temp=newchecksum>>16
newchecksum=newchecksum&0xffff
newchecksum=newchecksum+temp
newchecksum=~newchecksum
newchecksum=0xffff&newchecksum
file.write(f"正确 转发 {newttl} {hex(newchecksum)}\n")
else:
print("目的ip错误")
file.write("错误目标地址 丢弃\n")
istrue=False
continue
else:
print("不是ipv4协议,版本号错误")
print("-" * 33)
print("count is ",count)
参考链接:https://blog.csdn.net/weixin_40950781/article/details/117632309