前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >iptables MASQUERADE 如何选择源IP

iptables MASQUERADE 如何选择源IP

原创
作者头像
shumh
发布2022-10-30 20:25:37
3.3K0
发布2022-10-30 20:25:37
举报
文章被收录于专栏:netstacknetstack

原文发表在 http://shumh.net/posts/net/2022-10-14-masquerade/

本文重点介绍 iptables MASQUERADE 动作实现原理,需要读者具备 iptables 一些基础知识。iptables 的基础知识请翻阅 Iptables 指南 1.1.19

在讲 MASQUERADE 之前,先说 iptables 的另一个动作:SNAT,如果你对这个动作比较熟悉可以直接略过。

SNAT target

这个 target 是用来做源网络地址转换的,就是重写包的源 IP 地址。那么什么时候会用到这个 target 呢,一般在共享公网地址时使用,即主机访问公网时,通过 SNAT 规则将从本地出去的包的源地址改为 Internet 地址(主机在内网通信时的源地址通常是内网地址,不可在 Internet 上路由),这样从公网的回包才能送回主机。

SNAT 只能用在 nat 表的 POSTROUTING 链里。

Option

--to-source

Example

iptables -t nat -A POSTROUTING -p tcp -o eth0 -j SNAT --to-source 194.236.50.155-194.236.50.160:1024-32000

Explanation

指定源地址和端口,有以下几种方式:<li>1、单独的地址。<li>2、一段连续的地址,用连字符分隔,如194.236.50.155-194.236.50.160,这样可以实现负载平衡。每个流会被随机分配一个 IP,但对于同一个流使用的是同一个 IP。<li>3、在指定 -p tcp 或 -p udp 的前提下,可以指定源端口的范围,如194.236.50.155:1024-32000,这样包的源端口就被限制在1024-32000了。<li>注意,如果可能,iptables 总是想避免任何的端口变更,换句话说,它总是尽力使用建立连接时所用的端口。但是如果两台机子使用相同的源端口,iptables 将会把他们的其中之一映射到另外的一个端口。如果没有指定端口范围, 所有的在512以内的源端口会被映射到512以内的另一个端口,512和1023之间的将会被映射到 1024内,其他的将会被映射到大于或对于1024的端口,也就是说是同范围映射。还要注意,这种映射和目的端口无关。

MASQUERADE target

MASQUERADE 的作用和 SNAT 一样,区别是它不需要指定 --to-source。MASQUERADE 专门用于动态获取IP地址的连接,比如,拨号上网、DHCP连接等。

MASQUERADE(伪装)会自动获取网卡上的 IP 地址,而不用像 SNAT 那样需要使用 --to-source 指定,当 IP 发生变化时不需要手动改动。当网卡 down 掉时,MASQUERADE 不会保留任何相关的 conntrack 记录,如果我们使用 SNAT target,conntrack 记录是被保留下来的,直到被超时 GC,这会占用很多连接跟踪的内存。

注意,MASQUERADE 和 SNAT一样,只能用于 nat 表的 POSTROUTING链。

Option

--to-ports

Example

iptables -t nat -A POSTROUTING -p TCP -j MASQUERADE --to-ports 1024-31000

Explanation

在指定TCP或UDP的前提下,设置外出包能使用的端口,设置单个端口:--to-ports 1025,或者是端口范围:--to-ports 1024-3000

MASQUERADE 如何选择源 IP

前面聊了 MASQUERADE 的作用,现在终于到了本文的重点,当主机上配置多个 IP 地址时,MASQUERADE 如何从这些 IP 地址中选出一个合适的源 IP。

考虑如下环境:

机器 IP 配置如下:

代码语言:shell
复制
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 xdp qdisc mq state UP group default qlen 1000
    link/ether 52:54:00:91:a0:be brd ff:ff:ff:ff:ff:ff
    inet 10.0.10.13/24 brd 10.0.10.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet 10.0.10.14/24 scope global secondary eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::5054:ff:fe91:a0be/64 scope link 
       valid_lft forever preferred_lft forever
3: docker0: <BROADCAST,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 5e:88:9f:6d:a7:2b brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.101/32 scope link docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::5c88:9fff:fe6d:a72b/64 scope link 
       valid_lft forever preferred_lft forever

路由如下:

代码语言:shell
复制
default via 10.0.10.1 dev eth0
10.0.10.0/24 dev eth0 proto kernel scope link src 10.0.10.13
192.168.0.0/24 via 192.168.1.101 dev docker0 src 192.168.1.101
192.168.1.101 dev docker0 scope link

配置了 MASQUERADE 规则:

代码语言:shell
复制
iptables -t nat -A POSTROUTING -j MASQUERADE

如上所示,机器 eth0 Interface 上配置了 10.0.10.13/24, 10.0.10.14/24 两个地址,docker0 上配置了 192.168.1.101/32 地址。

问题1: 在机器上访问 10.0.10.15 时,MASQUERADE 选择的源 IP 是哪个?(10.0.10.13? 10.0.10.14? 192.168.1.101?)

问题2: 在机器上访问 192.168.0.2 时,MASQUERADE 选择的源 IP 是哪个?(10.0.10.13? 10.0.10.14? 192.168.1.101?)

更改 docker0 IP 地址的 scope,从 link 改为 global:

代码语言:shell
复制
3: docker0: <BROADCAST,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 5e:88:9f:6d:a7:2b brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.101/32 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::5c88:9fff:fe6d:a72b/64 scope link 
       valid_lft forever preferred_lft forever

问题3: 更改 docker0 IP 地址的 scope 后,此时在机器上访问 192.168.0.2 时,MASQUERADE 选择的源 IP 是哪个?(10.0.10.13? 10.0.10.14? 192.168.1.101?)

代码分析

MASQUERADE 在 NAT 表中注册的钩子函数

代码语言:c
复制
# net/netfilter/xt_MASQUERADE.c
static struct xt_target masquerade_tg_reg[] __read_mostly = {
	{
#if IS_ENABLED(CONFIG_IPV6)
		.name		= "MASQUERADE",
		.family		= NFPROTO_IPV6,
		.target		= masquerade_tg6,
		.targetsize	= sizeof(struct nf_nat_range),
		.table		= "nat",
		.hooks		= 1 << NF_INET_POST_ROUTING,
		.checkentry	= masquerade_tg6_checkentry,
		.destroy	= masquerade_tg_destroy,
		.me		= THIS_MODULE,
	}, {
#endif
		.name		= "MASQUERADE",
		.family		= NFPROTO_IPV4,
		.target		= masquerade_tg,
		.targetsize	= sizeof(struct nf_nat_ipv4_multi_range_compat),
		.table		= "nat",
		.hooks		= 1 << NF_INET_POST_ROUTING,
		.checkentry	= masquerade_tg_check,
		.destroy	= masquerade_tg_destroy,
		.me		= THIS_MODULE,
	}
};

masquerade_tg 函数

代码语言:c
复制
static unsigned int
masquerade_tg(struct sk_buff *skb, const struct xt_action_param *par)
{
	struct nf_nat_range2 range;
	const struct nf_nat_ipv4_multi_range_compat *mr;

	mr = par->targinfo;
	range.flags = mr->range[0].flags;
	range.min_proto = mr->range[0].min;
	range.max_proto = mr->range[0].max;

	return nf_nat_masquerade_ipv4(skb, xt_hooknum(par), &range,
				      xt_out(par));
}

nf_nat_masquerade_ipv4 是核心函数,其中 MASQUERADE 选择源 IP 的逻辑就在其中:

代码语言:c
复制
unsigned int
nf_nat_masquerade_ipv4(struct sk_buff *skb, unsigned int hooknum,
		       const struct nf_nat_range2 *range,
		       const struct net_device *out)
{
	struct nf_conn *ct;
	struct nf_conn_nat *nat;
	enum ip_conntrack_info ctinfo;
	struct nf_nat_range2 newrange;
	const struct rtable *rt;
	__be32 newsrc, nh;

	WARN_ON(hooknum != NF_INET_POST_ROUTING);

	ct = nf_ct_get(skb, &ctinfo);

	WARN_ON(!(ct && (ctinfo == IP_CT_NEW || ctinfo == IP_CT_RELATED ||
			 ctinfo == IP_CT_RELATED_REPLY)));

	/* Source address is 0.0.0.0 - locally generated packet that is
	 * probably not supposed to be masqueraded.
	 */
	if (ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u3.ip == 0)
		return NF_ACCEPT;

    /* 获取路由 */
	rt = skb_rtable(skb);
	/* 获取下一跳地址 */
	nh = rt_nexthop(rt, ip_hdr(skb)->daddr);
	/* 选择源 IP */
	newsrc = inet_select_addr(out, nh, RT_SCOPE_UNIVERSE);
	if (!newsrc) {
		pr_info("%s ate my IP address\n", out->name);
		return NF_DROP;
	}

	nat = nf_ct_nat_ext_add(ct);
	if (nat)
		nat->masq_index = out->ifindex;

	/* Transfer from original range. */
	memset(&newrange.min_addr, 0, sizeof(newrange.min_addr));
	memset(&newrange.max_addr, 0, sizeof(newrange.max_addr));
	newrange.flags       = range->flags | NF_NAT_RANGE_MAP_IPS;
	newrange.min_addr.ip = newsrc;
	newrange.max_addr.ip = newsrc;
	newrange.min_proto   = range->min_proto;
	newrange.max_proto   = range->max_proto;

	/* Hand modified range to generic setup. */
	return nf_nat_setup_info(ct, &newrange, NF_NAT_MANIP_SRC);
}

链路如下:

代码语言:shell
复制
masquerade_tg
  -> nf_nat_masquerade_ipv4(skb, xt_hooknum(par), &range,
                      xt_out(par))
    -> inet_select_addr(out, nh, RT_SCOPE_UNIVERSE)

重点来看 inet_select_addr 如何选择源 IP:

代码语言:c
复制
/* 入参:
    net_device: 出口设备
    dst: 下一跳地址
    scope: 0 (RT_SCOPE_UNIVERSE)
    enum rt_scope_t {
	   RT_SCOPE_UNIVERSE=0,
      /* User defined values  */
	   RT_SCOPE_SITE=200,
	   RT_SCOPE_LINK=253,
	   RT_SCOPE_HOST=254,
	   RT_SCOPE_NOWHERE=255
    };
    scope 详细见: http://www.embeddedlinux.org.cn/linux_net/0596002556/understandlni-CHP-30-SECT-2.html#understandlni-CHP-30-SECT-2.1
*/
__be32 inet_select_addr(const struct net_device *dev, __be32 dst, int scope)
{
	const struct in_ifaddr *ifa;
	__be32 addr = 0;
	unsigned char localnet_scope = RT_SCOPE_HOST;
	struct in_device *in_dev;
	struct net *net = dev_net(dev);
	int master_idx;

	rcu_read_lock();
	/* 获取出口设备上的所有地址 */
	in_dev = __in_dev_get_rcu(dev);
	if (!in_dev)
		goto no_in_dev;

	if (unlikely(IN_DEV_ROUTE_LOCALNET(in_dev)))
		localnet_scope = RT_SCOPE_LINK;

	in_dev_for_each_ifa_rcu(ifa, in_dev) {
	   /* 只关心设备上的主地址 */
		if (ifa->ifa_flags & IFA_F_SECONDARY)
			continue;
		/* 对比 scope,注意入参 scope 为 0 */
		if (min(ifa->ifa_scope, localnet_scope) > scope)
			continue;
		/* 如果 dst 不为空,判断 dst 和 设备上的地址是否在一个网段 */
		if (!dst || inet_ifa_match(dst, ifa)) {
			addr = ifa->ifa_local;
			break;
		}
		/* 找到地址 */
		if (!addr)
			addr = ifa->ifa_local;
	}

	if (addr)
		goto out_unlock;
/* 上面的逻辑如果没有找到合适的地址,这里会先尝试找出口设备的 master device,再遍历每一个设备*/
no_in_dev:
	master_idx = l3mdev_master_ifindex_rcu(dev);

	/* For VRFs, the VRF device takes the place of the loopback device,
	 * with addresses on it being preferred.  Note in such cases the
	 * loopback device will be among the devices that fail the master_idx
	 * equality check in the loop below.
	 */
	if (master_idx &&
	    (dev = dev_get_by_index_rcu(net, master_idx)) &&
	    (in_dev = __in_dev_get_rcu(dev))) {
		addr = in_dev_select_addr(in_dev, scope);
		if (addr)
			goto out_unlock;
	}

	/* Not loopback addresses on loopback should be preferred
	   in this case. It is important that lo is the first interface
	   in dev_base list.
	 */
	 /* 遍历所有设备 */
	for_each_netdev_rcu(net, dev) {
		if (l3mdev_master_ifindex_rcu(dev) != master_idx)
			continue;

		in_dev = __in_dev_get_rcu(dev);
		if (!in_dev)
			continue;

		addr = in_dev_select_addr(in_dev, scope);
		if (addr)
			goto out_unlock;
	}
out_unlock:
	rcu_read_unlock();
	return addr;
}

static __be32 in_dev_select_addr(const struct in_device *in_dev,
				 int scope)
{
	const struct in_ifaddr *ifa;

	in_dev_for_each_ifa_rcu(ifa, in_dev) {
	   /* 只关系主设备 */
		if (ifa->ifa_flags & IFA_F_SECONDARY)
			continue;
		if (ifa->ifa_scope != RT_SCOPE_LINK &&
		    ifa->ifa_scope <= scope)
			return ifa->ifa_local;
	}

	return 0;
}

MASQUERADE 选择源 IP 的逻辑我们已经看完了,现在来回答上面提的三个问题:

问题1: 在机器上访问 10.0.10.15 时,MASQUERADE 选择的源 IP 是哪个?(10.0.10.13? 10.0.10.14? 192.168.1.101?)

答: 10.0.10.13,访问 10.0.10.15 会匹配第二条路由,出口设备为 eth0,选择 eth0 上主地址 10.0.10.13

问题2: 在机器上访问 192.168.0.2 时,MASQUERADE 选择的源 IP 是哪个?(10.0.10.13? 10.0.10.14? 192.168.1.101?)

答: 10.0.10.13,访问 192.168.0.2 时会匹配 192.168.0.0/24 via 192.168.1.101 dev docker0 src 192.168.1.101 路由,出口设备为 docker0,但是因为 docker0 IP address scope 为 link,不满足 if (min(ifa->ifa_scope, localnet_scope) > scope) 条件,被过滤掉,从而会触发后面选择主设备的逻辑,选到 eth0 的主地址

问题3: 更改 docker0 IP 地址的 scope 后,此时在机器上访问 192.168.0.2 时,MASQUERADE 选择的源 IP 是哪个?(10.0.10.13? 10.0.10.14? 192.168.1.101?)

答: 192.168.1.101,访问 192.168.0.2 时会匹配 192.168.0.0/24 via 192.168.1.101 dev docker0 src 192.168.1.101 路由,出口设备为 docker0,但是因为 docker0 IP address scope 为 global,满足 if (min(ifa->ifa_scope, localnet_scope) > scope) 条件

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • SNAT target
  • MASQUERADE target
  • MASQUERADE 如何选择源 IP
  • 代码分析
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
http://www.vxiaotou.com