分享

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

 白雪~~~ 2012-02-25

. 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; 

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多