千夜瞬 发表于 2021-4-28 10:08

Linux的Netfilter框架注册Hook函数返回NF_QUEUE时用户态怎么接收

本帖最后由 千夜瞬 于 2021-4-28 10:10 编辑

楼主想要在Linux的Netfilter框架中注册一个Hook函数
Hook函数想要实现的功能是:将符合条件的数据包返回给用户态进行处理,也就是
return NF_QUEUE;
目前的Hook函数代码为
#include <linux/time.h>
#include <linux/init.h>
#include <linux/netfilter.h>
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/netdevice.h>
#include <linux/netfilter_ipv4.h>
#include <linux/net.h>
#include <net/ip.h>
#include <linux/if_ether.h>
#include <net/protocol.h>
#include <net/icmp.h>
#include <net/tcp.h>
#include <linux/if_vlan.h>
#include <linux/netfilter_bridge.h>


unsigned int my_func(void *priv, struct sk_buff *skb, const struct nf_hook_state *state){
      struct iphdr *iph=ip_hdr(skb);
      /* struct tcphdr *tcph;
      struct udphdr *udph; */
      struct icmphdr *icmph;
      int header=0;
      int length = 0;
      unsigned char *data=NULL;

      if(likely(iph->protocol==IPPROTO_ICMP))
      {
                icmph=icmp_hdr(skb);
                data=skb->data+iph->ihl*4+sizeof(struct icmphdr);
                header=iph->ihl*4+sizeof(struct icmphdr);
                length=ntohs(iph->tot_len)-iph->ihl*4-sizeof(struct icmphdr);
                printk("loss packet header = %d,length = %d",header,length);
                return NF_QUEUET;      
      }

      return NF_ACCEPT;               
}

static struct nf_hook_ops nfho = {
    .hook         = my_func,            //hook处理函数
      .pf             = PF_INET,            //协议类型
      .hooknum      = NF_INET_POST_ROUTING,    //hook注册点
      .priority       = NF_IP_PRI_FIRST,      //优先级
};

static int __init http_init(void){
                  
      if (nf_register_net_hook(&init_net,&nfho)) {
                printk(KERN_ERR"nf_register_hook() failed\n");
                return -1;
      }
      return 0;
}
static void __exit http_exit(void)
{
      nf_unregister_net_hook(&init_net,&nfho);
}

module_init(http_init);
module_exit(http_exit);
MODULE_AUTHOR("AFCC_");
MODULE_LICENSE("GPL");
现在内核态的Hook函数注册基本实现,但是用户态如何接收数据包现在还是一窍不通
原本用iptables命令行插入规则时,会有一个queue_num,通过queue_num从队列中取数据包
插规则的命令行是:
iptables -I INPUT -j NFQUEUE --queue-num 8008
用户空间处理数据包的代码是:
#include "nfqueue.h"

//定义winsize 结构体变量, 获取当前终端的大小
struct winsize size;
#define ENCLENBASE 16
extern unsigned char keyDataUnion;//extern:已经定义的外部变量
extern unsigned char ivData;

//如果当前的宏__LITTLE_ENDIAN已被定义过,则对“程序段1”进行编译,否则对“程序段2”进行编译。
//#define IPQUAD(addr) :带参数的宏
#ifdef __LITTLE_ENDIAN
#define IPQUAD(addr)            \
                ((unsigned char *)&addr),   \
                ((unsigned char *)&addr),   \
                ((unsigned char *)&addr),   \
                ((unsigned char *)&addr)
#else
#define IPQUAD(addr)            \
                ((unsigned char *)&addr),   \
                ((unsigned char *)&addr),   \
                ((unsigned char *)&addr),   \
                ((unsigned char *)&addr)
#endif


/**
* u_int8_t:__uint8_t>unsigned char,无符号字符型
* u_int32_t: __uint32_t》 unsigned int 无符号int型
* char* 或者 char[]去定义一个字符串
* char *str1 = "abcd1234";
   char str2[] = "abcd1234";
*/
void show_data(u_int8_t *data, u_int32_t data_size)
{
    u_int32_t i = 0;//unsigned int
    //typedef __uint32_t > u_int32
    //typedef unsigned int >__uint32_t
      for(i=0; i<data_size; ++i)
      printf("%02X", data);
      //以16进制的格式输出整数类型的数值,输出域宽为2,右对齐,不足的用字符0替代。
    printf("\n");
}



// 回调函数定义, 基本结构是先处理包,然后返回裁定
/**
* 回调函数:解析数据包并根据业务逻辑做出裁决
*/
static int cb(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, struct nfq_data *nfa, void *data)
{
    u_int32_t id = 0, i = 0;
    u_int8_t* payload = NULL;
    unsigned char *pdata = NULL;
    int pdataLen = 0, length = 0;
    struct nfqnl_msg_packet_hw *hwph = NULL; // nfqueue netlink消息数据包头硬件部分,MAC地址
    struct nfqnl_msg_packet_hdr *ph = NULL;// nfqueue netlink消息数据包头
    struct tcphdr *tcphdrp = NULL;
    struct udphdr *udphdrp = NULL;
    struct iphdr *iphdrp = NULL;
    //表示一个完整数据包的各种头,我们常用到的iphdr,tcphdr,udphdr,icmpdr

    uint16_t sport = 0, dport = 0;
    char src_mac;

    unsigned char tmp = {0};
    // 提取数据包头信息,包括id,协议和hook点信息
    ph = nfq_get_msg_packet_hdr(nfa);

    if (ph)
      id = ntohl(ph->packet_id);//将一个无符号长整形数从网络字节顺序转换为主机字节顺序。
    // 获取数据包载荷,data指针指向载荷,从实际的IP头开始
    pdataLen = nfq_get_payload(nfa, ((unsigned char**)&pdata)); //ip头+tcp头+数据的报文的长度
    if(pdataLen == -1)
      pdataLen = 0;
    iphdrp = (struct iphdr *)pdata;
    // 获取到终端的大小数据
    ioctl(STDIN_FILENO, TIOCGWINSZ, &size);
    // 打印出和终端大小一样的框框
    for(i = 0; i < size.ws_col; i++)
      printf("*");

    printf("packet: %u, packetN:%u , protocol = %u\n", id, ph->packet_id, iphdrp->protocol);
    //%u 无符号10进制整数
   

    if(iphdrp->protocol == IPPROTO_TCP)
    {
      /**
         * iphdrp->ihl;IP头部的长度,表示有32位长度多少个,在计算ip头部的长度时,通常用ipiph->ihl*4,或(iphdrp->ihl<<2)
         * iphdrp->tot_len;整个ip报文的长度,减去ip头部的长度,就是ip包的数据部分了,在使用过程中需要注意网络字节序的问题。
         * https://blog.csdn.net/jcw321/article/details/102530957
         * https://blog.csdn.net/qq_35493457/article/details/80588381
      */
      tcphdrp = (struct tcphdr*)((u_int8_t*)iphdrp + (iphdrp->ihl<<2));
      //ip报文的指针移动ip报文首部的长度,指向的就是tcp报文的首部
      length = pdataLen - (iphdrp->ihl<<2) - (tcphdrp->doff<<2);
      //ip报文的长度减去ip首部长度,减去tcp首部长度,就是payload数据长度
      payload = (u_int8_t*)( (u_int8_t*)tcphdrp + (tcphdrp->doff<<2) );
      //指向tcp数据部分


      sport = tcphdrp->source;
      dport = tcphdrp->dest;

      printf("len = %d\n", length);

      printf("二进制源码是:");
      for(int i=0;i<length;i++){
            printf("%02x,",payload);
      }
      /* 修改數據 */
      for(i = 0; i < (length > 1024 ? 1024 : length); i++)
            tmp = payload;//将数据部分同意置为发送过来的数据的最后一位
      memcpy(payload, tmp, length > 1024 ? 1024 : length);
      /**
         * void * memcpy ( void * dest, const void * src, size_t num );
         * 复制 src 所指的内存内容的前 num 个字节到 dest 所指的内存地址上。
      */

    }
    else if(iphdrp->protocol == IPPROTO_UDP)
    {
      udphdrp = (struct udphdr*)((u_int8_t*)iphdrp + (iphdrp->ihl<<2));
      length = pdataLen - (iphdrp->ihl<<2) - 8;
      payload = (u_int8_t*)((u_int8_t*)iphdrp + (iphdrp->ihl<<2) + 8);
      sport = udphdrp->source;
      dport = udphdrp->dest;
    }

    printf("line = %d, sport = %d, dport = %d\n", __LINE__, sport, dport);
    //__LINE__用以指示本行语句在源文件中的位置信息,也就是行数
   
   
    /* 如果對數據報進行了修改,那麼必須進行校驗 */
    set_ip_checksum(iphdrp);
    if(iphdrp->protocol == IPPROTO_TCP)
      set_tcp_checksum1(iphdrp);
    if(iphdrp->protocol == IPPROTO_UDP)
      set_udp_checksum1(iphdrp);

    /* 打印出和终端大小一样的框框 */
    for(i = 0; i < size.ws_col; i++)
      printf("*");
    // 设置裁定
    return nfq_set_verdict(qh, id, NF_ACCEPT, (u_int32_t)pdataLen, pdata);
    //用户态进程做出裁决后,调用nfq_set_verdict()通知内核,内核根据裁决继续处理数据包。
}


int nfq_create_queue_run(int queue_num)
{
    struct nfq_handle *h;
    struct nfq_q_handle *qh;
    struct nfnl_handle *nh;
    int    fd;
    int rv;
    char buf;

    /**
   * assert:断言
   * assert(expr):如果断言(expr)非真,程序停止,标准错误输出一条具体的错误信息
   * 也可以通过define定义宏
    */
   
    // 打开nfq_handle
    assert((h = nfq_open()) != NULL);
    // 先解开和AF_INET的绑定
    assert(nfq_unbind_pf(h, AF_INET) == 0);
    // 绑定到AF_INET
    assert(nfq_bind_pf(h, AF_INET) == 0);
    /**
   * int nfq_bind_pf(struct nfq_handle *h, u_int16_t pf)
   * h:nfq_open()返回struct nfq_handle结构的指针;
       pf: 协议族 AF_BRIDGE:7多协议桥,
                  AF_INET:2 路由模式
    */


    // 建立nfq_q_handle, 号码是 80 , 回调函数是cb
    // 可建立多个queue,用不同的号码区分即可
    // 此处的号码 nfq_create_queue 的第 2 个参数 80 与后来的设置 iptables 的那个队列是需要一一对应的
    assert((qh = nfq_create_queue(h, queue_num, &cb, NULL)) != NULL);
    /**
   * nfq_create_queue:将此套接字绑定到特定的队列号.
   * h : nfq_open()返回struct nfq_handle结构的指针;
      num: 队列的序列号,也就是iptables里设置的规则的nfqueue的序列号
      cb : 数据包的处理函数(回调函数)
      data: 自定义数据
    */


    // 设置数据拷贝模式, 全包拷贝
    assert(nfq_set_mode(qh, NFQNL_COPY_PACKET, 0xffff) == 0);
    /**
   * nfq_set_mode:从netlink系统里设置copy数据的方式
   * qh   :nfq_create_queue()函数的返回值
      mode : copy 方式
            NFQNL_COPY_NONE,- 丢弃
            NFQNL_COPY_META,- 复制元数据
            NFQNL_COPY_PACKET,- 复制整个数据包
      range:期望得到的数据包的大小
    */


    fd = nfq_fd(h);
    // 从netlink套接字接收数据
    while ((rv = recv(fd, buf, sizeof(buf), 0)) && rv >= 0) {
      // 处理数据,最终会调用到相应的回调函数
      nfq_handle_packet(h, buf, rv);
      /**
         * 处理接收的数据,处理的逻辑由nfq_create_queue()函数中的回调函数cb决定
         *h   : nfq_open()返回struct nfnl_handle结构的指针;
            buf: 待处理的数据
            len:数据长度
      */
    }
    // 释放队列
    nfq_destroy_queue(qh);

    nfq_close(h);
    return 0;
}




现在是用注册Hook函数的方式来实现这个功能,注册Hook函数时,除了return NF_QUEUE之外,似乎没有其他地方与queue_num有关
如果说不要标记队列序号,难道是所有的数据包都放入序号0的队列之中吗?
可是nfqueue应该是最多支持65536个子队列的
现在就是卡在了这里,请各位大佬指点如何在用户态接收内核通过nf_queeu传过来的数据包
页: [1]
查看完整版本: Linux的Netfilter框架注册Hook函数返回NF_QUEUE时用户态怎么接收