linux sock_raw原始套接字程式設計 (轉)和Linux下Libpcap原始碼分析和包過濾機制
sock_raw原始套接字程式設計可以接收到本機網絡卡上的資料幀或者資料包,對與監聽網路的流量和分析是很有作用的.一共可以有3種方式建立這種 socket
1.socket(AF_INET, SOCK_RAW, IPPROTO_TCP|IPPROTO_UDP|IPPROTO_ICMP)傳送接收ip資料包
2.socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP|ETH_P_ARP|ETH_P_ALL))傳送接收乙太網資料幀
3.socket(AF_INET, SOCK_PACKET, htons(ETH_P_IP|ETH_P_ARP|ETH_P_ALL))過時了,不要用啊
理解一下SOCK_RAW的原理, 比如網絡卡收到了一個 14+20+8+100+4 的udp的乙太網資料幀.
首先,網絡卡對該資料幀進行硬過濾(根據網絡卡的模式不同會有不同的動作,如果設定了promisc混雜模式的話,則不做任何過濾直接交給下一層輸 入例程,否則非本機mac或者廣播mac會被直接丟棄).按照上面的例子,如果成功的話,會進入ip輸入例程.但是在進入ip輸入例程之前,系統會檢查系 統中是否有通過socket(PF_PACKET, SOCK_RAW, ..)建立的套接字.如果有的話並且協議相符,在這個例子中就是需要ETH_P_IP或者ETH_P_ALL型別.系統就給每個這樣的socket接收緩 衝區傳送一個數據幀拷貝.然後進入下一步.
其次,進入了ip輸入例程(ip層會對該資料包進行軟過濾,就是檢查校驗或者丟棄非本機ip或者廣播ip的資料包等,具體要參考原始碼),例子 中就是如果成功的話會進入udp輸入例程.但是在交給udp輸入例程之前,系統會檢查系統中是否有通過socket(AF_INET, SOCK_RAW, ..)建立的套接字.如果有的話並且協議相符,在這個例子中就是需要IPPROTO_UDP型別.系統就給每個這樣的socket接收緩衝區傳送一個數據 幀拷貝.然後進入下一步.
最後,進入udp輸入例程 ...
ps:如果校驗和出錯的話,核心會直接丟棄該資料包的.而不會拷貝給sock_raw的套接字,因為校驗和都出錯了,資料肯定有問題的包括所有資訊都沒有意義了.
進一步分析他們的能力.
1. socket(AF_INET, SOCK_RAW, IPPROTO_UDP);
能:該套接字可以接收協議型別為(tcp udp icmp等)發往本機的ip資料包,從上面看的就是20+8+100.
不能:不能收到非發往本地ip的資料包(ip軟過濾會丟棄這些不是發往本機ip的資料包).
不能:不能收到從本機發送出去的資料包.
傳送的話需要自己組織tcp udp icmp等頭部.可以setsockopt來自己包裝ip頭部
這種套接字用來寫個ping程式比較適合
2. socket(PF_PACKET, SOCK_RAW, htons(x));
這個套接字比較強大,建立這種套接字可以監聽網絡卡上的所有資料幀.從上面看就是20+20+8+100.最後一個乙太網crc從來都不算進來 的,因為核心已經判斷過了,對程式來說沒有任何意義了.
能: 接收發往本地mac的資料幀
能: 接收從本機發送出去的資料幀(第3個引數需要設定為ETH_P_ALL)
能: 接收非發往本地mac的資料幀(網絡卡需要設定為promisc混雜模式)
協議型別一共有四個
ETH_P_IP 0x800 只接收發往本機mac的ip型別的資料幀
ETH_P_ARP 0x806 只接受發往本機mac的arp型別的資料幀
ETH_P_RARP 0x8035 只接受發往本機mac的rarp型別的資料幀
ETH_P_ALL 0x3 接收發往本機mac的所有型別ip arp rarp的資料幀, 接收從本機發出的所有型別的資料幀.(混雜模式開啟的情況下,會接收到非發往本地mac的資料幀)
傳送的時候需要自己組織整個乙太網資料幀.所有相關的地址使用struct sockaddr_ll 而不是struct sockaddr_in(因為協議簇是PF_PACKET不是AF_INET了),比如傳送給某個機器,對方的地址需要使用struct sockaddr_ll.
這種socket大小通吃,強悍
下面是一段相關的程式碼:
...
int sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
struct sockaddr_ll sll;
memset( &sll, 0, sizeof(sll) );
sll.sll_family = PF_PACKET;
struct ifreq ifstruct;
strcpy(ifstruct.ifr_name, "eth0");
ioctl(sockfd, SIOCGIFINDEX, &ifstruct);
sll.sll_ifindex = ifstruct.ifr_ifindex;
sll.sll_protocol = htons(ETH_P_ALL);
if(bind(fd, (struct sockaddr *) &sll, sizeof(sll)) == -1 ) {
perror("bind()");
...
int set_promisc(char *interface, int fd) {
struct ifreq ifr;
strcpy(ifr.ifr_name, interface);
if(ioctl(fd, SIOCGIFFLAGS, &ifr) == -1) {
perror("iotcl()");
return -1;
}
ifr.ifr_flags |= IFF_PROMISC;
if(ioctl(fd, SIOCSIFFLAGS, &ifr) == -1) {
perror("iotcl()");
return -1;
}
return 0;
}
int unset_promisc(char *interface, int fd) {
struct ifreq ifr;
strcpy(ifr.ifr_name, interface);
if(ioctl(fd, SIOCGIFFLAGS, &ifr) == -1) {
perror("iotcl()");
return -1;
}
ifr.ifr_flags &= ~IFF_PROMISC;
if(ioctl(fd, SIOCSIFFLAGS, &ifr) == -1) {
perror("iotcl()");
return -1;
}
return 0;
}
3. socket(AF_INET, SOCK_PACKET, htons(ETH_P_ALL))這個最好不要用,反正我不用...
總結使用方法: 1.只想收到發往本機某種協議的ip資料包的話用第一種就足夠了
2. 更多的詳細的內容請使用第二種.包括ETH_P_ALL引數和混雜模式都可以使它的能力不斷的加強.