Beispiel #1
0
/* 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;
}
Beispiel #2
0
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;
}
Beispiel #3
0
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;
}
Beispiel #4
0
/*
 * 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);
}
Beispiel #5
0
/*
 * 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;

}
Beispiel #6
0
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;
}