/* * $Id: dispatch.c 2347 2007-06-08 17:21:22Z miconda $ * * dispatcher module * * Copyright (C) 2004-2006 FhG Fokus * Copyright (C) 2005 Voice-System.ro * * This file is part of openser, a free SIP server. * * openser is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version * * openser is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * History * ------- * 2004-07-31 first version, by daniel * 2005-04-22 added ruri & to_uri hashing (andrei) * 2005-12-10 added failover support via avp (daniel) * */ #include #include #include #include "../../ut.h" #include "../../trim.h" #include "../../dprint.h" #include "../../action.h" #include "../../route.h" #include "../../dset.h" #include "../../mem/shm_mem.h" #include "../../parser/parse_uri.h" #include "../../parser/parse_from.h" #include "../../usr_avp.h" #include "../../mi/mi.h" #include "dispatch.h" typedef struct _ds_setidx { int id; int index; struct _ds_setidx *next; } ds_setidx_t, *ds_setidx_p; typedef struct _ds_dest { str uri; int flags; struct _ds_dest *next; } ds_dest_t, *ds_dest_p; typedef struct _ds_set { int id; /* id of dst set */ int nr; /* number of items in dst set */ int index; /* index pf dst set */ int last; /* last used item in dst set */ ds_dest_p dlist; struct _ds_set *next; } ds_set_t, *ds_set_p; extern int ds_force_dst; ds_setidx_p _ds_index = NULL; ds_set_p _ds_list = NULL; int _ds_list_nr = 0; /** * */ int ds_load_list(char *lfile) { char line[256], *p; FILE *f = NULL; int id, i, j, setn; str uri; struct sip_uri puri; ds_dest_p dp = NULL, dp0 = NULL; ds_set_p sp = NULL, sp0 = NULL; ds_setidx_p si = NULL, si0 = NULL; if(lfile==NULL || strlen(lfile)<=0) { LOG(L_ERR, "DISPATCHER:ds_load_list: bad list file\n"); return -1; } f = fopen(lfile, "r"); if(f==NULL) { LOG(L_ERR, "DISPATCHER:ds_load_list: can't open list file [%s]\n", lfile); return -1; } id = setn = 0; p = fgets(line, 256, f); while(p) { /* eat all white spaces */ while(*p && (*p==' ' || *p=='\t' || *p=='\r' || *p=='\n')) p++; if(*p=='\0' || *p=='#') goto next_line; /* get set id */ id = 0; while(*p>='0' && *p<='9') { id = id*10+ (*p-'0'); p++; } /* eat all white spaces */ while(*p && (*p==' ' || *p=='\t' || *p=='\r' || *p=='\n')) p++; if(*p=='\0' || *p=='#') { LOG(L_ERR, "DISPATCHER:ds_load_list: bad line [%s]\n", line); goto error; } /* get uri */ uri.s = p; while(*p && *p!=' ' && *p!='\t' && *p!='\r' && *p!='\n' && *p!='#') p++; uri.len = p-uri.s; /* check uri */ if(parse_uri(uri.s, uri.len, &puri)!=0) { LOG(L_ERR, "DISPATCHER:ds_load_list: bad uri [%.*s]\n", uri.len, uri.s); goto error; } /* check index */ si = _ds_index; while(si) { if(si->id == id) break; si = si->next; } if(si==NULL) { si = (ds_setidx_p)shm_malloc(sizeof(ds_setidx_t)); if(si==NULL) { LOG(L_ERR, "DISPATCHER:ds_load_list: no more memory\n"); goto error; } memset(si, 0, sizeof(ds_setidx_t)); si->id = id; si->next = _ds_index; if(_ds_index!=NULL) si->index = _ds_index->index + 1; _ds_index = si; } /* get dest set */ sp = _ds_list; while(sp) { if(sp->id == id) break; sp = sp->next; } if(sp==NULL) { sp = (ds_set_p)shm_malloc(sizeof(ds_set_t)); if(sp==NULL) { LOG(L_ERR, "DISPATCHER:ds_load_list: no more memory.\n"); goto error; } memset(sp, 0, sizeof(ds_set_t)); sp->next = _ds_list; _ds_list = sp; setn++; } sp->id = id; sp->nr++; sp->index = si->index; /* store uri */ dp = (ds_dest_p)shm_malloc(sizeof(ds_dest_t)); if(dp==NULL) { LOG(L_ERR, "DISPATCHER:ds_load_list: no more memory!\n"); goto error; } memset(dp, 0, sizeof(ds_dest_t)); dp->uri.s = (char*)shm_malloc(uri.len+1); if(dp->uri.s==NULL) { LOG(L_ERR, "DISPATCHER:ds_load_list: no more memory!!\n"); shm_free(dp); goto error; } strncpy(dp->uri.s, uri.s, uri.len); dp->uri.s[uri.len]='\0'; dp->uri.len = uri.len; dp->next = sp->dlist; sp->dlist = dp; DBG("DISPATCHER:ds_load_list: dest [%d/%d/%d] <%.*s>\n", sp->index, sp->id, sp->nr, dp->uri.len, dp->uri.s); next_line: p = fgets(line, 256, f); } fclose(f); f = NULL; DBG("DISPATCHER:ds_load_list: found [%d] dest sets\n", setn); /* re-index destination sets for fast access */ sp0 = (ds_set_p)shm_malloc(setn*sizeof(ds_set_t)); if(sp0==NULL) { LOG(L_ERR, "DISPATCHER:ds_load_list: no more memory!!\n"); goto error; } memset(sp0, 0, setn*sizeof(ds_set_t)); sp = _ds_list; for(i=setn-1; i>=0; i--) { memcpy(&sp0[i], sp, sizeof(ds_set_t)); if(i==setn-1) sp0[i].next = NULL; else sp0[i].next = &sp0[i+1]; dp0 = (ds_dest_p)shm_malloc(sp0[i].nr*sizeof(ds_dest_t)); if(dp0==NULL) { LOG(L_ERR, "DISPATCHER:ds_load_list: no more memory!\n"); ds_destroy_list(); goto error; } memset(dp0, 0, sp0[i].nr*sizeof(ds_dest_t)); dp = sp0[i].dlist; for(j=sp0[i].nr-1; j>=0; j--) { memcpy(&dp0[j], dp, sizeof(ds_dest_t)); if(j==sp0[j].nr-1) dp0[j].next = NULL; else dp0[j].next = &dp0[j+1]; dp = dp->next; } sp0[i].dlist = dp0; sp = sp->next; } sp = _ds_list; _ds_list = sp0; while(sp) { dp = sp->dlist; while(dp) { dp0 = dp; dp = dp->next; shm_free(dp0); } sp0 = sp; sp = sp->next; shm_free(sp0); } _ds_list_nr = setn; return 0; error: if(f!=NULL) fclose(f); si = _ds_index; while(si) { si0 = si; si = si->next; shm_free(si0); } _ds_index = NULL; sp = _ds_list; while(sp) { dp = sp->dlist; while(dp) { if(dp->uri.s!=NULL) shm_free(dp->uri.s); dp->uri.s=NULL; dp0 = dp; dp = dp->next; shm_free(dp0); } sp0 = sp; sp = sp->next; shm_free(sp0); } return -1; } /** * */ int ds_destroy_list() { int i; ds_set_p sp = NULL; ds_setidx_p si = NULL, si0 = NULL; sp = _ds_list; while(sp) { for(i=0; inr; i++) { if(sp->dlist[i].uri.s!=NULL) { shm_free(sp->dlist[i].uri.s); sp->dlist[i].uri.s = NULL; } } shm_free(sp->dlist); sp = sp->next; } if (_ds_list) shm_free(_ds_list); si = _ds_index; while(si) { si0 = si; si = si->next; shm_free(si0); } _ds_index = NULL; return 0; } /** * */ unsigned int ds_get_hash(str *x, str *y) { char* p; register unsigned v; register unsigned h; if(!x && !y) return 0; h=0; if(x) { p=x->s; if (x->len>=4) { for (; p<=(x->s+x->len-4); p+=4) { v=(*p<<24)+(p[1]<<16)+(p[2]<<8)+p[3]; h+=v^(v>>3); } } v=0; for (;p<(x->s+x->len); p++) { v<<=8; v+=*p; } h+=v^(v>>3); } if(y) { p=y->s; if (y->len>=4) { for (; p<=(y->s+y->len-4); p+=4) { v=(*p<<24)+(p[1]<<16)+(p[2]<<8)+p[3]; h+=v^(v>>3); } } v=0; for (;p<(y->s+y->len); p++) { v<<=8; v+=*p; } h+=v^(v>>3); } h=((h)+(h>>11))+((h>>13)+(h>>23)); return (h)?h:1; } /* * gets the part of the uri we will use as a key for hashing * params: key1 - will be filled with first part of the key * (uri user or "" if no user) * key2 - will be filled with the second part of the key * (uri host:port) * uri - str with the whole uri * parsed_uri - struct sip_uri pointer with the parsed uri * (it must point inside uri). It can be null * (in this case the uri will be parsed internally). * flags - if & DS_HASH_USER_ONLY, only the user part of the uri * will be used * returns: -1 on error, 0 on success */ static inline int get_uri_hash_keys(str* key1, str* key2, str* uri, struct sip_uri* parsed_uri, int flags) { struct sip_uri tmp_p_uri; /* used only if parsed_uri==0 */ if (parsed_uri==0){ if (parse_uri(uri->s, uri->len, &tmp_p_uri)<0){ LOG(L_ERR, "DISPATCHER: get_uri_hash_keys: invalid uri %.*s\n", uri->len, uri->len?uri->s:""); goto error; } parsed_uri=&tmp_p_uri; } /* uri sanity checks */ if (parsed_uri->host.s==0){ LOG(L_ERR, "DISPATCHER: get_uri_hash_keys: invalid uri, no host" "present: %.*s\n", uri->len, uri->len?uri->s:""); goto error; } /* we want: user@host:port if port !=5060 * user@host if port==5060 * user if the user flag is set*/ *key1=parsed_uri->user; key2->s=0; key2->len=0; if (!(flags & DS_HASH_USER_ONLY)){ /* key2=host */ *key2=parsed_uri->host; /* add port if needed */ if (parsed_uri->port.s!=0){ /* uri has a port */ /* skip port if == 5060 or sips and == 5061 */ if (parsed_uri->port_no != ((parsed_uri->type==SIPS_URI_T)?SIPS_PORT:SIP_PORT)) key2->len+=parsed_uri->port.len+1 /* ':' */; } } if (key1->s==0){ LOG(L_WARN, "DISPATCHER: get_uri_hashs_keys: empty username in:" " %.*s\n", uri->len, uri->len?uri->s:""); } return 0; error: return -1; } /** * */ int ds_hash_fromuri(struct sip_msg *msg, unsigned int *hash) { str from; str key1; str key2; if(msg==NULL || hash == NULL) { LOG(L_ERR, "DISPATCHER:ds_hash_fromuri: bad parameters\n"); return -1; } if(parse_from_header(msg)<0) { LOG(L_ERR, "DISPATCHER:ds_hash_fromuri:ERROR cannot parse From hdr\n"); return -1; } if(msg->from==NULL || get_from(msg)==NULL) { LOG(L_ERR, "DISPATCHER:ds_hash_fromuri:ERROR cannot get From uri\n"); return -1; } from = get_from(msg)->uri; trim(&from); if (get_uri_hash_keys(&key1, &key2, &from, 0, ds_flags)<0) return -1; *hash = ds_get_hash(&key1, &key2); return 0; } /** * */ int ds_hash_touri(struct sip_msg *msg, unsigned int *hash) { str to; str key1; str key2; if(msg==NULL || hash == NULL) { LOG(L_ERR, "DISPATCHER:ds_hash_touri: bad parameters\n"); return -1; } if ((msg->to==0) && ((parse_headers(msg, HDR_TO_F, 0)==-1) || (msg->to==0))) { LOG(L_ERR, "DISPATCHER:ds_hash_touri:ERROR cannot parse To hdr\n"); return -1; } to = get_to(msg)->uri; trim(&to); if (get_uri_hash_keys(&key1, &key2, &to, 0, ds_flags)<0) return -1; *hash = ds_get_hash(&key1, &key2); return 0; } /** * */ int ds_hash_callid(struct sip_msg *msg, unsigned int *hash) { str cid; if(msg==NULL || hash == NULL) { LOG(L_ERR, "DISPATCHER:ds_hash_callid: bad parameters\n"); return -1; } if(msg->callid==NULL && ((parse_headers(msg, HDR_CALLID_F, 0)==-1) || (msg->callid==NULL)) ) { LOG(L_ERR, "DISPATCHER:ds_hash_callid:ERROR cannot parse Call-Id\n"); return -1; } cid.s = msg->callid->body.s; cid.len = msg->callid->body.len; trim(&cid); *hash = ds_get_hash(&cid, NULL); return 0; } int ds_hash_ruri(struct sip_msg *msg, unsigned int *hash) { str* uri; str key1; str key2; if(msg==NULL || hash == NULL) { LOG(L_ERR, "DISPATCHER:ds_hash_ruri: bad parameters\n"); return -1; } if (parse_sip_msg_uri(msg)<0){ LOG(L_ERR, "DISPATCHER: ds_hash_ruri: ERROR: bad request uri\n"); return -1; } uri=GET_RURI(msg); if (get_uri_hash_keys(&key1, &key2, uri, &msg->parsed_uri, ds_flags)<0) return -1; *hash = ds_get_hash(&key1, &key2); return 0; } static inline int ds_get_index(int group, int *index) { ds_setidx_p si = NULL; if(index==NULL || group<0 || _ds_index==NULL) return -1; /* get the index of the set */ si = _ds_index; while(si) { if(si->id == group) { *index = si->index; break; } si = si->next; } if(si==NULL) { LOG(L_ERR, "DISPATCHER:ds_get_index: destination set [%d] not found\n",group); return -1; } return 0; } static inline int ds_update_dst(struct sip_msg *msg, str *uri, int mode) { struct action act; switch(mode) { case 1: act.type = SET_HOSTPORT_T; act.elem[0].type = STRING_ST; if(uri->len>4 && strncasecmp(uri->s,"sip:",4)==0) act.elem[0].u.string = uri->s+4; else act.elem[0].u.string = uri->s; act.next = 0; if (do_action(&act, msg) < 0) { LOG(L_ERR, "DISPATCHER:dst_update_dst: Error while setting host\n"); return -1; } if(route_type==FAILURE_ROUTE) { if (append_branch(msg, 0, 0, 0, Q_UNSPECIFIED, 0, 0)!=1 ) { LOG(L_ERR, "DISPATCHER:dst_update_dst: append_branch action" " failed\n"); return -1; } } break; default: if(route_type==FAILURE_ROUTE) { if (append_branch(msg, 0, uri, 0, Q_UNSPECIFIED, 0, 0)!=1 ) { LOG(L_ERR, "DISPATCHER:dst_update_dst: append_branch action" " failed\n"); return -1; } } else { if (set_dst_uri(msg, uri) < 0) { LOG(L_ERR, "DISPATCHER:dst_update_dst: Error while setting dst_uri\n"); return -1; } } break; } return 0; } /** * */ int ds_select_dst(struct sip_msg *msg, int set, int alg, int mode) { int idx, i, cnt; unsigned int hash; int_str avp_val; if(msg==NULL) { LOG(L_ERR, "DISPATCHER:ds_select_dst: bad parameters\n"); return -1; } if(_ds_list==NULL || _ds_index==NULL) { LOG(L_ERR, "DISPATCHER:ds_select_dst: no destination sets\n"); return -1; } if((mode==0) && (ds_force_dst==0) && (msg->dst_uri.s!=NULL || msg->dst_uri.len>0)) { LOG(L_ERR, "DISPATCHER:ds_select_dst: destination already set [%.*s]\n", msg->dst_uri.len, msg->dst_uri.s); return -1; } /* get the index of the set */ if(ds_get_index(set, &idx)!=0) { LOG(L_ERR, "DISPATCHER:ds_select_dst: destination set [%d] not found\n",set); return -1; } DBG("DISPATCHER:ds_select_dst: set index [%d->%d]\n", set, idx); hash = 0; switch(alg) { case 0: if(ds_hash_callid(msg, &hash)!=0) { LOG(L_ERR, "DISPATCHER:ds_select_dst: can't get callid hash\n"); return -1; } break; case 1: if(ds_hash_fromuri(msg, &hash)!=0) { LOG(L_ERR, "DISPATCHER:ds_select_dst: can't get From uri hash\n"); return -1; } break; case 2: if(ds_hash_touri(msg, &hash)!=0) { LOG(L_ERR, "DISPATCHER:ds_select_dst: can't get To uri hash\n"); return -1; } break; case 3: if (ds_hash_ruri(msg, &hash)!=0) { LOG(L_ERR, "DISPATCHER:ds_select_dst: can't get ruri hash\n"); return -1; } break; case 4: hash = _ds_list[idx].last; _ds_list[idx].last = (_ds_list[idx].last+1) % _ds_list[idx].nr; break; default: LOG(L_WARN, "WARNING: ds_select_dst: algo %d not implemented" " - using first entry...\n", alg); hash = 0; } DBG("DISPATCHER:ds_select_dst: alg hash [%u]\n", hash); cnt = 0; if(ds_use_default!=0 && _ds_list[idx].nr!=1) hash = hash%(_ds_list[idx].nr-1); else hash = hash%_ds_list[idx].nr; i=hash; while(_ds_list[idx].dlist[i].flags & DS_INACTIVE_DST) { if(ds_use_default!=0) i = (i+1)%(_ds_list[idx].nr-1); else i = (i+1)%_ds_list[idx].nr; if(i==hash) { if(ds_use_default!=0) { i = _ds_list[idx].nr-1; } else { return -1; } } } hash = i; if(ds_update_dst(msg, &_ds_list[idx].dlist[hash].uri, mode)!=0) { LOG(L_ERR, "DISPATCHER:ds_select_dst: cannot set dst addr\n"); return -1; } DBG("DISPATCHER:ds_select_dst: selected [%d-%d/%d/%d] <%.*s>\n", alg, set, idx, hash, _ds_list[idx].dlist[hash].uri.len, _ds_list[idx].dlist[hash].uri.s); if(!(ds_flags&DS_FAILOVER_ON)) return 1; if(dst_avp_name.n!=0) { if(ds_use_default!=0 && hash!=_ds_list[idx].nr-1) { avp_val.s = _ds_list[idx].dlist[_ds_list[idx].nr-1].uri; if(add_avp(AVP_VAL_STR|dst_avp_type, dst_avp_name, avp_val)!=0) return -1; cnt++; } /* add to avp */ for(i=hash-1; i>=0; i--) { if((_ds_list[idx].dlist[i].flags & DS_INACTIVE_DST) || (ds_use_default!=0 && i==(_ds_list[idx].nr-1))) continue; DBG("DISPATCHER:ds_select_dst: using entry [%d/%d]\n", set, i); avp_val.s = _ds_list[idx].dlist[i].uri; if(add_avp(AVP_VAL_STR|dst_avp_type, dst_avp_name, avp_val)!=0) return -1; cnt++; } for(i=_ds_list[idx].nr-1; i>hash; i--) { if((_ds_list[idx].dlist[i].flags & DS_INACTIVE_DST) || (ds_use_default!=0 && i==(_ds_list[idx].nr-1))) continue; DBG("DISPATCHER:ds_select_dst: using entry [%d/%d]\n", set, i); avp_val.s = _ds_list[idx].dlist[i].uri; if(add_avp(AVP_VAL_STR|dst_avp_type, dst_avp_name, avp_val)!=0) return -1; cnt++; } /* add to avp the first used dst */ avp_val.s = _ds_list[idx].dlist[hash].uri; if(add_avp(AVP_VAL_STR|dst_avp_type, dst_avp_name, avp_val)!=0) return -1; cnt++; } if(grp_avp_name.n!=0) { /* add to avp the group id */ avp_val.n = set; if(add_avp(grp_avp_type, grp_avp_name, avp_val)!=0) return -1; } if(cnt_avp_name.n!=0) { /* add to avp the number of dst */ avp_val.n = cnt; if(add_avp(cnt_avp_type, cnt_avp_name, avp_val)!=0) return -1; } return 1; } int ds_next_dst(struct sip_msg *msg, int mode) { struct usr_avp *avp; struct usr_avp *prev_avp; int_str avp_value; if(!(ds_flags&DS_FAILOVER_ON) || dst_avp_name.n==0) { LOG(L_WARN, "DISPATCHER:ds_next_dst: failover support disabled\n"); return -1; } prev_avp = search_first_avp(dst_avp_type, dst_avp_name, &avp_value, 0); if(prev_avp==NULL) return -1; /* used avp deleted -- strange */ avp = search_next_avp(prev_avp, &avp_value); destroy_avp(prev_avp); if(avp==NULL || !(avp->flags&AVP_VAL_STR)) return -1; /* no more avps or value is int */ if(ds_update_dst(msg, &avp_value.s, mode)!=0) { LOG(L_ERR, "DISPATCHER:ds_next_dst: cannot set dst addr\n"); return -1; } DBG("DISPATCHER:ds_next_dst: using [%.*s]\n", avp_value.s.len, avp_value.s.s); return 1; } int ds_mark_dst(struct sip_msg *msg, int mode) { int group, ret; struct usr_avp *prev_avp; int_str avp_value; if(!(ds_flags&DS_FAILOVER_ON)) { LOG(L_WARN, "DISPATCHER:ds_mark_dst: failover support disabled\n"); return -1; } prev_avp = search_first_avp(grp_avp_type, grp_avp_name, &avp_value, 0); if(prev_avp==NULL || prev_avp->flags&AVP_VAL_STR) return -1; /* grp avp deleted -- strange */ group = avp_value.n; prev_avp = search_first_avp(dst_avp_type, dst_avp_name, &avp_value, 0); if(prev_avp==NULL || !(prev_avp->flags&AVP_VAL_STR)) return -1; /* dst avp deleted -- strange */ if(mode==1) ret = ds_set_state(group, &avp_value.s, DS_INACTIVE_DST, 0); else ret = ds_set_state(group, &avp_value.s, DS_INACTIVE_DST, 1); DBG("DISPATCHER:ds_mark_dst: mode [%d] grp [%d] dst [%.*s]\n", mode, group, avp_value.s.len, avp_value.s.s); return (ret==0)?1:-1; } int ds_set_state(int group, str *address, int state, int type) { int i=0, idx=0; if(_ds_list==NULL) { LOG(L_ERR, "DISPATCHER:ds_set_state: the list is null\n"); return -1; } /* get the index of the set */ if(ds_get_index(group, &idx)!=0) { LOG(L_ERR, "DISPATCHER:ds_set_state: destination set [%d] not found\n",group); return -1; } while(i<_ds_list[idx].nr) { if(_ds_list[idx].dlist[i].uri.len==address->len && strncasecmp(_ds_list[idx].dlist[i].uri.s, address->s, address->len)==0) { if(type) _ds_list[idx].dlist[i].flags |= state; else _ds_list[idx].dlist[i].flags &= ~state; return 0; } i++; } return -1; } int ds_print_list(FILE *fout) { int i, j; if(_ds_list==NULL || _ds_list_nr<=0) { LOG(L_ERR, "DISPATCHER:ds_print_list: the list is null\n"); return -1; } fprintf(fout, "\nnumber of destination sets: %d\n", _ds_list_nr); for(i=0; i<_ds_list_nr; i++) { fprintf(fout, "\n set #%d\n", _ds_list[i].id); for(j=0; j<_ds_list[i].nr; j++) { fprintf(fout, " %c %.*s\n", (_ds_list[i].dlist[j].flags&DS_INACTIVE_DST)?'I':'A', _ds_list[i].dlist[j].uri.len, _ds_list[i].dlist[j].uri.s); } } return 0; } int ds_print_mi_list(struct mi_node* rpl) { int i, j, len; char* p; char c; struct mi_node* node = NULL; struct mi_node* set_node = NULL; struct mi_attr* attr = NULL; if(_ds_list==NULL || _ds_list_nr<=0) { LOG(L_ERR, "DISPATCHER:ds_mi_print_list: the list is null\n"); return 0; } p= int2str(_ds_list_nr, &len); node = add_mi_node_child(rpl, MI_DUP_VALUE, "SET_NO",6, p, len); if(node== NULL) return -1; for(i=0; i<_ds_list_nr; i++) { p = int2str(_ds_list[i].id, &len); set_node= add_mi_node_child(rpl, MI_DUP_VALUE,"SET", 3, p, len); if(set_node == NULL) return -1; for(j=0; j<_ds_list[i].nr; j++) { node= add_mi_node_child(set_node, 0, "URI", 3, _ds_list[i].dlist[j].uri.s, _ds_list[i].dlist[j].uri.len); if(node == NULL) return -1; c = (_ds_list[i].dlist[j].flags&DS_INACTIVE_DST)?'I':'A'; attr = add_mi_attr (node, MI_DUP_VALUE, "flag",4, &c, 1); if(attr == 0) return -1; } } return 0; }