libpcap的功能和使用方法

大耗子 2020年02月21日 356次浏览

libpcapd的主要功能:

  1. 数据包捕获:捕获流经网卡的原始数据包
  2. 自定义数据包发送:构造任何格式的原始数据包
  3. 流量采集与统计:采集网络中的流量信息
  4. 规则过滤:提供自带规则过滤功能,按需要选择过滤规则

Libpcap的安装:

切换到下载目录,解压压缩文件,配置,编译,安装

// 注意!!!!!要用管理员权限

tar zxvf 压缩包.tar.gz
sudo ./configure
sudo make
sudo make install

配置中如果出现错误,请查看你是否安装了所有的依赖包bison, m4, GNU, flex以及libpcap-dev(安装方法 sudo apt-get xxx)

或者直接用Linux Ubuntu库中自带的版本

sudo apt-get install iperf

Libpcap的抓包流程:

1. 查找网络设备:目的是发现可用的网卡,实现的函数为pcap_lookupdev(),如果当前有多个网卡,函数就会返回一个网络设备名的指针列表。
2. 打开网络设备:利用上一步中的返回值,可以决定使用哪个网卡,通过函数pcap_open_live()打开网卡,返回用于捕捉网络数据包的秒数字。
3. 获得网络参数:这里是利用函数pcap_lookupnet(),可以获得指定网络设备的IP地址和子网掩码。
4. 编译过滤策略:Lipcap的主要功能就是提供数据包的过滤,函数pcap_compile()来实现。
5. 设置过滤器:在上一步的基础上利用pcap_setfilter()函数来设置。
6. 利用回调函数,捕获数据包:函数pcap_loop()和pcap_dispatch()来抓去数据包,也可以利用函数pcap_next()和pcap_next_ex()来完成同样的工作。
7. 关闭网络设备:pcap_close()函数关系设备,释放资源。

Libpcap的函数调用:

1.获取网络接口

char * pcap_lookupdev(char * errbuf)
// 返回一个网卡设备名字

2.获取指定网卡的设备信息

int pcap_lookupnet(const char * device, bpf_u_int32 * netp, bpf_u_int32 * maskp, char * errbuf)
//可以获取指定设备的ip地址,子网掩码等信息

3.打开网络接口

pcap_t * pcap_open_live(const char * device, int snaplen, int promisc, int to_ms, char * errbuf)

4.构造规则

//构造完过滤表达式后,就可以使用pcap_compile()函数来编译。
int pcap_compile(pcap_t * p, struct bpf_program * fp, char * str, int optimize, bpf_u_int32 netmask)
//fp:这是一个传出参数,存放编译后的bpf
//str:过滤表达式
//optimize:是否需要优化过滤表达式
//metmask:简单设置为0即可

// 最后通过函数pcap_setfilter()来设置这个规则
int pcap_setfilter(pcap_t * p, struct bpf_program * fp)
//参数fp就是pcap_compile()的第二个参数,存放编译后的bpf

// 语法的简单使用
src host 127.0.0.1//选择只接受某个IP地址的数据包
dst port 8000//选择只接受TCP/UDP的目的端口是80的数据包
not tcp
//不接受TCP数据包
tcp[13]==0x02 and (dst port ** or dst port **)
//只接受SYN标志位置(TCP首部开始的第13个字节)且目标端口号是22或23的数据包
icmp[icmptype]==icmp-echoreply or icmp[icmptype]==icmp-echo
//只接受icmp的ping请求和ping响应的数据包
ehter dst 00:00:00:00:00:00//只接受以太网MAC地址为00:00:00:00:00:00的数据包
ip[8]==5//只接受ip的ttl=5的数据包(ip首位第八的字节为ttl)

5.查看链接层的类型

// 返回链接层的类型
int pcap_datalink(pcap_t * p)
使用方法:
    int linkhdrlen;
    int linktype;
    // Determine the datalink layer type.
    if ((linktype = pcap_datalink(pd)) < 0)
    {
        printf("pcap_datalink(): %s\n", pcap_geterr(pd));
        return;
    }
    // Set the datalink layer header size.
    switch (linktype)
    {
    case DLT_NULL:
        linkhdrlen = 4;
        break;
    case DLT_EN10MB:
        linkhdrlen = 14;
        break;
    case DLT_SLIP:
    case DLT_PPP:
        linkhdrlen = 24;
        break;
    default:
        printf("Unsupported datalink (%d)\n", linktype);
        return;
    }

6.获取包

// 获取单个包
u_char * pcap_next(pcap_t * p, struct pcap_pkthdr * h)
//如果返回值为NULL,表示没有抓到包
//第一个参数是第2步返回的pcap_t类型的指针
//第二个参数是保存收到的第一个数据包的pcap_pkthdr类型的指针

//循环获取包
int pcap_loop(pcap_t * p, int cnt, pcap_handler callback, u_char * user)
//第一个参数是pcap_t类型的指针
//第二个参数是需要抓的数据包的个数,一旦抓到了cnt个数据包,pcap_loop立即返回。
        //负数的cnt表示pcap_loop永远循环抓包,直到出现错误。
//第三个参数是一个回调函数指针

// 超时返回循环获取包
int pcap_dispatch(pcap_t * p, int cnt, pcap_handler callback, u_char * user)
//这个函数和pcap_loop()非常类似,只是在超过to_ms毫秒后就会返回(to_ms是pcap_open_live()的第4个参数)

7.回调函数

void callback(u_char * userarg, const struct pcap_pkthdr * pkthdr, const u_char * packet)
//第一个参数是pcap_loop的最后一个参数,当收到足够数量的包后pcap_loop会调用callback回调函数,同时将pcap_loop()的user参数传递给它
//第二个参数是收到的数据包的pcap_pkthdr类型的指针

pcap_pkthdr类型的定义如下:
struct pcap_pkthdr
{
  struct timeval ts;    /* time stamp */
  bpf_u_int32 caplen;   /* length of portion present */
  bpf_u_int32 len;      /* length this packet (off wire) */
};

void parse_packet(u_char *user, struct pcap_pkthdr *packethdr,
                  u_char *packetptr)
{
    struct ip* iphdr;
    struct icmphdr* icmphdr;
    struct tcphdr* tcphdr;
    struct udphdr* udphdr;
    char iphdrInfo[256], srcip[256], dstip[256];
    unsigned short id, seq;
    // Skip the datalink layer header and get the IP header fields.
    packetptr += linkhdrlen;
    iphdr = (struct ip*)packetptr;
    strcpy(srcip, inet_ntoa(iphdr->ip_src));
    strcpy(dstip, inet_ntoa(iphdr->ip_dst));
    
    sprintf(iphdrInfo, "ID:%d TOS:0x%x, TTL:%d IpLen:%d DgLen:%d",
            ntohs(iphdr->ip_id), iphdr->ip_tos, iphdr->ip_ttl,
            4*iphdr->ip_hl, ntohs(iphdr->ip_len));
    // Advance to the transport layer header then parse and display
    // the fields based on the type of hearder: tcp, udp or icmp.
    packetptr += 4*iphdr->ip_hl;
    switch (iphdr->ip_p)
    {
    case IPPROTO_TCP:
        tcphdr = (struct tcphdr*)packetptr;
        printf("TCP  %s:%d -> %s:%d\n", srcip, ntohs(tcphdr->source),
               dstip, ntohs(tcphdr->dest));
        printf("%s\n", iphdrInfo);
        printf("%c%c%c%c%c%c Seq: 0x%x Ack: 0x%x Win: 0x%x TcpLen: %d\n",
               (tcphdr->urg ? 'U' : '*'),
               (tcphdr->ack ? 'A' : '*'),
               (tcphdr->psh ? 'P' : '*'),
               (tcphdr->rst ? 'R' : '*'),
               (tcphdr->syn ? 'S' : '*'),
               (tcphdr->fin ? 'F' : '*'),
               ntohl(tcphdr->seq), ntohl(tcphdr->ack_seq),
               ntohs(tcphdr->window), 4*tcphdr->doff);
        break;
    case IPPROTO_UDP:
        udphdr = (struct udphdr*)packetptr;
        printf("UDP  %s:%d -> %s:%d\n", srcip, ntohs(udphdr->source),
               dstip, ntohs(udphdr->dest));
        printf("%s\n", iphdrInfo);
        break;
    case IPPROTO_ICMP:
        icmphdr = (struct icmphdr*)packetptr;
        printf("ICMP %s -> %s\n", srcip, dstip);
        printf("%s\n", iphdrInfo);
        memcpy(&id, (u_char*)icmphdr+4, 2);
        memcpy(&seq, (u_char*)icmphdr+6, 2);
        printf("Type:%d Code:%d ID:%d Seq:%d\n", icmphdr->type, icmphdr->code,
               ntohs(id), ntohs(seq));
        break;
    }
    printf(
        "+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\n");
}

8.释放网络接口

void pcap_close(pcap_t * p)
//该函数用于关闭pcap_open_live()获取的pcap_t的网络接口对象并释放相关资源。