[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上的变化流程。
* 第一个数据包
用一张图来表示第一个数据包的变化流程如下:

第一个请求包到达入口(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)个数据包的变化流程如下:

第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字段判断。
- 应用层
- HTTP
- Cookie
- Session
- HTTP报文格式
- HTTP的Header字段
- HTTPS
- 简介
- 原理
- RSA加密与解密
- 证书签名与验证
- TLS双向认证
- openssl命令汇总
- DNS
- DNS的记录类型
- DNS的报文格式
- FAQ
- 传输层
- TCP
- CloseWait
- 网络层
- IPv6
- 链路层
- 链接层基础知识
- VLAN
- FAQ
- Linux网络收发包
- 网卡收包
- 网卡发包
- 收发包FAQ
- LVS
- 安装-DR模式
- 基本原理
- Ipvsadm命令
- Netfilter
- Netfilter简介
- 注册钩子函数
- Netfilter中数据包流向
- Iptables的数据结构
- 连接跟踪
- 初识连接跟踪
- 连接跟踪详解
- 连接跟踪数据结构
- 数据包与连接的状态
- NAT
- IPVS
- KubeProxy的IPVS模式
- Linux虚拟网络设备
- 虚拟网络设备简介
- Tap
- VethPair
- Vlan
- Vxlan
- Flannel的VXLAN原理
- Openstack的VXLAN原理
- VXLAN总结
- Bridge
- 给容器设置主机网段IP
- Macvlan
- Ipvlan
- IPIP
- IPIP使用介绍
- IPIP源码分析
- Limdiag网络
- 详细设计
- kubeovn
- IP命令
- Calico
- Calico常见问题
- ARP无响应
- 其他
