具体代码如下:
- import socket
- import struct
- import time
-
-
- # 构建ICMP Timestamp请求数据包
- def build_icmp_request():
- # IP头部
- header = b'\x08\x00\x00\x00\x00\x00\x00\x00'
-
- # ICMP头部
- icmp_type = 13 # ICMP Timestamp请求类型
- icmp_code = 0
- icmp_checksum = 0
- icmp_id = 12345
- icmp_seq = 1
-
- # 时间戳数据
- timestamp = int(time.time())
- # 使用struct.pack()函数将icmp_id、icmp_seq和timestamp打包成二进制数据
- # !HHd表示使用网络字节序进行打包,H表示一个无符号短整数(2字节),d表示一个双精度浮点数(8字节)
- icmp_data = struct.pack('!HHd', icmp_id, icmp_seq, timestamp)
-
- # 计算ICMP校验和
- # 使用struct.pack()函数将icmp_type、icmp_code、icmp_checksum、icmp_id、icmp_seq和timestamp + 1打包成二进制数据
- # !BBHHHd表示使用网络字节序进行打包,B表示一个无符号字节(1字节),H表示一个无符号短整数(2字节),d表示一个双精度浮点数(8字节)
- icmp_checksum = calculate_checksum(
- struct.pack('!BBHHHd', icmp_type, icmp_code, icmp_checksum, icmp_id, icmp_seq, timestamp + 1))
-
- # 构建ICMP Timestamp请求数据包
- icmp_request = struct.pack('!BBHHHd', icmp_type, icmp_code, icmp_checksum, icmp_id, icmp_seq, timestamp) + icmp_data
-
- return header + icmp_request
-
-
- # 计算校验和
-
- # 1. `checksum = 0`:初始化变量`checksum`为0,用于计算校验和。
- #
- # 2. `countTo = (len(data) // 2) * 2`:计算需要进行校验和计算的数据的长度,将其除以2取整并乘以2,以确保数据长度为偶数。
- #
- # 3. `for count in range(0, countTo, 2):`:使用循环遍历数据,每次迭代处理两个字节的数据。
- #
- # 4. `thisVal = data[count + 1] * 256 + data[count]`:将两个字节的数据合并为一个16位的整数,其中高位字节乘以256后与低位字节相加。
- #
- # 5. `checksum = checksum + thisVal`:将计算得到的整数与之前的校验和相加。
- #
- # 6. `checksum = checksum & 0xffffffff`:将校验和限制在32位范围内。
- #
- # 7. `if countTo < len(data):`:检查数据长度是否为奇数。
- #
- # 8. `checksum = checksum + data[len(data) - 1]`:如果数据长度为奇数,将最后一个字节与校验和相加。
- #
- # 9. `checksum = checksum & 0xffffffff`:将校验和限制在32位范围内。
- #
- # 10. `checksum = (checksum >> 16) + (checksum & 0xffff)`:将校验和的高16位和低16位相加。
- #
- # 11. `checksum = checksum + (checksum >> 16)`:将结果的高16位与低16位再相加。
- #
- # 12. `answer = ~checksum`:将校验和取反。
- #
- # 13. `answer = answer & 0xffff`:将校验和限制在16位范围内。
- #
- # 14. `answer = answer >> 8 | (answer << 8 & 0xff00)`:将校验和的字节顺序进行反转。
- #
- # 15. `return answer`:返回计算得到的校验和。
-
- def calculate_checksum(data):
- checksum = 0
- countTo = (len(data) // 2) * 2
-
- for count in range(0, countTo, 2):
- thisVal = data[count + 1] * 256 + data[count]
- checksum = checksum + thisVal
- checksum = checksum & 0xffffffff
-
- if countTo < len(data):
- checksum = checksum + data[len(data) - 1]
- checksum = checksum & 0xffffffff
-
- checksum = (checksum >> 16) + (checksum & 0xffff)
- checksum = checksum + (checksum >> 16)
- answer = ~checksum
- answer = answer & 0xffff
- answer = answer >> 8 | (answer << 8 & 0xff00)
- return answer
-
-
- # 发送ICMP Timestamp请求并接收回应
- def send_icmp_request(destination):
- # 构建ICMP Timestamp请求数据包
- icmp_request = build_icmp_request()
-
- # 创建原始套接字
- # socket.socket()函数用于创建一个新的套接字对象。此处使用AF_INET参数指定使用IPv4协议族,SOCK_RAW参数指定使用原始套接字类型,IPPROTO_ICMP参数指定使用ICMP协议
- sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)
-
- # 设置套接字的选项
- # setsockopt()函数用于设置套接字选项的值。IPPROTO_IP参数表示要设置的选项属于IP协议,IP_HDRINCL参数表示要设置的选项是IP头部的包含选项,1表示要启用该选项
- sock.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)
-
- # 发送ICMP Timestamp请求数据包
- # sendto()方法用于发送数据到指定的目标地址。icmp_request参数是要发送的数据包,(destination, 0)参数表示目标地址和端口,其中destination是目标主机的IP地址,0表示端口号
- sock.sendto(icmp_request, (destination, 0))
-
- # 接收ICMP回应数据包
- # recvfrom()方法用于从套接字接收数据。1024参数表示一次最多接收的数据大小
- data, address = sock.recvfrom(1024)
-
- # 解析ICMP回应数据包
- icmp_reply = struct.unpack('!BBHHHd', data[20:36])
-
- # 提取时间戳信息
- timestamp = icmp_reply[5]
-
- return timestamp
-
-
- def main():
- # 示例用法
- destination = '192.168.134.128' # 目标主机IP地址
- timestamp = send_icmp_request(destination)
- print(f'Timestamp from {destination}: {timestamp}')
-
-
- if __name__ == '__main__':
- main()
运行结果:
