Friday, November 30, 2007

Linux Netfilter实现机制和扩展技术

Linux Netfilter实现机制和扩展技术

2.4.x的内核相对于2.2.x在IP协议栈部分有比较大的改动, Netfilter-iptables更是其一大特色,由于它功能强大,并且与内核完美结合,因此迅速成为Linux平台下进行网络应用扩展的主要利器, 这些扩展不仅包括防火墙的实现--这只是Netfilter-iptables的基本功能--还包括各种报文处理工作(如报文加密、报文分类统计等),甚 至还可以借助Netfilter-iptables机制来实现虚拟专用网(VPN)。本文将致力于深入剖析Netfilter-iptables的组织结 构,并详细介绍如何对其进行扩展。Netfilter目前已在ARP、IPv4和IPv6中实现,考虑到IPv4是目前网络应用的主流,本文仅就IPv4 的Netfilter实现进行分析。

要想理解Netfilter的工作原理,必须从对Linux IP报文处理流程的分析开始,Netfilter正是将自己紧密地构建在这一流程之中的。

1. IP Packet Flowing

IP 协议栈是Linux操作系统的主要组成部分,也是Linux的特色之一,素以高效稳定著称。Netfilter与IP协议栈是密切结合在一起的,要想理解 Netfilter的工作方式,必须理解IP协议栈是如何对报文进行处理的。下面将通过一个经由IP Tunnel传输的TCP报文的流动路径,简要介绍一下IPv4协议栈(IP层)的结构和报文处理过程。

IP Tunnel是2.0.x内核就已经提供了的虚拟局域网技术,它在内核中建立一个虚拟的网络设备,将正常的报文(第二层)封装在IP报文中,再通过TCP/IP网络进行传送。如果在网关之间建立IP Tunnel,并配合ARP报文的解析,就可以实现虚拟局域网。

我们从报文进入IP Tunnel设备准备发送开始。

1.1报文发送

ipip模块创建tunnel设备(设备名为tunl0~tunlx)时,设置报文发送接口(hard_start_xmit)为ipip_tunnel_xmit(),流程见下图:


图1 报文发送流程

1.2 报文接收

报文接收从网卡驱动程序开始,当网卡收到一个报文时,会产生一个中断,其驱动程序中的中断服务程序将调用确定的接收函数来处理。以下仍以IP Tunnel报文为例,网卡驱动程序为de4x5。流程分成两个阶段:驱动程序中断服务程序阶段和IP协议栈处理阶段,见下图:


图2 报文接收流程之驱动程序阶段


图3 报文接收流程之协议栈阶段

如果报文需要转发,则在上图红箭头所指处调用ip_forward():


图4 报文转发流程

从上面的流程可以看出,Netfilter以NF_HOOK()的形式出现在报文处理的过程之中。





回页首


2. Netfilter Frame

Netfilter 是2.4.x内核引入的,尽管它提供了对2.0.x内核中的ipfw以及2.2.x内核中的ipchains的兼容,但实际上它的工作和意义远不止于此。 从上面对IP报文的流程分析中可以看出,Netfilter和IP报文的处理是完全结合在一起的,同时由于其结构相对独立,又是可以完全剥离的。这种机制 也是Netfilter-iptables既高效又灵活的保证之一。

在剖析Netfilter机制之前,我们还是由浅入深的从Netfilter的使用开始。

2.1 编译

在Networking Options中选定Network packet filtering项,并将其下的IP:Netfilter Configurations小节的所有选项设为Module模式。编译并安装新内核,然后重启,系统的核内Netfilter就配置好了。以下对相关的 内核配置选项稍作解释,也可以参阅编译系统自带的Help:

【Kernel/User netlink socket】建立一类PF_NETLINK套接字族,用于核心与用户进程通信。当Netfilter需要使用用户队列来管理某些报文时就要使用这一机制;

【Network packet filtering (replaces ipchains)】Netfilter主选项,提供Netfilter框架;

【Network packet filtering debugging】Netfilter主选项的分支,支持更详细的Netfilter报告;

【IP: Netfilter Configuration】此节下是netfilter的各种选项的集合:

【Connection tracking (required for masq/NAT)】连接跟踪,用于基于连接的报文处理,比如NAT;

【IP tables support (required for filtering/masq/NAT)】这是Netfilter的框架,NAT等应用的容器;

【ipchains (2.2-style) support】ipchains机制的兼容代码,在新的Netfilter结构上实现了ipchains接口;

【ipfwadm (2.0-style) support】2.0内核防火墙ipfwadm兼容代码,基于新的Netfilter实现。

2.2 总体结构

Netfilter 是嵌入内核IP协议栈的一系列调用入口,设置在报文处理的路径上。网络报文按照来源和去向,可以分为三类:流入的、流经的和流出的,其中流入和流经的报文 需要经过路由才能区分,而流经和流出的报文则需要经过投递,此外,流经的报文还有一个FORWARD的过程,即从一个NIC转到另一个NIC。 Netfilter就是根据网络报文的流向,在以下几个点插入处理过程:

NF_IP_PRE_ROUTING,在报文作路由以前执行;

NF_IP_FORWARD,在报文转向另一个NIC以前执行;

NF_IP_POST_ROUTING,在报文流出以前执行;

NF_IP_LOCAL_IN,在流入本地的报文作路由以后执行;

NF_IP_LOCAL_OUT,在本地报文做流出路由前执行。

如图所示:


图5 Netfilter HOOK位置

Netfilter 框架为多种协议提供了一套类似的钩子(HOOK),用一个struct list_head nf_hooks[NPROTO][NF_MAX_HOOKS]二维数组结构存储,一维为协议族,二维为上面提到的各个调用入口。每个希望嵌入 Netfilter中的模块都可以为多个协议族的多个调用点注册多个钩子函数(HOOK),这些钩子函数将形成一条函数指针链,每次协议栈代码执行到 NF_HOOK()函数时(有多个时机),都会依次启动所有这些函数,处理参数所指定的协议栈内容。

每个注册的钩子函数经过处理后都将返回下列值之一,告知Netfilter核心代码处理结果,以便对报文采取相应的动作:

NF_ACCEPT:继续正常的报文处理;

NF_DROP:将报文丢弃;

NF_STOLEN:由钩子函数处理了该报文,不要再继续传送;

NF_QUEUE:将报文入队,通常交由用户程序处理;

NF_REPEAT:再次调用该钩子函数。

2.3 IPTables

Netfilter -iptables由两部分组成,一部分是Netfilter的"钩子",另一部分则是知道这些钩子函数如何工作的一套规则--这些规则存储在被称为 iptables的数据结构之中。钩子函数通过访问iptables来判断应该返回什么值给Netfilter框架。

在现有(kernel 2.4.21)中已内建了三个iptables:filter、nat和mangle,绝大部分报文处理功能都可以通过在这些内建(built-in)的表格中填入规则完成:

filter,该模块的功能是过滤报文,不作任何修改,或者接受,或者拒绝。它在NF_IP_LOCAL_IN、NF_IP_FORWARD和NF_IP_LOCAL_OUT三处注册了钩子函数,也就是说,所有报文都将经过filter模块的处理。

nat,网络地址转换(Network Address Translation),该模块以Connection Tracking模块为基础,仅对每个连接的第一个报文进行匹配和处理,然后交由Connection Tracking模块将处理结果应用到该连接之后的所有报文。nat在NF_IP_PRE_ROUTING、NF_IP_POST_ROUTING注册了 钩子函数,如果需要,还可以在NF_IP_LOCAL_IN和NF_IP_LOCAL_OUT两处注册钩子,提供对本地报文(出/入)的地址转换。nat 仅对报文头的地址信息进行修改,而不修改报文内容,按所修改的部分,nat可分为源NAT(SNAT)和目的NAT(DNAT)两类,前者修改第一个报文 的源地址部分,而后者则修改第一个报文的目的地址部分。SNAT可用来实现IP伪装,而DNAT则是透明代理的实现基础。

mangle,属于可以进行报文内容修改的IP Tables,可供修改的报文内容包括MARK、TOS、TTL等,mangle表的操作函数嵌入在Netfilter的NF_IP_PRE_ROUTING和NF_IP_LOCAL_OUT两处。

内核编程人员还可以通过注入模块,调用Netfilter的接口函数创建新的iptables。在下面的Netfilter-iptables应用中我们将进一步接触Netfilter的结构和使用方式。

2.4 Netfilter配置工具

iptables是专门针对2.4.x内核的Netfilter制作的核外配置工具,通过socket接口对Netfilter进行操作,创建socket的方式如下:

socket(TC_AF, SOCK_RAW, IPPROTO_RAW)

其中TC_AF就是AF_INET。核外程序可以通过创建一个"原始IP套接字"获得访问Netfilter的句柄,然后通过getsockopt()和setsockopt()系统调用来读取、更改Netfilter设置,详情见下。

iptables功能强大,可以对核内的表进行操作,这些操作主要指对其中规则链的添加、修改、清除,它的命令行参数主要 可分为四类:指定所操作的IP Tables(-t);指定对该表所进行的操作(-A、-D等);规则描述和匹配;对iptables命令本身的指令(-n等)。在下面的例子中,我们通 过iptables将访问10.0.0.1的53端口(DNS)的TCP连接引导到192.168.0.1地址上。

iptables -t nat -A PREROUTING -p TCP -i eth0 -d 10.0.0.1 --dport 53 -j DNAT --to-destination 192.168.0.1

由于iptables是操作核内Netfilter的用户界面,有时也把Netfilter-iptables简称为iptables,以便与ipchains、ipfwadm等老版本的防火墙并列。

2.5 iptables核心数据结构

2.5.1 表

在Linux内核里,iptables用struct ipt_table表示,定义如下(include/linux/netfilter_ipv4/ip_tables.h):

struct ipt_table
{
struct list_head list;
/* 表链 */
char name[IPT_TABLE_MAXNAMELEN];
/* 表名,如"filter"、"nat"等,为了满足自动模块加载的设计,包含该表的模块应命名为iptable_'name'.o */
struct ipt_replace *table;
/* 表模子,初始为initial_table.repl */
unsigned int valid_hooks;
/* 位向量,标示本表所影响的HOOK */
rwlock_t lock;
/* 读写锁,初始为打开状态 */
struct ipt_table_info *private;
/* iptable的数据区,见下 */
struct module *me;
/* 是否在模块中定义 */
};
struct ipt_table_info是实际描述表的数据结构(net/ipv4/netfilter/ip_tables.c):
struct ipt_table_info
{
unsigned int size;
/* 表大小 */
unsigned int number;
/* 表中的规则数 */
unsigned int initial_entries;
/* 初始的规则数,用于模块计数 */
unsigned int hook_entry[NF_IP_NUMHOOKS];
/* 记录所影响的HOOK的规则入口相对于下面的entries变量的偏移量 */
unsigned int underflow[NF_IP_NUMHOOKS];
/* 与hook_entry相对应的规则表上限偏移量,当无规则录入时,相应的hook_entry和underflow均为0 */
char entries[0] ____cacheline_aligned;
/* 规则表入口 */
};

例如内建的filter表初始定义如下(net/ipv4/netfilter/iptable_filter.c):

static struct ipt_table packet_filter
= { { NULL, NULL }, // 链表
"filter", // 表名
&initial_table.repl, // 初始的表模板
FILTER_VALID_HOOKS,// 定义为((1 << NF_IP6_LOCAL_IN) | (1 << NF_IP6_FORWARD) | (1 << NF_IP6_LOCAL_OUT)),
即关心INPUT、FORWARD、OUTPUT三点
RW_LOCK_UNLOCKED,// 锁
NULL, // 初始的表数据为空
THIS_MODULE // 模块标示
};

经过调用ipt_register_table(&packet_filter)后,filter表的private数据区即参照模板填好了。

2.5.2 规则

规则用struct ipt_entry结构表示,包含匹配用的IP头部分、一个Target和0个或多个Match。由于Match数不定,所以一条规则实际的占用空间是可变的。结构定义如下(include/linux/netfilter_ipv4):

struct ipt_entry
{
struct ipt_ip ip;
/* 所要匹配的报文的IP头信息 */
unsigned int nfcache;
/* 位向量,标示本规则关心报文的什么部分,暂未使用 */
u_int16_t target_offset;
/* target区的偏移,通常target区位于match区之后,而match区则在ipt_entry的末尾;
初始化为sizeof(struct ipt_entry),即假定没有match */
u_int16_t next_offset;
/* 下一条规则相对于本规则的偏移,也即本规则所用空间的总和,
初始化为sizeof(struct ipt_entry)+sizeof(struct ipt_target),即没有match */
unsigned int comefrom;
/* 位向量,标记调用本规则的HOOK号,可用于检查规则的有效性 */
struct ipt_counters counters;
/* 记录该规则处理过的报文数和报文总字节数 */
unsigned char elems[0];
/*target或者是match的起始位置 */
}

规则按照所关注的HOOK点,被放置在struct ipt_table::private->entries之后的区域,比邻排列。

2.5.3 规则填写过程

在了解了iptables在核心中的数据结构之后,我们再通过遍历一次用户通过iptables配置程序填写规则的过程,来了解这些数据结构是如何工作的了。

一个最简单的规则可以描述为拒绝所有转发报文,用iptables命令表示就是:

iptables -A FORWARD -j DROP;

iptables应用程序将命令行输入转换为程序可 读的格式(iptables-standalone.c::main()::do_command(),然后再调用libiptc库提供的 iptc_commit()函数向核心提交该操作请求。在libiptc/libiptc.c中定义了iptc_commit()(即TC_COMMIT ()),它根据请求设置了一个struct ipt_replace结构,用来描述规则所涉及的表(filter)和HOOK点(FORWARD)等信息,并在其后附接当前这条规则--一个 struct ipt_entry结构(实际上也可以是多个规则entry)。组织好这些数据后,iptc_commit()调用setsockopt()系统调用来启 动核心处理这一请求:

setsockopt(
sockfd, //通过socket(TC_AF, SOCK_RAW, IPPROTO_RAW)创建的套接字,其中TC_AF即AF_INET
TC_IPPROTO, //即IPPROTO_IP
SO_SET_REPLACE, //即IPT_SO_SET_REPLACE
repl, //struct ipt_replace结构
sizeof(*repl) + (*handle)->entries.size) //ipt_replace加上后面的ipt_entry

核心对于setsockopt()的处理是从协议栈中一层层传递上来的,调用过程如下图所示:


图6 规则填写过程

nf_sockopts 是在iptables进行初始化时通过nf_register_sockopt()函数生成的一个struct nf_sockopt_ops结构,对于ipv4来说,在net/ipv4/netfilter/ip_tables.c中定义了一个 ipt_sockopts变量(struct nf_sockopt_ops),其中的set操作指定为do_ipt_set_ctl(),因此,当nf_sockopt()调用对应的set操作时, 控制将转入net/ipv4/netfilter/ip_tables.c::do_ipt_set_ctl()中。

对于IPT_SO_SET_REPLACE命令,do_ipt_set_ctl()调用do_replace()来处 理,该函数将用户层传入的struct ipt_replace和struct ipt_entry组织到filter(根据struct ipt_replace::name项)表的hook_entry[NF_IP_FORWARD]所指向的区域,如果是添加规则,结果将是filter表 的private(struct ipt_table_info)项的hook_entry[NF_IP_FORWARD]和underflow[NF_IP_FORWARD]的差值扩大 (用于容纳该规则),private->number加1。

2.5.4 规则应用过程

以上描述 了规则注入核内iptables的过程,这些规则都挂接在各自的表的相应HOOK入口处,当报文流经该HOOK时进行匹配,对于与规则匹配成功的报文,调 用规则对应的Target来处理。仍以转发的报文为例,假定filter表中添加了如上所述的规则:拒绝所有转发报文。

如1.2节所示,经由本地转发的报文经过路由以后将调用ip_forward()来处理,在ip_forward()返回前,将调用如下代码:

NF_HOOK(PF_INET, NF_IP_FORWARD, skb, skb->dev, dev2, ip_forward_finish)
NF_HOOK是这样一个宏(include/linux/netfilter.h):
#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)))

也就是说,如果nf_hooks[PF_INET] [NF_IP_FORWARD]所指向的链表为空(即该钩子上没有挂处理函数),则直接调用ip_forward_finish(skb)完成 ip_forward()的操作;否则,则调用net/core/netfilter.c::nf_hook_slow()转入Netfilter的处 理。

这里引入了一个nf_hooks链表二维数组:

struct list_head nf_hooks[NPROTO][NF_MAX_HOOKS];

每一个希望使用Netfilter挂钩的表都需要将 表处理函数在nf_hooks数组的相应链表上进行注册。对于filter表来说,在其初始化 (net/ipv4/netfilter/iptable_filter.c::init())时,调用了net/core/netfilter.c:: nf_register_hook(),将预定义的三个struct nf_hook_ops结构(分别对应INPUT、FORWARD、OUTPUT链)连入链表中:

struct nf_hook_ops
{
struct list_head list;
//链表
nf_hookfn *hook;
//处理函数指针
int pf;
//协议号
int hooknum;
//HOOK号
int priority;
//优先级,在nf_hooks链表中各处理函数按优先级排序
};

对于filter表来说,FORWARD点的 hook设置成ipt_hook(),它将直接调用ipt_do_table()。几乎所有处理函数最终都将调用ipt_do_table()来查询表中 的规则,以调用对应的target。下图所示即为在FORWARD点上调用nf_hook_slow()的过程:


图7 规则应用流程

2.5.5 Netfilter的结构特点

由 上可见,nf_hooks链表数组是联系报文处理流程和iptables的纽带,在iptables初始化(各自的init()函数)时,一方面调用 nf_register_table()建立规则容器,另一方面还要调用nf_register_hook()将自己的挂钩愿望表达给Netfilter 框架。初始化完成之后,用户只需要通过用户级的iptables命令操作规则容器(添加规则、删除规则、修改规则等),而对规则的使用则完全不用操心。如 果一个容器内没有规则,或者nf_hooks上没有需要表达的愿望,则报文处理照常进行,丝毫不受Netfilter-iptables的影响;即使报文 经过了过滤规则的处理,它也会如同平时一样重新回到报文处理流程上来,因此从宏观上看,就像在行车过程中去了一趟加油站。

Netfilter不仅仅有此高效的设计,同时还具备很大的灵活性,这主要表现在Netfilter-iptables 中的很多部分都是可扩充的,包括Table、Match、Target以及Connection Track Protocol Helper,下面一节将介绍这方面的内容。





回页首


3. Netfilter-iptables Extensions

Netfilter 提供的是一套HOOK框架,其优势是就是易于扩充。可供扩充的Netfilter构件主要包括Table、Match、Target和 Connection Track Protocol Helper四类,分别对应四套扩展函数。所有扩展都包括核内、核外两个部分,核内部分置于 /net/ipv4/netfilter/下,模块名为ipt_'name'.o;核外部分置于 /extensions/下,动态链接库名为libipt_'name'.so。

3.1 Table

Table 在以上章节中已经做过介绍了,它作为规则存储的媒介,决定了该规则何时能起作用。系统提供的filter、nat、mangle涵盖了所有的HOOK点, 因此,大部分应用都可以围绕这三个已存在的表进行,但也允许编程者定义自己的拥有特殊目的的表,这时需要参考已有表的struct ipt_table定义创建新的ipt_table数据结构,然后调用ipt_register_table()注册该新表,并调用 ipt_register_hook()将新表与Netfilter HOOK相关联。

对表进行扩展的情形并不多见,因此这里也不详述。

3.2 Match & Target

Match和Target是Netfilter-iptables中最常使用的功能,灵活使用Match和Target,可以完成绝大多数报文处理功能。

3.2.1 Match数据结构

核心用struct ipt_match表征一个Match数据结构:

struct ipt_match
{
struct list_head list;
/* 通常初始化成{NULL,NULL},由核心使用 */
const char name[IPT_FUNCTION_MAXNAMELEN];
/* Match的名字,同时也要求包含该Match的模块文件名为ipt_'name'.o */
int (*match)(const struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
const void *matchinfo,
int offset,
const void *hdr,
u_int16_t datalen,
int *hotdrop);
/* 返回非0表示匹配成功,如果返回0且hotdrop设为1,则表示该报文应当立刻丢弃 */
int (*checkentry)(const char *tablename,
const struct ipt_ip *ip,
void *matchinfo,
unsigned int matchinfosize,
unsigned int hook_mask);
/* 在使用本Match的规则注入表中之前调用,进行有效性检查,如果返回0,规则就不会加入iptables中 */
void (*destroy)(void *matchinfo, unsigned int matchinfosize);
/* 在包含本Match的规则从表中删除时调用,与checkentry配合可用于动态内存分配和释放 */
struct module *me;
/* 表示当前Match是否为模块(NULL为否) */
};

定义好一个ipt_match结构后,可调用ipt_register_match()将本Match注册到ipt_match链表中备用,在模块方式下,该函数通常在init_module()中执行。

3.2.2 Match的用户级设置

要使用核心定义的Match(包括已有的和自定义的),必须在用户级的iptables程序中有所说明,iptables源代码也提供了已知的核心Match,但未知的Match则需要自行添加说明。

在iptables中,一个Match用struct iptables_match表示:

struct iptables_match
{
struct iptables_match *next;
/* Match链,初始为NULL */
ipt_chainlabel name;
/* Match名,和核心模块加载类似,作为动态链接库存在的Iptables Extension的命名规则为libipt_'name'.so
(对于ipv6为libip6t_'name'.so),
以便于iptables主程序根据Match名加载相应的动态链接库 */
const char *version;
/* 版本信息,一般设为NETFILTER_VERSION */
size_t size;
/* Match数据的大小,必须用IPT_ALIGN()宏指定对界 */
size_t userspacesize;
/*由于内核可能修改某些域,因此size可能与确切的用户数据不同,这时就应该把不会被改变的数据放在数据区的前面部分,
而这里就应该填写被改变的数据区大小;一般来说,这个值和size相同 */
void (*help)(void);
/* 当iptables要求显示当前match的信息时(比如iptables -m ip_ext -h),就会调用这个函数,
输出在iptables程序的通用信息之后 */
void (*init)(struct ipt_entry_match *m, unsigned int *nfcache);
/* 初始化,在parse之前调用 */
int (*parse)(int c, char **argv, int invert, unsigned int *flags,
const struct ipt_entry *entry,
unsigned int *nfcache,
struct ipt_entry_match **match);
/* 扫描并接收本match的命令行参数,正确接收时返回非0,flags用于保存状态信息 */
void (*final_check)(unsigned int flags);
/* 当命令行参数全部处理完毕以后调用,如果不正确,应该退出(exit_error()) */
void (*print)(const struct ipt_ip *ip,
const struct ipt_entry_match *match, int numeric);
/* 当查询当前表中的规则时,显示使用了当前match的规则的额外的信息 */
void (*save)(const struct ipt_ip *ip,
const struct ipt_entry_match *match);
/* 按照parse允许的格式将本match的命令行参数输出到标准输出,用于iptables-save命令 */
const struct option *extra_opts;
/* NULL结尾的参数列表,struct option与getopt(3)使用的结构相同 */
/* 以下参数由iptables内部使用,用户不用关心 */
unsigned int option_offset;
struct ipt_entry_match *m;
unsigned int mflags;
unsigned int used;
}
struct option {
const char *name;
/* 参数名称,用于匹配命令行输入 */
int has_arg;
/* 本参数项是否允许带参数,0表示没有,1表示有,2表示可有可无 */
int *flag;
/* 指定返回的参数值内容,如果为NULL,则直接返回下面的val值,否则返回0,val存于flag所指向的位置 */
int val;
/* 缺省的参数值 */
}

如对于--opt 参数来讲,在struct option中定义为{"opt",1,0,'1'},表示opt带参数值,如果出现-opt 参数,则返回'1'用于parse()中的int c参数。

实际使用时,各个函数都可以为空,只要保证name项与核心的对应Match名字相同就可以了。在定义了 iptables_match之后,可以调用register_match()让iptables主体识别这个新Match。当iptables命令中第 一次指定使用名为ip_ext的Match时,iptables主程序会自动加载libipt_ip_ext.so,并执行其中的_init()接口,所 以register_match()操作应该放在_init()中执行。

3.2.3 Target数据结构

Target数据结构struct ipt_target和struct ipt_match基本相同,不同之处只是用target函数指针代替match函数指针:

struct ipt_target
{
……
unsigned int (*target)(struct sk_buff **pskb,
unsigned int hooknum,
const struct net_device *in,
const struct net_device *out,
const void *targinfo,
void *userdata);
/* 如果需要继续处理则返回IPT_CONTINUE(-1),否则返回NF_ACCEPT、NF_DROP等值,它的调用者根据它的返回值来判断如何处理它处理过的报文*/
……
}

与ipt_register_match()对应,Target使用ipt_register_target()来进行注册,但文件命名、使用方法等均与Match相同。

3.2.4 Target的用户级设置

Target的用户级设置使用struct iptables_target结构,与struct iptables_match完全相同。register_target()用于注册新Target,方法也与Match相同。

3.3 Connection Track Protocol Helper

前 面提到,NAT仅对一个连接(TCP或UDP)的第一个报文进行处理,之后就依靠Connection Track机制来完成对后续报文的处理。Connection Track是一套可以和NAT配合使用的机制,用于在传输层(甚至应用层)处理与更高层协议相关的动作。

关于Connection Track,Netfilter中的实现比较复杂,而且实际应用频率不高,因此这里就不展开了,以后专文介绍。

3.4 iptables patch机制

对于Netfilter-iptables扩展工作,用户当然可以直接修改源代码并编译安装,但为了标准化和简便起见,在iptables源码包提供了一套patch机制,希望用户按照其格式要求进行扩展,而不必分别修改内核和iptables代码。

和Netfilter-iptables的结构特点相适应,对iptables进行扩展也需要同时修改内核和 iptables程序代码,因此patch也分为两个部分。在iptables-1.2.8中,核内补丁由patch-o-matic包提供, iptables-1.2.8的源码中的extensions目录则为iptables程序本身的补丁。

patch-o-matic提供了一个'runme'脚本来给核心打patch,按照它的规范,核内补丁应该包括五个部分,且命名有一定的规范,例如,如果Target名为ip_ext,那么这五个部分的文件名和功能分别为:

  • ip_ext.patch
    主文件,内容为diff格式的核心.c、.h源文件补丁,实际使用时类似给内核打patch(patch -p0
  • ip_ext.patch.config.in
    对<>/net/ipv4/netfilter/Config.in文件的修改,第一行是原Config.in中的一行,以指示 补丁添加的位置,后面则是添加在以上匹配行之后的内容。这个补丁的作用是使核心的配置界面中支持新增加的补丁选项;
  • ip_ext.patch.configure.help
    /Documentation/Configure.help的修改,第一行为原Configure.help中的一行帮助索引,以下几行的内容添加在这一行相关的帮助之后。这个补丁的作用是补充内核配置时对新增加的选项的说明;
  • ip_ext.patch.help
    用于runme脚本显示本patch的帮助信息;
  • ip_ext.patch.makefile
  • /net/ipv4/netfilter/Makefile的修改,和前两个文件的格式相同,用于在指定的位置上添加用于生成ipt_ip_ext.o的make指令。

示例可以参看patch-o-matic下的源文件。

iptables本身的扩展稍微简单一些,那就是在extensions目录下增加一个libipt_ip_ext.c的文件,然后在本子目录的Makefile的PF_EXT_SLIB宏中附加一个ip_ext字符串。

第一次安装时,可以在iptables的根目录下运行make pending-patches命令,此命令会自动调用runme脚本,将所有patch-o-matic下的patch文件打到内核中,之后需要重新配置和编译内核。

如果只需要安装所要求的patch,可以在patch-o-matic目录下直接运行runme ip_ext,它会完成ip_ext patch的安装。之后,仍然要重编内核以使patch生效。

iptables本身的make/make install过程可以编译并安装好libipt_ip_ext.so,之后,新的iptables命令就可以通过加载libipt_ip_ext.so来识别ip_ext target了。

Extensions还可以定义头文件,一般这个头文件核内核外都要用,因此,通常将其放置在/include/linux/netfilter_ipv4/目录下,在.c文件里指定头文件目录为 linux/netfilter_ipv4/。

灵活性是Netfilter-iptables机制的一大特色,因此,扩展Netfilter-iptables也是它的应用的关键。为了与此目标相适应,Netfilter-iptables在结构上便于扩展,同时也提供了一套扩展的方案,并有大量扩展样例可供参考。





回页首


4. 案例:用Netfilter实现VPN

虚拟专用网的关键就是隧道(Tunnel)技术,即将报文封装起来通过公用网络。利用Netfilter-iptables对报文的强大处理能力,完全可以以最小的开发成本实现一个高可配置的VPN。

本文第一部分即描述了IP Tunnel技术中报文的流动过程,从中可见,IP Tunnel技术的特殊之处有两点:

  • 一个特殊的网络设备tunl0~tunlx--发送时,用指定路由的办法将需要封装的内网报文交给该网络设备来处理,在"网卡驱动程序"中作封装,然后再作为正常的IP报文交给真正的网络设备发送出去;
  • 一个特殊的IP层协议IPIP--从外网传来的封装报文拥有一个特殊的协议号(IPIP),报文最终在该协议的处理程序(ipip_rcv())中解封,恢复内网IP头后,将报文注入IP协议栈底层(netif_rx())重新开始收包流程。

从中不难看出,在报文流出tunlx设备之后(即完成封装之后)需要经过OUTPUT的 Netfilter HOOK点,而在报文解封之前(ipip_rcv()得到报文之前),也要经过Netfilter的INPUT HOOK点,因此,完全有可能在这两个HOOK上做文章,完成报文的封装和解封过程。报文的接收过程可以直接沿用IPIP的处理方法,即自定义一个专门的 协议,问题的关键即在于如何获得需要封装的外发报文,从而与正常的非VPN报文相区别。我们的做法是利用Netfilter-iptables对IP头信 息的敏感程度,在内网中使用标准的内网专用IP段(如192.168.xxx.xxx),从而通过IP地址将其区分开。基于IP地址的VPN配置既方便现 有系统管理、又便于今后VPN系统升级后的扩充,而且可以结合Netfilter-iptables的防火墙设置,将VPN和防火墙有机地结合起来,共同 维护一个安全的专用网络。

在我们的方案中,VPN采用LAN-LAN方式(当然,Dial-in方式在技术上并没有什么区别),在LAN网关处设 置我们的VPN管理组件,从而构成一个安全网关。LAN内部的节点既可以正常访问防火墙限制以外非敏感的外网(如Internet的大部分站点),又可以 通过安全网关的甄别,利用VPN访问其他的专用网LAN。

由于本应用与原有的三个表在功能和所关心的HOOK点上有所不同,因此我们仿照filter表新建了一个vpn表,VPN功能分布在以下四个部分中:

  • iptables ENCRYPT Target:对于发往安全子网的报文,要求经过ENCRYPT target处理,加密原报文,产生认证码,并将报文封装在公网IPIP_EXT报文头中。ENCRYPT Target配置在vpn表的OUTPUT和FORWARD HOOK点上,根据目的方IP地址来区分是否需要经过ENCRYPT target加密处理。
  • IPIP_EXT协议:在接收该协议报文的处理函数IPIP_EXT_rcv()中用安全子网的IP地址信息代替公网间传输的隧道报文头中的IP地址,然后重新注入IP协议栈底层。
  • iptables IPIP_EXT Match:匹配报文头的协议标识是否为自定义的IPIP_EXT。经过IPIP_EXT_rcv()处理之后的报文必须是IPIP_EXT协议类型的,否则应丢弃。
  • iptables DECRYPT Target:对于接收到的来自安全子网的报文,经过IPIP_EXT协议处理之后,将IP头恢复为安全子网之间通信的IP头,再进入DECRYPT target处理,对报文进行完全解密和解封。

整个报文传输的流程可以用下图表示:


图8 VPN报文流动过程

对于外出报文(源于本地或内网),使用内部地址在FORWARD/OUTPUT点匹配成功,执行ENCRYPT,从Netfilter中返回后作为本地IPIP_EXT协议的报文继续往外发送。

对于接收到的报文,如果协议号为IPPROTO_IPIP_EXT,则匹配IPIP_EXT的Match成功,否则将在 INPUT点被丢弃;继续传送的报文从IP层传给IPIP_EXT的协议处理代码接收,在其中恢复内网IP的报文头后调用netif_rx()重新流入协 议栈。此时的报文将在INPUT/FORWARD点匹配规则,并执行DECRYPT,只有通过了DECRYPT的报文才能继续传送到本机的上层协议或者内 网。

附:iptables设置指令(样例):

iptables -t vpn -P FORWARD DROP
iptables -t vpn -A OUTPUT -d 192.168.0.0/24 -j ENCRYPT
iptables -t vpn -A INPUT -s 192.168.0.0/24 -m ipip_ah -j DECRYPT
iptables -t vpn -A FORWARD -s 192.168.0.0/24 -d 192.168.1.0 -j DECRYPT
iptables -t vpn -A FORWARD -s 192.168.1.0/24 -d 192.168.0.0/24 -j ENCRYPT

其中192.168.0.0/24是目的子网,192.168.1.0/24是本地子网



参考资料

  • [Linus Torvalds,2003] Linux内核源码v2.4.21

  • [Paul Russell,2002] Linux netfilter Hacking HOWTO v1.2

  • [Paul Russell,2002] iptables源码v1.2.1a

  • [Paul Russell,2000] LinuxWorld: San Jose August 2000,Netfilter Tutorial

  • [Oskar Andreasson,2001] iptables Tutorial 1.0.9

Linux网络代码导读v0.2 - collide的专栏 - CSDNBlog

Linux网络代码导读v0.2

作者:yawl <>
主页:http://www.nsfocus.com

1 前言

许多人在分析linux代码时对网络部分(主要是src/linux/net,src/linux/include/net及
src/linux/include/linux目录下的文件)比较感兴趣,确实,尽管已经从书本上学到了大量的
TCP/IP原理,不读源码的话,头脑中还是建立不起具体的印象。而分析这部分代码的一个问
题便是代码众多而资料很少。这篇文章的目的就是勾勒出一个框架,让读者能够大致能够了
解TCP/IP究竟是怎么工作的。以前见到的许多代码分析都是基于2.0内核的,在新的内核中
许多函数变了名字,这尤其给初学者带来了困难,本文是以2.4.0-test9的代码作例子,这样
对照代码时可能更清晰些。

其实网络部分的代码我只对防火墙部分一行行仔细分析过,其他许多地方也只是一知半解,
如果理解有误,欢迎指正。

建议在看本文的同时,用source insight(www.soucedyn.com)建立一个项目,同时看代码,
这样可能效果更好点。我也用过其他的一些工具,但在分析大量的代码的时候,没有一个工
具比它更方便的了。


2 正文

ISO的七层模型都非常熟悉了,当然,对于internet,用四层模型更为适合。在这两份模型里,
网络协议以层次的形式出现。而LINUX的内核代码中,严格分出清楚的层次却比较困难,因
为除了一些"内核线程(kernel thread外)",整个内核其实是个单一的进程。因此所谓"网络层
",只是一组相关的函数,而各层之间大多通过一般的函数调用的方式完成交互。

而从逻辑上,网络部分的代码更应该这样分层更为合理:
.BSD socket层:这一部分处理BSD socket相关操作,每个socket在内核中以struct socket结
构体现。
这一部分的文件主要有:/net/socket.c /net/protocols.c etc

.INET socket层:BSD socket是个可以用于各种网络协议的接口,而当用于tcp/ip,即建立
了AF_INET形式的socket时,还需要保留些额外的参数,于是就有了struct sock结构。
文件主要有:/net/ipv4/protocol.c /net/ipv4/af_inet.c /net/core/sock.c etc

.TCP/UDP层:处理传输层的操作,传输层用struct inet_protocol和struct proto两个结构表
示。
文件主要有:/net/ipv4/udp.c /net/ipv4/datagram.c /net/ipv4/tcp.c /net/ipv4/tcp_input.c
/net/ipv4//tcp_output.c /net/ipv4/tcp_minisocks.c /net/ipv4/tcp_output.c
/net/ipv4/tcp_timer.c etc

.IP层:处理网络层的操作,网络层用struct packet_type结构表示。
文件主要有:/net/ipv4/ip_forward.c ip_fragment.c ip_input.c ip_output.c etc.

.数据链路层和驱动程序:每个网络设备以struct net_device表示,通用的处理在dev.c中,
驱动程序都在/driver/net目录下。

网络部分还有很多其他文件,如防火墙,路由等,一般根据看到名字便能猜测出相应的处
理,此处不再赘述。

现在我要给出一张表,全文的内容就是为了说明这张表(如果你觉得我在文章中的语言比较
乏味,尽可抛掉他们,结合这张表自己看代码)。在我最初看网络部分代码时,比较喜欢
《linux kernel internals》的第八章的一段,其中有一个进程A通过网络远程向另一进程B发
包的例子,详细介绍了一个数据包如何从网络堆栈中走过的过程。我觉得这样可以更迅速的
帮助读者看清森林的全貌,因此本文参照这种结构来
叙述。

^
| sys_read fs/read_write.c
| sock_read net/socket.c
| sock_recvmsg net/socket.c
| inet_recvmsg net/ipv4/af_inet.c
| udp_recvmsg net/ipv4/udp.c
| skb_recv_datagram net/core/datagram.c
| -------------------------------------------
| sock_queue_rcv_skb include/net/sock.h
| udp_queue_rcv_skb net/ipv4/udp.c
| udp_rcv net/ipv4/udp.c
| ip_local_deliver_finish net/ipv4/ip_input.c
| ip_local_deliver net/ipv4/ip_input.c
| ip_recv net/ipv4/ip_input.c
| net_rx_action net/dev.c
| -------------------------------------------
| netif_rx net/dev.c
| el3_rx driver/net/3c309.c
| el3_interrupt driver/net/3c309.c

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

| sys_write fs/read_write.c
| sock_writev net/socket.c
| sock_sendmsg net/socket.c
| inet_sendmsg net/ipv4/af_inet.c
| udp_sendmsg net/ipv4/udp.c
| ip_build_xmit net/ipv4/ip_output.c
| output_maybe_reroute net/ipv4/ip_output.c
| ip_output net/ipv4/ip_output.c
| ip_finish_output net/ipv4/ip_output.c
| dev_queue_xmit net/dev.c
| --------------------------------------------
| el3_start_xmit driver/net/3c309.c
V



我们假设的环境如下:有两台主机通过互联网联在一起,其中一台机子运行这一个进程A,
另外一台运行进程B,进程A将向进程B发出一条信息,比如"Hello",而B接受此信息。
TCP处理本身非常复杂,为了便于叙述,在后面我们将用UDP作为例子。


2.1 建立套接字

在数据发送之前,要建立一个套接字(socket),在两边的程序中都会调用如下语句:

...
int sockfd;
sockfd=socket(AF_INET,SOCK_DGRAM,0);
...

这是个系统调用,因此会通过0x80中断进入系统内核,调用内核中的相应函数.当寻找系统调
用在内核中的对应流程时,一般前面加入"sys_"再找就是了,如对fork来说,就是调用
sys_fork。但是socket相关调用有些特殊,所有的这类调用都是通过一个入口,即
sys_socketcall进入系统内核,然后再通过参数调用具体的sys_socket,socket_bind等函数。

sys_socket会调用sock_create产生一个struct socket结构(见include/linux/net.h),每个套
接字在内核中都有一个这样的结构对应,在初始化了此结构的一些通用成员后(如分配
inode,根据第二个参数为type项赋值等),会根据其一个参数作响应的调度,即这
一句:
...
net_families[family]->create(sock, protocol);
...

我们的程序的第一个参数是AF_INET,所以此函数指针会指向inet_create();
(net_families是个数组,保留了网络协议族(net families)的信息,而这些协议族用
sock_register加载。)

在struct socket结构结构中最重要的信息保留在struct sock结构中,这个结构在网络代码中
经常使用,建议把它和其他常见结构(如struct sk_buff)打印出来放在手边。在inet_create
会为此结构分配内存,并根据套接字类型(其实就是socket函数的第二个参数),作各自不
同的初始化:
...
if (sk->prot->init)
sk->prot->init(sk);
...

如果类型是SOCK_STREAM的话会调用tcp_v4_init_sock,而SOCK_DGRAM类型的
socket没有额外的初始化了,到此socket调用结束。

还有一个值得注意的地方是当inet_create()调用完后,会接着调用sock_map_fd函数,这
个函数中会为套接字分配一个文件描述符并分配一个file文件。在应用层便可象处理文件一样
处理套接字了。

开始的时候可能有些流程难以跟下去,主要便是这些函数指针的实际指向会根据类型变化。


2.2 发送数据

当进程A想发送数据时,程序中会调用如下语句(如果用sendto函数的话会走类似的流程,
略):
...
write(sockfd,"Hello",strlen("Hello"));
...

write在内核中对应的函数就是sys_write,此函数首先根据文件描述符找到struct file结构,
如果此文件存在(file指针非空)且可写(file->f_mode & FMODE_WRITE为true),便调
用此文件结构的写操作:
...
if (file->f_op && (write = file->f_op->write) != NULL)
ret = write(file, buf, count, &file->f_pos);
...

其中f_op是个struct file_operations结构指针,在sock_map_fd中将其指向socket_file_ops,
其定义如下(/net/socket.c):
static struct file_operations socket_file_ops = {
llseek: sock_lseek,
read: sock_read,
write: sock_write,
poll: sock_poll,
ioctl: sock_ioctl,
mmap: sock_mmap,
open: sock_no_open, /* special open code to disallow open via /proc */
release: sock_close,
fasync: sock_fasync,
readv: sock_readv,
writev: sock_writev
};

此时wirte函数指针显然指向了sock_write,我们跟下去看,此函数将一个字符串缓冲整理成
struct msghdr,最后调用了sock_sendmsg.

sock_sendmsg中的scm_send我不了解(scm是Socket level control messages的简写),好
在它也不是很关键,我们注意到这句:
...
sock->ops->sendmsg(sock, msg, size, &scm);
...

又是个函数指针,sock->ops在inet_create()函数中被初始化,由于我们我们是UDP的套
接字,sock->ops指向了inet_dgram_ops(即sock->ops = &inet_dgram_ops;),其定义在
net/ipv4/Af_inet.c中:
struct proto_ops inet_dgram_ops = {
family: PF_INET,

release: inet_release,
bind: inet_bind,
connect: inet_dgram_connect,
socketpair: sock_no_socketpair,
accept: sock_no_accept,
getname: inet_getname,
poll: datagram_poll,
ioctl: inet_ioctl,
listen: sock_no_listen,
shutdown: inet_shutdown,
setsockopt: inet_setsockopt,
getsockopt: inet_getsockopt,
sendmsg: inet_sendmsg,
recvmsg: inet_recvmsg,
mmap: sock_no_mmap,
};

因此我们要看得便是inet_sendmsg()函数了,而马上,这个函数又通过函数指针调用了另
一函数:
...
sk->prot->sendmsg(sk, msg, size);
...

我们不得不再次寻找其具体指向。看到这里,说点题外话,怎么才能找到其具体定义呢?我
一般是这样:对上例而言,sk是个struct sock结构,到其定义(linux/net/sock.h中)出看到
prot是个struct proto结构,此时我们便在源代码树中寻找所有此结构的实例(这些诸如跳到
定义,寻找引用等工作在source insight中实在太方便快速了^_^),很快便会发现诸如
udp_prot,tcp_prot,raw_prot等,猜测是用了udp_prot,便再找一下它在源代码中的引用情
况,果然发现在inet_create中有这么一句:
...
prot=&udp_prot;
...

其实如果前面看inet_create函数时仔细一点会早点发现了,但我总没有这么细心:)。

我们顺着udp_sendmsg往下走:
在这个函数的主要作用是填充UDP头(源端口,目的端口等),接着调用了
ip_route_output,作用是查找出去的路由,而后:
...
ip_build_xmit(sk,
(sk->no_check == UDP_CSUM_NOXMIT ?
udp_getfrag_nosum :
udp_getfrag),
&ufh, ulen, &ipc, rt, msg->msg_flags);
...

ip_build_xmit函数的很大比例是生成sk_buff,并为数据包加入IP头。
后面有这么一句:
...
NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb, NULL,
rt->u.dst.dev,output_maybe_reroute);
...

简单的说,在没有防火墙代码干预的情况下,你可以将此处理解为直接调用
output_maybe_reroute,(具体可参看绿盟月刊14期中的《内核防火墙netfilter入门 》)
而output_maybe_reroute中只有一句:
return skb->dst->output(skb);

依旧照上面的方法(不过这个确实不太好找),发现其实这个指针是在ip_route_output中指
定的,(提示:ip_route_output_slow中:rth->u.dst.output=ip_output;),ip_route_output的
作用便是查找路由,并将结果记录到skb->dst中。

于是,我们开始看ip_output函数了,而它马上又走向了ip_finish_output~~。
每个网络设备,如网卡,在内核中由一个net_device表示,在ip_finish_output中找到其用到
的设备(也是在ip_route_output中初始化的),这个参数在会传给netfilter在
NF_IP_POST_ROUTING点登记的函数,结束后调用ip_finish_output2,而这个函数中又会
调用:
...
hh->hh_output(skb);
...

闲话少叙,实际调用了dev_queue_xmit,到此我们完成了TCP/IP层的工作,开始数据链路
层的处理。

在做了一些判断之后,实际的调用是这句:
...
dev->hard_start_xmit(skb, dev);
...

这个函数是在网卡的驱动程序中定义的,每个不同的网卡有不同的处理,我的网卡是比较通
用的3c509(其驱动程序是3c509.c),在网卡处理化的时候(el3_probe),有:
...
dev->hard_start_xmit = &el3_start_xmit;
...

再往下便是IO操作,将数据包真正的发到网络上去,至此发送过程结束。

中间我说的有些草率,完全没顾的上中间的如出错,阻塞,分片等特殊处理,只是将理想的
过程描述出来。
这篇短文的目的也只是帮助大家建立个大致的印象,其实每个地方的都有非常复杂的处理
(尤其是TCP部分)。


2.3 接受数据

当有数据到达网卡的时候,会产生一个硬件中断,然后调用网卡驱动程序中的函数来处理,
对我的3c509网卡来说,其处理函数为:el3_interrupt。(相应的IRQ号是在系统启动,网卡
初始化时通过request_irq函数决定的。)这个中断处理程序首先要做的当然就是进行一些IO
操作将数据读入(读IO用inw函数),当数据帧成功接受后,执行el3_rx(dev)进一步处理。

在el3_rx中,收到的数据报会被封装成struct sk_buff,并脱离驱动程序,转到通用的处理函
数netif_rx(dev.c)中。为了CPU的效率,上层的处理函数的将采用软中断的方式激活,
netif_rx的一个重要工作就是将传入的sk_buff放到等候队列中,并置软中断标志位,然后便
可放心返回,等待下一次网络数据包的到来:
...
__skb_queue_tail(&queue->input_pkt_queue,skb);
__cpu_raise_softirq(this_cpu, NET_RX_SOFTIRQ);
...

这个地方在2.2内核中一直被称为"底半"处理--bottom half,其内部实现基本类似,目的是快
速的从中断中返回。

过了一段时间后,一次CPU调度会由于某些原因会发生(如某进程的时间片用完)。在进程
调度函数即schedule()中,会检查有没有软中断发生,若有则运行相应的处理函数:
...
if (softirq_active(this_cpu) & softirq_mask(this_cpu))
goto handle_softirq;
handle_softirq_back:
...
...
handle_softirq:
do_softirq();
goto handle_softirq_back;
...

在系统初始化的时候,具体说是在net_dev_init中,此软中断的处理函数被定为
net_rx_action:
...
open_softirq(NET_TX_SOFTIRQ, net_tx_action, NULL);
...

当下一次进程调度被执行的时候,系统会检查是否发生NET_TX_SOFTIRQ软中断,若有则
调用net_rx_action。

net_tx_action函数既是2.2版本中的net_bh函数,在内核中有两个全局变量用来登记网络层
的,一个是链表ptype_all,另外一个是数组ptype_base[16],他们记载了所有内核能够处理
的第三层(按照OSI7层模型)协议。每个网络层的接收处理由一个
struct packet_type表示,而这个结构将通dev_add_pack函数将他们登记到ptype_all或
ptype_base中。只有packet_type中的type项为ETH_P_ALL时,才会登记到ptype_all链表
中,否则如ip_packet_type,会在数组ptype_base[16]找到相应的位置。两者不同点是如果
是以ETH_P_ALL类型登记,那么处理函数会受到所有类型的包,否则只能处理自己登记的类
型的。

skb->protocol是在el3_rx中赋值的,其实就是以太帧头信息中提取出的上层协议名,对于我
们的例子来说,这个值是ETH_P_IP,所以在net_tx_action中,会选择IP层的接收处理函
数,而从ip_packet_type 不难看出,这个函数便是ip_recv()。
pt_prev->func(实际指向ip_recv)前面有一个atomic_inc(&skb->users)操作(在2.2内核
中这个地方是一句skb_clone,原理类似),目的是增加这个sk_buff的引用数。网络层的接
收函数在处理完或因为某些原因要丢弃此sk_buff时(如防火墙)会调用kfree_skb,而
kfree_skb中首先会检查是否还有其他地方需要此函数,如果没有地方再用,才真正释放此内
存(__kfree_skb),否则只是计数器减一。

现在我们便来看看ip_recv(net/ipv4/ip_input.c)。这个函数的操作是非常清晰的:首先检查
这个包的合法性(版本号,长度,校验和等是否正确),如果合法则进行接下来的处理。在
2.4内核中,为了灵活处理防火墙代码,将原来的一个ip_recv分成了两部分,即将将原来的
的ip_recv的后半段独立出一个ip_rcv_finish函数。在ip_rcv_finish中,一部分是带有IP选项
(如源路由等)的IP包,例外就是通过ip_route_input查找路由,并将结果记录到skb->dst
中。此时接收到的包有两种,发往本地进程(需要传往上层协议)或转发(用作网关时),
此时需要的处理函数也不相同,如果传往本地,则调用ip_local_deliver(/net/ipv4/ip_input.c),
否则调用ip_forward(/net/ipv4/ip_forward.c).skb->dst->input这个函数指针会将数据报领上
正确的道路。

对我们的例子而言,此时应该是调用ip_local_deliver的时候了。
发来的包很有可能是碎片包,这样的话则首先应该把它们组装好再传给上层协议,这当然也
是ip_local_deliver函数所做的第一份工作,如果组装成功(返回的sk_buff不为空),则继续
处理(详细的组装算法可参见绿盟月刊13期中的《IP分片重组的分析和常见碎片攻击》)。
但此时代码又被netfilter一分为二了,象前面一样,我们直接到后半段,即
ip_local_deliver_finish(/net/ipv4/ip_input.c)中去。

传输层(如TCP,UDP,RAW)的处理被登记到了inet_protos中(通过
inet_add_protocol)。ip_local_deliver_finish会根据IP头信息中的上层协议信息(即
iph->protocol),调用相应的处理函数。为了简便,我们采用了udp,此时的ipprot->handler
实际便是udp_rcv了。

前面已经提到,在应用程序中建立的每个socket在内核中有一个struct socket/struct sock对
应。udp_rcv会通过udp_v4_lookup首先找到在内核中的sock,然后将其作参数调用
udp_queue_rcv_skb(/net/ipv4/udp.c)。马上,sock_queue_rcv_skb函数被调用,此函数
将sk_buff放入等待队列,然后通知上层数据到达:
...
kb_set_owner_r(skb, sk);
skb_queue_tail(&sk->receive_queue, skb);
if (!sk->dead)
sk->data_ready(sk,skb->len);
return 0;
...

sk->data_ready的定义在sock结构初始化的时候(sock_init_data):
...
sk->data_ready=sock_def_readable;
...

现在我们便要从上往下看起了:
进程B要接收数据报,在程序里调用:
...
read(sockfd,buff,sizeof(buff));
...

此系统调用在内核中的函数是sys_read(fs/read_write.c)以下的处理类似write的操作,不再
详述.udp_recvmsg函数会调用skb_recv_datagram,如果数据还没有到达,且socket设为阻
塞模式时,进程会挂起(signal_pending(current)),直到data_ready通知进程资源得到满
足后继续处理(wake_up_interruptible(sk->sleep);)。

2.4 skbuff

网络代码中有大量的处理涉及对sk_buff的操作,尽管此文中尽量将其回避了,但在仔细分析
的时候则必须对此作分析,数据包在网络协议层是以sk_buff的形式传送处理的,可以说它是
网络部分最重要的数据结构。具体分析建议参看alan cox的《Network Buffers And Memory
Management》,这篇发表在1996年10月的linux journal上。

这里引用phrack 55-12期中的一幅图,尽管它只描绘了sk_buff的极小的一个侧面,但却非常
有用,尤其是当你像我一样总忘记了skb_put是向前还是向后调指针的时候:)

--- -----------------hand
^ | |
| | | ^ skb_push
| | | |
| -----------------data--- ---
| | | ^ |
true | | | v skb_pull
size | | len
| | | | ^ skb_trim
| | | v |
| -----------------tail--- ---
| | | |
| | | v skb_put
v | |
--- -----------------end

linux网络层效率:在linux的网络层代码中指针被大量应用,其目的就是避免数据拷贝这类耗
费系统资源的操作。一个数据包的数据段部分在读入或发出时只经过两次拷贝,即从网卡中
考到核心态内存,和从核心态内存考到用户态内存。前些天看到,在一些提高sniffer抓包效
率的尝试中,turbo packet(一个内核补丁)采用了核心态和
用户态共享一段内存的办法,又减少了一次数据拷贝,进一步提高了效率。


3 后记:
这次的投稿又是到了最后关头仓促写出来的,看着里面拙劣的文笔,实在觉得有点对不住观
众~~如果有时间我会把这部分好好重写的,其实这也是我一直的愿望:)


4 参考文献:

[1.] phrack 55-12期
[2.] 2nd Edition
[3.] Network Buffers And Memory Management Alan Cox
http://www2.linuxjournal.com/lj-issues/issue30/1312.html
[4.] 浙大源码分析报告《Linux网络设备分析》潘纲
[5.] Linux IP Networking--A Guide to the Implementation and Modification of theLinux
Poptocol Stack
Glenn Herrin May 31,2000 http://www.movement.uklinux.net/linux-net.pdf

IP包的生成和发送接口 - collide的专栏

IP包的生成和发送接口

====================
(1) Linux内核中有3种基本的IP包生成器, 它们分别为ip_build_xmit(), ip_queue_xmit(),
ip_build_and_send_pkt(). ip_build_and_send_pkt()是一简单的IP包头封装接口,
它接照输入包的路由添加一个IP包头后直接输出,不进行分片处理, 用于tcp_v4_send_synack()中.
ip_send_reply()是基于ip_build_xmit()的一个函数,
用于tcp_v4_send_ack()和tcp_v4_send_reset()中.

(2) ip_build_xmit()使用用户定义的回调函数直接读取用户数据片段生成IP包输出.
如果需要分片,ip_build_xmit()按照最后一个片段到第一个片段的顺序来生成IP包,
这是因为第一个IP包片段的数据区可能包含对整个IP包数据区的校验码,
在回调函数中用户可能会计算输出数据的校验码,
采用从后向前的输出顺序可使校验码自然地写到第一个片段中.

(3) ip_queue_xmit()完成面向连接套接字输出包的路由和IP包头封装. 当套接字处于连接状态时,
所有从套接字发出的包都具有确定的路由, 无需为每一个输出包查询它的目的入口,
可将套接字直接绑定到路由入口上, 这由套接字的目的缓冲指针(dst_cache)来完成.
ip_queue_xmit()首先为输入包建立IP包头, 经过本地包过滤器后,
再将IP包分片输出(ip_fragment), 如果需要的话.

(4) IP包生成器的输出经过本地包过滤器后输入包的路由入口, 对于点播地址来说,
输入到IP输出器中(ip_output); 对于广播或同播地址来说, 输入到IP同播输出器(ip_mc_output).
在IP输出器中, 再经过路由后过滤器,
进入路由的"邻居"入口(dst->neighbour->output)或硬件帧头缓冲入口(dst->hh->hh_output).
邻居是指与主机自已在网络接口设备层次上直达的相邻主机.
邻居负责解析输出包的硬件投送地址, 将包投递给相邻的目的主机或网关主机.
当邻居成功解析包的硬件投送地址时, 将在包的目的入口上创建硬件帧头缓冲结构(dst->hh),
使得后继包可以直接使用组装好的帧头, 直接将包传递给包调度器(dev_queue_xmit).
包调度器按照包的优先级进行重排, 最后将包提交给设备驱动程序发送(dev->hard_start_xmit).

IP包生成接口
------------
; net/ipv4/ip_output.c:

int sysctl_ip_default_ttl = IPDEFTTL; 缺省的IP包生存期为64

/*
* Add an ip header to a skbuff and send it out.
*/
int ip_build_and_send_pkt(struct sk_buff *skb, struct sock *sk,
对包的数据体添加IP头后直接输出
u32 saddr, u32 daddr, struct ip_options *opt)
{
struct rtable *rt = (struct rtable *)skb->dst;
struct iphdr *iph;

/* Build the IP header. */
if (opt)
iph=(struct iphdr *)skb_push(skb,sizeof(struct iphdr) + opt->optlen);
else
iph=(struct iphdr *)skb_push(skb,sizeof(struct iphdr));

iph->version = 4;
iph->ihl = 5;
iph->tos = sk->protinfo.af_inet.tos;
iph->frag_off = 0;
if (ip_dont_fragment(sk, &rt->u.dst)) 如果IP包的目的入口禁止分片
iph->frag_off |= htons(IP_DF);
iph->ttl = sk->protinfo.af_inet.ttl; 取套接字协议选项中的生存期
iph->daddr = rt->rt_dst; 取IP包路由的目的地址
iph->saddr = rt->rt_src; 取IP包路由的源地址
iph->protocol = sk->protocol; 取套接字IP协议代码
iph->tot_len = htons(skb->len); IP包总长度
ip_select_ident(iph, &rt->u.dst); 为IP包分配标识号, 禁止分片的IP包标识为零
skb->nh.iph = iph;

if (opt && opt->optlen) {
iph->ihl += opt->optlen>>2;
ip_options_build(skb, opt, daddr, rt, 0); 设置IP选项区
}
ip_send_check(iph); 设置IP包头的校验和

/* Send it out. */
return NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb, NULL, rt->u.dst.dev,
output_maybe_reroute); 过滤输出并且目的路径可能会被改变
}

int ip_build_xmit(struct sock *sk,
int getfrag (const void *,
char *,
unsigned int,
unsigned int), 取数据片段的函数指针
const void *frag, 以上函数的调用参数
unsigned length,
struct ipcm_cookie *ipc, IP包配置信息
struct rtable *rt,
int flags) 从用户数据建立IP包
{
int err;
struct sk_buff *skb;
int df;
struct iphdr *iph;

/*
* Try the simple case first. This leaves fragmented frames, and by
* choice RAW frames within 20 bytes of maximum size(rare) to the long path
*/

if (!sk->protinfo.af_inet.hdrincl) { 如果IP包头不由用户创建
length += sizeof(struct iphdr); 取IP包总长

/*
* Check for slow path.
*/
if (length > rt->u.dst.pmtu || ipc->opt != NULL) 如果包长度大于目的入口的最大片断长
return ip_build_xmit_slow(sk,getfrag,frag,length,ipc,rt,flags);
} else {
if (length > rt->u.dst.dev->mtu) { 如果包长大于目的入口设备的最大片段长
ip_local_error(sk, EMSGSIZE, rt->rt_dst, sk->dport, rt->u.dst.dev->mtu);
return -EMSGSIZE;
}
}
if (flags&MSG_PROBE) 测试操作
goto out;

/*
* Do path mtu discovery if needed.
*/
df = 0;
if (ip_dont_fragment(sk, &rt->u.dst)) 如果禁止分片
df = htons(IP_DF);

/*
* Fast path for unfragmented frames without options.
*/
{
int hh_len = (rt->u.dst.dev->hard_header_len + 15)&~15;

skb = sock_alloc_send_skb(sk, length+hh_len+15,
0, flags&MSG_DONTWAIT, &err); 为套接字分配发送包
if(skb==NULL)
goto error;
skb_reserve(skb, hh_len); 保留硬件帧头空间
}

skb->priority = sk->priority; 取套接字的优先级
skb->dst = dst_clone(&rt->u.dst); 取路由的目的入口

skb->nh.iph = iph = (struct iphdr *)skb_put(skb, length);

if(!sk->protinfo.af_inet.hdrincl) {
iph->version=4;
iph->ihl=5;
iph->tos=sk->protinfo.af_inet.tos;
iph->tot_len = htons(length);
iph->frag_off = df;
iph->ttl=sk->protinfo.af_inet.mc_ttl;
ip_select_ident(iph, &rt->u.dst);
if (rt->rt_type != RTN_MULTICAST)
iph->ttl=sk->protinfo.af_inet.ttl;
iph->protocol=sk->protocol;
iph->saddr=rt->rt_src;
iph->daddr=rt->rt_dst;
iph->check=0;
iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl);
err = getfrag(frag, ((char *)iph)+iph->ihl*4,0, length-iph->ihl*4);
; 读取用户一片数据
}
else 如果IP包头由用户创建, 直接将用户数据读入IP头所在位置
err = getfrag(frag, (void *)iph, 0, length);

if (err)
goto error_fault;

err = NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb, NULL, rt->u.dst.dev,
output_maybe_reroute);
if (err > 0)
err = sk->protinfo.af_inet.recverr ? net_xmit_errno(err) : 0;
if (err)
goto error;
out:
return 0;

error_fault:
err = -EFAULT;
kfree_skb(skb);
error:
IP_INC_STATS(IpOutDiscards);
return err;
}
static int ip_build_xmit_slow(struct sock *sk,
int getfrag (const void *,
char *,
unsigned int,
unsigned int),
const void *frag,
unsigned length,
struct ipcm_cookie *ipc,
struct rtable *rt,
int flags) 建立IP选项区或者分片输出
{
unsigned int fraglen, maxfraglen, fragheaderlen;
int err;
int offset, mf;
int mtu;
u16 id = 0;

int hh_len = (rt->u.dst.dev->hard_header_len + 15)&~15;
int nfrags=0;
struct ip_options *opt = ipc->opt;
int df = 0;

mtu = rt->u.dst.pmtu;
if (ip_dont_fragment(sk, &rt->u.dst))
df = htons(IP_DF);

length -= sizeof(struct iphdr);

if (opt) {
fragheaderlen = sizeof(struct iphdr) + opt->optlen;
maxfraglen = ((mtu-sizeof(struct iphdr)-opt->optlen) & ~7) + fragheaderlen;
} else {
fragheaderlen = sizeof(struct iphdr);

/*
* Fragheaderlen is the size of 'overhead' on each buffer. Now work
* out the size of the frames to send.
*/

maxfraglen = ((mtu-sizeof(struct iphdr)) & ~7) + fragheaderlen;
} 求最大IP包长

if (length + fragheaderlen > 0xFFFF) {
ip_local_error(sk, EMSGSIZE, rt->rt_dst, sk->dport, mtu);
return -EMSGSIZE;
}

/*
* Start at the end of the frame by handling the remainder.
*/

offset = length - (length % (maxfraglen - fragheaderlen));
取最后一个片段的数据偏移量

/*
* Amount of memory to allocate for final fragment.
*/

fraglen = length - offset + fragheaderlen; 求取后一个片段IP包全长

if (length-offset==0) { 如果用户数据恰好是最大单片数据长度的整数倍
fraglen = maxfraglen;
offset -= maxfraglen-fragheaderlen;
}

/*
* The last fragment will not have MF (more fragments) set.
*/

mf = 0;

/*
* Don't fragment packets for path mtu discovery.
*/

if (offset > 0 && sk->protinfo.af_inet.pmtudisc==IP_PMTUDISC_DO) {
ip_local_error(sk, EMSGSIZE, rt->rt_dst, sk->dport, mtu);
return -EMSGSIZE;
}
if (flags&MSG_PROBE)
goto out;

/*
* Begin outputting the bytes.
*/

do {
char *data;
struct sk_buff * skb;

/*
* Get the memory we require with some space left for alignment.
*/

skb = sock_alloc_send_skb(sk, fraglen+hh_len+15, 0, flags&MSG_DONTWAIT, &err);
if (skb == NULL)
goto error;

/*
* Fill in the control structures
*/

skb->priority = sk->priority;
skb->dst = dst_clone(&rt->u.dst);
skb_reserve(skb, hh_len);

/*
* Find where to start putting bytes.
*/

data = skb_put(skb, fraglen);
skb->nh.iph = (struct iphdr *)data;

/*
* Only write IP header onto non-raw packets
*/

{
struct iphdr *iph = (struct iphdr *)data;

iph->version = 4;
iph->ihl = 5;
if (opt) {
iph->ihl += opt->optlen>>2;
ip_options_build(skb, opt,
ipc->addr, rt, offset);
}
iph->tos = sk->protinfo.af_inet.tos;
iph->tot_len = htons(fraglen - fragheaderlen + iph->ihl*4);
iph->frag_off = htons(offset>>3)|mf|df;
iph->id = id;
if (!mf) {
if (offset || !df) {
/* Select an unpredictable ident only
* for packets without DF or having
* been fragmented.
*/
__ip_select_ident(iph, &rt->u.dst);
id = iph->id;
}

/*
* Any further fragments will have MF set.
*/
mf = htons(IP_MF);
}
if (rt->rt_type == RTN_MULTICAST)
iph->ttl = sk->protinfo.af_inet.mc_ttl;
else
iph->ttl = sk->protinfo.af_inet.ttl;
iph->protocol = sk->protocol;
iph->check = 0;
iph->saddr = rt->rt_src;
iph->daddr = rt->rt_dst;
iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl);
data += iph->ihl*4;
}

/*
* User data callback
*/

if (getfrag(frag, data, offset, fraglen-fragheaderlen)) {
err = -EFAULT;
kfree_skb(skb);
goto error;
}

offset -= (maxfraglen-fragheaderlen); 片段从后向前进行分割, 是为了方便TCP包的校验
fraglen = maxfraglen;

nfrags++;

err = NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb, NULL,
skb->dst->dev, output_maybe_reroute);
if (err) {
if (err > 0)
err = sk->protinfo.af_inet.recverr ? net_xmit_errno(err) : 0;
if (err)
goto error;
}
} while (offset >= 0);

if (nfrags>1)
ip_statistics[smp_processor_id()*2 + !in_softirq()].IpFragCreates += nfrags;
out:
return 0;

error:
IP_INC_STATS(IpOutDiscards);
if (nfrags>1)
ip_statistics[smp_processor_id()*2 + !in_softirq()].IpFragCreates += nfrags;
return err;
}

/*
* Generic function to send a packet as reply to another packet.
* Used to send TCP resets so far. ICMP should use this function too.
*
* Should run single threaded per socket because it uses the sock
* structure to pass arguments.
*/
void ip_send_reply(struct sock *sk, struct sk_buff *skb, struct ip_reply_arg *arg,
unsigned int len)
{
struct {
struct ip_options opt;
char data[40]; 存放IP选项块
} replyopts;
struct ipcm_cookie ipc;
u32 daddr;
struct rtable *rt = (struct rtable*)skb->dst;

if (ip_options_echo(&replyopts.opt, skb)) 将包skb的IP选项刷新到replyopts结构中
return;

daddr = ipc.addr = rt->rt_src;
ipc.opt = NULL;

if (replyopts.opt.optlen) {
ipc.opt = &replyopts.opt;

if (ipc.opt->srr)
daddr = replyopts.opt.faddr;
}

if (ip_route_output(&rt, daddr, rt->rt_spec_dst, RT_TOS(skb->nh.iph->tos), 0))
return;

/* And let IP do all the hard work.

This chunk is not reenterable, hence spinlock.
Note that it uses the fact, that this function is called
with locally disabled BH and that sk cannot be already spinlocked.
*/
bh_lock_sock(sk);
sk->protinfo.af_inet.tos = skb->nh.iph->tos;
sk->priority = skb->priority;
sk->protocol = skb->nh.iph->protocol;
ip_build_xmit(sk, ip_reply_glue_bits, arg, len, &ipc, rt, MSG_DONTWAIT);
bh_unlock_sock(sk);

ip_rt_put(rt);
}
struct ip_reply_arg {
struct iovec iov[2];
int n_iov; /* redundant */
u32 csum;
int csumoffset; /* u16 offset of csum in iov[0].iov_base */
/* -1 if not needed */
};
/*
* Fetch data from kernel space and fill in checksum if needed.
*/
static int ip_reply_glue_bits(const void *dptr, char *to, unsigned int offset,
unsigned int fraglen)
{
struct ip_reply_arg *dp = (struct ip_reply_arg*)dptr;
u16 *pktp = (u16 *)to;
struct iovec *iov;
int len;
int hdrflag = 1;

iov = &dp->iov[0];
if (offset >= iov->iov_len) {
offset -= iov->iov_len;
iov++;
hdrflag = 0;
}
len = iov->iov_len - offset;
if (fraglen > len) { /* overlapping. */
dp->csum = csum_partial_copy_nocheck(iov->iov_base+offset, to, len,
dp->csum);
offset = 0;
fraglen -= len;
to += len;
iov++;
}

dp->csum = csum_partial_copy_nocheck(iov->iov_base+offset, to, fraglen,
dp->csum);

if (hdrflag && dp->csumoffset)
*(pktp + dp->csumoffset) = csum_fold(dp->csum); /* fill in checksum */
return 0;
}

int ip_queue_xmit(struct sk_buff *skb)
{
struct sock *sk = skb->sk;
struct ip_options *opt = sk->protinfo.af_inet.opt;
struct rtable *rt;
struct iphdr *iph;

/* Make sure we can route this packet. */
rt = (struct rtable *)__sk_dst_check(sk, 0); 取套接字所缓冲的发送包的目的路由入口
if (rt == NULL) { 如果尚未缓冲
u32 daddr;

/* Use correct destination address if we have options. */
daddr = sk->daddr; 取套接字的对端地址作为目的地址
if(opt && opt->srr) 如果具有信源路由选项
daddr = opt->faddr; 取信源路由的转发地址作为目的地址

/* If this fails, retransmit mechanism of transport layer will
* keep trying until route appears or the connection times itself
* out.
*/
if (ip_route_output(&rt, daddr, sk->saddr,
RT_TOS(sk->protinfo.af_inet.tos) | RTO_CONN | sk->localroute,
sk->bound_dev_if)) 查询目的地址的路由目的入口
goto no_route;
__sk_dst_set(sk, &rt->u.dst); 将该路由入口缓冲到套接字上
}
skb->dst = dst_clone(&rt->u.dst); 将路由入口绑定到发送包

if (opt && opt->is_strictroute && rt->rt_dst != rt->rt_gateway)
goto no_route; 如果是指定严格信源路由并且其转发地址不等于网关地址,则操作失败

/* OK, we know where to send it, allocate and build IP header. */
iph = (struct iphdr *) skb_push(skb, sizeof(struct iphdr) + (opt ? opt->optlen :
0));
*((__u16 *)iph) = htons((4 <<>protinfo.af_inet.tos & 0xff));
iph->tot_len = htons(skb->len);
iph->frag_off = 0;
iph->ttl = sk->protinfo.af_inet.ttl;
iph->protocol = sk->protocol;
iph->saddr = rt->rt_src;
iph->daddr = rt->rt_dst;
skb->nh.iph = iph;
/* Transport layer set skb->h.foo itself. */

if(opt && opt->optlen) { 建立IP选项区
iph->ihl += opt->optlen >> 2;
ip_options_build(skb, opt, sk->daddr, rt, 0);
}

return NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb, NULL, rt->u.dst.dev,
ip_queue_xmit2); 过滤输出

no_route:
IP_INC_STATS(IpOutNoRoutes);
kfree_skb(skb);
return -EHOSTUNREACH;
}

/* Queues a packet to be sent, and starts the transmitter if necessary.
* This routine also needs to put in the total length and compute the
* checksum. We use to do this in two stages, ip_build_header() then
* this, but that scheme created a mess when routes disappeared etc.
* So we do it all here, and the TCP send engine has been changed to
* match. (No more unroutable FIN disasters, etc. wheee...) This will
* most likely make other reliable transport layers above IP easier
* to implement under Linux.
*/
static inline int ip_queue_xmit2(struct sk_buff *skb)
{
struct sock *sk = skb->sk;
struct rtable *rt = (struct rtable *)skb->dst;
struct net_device *dev;
struct iphdr *iph = skb->nh.iph;

dev = rt->u.dst.dev;

/* This can happen when the transport layer has segments queued
* with a cached route, and by the time we get here things are
* re-routed to a device with a different MTU than the original
* device. Sick, but we must cover it.
*/
if (skb_headroom(skb) <>hard_header_len && dev->hard_header) {
如果包可用的硬件帧头空间不足
struct sk_buff *skb2;

skb2 = skb_realloc_headroom(skb, (dev->hard_header_len + 15) & ~15);
复制并重新分配原包
kfree_skb(skb);
if (skb2 == NULL)
return -ENOMEM;
if (sk)
skb_set_owner_w(skb2, sk); 设置包的拥有套接字
skb = skb2;
iph = skb->nh.iph;
}

if (skb->len > rt->u.dst.pmtu)
goto fragment;

if (ip_dont_fragment(sk, &rt->u.dst))
iph->frag_off |= __constant_htons(IP_DF);

ip_select_ident(iph, &rt->u.dst);

/* Add an IP checksum. */
ip_send_check(iph);

skb->priority = sk->priority;
return skb->dst->output(skb);

fragment:
if (ip_dont_fragment(sk, &rt->u.dst)) {
/* Reject packet ONLY if TCP might fragment
* it itself, if were careful enough.
*/
iph->frag_off |= __constant_htons(IP_DF);
NETDEBUG(printk(KERN_DEBUG "sending pkt_too_big to self\n"));

icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED,
htonl(rt->u.dst.pmtu));
kfree_skb(skb);
return -EMSGSIZE;
}
ip_select_ident(iph, &rt->u.dst);
return ip_fragment(skb, skb->dst->output);
}

/*
* This IP datagram is too large to be sent in one piece. Break it up into
* smaller pieces (each of size equal to IP header plus
* a block of the data of the original IP data part) that will yet fit in a
* single device frame, and queue such a frame for sending.
*
* Yes this is inefficient, feel free to submit a quicker one.
*/

int ip_fragment(struct sk_buff *skb, int (*output)(struct sk_buff*))
{
struct iphdr *iph;
unsigned char *raw;
unsigned char *ptr;
struct net_device *dev;
struct sk_buff *skb2;
unsigned int mtu, hlen, left, len;
int offset;
int not_last_frag;
struct rtable *rt = (struct rtable*)skb->dst;
int err = 0;

dev = rt->u.dst.dev;

/*
* Point into the IP datagram header.
*/

raw = skb->nh.raw;
iph = (struct iphdr*)raw;

/*
* Setup starting values.
*/

hlen = iph->ihl * 4;
left = ntohs(iph->tot_len) - hlen; /* Space per frame */
mtu = rt->u.dst.pmtu - hlen; /* Size of data space */
ptr = raw + hlen; /* Where to start from */

/*
* Fragment the datagram.
*/

offset = (ntohs(iph->frag_off) & IP_OFFSET) << 3;
not_last_frag = iph->frag_off & htons(IP_MF);

/*
* Keep copying data until we run out.
*/

while(left > 0) {
len = left;
/* IF: it doesn't fit, use 'mtu' - the data space left */
if (len > mtu)
len = mtu;
/* IF: we are not sending upto and including the packet end
then align the next start on an eight byte boundary */
if (len < left) {
len &= ~7;
}
/*
* Allocate buffer.
*/

if ((skb2 = alloc_skb(len+hlen+dev->hard_header_len+15,GFP_ATOMIC)) == NULL) {
NETDEBUG(printk(KERN_INFO "IP: frag: no memory for new fragment!\n"));
err = -ENOMEM;
goto fail;
}

/*
* Set up data on packet
*/

skb2->pkt_type = skb->pkt_type;
skb2->priority = skb->priority;
skb_reserve(skb2, (dev->hard_header_len+15)&~15);
skb_put(skb2, len + hlen);
skb2->nh.raw = skb2->data;
skb2->h.raw = skb2->data + hlen;

/*
* Charge the memory for the fragment to any owner
* it might possess
*/

if (skb->sk)
skb_set_owner_w(skb2, skb->sk);
skb2->dst = dst_clone(skb->dst);
skb2->dev = skb->dev;

/*
* Copy the packet header into the new buffer.
*/

memcpy(skb2->nh.raw, raw, hlen);

/*
* Copy a block of the IP datagram.
*/
memcpy(skb2->h.raw, ptr, len);
left -= len;

/*
* Fill in the new header fields.
*/
iph = skb2->nh.iph;
iph->frag_off = htons((offset >> 3));

/* ANK: dirty, but effective trick. Upgrade options only if
* the segment to be fragmented was THE FIRST (otherwise,
* options are already fixed) and make it ONCE
* on the initial skb, so that all the following fragments
* will inherit fixed options.
*/
if (offset == 0)
ip_options_fragment(skb);

/*
* Added AC : If we are fragmenting a fragment that's not the
* last fragment then keep MF on each bit
*/
if (left > 0 || not_last_frag)
iph->frag_off |= htons(IP_MF);
ptr += len;
offset += len;

#ifdef CONFIG_NETFILTER
/* Connection association is same as pre-frag packet */
skb2->nfct = skb->nfct;
nf_conntrack_get(skb2->nfct);
#ifdef CONFIG_NETFILTER_DEBUG
skb2->nf_debug = skb->nf_debug;
#endif
#endif

/*
* Put this fragment into the sending queue.
*/

IP_INC_STATS(IpFragCreates);

iph->tot_len = htons(len + hlen);

ip_send_check(iph);

err = output(skb2);
if (err)
goto fail;
}
kfree_skb(skb);
IP_INC_STATS(IpFragOKs);
return err;

fail:
kfree_skb(skb);
IP_INC_STATS(IpFragFails);
return err;
}

IP包的输出接口
--------------

/* Don't just hand NF_HOOK skb->dst->output, in case netfilter hook
changes route */
static inline int 发送包的目的入口指针可能被包过滤器所改变,
因此不能向包过滤器直接提供目的入口的输出函数
output_maybe_reroute(struct sk_buff *skb)
{
return skb->dst->output(skb); 通过目的入口输出, 对于点播(UNICAST)地址来说,
指向ip_output()
}

int ip_output(struct sk_buff *skb) 点播地址的IP包输出口
{
#ifdef CONFIG_IP_ROUTE_NAT
struct rtable *rt = (struct rtable*)skb->dst;
#endif

IP_INC_STATS(IpOutRequests);

#ifdef CONFIG_IP_ROUTE_NAT
if (rt->rt_flags&RTCF_NAT)
ip_do_nat(skb);
#endif

return ip_finish_output(skb);
}
__inline__ int ip_finish_output(struct sk_buff *skb)
{
struct net_device *dev = skb->dst->dev;

skb->dev = dev; 设置包的输出设备
skb->protocol = __constant_htons(ETH_P_IP); 设置包的帧类型

return NF_HOOK(PF_INET, NF_IP_POST_ROUTING, skb, NULL, dev,
ip_finish_output2);
}
static inline int ip_finish_output2(struct sk_buff *skb)
{
struct dst_entry *dst = skb->dst; 取IP包绑定的目的入口
struct hh_cache *hh = dst->hh; 取目的入口的硬件帧头缓冲结构

#ifdef CONFIG_NETFILTER_DEBUG
nf_debug_ip_finish_output2(skb);
#endif /*CONFIG_NETFILTER_DEBUG*/

if (hh) { 如果帧头缓冲结构存在
read_lock_bh(&hh->hh_lock);
memcpy(skb->data - 16, hh->hh_data, 16); 直接拷贝硬件帧头
read_unlock_bh(&hh->hh_lock);
skb_push(skb, hh->hh_len);
return hh->hh_output(skb); 通过帧头缓冲输出, 一般指向dev_queue_xmit()
} else if (dst->neighbour) 通过邻居结构(设备硬件地址解析和缓冲结构)输出
return dst->neighbour->output(skb); 指向neigh_resolve_output()
; 关于neighbour结构和arp过程可看自已"Linux硬件地址解析过程"的帖子
printk(KERN_DEBUG "khm\n");
kfree_skb(skb);
return -EINVAL;
}

IP包创建过程中选项区的操作
--------------------------

; net/ipv4/ip_options.c:

struct ip_options { IP选项区索引结构, 当接收到IP包时,
包缓冲的控制块(cb)前部即为此结构
__u32 faddr; /* Saved first hop address */
unsigned char optlen;
unsigned char srr;
unsigned char rr;
unsigned char ts;
unsigned char is_setbyuser:1, /* Set by setsockopt? */
is_data:1, /* Options in __data, rather than skb */
is_strictroute:1, /* Strict source route */
srr_is_hit:1, /* Packet destination addr was our one */
is_changed:1, /* IP checksum more not valid */
rr_needaddr:1, /* Need to record addr of outgoing dev */
ts_needtime:1, /* Need to record timestamp */
ts_needaddr:1; /* Need to record addr of outgoing dev */
unsigned char router_alert;
unsigned char __pad1;
unsigned char __pad2;
unsigned char __data[0];
};
struct inet_skb_parm
{
struct ip_options opt; /* Compiled IP options */
unsigned char flags;

#define IPSKB_MASQUERADED 1
#define IPSKB_TRANSLATED 2
#define IPSKB_FORWARDED 4
};

#define IPCB(skb) ((struct inet_skb_parm*)((skb)->cb))

/*
* Options "fragmenting", just fill options not
* allowed in fragments with NOOPs.
* Simple and stupid 8), but the most efficient way.
*/

void ip_options_fragment(struct sk_buff * skb)
{
unsigned char * optptr = skb->nh.raw; ??? 不是指向IP头吗,怎么用作IP选项区???
struct ip_options * opt = &(IPCB(skb)->opt); 取包缓冲控制块(cb)
int l = opt->optlen;
int optlen;

while (l > 0) {
switch (*optptr) {
case IPOPT_END:
return;
case IPOPT_NOOP:
l--;
optptr++;
continue;
}
optlen = optptr[1];
if (optlen<2>l)
return;
if (!IPOPT_COPIED(*optptr)) 将所有没有片断复制标志的选项置换成空选项
memset(optptr, IPOPT_NOOP, optlen);
l -= optlen;
optptr += optlen;
}
opt->ts = 0;
opt->rr = 0;
opt->rr_needaddr = 0;
opt->ts_needaddr = 0;
opt->ts_needtime = 0;
return;
}

/*
* Write options to IP header, record destination address to
* source route option, address of outgoing interface
* (we should already know it, so that this function is allowed be
* called only after routing decision) and timestamp,
* if we originate this datagram.
*
* daddr is real destination address, next hop is recorded in IP header.
* saddr is address of outgoing interface.
*/

void ip_options_build(struct sk_buff * skb, struct ip_options * opt,
u32 daddr, struct rtable *rt, int is_frag)
{
unsigned char * iph = skb->nh.raw;

memcpy(&(IPCB(skb)->opt), opt, sizeof(struct ip_options));
将IP选项参数复制到包缓冲上
memcpy(iph+sizeof(struct iphdr), opt->__data, opt->optlen);
将IP选项数据块复制到IP包选项区
opt = &(IPCB(skb)->opt);
opt->is_data = 0;

if (opt->srr) 将地址daddr拷贝到信源路由选项表的最后一项
memcpy(iph+opt->srr+iph[opt->srr+1]-4, &daddr, 4);

if (!is_frag) { 如果不是片段
if (opt->rr_needaddr) 记录输出设备的地址
ip_rt_get_source(iph+opt->rr+iph[opt->rr+2]-5, rt);
if (opt->ts_needaddr) 记录时戳地址
ip_rt_get_source(iph+opt->ts+iph[opt->ts+2]-9, rt);
if (opt->ts_needtime) { 记录时戳
struct timeval tv;
__u32 midtime;
do_gettimeofday(&tv);
midtime = htonl((tv.tv_sec % 86400) * 1000 + tv.tv_usec / 1000);
memcpy(iph+opt->ts+iph[opt->ts+2]-5, &midtime, 4);
}
return;
}
if (opt->rr) { 填充成空选项
memset(iph+opt->rr, IPOPT_NOP, iph[opt->rr+1]);
opt->rr = 0;
opt->rr_needaddr = 0;
}
if (opt->ts) {
memset(iph+opt->ts, IPOPT_NOP, iph[opt->ts+1]);
opt->ts = 0;
opt->ts_needaddr = opt->ts_needtime = 0;
}
}

/*
* Provided (sopt, skb) points to received options,
* build in dopt compiled option set appropriate for answering.
* i.e. invert SRR option, copy anothers,
* and grab room in RR/TS options.
*
* NOTE: dopt cannot point to skb.
*/

int ip_options_echo(struct ip_options * dopt, struct sk_buff * skb)
{
struct ip_options *sopt;
unsigned char *sptr, *dptr;
int soffset, doffset;
int optlen;
u32 daddr;

memset(dopt, 0, sizeof(struct ip_options)); 将输出选项区索引结构清零

dopt->is_data = 1;

sopt = &(IPCB(skb)->opt);

if (sopt->optlen == 0) {
dopt->optlen = 0;
return 0;
}

sptr = skb->nh.raw;
dptr = dopt->__data; 指向输出选项区开始

if (skb->dst)
daddr = ((struct rtable*)skb->dst)->rt_spec_dst;
else
daddr = skb->nh.iph->daddr;

if (sopt->rr) {
optlen = sptr[sopt->rr+1]; 取IP包路由记录表长度
soffset = sptr[sopt->rr+2]; 取当前地址记录索引
dopt->rr = dopt->optlen + sizeof(struct iphdr); 取输出选项区尾部
memcpy(dptr, sptr+sopt->rr, optlen); 复制路由记录表
if (sopt->rr_needaddr && soffset <= optlen) {
if (soffset + 3 > optlen)
return -EINVAL;
dptr[2] = soffset + 4; 更新地址记录索引
dopt->rr_needaddr = 1;
}
dptr += optlen; 更新选项区输出指针
dopt->optlen += optlen; 更新选项区长度变量
}
if (sopt->ts) {
optlen = sptr[sopt->ts+1]; 取时戳表选项长度
soffset = sptr[sopt->ts+2]; 取当前时戳记录索引
dopt->ts = dopt->optlen + sizeof(struct iphdr);
memcpy(dptr, sptr+sopt->ts, optlen); 复制时戳表
if (soffset <= optlen) {
if (sopt->ts_needaddr) {
if (soffset + 3 > optlen)
return -EINVAL;
dopt->ts_needaddr = 1;
soffset += 4;
}
if (sopt->ts_needtime) {
if (soffset + 3 > optlen)
return -EINVAL;
if ((dptr[3]&0xF) != IPOPT_TS_PRESPEC) {
dopt->ts_needtime = 1;
soffset += 4;
} else { 如果为取指定地址时戳
dopt->ts_needtime = 0;

if (soffset + 8 <= optlen) {
__u32 addr;

memcpy(&addr, sptr+soffset-1, 4);
if (inet_addr_type(addr) != RTN_LOCAL) {
dopt->ts_needtime = 1;
soffset += 8;
}
}
}
}
dptr[2] = soffset;
}
dptr += optlen;
dopt->optlen += optlen;
}
if (sopt->srr) {
unsigned char * start = sptr+sopt->srr;
u32 faddr;

optlen = start[1];
soffset = start[2];
doffset = 0;
if (soffset > optlen)
soffset = optlen + 1;
soffset -= 4;
if (soffset > 3) {
memcpy(&faddr, &start[soffset-1], 4);
for (soffset-=4, doffset=4; soffset > 3; soffset-=4, doffset+=4)
memcpy(&dptr[doffset-1], &start[soffset-1], 4);
/*
* RFC1812 requires to fix illegal source routes.
*/
if (memcmp(&skb->nh.iph->saddr, &start[soffset+3], 4) == 0)
doffset -= 4;
}
if (doffset > 3) {
memcpy(&start[doffset-1], &daddr, 4);
dopt->faddr = faddr;
dptr[0] = start[0];
dptr[1] = doffset+3;
dptr[2] = 4;
dptr += doffset+3;
dopt->srr = dopt->optlen + sizeof(struct iphdr);
dopt->optlen += doffset+3;
dopt->is_strictroute = sopt->is_strictroute;
}
}
while (dopt->optlen & 3) {
*dptr++ = IPOPT_END;
dopt->optlen++;
}
return 0;
}

其余一些相关函数
----------------

static inline struct dst_entry *
__sk_dst_check(struct sock *sk, u32 cookie) 校验套接字所缓冲的目的入口
{
struct dst_entry *dst = sk->dst_cache;

if (dst && dst->obsolete && dst->ops->check(dst, cookie) == NULL) {
sk->dst_cache = NULL;
return NULL;
}

return dst;
}
static struct dst_entry * ipv4_dst_check(struct dst_entry * dst, u32 cookie)
{ ; ipv4协议的目的入口校验函数
dst_release(dst);
return NULL;
}
static inline
int ip_dont_fragment(struct sock *sk, struct dst_entry *dst) 是否允许IP分片
{
return (sk->protinfo.af_inet.pmtudisc == IP_PMTUDISC_DO ||
(sk->protinfo.af_inet.pmtudisc == IP_PMTUDISC_WANT &&
!(dst->mxlock&(1<
}
static inline void ip_select_ident(struct iphdr *iph, struct dst_entry *dst)
分配IP包标识号
{
if (iph->frag_off&__constant_htons(IP_DF))
iph->id = 0;
else
__ip_select_ident(iph, dst);
}

/* Generate a checksum for an outgoing IP datagram. */
__inline__ void ip_send_check(struct iphdr *iph) 校验IP包头
{
iph->check = 0;
iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl);
}

#define SKB_EXT_ERR(skb) ((struct sock_exterr_skb *) ((skb)->cb))

void ip_local_error(struct sock *sk, int err, u32 daddr, u16 port, u32 info)
生成出错信息包
{
struct sock_exterr_skb *serr;
struct iphdr *iph;
struct sk_buff *skb;

if (!sk->protinfo.af_inet.recverr)
return;

skb = alloc_skb(sizeof(struct iphdr), GFP_ATOMIC);
if (!skb)
return;

iph = (struct iphdr*)skb_put(skb, sizeof(struct iphdr));
skb->nh.iph = iph;
iph->daddr = daddr;

serr = SKB_EXT_ERR(skb); 取包缓冲控制块(cb)
serr->ee.ee_errno = err; 错误码
serr->ee.ee_origin = SO_EE_ORIGIN_LOCAL; 错误来源
serr->ee.ee_type = 0;
serr->ee.ee_code = 0;
serr->ee.ee_pad = 0;
serr->ee.ee_info = info; 出错参数
serr->ee.ee_data = 0;
serr->addr_offset = (u8*)&iph->daddr - skb->nh.raw;
serr->port = port;

skb->h.raw = skb->tail;
skb_pull(skb, skb->tail - skb->data);

if (sock_queue_err_skb(sk, skb)) 加入套接字的出错信息包队列
kfree_skb(skb);
}
static inline int sock_queue_err_skb(struct sock *sk, struct sk_buff *skb)
{
/* Cast skb->rcvbuf to unsigned... It's pointless, but reduces
number of warnings when compiling with -W --ANK
*/
if (atomic_read(&sk->rmem_alloc) + skb->truesize >= (unsigned)sk->rcvbuf)
return -ENOMEM;
skb_set_owner_r(skb, sk);
skb_queue_tail(&sk->error_queue,skb);
if (!sk->dead)
sk->data_ready(sk,skb->len); 唤醒套接字等待队列
return 0;
}

/*
* Queue a received datagram if it will fit. Stream and sequenced
* protocols can't normally use this as they need to fit buffers in
* and play with them.
*
* Inlined as it's very short and called for pretty much every
* packet ever received.
*/

static inline void skb_set_owner_w(struct sk_buff *skb, struct sock *sk)
设置发送包的拥有套接字
{
sock_hold(sk);
skb->sk = sk;
skb->destructor = sock_wfree;
atomic_add(skb->truesize, &sk->wmem_alloc); 刷新发送内存的分配量
}
static inline void skb_set_owner_r(struct sk_buff *skb, struct sock *sk)
设置接收包的拥有套接字
{
skb->sk = sk;
skb->destructor = sock_rfree;
atomic_add(skb->truesize, &sk->rmem_alloc); 刷新接收内存的分配量
}
/*
* Write buffer destructor automatically called from kfree_skb.
*/
void sock_wfree(struct sk_buff *skb)
{
struct sock *sk = skb->sk;

/* In case it might be waiting for more memory. */
atomic_sub(skb->truesize, &sk->wmem_alloc);
sk->write_space(sk); 唤醒正在等待写的进程
sock_put(sk);
}

/*
* Read buffer destructor automatically called from kfree_skb.
*/
void sock_rfree(struct sk_buff *skb)
{
struct sock *sk = skb->sk;

atomic_sub(skb->truesize, &sk->rmem_alloc);
}

static inline long sock_sndtimeo(struct sock *sk, int noblock) 取套接字的发送超时
{
return noblock ? 0 : sk->sndtimeo;
}
static inline int sock_error(struct sock *sk) 读取并且复位套接字错误号
{
int err=xchg(&sk->err,0);
return -err;
}

/*
We do not cache source address of outgoing interface,
because it is used only by IP RR, TS and SRR options,
so that it out of fast path.

BTW remember: "addr" is allowed to be not aligned
in IP options!
*/

void ip_rt_get_source(u8 *addr, struct rtable *rt) 取路由输出设备的地址
{
u32 src;
struct fib_result res;

if (rt->key.iif == 0)
src = rt->rt_src;
else if (fib_lookup(&rt->key, &res) == 0) {
#ifdef CONFIG_IP_ROUTE_NAT
if (res.type == RTN_NAT)
src = inet_select_addr(rt->u.dst.dev, rt->rt_gateway, RT_SCOPE_UNIVERSE);
else
#endif
src = FIB_RES_PREFSRC(res);
fib_res_put(&res);
} else
src = inet_select_addr(rt->u.dst.dev, rt->rt_gateway, RT_SCOPE_UNIVERSE);
memcpy(addr, &src, 4);
}

struct sk_buff *sock_alloc_send_skb(struct sock *sk, unsigned long size,
unsigned long fallback, int noblock, int *errcode) 分配套接字的发送包
{
int err;
struct sk_buff *skb;
long timeo;

timeo = sock_sndtimeo(sk, noblock); 取套接字的发送超时

while (1) {
unsigned long try_size = size;

err = sock_error(sk); 读取并且复位套接字的错误码
if (err != 0)
goto failure;

/*
* We should send SIGPIPE in these cases according to
* 1003.1g draft 6.4. If we (the user) did a shutdown()
* call however we should not.
*
* Note: This routine isnt just used for datagrams and
* anyway some datagram protocols have a notion of
* close down.
*/

err = -EPIPE;
if (sk->shutdown&SEND_SHUTDOWN)
goto failure;

if (atomic_read(&sk->wmem_alloc) <>sndbuf) {
如果分配给套接字的发送内存小于其发送缓冲区
if (fallback) {
/* The buffer get won't block, or use the atomic queue.
* It does produce annoying no free page messages still.
*/
skb = alloc_skb(size, GFP_BUFFER);
if (skb)
break;
try_size = fallback;
}
skb = alloc_skb(try_size, sk->allocation);
if (skb)
break;
err = -ENOBUFS;
goto failure;
}

/*
* This means we have too many buffers for this socket already.
*/

set_bit(SOCK_ASYNC_NOSPACE, &sk->socket->flags);
set_bit(SOCK_NOSPACE, &sk->socket->flags);
err = -EAGAIN;
if (!timeo)
goto failure;
if (signal_pending(current))
goto interrupted;
timeo = sock_wait_for_wmem(sk, timeo);
}

skb_set_owner_w(skb, sk);
return skb;

interrupted:
err = sock_intr_errno(timeo);
failure:
*errcode = err;
return NULL;
}
/* It is almost wait_for_tcp_memory minus release_sock/lock_sock.
I think, these locks should be removed for datagram sockets.
*/
static long sock_wait_for_wmem(struct sock * sk, long timeo)
等待套接字可分配的发送内存
{
DECLARE_WAITQUEUE(wait, current);

clear_bit(SOCK_ASYNC_NOSPACE, &sk->socket->flags);
add_wait_queue(sk->sleep, &wait);
for (;;) {
if (signal_pending(current))
break;
set_bit(SOCK_NOSPACE, &sk->socket->flags);
set_current_state(TASK_INTERRUPTIBLE);
if (atomic_read(&sk->wmem_alloc) <>sndbuf)
break;
if (sk->shutdown & SEND_SHUTDOWN)
break;
if (sk->err)
break;
timeo = schedule_timeout(timeo);
}
__set_current_state(TASK_RUNNING);
remove_wait_queue(sk->sleep, &wait);
return timeo;
}


Edited by lucian_yao on 07/02/01 02:38
PM.

Linux的硬件地址解析过程 - collide的专栏

Linux的硬件地址解析过程

1) 在网络接口设备的硬件层之间能够直接进行包交换的设备构成了一个局域网,
局域网中的每一设备
具有唯一的硬件地址. 对TCPIP协议来说, 局域网中的每一设备又具有唯一的IP地址.
当IP包要从某一
设备发向局域网中具有另一IP地址的设备时, 信源设备必须获得信宿设备的硬件地址,
这就需要硬件
地址解析.arp协议是根据设备的IP地址获取其硬件地址的方法.
信源设备向局域网广播自已地址解析
请求, 局域网中其余设备都收到该请求, 具有所请求IP地址的设备向信源设备作出应答,
提供它的硬
件地址. 由于arp请求的广播特性, 某一设备可以对不是自已IP地址的请求作出应答,
这就是arp代理.

2) 在Linux内核中, 将能与自已在硬件层直接通信的外部主机的网络接口设备称为"邻居",
用neighbour
结构描述, 它包含设备的硬件地址信息.
系统中所有的IP包都通过路由所绑定的邻居发送到接口设备上.
邻居由邻居表(arp_tbl)来索引, 用邻居的IP地址可查询邻居表中某个设备的邻居.

3) 当邻居接收到要发送的IP包时, 如果邻居的硬件地址还未解析,
则将发送包暂时缓冲在arp_queue队
列中,然后发送地址解析请求, 这时的状态为未完成状态(NUD_INCOMPLETE).
如果1秒内没收到外部设备
应答, 邻居将重发arp请求, 如果重发达到3次, 则解析失败, 邻居为失败状态(NUD_FAILED).
当收到正
确应答, 邻居进入连接状态(NUD_REACHABLE),
这时arp_queue中发送包将被创建帧头后发送到设备上.

4) 邻居的IP地址与硬件地址的关系并不是固定的, 系统在接收来自邻居的IP包时,
会及时地证实(confirm)
邻居的IP地址与硬件地址的映射关系. 同时,
邻居表每60秒周期性地扫描邻居(neigh_periodic_timer),
一方面从表中清除那些解析失败和长时间(60秒)未被使用的邻居,
另一方面识别那些证实时间已超时的
邻居, 将它们从连接状态变为过期状态(NUD_STALE). 当邻居在过期状态发送IP包时,
就进入延迟状态
(NUD_DELAY), 如果在延迟状态5秒后邻居的硬件地址还是未被证实, 邻居则发送arp请求,
这时进入探测
状态(NUD_PROBE). 在探测状态, IP包并不进行缓冲, 仍旧使用过期的邻居地址发送,
如果探测失败,
邻居进入失败状态.

5) 为了缩短IP包到设备的传输路径, 在邻居结构上还引入了帧头缓冲结构(hh_cache).
如果邻居建立了
帧头缓冲, IP包将通过帧头缓冲的输出发送出去. 当邻居处于连接状态时, 帧头缓冲输出直接指向
dev_queue_xmit(), 当处于过期状态时, 帧头缓冲输出切换为邻居的输出口, 对以太网设备来说,
邻居的
输出口指向neigh_resolve_output(),
neigh_connect()和neigh_suspect()两个函数用来进行这种切换.

6) 当系统对外部设备arp的请求应答时, 系统将在arp_tbl中创建该外部设备的邻居,
刷新为过期状态.
当收到对其它设备的地址解析请求时, 系统将源设备的邻居刷新为过期状态. 当收到单发给自已,
但目
的IP地址不是自已主机地址的arp请求时, 如果设备允许转发并且该IP在代理表有定义,
则将它们缓冲到
proxy_queue队列, 等待一段随机的时间作出应答, 防止目标设备拥塞,
向对方提供的是自已的设备地址.

这一部分有点复杂, 还有很多细节尚未搞清, 只能写这么多, 欢迎大家指点.
现在自已的问题是: 在什么情况下必须使用arp代理?

; net/ipv4/ip_output.c:

static inline int ip_finish_output2(struct sk_buff *skb) IP包的发送出口
{
struct dst_entry *dst = skb->dst; 取IP包的路由结构
struct hh_cache *hh = dst->hh; 取路由的帧头缓冲

#ifdef CONFIG_NETFILTER_DEBUG
nf_debug_ip_finish_output2(skb);
#endif /*CONFIG_NETFILTER_DEBUG*/

if (hh) { 如果路由帧头缓冲非空
read_lock_bh(&hh->hh_lock);
memcpy(skb->data - 16, hh->hh_data, 16); 创建IP包的硬件帧头
read_unlock_bh(&hh->hh_lock);
skb_push(skb, hh->hh_len);
return hh->hh_output(skb); 通过帧头缓冲发出
} else if (dst->neighbour)
return dst->neighbour->output(skb); 通过邻居出口发出

printk(KERN_DEBUG "khm\n");
kfree_skb(skb);
return -EINVAL;
}

; net/ipv4/arp.c, core/neighbour.c:

#define NUD_INCOMPLETE 0x01
#define NUD_REACHABLE 0x02
#define NUD_STALE 0x04
#define NUD_DELAY 0x08
#define NUD_PROBE 0x10
#define NUD_FAILED 0x20

/* Dummy states */
#define NUD_NOARP 0x40
#define NUD_PERMANENT 0x80
#define NUD_NONE 0x00

/* NUD_NOARP & NUD_PERMANENT are pseudostates, they never change
and make no address resolution or NUD.
NUD_PERMANENT is also cannot be deleted by garbage collectors.
*/
#define NUD_IN_TIMER (NUD_INCOMPLETE|NUD_DELAY|NUD_PROBE)
#define
NUD_VALID (NUD_PERMANENT|NUD_NOARP|NUD_REACHABLE|NUD_PROBE|NUD_STALE|NUD_DELAY)
#define NUD_CONNECTED (NUD_PERMANENT|NUD_NOARP|NUD_REACHABLE)

#define NEIGH_HASHMASK 0x1F
#define PNEIGH_HASHMASK 0xF

struct neigh_table 网络邻居表
{
struct neigh_table *next;
int family;
int entry_size;
int key_len;
__u32 (*hash)(const void *pkey, const struct net_device *);
int (*constructor)(struct neighbour *);
int (*pconstructor)(struct pneigh_entry *);
void (*pdestructor)(struct pneigh_entry *);
void (*proxy_redo)(struct sk_buff *skb);
char *id;
struct neigh_parms parms;
/* HACK. gc_* shoul follow parms without a gap! */
int gc_interval; (60秒)
int gc_thresh1; (128)
int gc_thresh2; (512)
int gc_thresh3; (1024)
unsigned long last_flush;
struct timer_list gc_timer;
struct timer_list proxy_timer;
struct sk_buff_head proxy_queue;
int entries;
rwlock_t lock;
unsigned long last_rand;
struct neigh_parms *parms_list;
kmem_cache_t *kmem_cachep;
struct tasklet_struct gc_task;
struct neigh_statistics stats;
struct neighbour *hash_buckets[NEIGH_HASHMASK+1];
struct pneigh_entry *phash_buckets[PNEIGH_HASHMASK+1];
};
struct neigh_parms 邻居参数
{
struct neigh_parms *next;
int (*neigh_setup)(struct neighbour *);
struct neigh_table *tbl;
int entries;
void *priv;

void *sysctl_table;

int base_reachable_time; (30秒)
int retrans_time; (1秒)
int gc_staletime; (60秒)
int reachable_time; (30秒左右)
int delay_probe_time; (5秒)

int queue_len; (3)
int ucast_probes; (3)
int app_probes; (0)
int mcast_probes; (3)
int anycast_delay; (1秒)
int proxy_delay; (0.8秒)
int proxy_qlen; (64)
int locktime; (1秒)
};
struct neighbour 网络邻居结构
{
struct neighbour *next;
struct neigh_table *tbl;
struct neigh_parms *parms;
struct net_device *dev;
unsigned long used;
unsigned long confirmed;
unsigned long updated;
__u8 flags;
__u8 nud_state;
__u8 type;
__u8 dead;
atomic_t probes;
rwlock_t lock;
unsigned char ha[(MAX_ADDR_LEN+sizeof(unsigned long)-1)&~(sizeof(unsigned long)-1)];
struct hh_cache *hh;
atomic_t refcnt;
int (*output)(struct sk_buff *skb);
struct sk_buff_head arp_queue;
struct timer_list timer;
struct neigh_ops *ops;
u8 primary_key[0];
};
struct hh_cache 帧头缓冲结构
{
struct hh_cache *hh_next; /* Next entry */
atomic_t hh_refcnt; /* number of users */
unsigned short hh_type; /* protocol identifier, f.e ETH_P_IP */
int hh_len; /* length of header */
int (*hh_output)(struct sk_buff *skb);
rwlock_t hh_lock;
/* cached hardware header; allow for machine alignment needs. */
unsigned long hh_data[16/sizeof(unsigned long)];
};


struct neigh_table arp_tbl = arp网络邻居表, 用于TCPIP协议中的网络邻居信息索引
{
NULL,
AF_INET,
sizeof(struct neighbour) + 4,
4,
arp_hash,
arp_constructor,
NULL,
NULL,
parp_redo,
"arp_cache",
{ NULL, NULL, &arp_tbl, 0, NULL, NULL,
30*HZ, 1*HZ, 60*HZ, 30*HZ, 5*HZ,
3, 3, 0, 3, 1*HZ, (8*HZ)/10, 64, 1*HZ },30*HZ, 128, 512, 1024,
};

void __init arp_init (void) 地址解析模块初始化
{
neigh_table_init(&arp_tbl); 初始化arp协议网络邻居解析表

dev_add_pack(&arp_packet_type); 注册地址解析包接收器

proc_net_create ("arp", 0, arp_get_info);

#ifdef CONFIG_SYSCTL
neigh_sysctl_register(NULL, &arp_tbl.parms, NET_IPV4, NET_IPV4_NEIGH, "ipv4");
#endif
}

static struct neigh_table *neigh_tables; 所有的网络邻居解析表链表

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);
if (tbl->kmem_cachep == NULL)
tbl->kmem_cachep = kmem_cache_create(tbl->id,
(tbl->entry_size+15)&~15,
0, SLAB_HWCACHE_ALIGN,
NULL, NULL); 建立网络邻居信息结构内存分配器

#ifdef CONFIG_SMP
tasklet_init(&tbl->gc_task, SMP_TIMER_NAME(neigh_periodic_timer), (unsigned
long)tbl);
#endif
init_timer(&tbl->gc_timer);
tbl->lock = RW_LOCK_UNLOCKED;
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;
write_lock(&neigh_tbl_lock);
tbl->next = neigh_tables;
neigh_tables = tbl;
write_unlock(&neigh_tbl_lock);
}
unsigned long neigh_rand_reach_time(unsigned long base) 取在(base/2)左右的随机数
{
return (net_random() % base) + (base>>1);
}

static void SMP_TIMER_NAME(neigh_periodic_timer)(unsigned long arg)
{
struct neigh_table *tbl = (struct neigh_table*)arg;
unsigned long now = jiffies;
int i;


write_lock(&tbl->lock);

/*
* periodicly recompute ReachableTime from random function
*/

if (now - tbl->last_rand > 300*HZ) {
struct neigh_parms *p;
tbl->last_rand = now;
for (p=&tbl->parms; p; p = p->next)
p->reachable_time = neigh_rand_reach_time(p->base_reachable_time);
}

for (i=0; i <= NEIGH_HASHMASK; i++) { 扫描所有的网络邻居
struct neighbour *n, **np;

np = &tbl->hash_buckets
;
while ((n = *np) != NULL) {
unsigned state;

write_lock(&n->lock);

state = n->nud_state;
if (state&(NUD_PERMANENT|NUD_IN_TIMER)) { 对于不可释放或正在解析的邻居
write_unlock(&n->lock);
goto next_elt;
}

if ((long)(n->used - n->confirmed) < 0)
n->used = n->confirmed;

if (atomic_read(&n->refcnt) == 1 &&
(state == NUD_FAILED || now - n->used > n->parms->gc_staletime)) {
*np = n->next; 释放那些解析失败和引用时间超过(60)秒的邻居
n->dead = 1;
write_unlock(&n->lock);
neigh_release(n);
continue;
}

if (n->nud_state&NUD_REACHABLE &&
now - n->confirmed > n->parms->reachable_time) {
n->nud_state = NUD_STALE; 如果解析时间大于可达超时, 则邻居的地址失效
neigh_suspect(n);
}
write_unlock(&n->lock);

next_elt:
np = &n->next;
}
}

mod_timer(&tbl->gc_timer, now + tbl->gc_interval);
write_unlock(&tbl->lock);
}

int arp_bind_neighbour(struct dst_entry *dst) 将邻居绑定给指定路由
{
struct net_device *dev = dst->dev;
struct neighbour *n = dst->neighbour;

if (dev == NULL)
return -EINVAL;
if (n == NULL) {
u32 nexthop = ((struct rtable*)dst)->rt_gateway; 取网关地址
if (dev->flags&(IFF_LOOPBACK|IFF_POINTOPOINT))
nexthop = 0;
n = __neigh_lookup_errno(
#ifdef CONFIG_ATM_CLIP
dev->type == ARPHRD_ATM ? &clip_tbl :
#endif
&arp_tbl, &nexthop, dev); 在arp_tbl中查询目标地址nexthop的设备邻居
if (IS_ERR(n))
return PTR_ERR(n);
dst->neighbour = n;
}
return 0;
}
static inline struct neighbour *
__neigh_lookup_errno(struct neigh_table *tbl, const void *pkey,
struct net_device *dev)
{
struct neighbour *n = neigh_lookup(tbl, pkey, dev);

if (n)
return n;

return neigh_create(tbl, pkey, dev); 在邻居表中创建新的设备邻居
}
struct neighbour *neigh_lookup(struct neigh_table *tbl, const void *pkey,
struct net_device *dev)
{
struct neighbour *n;
u32 hash_val;
int key_len = tbl->key_len;

hash_val = tbl->hash(pkey, dev); 计算邻居的索引键

read_lock_bh(&tbl->lock);
for (n = tbl->hash_buckets[hash_val]; n; n = n->next) {
if (dev == n->dev &&
memcmp(n->primary_key, pkey, key_len) == 0) {
neigh_hold(n);
break;
}
}
read_unlock_bh(&tbl->lock);
return n;
}
static u32 arp_hash(const void *pkey, const struct net_device *dev)
{
u32 hash_val;

hash_val = *(u32*)pkey;
hash_val ^= (hash_val>>16);
hash_val ^= hash_val>>8;
hash_val ^= hash_val>>3;
hash_val = (hash_val^dev->ifindex)&NEIGH_HASHMASK;

return hash_val;
}
struct neighbour * neigh_create(struct neigh_table *tbl, const void *pkey,
struct net_device *dev)
{
struct neighbour *n, *n1;
u32 hash_val;
int key_len = tbl->key_len;
int error;

n = neigh_alloc(tbl); 为邻居表tbl创建新的邻居
if (n == NULL)
return ERR_PTR(-ENOBUFS);

memcpy(n->primary_key, pkey, key_len); 拷贝邻居的键值
n->dev = dev;
dev_hold(dev);

/* Protocol specific setup. */
if (tbl->constructor && (error = tbl->constructor(n)) < 0) {
neigh_release(n);
return ERR_PTR(error);
}

/* Device specific setup. */
if (n->parms && n->parms->neigh_setup &&
(error = n->parms->neigh_setup(n)) < 0) {
neigh_release(n);
return ERR_PTR(error);
}

n->confirmed = jiffies - (n->parms->base_reachable_time<<1);

hash_val = tbl->hash(pkey, dev);

write_lock_bh(&tbl->lock);
for (n1 = tbl->hash_buckets[hash_val]; n1; n1 = n1->next) {
if (dev == n1->dev &&
memcmp(n1->primary_key, pkey, key_len) == 0) {
neigh_hold(n1); 如果邻居表中有重键,则释放新建邻居,返回已有的邻居
write_unlock_bh(&tbl->lock);
neigh_release(n);
return n1;
}
}

n->next = tbl->hash_buckets[hash_val];
tbl->hash_buckets[hash_val] = n; 插入索引链
n->dead = 0;
neigh_hold(n);
write_unlock_bh(&tbl->lock);
NEIGH_PRINTK2("neigh %p is created.\n", n);
return n;
}

static struct neighbour *neigh_alloc(struct neigh_table *tbl) 分配邻居结构
{
struct neighbour *n;
unsigned long now = jiffies;

if (tbl->entries > tbl->gc_thresh3 || 如果邻居表的单元数目超过了(1024)
(tbl->entries > tbl->gc_thresh2 && 或者超过了(512)并且
now - tbl->last_flush > 5*HZ)) { 与上次刷新间隔超过5秒
if (neigh_forced_gc(tbl) == 0 && 释放过时的邻居
tbl->entries > tbl->gc_thresh3)
return NULL;
}

n = kmem_cache_alloc(tbl->kmem_cachep, SLAB_ATOMIC);
if (n == NULL)
return NULL;

memset(n, 0, tbl->entry_size);

skb_queue_head_init(&n->arp_queue); 当邻居的地址尚未解析时,
发向该邻居的包缓冲在该队列中
n->lock = RW_LOCK_UNLOCKED;
n->updated = n->used = now;
n->nud_state = NUD_NONE;
n->output = neigh_blackhole; 邻居的IP包注入口
n->parms = &tbl->parms; 继承邻居表的参数
init_timer(&n->timer);
n->timer.function = neigh_timer_handler; 接收超时临视器
n->timer.data = (unsigned long)n;
tbl->stats.allocs++;
neigh_glbl_allocs++;
tbl->entries++;
n->tbl = tbl;
atomic_set(&n->refcnt, 1);
n->dead = 1;
return n;
}
static int neigh_blackhole(struct sk_buff *skb)
{
kfree_skb(skb);
return -ENETDOWN;
}
static int neigh_forced_gc(struct neigh_table *tbl) 强制释放邻居
{
int shrunk = 0;
int i;

for (i=0; i<=NEIGH_HASHMASK; i++) {
struct neighbour *n, **np;

np = &tbl->hash_buckets
;
write_lock_bh(&tbl->lock);
while ((n = *np) != NULL) {
/* Neighbour record may be discarded if:
- nobody refers to it.
- it is not premanent
- (NEW and probably wrong)
INCOMPLETE entries are kept at least for
n->parms->retrans_time, otherwise we could
flood network with resolution requests.
It is not clear, what is better table overflow
or flooding.
*/
write_lock(&n->lock);
if (atomic_read(&n->refcnt) == 1 &&
!(n->nud_state&NUD_PERMANENT) &&
(n->nud_state != NUD_INCOMPLETE ||
jiffies - n->used > n->parms->retrans_time)) {
*np = n->next;
n->dead = 1;
shrunk = 1;
write_unlock(&n->lock);
neigh_release(n);
continue;
}
write_unlock(&n->lock);
np = &n->next;
}
write_unlock_bh(&tbl->lock);
}

tbl->last_flush = jiffies;
return shrunk;
}
static inline void neigh_release(struct neighbour *neigh)
{
if (atomic_dec_and_test(&neigh->refcnt))
neigh_destroy(neigh);
}
static int neigh_del_timer(struct neighbour *n)
{
if (n->nud_state & NUD_IN_TIMER) {
if (del_timer(&n->timer)) {
neigh_release(n);
return 1;
}
}
return 0;
}
void neigh_destroy(struct neighbour *neigh)
{
struct hh_cache *hh;

if (!neigh->dead) {
printk("Destroying alive neighbour %p from %08lx\n", neigh,
*(((unsigned long*)&neigh)-1));
return;
}

if (neigh_del_timer(neigh))
printk("Impossible event.\n");

while ((hh = neigh->hh) != NULL) {
neigh->hh = hh->hh_next;
hh->hh_next = NULL;
write_lock_bh(&hh->hh_lock);
hh->hh_output = neigh_blackhole;
write_unlock_bh(&hh->hh_lock);
if (atomic_dec_and_test(&hh->hh_refcnt))
kfree(hh);
} 清除网络邻居的帧头缓冲

if (neigh->ops && neigh->ops->destructor)
(neigh->ops->destructor)(neigh);

skb_queue_purge(&neigh->arp_queue); 清除邻居的IP包缓冲队列

dev_put(neigh->dev);

NEIGH_PRINTK2("neigh %p is destroyed.\n", neigh);

neigh_glbl_allocs--;
neigh->tbl->entries--;
kmem_cache_free(neigh->tbl->kmem_cachep, neigh);
}

/* As fast as possible without hh cache */

static __inline__ int neigh_max_probes(struct neighbour *n)
{
struct neigh_parms *p = n->parms;
return p->ucast_probes + p->app_probes + p->mcast_probes;
}

static void neigh_timer_handler(unsigned long arg)
{
unsigned long now = jiffies;
struct neighbour *neigh = (struct neighbour*)arg;
unsigned state;
int notify = 0;

write_lock(&neigh->lock);

state = neigh->nud_state;

if (!(state&NUD_IN_TIMER)) {
#ifndef CONFIG_SMP
printk("neigh: timer & !nud_in_timer\n");
#endif
goto out;
}

if ((state&NUD_VALID) &&
now - neigh->confirmed <>parms->reachable_time) {
neigh->nud_state = NUD_REACHABLE;
NEIGH_PRINTK2("neigh %p is still alive.\n", neigh);
neigh_connect(neigh);
goto out;
}
if (state == NUD_DELAY) {
NEIGH_PRINTK2("neigh %p is probed.\n", neigh);
neigh->nud_state = NUD_PROBE;
atomic_set(&neigh->probes, 0);
}

if (atomic_read(&neigh->probes) >= neigh_max_probes(neigh)) {
struct sk_buff *skb;

neigh->nud_state = NUD_FAILED;
notify = 1;
neigh->tbl->stats.res_failed++;
NEIGH_PRINTK2("neigh %p is failed.\n", neigh);

/* It is very thin place. report_unreachable is very complicated
routine. Particularly, it can hit the same neighbour entry!

So that, we try to be accurate and avoid dead loop. --ANK
*/
while(neigh->nud_state==NUD_FAILED && (skb=__skb_dequeue(&neigh->arp_queue)) !=
NULL) {
write_unlock(&neigh->lock);
neigh->ops->error_report(neigh, skb);
write_lock(&neigh->lock);
}
skb_queue_purge(&neigh->arp_queue);
goto out;
}

neigh->timer.expires = now + neigh->parms->retrans_time;
add_timer(&neigh->timer);
write_unlock(&neigh->lock);

neigh->ops->solicit(neigh, skb_peek(&neigh->arp_queue)); 重发arp请求
atomic_inc(&neigh->probes);
return;

out:
write_unlock(&neigh->lock);
#ifdef CONFIG_ARPD
if (notify && neigh->parms->app_probes)
neigh_app_notify(neigh);
#endif
neigh_release(neigh);
}
static int arp_constructor(struct neighbour *neigh)
{
u32 addr = *(u32*)neigh->primary_key; 主键为邻居的IP地址
struct net_device *dev = neigh->dev; 取邻居所在的主机设备
struct in_device *in_dev = in_dev_get(dev);

if (in_dev == NULL)
return -EINVAL;

neigh->type = inet_addr_type(addr); 取邻居的IP地址类型
if (in_dev->arp_parms)
neigh->parms = in_dev->arp_parms; 继承设备的arp参数

in_dev_put(in_dev);

if (dev->hard_header == NULL) {
neigh->nud_state = NUD_NOARP;
neigh->ops = &arp_direct_ops; 安装直达操作表
neigh->output = neigh->ops->queue_xmit; 向邻居的输出函数为(dev_queue_xmit)
} else {
/* Good devices (checked by reading texts, but only Ethernet is
tested)

ARPHRD_ETHER: (ethernet, apfddi)
ARPHRD_FDDI: (fddi)
ARPHRD_IEEE802: (tr)
ARPHRD_METRICOM: (strip)
ARPHRD_ARCNET:
etc. etc. etc.

ARPHRD_IPDDP will also work, if author repairs it.
I did not it, because this driver does not work even
in old paradigm.
*/

#if 1
/* So... these "amateur" devices are hopeless.
The only thing, that I can say now:
It is very sad that we need to keep ugly obsolete
code to make them happy.

They should be moved to more reasonable state, now
they use rebuild_header INSTEAD OF hard_start_xmit!!!
Besides that, they are sort of out of date
(a lot of redundant clones/copies, useless in 2.1),
I wonder why people believe that they work.
*/
switch (dev->type) {
default:
break;
case ARPHRD_ROSE:
#if defined(CONFIG_AX25) || defined(CONFIG_AX25_MODULE)
case ARPHRD_AX25:
#if defined(CONFIG_NETROM) || defined(CONFIG_NETROM_MODULE)
case ARPHRD_NETROM:
#endif
neigh->ops = &arp_broken_ops;
neigh->output = neigh->ops->output;
return 0;
#endif
;}
#endif
if (neigh->type == RTN_MULTICAST) {
neigh->nud_state = NUD_NOARP;
arp_mc_map(addr, neigh->ha, dev, 1);
} else if (dev->flags&(IFF_NOARP|IFF_LOOPBACK)) {
neigh->nud_state = NUD_NOARP;
memcpy(neigh->ha, dev->dev_addr, dev->addr_len);
} else if (neigh->type == RTN_BROADCAST || dev->flags&IFF_POINTOPOINT) {
neigh->nud_state = NUD_NOARP;
memcpy(neigh->ha, dev->broadcast, dev->addr_len);
}
if (dev->hard_header_cache)
neigh->ops = &arp_hh_ops; 安装邻居的帧头缓冲操作
else
neigh->ops = &arp_generic_ops;
if (neigh->nud_state&NUD_VALID) 地址是否已解析
neigh->output = neigh->ops->connected_output; 直接发送
else
neigh->output = neigh->ops->output; 先解析再发送
}
return 0;
}

struct neigh_ops
{
int family;
void (*destructor)(struct neighbour *);
void (*solicit)(struct neighbour *, struct sk_buff*);
void (*error_report)(struct neighbour *, struct sk_buff*);
int (*output)(struct sk_buff*);
int (*connected_output)(struct sk_buff*);
int (*hh_output)(struct sk_buff*);
int (*queue_xmit)(struct sk_buff*);
};
static struct neigh_ops arp_direct_ops = 无解析发送
{
AF_INET,
NULL,
NULL,
NULL,
dev_queue_xmit,
dev_queue_xmit,
dev_queue_xmit,
dev_queue_xmit
};
struct neigh_ops arp_broken_ops = 不完全解析
{
AF_INET,
NULL,
arp_solicit,
arp_error_report,
neigh_compat_output,
neigh_compat_output,
dev_queue_xmit,
dev_queue_xmit,
};
int neigh_compat_output(struct sk_buff *skb)
{
struct net_device *dev = skb->dev;

__skb_pull(skb, skb->nh.raw - skb->data);

if (dev->hard_header &&
dev->hard_header(skb, dev, ntohs(skb->protocol), NULL, NULL, skb->len) < 0 &&
dev->rebuild_header(skb))
return 0;

return dev_queue_xmit(skb);
}
static struct neigh_ops arp_hh_ops = 通帧头缓冲解析
{
AF_INET,
NULL,
arp_solicit,
arp_error_report,
neigh_resolve_output,
neigh_resolve_output,
dev_queue_xmit,
dev_queue_xmit
};
int neigh_resolve_output(struct sk_buff *skb)
{
struct dst_entry *dst = skb->dst;
struct neighbour *neigh;

if (!dst || !(neigh = dst->neighbour))
goto discard;

__skb_pull(skb, skb->nh.raw - skb->data);

if (neigh_event_send(neigh, skb) == 0) {
int err;
struct net_device *dev = neigh->dev;
if (dev->hard_header_cache && dst->hh == NULL) {
write_lock_bh(&neigh->lock);
if (dst->hh == NULL)
neigh_hh_init(neigh, dst, dst->ops->protocol);
err = dev->hard_header(skb, dev, ntohs(skb->protocol), neigh->ha, NULL, skb->len);
write_unlock_bh(&neigh->lock);
} else {
read_lock_bh(&neigh->lock);
err = dev->hard_header(skb, dev, ntohs(skb->protocol), neigh->ha, NULL, skb->len);
read_unlock_bh(&neigh->lock);
}
if (err >= 0)
return neigh->ops->queue_xmit(skb);
kfree_skb(skb);
return -EINVAL;
}
return 0;

discard:
NEIGH_PRINTK1("neigh_resolve_output: dst=%p neigh=%p\n", dst, dst ? dst->neighbour :
NULL);
kfree_skb(skb);
return -EINVAL;
}
static void neigh_hh_init(struct neighbour *n, struct dst_entry *dst, u16 protocol)
{
struct hh_cache *hh = NULL;
struct net_device *dev = dst->dev;

for (hh=n->hh; hh; hh = hh->hh_next)
if (hh->hh_type == protocol)
break;

if (!hh && (hh = kmalloc(sizeof(*hh), GFP_ATOMIC)) != NULL) {
memset(hh, 0, sizeof(struct hh_cache));
hh->hh_lock = RW_LOCK_UNLOCKED;
hh->hh_type = protocol;
atomic_set(&hh->hh_refcnt, 0);
hh->hh_next = NULL;
if (dev->hard_header_cache(n, hh)) {
kfree(hh);
hh = NULL;
} else {
atomic_inc(&hh->hh_refcnt);
hh->hh_next = n->hh;
n->hh = hh;
if (n->nud_state&NUD_CONNECTED) 如果设备地址有效
hh->hh_output = n->ops->hh_output; 继承邻居的帧头缓冲输出
else
hh->hh_output = n->ops->output; 继承邻居的输出
}
}
if (hh) {
atomic_inc(&hh->hh_refcnt);
dst->hh = hh;
}
}

static struct neigh_ops arp_generic_ops =
{
AF_INET,
NULL,
arp_solicit,
arp_error_report,
neigh_resolve_output,
neigh_connected_output,
dev_queue_xmit,
dev_queue_xmit
};
int neigh_connected_output(struct sk_buff *skb)
{
int err;
struct dst_entry *dst = skb->dst;
struct neighbour *neigh = dst->neighbour;
struct net_device *dev = neigh->dev;

__skb_pull(skb, skb->nh.raw - skb->data);

read_lock_bh(&neigh->lock);
err = dev->hard_header(skb, dev, ntohs(skb->protocol), neigh->ha, NULL, skb->len);
read_unlock_bh(&neigh->lock);
if (err >= 0)
return neigh->ops->queue_xmit(skb);
kfree_skb(skb);
return -EINVAL;
}

static inline int neigh_event_send(struct neighbour *neigh, struct sk_buff *skb)
{
neigh->used = jiffies;
if (!(neigh->nud_state&(NUD_CONNECTED|NUD_DELAY|NUD_PROBE)))
return __neigh_event_send(neigh, skb);
return 0;
}
int __neigh_event_send(struct neighbour *neigh, struct sk_buff *skb)
{
write_lock_bh(&neigh->lock);
if (!(neigh->nud_state&(NUD_CONNECTED|NUD_DELAY|NUD_PROBE))) {
if (!(neigh->nud_state&(NUD_STALE|NUD_INCOMPLETE))) {
if (neigh->parms->mcast_probes + neigh->parms->app_probes) {
atomic_set(&neigh->probes, neigh->parms->ucast_probes);
neigh->nud_state = NUD_INCOMPLETE;
neigh_hold(neigh);
neigh->timer.expires = jiffies + neigh->parms->retrans_time; 下一重发时间
add_timer(&neigh->timer);
write_unlock_bh(&neigh->lock);
neigh->ops->solicit(neigh, skb);
atomic_inc(&neigh->probes);
write_lock_bh(&neigh->lock);
} else {
neigh->nud_state = NUD_FAILED;
write_unlock_bh(&neigh->lock);

if (skb)
kfree_skb(skb);
return 1;
}
}
if (neigh->nud_state == NUD_INCOMPLETE) {
if (skb) {
if (skb_queue_len(&neigh->arp_queue) >= neigh->parms->queue_len) {
struct sk_buff *buff;
buff = neigh->arp_queue.prev;
__skb_unlink(buff, &neigh->arp_queue);
kfree_skb(buff);
}
__skb_queue_head(&neigh->arp_queue, skb);
}
write_unlock_bh(&neigh->lock);
return 1;
}
if (neigh->nud_state == NUD_STALE) {
NEIGH_PRINTK2("neigh %p is delayed.\n", neigh);
neigh_hold(neigh);
neigh->nud_state = NUD_DELAY;
neigh->timer.expires = jiffies + neigh->parms->delay_probe_time;
add_timer(&neigh->timer);
}
}
write_unlock_bh(&neigh->lock);
return 0;
}
static void arp_solicit(struct neighbour *neigh, struct sk_buff *skb) arp请求
{
u32 saddr;
u8 *dst_ha = NULL;
struct net_device *dev = neigh->dev;
u32 target = *(u32*)neigh->primary_key; 取邻居的IP地址
int probes = atomic_read(&neigh->probes);

if (skb && inet_addr_type(skb->nh.iph->saddr) == RTN_LOCAL)
saddr = skb->nh.iph->saddr;
else
saddr = inet_select_addr(dev, target, RT_SCOPE_LINK);

if ((probes -= neigh->parms->ucast_probes) < 0) {
if (!(neigh->nud_state&NUD_VALID))
printk(KERN_DEBUG "trying to ucast probe in NUD_INVALID\n");
dst_ha = neigh->ha;
read_lock_bh(&neigh->lock);
} else if ((probes -= neigh->parms->app_probes) < 0) {
#ifdef CONFIG_ARPD
neigh_app_ns(neigh);
#endif
return;
}

arp_send(ARPOP_REQUEST, ETH_P_ARP, target, dev, saddr,
dst_ha, dev->dev_addr, NULL);
if (dst_ha)
read_unlock_bh(&neigh->lock);
}
struct arphdr
{
unsigned short ar_hrd; 硬件地址格式
unsigned short ar_pro; 协议地址格式
unsigned char ar_hln; 硬件地址长度
unsigned char ar_pln; 协议地址长度
unsigned short ar_op; 命令代码

#if 0
/*
* Ethernet looks like this : This bit is variable sized however...
*/
unsigned char ar_sha[ETH_ALEN]; /* sender hardware address */
unsigned char ar_sip[4]; /* sender IP address */
unsigned char ar_tha[ETH_ALEN]; /* target hardware address */
unsigned char ar_tip[4]; /* target IP address */
#endif

};
void arp_send(int type, int ptype, u32 dest_ip,
struct net_device *dev, u32 src_ip,
unsigned char *dest_hw, unsigned char *src_hw,
unsigned char *target_hw)
{
struct sk_buff *skb;
struct arphdr *arp;
unsigned char *arp_ptr;

/*
* No arp on this interface.
*/

if (dev->flags&IFF_NOARP)
return;

/*
* Allocate a buffer
*/

skb = alloc_skb(sizeof(struct arphdr)+ 2*(dev->addr_len+4)
+ dev->hard_header_len + 15, GFP_ATOMIC); 创建地址解析包缓冲区
if (skb == NULL)
return;

skb_reserve(skb, (dev->hard_header_len+15)&~15); 保留硬件帧头区域,16字节对齐
skb->nh.raw = skb->data; 设置包头指针
arp = (struct arphdr *) skb_put(skb,sizeof(struct arphdr) + 2*(dev->addr_len+4));
; 分配地址解析包的数据区域
skb->dev = dev; 包的发送设备
skb->protocol = __constant_htons (ETH_P_ARP); 设置包的类型
if (src_hw == NULL)
src_hw = dev->dev_addr; 如果信源硬件地址为空, 取设备的硬件地址
if (dest_hw == NULL)
dest_hw = dev->broadcast; 如果信宿硬件地址为空, 取设备的广播地址

/*
* Fill the device header for the ARP frame
*/
if (dev->hard_header &&
dev->hard_header(skb,dev,ptype,dest_hw,src_hw,skb->len) < 0) 装配包的硬件帧头
goto out;

/*
* Fill out the arp protocol part.
*
* The arp hardware type should match the device type, except for FDDI,
* which (according to RFC 1390) should always equal 1 (Ethernet).
*/
/*
* Exceptions everywhere. AX.25 uses the AX.25 PID value not the
* DIX code for the protocol. Make these device structure fields.
*/
switch (dev->type) {
default:
arp->ar_hrd = htons(dev->type); 取设备类型作为地址解析包的地址类型
arp->ar_pro = __constant_htons(ETH_P_IP);
break;

#if defined(CONFIG_AX25) || defined(CONFIG_AX25_MODULE)
case ARPHRD_AX25:
arp->ar_hrd = __constant_htons(ARPHRD_AX25);
arp->ar_pro = __constant_htons(AX25_P_IP);
break;

#if defined(CONFIG_NETROM) || defined(CONFIG_NETROM_MODULE)
case ARPHRD_NETROM:
arp->ar_hrd = __constant_htons(ARPHRD_NETROM);
arp->ar_pro = __constant_htons(AX25_P_IP);
break;
#endif
#endif

#ifdef CONFIG_FDDI
case ARPHRD_FDDI:
arp->ar_hrd = __constant_htons(ARPHRD_ETHER);
arp->ar_pro = __constant_htons(ETH_P_IP);
break;
#endif
#ifdef CONFIG_TR
case ARPHRD_IEEE802_TR:
arp->ar_hrd = __constant_htons(ARPHRD_IEEE802);
arp->ar_pro = __constant_htons(ETH_P_IP);
break;
#endif
}

arp->ar_hln = dev->addr_len; 设置硬件地址长度
arp->ar_pln = 4; 设置协议地址长度
arp->ar_op = htons(type); 设置地址解析的操作码

arp_ptr=(unsigned char *)(arp+1); 指向包的参数区

memcpy(arp_ptr, src_hw, dev->addr_len); 设置源硬件地址
arp_ptr+=dev->addr_len;
memcpy(arp_ptr, &src_ip,4); 设置源IP地址
arp_ptr+=4;
if (target_hw != NULL)
memcpy(arp_ptr, target_hw, dev->addr_len);
else
memset(arp_ptr, 0, dev->addr_len);
arp_ptr+=dev->addr_len;
memcpy(arp_ptr, &dest_ip, 4); 设置目的IP地址
skb->dev = dev;

dev_queue_xmit(skb);
return;

out:
kfree_skb(skb);
}
static struct packet_type arp_packet_type =
{
__constant_htons(ETH_P_ARP),
NULL, /* All devices */
arp_rcv,
(void*)1,
NULL
};
static inline int skb_shared(struct sk_buff *skb)
{
return (atomic_read(&skb->users) != 1);
}
static inline struct sk_buff *skb_share_check(struct sk_buff *skb, int pri)
{
if (skb_shared(skb)) {
struct sk_buff *nskb;
nskb = skb_clone(skb, pri);
kfree_skb(skb);
return nskb;
}
return skb;
}

#define IN_DEV_PROXY_ARP(in_dev) (ipv4_devconf.proxy_arp || (in_dev)->cnf.proxy_arp)
#define IN_DEV_FORWARD(in_dev) ((in_dev)->cnf.forwarding)

int arp_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt)
{
struct arphdr *arp = skb->nh.arph;
unsigned char *arp_ptr= (unsigned char *)(arp+1);
struct rtable *rt;
unsigned char *sha, *tha;
u32 sip, tip;
u16 dev_type = dev->type;
int addr_type;
struct in_device *in_dev = in_dev_get(dev);
struct neighbour *n;

/*
* The hardware length of the packet should match the hardware length
* of the device. Similarly, the hardware types should match. The
* device should be ARP-able. Also, if pln is not 4, then the lookup
* is not from an IP number. We can't currently handle this, so toss
* it.
*/
if (in_dev == NULL || 如果设备还没打开
arp->ar_hln != dev->addr_len || 如果硬件地址长度不匹配
dev->flags & IFF_NOARP || 如果设备无需arp
skb->pkt_type == PACKET_OTHERHOST || 如果是属于外部任备的包
skb->pkt_type == PACKET_LOOPBACK || 如果是回送包
arp->ar_pln != 4) 如果IP地址长度不等于4
goto out;

if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL)
goto out_of_mem;

switch (dev_type) {
default:
if (arp->ar_pro != __constant_htons(ETH_P_IP))
goto out;
if (htons(dev_type) != arp->ar_hrd)
goto out;
break;
#ifdef CONFIG_NET_ETHERNET
case ARPHRD_ETHER:
/*
* ETHERNET devices will accept ARP hardware types of either
* 1 (Ethernet) or 6 (IEEE 802.2).
*/
if (arp->ar_hrd != __constant_htons(ARPHRD_ETHER) &&
arp->ar_hrd != __constant_htons(ARPHRD_IEEE802))
goto out;
if (arp->ar_pro != __constant_htons(ETH_P_IP))
goto out;
break;
#endif
#ifdef CONFIG_TR
case ARPHRD_IEEE802_TR:
/*
* Token ring devices will accept ARP hardware types of either
* 1 (Ethernet) or 6 (IEEE 802.2).
*/
if (arp->ar_hrd != __constant_htons(ARPHRD_ETHER) &&
arp->ar_hrd != __constant_htons(ARPHRD_IEEE802))
goto out;
if (arp->ar_pro != __constant_htons(ETH_P_IP))
goto out;
break;
#endif
#ifdef CONFIG_FDDI
case ARPHRD_FDDI:
/*
* According to RFC 1390, FDDI devices should accept ARP hardware types
* of 1 (Ethernet). However, to be more robust, we'll accept hardware
* types of either 1 (Ethernet) or 6 (IEEE 802.2).
*/
if (arp->ar_hrd != __constant_htons(ARPHRD_ETHER) &&
arp->ar_hrd != __constant_htons(ARPHRD_IEEE802))
goto out;
if (arp->ar_pro != __constant_htons(ETH_P_IP))
goto out;
break;
#endif
#if defined(CONFIG_AX25) || defined(CONFIG_AX25_MODULE)
case ARPHRD_AX25:
if (arp->ar_pro != __constant_htons(AX25_P_IP))
goto out;
if (arp->ar_hrd != __constant_htons(ARPHRD_AX25))
goto out;
break;
#if defined(CONFIG_NETROM) || defined(CONFIG_NETROM_MODULE)
case ARPHRD_NETROM:
if (arp->ar_pro != __constant_htons(AX25_P_IP))
goto out;
if (arp->ar_hrd != __constant_htons(ARPHRD_NETROM))
goto out;
break;
#endif
#endif
}

/* Understand only these message types */

if (arp->ar_op != __constant_htons(ARPOP_REPLY) &&
arp->ar_op != __constant_htons(ARPOP_REQUEST))
goto out; 只处理"应答"和"请求"两种操作码

/*
* Extract fields
*/
sha=arp_ptr; 指向参数区源硬件地址
arp_ptr += dev->addr_len;
memcpy(&sip, arp_ptr, 4); 发送者IP地址
arp_ptr += 4;
tha=arp_ptr; 所(要)解析的硬件地址
arp_ptr += dev->addr_len;
memcpy(&tip, arp_ptr, 4); 取所(要)解析的IP地址
/*
* Check for bad requests for 127.x.x.x and requests for multicast
* addresses. If this is one such, delete it.
*/
if (LOOPBACK(tip) || MULTICAST(tip))
goto out;

/*
* Process entry. The idea here is we want to send a reply if it is a
* request for us or if it is a request for someone else that we hold
* a proxy for. We want to add an entry to our cache if it is a reply
* to us or if it is a request for our address.
* (The assumption for this last is that if someone is requesting our
* address, they are probably intending to talk to us, so it saves time
* if we cache their address. Their address is also probably not in
* our cache, since ours is not in their cache.)
*
* Putting this another way, we only care about replies if they are to
* us, in which case we add them to the cache. For requests, we care
* about those for us and those for our proxies. We reply to both,
* and in the case of requests for us we add the requester to the arp
* cache.
*/

/* Special case: IPv4 duplicate address detection packet (RFC2131) */
if (sip == 0) { 如果发送者IP地址为零
if (arp->ar_op == __constant_htons(ARPOP_REQUEST) &&
inet_addr_type(tip) == RTN_LOCAL) 如果所请要解析的IP地址是主机的IP地址
arp_send(ARPOP_REPLY,ETH_P_ARP,tip,dev,tip,sha,dev->dev_addr,dev->dev_addr);
goto out;
}

if (arp->ar_op == __constant_htons(ARPOP_REQUEST) &&
ip_route_input(skb, tip, sip, 0, dev) == 0) { 查询目的地址在输入设备dev上的路由

rt = (struct rtable*)skb->dst;
addr_type = rt->rt_type;

if (addr_type == RTN_LOCAL) {
n = neigh_event_ns(&arp_tbl, sha, &sip, dev); 将请求设备的邻居刷新为"过期"状态
if (n) {
arp_send(ARPOP_REPLY,ETH_P_ARP,sip,dev,tip,sha,dev->dev_addr,sha);
neigh_release(n);
}
goto out;
} else if (IN_DEV_FORWARD(in_dev)) {
if ((rt->rt_flags&RTCF_DNAT) ||
(addr_type == RTN_UNICAST && rt->u.dst.dev != dev &&
(IN_DEV_PROXY_ARP(in_dev) || pneigh_lookup(&arp_tbl, &tip, dev, 0)))) {
n = neigh_event_ns(&arp_tbl, sha, &sip, dev);
if (n)
neigh_release(n);

if (skb->stamp.tv_sec == 0 || 如果是proxy_queue的redo包
skb->pkt_type == PACKET_HOST || 如果是发向自已的包
in_dev->arp_parms->proxy_delay == 0) {
arp_send(ARPOP_REPLY,ETH_P_ARP,sip,dev,tip,sha,dev->dev_addr,sha);
} else {
pneigh_enqueue(&arp_tbl, in_dev->arp_parms, skb); 缓冲请求包,作延迟处理
in_dev_put(in_dev);
return 0;
}
goto out;
}
}
}
; 收到应答包或非已的请求包
/* Update our ARP tables */

n = __neigh_lookup(&arp_tbl, &sip, dev, 0); 寻找发送者IP的邻居

#ifdef CONFIG_IP_ACCEPT_UNSOLICITED_ARP
/* Unsolicited ARP is not accepted by default.
It is possible, that this option should be enabled for some
devices (strip is candidate)
*/
if (n == NULL &&
arp->ar_op == __constant_htons(ARPOP_REPLY) &&
inet_addr_type(sip) == RTN_UNICAST)
n = __neigh_lookup(&arp_tbl, &sip, dev, -1);
#endif

if (n) {
int state = NUD_REACHABLE;
int override = 0;

/* If several different ARP replies follows back-to-back,
use the FIRST one. It is possible, if several proxy
agents are active. Taking the first reply prevents
arp trashing and chooses the fastest router.
*/
if (jiffies - n->updated >= n->parms->locktime)
override = 1;

/* Broadcast replies and request packets
do not assert neighbour reachability.
*/
if (arp->ar_op != __constant_htons(ARPOP_REPLY) ||
skb->pkt_type != PACKET_HOST)
state = NUD_STALE;
neigh_update(n, sha, state, override, 1);
neigh_release(n);
}

out:
kfree_skb(skb);
if (in_dev)
in_dev_put(in_dev);
out_of_mem:
return 0;
}
struct neighbour * neigh_event_ns(struct neigh_table *tbl,
u8 *lladdr, void *saddr,
struct net_device *dev) 将邻居刷新为"过期"状态
{
struct neighbour *neigh;

neigh = __neigh_lookup(tbl, saddr, dev, lladdr || !dev->addr_len);
if (neigh)
neigh_update(neigh, lladdr, NUD_STALE, 1, 1);
return neigh;
}
static inline struct neighbour *
__neigh_lookup(struct neigh_table *tbl, const void *pkey, struct net_device *dev, int
creat)
{
struct neighbour *n = neigh_lookup(tbl, pkey, dev);

if (n || !creat)
return n;

n = neigh_create(tbl, pkey, dev);
return IS_ERR(n) ? NULL : n;
}
int neigh_update(struct neighbour *neigh, const u8 *lladdr, u8 new, int override, int
arp)
{
u8 old;
int err;
int notify = 0;
struct net_device *dev = neigh->dev;

write_lock_bh(&neigh->lock);
old = neigh->nud_state;

err = -EPERM;
if (arp && (old&(NUD_NOARP|NUD_PERMANENT)))
goto out;

if (!(new&NUD_VALID)) {
neigh_del_timer(neigh);
if (old&NUD_CONNECTED)
neigh_suspect(neigh);
neigh->nud_state = new;
err = 0;
notify = old&NUD_VALID;
goto out;
}

/* Compare new lladdr with cached one */
if (dev->addr_len == 0) {
/* First case: device needs no address. */
lladdr = neigh->ha;
} else if (lladdr) {
/* The second case: if something is already cached
and a new address is proposed:
- compare new & old
- if they are different, check override flag
*/
if (old&NUD_VALID) {
if (memcmp(lladdr, neigh->ha, dev->addr_len) == 0)
lladdr = neigh->ha;
else if (!override)
goto out;
}
} else {
/* No address is supplied; if we know something,
use it, otherwise discard the request.
*/
err = -EINVAL;
if (!(old&NUD_VALID))
goto out;
lladdr = neigh->ha;
}

neigh_sync(neigh);
old = neigh->nud_state;
if (new&NUD_CONNECTED)
neigh->confirmed = jiffies;
neigh->updated = jiffies;

/* If entry was valid and address is not changed,
do not change entry state, if new one is STALE.
*/
err = 0;
if (old&NUD_VALID) {
if (lladdr == neigh->ha)
if (new == old || (new == NUD_STALE && (old&NUD_CONNECTED)))
goto out;
}
neigh_del_timer(neigh);
neigh->nud_state = new;
if (lladdr != neigh->ha) {
memcpy(&neigh->ha, lladdr, dev->addr_len);
neigh_update_hhs(neigh);
if (!(new&NUD_CONNECTED))
neigh->confirmed = jiffies - (neigh->parms->base_reachable_time<<1);
#ifdef CONFIG_ARPD
notify = 1;
#endif
}
if (new == old)
goto out;
if (new&NUD_CONNECTED)
neigh_connect(neigh);
else
neigh_suspect(neigh);
if (!(old&NUD_VALID)) {
struct sk_buff *skb;

/* Again: avoid dead loop if something went wrong */

while (neigh->nud_state&NUD_VALID &&
(skb=__skb_dequeue(&neigh->arp_queue)) != NULL) {
struct neighbour *n1 = neigh;
write_unlock_bh(&neigh->lock);
/* On shaper/eql skb->dst->neighbour != neigh :( */
if (skb->dst && skb->dst->neighbour)
n1 = skb->dst->neighbour;
n1->output(skb);
write_lock_bh(&neigh->lock);
}
skb_queue_purge(&neigh->arp_queue);
}
out:
write_unlock_bh(&neigh->lock);
#ifdef CONFIG_ARPD
if (notify && neigh->parms->app_probes)
neigh_app_notify(neigh);
#endif
return err;
}
static void neigh_sync(struct neighbour *n)
{
unsigned long now = jiffies;
u8 state = n->nud_state;

ASSERT_WL(n);
if (state&(NUD_NOARP|NUD_PERMANENT))
return;
if (state&NUD_REACHABLE) {
if (now - n->confirmed > n->parms->reachable_time) {
n->nud_state = NUD_STALE;
neigh_suspect(n);
}
} else if (state&NUD_VALID) {
if (now - n->confirmed <>parms->reachable_time) {
neigh_del_timer(n);
n->nud_state = NUD_REACHABLE;
neigh_connect(n);
}
}
}
static void neigh_connect(struct neighbour *neigh)
{
struct hh_cache *hh;

NEIGH_PRINTK2("neigh %p is connected.\n", neigh);

ASSERT_WL(neigh);

neigh->output = neigh->ops->connected_output;

for (hh = neigh->hh; hh; hh = hh->hh_next)
hh->hh_output = neigh->ops->hh_output;
}
static void neigh_suspect(struct neighbour *neigh)
{
struct hh_cache *hh;

NEIGH_PRINTK2("neigh %p is suspecteded.\n", neigh);

ASSERT_WL(neigh);

neigh->output = neigh->ops->output;

for (hh = neigh->hh; hh; hh = hh->hh_next)
hh->hh_output = neigh->ops->output;
}
static __inline__ void neigh_update_hhs(struct neighbour *neigh)
{
struct hh_cache *hh;
void (*update)(struct hh_cache*, struct net_device*, unsigned char*) =
neigh->dev->header_cache_update;

if (update) {
for (hh=neigh->hh; hh; hh=hh->hh_next) {
write_lock_bh(&hh->hh_lock);
update(hh, neigh->dev, neigh->ha);
write_unlock_bh(&hh->hh_lock);
}
}
}

struct pneigh_entry
{
struct pneigh_entry *next;
struct net_device *dev;
u8 key[0];
};
struct pneigh_entry * pneigh_lookup(struct neigh_table *tbl, const void *pkey,
struct net_device *dev, int creat)
{
struct pneigh_entry *n;
u32 hash_val;
int key_len = tbl->key_len;

hash_val = *(u32*)(pkey + key_len - 4);
hash_val ^= (hash_val>>16);
hash_val ^= hash_val>>8;
hash_val ^= hash_val>>4;
hash_val &= PNEIGH_HASHMASK;

read_lock_bh(&tbl->lock);

for (n = tbl->phash_buckets[hash_val]; n; n = n->next) {
if (memcmp(n->key, pkey, key_len) == 0 &&
(n->dev == dev || !n->dev)) {
read_unlock_bh(&tbl->lock);
return n;
}
}
read_unlock_bh(&tbl->lock);
if (!creat)
return NULL;

n = kmalloc(sizeof(*n) + key_len, GFP_KERNEL);
if (n == NULL)
return NULL;

memcpy(n->key, pkey, key_len);
n->dev = dev;

if (tbl->pconstructor && tbl->pconstructor(n)) {
kfree(n);
return NULL;
}

write_lock_bh(&tbl->lock);
n->next = tbl->phash_buckets[hash_val];
tbl->phash_buckets[hash_val] = n;
write_unlock_bh(&tbl->lock);
return n;
}
void pneigh_enqueue(struct neigh_table *tbl, struct neigh_parms *p,
struct sk_buff *skb) 缓冲外部设备的arp请求包
{
unsigned long now = jiffies;
long sched_next = net_random()%p->proxy_delay; 下一延迟间隔(0.8秒以内)

if (tbl->proxy_queue.qlen > p->proxy_qlen) {
kfree_skb(skb);
return;
}
skb->stamp.tv_sec = 0;
skb->stamp.tv_usec = now + sched_next;

spin_lock(&tbl->proxy_queue.lock);
if (del_timer(&tbl->proxy_timer)) {
long tval = tbl->proxy_timer.expires - now; 取定时器剩余时间
if (tval < sched_next)
sched_next = tval; 取大者
}
dst_release(skb->dst);
skb->dst = NULL;
dev_hold(skb->dev);
__skb_queue_tail(&tbl->proxy_queue, skb);
mod_timer(&tbl->proxy_timer, now + sched_next);
spin_unlock(&tbl->proxy_queue.lock);
}
static void neigh_proxy_process(unsigned long arg)
{
struct neigh_table *tbl = (struct neigh_table *)arg;
long sched_next = 0;
unsigned long now = jiffies;
struct sk_buff *skb;

spin_lock(&tbl->proxy_queue.lock);

skb = tbl->proxy_queue.next;

while (skb != (struct sk_buff*)&tbl->proxy_queue) {
struct sk_buff *back = skb;
long tdif = back->stamp.tv_usec - now;

skb = skb->next;
if (tdif <= 0) {
struct net_device *dev = back->dev;
__skb_unlink(back, &tbl->proxy_queue);
if (tbl->proxy_redo && netif_running(dev))
tbl->proxy_redo(back);
else
kfree_skb(back);

dev_put(dev);
} else if (!sched_next || tdif < sched_next)
sched_next = tdif;
}
del_timer(&tbl->proxy_timer);
if (sched_next)
mod_timer(&tbl->proxy_timer, jiffies + sched_next);
spin_unlock(&tbl->proxy_queue.lock);
}
static void parp_redo(struct sk_buff *skb)
{
arp_rcv(skb, skb->dev, NULL);
}
static void arp_error_report(struct neighbour *neigh, struct sk_buff *skb)
{
dst_link_failure(skb);
kfree_skb(skb);
}
static inline void dst_link_failure(struct sk_buff *skb)
{
struct dst_entry * dst = skb->dst;
if (dst && dst->ops && dst->ops->link_failure)
dst->ops->link_failure(skb);
}


Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=117148

如何发掘出更多退休的钱?

如何发掘出更多退休的钱? http://bbs.wenxuecity.com/bbs/tzlc/1328415.html 按照常规的说法,退休的收入必须得有退休前的80%,或者是4% withdrawal rule,而且每年还得要加2-3%对付通胀,这是一个很大...