Jabberd2 添加动态域名管理功能 (上)

本项目源码已托管在Github上, 项目名称: Jabberd2s

  • 由来
    由于业务需求需要对Jabberd2进行了些修改, 主要是实现动态域名管理的功能. Jabberd2 本身可以支持多域名的XMPP服务器(例如: xmpp.server1.domain, xmpp.server2.domain, 等等…)同时运行,但是所有XMPP域名服务配置(位于c2s.xml和sm.xml中的配置)必须在服务器启动时全部初始化完成.初始化后对域名下XMPP服务器就无法做更多的控制.因此,修改Jabberd2以支持实时控制域名服务器的离线和上线以及服务器配置实时修改.这些修改主要针对多域名主机的管理,所以Github的项目名称为Jabberd2s.

  • 实现细节
    [Image

先看这个简单的图示,绿色部分是初始化内容, 粉色部分是添加的功能.

源码自身初始化过程中sm在建立和router的连接后触发event_OPEN事件,

代码1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// sm/sm.c

int sm_sx_callback(sx_t s, sx_event_t e, void *data, void *arg) 
{
     ...
      case event_OPEN:
            log_write(sm->log, LOG_NOTICE, "connection to router established");
            ...
            if(xhash_iter_first(sm->hosts))
            do {
                xhash_iter_get(sm->hosts, (void *) &domain, &len, NULL);
                /* skip already requested SM id */
                if (strlen(sm->id) == len && strncmp(sm->id, domain, len) == 0)
                    continue;
                nad = nad_new();
                ns = nad_add_namespace(nad, uri_COMPONENT, NULL);
                elem = nad_append_elem(nad, ns, "bind", 0);
                nad_set_attr(nad, elem, -1, "name", domain, len);
                nad_append_attr(nad, -1, "multi", "to");
                log_debug(ZONE, "requesting domain bind for '%.*s'", len, domain);
                sx_nad_write(sm->router, nad);
            } while(xhash_iter_next(sm->hosts));
            break;

     ...
}

sm发给router <bind/>节声明新增的绑定域名:

1
<bind xmlns='https://jabberd.jabberstudio.org/ns/component/1.0' multi='to' name='xmpp.server1.domain'/>

router广播<presence/>给c2s和s2s, 处理bind和unbind的分别是_router_process_bind_router_process_unbind, 这两个函数都很长,关键代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// router/router.c

static void _router_process_bind(component_t comp, nad_t nad)
{
    ...
    /* advertise name */

    // 广播域名信息给每条路由

    // 最后一个参数决定是否unavailable
    _router_advertise(comp->r, name->domain, comp, 0);
    /* tell the new component about everyone else */

   // 主动反向应答给sm, <presence .. from='c2s'/>
    xhash_walk(comp->r->routes, _router_advertise_reverse, (void *) comp);

    ...
}

static void _router_process_unbind(component_t comp, nad_t nad)

{

    ...

    /* deadvertise name */
    if(xhash_get(comp->r->routes, name->domain) == NULL)
        _router_advertise(comp->r, name->domain, comp, 1);

   // 最后一个参数为1, 将发送type="unavailable"属性

    ...

}

真正发送<presence/>节的在_router_advertise函数里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// router/router.c

/** domain advertisement */
static void _router_advertise(router_t r, const char *domain, component_t src, int unavail)

{
    struct broadcast_st bc;
    int ns;
    log_debug(ZONE, "advertising %s to all routes (unavail=%d)", domain, unavail);
    bc.r = r;
    bc.src = src;
    /* create a new packet */
    bc.nad = nad_new();
    ns = nad_add_namespace(bc.nad, uri_COMPONENT, NULL);
    nad_append_elem(bc.nad, ns, "presence", 0);
    nad_append_attr(bc.nad, -1, "from", domain);

    // 最后一个参数为1, 将发送type="unavailable"属性

    if(unavail)
        nad_append_attr(bc.nad, -1, "type", "unavailable");
    xhash_walk(r->routes, _router_broadcast, (void *) &bc);
    nad_free(bc.nad);
}

对于<bind/>节router发送内容:

1
<presence xmlns='https://jabberd.jabberstudio.org/ns/component/1.0' from='xmpp.server1.domain'/>

对于<unbind/>节router发送内容:

1
<presence xmlns='https://jabberd.jabberstudio.org/ns/component/1.0' type='unavailable' from='xmpp.server1.domain'/>

再看c2s收到<presence />后所做的反映:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// c2s/c2s.c

static void _c2s_component_presence(c2s_t c2s, nad_t nad)

{
    int attr;
    char from[1024];
    sess_t sess;
    union xhashv xhv;
    if((attr = nad_find_attr(nad, 0, -1, "from", NULL)) < 0) {
        nad_free(nad);
        return;
    }
    strncpy(from, NAD_AVAL(nad, attr), NAD_AVAL_L(nad, attr));
    from[NAD_AVAL_L(nad, attr)] = '\0';
    if(nad_find_attr(nad, 0, -1, "type", NULL) < 0) {
        log_debug(ZONE, "component available from '%s'", from);
        log_debug(ZONE, "sm for serviced domain '%s' online", from);
        // 没有type属性, 则添加到c2s->sm_avail哈希字典中
        xhash_put(c2s->sm_avail, pstrdup(xhash_pool(c2s->sm_avail), from), (void *) 1);
        nad_free(nad);
        return;
    }
    if(nad_find_attr(nad, 0, -1, "type", "unavailable") < 0) {
        nad_free(nad);
        return;
    }

    // 有type属性, 而且属性值是unavailable

    log_debug(ZONE, "component unavailable from '%s'", from);
    if(xhash_get(c2s->sm_avail, from) != NULL) {
        log_debug(ZONE, "sm for serviced domain '%s' offline", from);
        if(xhash_iter_first(c2s->sessions))
            do {
                xhv.sess_val = &sess;
                xhash_iter_get(c2s->sessions, NULL, NULL, xhv.val);
                if(sess->resources != NULL && strcmp(sess->resources->jid->domain, from) == 0) {
                    log_debug(ZONE, "killing session %s", jid_user(sess->resources->jid));
                    // 关闭该域下的所有会话
                    sess->active = 0;
                    if(sess->s) sx_close(sess->s);
                }
            } while(xhash_iter_next(c2s->sessions));
        xhash_zap(c2s->sm_avail, from);
    }
}

因此,从sm的角度看所有的域名服务器都是一个会话端点,只需要删除这个host并发送<unbind/>通知给router即可使其离线:

1
<unbind xmlns='https://jabberd.jabberstudio.org/ns/component/1.0' multi='to' name='xmpp.server1.domain'/>

新增函数_sm_hosts_reconfig和信号量值sm_reconf, 修改了SIGUSR1信号触发的逻辑,

原本SIGUSR1关闭调试, SIGUSR2开启调试, 现在改为SIGUSR2切换Debug状态, SIGUSR1触发_sm_hosts_reconfig.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*  Use SIGUSR1 to reconfig hosts map */
static void _sm_signal_usr1(int signum)
{
    // Original line
    //set_debug_flag(0);
    sm_reconf = 1;
}
/*  Use SIGUSR2 to switch debug flag */
static void _sm_signal_usr2(int signum)
{
    //set_debug_flag(1);
    if(get_debug_flag())
        set_debug_flag(0);
    else
        set_debug_flag(1);
}

代码2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// sm/main.c

/* reconfig hosts with configurations and broadcast it */
static void _sm_hosts_reconfig(sm_t sm, config_t conf)
{
    config_elem_t elem;
    char id[1024];
    int len, i, ns, nelem;
    xht domains;
    char *host;
    nad_t nad;
    elem = config_get(conf, "local.id");
    /* We only care about host reconfiguration at here, don`t set sm->id to hosts*/
    if(!elem)return;
    domains = xhash_new(1021);
    for(i = 0; i < elem->nvalues; i++) {
        /* stringprep ids (domain names) so that they are in canonical form */
        strncpy(id, elem->values[i], 1024);
        id[1023] = '\0';
        if (stringprep_nameprep(id, 1024) != 0) {
            log_write(sm->log, LOG_ERR, "cannot stringprep id %s, aborting", id);
            exit(1);
        }
        if(xhash_get(sm->hosts, id) == NULL) {
            // new host added, send bind information, 新增主机域名
            nad = nad_new();
            ns = nad_add_namespace(nad, uri_COMPONENT, NULL);
            nelem = nad_append_elem(nad, ns, "bind", 0);
            nad_set_attr(nad, nelem, -1, "name", id, strlen(id));
            nad_append_attr(nad, -1, "multi", "to");
            log_debug(ZONE, "requesting domain bind for '%.*s'", strlen(id), id);
            sx_nad_write(sm->router, nad);
        } else {
            xhash_zap(sm->hosts, id); // eliminated host will stand at last
        }
        xhash_put(domains, pstrdup(xhash_pool(domains), id), sm);
    }

    // 剩下的都是被移除了的主机域名,发送unbind
    // send unbind information for eliminated hosts
    if(xhash_iter_first(sm->hosts)) {
        do {
            xhash_iter_get(sm->hosts, (void *) &host, &len, NULL);
            nad = nad_new();
            ns = nad_add_namespace(nad, uri_COMPONENT, NULL);
            nelem = nad_append_elem(nad, ns, "unbind", 0);
            nad_set_attr(nad, nelem, -1, "name", host, len);
            nad_append_attr(nad, -1, "multi", "to");
            log_debug(ZONE, "requesting domain bind for '%.*s'", len, host);
            sx_nad_write(sm->router, nad);
        }while(xhash_iter_next(sm->hosts));
    }
    // 因为sm->hosts这个哈希字典在sm中只用来查询,没有抛出其内部指针,

    // 所以不用考虑原有的hosts指针失效, 直接free掉整个旧hosts,替换新的
    // replace sm->hosts
    xhash_free(sm->hosts);
    sm->hosts = domains;
}

最后在main函数的while循环里加上sm_reconf触发:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
while(!sm_shutdown)
{
mio_run(sm->mio, 5);

// ...

// reload configurations
if(sm_reconf) {
log_write(sm->log, LOG_NOTICE, "SIGUSR1: reloading some configuration items ...");
sleep(20);
config_t conf;
conf = config_new();

if (conf && config_load(conf, config_file) == 0) {
_sm_hosts_reconfig(sm, conf);

config_free(conf);
log_write(sm->log, LOG_NOTICE, "reloading finished");
} else {
log_write(sm->log, LOG_WARNING, "couldn't reload config (%s)", config_file);
if (conf) config_free(conf);
}

sm_reconf = 0;
}
}

到此为止, 已经可以控制已有服务器的离线和上线,原因是由于完整的服务器配置由c2s管理,因此还需要对c2s修改,使其重新载入新的c2s.xml配置.

本项目源码已托管在Github上, 项目名称: Jabberd2s