Linux内核支持ftp,linux内核netfilter之ip_conntrack模块-FTP模式

FTP协议有两种工作方式:PORT方式和PASV方式,中文意思为主动式和被动式。

Port模式:ftp server:tcp 21

server:tcp 20 ------>client:dynamic

Pasv模式:ftp server:tcp 21

server:tcp dynamic

PORT(主动)方式的连接过程是:客户端向服务器的FTP端口(默认是21)发送连接请求,服务器接受连接,建立一条命令链路。当需要传送数据时,客户端在命令链路上用PORT命令告诉服务器:“我打开了XXXX端口,你过来连接我”。于是服务器从20端口向客户端的XXXX端口发送连接请求,建立一条数据链路来传送数据。

PASV(被动)方式的连接过程是:客户端向服务器的FTP端口(默认是21)发送连接请求,服务器接受连接,建立一条命令链路。当需要传送数据时,服务器在命令链路上用PASV命令告诉客户端:“我打开了XXXX端口,你过来连接我”。于是客户端向服务器的XXXX端口发送连接请求,建立一条数据链路来传送数据。

iptables模块关系:

1、modprobe ip_tables

当 iptables 对 filter、nat、mangle 任意一个表进行操作的时候,会自动加载 ip_tables

模块

另外,iptable_filter、iptable_nat、iptable_mangle 模块也会自动加载。

2、modprobe ip_conntrack

ip_conntrack 是状态检测机制,state 模块要用到

当 iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT

时,ip_conntrack 自动加载。

3、modprobe ip_conntrack_ftp

ip_conntrack_ftp 是本机做 FTP 时用的。

ip_nat_ftp 是通过本机的 FTP 需要用到的(若你的系统不需要路由转发,没必要用这个)

当 modprobe ip_nat_ftp 时,系统自动会加载 ip_conntrack_ftp 模块。

具体设置

加载模块(ports=21可以不写,如果用其他端口时要加上):

很多协议的控制信息在应用层数据中被包含,这些信息直接影响到了链路的建立,比如ftp协议就是这样,ftp分为port模式和pass模式,port模式中,起初client连接server的21端口,然后当需要传输data的时候,client发送一个控制包给server,包中包含client端开启的端口和自己的ip地址,server收到之后用自己的20端口去连接client控制包中建议的ip和端口,在这种情况下,如果client在nat后面使用私网地址,那么server将无法连接client,因此nat网关必须要处理这种情况,处理方式就是修改client发给server的控制包(如果加密将不可能修改,还好ftp是不加密的);在pass模式下,client连接server的21端口后,如果要传输data,client还要连接server的另一个随机端口,该端口是由server发送的控制包传给client的,如果client或者server端所在的防火墙禁止了任意非熟知端口,那么数据将被防火墙拦截;不管是port模式还是pass模式,防火墙都要处理“第二个”数据连接通路的放行问题,在linux中是通过RELATED状态来放行的,正如前文所述,只需配置一条--state

RELATED -j

ACCEPT规则即可,但是具体这个规则如何实现,linux的连接追踪模块又是怎样处理ftp的nat问题的,本文详述之。

首先从ip_conntrack的HOOK函数说起:

unsigned int ip_conntrack_in(...)

{

...

proto =

ip_ct_find_proto((*pskb)->nh.iph->protocol);

//从数据包中取出协议号

...//resolve_normal_ct会试图在已建立的连接中寻找刚进入的包属于的连接,如果找不到则新建立一个状态为NEW的连接,同时还要初始化该连接相关的数据,比如helper

ct =

resolve_normal_ct(*pskb,

proto,&set_reply,hooknum,&ctinfo);//此中调用的init_conntrack函数是一个做了很多事的函数

...

if (ret !=

NF_DROP &&

ct->helper) { //如果有helper则调用其help函数

ret = ct->helper->help(*pskb, ct,

ctinfo);

...

}

...

}

init_conntrack中有如下逻辑:

...//从链表中查找该连接,如果找到说明这是一个“预测”的连接

expected = LIST_FIND(&ip_conntrack_expect_list,

expect_cmp,

struct ip_conntrack_expect *, tuple);

...

if (expected) {

__set_bit(IPS_EXPECTED_BIT,

&conntrack->status);

//预测的连接到了,设置一个标志,在resolve_normal_ct得到已有连接的情况下会判断如果有了这个标志,则设置IP_CT_RELATED状态,该状态可用于filter的判断

expected->sibling = conntrack;

//预测的连接已经到来并且初始化了。expected->sibling在预测的时候是NULL,因为那时仅仅是预测,连接还没有真的到来,后面可以看到,ip_conntrak预测之后,ip_nat会使用预测结果,然后调用helper的help修改应用层的和连接相关的控制数据,比如ip地址和端口信息,在遍历一个已有连接的所有预测到的连接从而决定是否调用ip_nat的helper时,如果一个预测即一个ip_conntrack_expect的sibling字段非NULL,ip_nat将跳过此预测结果,因为它已经是真实的连接了,说明已经在它还是预测的连接的时候就已经被help过了。

...

}

...

每一个ip_conntrack都可以拥有多个helper,用于帮助处理连接相关的信息,比如ftp协议穿越防火墙就需要处理nat和副连接(data连接)问题,因此就有必要用一个helper模块来处理这一类情况,处理ftp

nat的helper和处理副连接的helper其实不是一类helper,前者是ip_nat_ftp结构体,后者是ip_conntrack_ftp结构体,虽然不同,但是它们的处理逻辑和注册逻辑都是一样的,因此到后面说ftp

nat的时候再统一说明。下面是ip_conntrack_ftp注册的help函数的实现逻辑

static int help(...)

{

...//操作skb,取出我们需要的一切信息

skb_copy_bits(skb, dataoff, ftp_buffer, skb->len -

dataoff);

...

array[0] =

(ntohl(ct->tuplehash[dir].tuple.src.ip)

>> 24) & 0xFF;

array[1] =

(ntohl(ct->tuplehash[dir].tuple.src.ip)

>> 16) & 0xFF;

array[2] =

(ntohl(ct->tuplehash[dir].tuple.src.ip)

>> 8) & 0xFF;

array[3] =

ntohl(ct->tuplehash[dir].tuple.src.ip)

& 0xFF;

//以上的这个array就是server需要连接的ip地址

for (i = 0;

i < ARRAY_SIZE(search); i++) {

if (search[i].dir != dir) continue;

found =

find_pattern(...);//在ftp_buffer中寻找search字符,如果找到了,则说明本次数据包需要help,其中有个参数是个数组,数组的每一个元素都是一个匹配键,此谓search,是一个ftp_search结构体类型的数组

if (found) break;

}

...//如果找不到则返回,说明本次到来的数据不需要help

exp =

ip_conntrack_expect_alloc();

...

//初始化一个ip_conntrack_expect,可以用于描述一个将要建立的连接

exp->expectfn = NULL;

ip_conntrack_expect_related(exp, ct);

//准备添加一个RELATED的连接,如果用户在iptables规则中配置RELATED连接可以通过,那么ftp的port模式数据连接就可以畅行无阻了。iptables的RELATED连接就是在这里被“预料”到的,然后加入进已有的连接。

ret =

NF_ACCEPT;

out:

UNLOCK_BH(&ip_ftp_lock);

return

ret;

}

最终会在ip_conntrack_expect_insert函数中将“预料”到的连接加入与此“预料”的连接相关联的已有连接的链表中,同时还将这个预料到的连接加入一个系统全局的链表中,并且如果已有的连接需要限制“预料”连接的建立连接时间,则需要启动一个定时器,定时器超时连接还不到的话,就会删除该预料的连接。这个related连接会被netfilter的state模块使用,比如你使用--state

NEW/ESTABLISHED/...的话,在state模块中的match回调函数中,系统会取出该数据包属于的连接,然后取出该连接的state,将之与参数的state比较,然后返回进入target抉择。

以上是数据在ip_conntrack模块中的流程,出了ip_conntrack就该进入ip_nat了,还是从其HOOK说起:

static unsigned int ip_nat_fn(...)

{

...

ct =

ip_conntrack_get(*pskb, &ctinfo);

//得到连接,如果没有得到则返回NULL

... //如果没有得到既有连接则返回ACCEPT(注意有ICMP重定向的特殊情况),由后续的链来抉择,不管怎样nat总在conntrack之后起作用,因此只要有连接,conntrack就会将之加入hash

switch

(ctinfo) {

...

case

IP_CT_NEW: //如果是一个连接的第一个包,那么就要初始化一系列结构体,包括两个方向的nat转换表,ftp等等需要help的协议的相关结构体等等

info = &ct->nat.info;

WRITE_LOCK(&ip_nat_lock);

if (!(info->initialized & (1

<< maniptype))) {

...

ret = ip_nat_rule_find(pskb, hooknum, in, out, ct, info);

...

return

do_bindings(ct, ctinfo, info, hooknum, pskb);

}

unsigned int do_bindings(...)

{

...

int proto =

(*pskb)->nh.iph->protocol;

...//实施地址/端口转换,省略。就是在两个方向的转换表中根据方向和地址/端口信息来修改数据包的协议头

helper =

info->helper; //info在ip_nat_setup_info也就是初始化连接的时候就会被建立,这里只是取出来

if (helper)

{

...//一个主连接可以有多个与之RELATED的副连接,因此下面就遍历这些副连接

list_for_each_prev(cur_item,

&ct->sibling_list) {

...//如果已经是established的连接了,则说明下面将要做的工作已经作过了,就不再做了。

if (exp_for_packet(exp, *pskb)) { //包合理则调用help函数,在help函数中处理特殊的nat转换,比如ftp的port模式相关的nat转换

ret = helper->help(ct, exp, info, ctinfo, hooknum,

pskb);

...

}

对于ip_nat_ftp帮助模块而言,其help函数的执行逻辑如下:

static unsigned int help(...)

{

...

ct_ftp_info

= &exp->help.exp_ftp_info;

...

ftp_data_fixup(ct_ftp_info, ct, pskb, ctinfo, exp);

...

}

最终ftp_data_fixup调用了mangle[ct_ftp_info->ftptype](...)函数,显然最后的函数完成了对数据包的修改,对数据包进行修改就是为了ftp服务器可以成功连接到客户端。由于客户端很多时候在具有nat功能的防火墙后,并且都是用私网地址,而在ftp的port模式下,如果客户端将一个私有地址建议给了ftp服务器用于连接,服务器是连接不到的,这个建议的ip地址在ftp的数据包中,因此必须修改数据包,将建议的地址和端口修改为nat后的地址和端口,同时再铺设一条nat,用于服务器连接客户端时将请求真正转到内网的客户端。ip_nat_mangle_tcp_packet是ip_nat_helper.c中的一个很重要的函数,就是它完成了对应用层数据包的修改。

ip_conntrack和ip_nat处理ftp总的过程就是,ip_conntrack模块得到了连接的信息,然后根据连接信息可以得到一个helper,需要说明,一个连接完全可以没有helper,而且大多数的都没有helper,是否需要helper是根据连接的类型决定的,一般触及应用层控制数据的修改时才会使用helper,比如ftp的控制命令是在应用层数据中被传输的,连接的类型是可以从数据包以及协议头中得到的,因此ip_conntrack模块需要数据包不能分段,也就是说需要完整的ip数据包。得到helper之后开始调用其help函数,然后判断当前数据包是否需要help,比如判断是否是ftp的特殊命令,该命令可以建立一条新的连接,如果是这样的话,那么help函数则“预测”到一条即将建立的连接并将之和当前连接关联,然后ip_conntrack基本就没有什么做的了,数据包继续在netfilter中流动,进入nat,同样的,nat也如ip_conntrack判断是否需要help,如果是则调用helper的help函数,判断是否需要help的依据一般就是是否在ip_conntrack模块中“预测”到了即将建立的连接,如果预测到了,那么就调用nat的helper的help函数,并且将预测到的连接参数传入,在ip_nat_ftp的help函数中根据预测连接的信息对应用层控制数据进行修改。

两类helper的注册都是在模块初始化的时候进行的,而helper与连接或者nat的绑定则是在连接初始化的时候进行的。ip_nat_fn是nat的HOOK,其中对于IP_CT_NEW包来讲需要调用call_expect,而后者最终调用下面的函数实现ftp相关的ip_nat_helper结构体的指定,该结构体在模块初始化时被注册:

unsigned int ip_nat_setup_info(...)

{

...

info->helper = LIST_FIND(&helpers,

helper_cmp, struct ip_nat_helper *, &reply);

//寻找ip_nat_ftp的helper,在ip_nat_ftp的init中,会调用ip_conntrack_helper_register将ftp相关的信息注册进内核,这些信息包含在ip_nat_helper结构体中,其中有很多静态数据是用于匹配helper的,比如新建一个连接,当数据越过conntrack而进入nat时会调用ip_nat_setup_info,在该函数中,如上述调用LIST_FIND,其实就是使用当前的addr,port等信息和注册的helper逐个进行比较,一旦有命中的则将此helper取出留作后用,ip_nat_helper中最重要的就是help函数了。

...

}

ip_nat_ftp模块的初始化函数如下:

static int __init init(void)

{

...

for (i = 0;

(i < MAX_PORTS) &&

ports[i]; i++) {

ftp[i].tuple.src.u.tcp.port = htons(ports[i]);

ftp[i].tuple.dst.protonum = IPPROTO_TCP;

ftp[i].mask.src.u.tcp.port = 0xFFFF;

ftp[i].mask.dst.protonum = 0xFFFF;

ftp[i].max_expected = 1;

ftp[i].timeout = 0;

ftp[i].flags = IP_CT_HELPER_F_REUSE_EXPECT;

ftp[i].me = ip_conntrack_ftp;

ftp[i].help = help;

...

ret = ip_conntrack_helper_register(&ftp[i]);

...

}

return

0;

}

类似的ip_conntrack的helper也是在模块初始化时注册,在连接初始化时被指定特定的连接的,道理和nat是一样的。

总之,helper模块一般是对需要在应用层数据中传输控制数据的协议进行帮助的,因为OS实现的协议栈并不包含应用层,但是有的时候必须对应用层控制数据进行修改,这时就不得不需要一个额外的帮助模块了,注意,一般helper修改的都是控制数据,而不是业务数据,所谓控制数据就是和业务无关的,仅仅影响到连接本身的数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值