C语言实现IPv6抓包程序

本文约 2100 字,阅读需 5 分钟。

C语言实现IPv6抓包程序

本文记录了如何借助libpcap实现一个简单的抓包程序。

准备

对于实现一个抓包程序,最重要的是熟悉每种协议。熟悉协议包括两部分:每个协议具体字段的意义和每个协议在C语言中对应的具体结构。 关于每个协议的理论知识,可以参考《计算机网络》(第五版,谢希仁编著)就,需要注意的是关于IPv6的部分书中只是简单提及,比如具体数字对应的协议可能还需用自己查找,最有效的方式是Wiki和官方文档,这些资料可能比较晦涩而且又是英文,但绝对正确、权威。 关于代码实现,可以查看/usr/include/netinet目录下的头文件,比如说icmp6.h下就记录了ICMPv6协议的格式:

struct icmp6_hdr
  {
    uint8_t     icmp6_type;   /* type field */
    uint8_t     icmp6_code;   /* code field */
    uint16_t    icmp6_cksum;  /* checksum field */
    union
      {
    uint32_t  icmp6_un_data32[1]; /* type-specific field */
    uint16_t  icmp6_un_data16[2]; /* type-specific field */
    uint8_t   icmp6_un_data8[4];  /* type-specific field */
      } icmp6_dataun;
  };

所以这一部分只要弄懂了协议其实十分简单。

此外,关于IPv6抓包,网上能找到的直接参考还是很少的,需要自己多下功夫!

实现

代码十分简单,就是在解析一个比特流:

/*
 ip_sniffer.c: capture packet(IPv4/IPv6)
 Copyright © 2017 Yu Zhao <dutzhaoyu@gmail.com>

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <pcap.h>
#include <netinet/in.h>
#include <net/ethernet.h>
#include <sys/socket.h>
#include <net/ethernet.h>
#include <netinet/ip_icmp.h>
#include <netinet/udp.h>
#include <netinet/tcp.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <netinet/icmp6.h>
#include <linux/ipv6.h>
#include <netinet/ip6.h>
#define DEFINE_ICMP 1
#define DEFINE_ICMPv6 58
#define DEFINE_TCP 6
#define DEFINE_UDP 17
#define DEFINE_FRAG 44

void ipv6_to_str(char *, const struct in6_addr *);

void print_ethernet_packet(const u_char *, int);
void print_ethernet_header(const u_char *, int);

void print_ipv4_packet(const u_char *, int);
void print_ipv6_packet(const u_char *, int);
void print_ipv4_header(const u_char *, int);
void print_ipv6_header(const u_char *, int);

void print_tcp_packet(const u_char *, int, int);
void print_udp_packet(const u_char *, int, int);
void print_tcp_header(const u_char *, int, int);
void print_udp_header(const u_char *, int, int);


void print_icmpv4_packet(const u_char *, int, int);
void print_icmpv4_header(const u_char *, int, int);
void print_icmpv6_packet(const u_char *, int, int);
void print_icmpv6_header(const u_char *, int, int);
void print_fragment(const u_char *, int, int);
void print_complete_packet(const u_char *, int);


FILE *fp;
enum { IPv4, IPv6 }IP_TYPE;
int mac_size, ip_size, trans_size;//icmp not belong to trans in theory.
int ipv6_cnt, ipv4_cnt, loss_cnt;

void my_packet_handler(u_char *args, const struct pcap_pkthdr *header, const u_char *packet) {
    struct ether_header *eth_header;
    eth_header = (struct ether_header *) packet;

    /* if packet is not complete,return */
    if (header->caplen < header->len) {
        loss_cnt++;
        return;
    }

    if (ntohs(eth_header->ether_type) == ETHERTYPE_IPV6) {
        fprintf(fp, "\n\n------------------------------IPv6------------------------------\n");
        IP_TYPE = IPv6;
        ipv6_cnt++;
        print_ethernet_packet(packet, header->len);
    } else if (ntohs(eth_header->ether_type) == ETHERTYPE_IP) {
        fprintf(fp, "\n\n------------------------------IPv4------------------------------\n");
        IP_TYPE = IPv4;
        ipv4_cnt++;
        print_ethernet_packet(packet, header->len);
    }
    printf("IPv4: %d,   IPv6: %d,   NOT COMPLETE: %d\r", ipv4_cnt, ipv6_cnt, loss_cnt);

    fprintf(fp, "Ethernet Header\n");
    print_complete_packet(packet, mac_size);

    fprintf(fp, "IP Header\n");
    print_complete_packet(packet+mac_size, ip_size);

    fprintf(fp, "TCP/UDP/ICMP Header\n");
    print_complete_packet(packet+mac_size+ip_size, trans_size);

    fprintf(fp, "DATA Packet\n");
    int header_sum = mac_size + ip_size + trans_size;
    print_complete_packet(packet + header_sum, header->len - header_sum);
}

int main(int argc, char const* argv[]) {
    pcap_t *handle;
    char error_buffer[PCAP_ERRBUF_SIZE];
    char *device;
    int snapshot_len = 1028;
    int promiscuous = 0;
    int timeout = 1000;

    mac_size = sizeof(struct ethhdr);
    ipv4_cnt = 0;
    ipv6_cnt = 0;
    loss_cnt = 0;

    device = pcap_lookupdev(error_buffer);
    if (device == NULL) {
        printf("Error finding device: %s\n", error_buffer);
        return 1;
    }
    printf("DEVICE:    %s\n", device);
    if((fp = fopen("sniffer_log.txt", "w")) == NULL) {
        fprintf(stderr, "Can not open sniffer_log.txt\n");
        exit(1);
    }
    handle = pcap_open_live(device, snapshot_len, promiscuous, timeout, error_buffer);
    pcap_loop(handle, 0, my_packet_handler, NULL);
    pcap_close(handle);
    if (fclose(fp) != 0) {
        fprintf(stderr, "Close sniffer_log.txt fail!\n");
        exit(1);
    }
    return 0;
}

void print_ethernet_packet(const u_char *packet, int size) {
    print_ethernet_header(packet, size);
    if (IPv4 == IP_TYPE) {
        print_ipv4_packet(packet, size);
    } else if (IPv6 == IP_TYPE) {
        print_ipv6_packet(packet, size);
    }
}

void print_ethernet_header(const u_char *packet, int size) {
    struct ethhdr *eth = (struct ethhdr *)packet;
    fprintf(fp , "Ethernet Header\n");
    fprintf(fp , "\t|-Destination Address  : %.2X-%.2X-%.2X-%.2X-%.2X-%.2X\n", eth->h_dest[0] , eth->h_dest[1] , eth->h_dest[2] , eth->h_dest[3] , eth->h_dest[4] , eth->h_dest[5] );
    fprintf(fp , "\t|-Source Address       : %.2X-%.2X-%.2X-%.2X-%.2X-%.2X\n", eth->h_source[0] , eth->h_source[1] , eth->h_source[2] , eth->h_source[3] , eth->h_source[4] , eth->h_source[5] );
    fprintf(fp , "\t|-Protocol             : %u\n",(unsigned short)eth->h_proto);
}

void print_ipv4_packet(const u_char *packet, int size) {
    print_ipv4_header(packet, size);
    struct iphdr *iph = (struct iphdr *)(packet  + sizeof(struct ethhdr) );
    int ip4len = iph->ihl*4;
    int offset = sizeof(struct ethhdr) + ip4len;
    switch (iph->protocol) {
       case DEFINE_ICMP:
            print_icmpv4_packet(packet, size, offset); break;
       case DEFINE_TCP:
            print_tcp_packet(packet, size, offset); break;
       case DEFINE_UDP:
            print_udp_packet(packet, size, offset); break;
    }
}

void print_ipv4_header(const u_char *packet, int size) {
    struct sockaddr_in source,dest;
    struct iphdr *iph = (struct iphdr *)(packet  + sizeof(struct ethhdr) );

    memset(&source, 0, sizeof(source));
    source.sin_addr.s_addr = iph->saddr;

    memset(&dest, 0, sizeof(dest));
    dest.sin_addr.s_addr = iph->daddr;

    fprintf(fp , "\tIP Header\n");
    fprintf(fp , "\t\t|-IP Version           : %d\n",(unsigned int)iph->version);
    fprintf(fp , "\t\t|-IP Header Length     : %d DWORDS or %d Bytes\n",(unsigned int)iph->ihl,((unsigned int)(iph->ihl))*4);
    fprintf(fp , "\t\t|-Type Of Service      : %d\n",(unsigned int)iph->tos);
    fprintf(fp , "\t\t|-IP Total Length      : %d  Bytes(size of Packet)\n",ntohs(iph->tot_len));
    fprintf(fp , "\t\t|-Identification       : %d\n",ntohs(iph->id));
    //fprintf(fp , "   |-Reserved ZERO Field   : %d\n",(unsigned int)iphdr->ip_reserved_zero);
    //fprintf(fp , "   |-Dont Fragment Field   : %d\n",(unsigned int)iphdr->ip_dont_fragment);
    //fprintf(fp , "   |-More Fragment Field   : %d\n",(unsigned int)iphdr->ip_more_fragment);
    fprintf(fp , "\t\t|-TTL                  : %d\n",(unsigned int)iph->ttl);
    fprintf(fp , "\t\t|-Protocol             : %d\n",(unsigned int)iph->protocol);
    fprintf(fp , "\t\t|-Checksum             : %d\n",ntohs(iph->check));
    fprintf(fp , "\t\t|-Source IP            : %s\n" , inet_ntoa(source.sin_addr) );
    fprintf(fp , "\t\t|-Destination IP       : %s\n" , inet_ntoa(dest.sin_addr) );
}

void print_ipv6_packet(const u_char *packet, int size) {
    print_ipv6_header(packet, size);
    struct ipv6hdr *iph = (struct ipv6hdr *)(packet + sizeof(struct ethhdr) );
    int offset = sizeof(struct ethhdr) + 40*sizeof(char);
    switch ((unsigned int)iph->nexthdr) {
        case DEFINE_ICMPv6:
            print_icmpv6_packet(packet, size, offset); break;
        case DEFINE_TCP:
            print_tcp_packet(packet, size, offset); break;
        case DEFINE_UDP:
            print_udp_packet(packet, size, offset); break;
        case DEFINE_FRAG:
            print_fragment(packet, size, offset);
            break;
    }
}

void print_ipv6_header(const u_char *packet, int size) {
    unsigned short iphdrlen;
    char *ipv6_addr = (char *)malloc(40*sizeof(char));
    struct ipv6hdr *iph = (struct ipv6hdr *)(packet + sizeof(struct ethhdr) );
    fprintf(fp, "\tIPv6 Header\n");
    fprintf(fp, "\t\t|-IP Version        : %d\n", (unsigned int)iph->version);
    fprintf(fp, "\t\t|-IP Traffic Class  : %d\n", (unsigned int)iph->priority);
    //fprintf(fp, "   |-IP Flow Label     : %d\n", ()iph->);
    fprintf(fp, "\t\t|-IP Payload Length : %d\n", (unsigned int)iph->payload_len);
    fprintf(fp, "\t\t|-IP  Next Header   : %d\n", (unsigned int)iph->nexthdr);
    fprintf(fp, "\t\t|-IP Hop Limit      : %d\n", (unsigned int)iph->hop_limit);
    ipv6_to_str(ipv6_addr, (const struct in6_addr *)&iph->saddr);
    fprintf(fp, "\t\t|-Source IP         : %s\n", ipv6_addr);
    ipv6_to_str(ipv6_addr, (const struct in6_addr *)&iph->daddr);
    fprintf(fp, "\t\t|-Destination IP    : %s\n", ipv6_addr);
    free(ipv6_addr);
    ip_size = 40*sizeof(char);
}

void ipv6_to_str(char * str, const struct in6_addr *addr) {
   sprintf(str, "%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x",
                 (int)addr->s6_addr[0], (int)addr->s6_addr[1],
                 (int)addr->s6_addr[2], (int)addr->s6_addr[3],
                 (int)addr->s6_addr[4], (int)addr->s6_addr[5],
                 (int)addr->s6_addr[6], (int)addr->s6_addr[7],
                 (int)addr->s6_addr[8], (int)addr->s6_addr[9],
                 (int)addr->s6_addr[10], (int)addr->s6_addr[11],
                 (int)addr->s6_addr[12], (int)addr->s6_addr[13],
                 (int)addr->s6_addr[14], (int)addr->s6_addr[15]);
}

void print_tcp_packet(const u_char *packet, int size, int offset) {
    print_tcp_header(packet, size, offset);
}

void print_tcp_header(const u_char *packet, int size, int offset) {
    struct tcphdr *tcph=(struct tcphdr*)(packet + offset);

    fprintf(fp , "\t\tTCP Header\n");
    fprintf(fp , "\t\t\t|-Source Port         : %u\n",ntohs(tcph->source));
    fprintf(fp , "\t\t\t|-Destination Port    : %u\n",ntohs(tcph->dest));
    fprintf(fp , "\t\t\t|-Sequence Number     : %u\n",ntohl(tcph->seq));
    fprintf(fp , "\t\t\t|-Acknowledge Number  : %u\n",ntohl(tcph->ack_seq));
    fprintf(fp , "\t\t\t|-Header Length       : %d DWORDS or %d BYTES\n" ,(unsigned int)tcph->doff,(unsigned int)tcph->doff*4);
    //fprintf(fp , "   |-CWR Flag : %d\n",(unsigned int)tcph->cwr);
    //fprintf(fp , "   |-ECN Flag : %d\n",(unsigned int)tcph->ece);
    fprintf(fp , "\t\t\t|-Urgent Flag         : %d\n",(unsigned int)tcph->urg);
    fprintf(fp , "\t\t\t|-Acknowledgement Flag: %d\n",(unsigned int)tcph->ack);
    fprintf(fp , "\t\t\t|-Push Flag           : %d\n",(unsigned int)tcph->psh);
    fprintf(fp , "\t\t\t|-Reset Flag          : %d\n",(unsigned int)tcph->rst);
    fprintf(fp , "\t\t\t|-Synchronise Flag    : %d\n",(unsigned int)tcph->syn);
    fprintf(fp , "\t\t\t|-Finish Flag         : %d\n",(unsigned int)tcph->fin);
    fprintf(fp , "\t\t\t|-Window              : %d\n",ntohs(tcph->window));
    fprintf(fp , "\t\t\t|-Checksum            : %d\n",ntohs(tcph->check));
    fprintf(fp , "\t\t\t|-Urgent Pointer      : %d\n",tcph->urg_ptr);

    trans_size = tcph->doff*4;//Data Offset
}

void print_udp_packet(const u_char *packet, int size, int offset) {
    print_udp_header(packet, size, offset);
}

void print_udp_header(const u_char *packet, int size, int offset) {
    struct udphdr *udph = (struct udphdr*)(packet + offset);

    fprintf(fp , "\t\tUDP Header\n");
    fprintf(fp , "\t\t\t|-Source Port      : %d\n" , ntohs(udph->source));
    fprintf(fp , "\t\t\t|-Destination Port : %d\n" , ntohs(udph->dest));
    fprintf(fp , "\t\t\t|-UDP Length       : %d\n" , ntohs(udph->len));
    fprintf(fp , "\t\t\t|-UDP Checksum     : %d\n" , ntohs(udph->check));

    trans_size = sizeof(struct udphdr);
}

void print_icmpv4_packet(const u_char *packet, int size, int offset) {
    print_icmpv4_header(packet, size, offset);
}

void print_icmpv4_header(const u_char *packet, int size, int offset) {
    struct icmphdr *icmph = (struct icmphdr *)(packet + offset);

    fprintf(fp , "\t\tICMP Header\n");
    fprintf(fp , "\t\t\t|-Type : %d",(unsigned int)(icmph->type));

    switch ((unsigned int)(icmph->type)) {
        case ICMP_ECHOREPLY:
            fprintf(fp, "\t\t\tEcho Reply\n"); break;
        case ICMP_DEST_UNREACH:
            fprintf(fp, "\t\t\tDestination Unreachable\n"); break;
        case ICMP_SOURCE_QUENCH:
            fprintf(fp, "\t\t\tSource Quench\n"); break;
        case ICMP_REDIRECT:
            fprintf(fp, "\t\t\tRedirect (change route)\n"); break;
        case ICMP_ECHO:
            fprintf(fp, "\t\t\tEcho Request\n"); break;
        case ICMP_TIME_EXCEEDED:
            fprintf(fp, "\t\t\tTime Exceeded\n"); break;
        case ICMP_PARAMETERPROB:
            fprintf(fp, "\t\t\tParameter Problem\n"); break;
        case ICMP_TIMESTAMP:
            fprintf(fp, "\t\t\tTimestamp Request\n"); break;
        case ICMP_TIMESTAMPREPLY:
            fprintf(fp, "\t\t\tTimestamp Reply\n"); break;
    }

    fprintf(fp , "\t\t\t|-Code : %d\n",(unsigned int)(icmph->code));
    fprintf(fp , "\t\t\t|-Checksum : %d\n",ntohs(icmph->checksum));
    //fprintf(fp , "   |-ID       : %d\n",ntohs(icmph->id));
    //fprintf(fp , "   |-Sequence : %d\n",ntohs(icmph->sequence));

    trans_size = sizeof(struct icmphdr);
}

void print_icmpv6_packet(const u_char *packet, int size, int offset) {
    print_icmpv6_header(packet, size, offset);
}

void print_icmpv6_header(const u_char *packet, int size, int offset) {
    struct icmp6_hdr *icmph = (struct icmp6_hdr *)(packet + offset);
    fprintf(fp ,"\t\tICMPv6 Header\n");
    fprintf(fp ,"\t\t\t|-Type       : %d\n", (unsigned int)(icmph->icmp6_type));
    fprintf(fp, "\t\t\t|-Code       : %d\n", (unsigned int)(icmph->icmp6_code));
    fprintf(fp, "\t\t\t|-Checksum   : %d\n", (unsigned int)(icmph->icmp6_code));

    trans_size = sizeof(struct icmp6_hdr);
}

void print_fragment(const u_char *packet, int size, int offset) {
    struct ip6_frag *frag_hdr = (struct ip6_frag *)(packet + offset);
    fprintf(fp, "\t\tFragment Header\n");
    fprintf(fp, "\t\t\t|-Next Header    : %d\n", (unsigned int)(frag_hdr->ip6f_nxt));
    fprintf(fp, "\t\t\t|-Fragment Offset: %d\n", (unsigned int)(frag_hdr->ip6f_offlg));
    fprintf(fp, "\t\t\t|-Identification : %d\n", (unsigned int)(frag_hdr->ip6f_ident));
    switch ((unsigned int)(frag_hdr->ip6f_nxt)) {
        case DEFINE_TCP:
            print_tcp_packet(packet, size, offset+sizeof(struct ip6_frag));
            break;
        case DEFINE_UDP:
            print_udp_packet(packet, size, offset+sizeof(struct ip6_frag));
            break;
    }
}

void print_complete_packet(const u_char *packet, int size) {
    for(int i = 0 ; i < size ; ++i) {
        if( i!=0 && i%16==0) {
            fprintf(fp , "         ");
            for(int j = i-16 ; j < i ; j++) {
                fprintf(fp , "%c", (packet[j]>=32 && packet[j]<=128) ? (unsigned char)packet[j] : '.');
            }
            fprintf(fp , "\n");
        }
        if(i%16==0) fprintf(fp, "   ");
        fprintf(fp , "%02X ",(unsigned int)packet[i]);
        if( size-1 == i) {
            for(int j = 0;j < 15-i%16; ++j) {
                fprintf(fp , "   ");
            }
            fprintf(fp , "         ");
            for(int j = i-i%16 ; j <= i ; ++j) {
                fprintf(fp , "%c", (packet[j]>=32 && packet[j]<=128) ? (unsigned char)packet[j] : '.');
            }
            fprintf(fp ,  "\n" );
        }
    }
}

抓包

编译以上代码:

$ gcc ip_sniffer.c -lpcap
$ sudo ./a.out

然后在浏览器打开一个窗口,如http://test-ipv6.com/。便可以抓到数据包,由于数据较多,选取一段:

总阅读量次。