PHP模块mcrypt模块安装

今天想了解一下PHP的加密函数,但是需要mcrypt模块,就自己编译一遍,没想到如此复杂,所以记录下来,也好和大家分享

PHP模块mcrypt安装步骤:

1. 确认是否已安装libmcrypt
ldconfig -p | grep libmcrypt

如果没有安装:
下载地址:http://sourceforge.net/projects/mcrypt
可以同时下载libmcrypt 和 mcrypt
先安装libmcrypt, 默认安装目录为 /usr/local , PHP 手册建议–disable-posix-threads ,不知何故
./configure && make && make install
ldconfig

再安装mcrypt, 默认安装目录为 /usr/local
./configure –with-libmcrypt-prefix=/usr/local

2. 确认是否已安装mhash
ldconfig -p | grep mhash

如果没有安装:
下载地址:http://mhash.sourceforge.net/
./configure && make && make install
ldconfig

3. 现在可以安装mcrypt模块了
cd php-x.x.x/ext/mcrypt
./configure –with-mcrypt=/usr/local/ && make && make install

4. 修改php.ini 就可以了

测试一下:

<?php
// Designate string to be encrypted 
$string "Applied Cryptography, by Bruce Schneier, is a wonderful cryptography reference."

$key "Four score and twenty years ago"
// Encryption/decryption key
$cipher_alg MCRYPT_TWOFISH

$iv=mcrypt_create_iv(mcrypt_get_iv_size($cipher_alg,MCRYPT_MODE_ECB), MCRYPT_RAND
); 
echo 
"Original string: $string \n"
;  
$encrypted_string mcrypt_encrypt($cipher_alg$key$stringMCRYPT_MODE_CBC$iv
); 
echo 
"Encrypted string: ".bin2hex($encrypted_string)."\n"
;
$decrypted_string mcrypt_decrypt($cipher_alg$key

$encrypted_stringMCRYPT_MODE_CBC$iv
); 
echo 
"Decrypted string: $decrypted_string\n"

?>

Linux连接跟踪源码分析[2]

5. 两个主要的连接跟踪函数: ip_conntrack_in()ip_confirm()
5.1.1 ip_conntrack_in()函数ip_conntrack_core.c
接收倒的数据包进入Netfilter后,首先进行分片处理,然后就会调用ip_conntrack_in函数, ip_conntrack_in 主要完成的工作就是判断数据包是否已在连接跟踪表中,如果不在,则为数据包分配ip_conntrack,并初始化它,然后,为这个数据包设置连接状态。
unsigned int ip_conntrack_in(unsigned int hooknum,
struct sk_buff **pskb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *))
{
struct ip_conntrack *ct;
enum ip_conntrack_info ctinfo;
struct ip_conntrack_protocol *proto;
int set_reply;
int ret;
/* 分片包会在前一个Hook中被处理,事实上,并不会触发该条件 */
if ((*pskb)->nh.iph->frag_off & htons(IP_OFFSET)) {
return NF_ACCEPT;
if (net_ratelimit()) {
printk(KERN_ERR "ip_conntrack_in: Frag of proto %u (hook=%u)\n",
(*pskb)->nh.iph->protocol, hooknum);
}
return NF_DROP;
}
/* 将当前数据包设置为未修改 */
(*pskb)->nfcache |= NFC_UNKNOWN;
/* 判断当前数据包是否已被检查过了*/
if ((*pskb)->nfct)
return NF_ACCEPT;
/* 根据当前数据包的协议,查找与之相应的struct ip_conntrack_protocol结构 */
proto = ip_ct_find_proto((*pskb)->nh.iph->protocol);
/* 如果是icmp错误报文 */
if ((*pskb)->nh.iph->protocol == IPPROTO_ICMP
&& icmp_error_track(*pskb, &ctinfo, hooknum))
return NF_ACCEPT;
/* 在全局的连接表中,查找与当前包相匹配的连接结构,返回的是struct ip_conntrack *类型指针,它用于描述一个数据包的连接状态 */
if (!(ct = resolve_normal_ct(*pskb, proto,&set_reply,hooknum,&ctinfo)))
return NF_ACCEPT;
if (IS_ERR(ct))
return NF_DROP;
IP_NF_ASSERT((*pskb)->nfct);
/* 如果注册了相应的协议的ip_conntrack_protocol结构,则在这里调用其中的packet函数做一些检查 */
ret = proto->packet(ct, *pskb, ctinfo);
if (ret == -1) {
/* Invalid */
nf_conntrack_put((*pskb)->nfct);
(*pskb)->nfct = NULL;
return NF_ACCEPT;
}
/* 如果注册了相应协议的ip_conntrack_helper结构,则在这里调用其help函数 */
if (ret != NF_DROP && ct->helper) {
ret = ct->helper->help(*pskb, ct, ctinfo);
if (ret == -1) {
/* Invalid */
nf_conntrack_put((*pskb)->nfct);
(*pskb)->nfct = NULL;
return NF_ACCEPT;
}
}
if (set_reply)
set_bit(IPS_SEEN_REPLY_BIT, &ct->status);
return ret;
}
连接跟踪模块将所有支持的协议,都使用struct ip_conntrack_protocol 结构封装,注册至全局链表中,这里首先调用函数ip_ct_find_proto根据当前数据包的协议值,找到协议注册对应的模块。然后调用resolve_normal_ct 函数进一步处理。
5.12 resolve_normal_ct()函数 ip_conntrack_core.c
函数判断数据包在连接跟踪表是否存在,如果不存在,则为数据包分配相应的连接跟踪节点空间并初始化,然后设置连接状态。
static inline struct ip_conntrack *
resolve_normal_ct(struct sk_buff *skb,
struct ip_conntrack_protocol *proto,
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 */
if (!get_tuple(skb->nh.iph, skb, skb->nh.iph->ihl*4, &tuple, proto))
return NULL;
/* 查找对应的tuple在连接跟踪表中是否存在 */
h = ip_conntrack_find_get(&tuple, NULL);
/* 如果不存在,初始化该连接 */
if (!h) {
h = init_conntrack(&tuple, proto, skb);
if (!h)
return NULL;
if (IS_ERR(h))
return (void *)h;
}
/* 判断连接方向 */
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);
*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);
*ctinfo = IP_CT_RELATED;
} else {
DEBUGP("ip_conntrack_in: new packet for %p\n",
h->ctrack);
*ctinfo = IP_CT_NEW;
}
*set_reply = 0;
}
/* 设置skb的对应成员,如使用计数器、数据包状态标记 */
skb->nfct = &h->ctrack->infos[*ctinfo];
return h->ctrack;
}
5.1.3获取tuple结构 ip_conntrack_core.c
get_tuple()函数将数据包转换成tuple结构
int get_tuple(const struct iphdr *iph,
const struct sk_buff *skb,
unsigned int dataoff,
struct ip_conntrack_tuple *tuple,
const struct ip_conntrack_protocol *protocol)
{
/* Never happen */
if (iph->frag_off & htons(IP_OFFSET)) {
printk("ip_conntrack_core: Frag of proto %u.\n",
iph->protocol);
return 0;
}
/* 设置来源、目的地址和协议号 */
tuple->src.ip = iph->saddr;
tuple->dst.ip = iph->daddr;
tuple->dst.protonum = iph->protocol;
tuple->src.u.all = tuple->dst.u.all = 0;
/* 这里根据协议的不同调用各自的函数 */
return protocol->pkt_to_tuple(skb, dataoff, tuple);
}
tcp协议为例,ip_conntrack_proto_tcp.c :
static int tcp_pkt_to_tuple(const struct sk_buff *skb,
unsigned int dataoff,
struct ip_conntrack_tuple *tuple)
{
struct tcphdr hdr;
/* Actually only need first 8 bytes. */
if (skb_copy_bits(skb, dataoff, &hdr, 8) != 0)
return 0;
/* 根据报头的端口信息,设置tuple对应成员 */
tuple->src.u.tcp.port = hdr.source;
tuple->dst.u.tcp.port = hdr.dest;
return 1;
}
5.1.4搜索hash
要对Hash表进行遍历,首要需要找到hash表的入口,然后来遍历该入口指向的链表。每个链表的节点是struct ip_conntrack_tuple_hash,它封装了tuple,所谓封装,就是把待查找的tuple与节点中已存的tuple相比较。

struct ip_conntrack_tuple_hash *
ip_conntrack_find_get(const struct ip_conntrack_tuple *tuple,
const struct ip_conntrack *ignored_conntrack)
{
struct ip_conntrack_tuple_hash *h;
READ_LOCK(&ip_conntrack_lock);
/* 查找整个hash表,返回一个节点 */
h = __ip_conntrack_find(tuple, ignored_conntrack);
/* 找到则累加计数器 */
if (h)
atomic_inc(&h->ctrack->ct_general.use);
READ_UNLOCK(&ip_conntrack_lock);
return h;
}
计算hash值,是调用hash_conntrack函数,根据数据包对应的tuple实现的:
static struct ip_conntrack_tuple_hash *
__ip_conntrack_find(const struct ip_conntrack_tuple *tuple,
const struct ip_conntrack *ignored_conntrack)
{
struct ip_conntrack_tuple_hash *h;
/* 计算tuplehash值,这样,tuple对应的hash表入口即为ip_conntrack_hash[hash],也就是链表的首节点*/
unsigned int hash = hash_conntrack(tuple);
MUST_BE_READ_LOCKED(&ip_conntrack_lock);
/* 找到链表入口后遍历链表,返回一个节点。比较函数是conntrack_tuple_cmp() */
h = LIST_FIND(&ip_conntrack_hash[hash],
conntrack_tuple_cmp,
struct ip_conntrack_tuple_hash *,
tuple, ignored_conntrack);
return h;
}
5.1.5 init_conntrack()函数 ip_conntrack_core.c
static struct ip_conntrack_tuple_hash *
init_conntrack(const struct ip_conntrack_tuple *tuple,
struct ip_conntrack_protocol *protocol,
struct sk_buff *skb)
{
struct ip_conntrack *conntrack;
struct ip_conntrack_tuple repl_tuple;
size_t hash;
struct ip_conntrack_expect *expected;
int i;
static unsigned int drop_next;
/* 如果计算hash值的随机数种子没有被初始化,则初始化之 */
if (!ip_conntrack_hash_rnd_initted) {
get_random_bytes(&ip_conntrack_hash_rnd, 4);
ip_conntrack_hash_rnd_initted = 1;
}
/* 计算hash */
hash = hash_conntrack(tuple);
/* 判断连接跟踪表是否已满 */
if (ip_conntrack_max &&
atomic_read(&ip_conntrack_count) >= ip_conntrack_max) {
/* Try dropping from random chain, or else from the
chain about to put into (in case they’re trying to
bomb one hash chain). */
unsigned int next = (drop_next++)%ip_conntrack_htable_size;
if (!early_drop(&ip_conntrack_hash[next])
&& !early_drop(&ip_conntrack_hash[hash])) {
#if defined(CONFIG_MIPS_BRCM)
/* Sorry, we have to kick one out regardless. */
while (!regardless_drop(&ip_conntrack_hash[next]))
next = (drop_next++)%ip_conntrack_htable_size;
#else
if (net_ratelimit())
printk(KERN_WARNING
"ip_conntrack: table full, dropping"
" packet.\n");
return ERR_PTR(-ENOMEM);
#endif
}
}
/* 根据当前的tuple取反,计算该数据包的应答tuple */
if (!invert_tuple(&repl_tuple, tuple, protocol)) {
DEBUGP("Can’t invert tuple.\n");
return NULL;
}
/* 为数据包对应的连接分配空间 */
conntrack = kmem_cache_alloc(ip_conntrack_cachep, GFP_ATOMIC);
if (!conntrack) {
DEBUGP("Can’t allocate conntrack.\n");
return ERR_PTR(-ENOMEM);
}
/* 初始化该结构,使用计数器累加,设置destroy函数指针,设置两个方向的tuple */
memset(conntrack, 0, sizeof(*conntrack));
atomic_set(&conntrack->ct_general.use, 1);
conntrack->ct_general.destroy = destroy_conntrack;
conntrack->tuplehash[IP_CT_DIR_ORIGINAL].tuple = *tuple;
conntrack->tuplehash[IP_CT_DIR_ORIGINAL].ctrack = conntrack;
conntrack->tuplehash[IP_CT_DIR_REPLY].tuple = repl_tuple;
conntrack->tuplehash[IP_CT_DIR_REPLY].ctrack = conntrack;
for (i=0; i < IP_CT_NUMBER; i++)
conntrack->infos.master = &conntrack->ct_general;
/* 创建一个该协议对应的ip_conntrack_protocol结构 */
if (!protocol->new(conntrack, skb)) {
kmem_cache_free(ip_conntrack_cachep, conntrack);
return NULL;
}
/*初始化时间计数器,并设置超时初始函数 */
init_timer(&conntrack->timeout);
conntrack->timeout.data = (unsigned long)conntrack;
conntrack->timeout.function = death_by_timeout;
/* 初始化预期连接链表 */
INIT_LIST_HEAD(&conntrack->sibling_list);
WRITE_LOCK(&ip_conntrack_lock);
/* Need finding and deleting of expected ONLY if we win race */
READ_LOCK(&ip_conntrack_expect_tuple_lock);
/* 在预期连接表中查找,有没有匹配的预期连接 */
expected = LIST_FIND(&ip_conntrack_expect_list, expect_cmp,
struct ip_conntrack_expect *, tuple);
READ_UNLOCK(&ip_conntrack_expect_tuple_lock);
/* If master is not in hash table yet (ie. packet hasn’t left
this machine yet), how can other end know about expected?
Hence these are not the droids you are looking for (if
master ct never got confirmed, we’d hold a reference to it
and weird things would happen to future packets). */
if (expected && !is_confirmed(expected->expectant))
expected = NULL;
/* 如果没有找到期望的连接,则搜索相关的helper结构 */
if (!expected)
conntrack->helper = ip_ct_find_helper(&repl_tuple);
/* If the expectation is dying, then this is a loser. */
if (expected
&& expected->expectant->helper->timeout
&& ! del_timer(&expected->timeout))
expected = NULL;
if (expected) {
DEBUGP("conntrack: expectation arrives ct=%p exp=%p\n",
conntrack, expected);
/* Welcome, Mr. Bond. We’ve been expecting you… */
__set_bit(IPS_EXPECTED_BIT, &conntrack->status);
conntrack->master = expected;
expected->sibling = conntrack;
#if CONFIG_IP_NF_CONNTRACK_MARK
conntrack->mark = expected->expectant->mark;
#endif
LIST_DELETE(&ip_conntrack_expect_list, expected);
expected->expectant->expecting–;
nf_conntrack_get(&master_ct(conntrack)->infos[0]);
}
atomic_inc(&ip_conntrack_count);
WRITE_UNLOCK(&ip_conntrack_lock);
if (expected && expected->expectfn)
expected->expectfn(conntrack);
/* 返回初始方向的hash节点 */
return &conntrack->tuplehash[IP_CT_DIR_ORIGINAL];
}
5.2.1 ip_ confirm函数 ip_conntrack_standalone.c
ip_confirm直接返回ip_conntrack_confirm(),ip_conntrack_confirm()里又调用了__ip_conntrack_confirm()函数
static unsigned int ip_confirm(unsigned int hooknum,
struct sk_buff **pskb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *))
{
/* We’ve seen it coming out the other side: confirm it */
return ip_conntrack_confirm(*pskb);
}
static inline int ip_conntrack_confirm(struct sk_buff *skb)
{
if (skb->nfct
&& !is_confirmed((struct ip_conntrack *)skb->nfct->master))
return __ip_conntrack_confirm(skb->nfct);
return NF_ACCEPT;
}
在数据包穿过filter到达post_routinglocal_in节点时,__ip_conntrack_confirm函数将该连接正式加入连接状态表中
int
__ip_conntrack_confirm(struct nf_ct_info *nfct)
{
unsigned int hash, repl_hash;
struct ip_conntrack *ct;
enum ip_conntrack_info ctinfo;
ct = __ip_conntrack_get(nfct, &ctinfo);
/* ipt_REJECT uses ip_conntrack_attach to attach related
ICMP/TCP RST packets in other direction. Actual packet
which created connection will be IP_CT_NEW or for an
expected connection, IP_CT_RELATED. */
if (CTINFO2DIR(ctinfo) != IP_CT_DIR_ORIGINAL)
return NF_ACCEPT;
hash = hash_conntrack(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple);
repl_hash = hash_conntrack(&ct->tuplehash[IP_CT_DIR_REPLY].tuple);
/* We’re not in hash table, and we refuse to set up related
connections for unconfirmed conns. But packet copies and
REJECT will give spurious warnings here. */
/* IP_NF_ASSERT(atomic_read(&ct->ct_general.use) == 1); */
/* No external references means noone else could have
confirmed us. */
IP_NF_ASSERT(!is_confirmed(ct));
DEBUGP("Confirming conntrack %p\n", ct);
WRITE_LOCK(&ip_conntrack_lock);
/* See if there’s one in the list already, including reverse:
NAT could have grabbed it without realizing, since we’re
not in the hash. If there is, we lost race. */
if (!LIST_FIND(&ip_conntrack_hash[hash],
conntrack_tuple_cmp,
struct ip_conntrack_tuple_hash *,
&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple, NULL)
&& !LIST_FIND(&ip_conntrack_hash[repl_hash],
conntrack_tuple_cmp,
struct ip_conntrack_tuple_hash *,
&ct->tuplehash[IP_CT_DIR_REPLY].tuple, NULL)) {
list_prepend(&ip_conntrack_hash[hash],
&ct->tuplehash[IP_CT_DIR_ORIGINAL]);
list_prepend(&ip_conntrack_hash[repl_hash],
&ct->tuplehash[IP_CT_DIR_REPLY]);
/* Timer relative to confirmation time, not original
setting time, otherwise we’d get timer wrap in
weird delay cases. */
ct->timeout.expires += jiffies;
add_timer(&ct->timeout);
atomic_inc(&ct->ct_general.use);
set_bit(IPS_CONFIRMED_BIT, &ct->status);
WRITE_UNLOCK(&ip_conntrack_lock);
return NF_ACCEPT;
}
WRITE_UNLOCK(&ip_conntrack_lock);
return NF_DROP;
}
6. 具体协议的连接跟踪源码分析
前面是conntrack的总体框架,下面以tftp为例分析其具体实现
相关的文件: ip_conntrack_tftp.c ip_conntrack_tftp.h
6.1 helperexpect结构
6.1.1 ip_conntrack_helper结构
每个使用动态地址和端口的协议一般都定义了数据结构ip_conntrack_helper,并把它放到一个全局的链表中。这类协议一般都会有控制连接和数据连接两个不同的连接,如FTP协议等。
struct ip_conntrack_helper
{
struct list_head list; /* 链表头 */
const char *name; /* 模块的名称 */
unsigned char flags;
struct module *me; /* 指向模块本身 */
unsigned int max_expected; /* 期望的连接数的最大值 */
unsigned int timeout; /* 超时位 */
struct ip_conntrack_tuple tuple;
struct ip_conntrack_tuple mask;
int (*help)(struct sk_buff *skb,
struct ip_conntrack *ct,
enum ip_conntrack_info conntrackinfo);
};
6.1.2 tftpinit()初始化
将协议对应的ip_conntrack_helper结构数组初始化,并链接到全局链表里
static int __init init(void)
{
int i, ret;
char *tmpname;
if (!ports[0])
ports[0]=TFTP_PORT;
for (i = 0 ; (i < MAX_PORTS) && ports ; i++) {
/* Create helper structure */
memset(&tftp, 0, sizeof(struct ip_conntrack_helper));
tftp.tuple.dst.protonum = IPPROTO_UDP; /* 协议号17 */
tftp.tuple.src.u.udp.port = htons(ports); /* 源端口500 */
tftp.mask.dst.protonum = 0xFFFF;
tftp.mask.src.u.udp.port = 0xFFFF;
tftp.max_expected = 1;
tftp.timeout = 0;
tftp.flags = IP_CT_HELPER_F_REUSE_EXPECT;
tftp.me = THIS_MODULE;
tftp.help = tftp_help; /* tftp_help函数,最重要的部分 */
tmpname = &tftp_names[0];
if (ports == TFTP_PORT)
sprintf(tmpname, "tftp");
else
sprintf(tmpname, "tftp-%d", i);
tftp.name = tmpname;
DEBUGP("port #%d: %d\n", i, ports);
/* tftphelper结构注册到一个全局链表&helpter */
ret=ip_conntrack_helper_register(&tftp);
if (ret) {
printk("ERROR registering helper for port %d\n",
ports);
fini();
return(ret);
}
ports_c++;
}
return(0);
}
6.1.3 ip_conntrack_expect结构
结构定义在ip_conntrack.hip_conntrack里的master变量就是这个结构,它的作用后面再说
struct ip_conntrack_expect
{
/* 链表头,在被某连接引用之前,所有expect结构都由此链表维护 */
struct list_head list;
/* 引用计数 */
atomic_t use;
/* 主连接的预期的子连接的链表 */
struct list_head expected_list;
/* 期待者,即预期连接对应的主连接,换句话说就是将此连接当作是其预期连接的连接… */
struct ip_conntrack *expectant;
/* 预期连接对应的真实的子连接 */
struct ip_conntrack *sibling;
/* 连接的tuple */
struct ip_conntrack_tuple ct_tuple;
/* 定时器 */
struct timer_list timeout;
/* 预期连接的tuplemask,搜索预期连接时要用到的 */
struct ip_conntrack_tuple tuple, mask;
/* 预期连接函数,一般是NULL,有特殊需要时才定义 */
int (*expectfn)(struct ip_conntrack *new);
/* TCP协议时,主连接中描述子连接的数据起始处对应的序列号值 */
u_int32_t seq;
/* 跟踪各个多连接IP层协议相关的数据 */
union ip_conntrack_expect_proto proto;
/* 跟踪各个多连接应用层协议相关的数据 */
union ip_conntrack_expect_help help;
};
6.2数据包在连接跟踪模块中的行进过程
6.2.1 ip_conntrack_in阶段
数据包进入netfilter后,首先会调用ip_conntrack_in()函数,确认数据包未被检查过后,首先确定数据包使用的协议类型,如果是ICMP错误报文的话要做特别处理:
proto = ip_ct_find_proto((*pskb)->nh.iph->protocol);
proto是一个ip_conntrack_protocol指针,该函数根据sk_buffprotocol字段在全局的protocol_list变量里搜索匹配的ip_conntrack_protocol结构,bcm源码里注册了esp,gre,icmp,tcp,udp等协议
这里返回的是udp协议的ip_conntrack_protocol结构ip_conntrack_protocol_udp
接下来就要在连接跟踪表内查找属于该数据包的连接状态,如果能找到,说明该数据包是之前某连接的后续报文,那么只需要做一些标记处理就可以放行了,如果找不到,说明是一个新的连接,那么就创建一个新的连接状态,然后仍然放行(连接跟踪只是跟踪记录连接状态,不修改也不过滤)。将找到的,或者新建的连接状态返回给参数ctct就是一个ip_conntrack指针。
ct = resolve_normal_ct(*pskb, proto,&set_reply,hooknum,&ctinfo)
前面说了连接跟踪表是一个采用hash算法的链表数组,搜索hash表药先获取该连接的tuple结构,一般包括协议、来源/目的地址、来源/目的端口等,因不同协议而异(参考前面对tuple结构的描述)。resolve_normal_ct调用get_tuple函数获取报文的tuple结构,返回到第四个参数&tuple里:
get_tuple(skb->nh.iph, skb, skb->nh.iph->ihl*4, &tuple, proto)
tftp使用udp协议,固定端口是69
然后就调用函数:
h = ip_conntrack_find_get(&tuple, NULL)
在连接跟踪表里查找连接,h是一个ip_conntrack_tuple_hash指针,是链表的一个节点
查找的时候先计算hash
hash = hash_conntrack(tuple)
根据hash值找到链表的入口,然后遍历链表比较tuple
LIST_FIND(&ip_conntrack_hash[hash],
conntrack_tuple_cmp,
struct ip_conntrack_tuple_hash *,
tuple, ignored_conntrack);
查不到就新建一个连接,查到了就跳过这一步,这里假设是第一个数据包,那么肯定查不到
h = init_conntrack(&tuple, proto, skb)
新建连接时首先也要计算hash值(又算一遍),为了将来决定将它放入哪条链用
hash = hash_conntrack(tuple)
接下来计算相反方向的tuple,即repl_tuple
invert_tuple(&repl_tuple, tuple, protocol)
只是简单的将来源/目的颠倒而已
一个完整的连接状态是包括两个方向的tuple的,可以看到在ip_conntrack结构中
struct ip_conntrack_tuple_hash tuplehash[IP_CT_DIR_MAX]
定义了一个conntrack_tuple_hash数组,它包括tuplehash[IP_CT_DIR_ORIGINAL]tuplehash[IP_CT_DIR_REPLY]两个元素
然后就要为新的连接创建一个ip_conntrack结构了
atomic_set(&conntrack->ct_general.use, 1);
conntrack->ct_general.destroy = destroy_conntrack;
conntrack->tuplehash[IP_CT_DIR_ORIGINAL].tuple = *tuple;
conntrack->tuplehash[IP_CT_DIR_ORIGINAL].ctrack = conntrack;
conntrack->tuplehash[IP_CT_DIR_REPLY].tuple = repl_tuple;
conntrack->tuplehash[IP_CT_DIR_REPLY].ctrack = conntrack;
引用计数加1,初始化了一个destory函数,用来销毁该结构的。然后是两个方向的tuple
ctracktuple里的一个指针,用来指向它所匿属于的ip_conntrack(两个互相指,可能是为了传递参数的时候比较方便,不过这个ctrack在新的内核中被去掉了)
将所有状态的infos变量都初始化,infos是一个nf_ct_info结构的数组,实际上是一个nf_conntrack结构数组,包含一个引用计数和一个销毁函数,见前面关于infos的定义
for (i=0; i < IP_CT_NUMBER; i++)
conntrack->infos.master = &conntrack->ct_general;
ct_generalip_conntrack的第一个变量,结合下面的语句:
skb->nfct = &h->ctrack->infos[*ctinfo]
我们就可以通过:
struct ip_conntrack *ct = (struct ip_conntrack *)nfct->master
来从sk_buff里的nfct变量获取其所属连接的ip_conntrack结构
因为是一个新的连接,所以要调用对应的协议的ip_conntrack_protocol结构里的new函数,这个函数的作用因协议不同而异,而udp协议的new函数实际上不做任何事情,直接返回1
protocol->new(conntrack, skb)
初始化一些超时相关的变量
init_timer(&conntrack->timeout);
conntrack->timeout.data = (unsigned long)conntrack;
conntrack->timeout.function = death_by_timeout;
根据tuple在预期连接全局链表里搜索预期的连接,比较函数是expect_cmp
expected = LIST_FIND(&ip_conntrack_expect_list, expect_cmp,
struct ip_conntrack_expect *, tuple)
到底什么是预期的连接,现在还不是很清楚,但ip_conntrack_expect_list全局变量还没出现过,应该是空的,所以什么也查不到
查不到预期的连接,就查找有没有对应的helper结构,跟据tuple对应的repl_tuple,在全局链表helpers里搜索,此时协议是udp,目的端口是69
conntrack->helper = ip_ct_find_helper(&repl_tuple)
可想而知,在conntrack_tftp模块初始化时注册的tftphelper结构被找到了,还是来看一下最终的比较函数
第一个参数是当前数据包的repl_tuple,后两个参数是tftp初始化时ip_conntrack_helper里的tuplemask
static inline int ip_ct_tuple_mask_cmp(const struct ip_conntrack_tuple *t,
const struct ip_conntrack_tuple *tuple,
const struct ip_conntrack_tuple *mask)
{
return !(((t->src.ip ^ tuple->src.ip) & mask->src.ip)
|| ((t->dst.ip ^ tuple->dst.ip) & mask->dst.ip)
|| ((t->src.u.all ^ tuple->src.u.all) & mask->src.u.all)
|| ((t->dst.u.all ^ tuple->dst.u.all) & mask->dst.u.all)
|| ((t->dst.protonum ^ tuple->dst.protonum)& mask->dst.protonum));
}
mask用来区分哪些内容需要比较,哪些不需要,在ip_conntrack_tftp.c的初始化函数里,我们看到
tftp.mask.dst.protonum = 0xFFFF;
tftp.mask.src.u.udp.port = 0xFFFF;
所以比较函数里起作用的只有划线的两行,即协议和端口号,这里是UDP 69
最终将tftphelper结构返回给了conntrack->helper
init_conntrack函数返回,接下来确定数据包属于连接跟踪的哪个的状态,即ctinfo的值(是个枚举类型,这里应该是IP_CT_NEW,因为是第一个数据包),然后将对应的infos变量附给sk_buff *skb中的nfct,这样从skb里也可以得到本数据包的连接状态信息了
在前面见到过“ if ((*pskb)->nfct) return NF_ACCEPT;”之类的语句,意思就是每个包与一个状态结构关联,如果关联已存在,就不需要重复检查
skb->nfct = &h->ctrack->infos[*ctinfo]
resolve_normal_ct函数里返回后调用ip_conntrack_protocol里的packet函数,对udp协议来说只是刷新一下超时位
ip_ct_refresh(conntrack, ip_ct_udp_timeout);
接下来要调用help函数了,之前把tftp注册的helper结构附给了conntrack-helper
ret = ct->helper->help(*pskb, ct, ctinfo)
最关键的地方到了,看看tftp_help函数做了些什么
static int tftp_help(struct sk_buff *skb,
struct ip_conntrack *ct,
enum ip_conntrack_info ctinfo)
{
struct tftphdr tftph;
struct ip_conntrack_expect *exp;
if (skb_copy_bits(skb, skb->nh.iph->ihl * 4 + sizeof(struct udphdr),
&tftph, sizeof(tftph)) != 0)
return NF_ACCEPT;
switch (ntohs(tftph.opcode)) {
/* RRQ and WRQ works the same way */
case TFTP_OPCODE_READ:
case TFTP_OPCODE_WRITE:
DEBUGP("");
DUMP_TUPLE(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple);
DUMP_TUPLE(&ct->tuplehash[IP_CT_DIR_REPLY].tuple);
/* 初始化了一个ip_conntrack_expect结构 */
exp = ip_conntrack_expect_alloc();
if (exp == NULL)
return NF_ACCEPT;
/* expecttuple设置为连接的应答方向的tuple,然后将源/目的地址,目的端口以及协议的mask位都置了1,只有源端口置0,也就是tftp里的69端口对应的mask */
exp->tuple = ct->tuplehash[IP_CT_DIR_REPLY].tuple;
exp->mask.src.ip = 0xffffffff;
exp->mask.dst.ip = 0xffffffff;
exp->mask.dst.u.udp.port = 0xffff;
exp->mask.dst.protonum = 0xffff;
exp->expectfn = NULL;
DEBUGP("expect: ");
DUMP_TUPLE(&exp->tuple);
DUMP_TUPLE(&exp->mask);
/* 将创建的expect结构与当前的连接关联起来 */
ip_conntrack_expect_related(exp, ct);
break;
case TFTP_OPCODE_DATA:
case TFTP_OPCODE_ACK:
DEBUGP("Data/ACK opcode\n");
break;
case TFTP_OPCODE_ERROR:
DEBUGP("Error opcode\n");
break;
default:
DEBUGP("Unknown opcode\n");
}
return NF_ACCEPT;
}
函数主要做了两件事情,首先初始化了一个ip_conntrack_expect结构,并将conntrack中应答方向的tuple结构附值给exp-tuple,然后调用:ip_conntrack_expect_related(exp, ct)
将这个expect结构与当前的连接状态关联起来,并把它注册到一个专门组织expect结构的全局链表ip_conntrack_expect_list里。
expect结构有什么用呢?当有返回的数据包时,首先仍然是搜索hash表,如果找不到可以匹配的连接,还会在全局链表里搜索匹配的expect结构,然后找到相关的连接状态。
为什么要这样做呢?假设有主机A向主机B发送消息,如下
A10.10.10.11001——> B10.10.10.269
A1001端口发往B69端口,连接跟踪模块跟踪并记录了次条连接,保存在一个ip_conntrack结构里(用tuple来识别),但是我们知道,一个连接有两个方向,我们怎么确定两个方向相反的数据包是属于同一个连接的呢?最简单的判断方法就是将地址和端口号倒过来,就是说如果有下面的数据包:
B10.10.10.269)——> A10.10.10.11001
虽然源/目的端口/地址全都不一样,不能匹配初始数据包的tuple,但是与对应的repl_tuple完全匹配,所以显然应该是同一个连接,所以见到这样的数据包时就可以直接确定其所属的连接了,当然不需要什么expect
然而不是所有协议都这么简单,对于tftp协议,相应的数据包可能是
B10.10.10.21002)——> A10.10.10.11001
并不完全颠倒,就是说不能直接匹配初始数据包的tuple的反防向的repl_tuple,在hash表里找不到对应的节点,但我们仍然认为它和前面第一条消息有密切的联系,甚至我们可以明确,将所有下面形式的数据包都归属于这一连接的相关连接
B10.10.10.2XXX)——> A10.10.10.11001
怎么实现这一想法呢,只好再多创建一个expect了,它的tuple结构和repl_tuple完全相同,只是在mask中将源端口位置0,就是说不比较这一项,只要其他项匹配就OK
(注意一下,ip_conntrack_expect结构里有个maskip_conntrack_helper里也有个mask,即使是同一个连接,它们的值也是不一样的。)
以上就是helperexpect的作用了,但是具体的实现方法还跟协议有关,像ftp的连接跟踪就相当复杂。
help函数返回后,连接跟踪的第一阶段就结束了。
6.2.2 ip_confirm阶段
3.1 init_or_cleanup()里面可以看到,数据包再次进入ip_conntrack模块是在NF_IP_LOCAL_IN点上,由ip_confirm函数来处理。
ip_confirm直接返回ip_conntrack_confirm(*pskb)
ip_conntrack_confirm检查一下数据包是否已经被检查过了,然后调用
__ip_conntrack_confirm(skb->nfct)
int __ip_conntrack_confirm(struct nf_ct_info *nfct)
{
unsigned int hash, repl_hash;
struct ip_conntrack *ct;
enum ip_conntrack_info ctinfo;
/* 获取该数据包的连接状态,就是通过前面提过的
struct ip_conntrack *ct = (struct ip_conntrack *)nfct->master
来实现的 */
ct = __ip_conntrack_get(nfct, &ctinfo);
/* 如果当前包不是一个初始方向的数据包,则直接返回,因为该连接状态在之前已经被添加到连接状态表里了,所以不需要再做什么 */
if (CTINFO2DIR(ctinfo) != IP_CT_DIR_ORIGINAL)
return NF_ACCEPT;
/* 计算两个方向的hash */
hash = hash_conntrack(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple);
repl_hash = hash_conntrack(&ct->tuplehash[IP_CT_DIR_REPLY].tuple);
IP_NF_ASSERT(!is_confirmed(ct));
DEBUGP("Confirming conntrack %p\n", ct);
WRITE_LOCK(&ip_conntrack_lock);
/* 根据计算出来的hash值,在hash表中找到合适的节点,将当前连接插入,并设置相应的超时和状态位。 */
if (!LIST_FIND(&ip_conntrack_hash[hash],
conntrack_tuple_cmp,
struct ip_conntrack_tuple_hash *,
&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple, NULL)
&& !LIST_FIND(&ip_conntrack_hash[repl_hash],
conntrack_tuple_cmp,
struct ip_conntrack_tuple_hash *,
&ct->tuplehash[IP_CT_DIR_REPLY].tuple, NULL)) {
list_prepend(&ip_conntrack_hash[hash],
&ct->tuplehash[IP_CT_DIR_ORIGINAL]);
list_prepend(&ip_conntrack_hash[repl_hash],
&ct->tuplehash[IP_CT_DIR_REPLY]);
ct->timeout.expires += jiffies;
add_timer(&ct->timeout);
atomic_inc(&ct->ct_general.use);
set_bit(IPS_CONFIRMED_BIT, &ct->status);
WRITE_UNLOCK(&ip_conntrack_lock);
return NF_ACCEPT;
}
WRITE_UNLOCK(&ip_conntrack_lock);
return NF_DROP;
}
/* 到此,一个连就正式的被添加到系统的连接状态表里面了。 */
6.2.3连接跟踪对后续报文的处理
首先仍然是获取协议信息
proto = ip_ct_find_proto((*pskb)->nh.iph->protocol);
然后调用resolve_normal_ct()
ct = resolve_normal_ct(*pskb, proto,&set_reply,hooknum,&ctinfo)
resolve_normal_ct函数内部,
get_tuple(skb->nh.iph, skb, skb->nh.iph->ihl*4, &tuple, proto)
然后查找hash
h = ip_conntrack_find_get(&tuple, NULL)
这时候,还是举刚才的例子,如果之前曾有这样的数据包通过:
A10.10.10.11001——> B10.10.10.269
那么相应的连接状态应该已经建立了,这时候如果有完全相反的数据包
B10.10.10.269——> A10.10.10.11001
那么在搜索hash表的时候就能顺利找到对应的连接,找到以后就简单了
skb->nfct = &h->ctrack->infos[*ctinfo]
将连接状态附值给nfct位,接下来就没什么特别的事情要做了,设置几个标志位,检查一下数据包等等
我们主要关心的还是这样的情况,如果后续的数据包是这样的:
B10.10.10.21002——> A10.10.10.11001
那么程序执行到ip_conntrack_find_get处时,会发现在hash表里找不到与当前数据包相匹配的连接,于是还是调用init_conntrack()创建连接
接下来的部分都和初始连接一样,计算hash值,初始化一个ip_conntrack等等,直到:
expected = LIST_FIND(&ip_conntrack_expect_list, expect_cmp,
struct ip_conntrack_expect *, tuple)
之前注册的expect被找到了,找到以后,进行下面的操作
if (expected) {
DEBUGP("conntrack: expectation arrives ct=%p exp=%p\n",
conntrack, expected);
/* 设置状态位为IPS_EXPECTED_BIT,当前的连接是一个预期的连接 */
__set_bit(IPS_EXPECTED_BIT, &conntrack->status);
/* conntrackmaster位指向搜索到的expected,而expectedsibling位指向conntrack……..解释一下,这时候有两个conntrack,一个是一开始的初始连接(比如69端口的那个)也就是主连接conntrack1,一个是现在正在处理的连接(1002)子连接conntrack2,两者和expect的关系是:
1 expectsibling指向conntrack2,而expectant指向conntrack1
2一个主连接conntrack1可以有若干个expectint expecting表示当前数量),这些
expect也用一个链表组织,conntrack1中的struct list_head sibling_list就是该
链表的头。
3一个子连接只有一个主连接,conntrack2struct ip_conntrack_expect *master
指向expect
通过一个中间结构expect将主连接和子连接关联起来 */
conntrack->master = expected;
expected->sibling = conntrack;
/* 将此连接从预期连接全局链表里删除(只是从全局链表里删除,但连接本身还在),因为此连接即将被正式添加到全局的连接表里,所以下次如果再有B10.10.10.21002——> A10.10.10.11001)这样的数据包,就能直接从hash表里找到了,不必再借用expect。因此主连接中的预期连接数expecting也自动减1 */
LIST_DELETE(&ip_conntrack_expect_list, expected);
expected->expectant->expecting–;
nf_conntrack_get(&master_ct(conntrack)->infos[0]);
}
接下来,如果expect里的expectfn函数有定义的话就执行它(一般是没有的)
if (expected && expected->expectfn)
expected->expectfn(conntrack);
后面的过程略去了,和之前差不多

Linux连接跟踪源码分析

IP Connection tracking
连接跟踪用来跟踪和记录连接状态,是netfilter的一部份,也是通过在hook点上注册相应的结构来工作的。
无论是发送,接收,还是转发的数据包,都要经过两个conntrack模块。
第一个conntrack点的优先级是最高的,所有数据包进入netfilter后都会首先被它处理,其作用是创建ip_conntrack结构。而最后一个conntrack的优先级最低,总是在数据包离开netfilter之前做最后的处理,它的作用是将该数据包的连接跟踪结构添加到系统的连接状态表中
1. ip_conntarck结构 ip_conntrack.h
内核中用一个ip_conntrack结构来描述一个连接的状态
struct ip_conntrack
{
/* nf_conntrack结构定义于include/linux/skbuff.hLine89,其中包括一个计数器use和一个destroy函数。计数器use对本连接记录的公开引用次数进行计数 */
struct nf_conntrack ct_general;
/*其中的IP_CT_DIR_MAX是一个枚举类型ip_conntrack_dir(位于include/linux/netfilter_ipv4/ip_conntrack_tuple.hLine65)的第3个成员,从这个结构实例在源码中的使用看来,实际上这是定义了两个tuple多元组的hash表项tuplehash[IP_CT_DIR_ORIGINAL/0]tuplehash[IP_CT_DIR_REPLY/1],利用两个不同方向的tuple定位一个连接,同时也可以方便地对ORIGINAL以及REPLY两个方向进行追溯*/
struct ip_conntrack_tuple_hash tuplehash[IP_CT_DIR_MAX];
/* 这是一个位图,是一个状态域。在实际的使用中,它通常与一个枚举类型ip_conntrack_status(位于include/linux/netfilter_ipv4/ip_conntrack.hLine33)进行位运算来判断连接的状态。其中主要的状态包括:
IPS_EXPECTED(_BIT),表示一个预期的连接
(_BIT),表示一个双向的连接
IPS_ASSURED(_BIT),表示这个连接即使发生超时也不能提早被删除
IPS_CONFIRMED(_BIT),表示这个连接已经被确认(初始包已经发出) */
unsigned long status;
/*其类型timer_list位于include/linux/timer.hLine11,其核心是一个处理函数。这个成员表示当发生连接超时时,将调用此处理函数*/
struct timer_list timeout;
/*所谓预期的连接的链表,其中存放的是我们所期望的其它相关连接*/
struct list_head sibling_list;
/*目前的预期连接数量*/
unsigned int expecting;
/*结构ip_conntrack_expect位于ip_conntrack.h,这个结构用于将一个预期的连接分配给现有的连接,也就是说本连接是这个master的一个预期连接*/
struct ip_conntrack_expect *master;
/* helper模块。这个结构定义于ip_conntrack_helper.h,这个模块提供了一个可以用于扩展Conntrack功能的接口。经过连接跟踪HOOK的每个数据报都将被发给每个已经注册的helper模块(注册以及卸载函数分别为ip_conntrack_helper_register()以及ip_conntrack_helper_unregister(),分别位于ip_conntrack_core.c)。这样我们就可以进行一些动态的连接管理了*/
struct ip_conntrack_helper *helper;
/*一系列的nf_ct_info类型(定义于include/linux/skbuff.h Line92,实际上就是nf_conntrack结构)的结构,每个结构对应于某种状态的连接。这一系列的结构会被sk_buff结构的nfct指针所引用,描述了所有与此连接有关系的数据报。其状态由枚举类型ip_conntrack_info定义(位于include/linux/netfilter_ipv4/ip_conntrack.hLine12)共有5个成员:
IP_CT_ESTABLISHED 数据报属于已经完全建立的连接
IP_CT_RELATED 数据报属于一个新的连接,但此连接与一个现有连接相关(预期连接);或者是ICMP错误
IP_CT_NEW 数据报属于一个新的连接
IP_CT_IS_REPLY 数据报属于一个连接的回复
IP_CT_NUMBER 不同IP_CT类型的数量,这里为7NEW仅存于一个方向上 */
struct nf_ct_info infos[IP_CT_NUMBER];
/* 为其他模块保留的部分 */
union ip_conntrack_proto proto;
union ip_conntrack_help help;
#ifdef CONFIG_IP_NF_NAT_NEEDED
struct {
struct ip_nat_info info;
union ip_conntrack_nat_help help;
#if defined(CONFIG_IP_NF_TARGET_MASQUERADE) || \
defined(CONFIG_IP_NF_TARGET_MASQUERADE_MODULE)
int masq_index;
#endif
#if defined(CONFIG_IP_NF_RTSP) || defined(CONFIG_IP_NF_RTSP_MODULE)
struct ip_nat_rtsp_info rtsp_info;
#endif
} nat;
#endif /* CONFIG_IP_NF_NAT_NEEDED */
#if defined(CONFIG_IP_NF_CONNTRACK_MARK)
unsigned long mark;
#endif
};
struct ip_conntrack_tuple_hash结构描述链表中的节点,这个数组包含初始应答两个成员(tuplehash[IP_CT_DIR_ORIGINAL]tuplehash[IP_CT_DIR_REPLY]),所以,当一个数据包进入连接跟踪模块后,先根据这个数据包的套接字对转换成一个初始的”tuple,赋值给tuplehash[IP_CT_DIR_ORIGINAL],然后对这个数据包取反,计算出应答tuple,赋值给tuplehash[IP_CT_DIR_REPLY],这样,一条完整的连接已经跃然纸上了。
enum ip_conntrack_dir
{
IP_CT_DIR_ORIGINAL,
IP_CT_DIR_REPLY,
IP_CT_DIR_MAX
};
2. 连接跟踪表
Netfilter来源地址/来源端口+目的地址/目的端口,即一个“tuple”,来唯一标识一个连接。用一张连接跟踪表来描述所有的连接状态,该表用了hash算法。
hash表用一个全局指针来描述(ip_conntrack_core.c)
struct list_head *ip_conntrack_hash;
表的大小,即hash节点的个数由ip_conntrack_htable_size全局变量决定,默认是根据内存计算出来的。而每个hash节点又是一条链表的首部,所以,连接跟踪表就是一个由ip_conntrack_htable_size 条链表构成的一个hash表,整个连接跟踪表大小使用全局变量ip_conntrack_max描述,与hash表的关系是ip_conntrack_max = 8 * ip_conntrack_htable_size
链表的每个节点,都是一个ip_conntrack_tuple_hash结构:
struct ip_conntrack_tuple_hash
{
/* 用来组织链表 */
struct list_head list;
/* 用来描述一个tuple */
struct ip_conntrack_tuple tuple;
/* this == &ctrack->tuplehash[DIRECTION(this)]. */
struct ip_conntrack *ctrack;
};
实际描述一个tuple的是ip_conntrack_tuple结构 ip_conntrack_tuple.h
struct ip_conntrack_tuple
{
/* */
struct ip_conntrack_manip src;
/* These are the parts of the tuple which are fixed. */
struct {
/* 目的地址 */
u_int32_t ip;
union {
/* Add other protocols here. */
u_int64_t all;
struct {
u_int16_t port;
} tcp;
struct {
u_int16_t port;
} udp;
struct {
u_int8_t type, code;
} icmp;
struct {
u_int16_t protocol;
u_int8_t version;
u_int32_t key;
} gre;
struct {
u_int16_t spi;
} esp;
} u;
/* 协议类型 */
u_int16_t protonum;
} dst;
};
对于所有IP协议,协议类型、源地址、目的地址这三个参数是识别连接所必须的,具体到各个协议,就要提取出各协议的唯一特征数据,如TCPUDP的源端口、目的端口,ICMPIDTYPECODE等值,这些值就是tuple结构要处理的数据。各协议相关数据是以联合(union)形式定义在tuple结构中的,netfilter缺省支持TCPUDPICMP协议,如果还要支持其他IP协议,如GREESPAHSCTP等,需要在联合中添加相应的协议参数值。
ip_conntrack_manipip_conntrack_manip_proto ip_conntrack_tuple.h
struct ip_conntrack_manip
{
u_int32_t ip;
union ip_conntrack_manip_proto u;
};
union ip_conntrack_manip_proto
{
/* Add other protocols here. */
u_int32_t all;
struct {
u_int16_t port;
} tcp;
struct {
u_int16_t port;
} udp;
struct {
u_int16_t id;
} icmp;
struct {
u_int32_t key;
} gre;
struct {
u_int16_t spi;
} esp;
};
Netfilter将每一个数据包转换成tuple,再根据tuple计算出hash值,这样,就可以使用ip_conntrack_hash[hash_id]找到hash表中链表的入口,并组织链表;找到hash表中链表入口后,如果链表中不存在此“tuple”,则是一个新连接,就把tuple插入到链表的合适位置;两个节点tuple[ORIGINAL]tuple[REPLY]虽然是分开的,在两个链表当中,但是如前所述,它们同时又被封装在ip_conntrack结构的tuplehash数组中
3. 连接跟踪初始化
初始化函数init()调用init_or_cleanup(1)函数 ip_conntrack_standalone.c
static int __init init(void)
{
return init_or_cleanup(1);
}
3.1 init_or_cleanup()函数
int init_or_cleanup函数,(ip_conntrack_standalone.c)参数为1则执行init,为0则执行clean,它主要做三件工作:
1. 调用ip_conntrack_init()初始化连接跟踪表的相关变量,见3.2
2. 初始化proc文件系统节点
3. 为连接跟踪注册hook
static int init_or_cleanup(int init)
{
struct proc_dir_entry *proc;
int ret = 0;
if (!init) goto cleanup;
/* 初始化连接跟踪的一些变量和数据结构,如连接跟踪表的大小,Hash表的大小等 */
ret = ip_conntrack_init();
if (ret < 0)
goto cleanup_nothing;
/* 初始化proc文件系统 */
proc = proc_net_create("ip_conntrack", 0440, list_conntracks);
proc = proc_net_create("ip_clear_dnsconntrack",0644,clear_dns_conntracks);
if (!proc) goto cleanup_init;
proc->owner = THIS_MODULE;
/* 为连接跟踪注册hook,一共六个,所在的hook点、注册的hook函数和优先级分别如下(和最开始的图是一致的):
NF_IP_PRE_ROUTING
ip_conntrack_defrag NF_IP_PRI_CONNTRACK_DEFRAG
ip_conntrack_in NF_IP_PRI_CONNTRACK
NF_IP_LOCAL_OUT
ip_conntrack_defrag NF_IP_PRI_CONNTRACK_DEFRAG
ip_conntrack_local NF_IP_PRI_CONNTRACK
NF_IP_POST_ROUTING
ip_refrag NF_IP_PRI_LAST
NF_IP_LOCAL_IN
ip_confirm NF_IP_PRI_LAST-1
优先级的顺序为:
NF_IP_PRI_FIRST (最高)
NF_IP_PRI_CONNTRACK_DEFRAG
NF_IP_PRI_CONNTRACK
NF_IP_PRI_MANGLE
NF_IP_PRI_NAT_DST
NF_IP_PRI_FILTER
NF_IP_PRI_NAT_SRC
NF_IP_PRI_LAST (最低)
我们知道,LOCAL_OUTPRE_ROUTING点可以看作是netfilter的入口,而POST_ROUTINGLOCAL_IN可以看作是出口。也就是说,在每个数据包刚一进入netfilter之后首先都会调用ip_conntrack_defrag做分片处理,紧接着就是对收到的和发出的数据包分别进行ip_conntrack_inip_conntrack_loaclip_conntrack_loacl里还是调用了ip_conntrack_in)。而在数据包即将离开netfilter之前,会对进入主机的数据包进行ip_confirm处理,对发出的数据包进行ip_refrag处理(ip_refrag里也会调用ip_confirm)。这就是整个连接跟踪模块在netfilter中的分布情况。
另外,我们分析的是2.6.8的内核,在linux2.6.12中,这个地方还增加了两个hook,分别是ip_conntrack_helper_out_opsip_conntrack_helper_in_ops。将helper模块相关的处理提前了。 */
ret = nf_register_hook(&ip_conntrack_defrag_ops);
if (ret < 0) {
printk("ip_conntrack: can’t register pre-routing defrag hook.\n");
goto cleanup_proc;
}
ret = nf_register_hook(&ip_conntrack_defrag_local_out_ops);
if (ret < 0) {
printk("ip_conntrack: can’t register local_out defrag hook.\n");
goto cleanup_defragops;
}
ret = nf_register_hook(&ip_conntrack_in_ops);
if (ret < 0) {
printk("ip_conntrack: can’t register pre-routing hook.\n");
goto cleanup_defraglocalops;
}
ret = nf_register_hook(&ip_conntrack_local_out_ops);
if (ret < 0) {
printk("ip_conntrack: can’t register local out hook.\n");
goto cleanup_inops;
}
ret = nf_register_hook(&ip_conntrack_out_ops);
if (ret < 0) {
printk("ip_conntrack: can’t register post-routing hook.\n");
goto cleanup_inandlocalops;
}
ret = nf_register_hook(&ip_conntrack_local_in_ops);
if (ret < 0) {
printk("ip_conntrack: can’t register local in hook.\n");
goto cleanup_inoutandlocalops;
}
#ifdef CONFIG_SYSCTL
ip_ct_sysctl_header = register_sysctl_table(ip_ct_net_table, 0);
if (ip_ct_sysctl_header == NULL) {
printk("ip_conntrack: can’t register to sysctl.\n");
goto cleanup;
}
#endif
return ret;
cleanup:
#ifdef CONFIG_SYSCTL
unregister_sysctl_table(ip_ct_sysctl_header);
#endif
nf_unregister_hook(&ip_conntrack_local_in_ops);
cleanup_inoutandlocalops:
nf_unregister_hook(&ip_conntrack_out_ops);
cleanup_inandlocalops:
nf_unregister_hook(&ip_conntrack_local_out_ops);
cleanup_inops:
nf_unregister_hook(&ip_conntrack_in_ops);
cleanup_defraglocalops:
nf_unregister_hook(&ip_conntrack_defrag_local_out_ops);
cleanup_defragops:
nf_unregister_hook(&ip_conntrack_defrag_ops);
cleanup_proc:
proc_net_remove("ip_conntrack");
proc_net_remove("ip_clear_dnsconntrack");
cleanup_init:
ip_conntrack_cleanup();
cleanup_nothing:
return ret;
}
3.2 ip_conntrack_init() 函数
ip_conntrack_init 函数(ip_conntrack_core.c)用于初始化连接跟踪的包括hash表相关参数在内一些重要的变量:
int __init ip_conntrack_init(void)
{
unsigned int i;
int ret;
/* Idea from tcp.c: use 1/16384 of memory. On i386: 32MB
* machine has 256 buckets. >= 1GB machines have 8192 buckets. */
/* 如果指定hash表的大小则用制定值,否则根据内存计算 */
if (hashsize) {
ip_conntrack_htable_size = hashsize;
} else {
ip_conntrack_htable_size
= (((num_physpages << PAGE_SHIFT) / 16384)
/ sizeof(struct list_head));
if (num_physpages > (1024 * 1024 * 1024 / PAGE_SIZE))
ip_conntrack_htable_size = 8192;
if (ip_conntrack_htable_size < 16)
ip_conntrack_htable_size = 16;
}
ip_conntrack_max = 8 * ip_conntrack_htable_size;
#ifdef CONFIG_MIPS_BRCM
ip_conntrack_max=0;
#endif
printk("ip_conntrack version %s (%u buckets, %d max)"
" – %Zd bytes per conntrack\n", IP_CONNTRACK_VERSION,
ip_conntrack_htable_size, ip_conntrack_max,
sizeof(struct ip_conntrack));
/*注册socket选项*/
ret = nf_register_sockopt(&so_getorigdst);
if (ret != 0) {
printk(KERN_ERR "Unable to register netfilter socket option\n");
return ret;
}
/* hash表分配连续内存页 */
ip_conntrack_hash = vmalloc(sizeof(struct list_head)
* ip_conntrack_htable_size);
if (!ip_conntrack_hash) {
printk(KERN_ERR "Unable to create ip_conntrack_hash\n");
goto err_unreg_sockopt;
}
/* 分配高速缓存 */
ip_conntrack_cachep = kmem_cache_create("ip_conntrack",
sizeof(struct ip_conntrack), 0,
SLAB_HWCACHE_ALIGN,
NULL, NULL);
if (!ip_conntrack_cachep) {
printk(KERN_ERR "Unable to create ip_conntrack slab cache\n");
goto err_free_hash;
}
/* Don’t NEED lock here, but good form anyway. */
WRITE_LOCK(&ip_conntrack_lock);
/* netfilter中对每个要进行跟踪的IP协议定义了一个ip_conntrack_protocol结构,每个IP协议的连接跟踪处理就是要填写这样一个结构,这里的ip_conntrack_protocol_tcpip_conntrack_protocol_udp等都是这个结构,用list_append将这些需要跟踪的协议组织成链表 */
list_append(&protocol_list, &ip_conntrack_protocol_tcp);
list_append(&protocol_list, &ip_conntrack_protocol_udp);
list_append(&protocol_list, &ip_conntrack_protocol_icmp);
list_append(&protocol_list, &ip_conntrack_protocol_esp);
WRITE_UNLOCK(&ip_conntrack_lock);
/* 初始化hash */
for (i = 0; i < ip_conntrack_htable_size; i++)
INIT_LIST_HEAD(&ip_conntrack_hash);
/* For use by ipt_REJECT */
ip_ct_attach = ip_conntrack_attach;
/* Set up fake conntrack:
– to never be deleted, not in any hashes */
atomic_set(&ip_conntrack_untracked.ct_general.use, 1);
/* – and look it like as a confirmed connection */
set_bit(IPS_CONFIRMED_BIT, &ip_conntrack_untracked.status);
/* – and prepare the ctinfo field for REJECT & NAT. */
ip_conntrack_untracked.infos[IP_CT_NEW].master =
ip_conntrack_untracked.infos[IP_CT_RELATED].master =
ip_conntrack_untracked.infos[IP_CT_RELATED + IP_CT_IS_REPLY].master =
&ip_conntrack_untracked.ct_general;
return ret;
err_free_hash:
vfree(ip_conntrack_hash);
err_unreg_sockopt:
nf_unregister_sockopt(&so_getorigdst);
return -ENOMEM;
}
4. 协议的扩展,ip_conntrack_protocol结构
各种协议使用一个全局的协议列表存放,即protocol_listip_conntrack_core.h),使用结构ip_conntrack_protocol来表示(ip_conntrack_protocol.h
struct ip_conntrack_protocol
{
/* Next pointer. */
struct list_head list;
/* Protocol number. */
u_int8_t proto;
/* Protocol name */
const char *name;
/* 其指向函数的作用是将协议加入到ip_conntrack_tupledst子结构中*/
int (*pkt_to_tuple)(const struct sk_buff *skb,
unsigned int dataoff,
struct ip_conntrack_tuple *tuple);
/* 其指向函数的作用是将源和目的多元组中协议部分的值进行互换,包括IP地址、端口等 */
int (*invert_tuple)(struct ip_conntrack_tuple *inverse,
const struct ip_conntrack_tuple *orig);
/* 打印多元组中的协议信息*/
unsigned int (*print_tuple)(char *buffer,
const struct ip_conntrack_tuple *);
/* 打印整个连接记录*/
unsigned int (*print_conntrack)(char *buffer,
const struct ip_conntrack *);
/* 判断数据包是否合法,并调整相应连接的信息,也就是实现各协议的状态检测,对于UDP等本身是无连接的协议的判断比较简单,netfilter建立一个虚拟连接,每个新发包都是合法包,只等待回应包到后连接都结束;但对于TCP之类的有状态协议必须检查数据是否符合协议的状态转换过程,这是靠一个状态转换数组实现的。返回数据报的verdict */
int (*packet)(struct ip_conntrack *conntrack,
const struct sk_buff *skb,
enum ip_conntrack_info ctinfo);
/* 当此协议的一个新连接发生时,调用其指向的这个函数,调用返回true时再继续调用packet()函数*/
int (*new)(struct ip_conntrack *conntrack, const struct sk_buff *skb);
/* 删除一个连接状态*/
void (*destroy)(struct ip_conntrack *conntrack);
/* 判断是否有数据报匹配预期的连接*/
int (*exp_matches_pkt)(struct ip_conntrack_expect *exp,
const struct sk_buff *skb);
/ * 指向模块本身,统计模块是否被使用 */
struct module *me;
};
要编写自己的IP协议跟踪模块,先要分析这些协议头中哪些信息可以用来唯一识别连接,作NAT时要修改哪些信息,把这些信息添加到ip_conntrack_tuple结构的联合中;然后填写该协议的ip_conntrack_protocol结构,实现结构中的内部函数;最后在ip_conntrack_init()函数中用list_append()将此结构挂接到协议跟踪链表中,也可以在各自的模块初始化时用ip_conntrack_protocol_register()ip_conntrack_protocol_unregister()来添加/删除协议。

关于php的mysql_query

mysql_query($sql,[$link]); 其中第二个参数是缺省的,但是使用中发现最好还是不要缺省;如果php.ini 里配置了正确的mysql的主机名、用户名、密码的话,你不需要mysql_connect(); 直接执行如下代码都是可以的:

<?php
    ini_set
(‘mysql.trace_mode’,true
);
    
$result mysql_query(‘select 1+1’
);
    
print_r(mysql_fetch_assoc($result
));
    
mysql_free_result($result
);
?>

这样一来,如果mysql_query() 不使用第二个参数,我们的php.ini 有没有配置正确的mysql链接信息,当我们使用mysql_connect() 产生的连接丢失后,我们将会发现mysql却在试图连接一个我们不知道的地方(就是php.ini里配置的地方)

小提示mysql_query() 的结果集是需要用mysql_free_result释放的,否则将产生一个警告(Warning: Unknown: 1 result set(s) not freed. Use mysql_free_result to free result sets which were requested using mysql_query() in Unknown on line 0)只是这个警告在mysql.trace_mode 为off时时不显示的,所以我们可能会很容易忽略mysql_free_result的使用

HTTP Keep Alive

HTTP Keep Alive

Http Keep-Alive seems to be massively misunderstood. Here’s a short description of how it works, under both 1.0 and 1.1, with some added information about how Java handles it.

Http operates on what is called a request-response paradigm. This means that a _client_ generates a request for information, and passes it to the server, which answers it. In the original implementation of HTTP, each request created a new socket connection to the server, sent the request, then read from that connection to get the response.

This approach had one big advantage – it was simple. Simple to describe, simple to understand, and simple to code. It also had one big disadvantage – it was slow. So, keep-alive connections were invented for HTTP.
HTTP/1.0

Under HTTP 1.0, there is no official specification for how keepalive operates. It was, in essence, tacked on to an existing protocol. If the browser supports keep-alive, it adds an additional header to the request:

Connection: Keep-Alive

Then, when the server receives this request and generates a response, it also adds a header to the response:

Connection: Keep-Alive

Following this, the connection is NOT dropped, but is instead kept open. When the client sends another request, it uses the same connection. This will continue until either the client or the server decides that the conversation is over, and one of them drops the connection.
HTTP/1.1

Under HTTP 1.1, the official keepalive method is different. All connections are kept alive, unless stated otherwise with the following header:

Connection: close

The Connection: Keep-Alive header no longer has any meaning because of this.

Additionally, an optional Keep-Alive: header is described, but is so underspecified as to be meaningless. Avoid it.
Not reliable

HTTP is a stateless protocol – this means that every request is independent of every other. Keep alive doesn抰 change that. Additionally, there is no guarantee that the client or the server will keep the connection open. Even in 1.1, all that is promised is that you will probably get a notice that the connection is being closed. So keepalive is something you should not write your application to rely upon.
KeepAlive and POST

The HTTP 1.1 spec states that following the body of a POST, there are to be no additional characters. It also states that "certain" browsers may not follow this spec, putting a CRLF after the body of the POST. Mmm-hmm. As near as I can tell, most browsers follow a POSTed body with a CRLF. There are two ways of dealing with this: Disallow keepalive in the context of a POST request, or ignore CRLF on a line by itself. Most servers deal with this in the latter way, but there’s no way to know how a server will handle it without testing.
Java abstraction – client side

On the client side, Java abstracts the notion of a keepalive request away from the programmer. The HttpURLConnection class implements keepalive automatically, without intervention being either required or possible on the part of the programmer. It does this through an internal cache of client connections, which is maintained as one of the implementation details of the class. Incidentally, this means that keepalive is an implementation detail of the Javasoft class library, which may or may not be available in other class libraries.
Java abstraction – server side

On the server side, Java again abstracts out the notion of a keepalive request. The HttpServlet, HttpServletRequest, and HtppServletResponse classes implement keepalive automatically. In this case, however, some intervention is possible. As mentioned in the KeepAliveServlet that comes with the JavaWebServer, the actual keepalive behavior that occurs depends on two factors: setting content length, and size of output. If content length is set as part of the response, keepalive will be used if the client supports it. If content length is not set, the servlet implementation will attempt to buffer the response to determine it’s content length. If the buffering is successful, keepalive will be used. In the Javasoft implementation, a 4k buffer is used. This means that if the content length is not set, and the amount of data returned is over 4k, keepalive will not be used. As with HttpURLConnection, this is an implementation detail of the Javasoft class library, which may or may not be available in other class libraries.

SO_KEEPALIVE包由TCP处理,而Keep-Alive由HTTP处理,keep-alive是需要客户端支持,so_KEEPALIVE是利用了tcp的双向全双工连接特性,在web-server端可以不断开连接,来接受客户端更多的请求,应该是能够改善连接时间,不过似乎在连接非常多的情况下会大大加重服务器负担,需要tcp/ip协议盏支持,并且不同客户端之间有不同的设定
在观测时发现以下现象:当keepalive off时的值与keepalive on时的值比较得以下结果:

ps -ef|grep httpd|wc -l的值小。
netstat -an | grep :80 |wc -l的值大。
 

摘自:http://hi.baidu.com/toughguy/blog/item/626410fb51abfa176d22eb38.html

轻量级 Web 服务器

最近几年,市场上出现了很多有趣的 Web 服务器实现,包括 lighthttpd、litespeed 和 mongrel 等。这些 Web 服务器都宣称结合了性能、易管理性、可移植性、安全性和其他相关价值。下面的工程研究将调查轻量级 Web 服务器,以帮助您选择最可能满足下一个项目的技术需求的 Web 服务器。

“轻量级” Web 服务器,例如 lighthttpdlitespeedmongrel,可以为项目带来很多的好处。本文调查这种可能性,并展示这些 Web 服务器的适用性。

第一个重要的方面是清楚地理解所调查的领域(请参阅 参考资料,以了解更详细的信息)。终端用户在 Internet 上的基本动作就是 “进入一个 Web 页面”。从大处讲,这牵涉到两个应用程序之间的协作:

        

  • 一个 Web 浏览器,例如 Firefox 或 Internet Explorer,用于请求一个特定的页面,并且以人类可读的方式显示从另一个应用程序那里收到的内容。
  •     

  • 一个 Web 服务器,通常是在远程机器上,负责对页面请求作出响应,返回 HTML 编码的或类似的数据流。

所有 Web 用户直接与浏览器交互,因此他们的选择和分析相应地有些狂热。而服务器只对站点的技术人员可见。根据 Netcraft 最近的调查,虽然存在很多不同的 Web 服务器,但是其中两种 Web 服务器就占据了 90% 的份额,这两种 Web 服务器是 Apache 和 Internet Information Server (IIS)。它们都是经过高度锤炼的产品,并且声称不仅具有广泛的内在技术特性,而且有很多配套的书籍、增件、顾问、提供商等。那么,它们是否还有值得改造的地方呢?

答案是肯定的。评价一个 Web 服务器的重要指标有:

        

  • 性能:对请求作出响应的速度有多快?
  •     

  • 可伸缩性:当很多用户同时访问它时,服务器还能继续可靠地运行吗?
  •     

  • 安全性:服务器是否只执行它应该执行的操作。它在认证用户和加密传输方面提供了怎样的支持?它的使用是否使附近的应用程序或主机变得更易受攻击?
  •     

  • 可靠性:服务器的失效模式和故障发生率如何?
  •     

  • 标准遵从性:服务器遵从相关的 RFC 吗?
  •     

  • 灵活性:是否可以对服务器进行调优,以支持较重的请求负载、需要计算的动态页面或者代价不菲的认证等等?
  •     

  • 平台需求:该服务器可用于哪些平台?它是否有特定的硬件需求?
  •     

  • 易管理性:服务器是否易于设置和维护?它是否与日志记录、审计、成本计算等组织标准兼容?

Apache 和 IIS 不能同时在那么多的标准方面做到最好。理论上讲,显然那些定向的产品至少能在以上的一至两个方面超越市场领头产品。

关于轻量级 Web 服务器的一件有趣的、值得调查的事情是,它们之间的竞争远远不止是理论上的:仔细研究表明,它们有很多 东西可以提供,并且即使在很多常见的情况下,它们相对于 Apache 和 IIS 也坚持了自己的风格。虽然可以合理地认为市场领头产品已经经过了小心的优化,从而能够有效地在性能(举个例子)方面避免被击败,但是很多小型的竞争对手因为只提供简单的静态 Web 页面服务,速度反而更快。当使用这些 Web 服务器运行测试时,您会感觉好像是在赛道上驾驶一辆 go-kart 小车,不知不觉竟然超过了 Porsche 和 Viper 车。这还不是全部:有时候,轻量级 Web 服务器可作为那些大哥级服务器的有效补充,而不只是与它们竞争。即使您知道自己将使用 Apache,有时候通过将它与一个轻量级伙伴搭档,反而可以最大限度地利用它。最好的解决方案有时候需要两个或更多 Web 服务器的协作。

Web 服务的轻巧性

本调查中重点关注的 “轻巧性” 实际上是一种主观质量,就像 “艺术” 或 “风格”。它通常意味着简单、易于安装、流线化、要求低和健壮 —— 比 Apache 和 IIS 更小、更简单,当然,在试图满足大量市场的过程中,它们已经变得异常复杂。出于这个目的,虽然 Java Web Server、AOLserver 和 Zeus 拥有迷人的可移植性和性能优势,但是它们的复杂性和大小使其不得不被拒之门外。

轻量级 Web 服务器可以适用于市场领头产品和其他 “重量级” 服务器无法胜任的情况。例如,整个服务器可以打包在一个文件中。这意味着开发人员可以方便地携带生产环境所需的所有工具。即使在生产服务器上运行的是 Apache,也仍然可以在宾馆的房间里,借助只需数秒钟就可以安装完毕的轻量级 Web 服务器以尝试新想法。而且,由于轻量级 Web 服务器要求很低,因此可以在那些无法负担 IIS 的主机上顺畅地运行。

单文件打包

    

        

            

            

        

    

            

                

                    

                        

                    

                

            

单文件打包
                        Apache 需要小心地安装散布在多个目录中的很多文件。与之截然不同的是,下面的 Web 服务器却打包在一个可执行文件中。我的一个雇主 Phaseit 的专长是部署和打包,我们能使 Apache 的安装看上去比平常更简单一些。但是即使我们做得最好,Apache 或 IIS 与轻量级 Web 服务器在 “空间占用” 方面也仍然有很大的差异:前者要占用大量的空间。

            

小的、轻量级的 Web 服务器还可以在小功率的主机上良好地运行。在我们的公司(Phaseit —— 见 侧栏)中,我们在远程的、条件欠佳或配置较低的环境中的工业计算机上运行专用的 硬件。在这些情况下,能够通过一个对处理能力或磁盘空间要求很低的应用程序来提供 Web 页面是一个很大的优势。这意味着我们的机器可以避免 Apache 的开发和处理能力所带来的开销,构建基于 Web 的管理控制台。

从某种程度上讲,几乎所有轻量级 Web 服务器都是开放源码的。如果我们需要某一款 Web 服务器所特有的行为,那么下面概述的一些 Web 服务器都非常小巧,易于理解,也易于增强,只有两个例外。这些 Web 服务器为嵌入 Web 服务的项目提供极好的原始材料,不管这些 Web 服务是在特殊的硬件中,还是在为在通用计算机上运行而设计的特定应用程序中。它们还广泛用于具有传统外观的 Web 站点:

        

  • YouTube 依靠 lighttpd 快速交付归档的内容,例如视频;
  •     

  • cdServe 运行 “German Woodworking Machinery and Tools” CD;
  •     

  • LiteSpeed 宣扬它在 twitter、www.funnyoride.com、www.airliners.com、WordPress.com、fanfiction.com、SlashGear、www.forumactif.com 和其他著名 Web 站点上担任的角色;
  •     

  • OpenSUSE、RubyOnRails、MarkaBoo 和其他一些著名站点依赖于 Mongrel;
  •     

  • demon.net、bluelight.com、mtv.com、The Drudge Report、garfield.com 等站点则使用 thttpd;
  •     

  • 等等。

下面的例子说明了开发人员使用轻量级服务器的轻巧性:在我们公司,我们采用专门的硬件提供办公室电话解决方案。它基于定制的、以传统的 Linux® 应用程序的形式运行的软件。只需一个附加文件和一点 init.d 配置,很容易添加一个强大的 “Web 控制台”,该 Web 控制台能提供硬件和软件的管理界面。 终端用户可以从任何浏览器中监视和配置他们的计算机,而不必安排专门的硬件连接或解决使用 “垂直” 硬件时常见的其他复杂性。

面向服务架构(SOA)被认为难以使用。在我们的经验中,SOA 至少有一部分这方面的缺点阻碍了 Web 服务的使用。我们利用轻量级 Web 服务来设置快速的 SOA,以进行演示。

轻量级服务器甚至可以用于生产数据中心,包括前面列出的 high-profile 站点。性能非常高的站点会将操作分开,从而最大限度地利用缓存、代理等技术。例如,一个基于 Apache 的站点可能采用一种这样的架构:通过小型的 Web 服务器从专用的文件系统提供缓慢变化的图片。终端用户看到的结果实际上是 Apache 和一个或多个辅助 Web 服务器通过协作得到的输出,它们各自担任自己擅长的角色。这样的安排可以以非常低的计算成本提供非常 快的结果。

虽然轻量级 Web 服务器有很多共同之处,但是各有各的不同。大多数轻量级 Web 服务器是用 C 编写的,但是实践证明,有些其他实现语言也可以成功地用于实现服务器,对此我已经做了实验,这些语言包括 Erlang、Java、Lisp、Lua、Perl、Python 和 Tcl。如果其中有您喜欢的语言,那么也许可以找到适合您的 Web 服务器。

由于很多特定的原因,您可能会将目光投向某种 “不常见” 的语言:

        

  • 教学:使用轻量级 Web 服务器来制定一个重要、但是并不太大的目标。这是获得使用某种语言的经验的好方法。
  •     

  • 虽然用 C 编写的轻量级 Web 服务器大小为 10-50 KB,更高级的语言有 100 KB 到数 MB 的运行时,但整个 Web 服务器的源文件可能只占几千个字节。这种 Web 服务器占用的空间很小,因此比 Apache 更易于与技术伙伴共享。
  •     

  • 更高级的语言可以使实验更吸引人 —— 例如,添加一个新的 HTTP/1.1 特性可能只需几行源代码。这些轻量级服务器是非常方便的实验材料。
  •     

  • 将 HTTP 服务器添加到已有的、用高级语言编写的应用程序中只需增加几行源代码。

Athana 可以作为这些主题的例子。它是用 Python 编写的 Web 服务器。它支持 HTTP 多部分(上传)、会话、 cookie 等。从 0.2.1 版开始,Athana 一直被编写在一个单独的、精心组织的源文件中。

如前所述,不同的轻量级 Web 服务器有着不同的优点,它们或多或少独立于编程语言。所有轻量级 Web 服务器都比 Apache 更小、更易于配置。与 Apache 相比,有些轻量级 Web 服务器更快,有些则快得多。有些则强调安全性、重负载下的从容性、可扩展性或者内存占有量。在任何情况下,都可以以一种不适用于 Apache 的方式彻底地理解这些服务器。

哪些特定的产品使这些可能性成为现实?即使只留意 “轻量级” 服务器,面对的也是一个很大的难于管理的产品集合。不过可以将它们按子类来划分:超轻型、关注安全型、支持特定语言型等等。

我特别喜欢其中的超轻型 Web 服务器,它们比 Apache 小得多。如此小的应用程序可以直接记住,系统地、严密地加以考虑,以证明 它们的安全性或可伸缩性。小型 Web 服务器包括:

        

  • Cheetah Server,用不到一千行的 C 代码编写而成。
  •     

  • DustMote,一个非常 小的 Web 服务器,用一个大约 3000 字节的 Tcl 源文件实现。
  •     

  • fnord,大小取决于平台和配置,不超过 20K。虽然很小,但是它支持虚拟主机、CGI 和 keep-alive。
  •     

  • ihttpd,使用不到 800 行的 C 代码,包括 CGI,并通过 inetd 提供页面。
  •     

  • im-httpd,非常小的服务器 —— 只有大约 7 KB,链接到 glibc。而且它也非常快。
  •     

  • mattows,支持 CGI,只有 600 行 C 代码。
  •     

  • Scrinchy,虽然很小,不到 30KB,但是支持多种脚本编制语言,包括一种特殊用途的、基于栈的 Sy 脚本语言。
  •     

  • ZWS 演示了一个即使是使用 500 多行带足够注释的 zsh (!) 编写的应用程序 —— 在这里是一个 HTTP 0.9+ 服务器 —— 也可以有多强大。

体积小并不妨碍这些服务器被正式使用。例如,fnord 可以处理数千个同时进行的连接。

 

也许轻量级作为一个类别最令人印象深刻的成就是高性能服务器:

        

  • cghttpd 是一个小型 Web 服务器,它被理解为使用 2.6 系列内核中可用的异步功能的一个试验品。
  •     

  • darkhttpd 是一个快速的、单线程的 HTTP/1.1 服务器。
  •     

  • Gatling 是为高性能设计的。它的特性包括 FTP、IPv6、虚拟主机、CGI 等。
  •     

  • Kernux 是一个 Linux 内核模块,它实现了一个 HTTP 守护进程。
  •     

  • lighttpd 是使用率排名第五的 Web 服务器(排名还在上升)。它为很多同时进行的连接进行了优化:“典型的场景是使用 lighttpd 作为一个下载(off-load)服务器,以提供静态内容……”
  •     

  • LiteSpeed Web Server 是一款轻量级商业 Web 服务器,强调性能和安全性。 LiteSpeed Technologies 公司宣传为静态内容提速了 6 倍,在解释页面方面也有一定的提高。
  •     

  • Miniature JWS,也称 tjws,它是基于 Java 的 Web 服务器,可以处理 servlet、JSP 和数千个并发连接,而大小只有 77 KB。它的作者声称它 “比 Apache 2.x 快 10%”。
  •     

  • Yaws 是用 Erlang 编写的一款高性能 HTTP/1.1 服务器。

 

有些 Web 服务器被实现为类或库,以便嵌入 到较大的应用程序中。 在这些 Web 服务器当中,我发现特别有趣的有:

        

  • EHS —— “嵌入式 HTTP 服务器”,被设计为一个 C++ 类,用于嵌入到较大的 C++ 应用程序;还有
  •     

  • Embedded TCL Web Server,它是一个很普通的 Web 服务器,支持 SSL 和 Basic Authentication,速度非常快 —— 其作者使它至少与 lighthttpd 和 AOLserver 一样快。它是用不到 100 行 Tcl 编写的。

 

Python 是几种适合不寻常环境的 Web 服务器的实现语言,这些 Web 服务器包括:

        

  • cdServer 是一个小型的、用 Python 编写的 HTTP 服务器,它 “被设计用来提供来自 CD-ROM 的(静态)内容” 。它在提供动态内容方面能力有限。我们有几个涉及不受影响的 “live CDs” 的项目,在这些项目中像 cdServer 之类的工具很关键。
  •     

  • edna,一款智能的用 Python 编写的 MP3 服务器,它是用 HTTP 实现的。

 

还有其他一些用 Perl 和其他不出名的语言编写的轻量级 Web 服务器:

        

  • Camlserv,用 ocaml 编写的一个完整的 Web 服务器,目标是 “高度交互式的 Web 页面”。它由几千行 ocaml 编写而成,其中大部分代码都与 MySQL 和 HTML 的特殊处理有关。
  •     

  • dhttpd 用和 Apache 相同的格式记录访问。它支持 CGI,并具有内建的 Perl 解释器、虚拟主机、IPv6、带宽管理和安全性等方面的特性。
  •     

  • DNHTTPD 是用 Perl 编写的,用于 UNIX®。它支持虚拟主机、SSL 连接、CGI 等。
  •     

  • Jellybean 是用 Perl 编写的基于 HTTP 的 Perl Object Server。
  •     

  • lns.http 是一个 Common LISP HTTP/1.1 Web 框架。
  •     

  • Mongrel 是用 Ruby 编写的、用于 HTTP 的一个库和服务器。
  •     

  • Nanoweb 是用 PHP 编写的一款快速、健壮的 Web 服务器。它宣称具有丰富的特性,包括完全遵从 HTTP/1.1、访问控制、身份验证、虚拟主机、SSL 兼容性等。
  •     

  • Naridesh 是用 Perl 编写的 Web 服务器。
  •     

  • OpenAngel 是用 Perl 编写的。它强调的重点是安全性。
  •     

  • Xavante 是用 Lua 编写的 HTTP/1.1 Web 服务器。
  •     

  • XSP 是用 C# 编写的,用于运行 ASP.NET。

 

有时候您可能需要其他一些用 C 编写的、具有不常见的次要优势的轻量级 Web 服务器:

        

  • ABYSS 可以在 UNIX 和 Win32 之间移植,其 “目的是成为完全遵从 HTTP/1.1 的 Web 服务器”。它占用的内存很少。
  •     

  • Anti-Web HTTPD(也称 “Anti-Web”、“awhttpd” 和 “AW”)是一款单进程、无线程、支持 CGI 的服务器,它强调安全性和简单性。
  •     

  • MHTTPD 支持从外部文件或 LDAP 服务器进行的 MHTTPD Basic Authentication。
  •     

  • mini-httpd 可以在一个系统线程中处理多个并发请求,但是在主机上占用的内存或 CPU 很少。
  •     

  • Naken Web 类似于很多其他的轻量级服务器 —— 它支持 Basic Authentication、静态内容等 —— 但是它的作者将它设计为用于 Webcam 操作,并且在 Gumstix、WRT54GL、OpenWrt 和其他新的平台上运行。
  •     

  • Null httpd 是一款多线程的、简单的、可移植的 Web 服务器。
  •     

  • Seminole 是一款商业 Web 服务器,内存需求较小,功能较多。
  •     

  • thttpd throttle,支持 chroot、 Basic Authentication 等。
        
        

Web 服务器远远不止是 Apache 和 IIS 的天下。您可以发现很多其他的 Web 服务器,它们很小,易于理解,但是又足够快,可以被正式使用。这样的 Web 服务器可以很好地加快您的下一个项目。

学习

获得产品和技术

Cameron Laird 长期为 developerWorks 撰稿,并且曾担任专栏作家。他经常撰写关于能加快其雇主的应用程序开发的开放源代码项目的文章,着重关注可靠性和安全性。

 摘自:http://www.ibm.com/developerworks/cn/web/wa-ltwebserv/

莫做电脑的奴隶

一年多来,一直在没日没夜地研究计算机技术,只希望能多学一些东西能挣很多钱;一年过去了,工资还是没有涨,而我却成了计算机的奴隶,没有计算机将无法工作,没有计算机也就没有了乐趣,没有了计算机我会疯掉的。

除了多知道一些计算机的东西,我什么也不会;买东西只能去超市,和人打交道,我注定是要失败的。我只了解计算机,不了解人。我下一步要做的就是,少了解计算机,多了解人,多参加一些公共场合的活动,尽量里计算机远一些。

关于内存泄漏

转自:http://baike.baidu.com/view/1068433.htm

内存泄漏

  也称作“存储渗漏

  用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元。直到程序结束。即所谓内存泄漏。

  内存泄漏概念

  简单的说就是申请了一块内存空间,使用完毕后没有释放掉。它的一般表现方式是程序运行时间越长,占用内存越多,最终用尽全部内存,整个系统崩溃。由程序申请的一块内存,且没有任何一个指针指向它,那么这块内存就泄露了。

  泄漏的分类

  以发生的方式来分类,内存泄漏可以分为4类:

  (1). 常发性内存泄漏。发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏。

  (2). 偶发性内存泄漏。发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要。

  (3). 一次性内存泄漏。发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块仅且一块内存发生泄漏。比如,在类的构造函数中分配内存,在析构函数中却没有释放该内存,所以内存泄漏只会发生一次。

  (4). 隐式内存泄漏。程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏。

  从用户使用程序的角度来看,内存泄漏本身不会产生什么危害,作为一般的用户,根本感觉不到内存泄漏的存在。真正有危害的是内存泄漏的堆积,这会最终消耗尽系统所有的内存。从这个角度来说,一次性内存泄漏并没有什么危害,因为它不会堆积,而隐式内存泄漏危害性则非常大,因为较之于常发性和偶发性内存泄漏它更难被检测到。

  内存泄漏的表现

  

内存泄漏或者是说,资源耗尽后,系统会表现出什么现象哪?

  cpu资源耗尽:估计是机器没有反应了,键盘,鼠标,以及网络等等。这个在windows上经常看见,特别是中了毒。

  进程id耗尽:没法创建新的进程了,串口或者telnet都没法创建了。

  硬盘耗尽: 机器要死了,交换内存没法用,日志也没法用了,死是很正常的。

  内存泄漏或者内存耗尽:新的连接无法创建,free的内存比较少。发生内存泄漏的程序很多,但是要想产生一定的后果,就需要这个进程是无限循环的,是个服务进程。当然,内核也是无限循环的,所以,如果内核发生了内存泄漏,情况就更加不妙。内存泄漏是一种很难定位和跟踪的错误,目前还没看到有什么好用的工具(当然,用户空间有一些工具,有静态分析的,也会动态分析的,但是找内核的内存泄漏,没有好的开源工具)

  内存泄漏和对象的引用计数有很大的关系,再加上c/c++都没有自动的垃圾回收机制,如果没有手动释放内存,问题就会出现。如果要避免这个问题,还是要从代码上入手,良好的编码习惯和规范,是避免错误的不二法门。

  一般我们常说的内存泄漏是指堆内存的泄漏。

  堆内存是指程序从堆中分配的,大小任意的(内存块的大小可以在程序运行期决定),使用完后必须显示释放的内存。

  应用程序一般使用malloc,realloc,new等函数从堆中分配到一块内存,使用完后,程序必须负责相应的调用free或delete释放该内存块,否则,这块内存就不能被再次使用,我们就说这块内存泄漏了。

  (附)部分内存泄漏检测工具

  1.ccmalloc-Linux和Solaris下对C和C++程序的简单的使用内存泄漏和malloc调试库。

  2.Dmalloc-Debug Malloc Library.

  3.Electric Fence-Linux分发版中由Bruce Perens编写的malloc()调试库。

  4.Leaky-Linux下检测内存泄漏的程序。

  5.LeakTracer-Linux、Solaris和HP-UX下跟踪和分析C++程序中的内存泄漏。

  6.MEMWATCH-由Johan Lindh编写,是一个开放源代码C语言内存错误检测工具,主要是通过gcc的precessor来进行。

  7.Valgrind-Debugging and profiling Linux programs, aiming at programs written in C and C++.

  8.KCachegrind-A visualization tool for the profiling data generated by Cachegrind and Calltree.

  9.IBM Rational PurifyPlus-帮助开发人员查明C/C++、托管.NET、Java和VB6代码中的性能和可靠性错误。PurifyPlus 将内存错误和泄漏检测、应用程序性能描述、代码覆盖分析等功能组合在一个单一、完整的工具包中。

  10.Parasoft Insure++-针对C/C++应用的运行时错误自动检测工具,它能够自动监测C/C++程序,发现其中存在着的内存破坏、内存泄漏、指针错误和I/O等错误。并通过使用一系列独特的技术(SCI技术和变异测试等),彻底的检查和测试我们的代码,精确定位错误的准确位置并给出详细的诊断信息。能作为Microsoft Visual C++的一个插件运行。

  11.Compuware DevPartner for Visual C++ BoundsChecker Suite-为C++开发者设计的运行错误检测和调试工具软件。作为Microsoft Visual Studio和C++ 6.0的一个插件运行。

  12.Electric Software GlowCode-包括内存泄漏检查,code profiler,函数调用跟踪等功能。给C++和.Net开发者提供完整的错误诊断,和运行时性能分析工具包。

  13.Compuware DevPartner Java Edition-包含Java内存检测,代码覆盖率测试,代码性能测试,线程死锁,分布式应用等几大功能模块。

  14.Quest JProbe-分析Java的内存泄漏。

  15.ej-technologies JProfiler-一个全功能的Java剖析工具,专用于分析J2SE和J2EE应用程序。它把CPU、执行绪和内存的剖析组合在一个强大的应用中。

  16.BEA JRockit-用来诊断Java内存泄漏并指出根本原因,专门针对Intel平台并得到优化,能在Intel硬件上获得最高的性能。

PHP数组求差 array_diff 与 array_diff_assoc

程序修改了之后,需要和以前的输出做比较,就是用了数组的求差函数array_diff() ,以前没用过,求出来的东西和我想要的不太一样,因为数组比较大,不太好看出问题,但是删除一部分之后,结果就正常了,终于在花费了3个小时的时间,发现了问题的所在;我渴望array_diff() 能比较key和value,而实际上它只比较了value,而不关心key,如果关心key的话,可以使用函数array_diff_assoc();

另外,php数组求交集的函数:

array_intersect — 计算数组的交集,只关心value,不关心key
array_intersect_assoc — 带索引检查计算数组的交集