Linux——原始通訊端

來源:互聯網
上載者:User

 

緬懷Stevens大師。

本文只是,對UNPv1前幾節的轉載!!!並非原創!!!!!要想真的學會raw socket 把UNPv1 28章的3個程式從頭到尾好好看看一下!!

最好的參考資料:

1.師從互連網。

2.Linux man 命令:man  7 raw。

3.UNP v1第28章 。

4.http://www.cublog.cn/u2/62281/showart_1096746.html

http://linux.chinaitlab.com/c/389513.html

 

第一條:概述

使用原始通訊端(raw socket)可以發送和接收到主機網卡上的資料幀或者資料包,簡而言之,可以編寫基於IP協議的程式。

man中指出:Raw  sockets 允許使用者建立新的IPv4協議。原始套介面收發的未經處理資料報(raw  datagram)不包括鏈路層的頭部。 

UNPV1中:原始套介面提供了普通TCP和UDP socket不能提供的3個能力如下:

1.進程使用raw socket 可以讀寫ICMPv4、IGMPv4、ICMPv6等分組。這個能力還使得使用ICMP或IGMP構造的應用程式能夠完全作為使用者進程處理,而不必往核心中添加額外代碼。從下一條可以看出核心中是包括ICMP和IGMP協議的處理代碼的。

 

2.大多數核心只處理IPv4資料報中一個名為協議的8位欄位的值為1(ICMP)、2(IGMP)、6(TCP)、17(UDP)四種情況。然而該欄位的值還有許多其他值。進程使用raw socket 就可以讀寫那些核心不處理的IPv4資料報了。

3.通過使用raw socket ,進程可以使用IP_HDRINCL套介面選項自行構造IPv4頭部。這個能力可用於構造TCP或UDP分組等。//Head is include with data。

本文中只涉及資料報相關的內容,至於乙太網路幀,留待下一篇文章

第二條:建立Raw socket

1.只有root使用者才能建立raw socket,以防止普通使用者向Internet輸入他們自行構造的IP資料報。

int sockfd=socket(AF_INET,SOCK_RAW,protocol);

protocol參數其值如下:定義在netinet/in.h

如果指定protocol為0時,原始通訊端可以接收核心傳遞給原始通訊端的任何IP資料包

    IPPROTO_IP = 0,   /* Dummy protocol for TCP.  *///這個協議的Dummy的意思是系統什麼也不做。

 

    IPPROTO_HOPOPTS = 0,   /* IPv6 Hop-by-Hop options.  */

    IPPROTO_ICMP = 1,   /* Internet Control Message Protocol.  */

    IPPROTO_IGMP = 2,   /* Internet Group Management Protocol. */

    IPPROTO_IPIP = 4,   /* IPIP tunnels (older KA9Q tunnels use 94).  */

    IPPROTO_TCP = 6,   /* Transmission Control Protocol.  */

    IPPROTO_EGP = 8,   /* Exterior Gateway Protocol.  */

    IPPROTO_PUP = 12,   /* PUP protocol.  */

    IPPROTO_UDP = 17,   /* User Datagram Protocol.  */

    IPPROTO_IDP = 22,   /* XNS IDP protocol.  */

    IPPROTO_TP = 29,   /* SO Transport Protocol Class 4.  */

    IPPROTO_DCCP = 33,   /* Datagram Congestion Control Protocol.  */

    IPPROTO_IPV6 = 41,     /* IPv6 header.  */

    IPPROTO_ROUTING = 43,  /* IPv6 routing header.  */

    IPPROTO_FRAGMENT = 44, /* IPv6 fragmentation header.  */

    IPPROTO_RSVP = 46,   /* Reservation Protocol.  */

    IPPROTO_GRE = 47,   /* General Routing Encapsulation.  */

    IPPROTO_ESP = 50,      /* encapsulating security payload.  */

    IPPROTO_AH = 51,       /* authentication header.  */

    IPPROTO_ICMPV6 = 58,   /* ICMPv6.  */

    IPPROTO_NONE = 59,     /* IPv6 no next header.  */

    IPPROTO_DSTOPTS = 60,  /* IPv6 destination options.  */

    IPPROTO_MTP = 92,   /* Multicast Transport Protocol.  */

    IPPROTO_ENCAP = 98,   /* Encapsulation Header.  */

    IPPROTO_PIM = 103,   /* Protocol Independent Multicast.  */

    IPPROTO_COMP = 108,   /* Compression Header Protocol.  */

    IPPROTO_SCTP = 132,   /* Stream Control Transmission Protocol.  */

    IPPROTO_UDPLITE = 136, /* UDP-Lite protocol.  */

    IPPROTO_RAW = 255,   /* Raw IP packets.  */

2.當需要編寫自己的IP資料包首部時,在raw socket開啟IP_HDRINCL套介面選項//Head is include with data:就是核心不會為這個資料報自動添加IP頭部。使用者告訴核心首部由我們自己構造,並且和資料放在一起,核心不用操心了~~~

 

const int on=1;

if(setsocketopt(scokfd,IPPROTO_IP,IP_HDRINCL ,&on,sizeof(on))<0)

            perror("setsocketopt error:");

 

原始通訊端直接使用IP協議的通訊端,所以是非連線導向的。在這個通訊端上可以調用connect和bind函數,分別執行綁定對方和本地地址。

3.可以在這個原始套介面上調用bind函數,不過比較少見。bind函數僅僅設定本地地址,因為原始套介面不存在連接埠的概念。就輸出而言,調用bind設定的是將用於從這個原始套介面發送的所有資料報的源IP地址(只有IP_HDRINCL套介面選項未開啟的前提下)。如果不調用bind,核心就把源IP地址設定為外出介面的主IP地址。
4.可以在這個原始套介面上調用connect函數,不過也比較少見。connect函數僅僅設定遠地地址,同樣因為原始套介面不存在連接埠號碼的概念。就輸出而言,調用connect之後我們可以把sendto調用改為write調用,因為宿IP地址已經指定了。

第三條:Raw socket 輸出

1    普通輸出可以調用sendto或sendmsg並指定宿IP地址完成。如果在套介面已經串連(調用connect),那麼可以調用write,writev或send。
2    如果IP_HDRINCL選項未開啟,那麼由進程讓核心發送的資料的起始地址指的是IP頭部之後的第一個位元組,因為核心將構造IP頭部並把它置於來自進程的資料之前。核心把所構造IPv4頭部的協議欄位設定成來自socket調用的第三個參數。
3    如果IP_HDRINCL選項已開啟,那麼由進程讓核心發送的資料的起始地址指的是IP頭部的第一個位元組。進程調用輸出函數寫出的資料量必須包括IP頭部的大小。整個IP頭部由進程構造,不過:(a)IPv4識別欄位可置為0,從而告知核心設定該值;(b)IPv4頭部檢驗和總是由核心計算並儲存;(c)IPv4選項欄位是可選的。
4    核心對於超出外出介面MTU的原始分組執行分區。
5    對於IPv4,計算並設定IPv4頭部之後所含的任何頭部校正和總是由使用者進程負責。也就是說,必須在調用sendto等之前計算資料的校正和並將其存入相應選項中。

IPv6的差異:見(RFC3542)
1    通過IPv6原始套介面發送和接收的協議頭部中的所有欄位均採用網路位元組序。
2    IPv6不存在與IPv4的IP_HDRINCL套介面類似的東西。通過IPv6原始套介面無法讀入或寫出完整的IPv6分組(包括IPv6頭部和任何擴充頭部)。IPv6頭部的幾乎所有欄位以及所有擴充頭部都可以通過套介面選項或輔助資料由應用進程指定或擷取。如果應用進程需要讀入或寫出完整的IPv6資料報,那就必須使用資料鏈路訪問。
3    IPv6原始套介面的校正和處理存在差異,如下:
IPV6_CHECKSUM套介面選項
對於ICMPv6原始套介面,核心總是計算並儲存ICMPv6頭部中的校正和。這點不同於ICMPv4原始套介面,也就是說,ICMPv4頭部中的校正和必須由應用進程自己計算並儲存。ICMPv4和ICMPv6都要求計算校正和,但ICMPv6卻在其校正和中包括一個偽頭部(pseudoheader)。該偽頭部中的欄位之一是源IPv6地址,而應用進程通常讓核心選擇其值。與其讓應用進程就為了計算校正和而不得不試圖執行選擇這個地址,還不如讓核心計算校正和來的容易。
對於其他IPv6原始套介面(不是以IPPROTO_ICMPV6為第三個參數調用socket建立的那些原始套介面),進程可以使用一個套介面選項告知核心是否計算並儲存外出分組中的校正和,且驗證接收分組中的校正和。該選項預設是禁止的,不過把它的值設定為某個非負值就可以開啟該選項。如:
int offset = 2;
if( setsockopt( sockfd, IPPROTO_IPV6, IPV6_CHECKSUM, &offset, sizeof(offset) ) < 0 )
                    perror("setsocketopt error:");
上面代碼不止開啟指定套介面上的校正和,而且告知核心這個16位的校正和欄位的位元組位移量:本例中為 自 應用資料開始處起位移2個位元組。禁止該選項要求把這個位移量設定為-1. 一旦開啟核心將為在指定套介面上發送的外出分組計算並儲存校正和,並且為在該套介面接收的外來分組驗證校正和。

第四條:Raw socket 輸入

就原始套介面的輸入我們必須首先回答的問題是:核心把哪些收到的IP資料報傳遞到原始套介面?這兒遵循如下規則:
1    接收到的UDP分組和TCP分組絕不傳遞到任何原始套介面。如果一個進程想要讀取含有 
UDP分組或TCP分組的IP資料報,它就必須在資料連結層讀取這些分組。
2    大多數ICMP分組在核心完成處理其中的ICMP訊息後傳遞到原始套介面。源自Berkeley的實現把不同的回射請求,時間戳記請求或位址遮罩請求的所有接收到的ICMP分組傳遞給原始套介面。
3    所有IGMP分組在核心中完成處理其中的IGMP訊息後傳遞到原始套介面。
4    核心不認識其協議欄位的所有IP資料報傳遞到原始套介面。核心對這些分組執行的唯一處理是針對某些IP頭部欄位的最小驗證:IP版本,IPv4頭部校正和,頭部長度以及宿IP地址。
5    如果某個資料報以片段形式到達,那麼在它的所有片段均達到且重組出該資料報之前,不傳遞任何分區分組到原始套介面。
當核心有一個需要傳遞到原始套介面的IP資料報時,它將檢查所有進程上的所有原始套介面,以尋找所匹配的套介面。每個匹配的套介面將被地送到該IP資料報的一個拷貝,核心對每個原始套介面均執行如下3個測試,只有這3個測試結果均為真,核心才把接收到的資料報遞送到這個套介面:
1    如果建立這個原始套介面時指定了非0的協議參數(socket的第三個參數),那麼收到的資料報的協議欄位必須匹配該值,否則該資料報不遞送到這個套介面。
2    如果這個原始套介面已經由bind綁定了某個本地IP,那麼接收到的資料報的宿IP地址必須匹配這個綁定地址,否則該資料報不遞送到這個套介面。
3    如果這個原始套介面已經由connect調用了指定某個遠地IP地址,那麼接收到的資料報的源IP地址必須匹配這個已串連地址,否則該資料報不遞送到該套介面。
注意:如果一個原始套介面是以0值協議參數建立的,而且既未對它調用過bind,也未對它調用過connect,那麼該套介面將被遞送以可由核心傳遞到原始套介面的每個未經處理資料報的一個拷貝。
無論何時往一個原始套介面遞送一個接收到的資料報,傳遞到該套介面所在進程的都是包括IP頭部在內的完整資料報。然而對於原始IPv6套介面,傳遞到套介面的只是扣除了IPv6頭部和所有擴充頭部的淨荷(payload)。

ICMPv6類型過濾
原始ICMPv4套介面被遞送以由核心接收的大多數ICMPv4訊息。然而ICMPv6在功用上是ICMPv4的超集,把ARP和IGMP的功能也包括在裡面。因此相比原始ICMP套介面,原始ICMPv6套介面有可能收取多得多的分組。可是使用原始套介面的應用程式大多數僅僅關注所有ICMP訊息的某個小子集。
為了縮減由核心通過原始ICMPv6套介面傳遞到應用進程的分組數量,應用進程可以自行提供一個過濾器。原始ICMPv6套介面上的過濾器使用定義在<netinet/icmp.h>標頭檔中的資料類型struct icmp6_filter聲明,並使用level參數為IPPROTO_ICMPV6且optname參數為ICMP6_FILTER的setsockopt和getsockopt調用來設定和擷取。
一下6個宏用於操作icmp6_filter結構:

#include<netinet/icmp6.h>
void ICMP6_FILTER_SETPASSALL( struct icmp6_filter *filt );    //所有訊息都傳遞到應用進程

void ICMP6_FILTER_SETBLOCKALL( struct icmp6_filter *filt ); //不傳遞任何訊息類型,建立
//ICMP6原始套介面後,系統
//預設允許所有ICMP6訊息傳遞

void ICMP6_FILTER_SETPASS( int msgtype, struct icmp6_filter *filt ); //允許存取某個指定訊息類型

void ICMP6_FILTER_SETBLOCK( int msgtype, struct icmp6_filter *filt ); //阻止某個訊息類型傳遞

int ICMP6_FILTER_WILLPASS( int msgtype, const struct icmp6_filter *filt ); //如果指定訊息類型
//被過濾器允許存取返回1,否則返回0

int ICMP6_FILTER_WILLBLOCK( int msgtype, const struct icmp6_filter *filt ); //如果指定訊息類
//型被阻止返回1,否則返回0
傳回值:1---若過濾器允許存取(或阻止)給定訊息類型                 否則返回0
        
        所以這些宏中的filt參數指向某個icmp6_filter變數的一個指標,其中前四個宏修改該變數,後2個宏查看該變數。msgtype參數在0-255之間取值,指定ICMP類型。

樣本如下(該例子為只想接收ICMPv6路由器通告訊息的某個應用程式程式碼片段):
    struct icmp6_filter myfilt;
    fd = socket( AF_INET, SOCK_RAW, IPPROTO_ICMPV6 );
    ICMPV6_FILTER_SETBLOCK( *myfilt );
    ICMPV6_FILTER_SETPASS( ND_ROUTER_ADVERT, &myfilt );
    setsockopt( fd, IPPROTO_ICMPV6, ICMP6_FILTER, &myfilt, sizeof(myfilt) );
本例首先阻止所有訊息類型的傳遞(因為預設設定是傳遞所有訊息類型),然後只允許存取路由器通告訊息的傳遞。儘管如此設定了過濾器,該應用程式仍做好接收所有訊息類型的準備,因為在socket和setsockopt這兩個調用之間到達的任何ICMPv6訊息將被添加到接收隊列中。ICMP6_FILTER套介面選項僅僅是一個最佳化措施。

 

 

 

 

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.