xmpp使用util/pool作为内存池, 相比于常见的内存池模式, xmpp的pool使用了一种个性的设计: 一个pool为一个对象而生, 而对象随着pool的消亡而释放, 一个程序里可能有成百数千的pool, 每个pool管理一个小小的对象, 并且分配过的内存是不能重用的, 也没必要一点点的释放, 把整个pool销毁是唯一的释放方法.
1, 数据结构:
pool_cleanup_t是内存的释放回调函数, 负责真正的free内存.
pheap是存储内存块的结构体, 每个malloc返回的内存对应一个pheap, size表示malloc(size)的size, used表示使用了多少.
pfree是内存释放链表, 每个结点里存储了一个pheap以及对应的pool_cleanup_t, 后者负责前者的释放.
pool_t就是内存池了, 其中heap表示当前可用的内存块, cleanup与cleanup_tail表示内存释放链表的头与尾, size表示内存池至今总共分配过的内存.
到底pool是怎么工作的呢? 简单的说, 按heap分配内存并压入pfree链表, 内存分配从heap中切割得到, 最终遍历pfree链表释放所有分配过的heap.
/** * pool_cleanup_t - callback type which is associated * with a pool entry; invoked when the pool entry is * free'd **/ typedef void (*pool_cleanup_t)(void *arg); /** * pheap - singular allocation of memory **/ struct pheap { void *block; int size, used; }; /** * pfree - a linked list node which stores an * allocation chunk, plus a callback **/ struct pfree { pool_cleanup_t f; void *arg; struct pheap *heap; struct pfree *next; }; /** * pool - base node for a pool. Maintains a linked list * of pool entries (pfree) **/ typedef struct pool_struct { int size; struct pfree *cleanup; struct pfree *cleanup_tail; struct pheap *heap; #ifdef POOL_DEBUG char name[8], zone[32]; int lsize; #endif } _pool, *pool_t;
2, API实现分析:
先贴出所有暴露给用户的API:
JABBERD2_API pool_t _pool_new(char *file, int line); /* new pool :) */ JABBERD2_API pool_t _pool_new_heap(int size, char *file, int line); /* creates a new memory pool with an initial heap size */ JABBERD2_API void *pmalloc(pool_t, int size); /* wrapper around malloc, takes from the pool, cleaned up automatically */ JABBERD2_API void *pmalloc_x(pool_t p, int size, char c); /* Wrapper around pmalloc which prefils buffer with c */ JABBERD2_API void *pmalloco(pool_t p, int size); /* YAPW for zeroing the block */ JABBERD2_API char *pstrdup(pool_t p, const char *src); /* wrapper around strdup, gains mem from pool */ JABBERD2_API char *pstrdupx(pool_t p, const char *src, int len); /* use given len */ JABBERD2_API void pool_stat(int full); /* print to stderr the changed pools and reset */ JABBERD2_API void pool_cleanup(pool_t p, pool_cleanup_t fn, void *arg); /* calls f(arg) before the pool is freed during cleanup */ JABBERD2_API void pool_free(pool_t p); /* calls the cleanup functions, frees all the data on the pool, and deletes the pool itself */ JABBERD2_API int pool_size(pool_t p); /* returns total bytes allocated in this pool */
1, _pool_new与_pool_new_heap: 这俩是一组的, 前者不预分配内存, 后者预分配内存, 但显然命名容易令人误入歧途.
前者创建pool_t结构体, pfree(回收内存链表), heap(切割内存的堆), size(总分配尺寸)设置为0, 然后就返回了.
后者调用前者创建pool_t结构体, 但紧随其后预分配了一个heap存储到pool_t->heap中, 显然有了内存池的效果, 但并不是说前者创建的pool就没法用, 后面继续看.
/** make an empty pool */ pool_t _pool_new(char *zone, int line) { pool_t p; while((p = _pool__malloc(sizeof(_pool))) == NULL) sleep(1); p->cleanup = NULL; p->heap = NULL; p->size = 0; #ifdef POOL_DEBUG p->lsize = -1; p->zone[0] = '\0'; snprintf(p->zone, sizeof(p->zone), "%s:%i", zone, line); sprintf(p->name,"%X",(int)p); if(pool__disturbed == NULL) { pool__disturbed = (xht)1; /* reentrancy flag! */ pool__disturbed = xhash_new(POOL_NUM); } if(pool__disturbed != (xht)1) xhash_put(pool__disturbed,p->name,p); #endif return p; } pool_t _pool_new_heap(int size, char *zone, int line) { pool_t p; p = _pool_new(zone, line); p->heap = _pool_heap(p,size); return p; }
既然用到了_pool_heap, 接着看它实现:先malloc一个pheap结构体, 再malloc一块预分配的内存到phead->block, 并调用_pool_free函数把一个pfree结点插入到pool->cleanup链表中, 其中内存释放回调函数是_pool_heap_free, pheap作为_pool_free第三个参数传入, 并在将来释放时作为typedef void (*pool_cleanup_t)(void *arg);
的参数传入以便释放使用.
/** create a heap and make sure it get's cleaned up */ static struct pheap *_pool_heap(pool_t p, int size) { struct pheap *ret; struct pfree *clean; /* make the return heap */ while((ret = _pool__malloc(sizeof(struct pheap))) == NULL) sleep(1); while((ret->block = _pool__malloc(size)) == NULL) sleep(1); ret->size = size; p->size += size; ret->used = 0; /* append to the cleanup list */ clean = _pool_free(p, _pool_heap_free, (void *)ret); clean->heap = ret; /* for future use in finding used mem for pstrdup */ _pool_cleanup_append(p, clean); return ret; }
2, 接着是最重要的分配内存函数: 其余接口均通过此接口获取内存, 然后完成进一步操作. 首先, 如果这不是一个预分配内存的pool或者 尺寸>预分配的内存/2, 那么pool直接
malloc一块全新的内存返回, 当然要在pool的cleanup链表中记录下来内存地址以便后续释放(_pool_cleanup_append). 如果pool->heap足够分配, 那么首先将当前heap指针调整到8字节对齐, 否则将地址起始的内存解释为大于4字节的变量时(包括int, long, >4字节的结构体等), 将可能引起bus error(总线错误), 这在内存池的设计里是必须考虑的, 已经见惯不惯了.
注意这里是调整heap的起始地址到8倍数, 而不是调整请求的size到8倍数, 我习惯后者, 但效果是一样的. 最后, 判断当前pool->heap剩余内存是否足够, 如果不够则丢弃heap剩余部分, 直接_pool_heap分配一块新的heap, 与原先的那块heap一样长, 然后从heap里割一块内存返回.
void *pmalloc(pool_t p, int size) { void *block; if(p == NULL) { fprintf(stderr,"Memory Leak! [pmalloc received NULL pool, unable to track allocation, exiting]\n"); abort(); } /* if there is no heap for this pool or it's a big request, just raw, I like how we clean this :) */ if(p->heap == NULL || size > (p->heap->size / 2)) { while((block = _pool__malloc(size)) == NULL) sleep(1); p->size += size; _pool_cleanup_append(p, _pool_free(p, _pool__free, block)); return block; } /* we have to preserve boundaries, long story :) */ if(size >= 4) while(p->heap->used&7) p->heap->used++; /* if we don't fit in the old heap, replace it */ if(size > (p->heap->size - p->heap->used)) p->heap = _pool_heap(p, p->heap->size); /* the current heap has room */ block = (char *)p->heap->block + p->heap->used; p->heap->used += size; return block; }
3, 内存池的释放:
很简单, 遍历pool->cleanup链表, 回调每个pfree结点的回调函数, 传入分配的内存地址, 之后释放pfree结点自身的内存, 并在遍历完成后释放pool自身内存.
(多提一点, pool_cleanup_append和pool_cleanup是一套函数, 都是向pool添加一个pfree, 前者是追加, 后者是向前插入)
注意, _pool__free, _pool__new相当于free, malloc, 定义如下:
#define _pool__malloc malloc #define _pool__free free
void pool_free(pool_t p) { struct pfree *cur, *stub; if(p == NULL) return; cur = p->cleanup; while(cur != NULL) { (*cur->f)(cur->arg); stub = cur->next; _pool__free(cur); cur = stub; } #ifdef POOL_DEBUG if (pool__disturbed != NULL && pool__disturbed != (xht)1) xhash_zap(pool__disturbed,p->name); #endif _pool__free(p); }
4, 其他一些函数: 列举俩, 简单一看就可以了.
/** XXX efficient: move this to const char * and then loop throug the existing heaps to see if src is within a block in this pool */ char *pstrdup(pool_t p, const char *src) { char *ret; if(src == NULL) return NULL; ret = pmalloc(p,strlen(src) + 1); strcpy(ret,src); return ret; } /** use given size */ char *pstrdupx(pool_t p, const char *src, int len) { char *ret; if(src == NULL || len <= 0) return NULL; ret = pmalloc(p,len + 1); memcpy(ret,src,len); ret[len] = '\0'; return ret; }
util/base64是专用于base64编码与解码的接口, jabberd2基于apache apr做了二次封装.
/* base64 functions */ JABBERD2_API int apr_base64_decode_len(const char *bufcoded); JABBERD2_API int apr_base64_decode(char *bufplain, const char *bufcoded); JABBERD2_API int apr_base64_encode_len(int len); JABBERD2_API int apr_base64_encode(char *encoded, const unsigned char *string, int len); /* convenience, result string must be free()'d by caller */ JABBERD2_API char *b64_encode(char *buf, int len); JABBERD2_API char *b64_decode(char *buf);
apache apr的base64接口的确很推荐使用, 比直接openssl要简单可靠的多, apr_base64_decode_len和 apr_base64_encode_len 用于计算base64需要的outbuf的大小,
jabberd2会根据返回值使用Malloc分配内存, 将结果存入其中返回, 需要由调用者负责释放内存, 看一下代码就懂了:
/* convenience functions for j2 */ char *b64_encode(char *buf, int len) { int elen; char *out; if(len == 0) len = strlen(buf); elen = apr_base64_encode_len(len); out = (char *) malloc(sizeof(char) * (elen + 1)); apr_base64_encode(out, buf, len); return out; } char *b64_decode(char *buf) { int elen; char *out; elen = apr_base64_decode_len(buf, -1); out = (char *) malloc(sizeof(char) * (elen + 1)); apr_base64_decode(out, buf, -1); return out; }
util/hex用于hex编码与解码, 其声明在util/util.h中, 定义于util/hex.c.
/* hex conversion utils */ JABBERD2_API void hex_from_raw(char *in, int inlen, char *out); JABBERD2_API int hex_to_raw(char *in, int inlen, char *out);
util/md5用于md5计算, 是原生的openssl md5实现, 但作者通过宏替换的方式, 将代码变得更加jabberd2了, 可以从下面看出来:
作者用4个宏将原生的md5结构体与接口进行了替换, 显得更加优雅了, 自己挂上了JABBERD2_API... 其实他什么事情都没有做.
#include <openssl/md5.h> #define md5_state_t MD5_CTX #define md5_init(c) MD5_Init(c) #define md5_append(c, data, len) MD5_Update(c, data, len); #define md5_finish(c, md) MD5_Final(md, c) typedef uint8_t md5_byte_t; /* 8-bit byte */ typedef uint32_t md5_word_t; /* 32-bit word */ /* Define the state of the MD5 Algorithm. */ typedef struct md5_state_s { md5_word_t count[2]; /* message length in bits, lsw first */ md5_word_t abcd[4]; /* digest buffer */ md5_byte_t buf[64]; /* accumulate block */ } md5_state_t; #ifdef __cplusplus extern "C" { #endif /* Initialize the algorithm. */ JABBERD2_API void md5_init(md5_state_t *pms); /* Append a string to the message. */ JABBERD2_API void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes); /* Finish the message and return the digest. */ JABBERD2_API void md5_finish(md5_state_t *pms, md5_byte_t digest[16]);
util/str是一系列操作字符串的函数: 与标准库的区别就是保证NULL形参安全, 大部分的实现都是间接调用了标准库函数, 其中j_strcat行为最特殊, 它与strcat功能不同, 它实际上是strcpy后返回字符串末尾的指针, 这是因为该函数用于spool的特殊用途, 下面将会讲到.
j_atoi的第二个参数是default的意思, 如果第一个参数为NULL的话, 则返回def.
/* --------------------------------------------------------- */ /* */ /* String management routines */ /* */ /** --------------------------------------------------------- */ JABBERD2_API char *j_strdup(const char *str); /* provides NULL safe strdup wrapper */ JABBERD2_API char *j_strcat(char *dest, char *txt); /* strcpy() clone */ JABBERD2_API int j_strcmp(const char *a, const char *b); /* provides NULL safe strcmp wrapper */ JABBERD2_API int j_strcasecmp(const char *a, const char *b); /* provides NULL safe strcasecmp wrapper */ JABBERD2_API int j_strncmp(const char *a, const char *b, int i); /* provides NULL safe strncmp wrapper */ JABBERD2_API int j_strncasecmp(const char *a, const char *b, int i); /* provides NULL safe strncasecmp wrapper */ JABBERD2_API int j_strlen(const char *a); /* provides NULL safe strlen wrapper */ JABBERD2_API int j_atoi(const char *a, int def); /* checks for NULL and uses default instead, convienence */ JABBERD2_API char *j_attr(const char** atts, const char *attr); /* decode attr's (from expat) */ JABBERD2_API char *j_strnchr(const char *s, int c, int n); /* like strchr, but only searches n chars */
在util/str中还实现了spool, 即string pool, 其实就是字符串链表:
spool_node是结点, spool_struct使用Pool分配结点与自身所需内存.
/* --------------------------------------------------------- */ /* */ /* String pools (spool) functions */ /* */ /* --------------------------------------------------------- */ struct spool_node { char *c; struct spool_node *next; }; typedef struct spool_struct { pool_t p; int len; struct spool_node *last; struct spool_node *first; } *spool; JABBERD2_API spool spool_new(pool_t p); /* create a string pool */ JABBERD2_API void spooler(spool s, ...); /* append all the char * args to the pool, terminate args with s again */ JABBERD2_API char *spool_print(spool s); /* return a big string */ JABBERD2_API void spool_add(spool s, char *str); /* add a single string to the pool */ JABBERD2_API void spool_escape(spool s, char *raw, int len); /* add and xml escape a single string to the pool */ JABBERD2_API char *spools(pool_t p, ...); /* wrap all the spooler stuff in one function, the happy fun ball! */
关键看一下spool的创建:
spool spool_new(pool_t p) { spool s; s = pmalloc(p, sizeof(struct spool_struct)); s->p = p; s->len = 0; s->last = NULL; s->first = NULL; return s; }
以及添加一个字符串到spool:
static void _spool_add(spool s, char *goodstr) { struct spool_node *sn; sn = pmalloc(s->p, sizeof(struct spool_node)); sn->c = goodstr; sn->next = NULL; s->len += strlen(goodstr); if(s->last != NULL) s->last->next = sn; s->last = sn; if(s->first == NULL) s->first = sn; } void spool_add(spool s, char *str) { if(str == NULL || strlen(str) == 0) return; _spool_add(s, pstrdup(s->p, str)); }
可以看出, 整个spool是基于pool创建的, 包括spool_node以及pstrdup生成的字符串副本.
另外, spool的使用接口必须看一下:
第一个接口遍历所有不定参数, spool s本身是一个指向struct spool_struct的指针, ...则是一系列的char *字符串, 在while(1)中, 不断的获取函数函数, 直到(spool)arg == s, 即spooler的使用方法一定是:spooler(s, str1, str2, str3, s)的形式, 循环终止于此, 对于每个str都被spool_add到spool中.
第二个接口依旧使用spool的pool开辟所有spool中字符串总长度+1的buffer, 然后把每个spool_node中的字符串j_strcat追加到buffer中, 最后返回这个buffer.
void spooler(spool s, ...) { va_list ap; char *arg = NULL; if(s == NULL) return; va_start(ap, s); /* loop till we hit our end flag, the first arg */ while(1) { arg = va_arg(ap,char *); if((spool)arg == s) break; else spool_add(s, arg); } va_end(ap); } char *spool_print(spool s) { char *ret,*tmp; struct spool_node *next; if(s == NULL || s->len == 0 || s->first == NULL) return NULL; ret = pmalloc(s->p, s->len + 1); *ret = '\0'; next = s->first; tmp = ret; while(next != NULL) { tmp = j_strcat(tmp,next->c); next = next->next; } return ret; }
该接口综合上述几个接口, 一次性完成若干字符串的串联并返回结果:
/** convenience :) */ char *spools(pool_t p, ...) { va_list ap; spool s; char *arg = NULL; if(p == NULL) return NULL; s = spool_new(p); va_start(ap, p); /* loop till we hit our end flag, the first arg */ while(1) { arg = va_arg(ap,char *); if((pool_t)arg == p) break; else spool_add(s, arg); } va_end(ap); return spool_print(s); }
另外, spool对XML的esacape/unescape做了良好的支持, 实现如下:
同spool_add一样, spool_escape在_spool_add前进行了xml转义, 实现方法很朴素, 看代码即可.
void spool_escape(spool s, char *raw, int len) { if(raw == NULL || len <= 0) return; _spool_add(s, strescape(s->p, raw, len)); } char *strunescape(pool_t p, char *buf) { int i,j=0; char *temp; if (buf == NULL) return(NULL); if (strchr(buf,'&') == NULL) return(buf); if(p != NULL) temp = pmalloc(p,strlen(buf)+1); else temp = malloc(strlen(buf)+1); if (temp == NULL) return(NULL); for(i=0;i<strlen(buf);i++) { if (buf[i]=='&') { if (strncmp(&buf[i],"&",5)==0) { temp[j] = '&'; i += 4; } else if (strncmp(&buf[i],""",6)==0) { temp[j] = '\"'; i += 5; } else if (strncmp(&buf[i],"'",6)==0) { temp[j] = '\''; i += 5; } else if (strncmp(&buf[i],"<",4)==0) { temp[j] = '<'; i += 3; } else if (strncmp(&buf[i],">",4)==0) { temp[j] = '>'; i += 3; } } else { temp[j]=buf[i]; } j++; } temp[j]='\0'; return(temp); } char *strescape(pool_t p, char *buf, int len) { int i,j,newlen = len; char *temp; if (buf == NULL || len < 0) return NULL; for(i=0;i<len;i++) { switch(buf[i]) { case '&': newlen+=5; break; case '\'': newlen+=6; break; case '\"': newlen+=6; break; case '<': newlen+=4; break; case '>': newlen+=4; break; } } if(p != NULL) temp = pmalloc(p,newlen+1); else temp = malloc(newlen+1); if(newlen == len) { memcpy(temp,buf,len); temp[len] = '\0'; return temp; } for(i=j=0;i<len;i++) { switch(buf[i]) { case '&': memcpy(&temp[j],"&",5); j += 5; break; case '\'': memcpy(&temp[j],"'",6); j += 6; break; case '\"': memcpy(&temp[j],""",6); j += 6; break; case '<': memcpy(&temp[j],"<",4); j += 4; break; case '>': memcpy(&temp[j],">",4); j += 4; break; default: temp[j++] = buf[i]; } } temp[j] = '\0'; return temp; }
在str.c中还有两个函数, 一个是求raw的sha1, 一个是求hex过的sha1, 后者调用前者, 依赖于openssl的SHA1, 代码如下:其中hex_from_raw在hex.c中提到过了.
/** convenience (originally by Thomas Muldowney) */ void shahash_r(const char* str, char hashbuf[41]) { unsigned char hashval[20]; shahash_raw(str, hashval); hex_from_raw(hashval, 20, hashbuf); } void shahash_raw(const char* str, unsigned char hashval[20]) { #ifdef HAVE_SSL /* use OpenSSL functions when available */ # include <openssl/sha.h> SHA1((unsigned char *)str, strlen(str), hashval); #else sha1_hash((unsigned char *)str, strlen(str), hashval); #endif
util/jsignal是信号处理函数的注册接口: 很简单, 但因为jabberd2跨平台支持win32与linux, 所以显得代码量略多, 实际linux代码只有这么一丁点, 使用sigaction注册信号, 如果不是SIGALRM, 还注册了SA_RESTART的flag, 返回值是之前的信号处理函数.
/* Portable signal function */ typedef void jsighandler_t(int); JABBERD2_API jsighandler_t* jabber_signal(int signo, jsighandler_t *func); jsighandler_t* jabber_signal(int signo, jsighandler_t *func) { #ifdef _WIN32 if(signo == SIGTERM) jabber_term_handler = func; return NULL; #else struct sigaction act, oact; act.sa_handler = func; sigemptyset(&act.sa_mask); act.sa_flags = 0; #ifdef SA_RESTART if (signo != SIGALRM) act.sa_flags |= SA_RESTART; #endif if (sigaction(signo, &act, &oact) < 0) return (SIG_ERR); return (oact.sa_handler); #endif }
util/sha1定义了计算sha1的接口: 如果没有openssl支持, 那么jabberd2实现了自己的sha1, 如果有openssl支持, 则采用了util/md5一样的方法用宏替换掉了openssl的函数名与结构名, 通常我们用最后一个接口, 上面在util/str中已经见过, 直接即可计算得到raw的sha1结果.
#ifdef HAVE_SSL #include <openssl/sha.h> #define sha1_state_t SHA_CTX #define sha1_init(c) SHA1_Init(c) #define sha1_append(c, data, len) SHA1_Update(c, data, len); #define sha1_finish(c, md) SHA1_Final(md, c) #define sha1_hash(data, len, md) SHA1(data, len, md); #else #include <inttypes.h> typedef struct sha1_state_s { uint32_t H[5]; uint32_t W[80]; int lenW; uint32_t sizeHi,sizeLo; } sha1_state_t; JABBERD2_API void sha1_init(sha1_state_t *ctx); JABBERD2_API void sha1_append(sha1_state_t *ctx, const unsigned char *dataIn, int len); JABBERD2_API void sha1_finish(sha1_state_t *ctx, unsigned char hashout[20]); JABBERD2_API void sha1_hash(const unsigned char *dataIn, int len, unsigned char hashout[20]); #endif
util/jqueue实现了优先级队列: 其原理非常简单, 即插入时刻保证优先级有序即可, 数据结构采用了双向非循环链表, 而不是使用复杂的平衡树或者堆结构.
其中, _jqueue_node_t做为node, 其中存有data数据和priority优先级.
而jqueue_t中有pool内存池, front,back即链表的头尾结点, size表示结点个数, init_time表示jqueue的创建时间,最重要的是cache, 其实就是被将被jqueue_pull取走data后留下的_jqueue_node_t插入到cache链表中, 下次就不用从pool重新开辟了, 俗称"对象池".
操作接口只有Push和pull, 直接看一下实现即可.
/* * priority queues */ typedef struct _jqueue_node_st *_jqueue_node_t; struct _jqueue_node_st { void *data; int priority; _jqueue_node_t next; _jqueue_node_t prev; }; typedef struct _jqueue_st { pool_t p; _jqueue_node_t cache; _jqueue_node_t front; _jqueue_node_t back; int size; char *key; time_t init_time; } *jqueue_t; JABBERD2_API jqueue_t jqueue_new(void); JABBERD2_API void jqueue_free(jqueue_t q); JABBERD2_API void jqueue_push(jqueue_t q, void *data, int pri); JABBERD2_API void *jqueue_pull(jqueue_t q); JABBERD2_API int jqueue_size(jqueue_t q); JABBERD2_API time_t jqueue_age(jqueue_t q);
jqueue自己创建了专用的Pool用于开辟内存, 并且jqueue自身就是使用pool分配的. 可以从Jqueue的释放中了解到pool的设计多么有意思, 只要把pool自己释放了, jqueue就不见了, 根本没有内存泄漏的机会.
jqueue_t jqueue_new(void) { pool_t p; jqueue_t q; p = pool_new(); q = (jqueue_t) pmalloco(p, sizeof(struct _jqueue_st)); q->p = p; q->init_time = time(NULL); return q; } void jqueue_free(jqueue_t q) { assert((int) (q != NULL)); pool_free(q->p); }
接下来是插入一个元素到优先级队列: 在jqueue_push中, 先检查cache是否有空闲的node, 有就取出来使用, 否则从Pool重新开辟. 如果队列当前非空, 那么需要根据优先级将Node插入到合适的位置, for循环就是做这事的. 在jqueue里, 优先级最大的在头部, 所以遍历queue时从back向front走. 说实话, 作者这个next和prev正好搞反了, 读起来很别扭.
void jqueue_push(jqueue_t q, void *data, int priority) { _jqueue_node_t qn, scan; assert((int) (q != NULL)); q->size++; /* node from the cache, or make a new one */ qn = q->cache; if(qn != NULL) q->cache = qn->next; else qn = (_jqueue_node_t) pmalloc(q->p, sizeof(struct _jqueue_node_st)); qn->data = data; qn->priority = priority; qn->next = NULL; qn->prev = NULL; /* first one */ if(q->back == NULL && q->front == NULL) { q->back = qn; q->front = qn; return; } /* find the first node with priority <= to us */ for(scan = q->back; scan != NULL && scan->priority > priority; scan = scan->next); /* didn't find one, so we have top priority - push us on the front */ if(scan == NULL) { qn->prev = q->front; qn->prev->next = qn; q->front = qn; return; } /* push us in front of scan */ qn->next = scan; qn->prev = scan->prev; if(scan->prev != NULL) scan->prev->next = qn; else q->back = qn; scan->prev = qn; }
jqueue_pull和剩下的接口就很简单了: 取出q->front中的data, 如果还有剩余结点, 令下一个结点的next=NULL并成为新的front, 把取出的front插入到cache头部, 返回data.
void *jqueue_pull(jqueue_t q) { void *data; _jqueue_node_t qn; assert((int) (q != NULL)); if(q->front == NULL) return NULL; data = q->front->data; qn = q->front; if(qn->prev != NULL) qn->prev->next = NULL; q->front = qn->prev; /* node to cache for later reuse */ qn->next = q->cache; q->cache = qn; if(q->front == NULL) q->back = NULL; q->size--; return data; } int jqueue_size(jqueue_t q) { return q->size; } time_t jqueue_age(jqueue_t q) { return time(NULL) - q->init_time; }
util/datetime定义了两个接口, 一个从字符串转time_t, 一个从time_t转字符串, 预定义了几种时间格式使用sscanf尝试解析, 按照我个人理解:
datetime_in传入的是各地的时间, 各不相同, 有若干种格式, 有的采用gmt+/-的格式表示各地的当地时间, 所以fix就是存储各地时间与gmt的差值, 函数最终希望生成的是gmt时间, 但mktime函数会将struct tm中的时间当作本地时间转换为time_t(即转换为time_t后加上了了本地时间与gmt的差值), 所以mktime()需要先-(tz.tz_minuteswest * 60)以便保证mktime是向gmt转换, 之后再将各地的时间与gmt的差值+fix补回去, 这样就得到了标准GMT时间.
typedef enum { dt_DATE = 1, dt_TIME = 2, dt_DATETIME = 3, dt_LEGACY = 4 } datetime_t; JABBERD2_API time_t datetime_in(char *date); JABBERD2_API void datetime_out(time_t t, datetime_t type, char *date, int datelen);
/* formats */ #define DT_DATETIME_P "%04d-%02d-%02dT%02d:%02d:%lf+%02d:%02d" #define DT_DATETIME_M "%04d-%02d-%02dT%02d:%02d:%lf-%02d:%02d" #define DT_DATETIME_Z "%04d-%02d-%02dT%02d:%02d:%lfZ" #define DT_TIME_P "%02d:%02d:%lf+%02d:%02d" #define DT_TIME_M "%02d:%02d:%lf-%02d:%02d" #define DT_TIME_Z "%02d:%02d:%lfZ" #define DT_LEGACY "%04d%02d%02dT%02d:%02d:%lf" time_t datetime_in(char *date) { struct tm gmt, off; double sec; off_t fix = 0; struct timeval tv; struct timezone tz; assert((int) (date != NULL)); /* !!! sucks having to call this each time */ tzset(); memset(&gmt, 0, sizeof(struct tm)); memset(&off, 0, sizeof(struct tm)); if(sscanf(date, DT_DATETIME_P, &gmt.tm_year, &gmt.tm_mon, &gmt.tm_mday, &gmt.tm_hour, &gmt.tm_min, &sec, &off.tm_hour, &off.tm_min) == 8) { gmt.tm_sec = (int) sec; gmt.tm_year -= 1900; gmt.tm_mon--; fix = off.tm_hour * 3600 + off.tm_min * 60; } else if(sscanf(date, DT_DATETIME_M, &gmt.tm_year, &gmt.tm_mon, &gmt.tm_mday, &gmt.tm_hour, &gmt.tm_min, &sec, &off.tm_hour, &off.tm_min) == 8) { gmt.tm_sec = (int) sec; gmt.tm_year -= 1900; gmt.tm_mon--; fix = - off.tm_hour * 3600 - off.tm_min * 60; } else if(sscanf(date, DT_DATETIME_Z, &gmt.tm_year, &gmt.tm_mon, &gmt.tm_mday, &gmt.tm_hour, &gmt.tm_min, &sec) == 6) { gmt.tm_sec = (int) sec; gmt.tm_year -= 1900; gmt.tm_mon--; fix = 0; } else if(sscanf(date, DT_TIME_P, &gmt.tm_hour, &gmt.tm_min, &sec, &off.tm_hour, &off.tm_min) == 5) { gmt.tm_sec = (int) sec; fix = off.tm_hour * 3600 + off.tm_min * 60; } else if(sscanf(date, DT_TIME_M, &gmt.tm_hour, &gmt.tm_min, &sec, &off.tm_hour, &off.tm_min) == 5) { gmt.tm_sec = (int) sec; fix = - off.tm_hour * 3600 - off.tm_min * 60; } else if(sscanf(date, DT_TIME_Z, &gmt.tm_hour, &gmt.tm_min, &sec) == 3) { gmt.tm_sec = (int) sec; fix = - off.tm_hour * 3600 - off.tm_min * 60; } else if(sscanf(date, DT_LEGACY, &gmt.tm_year, &gmt.tm_mon, &gmt.tm_mday, &gmt.tm_hour, &gmt.tm_min, &sec) == 6) { gmt.tm_sec = (int) sec; gmt.tm_year -= 1900; gmt.tm_mon--; fix = 0; } gmt.tm_isdst = -1; gettimeofday(&tv, &tz); return mktime(&gmt) + fix - (tz.tz_minuteswest * 60); }
datetime_out函数, 就直接调用了gmtime获取了GTM时间, 之后格式化成了某一种格式, 这个函数比较简单, 没有那么复杂的时间概念.
void datetime_out(time_t t, datetime_t type, char *date, int datelen) { struct tm *gmt; assert((int) type); assert((int) (date != NULL)); assert((int) datelen); gmt = gmtime(&t); switch(type) { case dt_DATE: snprintf(date, datelen, "%04d-%02d-%02d", gmt->tm_year + 1900, gmt->tm_mon + 1, gmt->tm_mday); break; case dt_TIME: snprintf(date, datelen, "%02d:%02d:%02dZ", gmt->tm_hour, gmt->tm_min, gmt->tm_sec); break; case dt_DATETIME: snprintf(date, datelen, "%04d-%02d-%02dT%02d:%02d:%02dZ", gmt->tm_year + 1900, gmt->tm_mon + 1, gmt->tm_mday, gmt->tm_hour, gmt->tm_min, gmt->tm_sec); break; case dt_LEGACY: snprintf(date, datelen, "%04d%02d%02dT%02d:%02d:%02d", gmt->tm_year + 1900, gmt->tm_mon + 1, gmt->tm_mday, gmt->tm_hour, gmt->tm_min, gmt->tm_sec); break; } }
util/rate用于频率限制, 在实现上并没有限制使用场景, 是非常通用的:
注释本身很清晰了, 既如果我们seconds秒内做了total次, 那么我们休止wait秒. 其中time和count用于记录time秒内操作了count次, bad记录了进入休止状态的时刻 当休止超过wait秒后, 限制解除.
/* * rate limiting */ typedef struct rate_st { int total; /* if we exceed this many events */ int seconds; /* in this many seconds */ int wait; /* then go bad for this many seconds */ time_t time; /* time we started counting events */ int count; /* event count */ time_t bad; /* time we went bad, or 0 if we're not */ } *rate_t; JABBERD2_API rate_t rate_new(int total, int seconds, int wait); JABBERD2_API void rate_free(rate_t rt); JABBERD2_API void rate_reset(rate_t rt); /** * Add a number of events to the counter. This takes care of moving * the sliding window, if we've moved outside the previous window. */ JABBERD2_API void rate_add(rate_t rt, int count); /** * @return The amount of events we have left before we hit the rate * limit. This could be number of bytes, or number of * connection attempts, etc. */ JABBERD2_API int rate_left(rate_t rt); /** * @return 1 if we're under the rate limit and everything is fine or * 0 if the rate limit has been exceeded and we should throttle * something. */ JABBERD2_API int rate_check(rate_t rt);
实现很简单, 总体来说并不是准确的, 只是大致那么一算:
rate_new 创建并配置了rate_t结构体.
rate_free 释放.
rate_reset 重置.
rate_left 如果当前已休止, 那么没有剩余计数, 否则返回total-count.
rate_add 这个函数只关心在seconds秒内的情况下, 是否count > total, 否则会重置计数.
rate_check 如果还没计数过, 或者没超过限制, 立即返回. 如果处于bad阶段, 那么判断是否超过wait, 超过则恢复并重置, 没超过则返回0.
rate_t rate_new(int total, int seconds, int wait) { rate_t rt = (rate_t) calloc(1, sizeof(struct rate_st)); rt->total = total; rt->seconds = seconds; rt->wait = wait; return rt; } void rate_free(rate_t rt) { free(rt); } void rate_reset(rate_t rt) { rt->time = 0; rt->count = 0; rt->bad = 0; } void rate_add(rate_t rt, int count) { time_t now; now = time(NULL); /* rate expired */ if(now - rt->time >= rt->seconds) rate_reset(rt); rt->count += count; /* first event, so set the time */ if(rt->time == 0) rt->time = now; /* uhoh, they stuffed up */ if(rt->count >= rt->total) rt->bad = now; } int rate_left(rate_t rt) { /* if we're bad, then there's none left */ if(rt->bad != 0) return 0; return rt->total - rt->count; } int rate_check(rate_t rt) { /* not tracking */ if(rt->time == 0) return 1; /* under the limit */ if(rt->count < rt->total) return 1; /* currently bad */ if(rt->bad != 0) { /* wait over, they're good again */ if(time(NULL) - rt->bad >= rt->wait) { rate_reset(rt); return 1; } /* keep them waiting */ return 0; } /* they're inside the time, and not bad yet */ return 1; }
util/serial是一个内存中的序列化操作, 被其他地方使用到, 接口如下:
分成两个系列, 一个是(反)序列化string的, 一个是(反)序列化int的.
序列化在这里是指: 把string/int 存储到一个buffer里, 或者从buffer里反序列化得到一个string/int.
/* * serialisation helper functions */ JABBERD2_API int ser_string_get(char **dest, int *source, const char *buf, int len); JABBERD2_API int ser_int_get(int *dest, int *source, const char *buf, int len); JABBERD2_API void ser_string_set(char *source, int *dest, char **buf, int *len); JABBERD2_API void ser_int_set(int source, int *dest, char **buf, int *len);
buf是指针的地址, 因为用户的内存可能不够需要realloc, 所以要传char **buf.
ser_string_set序列化char *source字符串到buf中, SER_SAFE检测buf的尺寸是否足够, 其中int *dest表示buf已经使用的尺寸, int *len表示buf总长度, 函数就修改int *dest和int *len(因为调用SER_SAFE在内存不足情况下会调用realloc).
ser_int_set原来类似, 使用了一个union转换int到字节数组.
void ser_string_set(char *source, int *dest, char **buf, int *len) { int need = sizeof(char) * (strlen(source) + 1); /* make more space if necessary */ SER_SAFE(*buf, *dest + need, *len); /* copy it in */ strcpy(*buf + *dest, source); /* and shift the pointer */ *dest += need; } void ser_int_set(int source, int *dest, char **buf, int *len) { union { char c[sizeof(int)]; int i; } u; int i; /* make more space if necessary */ SER_SAFE(*buf, *dest + sizeof(int), *len) /* copy it in */ u.i = source; for(i = 0; i < sizeof(int); i++) (*buf)[*dest + i] = u.c[i]; /* and shift the pointer */ *dest += sizeof(int); }
其中SER_SAFE宏与实现如下:_ser_realloc重新分配一块内存, 存入*oblocks中, 新分配的内存大小长度为len, 但_ser_realloc会将其尺寸对齐到BLOCKSIZE.
SER_SAFE宏则判断buf剩余内存是否足够, 不够则调用_ser_realloc.
/* shamelessy stolen from nad.c */ #define BLOCKSIZE 1024 /** internal: do and return the math and ensure it gets realloc'd */ static int _ser_realloc(void **oblocks, int len) { void *nblocks; int nlen; /* round up to standard block sizes */ nlen = (((len-1)/BLOCKSIZE)+1)*BLOCKSIZE; /* keep trying till we get it */ while((nblocks = realloc(*oblocks, nlen)) == NULL) sleep(1); *oblocks = nblocks; return nlen; } /** this is the safety check used to make sure there's always enough mem */ #define SER_SAFE(blocks, size, len) if((size) > len) len = _ser_realloc((void**)&(blocks),(size));
反序列化两个接口如下, 一样会修改int *source, 对于string会strdup一份字符串的副本返回到char **dest; 对于int则通过int *dest返回.
int ser_string_get(char **dest, int *source, const char *buf, int len) { const char *end, *c; /* end of the buffer */ end = buf + ((sizeof(char) * (len - 1))); /* make sure we have a \0 before the end of the buffer */ c = &(buf[*source]); while(c <= end && *c != '\0') c++; if(c > end) /* we ran past the end, fail */ return 1; /* copy the string */ *dest = strdup(&(buf[*source])); /* and move the pointer */ *source += strlen(*dest) + 1; return 0; } int ser_int_get(int *dest, int *source, const char *buf, int len) { union { char c[sizeof(int)]; int i; } u; int i; /* we need sizeof(int) bytes */ if(&(buf[*source]) + sizeof(int) > buf + (sizeof(char) * len)) return 1; /* copy the bytes into the union. we do it this way to avoid alignment problems */ for(i = 0; i < sizeof(int); i++) { u.c[i] = buf[*source]; (*source)++; } *dest = u.i; return 0; }
util/inaddr是一套自封装的网络地址操作库, 实现了常见的inet_pton, inet_ntop等功能.
JABBERD2_API int j_inet_pton(char *src, struct sockaddr_storage *dst); JABBERD2_API const char *j_inet_ntop(struct sockaddr_storage *src, char *dst, size_t size); JABBERD2_API int j_inet_getport(struct sockaddr_storage *sa); JABBERD2_API int j_inet_setport(struct sockaddr_storage *sa, in_port_t port); JABBERD2_API socklen_t j_inet_addrlen(struct sockaddr_storage *sa);
这些接口的实现其实需要特别关注一下, 因为是兼容IPV4与IPV6的, 其中的原理与技巧需要掌握.
j_inet_pton将src字符串表达的地址转换存储到struct sockaddr_storage中, 该结构体保证兼容IPV4与IPV6的尺寸. 如果不支持inet_pton, 那么只使用inet_aton尝试转换ipv4, 对于ipv6则以失败告终. 如果支持inet_pton, 那么分别尝试inet_pton的AF_INET和AF_INET6, 只要成功则返回, 这算是个小技巧.
int j_inet_pton(char *src, struct sockaddr_storage *dst) { #ifndef HAVE_INET_PTON struct sockaddr_in *sin; memset(dst, 0, sizeof(struct sockaddr_storage)); sin = (struct sockaddr_in *)dst; if(inet_aton(src, &sin->sin_addr)) { dst->ss_family = AF_INET; return 1; } return 0; #else struct sockaddr_in *sin; struct sockaddr_in6 *sin6; memset(dst, 0, sizeof(struct sockaddr_storage)); sin = (struct sockaddr_in *)dst; sin6 = (struct sockaddr_in6 *)dst; if(inet_pton(AF_INET, src, &sin->sin_addr) > 0) { dst->ss_family = AF_INET; return 1; } if(inet_pton(AF_INET6, src, &sin6->sin6_addr) > 0) { dst->ss_family = AF_INET6; #ifdef SIN6_LEN sin6->sin6_len = sizeof(struct sockaddr_in6); #endif return 1; } return 0; #endif }
j_inet_ntop是将地址转换成可读字符串, 基本和上面类似, 看一下即可:
const char *j_inet_ntop(struct sockaddr_storage *src, char *dst, size_t size) { #ifndef HAVE_INET_NTOP char *tmp; struct sockaddr_in *sin; sin = (struct sockaddr_in *)src; /* if we don't have inet_ntop we only accept AF_INET * it's unlikely that we would have use for AF_INET6 */ if(src->ss_family != AF_INET) { return NULL; } tmp = inet_ntoa(sin->sin_addr); if(!tmp || strlen(tmp)>=size) { return NULL; } strncpy(dst, tmp, size); return dst; #else struct sockaddr_in *sin; struct sockaddr_in6 *sin6; sin = (struct sockaddr_in *)src; sin6 = (struct sockaddr_in6 *)src; switch(src->ss_family) { case AF_UNSPEC: case AF_INET: return inet_ntop(AF_INET, &sin->sin_addr, dst, size); case AF_INET6: return inet_ntop(AF_INET6, &sin6->sin6_addr, dst, size); default: return NULL; } #endif }
j_inet_getport就是获取地址中的port, 兼容ipv4/ipv6.
int j_inet_getport(struct sockaddr_storage *sa) { struct sockaddr_in *sin; struct sockaddr_in6 *sin6; switch(sa->ss_family) { case AF_INET: sin = (struct sockaddr_in *)sa; return ntohs(sin->sin_port); case AF_INET6: sin6 = (struct sockaddr_in6 *)sa; return ntohs(sin6->sin6_port); default: return 0; } }
j_inet_setport类似:
int j_inet_setport(struct sockaddr_storage *sa, in_port_t port) { struct sockaddr_in *sin; struct sockaddr_in6 *sin6; sin = (struct sockaddr_in *)sa; sin6 = (struct sockaddr_in6 *)sa; switch(sa->ss_family) { case AF_INET: sin->sin_port = htons(port); return 1; case AF_INET6: sin6->sin6_port = htons(port); return 1; default: return 0; } }
最后:
socklen_t j_inet_addrlen(struct sockaddr_storage *sa) { #ifdef SIN6_LEN if(sa->ss_len != 0) return sa->ss_len; #endif switch(sa->ss_family) { case AF_INET: return sizeof(struct sockaddr_in); case AF_INET6: return sizeof(struct sockaddr_in6); default: return sizeof(struct sockaddr_storage); } }
util/access是基于IP的黑白名单, 支持带掩码的IP地址, 即可以按照网段过滤, 说白了就和路由表一样, 对于一个IP, 先用IP&mask, 然后判断是否符合access_rule_t中的ip, 其实现技巧也体现了很多网络IPV4,IPV6方面的处理, 需要特别关注一下.
/* * IP-based access controls */ typedef struct access_rule_st { struct sockaddr_storage ip; int mask; } *access_rule_t; typedef struct access_st { int order; /* 0 = allow,deny 1 = deny,allow */ access_rule_t allow; int nallow; access_rule_t deny; int ndeny; } *access_t; JABBERD2_API access_t access_new(int order); JABBERD2_API void access_free(access_t access); JABBERD2_API int access_allow(access_t access, char *ip, char *mask); JABBERD2_API int access_deny(access_t access, char *ip, char *mask); JABBERD2_API int access_check(access_t access, char *ip);
首先, 创建与释放access结构体, 其中access->allow和access->deny是两个struct access_rule_st的数组.
access_t access_new(int order) { access_t access = (access_t) calloc(1, sizeof(struct access_st)); access->order = order; return access; } void access_free(access_t access) { if(access->allow != NULL) free(access->allow); if(access->deny != NULL) free(access->deny); free(access); }
下面的函数是计算一个IPV4掩码的长度, 什么意思呢? 掩码也是一个IP地址的格式, xxx.xxx.xxx.xxx, 是一个4字节整形, 该函数就是计算一下最右边的比特为1的位置是多少(假设是offset, 是距离IP地址最左位的比特数), 即掩码的位数. 那么对于一个IP地址, 就可以取出IP的左边offset个比特, 判断是否和access_rule_st->ip相同, 相同则命中.
代码中, 需要先将ipv4字符串转换成网络序4字节整形, 然后做ntohl转换成本地序, 之后即可开始循环的右移, 直到整形最低位为1, 那么netsize此时就是掩码的长度了.同时, 也支持直接传递mask的长度, 那么直接j_atoi就可以立即得到掩码长度了.
注意一个细节, 如果不支持inet_pton, 那么则判断是否包含'.', 并尝试使用inet_aton转换掩码mask, 如果成功则说明地址是ipv4, 否则认为mask传入了掩码长度而非掩码地址, 即在此情况下是不支持ipv6的. 但如果支持inet_pton, 也一样是不支持IPV6地址掩码的, 只允许IPV6地址长度.
static int _access_calc_netsize(const char *mask, int defaultsize) { struct in_addr legacy_mask; int netsize; #ifndef HAVE_INET_PTON if(strchr(mask, '.') && inet_aton(mask, &legacy_mask)) #else if(inet_pton(AF_INET, mask, &legacy_mask.s_addr) > 0) #endif { /* netmask has been given in dotted decimal form */ int temp = ntohl(legacy_mask.s_addr); netsize = 32; while(netsize && temp%2==0) { netsize--; temp /= 2; } } else { /* numerical netsize */ netsize = j_atoi(mask, defaultsize); } return netsize; }
这个函数更简单, 把ipv4映射的ipv6地址转换成ipv4地址, 因为ipv6的12,15字节是ipv4地址的大端4字节, 所以在本地每次左移8比特生成对应的本地序地址整形, 之后转向网络序存储到地址结构体中.
memset(dst, 0, sizeof(struct sockaddr_in)); dst->sin_family = AF_INET; dst->sin_addr.s_addr = htonl((((int)src->sin6_addr.s6_addr[12]*256+src->sin6_addr.s6_addr[13])*256+src->sin6_addr.s6_addr[14])*256+(int)src->sin6_addr.s6_addr[15]) ;
再看一下怎么添加白名单与黑名单: 两者代码是冗余的, 看一下添加白名单即可. 先j_inet_pton校验地址是否有效, 之后计算掩码长度, 此处直接传入了defaultsize, 即全掩码, 表示IP地址必须完全匹配. 计算完掩码长度后, 给access->allow数组realloc新添加一个rule, 把ip与掩码拷贝进去.
int access_allow(access_t access, char *ip, char *mask) { struct sockaddr_storage ip_addr; int netsize; if(j_inet_pton(ip, &ip_addr) <= 0) return 1; netsize = _access_calc_netsize(mask, ip_addr.ss_family==AF_INET ? 32 : 128); access->allow = (access_rule_t) realloc(access->allow, sizeof(struct access_rule_st) * (access->nallow + 1)); memcpy(&access->allow[access->nallow].ip, &ip_addr, sizeof(ip_addr)); access->allow[access->nallow].mask = netsize; access->nallow++; return 0; } int access_deny(access_t access, char *ip, char *mask) { struct sockaddr_storage ip_addr; int netsize; if(j_inet_pton(ip, &ip_addr) <= 0) return 1; netsize = _access_calc_netsize(mask, ip_addr.ss_family==AF_INET ? 32 : 128); access->deny = (access_rule_t) realloc(access->deny, sizeof(struct access_rule_st) * (access->ndeny + 1)); memcpy(&access->deny[access->ndeny].ip, &ip_addr, sizeof(ip_addr)); access->deny[access->ndeny].mask = netsize; access->ndeny++; return 0; }
下面就是用户接口了, 黑白名单有作用顺序的区别, 先检测黑名单还是先检测白名单, 结果是不同的, 因为黑名单和白名单可能同时命中. 先遍历allow数组, 确定是否allow, 然后deny数组确定是否deny, 最后根据order决定黑白名单的先后顺序.
int access_check(access_t access, char *ip) { struct sockaddr_storage addr; access_rule_t rule; int i, allow = 0, deny = 0; if(j_inet_pton(ip, &addr) <= 0) return 0; /* first, search the allow list */ for(i = 0; !allow && i < access->nallow; i++) { rule = &access->allow[i]; if(_access_check_match(&addr, &rule->ip, rule->mask)) allow = 1; } /* now the deny list */ for(i = 0; !deny && i < access->ndeny; i++) { rule = &access->deny[i]; if(_access_check_match(&addr, &rule->ip, rule->mask)) deny = 1; } /* allow then deny */ if(access->order == 0) { if(allow) return 1; if(deny) return 0; /* allow by default */ return 1; } /* deny then allow */ if(deny) return 0; if(allow) return 1; /* deny by default */ return 0; }
其中用到的_access_check_match判断两个IP在掩码作用下是否相同:IN6_IS_ADDR_V4MAPPED宏出现在util/util_compat.h中, 用于判断IPV6地址是否是IPV4映射的, 具体IPV6地址格式得单独看一下了.
函数很简单, 先判断两个地址是否协议相同, 不同则判断是否是IPV4映射的IPV6,会做一次转换, 之后递归调用函数再次校验.
对于IPV4地址, 会先制作出掩码, 然后转成网络序和网络序的地址进行运算判断是否相等.
对于IPV6地址, 稍微特殊一点, 会先比较前netsize / 8 字节, 如果掩码没有占全最后一个字节, 那么最后一个字节需要按位比较, 剩下的和IPV4的比较一样.
#ifndef IN6_IS_ADDR_V4MAPPED /** check if an IPv6 is just a mapped IPv4 address */ #define IN6_IS_ADDR_V4MAPPED(a) \ ((*(const uint32_t *)(const void *)(&(a)->s6_addr[0]) == 0) && \ (*(const uint32_t *)(const void *)(&(a)->s6_addr[4]) == 0) && \ (*(const uint32_t *)(const void *)(&(a)->s6_addr[8]) == ntohl(0x0000ffff))) #endif /** check if two ip addresses are within the same subnet */ static int _access_check_match(struct sockaddr_storage *ip_1, struct sockaddr_storage *ip_2, int netsize) { struct sockaddr_in *sin_1; struct sockaddr_in *sin_2; struct sockaddr_in6 *sin6_1; struct sockaddr_in6 *sin6_2; int i; sin_1 = (struct sockaddr_in *)ip_1; sin_2 = (struct sockaddr_in *)ip_2; sin6_1 = (struct sockaddr_in6 *)ip_1; sin6_2 = (struct sockaddr_in6 *)ip_2; /* addresses of different families */ if(ip_1->ss_family != ip_2->ss_family) { /* maybe on of the addresses is just a IPv6 mapped IPv4 address */ if (ip_1->ss_family == AF_INET && ip_2->ss_family == AF_INET6 && IN6_IS_ADDR_V4MAPPED(&sin6_2->sin6_addr)) { struct sockaddr_storage t; struct sockaddr_in *temp; temp = (struct sockaddr_in *)&t; _access_unmap_v4(sin6_2, temp); if(netsize>96) netsize -= 96; return _access_check_match(ip_1, &t, netsize); } if (ip_1->ss_family == AF_INET6 && ip_2->ss_family == AF_INET && IN6_IS_ADDR_V4MAPPED(&sin6_1->sin6_addr)) { struct sockaddr_storage t; struct sockaddr_in *temp; temp = (struct sockaddr_in *)&t; _access_unmap_v4(sin6_1, temp); if(netsize>96) netsize -= 96; return _access_check_match(&t, ip_2, netsize); } return 0; } /* IPv4? */ if(ip_1->ss_family == AF_INET) { int netmask; if(netsize > 32) netsize = 32; netmask = htonl(-1 << (32-netsize)); return ((sin_1->sin_addr.s_addr&netmask) == (sin_2->sin_addr.s_addr&netmask)); } /* IPv6? */ if(ip_1->ss_family == AF_INET6) { unsigned char bytemask; if(netsize > 128) netsize = 128; for(i=0; i<netsize/8; i++) if(sin6_1->sin6_addr.s6_addr[i] != sin6_2->sin6_addr.s6_addr[i]) return 0; if(netsize%8 == 0) return 1; bytemask = 0xff << (8 - netsize%8); return ((sin6_1->sin6_addr.s6_addr[i]&bytemask) == (sin6_2->sin6_addr.s6_addr[i]&bytemask)); } /* unknown address family */ return 0; }
util/log用于打印日志, 支持syslog, stdout, file三种方式, 但代码里有不少关于DEBUG编译的特殊逻辑, 我看着也挺绕, 就不说明了.
log.c依赖util.h中的如下声明:
/* logging */ typedef enum { log_STDOUT, log_SYSLOG, log_FILE } log_type_t; typedef struct log_st { log_type_t type; FILE *file; } *log_t; typedef struct log_facility_st { const char *facility; int number; } log_facility_t; JABBERD2_API log_t log_new(log_type_t type, const char *ident, const char *facility); JABBERD2_API void log_write(log_t log, int level, const char *msgfmt, ...); JABBERD2_API void log_free(log_t log);
在log.c中可以看到这些日志级别与syslog日志级别相关的定义, log_new创建log, 对于syslog调用_log_facility获取syslog日记级别的数值, openlog打开syslog, 如果是file类型则创建文件, stdout则直接赋值FILE即可.
static int _log_facility(const char *facility) { log_facility_t *lp; if (facility == NULL) { return -1; } for (lp = _log_facilities; lp->facility; lp++) { if (!strcasecmp(lp->facility, facility)) { break; } } return lp->number; } log_t log_new(log_type_t type, const char *ident, const char *facility) { log_t log; int fnum = 0; log = (log_t) calloc(1, sizeof(struct log_st)); log->type = type; if(type == log_SYSLOG) { fnum = _log_facility(facility); if (fnum < 0) fnum = LOG_LOCAL7; openlog(ident, LOG_PID, fnum); return log; } else if(type == log_STDOUT) { log->file = stdout; return log; } log->file = fopen(ident, "a+"); if(log->file == NULL) { fprintf(stderr, "ERROR: couldn't open logfile: %s\n" " logging will go to stdout instead\n", strerror(errno)); log->type = log_STDOUT; log->file = stdout; } return log; }
下面的函数打印日志, 对于syslog类型优先使用vsyslog, 但如果没有vsyslog接口则使用vsnprintf+syslog完成相同功能.对于非syslog日志, 则自己拼装格式:时间[日志级别], 此处日志级别采用了syslog的级别设定, 从_log_level可以看出. 另外注意, 因为操作的FILE, 所以每次fprintf后立即调用了fflush, 除此之外, log对每行日志做了1024长度的限制, 过长将会被截断.
顺带log_free, 对于syslog调用closelog, 对于FILE则flclose.
void log_write(log_t log, int level, const char *msgfmt, ...) { va_list ap; char *pos, message[MAX_LOG_LINE+1]; int sz, len; time_t t; if(log && log->type == log_SYSLOG) { va_start(ap, msgfmt); #ifdef HAVE_VSYSLOG vsyslog(level, msgfmt, ap); #else len = vsnprintf(message, MAX_LOG_LINE, msgfmt, ap); if (len > MAX_LOG_LINE) message[MAX_LOG_LINE] = '\0'; else message[len] = '\0'; syslog(level, "%s", message); #endif va_end(ap); #ifndef DEBUG return; #endif } /* timestamp */ t = time(NULL); pos = ctime(&t); sz = strlen(pos); /* chop off the \n */ pos[sz-1]=' '; /* insert the header */ len = snprintf(message, MAX_LOG_LINE, "%s[%s] ", pos, _log_level[level]); if (len > MAX_LOG_LINE) message[MAX_LOG_LINE] = '\0'; else message[len] = '\0'; /* find the end and attach the rest of the msg */ for (pos = message; *pos != '\0'; pos++); /*empty statement */ sz = pos - message; va_start(ap, msgfmt); vsnprintf(pos, MAX_LOG_LINE - sz, msgfmt, ap); va_end(ap); #ifndef DEBUG if(log && log->type != log_SYSLOG) { #endif if(log && log->file) { fprintf(log->file,"%s", message); fprintf(log->file, "\n"); fflush(log->file); } #ifndef DEBUG } #endif #ifdef DEBUG if (!debug_log_target) { debug_log_target = stderr; } /* If we are in debug mode we want everything copied to the stdout */ if ((log == 0) || (get_debug_flag() && log->type != log_STDOUT)) { fprintf(debug_log_target, "%s\n", message); fflush(debug_log_target); } #endif /*DEBUG*/ } void log_free(log_t log) { if(log->type == log_SYSLOG) closelog(); else if(log->type == log_FILE) fclose(log->file); free(log); }