传统的conntrack和NAT处理只对IP层和传输层头部进行转换处理,但是一些应用层协议,在协议数据报文中包含了地址信息。为了使得这些应用也能透明地完成NAT转换,NAT使用一种称作ALG的技术,它能对这些应用程序在通信时所包含的地址信息也进行相应的NAT转换。
例如:对于FTP协议的PORT/PASV命令,在数据包载荷中需要包含地址信息,并且数据包如果需要做NAT,那应用层数据部分的地址也需要做NAT转换。实现这种转换,需要在普通的conntrack条目基础上增加一个expect conntrack(期望连接)来记录这个连接上的额外信息。
本文将以tftp和ftp协议举例期望连接。
1. 以tftp举例期望连接
tftp是基于UDP的应用层协议,用于简单的文件传输。
1.1 实验拓扑
测试拓扑如下,在路由器R1上通过tftp请求PC1上的文件“ethreg.sh”,R1和PC1之间有一个路由器R2让R1位于局域网中,PC1位于公网中。在R1上输入以下命令:
tftp –gr ethreg.sh 192.168.10.100
在PC1上抓包结果如下:
第一个数据包为read request请求,数据包方向为R1->PC1(192.168.1.1:50173-> 192.168.10.100:69),其中69中tftp端口号,数据包传输过程经过了SNAT转换。
数据部分共经过两次传输完成,发送数据方向为PC1->R1(192.168.10.100:3873-> 192.168.1.1:50173),数据包在传输过程经过了DNAT转换。
可见传输数据时,第一个包是从Server端即PC1发出的,而PC1处在NAT外面,而通常情况下,这样的数据包是无法通过NAT达到局域网的,因为路由器R2上并没有记录这个连接做NAT的连接条目,NAT的作用之一就是隐藏局域网中的主机。
下面来看一下tftp协议如何依靠期望连接实现数据包的NAT转换的。
1.2 tftp协议的期望连接实现
在编译内核时打开CONFIG_NF_CONNTRACK_TFTP宏,使其以模块形式编译到内核,来支持tftp建立期望连接。该模块的init函数为nf_conntrack_tftp_init(),它初始化了一个conntrack helper方法,并将其注册到helper extension的链表中去(conntrack extensions见前面的博文)。
static struct nf_conntrack_helper tftp[MAX_PORTS][2]__read_mostly;
tftp conntrack模块的初始化函数:
static int __init nf_conntrack_tftp_init(void)
{
int i, j, ret;
char *tmpname;
if (ports_c == 0)
ports[ports_c++] = TFTP_PORT;
for (i = 0; i < ports_c; i++) {
memset(&tftp[i], 0, sizeof(tftp[i]));
tftp[i][0].tuple.src.l3num = AF_INET;
tftp[i][1].tuple.src.l3num = AF_INET6;
for (j = 0; j < 2; j++) {
tftp[i][j].tuple.dst.protonum = IPPROTO_UDP;
tftp[i][j].tuple.src.u.udp.port = htons(ports[i]);
tftp[i][j].expect_policy = &tftp_exp_policy;
tftp[i][j].me = THIS_MODULE;
tftp[i][j].help = tftp_help;
tmpname = &tftp_names[i][j][0];
if (ports[i] == TFTP_PORT)
sprintf(tmpname, "tftp");
else
sprintf(tmpname, "tftp-%u", i);
tftp[i][