2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 1999 - 2005, Digium, Inc.
6 * Mark Spencer <markster@digium.com>
8 * Funding provided by nic.at
10 * See http://www.asterisk.org for more information about
11 * the Asterisk project. Please do not directly contact
12 * any of the maintainers of this project for assistance;
13 * the project provides a web site, mailing lists and IRC
14 * channels for your use.
16 * This program is free software, distributed under the terms of
17 * the GNU General Public License Version 2. See the LICENSE file
18 * at the top of the source tree.
23 * \brief DNS SRV Record Lookup Support for Asterisk
25 * \author Mark Spencer <markster@digium.com>
27 * \arg See also \ref AstENUM
29 * \note Funding provided by nic.at
34 ASTERISK_FILE_VERSION(__FILE__
, "$Revision$")
36 #include <sys/types.h>
37 #include <netinet/in.h>
38 #include <arpa/nameser.h>
39 #if __APPLE_CC__ >= 1495
40 #include <arpa/nameser_compat.h>
47 #include "asterisk/channel.h"
48 #include "asterisk/logger.h"
49 #include "asterisk/srv.h"
50 #include "asterisk/dns.h"
51 #include "asterisk/options.h"
52 #include "asterisk/utils.h"
53 #include "asterisk/linkedlists.h"
61 unsigned short priority
;
62 unsigned short weight
;
64 unsigned int weight_sum
;
65 AST_LIST_ENTRY(srv_entry
) list
;
70 unsigned int have_weights
:1;
71 AST_LIST_HEAD_NOLOCK(srv_entries
, srv_entry
) entries
;
74 static int parse_srv(unsigned char *answer
, int len
, unsigned char *msg
, struct srv_entry
**result
)
77 unsigned short priority
;
78 unsigned short weight
;
80 } __attribute__ ((__packed__
)) *srv
= (struct srv
*) answer
;
84 struct srv_entry
*entry
;
86 if (len
< sizeof(*srv
))
89 answer
+= sizeof(*srv
);
92 if ((res
= dn_expand(msg
, answer
+ len
, answer
, repl
, sizeof(repl
) - 1)) <= 0) {
93 ast_log(LOG_WARNING
, "Failed to expand hostname\n");
97 /* the magic value "." for the target domain means that this service
98 is *NOT* available at the domain we searched */
99 if (!strcmp(repl
, "."))
102 if (!(entry
= ast_calloc(1, sizeof(*entry
) + strlen(repl
))))
105 entry
->priority
= ntohs(srv
->priority
);
106 entry
->weight
= ntohs(srv
->weight
);
107 entry
->port
= ntohs(srv
->port
);
108 strcpy(entry
->host
, repl
);
115 static int srv_callback(void *context
, unsigned char *answer
, int len
, unsigned char *fullanswer
)
117 struct srv_context
*c
= (struct srv_context
*) context
;
118 struct srv_entry
*entry
= NULL
;
119 struct srv_entry
*current
;
121 if (parse_srv(answer
, len
, fullanswer
, &entry
))
127 AST_LIST_TRAVERSE_SAFE_BEGIN(&c
->entries
, current
, list
) {
128 /* insert this entry just before the first existing
129 entry with a higher priority */
130 if (current
->priority
<= entry
->priority
)
133 AST_LIST_INSERT_BEFORE_CURRENT(&c
->entries
, entry
, list
);
137 AST_LIST_TRAVERSE_SAFE_END
;
139 /* if we didn't find a place to insert the entry before an existing
140 entry, then just add it to the end */
142 AST_LIST_INSERT_TAIL(&c
->entries
, entry
, list
);
147 /* Do the bizarre SRV record weight-handling algorithm
148 involving sorting and random number generation...
150 See RFC 2782 if you want know why this code does this
152 static void process_weights(struct srv_context
*context
)
154 struct srv_entry
*current
;
155 struct srv_entries newlist
= AST_LIST_HEAD_NOLOCK_INIT_VALUE
;
157 while (AST_LIST_FIRST(&context
->entries
)) {
158 unsigned int random_weight
;
159 unsigned int weight_sum
;
160 unsigned short cur_priority
= AST_LIST_FIRST(&context
->entries
)->priority
;
161 struct srv_entries temp_list
= AST_LIST_HEAD_NOLOCK_INIT_VALUE
;
164 AST_LIST_TRAVERSE_SAFE_BEGIN(&context
->entries
, current
, list
) {
165 if (current
->priority
!= cur_priority
)
168 AST_LIST_REMOVE_CURRENT(&context
->entries
, list
);
169 AST_LIST_INSERT_TAIL(&temp_list
, current
, list
);
171 AST_LIST_TRAVERSE_SAFE_END
;
173 while (AST_LIST_FIRST(&temp_list
)) {
175 AST_LIST_TRAVERSE(&temp_list
, current
, list
)
176 current
->weight_sum
= weight_sum
+= current
->weight
;
178 /* if all the remaining entries have weight == 0,
179 then just append them to the result list and quit */
180 if (weight_sum
== 0) {
181 AST_LIST_APPEND_LIST(&newlist
, &temp_list
, list
);
185 random_weight
= 1 + (unsigned int) ((float) weight_sum
* (ast_random() / ((float) RAND_MAX
+ 1.0)));
187 AST_LIST_TRAVERSE_SAFE_BEGIN(&temp_list
, current
, list
) {
188 if (current
->weight
< random_weight
)
191 AST_LIST_REMOVE_CURRENT(&temp_list
, list
);
192 AST_LIST_INSERT_TAIL(&newlist
, current
, list
);
195 AST_LIST_TRAVERSE_SAFE_END
;
200 /* now that the new list has been ordered,
203 AST_LIST_APPEND_LIST(&context
->entries
, &newlist
, list
);
206 int ast_get_srv(struct ast_channel
*chan
, char *host
, int hostlen
, int *port
, const char *service
)
208 struct srv_context context
= { .entries
= AST_LIST_HEAD_NOLOCK_INIT_VALUE
};
209 struct srv_entry
*current
;
212 if (chan
&& ast_autoservice_start(chan
) < 0)
215 ret
= ast_search_dns(&context
, service
, C_IN
, T_SRV
, srv_callback
);
217 if (context
.have_weights
)
218 process_weights(&context
);
221 ret
|= ast_autoservice_stop(chan
);
223 /* TODO: there could be a "." entry in the returned list of
224 answers... if so, this requires special handling */
226 /* the list of entries will be sorted in the proper selection order
227 already, so we just need the first one (if any) */
229 if ((ret
> 0) && (current
= AST_LIST_REMOVE_HEAD(&context
.entries
, list
))) {
230 ast_copy_string(host
, current
->host
, hostlen
);
231 *port
= current
->port
;
233 if (option_verbose
> 3) {
234 ast_verbose(VERBOSE_PREFIX_3
"ast_get_srv: SRV lookup for '%s' mapped to host %s, port %d\n",
235 service
, host
, *port
);
242 while ((current
= AST_LIST_REMOVE_HEAD(&context
.entries
, list
)))