Linux網路程式設計——原始套接字程式設計
原始套接字的建立
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 ):出錯
例項如下:
- // 所需標頭檔案
- #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,
size_t nbytes,
int flags,
struct sockaddr *from,
socklen_t *addrlen );
引數:
sockfd:原始套接字
buf:接收資料緩衝區
nbytes:接收資料緩衝區的大小
flags:套接字標誌(常為0)
from:這裡沒有用,寫 NULL
addrlen:這裡沒有用,寫 NULL
返回值:
成功:接收到的字元數
失敗:-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 = %d\n", 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 );
引數:
sockfd:原始套接字
buf:傳送資料緩衝區
nbytes:傳送資料緩衝區的大小
flags:一般為 0
to:本機網路介面,指傳送的資料應該從本機的哪個網絡卡出去,而不是以前的目的地址
addrlen:to 所指向內容的長度
返回值:
成功:傳送資料的字元數
失敗: -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