一、Linux网络部分代码分析
Linux网络层采用统一的缓冲区结构skbuff,一个个单独的skbuff被组织成双向链表的形式。网卡接收到数据帧后,系统内核为接收到的数据帧分配一块内存,然后将数据整理成skbuff的结构.在网络协议处理的时候,数据均以skbuff的形式在各层之间传递、处理。
skbuff的强大功能在于它提供了众多指针,可以快速的定位协议头位置;它也同时保留了许多数据包信息(如使用的网络设备等),以便协议层根据需要灵活应用.
在IP协议层有三个关键函数:ip_rcv( )、ip_forward( )、ip_output( ),分别处理IP层的接收、转发和发送工作。防火墙的功能函数将在此三个函数中调用。
二、包过滤
包过滤主要工作于IP层。Linux在应用层利用ipchains( )来实现对包过滤的实现。可以用该函数实现过滤规则的添加、删除、设置、更改,在对包过滤规则进行设置的同时,还可以指定对数据包进行IP伪装。在Linux 内核中有三条内置的规则链(input chain, forward chain, output chain),分别对应接收检测,转发检测和发送检测(内置链不可删除),每一条chain 包含一系列过滤规则及链的缺省策略.利用各规则链可以对输入、转发、和输出的数据包进行过滤。
其实现过程如下:
*在不同检测点进入相应过滤链。
*顺序检查每一条过滤规则,找出与之匹配的规则(ACCEPT, REJECT, DENY, MASQ,REDICT,RETURN).
*当遇到第一条匹配的规则时采取以下行动:
a. 将规则应用于此数据包;
b. 每条规则都包含有packet和byte数的计数器;
c. 如果设置记录功能,则记录。
*当没有规则匹配时,采用链的缺省策略。
具体流程结合代码来说:
三种内置规则链分别作用于对应的三个函数ip_rcv( )、ip_forward( )和ip_output( )。
1、ip_rcv()是IP层的接收函数,由它来处理网卡接收到的数据包,它首先检查:
a.长度是否正确;
b.版本号是否正确(是IPV4还是IPV6?);
c.校验和是否正确。
在确定这些信息无误后,则调用包过滤检测:
fwres = call_in_firewall(PF_INET, dev, iph, &rport, &skb);
call_in_firewall 会对输入的数据包进行规则检查,fwres返回的便是匹配出来的规则.如果对应的规则为不接受该数据包,则立即将此数据包丢弃:
if (fwres < FW_ACCEPT && fwres != FW_REJECT)
goto drop;
如果规则允许接受则查找路由表,对输入的数据包进行路由:
ip_route_input(skb, iph->daddr, iph->saddr, iph->tos, dev)
此时路由信息已包括在了skbuff数据结构的dst 项中,紧接着调用skb->dst->input(skb) 继续处理。对发往本地高层协议的包,则调用ip_local_deliver(),进行处理。对转往其他主机的包,则实际调用ip_forward()处理。值得注意的是, 经伪装的包在回来时,其目的IP是防火墙的IP,经路由后,也送入ip_local_deliver( )处理,在ip_local_deliver ( )内部先解伪装,然后再查一次路由,发往本地的直接送往高层,,否则依然调用ip_forward( )。
2、ip_forward( )用来处理发往其他主机的数据包,其函数流程为:
1)、因为ip_forward()接收的参数是一个skbuff,它首先利用skbuff的指针,把IP头找出: iph = skb->nh.iph
2)、因为ip_forward()由ip_rcv()调用,而在ip_rcv()中已查过了路由,此处只需利用skbuff的指针定位路由信息即可:
struct rtable *rt; /* Route we use */
rt = (struct rtable*)skb->dst;
3)、如果此IP包的生存时间(ttl)已到,则丢弃。
if (iph->ttl <= 1)
goto too_many_hops;
4)、如果在选项中指定了严格的源路由功能(strict source routing) ,且此处无法达到,也丢弃:
if (opt->is_strictroute && rt->rt_dst != rt->rt_gateway)
goto sr_failed;
5)、如果指定的伪装功能,且上层协议是ICMP,则在此处处理一部分,且跳过后面的包过滤处理
#ifdef CONFIG_IP_MASQUERADE
if(!(IPCB(skb)->flags&IPSKB_MASQUERADED)) {
if (iph->protocol == IPPROTO_ICMP) {
........
fw_res = ip_fw_masq_icmp(&skb, maddr);
if (fw_res)
/* ICMP matched - skip firewall */
goto skip_call_fw_firewall;
........
}
}
#endif
6)、如果上一步的前提不成立,则要经过一次包过滤。
fw_res=call_fw_firewall(PF_INET, dev2, iph, NULL, &skb);
7)、在当前linux版本中,包过滤与伪装功能在许多地方是紧密联系在一起的,如采用同样的配置工具ipchains,同样的配置接口setsocketopt(),其中是否启动伪装的标志也在防火墙的chains中.
8)、因为伪装可能改变了skbuff的一些信息,此时要重新定位一下IP头及其选项:
iph = skb->nh.iph;
opt = &(IPCB(skb)->opt);
9)、因为转发的数据总是要送出的,紧接着会调用call_out_firewall(),并把数据送出去.
3、在ip_local_deliver()中.
1).如果需要,首先重组IP包:
if (sysctl_ip_always_defrag == 0 &&
(iph->frag_off & htons(IP_MF|IP_OFFSET))) {
skb = ip_defrag(skb);
if (!skb)
return 0;
iph = skb->nh.iph;
2).然后调用ip_fw_demasquerade解开IP伪装。
ret = ip_fw_demasquerade(&skb);
3)、再次调用路由查找,根据真正的IP来发送此包。
ip_route_input(skb, iph->daddr, iph->saddr, iph->tos, skb->dev)
4)、根据路由发往上层或是转发(略)。
在对数据包进行处理的全部过程中,分别调用了以下三个函数:call_in_firewall(), call_fw_firewall()和 call_out_firewall()。察看这三个函数的具体实现发现,其核心过程都在于ip_fw_check() 这个函数,它完成了数据包与规则的实际匹配。
ip_fw_check()函数分析:防火墙的规则链由ip_chain数据结构描述,其中包含了指向链中第一条规则的指针和链的缺省策略。规则链中的每条规则由ip_fwkernel数据结构描述。ip_fw_check()所做的就是将每一个ip包与规则链中的每一条规则(实际就是ip_fw数据结构所描述的内容)按照链表的组织顺序一一比较,若匹配则并返回规则的行动项。
结束语
以上简单的介绍了linux下实现包过滤的基本方法,希望对大家起到抛砖引玉的作用。