/* Parse ALLOCATE request */ static pj_status_t parse_allocate_req(alloc_request *cfg, pj_stun_session *sess, const pj_stun_rx_data *rdata, const pj_sockaddr_t *src_addr, unsigned src_addr_len) { const pj_stun_msg *req = rdata->msg; pj_stun_bandwidth_attr *attr_bw; pj_stun_req_transport_attr *attr_req_tp; pj_stun_res_token_attr *attr_res_token; pj_stun_lifetime_attr *attr_lifetime; pj_bzero(cfg, sizeof(*cfg)); /* Get BANDWIDTH attribute, if any. */ attr_bw = (pj_stun_uint_attr*) pj_stun_msg_find_attr(req, PJ_STUN_ATTR_BANDWIDTH, 0); if (attr_bw) { cfg->bandwidth = attr_bw->value; } else { cfg->bandwidth = DEFA_CLIENT_BANDWIDTH; } /* Check if we can satisfy the bandwidth */ if (cfg->bandwidth > MAX_CLIENT_BANDWIDTH) { pj_stun_session_respond(sess, rdata, PJ_STUN_SC_ALLOCATION_QUOTA_REACHED, "Invalid bandwidth", NULL, PJ_TRUE, src_addr, src_addr_len); return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_ALLOCATION_QUOTA_REACHED); } /* MUST have REQUESTED-TRANSPORT attribute */ attr_req_tp = (pj_stun_uint_attr*) pj_stun_msg_find_attr(req, PJ_STUN_ATTR_REQ_TRANSPORT, 0); if (attr_req_tp == NULL) { pj_stun_session_respond(sess, rdata, PJ_STUN_SC_BAD_REQUEST, "Missing REQUESTED-TRANSPORT attribute", NULL, PJ_TRUE, src_addr, src_addr_len); return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_BAD_REQUEST); } cfg->tp_type = PJ_STUN_GET_RT_PROTO(attr_req_tp->value); /* Can only support UDP for now */ if (cfg->tp_type != PJ_TURN_TP_UDP) { pj_stun_session_respond(sess, rdata, PJ_STUN_SC_UNSUPP_TRANSPORT_PROTO, NULL, NULL, PJ_TRUE, src_addr, src_addr_len); return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNSUPP_TRANSPORT_PROTO); } /* Get RESERVATION-TOKEN attribute, if any */ attr_res_token = (pj_stun_res_token_attr*) pj_stun_msg_find_attr(req, PJ_STUN_ATTR_RESERVATION_TOKEN, 0); if (attr_res_token) { /* We don't support RESERVATION-TOKEN for now */ pj_stun_session_respond(sess, rdata, PJ_STUN_SC_BAD_REQUEST, "RESERVATION-TOKEN is not supported", NULL, PJ_TRUE, src_addr, src_addr_len); return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_BAD_REQUEST); } /* Get LIFETIME attribute */ attr_lifetime = (pj_stun_uint_attr*) pj_stun_msg_find_attr(req, PJ_STUN_ATTR_LIFETIME, 0); if (attr_lifetime) { cfg->lifetime = attr_lifetime->value; if (cfg->lifetime < MIN_LIFETIME) { pj_stun_session_respond(sess, rdata, PJ_STUN_SC_BAD_REQUEST, "LIFETIME too short", NULL, PJ_TRUE, src_addr, src_addr_len); return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_BAD_REQUEST); } if (cfg->lifetime > MAX_LIFETIME) cfg->lifetime = MAX_LIFETIME; } else { cfg->lifetime = DEF_LIFETIME; } return PJ_SUCCESS; }
int ice_test(void) { pj_pool_t *pool; pj_stun_config stun_cfg; unsigned i; int rc; struct sess_cfg_t { const char *title; unsigned server_flag; struct test_cfg ua1; struct test_cfg ua2; } sess_cfg[] = { /* Role comp# host? stun? turn? flag? ans_del snd_del des_del */ { "hosts candidates only", 0xFFFF, {ROLE1, 1, YES, NO, NO, NO, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}}, {ROLE2, 1, YES, NO, NO, NO, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}} }, { "host and srflxes", 0xFFFF, {ROLE1, 1, YES, YES, NO, NO, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}}, {ROLE2, 1, YES, YES, NO, NO, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}} }, { "host vs relay", 0xFFFF, {ROLE1, 1, YES, NO, NO, NO, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}}, {ROLE2, 1, NO, NO, YES, NO, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}} }, { "relay vs host", 0xFFFF, {ROLE1, 1, NO, NO, YES, NO, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}}, {ROLE2, 1, YES, NO, NO, NO, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}} }, { "relay vs relay", 0xFFFF, {ROLE1, 1, NO, NO, YES, NO, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}}, {ROLE2, 1, NO, NO, YES, NO, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}} }, { "all candidates", 0xFFFF, {ROLE1, 1, YES, YES, YES, NO, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}}, {ROLE2, 1, YES, YES, YES, NO, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}} }, }; pool = pj_pool_create(mem, NULL, 512, 512, NULL); rc = create_stun_config(pool, &stun_cfg); if (rc != PJ_SUCCESS) { pj_pool_release(pool); return -7; } /* Simple test first with host candidate */ if (1) { struct sess_cfg_t cfg = { "Basic with host candidates", 0x0, /* Role comp# host? stun? turn? flag? ans_del snd_del des_del */ {ROLE1, 1, YES, NO, NO, 0, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}}, {ROLE2, 1, YES, NO, NO, 0, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}} }; rc = perform_test(cfg.title, &stun_cfg, cfg.server_flag, &cfg.ua1, &cfg.ua2); if (rc != 0) goto on_return; cfg.ua1.comp_cnt = 2; cfg.ua2.comp_cnt = 2; rc = perform_test("Basic with host candidates, 2 components", &stun_cfg, cfg.server_flag, &cfg.ua1, &cfg.ua2); if (rc != 0) goto on_return; } /* Simple test first with srflx candidate */ if (1) { struct sess_cfg_t cfg = { "Basic with srflx candidates", 0xFFFF, /* Role comp# host? stun? turn? flag? ans_del snd_del des_del */ {ROLE1, 1, YES, YES, NO, 0, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}}, {ROLE2, 1, YES, YES, NO, 0, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}} }; rc = perform_test(cfg.title, &stun_cfg, cfg.server_flag, &cfg.ua1, &cfg.ua2); if (rc != 0) goto on_return; cfg.ua1.comp_cnt = 2; cfg.ua2.comp_cnt = 2; rc = perform_test("Basic with srflx candidates, 2 components", &stun_cfg, cfg.server_flag, &cfg.ua1, &cfg.ua2); if (rc != 0) goto on_return; } /* Simple test with relay candidate */ if (1) { struct sess_cfg_t cfg = { "Basic with relay candidates", 0xFFFF, /* Role comp# host? stun? turn? flag? ans_del snd_del des_del */ {ROLE1, 1, NO, NO, YES, 0, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}}, {ROLE2, 1, NO, NO, YES, 0, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}} }; rc = perform_test(cfg.title, &stun_cfg, cfg.server_flag, &cfg.ua1, &cfg.ua2); if (rc != 0) goto on_return; cfg.ua1.comp_cnt = 2; cfg.ua2.comp_cnt = 2; rc = perform_test("Basic with relay candidates, 2 components", &stun_cfg, cfg.server_flag, &cfg.ua1, &cfg.ua2); if (rc != 0) goto on_return; } /* Failure test with STUN resolution */ if (1) { struct sess_cfg_t cfg = { "STUN resolution failure", 0x0, /* Role comp# host? stun? turn? flag? ans_del snd_del des_del */ {ROLE1, 2, NO, YES, NO, 0, 0, 0, 0, {PJNATH_ESTUNTIMEDOUT, -1}}, {ROLE2, 2, NO, YES, NO, 0, 0, 0, 0, {PJNATH_ESTUNTIMEDOUT, -1}} }; rc = perform_test(cfg.title, &stun_cfg, cfg.server_flag, &cfg.ua1, &cfg.ua2); if (rc != 0) goto on_return; cfg.ua1.client_flag |= DEL_ON_ERR; cfg.ua2.client_flag |= DEL_ON_ERR; rc = perform_test("STUN resolution failure with destroy on callback", &stun_cfg, cfg.server_flag, &cfg.ua1, &cfg.ua2); if (rc != 0) goto on_return; } /* Failure test with TURN resolution */ if (1) { struct sess_cfg_t cfg = { "TURN allocation failure", 0xFFFF, /* Role comp# host? stun? turn? flag? ans_del snd_del des_del */ {ROLE1, 2, NO, NO, YES, WRONG_TURN, 0, 0, 0, {PJ_STATUS_FROM_STUN_CODE(401), -1}}, {ROLE2, 2, NO, NO, YES, WRONG_TURN, 0, 0, 0, {PJ_STATUS_FROM_STUN_CODE(401), -1}} }; rc = perform_test(cfg.title, &stun_cfg, cfg.server_flag, &cfg.ua1, &cfg.ua2); if (rc != 0) goto on_return; cfg.ua1.client_flag |= DEL_ON_ERR; cfg.ua2.client_flag |= DEL_ON_ERR; rc = perform_test("TURN allocation failure with destroy on callback", &stun_cfg, cfg.server_flag, &cfg.ua1, &cfg.ua2); if (rc != 0) goto on_return; } /* STUN failure, testing TURN deallocation */ if (1) { struct sess_cfg_t cfg = { "STUN failure, testing TURN deallocation", 0xFFFF & (~(CREATE_STUN_SERVER)), /* Role comp# host? stun? turn? flag? ans_del snd_del des_del */ {ROLE1, 2, YES, YES, YES, 0, 0, 0, 0, {PJNATH_ESTUNTIMEDOUT, -1}}, {ROLE2, 2, YES, YES, YES, 0, 0, 0, 0, {PJNATH_ESTUNTIMEDOUT, -1}} }; rc = perform_test(cfg.title, &stun_cfg, cfg.server_flag, &cfg.ua1, &cfg.ua2); if (rc != 0) goto on_return; cfg.ua1.client_flag |= DEL_ON_ERR; cfg.ua2.client_flag |= DEL_ON_ERR; rc = perform_test("STUN failure, testing TURN deallocation (cb)", &stun_cfg, cfg.server_flag, &cfg.ua1, &cfg.ua2); if (rc != 0) goto on_return; } rc = 0; /* Iterate each test item */ for (i=0; i<PJ_ARRAY_SIZE(sess_cfg); ++i) { struct sess_cfg_t *cfg = &sess_cfg[i]; unsigned delay[] = { 50, 2000 }; unsigned d; PJ_LOG(3,("", " %s", cfg->title)); /* For each test item, test with various answer delay */ for (d=0; d<PJ_ARRAY_SIZE(delay); ++d) { struct role_t { pj_ice_sess_role ua1; pj_ice_sess_role ua2; } role[] = { { ROLE1, ROLE2}, { ROLE2, ROLE1}, { ROLE1, ROLE1}, { ROLE2, ROLE2} }; unsigned j; cfg->ua1.answer_delay = delay[d]; cfg->ua2.answer_delay = delay[d]; /* For each test item, test with role conflict scenarios */ for (j=0; j<PJ_ARRAY_SIZE(role); ++j) { unsigned k1; cfg->ua1.role = role[j].ua1; cfg->ua2.role = role[j].ua2; /* For each test item, test with different number of components */ for (k1=1; k1<=2; ++k1) { unsigned k2; cfg->ua1.comp_cnt = k1; for (k2=1; k2<=2; ++k2) { char title[120]; sprintf(title, "%s/%s, %dms answer delay, %d vs %d components", pj_ice_sess_role_name(role[j].ua1), pj_ice_sess_role_name(role[j].ua2), delay[d], k1, k2); cfg->ua2.comp_cnt = k2; rc = perform_test(title, &stun_cfg, cfg->server_flag, &cfg->ua1, &cfg->ua2); if (rc != 0) goto on_return; } } } } } on_return: destroy_stun_config(&stun_cfg); pj_pool_release(pool); return rc; }
int sess_auth_test(void) { pj_pool_t *pool; int rc; PJ_LOG(3,(THIS_FILE, " STUN session authentication test")); /* Init STUN config */ pj_stun_config_init(&stun_cfg, mem, 0, NULL, NULL); /* Create pool and timer heap */ pool = pj_pool_create(mem, "authtest", 200, 200, NULL); if (pj_timer_heap_create(pool, 20, &stun_cfg.timer_heap)) { pj_pool_release(pool); return -5; } /* Basic retransmission test */ rc = run_client_test("Retransmission", // title PJ_FALSE, // server responding PJ_STUN_AUTH_NONE, // server auth PJ_STUN_AUTH_NONE, // client auth NULL, // realm NULL, // username NULL, // nonce NULL, // password PJ_FALSE, // dummy MI PJ_TRUE, // expected error PJNATH_ESTUNTIMEDOUT,// expected code NULL, // expected realm NULL, // expected nonce &retransmit_check // more check ); if (rc != 0) { goto done; } /* * Short term credential. * draft-ietf-behave-rfc3489bis-15#section-10.1.2 */ /* * If the message does not contain both a MESSAGE-INTEGRITY and a * USERNAME attribute, If the message is a request, the server MUST * reject the request with an error response. This response MUST * use an error code of 400 (Bad Request). */ rc = run_client_test("Missing MESSAGE-INTEGRITY (short term)", // title PJ_TRUE, // server responding PJ_STUN_AUTH_SHORT_TERM, // server auth PJ_STUN_AUTH_NONE, // client auth NULL, // realm NULL, // username NULL, // nonce NULL, // password PJ_FALSE, // dummy MI PJ_TRUE, // expected error PJ_STATUS_FROM_STUN_CODE(400),// expected code NULL, // expected realm NULL, // expected nonce NULL // more check ); if (rc != 0) { goto done; } /* If the USERNAME does not contain a username value currently valid * within the server: If the message is a request, the server MUST * reject the request with an error response. This response MUST use * an error code of 401 (Unauthorized). */ rc = run_client_test("USERNAME mismatch (short term)", // title PJ_TRUE, // server responding PJ_STUN_AUTH_SHORT_TERM, // server auth PJ_STUN_AUTH_SHORT_TERM, // client auth NULL, // realm "anotheruser", // username NULL, // nonce "anotherpass", // password PJ_FALSE, // dummy MI PJ_TRUE, // expected error PJ_STATUS_FROM_STUN_CODE(401),// expected code NULL, // expected realm NULL, // expected nonce NULL // more check ); if (rc != 0) { goto done; } /* Using the password associated with the username, compute the value * for the message-integrity as described in Section 15.4. If the * resulting value does not match the contents of the MESSAGE- * INTEGRITY attribute: * * - If the message is a request, the server MUST reject the request * with an error response. This response MUST use an error code * of 401 (Unauthorized). */ rc = run_client_test("MESSAGE-INTEGRITY mismatch (short term)", // title PJ_TRUE, // server responding PJ_STUN_AUTH_SHORT_TERM, // server auth PJ_STUN_AUTH_SHORT_TERM, // client auth NULL, // realm USERNAME, // username NULL, // nonce "anotherpass", // password PJ_FALSE, // dummy MI PJ_TRUE, // expected error PJ_STATUS_FROM_STUN_CODE(401),// expected code NULL, // expected realm NULL, // expected nonce NULL // more check ); if (rc != 0) { goto done; } /* USERNAME is not present, server must respond with 400 (Bad * Request). */ rc = run_client_test("Missing USERNAME (short term)",// title PJ_TRUE, // server responding PJ_STUN_AUTH_SHORT_TERM, // server auth PJ_STUN_AUTH_NONE, // client auth NULL, // realm NULL, // username NULL, // nonce NULL, // password PJ_TRUE, // dummy MI PJ_TRUE, // expected error PJ_STATUS_FROM_STUN_CODE(400), // expected code NULL, // expected realm NULL, // expected nonce NULL // more check ); if (rc != 0) { goto done; } /* Successful short term authentication */ rc = run_client_test("Successful scenario (short term)", // title PJ_TRUE, // server responding PJ_STUN_AUTH_SHORT_TERM, // server auth PJ_STUN_AUTH_SHORT_TERM, // client auth NULL, // realm USERNAME, // username NULL, // nonce PASSWORD, // password PJ_FALSE, // dummy MI PJ_FALSE, // expected error PJ_SUCCESS, // expected code NULL, // expected realm NULL, // expected nonce NULL // more check ); if (rc != 0) { goto done; } /* * (our own) Extended tests for long term credential */ /* When server wants to use short term credential, but request has * REALM, reject with .... 401 ??? */ rc = run_client_test("Unwanted REALM (short term)", // title PJ_TRUE, // server responding PJ_STUN_AUTH_SHORT_TERM, // server auth PJ_STUN_AUTH_NONE, // client auth REALM, // realm USERNAME, // username NULL, // nonce PASSWORD, // password PJ_TRUE, // dummy MI PJ_TRUE, // expected error PJ_STATUS_FROM_STUN_CODE(401), // expected code NULL, // expected realm NULL, // expected nonce &long_term_check2 // more check ); if (rc != 0) { goto done; } /* * Long term credential. * draft-ietf-behave-rfc3489bis-15#section-10.2.2 */ /* If the message does not contain a MESSAGE-INTEGRITY attribute, the * server MUST generate an error response with an error code of 401 * (Unauthorized). This response MUST include a REALM value. It is * RECOMMENDED that the REALM value be the domain name of the * provider of the STUN server. The response MUST include a NONCE, * selected by the server. The response SHOULD NOT contain a * USERNAME or MESSAGE-INTEGRITY attribute. */ rc = run_client_test("Missing M-I (long term)", // title PJ_TRUE, // server responding PJ_STUN_AUTH_LONG_TERM, // server auth PJ_STUN_AUTH_NONE, // client auth NULL, // client realm NULL, // client username NULL, // client nonce NULL, // client password PJ_FALSE, // client dummy MI PJ_TRUE, // expected error PJ_STATUS_FROM_STUN_CODE(401), // expected code REALM, // expected realm NONCE, // expected nonce &long_term_check1 // more check ); if (rc != 0) { goto done; } /* If the message contains a MESSAGE-INTEGRITY attribute, but is * missing the USERNAME, REALM or NONCE attributes, the server MUST * generate an error response with an error code of 400 (Bad * Request). This response SHOULD NOT include a USERNAME, NONCE, * REALM or MESSAGE-INTEGRITY attribute. */ /* Missing USERNAME */ rc = run_client_test("Missing USERNAME (long term)", // title PJ_TRUE, // server responding PJ_STUN_AUTH_LONG_TERM, // server auth PJ_STUN_AUTH_NONE, // client auth REALM, // client realm NULL, // client username NONCE, // client nonce PASSWORD, // client password PJ_TRUE, // client dummy MI PJ_TRUE, // expected error PJ_STATUS_FROM_STUN_CODE(400), // expected code NULL, // expected realm NULL, // expected nonce &long_term_check2 // more check ); if (rc != 0) { goto done; } /* Missing REALM */ rc = run_client_test("Missing REALM (long term)", // title PJ_TRUE, // server responding PJ_STUN_AUTH_LONG_TERM, // server auth PJ_STUN_AUTH_NONE, // client auth NULL, // client realm USERNAME, // client username NONCE, // client nonce PASSWORD, // client password PJ_TRUE, // client dummy MI PJ_TRUE, // expected error PJ_STATUS_FROM_STUN_CODE(400), // expected code NULL, // expected realm NULL, // expected nonce &long_term_check2 // more check ); if (rc != 0) { goto done; } /* Missing NONCE */ rc = run_client_test("Missing NONCE (long term)", // title PJ_TRUE, // server responding PJ_STUN_AUTH_LONG_TERM, // server auth PJ_STUN_AUTH_NONE, // client auth REALM, // client realm USERNAME, // client username NULL, // client nonce PASSWORD, // client password PJ_TRUE, // client dummy MI PJ_TRUE, // expected error PJ_STATUS_FROM_STUN_CODE(400), // expected code NULL, // expected realm NULL, // expected nonce &long_term_check2 // more check ); if (rc != 0) { goto done; } /* If the NONCE is no longer valid, the server MUST generate an error * response with an error code of 438 (Stale Nonce). This response * MUST include a NONCE and REALM attribute and SHOULD NOT incude the * USERNAME or MESSAGE-INTEGRITY attribute. Servers can invalidate * nonces in order to provide additional security. See Section 4.3 * of [RFC2617] for guidelines. */ // how?? /* If the username in the USERNAME attribute is not valid, the server * MUST generate an error response with an error code of 401 * (Unauthorized). This response MUST include a REALM value. It is * RECOMMENDED that the REALM value be the domain name of the * provider of the STUN server. The response MUST include a NONCE, * selected by the server. The response SHOULD NOT contain a * USERNAME or MESSAGE-INTEGRITY attribute. */ rc = run_client_test("Invalid username (long term)", // title PJ_TRUE, // server responding PJ_STUN_AUTH_LONG_TERM, // server auth PJ_STUN_AUTH_LONG_TERM, // client auth REALM, // client realm "anotheruser", // client username "a nonce", // client nonce "somepassword", // client password PJ_FALSE, // client dummy MI PJ_TRUE, // expected error PJ_STATUS_FROM_STUN_CODE(401), // expected code REALM, // expected realm NONCE, // expected nonce &long_term_check1 // more check ); if (rc != 0) { goto done; } /* Successful long term authentication */ rc = run_client_test("Successful scenario (long term)", // title PJ_TRUE, // server responding PJ_STUN_AUTH_LONG_TERM, // server auth PJ_STUN_AUTH_LONG_TERM, // client auth REALM, // client realm USERNAME, // client username "anothernonce", // client nonce PASSWORD, // client password PJ_FALSE, // client dummy MI PJ_FALSE, // expected error 0, // expected code NULL, // expected realm NULL, // expected nonce &long_term_check3 // more check ); if (rc != 0) { goto done; } /* * (our own) Extended tests for long term credential */ /* If REALM doesn't match, server must respond with 401 */ #if 0 // STUN session now will just use the realm sent in the // response, so this test will fail because it will // authenticate successfully. rc = run_client_test("Invalid REALM (long term)", // title PJ_TRUE, // server responding PJ_STUN_AUTH_LONG_TERM, // server auth PJ_STUN_AUTH_LONG_TERM, // client auth "anotherrealm", // client realm USERNAME, // client username NONCE, // client nonce PASSWORD, // client password PJ_FALSE, // client dummy MI PJ_TRUE, // expected error PJ_STATUS_FROM_STUN_CODE(401), // expected code REALM, // expected realm NONCE, // expected nonce &long_term_check1 // more check ); if (rc != 0) { goto done; } #endif /* Invalid HMAC */ /* Valid static short term, without NONCE */ /* Valid static short term, WITH NONCE */ /* Valid static long term (with NONCE */ /* Valid dynamic short term (without NONCE) */ /* Valid dynamic short term (with NONCE) */ /* Valid dynamic long term (with NONCE) */ done: pj_timer_heap_destroy(stun_cfg.timer_heap); pj_pool_release(pool); return rc; }
/* * Callback upon request completion. */ static void on_request_complete(pj_stun_session *stun_sess, pj_status_t status, void *token, pj_stun_tx_data *tdata, const pj_stun_msg *response, const pj_sockaddr_t *src_addr, unsigned src_addr_len) { nat_detect_session *sess; pj_stun_sockaddr_attr *mattr = NULL; pj_stun_changed_addr_attr *ca = NULL; pj_uint32_t *tsx_id; int cmp; unsigned test_id; PJ_UNUSED_ARG(token); PJ_UNUSED_ARG(tdata); PJ_UNUSED_ARG(src_addr); PJ_UNUSED_ARG(src_addr_len); sess = (nat_detect_session*) pj_stun_session_get_user_data(stun_sess); pj_mutex_lock(sess->mutex); /* Find errors in the response */ if (status == PJ_SUCCESS) { /* Check error message */ if (PJ_STUN_IS_ERROR_RESPONSE(response->hdr.type)) { pj_stun_errcode_attr *eattr; int err_code; eattr = (pj_stun_errcode_attr*) pj_stun_msg_find_attr(response, PJ_STUN_ATTR_ERROR_CODE, 0); if (eattr != NULL) err_code = eattr->err_code; else err_code = PJ_STUN_SC_SERVER_ERROR; status = PJ_STATUS_FROM_STUN_CODE(err_code); } else { /* Get MAPPED-ADDRESS or XOR-MAPPED-ADDRESS */ mattr = (pj_stun_sockaddr_attr*) pj_stun_msg_find_attr(response, PJ_STUN_ATTR_XOR_MAPPED_ADDR, 0); if (mattr == NULL) { mattr = (pj_stun_sockaddr_attr*) pj_stun_msg_find_attr(response, PJ_STUN_ATTR_MAPPED_ADDR, 0); } if (mattr == NULL) { status = PJNATH_ESTUNNOMAPPEDADDR; } /* Get CHANGED-ADDRESS attribute */ ca = (pj_stun_changed_addr_attr*) pj_stun_msg_find_attr(response, PJ_STUN_ATTR_CHANGED_ADDR, 0); if (ca == NULL) { status = PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_SERVER_ERROR); } } } /* Save the result */ tsx_id = (pj_uint32_t*) tdata->msg->hdr.tsx_id; test_id = tsx_id[2]; if (test_id >= ST_MAX) { PJ_LOG(4,(sess->pool->obj_name, "Invalid transaction ID %u in response", test_id)); end_session(sess, PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_SERVER_ERROR), PJ_STUN_NAT_TYPE_ERR_UNKNOWN); goto on_return; } PJ_LOG(5,(sess->pool->obj_name, "Completed %s, status=%d", test_names[test_id], status)); sess->result[test_id].complete = PJ_TRUE; sess->result[test_id].status = status; if (status == PJ_SUCCESS) { pj_memcpy(&sess->result[test_id].ma, &mattr->sockaddr.ipv4, sizeof(pj_sockaddr_in)); pj_memcpy(&sess->result[test_id].ca, &ca->sockaddr.ipv4, sizeof(pj_sockaddr_in)); } /* Send Test 1B only when Test 2 completes. Must not send Test 1B * before Test 2 completes to avoid creating mapping on the NAT. */ if (!sess->result[ST_TEST_1B].executed && sess->result[ST_TEST_2].complete && sess->result[ST_TEST_2].status != PJ_SUCCESS && sess->result[ST_TEST_1].complete && sess->result[ST_TEST_1].status == PJ_SUCCESS) { cmp = pj_memcmp(&sess->local_addr, &sess->result[ST_TEST_1].ma, sizeof(pj_sockaddr_in)); if (cmp != 0) send_test(sess, ST_TEST_1B, &sess->result[ST_TEST_1].ca, 0); } if (test_completed(sess)<3 || test_completed(sess)!=test_executed(sess)) goto on_return; /* Handle the test result according to RFC 3489 page 22: +--------+ | Test | | 1 | +--------+ | | V /\ /\ N / \ Y / \ Y +--------+ UDP <-------/Resp\--------->/ IP \------------->| Test | Blocked \ ? / \Same/ | 2 | \ / \? / +--------+ \/ \/ | | N | | V V /\ +--------+ Sym. N / \ | Test | UDP <---/Resp\ | 2 | Firewall \ ? / +--------+ \ / | \/ V |Y /\ /\ | Symmetric N / \ +--------+ N / \ V NAT <--- / IP \<-----| Test |<--- /Resp\ Open \Same/ | 1B | \ ? / Internet \? / +--------+ \ / \/ \/ | |Y | | | V | Full | Cone V /\ +--------+ / \ Y | Test |------>/Resp\---->Restricted | 3 | \ ? / +--------+ \ / \/ |N | Port +------>Restricted Figure 2: Flow for type discovery process */ switch (sess->result[ST_TEST_1].status) { case PJNATH_ESTUNTIMEDOUT: /* * Test 1 has timed-out. Conclude with NAT_TYPE_BLOCKED. */ end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_BLOCKED); break; case PJ_SUCCESS: /* * Test 1 is successful. Further tests are needed to detect * NAT type. Compare the MAPPED-ADDRESS with the local address. */ cmp = pj_memcmp(&sess->local_addr, &sess->result[ST_TEST_1].ma, sizeof(pj_sockaddr_in)); if (cmp==0) { /* * MAPPED-ADDRESS and local address is equal. Need one more * test to determine NAT type. */ switch (sess->result[ST_TEST_2].status) { case PJ_SUCCESS: /* * Test 2 is also successful. We're in the open. */ end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_OPEN); break; case PJNATH_ESTUNTIMEDOUT: /* * Test 2 has timed out. We're behind somekind of UDP * firewall. */ end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_SYMMETRIC_UDP); break; default: /* * We've got other error with Test 2. */ end_session(sess, sess->result[ST_TEST_2].status, PJ_STUN_NAT_TYPE_ERR_UNKNOWN); break; } } else { /* * MAPPED-ADDRESS is different than local address. * We're behind NAT. */ switch (sess->result[ST_TEST_2].status) { case PJ_SUCCESS: /* * Test 2 is successful. We're behind a full-cone NAT. */ end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_FULL_CONE); break; case PJNATH_ESTUNTIMEDOUT: /* * Test 2 has timed-out Check result of test 1B.. */ switch (sess->result[ST_TEST_1B].status) { case PJ_SUCCESS: /* * Compare the MAPPED-ADDRESS of test 1B with the * MAPPED-ADDRESS returned in test 1.. */ cmp = pj_memcmp(&sess->result[ST_TEST_1].ma, &sess->result[ST_TEST_1B].ma, sizeof(pj_sockaddr_in)); if (cmp != 0) { /* * MAPPED-ADDRESS is different, we're behind a * symmetric NAT. */ end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_SYMMETRIC); } else { /* * MAPPED-ADDRESS is equal. We're behind a restricted * or port-restricted NAT, depending on the result of * test 3. */ switch (sess->result[ST_TEST_3].status) { case PJ_SUCCESS: /* * Test 3 is successful, we're behind a restricted * NAT. */ end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_RESTRICTED); break; case PJNATH_ESTUNTIMEDOUT: /* * Test 3 failed, we're behind a port restricted * NAT. */ end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_PORT_RESTRICTED); break; default: /* * Got other error with test 3. */ end_session(sess, sess->result[ST_TEST_3].status, PJ_STUN_NAT_TYPE_ERR_UNKNOWN); break; } } break; case PJNATH_ESTUNTIMEDOUT: /* * Strangely test 1B has failed. Maybe connectivity was * lost? Or perhaps port 3489 (the usual port number in * CHANGED-ADDRESS) is blocked? */ switch (sess->result[ST_TEST_3].status) { case PJ_SUCCESS: /* Although test 1B failed, test 3 was successful. * It could be that port 3489 is blocked, while the * NAT itself looks to be a Restricted one. */ end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_RESTRICTED); break; default: /* Can't distinguish between Symmetric and Port * Restricted, so set the type to Unknown */ end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_ERR_UNKNOWN); break; } break; default: /* * Got other error with test 1B. */ end_session(sess, sess->result[ST_TEST_1B].status, PJ_STUN_NAT_TYPE_ERR_UNKNOWN); break; } break; default: /* * We've got other error with Test 2. */ end_session(sess, sess->result[ST_TEST_2].status, PJ_STUN_NAT_TYPE_ERR_UNKNOWN); break; } } break; default: /* * We've got other error with Test 1. */ end_session(sess, sess->result[ST_TEST_1].status, PJ_STUN_NAT_TYPE_ERR_UNKNOWN); break; } on_return: pj_mutex_unlock(sess->mutex); }
/* * Notify the STUN transaction about the arrival of STUN response. */ PJ_DEF(pj_status_t) pj_stun_client_tsx_on_rx_msg(pj_stun_client_tsx *tsx, const pj_stun_msg *msg, const pj_sockaddr_t *src_addr, unsigned src_addr_len) { pj_stun_errcode_attr *err_attr; pj_status_t status; /* Must be STUN response message */ if (!PJ_STUN_IS_SUCCESS_RESPONSE(msg->hdr.type) && !PJ_STUN_IS_ERROR_RESPONSE(msg->hdr.type)) { PJ_LOG(4,(tsx->obj_name, "STUN rx_msg() error: not response message")); return PJNATH_EINSTUNMSGTYPE; } /* We have a response with matching transaction ID. * We can cancel retransmit timer now. */ if (tsx->retransmit_timer.id) { pj_timer_heap_cancel(tsx->timer_heap, &tsx->retransmit_timer); tsx->retransmit_timer.id = 0; } /* Find STUN error code attribute */ err_attr = (pj_stun_errcode_attr*) pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_ERROR_CODE, 0); if (err_attr && err_attr->err_code <= 200) { /* draft-ietf-behave-rfc3489bis-05.txt Section 8.3.2: * Any response between 100 and 299 MUST result in the cessation * of request retransmissions, but otherwise is discarded. */ PJ_LOG(4,(tsx->obj_name, "STUN rx_msg() error: received provisional %d code (%.*s)", err_attr->err_code, (int)err_attr->reason.slen, err_attr->reason.ptr)); return PJ_SUCCESS; } if (err_attr == NULL) { status = PJ_SUCCESS; } else { status = PJ_STATUS_FROM_STUN_CODE(err_attr->err_code); } /* Call callback */ if (!tsx->complete) { tsx->complete = PJ_TRUE; if (tsx->cb.on_complete) { tsx->cb.on_complete(tsx, status, msg, src_addr, src_addr_len); } /* We might have been destroyed, don't try to access the object */ } return PJ_SUCCESS; }
static pj_status_t handle_auth_challenge(pj_stun_session *sess, const pj_stun_tx_data *request, const pj_stun_msg *response, const pj_sockaddr_t *src_addr, unsigned src_addr_len, pj_bool_t *notify_user) { const pj_stun_errcode_attr *ea; *notify_user = PJ_TRUE; if (response==NULL) return PJ_SUCCESS; if (sess->auth_type != PJ_STUN_AUTH_LONG_TERM) return PJ_SUCCESS; if (!PJ_STUN_IS_ERROR_RESPONSE(response->hdr.type)) { sess->auth_retry = 0; return PJ_SUCCESS; } ea = (const pj_stun_errcode_attr*) pj_stun_msg_find_attr(response, PJ_STUN_ATTR_ERROR_CODE, 0); if (!ea) { PJ_LOG(4,(SNAME(sess), "Invalid error response: no ERROR-CODE" " attribute")); *notify_user = PJ_FALSE; return PJNATH_EINSTUNMSG; } if (ea->err_code == PJ_STUN_SC_UNAUTHORIZED || ea->err_code == PJ_STUN_SC_STALE_NONCE) { const pj_stun_nonce_attr *anonce; const pj_stun_realm_attr *arealm; pj_stun_tx_data *tdata; unsigned i; pj_status_t status; anonce = (const pj_stun_nonce_attr*) pj_stun_msg_find_attr(response, PJ_STUN_ATTR_NONCE, 0); if (!anonce) { PJ_LOG(4,(SNAME(sess), "Invalid response: missing NONCE")); *notify_user = PJ_FALSE; return PJNATH_EINSTUNMSG; } /* Bail out if we've supplied the correct nonce */ if (pj_strcmp(&anonce->value, &sess->next_nonce)==0) { return PJ_SUCCESS; } /* Bail out if we've tried too many */ if (++sess->auth_retry > 3) { PJ_LOG(4,(SNAME(sess), "Error: authentication failed (too " "many retries)")); return PJ_STATUS_FROM_STUN_CODE(401); } /* Save next_nonce */ pj_strdup(sess->pool, &sess->next_nonce, &anonce->value); /* Copy the realm from the response */ arealm = (pj_stun_realm_attr*) pj_stun_msg_find_attr(response, PJ_STUN_ATTR_REALM, 0); if (arealm) { pj_strdup(sess->pool, &sess->server_realm, &arealm->value); while (sess->server_realm.slen && !sess->server_realm.ptr[sess->server_realm.slen-1]) { --sess->server_realm.slen; } } /* Create new request */ status = pj_stun_session_create_req(sess, request->msg->hdr.type, request->msg->hdr.magic, NULL, &tdata); if (status != PJ_SUCCESS) return status; /* Duplicate all the attributes in the old request, except * USERNAME, REALM, M-I, and NONCE, which will be filled in * later. */ for (i=0; i<request->msg->attr_count; ++i) { const pj_stun_attr_hdr *asrc = request->msg->attr[i]; if (asrc->type == PJ_STUN_ATTR_USERNAME || asrc->type == PJ_STUN_ATTR_REALM || asrc->type == PJ_STUN_ATTR_MESSAGE_INTEGRITY || asrc->type == PJ_STUN_ATTR_NONCE) { continue; } tdata->msg->attr[tdata->msg->attr_count++] = pj_stun_attr_clone(tdata->pool, asrc); } /* Will retry the request with authentication, no need to * notify user. */ *notify_user = PJ_FALSE; PJ_LOG(4,(SNAME(sess), "Retrying request with new authentication")); /* Retry the request */ status = pj_stun_session_send_msg(sess, request->token, PJ_TRUE, request->retransmit, src_addr, src_addr_len, tdata); } else { sess->auth_retry = 0; } return PJ_SUCCESS; }