从linux收包与发包过程看iptables链如何发挥作用
1、linux收包过程(以udp包为例)
2、linux发包过程(以udp包为例)
3、收发包过程中iptables的hook如何发挥作用
主要分为三个部分:
- 接收数据的处理流程是:PREROUTING链 -> 路由判断(是本机)-> INPUT链 -> 本机进程
- 发送数据包流程是:路由选择 -> OUTPUT链 -> POSTROUTING链 -> 网卡发出
- 转发数据过程:PREROUTING链 -> 路由判断(不是本设备,找到下一跳) -> FORWARD链 -> POSTROUTING链 -> 网卡发出
3-1、接收数据过程分析:
过程分析:
Linux 在网络包接收在 IP 层的入口函数是 ip_rcv。网络在这里包碰到的第一个 HOOK 就是 PREROUTING。当该钩子上的规则都处理完后,会进行路由选择。如果发现是本设备的网络包,进入 ip_local_deliver 中,在这里又会遇到 INPUT 钩子
源码分析:
先看ip_rcv
//file: net/ipv4/ip_input.c
int ip_rcv(struct sk_buff *skb, ......){
......
return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, skb, dev, NULL,
ip_rcv_finish);
}
NF_HOOK 这个函数会执行到 iptables 中 pre_routing 里的各种表注册的各种规则。
当处理完后,进入 ip_rcv_finish。在这里函数里将进行路由选择。
这也就是 PREROUTING 这一链名字得来的原因,因为是在路由前执行的。
//file: net/ipv4/ip_input.c
static int ip_rcv_finish(struct sk_buff *skb){
...
if (!skb_dst(skb)) {
int err = ip_route_input_noref(skb, iph->daddr, iph->saddr,
iph->tos, skb->dev);
...
}
...
return dst_input(skb);
}
如果发现是本地设备上的接收,会进入 ip_local_deliver 函数。
接着是又会执行到 LOCAL_IN 钩子,这也就是我们说的 INPUT 链。
//file: net/ipv4/ip_input.c
int ip_local_deliver(struct sk_buff *skb){
......
return NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_IN, skb, skb->dev, NULL,
ip_local_deliver_finish);
}
3-2、发送数据过程分析:
过程分析:
Linux 在网络包发送的过程中,首先是发送的路由选择,然后碰到的第一个 HOOK 就是 OUTPUT,然后接着进入 POSTROUTING 链。
源码分析:
网络层发送的入口函数是 ip_queue_xmit。
//file: net/ipv4/ip_output.c
int ip_queue_xmit(struct sk_buff *skb, struct flowi *fl)
{
// 路由选择过程
// 选择完后记录路由信息到 skb 上
rt = (struct rtable *)__sk_dst_check(sk, 0);
if (rt == NULL) {
// 没有缓存则查找路由项
rt = ip_route_output_ports(...);
sk_setup_caps(sk, &rt->dst);
}
skb_dst_set_noref(skb, &rt->dst);
...
//发送
ip_local_out(skb);
}
在这里先进行了发送时的路由选择,然后进入发送时的 IP 层函数 __ip_local_out。
//file: net/ipv4/ip_output.c
int __ip_local_out(struct sk_buff *skb)
{
struct iphdr *iph = ip_hdr(skb);
iph->tot_len = htons(skb->len);
ip_send_check(iph);
return nf_hook(NFPROTO_IPV4, NF_INET_LOCAL_OUT, skb, NULL,
skb_dst(skb)->dev, dst_output);
}
上面的NF_HOOK将发送数据包送入到 NF_INET_LOCAL_OUT (OUTPUT) 链。
执行完后,进入 dst_output。
//file: include/net/dst.h
static inline int dst_output(struct sk_buff *skb)
{
return skb_dst(skb)->output(skb);
}
在这里获取到之前的选路,并调用选到的 output 发送。将进入 ip_output。
//file: net/ipv4/ip_output.c
int ip_output(struct sk_buff *skb)
{
...
//再次交给 netfilter,完毕后回调 ip_finish_output
return NF_HOOK_COND(NFPROTO_IPV4, NF_INET_POST_ROUTING, skb, NULL, dev,
ip_finish_output,
!(IPCB(skb)->flags & IPSKB_REROUTED));
}
3-3、转发数据过程分析:
过程分析:
除了接收和发送过程以外,Linux 内核还可以像路由器一样来工作。它将接收到网络包(不属于自己的),然后根据路由表选到合适的网卡设备将其转发出去。
这个过程中,先是经历接收数据的前半段。在 ip_rcv 中经过 PREROUTING 链,然后路由后发现不是本设备的包,那就进入 ip_forward 函数进行转发,在这里又会遇到 FORWARD 链。最后还会进入 ip_output 进行真正的发送,遇到 POSTROUTING 链。
源码分析:
先是进入 IP 层入口 ip_rcv,在这里遇到 PREROUTING 链。
//file: net/ipv4/ip_input.c
int ip_rcv(struct sk_buff *skb, ......){
......
return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, skb, dev, NULL,
ip_rcv_finish);
}
PREROUTING 链条上的规则都处理完后,进入 ip_rcv_finish,
在这里路由选择,然后进入 dst_input。
//file: include/net/dst.h
static inline int dst_input(struct sk_buff *skb)
{
return skb_dst(skb)->input(skb);
}
转发过程的这几步和接收过程一模一样的。不过内核路径就要从上面的 input 方法调用开始不一样。
非本设备的不会进入 ip_local_deliver,而是会进入到 ip_forward。
//file: net/ipv4/ip_forward.c
int ip_forward(struct sk_buff *skb)
{
......
return NF_HOOK(NFPROTO_IPV4, NF_INET_FORWARD, skb, skb->dev,
rt->dst.dev, ip_forward_finish);
}
在 ip_forward_finish 里会送到 IP 层的发送函数 ip_output。
//file: net/ipv4/ip_output.c
int ip_output(struct sk_buff *skb)
{
...
//再次交给 netfilter,完毕后回调 ip_finish_output
return NF_HOOK_COND(NFPROTO_IPV4, NF_INET_POST_ROUTING, skb, NULL, dev,
ip_finish_output,
!(IPCB(skb)->flags & IPSKB_REROUTED));
}
3-4、汇总: