3c11dcc70301674b3a24b71457d84f06eaa44294
[asterisk/asterisk.git] / res / res_pjsip / config_transport.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2013, Digium, Inc.
5  *
6  * Joshua Colp <jcolp@digium.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 #include "asterisk.h"
20
21 #include <pjsip.h>
22 #include <pjlib.h>
23
24 #include "asterisk/res_pjsip.h"
25 #include "asterisk/res_pjsip_cli.h"
26 #include "asterisk/logger.h"
27 #include "asterisk/astobj2.h"
28 #include "asterisk/sorcery.h"
29 #include "asterisk/acl.h"
30 #include "include/res_pjsip_private.h"
31
32 static int sip_transport_to_ami(const struct ast_sip_transport *transport,
33                                 struct ast_str **buf)
34 {
35         return ast_sip_sorcery_object_to_ami(transport, buf);
36 }
37
38 static int format_ami_endpoint_transport(const struct ast_sip_endpoint *endpoint,
39                                          struct ast_sip_ami *ami)
40 {
41         RAII_VAR(struct ast_str *, buf,
42                  ast_sip_create_ami_event("TransportDetail", ami), ast_free);
43         RAII_VAR(struct ast_sip_transport *,
44                  transport, ast_sorcery_retrieve_by_id(
45                          ast_sip_get_sorcery(), "transport",
46                          endpoint->transport), ao2_cleanup);
47         if (!buf) {
48                 return -1;
49         }
50
51         if (!transport) {
52                 astman_send_error_va(ami->s, ami->m, "Unable to retrieve "
53                                      "transport %s\n", endpoint->transport);
54                 return -1;
55         }
56
57         sip_transport_to_ami(transport, &buf);
58
59         ast_str_append(&buf, 0, "EndpointName: %s\r\n",
60                        ast_sorcery_object_get_id(endpoint));
61
62         astman_append(ami->s, "%s\r\n", ast_str_buffer(buf));
63         return 0;
64 }
65
66 struct ast_sip_endpoint_formatter endpoint_transport_formatter = {
67         .format_ami = format_ami_endpoint_transport
68 };
69
70 static int destroy_transport_state(void *data)
71 {
72         pjsip_transport *transport = data;
73         pjsip_transport_shutdown(transport);
74         return 0;
75 }
76
77 /*! \brief Destructor for transport state information */
78 static void transport_state_destroy(void *obj)
79 {
80         struct ast_sip_transport_state *state = obj;
81
82         if (state->transport) {
83                 ast_sip_push_task_synchronous(NULL, destroy_transport_state, state->transport);
84         }
85 }
86
87 /*! \brief Destructor for transport */
88 static void transport_destroy(void *obj)
89 {
90         struct ast_sip_transport *transport = obj;
91
92         ast_string_field_free_memory(transport);
93         ast_free_ha(transport->localnet);
94
95         if (transport->external_address_refresher) {
96                 ast_dnsmgr_release(transport->external_address_refresher);
97         }
98
99         ao2_cleanup(transport->state);
100 }
101
102 /*! \brief Allocator for transport */
103 static void *transport_alloc(const char *name)
104 {
105         struct ast_sip_transport *transport = ast_sorcery_generic_alloc(sizeof(*transport), transport_destroy);
106
107         if (!transport) {
108                 return NULL;
109         }
110
111         if (ast_string_field_init(transport, 256)) {
112                 ao2_cleanup(transport);
113                 return NULL;
114         }
115
116         pjsip_tls_setting_default(&transport->tls);
117         transport->tls.ciphers = transport->ciphers;
118
119         return transport;
120 }
121
122 static void set_qos(struct ast_sip_transport *transport, pj_qos_params *qos)
123 {
124         if (transport->tos) {
125                 qos->flags |= PJ_QOS_PARAM_HAS_DSCP;
126                 qos->dscp_val = transport->tos;
127         }
128         if (transport->cos) {
129                 qos->flags |= PJ_QOS_PARAM_HAS_SO_PRIO;
130                 qos->so_prio = transport->cos;
131         }
132 }
133
134 /*! \brief Apply handler for transports */
135 static int transport_apply(const struct ast_sorcery *sorcery, void *obj)
136 {
137         struct ast_sip_transport *transport = obj;
138         RAII_VAR(struct ast_sip_transport *, existing, ast_sorcery_retrieve_by_id(sorcery, "transport", ast_sorcery_object_get_id(obj)), ao2_cleanup);
139         pj_status_t res = -1;
140
141         if (!existing || !existing->state) {
142                 if (!(transport->state = ao2_alloc(sizeof(*transport->state), transport_state_destroy))) {
143                         ast_log(LOG_ERROR, "Transport state for '%s' could not be allocated\n", ast_sorcery_object_get_id(obj));
144                         return -1;
145                 }
146         } else {
147                 transport->state = existing->state;
148                 ao2_ref(transport->state, +1);
149         }
150
151         /* Once active a transport can not be reconfigured */
152         if (transport->state->transport || transport->state->factory) {
153                 return -1;
154         }
155
156         if (transport->host.addr.sa_family != PJ_AF_INET && transport->host.addr.sa_family != PJ_AF_INET6) {
157                 ast_log(LOG_ERROR, "Transport '%s' could not be started as binding not specified\n", ast_sorcery_object_get_id(obj));
158                 return -1;
159         }
160
161         /* Set default port if not present */
162         if (!pj_sockaddr_get_port(&transport->host)) {
163                 pj_sockaddr_set_port(&transport->host, (transport->type == AST_TRANSPORT_TLS) ? 5061 : 5060);
164         }
165
166         /* Now that we know what address family we can set up a dnsmgr refresh for the external media address if present */
167         if (!ast_strlen_zero(transport->external_signaling_address)) {
168                 if (transport->host.addr.sa_family == pj_AF_INET()) {
169                         transport->external_address.ss.ss_family = AF_INET;
170                 } else if (transport->host.addr.sa_family == pj_AF_INET6()) {
171                         transport->external_address.ss.ss_family = AF_INET6;
172                 } else {
173                         ast_log(LOG_ERROR, "Unknown address family for transport '%s', could not get external signaling address\n",
174                                         ast_sorcery_object_get_id(obj));
175                         return -1;
176                 }
177
178                 if (ast_dnsmgr_lookup(transport->external_signaling_address, &transport->external_address, &transport->external_address_refresher, NULL) < 0) {
179                         ast_log(LOG_ERROR, "Could not create dnsmgr for external signaling address on '%s'\n", ast_sorcery_object_get_id(obj));
180                         return -1;
181                 }
182         }
183
184         if (transport->type == AST_TRANSPORT_UDP) {
185                 if (transport->host.addr.sa_family == pj_AF_INET()) {
186                         res = pjsip_udp_transport_start(ast_sip_get_pjsip_endpoint(), &transport->host.ipv4, NULL, transport->async_operations, &transport->state->transport);
187                 } else if (transport->host.addr.sa_family == pj_AF_INET6()) {
188                         res = pjsip_udp_transport_start6(ast_sip_get_pjsip_endpoint(), &transport->host.ipv6, NULL, transport->async_operations, &transport->state->transport);
189                 }
190
191                 if (res == PJ_SUCCESS && (transport->tos || transport->cos)) {
192                         pj_sock_t sock;
193                         pj_qos_params qos_params;
194
195                         sock = pjsip_udp_transport_get_socket(transport->state->transport);
196                         pj_sock_get_qos_params(sock, &qos_params);
197                         set_qos(transport, &qos_params);
198                         pj_sock_set_qos_params(sock, &qos_params);
199                 }
200         } else if (transport->type == AST_TRANSPORT_TCP) {
201                 pjsip_tcp_transport_cfg cfg;
202
203                 pjsip_tcp_transport_cfg_default(&cfg, transport->host.addr.sa_family);
204                 cfg.bind_addr = transport->host;
205                 cfg.async_cnt = transport->async_operations;
206                 set_qos(transport, &cfg.qos_params);
207
208                 res = pjsip_tcp_transport_start3(ast_sip_get_pjsip_endpoint(), &cfg, &transport->state->factory);
209         } else if (transport->type == AST_TRANSPORT_TLS) {
210                 transport->tls.ca_list_file = pj_str((char*)transport->ca_list_file);
211                 transport->tls.cert_file = pj_str((char*)transport->cert_file);
212                 transport->tls.privkey_file = pj_str((char*)transport->privkey_file);
213                 transport->tls.password = pj_str((char*)transport->password);
214                 set_qos(transport, &transport->tls.qos_params);
215
216                 res = pjsip_tls_transport_start2(ast_sip_get_pjsip_endpoint(), &transport->tls, &transport->host, NULL, transport->async_operations, &transport->state->factory);
217         } else if ((transport->type == AST_TRANSPORT_WS) || (transport->type == AST_TRANSPORT_WSS)) {
218                 if (transport->cos || transport->tos) {
219                         ast_log(LOG_WARNING, "TOS and COS values ignored for websocket transport\n");
220                 }
221                 res = PJ_SUCCESS;
222         }
223
224         if (res != PJ_SUCCESS) {
225                 char msg[PJ_ERR_MSG_SIZE];
226
227                 pj_strerror(res, msg, sizeof(msg));
228                 ast_log(LOG_ERROR, "Transport '%s' could not be started: %s\n", ast_sorcery_object_get_id(obj), msg);
229                 return -1;
230         }
231         return 0;
232 }
233
234 /*! \brief Custom handler for turning a string protocol into an enum */
235 static int transport_protocol_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
236 {
237         struct ast_sip_transport *transport = obj;
238
239         if (!strcasecmp(var->value, "udp")) {
240                 transport->type = AST_TRANSPORT_UDP;
241         } else if (!strcasecmp(var->value, "tcp")) {
242                 transport->type = AST_TRANSPORT_TCP;
243         } else if (!strcasecmp(var->value, "tls")) {
244                 transport->type = AST_TRANSPORT_TLS;
245         } else if (!strcasecmp(var->value, "ws")) {
246                 transport->type = AST_TRANSPORT_WS;
247         } else if (!strcasecmp(var->value, "wss")) {
248                 transport->type = AST_TRANSPORT_WSS;
249         } else {
250                 return -1;
251         }
252
253         return 0;
254 }
255
256 static const char *transport_types[] = {
257         [AST_TRANSPORT_UDP] = "udp",
258         [AST_TRANSPORT_TCP] = "tcp",
259         [AST_TRANSPORT_TLS] = "tls",
260         [AST_TRANSPORT_WS] = "ws",
261         [AST_TRANSPORT_WSS] = "wss"
262 };
263
264 static int transport_protocol_to_str(const void *obj, const intptr_t *args, char **buf)
265 {
266         const struct ast_sip_transport *transport = obj;
267
268         if (ARRAY_IN_BOUNDS(transport->type, transport_types)) {
269                 *buf = ast_strdup(transport_types[transport->type]);
270         }
271
272         return 0;
273 }
274
275 /*! \brief Custom handler for turning a string bind into a pj_sockaddr */
276 static int transport_bind_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
277 {
278         struct ast_sip_transport *transport = obj;
279         pj_str_t buf;
280         int rc = pj_sockaddr_parse(pj_AF_UNSPEC(), 0, pj_cstr(&buf, var->value), &transport->host);
281
282         return rc != PJ_SUCCESS ? -1 : 0;
283 }
284
285 static int transport_bind_to_str(const void *obj, const intptr_t *args, char **buf)
286 {
287         const struct ast_sip_transport *transport = obj;
288
289         if (!(*buf = ast_calloc(MAX_OBJECT_FIELD, sizeof(char)))) {
290                 return -1;
291         }
292
293         /* include port as well as brackets if IPv6 */
294         pj_sockaddr_print(&transport->host, *buf, MAX_OBJECT_FIELD, 1 | 2);
295
296         return 0;
297 }
298
299 /*! \brief Custom handler for TLS boolean settings */
300 static int transport_tls_bool_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
301 {
302         struct ast_sip_transport *transport = obj;
303
304         if (!strcasecmp(var->name, "verify_server")) {
305                 transport->tls.verify_server = ast_true(var->value) ? PJ_TRUE : PJ_FALSE;
306         } else if (!strcasecmp(var->name, "verify_client")) {
307                 transport->tls.verify_client = ast_true(var->value) ? PJ_TRUE : PJ_FALSE;
308         } else if (!strcasecmp(var->name, "require_client_cert")) {
309                 transport->tls.require_client_cert = ast_true(var->value) ? PJ_TRUE : PJ_FALSE;
310         } else {
311                 return -1;
312         }
313
314         return 0;
315 }
316
317 static int verify_server_to_str(const void *obj, const intptr_t *args, char **buf)
318 {
319         const struct ast_sip_transport *transport = obj;
320         *buf = ast_strdup(AST_YESNO(transport->tls.verify_server));
321         return 0;
322 }
323
324 static int verify_client_to_str(const void *obj, const intptr_t *args, char **buf)
325 {
326         const struct ast_sip_transport *transport = obj;
327         *buf = ast_strdup(AST_YESNO(transport->tls.verify_client));
328         return 0;
329 }
330
331 static int require_client_cert_to_str(const void *obj, const intptr_t *args, char **buf)
332 {
333         const struct ast_sip_transport *transport = obj;
334         *buf = ast_strdup(AST_YESNO(transport->tls.require_client_cert));
335         return 0;
336 }
337
338 /*! \brief Custom handler for TLS method setting */
339 static int transport_tls_method_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
340 {
341         struct ast_sip_transport *transport = obj;
342
343         if (!strcasecmp(var->value, "default")) {
344                 transport->tls.method = PJSIP_SSL_DEFAULT_METHOD;
345         } else if (!strcasecmp(var->value, "unspecified")) {
346                 transport->tls.method = PJSIP_SSL_UNSPECIFIED_METHOD;
347         } else if (!strcasecmp(var->value, "tlsv1")) {
348                 transport->tls.method = PJSIP_TLSV1_METHOD;
349         } else if (!strcasecmp(var->value, "sslv2")) {
350                 transport->tls.method = PJSIP_SSLV2_METHOD;
351         } else if (!strcasecmp(var->value, "sslv3")) {
352                 transport->tls.method = PJSIP_SSLV3_METHOD;
353         } else if (!strcasecmp(var->value, "sslv23")) {
354                 transport->tls.method = PJSIP_SSLV23_METHOD;
355         } else {
356                 return -1;
357         }
358
359         return 0;
360 }
361
362 static const char *tls_method_map[] = {
363         [PJSIP_SSL_DEFAULT_METHOD] = "default",
364         [PJSIP_SSL_UNSPECIFIED_METHOD] = "unspecified",
365         [PJSIP_TLSV1_METHOD] = "tlsv1",
366         [PJSIP_SSLV2_METHOD] = "sslv2",
367         [PJSIP_SSLV3_METHOD] = "sslv3",
368         [PJSIP_SSLV23_METHOD] = "sslv23",
369 };
370
371 static int tls_method_to_str(const void *obj, const intptr_t *args, char **buf)
372 {
373         const struct ast_sip_transport *transport = obj;
374         if (ARRAY_IN_BOUNDS(transport->tls.method, tls_method_map)) {
375                 *buf = ast_strdup(tls_method_map[transport->tls.method]);
376         }
377         return 0;
378 }
379
380 /*! \brief Custom handler for TLS cipher setting */
381 static int transport_tls_cipher_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
382 {
383         struct ast_sip_transport *transport = obj;
384         pj_ssl_cipher cipher;
385
386         if (transport->tls.ciphers_num == (SIP_TLS_MAX_CIPHERS - 1)) {
387                 return -1;
388         }
389
390         /* TODO: Check this over/tweak - it's taken from pjsua for now */
391         if (!strnicmp(var->value, "0x", 2)) {
392                 pj_str_t cipher_st = pj_str((char*)var->value + 2);
393                 cipher = pj_strtoul2(&cipher_st, NULL, 16);
394         } else {
395                 cipher = atoi(var->value);
396         }
397
398         if (pj_ssl_cipher_is_supported(cipher)) {
399                 transport->ciphers[transport->tls.ciphers_num++] = cipher;
400                 return 0;
401         } else {
402                 ast_log(LOG_ERROR, "Cipher '%s' is unsupported\n", var->value);
403                 return -1;
404         }
405 }
406
407 static int transport_tls_cipher_to_str(const void *obj, const intptr_t *args, char **buf)
408 {
409         RAII_VAR(struct ast_str *, str, ast_str_create(MAX_OBJECT_FIELD), ast_free);
410         const struct ast_sip_transport *transport = obj;
411         int i;
412
413         if (!str) {
414                 return -1;
415         }
416
417         for (i = 0; i < transport->tls.ciphers_num; ++i) {
418                 ast_str_append(&str, 0, "%s", pj_ssl_cipher_name(transport->ciphers[i]));
419                 if (i < transport->tls.ciphers_num - 1) {
420                         ast_str_append(&str, 0, ",");
421                 }
422         }
423
424         *buf = ast_strdup(ast_str_buffer(str));
425         return 0;
426 }
427
428 /*! \brief Custom handler for localnet setting */
429 static int transport_localnet_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
430 {
431         struct ast_sip_transport *transport = obj;
432         int error = 0;
433
434         if (!(transport->localnet = ast_append_ha("d", var->value, transport->localnet, &error))) {
435                 return -1;
436         }
437
438         return error;
439 }
440
441 static int localnet_to_str(const void *obj, const intptr_t *args, char **buf)
442 {
443         RAII_VAR(struct ast_str *, str, ast_str_create(MAX_OBJECT_FIELD), ast_free);
444         const struct ast_sip_transport *transport = obj;
445
446         ast_ha_join(transport->localnet, &str);
447         *buf = ast_strdup(ast_str_buffer(str));
448         return 0;
449 }
450
451 static struct ao2_container *cli_get_container(void)
452 {
453         RAII_VAR(struct ao2_container *, container, NULL, ao2_cleanup);
454         RAII_VAR(struct ao2_container *, s_container, NULL, ao2_cleanup);
455
456         container = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "transport",
457                 AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
458         if (!container) {
459                 return NULL;
460         }
461
462         s_container = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, 0,
463                 ast_sorcery_object_id_compare, NULL);
464         if (!s_container) {
465                 return NULL;
466         }
467
468         if (ao2_container_dup(s_container, container, 0)) {
469                 return NULL;
470         }
471         ao2_ref(s_container, +1);
472         return s_container;
473 }
474
475 static int cli_iterator(const void *container, ao2_callback_fn callback, void *args)
476 {
477         const struct ast_sip_endpoint *endpoint = container;
478         struct ast_sip_transport *transport = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(),
479                 "transport", endpoint->transport);
480
481         if (!transport) {
482                 return -1;
483         }
484         return callback(transport, args, 0);
485 }
486
487 static int cli_print_header(void *obj, void *arg, int flags)
488 {
489         struct ast_sip_cli_context *context = arg;
490         int indent = CLI_INDENT_TO_SPACES(context->indent_level);
491         int filler = CLI_MAX_WIDTH - indent - 61;
492
493         if (!context->output_buffer) {
494                 return -1;
495         }
496
497         ast_str_append(&context->output_buffer, 0,
498                 "%*s:  <TransportId........>  <Type>  <cos>  <tos>  <BindAddress%*.*s>\n",
499                 indent, "Transport", filler, filler, CLI_HEADER_FILLER);
500
501         return 0;
502 }
503
504 static int cli_print_body(void *obj, void *arg, int flags)
505 {
506         struct ast_sip_transport *transport = obj;
507         struct ast_sip_cli_context *context = arg;
508         char hoststr[PJ_INET6_ADDRSTRLEN];
509
510         if (!context->output_buffer) {
511                 return -1;
512         }
513
514         pj_sockaddr_print(&transport->host, hoststr, sizeof(hoststr), 3);
515
516         ast_str_append(&context->output_buffer, 0, "%*s:  %-21s  %6s  %5x  %5x  %s\n",
517                 CLI_INDENT_TO_SPACES(context->indent_level), "Transport",
518                 ast_sorcery_object_get_id(transport),
519                 ARRAY_IN_BOUNDS(transport->type, transport_types) ? transport_types[transport->type] : "Unknown",
520                 transport->cos, transport->tos, hoststr);
521
522         if (context->show_details
523                 || (context->show_details_only_level_0 && context->indent_level == 0)) {
524                 ast_str_append(&context->output_buffer, 0, "\n");
525                 ast_sip_cli_print_sorcery_objectset(transport, context, 0);
526         }
527
528         return 0;
529 }
530
531 static struct ast_sip_cli_formatter_entry  cli_formatter = {
532         .name = "transport",
533         .print_header = cli_print_header,
534         .print_body = cli_print_body,
535         .get_container = cli_get_container,
536         .iterator = cli_iterator,
537         .comparator = ast_sorcery_object_id_compare,
538 };
539
540 static struct ast_cli_entry cli_commands[] = {
541         AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "List PJSIP Transports",
542                 .command = "pjsip list transports",
543                 .usage = "Usage: pjsip list transports\n"
544                                  "       List the configured PJSIP Transports\n"),
545         AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "Show PJSIP Transports",
546                 .command = "pjsip show transports",
547                 .usage = "Usage: pjsip show transports\n"
548                                  "       Show the configured PJSIP Transport\n"),
549         AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "Show PJSIP Transport",
550                 .command = "pjsip show transport",
551                 .usage = "Usage: pjsip show transport <id>\n"
552                                  "       Show the configured PJSIP Transport\n"),
553 };
554
555 /*! \brief Initialize sorcery with transport support */
556 int ast_sip_initialize_sorcery_transport(void)
557 {
558         struct ast_sorcery *sorcery = ast_sip_get_sorcery();
559
560         ast_sorcery_apply_default(sorcery, "transport", "config", "pjsip.conf,criteria=type=transport");
561
562         if (ast_sorcery_object_register_no_reload(sorcery, "transport", transport_alloc, NULL, transport_apply)) {
563                 return -1;
564         }
565
566         ast_sorcery_object_field_register(sorcery, "transport", "type", "", OPT_NOOP_T, 0, 0);
567         ast_sorcery_object_field_register_custom(sorcery, "transport", "protocol", "udp", transport_protocol_handler, transport_protocol_to_str, 0, 0);
568         ast_sorcery_object_field_register_custom(sorcery, "transport", "bind", "", transport_bind_handler, transport_bind_to_str, 0, 0);
569         ast_sorcery_object_field_register(sorcery, "transport", "async_operations", "1", OPT_UINT_T, 0, FLDSET(struct ast_sip_transport, async_operations));
570         ast_sorcery_object_field_register(sorcery, "transport", "ca_list_file", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_transport, ca_list_file));
571         ast_sorcery_object_field_register(sorcery, "transport", "cert_file", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_transport, cert_file));
572         ast_sorcery_object_field_register(sorcery, "transport", "priv_key_file", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_transport, privkey_file));
573         ast_sorcery_object_field_register(sorcery, "transport", "password", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_transport, password));
574         ast_sorcery_object_field_register(sorcery, "transport", "external_signaling_address", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_transport, external_signaling_address));
575         ast_sorcery_object_field_register(sorcery, "transport", "external_signaling_port", "0", OPT_UINT_T, PARSE_IN_RANGE, FLDSET(struct ast_sip_transport, external_signaling_port), 0, 65535);
576         ast_sorcery_object_field_register(sorcery, "transport", "external_media_address", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_transport, external_media_address));
577         ast_sorcery_object_field_register(sorcery, "transport", "domain", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_transport, domain));
578         ast_sorcery_object_field_register_custom(sorcery, "transport", "verify_server", "", transport_tls_bool_handler, verify_server_to_str, 0, 0);
579         ast_sorcery_object_field_register_custom(sorcery, "transport", "verify_client", "", transport_tls_bool_handler, verify_client_to_str, 0, 0);
580         ast_sorcery_object_field_register_custom(sorcery, "transport", "require_client_cert", "", transport_tls_bool_handler, require_client_cert_to_str, 0, 0);
581         ast_sorcery_object_field_register_custom(sorcery, "transport", "method", "", transport_tls_method_handler, tls_method_to_str, 0, 0);
582         ast_sorcery_object_field_register_custom(sorcery, "transport", "cipher", "", transport_tls_cipher_handler, transport_tls_cipher_to_str, 0, 0);
583         ast_sorcery_object_field_register_custom(sorcery, "transport", "local_net", "", transport_localnet_handler, localnet_to_str, 0, 0);
584         ast_sorcery_object_field_register(sorcery, "transport", "tos", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_transport, tos));
585         ast_sorcery_object_field_register(sorcery, "transport", "cos", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_transport, cos));
586
587         ast_sip_register_endpoint_formatter(&endpoint_transport_formatter);
588         ast_sip_register_cli_formatter(&cli_formatter);
589         ast_cli_register_multiple(cli_commands, ARRAY_LEN(cli_commands));
590
591         return 0;
592 }
593
594 int ast_sip_destroy_sorcery_transport(void)
595 {
596         ast_cli_unregister_multiple(cli_commands, ARRAY_LEN(cli_commands));
597         ast_sip_unregister_cli_formatter(&cli_formatter);
598         return 0;
599 }