绝大部分操作系统在处理UDP闭合端口时,存在一种共性行为,我们可以通过这种行为来确定某个IP地址上是否有主机存活。当你发送一个UDP数据包到主机的某个关闭的UDP端口上时,目标主机通常会返回一个ICMP包指示目标端口不可达。这样的ICMP信息意味着目标主机是存活的,因为我们可以假设如果没有接收到发送的UDP数据的任何响应,目标主机应该不存在。挑选一个不太可能被使用的UDP端口来确保这种方式的有效性是必要的,为了达到最大范围的覆盖度,我们可以查探多个端口以避免正好将数据发送到活动的UDP服务上。
在Windows和Linux上访问原始套接字有些许不同,但我们更中意于在多平台部署同样的嗅探器以实现更大的灵活性。我们将先创建套接字对象,然后再判断程序在哪个平台上运行。在Windows平台上,我们需要通过套接字输入/输出控制(IOCTL)1设置一些额外的标志,它允许在网络接口上启用混杂模式。在第一个例子中,我们只需设置原始套接字嗅探器,读取一个数据包,然后退出即可。
代码如下:
import socket
import os
#监听的主机
host="127.0.0.1"
if os.name == "nt":#os.name——判断现在正在实用的平台,Windows 返回 ‘nt', Linux 返回’posix'。
socket_protocol =socket.IPPROTO_IP
else:
socket_protocol=socket.IPPROTO_ICMP
#创建原始套接字,然后绑定在公开接口上。
sniffer = socket.socket(socket.AF_INET,socket.SOCK_RAW,socket_protocol)
sniffer.bind((host,0))
#设置在捕获的数据包中包含IP头
sniffer.setsockopt(socket.IPPROTO_IP,socket.IP_HDRINCL,1)
#在windows平台上,我们需要设置IOCTL以启动混在模式
if os.name=='nt':
sniffer.ioctl(socket.SIO_RCVALL,socket.RCVALL_ON)
print(sniffer.recvfrom(65565))
if os.name=='nt':
sniffer.ioctl(socket.SIO_RCVALL,socket.RCVALL_OFF)
网络编程员们可以以端口号0来作为连接参数。这样的话操作系统就会从动态端口号范围内搜索接下来可以使用的端口号。
os.name——判断现在正在实用的平台,Windows 返回 ‘nt', Linux 返回’posix'。
ioctl(int fd, int request, void * arg) 设备驱动程序中对设备的I/O通道进行管理的函数。
- int fd 文件句柄. 用于socket时, 是socket套接字.
- int request 函数定义的所有操作. 关于socket的操作, 定义在文件中.
- void *arg 指针的类型依赖于request参数.
以管理员身份运行该python文件,并打开另一个终端ping某个主机,比如:ping baidu.com
解析IP层
import socket
import os
import struct
#ctypes模块创建类似于C的结构体,这允许我们以友好的方式处理和显示IP头和其中的组成部分。
from ctypes import *
#监听的主机
host="10.60.17.46"
#IP头定义
class IP(Structure):
_fields_=[
("ih1",c_ubyte,4),
("version",c_ubyte,4),
("tos",c_ubyte),
("1en",c_ushort),
("id",c_ushort),
("offset",c_ushort),
("tt1",c_ubyte),
("protocol_num",c_ubyte),
("sum",c_ushort),
("src",c_ulong),
("dst",c_ulong)
]
def __new__(self,socket_buffer=None):
#from_buffer_copy方法在__new__方法将收到的数据生成一个IP class的实例
return self.from_buffer_copy(socket_buffer)
def __init__(self,socket_buffer=None):
#协议字段与协议名称对应
self.protocol_map={1:"ICMP",6:"TCP",17:"UDP"}
#可读性更强的IP地址
#inet_ntoa()把IP数据转换成字符串。
self.src_address = socket.inet_ntoa(struct.pack("<I", self.src))
self.dst_address = socket.inet_ntoa(struct.pack("<I", self.dst))
#协议类型
try:
self.protocol=self.protocol_map[self.protocol_num]
except:
self.protocol=str(self.protocol_map)
#创建原始套接字,然后绑定在公开接口上。
if os.name == "nt":#os.name——判断现在正在实用的平台,Windows 返回 ‘nt', Linux 返回’posix'。
socket_protocol =socket.IPPROTO_IP
else:
socket_protocol=socket.IPPROTO_ICMP
# protocol:协议类型
# 传输层:IPPROTO_TCP、IPPROTO_UDP、IPPROTO_ICMP
# 网络层:htons(ETH_P_IP|ETH_P_ARP|ETH_P_ALL)
sniffer = socket.socket(socket.AF_INET,socket.SOCK_RAW,socket_protocol)
sniffer.bind((host,0))#网络编程员们可以以端口号0来作为连接参数。这样的话操作系统就会从动态端口号范围内搜索接下来可以使用的端口号。网络编程员们可以以端口号0来作为连接参数。这样的话操作系统就会从动态端口号范围内搜索接下来可以使用的端口号。
#设置在捕获的数据包中包含IP头
#IP_HDRINCL在数据包中包含IP首部
sniffer.setsockopt(socket.IPPROTO_IP,socket.IP_HDRINCL,1)
#在windows平台上,我们需要设置IOCTL以启动混在模式
if os.name=='nt':
sniffer.ioctl(socket.SIO_RCVALL,socket.RCVALL_ON)
try:
while True:
#读取数据包
rwa_buffer = sniffer.recvfrom(65565)[0]
#将缓冲区的前20个字节按IP头进行解析
ip_header=IP(rwa_buffer[0:20])
print("Protocol:%s %s ---> %s"%(ip_header.protocol,ip_header.src_address,ip_header.dst_address))
except KeyboardInterrupt:
#如果在windows下运行,关闭混杂模式。
if os.name == 'nt':
sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF)