文章目录
1. 网络层处理框架(三层)
声明:未经本人同意,严禁一切形式转载!!! |
---|
函数 | 作用 |
---|---|
ip_rcv | 报文检查(格式、是否发往本地) |
ip_rcv_finish | 查询路由表确定转发还是本地处理 |
dst_input | 真正转发/本地处理接口 |
ip_local_deliver | 如果是分片报文,重组后交由上层处理 |
ip_defrag | 分片报文重组 |
ip_local_deliver_finish | 去IP头、交由传输层协议继续处理 |
ip_forward | 是否转发、查询安全策略路由(如IPSec策略), 修改TTL等 |
ip_forward_finish | IP选项处理 |
ip_queue_xmit | 检查路由是否有效(无效则重新查询),添加IP头部 |
dst_output | 根据路由结果,进行转发操作 |
ip_output | 如果IP超过MTU,则进行分片 |
ip_fragment | IP报文分片处理接口 |
ip_finish_output | 指定二层头中的报文类型为IP类型(0x0800),IP层最后一个函数 |
ip_finish_output2 | 填充二层头(涉及到ARP查询目的MAC) |
neigh_resolve_output | ARP相关处理 |
1.1 PRE_ROUTING部分函数介绍
1.1.1 ip_rcv()
此函数是接收报文时IP层的入口函数,very very 重要。它的主要功能如下:
- 是否发给本设备的报文(目的MAC是本设备)
- IP报文头部格式检查(其中包括校验和)
- 进入NF_IP_PRE_ROUTING点
代码如下:
/*
* Main IP Receive routine.
*/
int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt)
{
struct iphdr *iph;
/* When the interface is in promisc. mode, drop all the crap
* that it receives, do not try to analyse it.
*/
/*混杂模式时丢弃所有报文????
*/
if (skb->pkt_type == PACKET_OTHERHOST)/*根据目的MAC来设置的,只有发给自己的报文,才会继续后续IP层处理*/
goto drop;
IP_INC_STATS_BH(IPSTATS_MIB_INRECEIVES);
if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) {
IP_INC_STATS_BH(IPSTATS_MIB_INDISCARDS);
goto out;
}
/*线性区域是指:存放报文的那部分区域,head~data~tail~end区域*/
if (!pskb_may_pull(skb, sizeof(struct iphdr)))/*如果skb线性区域的长度比标准iph还小,则说明报文格式有误*/
goto inhdr_error;
iph = skb->nh.iph;
/*
* RFC1122: 3.1.2.2 MUST silently discard any IP frame that fails the checksum.
*
* Is the datagram acceptable?
*
* 1. Length at least the size of an ip header
* 2. Version of 4
* 3. Checksums correctly. [Speed optimisation for later, skip loopback checksums]
* 4. Doesn't have a bogus length
*/
if (iph->ihl < 5 || iph->version != 4)/*只处理ipv4*/
goto inhdr_error;
/*
*ph->ihl*4真实头部长度,因为可能包含option字段部分
*意思是说:既然包含了ip选项字段,那么skb线性区长度必然会大于ip头部长度
*/
if (!pskb_may_pull(skb, iph->ihl*4))
goto inhdr_error;
iph = skb->nh.iph;
/*
*校验报文校验和是否正确的方式是:重新计算整个报文,为0说明校验和正确
*/
if (ip_fast_csum((u8 *)iph, iph->ihl) != 0)/*校验和为0,说明校验和正确*/
goto inhdr_error;
{
__u32 len = ntohs(iph->tot_len);
if (skb->len < len || len < (iph->ihl<<2))/*报文长度校验*/
goto inhdr_error;
/* Our transport medium may have padded the buffer out. Now we know it
* is IP we can trim to the true length of the frame.
* Note this now means skb->len holds ntohs(iph->tot_len).
*/
if (pskb_trim_rcsum(skb, len)) {
IP_INC_STATS_BH(IPSTATS_MIB_INDISCARDS);
goto drop;
}
}
return NF_HOOK(PF_INET, NF_IP_PRE_ROUTING, skb, dev, NULL,
ip_rcv_finish);
inhdr_error:
IP_INC_STATS_BH(IPSTATS_MIB_INHDRERRORS);
drop:
kfree_skb(skb);
out:
return NET_RX_DROP;
}
1.1.2 ip_rcv_finish()
此函数的功能包括:
- 查询路由表
- IP头部中选项字段解析
- 进入
dst_input()
函数,此函数中会根据查询的路由结果,决定报文是“转发”、“交由上层处理”、“丢弃”。
代码如下:
/*
* ip_rcv_finish:最主要的工作是查询路由表;解析选项字段
* 然后根据路由决定是
* 1. 向上层传递
* 2. 转发
* 3. 丢弃
**/
static inline int ip_rcv_finish(struct sk_buff *skb)
{
struct net_device *dev = skb->dev;
struct iphdr *iph = skb->nh.iph;
/*
* Initialise the virtual path cache for the packet. It describes
* how the packet travels inside Linux networking.
*/
/*所有收到的数据包由于都不包含路由信息,so应该都需要在此进行路由查询*/
if (skb->dst == NULL) {
/*目的路由表项为空,且查询路由失败,则丢弃报文*/
if (ip_route_input(skb, iph->daddr, iph->saddr, iph->tos, dev))/*!!!!!!!!core opterations!!!!!!!!!*/
goto drop;
}
#ifdef CONFIG_NET_CLS_ROUTE
if (skb->dst->tclassid) {
struct ip_rt_acct *st = ip_rt_acct + 256*smp_processor_id();
u32 idx = skb->dst->tclassid;
st[idx&0xFF].o_packets++;
st[idx&0xFF].o_bytes+=skb->len;
st[(idx>>16)&0xFF].i_packets++;
st[(idx>>16)&0xFF].i_bytes+=skb->len;
}
#endif
if (iph->ihl > 5) {
/*IP头部中包含选项字段*/
struct ip_options *opt;
/* It looks as overkill, because not all
IP options require packet mangling.
But it is the easiest for now, especially taking
into account that combination of IP options
and running sniffer is extremely rare condition.
--ANK (980813)
*/
if (skb_cow(skb, skb_headroom(skb))) {
/*headroom空间不足的话,扩展头部*/
IP_INC_STATS_BH(IPSTATS_MIB_INDISCARDS);
goto drop;
}
iph = skb->nh.iph;
if (ip_options_compile(NULL, skb))/*解析opt字段*/
goto inhdr_error;
opt = &(IPCB(skb