原始套接字編程和以前的 UDP 編程差很少,無非就是建立一個套接字後,經過這個套接字接收數據或者發送數據。區別在於,原始套接字能夠自行組裝數據包(假裝本地 IP,本地 MAC),能夠接收本機網卡上全部的數據幀(數據包)。另外,必須在管理員權限下才能使用原始套接字。golang
原始套接字的建立編程
int socket ( int family, int type, int protocol );
參數:服務器
返回值:網絡
成功( >0 ):套接字,這裏爲鏈路層的套接字 失敗( <0 ):出錯
須要C/C++ Linux服務器架構師學習資料加羣812855908(資料包括C/C++,Linux,golang技術,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協程,DPDK,ffmpeg等)架構
實例以下:socket
// 所需頭文件 #include <sys/socket.h> #include <netinet/ether.h> #include <stdio.h> // perror int main(int argc,char *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; }
獲取鏈路層的數據包工具
ssize_t recvfrom( int sockfd,學習
void *buf,spa
size_t nbytes,操作系統
int flags,
struct sockaddr *from,
socklen_t *addrlen );
參數:
返回值:
成功:接收到的字符數 失敗:-1
實例以下:
#include <stdio.h> #include <netinet/in.h> #include <sys/socket.h> #include <netinet/ether.h> int main(int argc,char *argv[]) { unsigned char buf[1024] = {0}; int sock_raw_fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); //獲取鏈路層的數據包 int len = recvfrom(sock_raw_fd, buf, sizeof(buf), 0, NULL, NULL); printf("len = %dn", len); return 0; }
混雜模式
默認的狀況下,咱們接收數據,目的地址是本地地址,纔會接收。有時候咱們想接收全部通過網卡的全部數據流,而不論其目的地址是不是它,這時候咱們須要設置網卡爲混雜模式。
網卡的混雜模式通常在網絡管理員分析網絡數據做爲網絡故障診斷手段時用到,同時這個模式也被網絡黑客利用來做爲網絡數據竊聽的入口。在 Linux 操做系統中設置網卡混雜模式時須要管理員權限。在 Windows 操做系統和 Linux 操做系統中都有使用混雜模式的抓包工具,好比著名的開源軟件 Wireshark。
經過命令給 Linux 網卡設置混雜模式(須要管理員權限)
設置混雜模式:ifconfig eth0 promisc
取消混雜模式:ifconfig eth0 -promisc
經過代碼給 Linux 網卡設置混雜模式
代碼以下:
struct ifreq req; //網絡接口地址 strncpy(req.ifr_name, "eth0", IFNAMSIZ); //指定網卡名稱 if(-1 == ioctl(sock_raw_fd, SIOCGIFINDEX, &req)) //獲取網絡接口 { perror("ioctl"); close(sock_raw_fd); exit(-1); } req.ifr_flags |= IFF_PROMISC; if(-1 == ioctl(sock_raw_fd, SIOCSIFINDEX, &req)) //網卡設置混雜模式 { perror("ioctl"); close(sock_raw_fd); exit(-1); }
發送自定義的數據包:
ssize_t sendto( int sockfd,
const void *buf,
size_t nbytes,int flags,
const struct sockaddr *to,
socklen_t addrlen );
參數:
返回值:
成功:發送數據的字符數 失敗: -1
本機網絡接口的定義
發送完整代碼以下:
struct sockaddr_ll sll; //原始套接字地址結構 struct ifreq req; //網絡接口地址 strncpy(req.ifr_name, "eth0", IFNAMSIZ); //指定網卡名稱 if(-1 == ioctl(sock_raw_fd, SIOCGIFINDEX, &req)) //獲取網絡接口 { perror("ioctl"); close(sock_raw_fd); exit(-1); } /*將網絡接口賦值給原始套接字地址結構*/ bzero(&sll, sizeof(sll)); sll.sll_ifindex = req.ifr_ifindex; // 發送數據 // send_msg, msg_len 這裏尚未定義,模擬一下 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