防火墙技术分析讲义
yawl@docshow.net
一 基本概念
1.1 防火墙分类:
包过滤
代理(应用层网关)
1.2 代理:
两个连接(browser与proxy之间,proxy与web server之间)。
工作在应用层。
直接发往服务器的包:
GET / HTTP/1.1
Accept: */*
Accept-Language: zh-cn
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)
Host: www.lisoleg.net
Connection: Keep-Alive
往代理发出的包:
GET http://www.lisoleg.net/ HTTP/1.1
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-powerpoint, application/msword, */*
Accept-Language: zh-cn
Accept-Encoding: gzip, deflate
If-Modified-Since: Thu, 14 Dec 2000 07:24:52 GMT
If-None-Match: "8026-185-3a3875c4"
User-Agent: Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)
Host: www.lisoleg.net
Proxy-Connection: Keep-Alive
增强:
cache
1.3 包过滤
单IP包检测
缺陷:无状态
1.4 增强1-状态检测(Stateful Inspection),又称动态包过滤(dynamic packet filtering)
1.4.1 规则表和动态状态表
1.4.2 ftp的例子:
A 4847->B 21 PORT 192,168,7,60,18,241
B 21->A 4847 PORT command successful.
B 20->A 4849 syn
> A classic example is transferring files using FTP. The firewall remembers the details of the
> incoming request to get a file from an FTP server. The firewall then tracks the back-channel
> request (the FTP Port command) by the server for transferring information back to the client.
> As long as the information agrees (same IP addresses, no changes in port numbers, and no
> non-FTP requests), the firewall allows the traffic. After the transfer is complete, the
> firewall closes the ports involved.
1.4.3 两种实现方法:
1.4.3.1 checkpoint FW1,netfilter
1.4.3.2 动态添加规则(ipchains patch)
> I believe it does exactly what I want: Installing a temporary
> "backward"-rule to let packets in as a response to an
> outgoing request.
1.5 增强2-地址转换:
1.5.1 静态NAT
1.5.2 动态NAT
1.5.3 地址伪装
1.6 增强3-VPN:
位置的优越性
二 Linux下防火墙的实现之一(2.2内核):
2.1 截获位置:
网络层
----------------------------------------------------------------
| ACCEPT/ lo interface |
v REDIRECT _______ |
--> C --> S --> ______ --> D --> ~~~~~~~~ -->|forward|----> _______ -->
h a |input | e {Routing } |Chain | |output |ACCEPT
e n |Chain | m {Decision} |_______| --->|Chain |
c i |______| a ~~~~~~~~ | | ->|_______|
k t | s | | | | |
s y | q | v | | |
u | v e v DENY/ | | v
m | DENY/ r Local Process REJECT | | DENY/
| v REJECT a | | | REJECT
| DENY d --------------------- |
v e -----------------------------
DENY
2.2 提炼出的代码:
输入检测:
/*
* Main IP Receive routine.
*/
int ip_rcv(struct sk_buff *skb, struct device *dev, struct packet_type *pt)
{
#ifdef CONFIG_FIREWALL
int fwres;
u16 rport;
#endif /* CONFIG_FIREWALL */
......
#ifdef CONFIG_FIREWALL
/*
* See if the firewall wants to dispose of the packet.
*
* We can't do ICMP reply or local delivery before routing,
* so we delay those decisions until after route. --RR
*/
fwres = call_in_firewall(PF_INET, dev, iph, &rport, &skb);
if (fwres < FW_ACCEPT && fwres != FW_REJECT)
goto drop;
iph = skb->nh.iph;
#endif /* CONFIG_FIREWALL */
......
#ifdef CONFIG_FIREWALL
if (fwres == FW_REJECT) {
icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0);
goto drop;
}
#endif /* CONFIG_FIREWALL */
return skb->dst->input(skb); //根据路由查找的结果决定是转发(ip_forward)还是发往上层(ip_local_deliver)
drop:
kfree_skb(skb); //如果规则匹配的结果是FW_REJECT,FW_BLOCK,丢弃此包
return(0);
}
转发检测:
int ip_forward(struct sk_buff *skb)
{
...
#ifdef CONFIG_FIREWALL
fw_res=call_fw_firewall(PF_INET, dev2, iph, NULL, &skb);
switch (fw_res) {
case FW_ACCEPT:
case FW_MASQUERADE:
break;
case FW_REJECT:
icmp_send(skb, ICMP_DEST_UNREACH, ICMP_HOST_UNREACH, 0);
/* fall thru */
default:
kfree_skb(skb);
return -1;
}
#endif
...
}
输出检测:(不同的上层协议走不同的流程,因此检测点较多)
UDP/RAW/ICMP报文:ip_build_xmit
TCP报文:ip_queue_xmit
转发的包:ip_forward
其他:ip_build_and_send_pkt
实际的匹配:
/*
* Returns one of the generic firewall policies, like FW_ACCEPT.
*
* The testing is either false for normal firewall mode or true for
* user checking mode (counters are not updated, TOS & mark not done).
*/
static int
ip_fw_check(struct iphdr *ip, //IP头位置
const char *rif, //出口网卡的名字
__u16 *redirport, //端口转发时用到
struct ip_chain *chain, //规则链的名字
struct sk_buff *skb, //要检测的数据包
unsigned int slot,
int testing) //见函数本身的注释
调用举例:
call_in_firewall实际调用ipfw_input_check,而ipfw_input_check中有:
int ipfw_input_check(struct firewall_ops *this, int pf, struct device *dev,
void *phdr, void *arg, struct sk_buff **pskb)
{
return ip_fw_check(phdr, dev->name,
arg, IP_FW_INPUT_CHAIN, *pskb, SLOT_NUMBER(), 0);
}
实际流程:
ip_fw_check
{
从传入的skb参数中提取源地址src,目的地址dst,源端口src_port,目的端口dst_port,
TCP发起连接标志tcpsyn,分片包位移offset,IP包TOS消息oldtos;
......
f = chain->chain; //取出规则链的的一条规则,规则链由chain参数传入
count = 0;
do {
for (; f; f = f->next) { //遍历规则链中的规则,直到匹配(ip_rule_match返回1)
count++;
if (ip_rule_match(f,rif,ip,
tcpsyn,src_port,dst_port,offset)) {
if (!testing
&& !ip_fw_domatch(f, ip, rif, chain->label,//作些标记,一般返回1
skb, slot,
src_port, dst_port,
count, tcpsyn)) {
ret = FW_BLOCK;
goto out;
}
break;
}
}
if(f) { //找到匹配规则
......
}else { //这次遍历根本没找到
是从别的地方跳转过来的,则转回去,然后继续遍历;
否则应用这条链的缺省规则;
}
} while (ret == FW_SKIP+2);
out:
......
return ret;
}
碎片:
根据第一个片的消息进行过滤,其他分片则允许通过。如果规则是丢弃的话,虽然后面的分片都可到达主机,
但由于第一片被滤掉了,无法重组成功,因此从效果上也相当于整个IP包被丢弃。
存在的漏洞等.
2.3 规则:
from 192.168.7.0/24 to 192.168.6.32/32 tcp 80 BLOCK
规则的数据结构表示:
规则链
struct ip_chain
{
ip_chainlabel label; /* Defines the label for each block */
struct ip_chain *next; /* Pointer to next block */
struct ip_fwkernel *chain; /* Pointer to first rule in block */
__u32 refcount; /* Number of refernces to block */
int policy; /* Default rule for chain. Only *
* used in built in chains */
struct ip_reent reent[0]; /* Actually several of these */
};
规则
struct ip_fwkernel
{
struct ip_fw ipfw;
struct ip_fwkernel *next; /* where to go next if current
* rule doesn't match */
struct ip_chain *branch; /* which branch to jump to if
* current rule matches */
int simplebranch; /* Use this if branch == NULL */
struct ip_counters counters[0]; /* Actually several of these */
};
待匹配的数据包消息
struct ip_fw
{
struct in_addr fw_src, fw_dst; /* Source and destination IP addr */
struct in_addr fw_smsk, fw_dmsk; /* Mask for src and dest IP addr */
__u32 fw_mark; /* ID to stamp on packet */
__u16 fw_proto; /* Protocol, 0 = ANY */
__u16 fw_flg; /* Flags word */
__u16 fw_invflg; /* Inverse flags */
__u16 fw_spts[2]; /* Source port range. */
__u16 fw_dpts[2]; /* Destination port range. */
__u16 fw_redirpt; /* Port to redirect to. */
__u16 fw_outputsize; /* Max amount to output to
NETLINK */
char fw_vianame[IFNAMSIZ]; /* name of interface "via" */
__u8 fw_tosand, fw_tosxor; /* Revised packet priority */
};
2.4 地址转换
ip_fw_demasquerade
ip_fw_masquerade
三 Linux下防火墙的实现之二(2.4内核):
3.1
A Packet Traversing the Netfilter System:
--->PRE------>[ROUTE]--->FWD---------->POST------>
Conntrack | Filter ^ NAT (Src)
Mangle | | Conntrack
NAT (Dst) | [ROUTE]
(QDisc) v |
IN Filter OUT Conntrack
| Conntrack ^ Mangle
| | NAT (Dst)
v | Filter
3.2 例子
## Insert connection-tracking modules (not needed if built into kernel).
# insmod ip_conntrack
# insmod ip_conntrack_ftp
## Create chain which blocks new connections, except if coming from inside.
# iptables -N block
# iptables -A block -m state --state ESTABLISHED,RELATED -j ACCEPT
# iptables -A block -m state --state NEW -i ! ppp0 -j ACCEPT
# iptables -A block -j DROP
## Jump to that chain from INPUT and FORWARD chains.
# iptables -A INPUT -j block
# iptables -A FORWARD -j block
3.3 规则的描述
一条规则分为三部分:
struct ipt_entry //主要用来匹配IP头
struct ip_match //额外的匹配(tcp头,mac地址等)
struct ip_target //除缺省的动作外(如ACCEPT,DROP),可以增加新的(如REJECT)。
3.4 代码提炼
ip_input.c:
/*
* Main IP Receive routine.
*/
int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt)
{
...
return NF_HOOK(PF_INET, NF_IP_PRE_ROUTING, skb, dev, NULL,
ip_rcv_finish);
...
}
netfilter.h:
#ifdef CONFIG_NETFILTER
#define NF_HOOK(pf, hook, skb, indev, outdev, okfn) \
(list_empty(&nf_hooks[(pf)][(hook)]) \
? (okfn)(skb) \
: nf_hook_slow((pf), (hook), (skb), (indev), (outdev), (okfn)))
#else /* !CONFIG_NETFILTER */
#define NF_HOOK(pf, hook, skb, indev, outdev, okfn) (okfn)(skb)
#endif /*CONFIG_NETFILTER*/
大的框架:"HOOK表":
struct list_head nf_hooks[NPROTO][NF_MAX_HOOKS]; //netfilter.c
通过nf_register_hook和nf_unregister_hook完成添加删除工作,nf_iterate负责执行hook上的函数。
增加用户自定义的HOOK,参见【8】,【10】:
重要流程(建议结合netfilter hacking howto 4.1.3来看):
/* Returns one of the generic firewall policies, like NF_ACCEPT. */
unsigned int
ipt_do_table(struct sk_buff **pskb,
unsigned int hook,
const struct net_device *in,
const struct net_device *out,
struct ipt_table *table,
void *userdata)
{
struct ipt_entry *e;
struct ipt_entry_target *t;
unsigned int verdict = NF_DROP;
table_base = (void *)table->private->entries
+ TABLE_OFFSET(table->private,
cpu_number_map(smp_processor_id()));
e = get_entry(table_base, table->private->hook_entry[hook]);
...
ip_packet_match(ip, indev, outdev, &e->ip, offset);
...
IPT_MATCH_ITERATE(e, do_match, *pskb, in, out, offset, protohdr, datalen, &hotdrop)
...
t = ipt_get_target(e);
...
verdict = t->u.kernel.target->target(pskb, hook, in, out, t->data, userdata);//非标准的target走这一步
...
return verdict;
}
要加强对这段话的理解(netfilter hacking howto 4.1节) :
>iptables does not register with any netfilter hooks: it relies on
>other modules to do that and feed it the packets as appropriate; a
>module must register the netfilter hooks and ip_tables separately, and
>provide the mechanism to call ip_tables when the hook is reached.
四 Linux下防火墙的实现之三(checkpoint FW1)
让我们看看checkpoint的在linux上的防火墙是如何实现的,最终我们会发现,竟然和lkm使用的手段差不多:)
fw1通过dev_add_pack的办法加载输入过滤函数,但是在net_bh()中,传往网络层的skbuff是克隆的,即
skb2=skb_clone(skb, GFP_ATOMIC);
if(skb2)
pt_prev->func(skb2, skb->dev, pt_prev);
而fw1是怎么解决这个问题的呢?见下面的代码:
输入一:
; 哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪?
align 4
; 圹圹圹圹圹圹圹?S U B R O U T I N E 圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹?
; Attributes: bp-based frame
public fwinstallin
fwinstallin proc near ; CODE XREF: fwinstall+E9p
; fwinstall+149p
var_18 = byte ptr -18h
arg_0 = dword ptr 8
push ebp
mov ebp, esp
sub esp, 10h
push esi
push ebx
mov esi, ds:dev_base
cmp [ebp+arg_0], 0
jz short loc_0_802CBD0
add esp, 0FFFFFFF4h
push offset fw_ip_packet_type
call dev_add_pack
mov ebx, fw_ip_packet_type+10h ;如果考虑字节对齐问题的话fw_ip_packet_type+10h这时应该是ip_packet_type
mov dword ptr ds:fw_type_list, ebx
jmp short loc_0_802CB9C
; 哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪?
align 4
loc_0_802CB90: ; CODE XREF: fwinstallin+41j
add esp, 0FFFFFFF4h
push ebx
call dev_remove_pack ;fw1把ip_packet_type歇载掉了,然后自己在自己的处理函数(fw_filterin)中调ip_recv
mov ebx, [ebx+10h]
loc_0_802CB9C: ; CODE XREF: fwinstallin+2Dj
add esp, 10h
test ebx, ebx
jnz short loc_0_802CB90
test esi, esi
jz short loc_0_802CC14
loc_0_802CBA7: ; CODE XREF: fwinstallin+68j
test byte ptr fwdebug, 81h
jz short loc_0_802CBC3
add esp, 0FFFFFFF8h
mov eax, [esi]
push eax
push offset aFwinstallinS ; "fwinstallin: %s\n"
call fwkdebug_printf
add esp, 10h
loc_0_802CBC3: ; CODE XREF: fwinstallin+4Ej
mov esi, [esi+28h]
test esi, esi
jnz short loc_0_802CBA7
jmp short loc_0_802CC14
; 哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪?
align 8
loc_0_802CBD0: ; CODE XREF: fwinstallin+12j
cmp dword ptr ds:fw_type_list, 0
jz short loc_0_802CC14
add esp, 0FFFFFFF4h
push offset fw_ip_packet_type
call dev_remove_pack
add esp, 10h
cmp dword ptr ds:fw_type_list, 0
jz short loc_0_802CC14
loc_0_802CBF2: ; CODE XREF: fwinstallin+B2j
add esp, 0FFFFFFF4h
mov eax, dword ptr ds:fw_type_list
push eax
call dev_add_pack
mov eax, dword ptr ds:fw_type_list
add esp, 10h
mov eax, [eax+10h]
mov dword ptr ds:fw_type_list, eax
test eax, eax
jnz short loc_0_802CBF2
loc_0_802CC14: ; CODE XREF: fwinstallin+45j
; fwinstallin+6Aj ...
lea esp, [ebp+var_18]
xor eax, eax
pop ebx
pop esi
mov esp, ebp
pop ebp
retn
fwinstallin endp
输入二:
public fw_ip_packet_type
fw_ip_packet_type dd 8, 0, offset fw_filterin, 2 dup(0) ; DATA XREF: fwinstallin+17o
输出的挂载和lkm的手法一样,更改dev->hard_start_xmit。dev结构在2.2版本的发展过程中变了一次,
为了兼容fw1对这点也做了处理。
输出一:
; 哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪?
align 4
; 圹圹圹圹圹圹圹?S U B R O U T I N E 圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹?
; Attributes: bp-based frame
public fwinstallout
fwinstallout proc near ; CODE XREF: fwinstall+FBp
; fwinstall+153p
var_18 = byte ptr -18h
arg_0 = dword ptr 8
push ebp
mov ebp, esp
sub esp, 0Ch
push edi
push esi
push ebx
mov edi, [ebp+arg_0]
xor esi, esi
mov ebx, ds:dev_base
jmp short loc_0_802D0A8
loc_0_802D096: ; CODE XREF: fwinstallout+50j
add esp, 0FFFFFFFCh
push edi
push esi
push ebx
call installout_on_device
add esp, 10h
mov ebx, [ebx+28h]
inc esi
loc_0_802D0A8: ; CODE XREF: fwinstallout+14j
test ebx, ebx
jz short loc_0_802D0F8
test byte ptr fwdebug, 81h
jz short loc_0_802D0CD
xor eax, eax
mov ax, [ebx+50h]
push eax
mov eax, [ebx]
push eax
push esi
push offset aFwinstalloutIn ; "fwinstallout: interface %d: name=%s, fl"...
call fwkdebug_printf
add esp, 10h
loc_0_802D0CD: ; CODE XREF: fwinstallout+33j
cmp esi, 3Fh
jle short loc_0_802D096
add esp, 0FFFFFFF8h
push 40h
push offset aFw1CanOnlyHand ; "FW-1: Can only handle %d interfaces\n"
call fwkdebug_printf
add esp, 10h
test edi, edi
jz short loc_0_802D0F8
add esp, 0FFFFFFF4h
push offset aFw1NotAllInter ; "FW-1: Not all interfaces installed\n"
call fwkdebug_printf
add esp, 10h
loc_0_802D0F8: ; CODE XREF: fwinstallout+2Aj
; fwinstallout+66j
mov fw_nif, esi
test byte ptr fwdebug, 81h
jz short loc_0_802D124
add esp, 0FFFFFFFCh
mov eax, offset aUn ; "un"
test edi, edi
jz short loc_0_802D118
mov eax, offset unk_0_80687E4
loc_0_802D118: ; CODE XREF: fwinstallout+91j
push eax
push esi
push offset aFw1DInterfaces ; "FW-1: %d interfaces %sinstalled\n"
call fwkdebug_printf
loc_0_802D124: ; CODE XREF: fwinstallout+85j
lea esp, [ebp+var_18]
xor eax, eax
pop ebx
pop esi
pop edi
mov esp, ebp
pop ebp
retn
fwinstallout endp
输出二:
; 哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪?
align 10h
; 圹圹圹圹圹圹圹?S U B R O U T I N E 圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹?
; Attributes: bp-based frame
public installout_on_device
installout_on_device proc near ; CODE XREF: fwinstallout+1Cp
var_18 = byte ptr -18h
var_4 = dword ptr -4
arg_0 = dword ptr 8
arg_4 = dword ptr 0Ch
arg_8 = dword ptr 10h
push ebp
mov ebp, esp
sub esp, 0Ch
push edi
push esi
push ebx
mov edi, [ebp+arg_0]
mov esi, [ebp+arg_4]
mov ebx, [ebp+arg_8]
add esp, 0FFFFFFF4h
push edi
call xmit_func_addr
mov [ebp+var_4], eax
add esp, 10h
test ebx, ebx
jz short loc_0_802CFD4
mov ebx, esi
shl ebx, 4
cmp (oftab+4)[ebx], 0
jz short loc_0_802CF90
add esp, 0FFFFFFF4h
push offset aFw1OutputFilte ; "FW-1: Output filter already installed\n"
call fwkdebug_printf
mov eax, 6Ah
jmp loc_0_802D074
输出三:
; 哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪?
align 8
; 圹圹圹圹圹圹圹?S U B R O U T I N E 圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹?
; Attributes: bp-based frame
public xmit_func_addr
xmit_func_addr proc near ; CODE XREF: installout_on_device+16p
arg_0 = dword ptr 8
push ebp
mov ebp, esp
mov edx, [ebp+arg_0]
lea eax, [edx+0ACh]
cmp kver, 0Dh
jle short loc_0_802CB5B
lea eax, [edx+0B0h]
loc_0_802CB5B: ; CODE XREF: xmit_func_addr+13j
mov esp, ebp
pop ebp
retn
xmit_func_addr endp
FW1与linux的一些比较,可以参看参考文献【11】
五 参考文献
【1】了解Check Point FW-1状态表
http://magazine.nsfocus.com/detail.asp?id=538
【2】A Stateful Inspection of FireWall-1
http://www.dataprotect.com/bh2000/
【3】Linux IPCHAINS-HOWTO
http://www.linuxdoc.org
【4】防火墙新生代:Stateful-inspection
http://www.liuxuan.com/safe/anquan/html/firewall/04.htm
【5】netfilter站点上的文档
http://netfilter.kernelnotes.org
【6】Application Gateways and Stateful Inspection:A Brief Note Comparing and Contrasting
http://www.avolio.com/apgw+spf.html
【7】Internet Firewalls:Frequently Asked Questions
http://www.interhack.net/pubs/fwfaq
【8】Writing a Module for netfilter
http://www.linux-mag.com/2000-06/gear_01.html
【9】ipchains的源代码分析
http://www.lisoleg.net/lisoleg/network/ipchains.zip
【10】内核防火墙netfilter入门
http://magazine.nsfocus.com/detail.asp?id=637
【11】Check Point Firewall-1 on Linux, Part Two
http://www.securityfocus.com/frames/?focus=linux&content=/focus/linux/articles/checkpoint2.html
下面是我看RH6.2(Kernel 2-2-14)的TCP/IP代码的笔记
先从init/main.c的start_kernel函数说起。
在这个函数里面调用kernel_thread启动了init进程,这个进程对应的函数是同一个文件里面的init函数,在init函数里面调用了一个
叫do_basic_setup的在同一个文件里面的函数,这个函数调用了net/socket.c里面的sock_init函数,这个函数就是TCP/IP协议栈,也包括ipx等的入口。
首先sock_init函数里面有很多ifdef这样的东东,我觉得对于一个普通的主机来说,这些都不会配置的,它们包括:
SLAB_SKB,CONFIG_WAN_ROUTER,CONFIG_FIREWALL,CONFIG_RTNETLINK,CONFIG_NETLINK_DEV
去掉了这些编译选项以后就剩下这样的代码:
for (i = 0; i < NPROTO; i++)
net_families[i] = NULL;
sk_init();
proto_init();
其中net_families在include/linux/net.h里面定义,是这样的:
struct net_proto_family
{
int family;
int (*create)(struct socket *sock, int protocol);
/* These are counters for the number of different methods of
each we support */
short authentication;
short encryption;
short encrypt_net;
};
其中有用的只有前两项,那个create的callback函数是每个协议,例如AF_INET等初始化上层协议如TCP/ICMP协议需要的,以后还会遇到的,这里先放着把
sk_init函数在net/core/sock.c里面,没什么说的..
struct sock *sk_alloc(int family, int priority, int zero_it)
{
struct sock *sk = kmem_cache_alloc(sk_cachep, priority);
if(sk) {
if (zero_it)
memset(sk, 0, sizeof(struct sock));
sk->family = family;
}
return sk;
}
proto_init函数在同一个文件里面:
void __init proto_init(void)
{
extern struct net_proto protocols[];
struct net_proto *pro;
pro = protocols;
while (pro->name != NULL)
{
(*pro->init_func)(pro);
pro++;
}
}
struct net_proto在include/linux/net.h里面是这样的:
struct net_proto
{
const char *name; /* Protocol name */
void (*init_func)(struct net_proto *); /* Bootstrap */
};
这个protocols的数组是在net/protocols.c里面定义的,包含了一堆的协议初始化结构体,其中我只注意两个:AF_INET和AF_PACKET
它们的初始化函数分别是inet_proto_init和packet_proto_init
下面来看看IPv4协议和PACKET协议的初始化过程。
首先看PACKET协议,首先我们假定PACKET协议是编译在核心里面的,而不是一个MODULE,这样得到packet_proto_init函数在net/packet/af_packet.c里面是这样的:
void __init packet_proto_init(struct net_proto *pro)
{
sock_register(&packet_family_ops);
register_netdevice_notifier(&packet_netdev_notifier);
}
其中sock_register函数在net/socket.c里面,就是简单的设置前面说的net_families数组中间对应的值:
int sock_register(struct net_proto_family *ops)
{
if (ops->family >= NPROTO) {
printk(KERN_CRIT "protocol %d >= NPROTO(%d)\n",
ops->family, NPROTO);
return -ENOBUFS;
}
net_families[ops->family]=ops;
return 0;
}
这里要说明的是packet_netdev_notifier是一个struct notifier_block类型,这个struct是在include/linux/notifier.h里面的:
struct notifier_block
{
int (*notifier_call)(struct notifier_block *self, unsigned long, void *);
struct notifier_block *next;
int priority;
};
而register_netdevice_notifier函数在net/core/dev.c里面,是这样的:
int register_netdevice_notifier(struct notifier_block *nb)
{
return notifier_chain_register(&netdev_chain, nb);
}
而notifier_chain_register函数在include/linux/notifier.h里面,是这样的:
extern __inline__ int notifier_chain_register(
struct notifier_block **list, struct notifier_block *n)
{
while(*list)
{
if(n->priority > (*list)->priority)
break;
list= &((*list)->next);
}
n->next = *list;
*list=n;
return 0;
}
显然就是根据每个block的优先级把这个block排列在一个block的链表里面,在notifier_chain_register函数里面我们可以发现这个链表是netdev_chain。实际上这个链表的作用就是在每个interface打开,关闭状态改变或者外界调用相应的ioctl的时候通知这个链表上面的所有相关的设备,而每一个协议都调用register_netdevice_notifier注册了一个netdev_notifier的结构体,这样就可以在interface改变的时候得到通知了(通过调用每个notifier_call函数)。
下面来看inet_proto_init函数,这个函数在net/ipv4/af_inet.c中间,里面也有很多ifdef的编译选项,假定下面几个是没有定义的:CONFIG_NET_IPIP,CONFIG_NET_IPGRE,CONFIG_IP_FIREWALL,CONFIG_IP_MASQUERADE,CONFIG_IP_MROUTE
假定下面几个是定义了的:CONFIG_INET_RARP,CONFIG_PROC_FS
下面是整理后的代码:
(void) sock_register(&inet_family_ops);
for(p = inet_protocol_base; p != NULL;) {
struct inet_protocol *tmp=(struct inet_protocol *)p->next;
inet_add_protocol(p);
printk("%s%s",p->name,tmp?", ":"\n");
p = tmp;
}
arp_init();
ip_init();
tcp_v4_init(&inet_family_ops);
tcp_init();
icmp_init(&inet_family_ops);
rarp_ioctl_hook = rarp_ioctl;
proc_net_register(&proc_net_rarp);
proc_net_register(&proc_net_raw);
proc_net_register(&proc_net_snmp);
proc_net_register(&proc_net_netstat);
proc_net_register(&proc_net_sockstat);
proc_net_register(&proc_net_tcp);
proc_net_register(&proc_net_udp);
其中的sock_register函数的作用已经在前面说了,现在来看看struct inet_protocol和inet_add_protocol函数。前面的结构体
是在include/net/protocol.h里面:
struct inet_protocol
{
int (*handler)(struct sk_buff *skb, unsigned short len);
void (*err_handler)(struct sk_buff *skb, unsigned char *dp, int len);
struct inet_protocol *next;
unsigned char protocol;
unsigned char copy:1;
void *data;
const char *name;
};
第一个函数是用来接收数据的callback函数,第二个是错误处理函数,其它的copy是用来协议共享的,这个以后再说,data当然就是这个结构体的私有数据了。
inet_add_protocol函数是在net/ipv4/protocol.c里面的:
void inet_add_protocol(struct inet_protocol *prot)
{
unsigned char hash;
struct inet_protocol *p2;
hash = prot->protocol & (MAX_INET_PROTOS - 1);
prot ->next = inet_protos[hash];
inet_protos[hash] = prot;
prot->copy = 0;
p2 = (struct inet_protocol *) prot->next;
while(p2 != NULL)
{
if (p2->protocol == prot->protocol)
{
prot->copy = 1;
break;
}
p2 = (struct inet_protocol *) p2->next;
}
}
显然这个函数就是建立一个hash表,然后每个hash表项都是一个链表头,然后通过这个hash表加链表的方式访问每个协议结构体。在这里你也见到了copy成员的用法了把。
arp_init函数是在net/ipv4/arp.c里面的(假定没有定义CONFIG_SYSCTL):
neigh_table_init(&arp_tbl);
dev_add_pack(&arp_packet_type);
proc_net_register(&proc_net_arp);
不知道是不是有人眼睛一亮啊,呵呵,看到了dev_add_pack函数。
还是一步步来把。
neigh_table_init函数在net/core/neighbour.c中间:
void neigh_table_init(struct neigh_table *tbl)
{
unsigned long now = jiffies;
tbl->parms.reachable_time = neigh_rand_reach_time(
tbl->parms.base_reachable_time);
init_timer(&tbl->gc_timer);
tbl->gc_timer.data = (unsigned long)tbl;
tbl->gc_timer.function = neigh_periodic_timer;
tbl->gc_timer.expires = now + tbl->gc_interval +
tbl->parms.reachable_time;
add_timer(&tbl->gc_timer);
init_timer(&tbl->proxy_timer);
tbl->proxy_timer.data = (unsigned long)tbl;
tbl->proxy_timer.function = neigh_proxy_process;
skb_queue_head_init(&tbl->proxy_queue);
tbl->last_flush = now;
tbl->last_rand = now + tbl->parms.reachable_time*20;
tbl->next = neigh_tables;
neigh_tables = tbl;
}
jiffies是当前系统的时间,在i386系统上面好象一个jiffies代表50ms,显然这个函数就是生成两个timer将一个放在系统的timerlist里面。那个gc_timer的意思是garbage collect timer,因为每过一段时间arp的cache就应该更新,所以要有一个expires时间,这段时间过了以后就要更新arp地址了,那个proxy_timer还没有看是什么,不过我假定我的机器不使用proxy也不做成proxy,所以proxy相关的都没有管:P
那个timer的function显然是时钟到期的回调函数,data是这个回调函数要使用的私有数据了。
下面是dev_add_pack函数,它在net/core/dev.c里面:
void dev_add_pack(struct packet_type *pt)
{
int hash;
#ifdef CONFIG_NET_FASTROUTE
/* Hack to detect packet socket */
if (pt->data) {
netdev_fastroute_obstacles++;
dev_clear_fastroute(pt->dev);
}
#endif
if(pt->type==htons(ETH_P_ALL))
{
netdev_nit++;
pt->next=ptype_all;
ptype_all=pt;
}
else
{
hash=ntohs(pt->type)&15;
pt->next = ptype_base[hash];
ptype_base[hash] = pt;
}
}
显然系统保留了两个表,一个是ptype_all,用来接收所有类型的包的链表,一个是一个hash数组+链表的结构,用来接收特定类型的包。
struct packet_type的定义在include/linux/netdevice.h里面,我保留原来的注释,这样就不用我多说了:)
{
unsigned short type;
/* This is really htons(ether_type). */
struct device *dev;
/* NULL is wildcarded here */
int (*func) (struct sk_buff *,
struct device *, struct packet_type *);
void *data;
/* Private to the packet type */
struct packet_type *next;
};
其中的func当然是回调函数了,举个例子来说,arp_packet_type是这样的:
static struct packet_type arp_packet_type =
{
__constant_htons(ETH_P_ARP),
NULL, /* All devices */
arp_rcv,
NULL,
NULL
};
arp_init函数还有最后一个proc_net_register函数,这个函数在include/linux/proc_fs.h里面:
static inline int proc_net_register(struct proc_dir_entry * x)
{
return proc_register(proc_net, x);
}
而proc_register在fs/proc/root.c里面,主要作用是在proc_net对应的目录下面生成每个协议的子目录,例如TCP等在/proc目录下面生成相应的目录,用户可以通过访问/proc/net目录下面的相应目录得到每个协议的统计参数。
下面是ip_init函数,它在net/ipv4/ip_output.c里面:(下面假定定义了CONFIG_PROC_FS,CONFIG_IP_MULTICAST和CONFIG_NET_CLS_ROUTE)
__initfunc(void ip_init(void))
{
dev_add_pack(&ip_packet_type);
ip_rt_init();
proc_net_register(&proc_net_igmp);
}
前面的dev_add_pack是说过的,这里就不再说了,而且proc_net_register也是前面提过的,这里都不说了,先来看看ip_rt_init函数把,它在net/ipv4/route.c里面,函数是这样的:
__initfunc(void ip_rt_init(void))
{
struct proc_dir_entry *ent;
devinet_init();
ip_fib_init();
rt_periodic_timer.function = rt_check_expire;
/* All the timers, started at system startup tend
to synchronize. Perturb it a bit.
*/
rt_periodic_timer.expires = jiffies + net_random()%
ip_rt_gc_interval + ip_rt_gc_interval;
add_timer(&rt_periodic_timer);
proc_net_register(&(struct proc_dir_entry) {
PROC_NET_RTCACHE, 8, "rt_cache",
S_IFREG | S_IRUGO, 1, 0, 0,
0, &proc_net_inode_operations,
rt_cache_get_info
});
ent = create_proc_entry("net/rt_acct", 0, 0);
ent->read_proc = ip_rt_acct_read;
}
这个函数总的看来就是注册几个notifier(后面还要看的)和初始化路由表的timer,最后就在/proc目录下面创建一个目录项。其中proc_net_register函数就不说了,而create_proc_entry函数就是在/proc/net目录下面创建一个rt_acct,就是路由参数统计(account)目录,读函数就是ip_rt_acct_read,这个函数就是从全局变量ip_rt_acct中间拷贝数据到用户缓冲中而已。
devinet_init函数是net/ipv4/devinet.c里面的函数,整理后如下:
register_gifconf(PF_INET, inet_gifconf);
register_netdevice_notifier(&ip_netdev_notifier);
register_netdevice_notifier函数在说PACKET协议的时候提过,这里不说了,register_gifconf函数是用来注册对应SIOCGIFCONF这个系统调用的协议无关的一个回调函数,这个函数对于PF_INET来说就是inet_gifconf函数。
其中inet_gifconf函数是net/ipv4/devinet.c里面的,我大概的看了一点,主要好象是在所有的interface里面做一个循环,得到相应的name和address然后返回的。不过不是非常确定。大家参谋呀:)
而register_gifconf函数本身是在net/core/dev.c里面的,如下:
static gifconf_func_t * gifconf_list [NPROTO];
int register_gifconf(unsigned int family, gifconf_func_t * gifconf)
{
if (family>=NPROTO)
return -EINVAL;
gifconf_list[family] = gifconf;
return 0;
}
这个函数的意义一目了然,就不说了。
gifconf_list里的函数会在dev_ifconf函数中间被调用,而dev_ifconf函数被dev_ioctl函数调用,dev_ioctl函数负责所有的针对interface的I/O控制。所以我们调用的interface的ioctl函数有一部分就会分到每个协议的gifconf函数里面来,我猜gifconf大概是generous interface configure的意思。就是通用接口配置的意思。
下面再看ip_fib_init函数,它在net/ipv4/fib_frontend.c中间,如下:
(假定没有define CONFIG_IP_MULTIPLE_TABLES,这个参数好象是要创建两个路由表,一个是local的,一个叫main)
__initfunc(void ip_fib_init(void))
{
proc_net_register(&(struct proc_dir_entry) {
PROC_NET_ROUTE, 5, "route",
S_IFREG | S_IRUGO, 1, 0, 0,
0, &proc_net_inode_operations,
fib_get_procinfo
});
fib_rules_init();
register_netdevice_notifier(&fib_netdev_notifier);
register_inetaddr_notifier(&fib_inetaddr_notifier);
}
其中proc_net_register和register_netdevice_notifier函数上面已经提过了,register_inetaddr_notifier函数的作用和register_netdevice_notifier差不多,这个函数也是调用的notifier_chain_register函数注册一个回调函数,这个回调函数在interface加上和删除的时候被调用,fib_rules_init函数其实也差不多,这个函数在net/ipv4/fib_rules.c里面,它其实就是调用一个
register_netdevice_notifier函数注册fib_rules_notifier回调结构体。
fib代表IPv4 Forwarding Information Base,就是IPv4转发信息的意思
下面是分析tcp_v4_init的时候了,这个函数在net/ipv4/tcp_ipv4.c里面:
__initfunc(void tcp_v4_init(struct net_proto_family *ops))
{
int err;
tcp_inode.i_mode = S_IFSOCK;
tcp_inode.i_sock = 1;
tcp_inode.i_uid = 0;
tcp_inode.i_gid = 0;
tcp_socket->inode = &tcp_inode;
tcp_socket->state = SS_UNCONNECTED;
tcp_socket->type=SOCK_RAW;
if ((err=ops->create(tcp_socket, IPPROTO_TCP))<0)
panic("Failed to create the TCP control socket.\n");
tcp_socket->sk->allocation=GFP_ATOMIC;
tcp_socket->sk->num = 256;
tcp_socket->sk->ip_ttl = MAXTTL;
}
tcp_inode当然就是一个inode节点了,而tcp_socket等于tcp_inode.u.socket_i,通过一个指针他们指向同一个内存.
tcp_socket是用来通信使用的,可以叫TCP的control socket或者是communicationsocket,当TCP通信没有相应的socket的时候这个socket就充当了socket的角色.比如在一个关闭端口上收到SYN时发送RST,或者是在三次握手的时候发送SYN(还没有accept产生新的socket)
值得注意的是ops->create函数的调用,我们前面见过对于AF_INET来说这个回调函数是net/ipv4/af_inet.c的inet_create函数,这个函数是用来创建一个socket的时候用的,由于函数比较长,这里先略过分析,这第一次的分析只是一个大致流程的熟悉而已.
由于有socket创建和通信,所以这段代码是协议相关的,所以把这段代码从原来的tcp.c里面提取了出来
下面是tcp_init函数,它在net/ipv4/tcp.c里面,大体上来说就是创建了几个hash表和bucket.这段代码创建了下面几个全局对象:
tcp_openreq_cachep
tcp_bucket_cachep
tcp_timewait_cachep
tcp_ehash
tcp_bhash
其中ehash代表established hash, bhash代表bind hash,它们当然分别是所有的满足TCP_ESTABLISHED <= sk->state < TCP_CLOSE状态的SOCK.但是我不清楚bucket在这里是什么意思.anyone knows?那几个cachep的作用也不是很清楚.由于整个函数主要是内存分配和错误处理,这里不贴了.
再下来就是icmp_init函数了,在net/ipv4/icmp.c里面,事实上,如果把tcp_v4_init里面的IPPROTO_TCP替换成IPPROTO_ICMP,基本都是一样的.
剩下的proc_net_register函数前面已经讲过了,这里就不说了.
到这里为止,Linux下面IP栈的开始的工作我们基本应该有了个了解,其中有几个关键的函数:
dev_add_pack:
注册一个链路层以上的处理函数,一般是用来使用新的网络层协议的,不过如果注册时重复也是可以的,这时候系统会设置一个copy位.如果是ETH_P_ALL则会接收所有的数据包.加入的元素保存在ptype_all链表和ptype_base hash链表中间.
inet_add_protocol:
注册一个建立在IP层以上的协议,例如TCP和UDP等
proc_net_register(还有类似的proc_register):
在/proc/net目录下面创建一个子目录项来使管理者能通过文件系统得到统计信息
现在迷惑的地方还有很多,一个是结构体sk_buff的每个成员的意义,一个是结构体sock的意义,不过这两个问题应该在以后看多了就知道了.下面我就打算一个个分析每个协议的处理了,包括状态转化/数据发送/接收.








