原始套接字編程和之前的 UDP 編程差不多,無非就是創建一個套接字後,通過這個套接字接收數據或者發送數據。區別在於,原始套接字可以自行組裝數據包(偽裝本地 IP,本地 MAC),可以接收本機網卡上所有的數據幀(數據包)。另外,必須在管理員權限下才能使用原始套接字。
原始套接字的創建:int socket ( int family, int type, int protocol );
參數:
family:協議族 這裡寫 PF_PACKET
type: 套接字類,這裡寫 SOCK_RAW
protocol:協議類別,指定可以接收或發送的數據包類型,不能寫 “0”,取值如下,注意,傳參時需要用 htons() 進行字節序轉換。
ETH_P_IP:IPV4數據包
ETH_P_ARP:ARP數據包
ETH_P_ALL:任何協議類型的數據包
返回值:
成功( >0 ):套接字,這裡為鏈路層的套接字
失敗( <0 ):出錯
實例如下:
1 // 所需頭文件 2 #include <sys/socket.h> 3 #include <netinet/ether.h> 4 #include <stdio.h> // perror 5 6 int main(int argc,char *argv[]) 7 { 8 int sock_raw_fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL) ); 9 10 if(sock_raw_fd < 0){ 11 perror("socket"); 12 return -1; 13 } 14 15 return 0; 16 }
獲取鏈路層的數據包:
ssize_t recvfrom( int sockfd,
void *buf,
size_t nbytes,
int flags,
struct sockaddr *from,
socklen_t *addrlen );
參數:
sockfd: 原始套接字
buf: 接收數據緩沖區
nbytes: 接收數據緩沖區的大小
flags: 套接字標志(常為0)
from: 這裡沒有用,寫 NULL
addrlen:這裡沒有用,寫 NULL
返回值:
成功:接收到的字符數
失敗:-1
實例如下:
1 #include <stdio.h> 2 #include <netinet/in.h> 3 #include <sys/socket.h> 4 #include <netinet/ether.h> 5 6 int main(int argc,char *argv[]) 7 { 8 unsigned char buf[1024] = {0}; 9 int sock_raw_fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); 10 11 //獲取鏈路層的數據包 12 int len = recvfrom(sock_raw_fd, buf, sizeof(buf), 0, NULL, NULL); 13 printf("len = %d\n", len); 14 15 return 0; 16 }
混雜模式
默認的情況下,我們接收數據,目的地址是本地地址,才會接收。有時候我們想接收所有經過網卡的所有數據流,而不論其目的地址是否是它,這時候我們需要設置網卡為混雜模式。
網卡的混雜模式一般在網絡管理員分析網絡數據作為網絡故障診斷手段時用到,同時這個模式也被網絡黑客利用來作為網絡數據竊聽的入口。在 Linux 操作系統中設置網卡混雜模式時需要管理員權限。在 Windows 操作系統和 Linux 操作系統中都有使用混雜模式的抓包工具,比如著名的開源軟件 Wireshark。
通過命令給 Linux 網卡設置混雜模式(需要管理員權限)
設置混雜模式:ifconfig eth0 promisc
取消混雜模式:ifconfig eth0 -promisc
通過代碼給 Linux 網卡設置混雜模式
代碼如下:
1 struct ifreq ethreq; //網絡接口地址 2 3 strncpy(ethreq.ifr_name, "eth0", IFNAMSIZ); //指定網卡名稱 4 if(-1 == ioctl(sock_raw_fd, SIOCGIFINDEX, ðreq)) //獲取網絡接口 5 { 6 perror("ioctl"); 7 close(sock_raw_fd); 8 exit(-1); 9 } 10 11 ethreq.ifr_flags |= IFF_PROMISC; 12 if(-1 == ioctl(sock_raw_fd, SIOCSIFINDEX, ðreq)) //網卡設置混雜模式 13 { 14 perror("ioctl"); 15 close(sock_raw_fd); 16 exit(-1); 17 }
發送自定義的數據包:
ssize_t sendto( int sockfd,
const void *buf,
size_t nbytes,int flags,
const struct sockaddr *to,
socklen_t addrlen );
參數:
sockfd: 原始套接字
buf: 發送數據緩沖區
nbytes: 發送數據緩沖區的大小
flags: 一般為 0
to: 本機網絡接口,指發送的數據應該從本機的哪個網卡出去,而不是以前的目的地址
addrlen:to 所指向內容的長度
返回值:
成功:發送數據的字符數
失敗: -1
本機網絡接口的定義
發送完整代碼如下:
1 struct sockaddr_ll sll; //原始套接字地址結構 2 struct ifreq ethreq; //網絡接口地址 3 4 strncpy(ethreq.ifr_name, "eth0", IFNAMSIZ); //指定網卡名稱 5 if(-1 == ioctl(sock_raw_fd, SIOCGIFINDEX, ðreq)) //獲取網絡接口 6 { 7 perror("ioctl"); 8 close(sock_raw_fd); 9 exit(-1); 10 } 11 12 /*將網絡接口賦值給原始套接字地址結構*/ 13 bzero(&sll, sizeof(sll)); 14 sll.sll_ifindex = ethreq.ifr_ifindex; 15 16 // 發送數據 17 // send_msg, msg_len 這裡還沒有定義,模擬一下 18 int len = sendto(sock_raw_fd, send_msg, msg_len, 0 , (struct sockaddr *)&sll, sizeof(sll)); 19 if(len == -1) 20 { 21 perror("sendto"); 22 }
這裡頭文件情況如下:
1 #include <net/if.h>// struct ifreq 2 #include <sys/ioctl.h> // ioctl、SIOCGIFADDR 3 #include <sys/socket.h> // socket 4 #include <netinet/ether.h> // ETH_P_ALL 5 #include <netpacket/packet.h> // struct sockaddr_ll