linux下使用iptables ulog+netlink在内核中抓取特定数据包

来源:互联网 发布:阿里云服务器能干嘛 编辑:程序博客网 时间:2024/05/16 08:22
前言

    iptables是linux下基于Netfilter框架实现的防火墙软件。通过iptables我们可以方便的对内核中流动的数据包进行一些处理。iptables拥有强大的log(数据包记录)功能,可以把按特定规则匹配的ip数据包以log的形式传递到用户层供用户分析,这样我们就可以非常方便的了解内核中都有哪些ip数据包在传递以及这些数据包的内容。
    iptables有三种log记录形式,分别是log、ulog、nflog。log用于将匹配的数据包记录到系统的syslog中去,用户也可以直接通过dmesg命令查看。log命令只记录包头的一些。ulog通过netlink套接字将数据包多播到指定netlink多播组,这样任何感兴趣的进程都可以通过建立netlink套接字来接受内核中的数据包信息。ulog可以将整个数据包拷贝并发送给应用程序,当然也可以指定发送方数据包的字节数。nflog类似于ulog,但是功能更加强大。nflog不仅可以接受来自iptables的数据,还可以向iptables通过netlink套接字发送控制数据。本文重点讲ulog。

一、iptables中的ulog设定

    可以使用--ulog在某个链上添加ulog规则。
    例如:iptables -A INPUT -p TCP --dport 22 -j ULOG --ulog-nlgroup 2
    在input链上把所有目的端口号为22的数据包发送到netlink多播组2中。
    ulog还有以下几个选项:
    --ulog-nlgroup:指定向哪个netlink组发送包,比如-- ulog-nlgroup 5。一个有32个netlink组,它们被简单地编号位1-32。默认值是1。
    --ulog-prefix:指定记录信息的前缀,以便于区分不同的信息。使用方法和 LOG的prefix一样,只是长度可以达到32个字符。
    --ulog-cprange:指定每个包要向“ULOG在用户空间的代理”发送的字节数, 如--ulog-cprange 100,表示把整个包的前100个字节拷贝到用户空间记录下来,其 中包含了这个包头,还有一些包的引导数据。默认值是0,表示拷贝整个包,不管它有多大。
    --ulog-qthreshold:告诉ULOG在向用户空间发送数据以供记录之前,要在内核里 收集的包的数量(译者注:就像集装箱),如--ulog-qthreshold 10。这表示先在 内核里积聚10个包,再把它们发送到用户空间里,它们会被看作同一个netlink的信息,只是由好几部分组 成罢了。默认值是1,这是为了向后兼容,因为以前的版本不能处理分段的信息。

二、netlink应用层程序的编写

    写完iptbales规则后,还需要对应的应用层程序才能得到iptbales发来的数据包。ulogd程序可以非常方便的接受iptables发来的ulog并将数据记录在文件或者MySQL中。但是笔者更喜欢自己写程序按照自己的需求接受并分析iptables发来的数据包。
    在写应用层程序之前我们首先添加iptables规则:
    iptables -A INPUT -p icmp -j ULOG --ulog-nlgroup 6 --ulog-prefix "ulog-test" --ulog-cprange 100
    这条规则会将所有发送到本机的icmp数据包多播到netlink组6。下面开始写程序:
    首先要创建一个netlink套接字:
        int sockaddr_nl_len=sizeof(struct sockaddr_nl);        struct sockaddr_nl local; //创建netlink地址结构体,该结构体在linux/netlink.h中        struct sockaddr_nl kpeer;        int nf_sock=socket(AF_NETLINK, SOCK_RAW, NETLINK_NFLOG);        if(nf_sock<0){                perror("nf_sock create error:");                return -1;        }        memset(&kpeer, 0, sizeof(kpeer));        memset(&local, 0, sizeof(local));        local.nl_family = AF_NETLINK;        local.nl_pid = getpid();        local.nl_groups = 6;//这里填上之前iptables规则中写的组号

    之后绑定套接字:
        if(bind(nf_sock, (struct sockaddr *)&local, sizeof(local)) != 0){                perror("nf_sock bind() error:");                return -1;        }

    需要注意的是因为默认情况下用户层netlink只允许监听组号为1的组,所以这里还需要设置套接字选项使得我们的套接字可以监听组号为6的netlink组。
        int group=6;        setsockopt(nf_sock,270,NETLINK_ADD_MEMBERSHIP, &group, sizeof(group));

    设置为套接字后就可以接受数据了:
                ret = recvfrom(nf_sock, buf, 2048,0, (struct sockaddr*)&kpeer, &sockaddr_nl_len);                if(!ret){                        perror("recv form kerner:");                        exit(-1);                }

    iptables发送的netlink数据包有两个包头,分别是nlmsghdr和ulog_packet_msg_t,要解析iptables的ulog数据包首先要分析这两个包头。
    任何netlink数据都会有一个nlmsghdr头,该数据结构在linux/netlink.h中定义,感兴趣的可以自己去看头文件:
                struct nlmsghdr *nlmh;    将nlmh指针指向数据缓存,这样可以读取nlmsghdr头的内容                nlmh=(struct nlmsghdr *)buf;                printf("**************recv %d msg:\n nlmsg_len:%d,nlmsg_type:%d \n",ret,nlmh->nlmsg_len,nlmh->nlmsg_type);    接着是由iptables专门为ulog定义的ulog_packet_msg_t结构包头,该结构在<linux/netfilter_ipv4/ipt_ULOG.h>中定义<span style="font-family: 微软雅黑; font-size: 14px;">(注意:如果提示找不到宏IFNAMSIZ,则可以添加包含头文件<linux/netdevice.h>)</span>:                nlmh++; //跳过nlmh包头,直接定位到下一个包头即ulog_packet_msg_t包头                ulog_packet_msg_t * ulog_msg;                ulog_msg=(ulog_packet_msg_t *)nlmh; //将ulog_msg指针指向ulog_packet_msg_t包头在数据包缓存中的位置                printf("mark:%d,timestamp_sec:%ld,prefix:%s\n",ulog_msg->mark,ulog_msg->timestamp_sec,ulog_msg->prefix); //timestamp代表时间戳,prefix则是之前在规则中写的前缀

    ulog包头中的payload指针指向我们真正要抓取到的IP数据包。可以通过以下代码得到ip包的包头,剩下的就是常规的ip数据包分析了,如果对ip数据包分析感兴趣的人可以参看我的另一篇博文http://blog.csdn.net/l1902090/article/details/25913351:
 
              struct iphdr *iph=(struct iphdr *)ulog_msg->payload;                struct in_addr saddr;                saddr.s_addr=iph->saddr;                printf("saddr:%s,ihl:%d\n",inet_ntoa(saddr),iph->ihl);                printf("protocol:");                switch(iph->protocol)                {                        case IPPROTO_TCP:printf("TCP\n");break;                        case IPPROTO_UDP:printf("UDP\n");break;                        case IPPROTO_ICMP:printf("ICMP\n");break;                        default:printf("unknown protocol\n");                }                if(iph->protocol==IPPROTO_TCP||iph->protocol==IPPROTO_UDP)                {                        struct tcphdr *tcph;                        tcph=(struct tcphdr *)((void *)iph+4*(iph->ihl));                        printf("dest port:%d,source port:%d\n",ntohs(tcph->dest),tcph->source);                }

0 0
原创粉丝点击