全部博文(146)
分类: LINUX
2008-02-01 12:10:07
这篇文章我们简要分析Linux内核的IP协议栈的总体结构,按照由简至繁的原则,逐步剖析相关代码。我们的分析以linux-2.4.35版本为准。
如下图所示,我们先将IP协议栈代码与内核其它代码作一个粗略的切割。
从上图我们可以确定四个代码函数作为网络数据收发的切入点。首先,网卡驱动程序接收到硬件上传数据,将该数据通过网卡适配层的接入函数netif_rx接收进入协议栈。IP协议栈处理网络数据将有两种处理可能,1)如果该数据包的目的地是本机进程,则通过ip_local_deliver传给上层即传输层处理;2)如果该数据包的最终目的地不是本机进程,且本机设置为支持数据包转发,则查找路由等动作确定发送接口,并调用dev_queue_xmit发送给网卡缓冲。传输层发送数据时,先通过ip_queue_xmit发送至协议栈即网络层,协议栈再查找路由确定发送数据包的网卡,最后调用dev_queue_xmit发送数据。
在IP协议栈中流动的数据是struct sk_buff,该结构定义在skbuff.h中。其主要包括:
struct sk_buff {
/* These two members must be first. */
struct sk_buff * next; /* Next buffer in list */
struct sk_buff * prev; /* Previous buffer in list */
struct sk_buff_head * list; /* List we are on */
struct sock *sk; /* Socket we are owned by */
struct timeval stamp; /* Time we arrived */
struct net_device *dev; /* Device we arrived on/are leaving by */
struct net_device *real_dev; /* For support of point to point protocols
(e.g. 802.3ad) over bonding, we must save the
physical device that got the packet before
replacing skb->dev with the virtual device. */
/* Transport layer header */
union
{
struct tcphdr *th;
struct udphdr *uh;
struct icmphdr *icmph;
struct igmphdr *igmph;
struct iphdr *ipiph;
struct spxhdr *spxh;
unsigned char *raw;
} h;
/* Network layer header */
union
{
struct iphdr *iph;
struct ipv6hdr *ipv6h;
struct arphdr *arph;
struct ipxhdr *ipxh;
unsigned char *raw;
} nh;
/* Link layer header */
union
{
struct ethhdr *ethernet;
unsigned char *raw;
} mac;
struct dst_entry *dst;
/*
* This is the control buffer. It is free to use for every
* layer. Please put your private variables there. If you
* want to keep them across layers you have to do a skb_clone()
* first. This is owned by whoever has the skb queued ATM.
*/
char cb[48];
unsigned int len; /* Length of actual data */
unsigned int data_len;
unsigned int csum; /* Checksum */
unsigned char __unused, /* Dead field, may be reused */
cloned, /* head may be cloned (check refcnt to be sure). */
pkt_type, /* Packet class */
ip_summed; /* Driver fed us an IP checksum */
__u32 priority; /* Packet queueing priority */
atomic_t users; /* User count - see datagram.c,tcp.c */
unsigned short protocol; /* Packet protocol from driver. */
unsigned short security; /* Security level of packet */
unsigned int truesize; /* Buffer size */
unsigned char *head; /* Head of buffer */
unsigned char *data; /* Data head pointer */
unsigned char *tail; /* Tail pointer */
unsigned char *end; /* End pointer */
void (*destructor)(struct sk_buff *); /* Destruct function */
#ifdef CONFIG_NETFILTER
/* Can be used for communication between hooks. */
unsigned long nfmark;
/* Cache info */
__u32 nfcache;
/* Associated connection, if any */
struct nf_ct_info *nfct;
#ifdef CONFIG_NETFILTER_DEBUG
unsigned int nf_debug;
#endif
#endif /*CONFIG_NETFILTER*/
#if defined(CONFIG_HIPPI)
union{
__u32 ifield;
} private;
#endif
#ifdef CONFIG_NET_SCHED
__u32 tc_index; /* traffic control index */
#endif
};
Linux内核IP协议栈不仅仅收发网络数据包,并且提供了扩展框架即netfilter以实现数据包的过滤、修改和各种变换功能。netfilter框架非常灵活,Linux防火墙、网络攻击检测和NAT就是建立在netfilter的基础之上。netfilter通过在数据包经过的各个路径上插入回调函数,实现对数据包的各种操作。在Linux IP协议栈中定义了五条数据包路径(netfilter_ipv4.h),它们是:
After promisc drops, checksum checks. 数据包从网络进入IP协议栈并经过数据checksum等基本验证后的地方。
If the packet is destined for this box. 如果该数据包的目的地是本机,该路径代表网络层与传输层的接口之处。
If the packet is destined for another interface. 数据转发。
Packets coming from a local process. 与NF_IP_LOCAL_IN相反,为传输层至网络层的接口之处。
Packets about to hit the wire. 在数据发送出网卡设备之前。
为了将netfilter回调函数(hook)插入到数据包处理路径之中,需要将原来的函数分成前后两部分。在Linux内核中使用宏NF_HOOK实现该功能,其定义在netfilter.h中。
extern struct list_head nf_hooks[NPROTO][NF_MAX_HOOKS];
#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)))
我们先不分析上面的代码,直接看一个实例,相信大家能够理解:
return NF_HOOK(PF_INET, NF_IP_LOCAL_IN, skb, skb->dev, NULL,
ip_local_deliver_finish);
通过搜索内核中的NF_HOOK和结合上篇文档中提到的IP协议栈框架,现在我们可以得出一个更为详细的框架图。
上图中{n}代表一个钩子NF_HOOK。
由NF_HOOK宏可知,所有的钩子函数都进入了nf_hook_slow函数,这就是进入netfilter框架的入口,虽然接口简单但里面的世界却复杂和费解。