您的位置:首页 > 理论基础 > 计算机网络

Linux下如何实现网络状态检测

2014-09-05 15:46 561 查看
. iptables规则中的state匹配
在2.4/2.6内核的Linux中的防火墙代码netfilter中实现了状态检测(stateful inspection)检测技术,在命令行接口的iptables命令是通过匹配“-m state”来实现,“-m state”匹配中定义了四种状态:
NEW,表示新连接;
ESTABLISHED,表示已经建立的连接;
RELATED,表示相关的子连接;
INVALID,表示非法状态。
所以在制定iptables规则时,只需要定义单向的规则,再加上如下两条规则,就可以保证连接回应包能正确通过防火墙而不必加反向规则:

iptables -I FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT

iptables -I FORWARD -m state --state INVALID -j DROP

(如果是保护本机,将FORWARD改为INPUT)
2. 内核中的state匹配的实现
(以下的内核代码基于2.4.26内核)

和匹配state对应的内核部分实现是ipt_state.c(net/ipv4/netfilter),匹配部分的代码非常简单,就是根据函数ip_conntrack_get()的返回值来判断数据包的状态类型:

if (!ip_conntrack_get((struct sk_buff *)skb, &ctinfo))

statebit = IPT_STATE_INVALID;

else

statebit = IPT_STATE_BIT(ctinfo);

3. skb与ip_conntrack的关联
再跟踪到ip_conntrack_get()函数 (net/ipv4/netfilter_ipv4/ip_conntrack_core.c),该函数有两个参数,一个是指向sk_buff数据包的指 针skb,另一个是枚举类型enum ip_conntrack_info的指针,
实际上是一个返回参数,将返回数据包的状态类型;该函数的返回值是连接结构struct ip_conntrack的指针,表示该包属于哪个连接,如果连接不存在,返回空,该包也将被认为是非法包:
struct ip_conntrack *

ip_conntrack_get(struct sk_buff *skb, enum ip_conntrack_info *ctinfo)

{

if (skb->nfct)

return __ip_conntrack_get(skb->nfct, ctinfo);

return NULL;

}
ip_conntrack_get()函数本身也很简单,实际上是__ip_conntrack_get()函数 (net/ipv4/netfilter_ipv4/ip_conntrack_core.c)的包裹函数,判断skb的nfct是否非空,非空则将其和 枚举指针ctinfo传给__ip_conntrack_get()函数,nfct是sk_buff中用于描述netfilter
conntrack信息的struct nf_ct_info指针,在内核配置了CONFIG_NETFILTER后有效。struct nf_ct_info结构在include/linux/sk_buff.h中定义:

struct nf_conntrack {

atomic_t use;

void (*destroy)(struct nf_conntrack *);

};
struct nf_ct_info {

struct nf_conntrack *master;

};

在__ip_conntrack_get()函数中,通过nfct->master获取连接指针ct,而nfct相对于ct成员infos的偏移就是连接状态:
static inline struct ip_conntrack *

__ip_conntrack_get(struct nf_ct_info *nfct, enum ip_conntrack_info *ctinfo)

{

struct ip_conntrack *ct

= (struct ip_conntrack *)nfct->master;
/* ctinfo is the index of the nfct inside the conntrack */

*ctinfo = nfct - ct->infos;

IP_NF_ASSERT(*ctinfo >= 0 && *ctinfo < IP_CT_NUMBER);

return ct;

}
由此可见,netfilter的状态检测过程很简洁,如果skb包中的nfct指针设置了的话,可以很快地确定该skb包属于哪个连接,如果不属于任何连接则是非法包。现在的焦点是skb中的nfct值是如何设置的?

4. netfilter如何实现中skb与ip_conntrack连接的关联
nfct值是在resolve_normal_ct()函数(net/ipv4/netfilter/ip_conntrack_core.c)中定义的,该函
数被ip_conntrack_in()函数(net/ipv4/netfilter/ip_conntrack_core.c)调用,在 ip_conntrack_local()函数(net/ipv4/netfilter/ip_conntrack_standalone.c)中调用了
ip_conntrack_in()函数,而这两个函数分别是PREROUTING链和OUTPUT链的起始函数,由下面两个结构
定义(net/ipv4/netfilter/ip_conntrack_standalone.c):
static struct nf_hook_ops ip_conntrack_in_ops

= { { NULL, NULL }, ip_conntrack_in, PF_INET, NF_IP_PRE_ROUTING,

NF_IP_PRI_CONNTRACK };
static struct nf_hook_ops ip_conntrack_local_out_ops

= { { NULL, NULL }, ip_conntrack_local, PF_INET, NF_IP_LOCAL_OUT,

NF_IP_PRI_CONNTRACK };

这说明netfilter是在连接的第一个包时就建立连接了,这个连接可能是由外部发起的,也可能是由本机发起的,后续包查找连接HASH表找到相应的连接,然后赋值给skb结构中的nfct,这就是resolve_normal_ct()函数的工作:
/* On success, returns conntrack ptr, sets skb->nfct and ctinfo */

static inline struct ip_conntrack *

resolve_normal_ct(struct sk_buff *skb, // 数据包

struct ip_conntrack_protocol *proto, // 连接协议跟踪,该结构对应协议特殊处理过程,如TCP的状态转换等

int *set_reply, // 返回值,表示该包是否是响应方的包

unsigned int hooknum,

enum ip_conntrack_info *ctinfo)

{

struct ip_conntrack_tuple tuple;

struct ip_conntrack_tuple_hash *h;
IP_NF_ASSERT((skb->nh.iph->frag_off & htons(IP_OFFSET)) == 0);
// 确定该包对应的tuple,对TCP/UDP协议来说就是地址协议端口的5元组

if (!get_tuple(skb->nh.iph, skb->len, &tuple, proto))

return NULL;
/* look for tuple match */

// 根据tuple值查找连接HASH

h = ip_conntrack_find_get(&tuple, NULL);

if (!h) {

// 在当前连接表中没找到,说明是新连接的包,初始化新连接

h = init_conntrack(&tuple, proto, skb);

if (!h)

return NULL;

// 该包非法返回NULL,其他错误会返回错误号的负值

if (IS_ERR(h))

return (void *)h;

}
/* It exists; we have (non-exclusive) reference. */

if (DIRECTION(h) == IP_CT_DIR_REPLY) {

// 连接回应方向包

*ctinfo = IP_CT_ESTABLISHED + IP_CT_IS_REPLY;

/* Please set reply bit if this packet OK */

// 这个包是响应方的包

*set_reply = 1;

} else {

// 连接原始方向的包

/* Once we've had two way comms, always ESTABLISHED. */

if (test_bit(IPS_SEEN_REPLY_BIT, &h->ctrack->status)) {

DEBUGP("ip_conntrack_in: normal packet for %p\n",

h->ctrack);

// 如果不是第一个包,设置ESTABLISHED属性,这个位标志表示发现了响应方发的包,

// 表示连接可以建立,该标志在ip_conntrack_in()函数中设置

*ctinfo = IP_CT_ESTABLISHED;

} else if (test_bit(IPS_EXPECTED_BIT, &h->ctrack->status)) {

DEBUGP("ip_conntrack_in: related packet for %p\n",

h->ctrack);

// 该标志表示是子连接的包,如FTP的数据连接

// 该标志在init_conntrack()函数中设置

*ctinfo = IP_CT_RELATED;

} else {

DEBUGP("ip_conntrack_in: new packet for %p\n",

h->ctrack);

// 没有任何标志,是连接的第一个包,设置为NEW属性

*ctinfo = IP_CT_NEW;

}

// 这个包是发起方的包

*set_reply = 0;

}

// infos是数量为IP_CT_NUMBER(=5)的数组,分别对应连接发起方向的

// NEW(2)/ESTABLISHED(0)/RELATED(1)

// 和连接响应方向的ESTABLISHED(3)/RELATED(4)

// 每个skb包都会划归为其中一类, 也就是ctinfo的值

// 每个skb包根据其类型,将其nfct值设为相应infos项的地址

skb->nfct = &h->ctrack->infos[*ctinfo];

return h->ctrack;

}
在init_conntrack()函数中将连接的infos值初始化为:

...

for (i=0; i < IP_CT_NUMBER; i++)

conntrack->infos[i].master = &conntrack->ct_general;

...

也就是infos各项的值都是相同的,没有发现在其他地方重新赋值,所以把所有项实际都是相同的,指向同一个连接。设计之初可能是考虑要区分, 但实现时还是只用到一个就行了,在其他地方基本只用到infos[0]进行处理,infos的用处目前只用来判断状态,也就是每一项相对于infos [0]的偏移值。
最后讨论一下为什么在__ip_conntrack_get()函数中nfct->master值就是连接的地址:

__ip_conntrack_get(struct nf_ct_info *nfct, enum ip_conntrack_info *ctinfo)

{

struct ip_conntrack *ct

= (struct ip_conntrack *)nfct->master;

...

skb的nfct值是infos数组中某一项的地址,而这些项中的master成员都初始化为连接中ct_general的地址,而ct_general参数是连接结构的第一项参数,所以其地址和连接结构的地址是相同,在Linux内核中,类似用法很多。
5. 结论
在每个skb数据包进入netfilter架构时,netfilter就会给其建立连接,通过简单方法就能找到连接,能区分出发起方、响应方以及第一个包还是后续包,并能识别子连接,从而实现状态检测。
连接结构中的infos数组就目前而言似乎不是很必要,单值就可以,然后增加一个连接状态参数,而不是通过infos的偏移来判断。

// Linux下检测网络状态是否正常

#include <sys/types.h>

#include <string.h>

#include <stdlib.h>

#include <sys/ioctl.h>

#include <stdio.h>

#include <errno.h>

#include <net/if.h>

struct ethtool_value {

__uint32_t cmd;

__uint32_t data;

};

int main(int argc, char* argv[])

{

struct ethtool_value edata;

int fd = -1, err = 0;

struct ifreq ifr;

memset(&ifr, 0, sizeof(ifr));

strcpy(ifr.ifr_name, "eth0");

fd = socket(AF_INET, SOCK_DGRAM, 0);

if (fd < 0) {

perror("Cannot get control socket");

return 70;

}

edata.cmd = 0x0000000a;

ifr.ifr_data = (caddr_t)&edata;

err = ioctl(fd, 0x8946, &ifr);

if (err == 0) {

fprintf(stdout, "Link detected: %s/n",

edata.data ? "yes":"no");

} else if (errno != EOPNOTSUPP) {

perror("Cannot get link status");

}

return 0;

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: