From 68446a09672bbb32172f7bebb0efb3a838a2d827 Mon Sep 17 00:00:00 2001 From: Benny Prijono Date: Thu, 1 Mar 2007 23:39:08 +0000 Subject: More STUN work git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@1030 74dad513-b988-da41-8d7b-12977e46ad98 --- pjlib-util/include/pjlib-util/errno.h | 16 +- pjlib-util/src/pjlib-util/stun_msg.c | 31 ++- pjlib-util/src/pjlib-util/stun_session.c | 2 +- pjlib-util/src/pjstun-client/client_main.c | 351 +++++++++++++++++++++------ pjlib-util/src/pjstun-srv-test/server_main.c | 29 +-- 5 files changed, 329 insertions(+), 100 deletions(-) (limited to 'pjlib-util') diff --git a/pjlib-util/include/pjlib-util/errno.h b/pjlib-util/include/pjlib-util/errno.h index 3f004ca3..d33ed1a2 100644 --- a/pjlib-util/include/pjlib-util/errno.h +++ b/pjlib-util/include/pjlib-util/errno.h @@ -301,18 +301,24 @@ #define PJLIB_UTIL_ESTUNNOHANDLER (PJLIB_UTIL_ERRNO_START+116)/* 320116 */ /** * @hideinitializer - * Invalid STUN MESSAGE-INTEGRITY attribute position in message. - * STUN MESSAGE-INTEGRITY must be put last in the message, or before - * FINGERPRINT attribute. + * Found non-FINGERPRINT attribute after MESSAGE-INTEGRITY. This is not + * valid since MESSAGE-INTEGRITY MUST be the last attribute or the + * attribute right before FINGERPRINT before the message. */ -#define PJLIB_UTIL_ESTUNMSGINT (PJLIB_UTIL_ERRNO_START+117)/* 320117 */ +#define PJLIB_UTIL_ESTUNMSGINTPOS (PJLIB_UTIL_ERRNO_START+118)/* 320118 */ +/** + * @hideinitializer + * Found attribute after FINGERPRINT. This is not valid since FINGERPRINT + * MUST be the last attribute in the message. + */ +#define PJLIB_UTIL_ESTUNFINGERPOS (PJLIB_UTIL_ERRNO_START+119)/* 320119 */ /** * @hideinitializer * Missing STUN USERNAME attribute. * When credential is included in the STUN message (MESSAGE-INTEGRITY is * present), the USERNAME attribute must be present in the message. */ -#define PJLIB_UTIL_ESTUNNOUSERNAME (PJLIB_UTIL_ERRNO_START+118)/* 320118 */ +#define PJLIB_UTIL_ESTUNNOUSERNAME (PJLIB_UTIL_ERRNO_START+120)/* 320120 */ #define PJ_STATUS_FROM_STUN_CODE(code) (PJLIB_UTIL_ERRNO_START+code) diff --git a/pjlib-util/src/pjlib-util/stun_msg.c b/pjlib-util/src/pjlib-util/stun_msg.c index e8153e10..6954434a 100644 --- a/pjlib-util/src/pjlib-util/stun_msg.c +++ b/pjlib-util/src/pjlib-util/stun_msg.c @@ -988,7 +988,7 @@ static pj_status_t decode_msg_integrity_attr(pj_pool_t *pool, pj_stun_msg_integrity_attr *attr; /* Check that struct size is valid */ - pj_assert(sizeof(pj_stun_msg_integrity_attr)==STUN_MSG_INTEGRITY_LEN); + pj_assert(sizeof(pj_stun_msg_integrity_attr)==ATTR_LEN); /* Create attribute */ attr = PJ_POOL_ZALLOC_TYPE(pool, pj_stun_msg_integrity_attr); @@ -1856,9 +1856,7 @@ PJ_DEF(pj_status_t) pj_stun_msg_encode(pj_stun_msg *msg, /* Print each attribute */ for (i=0; iattr_count; ++i) { const struct attr_desc *adesc; - const pj_stun_attr_hdr *attr_hdr; - - attr_hdr = msg->attr[i]; + const pj_stun_attr_hdr *attr_hdr = msg->attr[i]; if (attr_hdr->type == PJ_STUN_ATTR_MESSAGE_INTEGRITY) { pj_assert(amsg_integrity == NULL); @@ -1891,6 +1889,27 @@ PJ_DEF(pj_status_t) pj_stun_msg_encode(pj_stun_msg *msg, buf_size -= printed; } + /* We may have stopped printing attribute because we found + * MESSAGE-INTEGRITY or FINGERPRINT. Scan the rest of the + * attributes. + */ + for ( ++i; iattr_count; ++i) { + const pj_stun_attr_hdr *attr_hdr = msg->attr[i]; + + /* There mustn't any attribute after FINGERPRINT */ + PJ_ASSERT_RETURN(afingerprint == NULL, PJLIB_UTIL_ESTUNFINGERPOS); + + if (attr_hdr->type == PJ_STUN_ATTR_MESSAGE_INTEGRITY) { + /* There mustn't be MESSAGE-INTEGRITY before */ + PJ_ASSERT_RETURN(amsg_integrity == NULL, + PJLIB_UTIL_ESTUNMSGINTPOS); + amsg_integrity = (pj_stun_msg_integrity_attr*) attr_hdr; + + } else if (attr_hdr->type == PJ_STUN_ATTR_FINGERPRINT) { + afingerprint = (pj_stun_fingerprint_attr*) attr_hdr; + } + } + /* We MUST update the message length in the header NOW before * calculating MESSAGE-INTEGRITY and FINGERPRINT. * Note that length is not including the 20 bytes header. @@ -1921,13 +1940,13 @@ PJ_DEF(pj_status_t) pj_stun_msg_encode(pj_stun_msg *msg, if (i < msg->attr_count-2) { /* Should not happen for message generated by us */ pj_assert(PJ_FALSE); - return PJLIB_UTIL_ESTUNMSGINT; + return PJLIB_UTIL_ESTUNMSGINTPOS; } else if (i == msg->attr_count-2) { if (msg->attr[i+1]->type != PJ_STUN_ATTR_FINGERPRINT) { /* Should not happen for message generated by us */ pj_assert(PJ_FALSE); - return PJLIB_UTIL_ESTUNMSGINT; + return PJLIB_UTIL_ESTUNMSGINTPOS; } else { afingerprint = (pj_stun_fingerprint_attr*) msg->attr[i+1]; } diff --git a/pjlib-util/src/pjlib-util/stun_session.c b/pjlib-util/src/pjlib-util/stun_session.c index 579f0aca..eea1c2ec 100644 --- a/pjlib-util/src/pjlib-util/stun_session.c +++ b/pjlib-util/src/pjlib-util/stun_session.c @@ -571,7 +571,7 @@ PJ_DEF(pj_status_t) pj_stun_session_send_msg( pj_stun_session *sess, /* Encode message */ status = pj_stun_msg_encode(tdata->msg, tdata->pkt, tdata->max_len, - 0, NULL, &tdata->pkt_size); + 0, password, &tdata->pkt_size); if (status != PJ_SUCCESS) { pj_stun_msg_destroy_tdata(sess, tdata); pj_mutex_unlock(sess->mutex); diff --git a/pjlib-util/src/pjstun-client/client_main.c b/pjlib-util/src/pjstun-client/client_main.c index 4ef95d85..4f82f743 100644 --- a/pjlib-util/src/pjstun-client/client_main.c +++ b/pjlib-util/src/pjstun-client/client_main.c @@ -19,11 +19,35 @@ #include #include -#include - #define THIS_FILE "client_main.c" +static struct global +{ + pj_stun_endpoint *endpt; + pj_pool_t *pool; + pj_caching_pool cp; + pj_timer_heap_t *th; + pj_stun_session *sess; + unsigned sess_options; + pj_sock_t sock; + pj_thread_t *thread; + pj_bool_t quit; + + pj_sockaddr_in dst_addr; /**< destination addr */ + +} g; + +static struct options +{ + char *dst_addr; + char *dst_port; + char *realm; + char *user_name; + char *password; + pj_bool_t use_fingerprint; +} o; + static my_perror(const char *title, pj_status_t status) { @@ -33,20 +57,17 @@ static my_perror(const char *title, pj_status_t status) PJ_LOG(3,(THIS_FILE, "%s: %s", title, errmsg)); } -static pj_status_t on_send_msg(pj_stun_tx_data *tdata, +static pj_status_t on_send_msg(pj_stun_session *sess, const void *pkt, pj_size_t pkt_size, - unsigned addr_len, - const pj_sockaddr_t *dst_addr) + const pj_sockaddr_t *dst_addr, + unsigned addr_len) { - pj_sock_t sock; pj_ssize_t len; pj_status_t status; - sock = (pj_sock_t) pj_stun_session_get_user_data(tdata->sess); - len = pkt_size; - status = pj_sock_sendto(sock, pkt, &len, 0, dst_addr, addr_len); + status = pj_sock_sendto(g.sock, pkt, &len, 0, dst_addr, addr_len); if (status != PJ_SUCCESS) my_perror("Error sending packet", status); @@ -54,105 +75,295 @@ static pj_status_t on_send_msg(pj_stun_tx_data *tdata, return status; } -static void on_bind_response(pj_stun_session *sess, - pj_status_t status, - pj_stun_tx_data *request, - const pj_stun_msg *response) +static void on_request_complete(pj_stun_session *sess, + pj_status_t status, + pj_stun_tx_data *tdata, + const pj_stun_msg *response) { - my_perror("on_bind_response()", status); + if (status == PJ_SUCCESS) { + puts("Client transaction completes"); + } else { + my_perror("Client transaction error", status); + } +} + +static int worker_thread(void *unused) +{ + PJ_UNUSED_ARG(unused); + + while (!g.quit) { + pj_time_val timeout = {0, 50}; + pj_fd_set_t readset; + int n; + + pj_timer_heap_poll(g.th, NULL); + + PJ_FD_ZERO(&readset); + PJ_FD_SET(g.sock, &readset); + + n = pj_sock_select(g.sock+1, &readset, NULL, NULL, &timeout); + if (n > 0) { + if (PJ_FD_ISSET(g.sock, &readset)) { + char buffer[512]; + pj_ssize_t len; + pj_sockaddr_in addr; + int addrlen; + pj_status_t rc; + + len = sizeof(buffer); + addrlen = sizeof(addr); + rc = pj_sock_recvfrom(g.sock, buffer, &len, 0, &addr, &addrlen); + if (rc == PJ_SUCCESS && len > 0) { + rc = pj_stun_session_on_rx_pkt(g.sess, buffer, len, + PJ_STUN_IS_DATAGRAM|PJ_STUN_CHECK_PACKET, + NULL, &addr, addrlen); + if (rc != PJ_SUCCESS) + my_perror("Error processing packet", rc); + } + } + } else if (n < 0) + pj_thread_sleep(50); + } + + return 0; } -int main() +static int init() { - pj_stun_endpoint *endpt = NULL; - pj_pool_t *pool = NULL; - pj_caching_pool cp; - pj_timer_heap_t *th = NULL; - pj_stun_session *sess; - pj_sock_t sock = PJ_INVALID_SOCKET; pj_sockaddr_in addr; pj_stun_session_cb stun_cb; - pj_stun_tx_data *tdata; - pj_str_t s; pj_status_t status; + g.sock = PJ_INVALID_SOCKET; + status = pj_init(); status = pjlib_util_init(); - pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0); - - pool = pj_pool_create(&cp.factory, NULL, 1000, 1000, NULL); + pj_caching_pool_init(&g.cp, &pj_pool_factory_default_policy, 0); + + if (o.dst_addr) { + pj_str_t s; + pj_uint16_t port; + + if (o.dst_port) + port = (pj_uint16_t) atoi(o.dst_port); + else + port = PJ_STUN_PORT; + + status = pj_sockaddr_in_init(&g.dst_addr, pj_cstr(&s, o.dst_addr), port); + if (status != PJ_SUCCESS) { + my_perror("Invalid address", status); + return status; + } + + printf("Destination address set to %s:%d\n", o.dst_addr, (int)port); + } else { + printf("Error: address must be specified\n"); + return PJ_EINVAL; + } + + g.pool = pj_pool_create(&g.cp.factory, NULL, 1000, 1000, NULL); - status = pj_timer_heap_create(pool, 1000, &th); + status = pj_timer_heap_create(g.pool, 1000, &g.th); pj_assert(status == PJ_SUCCESS); - status = pj_stun_endpoint_create(&cp.factory, 0, NULL, th, &endpt); + status = pj_stun_endpoint_create(&g.cp.factory, 0, NULL, g.th, &g.endpt); pj_assert(status == PJ_SUCCESS); - status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &sock); + status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &g.sock); pj_assert(status == PJ_SUCCESS); - status = pj_sockaddr_in_init(&addr, pj_cstr(&s, "127.0.0.1"), PJ_STUN_PORT); + status = pj_sockaddr_in_init(&addr, NULL, 0); pj_assert(status == PJ_SUCCESS); pj_memset(&stun_cb, 0, sizeof(stun_cb)); stun_cb.on_send_msg = &on_send_msg; - stun_cb.on_bind_response = &on_bind_response; + stun_cb.on_request_complete = &on_request_complete; - status = pj_stun_session_create(endpt, NULL, &stun_cb, &sess); + status = pj_stun_session_create(g.endpt, NULL, &stun_cb, &g.sess); pj_assert(status == PJ_SUCCESS); - pj_stun_session_set_user_data(sess, (void*)sock); + if (o.realm) { + pj_str_t r, u, p; - status = pj_stun_session_create_bind_req(sess, &tdata); - pj_assert(status == PJ_SUCCESS); - - status = pj_stun_session_send_msg(sess, 0, sizeof(addr), &addr, tdata); - pj_assert(status == PJ_SUCCESS); + if (o.user_name == NULL) { + printf("error: username must be specified\n"); + return PJ_EINVAL; + } + if (o.password == NULL) + o.password = ""; + g.sess_options = PJ_STUN_USE_LONG_TERM_CRED; + pj_stun_session_set_long_term_credential(g.sess, pj_cstr(&r, o.realm), + pj_cstr(&u, o.user_name), + pj_cstr(&p, o.password)); + puts("Using long term credential"); + } else if (o.user_name) { + pj_str_t u, p; + + if (o.password == NULL) + o.password = ""; + g.sess_options = PJ_STUN_USE_SHORT_TERM_CRED; + pj_stun_session_set_short_term_credential(g.sess, + pj_cstr(&u, o.user_name), + pj_cstr(&p, o.password)); + puts("Using short term credential"); + } else { + puts("Credential not set"); + } - while (1) { - pj_fd_set_t rset; - int n; - pj_time_val timeout; + if (o.use_fingerprint) + g.sess_options |= PJ_STUN_USE_FINGERPRINT; - if (kbhit()) { - if (_getch()==27) - break; - } + status = pj_thread_create(g.pool, "stun", &worker_thread, NULL, + 0, 0, &g.thread); + if (status != PJ_SUCCESS) + return status; - PJ_FD_ZERO(&rset); - PJ_FD_SET(sock, &rset); + return PJ_SUCCESS; +} - timeout.sec = 0; timeout.msec = 100; - n = pj_sock_select(FD_SETSIZE, &rset, NULL, NULL, &timeout); +static int shutdown() +{ + if (g.thread) { + g.quit = 1; + pj_thread_join(g.thread); + pj_thread_destroy(g.thread); + g.thread = NULL; + } + if (g.sess) + pj_stun_session_destroy(g.sess); + if (g.endpt) + pj_stun_endpoint_destroy(g.endpt); + if (g.sock != PJ_INVALID_SOCKET) + pj_sock_close(g.sock); + if (g.th) + pj_timer_heap_destroy(g.th); + if (g.pool) + pj_pool_release(g.pool); + + pj_pool_factory_dump(&g.cp.factory, PJ_TRUE); + pj_caching_pool_destroy(&g.cp); + + return PJ_SUCCESS; +} - if (PJ_FD_ISSET(sock, &rset)) { - char pkt[512]; - pj_ssize_t len; +static void menu(void) +{ + puts("Menu:"); + puts(" b Send Bind request"); + puts(" q Quit"); + puts(""); + printf("Choice: "); +} - len = sizeof(pkt); - status = pj_sock_recv(sock, pkt, &len, 0); - if (status == PJ_SUCCESS) { - pj_stun_session_on_rx_pkt(sess, pkt, len, NULL); +static void console_main(void) +{ + while (!g.quit) { + char input[10]; + + menu(); + + fgets(input, sizeof(input), stdin); + + switch (input[0]) { + case 'b': + { + pj_stun_tx_data *tdata; + pj_status_t rc; + + rc = pj_stun_session_create_bind_req(g.sess, &tdata); + pj_assert(rc == PJ_SUCCESS); + + rc = pj_stun_session_send_msg(g.sess, g.sess_options, + &g.dst_addr, sizeof(g.dst_addr), + tdata); + if (rc != PJ_SUCCESS) + my_perror("Error sending STUN request", rc); } + break; + case 'q': + g.quit = 1; + break; + default: + break; } - - pj_timer_heap_poll(th, NULL); } +} -on_return: - if (sock != PJ_INVALID_SOCKET) - pj_sock_close(sock); - if (endpt) - pj_stun_endpoint_destroy(endpt); - if (th) - pj_timer_heap_destroy(th); - if (pool) - pj_pool_release(pool); - pj_caching_pool_destroy(&cp); - return 0; +static void usage(void) +{ + puts("Usage: pjstun_client TARGET [OPTIONS]"); + puts(""); + puts("where TARGET is \"host[:port]\""); + puts(""); + puts("and OPTIONS:"); + puts(" --realm, -r Set realm of the credential"); + puts(" --username, -u Set username of the credential"); + puts(" --password, -p Set password of the credential"); + puts(" --fingerprint, -F Use fingerprint for outgoing requests"); + puts(" --help, -h"); } +int main(int argc, char *argv[]) +{ + struct pj_getopt_option long_options[] = { + { "realm", 1, 0, 'r'}, + { "username", 1, 0, 'u'}, + { "password", 1, 0, 'p'}, + { "fingerprint",0, 0, 'F'}, + { "help", 0, 0, 'h'} + }; + int c, opt_id; + char *pos; + pj_status_t status; + + while((c=pj_getopt_long(argc,argv, "r:u:p:hF", long_options, &opt_id))!=-1) { + switch (c) { + case 'r': + o.realm = pj_optarg; + break; + case 'u': + o.user_name = pj_optarg; + break; + case 'p': + o.password = pj_optarg; + break; + case 'h': + usage(); + return 0; + case 'F': + o.use_fingerprint = PJ_TRUE; + break; + default: + printf("Argument \"%s\" is not valid. Use -h to see help", + argv[pj_optind]); + return 1; + } + } + + if (pj_optind == argc) { + puts("Error: TARGET is needed"); + return 1; + } + + if ((pos=pj_ansi_strchr(argv[pj_optind], ':')) != NULL) { + o.dst_addr = argv[pj_optind]; + *pos = '\0'; + o.dst_port = pos+1; + } else { + o.dst_addr = argv[pj_optind]; + } + + status = init(); + if (status != PJ_SUCCESS) + goto on_return; + + console_main(); + +on_return: + shutdown(); + return status ? 1 : 0; +} diff --git a/pjlib-util/src/pjstun-srv-test/server_main.c b/pjlib-util/src/pjstun-srv-test/server_main.c index 6c15b5ff..a731c0a2 100644 --- a/pjlib-util/src/pjstun-srv-test/server_main.c +++ b/pjlib-util/src/pjstun-srv-test/server_main.c @@ -123,7 +123,6 @@ static pj_status_t on_rx_binding_request(pj_stun_session *sess, { struct service *svc = (struct service *) pj_stun_session_get_user_data(sess); pj_stun_tx_data *tdata; - pj_stun_auth_policy pol; pj_status_t status; /* Create response */ @@ -131,20 +130,6 @@ static pj_status_t on_rx_binding_request(pj_stun_session *sess, if (status != PJ_SUCCESS) return status; -#if 1 - pj_memset(&pol, 0, sizeof(pol)); - pol.type = PJ_STUN_POLICY_STATIC_LONG_TERM; - pol.user_data = NULL; - pol.data.static_long_term.realm = pj_str("realm"); - pol.data.static_long_term.username = pj_str("user"); - pol.data.static_long_term.password = pj_str("password"); - pol.data.static_long_term.nonce = pj_str("nonce"); - status = pj_stun_verify_credential(pkt, pkt_len, msg, &pol, tdata->pool, - &tdata->msg); - if (!tdata->msg) - return status; -#endif - /* Create MAPPED-ADDRESS attribute */ status = pj_stun_msg_add_generic_ip_addr_attr(tdata->pool, tdata->msg, PJ_STUN_ATTR_MAPPED_ADDR, @@ -387,15 +372,23 @@ pj_status_t server_main(void) } #else pj_status_t status; - char line[10]; status = pj_thread_create(server.pool, "stun_server", &worker_thread, NULL, 0, 0, &server.threads[0]); if (status != PJ_SUCCESS) return server_perror(THIS_FILE, "create_thread() error", status); - puts("Press ENTER to quit"); - fgets(line, sizeof(line), stdin); + while (!server.thread_quit_flag) { + char line[10]; + + printf("Menu:\n" + " q Quit\n" + "Choice:"); + + fgets(line, sizeof(line), stdin); + if (line[0] == 'q') + server.thread_quit_flag = 1; + } #endif -- cgit v1.2.3