文件的自定义包发送接收

需求

对一个特定的文件进行分片发送,构造数据包,发送数据包,接收数据包并提取有效数据,对数据组合还原为原文件。

设计

当前,基于socket的网络编程已成为当今不可替代的编程方法,它将网络通讯当作文件描述符进行处理,把对这个“网络文件”(即socket套接字)的操作抽象成一种类似于文件操作的方式进行。从实现细节上,这种工作方式根据TCP/IP的网络通讯模型,封装了一系列的实现,使得我们只需要使用一个指定的参数,就可以实现在基于所需协议的数据的发送和接收。
但是,如果我们对那些系统自动给我们做的工作感兴趣,希望与发送的数据作“面对面”的接触,根据需求,传统的socket只能实现传输层之上的数据传输,不能自己构造数据包,而且这里需要更底层的操作,比如网络层和数据链路层,故不可用。

方案:Libnet + libpcap

这个方案基于C语言实现,对于一个特定的文件,得到其HEX数据,根据需求分成若干个部分,前面每个部分大小一致,剩余作为最后一个部分,将这个数据分别利用libnet组包发送包,利用libpcap抓包解析包,得到后取其有效数据,也就是HEX数据,对其组装,得到最后的文件,整个过程结束。

libnet

libnet是UNIX系统同台上网络安全工具开发的重要的库,它和libpcap、libnids一起,给网络安全工具的开发人员提供了一组丰富而且完全的武器,使之得以很方便地编写出结构化强、健壮性好、可移植性高等特点的程序。
libnet提供一系列的接口函数,实现和封装了数据包的构造和发送过程。利用它可以亲自构造从应用层到链路层的各层协议的数据包头,并将这些包头与有效数据有序地组合在一起发送出去。当然,它也是基于tcp/ip协议族模型的。

利用libnet函数库开发应用程序的基本步骤非常简单:
1、数据包内存初始化;
2、构造数据包;
3、发送数据;
4、释放资源;

libnet提供的接口函数按其作用可分为四类:
* 内存管理(分配和释放)函数
* 地址解析函数
* 数据包构造函数
* 数据包发送函数
接口函数及其功能
以下分别列出这些接口函数及其功能
内存管理函数
单数据包内存初始化:
int libnet_init_packet(u_short packet_size, u_char **buf);
单数据包内存释放:
void libnet_destroy_packet(u_char **buf);
多数据包内存初始化:
int libnet_init_packet_arena(struct libnet_arena **arena,
u_short packet_num, u_short packet_size);
访问多数据包内存中的下一个数据包:
u_char *libnet_next_packet_from_arena(struct libnet_arena **arena,
u_short packet_size);
多数据包内存释放:
void libnet_destroy_packet_arena(struct libnet_arena **arena);
地址解析函数
解析主机名:
u_char *libnet_host_lookup(u_long ip, u_short use_name);
解析主机名(可重入函数):
void libnet_host_lookup_r(u_long ip, u_short use_name, u_char *buf);
域名解析:
u_long libnet_name_resolve(u_char *ip, u_short use_name);
获取接口设备IP地址:
u_long libnet_get_ipaddr(struct libnet_link_int *l,
const u_char *device, const u_char *ebuf);
获取接口设备硬件地址:
struct ether_addr *libnet_get_hwaddr(struct libnet_link_int *l,
const u_char *device,
const u_char *ebuf);
数据包构造函数(数据包类型多样,这里太多,列几个主要的)
ARP协议数据包:
int libnet_build_arp(u_short hrdw, u_short prot, u_short h_len,
u_short p_len, u_short op, u_char *s_ha,
u_char *s_pa, u_char *t_ha, u_char *t_pa,
const u_char *payload, int payload_len,
u_char *packet_buf);
以太网协议数据包:
int libnet_build_ethernet(u_char *daddr, u_char *saddr, u_short id,
const u_char *payload, int payload_len,
u_char *packet_buf);
IP协议数据包:
int libnet_build_ip(u_short len, u_char tos, u_short ip_id, u_short frag,
u_char ttl, u_char protocol, u_long saddr,
u_long daddr, const u_char *payload, int payload_len,
u_char *packet_buf);
TCP协议数据包:
int libnet_build_tcp(u_short th_sport, u_short th_dport, u_long th_seq,
u_long th_ack, u_char th_flags, u_short th_win,
u_short th_urg, const u_char *payload,
int payload_len, u_char *packet_buf);
UDP协议数据包:
int libnet_build_udp(u_short sport, u_short dport, const u_char *payload,
int payload_len, u_char *packet_buf);
IP协议数据包选项:
int libnet_insert_ipo(struct ipoption *opt, u_char opt_len,
u_char *packet_buf);
TCP协议数据包选项:
int libnet_insert_tcpo(struct tcpoption *opt, u_char opt_len,
u_char *packet_buf);
数据包发送函数
打开raw socket:
int libnet_open_raw_sock(int protocol);
关闭raw socket:
int libnet_close_raw_sock(int socket);
选择接口设备:
int libnet_select_device(struct sockaddr_in *sin,
u_char **device, u_char *ebuf);
打开链路层接口设备:
struct libnet_link_int *libnet_open_link_interface(char *device,
char *ebuf);
关闭链路层接口设备:
int libnet_close_link_interface(struct libnet_link_int *l);
发送IP数据包:
int libnet_write_ip(int socket, u_char *packet, int packet_size);
发送链路层数据包:
int libnet_write_link_layer(struct libnet_link_int *l,
const u_char *device, u_char *packet,
int packet_size);
检验和计算:
int libnet_do_checksum(u_char *packet, int protocol, int packet_size);
相关的支持函数
随机数种子生成器:
int libnet_seed_prand();
获取随机数:
u_long libnet_get_prand(int modulus);
16进制数据输出:
void libnet_hex_dump(u_char * buf, int len, int swap, FILE *stream);
端口列表链初始化:
int libnet_plist_chain_new(struct libnet_plist_chain **plist,
char *token_list);
获取端口列表链的下一项(端口范围):
int libnet_plist_chain_next_pair(struct libnet_plist_chain *plist,
u_short *bport, u_short *eport);
端口列表链输出显示:
int libnet_plist_chain_dump(struct libnet_plist_chain *plist);
获取端口列表链:
u_char *libnet_plist_chain_dump_string(struct libnet_plist_chain *plist);
端口列表链内存释放:
void libnet_plist_chain_free(struct libnet_plist_chain *plist);

libpcap

libpcap是一个网络数据包捕获函数库,功能非常强大,Linux下著名的tcpdump就是以它为基础的。
如何使用libpcap:
首先要使用libpcap,我们必须包含pcap.h头文件,可以在/usr/local/include/pcap/pcap.h找到,其中包含了每个类型定义的详细说明。

1.获取网络接口
首先我们需要获取监听的网络接口:
我们可以手动指定或让libpcap自动选择,先介绍如何让libpcap自动选择:
char * pcap_lookupdev(char * errbuf)
上面这个函数返回第一个合适的网络接口的字符串指针,如果出错,则errbuf存放出错信息字符串,errbuf至少应该是PCAP_ERRBUF_SIZE个字节长度的。注意,很多libpcap函数都有这个参数。
pcap_lookupdev()一般可以在跨平台的,且各个平台上的网络接口名称都不相同的情况下使用。
如果我们手动指定要监听的网络接口,则这一步跳过,我们在第二步中将要监听的网络接口字符串硬编码在pcap_open_live里。

2.释放网络接口
在操作为网络接口后,我们应该要释放它:
void pcap_close(pcap_t * p)
该函数用于关闭pcap_open_live()获取的pcap_t的网络接口对象并释放相关资源。

3.打开网络接口
获取网络接口后,我们需要打开它:
pcap_t * pcap_open_live(const char * device, int snaplen, int promisc, int to_ms, char * errbuf)
上面这个函数会返回指定接口的pcap_t类型指针,后面的所有操作都要使用这个指针。
第一个参数是第一步获取的网络接口字符串,可以直接使用硬编码。
第二个参数是对于每个数据包,从开头要抓多少个字节,我们可以设置这个值来只抓每个数据包的头部,而不关心具体的内容。典型的以太网帧长度是1518字节,但其他的某些协议的数据包会更长一点,但任何一个协议的一个数据包长度都必然小于65535个字节。
第三个参数指定是否打开混杂模式(Promiscuous Mode),0表示非混杂模式,任何其他值表示混合模式。如果要打开混杂模式,那么网卡必须也要打开混杂模式,可以使用如下的命令打开eth0混杂模式:
ifconfig eth0 promisc
第四个参数指定需要等待的毫秒数,超过这个数值后,第3步获取数据包的这几个函数就会立即返回。0表示一直等待直到有数据包到来。
第五个参数是存放出错信息的数组。

4.获取数据包
打开网络接口后就已经开始监听了,那如何知道收到了数据包呢?有下面3种方法:
a)
u_char * pcap_next(pcap_t * p, struct pcap_pkthdr * h)
如果返回值为NULL,表示没有抓到包
第一个参数是第2步返回的pcap_t类型的指针
第二个参数是保存收到的第一个数据包的pcap_pkthdr类型的指针
pcap_pkthdr类型的定义如下:
[cpp] view plain copy
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) */
};

注意这个函数只要收到一个数据包后就会立即返回.

b)
int pcap_loop(pcap_t * p, int cnt, pcap_handler callback, u_char * user)
第一个参数是第2步返回的pcap_t类型的指针
第二个参数是需要抓的数据包的个数,一旦抓到了cnt个数据包,pcap_loop立即返回。负数的cnt表示pcap_loop永远循环抓包,直到出现错误。
第三个参数是一个回调函数指针,它必须是如下的形式:
void callback(u_char * userarg, const struct pcap_pkthdr * pkthdr, const u_char * packet)
第一个参数是pcap_loop的最后一个参数,当收到足够数量的包后pcap_loop会调用callback回调函数,同时将pcap_loop()的user参数传递给它
第二个参数是收到的数据包的pcap_pkthdr类型的指针
第三个参数是收到的数据包数据
c)
int pcap_dispatch(pcap_t * p, int cnt, pcap_handler callback, u_char * user)
这个函数和pcap_loop()非常类似,只是在超过to_ms毫秒后就会返回(to_ms是pcap_open_live()的第4个参数)

5.分析数据包
我们既然已经抓到数据包了,那么我们要开始分析了,这部分留给读者自己完成,具体内容可以参考相关的网络协议说明。在本文的最后,我会示范性的写一个分析arp协议的sniffer,仅供参考。要特别注意一点,网络上的数据是网络字节顺序的,因此分析前需要转换为主机字节顺序(ntohs()函数)。

6.过滤数据包
我们抓到的数据包往往很多,如何过滤掉我们不感兴趣的数据包呢?
几乎所有的操作系统(BSD, AIX, Mac OS, Linux等)都会在内核中提供过滤数据包的方法,主要都是基于BSD Packet Filter(BPF)结构的。libpcap利用BPF来过滤数据包。
过滤数据包需要完成3件事:
a) 构造一个过滤表达式
b) 编译这个表达式
c) 应用这个过滤器

a)
BPF使用一种类似于汇编语言的语法书写过滤表达式,不过libpcap和tcpdump都把它封装成更高级且更容易的语法了,具体可以man tcpdump,以下是一些例子:
src host 192.168.1.177
只接收源ip地址是192.168.1.177的数据包
dst port 80
只接收tcp/udp的目的端口是80的数据包
not tcp
只接收不使用tcp协议的数据包
tcp[13] == 0x02 and (dst port 22 or dst port 23)
只接收SYN标志位置位且目标端口是22或23的数据包(tcp首部开始的第13个字节)
icmp[icmptype] == icmp-echoreply or icmp[icmptype] == icmp-echo
只接收icmp的ping请求和ping响应的数据包
ehter dst 00:e0:09:c1:0e:82
只接收以太网mac地址是00:e0:09:c1:0e:82的数据包
ip[8] == 5
只接收ip的ttl=5的数据包(ip首部开始的第8个字节)
b)
构造完过滤表达式后,我们需要编译它,使用如下函数:
int pcap_compile(pcap_t * p, struct bpf_program * fp, char * str, int optimize, bpf_u_int32 netmask)
fp:这是一个传出参数,存放编译后的bpf
str:过滤表达式
optimize:是否需要优化过滤表达式
metmask:简单设置为0即可

c)
最后我们需要应用这个过滤表达式:
int pcap_setfilter(pcap_t * p, struct bpf_program * fp)
第二个参数fp就是前一步pcap_compile()的第二个参数

应用完过滤表达式之后我们便可以使用pcap_loop()或pcap_next()等抓包函数来抓包了。

另外提供其他思路

方案1:可以使用sendip发送数据包,接收方式同样采用libpcap.
在正常的网络环境中,很难产生错误的IP 包,也很难产生我们想要的错误的IP 包,为此,要完成对产品的测试,我们必须自己来制造各种各样错误的IP 包,本篇的目的就是介绍如何利用各种发包工具来制造自己想要的错误的IP 包。SENDIP 是一个LINUX 下的命令行工具,可以通过命令行参数的方式发送各种格式的IP 包,它有大量的命令行参数来规定各种协议的头格式,目前可支持NTP, BGP, RIP, RIPng,TCP, UDP, ICMP 或raw IPv4 和IPv6 包格式,并且可以随意在包中添加数据。
方案2:pcap4j(https://github.com/kaitoy/pcap4j)
Pcap4J is a Java library for capturing, crafting and sending packets. Pcap4J wraps a native packet capture library (libpcap or WinPcap) via JNA and provides you Java-Oriented APIs.
方案3:scapy(https://github.com/secdev/scapy)
scapy是python写的一个功能强大的交互式数据包处理程序,可用来发送、嗅探、解析和伪造网络数据包,常常被用到网络攻击和测试中

实现

#define BUF_SIZE 256 //定义单个数据包携带的数据大小,单位是字节

/*
函数实现将数据装入数据包,构造数据包(这里以tcp为例),发送数据包的工作,这里可以指定以太网头、ip头,tcp头各个元组的值,从哪个网卡发送以及源和目的ip地址和物理地址。
*/
int func_package(u_char payload[],int bytes_in)
{
    libnet_t *handle; /* Libnet句柄 */
    int packet_size; /* 构造的数据包大小 */
    char *device = "eth0"; /* 设备名字,也支持点十进制的IP地址,会自己找到匹配的设备 */
    char *src_ip_str = "192.168.1.119"; /* 源IP地址字符串 */
    char *dst_ip_str = "192.168.1.101"; /* 目的IP地址字符串 */
    u_char src_mac[6] = {0x00, 0x0c, 0x29, 0x4b, 0x76, 0x97}; /* 源MAC */
    u_char dst_mac[6] = {0x20, 0x7c, 0x8f, 0x72, 0x34, 0x06}; /* 目的MAC */
    u_long dst_ip, src_ip; /* 网路序的目的IP和源IP */
    char error[LIBNET_ERRBUF_SIZE]; /* 出错信息 */
    libnet_ptag_t eth_tag, ip_tag, tcp_tag, tcp_op_tag; /* 各层build函数返回值 */
    u_short proto = IPPROTO_TCP; /* 传输层协议 */
    u_long payload_s = 0; /* 承载数据的长度,初值为0 */

    /* 把目的IP地址字符串转化成网络序 */
    dst_ip = libnet_name2addr4(handle, dst_ip_str, LIBNET_RESOLVE);
    /* 把源IP地址字符串转化成网络序 */
    src_ip = libnet_name2addr4(handle, src_ip_str, LIBNET_RESOLVE);

    /* 初始化Libnet */
    if ( (handle = libnet_init(LIBNET_LINK, device, error)) == NULL ) {
        printf("libnet_init failure\n");
        return (-1);
    };

    //strncpy(payload, "testtesttest", sizeof(payload)-1); /* 构造负载的内容 */
    //payload_s = strlen(payload); /* 计算负载内容的长度 */
    payload_s = bytes_in; /* 计算负载内容的长度 */

#if 0
    /* 构建TCP的选项,通常在第一个TCP通信报文中设置MSS */
    tcp_op_tag = libnet_build_tcp_options(
                payload,
                payload_s,
                handle,
                0
    );
    if (tcp_op_tag == -1) {
        printf("build_tcp_options failure\n");
        return (-2);
    };
#endif

    tcp_tag = libnet_build_tcp(
                30330,                    /* 源端口 */
                30331,                    /* 目的端口 */
                8888,                    /* 序列号 */
                8889,                    /* 确认号 */
                TH_PUSH | TH_ACK,        /* Control flags */
                14600,                    /* 窗口尺寸 */
                0,                        /* 校验和,0为自动计算 */
                bytes_in,                        /* 紧急指针 */
                LIBNET_TCP_H + payload_s, /* 长度 */
                payload,                    /* 负载内容 */
                payload_s,                /* 负载内容长度 */
                handle,                    /* libnet句柄 */
                0                        /* 新建包 */
    );
    if (tcp_tag == -1) {
        printf("libnet_build_tcp failure\n");
        return (-3);
    };

    /* 构造IP协议块,返回值是新生成的IP协议快的一个标记 */
    ip_tag = libnet_build_ipv4(
        LIBNET_IPV4_H + LIBNET_TCP_H + payload_s, /* IP协议块的总长,*/
        0, /* tos */
        (u_short) libnet_get_prand(LIBNET_PRu16), /* id,随机产生0~65535 */
        0, /* frag 片偏移 */
        (u_int8_t)libnet_get_prand(LIBNET_PR8), /* ttl,随机产生0~255 */
        proto, /* 上层协议 */
        0, /* 校验和,此时为0,表示由Libnet自动计算 */
        src_ip, /* 源IP地址,网络序 */
        dst_ip, /* 目标IP地址,网络序 */
        NULL, /* 负载内容或为NULL */
        0, /* 负载内容的大小*/
        handle, /* Libnet句柄 */
        0 /* 协议块标记可修改或创建,0表示构造一个新的*/
    );
    if (ip_tag == -1) {
        printf("libnet_build_ipv4 failure\n");
        return (-4);
    };

    /* 构造一个以太网协议块,只能用于LIBNET_LINK */
    eth_tag = libnet_build_ethernet(
        dst_mac, /* 以太网目的地址 */
        src_mac, /* 以太网源地址 */
        ETHERTYPE_IP, /* 以太网上层协议类型,此时为IP类型 */
        NULL, /* 负载,这里为空 */ 
        0, /* 负载大小 */
        handle, /* Libnet句柄 */
        0 /* 协议块标记,0表示构造一个新的 */ 
    );
    if (eth_tag == -1) {
        printf("libnet_build_ethernet failure\n");
        return (-5);
    };

    packet_size = libnet_write(handle); /* 发送已经构造的数据包*/

    libnet_destroy(handle); /* 释放句柄 */

    return 0;
}

/*
主函数完成文件分片组合
*/
int main()
{
    //u_char payload[255] = {0}; /* 承载数据的数组,初值为空 */
    //strncpy(payload, "fanshuquan", sizeof(payload)-1); /* 构造负载的内容 */
    //func_package(payload);

    FILE *in_file;
    FILE *out_file;
    u_char data[BUF_SIZE];
    size_t bytes_in;
    size_t bytes_out;
    long len = 0;
    char* old = "o";
    char* n = "n";
    int i=0,j=0;

    if ( (in_file = fopen(old, "rb")) == NULL )
    {
    printf("old file error\n");
        return 2;
    }

    if ( (out_file = fopen(n, "wb")) == NULL )
    {
        printf("new file error\n");
        return 3;
    }


    while ( (bytes_in = fread(data, 1, BUF_SIZE, in_file)) > 0 )
    {
        //func_package((u_char *)bytes_in);

  for(i=0; iprintf(" %02x", data[i]);  
    if( (i + 1) % 16 == 0 )  
    {  
      printf("\n");  
    }  
  }
        func_package(data,bytes_in);
        printf("%d\n",strlen(data));
        printf("%d****\n",bytes_in);

      bytes_out = fwrite(data, 1, bytes_in, out_file);

/*
        bytes_out = fwrite(data, 1, bytes_in, out_file);
        if ( bytes_in != bytes_out )
        {
            printf("Fatal write error.\n");
            return 4;
        }
        len += bytes_out;
        printf("copying file .... %d bytes copy\n", len);
*/
        printf("%d\n",j++);
    }
//for(i=0;i
strcpy(data,"end_of_file");
func_package(data,11);


    fclose(in_file);
    //fclose(out_file);

    return 0;
}
//定义bpf过滤器规则
#define FILTER "dst host 192.168.1.101 and ether dst host 20:7c:8f:72:34:06 and dst port 30331 and tcp"

//实现抓包并组合数据
void fun_pcap(FILE * out_file)
{
    char errBuf[PCAP_ERRBUF_SIZE], *devStr;

    devStr = pcap_lookupdev(errBuf);

    if(devStr)
    {
        printf("success: device: %s\n", devStr);
    }
    else
    {
        printf("error: %s\n", errBuf);
        exit(1);
    }

    pcap_t * device = pcap_open_live(devStr, 63335, 1, 0, errBuf);

    if(!device)
    {
        printf("error: pcap_open_live(): %s\n", errBuf);
        exit(1);
    }

    struct bpf_program filter;
    pcap_compile(device, &filter, FILTER, 1, 0);
    pcap_setfilter(device, &filter);
    struct pcap_pkthdr packet;

    const u_char * pktStr;
    int j=0;
    while((pktStr = pcap_next(device, &packet)))
    {
        printf("%d\n",j++);
        int i,bytes_in, bytes_out;
        u_char data[BUF_SIZE];

        bytes_in = pktStr[52]*256 + pktStr[53];

        for(i=54; i<54+bytes_in; ++i)
        {
            data[i-54] = pktStr[i];
        }

        if((data[0]==0x65)&&(data[1]==0x6e)&&(data[2]==0x64)&&(data[3]==0x5f)&&(data[4]==0x6f)&&(data[5]==0x66)&&(data[6]==0x5f)&&(data[7]==0x66)&&(data[8]==0x69)&&(data[9]==0x6c)&&(data[10]==0x65))
        {
            fclose(out_file);
            printf("end of file\n");
            exit(1);
        }
        else
            bytes_out = fwrite(data, 1, packet.len-54, out_file);
/*
        if(bytes_in != bytes_out)
        {
            printf("write error!\n");
            exit(1);
        }
*/
//        printf("\ndata length: %d\n", bytes_## 标题 ##out);
//        memset(data,0,sizeof(data));
//        if(j==1415) fclose(out_file);
    }

    pcap_close(device);
}

资料参考

1、传统socket
2、rawsockets
3、Libnet,libpcap
4、Shell脚本(利用sendip等)
5、Sendip(http://www.earth.li/projectpurple/progs/sendip.html)
SENDIP 是一个LINUX 下的命令行工具,可以通过命令行参数的方式发送各种格式的IP 包,它有大量的命令行参数来规定各种协议的头格式,目前可支持NTP, BGP, RIP, RIPng,TCP, UDP, ICMP 或raw IPv4 和IPv6 包格式,并且可以随意在包中添加数据。
Sendip使用说明(http://blog.csdn.net/stephen_yin/article/details/6575411)
6、rawsocket for java(https://github.com/dangan249/RawSocket/tree/master/rocksaw-1.0.3)
7、Packet sender
Packet Sender is an open source utility to allow sending and receiving TCP and UDP packets. The mainline branch officially supports Windows, Mac, and Desktop Linux (with Qt). Other places may recompile and redistribute Packet Sender. Packet Sender is free and licensed GPL v2 or later. It can be used for both commercial and personal use.
带界面的发送与接收tcp与udp包软件,带有界面。
8、https://github.com/secdev/scapy
scapy是python写的一个功能强大的交互式数据包处理程序,可用来发送、嗅探、解析和伪造网络数据包,常常被用到网络攻击和测试中
一个博客:http://www.cnblogs.com/xiaowuyi/p/3329795.html
9、MINA(apache)Apache Mina是一个能够帮助用户开发高性能和高伸缩性网络应用程序的框架。它通过Java nio技术基于TCP/IP和UDP/IP协议提供了抽象的、事件驱动的、异步的API。
10、NETTY(JBoss做的一个Jar包,高效)

JAVA中使用JNI调用C++代码(http://www.tuicool.com/articles/bYRRBb)

你可能感兴趣的:(底层网络编程)