嘗試探索基於Linux C的網卡抓包過程

來源:互聯網
上載者:User

        其實想探究網卡抓包問題已經有好久了。前幾天找了時間算是基本上瞭解了那部分的一些基本東西,在這裡只是贅述罷了。

抓包首先便要知道經過網卡的資料其實都是通過底層的鏈路層(MAC),在Linux系統中我們擷取網卡的資料流量其實是直接從鏈路層收發資料幀。至於如何進行TCP/UDP串連本文就不再贅述(之前的一段關於web server的程式已經大概說明),直接從最關鍵的原始通訊端( raw socket)開始。

通常情況下程式設計人員接觸的網路知識限於如下兩類:

(1)流式通訊端(SOCK_STREAM),它是一種連線導向的通訊端,對應於TCP應用程式。

(2)資料通訊端(SOCK_DGRAM),它是一種不需連線的通訊端,對應於的UDP應用程式。

 除了以上兩種基本的通訊端外還有一類原始通訊端,它是一種對原始網路報文進行處理的通訊端。

流式通訊端(SOCK_STREAM)和資料通訊端(SOCK_DGRAM)涵蓋了一般應用程式層次的TCP/IP應用。

原始通訊端的建立使用與通用的通訊端建立的方法是一致的,只是在通訊端類型的選項上使用的是另一個SOCK_RAW。在使用socket函數進行函數建立完畢的時候,還要進行通訊端資料中格式類型的指定,設定從通訊端中可以接收到的網路資料格式

fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));

fd為返回的通訊端描述符SOCK_RAW即建立原始通訊端,這裡的htons(ETH_P_ALL),是socket()的第三個參數 protocol ,即協議類型。是一個定義在<netinet/in.h>中的常量,基本上我們常用的是,ETH_P_IP  , ETH_P_ARP,ETH_P_RARP或ETH_P_ALL, 當然鏈路層協議很多,肯定不止我們說的這幾個,但我們一般只關心這幾個就夠我們用了。這裡簡單 提一下網路資料收發的一點基礎。協議棧在組織資料收發流程時需要處理好兩個方面的問題:“從上倒下”,即資料發送的任務;“從下到上”,即資料接收的任
務。資料發送相對接收來說要容易些,因為對於資料接收而言,網卡驅動還要明確什麼樣的資料該接收、什麼樣的不該接收等問題。

他們都有特定的含義:

protocol

作用

ETH_P_IP

0X0800

只接收發往目的MAC是原生IP類型的資料幀

ETH_P_ARP

0X0806

只接收發往目的MAC是原生ARP類型的資料幀

ETH_P_RARP

0X8035

只接受發往目的MAC是原生RARP類型的資料幀

ETH_P_ALL

0X0003

接收發往目的MAC是原生所有類型(ip,arp,rarp)的資料幀,同時還可以接收從本機發出去的所有資料幀。在混雜模式開啟的情況下,還會接收到發往目的MAC為非本地硬體地址的資料幀。

根據自己的抓包的對象我們選取相應的參數值,就可以擷取不同的資料包。

這裡我選擇的是ETH_P_ALL,可以擷取所有經過原生資料包,但有一個前提便是需要設定網卡為混雜模式(Promiscuous Mode),這裡我們就必須去瞭解一個重要的資料類型struct ifreq ;關於他的定義直接百度如下:

struct ifreq  {# define IFHWADDRLEN 6# define IFNAMSIZ IF_NAMESIZE    union      {        char ifrn_name[IFNAMSIZ]; /* Interface name, e.g. "en0". */      } ifr_ifrn;    union      {        struct sockaddr ifru_addr;        struct sockaddr ifru_dstaddr;        struct sockaddr ifru_broadaddr;        struct sockaddr ifru_netmask;        struct sockaddr ifru_hwaddr;        short int ifru_flags;        int ifru_ivalue;        int ifru_mtu;        struct ifmap ifru_map;        char ifru_slave[IFNAMSIZ]; /* Just fits the size */        char ifru_newname[IFNAMSIZ];        __caddr_t ifru_data;      } ifr_ifru;  };# define ifr_name ifr_ifrn.ifrn_name /* interface name*/   在這裡就是網卡eth0或eth1# define ifr_hwaddr ifr_ifru.ifru_hwaddr /* MAC address */ mac地址# define ifr_addr ifr_ifru.ifru_addr /* address */   source地址# define ifr_dstaddr ifr_ifru.ifru_dstaddr /* other end of p-p lnk */   目的ip地址# define ifr_broadaddr ifr_ifru.ifru_broadaddr /* broadcast address */ 廣播位址# define ifr_netmask ifr_ifru.ifru_netmask /* interface net mask */ 子網路遮罩# define ifr_flags ifr_ifru.ifru_flags /* flags */   模式標誌 設定混雜模式# define ifr_metric ifr_ifru.ifru_ivalue /* metric */# define ifr_mtu ifr_ifru.ifru_mtu /* mtu */# define ifr_map ifr_ifru.ifru_map /* device map */# define ifr_slave ifr_ifru.ifru_slave /* slave device */# define ifr_data ifr_ifru.ifru_data /* for use by interface */# define ifr_ifindex ifr_ifru.ifru_ivalue /* interface index */# define ifr_bandwidth ifr_ifru.ifru_ivalue /* link bandwidth */# define ifr_qlen ifr_ifru.ifru_ivalue /* queue length */# define ifr_newname ifr_ifru.ifru_newname /* New name */# define _IOT_ifreq _IOT(_IOTS(char),IFNAMSIZ,_IOTS(char),16,0,0)# define _IOT_ifreq_short _IOT(_IOTS(char),IFNAMSIZ,_IOTS(short),1,0,0)# define _IOT_ifreq_int _IOT(_IOTS(char),IFNAMSIZ,_IOTS(int),1,0,0)

上面好多的定義,但用到的只是幾個,我做了相應的中文標註,設定混雜模式需要的就是將ifr_flags 設定為 IFF_PROMISC就可以了。

在這之後需要設定socket選項如下

ret= int setsockopt( intsocket, int level, int option_name, const void *option_value, size_toption_len);

第一個參數socket是通訊端描述符。第二個參數level是被設定的選項的層級,如果想要在通訊端層級上設定選項,就必須把level設定為 SOL_SOCKET。option_name指定準備設定的選項,option_name可以有哪些取值,這取決於level。

完成這步後就可以通過

{      i struct ifreq ifr;    strcpy(ifr.ifr_name, “eth0”);    ioctl(fd, SIOCGIFHWADDR, &ifr);}

擷取網卡的索引介面了,接著我們要做的就是將建立的原始通訊端綁定在指定的網卡上了

ret= bind(fd, (struct sockaddr *)& sock, sizeof(sock));這裡的sock便不再是之前TCP串連時的 struct sockaddr_in 了,而是struct  sockaddr_ll,定義如下:

struct sockaddr_ll{     unsigned short sll_family; /* 總是 AF_PACKET */     unsigned short sll_protocol; /* 物理層的協議 */     int sll_ifindex; /* 介面號 */     unsigned short sll_hatype; /* 前序類型 */     unsigned char sll_pkttype; /* 分組類型 */     unsigned char sll_halen; /* 地址長度 */     unsigned char sll_addr[8]; /* 物理層地址 */ };

對應設定如下

struct sockaddr_ll   sock;sock.sll_family = AF_PACKET;sock.sll_ifindex = stIf.ifr_ifindex;(struct   ifreq)sock.sll_protocol = htons(ETH_P_ALL);ret = bind(fd, (struct sockaddr *)&, sizeof(sock));

這樣我們就可以開始通過recvfrom()等函數擷取對應通訊端上的資料了。

說到這裡我們已經獲得了網卡上的一段資料包,可是資料包是什麼樣子的?這裡你就必須瞭解什麼是Ethnet,即以太資料幀

以太幀首部中2位元組的框架類型欄位指定了其上層所承載的具體協議,常見的有0x0800表示是IP報文、0x0806表示RARP協議、0x0806即為我們將要討論的ARP協議。硬體類型: 1表示乙太網路。

網卡從線路上收到訊號流,網卡的驅動程式會去檢查資料幀開始的前6個位元組,即目的主機的MAC地址,如果和自己的網卡地址一致它才會接收這個幀,不符合的一般都是直接無視。然後該資料幀會被網路驅動程式分解,IP報文將通過網路通訊協定棧,最後傳送到應用程式那裡。

從這裡我們可以看出以太資料幀頭包含了MAC地址與框架類型,這正是我們需要的,所以我們便要開始對以太幀頭進行解析,這就涉及另一個重要的資料類型struct ether_header;

其中重要的資料資訊有

u_charether_dhost[ETHER_ADDR_LEN];//dest 的MAC地址資訊
u_char ether_shost[ETHER_ADDR_LEN];// source的MAC地址資訊
u_short ether_type; // ip 協議類型 :ipv4……….等

static int ethdump_parseEthHead(const struct ether_header *pstEthHead){    unsigned short usEthPktType;    /* 協議類型、源MAC、目的MAC */    usEthPktType=ntohs(pstEthHead->ether_type);  printf("EthType:0x%04x(%s)\n",usEthPktType,ethdump_getProName(usEthPktType));    ethdump_showMac( pstEthHead->ether_shost);    ethdump_showMac(pstEthHead->ether_dhost);        return 0;    }ntohs(ether_type)返回一個以主機位元組順序表達的數static void ethdump_showMac(const char acHWAddr[]) {    for(i = 0; i < ETHER_ADDR_LEN - 1; i++)    {        printf("%02x:", *((unsigned char *)&(acHWAddr[i])));    }    printf("%02x] \n", *((unsigned char *)&(acHWAddr[i])));}

接著我們就需要對下一段內容,即IP資料包進行解析,這裡對應的資料結構為struct ip,對於他的定義有些複雜,我也不是很懂,但是我們只需要擷取

u_int8_tip_p; /* protocol */協議類型

structin_addr ip_src, ip_dst; /* source and dest address */源IP與目的IP地址

通過getprotobynumber( ); 返回對應於給定協議號的相關協議資訊,輸出對應的p_name。

以及通過inet_nota(ip_src)列印出對應的source IP 與 dest IP。就可以擷取目前我們需要的內容。

static int ethdump_parseIpHead(const struct ip *pstIpHead){    struct protoent *pstIpProto = NULL;    /* 協議類型、源IP地址、目的IP地址 */    pstIpProto = getprotobynumber(pstIpHead->ip_p);    if(NULL != pstIpProto)    {        printf("IP-Type: %d (%s) \n", pstIpHead->ip_p, pstIpProto->p_name);    }    else    {        printf("IP-Type: %d (%s)\n ", pstIpHead->ip_p, "None");    }    printf("SAddr=[%s] \n", inet_ntoa(pstIpHead->ip_src));    printf("DAddr=[%s] ", inet_ntoa(pstIpHead->ip_dst));    printf("\n========================================\n");    return 0;}

到這裡基本上我們就完成了對資料包的抓取,以及擷取 source mac ,dest mac ,source ip ,des ip ,以及對應協議類型的分析。當然還可以進行進一步的資料分析,以及IP段、協議棧等的分析。越往後走涉及的東西就越複雜。

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.