談起socket編程大家也許會想起QQ和IE沒錯還有許多網絡工具如PPNetMeeting等在應用層實現的應用程序也是用socket來實現的Socket是一個網絡編程接口實現於網絡應用層Windows Socket包括了一套系統組件充分利用了Microsoft Windows 消息驅動的特點Socket規范版是在年月發行的並廣泛用於此後出現的Windowsx操作系統中Socket規范版(其在Windows平台上的版本是Winsock也叫Winsock)在 年 月發行Windows NT 及以後版本的Windows系統支持Winsock在Winsock中支持多個傳輸協議的原始套接字重疊I/O模型服務質量控制等
本文向大家介紹Windows Sockets的一些關於用C#實現的原始套接字(Raw Socket)的編程以及在此基礎上實現的網絡封包監視技術同Winsock相比Winsock最明顯的就是支持了Raw Socket套接字類型使用Raw Socket可把網卡設置成混雜模式在這種模式下我們可以收到網絡上的IP包當然包括目的不是本機的IP包通過原始套接字我們也可以更加自如地控制Windows下的多種協議而且能夠對網絡底層的傳輸機制進行控制
在本文例子中我在nbyteBasicClass命名空間實現了RawSocket類它包含了我們實現數據包監視的核心技術在實現這個類之前需要先寫一個IP頭結構來暫時存放一些有關網絡封包的信息
[StructLayout(LayoutKindExplicit)]
public struct IPHeader
{
[FieldOffset()] public byte ip_verlen; //I位首部長度+位IP版本號
[FieldOffset()] public byte ip_tos; //位服務類型TOS
[FieldOffset()] public ushort ip_totallength; //位數據包總長度(字節)
[FieldOffset()] public ushort ip_id; //位標識
[FieldOffset()] public ushort ip_offset; //位標志位
[FieldOffset()] public byte ip_ttl; //位生存時間 TTL
[FieldOffset()] public byte ip_protocol; //位協議(TCP UDP ICMP Etc)
[FieldOffset()] public ushort ip_checksum; //位IP首部校驗和
[FieldOffset()] public uint ip_srcaddr; //位源IP地址
[FieldOffset()] public uint ip_destaddr; //位目的IP地址
}
這樣當每一個封包到達時候可以用強制類型轉化把包中的數據流轉化為一個個IPHeader對象
下面就開始寫RawSocket類了一開始先定義幾個參數包括
private bool error_occurred; //套接字在接收包時是否產生錯誤
public bool KeepRunning; //是否繼續進行
private static int len_receive_buf; //得到的數據流的長度
byte [] receive_buf_bytes; //收到的字節
private Socket socket = null; //聲明套接字
還有一個常量
const int SIO_RCVALL = unchecked((int)x);//監聽所有的數據包
這裡的SIO_RCVALL是指示RawSocket接收所有的數據包在以後的IOContrl函數中要用在下面的構造函數中實現了對一些變量參數的初始化
public RawSocket() //構造函數
{
error_occurred=false;
len_receive_buf = ;
receive_buf_bytes = new byte[len_receive_buf];
}
下面的函數實現了創建RawSocket並把它與終結點(IPEndPoint本機IP和端口)綁定
public void CreateAndBindSocket(string IP) //建立並綁定套接字
{
socket = new Socket(AddressFamilyInterNetwork SocketTypeRaw ProtocolTypeIP);
socketBlocking = false; //置socket非阻塞狀態
socketBind(new IPEndPoint(IPAddressParse(IP) )); //綁定套接字
if (SetSocketOption()==false) error_occurred=true;
}
其中在創建套接字的一句socket = new Socket(AddressFamilyInterNetwork SocketTypeRaw ProtocolTypeIP);中有個參數
第一個參數是設定地址族MSDN上的描述是指定 Socket 實例用來解析地址的尋址方案當要把套接字綁定到終結點(IPEndPoint)時需要使用InterNetwork成員即采用IP版本的地址格式這也是當今大多數套接字編程所采用一個尋址方案(AddressFamily)
第二個參數設置的套接字類型就是我們使用的Raw類型了SocketType是一個枚舉數據類型Raw套接字類型支持對基礎傳輸協議的訪問通過使用 SocketTypeRaw你不光可以使用傳輸控制協議(Tcp)和用戶數據報協議(Udp)進行通信也可以使用網際消息控制協議 (Icmp) 和 Internet 組管理協議 (Igmp) 來進行通信在發送時您的應用程序必須提供完整的 IP 標頭所接收的數據報在返回時會保持其 IP 標頭和選項不變
第三個參數設置協議類型Socket 類使用 ProtocolType 枚舉數據類型向 Windows Socket API 通知所請求的協議這裡使用的是IP協議所以要采用ProtocolTypeIP參數
在CreateAndBindSocket函數中有一個自定義的SetSocketOption函數它和Socket類中的SetSocketOption不同我們在這裡定義的是具有IO控制功能的SetSocketOption它的定義如下
private bool SetSocketOption() //設置raw socket
{
bool ret_value = true;
try
{
socketSetSocketOption(SocketOptionLevelIP SocketOptionNameHeaderIncluded );
byte []IN = new byte[]{ };
byte []OUT = new byte[];
//低級別操作模式接受所有的數據包這一步是關鍵必須把socket設成raw和IP Level才可用SIO_RCVALL
int ret_code = socketIOControl(SIO_RCVALL IN OUT);
ret_code = OUT[] + OUT[] + OUT[] + OUT[];//把個位字節合成一個位整數
if(ret_code != ) ret_value = false;
}
catch(SocketException)
{
ret_value = false;
}
return ret_value;
}
其中設置套接字選項時必須使套接字包含IP包頭否則無法填充IPHeader結構也無法獲得數據包信息
int ret_code = socketIOControl(SIO_RCVALL IN OUT);是函數中最關鍵的一步了因為在windows中我們不能用Receive函數來接收raw socket上的數據這是因為所有的IP包都是先遞交給系統核心然後再傳輸到用戶程序當發送一個raws socket包的時候(比如syn)核心並不知道也沒有這個數據被發送或者連接建立的記錄因此當遠端主機回應的時候系統核心就把這些包都全部丟掉從而到不了應用程序上所以就不能簡單地使用接收函數來接收這些數據報要達到接收數據的目的就必須采用嗅探接收所有通過的數據包然後進行篩選留下符合我們需要的可以通過設置SIO_RCVALL表示接收所有網絡上的數據包接下來介紹一下IOControl函數MSDN解釋它說是設置套接字為低級別操作模式怎麼低級別操作法?其實這個函數與API中的WSAIoctl函數很相似WSAIoctl函數定義如下
int WSAIoctl(
SOCKET s //一個指定的套接字
DWORD dwIoControlCode //控制操作碼
LPVOID lpvInBuffer //指向輸入數據流的指針
DWORD cbInBuffer //輸入數據流的大小(字節數)
LPVOID lpvOutBuffer // 指向輸出數據流的指針
DWORD cbOutBuffer //輸出數據流的大小(字節數)
LPDWORD lpcbBytesReturned //指向輸出字節流數目的實數值
LPWSAOVERLAPPED lpOverlapped //指向一個WSAOVERLAPPED結構
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine//指向操作完成時執行的例程
);
C#的IOControl函數不像WSAIoctl函數那麼復雜其中只包括其中的控制操作碼輸入字節流輸出字節流三個參數不過這三個參數已經足夠了我們看到函數中定義了一個字節數組byte []IN = new byte[]{ }實際上它是一個值為的DWORD或是Int同樣byte []OUT = new byte[];也是它整和了一個int作為WSAIoctl函數中參數lpcbBytesReturned指向的值
因為設置套接字選項時可能會發生錯誤需要用一個值傳遞錯誤標志
public bool ErrorOccurred
{
get
{
return error_occurred;
}
}
下面的函數實現的數據包的接收
//解析接收的數據包形成PacketArrivedEventArgs事件數據類對象並引發PacketArrival事件
unsafe private void Receive(byte [] buf int len)
{
byte temp_protocol=;
uint temp_version=;
uint temp_ip_srcaddr=;
uint temp_ip_destaddr=;
short temp_srcport=;
short temp_dstport=;
IPAddress temp_ip;
PacketArrivedEventArgs e=new PacketArrivedEventArgs();//新網絡數據包信息事件
fixed(byte *fixed_buf = buf)
{
IPHeader * head = (IPHeader *) fixed_buf;//把數據流整和為IPHeader結構
eHeaderLength=(uint)(head>ip_verlen & xF) << ;
temp_protocol = head>ip_protocol;
switch(temp_protocol)//提取協議類型
{
case : eProtocol=ICMP; break;
case : eProtocol=IGMP; break;
case : eProtocol=TCP; break;
case : eProtocol=UDP; break;
default: eProtocol= UNKNOWN; break;
}
temp_version =(uint)(head>ip_verlen & xF) >> ;//提取IP協議版本
eIPVersion = temp_versionToString();
//以下語句提取出了PacketArrivedEventArgs對象中的其他參數
temp_ip_srcaddr = head>ip_srcaddr;
temp_ip_destaddr = head>ip_destaddr;
temp_ip = new IPAddress(temp_ip_srcaddr);
eOriginationAddress =temp_ipToString();
temp_ip = new IPAddress(temp_ip_destaddr);
eDestinationAddress = temp_ipToString();
temp_srcport = *(short *)&fixed_buf[eHeaderLength];
temp_dstport = *(short *)&fixed_buf[eHeaderLength+];
eOriginationPort=IPAddressNetworkToHostOrder(temp_srcport)ToString();
eDestinationPort=IPAddressNetworkToHostOrder(temp_dstport)ToString();
ePacketLength =(uint)len;
eMessageLength =(uint)len eHeaderLength;
eReceiveBuffer=buf;
//把buf中的IP頭賦給PacketArrivedEventArgs中的IPHeaderBuffer
ArrayCopy(bufeIPHeaderBuffer(int)eHeaderLength);
//把buf中的包中內容賦給PacketArrivedEventArgs中的MessageBuffer
ArrayCopy(buf(int)eHeaderLengtheMessageBuffer(int)eMessageLength);
}
//引發PacketArrival事件
OnPacketArrival(e);
}
大家注意到了在上面的函數中我們使用了指針這種所謂的不安全代碼可見在C#中指針和移位運算這些原始操作也可以給程序員帶來編程上的便利在函數中聲明PacketArrivedEventArgs類對象以便通過OnPacketArrival(e)函數通過事件把數據包信息傳遞出去其中PacketArrivedEventArgs類是RawSocket類中的嵌套類它繼承了系統事件(Event)類封裝了數據包的IP端口協議等其他數據包頭中包含的信息在啟動接收數據包的函數中我們使用了異步操作的方法以下函數開啟了異步監聽的接口
public void Run() //開始監聽
{
IAsyncResult ar = socketBeginReceive(receive_buf_bytes len_receive_buf SocketFlagsNone new AsyncCallback(CallReceive) this);
}
SocketBeginReceive函數返回了一個異步操作的接口並在此接口的生成函數BeginReceive中聲明了異步回調函數CallReceive並把接收到的網絡數據流傳給receive_buf_bytes這樣就可用一個帶有異步操作的接口參數的異步回調函數不斷地接收數據包
private void CallReceive(IAsyncResult ar)//異步回調
{
int received_bytes;
received_bytes = socketEndReceive(ar);
Receive(receive_buf_bytes received_bytes);
if (KeepRunning) Run();
}
此函數當掛起或結束異步讀取後去接收一個新的數據包這樣能保證讓每一個數據包都能夠被程序探測到
下面通過聲明代理事件句柄來實現和外界的通信
public delegate void PacketArrivedEventHandler(Object sender PacketArrivedEventArgs args);
//事件句柄包到達時引發事件
public event PacketArrivedEventHandler PacketArrival;//聲明時間句柄函數
這樣就可以實現對數據包信息的獲取采用異步回調函數可以提高接收數據包的效率並通過代理事件把封包信息傳遞到外界既然能把所有的封包信息傳遞出去就可以實現對數據包的分析了)不過RawSocket的任務還沒有完最後不要望了關閉套接字啊
public void Shutdown() //關閉raw socket
{
if(socket != null)
{
socketShutdown(SocketShutdownBoth);
socketClose();
}
}
以上介紹了RawSocket類通過構造IP頭獲取了包中的信息並通過異步回調函數實現了數據包的接收並使用時間代理句柄和自定義的數據包信息事件類把數據包信息發送出去從而實現了網絡數據包的監視這樣我們就可以在外部添加一些函數對數據包進行分析了
From:http://tw.wingwit.com/Article/program/net/201311/13981.html