Linux原始套接字抓取底层报文

1.原始套接字使用场景

        我们平常所用到的网络编程都是在应用层收发数据,每个程序只能收到发给自己的数据,即每个程序只能收到来自该程序绑定的端口的数据。收到的数据往往只包括应用层数据,原有的头部信息在传递过程中被隐藏了。某些情况下我们需要执行更底层的操作,比如监听所有本机收发的数据、修改报头等,而像SOCK_STREAM、SOCK_DGRAMZ则通常用于应用层,并不能满足该需求。

         通过原始套接字,我们可以抓取所有发送到本机的IP包(包括IP头和TCP/UDP/ICMP包头),也可以抓取所有本机收到的帧(包括数据链路层协议头)。普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以。利用原始套接字,我们可以自己构造IP头。

2.原始套接字分类及套接字函数简介

         有两种原始套接字:
                  一种是处理IP层及其上的数据,通过指定socket第一个参数为AF_INET来创建这种套接字。
                  另一种是处理数据链路层及其上的数据,通过指定socket第一个参数为PF_PACKET来创建这种套接字。

        socket函数简介:

    int socket(int domain, int type, int proto)

#include <sys/types.h>

#include <sys/socket.h>

参数 选项 作用
domain AF_INET 获取用网络层开始的数据(完整的IP数据包)  
PF_PACKET 获取数据链路层开始的数据(链路层头+IP 数据包)  
... ... 其他domain选项暂未使用,不予说明  
type SOCK_STREAM 面向连接的流式套接字  
SOCK_DGRAM 面向无连接的数据包套接字  
SOCK_RAW 接收底层数据报文的原始套接字  
SOCK_PACKET 过时类型,新版本不建议使用  
... ... 其他type选项暂未使用,不予说明  
proto 0 一般默认填写0  
ETH_P_IP  ETH_P_IP 0x800 只接收发往本机mac的ip类型的数据帧  
ETH_P_ARP ETH_P_ARP 0x806 只接受发往本机mac的arp类型的数据帧  
ETH_P_RARP ETH_P_RARP 0x8035 只接受发往本机mac的rarp类型的数据帧  
ETH_P_ALL ETH_P_ALL 0x3 接收发往本机mac的所有类型ip arp rarp的数据帧, 接收从本机发出的所有类型的数据帧.(混杂模式打开的情况下,会接收到非发往本地mac的数据帧)  
       

3. 套接字用法及应用场景    

创建套接字及应用场景
创建方式 工作域 发送报文 接收报文
socket(AF_INET,SOCK_STREAM, 0) 网络层 接收发送都是相应端口的应用层数据 接收发往本地IP的报文
socket(AF_INET, SOCK_DGRAM, 0)
socket(AF_INET, SOCK_RAW, 0) 只能发送包含TCP报头或UDP报头或包含其他传输协议的报文,IP报头以及以太网帧头则由内核自动加封 用户获得完整的包含IP头的IP数据包
socket(PF_PACKET, SOCK_DGRAM, 0) 链路层 获得IPV4的数据链路层帧,但不包括以太网帧头( 6源mac + 6目的mac + 2) 接收发往本地MAC的报文。主要用于获取底层数据
socket(PF_PACKET, SOCK_RAW, 0) 获得IPV4的数据链路层帧,即数据包含以太网帧头(14+20+(8:udp 或 20:tcp))

4. 两种套接字对比

      在上面的表格中我们知道socket(AF_INET, SOCK_RAW,...)  和socket(PF_PACKET, SOCK_DGRAM,0)这两种创建的套接字均可以接收到完整的IP报文,但是他们接收到的有什么区别呢? 接下来简单的说一下:

      结起来就是:
               socket(AF_INET, SOCK_RAW, IPPROTO_TCP|IPPROTO_UDP|IPPROTO_ICMP)发送接收ip数据包
     能:该套接字可以接收协议类型为(tcp udp icmp等)发往本机的ip数据包
    不能:收到非发往本地ip的数据包(ip软过滤会丢弃这些不是发往本机ip的数据包)
    不能:收到从本机发送出去的数据包
    发送的话需要自己组织tcp udp icmp等头部.可以setsockopt来自己包装ip头部,这种套接字用来写个ping程序比较适合。

              socket(PF_PACKET, SOCK_RAW|SOCK_DGRAM, htons(ETH_P_IP|ETH_P_ARP|ETH_P_ALL))发送接收以太网数据帧。这种套接字比较强大,可以监听网卡上的所有数据帧

    能: 接收发往本地mac的数据帧
    能: 接收从本机发送出去的数据帧(第3个参数需要设置为ETH_P_ALL)
    能: 接收非发往本地mac的数据帧(网卡需要设置为promisc混杂模式)
协议类型一共有四个
ETH_P_IP 0x800 只接收发往本机mac的ip类型的数据帧
ETH_P_ARP 0x806 只接受发往本机mac的arp类型的数据帧
ETH_P_RARP 0x8035 只接受发往本机mac的rarp类型的数据帧
ETH_P_ALL 0x3 接收发往本机mac的所有类型ip arp rarp的数据帧, 接收从本机发出的所有类型的数据帧.(混杂模式打开的情况下,会接收到非发往本地mac的数据帧)

5. 应用例子:抓取所有IP包(代码系转载)

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <linux/if_packet.h>
#include <netinet/if_ether.h>
#include <netinet/in.h>

typedef struct _iphdr //定义IP首部 
{ 
    unsigned char h_verlen;       //4位首部长度+4位IP版本号 
    unsigned char tos;            //8位服务类型TOS 
    unsigned short total_len;     //16位总长度(字节) 
    unsigned short ident;         //16位标识 
    unsigned short frag_and_flags;//3位标志位 
    unsigned char ttl;            //8位生存时间 TTL 
    unsigned char proto;          //8位协议 (TCP, UDP 或其他) 
    unsigned short checksum;      //16位IP首部校验和 
    unsigned int sourceIP;        //32位源IP地址 
    unsigned int destIP;          //32位目的IP地址 
}IP_HEADER; 

typedef struct _udphdr //定义UDP首部
{
    unsigned short uh_sport;    //16位源端口
    unsigned short uh_dport;    //16位目的端口
    unsigned short uh_len;        //16位UDP包长度
    unsigned short uh_sum;        //16位校验和
}UDP_HEADER;

typedef struct _tcphdr //定义TCP首部 
{ 
    unsigned short th_sport;     //16位源端口 
    unsigned short th_dport;     //16位目的端口 
    unsigned int th_seq;         //32位序列号 
    unsigned int th_ack;         //32位确认号 
    unsigned char th_lenres;     //4位首部长度/6位保留字 
    unsigned char th_flag;       //6位标志位
    unsigned short th_win;       //16位窗口大小
    unsigned short th_sum;       //16位校验和
    unsigned short th_urp;       //16位紧急数据偏移量
}TCP_HEADER; 

typedef struct _icmphdr {  
    unsigned char  icmp_type;  
    unsigned char icmp_code;      /* type sub code */  
    unsigned short icmp_cksum;  
    unsigned short icmp_id;  
    unsigned short icmp_seq;  
    /* This is not the std header, but we reserve space for time */  
    unsigned short icmp_timestamp;  
}ICMP_HEADER;

void analyseIP(IP_HEADER *ip);
void analyseTCP(TCP_HEADER *tcp);
void analyseUDP(UDP_HEADER *udp);
void analyseICMP(ICMP_HEADER *icmp);


int main(void)
{
    int sockfd;
     IP_HEADER *ip;
    char buf[10240];
    ssize_t n;
    /* capture ip datagram without ethernet header */
    if ((sockfd = socket(PF_PACKET,  SOCK_DGRAM, htons(ETH_P_IP)))== -1)
    {    
        printf("socket error!\n");
        return 1;
    }
    while (1)
    {
        n = recv(sockfd, buf, sizeof(buf), 0);
        if (n == -1)
        {
            printf("recv error!\n");
            break;
        }
        else if (n==0)
            continue;
        //接收数据不包括数据链路帧头
        ip = ( IP_HEADER *)(buf);
        analyseIP(ip);
        size_t iplen =  (ip->h_verlen&0x0f)*4;
        TCP_HEADER *tcp = (TCP_HEADER *)(buf +iplen);
        if (ip->proto == IPPROTO_TCP)
        {
            TCP_HEADER *tcp = (TCP_HEADER *)(buf +iplen);
            analyseTCP(tcp);
        }
        else if (ip->proto == IPPROTO_UDP)
        {
            UDP_HEADER *udp = (UDP_HEADER *)(buf + iplen);
            analyseUDP(udp);
        }
        else if (ip->proto == IPPROTO_ICMP)
        {
            ICMP_HEADER *icmp = (ICMP_HEADER *)(buf + iplen);
            analyseICMP(icmp);
        }
        else if (ip->proto == IPPROTO_IGMP)
        {
            printf("IGMP----\n");
        }
        else
        {
            printf("other protocol!\n");
        }        
        printf("\n\n");
    }
    close(sockfd);
    return 0;
}

void analyseIP(IP_HEADER *ip)
{
    unsigned char* p = (unsigned char*)&ip->sourceIP;
    printf("Source IP\t: %u.%u.%u.%u\n",p[0],p[1],p[2],p[3]);
    p = (unsigned char*)&ip->destIP;
    printf("Destination IP\t: %u.%u.%u.%u\n",p[0],p[1],p[2],p[3]);

}

void analyseTCP(TCP_HEADER *tcp)
{
    printf("TCP -----\n");
    printf("Source port: %u\n", ntohs(tcp->th_sport));
    printf("Dest port: %u\n", ntohs(tcp->th_dport));
}

void analyseUDP(UDP_HEADER *udp)
{
    printf("UDP -----\n");
    printf("Source port: %u\n", ntohs(udp->uh_sport));
    printf("Dest port: %u\n", ntohs(udp->uh_dport));
}

void analyseICMP(ICMP_HEADER *icmp)
{
    printf("ICMP -----\n");
    printf("type: %u\n", icmp->icmp_type);
    printf("sub code: %u\n", icmp->icmp_code);
}

 

上一篇:C++第三章学习笔记——处理数据


下一篇:[ 问题记录 ] 针对基础的查漏补缺