pjsip configuration: Make transport TOS values consistent with endpoints
[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         int tos_as_dscp = transport->tos >> 2;
125
126         if (transport->tos) {
127                 qos->flags |= PJ_QOS_PARAM_HAS_DSCP;
128                 qos->dscp_val = tos_as_dscp;
129         }
130         if (transport->cos) {
131                 qos->flags |= PJ_QOS_PARAM_HAS_SO_PRIO;
132                 qos->so_prio = transport->cos;
133         }
134 }
135
136 /*! \brief Apply handler for transports */
137 static int transport_apply(const struct ast_sorcery *sorcery, void *obj)
138 {
139         struct ast_sip_transport *transport = obj;
140         RAII_VAR(struct ast_sip_transport *, existing, ast_sorcery_retrieve_by_id(sorcery, "transport", ast_sorcery_object_get_id(obj)), ao2_cleanup);
141         pj_status_t res = -1;
142
143         if (!existing || !existing->state) {
144                 if (!(transport->state = ao2_alloc(sizeof(*transport->state), transport_state_destroy))) {
145                         ast_log(LOG_ERROR, "Transport state for '%s' could not be allocated\n", ast_sorcery_object_get_id(obj));
146                         return -1;
147                 }
148         } else {
149                 transport->state = existing->state;
150                 ao2_ref(transport->state, +1);
151         }
152
153         /* Once active a transport can not be reconfigured */
154         if (transport->state->transport || transport->state->factory) {
155                 return -1;
156         }
157
158         if (transport->host.addr.sa_family != PJ_AF_INET && transport->host.addr.sa_family != PJ_AF_INET6) {
159                 ast_log(LOG_ERROR, "Transport '%s' could not be started as binding not specified\n", ast_sorcery_object_get_id(obj));
160                 return -1;
161         }
162
163         /* Set default port if not present */
164         if (!pj_sockaddr_get_port(&transport->host)) {
165                 pj_sockaddr_set_port(&transport->host, (transport->type == AST_TRANSPORT_TLS) ? 5061 : 5060);
166         }
167
168         /* Now that we know what address family we can set up a dnsmgr refresh for the external media address if present */
169         if (!ast_strlen_zero(transport->external_signaling_address)) {
170                 if (transport->host.addr.sa_family == pj_AF_INET()) {
171                         transport->external_address.ss.ss_family = AF_INET;
172                 } else if (transport->host.addr.sa_family == pj_AF_INET6()) {
173                         transport->external_address.ss.ss_family = AF_INET6;
174                 } else {
175                         ast_log(LOG_ERROR, "Unknown address family for transport '%s', could not get external signaling address\n",
176                                         ast_sorcery_object_get_id(obj));
177                         return -1;
178                 }
179
180                 if (ast_dnsmgr_lookup(transport->external_signaling_address, &transport->external_address, &transport->external_address_refresher, NULL) < 0) {
181                         ast_log(LOG_ERROR, "Could not create dnsmgr for external signaling address on '%s'\n", ast_sorcery_object_get_id(obj));
182                         return -1;
183                 }
184         }
185
186         if (transport->type == AST_TRANSPORT_UDP) {
187                 if (transport->host.addr.sa_family == pj_AF_INET()) {
188                         res = pjsip_udp_transport_start(ast_sip_get_pjsip_endpoint(), &transport->host.ipv4, NULL, transport->async_operations, &transport->state->transport);
189                 } else if (transport->host.addr.sa_family == pj_AF_INET6()) {
190                         res = pjsip_udp_transport_start6(ast_sip_get_pjsip_endpoint(), &transport->host.ipv6, NULL, transport->async_operations, &transport->state->transport);
191                 }
192
193                 if (res == PJ_SUCCESS && (transport->tos || transport->cos)) {
194                         pj_sock_t sock;
195                         pj_qos_params qos_params;
196
197                         sock = pjsip_udp_transport_get_socket(transport->state->transport);
198                         pj_sock_get_qos_params(sock, &qos_params);
199                         set_qos(transport, &qos_params);
200                         pj_sock_set_qos_params(sock, &qos_params);
201                 }
202         } else if (transport->type == AST_TRANSPORT_TCP) {
203                 pjsip_tcp_transport_cfg cfg;
204
205                 pjsip_tcp_transport_cfg_default(&cfg, transport->host.addr.sa_family);
206                 cfg.bind_addr = transport->host;
207                 cfg.async_cnt = transport->async_operations;
208                 set_qos(transport, &cfg.qos_params);
209
210                 res = pjsip_tcp_transport_start3(ast_sip_get_pjsip_endpoint(), &cfg, &transport->state->factory);
211         } else if (transport->type == AST_TRANSPORT_TLS) {
212                 transport->tls.ca_list_file = pj_str((char*)transport->ca_list_file);
213                 transport->tls.cert_file = pj_str((char*)transport->cert_file);
214                 transport->tls.privkey_file = pj_str((char*)transport->privkey_file);
215                 transport->tls.password = pj_str((char*)transport->password);
216                 set_qos(transport, &transport->tls.qos_params);
217
218                 res = pjsip_tls_transport_start2(ast_sip_get_pjsip_endpoint(), &transport->tls, &transport->host, NULL, transport->async_operations, &transport->state->factory);
219         } else if ((transport->type == AST_TRANSPORT_WS) || (transport->type == AST_TRANSPORT_WSS)) {
220                 if (transport->cos || transport->tos) {
221                         ast_log(LOG_WARNING, "TOS and COS values ignored for websocket transport\n");
222                 }
223                 res = PJ_SUCCESS;
224         }
225
226         if (res != PJ_SUCCESS) {
227                 char msg[PJ_ERR_MSG_SIZE];
228
229                 pj_strerror(res, msg, sizeof(msg));
230                 ast_log(LOG_ERROR, "Transport '%s' could not be started: %s\n", ast_sorcery_object_get_id(obj), msg);
231                 return -1;
232         }
233         return 0;
234 }
235
236 /*! \brief Custom handler for turning a string protocol into an enum */
237 static int transport_protocol_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
238 {
239         struct ast_sip_transport *transport = obj;
240
241         if (!strcasecmp(var->value, "udp")) {
242                 transport->type = AST_TRANSPORT_UDP;
243         } else if (!strcasecmp(var->value, "tcp")) {
244                 transport->type = AST_TRANSPORT_TCP;
245         } else if (!strcasecmp(var->value, "tls")) {
246                 transport->type = AST_TRANSPORT_TLS;
247         } else if (!strcasecmp(var->value, "ws")) {
248                 transport->type = AST_TRANSPORT_WS;
249         } else if (!strcasecmp(var->value, "wss")) {
250                 transport->type = AST_TRANSPORT_WSS;
251         } else {
252                 return -1;
253         }
254
255         return 0;
256 }
257
258 static const char *transport_types[] = {
259         [AST_TRANSPORT_UDP] = "udp",
260         [AST_TRANSPORT_TCP] = "tcp",
261         [AST_TRANSPORT_TLS] = "tls",
262         [AST_TRANSPORT_WS] = "ws",
263         [AST_TRANSPORT_WSS] = "wss"
264 };
265
266 static int transport_protocol_to_str(const void *obj, const intptr_t *args, char **buf)
267 {
268         const struct ast_sip_transport *transport = obj;
269
270         if (ARRAY_IN_BOUNDS(transport->type, transport_types)) {
271                 *buf = ast_strdup(transport_types[transport->type]);
272         }
273
274         return 0;
275 }
276
277 /*! \brief Custom handler for turning a string bind into a pj_sockaddr */
278 static int transport_bind_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
279 {
280         struct ast_sip_transport *transport = obj;
281         pj_str_t buf;
282         int rc = pj_sockaddr_parse(pj_AF_UNSPEC(), 0, pj_cstr(&buf, var->value), &transport->host);
283
284         return rc != PJ_SUCCESS ? -1 : 0;
285 }
286
287 static int transport_bind_to_str(const void *obj, const intptr_t *args, char **buf)
288 {
289         const struct ast_sip_transport *transport = obj;
290
291         if (!(*buf = ast_calloc(MAX_OBJECT_FIELD, sizeof(char)))) {
292                 return -1;
293         }
294
295         /* include port as well as brackets if IPv6 */
296         pj_sockaddr_print(&transport->host, *buf, MAX_OBJECT_FIELD, 1 | 2);
297
298         return 0;
299 }
300
301 /*! \brief Custom handler for TLS boolean settings */
302 static int transport_tls_bool_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
303 {
304         struct ast_sip_transport *transport = obj;
305
306         if (!strcasecmp(var->name, "verify_server")) {
307                 transport->tls.verify_server = ast_true(var->value) ? PJ_TRUE : PJ_FALSE;
308         } else if (!strcasecmp(var->name, "verify_client")) {
309                 transport->tls.verify_client = ast_true(var->value) ? PJ_TRUE : PJ_FALSE;
310         } else if (!strcasecmp(var->name, "require_client_cert")) {
311                 transport->tls.require_client_cert = ast_true(var->value) ? PJ_TRUE : PJ_FALSE;
312         } else {
313                 return -1;
314         }
315
316         return 0;
317 }
318
319 static int verify_server_to_str(const void *obj, const intptr_t *args, char **buf)
320 {
321         const struct ast_sip_transport *transport = obj;
322         *buf = ast_strdup(AST_YESNO(transport->tls.verify_server));
323         return 0;
324 }
325
326 static int verify_client_to_str(const void *obj, const intptr_t *args, char **buf)
327 {
328         const struct ast_sip_transport *transport = obj;
329         *buf = ast_strdup(AST_YESNO(transport->tls.verify_client));
330         return 0;
331 }
332
333 static int require_client_cert_to_str(const void *obj, const intptr_t *args, char **buf)
334 {
335         const struct ast_sip_transport *transport = obj;
336         *buf = ast_strdup(AST_YESNO(transport->tls.require_client_cert));
337         return 0;
338 }
339
340 /*! \brief Custom handler for TLS method setting */
341 static int transport_tls_method_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
342 {
343         struct ast_sip_transport *transport = obj;
344
345         if (!strcasecmp(var->value, "default")) {
346                 transport->tls.method = PJSIP_SSL_DEFAULT_METHOD;
347         } else if (!strcasecmp(var->value, "unspecified")) {
348                 transport->tls.method = PJSIP_SSL_UNSPECIFIED_METHOD;
349         } else if (!strcasecmp(var->value, "tlsv1")) {
350                 transport->tls.method = PJSIP_TLSV1_METHOD;
351         } else if (!strcasecmp(var->value, "sslv2")) {
352                 transport->tls.method = PJSIP_SSLV2_METHOD;
353         } else if (!strcasecmp(var->value, "sslv3")) {
354                 transport->tls.method = PJSIP_SSLV3_METHOD;
355         } else if (!strcasecmp(var->value, "sslv23")) {
356                 transport->tls.method = PJSIP_SSLV23_METHOD;
357         } else {
358                 return -1;
359         }
360
361         return 0;
362 }
363
364 static const char *tls_method_map[] = {
365         [PJSIP_SSL_DEFAULT_METHOD] = "default",
366         [PJSIP_SSL_UNSPECIFIED_METHOD] = "unspecified",
367         [PJSIP_TLSV1_METHOD] = "tlsv1",
368         [PJSIP_SSLV2_METHOD] = "sslv2",
369         [PJSIP_SSLV3_METHOD] = "sslv3",
370         [PJSIP_SSLV23_METHOD] = "sslv23",
371 };
372
373 static int tls_method_to_str(const void *obj, const intptr_t *args, char **buf)
374 {
375         const struct ast_sip_transport *transport = obj;
376         if (ARRAY_IN_BOUNDS(transport->tls.method, tls_method_map)) {
377                 *buf = ast_strdup(tls_method_map[transport->tls.method]);
378         }
379         return 0;
380 }
381
382 /*! \brief Custom handler for TLS cipher setting */
383 static int transport_tls_cipher_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
384 {
385         struct ast_sip_transport *transport = obj;
386         pj_ssl_cipher cipher;
387
388         if (transport->tls.ciphers_num == (SIP_TLS_MAX_CIPHERS - 1)) {
389                 return -1;
390         }
391
392         /* TODO: Check this over/tweak - it's taken from pjsua for now */
393         if (!strnicmp(var->value, "0x", 2)) {
394                 pj_str_t cipher_st = pj_str((char*)var->value + 2);
395                 cipher = pj_strtoul2(&cipher_st, NULL, 16);
396         } else {
397                 cipher = atoi(var->value);
398         }
399
400         if (pj_ssl_cipher_is_supported(cipher)) {
401                 transport->ciphers[transport->tls.ciphers_num++] = cipher;
402                 return 0;
403         } else {
404                 ast_log(LOG_ERROR, "Cipher '%s' is unsupported\n", var->value);
405                 return -1;
406         }
407 }
408
409 static int transport_tls_cipher_to_str(const void *obj, const intptr_t *args, char **buf)
410 {
411         RAII_VAR(struct ast_str *, str, ast_str_create(MAX_OBJECT_FIELD), ast_free);
412         const struct ast_sip_transport *transport = obj;
413         int i;
414
415         if (!str) {
416                 return -1;
417         }
418
419         for (i = 0; i < transport->tls.ciphers_num; ++i) {
420                 ast_str_append(&str, 0, "%s", pj_ssl_cipher_name(transport->ciphers[i]));
421                 if (i < transport->tls.ciphers_num - 1) {
422                         ast_str_append(&str, 0, ",");
423                 }
424         }
425
426         *buf = ast_strdup(ast_str_buffer(str));
427         return 0;
428 }
429
430 /*! \brief Custom handler for localnet setting */
431 static int transport_localnet_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
432 {
433         struct ast_sip_transport *transport = obj;
434         int error = 0;
435
436         if (!(transport->localnet = ast_append_ha("d", var->value, transport->localnet, &error))) {
437                 return -1;
438         }
439
440         return error;
441 }
442
443 static int localnet_to_str(const void *obj, const intptr_t *args, char **buf)
444 {
445         RAII_VAR(struct ast_str *, str, ast_str_create(MAX_OBJECT_FIELD), ast_free);
446         const struct ast_sip_transport *transport = obj;
447
448         ast_ha_join(transport->localnet, &str);
449         *buf = ast_strdup(ast_str_buffer(str));
450         return 0;
451 }
452
453 /*! \brief Custom handler for TOS setting */
454 static int transport_tos_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
455 {
456         struct ast_sip_transport *transport = obj;
457         unsigned int value;
458
459         if (ast_str2tos(var->value, &value)) {
460                 ast_log(LOG_ERROR, "Error configuring transport '%s' - Could not "
461                         "interpret 'tos' value '%s'\n",
462                         ast_sorcery_object_get_id(transport), var->value);
463                 return -1;
464         }
465
466         if (value % 4) {
467                 value = value >> 2;
468                 value = value << 2;
469                 ast_log(LOG_WARNING,
470                         "transport '%s' - 'tos' value '%s' uses bits that are "
471                         "discarded when converted to DSCP. Using equivalent %d instead.\n",
472                         ast_sorcery_object_get_id(transport), var->value, value);
473         }
474
475         transport->tos = value;
476         return 0;
477 }
478
479 static int tos_to_str(const void *obj, const intptr_t *args, char **buf)
480 {
481         const struct ast_sip_transport *transport = obj;
482         ast_tos2str_buf(transport->tos, buf);
483         return 0;
484 }
485
486 static struct ao2_container *cli_get_container(void)
487 {
488         RAII_VAR(struct ao2_container *, container, NULL, ao2_cleanup);
489         RAII_VAR(struct ao2_container *, s_container, NULL, ao2_cleanup);
490
491         container = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "transport",
492                 AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
493         if (!container) {
494                 return NULL;
495         }
496
497         s_container = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, 0,
498                 ast_sorcery_object_id_compare, NULL);
499         if (!s_container) {
500                 return NULL;
501         }
502
503         if (ao2_container_dup(s_container, container, 0)) {
504                 return NULL;
505         }
506         ao2_ref(s_container, +1);
507         return s_container;
508 }
509
510 static int cli_iterator(const void *container, ao2_callback_fn callback, void *args)
511 {
512         const struct ast_sip_endpoint *endpoint = container;
513         struct ast_sip_transport *transport = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(),
514                 "transport", endpoint->transport);
515
516         if (!transport) {
517                 return -1;
518         }
519         return callback(transport, args, 0);
520 }
521
522 static int cli_print_header(void *obj, void *arg, int flags)
523 {
524         struct ast_sip_cli_context *context = arg;
525         int indent = CLI_INDENT_TO_SPACES(context->indent_level);
526         int filler = CLI_MAX_WIDTH - indent - 61;
527
528         if (!context->output_buffer) {
529                 return -1;
530         }
531
532         ast_str_append(&context->output_buffer, 0,
533                 "%*s:  <TransportId........>  <Type>  <cos>  <tos>  <BindAddress%*.*s>\n",
534                 indent, "Transport", filler, filler, CLI_HEADER_FILLER);
535
536         return 0;
537 }
538
539 static int cli_print_body(void *obj, void *arg, int flags)
540 {
541         struct ast_sip_transport *transport = obj;
542         struct ast_sip_cli_context *context = arg;
543         char hoststr[PJ_INET6_ADDRSTRLEN];
544
545         if (!context->output_buffer) {
546                 return -1;
547         }
548
549         pj_sockaddr_print(&transport->host, hoststr, sizeof(hoststr), 3);
550
551         ast_str_append(&context->output_buffer, 0, "%*s:  %-21s  %6s  %5x  %5x  %s\n",
552                 CLI_INDENT_TO_SPACES(context->indent_level), "Transport",
553                 ast_sorcery_object_get_id(transport),
554                 ARRAY_IN_BOUNDS(transport->type, transport_types) ? transport_types[transport->type] : "Unknown",
555                 transport->cos, transport->tos, hoststr);
556
557         if (context->show_details
558                 || (context->show_details_only_level_0 && context->indent_level == 0)) {
559                 ast_str_append(&context->output_buffer, 0, "\n");
560                 ast_sip_cli_print_sorcery_objectset(transport, context, 0);
561         }
562
563         return 0;
564 }
565
566 static struct ast_sip_cli_formatter_entry  cli_formatter = {
567         .name = "transport",
568         .print_header = cli_print_header,
569         .print_body = cli_print_body,
570         .get_container = cli_get_container,
571         .iterator = cli_iterator,
572         .comparator = ast_sorcery_object_id_compare,
573 };
574
575 static struct ast_cli_entry cli_commands[] = {
576         AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "List PJSIP Transports",
577                 .command = "pjsip list transports",
578                 .usage = "Usage: pjsip list transports\n"
579                                  "       List the configured PJSIP Transports\n"),
580         AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "Show PJSIP Transports",
581                 .command = "pjsip show transports",
582                 .usage = "Usage: pjsip show transports\n"
583                                  "       Show the configured PJSIP Transport\n"),
584         AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "Show PJSIP Transport",
585                 .command = "pjsip show transport",
586                 .usage = "Usage: pjsip show transport <id>\n"
587                                  "       Show the configured PJSIP Transport\n"),
588 };
589
590 /*! \brief Initialize sorcery with transport support */
591 int ast_sip_initialize_sorcery_transport(void)
592 {
593         struct ast_sorcery *sorcery = ast_sip_get_sorcery();
594
595         ast_sorcery_apply_default(sorcery, "transport", "config", "pjsip.conf,criteria=type=transport");
596
597         if (ast_sorcery_object_register_no_reload(sorcery, "transport", transport_alloc, NULL, transport_apply)) {
598                 return -1;
599         }
600
601         ast_sorcery_object_field_register(sorcery, "transport", "type", "", OPT_NOOP_T, 0, 0);
602         ast_sorcery_object_field_register_custom(sorcery, "transport", "protocol", "udp", transport_protocol_handler, transport_protocol_to_str, 0, 0);
603         ast_sorcery_object_field_register_custom(sorcery, "transport", "bind", "", transport_bind_handler, transport_bind_to_str, 0, 0);
604         ast_sorcery_object_field_register(sorcery, "transport", "async_operations", "1", OPT_UINT_T, 0, FLDSET(struct ast_sip_transport, async_operations));
605         ast_sorcery_object_field_register(sorcery, "transport", "ca_list_file", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_transport, ca_list_file));
606         ast_sorcery_object_field_register(sorcery, "transport", "cert_file", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_transport, cert_file));
607         ast_sorcery_object_field_register(sorcery, "transport", "priv_key_file", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_transport, privkey_file));
608         ast_sorcery_object_field_register(sorcery, "transport", "password", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_transport, password));
609         ast_sorcery_object_field_register(sorcery, "transport", "external_signaling_address", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_transport, external_signaling_address));
610         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);
611         ast_sorcery_object_field_register(sorcery, "transport", "external_media_address", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_transport, external_media_address));
612         ast_sorcery_object_field_register(sorcery, "transport", "domain", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_transport, domain));
613         ast_sorcery_object_field_register_custom(sorcery, "transport", "verify_server", "", transport_tls_bool_handler, verify_server_to_str, 0, 0);
614         ast_sorcery_object_field_register_custom(sorcery, "transport", "verify_client", "", transport_tls_bool_handler, verify_client_to_str, 0, 0);
615         ast_sorcery_object_field_register_custom(sorcery, "transport", "require_client_cert", "", transport_tls_bool_handler, require_client_cert_to_str, 0, 0);
616         ast_sorcery_object_field_register_custom(sorcery, "transport", "method", "", transport_tls_method_handler, tls_method_to_str, 0, 0);
617         ast_sorcery_object_field_register_custom(sorcery, "transport", "cipher", "", transport_tls_cipher_handler, transport_tls_cipher_to_str, 0, 0);
618         ast_sorcery_object_field_register_custom(sorcery, "transport", "local_net", "", transport_localnet_handler, localnet_to_str, 0, 0);
619         ast_sorcery_object_field_register_custom(sorcery, "transport", "tos", "0", transport_tos_handler, tos_to_str, 0, 0);
620         ast_sorcery_object_field_register(sorcery, "transport", "cos", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_transport, cos));
621
622         ast_sip_register_endpoint_formatter(&endpoint_transport_formatter);
623         ast_sip_register_cli_formatter(&cli_formatter);
624         ast_cli_register_multiple(cli_commands, ARRAY_LEN(cli_commands));
625
626         return 0;
627 }
628
629 int ast_sip_destroy_sorcery_transport(void)
630 {
631         ast_cli_unregister_multiple(cli_commands, ARRAY_LEN(cli_commands));
632         ast_sip_unregister_cli_formatter(&cli_formatter);
633         return 0;
634 }