AI写作智能体 自主规划任务,支持联网查询和网页读取,多模态高效创作各类分析报告、商业计划、营销方案、教学内容等。 广告
[TOC] ### **案例:Kubernetes的NodePort** 我们以Kubernetes的NodePort来学习NAT。如下,主机B和主机C是一个K8S集群,在主机C上有一个Pod,IP为172.26.0.3,容器监听8080端口。我们同时在这个K8S集群中为这个Pod创建了一个Service,通过NodePort 30000来对外暴露服务。 ![[图片]](https://img.kancloud.cn/ca/5b/ca5bf211d83187e602d00962fc95898f_649x147.png) 接下来,我们从主机A上发起请求,访问192.168.0.2:30000,然后来看一下数据包在主机上的变化情况是什么样的。 ### **数据包的变化流程** 我们来看一下数据包在主机B上的变化流程。 * 第一个数据包 用一张图来表示第一个数据包的变化流程如下: ![](https://img.kancloud.cn/71/7b/717b6fd704b9258e5b760c2bbabab468_644x319.png) 第一个请求包到达入口(PREROUTING)处时,查找nat表PREROUTING链的规则,如果有匹配到这个数据包的DNAT规则,则更改数据包的dst,并更改数据包指向的连接的reply_tuple的src为`--to-destination`(因为回复包要匹配到reply_tuple,回复包的src是DNAT规则的`--to-destination`)。 第一个请求包到达出口处(POSTROUTING)时,查找nat表POSTROUTING链的规则,如果有匹配到这个数据包的SNAT规则,则更改数据包的src,并更改数据包指向的连接的reply_tuple的dst(因为回复包要匹配到reply_tuple,回复包的dst就是SNAT规则的`--to-source`) 第一个回复包到达入口(PREROUTING)处时,此时是无需查找nat表PREROUTING链中的iptables规则,直接根据ct的original_tuple,更改数据包的dst。 第一个回复包到达出口(POSTROUTING)处时,此时也无需查找nat表中POSTROUTING中的iptables规则,直接根据ct的original_tuple,更改数据包的src。 * 第N个数据包 用一张图来表示第N(N>=2)个数据包的变化流程如下: ![](https://img.kancloud.cn/9f/f5/9ff5a485e8ca65073ace1e598318a729_639x321.png) 第N个请求包在入口(PREROUTING)处,不再查找iptables规则,根据连接的reply_tuple,直接更改数据包的dst。 第N个请求包在出口(POSTROUTING)处,不再查找iptables规则,根据连接的reply_tuple,直接更改数据包的src。 第N个回复包在入口(PREROUTING)处,不再查找iptables规则,根据连接的original_tuple,直接更改数据包的dst。 第N个回复包在出口(POSTROUTING)处,不再查找iptables规则,根据连接的original_tuple,直接更改数据包的src。 ### **NAT函数** 在上面的流程中,我们发现,只有第一个请求包才会查找iptables规则,后面的请求包以及所有的回复包,都不会查找iptables规则。那么如何判断数据包是第一个请求包,或者是回复包呢? 根据文章[《数据包及连接的状态》](https://www.kancloud.cn/pshizhsysu/network/2190116),我们知道,第一个请求包的nfctinfo字段的值都是`IP_CT_NEW`,所以根据这个字段可以判断是否是第一个请求包。 我们来看一下NAT函数的逻辑。NAT模块在NF_INET_PRE_ROUTING与NF_INET_POST_ROUTING处注册的函数最终调用的都是`nf_nat_ipv4_fn()`函数,该函数的逻辑如下: 第一个请求包的nfctinfo为`IP_CT_NEW`,所以会调用`do_chains()`这个函数指针所指向的函数遍历iptables规则;第一个回复包的nfctinfo为`IP_CT_ESTABLISHED`,所以进入switch语句的default分支下,该分支其实什么都没做;所有回复包的nfctinfo为`IP_CT_ESTABLISHED_REPLY`,所以也会进入switch语句default分支下,什么都没做。 ``` unsigned int nf_nat_ipv4_fn(void *priv, struct sk_buff *skb, const struct nf_hook_state *state, unsigned int (*do_chain)(void *priv, struct sk_buff *skb, const struct nf_hook_state *state, struct nf_conn *ct)) { struct nf_conn *ct; enum ip_conntrack_info ctinfo; ... ct = nf_ct_get(skb, &ctinfo); ... switch (ctinfo) { // 判断数据包的ctinfo case IP_CT_RELATED: case IP_CT_RELATED_REPLY: ... case IP_CT_NEW: /* Seen it before? This can happen for loopback, retrans, * or local packets. */ if (!nf_nat_initialized(ct, maniptype)) { unsigned int ret; ret = do_chain(priv, skb, state, ct); // 遍历iptables规则,并更改ct,注意这里不更改数据包,在下面更改 if (ret != NF_ACCEPT) return ret; if (nf_nat_initialized(ct, HOOK2MANIP(state->hook))) break; ret = nf_nat_alloc_null_binding(ct, state->hook); if (ret != NF_ACCEPT) return ret; } else { ... } break; default: /* ESTABLISHED */ NF_CT_ASSERT(ctinfo == IP_CT_ESTABLISHED || ctinfo == IP_CT_ESTABLISHED_REPLY); ... } // 根据hook点以及ct,更改数据包的src或dst;hook点为PREROUTING,更改dst;hook点为POSTROUTING,更改src return nf_nat_packet(ct, ctinfo, state->hook, skb); ... } ``` ### **FAQ** **Q:如何判断数据包是否要查找iptables规则进行转换,还是根据ct进行转换?** A:根据数据包的nfctinfo字段判断。