Linux網(wǎng)絡(luò)編程:原始套接字編程及實(shí)例分析
一、原始套接字能干什么?
通常情況下程序員接所接觸到的套接字(Socket)為兩類(lèi):
(1)流式套接字(SOCK_STREAM):一種面向連接的Socket,針對(duì)于面向連接的TCP 服務(wù)應(yīng)用;
(2)數(shù)據(jù)報(bào)式套接字(SOCK_DGRAM):一種無(wú)連接的Socket,對(duì)應(yīng)于無(wú)連接的UDP 服務(wù)應(yīng)用。
從用戶的角度來(lái)看,SOCK_STREAM、SOCK_DGRAM 這兩類(lèi)套接字似乎的確涵蓋了TCP/IP 應(yīng)用的全部,因?yàn)榛赥CP/IP 的應(yīng)用,從協(xié)議棧的層次上講,在傳輸層的確只可能建立于TCP 或 UDP協(xié)議之上,而SOCK_STREAM、SOCK_DGRAM 又分別對(duì)應(yīng)于TCP和UDP,所以幾乎所有的應(yīng)用都可以用這兩類(lèi)套接字實(shí)現(xiàn)。
但是,當(dāng)我們面對(duì)如下問(wèn)題時(shí),SOCK_STREAM、SOCK_DGRAM 將顯得這樣無(wú)助:
(1)怎樣發(fā)送一個(gè)自定義的IP 包?
(2)怎樣發(fā)送一個(gè)ICMP 協(xié)議包?
(3)怎樣分析所有經(jīng)過(guò)網(wǎng)絡(luò)的包,而不管這樣包是否是發(fā)給自己的?
(4)怎樣偽裝本地的IP 地址?
這使得我們必須面對(duì)另外一個(gè)深刻的主題——原始套接字(SOCK_RAW)。原始套接字廣泛應(yīng)用于高級(jí)網(wǎng)絡(luò)編程,也是一種廣泛的黑客手段。著名的網(wǎng)絡(luò)sniffer(一種基于被動(dòng)偵聽(tīng)原理的網(wǎng)絡(luò)分析方式)、拒絕服務(wù)攻擊(DOS)、IP 欺騙等都可以通過(guò)原始套接字實(shí)現(xiàn)。
原始套接字(SOCK_RAW)可以用來(lái)自行組裝數(shù)據(jù)包,可以接收本機(jī)網(wǎng)卡上所有的數(shù)據(jù)幀(數(shù)據(jù)包),對(duì)于監(jiān)聽(tīng)網(wǎng)絡(luò)流量和分析網(wǎng)絡(luò)數(shù)據(jù)很有作用。
原始套接字是基于IP 數(shù)據(jù)包的編程(SOCK_PACKET 是基于數(shù)據(jù)鏈路層的編程)。另外,必須在管理員權(quán)限下才能使用原始套接字。
原始套接字(SOCK_RAW)與標(biāo)準(zhǔn)套接字(SOCK_STREAM、SOCK_DGRAM)的區(qū)別在于原始套接字直接置“根”于操作系統(tǒng)網(wǎng)絡(luò)核心(Network Core),而 SOCK_STREAM、SOCK_DGRAM 則“懸浮”于 TCP 和 UDP 協(xié)議的外圍。
流式套接字只能收發(fā) TCP 協(xié)議的數(shù)據(jù),數(shù)據(jù)報(bào)套接字只能收發(fā) UDP 協(xié)議的數(shù)據(jù),原始套接字可以收發(fā)內(nèi)核沒(méi)有處理的數(shù)據(jù)包。
#p#
二、原始套接字編程
原始套接字編程和之前的UDP 編程差不多,無(wú)非就是創(chuàng)建一個(gè)套接字后,通過(guò)這個(gè)套接字接收數(shù)據(jù)或者發(fā)送數(shù)據(jù)。區(qū)別在于,原始套接字可以自行組裝數(shù)據(jù)包(偽裝本地 IP,本地 MAC),可以接收本機(jī)網(wǎng)卡上所有的數(shù)據(jù)幀(數(shù)據(jù)包)。另外,必須在管理員權(quán)限下才能使用原始套接字。
原始套接字的創(chuàng)建:
int socket ( int family, int type, int protocol );
參數(shù):
family:協(xié)議族 這里寫(xiě) PF_PACKET
type: 套接字類(lèi),這里寫(xiě) SOCK_RAW
protocol:協(xié)議類(lèi)別,指定可以接收或發(fā)送的數(shù)據(jù)包類(lèi)型,不能寫(xiě) “0”,取值如下,注意,傳參時(shí)需要用 htons() 進(jìn)行字節(jié)序轉(zhuǎn)換。
ETH_P_IP:IPV4數(shù)據(jù)包
ETH_P_ARP:ARP數(shù)據(jù)包
ETH_P_ALL:任何協(xié)議類(lèi)型的數(shù)據(jù)包
返回值:
成功( >0 ):套接字,這里為鏈路層的套接字
失敗( <0 ):出錯(cuò)
實(shí)例如下:
- // 所需頭文件
- #include <sys/socket.h>
- #include <netinet/ether.h>
- #include <stdio.h> // perror
- int main(int argc,charchar *argv[])
- {
- int sock_raw_fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL) );
- if(sock_raw_fd < 0){
- perror("socket");
- return -1;
- }
- return 0;
- }
獲取鏈路層的數(shù)據(jù)包:
ssize_t recvfrom( int sockfd,
void *buf,
size_t nbytes,
int flags,
struct sockaddr *from,
socklen_t *addrlen );
參數(shù):
sockfd: 原始套接字
buf: 接收數(shù)據(jù)緩沖區(qū)
nbytes: 接收數(shù)據(jù)緩沖區(qū)的大小
flags: 套接字標(biāo)志(常為0)
from: 這里沒(méi)有用,寫(xiě) NULL
addrlen:這里沒(méi)有用,寫(xiě) NULL
返回值:
成功:接收到的字符數(shù)
失敗:-1
實(shí)例如下:
- #include <stdio.h>
- #include <netinet/in.h>
- #include <sys/socket.h>
- #include <netinet/ether.h>
- int main(int argc,charchar *argv[])
- {
- unsigned char buf[1024] = {0};
- int sock_raw_fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
- //獲取鏈路層的數(shù)據(jù)包
- int len = recvfrom(sock_raw_fd, buf, sizeof(buf), 0, NULL, NULL);
- printf("len = %d\n", len);
- return 0;
- }
混雜模式
默認(rèn)的情況下,我們接收數(shù)據(jù),目的地址是本地地址,才會(huì)接收。有時(shí)候我們想接收所有經(jīng)過(guò)網(wǎng)卡的所有數(shù)據(jù)流,而不論其目的地址是否是它,這時(shí)候我們需要設(shè)置網(wǎng)卡為混雜模式。
網(wǎng)卡的混雜模式一般在網(wǎng)絡(luò)管理員分析網(wǎng)絡(luò)數(shù)據(jù)作為網(wǎng)絡(luò)故障診斷手段時(shí)用到,同時(shí)這個(gè)模式也被網(wǎng)絡(luò)黑客利用來(lái)作為網(wǎng)絡(luò)數(shù)據(jù)竊聽(tīng)的入口。在 Linux 操作系統(tǒng)中設(shè)置網(wǎng)卡混雜模式時(shí)需要管理員權(quán)限。在 Windows 操作系統(tǒng)和 Linux 操作系統(tǒng)中都有使用混雜模式的抓包工具,比如著名的開(kāi)源軟件 Wireshark。
通過(guò)命令給 Linux 網(wǎng)卡設(shè)置混雜模式(需要管理員權(quán)限)
設(shè)置混雜模式:ifconfig eth0 promisc
取消混雜模式:ifconfig eth0 -promisc
通過(guò)代碼給 Linux 網(wǎng)卡設(shè)置混雜模式
代碼如下:
- struct ifreq ethreq; //網(wǎng)絡(luò)接口地址
- strncpy(ethreq.ifr_name, "eth0", IFNAMSIZ); //指定網(wǎng)卡名稱(chēng)
- if(-1 == ioctl(sock_raw_fd, SIOCGIFINDEX, ðreq)) //獲取網(wǎng)絡(luò)接口
- {
- perror("ioctl");
- close(sock_raw_fd);
- exit(-1);
- }
- ethreq.ifr_flags |= IFF_PROMISC;
- if(-1 == ioctl(sock_raw_fd, SIOCSIFINDEX, ðreq)) //網(wǎng)卡設(shè)置混雜模式
- {
- perror("ioctl");
- close(sock_raw_fd);
- exit(-1);
- }
發(fā)送自定義的數(shù)據(jù)包:
ssize_t sendto( int sockfd,
const void *buf,
size_t nbytes,int flags,
const struct sockaddr *to,
socklen_t addrlen );
參數(shù):
sockfd: 原始套接字
buf: 發(fā)送數(shù)據(jù)緩沖區(qū)
nbytes: 發(fā)送數(shù)據(jù)緩沖區(qū)的大小
flags: 一般為 0
to: 本機(jī)網(wǎng)絡(luò)接口,指發(fā)送的數(shù)據(jù)應(yīng)該從本機(jī)的哪個(gè)網(wǎng)卡出去,而不是以前的目的地址
addrlen:to 所指向內(nèi)容的長(zhǎng)度
返回值:
成功:發(fā)送數(shù)據(jù)的字符數(shù)
失敗: -1
本機(jī)網(wǎng)絡(luò)接口的定義
發(fā)送完整代碼如下:
- struct sockaddr_ll sll; //原始套接字地址結(jié)構(gòu)
- struct ifreq ethreq; //網(wǎng)絡(luò)接口地址
- strncpy(ethreq.ifr_name, "eth0", IFNAMSIZ); //指定網(wǎng)卡名稱(chēng)
- if(-1 == ioctl(sock_raw_fd, SIOCGIFINDEX, ðreq)) //獲取網(wǎng)絡(luò)接口
- {
- perror("ioctl");
- close(sock_raw_fd);
- exit(-1);
- }
- /*將網(wǎng)絡(luò)接口賦值給原始套接字地址結(jié)構(gòu)*/
- bzero(&sll, sizeof(sll));
- sll.sll_ifindex = ethreq.ifr_ifindex;
- // 發(fā)送數(shù)據(jù)
- // send_msg, msg_len 這里還沒(méi)有定義,模擬一下
- int len = sendto(sock_raw_fd, send_msg, msg_len, 0 , (struct sockaddr *)&sll, sizeof(sll));
- if(len == -1)
- {
- perror("sendto");
- }
這里頭文件情況如下:
- #include <net/if.h>// struct ifreq
- #include <sys/ioctl.h> // ioctl、SIOCGIFADDR
- #include <sys/socket.h> // socket
- #include <netinet/ether.h> // ETH_P_ALL
- #include <netpacket/packet.h> // struct sockaddr_ll
#p#
三、原始套接字實(shí)例:MAC頭部報(bào)文分析
由上得知,我們可以通過(guò)原始套接字以及 recvfrom( ) 可以獲取鏈路層的數(shù)據(jù)包,那我們接收的鏈路層數(shù)據(jù)包到底長(zhǎng)什么樣的呢?
鏈路層封包格式
MAC 頭部(有線局域網(wǎng))
注意:CRC、PAD 在組包時(shí)可以忽略
鏈路層數(shù)據(jù)包的其中一種情況:
- unsigned char msg[1024] = {
- //--------------組MAC--------14------
- 0xb8, 0x88, 0xe3, 0xe1, 0x10, 0xe6, // dst_mac: b8:88:e3:e1:10:e6
- 0xc8, 0x9c, 0xdc, 0xb7, 0x0f, 0x19, // src_mac: c8:9c:dc:b7:0f:19
- 0x08, 0x00, // 類(lèi)型:0x0800 IP協(xié)議
- // …… ……
- // …… ……
- };
接收的鏈路層數(shù)據(jù)包,并對(duì)其進(jìn)行簡(jiǎn)單分析:
- #include <stdio.h>
- #include <string.h>
- #include <stdlib.h>
- #include <sys/socket.h>
- #include <netinet/in.h>
- #include <arpa/inet.h>
- #include <netinet/ether.h>
- int main(int argc,charchar *argv[])
- {
- int i = 0;
- unsigned char buf[1024] = "";
- int sock_raw_fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
- while(1)
- {
- unsigned char src_mac[18] = "";
- unsigned char dst_mac[18] = "";
- //獲取鏈路層的數(shù)據(jù)幀
- recvfrom(sock_raw_fd, buf, sizeof(buf),0,NULL,NULL);
- //從buf里提取目的mac、源mac
- sprintf(dst_mac,"%02x:%02x:%02x:%02x:%02x:%02x", buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]);
- sprintf(src_mac,"%02x:%02x:%02x:%02x:%02x:%02x", buf[6], buf[7], buf[8], buf[9], buf[10], buf[11]);
- //判斷是否為IP數(shù)據(jù)包
- if(buf[12]==0x08 && buf[13]==0x00)
- {
- printf("______________IP數(shù)據(jù)報(bào)_______________\n");
- printf("MAC:%s >> %s\n",src_mac,dst_mac);
- }//判斷是否為ARP數(shù)據(jù)包
- else if(buf[12]==0x08 && buf[13]==0x06)
- {
- printf("______________ARP數(shù)據(jù)報(bào)_______________\n");
- printf("MAC:%s >> %s\n",src_mac,dst_mac);
- }//判斷是否為RARP數(shù)據(jù)包
- else if(buf[12]==0x80 && buf[13]==0x35)
- {
- printf("______________RARP數(shù)據(jù)報(bào)_______________\n");
- printf("MAC:%s>>%s\n",src_mac,dst_mac);
- }
- }
- return 0;
- }
記得以管理者權(quán)限運(yùn)行程序: