• netfilter的钩子——数据包在内核态得捕获、修改和转发


    转发:http://blog.csdn.net/stonesharp/article/details/27091391

    数据包在内核态得捕获、修改和转发(基于 netfilter)
        忙活了好几天,经过多次得死机和重启,终于把截获的数据包转发的功能给实现了。同时,也吧sk_buff结构学习了一下。
        本程序利用netfilter的钩子函数在PREROUTING处捕获数据包,并且修改数据包首部信息,之后直接转发,从而实现对数据包转发得功能。修改数据包得数据和地址之后,最主要的就是对tcp或dp校验和得计算,内核中有相应得函数,但是调用时要明白各个参数所代表得含义。在本程序中,为了验证对 skb->data指针的理解,本人还试着对截获的数据包进行了push和pull得调用。现拿出来与大家分享。


    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/netfilter.h>
    #include <linux/socket.h>/*PF_INET*/
    #include <linux/netfilter_ipv4.h>/*NF_IP_PRE_FIRST*/
    #include <linux/skbuff.h>
    #include <linux/netdevice.h>
    #include <linux/inet.h> /*in_aton()*/
    #include <net/ip.h>
    #include <net/tcp.h>
    
    #define ETHALEN 14
    
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("bbo");
    
    static struct nf_hook_ops nfho;
    
    unsigned int checksum(unsigned int hooknum,
                          struct sk_buff *__skb,
                          const struct net_device *in,
                          const struct net_device *out,
                          int (*okfn)(struct sk_buff *))
    {
        struct sk_buff *skb;
        struct net_device *dev;
        struct iphdr *iph;
        struct tcphdr *tcph;
        int tot_len;
        int iph_len;
        int tcph_len;
        int ret;
    
        skb = __skb;
        if (skb == NULL)
            return NF_ACCEPT;
    
        iph = ip_hdr(skb);
        if (iph == NULL)
            return NF_ACCEPT;
    
        tot_len = ntohs(iph->tot_len);
    
        if (iph->daddr == in_aton("173.26.100.224"))
        {
            iph_len = ip_hdrlen(skb);/*in ip.h*/
            skb_pull(skb, iph_len); //skb->data指针定位到了传输层
    
            skb_reset_transport_header(skb);/*重置首部长度,现在的首部长度包括了的ip首部长度*/
            if (iph->protocol == IPPROTO_TCP)
            {
                tcph = tcp_hdr(skb);
                tcph_len = tcp_hdrlen(skb);
                if (tcph->dest == htons(3306)) //根据自己得需求来进行过滤数据包
                {
                    iph->saddr = in_aton("1.2.3.4");
                    dev = dev_get_by_name(&init_net, "eth0");
    
                    tcph->check = 0;
                    skb->csum = csum_partial((unsigned char *)tcph, tot_len - iph_len, 0);
                    tcph->check = csum_tcpudp_magic(iph->saddr,
                                                    iph->daddr,
                                                    ntohs(iph->tot_len) - iph_len, iph->protocol,
                                                    skb->csum);
                    iph->check = 0;
                    iph->check = ip_fast_csum(iph, iph->ihl);
    
                    skb->ip_summed = CHECKSUM_NONE;
                    skb->pkt_type = PACKET_OTHERHOST;
                    skb->dev = dev;
                    skb_push(skb, iph_len); /*在返回之前,先将skb中得信息恢复至原始L3层状态*/
                    //skb_reset_transport_header(skb);
    
                    skb_push(skb, ETHALEN);//将skb->data指向l2层,之后将数据包通过 dev_queue_xmit()发出
    
                    ret = dev_queue_xmit(skb);
                    if (ret < 0)
                    {
                        printk("dev_queue_xmit() error
    ");
                        goto out;
                    }
                    return NF_STOLEN;
                }
            }
            skb_push(skb, iph_len); /*在返回之前,先将skb中得信息恢复至原始L3层状态*/
            skb_reset_transport_header(skb);
        }
    
        return NF_ACCEPT;
    out:
        dev_put(dev);
        //free(skb);
    
        return NF_DROP;
    }
    
    static int __init filter_init(void)
    {
        int ret;
        nfho.hook = checksum;
        nfho.pf = AF_INET;
        nfho.hooknum = NF_INET_PRE_ROUTING;
        nfho.priority = NF_IP_PRI_FIRST;
    
        ret = nf_register_hook(&nfho);
        if (ret < 0)
        {
            printk("%s
    ", "can't modify skb hook!");
            return ret;
        }
    
        return 0;
    }
    
    static void filter_fini(void)
    {
        nf_unregister_hook(&nfho);
    }
    
    module_init(filter_init);
    module_exit(filter_fini);
    

      



    module_init(filter_init);
    module_exit(filter_fini);
        本程序是利用了截获得数据包进行得实验,由于数据包中得有原始mac,所以未对数据包得mac进行修改操作。其实,在调用dev_queue_xmit(skb)函数前,大家应该构造得是一个完整得skb,即应该根据需要来对mac进行修改,不过,不需要对L2层进行校验计算。大家如果需要就根据此程序进行修改吧,不算麻烦。
       写程序时我还有一点不明白,就是计算tcp得校验和时

                    tcph->check = 0;
                    skb->csum = csum_partial((unsigned char *)tcph, tot_len - iph_len,0);
                    tcph->check = csum_tcpudp_magic(iph->saddr,
                            iph->daddr,
                            ntohs(iph->tot_len) - iph_len,iph->protocol,
                            skb->csum)

                这样写,正确!

        而另一种计算方式却时错误,如下。如果大家有明白原因的请指点,谢谢!

                    skb->csum = csum_partial((unsigned char *)(tcph + tcph_len) , tot_len - iph_len - tcph_len,0);

                    tcph->check = 0;
                    tcph->check = csum_tcpudp_magic(iph->saddr,
                            iph->daddr,
                            ntohs(iph->tot_len) - iph_len,iph->protocol,
                            csum_partial((unsigned char *)tcph,tcph_len, skb->csum));

           这样写,错误!

    一、构造数据包简析 

    这里并不详细介绍如何在内核中构造数据包,下文如有需要会在适当的位置进行分析。这里简单的分析讲一下内核态基于Netfilter框架构造数据包的方式。 

           内核中可以用到的构造数据包的方式,个人认为可以分为两种。 

    其一,我们直接用alloc_skb申请一个skb结构体,然后根据实际的应用填充不同的成员,或者基于当前数据包的skb,调用skb_copy_expand()函数等新申请一个nskb,并且拷贝skb的内容。 

    其二,也是个人比较常用的,就是直接在先前接收到的数据包skb上作修改,主要有源IP、目IP,如果是TCP/UDP协议的话,还有源端口目的端口号。总之,就是根据自己的需求去调整数据包的相关成员即可。 

    通常,这两种方式最终可能都要涉及到重新计算各个部分的校验和,这也是必须的。 

    二、如何发送构造的数据包 

           承接上文,数据包已经构造完毕,下一步关键就是如何发送数据包了。个人这里总结的有两种方法。 

           方法一,就是让数据包接着按照Netfilter的流程进行传输。因为数据包的一些内容已经被更改,尤其是当源IP和目的IP被更改,主要是交换的情况下,是需要确保有路由可查的。 

           NF框架中查路由的位置一是在PREROUTING之后,而是在LOCALOUT之后。又由于这里是需要将数据包从本地发送出去。因此,可以考虑让修改后的数据包从LOCALOUT点发出。 

           内核代码中有这种方式的典型体现。本文涉及的相关内核代码的版本都是2.6.18.3。源文件为ipt_REJECT.c,函数send_reset用于往当前接收到数据包的源IP上发送RST包,整个函数涉及了数据包的构造和发送,这里一起做个简单分析。 

    Java代码  收藏代码
    1. /* Send RST reply */  
    2.   
    3. static void send_reset(struct sk_buff *oldskb, int hook)  
    4.   
    5. {  
    6.   
    7.     struct sk_buff *nskb;  
    8.   
    9.     struct iphdr *iph = oldskb->nh.iph;  
    10.   
    11.     struct tcphdr _otcph, *oth, *tcph;  
    12.   
    13.     struct rtable *rt;  
    14.   
    15.     u_int16_t tmp_port;  
    16.   
    17.     u_int32_t tmp_addr;  
    18.   
    19.     int needs_ack;  
    20.   
    21.     int hh_len;  
    22.     /* 判断是否是分片包*/  
    23.   
    24.     if (oldskb->nh.iph->frag_off & htons(IP_OFFSET))  
    25.   
    26.         return;  
    27. /*得到TCP头部指针*/  
    28.   
    29.     oth = skb_header_pointer(oldskb, oldskb->nh.iph->ihl * 4,  
    30.   
    31.                  sizeof(_otcph), &_otcph);  
    32.   
    33.     if (oth == NULL)  
    34.   
    35.         return;  
    36.     /* 当期收到的包就是RST包,就不用再发送RST包了*/  
    37.   
    38.     if (oth->rst)  
    39.   
    40.         return;  
    41.     /*检查数据包的校验和是否正确*/  
    42.     if (nf_ip_checksum(oldskb, hook, iph->ihl * 4, IPPROTO_TCP))  
    43.         return;  
    44.     /*这一步比较关键,做的就是更新路由的工作。该函数的主要工作就是将当前数据包的源IP当做路由的目的IP,同时考虑数据包的目的IP,得到去往该源IP的路由*/  
    45.   
    46.     if ((rt = route_reverse(oldskb, oth, hook)) == NULL)  
    47.   
    48.         return;  
    49.     hh_len = LL_RESERVED_SPACE(rt->u.dst.dev);  
    50.     /* 拷贝当前的oldskb,包括skb结构体和数据部分。这就是我们上面提到的构造数据包的第一种方式*/  
    51.   
    52.     nskb = skb_copy_expand(oldskb, hh_len, skb_tailroom(oldskb),  
    53.   
    54.                    GFP_ATOMIC);  
    55.   
    56.     if (!nskb) {  
    57.   
    58.         dst_release(&rt->u.dst);  
    59.   
    60.         return;  
    61.   
    62.     }  
    63.     /*因为是拷贝的oldskb,这里不需要再引用了,因此释放对该路由项的引用*/  
    64.     dst_release(nskb->dst);  
    65.     /*将新构造数据包引用的路由指向上面由route_reverse函数返回的新的路由项 */  
    66.     nskb->dst = &rt->u.dst;  
    67.     /* 清除nskb中拷贝过来的oldskb中链接跟踪相关的内容*/  
    68.   
    69.     nf_reset(nskb);  
    70.   
    71.     nskb->nfmark = 0;  
    72.   
    73.    skb_init_secmark(nskb);  
    74.     /*以下就是构造数据包的实际数据部分。如果我们将这里不为nskb新申请缓冲区,而直接指向oldskb的缓冲区,就使我们上面提到的第二种构造数据包的方法。*/  
    75.   
    76.     /*获取nskb的tcp header*/  
    77.   
    78.     tcph = (struct tcphdr *)((u_int32_t*)nskb->nh.iph + nskb->nh.iph->ihl);  
    79.   
    80.     /*交换源和目的IP */  
    81.   
    82.     tmp_addr = nskb->nh.iph->saddr;  
    83.   
    84.     nskb->nh.iph->saddr = nskb->nh.iph->daddr;  
    85.   
    86.     nskb->nh.iph->daddr = tmp_addr;  
    87.   
    88.     /*交换源和目的端口 */  
    89.   
    90.     tmp_port = tcph->source;  
    91.   
    92.     tcph->source = tcph->dest;  
    93.   
    94.     tcph->dest = tmp_port;  
    95.   
    96.   
    97.     /*重置TCP头部的长度,并修改IP头部中记录的数据包的总长度。因为这里是发送RST报文,只需要有TCP的头部,不需要TCP的数据部分*/  
    98.   
    99.     tcph->doff = sizeof(struct tcphdr)/4;  
    100.   
    101.     skb_trim(nskb, nskb->nh.iph->ihl*4 + sizeof(struct tcphdr));  
    102.   
    103.     nskb->nh.iph->tot_len = htons(nskb->len);  
    104.   
    105.     /*重新设置 seq, ack_seq,分两种情况(TCP/IP详解有描述)*/  
    106.   
    107.     if (tcph->ack) { /*原始数据包中ACK标记位置位的情况*/  
    108.   
    109.         needs_ack = 0;  
    110.   
    111.         tcph->seq = oth->ack_seq; /*原始数据包的ack_seq作为nskb的seq*/  
    112.   
    113.         tcph->ack_seq = 0;  
    114.   
    115.     } else { /*原始数据包中ACK标记位没有置位的情况,初始连接SYN或者结束连接FIN等*/  
    116.   
    117.         needs_ack = 1;  
    118.   
    119.         /*这种情况应该是SYN或者FIN包,由于SYN和FIN包都占用1个字节的长度。因此ack_seq应该等于旧包的seq+1即可。这里之所以这样表示,可能是还存在其他情况的数据包。*/  
    120.   
    121.         tcph->ack_seq = htonl(ntohl(oth->seq) + oth->syn + oth->fin  
    122.   
    123.                       + oldskb->len - oldskb->nh.iph->ihl*4  
    124.   
    125.                       - (oth->doff<<2));  
    126.   
    127.         tcph->seq = 0;  
    128.   
    129.     }  
    130.   
    131.     /* RST标记位置1*/  
    132.   
    133.     ((u_int8_t *)tcph)[13] = 0;  
    134.   
    135.     tcph->rst = 1;  
    136.   
    137.     tcph->ack = needs_ack;  
    138.   
    139.      tcph->window = 0;  
    140.   
    141.     tcph->urg_ptr = 0;  
    142.   
    143.     /*重新计算TCP校验和*/  
    144.   
    145.     tcph->check = 0;  
    146.   
    147.     tcph->check = tcp_v4_check(tcph, sizeof(struct tcphdr),  
    148.   
    149.                    nskb->nh.iph->saddr,  
    150.   
    151.                    nskb->nh.iph->daddr,  
    152.   
    153.                    csum_partial((char *)tcph,  
    154.   
    155.                         sizeof(struct tcphdr), 0));  
    156.   
    157.   
    158.     /* 修改IP包的TTL,并且设置禁止分片*/  
    159.   
    160.     nskb->nh.iph->ttl = dst_metric(nskb->dst, RTAX_HOPLIMIT);  
    161.   
    162.     /* Set DF, id = 0 */  
    163.   
    164.     nskb->nh.iph->frag_off = htons(IP_DF);  
    165.   
    166.     nskb->nh.iph->id = 0;  
    167.   
    168.   
    169.     /*重新计算IP数据包头部校验和*/  
    170.   
    171.     nskb->nh.iph->check = 0;  
    172.   
    173.     nskb->nh.iph->check = ip_fast_csum((unsigned char *)nskb->nh.iph,  
    174.   
    175.                        nskb->nh.iph->ihl);  
    176.   
    177.   
    178.     /* "Never happens" */  
    179.   
    180.     if (nskb->len > dst_mtu(nskb->dst))  
    181.   
    182.         goto free_nskb;  
    183.   
    184. /*使nskb和oldskb的链接记录关联*/  
    185.   
    186.     nf_ct_attach(nskb, oldskb);  
    187. /*这里就是最终发送数据包的方式,具体方法就是让新数据包经过LOACLOUT的hook点,然后查路由,最后经由PREROUTING点,将数据包发送出去。 
    188.  
    189. 其实这里我还是有1个疑问:(1)为什么不可以直接查找路由,而必须先经过LOCALOUT点;*/  
    190.   
    191.     NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, nskb, NULL, nskb->dst->dev,  
    192.   
    193.         dst_output);  
    194.   
    195.     return;  
    196. free_nskb:  
    197.   
    198.     kfree_skb(nskb);  
    199. }  




    其实,这不是丢到了高层,而是和ip_queue_xmit()发送过程意义一样。 
    对这包进行重新路由后,封装了头部,之后,放到了NF_IP_LOCAL_IN之前而已。 

    其实,这里面只要修改了中途修改了ip地址,肯定是需要手动重新路由的。 
    这就涉及到一些比较复杂的route cache的查找,如果没有就去查找route tables;之后,进行路由结构和neighbour结构的关联,就涉及到邻居子系统的相关操作;接着就涉及到arp cache的查找,如果没有,进行一些操作,arp的过程等等,才找到了相关的ip对应的mac信息。

  • 相关阅读:
    SQL优化——union与union all 、left join 和 inner join 及 内连接
    用jdk自带工具jvisualvm查看系统内存使用情况
    不重启也能清理catalina.out日志的方法
    自己用crontab弄个定时任务备份日志重启项目
    三层架构的一点理解以及Dapper一对多查询
    Nuget包添加文件
    WPF启动流程-自己手写Main函数
    WPF实现手势解锁
    VS提交码云权限问题
    Json转Dictionary
  • 原文地址:https://www.cnblogs.com/x_wukong/p/5922779.html
Copyright © 2020-2023  润新知