注銷

          注銷

            BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
            112 隨筆 :: 7 文章 :: 18 評論 :: 0 Trackbacks

          【導(dǎo)讀】本文向大家介紹Windows Sockets的一些關(guān)于用C#實現(xiàn)的原始套接字(Raw Socket)的編程,以及在此基礎(chǔ)上實現(xiàn)的網(wǎng)絡(luò)封包監(jiān)視技術(shù)。

          同Winsock1相比,Winsock2最明顯的就是支持了Raw Socket套接字類型,使用Raw Socket,可把網(wǎng)卡設(shè)置成混雜模式,在這種模式下,我們可以收到網(wǎng)絡(luò)上的IP包,當然包括目的不是本機的IP包,通過原始套接字,我們也可以更加自如地控制Windows下的多種協(xié)議,而且能夠?qū)W(wǎng)絡(luò)底層的傳輸機制進行控制。? 在本文例子中,nbyte.BasicClass命名空間實現(xiàn)了RawSocket類,它包含了我們實現(xiàn)數(shù)據(jù)包監(jiān)視的核心技術(shù)。在實現(xiàn)這個類之前,需要先寫一個IP頭結(jié)構(gòu),來暫時存放一些有關(guān)網(wǎng)絡(luò)封包的信息:

          [StructLayout(LayoutKind.Explicit)]

          public struct IPHeader

          {

            [FieldOffset(0)] public byte ip_verlen; //I4位首部長度+4位IP版本號

            [FieldOffset(1)] public byte ip_tos; //8位服務(wù)類型TOS

            [FieldOffset(2)] public ushort ip_totallength; //16位數(shù)據(jù)包總長度(字節(jié))

            [FieldOffset(4)] public ushort ip_id; //16位標識

            [FieldOffset(6)] public ushort ip_offset; //3位標志位

            [FieldOffset(8)] public byte ip_ttl; //8位生存時間 TTL

            [FieldOffset(9)] public byte ip_protocol; //8位協(xié)議(TCP, UDP, ICMP, Etc.)

            [FieldOffset(10)] public ushort ip_checksum; //16位IP首部校驗和

            [FieldOffset(12)] public uint ip_srcaddr; //32位源IP地址

            [FieldOffset(16)] public uint ip_destaddr; //32位目的IP地址

          }?


          這樣,當每一個封包到達時候,可以用強制類型轉(zhuǎn)化把包中的數(shù)據(jù)流轉(zhuǎn)化為一個個IPHeader對象。

          下面就開始寫RawSocket類了,一開始,先定義幾個參數(shù),包括:

          private bool error_occurred; //套接字在接收包時是否產(chǎn)生錯誤

          public bool KeepRunning; //是否繼續(xù)進行

          private static int len_receive_buf; //得到的數(shù)據(jù)流的長度

          byte [] receive_buf_bytes; //收到的字節(jié)

          private Socket socket = null; //聲明套接字?


          還有一個常量:

          const int SIO_RCVALL = unchecked((int)0x98000001);//監(jiān)聽所有的數(shù)據(jù)包?


          這里的SIO_RCVALL是指示RawSocket接收所有的數(shù)據(jù)包,在以后的IOContrl函數(shù)中要用,在下面的構(gòu)造函數(shù)中,實現(xiàn)了對一些變量參數(shù)的初始化:

          public RawSocket() //構(gòu)造函數(shù)

          {

            error_occurred=false;

            len_receive_buf = 4096;

            receive_buf_bytes = new byte[len_receive_buf];

          }?


          下面的函數(shù)實現(xiàn)了創(chuàng)建RawSocket,并把它與終結(jié)點(IPEndPoint:本機IP和端口)綁定:

          public void CreateAndBindSocket(string IP) //建立并綁定套接字

          {

            socket = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.IP);

            socket.Blocking = false; //置socket非阻塞狀態(tài)

            socket.Bind(new IPEndPoint(IPAddress.Parse(IP), 0)); //綁定套接字

            if (SetSocketOption()==false) error_occurred=true;

          }?


          其中,在創(chuàng)建套接字的一句

          socket = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.IP);?


          中有3個參數(shù):

          第一個參數(shù)是設(shè)定地址族,MSDN上的描述是“指定 Socket 實例用來解析地址的尋址方案”,當要把套接字綁定到終結(jié)點(IPEndPoint)時,需要使用InterNetwork成員,即采用IP版本4的地址格式,這也是當今大多數(shù)套接字編程所采用一個尋址方案(AddressFamily)。

          第二個參數(shù)設(shè)置的套接字類型就是我們使用的Raw類型了,SocketType是一個枚舉數(shù)據(jù)類型,Raw套接字類型支持對基礎(chǔ)傳輸協(xié)議的訪問。通過使用 SocketType.Raw,你不光可以使用傳輸控制協(xié)議(Tcp)和用戶數(shù)據(jù)報協(xié)議(Udp)進行通信,也可以使用網(wǎng)際消息控制協(xié)議 (Icmp) 和 Internet 組管理協(xié)議 (Igmp) 來進行通信。在發(fā)送時,您的應(yīng)用程序必須提供完整的 IP 標頭。所接收的數(shù)據(jù)報在返回時會保持其 IP 標頭和選項不變。

          第三個參數(shù)設(shè)置協(xié)議類型,Socket 類使用 ProtocolType 枚舉數(shù)據(jù)類型向 Windows Socket API 通知所請求的協(xié)議。這里使用的是IP協(xié)議,所以要采用ProtocolType.IP參數(shù)。

          在CreateAndBindSocket函數(shù)中有一個自定義的SetSocketOption函數(shù),它和Socket類中的SetSocketOption不同,我們在這里定義的是具有IO控制功能的SetSocketOption,它的定義如下:

          private bool SetSocketOption() //設(shè)置raw socket

          {

            bool ret_value = true;

            try

            {

          socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.HeaderIncluded, 1);

          byte []IN = new byte[4]{1, 0, 0, 0};

          byte []OUT = new byte[4];

          //低級別操作模式,接受所有的數(shù)據(jù)包,這一步是關(guān)鍵,必須把socket設(shè)成raw和IP Level才可用SIO_RCVALL

          int ret_code = socket.IOControl(SIO_RCVALL, IN, OUT);

          ret_code = OUT[0] + OUT[1] + OUT[2] + OUT[3];//把4個8位字節(jié)合成一個32位整數(shù)

          if(ret_code != 0) ret_value = false;

            }

            catch(SocketException)

            {

          ret_value = false;

            }

            return ret_value;

          }?


          其中,設(shè)置套接字選項時必須使套接字包含IP包頭,否則無法填充IPHeader結(jié)構(gòu),也無法獲得數(shù)據(jù)包信息。

          int ret_code = socket.IOControl(SIO_RCVALL, IN, OUT);?


          是函數(shù)中最關(guān)鍵的一步了,因為,在windows中我們不能用Receive函數(shù)來接收raw socket上的數(shù)據(jù),這是因為,所有的IP包都是先遞交給系統(tǒng)核心,然后再傳輸?shù)接脩舫绦颍敯l(fā)送一個raws socket包的時候(比如syn),核心并不知道,也沒有這個數(shù)據(jù)被發(fā)送或者連接建立的記錄,因此,當遠端主機回應(yīng)的時候,系統(tǒng)核心就把這些包都全部丟掉,從而到不了應(yīng)用程序上。所以,就不能簡單地使用接收函數(shù)來接收這些數(shù)據(jù)報。要達到接收數(shù)據(jù)的目的,就必須采用嗅探,接收所有通過的數(shù)據(jù)包,然后進行篩選,留下符合我們需要的。可以通過設(shè)置SIO_RCVALL,表示接收所有網(wǎng)絡(luò)上的數(shù)據(jù)包。接下來介紹一下IOControl函數(shù)。MSDN解釋它說是設(shè)置套接字為低級別操作模式,怎么低級別操作法?其實這個函數(shù)與API中的WSAIoctl函數(shù)很相似。WSAIoctl函數(shù)定義如下:

          int WSAIoctl(

            SOCKET s, //一個指定的套接字

            DWORD dwIoControlCode, //控制操作碼

            LPVOID lpvInBuffer, //指向輸入數(shù)據(jù)流的指針

            DWORD cbInBuffer, //輸入數(shù)據(jù)流的大小(字節(jié)數(shù))

            LPVOID lpvOutBuffer, // 指向輸出數(shù)據(jù)流的指針

            DWORD cbOutBuffer, //輸出數(shù)據(jù)流的大小(字節(jié)數(shù))

            LPDWORD lpcbBytesReturned, //指向輸出字節(jié)流數(shù)目的實數(shù)值

            LPWSAOVERLAPPED lpOverlapped, //指向一個WSAOVERLAPPED結(jié)構(gòu)

            LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine//指向操作完成時執(zhí)行的例程

          );?


          C#的IOControl函數(shù)不像WSAIoctl函數(shù)那么復(fù)雜,其中只包括其中的控制操作碼、輸入字節(jié)流、輸出字節(jié)流三個參數(shù),不過這三個參數(shù)已經(jīng)足夠了。我們看到函數(shù)中定義了一個字節(jié)數(shù)組:byte []IN = new byte[4]{1, 0, 0, 0}實際上它是一個值為1的DWORD或是Int32,同樣byte []OUT = new byte[4];也是,它整和了一個int,作為WSAIoctl函數(shù)中參數(shù)lpcbBytesReturned指向的值。

          因為設(shè)置套接字選項時可能會發(fā)生錯誤,需要用一個值傳遞錯誤標志:

          public bool ErrorOccurred

          {

            get

            {

          return error_occurred;

            } }?

          下面的函數(shù)實現(xiàn)的數(shù)據(jù)包的接收:

          //解析接收的數(shù)據(jù)包,形成PacketArrivedEventArgs事件數(shù)據(jù)類對象,并引發(fā)PacketArrival事件

          unsafe private void Receive(byte [] buf, int len)

          {

            byte temp_protocol=0;

            uint temp_version=0;

            uint temp_ip_srcaddr=0;

            uint temp_ip_destaddr=0;

            short temp_srcport=0;

            short temp_dstport=0;

            IPAddress temp_ip;

            PacketArrivedEventArgs e=new PacketArrivedEventArgs();//新網(wǎng)絡(luò)數(shù)據(jù)包信息事件

            fixed(byte *fixed_buf = buf)

            {

          IPHeader * head = (IPHeader *) fixed_buf;//把數(shù)據(jù)流整和為IPHeader結(jié)構(gòu)

          e.HeaderLength=(uint)(head->ip_verlen & 0x0F) << 2;

          temp_protocol = head->ip_protocol;

          switch(temp_protocol)//提取協(xié)議類型

          {

            case 1: e.Protocol="ICMP"; break;

            case 2: e.Protocol="IGMP"; break;

            case 6: e.Protocol="TCP"; break;

            case 17: e.Protocol="UDP"; break;

            default: e.Protocol= "UNKNOWN"; break;

          }

          temp_version =(uint)(head->ip_verlen & 0xF0) >> 4;//提取IP協(xié)議版本

          e.IPVersion = temp_version.ToString();

          //以下語句提取出了PacketArrivedEventArgs對象中的其他參數(shù)

          temp_ip_srcaddr = head->ip_srcaddr;

          temp_ip_destaddr = head->ip_destaddr;

          temp_ip = new IPAddress(temp_ip_srcaddr);

          e.OriginationAddress =temp_ip.ToString();

          temp_ip = new IPAddress(temp_ip_destaddr);

          e.DestinationAddress = temp_ip.ToString();

          temp_srcport = *(short *)&fixed_buf[e.HeaderLength];

          temp_dstport = *(short *)&fixed_buf[e.HeaderLength+2];

          e.OriginationPort=IPAddress.NetworkToHostOrder(temp_srcport).ToString();

          e.DestinationPort=IPAddress.NetworkToHostOrder(temp_dstport).ToString();

          e.PacketLength =(uint)len;

          e.MessageLength =(uint)len - e.HeaderLength;

          e.ReceiveBuffer=buf;

          //把buf中的IP頭賦給PacketArrivedEventArgs中的IPHeaderBuffer

          Array.Copy(buf,0,e.IPHeaderBuffer,0,(int)e.HeaderLength);

          //把buf中的包中內(nèi)容賦給PacketArrivedEventArgs中的MessageBuffer

          Array.Copy(buf,(int)e.HeaderLength,e.MessageBuffer,0,(int)e.MessageLength);

            }

            //引發(fā)PacketArrival事件

            OnPacketArrival(e);

          }?


          大家注意到了,在上面的函數(shù)中,我們使用了指針這種所謂的不安全代碼,可見在C#中指針和移位運算這些原始操作也可以給程序員帶來編程上的便利。在函數(shù)中聲明PacketArrivedEventArgs類對象,以便通過OnPacketArrival(e)函數(shù)通過事件把數(shù)據(jù)包信息傳遞出去。其中PacketArrivedEventArgs類是RawSocket類中的嵌套類,它繼承了系統(tǒng)事件(Event)類,封裝了數(shù)據(jù)包的IP、端口、協(xié)議等其他數(shù)據(jù)包頭中包含的信息。在啟動接收數(shù)據(jù)包的函數(shù)中,我們使用了異步操作的方法,以下函數(shù)開啟了異步監(jiān)聽的接口:

          public void Run() //開始監(jiān)聽

          {

            IAsyncResult ar = socket.BeginReceive(receive_buf_bytes, 0, len_receive_buf, SocketFlags.None, new AsyncCallback(CallReceive), this);

          }?


          Socket.BeginReceive函數(shù)返回了一個異步操作的接口,并在此接口的生成函數(shù)BeginReceive中聲明了異步回調(diào)函數(shù)CallReceive,并把接收到的網(wǎng)絡(luò)數(shù)據(jù)流傳給receive_buf_bytes,這樣就可用一個帶有異步操作的接口參數(shù)的異步回調(diào)函數(shù)不斷地接收數(shù)據(jù)包:

          private void CallReceive(IAsyncResult ar)//異步回調(diào)

          {

            int received_bytes;

            received_bytes = socket.EndReceive(ar);

            Receive(receive_buf_bytes, received_bytes);

            if (KeepRunning) Run();

          }?


          此函數(shù)當掛起或結(jié)束異步讀取后去接收一個新的數(shù)據(jù)包,這樣能保證讓每一個數(shù)據(jù)包都能夠被程序探測到。

          下面通過聲明代理事件句柄來實現(xiàn)和外界的通信:

          public delegate void PacketArrivedEventHandler(Object sender, PacketArrivedEventArgs args);

          //事件句柄:包到達時引發(fā)事件

          public event PacketArrivedEventHandler PacketArrival;//聲明時間句柄函數(shù)?


          這樣就可以實現(xiàn)對數(shù)據(jù)包信息的獲取,采用異步回調(diào)函數(shù),可以提高接收數(shù)據(jù)包的效率,并通過代理事件把封包信息傳遞到外界。既然能把所有的封包信息傳遞出去,就可以實現(xiàn)對數(shù)據(jù)包的分析了:)不過RawSocket的任務(wù)還沒有完,最后不要望了關(guān)閉套接字啊:

          public void Shutdown() //關(guān)閉raw socket

          {

            if(socket != null)

            {

          socket.Shutdown(SocketShutdown.Both);

          socket.Close();

            } }?


          以上介紹了RawSocket類通過構(gòu)造IP頭獲取了包中的信息,并通過異步回調(diào)函數(shù)實現(xiàn)了數(shù)據(jù)包的接收,并使用時間代理句柄和自定義的數(shù)據(jù)包信息事件類把數(shù)據(jù)包信息發(fā)送出去,從而實現(xiàn)了網(wǎng)絡(luò)數(shù)據(jù)包的監(jiān)視,這樣我們就可以在外部添加一些函數(shù)對數(shù)據(jù)包進行分析了。

          posted on 2006-10-14 15:07 注銷..... 閱讀(297) 評論(0)  編輯  收藏 所屬分類: 閱讀摘要
          主站蜘蛛池模板: 盐源县| 绥德县| 余江县| 浦东新区| 杭锦后旗| 周宁县| 惠东县| 崇义县| 阳谷县| 湖南省| 蒙阴县| 南开区| 麟游县| 彭泽县| 集安市| 剑河县| 阜宁县| 和田县| 新巴尔虎右旗| 朝阳市| 铅山县| 罗平县| 北宁市| 湖口县| 合江县| 长武县| 澜沧| 龙井市| 天台县| 普宁市| 镇远县| 郁南县| 布拖县| 文昌市| 偃师市| 盘山县| 麟游县| 罗山县| 丰县| 崇信县| 义马市|