Chinaunix

标题: 【求助】利用netfilter进行网络数据加密的问题 [打印本页]

作者: zhengli85    时间: 2014-07-15 23:39
标题: 【求助】利用netfilter进行网络数据加密的问题
本帖最后由 zhengli85 于 2014-07-16 01:46 编辑

在CentOS 5.6 x86_64中利用Netfilter对TCP、UDP数据进行加密,但出现一点问题,还请各位帮忙看一下:

一、问题描述
1、该代码在5.2 i386环境下是正常的,所以不知道是不是64位的问题;
2、流入的数据能够正常识别并解密,但是流出的数据出现问题,打印输出后发现从skb获取的数据指向并不是真正需要数据

二、系统环境
[root@localhost ~]# lsb_release -a
LSB Version:    :core-4.0-amd64:core-4.0-ia32:core-4.0-noarch:graphics-4.0-amd64:graphics-4.0-ia32:graphics-4.0-noarch:printing-4.0-

amd64:printing-4.0-ia32:printing-4.0-noarch
Distributor ID: CentOS
Description:    CentOS release 5.6 (Final)
Release:        5.6
Codename:       Final

[root@localhost ~]# uname -a
Linux localhost.local5.6 2.6.18-238.el5 #1 SMP Thu Jan 13 15:51:15 EST 2011 x86_64 x86_64 x86_64 GNU/Linux

三、问题代码
/*******************************************************************************************************
*根据sk_buff、加密类型、数据方向进行加密
*******************************************************************************************************/
static void crypt_ip_data_skb(struct sk_buff *skb,unsigned int crypt_type,int watch_flag)
{
        char  *        data=NULL;       
        unsigned short data_len=0;
        __u8        protocol_type=0;
        struct tcphdr *tcphead=NULL;
        struct udphdr *udphead=NULL;

        int         ip_head_len=0;
        int        tcp_udp_len=0;

#ifdef DEBUG_MODE
        printk(KERN_EMERG"crypt_ip_data_skb\tcrypt_type:%d\twatch_flag:%d\n",crypt_type,watch_flag);
#endif

        if (!skb )return;
        if (!(skb->nh.iph)) return;

        protocol_type=skb->nh.iph->protocol;
        ip_head_len=(skb->nh.iph->ihl * 4);

        switch(protocol_type)
        {
                case        IPPROTO_TCP:
                {
                        tcphead= (struct tcphdr *)(skb->data  + ip_head_len);
                        tcp_udp_len=tcphead->doff*4;
                        data=(unsigned char *)tcphead+tcp_udp_len;
                        data_len=ntohs(skb->nh.iph->tot_len)-ip_head_len-tcp_udp_len;
                        break;
                }
                case        IPPROTO_UDP:
                {
                        udphead= (struct udphdr *)(skb->data  + ip_head_len);       
                        data=(unsigned char *)udphead+8;//sizeof(udphdr)=8
                        data_len=ntohs(udphead->len)-8;
                        break;
                }
                default:
                {
                        return ;
                }
        }

        //根据数据方向进行加减密
        if(watch_flag==fliter_watch_in)
        {
        #ifdef DEBUG_MODE
                printk(KERN_EMERG"fliter_watch_in\tdata:0x%08x\n",(int)data);
        #endif
                decrypt_data_raw(data,data_len,crypt_type);
        }

        if(watch_flag==fliter_watch_out)
        {

        #ifdef DEBUG_MODE
                printk(KERN_EMERG"fliter_watch_out\tdata:0x%08x\n",(int)data);
        #endif

        #ifdef DEBUG_MODE
                printk(KERN_EMERG"%02x%02x%02x%02x\n",data[0],data[1],data[2],data[3]);
        #endif       

                encrypt_data_raw(data,data_len,crypt_type);

        #ifdef DEBUG_MODE
                printk(KERN_EMERG"%02x%02x%02x%02x\n",data[0],data[1],data[2],data[3]);
        #endif
       
        }

        return;
}

四、hook点

   pre_hook.hook     = watch_in;
   pre_hook.pf       = PF_INET;
   pre_hook.priority = NF_IP_PRI_LAST;
   pre_hook.hooknum  = NF_IP_LOCAL_IN;
   
   post_hook.hook     = watch_out;
   post_hook.pf       = PF_INET;
   post_hook.priority = NF_IP_PRI_LAST;
   post_hook.hooknum  = NF_IP_LOCAL_OUT;
   
   nf_register_hook(&pre_hook);
   nf_register_hook(&post_hook);
作者: zhengli85    时间: 2014-07-16 01:43
本帖最后由 zhengli85 于 2014-07-16 01:45 编辑

对代码部分代码更改如下,进行数据输出显示,发现:
1、在5.2 i386下,通过sk_buff的data字段进行数据获取,根据ip头+tcp/udp头+传输数据的组包方式可获取传输数据
2、在5.6  x86_64下,通过sk_buff的data字段进行数据获取,数据只有ip头+tcp/udp头,传输数据没有紧跟其后,因此根据ip头+tcp/udp头+传输数据的组包方式无法正确获取传输数据,因此造成加密不正常
3、5.2 i386下的内核版本是
[root@localhost kernel]# uname -a
Linux localhost.localdomain 2.6.18-92.el5xen #1 SMP Tue Apr 29 13:45:57 EDT 2008 i686 i686 i386 GNU/Linux
4、初步怀疑是由于内核版本升级,sk_buff结构组织变化导致


        if(watch_flag==fliter_watch_out)
        {
        //#ifdef DEBUG_MODE
                printk(KERN_EMERG"fliter_watch_out\n");
                printk(KERN_EMERG"data:0x%08x\n",data);
                printk(KERN_EMERG"skb_head:0x%08x\tskb_data:0x%08x\tskb_tail:0x%08x\tskb_end:0x%08x\n",skb->head,skb->data,skb->tail,skb->end);

                for(i=0;i<30;i++)//取30这个数,纯粹是跟测试数据相关,由于调试用的
                {
                        printk(KERN_EMERG"skb_data:%02x%02x%02x%02x\n",skb->data[i*4],skb->data[i*4+1],skb->data[i*4+2],skb->data[i*4+3]);
                }
        //#endif               

                //encrypt_data_raw(data,data_len,crypt_type);
        }
作者: zhengli85    时间: 2014-07-16 02:01
同一通信过程:
IP头:20字节
TCP头:32字节
数据:13字节

输出sk_buff的几个字段进行验证
unsigned char                *head,
                        *data,
                        *tail,
                        *end

1、5.2 i386下
skb_head:0xeb7edc00     skb_data:0xeb7edccc     skb_tail:0xeb7edd0d     skb_end:0xeb7ede00
skb_data+20+32+13=skb_tail

ip头+tcp/udp头+传输数据

2、在5.6  x86_64下
skb_head:0x12b21000     skb_data:0x12b210cc     skb_tail:0x12b21100     skb_end:0x12b21100
skb_data+20+32=skb_tail

ip头+tcp/udp头
作者: humjb_1983    时间: 2014-07-16 12:41
请看看end之后是否放了你的数据?
如果使用了sg,那么数据是放在skb_shared_info中的。
另外,对于包头或相关数据的解析,建议使用内核提供的标准接口,新版本中32和64位中skb中的数据解析是不一样的。
作者: zhengli85    时间: 2014-07-16 15:35
回复 4# humjb_1983


多谢您的提示与建议
见笑了,对Linux不熟悉,只是做个应急的东西,参考网上的文章依葫芦画瓢写的。

1、关于“请看看end之后是否放了你的数据?”

在测试里,多打印了一部分end之后的数据,并未发现有我的数据。


2、关于“如果使用了sg,那么数据是放在skb_shared_info中的。”

正在看skb_shared_info的信息,请问是否使用sg是如何判断的?


3、关于“另外,对于包头或相关数据的解析,建议使用内核提供的标准接口,新版本中32和64位中skb中的数据解析是不一样的。”

一开始做,以为网络数据是标准的,就按协议自己解析了,在5.2 i386里没发现问题,也就没改动,解决上述问题后一并再修改。


作者: humjb_1983    时间: 2014-07-16 16:24
zhengli85 发表于 2014-07-16 15:35
回复 4# humjb_1983

ethtool -k ethx
可以看到指定网卡是否开启了sg。
作者: zhengli85    时间: 2014-07-16 16:43
回复 6# humjb_1983

这是没有开启sg选项吧?
[root@localhost ~]# ethtool -k ethxOffload parameters for ethx:
Cannot get device rx csum settings: No such device
Cannot get device tx csum settings: No such device
Cannot get device scatter-gather settings: No such device
Cannot get device tcp segmentation offload settings: No such device
Cannot get device udp large send offload settings: No such device
Cannot get device generic segmentation offload settings: No such device
Cannot get device GRO settings: No such device
no offload info available



作者: humjb_1983    时间: 2014-07-16 17:57
呵呵,要把ethx换成实际的网卡名,比如eth0

作者: zhengli85    时间: 2014-07-16 21:30
回复 8# humjb_1983


   呵呵
[root@localhost ~]# ethtool -k eth0
Offload parameters for eth0:
Cannot get device udp large send offload settings: Operation not supported
rx-checksumming: on
tx-checksumming: on
scatter-gather: on
tcp segmentation offload: on
udp fragmentation offload: off
generic segmentation offload: off
generic-receive-offload: off



作者: zhengli85    时间: 2014-07-17 11:45
本帖最后由 zhengli85 于 2014-07-21 21:04 编辑

回复 4# humjb_1983


   多谢指教,通过skb_shared_info确实能够获取数据部分===================================================================================

        printk(KERN_EMERG"the skb have fragment counts:%d\n",skb_shinfo(skb)->nr_frags);
        if(skb_shinfo(skb)->nr_frags>0)//本skb中其他页中的数据部分
        {
                printk(KERN_EMERG"skb_shinfo(skb)->nr_frags>0\n" ) ;
                for(i=0;i< skb_shinfo(skb)->nr_frags;i++)
                {
                        printk(KERN_EMERG"the skb frags[%d] page_offset is:%d\n",i,skb_shinfo(skb)->frags[ i ].page_offset);
                        printk(KERN_EMERG"the skb frags[%d] size is:%d\n",i,skb_shinfo(skb)->frags[ i ].size);
                        vaddr=kmap_skb_frag(&skb_shinfo(skb)->frags);
                        print_data_raw(vaddr + skb_shinfo(skb)->frags[ i ].page_offset,skb_shinfo(skb)->frags[ i ].size);//自己的二进制输出函数
                }
       }

        if(skb_shinfo(skb)->frag_list)//其他碎片skb中的数据部分
                printk(KERN_EMERG"the skb have frag list\n" ) ;


===================================================================================

另:用skb_linearize(skb)将skb合并成一个,也能获取其数据部分,然后对数据部分进行加密,但发送出去的还是未加密数据,而加密后输出内存数据显示是加密了的,这是因为我获取的只是一个副本的原因吗?

作者: humjb_1983    时间: 2014-07-17 14:03
用skb_linearize(skb)将skb合并成一个,也能获取其数据部分,然后对数据部分进行加密,但发送出去的还是未加密数据,而加密后输出内存数据显示是加密了的,这是因为我获取的只是一个副本的原因吗?
---能把相关代码贴来看看么?谢谢!
作者: zhengli85    时间: 2014-07-17 15:18
回复 11# humjb_1983


if(skb_shinfo(skb)->nr_frags>0)//在一个页里为0{
                if (0 != skb_linearize(skb))//对于多个fragment的情况,将其合并
                {
                //#ifdef DEBUG_MODE
                        printk(KERN_EMERG"skb_linearize(sb) failed\n");
                //#endif
                                return NF_ACCEPT;
                }
}

crypt_ip_data_skb(skb,crypt_type,watch_flag);


/*******************************************************************************************************
*根据sk_buff、加密类型、数据方向进行加密
*******************************************************************************************************/
static void crypt_ip_data_skb(struct sk_buff *skb,unsigned int crypt_type,int watch_flag)
{
  unsigned        char  *data=NULL;
        unsigned short data_len=0;
        __u8                                 protocol_type=0;
        struct tcphdr *tcphead=NULL;
        struct udphdr *udphead=NULL;

        int         ip_head_len=0;
        int        tcp_udp_len=0;

#ifdef DEBUG_MODE
        printk(KERN_EMERG"crypt_ip_data_skb\tcrypt_type:%d\twatch_flag:%d\n",crypt_type,watch_flag);
#endif

        if (!skb )return;
        if (!(skb->nh.iph)) return;
        protocol_type=skb->nh.iph->protocol;
        ip_head_len=(skb->nh.iph->ihl * 4);
       
        switch(protocol_type)
        {
                case        IPPROTO_TCP:
                {
                        tcphead= (struct tcphdr *)(skb->data  + ip_head_len);
                        tcp_udp_len=tcphead->doff*4;
                        data=(unsigned char *)tcphead+tcp_udp_len;
                        data_len=ntohs(skb->nh.iph->tot_len)-ip_head_len-tcp_udp_len;
                        break;
                }

                case        IPPROTO_UDP:
                {
                        udphead= (struct udphdr *)(skb->data  + ip_head_len);       
                        data=(unsigned char *)udphead+8;//sizeof(udphdr)=8
                        data_len=ntohs(udphead->len)-8;
                        break;
                }

                default:
                {
                        return ;
                }
        }

        //根据数据方向进行加减密
        if(watch_flag==fliter_watch_in)
        {
        #ifdef DEBUG_MODE
                printk(KERN_EMERG"fliter_watch_in\tdata:0x%08x\tskb_data:0x%08x\n",data,skb->data);
        #endif

                decrypt_data_raw(data,data_len,crypt_type);
        }

        if(watch_flag==fliter_watch_out)
        {
        //#ifdef DEBUG_MODE
                printk(KERN_EMERG"fliter_watch_out\n");
                printk(KERN_EMERG"the skb have fragment counts:%d\n",skb_shinfo(skb)->nr_frags);//测试中skb_shinfo(skb)->nr_frags=0,合并成功
                printk(KERN_EMERG"data:0x%08x\n",data);
                printk(KERN_EMERG"skb_head:0x%08x\tskb_data:0x%08x\tskb_tail:0x%08x\tskb_end:0x%08x\n",skb->head,skb->data,skb->tail,skb->end);

                printk(KERN_EMERG"tcp_udp_len:%d\tdata_len:%d\n",tcp_udp_len,data_len);               
                printk(KERN_EMERG"skb_len:%d\tskb_data_len:%d\n",skb->len,skb->data_len);

        //#endif
                print_data_raw(skb->data+ip_head_len+tcp_udp_len,data_len);//输出明文
               
                encrypt_data_raw(skb->data+ip_head_len+tcp_udp_len,data_len,crypt_type);
               
                print_data_raw(skb->data+ip_head_len+tcp_udp_len,data_len);//输出密文
        }
       
        return;
}

作者: wan3610425    时间: 2014-07-17 16:17
可以把hook点LOCAL_OUT的优先级提高为最高NF_IP_PRI_FIRST,直接按照你改之前的代码试下,,
作者: humjb_1983    时间: 2014-07-17 17:00
“用skb_linearize(skb)将skb合并成一个,也能获取其数据部分,然后对数据部分进行加密,但发送出去的还是未加密数据,而加密后输出内存数据显示是加密了”
----如果这个能确认的话,那就应该不是这个流程的问题了,应该是后面发送流程的问题了,可以打点跟一下后面的发送流程中的数据是否是从“加密后的数据”中取的。。。
作者: zhengli85    时间: 2014-07-17 21:09
回复 13# wan3610425


   感谢,我试试看
作者: zhengli85    时间: 2014-07-17 21:11
回复 14# humjb_1983


   嗯,我再看看,看这么长的函数体没晕到你吧?
作者: zhengli85    时间: 2014-07-17 22:15
本帖最后由 zhengli85 于 2014-07-17 22:16 编辑

回复 13# wan3610425

单纯的把hook点LOCAL_OUT的优先级设为NF_IP_PRI_FIRST,并没有解决问题,反而是回到问题的原点,传输的数据在其他分页里

pre_hook.hook     = watch_in;
pre_hook.pf       = PF_INET;
pre_hook.priority = NF_IP_PRI_LAST;
pre_hook.hooknum  = NF_IP_LOCAL_IN;

post_hook.hook     = watch_out;
post_hook.pf       = PF_INET;
post_hook.priority = NF_IP_PRI_FIRST;
post_hook.hooknum  = NF_IP_LOCAL_OUT;

nf_register_hook(&pre_hook);
nf_register_hook(&post_hook);


作者: wan3610425    时间: 2014-07-18 14:48
不好意思,我理解错了,以为是ip分段导致的,然后又误以为ip分段是在LOCAL_OUT点完成的。就建议你改优先级了

刚才看了下这个聚合分散,有点想法,不知道对你有用没。首先这个skb_linearize(skb)是在将分散片拷贝到线性区,你对线性区进行加密,底层发送的时候是否还是对分散片发送呢?
然后既然选择了利用这种GSO机制,你对每个报文又重新拷贝到线性区,这个工作有点相违背吧,我觉得可以直接访问到分片中,对分片进行加密。
作者: zhengli85    时间: 2014-07-20 21:39
回复 18# wan3610425


   感谢您的关注与指点
1、在10楼里提到了,通过skb_shared_info直接在分片里获取数据并加密,但是发送出去的也是明文。

2、后面提到的用skb_linearize(skb)将分散片拷贝到线性区然后加密,您说的有道理,实际也是不行的,贴出代码只是对11楼的回应。

再次感谢,还在继续分析。





欢迎光临 Chinaunix (http://bbs.chinaunix.net/) Powered by Discuz! X3.2