絕大部分操作系統在處理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)