絕大部分作業系統在處理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
标題經典的IPv4頭結構
解析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)