IPtable原理
  BPI26WEjHJmG 2023年11月02日 51 0

1 iptable的四表五链

Netfilter 的实现可以简单地归纳为四表五链

五链:在内核协议栈的各个重要关卡,埋下了五个HOOK(钩子函数)。每一个钩子函数对应的一些列的规则,以链表的形式存在,所以俗称为五链

五链主要体现在内核的接收,发送,和转发上面

2 内核的接收过程

  • Linux内核网络包接收在IP层的入口函数是ip_rcv。
  • 网络包在这里碰到的第一个HOOK就是PREROUTING。
  • 当PREROUTING处理完后,会进行路由选择,如果发现是本设备的网络包。进入ip_local_deliver中,在这里又会遇到INPUT钩子

IPtable原理_网络协议栈

//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);

}
//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钩子函数

int ip_route_input_noref(struct sk_buff *skb, __be32 daddr, __be32 saddr,
			 u8 tos, struct net_device *dev)
{
	.......
	res = ip_route_input_slow(skb, daddr, saddr, tos, dev);
	rcu_read_unlock();
	return res;
}
static int ip_route_input_slow(struct sk_buff *skb, __be32 daddr, __be32 saddr,
			       u8 tos, struct net_device *dev)
{
	.......

	res.fi = NULL;
	res.table = NULL;
	if (ipv4_is_lbcast(daddr) || (saddr == 0 && daddr == 0))
		goto brd_input;

	/* Accept zero addresses only to limited broadcast;
	 * I even do not know to fix it or not. Waiting for complains :-)
	 */
	if (ipv4_is_zeronet(saddr))
		goto martian_source;

	if (ipv4_is_zeronet(daddr))
		goto martian_destination;

	/* Following code try to avoid calling IN_DEV_NET_ROUTE_LOCALNET(),
	 * and call it once if daddr or/and saddr are loopback addresses
	 */
	if (ipv4_is_loopback(daddr)) {
		if (!IN_DEV_NET_ROUTE_LOCALNET(in_dev, net))
			goto martian_destination;
	} else if (ipv4_is_loopback(saddr)) {
		if (!IN_DEV_NET_ROUTE_LOCALNET(in_dev, net))
			goto martian_source;
	}

	
out:	return err;

brd_input:
	if (skb->protocol != htons(ETH_P_IP))
		goto e_inval;

	if (!ipv4_is_zeronet(saddr)) {
		err = fib_validate_source(skb, saddr, 0, tos, 0, dev,
					  in_dev, &itag);
		if (err < 0)
			goto martian_source;
	}
	flags |= RTCF_BROADCAST;
	res.type = RTN_BROADCAST;
	RT_CACHE_STAT_INC(in_brd);

local_input:
	do_cache = false;
	if (res.fi) {
		if (!itag) {
			rth = rcu_dereference(FIB_RES_NH(res).nh_rth_input);
			if (rt_cache_valid(rth)) {
				skb_dst_set_noref(skb, &rth->dst);
				err = 0;
				goto out;
			}
			do_cache = true;
		}
	}
	// local_input
	rth = rt_dst_alloc(net->loopback_dev, flags | RTCF_LOCAL, res.type,
			   IN_DEV_CONF_GET(in_dev, NOPOLICY), false, do_cache);
	if (!rth)
		goto e_nobufs;

	.......
}
static struct rtable *rt_dst_alloc(struct net_device *dev,
				   unsigned int flags, u16 type,
				   bool nopolicy, bool noxfrm, bool will_cache)
{
	struct rtable *rt;

	rt = dst_alloc(&ipv4_dst_ops, dev, 1, DST_OBSOLETE_FORCE_CHK,
		       (will_cache ? 0 : (DST_HOST | DST_NOCACHE)) |
		       (nopolicy ? DST_NOPOLICY : 0) |
		       (noxfrm ? DST_NOXFRM : 0));

	if (rt) {
		rt->rt_genid = rt_genid_ipv4(dev_net(dev));
		rt->rt_flags = flags;
		rt->rt_type = type;
		rt->rt_is_input = 0;
		rt->rt_iif = 0;
		rt->rt_pmtu = 0;
		rt->rt_mtu_locked = 0;
		rt->rt_gateway = 0;
		rt->rt_uses_gateway = 0;
		rt->rt_table_id = 0;
		INIT_LIST_HEAD(&rt->rt_uncached);

		rt->dst.output = ip_output;
		if (flags & RTCF_LOCAL)
             // 本地路由 
			rt->dst.input = ip_local_deliver;
	}

	return rt;
}
/*
 * 	Deliver IP Packets to the higher protocol layers.
 */
int ip_local_deliver(struct sk_buff *skb)
{
	/*
	 *	Reassemble IP fragments.
	 */
	struct net *net = dev_net(skb->dev);

	if (ip_is_fragment(ip_hdr(skb))) {
		if (ip_defrag(net, skb, IP_DEFRAG_LOCAL_DELIVER))
			return 0;
	}
	// 执行NF_INET_LOCAL_IN钩子函数
	return NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_IN,
		       net, NULL, skb, skb->dev, NULL,
		       ip_local_deliver_finish);
}

接收总结:网卡数据进入协议栈->PREROUTING->路由判断(本机)->INPUT链->....

3 发送数据过程

  • Linux在发送网络数据包过程中,进入协议栈后,进行路由选择。第一个HOOK钩子就是OUTPUT,然后进入POSTRUTING链

IPtable原理_IPV4_02

//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);
}
//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);
 // 执行NF_HOOK 将数据包发送到NF_INET_LOCAL_OUT(OUTPUT)
 return nf_hook(NFPROTO_IPV4, NF_INET_LOCAL_OUT, skb, NULL,
         skb_dst(skb)->dev, dst_output);
}

执行完进入到dst_output

总结发送过程:路由选择->OUTPUT链->POSTRUTING链->....

4 转发过程

  • Linux可以像路由器一样来工作。接收到不属于自己的包,根据路由表现在合适的网卡设备,将数据转发出去
  • 这个过程需要经过接收数据的前半段,在ip_rcv中经过PRERUTING链,然后路由发现不是自己的本设备的包,就进入ip_forward进行转发。
  • 在这里又会遇到FORWARD链路。最后进入ip_output进行真正的发送,遇到POSTROUTING链

IPtable原理_网络协议栈_03

//file: net/ipv4/ip_input.c
int ip_rcv(struct sk_buff *skb, ......){
    ......
    // NF_INET_PRE_ROUTING 链上规则处理完后进入ip_rcv_finish,在这里进行路由然后进入des_input
    return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, skb, dev, NULL,
               ip_rcv_finish);

}
//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)
{
 ......
 // 这里的ip_forward_finish路面会发生到IP层的ip_output
 return NF_HOOK(NFPROTO_IPV4, NF_INET_FORWARD, skb, skb->dev,
         rt->dst.dev, ip_forward_finish);
}
//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));
}

总结转发的过程:PRERUTING->路由判断(非本机)->FORWARD->POSTRUTING

5 IPtable汇总

IPtable原理_IP_04

总结:接收过程1,2 发送的过程是4, 5 ,转发的过程是1,3,5

iptables 中的五个链。在每一个链上都可能是由许多个规则组成的。在 NF_HOOK 执行到这个链的时候,就会把规则按照优先级挨个过一遍。如果有符合条件的规则,则执行规则对应的动作。

IPtable原理_IPV4_05

不同的规则又分为四个部分:raw mangle nat filter

raw: 表的作用是将命中规则的包,跳过其他表的处理,它的优先级最高

mangle: 表的作用是根据规则的修改数据包的一些标志位。比如TTL

nat: 表的作用是实现网络地址转换

filter: 表的重要是过来某一些数据包,防火墙的基础

IPtable原理_网络协议栈_06

Raw 表目的是跳过其它表,所以只需要在接收和发送两大过程的最开头处把关,所以只需要用到 PREROUTING 和 OUTPUT 两个钩子。

Mangle 表有可能会在任意位置都有可能会修改网络包,所以它是用到了全部的钩子位置。

NAT 分为 SNAT(Source NAT)和 DNAT(Destination NAT)两种,可能会工作在 PREROUTING、INPUT、OUTPUT、POSTROUTING 四个位置。

Filter 只在 INPUT、OUTPUT 和 FORWARD 这三步中工作就够了。



【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
  llt0tXqeaug8   2023年11月28日   18   0   0 vimIPDNS
  wHaAsJanHOFo   2023年11月19日   16   0   0 IP
  wHaAsJanHOFo   2023年11月30日   11   0   0 微信权重IP
  lh6O4DgR0ZQ8   2023年11月14日   21   0   0 网络地址网络接口IP
BPI26WEjHJmG
作者其他文章 更多