12.8 网络层
12.8.4 分组转发
转发IP分组,根据目标地址分为:
1. 直接和本地相连。
2. 不直接相连,需要网关转发。
int ip_route_input_noref(skb, daddr, saddr, tos, net_dev):
//查找路由表。
如果 skb->_skb_refdst 成员缓存了 struct dst_entry 信息,无需查找路由表。
ip_forward 流程:
ip_forward_options:
处理IP选项。
最后调用 skb->dst->output,即 ip_output。
struct sock {
struct dst_entry *sk_dst_cache; //缓存的路由结构体。
}
如何缓存一个struct dst_entry dst ?
rcu_assign_pointer(sk->sk_dst_cache, dst);
所以若同一 socket 的所有分组的目的地址都相同,则只需在发送第一个分组时查找 struct dst_entry。
后续分组可使用缓存的 struct dst_entry。
12.8.5 发送分组
ip_queue_xmit:更高层协议使用,如TCP,UDP。
举例:
tcp_transmit_skb ->
icsk->icsk_af_ops->queue_xmit(skb, &inet->cork.fl);
// 即 ip_queue_xmit ()
所以 转发和本地发送的数据,都最终调用 ip_output。
1. 转移到网络访问层
int ip_output(struct sk_buff *skb)
{
struct net_device *dev = skb_dst(skb)->dev;
skb->dev = dev; //将skb->dev改为路由表指示的出口设备。
NF_HOOK_COND(NFPROTO_IPV4, NF_INET_POST_ROUTING, skb, , dev,
ip_finish_output, );
}
dst->neighbour->output = dev_queue_xmit;
neighbour:ARP层相关。
dev_queue_xmit
-> dev_hard_start_xmit
-> ops->ndo_start_xmit(skb, dev); //不同网卡的驱动自定义。
2. 分片
分片原理:
ip_fragment:实现分片,并发送。
包括:
设置分片偏移量。
除最后一个分片都设置MF标志。
3. 路由
struct dst_entry { // 路由查找时填充成员。
struct net_device *dev;
int (*input)(struct sk_buff *); // 处理进入分组。
int (*output)(struct sk_buff *); // 处理外出分组。
};
本地 转发
input指针 ip_local_deliver ip_forward
output指针 ip_ouput ip_ouput
struct neighbour { //就是ARP表项,存储本地网络的IP和硬件地址。
struct neighbour *next;
struct neigh_table *tbl;
unsigned char ha[ALIGN(MAX_ADDR_LEN, sizeof(unsigned long))];
//硬件地址
int (*output)(struct neighbour *, struct sk_buff *);
// 用于转发数据包时发送数据包到邻居设备上。
//通常为dev_queue_xmit; ,最终调用网卡实现的 ndo_start_xmit 函数指针。
struct net_device *dev;
};
12.8.6 netfilter
1. 扩展网络功能
netfilter可扩展的功能:
1. 不同方向的包过滤。
2. NAT。
3. 拆分、修改包。
2. 调用钩子函数
netfilter钩子函数会中断网络层函数。
如何调用钩子函数?
使用宏 NF_HOOK
int NF_HOOK(uint8_t pf, unsigned int hook, struct sk_buff *skb, struct net_device *in, struct net_device *out, int (*okfn)(struct sk_buff *))
NF_HOOK
-> NF_HOOK_THRESH(INT_MIN)
int NF_HOOK_THRESH(pf, hook, skb, in, out, okfn, INT_MIN)
//所有钩子函数都执行,因为INT_MIN值的最小
{
int ret = nf_hook_thresh(pf, hook, skb, in, out, okfn, thresh);
//nf_hook_thresh->nf_hook_slow
if (ret == 1) //ret=1 即没有注册钩子函数
ret = okfn(skb);
}
宏的参数介绍:
pf:
调用哪个协议族的钩子函数。
hook:钩子编号。如:
NF_IP_FORWORD
NF_IP_LOCAL_OUT
okfn:
钩子函数结构后执行。
thresh:
优先级高于该值的钩子函数都将执行。
最小为:INT_MIN,此时所有钩子函数都执行。
使用举例:
int ip_forward(struct sk_buff *skb)
{
...
return NF_HOOK(NFPROTO_IPV4, NF_INET_FORWARD, skb, skb->dev,
rt->dst.dev, ip_forward_finish);
}
所以 NF_INET_FORWARD 钩子函数执行完后,就调用okfn函数,即ip_forward_finish。
如果:
1. 内核没有开启CONFIG_NETFILTER。
或
2. 没有注册对应pf,hook钩子函数。
则不调用钩子函数,直接执行okfn函数,即ip_forward_finish。
ip_forward_finish:实现转发操作,包含:
1. 递减TTL(生存时间)。
2. 修改 IP包头。
3. 调用dev_queue_xmit。
4. 更新路由缓存项,以加速下一次路由。
当没有配置CONFIG_NETFILTER:
#define NF_HOOK(pf, hook, skb, indev, outdev, okfn)
(okfn)(skb)
3. 扫描挂钩表
nf_hook_slow:扫描钩子链表。
所有的钩子函数保存在:
struct list_head nf_hooks [ NFPROTO_NUMPROTO ] [ NF_MAX_HOOKS ];
NFPROTO_NUMPROTO:
支持的协议族数量。如IPV4,IPV6,ARP,BRIDGE。
默认值:13。
NF_MAX_HOOKS:
一个协议族最多的挂钩链表。
默认值:8。
最多链表数量:13 * 8。
IPV4协议族定义 5 个链表:
NF_INET_PRE_ROUTING
NF_INET_LOCAL_IN
NF_INET_FORWARD
NF_INET_LOCAL_OUT
NF_INET_POST_ROUTING
这 5 个链表中连接了各自钩子函数。
钩子函数用 nf_hook_ops 表示:
struct nf_hook_ops *arpfilter_ops;
struct nf_hook_ops *mangle_ops;
struct nf_hook_ops *filter_ops;
struct nf_hook_ops *ipv4_conntrack_ops;
struct nf_hook_ops *rawtable_ops;
struct nf_hook_ops *ebt_ops_filter;
struct nf_hook_ops {
struct list_head list;
nf_hookfn *hook; // 钩子函数。
u_int8_t pf; // 协议族。
unsigned int hooknum;
// 钩子编号,如 NF_INET_POST_ROUTING
int priority;
// 钩子的优先级。按先后执行。
//如 INT_MIN
};
nf_hook_slow
-> nf_iterate
nf_iterate:执行指定协议族,指定钩子编号中注册的所有钩子函数。
如:
协议族为NFPROTO_IPV4,
钩子编号为NF_INET_POST_ROUTING。
(当然是,大于指定优先级的所有钩子函数。)
4. 激活钩子函数
钩子函数的返回值:
1. NF_ACCEPT:
包通过当前检查点,继续协议栈后续处理。
2. NF_DROP:
该包将被丢弃。无需其他处理。
3. NF_QUEUE:
包被送到用户空间的一个队列中,供用户态程序后续处理。
不再执行其他钩子函数。
4. NF_REPEAT
重复执行当前hook链规则检查。
12.8.7 IPv6
1. 概述
IPv6地址长度:128位。
注:IPv5名称被STP协议使用。
IPv6 报文格式:
2. 实现
IPv6低层和IPv4低层一样。
IPv6不会分片,所以帧格式中无需分片信息。
IPv6处理流程: