Linux網絡編程技術之原始套接字編程詳解

原始套接字編程和以前的 UDP 編程差很少,無非就是建立一個套接字後,經過這個套接字接收數據或者發送數據。區別在於,原始套接字能夠自行組裝數據包(假裝本地 IP,本地 MAC),能夠接收本機網卡上全部的數據幀(數據包)。另外,必須在管理員權限下才能使用原始套接字。golang

原始套接字的建立編程

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 ):出錯

須要C/C++ Linux服務器架構師學習資料加羣812855908(資料包括C/C++,Linux,golang技術,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協程,DPDK,ffmpeg等)架構

Linux網絡編程技術之原始套接字編程詳解

實例以下: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 );

參數:

  • 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 = %dn", len);
 
    return 0;
}

混雜模式

默認的狀況下,咱們接收數據,目的地址是本地地址,纔會接收。有時候咱們想接收全部通過網卡的全部數據流,而不論其目的地址是不是它,這時候咱們須要設置網卡爲混雜模式。

網卡的混雜模式通常在網絡管理員分析網絡數據做爲網絡故障診斷手段時用到,同時這個模式也被網絡黑客利用來做爲網絡數據竊聽的入口。在 Linux 操做系統中設置網卡混雜模式時須要管理員權限。在 Windows 操做系統和 Linux 操做系統中都有使用混雜模式的抓包工具,好比著名的開源軟件 Wireshark。

經過命令給 Linux 網卡設置混雜模式(須要管理員權限)

設置混雜模式:ifconfig eth0 promisc

Linux網絡編程技術之原始套接字編程詳解

取消混雜模式:ifconfig eth0 -promisc

Linux網絡編程技術之原始套接字編程詳解

經過代碼給 Linux 網卡設置混雜模式

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

本機網絡接口的定義

Linux網絡編程技術之原始套接字編程詳解

發送完整代碼以下:

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
相關文章
相關標籤/搜索