Linux内核中的NAT处理

概念

        NAT(Network Address Translation,网络地址转换),是一种网络通信协议,用于将私有网络中的IP地址转换成公网IP地址,以访问公网资源,实现内网与外网之间的通信;NAT同时也是一种网络安全处理策略。

背景

       NAT根据地址转换操作的不同可细分为多种类型,如SNAT、DNAT、全锥型NAT等等,Linux内核中是如何进行NAT处理的,这里以IPv4 NAT处理为例进行学习讨论。

NAT处理

        之前在学习Linux内核连接跟踪时提到了Netfilter框架;Netfilter主要功能包括:

        包过滤:Netfilter可以根据预定义的规则过滤网络数据包,允许或拒绝它们的传递。

        NAT:允许修改数据包的源地址、目的地址和端口信息等。

        连接跟踪:追踪网络连接状态,识别相关数据包,并根据连接状态应用相应的规则。

        数据包修改:可以对数据报的各个字段进行修改。

       由此可见,Linux 内核NAT处理依赖于Netfilter框架;NAT处理即在内核协议栈特定HOOK点(钩子)挂载NAT处理函数,以ipv4为例,和NAT有关的HOOK操作定义如下:

static const struct nf_hook_ops nf_nat_ipv4_ops[] = {
	{
		.hook		= ipt_do_table,
		.pf		= NFPROTO_IPV4,
		.hooknum	= NF_INET_PRE_ROUTING,
		.priority	= NF_IP_PRI_NAT_DST,
	},
	{
		.hook		= ipt_do_table,
		.pf		= NFPROTO_IPV4,
		.hooknum	= NF_INET_POST_ROUTING,
		.priority	= NF_IP_PRI_NAT_SRC,
	},
	{
		.hook		= ipt_do_table,
		.pf		= NFPROTO_IPV4,
		.hooknum	= NF_INET_LOCAL_OUT,
		.priority	= NF_IP_PRI_NAT_DST,
	},
	{
		.hook		= ipt_do_table,
		.pf		= NFPROTO_IPV4,
		.hooknum	= NF_INET_LOCAL_IN,
		.priority	= NF_IP_PRI_NAT_SRC,
	},
};

        从上述代码可以看出,依据HOOK点的位置,NAT处理分为对转发报文的处理和本机报文的处理;从HOOK点处理函数的优先级,NAT处理分为DNAT处理和SNAT处理,接下来分别讨论。

        在介绍NAT处理前,首先回顾下报文在内核协议处理的过程及几个关键HOOK点位置,如下图所示:

转发报文

        和转发报文或者说非本机报文NAT处理相关的HOOK操作定义为:

    {
		.hook		= ipt_do_table,
		.pf		= NFPROTO_IPV4,
		.hooknum	= NF_INET_PRE_ROUTING,
		.priority	= NF_IP_PRI_NAT_DST,
	},
	{
		.hook		= ipt_do_table,
		.pf		= NFPROTO_IPV4,
		.hooknum	= NF_INET_POST_ROUTING,
		.priority	= NF_IP_PRI_NAT_SRC,
	},

        报文进入内核协议栈,到达NF_INET_PRE_ROUTING,路由决策前对报文进行DNAT转换,DNAT 处理后路由决策依据转换后的目的IP查找并确定报文的下一跳出口。

         在NF_INET_POST_ROUTING报文外发前,对报文进行SNAT处理,可以将内网IP统一转换为一个公网IP进行外部资源访问,同时也是一种对内网IP的保护。

本机报文

        本机报文分为到本机的报文和本机外出的报文,本机报文也可以进行SNAT或DNAT处理,具体相关定义如下:

   {
		.hook		= ipt_do_table,
		.pf		= NFPROTO_IPV4,
		.hooknum	= NF_INET_LOCAL_OUT,
		.priority	= NF_IP_PRI_NAT_DST,
	},
	{
		.hook		= ipt_do_table,
		.pf		= NFPROTO_IPV4,
		.hooknum	= NF_INET_LOCAL_IN,
		.priority	= NF_IP_PRI_NAT_SRC,
	},

        当本机报文上送到上层应用程序前,在HOOK点NF_INET_LOCAL_IN处进行SNAT转换,该情况发生在通过反向代理访问外网资源的情况,客户端发送请求报文目的IP为代理服务器的IP,请求报文到达代理服务器,将报文目的IP转换为对真正服务IP地址的访问,服务器回复报文,SIP为服务器IP,目的IP为客户端IP,报文到达代理服务器(防火墙可充当代理服务器),将报文源IP转为代理服务器IP,从而保证正反向的一致性;

        本机外出报文在HOOK点NF_INET_LOCAL_OUT处作DNAT转换,需要注意的时,本机外出报文到达该HOOK点时还未确定邻居信息(出接口),如果此时防火墙充当代理服务器的报文,DNAT处理的目的是,将对代理服务器的访问转换位到真正服务器的访问。

NAT处理

        在理解NAT处理逻辑前,首先须对iptables规则四表五链的组织有个认知;

        四表:

        raw表:配置连接跟踪的规则,影响连接跟踪的状态。

        mangle表:修改数据包内容,提供一些额外的修改项,如Qos,TTL等

        nat表:网络地址转换,允许修改报文源、目的IP地址,源、目的端口信息等

        filter表:用于过滤网络数据包,主要用于控制网络数据报文的通过与阻止。

         五链:

        五链与Netfilter框架中,报文处理的HOOK点相对应,具体如下:

        PREROUTING链:数据包路由处理之前经过该链

        INPUT链:目的IP为本机的报文应用此链中的规则

        FORWARD链:需要转发的报文应用此链中的规则

        OUTPUT链:本地输出报文应用此链中的规则

        POSTROUTING链:经过路由后的报文应用此链中的规则

        不同的表有优先级处理顺序,不同的链根据处理位置不同也有先后,同一表中可以存在多条链,iptables 四表五链总体组织如下:

        一条nat iptable规则配置如下:

        了解iptables 规则在内核的组织方式,是理解nat处理以及其他规则处理流程的前提,否则根本看不明白,一头雾水,iptables规则的维护涉及到几个数据结构,下面分别讨论:

数据结构

表结构xt_table
struct xt_table {
	struct list_head list; 
	/* 内核不仅有iptables,还有ip6tables, arptables, ebtables,对应不同的协议,每种协议都有多个表(raw,magle...),
		一个协议的多个表通过list维护 */

	/* What hooks you will enter on */
	unsigned int valid_hooks; /* 该表使用的hook点,也对应了该表中有几条链 */

	/* Man behind the curtain... */
	struct xt_table_info *private; /* 记录表本身的信息,size, 规则数,每条链的入口地址等 */

	/* hook ops that register the table with the netfilter core */
	struct nf_hook_ops *ops; 

	/* Set this to THIS_MODULE if you are a module, otherwise NULL */
	struct module *me; 

	u_int8_t af;		/* address/protocol family */
	int priority;		/* hook order */ /* 优先级顺序 */

	/* A unique name... */
	const char name[XT_TABLE_MAXNAMELEN]; /*表名, 如:nat, filter等 */
};
规则ipt_entry
struct ipt_entry {
	struct ipt_ip ip; /* 记录ip头相关信息 */

	/* Mark with fields that we care about. */
	unsigned int nfcache;

	/* Size of ipt_entry + matches */
	__u16 target_offset; /* target 相对于ipt_entry起始地址的偏移  */
	/* Size of ipt_entry + matches + target */
	__u16 next_offset; /* 下一条规则相对于当前规则起始位置的偏移 */

	/* Back pointer */
	unsigned int comefrom;

	/* Packet and byte counters. */
	struct xt_counters counters; /* 经过该规则报文个数或字节的统计 */

	/* The matches (if any), then the target. */
	unsigned char elems[]; /* 存储match信息,target信息,match在前,target在后,数组长度0表示长度可变 */
};
匹配xt_entry_match
struct xt_entry_match {
	union {
		struct {
			__u16 match_size; /* match 信息大小 */

			/* Used by userspace */
			char name[XT_EXTENSION_MAXNAMELEN];
			__u8 revision;
		} user;
		struct {
			__u16 match_size; /* match 信息大小 */

			/* Used inside the kernel */
			struct xt_match *match;
		} kernel;

		/* Total length */
		__u16 match_size;  /* 整个match结构体大小 */
	} u;

	unsigned char data[]; /* 存储match的具体内容 */
};
目标动作target
struct xt_entry_target {
	union {
		struct {
			__u16 target_size; /* target 的大小 */

			/* Used by userspace */
			char name[XT_EXTENSION_MAXNAMELEN];
			__u8 revision;
		} user;
		struct {
			__u16 target_size; /* target 大小 */

			/* Used inside the kernel */
			struct xt_target *target;
		} kernel;

		/* Total length */
		__u16 target_size;/* target总大小 */
	} u;

	unsigned char data[0]; /* target具体信息 */
};

处理流程

        从nat操作的定义看,NAT的核心处理函数为ipt_do_table;具体定义如下:

unsigned int
ipt_do_table(void *priv,
	     struct sk_buff *skb,
	     const struct nf_hook_state *state)
{
	const struct xt_table *table = priv; /* 确认了某个表 */
	unsigned int hook = state->hook; /* 获取hook点 */
	static const char nulldevname[IFNAMSIZ] __attribute__((aligned(sizeof(long))));
	const struct iphdr *ip;
	/* Initializing verdict to NF_DROP keeps gcc happy. */
	unsigned int verdict = NF_DROP;
	const char *indev, *outdev;
	const void *table_base;
	struct ipt_entry *e, **jumpstack;
	unsigned int stackidx, cpu;
	const struct xt_table_info *private;
	struct xt_action_param acpar;
	unsigned int addend;

	/* Initialization */
	stackidx = 0;
	ip = ip_hdr(skb); /* 获取的ip头 */
	indev = state->in ? state->in->name : nulldevname; /* 获取报文入接口名 */
	outdev = state->out ? state->out->name : nulldevname; /* 获取报文出接口名 */
	/* We handle fragments by dealing with the first fragment as
	 * if it was a normal packet.  All other fragments are treated
	 * normally, except that they will NEVER match rules that ask
	 * things we don't know, ie. tcp syn flag or ports).  If the
	 * rule is also a fragment-specific rule, non-fragments won't
	 * match it. */
	acpar.fragoff = ntohs(ip->frag_off) & IP_OFFSET;
	acpar.thoff   = ip_hdrlen(skb);
	acpar.hotdrop = false;
	acpar.state   = state;

	WARN_ON(!(table->valid_hooks & (1 << hook)));
	local_bh_disable();
	addend = xt_write_recseq_begin();
	private = READ_ONCE(table->private); /* Address dependency. */
	cpu        = smp_processor_id(); /* 当前cpu id */
	table_base = private->entries; /* 表的起始地址,表第一条规则的起始地址 */
	jumpstack  = (struct ipt_entry **)private->jumpstack[cpu];

	/* Switch to alternate jumpstack if we're being invoked via TEE.
	 * TEE issues XT_CONTINUE verdict on original skb so we must not
	 * clobber the jumpstack.
	 *
	 * For recursion via REJECT or SYNPROXY the stack will be clobbered
	 * but it is no problem since absolute verdict is issued by these.
	 */
	if (static_key_false(&xt_tee_enabled))
		jumpstack += private->stacksize * __this_cpu_read(nf_skb_duplicated);

	e = get_entry(table_base, private->hook_entry[hook]); /* 获取某条(hook确定)链里的第一条规则指针 */

	do {
		const struct xt_entry_target *t;
		const struct xt_entry_match *ematch;
		struct xt_counters *counter;

		WARN_ON(!e);
		if (!ip_packet_match(ip, indev, outdev,
		    &e->ip, acpar.fragoff)) { /* e->ip 记录表了匹配信息,  用报文IP 信息去匹配 */
 no_match:
			e = ipt_next_entry(e); /* 获取该链下一条规则 */
			continue;
		}

		xt_ematch_foreach(ematch, e) {
			acpar.match     = ematch->u.kernel.match;
			acpar.matchinfo = ematch->data;
			if (!acpar.match->match(skb, &acpar))
				goto no_match;
		}

		counter = xt_get_this_cpu_counter(&e->counters); /* 更新规则的统计信息 */
		ADD_COUNTER(*counter, skb->len, 1);

		t = ipt_get_target_c(e); /* 获取规则链的target信息  */
		WARN_ON(!t->u.kernel.target);

#if IS_ENABLED(CONFIG_NETFILTER_XT_TARGET_TRACE)
		/* The packet is traced: log it */
		if (unlikely(skb->nf_trace))
			trace_packet(state->net, skb, hook, state->in,
				     state->out, table->name, private, e);
#endif
		/* Standard target? */
		if (!t->u.kernel.target->target) {
			int v;

			v = ((struct xt_standard_target *)t)->verdict;
			if (v < 0) {
				/* Pop from stack? */
				if (v != XT_RETURN) {
					verdict = (unsigned int)(-v) - 1;
					break;
				}
				if (stackidx == 0) {
					e = get_entry(table_base,
					    private->underflow[hook]);
				} else {
					e = jumpstack[--stackidx];
					e = ipt_next_entry(e);
				}
				continue;
			}
			if (table_base + v != ipt_next_entry(e) &&
			    !(e->ip.flags & IPT_F_GOTO)) {
				if (unlikely(stackidx >= private->stacksize)) {
					verdict = NF_DROP;
					break;
				}
				jumpstack[stackidx++] = e;
			}

			e = get_entry(table_base, v);
			continue;
		}

		acpar.target   = t->u.kernel.target;
		acpar.targinfo = t->data;

		verdict = t->u.kernel.target->target(skb, &acpar); /* 依据具体action 处理报文 */
		if (verdict == XT_CONTINUE) {
			/* Target might have changed stuff. */
			ip = ip_hdr(skb);
			e = ipt_next_entry(e);
		} else {
			/* Verdict */
			break;
		}
	} while (!acpar.hotdrop);

	xt_write_recseq_end(addend);
	local_bh_enable();

	if (acpar.hotdrop)
		return NF_DROP;
	else return verdict;
}

        从上述处理过程来看,NAT的处理过程即找到iptables 具体table表的(nat表的)起始地址,然后根据hook点找到对应链的起始地址,之后遍历链表中所有的规则,逐一匹配,一旦匹配依据target处理函数处理并给出处理结果(NF_DROP/NF_ACCEPT/NF_STOLEN..)。

  • 28
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Linux内核的网络协议栈是指在操作系统内核实现的一系列网络协议和功能。它负责处理网络数据包的收发、路由选择、协议解析等一系列操作,以实现网络通信和数据传输。 Linux内核的网络协议栈包括以下几个层次: 1. 网络接口层(Network Interface Layer):负责处理物理网络接口的驱动程序和硬件设备的通信。它提供了对底层网络设备的抽象,如以太网、Wi-Fi、蓝牙等。 2. 网络层(Network Layer):负责处理IP协议相关的操作,包括IP地址分配、路由选择和IP数据包的转发等。其主要的协议有IPv4和IPv6。 3. 传输层(Transport Layer):负责处理端到端的数据传输,主要通过TCP(传输控制协议)和UDP(用户数据报协议)来实现。TCP提供可靠的、面向连接的数据传输,而UDP提供无连接的、不可靠的数据传输。 4. 应用层(Application Layer):负责处理特定应用程序的数据传输和协议,如HTTP、FTP、DNS等。应用层协议依赖于传输层和网络层的支持,通过这些协议实现应用程序之间的通信。 此外,Linux内核还提供了一些额外的功能,如网络地址转换(NAT)、防火墙(iptables)、网络隧道(Tunneling)等,以满足网络通信和安全的需求。 总之,Linux内核的网络协议栈是一个复杂而庞大的系统,通过不同层次的协议和功能实现了网络通信的各个方面。它为应用程序提供了丰富的网络功能,使得Linux成为一个强大的网络操作系统。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值