From 54037d38631be9cb082b61bbfd3cc6c218283ea1 Mon Sep 17 00:00:00 2001 From: Benny Prijono Date: Tue, 20 Mar 2007 22:36:54 +0000 Subject: Today's work git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@1089 74dad513-b988-da41-8d7b-12977e46ad98 --- pjnath/include/pjnath/errno.h | 1 + pjnath/include/pjnath/ice.h | 45 +- pjnath/include/pjnath/stun_auth.h | 43 +- pjnath/src/pjnath/ice.c | 931 +++++++++++++++++++++++++++++--------- pjnath/src/pjnath/stun_auth.c | 6 +- pjnath/src/pjnath/stun_session.c | 102 +++-- 6 files changed, 855 insertions(+), 273 deletions(-) (limited to 'pjnath') diff --git a/pjnath/include/pjnath/errno.h b/pjnath/include/pjnath/errno.h index cbefee85..9e1881f1 100644 --- a/pjnath/include/pjnath/errno.h +++ b/pjnath/include/pjnath/errno.h @@ -45,6 +45,7 @@ #define PJNATH_ESTUNINMSGTYPE -1 #define PJNATH_ESTUNFINGERPRINT -1 #define PJNATH_ESTUNNOTRESPOND -1 +#define PJNATH_ESTUNNOXORMAP -1 /** * @hideinitializer diff --git a/pjnath/include/pjnath/ice.h b/pjnath/include/pjnath/ice.h index f42a793b..9f86894f 100644 --- a/pjnath/include/pjnath/ice.h +++ b/pjnath/include/pjnath/ice.h @@ -27,6 +27,8 @@ #include #include #include +#include + PJ_BEGIN_DECL @@ -67,14 +69,17 @@ typedef struct pj_ice pj_ice; #define PJ_ICE_MAX_CAND 16 #define PJ_ICE_MAX_COMP 8 #define PJ_ICE_MAX_CHECKS 32 +#define PJ_ICE_TA_VAL 20 /** * ICE component */ typedef struct pj_ice_comp { - unsigned comp_id; - pj_sock_t sock; + unsigned comp_id; + pj_sock_t sock; + pj_stun_session *stun_sess; + pj_sockaddr local_addr; } pj_ice_comp; @@ -104,18 +109,13 @@ typedef enum pj_ice_check_state typedef struct pj_ice_check { - unsigned cand_id; - pj_uint32_t comp_id; - pj_str_t foundation; - - pj_uint64_t check_prio; - pj_ice_check_state check_state; - - pj_ice_cand_type rem_type; - pj_str_t rem_foundation; - pj_uint32_t rem_prio; - pj_sockaddr rem_addr; - pj_sockaddr rem_base_addr; + pj_ice_cand *lcand; + pj_ice_cand *rcand; + + pj_uint64_t prio; + pj_ice_check_state state; + pj_bool_t nominated; + pj_status_t err_code; } pj_ice_check; @@ -131,6 +131,7 @@ typedef struct pj_ice_checklist pj_ice_checklist_state state; unsigned count; pj_ice_check checks[PJ_ICE_MAX_CHECKS]; + pj_timer_entry timer; } pj_ice_checklist; @@ -175,9 +176,10 @@ struct pj_ice int sock_type; pj_ice_role role; pj_ice_state state; - pj_ice_cb cb; + pj_stun_config stun_cfg; + /* STUN credentials */ pj_str_t tx_uname; pj_str_t tx_pass; @@ -205,14 +207,10 @@ struct pj_ice pj_dns_async_query *resv_q; pj_bool_t relay_enabled; pj_sockaddr stun_srv; - - /* STUN sessions */ - pj_stun_session *tx_sess; - pj_stun_session *rx_sess; }; -PJ_DECL(pj_status_t) pj_ice_create(pj_stun_config *cfg, +PJ_DECL(pj_status_t) pj_ice_create(pj_stun_config *stun_cfg, const char *name, pj_ice_role role, const pj_ice_cb *cb, @@ -256,17 +254,16 @@ PJ_DECL(pj_status_t) pj_ice_add_cand(pj_ice *ice, PJ_DECL(unsigned) pj_ice_get_cand_cnt(pj_ice *ice); PJ_DECL(pj_status_t) pj_ice_enum_cands(pj_ice *ice, - unsigned sort_by, unsigned *p_count, unsigned cand_ids[]); -PJ_DECL(unsigned) pj_ice_get_default_cand(pj_ice *ice, - int *cand_id); +PJ_DECL(pj_status_t) pj_ice_get_default_cand(pj_ice *ice, + unsigned comp_id, + int *cand_id); PJ_DECL(pj_status_t) pj_ice_get_cand(pj_ice *ice, unsigned cand_id, pj_ice_cand **p_cand); PJ_DECL(pj_status_t) pj_ice_create_check_list(pj_ice *ice, - pj_bool_t is_remote_offer, unsigned rem_cand_cnt, const pj_ice_cand rem_cand[]); diff --git a/pjnath/include/pjnath/stun_auth.h b/pjnath/include/pjnath/stun_auth.h index e30ebc95..1a9f6a0c 100644 --- a/pjnath/include/pjnath/stun_auth.h +++ b/pjnath/include/pjnath/stun_auth.h @@ -154,10 +154,46 @@ typedef struct pj_stun_auth_cred pj_str_t *realm, pj_str_t *nonce); + /** + * Get the credential to be put in outgoing message. + * + * @param msg The outgoing message where the credential is + * to be applied. + * @param user_data The user data as specified in the credential. + * @param pool Pool where the callback can allocate memory + * to fill in the credential. + * @param realm On return, the callback may specify the realm + * if long term credential is desired, otherwise + * this string must be set to empty. + * @param username On return, the callback must fill in with the + * username. + * @param nonce On return, the callback may optionally fill in + * this argument with NONCE value if desired, + * otherwise this argument must be set to empty. + * @param data_type On return, the callback must set this argument + * with the type of password in the data argument. + * @param data On return, the callback must set this with + * the password, encoded according to data_type + * argument. + * + * @return The callback must return PJ_SUCCESS, otherwise + * the message transmission will be cancelled. + */ + pj_status_t (*get_cred)(const pj_stun_msg *msg, + void *user_data, + pj_pool_t *pool, + pj_str_t *realm, + pj_str_t *username, + pj_str_t *nonce, + int *data_type, + pj_str_t *data); + /** * Get the password for the specified username. This function * is also used to check whether the username is valid. * + * @param msg The STUN message where the password will be + * applied to. * @param user_data The user data as specified in the credential. * @param realm The realm as specified in the message. * @param username The username as specified in the message. @@ -175,7 +211,8 @@ typedef struct pj_stun_auth_cred * is returned, it is assumed that the * username is not valid. */ - pj_status_t (*get_password)(void *user_data, + pj_status_t (*get_password)(const pj_stun_msg *msg, + void *user_data, const pj_str_t *realm, const pj_str_t *username, pj_pool_t *pool, @@ -187,6 +224,7 @@ typedef struct pj_stun_auth_cred * in the message can be accepted. If this callback returns * PJ_FALSE, 438 (Stale Nonce) response will be created. * + * @param msg The STUN message where the nonce was received. * @param user_data The user data as specified in the credential. * @param realm The realm as specified in the message. * @param username The username as specified in the message. @@ -195,7 +233,8 @@ typedef struct pj_stun_auth_cred * @return The callback MUST return non-zero if the * NONCE can be accepted. */ - pj_bool_t (*verify_nonce)(void *user_data, + pj_bool_t (*verify_nonce)(const pj_stun_msg *msg, + void *user_data, const pj_str_t *realm, const pj_str_t *username, const pj_str_t *nonce); diff --git a/pjnath/src/pjnath/ice.c b/pjnath/src/pjnath/ice.c index eb2ead7f..7273399b 100644 --- a/pjnath/src/pjnath/ice.c +++ b/pjnath/src/pjnath/ice.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -26,6 +27,17 @@ #include +/* String names for candidate types */ +static const char *cand_type_names[] = +{ + "Host", + "Server Reflexive", + "Peer Reflexive", + "Relayed" + +}; + +/* String names for pj_ice_check_state */ static const char *check_state_name[] = { "Frozen", @@ -35,11 +47,27 @@ static const char *check_state_name[] = "Failed" }; +static const char *clist_state_name[] = +{ + "Idle", + "Running", + "Completed" +}; + +const pj_str_t host_foundation = {"host", 4}; +const pj_str_t mapped_foundation = {"srfx", 4}; +const pj_str_t relayed_foundation = {"rlyd", 4}; +const pj_str_t peer_mapped_foundation = {"peer", 4}; + +#define CHECK_NAME_LEN 128 +#define LOG(expr) PJ_LOG(4,expr) + static void destroy_ice(pj_ice *ice, pj_status_t reason); static void ice_set_state(pj_ice *ice, pj_ice_state new_state); - +static pj_status_t start_periodic_check(pj_timer_heap_t *th, + pj_timer_entry *te); static pj_status_t on_stun_send_msg(pj_stun_session *sess, const void *pkt, pj_size_t pkt_size, @@ -51,7 +79,7 @@ static pj_status_t on_stun_rx_request(pj_stun_session *sess, const pj_stun_msg *msg, const pj_sockaddr_t *src_addr, unsigned src_addr_len); -static void on_stun_request_complete(pj_stun_session *sess, +static void on_stun_request_complete(pj_stun_session *stun_sess, pj_status_t status, pj_stun_tx_data *tdata, const pj_stun_msg *response); @@ -66,19 +94,29 @@ static pj_status_t stun_auth_get_auth(void *user_data, pj_pool_t *pool, pj_str_t *realm, pj_str_t *nonce); -static pj_status_t stun_auth_get_password(void *user_data, +static pj_status_t stun_auth_get_cred(const pj_stun_msg *msg, + void *user_data, + pj_pool_t *pool, + pj_str_t *realm, + pj_str_t *username, + pj_str_t *nonce, + int *data_type, + pj_str_t *data); +static pj_status_t stun_auth_get_password(const pj_stun_msg *msg, + void *user_data, const pj_str_t *realm, const pj_str_t *username, pj_pool_t *pool, int *data_type, pj_str_t *data); -static pj_bool_t stun_auth_verify_nonce(void *user_data, +static pj_bool_t stun_auth_verify_nonce(const pj_stun_msg *msg, + void *user_data, const pj_str_t *realm, const pj_str_t *username, const pj_str_t *nonce); -PJ_DEF(pj_status_t) pj_ice_create(pj_stun_config *cfg, +PJ_DEF(pj_status_t) pj_ice_create(pj_stun_config *stun_cfg, const char *name, pj_ice_role role, const pj_ice_cb *cb, @@ -88,18 +126,16 @@ PJ_DEF(pj_status_t) pj_ice_create(pj_stun_config *cfg, { pj_pool_t *pool; pj_ice *ice; - pj_stun_session_cb sess_cb; - pj_stun_auth_cred auth_cred; pj_status_t status; - PJ_ASSERT_RETURN(cfg && cb && p_ice, PJ_EINVAL); + PJ_ASSERT_RETURN(stun_cfg && cb && p_ice, PJ_EINVAL); PJ_ASSERT_RETURN(sock_type==PJ_SOCK_DGRAM || sock_type==PJ_SOCK_STREAM, PJ_EINVAL); if (!name) name = "ice%p"; - pool = pj_pool_create(cfg->pf, name, 4000, 4000, NULL); + pool = pj_pool_create(stun_cfg->pf, name, 4000, 4000, NULL); ice = PJ_POOL_ZALLOC_T(pool, pj_ice); ice->pool = pool; ice->af = af; @@ -117,49 +153,13 @@ PJ_DEF(pj_status_t) pj_ice_create(pj_stun_config *cfg, } pj_memcpy(&ice->cb, cb, sizeof(*cb)); + pj_memcpy(&ice->stun_cfg, stun_cfg, sizeof(*stun_cfg)); - /* Init STUN callbacks */ - pj_bzero(&sess_cb, sizeof(sess_cb)); - sess_cb.on_request_complete = &on_stun_request_complete; - sess_cb.on_rx_indication = &on_stun_rx_indication; - sess_cb.on_rx_request = &on_stun_rx_request; - sess_cb.on_send_msg = &on_stun_send_msg; - - /* Init STUN authentication credential */ - pj_bzero(&auth_cred, sizeof(auth_cred)); - auth_cred.type = PJ_STUN_AUTH_CRED_DYNAMIC; - auth_cred.data.dyn_cred.get_auth = &stun_auth_get_auth; - auth_cred.data.dyn_cred.get_password = &stun_auth_get_password; - auth_cred.data.dyn_cred.verify_nonce = &stun_auth_verify_nonce; - - /* Create STUN session for outgoing requests */ - status = pj_stun_session_create(cfg, ice->obj_name, &sess_cb, PJ_FALSE, - &ice->tx_sess); - if (status != PJ_SUCCESS) { - destroy_ice(ice, status); - return status; - } - - pj_stun_session_set_user_data(ice->tx_sess, ice); - auth_cred.data.dyn_cred.user_data = ice->tx_sess; - pj_stun_session_set_credential(ice->tx_sess, &auth_cred); - - /* Create STUN session for incoming requests */ - status = pj_stun_session_create(cfg, ice->obj_name, &sess_cb, PJ_FALSE, - &ice->rx_sess); - if (status != PJ_SUCCESS) { - destroy_ice(ice, status); - return status; - } - - pj_stun_session_set_user_data(ice->rx_sess, ice); - auth_cred.data.dyn_cred.user_data = ice->rx_sess; - pj_stun_session_set_credential(ice->rx_sess, &auth_cred); /* Done */ *p_ice = ice; - PJ_LOG(4,(ice->obj_name, "ICE session created")); + LOG((ice->obj_name, "ICE session created")); return PJ_SUCCESS; } @@ -169,7 +169,7 @@ static void destroy_ice(pj_ice *ice, pj_status_t reason) { if (reason == PJ_SUCCESS) { - PJ_LOG(4,(ice->obj_name, "Destroying ICE session")); + LOG((ice->obj_name, "Destroying ICE session")); } if (ice->resv_q) { @@ -317,6 +317,7 @@ PJ_DEF(pj_status_t) pj_ice_set_srv_addr(pj_ice *ice, } + PJ_DEF(pj_status_t) pj_ice_add_comp(pj_ice *ice, unsigned comp_id, const pj_sockaddr_t *local_addr, @@ -344,18 +345,77 @@ PJ_DEF(pj_status_t) pj_ice_add_comp(pj_ice *ice, return PJ_SUCCESS; } +typedef struct stun_data +{ + pj_ice *ice; + unsigned comp_id; + pj_ice_comp *comp; +} stun_data; + PJ_DEF(pj_status_t) pj_ice_add_sock_comp( pj_ice *ice, unsigned comp_id, pj_sock_t sock) { + pj_stun_session_cb sess_cb; + pj_ice_comp *comp; + pj_stun_auth_cred auth_cred; + stun_data *sd; + int addr_len; + pj_status_t status; + PJ_ASSERT_RETURN(ice && sock != PJ_INVALID_SOCKET, PJ_EINVAL); PJ_ASSERT_RETURN(ice->comp_cnt < PJ_ARRAY_SIZE(ice->comp), PJ_ETOOMANY); pj_mutex_lock(ice->mutex); - ice->comp[ice->comp_cnt].comp_id = comp_id; - ice->comp[ice->comp_cnt].sock = sock; - ++ice->comp_cnt; + + comp = &ice->comp[ice->comp_cnt]; + comp->comp_id = comp_id; + comp->sock = sock; + + addr_len = sizeof(comp->local_addr); + status = pj_sock_getsockname(sock, &comp->local_addr, &addr_len); + if (status != PJ_SUCCESS) { + pj_mutex_unlock(ice->mutex); + return status; + } + + + /* Init STUN callbacks */ + pj_bzero(&sess_cb, sizeof(sess_cb)); + sess_cb.on_request_complete = &on_stun_request_complete; + sess_cb.on_rx_indication = &on_stun_rx_indication; + sess_cb.on_rx_request = &on_stun_rx_request; + sess_cb.on_send_msg = &on_stun_send_msg; + + /* Create STUN session for this component */ + status = pj_stun_session_create(&ice->stun_cfg, ice->obj_name, + &sess_cb, PJ_FALSE, + &comp->stun_sess); + if (status != PJ_SUCCESS) { + pj_mutex_unlock(ice->mutex); + return status; + } + + /* Associate data with this STUN session */ + sd = PJ_POOL_ZALLOC_T(ice->pool, struct stun_data); + sd->ice = ice; + sd->comp_id = comp_id; + sd->comp = comp; + pj_stun_session_set_user_data(comp->stun_sess, sd); + + /* Init STUN authentication credential */ + pj_bzero(&auth_cred, sizeof(auth_cred)); + auth_cred.type = PJ_STUN_AUTH_CRED_DYNAMIC; + auth_cred.data.dyn_cred.get_auth = &stun_auth_get_auth; + auth_cred.data.dyn_cred.get_cred = &stun_auth_get_cred; + auth_cred.data.dyn_cred.get_password = &stun_auth_get_password; + auth_cred.data.dyn_cred.verify_nonce = &stun_auth_verify_nonce; + auth_cred.data.dyn_cred.user_data = comp->stun_sess; + pj_stun_session_set_credential(comp->stun_sess, &auth_cred); + + /* Done */ + ice->comp_cnt++; pj_mutex_unlock(ice->mutex); return PJ_SUCCESS; @@ -377,7 +437,45 @@ static pj_status_t stun_auth_get_auth(void *user_data, } -static pj_status_t stun_auth_get_password(void *user_data, +/* Get credential to be sent with outgoing message */ +static pj_status_t stun_auth_get_cred(const pj_stun_msg *msg, + void *user_data, + pj_pool_t *pool, + pj_str_t *realm, + pj_str_t *username, + pj_str_t *nonce, + int *data_type, + pj_str_t *data) +{ + pj_stun_session *sess = (pj_stun_session *)user_data; + stun_data *sd = (stun_data*) pj_stun_session_get_user_data(sess); + pj_ice *ice = sd->ice; + + PJ_UNUSED_ARG(pool); + realm->slen = nonce->slen = 0; + + if (PJ_STUN_IS_RESPONSE(msg->hdr.type) || + PJ_STUN_IS_ERROR_RESPONSE(msg->hdr.type)) + { + /* Outgoing responses need to have the same credential as + * incoming requests. + */ + *username = ice->rx_uname; + *data_type = 0; + *data = ice->rx_pass; + } + else { + *username = ice->tx_uname; + *data_type = 0; + *data = ice->tx_pass; + } + + return PJ_SUCCESS; +} + +/* Get password to be used to authenticate incoming message */ +static pj_status_t stun_auth_get_password(const pj_stun_msg *msg, + void *user_data, const pj_str_t *realm, const pj_str_t *username, pj_pool_t *pool, @@ -385,18 +483,24 @@ static pj_status_t stun_auth_get_password(void *user_data, pj_str_t *data) { pj_stun_session *sess = (pj_stun_session *)user_data; - pj_ice *ice = (pj_ice*) pj_stun_session_get_user_data(sess); + stun_data *sd = (stun_data*) pj_stun_session_get_user_data(sess); + pj_ice *ice = sd->ice; PJ_UNUSED_ARG(realm); PJ_UNUSED_ARG(pool); - if (sess == ice->tx_sess) { + if (PJ_STUN_IS_RESPONSE(msg->hdr.type) || + PJ_STUN_IS_ERROR_RESPONSE(msg->hdr.type)) + { + /* Incoming response is authenticated with TX credential */ /* Verify username */ if (pj_strcmp(username, &ice->tx_uname) != 0) return -1; *data_type = 0; *data = ice->tx_pass; + } else { + /* Incoming request is authenticated with RX credential */ /* The agent MUST accept a credential if the username consists * of two values separated by a colon, where the first value is * equal to the username fragment generated by the agent in an offer @@ -407,18 +511,21 @@ static pj_status_t stun_auth_get_password(void *user_data, PJ_TODO(CHECK_USERNAME_FOR_INCOMING_STUN_REQUEST); *data_type = 0; *data = ice->rx_pass; + } return PJ_SUCCESS; } -static pj_bool_t stun_auth_verify_nonce(void *user_data, +static pj_bool_t stun_auth_verify_nonce(const pj_stun_msg *msg, + void *user_data, const pj_str_t *realm, const pj_str_t *username, const pj_str_t *nonce) { /* We don't use NONCE */ + PJ_UNUSED_ARG(msg); PJ_UNUSED_ARG(user_data); PJ_UNUSED_ARG(realm); PJ_UNUSED_ARG(username); @@ -483,7 +590,7 @@ static pj_status_t gather_host_cands(pj_ice *ice) } status = pj_ice_add_cand(ice, i, PJ_ICE_CAND_TYPE_HOST, 65535, - NULL, &addr, &addr, NULL, + &host_foundation, &addr, &addr, NULL, sizeof(pj_sockaddr_in), NULL); if (status != PJ_SUCCESS) return status; @@ -492,18 +599,22 @@ static pj_status_t gather_host_cands(pj_ice *ice) return PJ_SUCCESS; } -static pj_status_t gather_mapped_cands(pj_ice *ice) +/* Eliminate redundant candidates. */ +static void eliminate_redundant_cand(unsigned *cnt, + pj_ice_cand cand[]) { - PJ_UNUSED_ARG(ice); - return PJ_ENOTSUP; -} -static pj_status_t gather_relayed_cands(pj_ice *ice) -{ - PJ_UNUSED_ARG(ice); - return PJ_ENOTSUP; + /* A candidate is redundant if its transport address equals another + * candidate, and its base equals the base of that other candidate. + * Note that two candidates can have the same transport address yet + * have different bases, and these would not be considered redundant. + */ + PJ_TODO(ELIMINATE_REDUNDANT_CANDIDATES); + PJ_UNUSED_ARG(cnt); + PJ_UNUSED_ARG(cand); } + PJ_DEF(pj_status_t) pj_ice_start_gather(pj_ice *ice, unsigned flags) { @@ -516,6 +627,9 @@ PJ_DEF(pj_status_t) pj_ice_start_gather(pj_ice *ice, if (status != PJ_SUCCESS) return status; + /* Eliminate redundant host candidates. */ + eliminate_redundant_cand(&ice->lcand_cnt, ice->lcand); + PJ_TODO(GATHER_MAPPED_AND_RELAYED_CANDIDATES); ice_set_state(ice, PJ_ICE_STATE_CAND_COMPLETE); @@ -555,6 +669,11 @@ PJ_DEF(pj_status_t) pj_ice_add_cand(pj_ice *ice, { pj_ice_cand *lcand; pj_status_t status = PJ_SUCCESS; + char tmp[128]; + + PJ_ASSERT_RETURN(ice && comp_id && type && local_pref && + foundation && addr && base_addr && addr_len, + PJ_EINVAL); pj_mutex_lock(ice->mutex); @@ -578,6 +697,21 @@ PJ_DEF(pj_status_t) pj_ice_add_cand(pj_ice *ice, if (p_cand_id) *p_cand_id = ice->lcand_cnt; + pj_ansi_strcpy(tmp, pj_inet_ntoa(lcand->addr.ipv4.sin_addr)); + LOG((ice->obj_name, + "Candidate %d added: comp_id=%d, type=%s, foundation=%.*s, " + "addr=%s:%d, base=%s:%d, prio=0x%x (%u)", + ice->lcand_cnt, + lcand->comp_id, + cand_type_names[lcand->type], + (int)lcand->foundation.slen, + lcand->foundation.ptr, + tmp, + (int)pj_ntohs(lcand->addr.ipv4.sin_port), + pj_inet_ntoa(lcand->base_addr.ipv4.sin_addr), + (int)pj_htons(lcand->base_addr.ipv4.sin_port), + lcand->prio, lcand->prio)); + ++ice->lcand_cnt; on_error: @@ -593,13 +727,12 @@ PJ_DEF(unsigned) pj_ice_get_cand_cnt(pj_ice *ice) PJ_DEF(pj_status_t) pj_ice_enum_cands(pj_ice *ice, - unsigned sort_by, unsigned *p_count, unsigned cand_ids[]) { unsigned i, count; - PJ_UNUSED_ARG(sort_by); + PJ_ASSERT_RETURN(ice && p_count && *p_count && cand_ids, PJ_EINVAL); pj_mutex_lock(ice->mutex); @@ -614,6 +747,31 @@ PJ_DEF(pj_status_t) pj_ice_enum_cands(pj_ice *ice, } +PJ_DEF(pj_status_t) pj_ice_get_default_cand(pj_ice *ice, + unsigned comp_id, + int *cand_id) +{ + PJ_ASSERT_RETURN(ice && comp_id && cand_id, PJ_EINVAL); + + pj_mutex_lock(ice->mutex); + + /* First find in valid list if we have nominated pair */ + + /* If there's no nominated pair, find relayed candidate */ + + /* If there's no relayed candidate, find server reflexive candidate */ + + /* Otherwise return host candidate */ + + pj_assert(!"Not implemented yet"); + PJ_TODO(IMPLEMENT_GET_DEFAULT_CAND); + + pj_mutex_unlock(ice->mutex); + + return PJ_SUCCESS; +} + + PJ_DEF(pj_status_t) pj_ice_get_cand(pj_ice *ice, unsigned cand_id, pj_ice_cand **p_cand) @@ -634,8 +792,20 @@ PJ_DEF(pj_status_t) pj_ice_get_cand(pj_ice *ice, # define MAX(a,b) (a > b ? a : b) #endif -static pj_uint64_t CALC_CHECK_PRIO(pj_uint32_t O, pj_uint32_t A) +static pj_uint64_t CALC_CHECK_PRIO(const pj_ice *ice, + const pj_ice_cand *lcand, + const pj_ice_cand *rcand) { + pj_uint32_t O, A; + + if (ice->role == PJ_ICE_ROLE_CONTROLLING) { + O = lcand->prio; + A = rcand->prio; + } else { + O = rcand->prio; + A = lcand->prio; + } + return ((pj_uint64_t)1 << 32) * MIN(O, A) + (pj_uint64_t)2 * MAX(O, A) + (O>A ? 1 : 0); } @@ -644,18 +814,24 @@ static const char *dump_check(char *buffer, unsigned bufsize, const pj_ice *ice, const pj_ice_check *check) { - char local_addr[128]; + const pj_ice_cand *lcand = check->lcand; + const pj_ice_cand *rcand = check->rcand; + char laddr[CHECK_NAME_LEN]; int len; - pj_ansi_strcpy(local_addr, - pj_inet_ntoa(ice->lcand[check->cand_id].addr.ipv4.sin_addr)); + pj_ansi_strcpy(laddr, pj_inet_ntoa(lcand->addr.ipv4.sin_addr)); + + if (lcand->addr.addr.sa_family == PJ_AF_INET) { + len = pj_ansi_snprintf(buffer, bufsize, + "%s:%d-->%s:%d", + laddr, (int)pj_ntohs(lcand->addr.ipv4.sin_port), + pj_inet_ntoa(rcand->addr.ipv4.sin_addr), + (int)pj_ntohs(rcand->addr.ipv4.sin_port)); + } else { + len = pj_ansi_snprintf(buffer, bufsize, "IPv6->IPv6"); + } + - len = pj_ansi_snprintf(buffer, bufsize, - "%s:%d-->%s:%d", - local_addr, - (int)pj_ntohs(ice->lcand[check->cand_id].addr.ipv4.sin_port), - pj_inet_ntoa(check->rem_addr.ipv4.sin_addr), - (int)pj_ntohs(check->rem_addr.ipv4.sin_port)); if (len < 0) len = 0; else if (len >= (int)bufsize) @@ -670,20 +846,42 @@ static void dump_checklist(const char *title, const pj_ice *ice, const pj_ice_checklist *clist) { unsigned i; - char buffer[128]; + char buffer[CHECK_NAME_LEN]; - PJ_LOG(4,(ice->obj_name, "%s", title)); + LOG((ice->obj_name, "%s", title)); for (i=0; icount; ++i) { const pj_ice_check *c = &clist->checks[i]; - PJ_LOG(4,(ice->obj_name, " %d: %s (prio=%u, state=%s)", - i, dump_check(buffer, sizeof(buffer), ice, c), - c->check_prio, check_state_name[c->check_state])); + LOG((ice->obj_name, " %d: %s (prio=%u, state=%s)", + i, dump_check(buffer, sizeof(buffer), ice, c), + c->prio, check_state_name[c->state])); } } #else #define dump_checklist(ice, clist) #endif +static void check_set_state(pj_ice *ice, pj_ice_check *check, + pj_ice_check_state st, + pj_status_t err_code) +{ + char buf[CHECK_NAME_LEN]; + LOG((ice->obj_name, "Check %s: state changed from %s to %s", + dump_check(buf, sizeof(buf), ice, check), + check_state_name[check->state], + check_state_name[st])); + check->state = st; + check->err_code = err_code; +} + +static void clist_set_state(pj_ice *ice, pj_ice_checklist *clist, + pj_ice_checklist_state st) +{ + LOG((ice->obj_name, "Checklist: state changed from %s to %s", + clist_state_name[clist->state], + clist_state_name[st])); + clist->state = st; +} + static void sort_checklist(pj_ice_checklist *clist) { unsigned i; @@ -691,9 +889,7 @@ static void sort_checklist(pj_ice_checklist *clist) for (i=0; icount-1; ++i) { unsigned j, highest = i; for (j=i+1; jcount; ++j) { - if (clist->checks[j].check_prio > - clist->checks[highest].check_prio) - { + if (clist->checks[j].prio > clist->checks[highest].prio) { highest = j; } } @@ -709,63 +905,151 @@ static void sort_checklist(pj_ice_checklist *clist) } } + +enum +{ + SOCKADDR_EQUAL = 0, + SOCKADDR_NOT_EQUAL = 1 +}; + +/* Utility: compare sockaddr. + * Returns 0 if equal. + */ +static int sockaddr_cmp(const pj_sockaddr *a1, const pj_sockaddr *a2) +{ + if (a1->addr.sa_family != a2->addr.sa_family) + return SOCKADDR_NOT_EQUAL; + + if (a1->addr.sa_family == PJ_AF_INET) { + return !(a1->ipv4.sin_addr.s_addr == a2->ipv4.sin_addr.s_addr && + a1->ipv4.sin_port == a2->ipv4.sin_port); + } else if (a1->addr.sa_family == PJ_AF_INET6) { + return pj_memcmp(&a1->ipv6, &a2->ipv6, sizeof(a1->ipv6)); + } else { + pj_assert(!"Invalid address family!"); + return SOCKADDR_NOT_EQUAL; + } +} + + +/* Prune checklist, this must have been done after the checklist + * is sorted. + */ +static void prune_checklist(pj_ice *ice, pj_ice_checklist *clist) +{ + unsigned i; + + /* Since an agent cannot send requests directly from a reflexive + * candidate, but only from its base, the agent next goes through the + * sorted list of candidate pairs. For each pair where the local + * candidate is server reflexive, the server reflexive candidate MUST be + * replaced by its base. Once this has been done, the agent MUST prune + * the list. This is done by removing a pair if its local and remote + * candidates are identical to the local and remote candidates of a pair + * higher up on the priority list. The result is a sequence of ordered + * candidate pairs, called the check list for that media stream. + */ + for (i=0; icount; ++i) { + pj_ice_cand *licand = clist->checks[i].lcand; + pj_ice_cand *ricand = clist->checks[i].rcand; + const pj_sockaddr *liaddr; + unsigned j; + + if (licand->type == PJ_ICE_CAND_TYPE_MAPPED) + liaddr = &licand->base_addr; + else + liaddr = &licand->addr; + + for (j=i+1; jcount;) { + pj_ice_cand *ljcand = clist->checks[j].lcand; + pj_ice_cand *rjcand = clist->checks[j].rcand; + const pj_sockaddr *ljaddr; + + if (ljcand->type == PJ_ICE_CAND_TYPE_MAPPED) + ljaddr = &licand->base_addr; + else + ljaddr = &licand->addr; + + if (sockaddr_cmp(liaddr, ljaddr) == SOCKADDR_EQUAL && + sockaddr_cmp(&ricand->addr, &rjcand->addr) == SOCKADDR_EQUAL) + { + /* Found duplicate, remove it */ + char buf[CHECK_NAME_LEN]; + + LOG((ice->obj_name, "Check %s pruned", + dump_check(buf, sizeof(buf), ice, &clist->checks[j]))); + + pj_array_erase(clist->checks, sizeof(clist->checks[0]), + clist->count, j); + --clist->count; + + } else { + ++j; + } + } + } +} + +typedef struct timer_data +{ + pj_ice *ice; + pj_ice_checklist *clist; +} timer_data; + PJ_DEF(pj_status_t) pj_ice_create_check_list(pj_ice *ice, - pj_bool_t is_remote_offer, - unsigned rem_cand_cnt, - const pj_ice_cand rem_cand[]) + unsigned rcand_cnt, + const pj_ice_cand rcand[]) { pj_ice_checklist *clist; + timer_data *td; unsigned i, j; - PJ_ASSERT_RETURN(ice && rem_cand_cnt && rem_cand, PJ_EINVAL); - PJ_ASSERT_RETURN(rem_cand_cnt < PJ_ICE_MAX_CAND, PJ_ETOOMANY); + PJ_ASSERT_RETURN(ice && rcand_cnt && rcand, PJ_EINVAL); + PJ_ASSERT_RETURN(rcand_cnt + ice->rcand_cnt <= PJ_ICE_MAX_CAND, + PJ_ETOOMANY); pj_mutex_lock(ice->mutex); /* Save remote candidates */ ice->rcand_cnt = 0; - for (i=0; ircand[ice->rcand_cnt++]; - pj_memcpy(cn, &rem_cand[i], sizeof(pj_ice_cand)); - pj_strdup(ice->pool, &cn->foundation, &rem_cand[i].foundation); + pj_memcpy(cn, &rcand[i], sizeof(pj_ice_cand)); + pj_strdup(ice->pool, &cn->foundation, &rcand[i].foundation); } /* Generate checklist */ clist = &ice->cklist; for (i=0; ilcand_cnt; ++i) { - for (j=0; jrcand_cnt; ++j) { - pj_ice_check *c = &clist->checks[clist->count++]; + pj_ice_cand *lcand = &ice->lcand[i]; + pj_ice_cand *rcand = &ice->rcand[j]; + pj_ice_check *chk = &clist->checks[clist->count]; + + if (clist->count > PJ_ICE_MAX_CHECKS) { + pj_mutex_unlock(ice->mutex); + return PJ_ETOOMANY; + } else { + clist->count++; + } /* A local candidate is paired with a remote candidate if * and only if the two candidates have the same component ID * and have the same IP address version. */ - if (ice->lcand[i].comp_id != rem_cand[j].comp_id || - pj_strcmp(&ice->lcand[i].foundation,&rem_cand[j].foundation)==0) + if (lcand->comp_id != rcand->comp_id || + lcand->addr.addr.sa_family != rcand->addr.addr.sa_family) { continue; } - c->cand_id = i; - c->comp_id = ice->lcand[i].comp_id; - c->foundation = ice->lcand[i].foundation; - if (is_remote_offer) { - c->check_prio = CALC_CHECK_PRIO(rem_cand[j].prio, - ice->lcand[i].prio); - } else { - c->check_prio = CALC_CHECK_PRIO(ice->lcand[i].prio, - rem_cand[j].prio); - } + chk->lcand = lcand; + chk->rcand = rcand; + chk->state = PJ_ICE_CHECK_STATE_FROZEN; - c->rem_type = rem_cand[j].type; - pj_strdup(ice->pool, &c->rem_foundation, &rem_cand[j].foundation); - c->rem_prio = rem_cand[j].prio; - pj_memcpy(&c->rem_addr, &rem_cand[j].addr, - sizeof(rem_cand[j].addr)); - pj_memcpy(&c->rem_base_addr, &rem_cand[j].base_addr, - sizeof(rem_cand[j].addr)); + chk->prio = CALC_CHECK_PRIO(ice, lcand, rcand); } } @@ -773,9 +1057,18 @@ PJ_DEF(pj_status_t) pj_ice_create_check_list(pj_ice *ice, sort_checklist(clist); /* Prune the checklist */ - for (i=0; icount; ++i) { - PJ_TODO(PRUNE_CHECKLIST); - } + prune_checklist(ice, clist); + + /* Init timer entry in the checklist. Initially the timer ID is FALSE + * because timer is not running. + */ + clist->timer.id = PJ_FALSE; + td = PJ_POOL_ZALLOC_T(ice->pool, timer_data); + td->ice = ice; + td->clist = clist; + clist->timer.user_data = (void*)td; + clist->timer.cb = &start_periodic_check; + /* Log checklist */ dump_checklist("Checklist created:", ice, clist); @@ -798,20 +1091,26 @@ static pj_status_t perform_check(pj_ice *ice, pj_ice_checklist *clist, unsigned check_id) { pj_stun_tx_data *tdata; + pj_ice_comp *comp; struct req_data *rd; pj_ice_check *check; + const pj_ice_cand *lcand; + const pj_ice_cand *rcand; pj_uint32_t prio; char buffer[128]; pj_status_t status; check = &clist->checks[check_id]; + lcand = check->lcand; + rcand = check->rcand; + comp = &ice->comp[lcand->comp_id]; - PJ_LOG(5,(ice->obj_name, - "Sending connectivity check for check %d: %s", - check_id, dump_check(buffer, sizeof(buffer), ice, check))); + LOG((ice->obj_name, + "Sending connectivity check for check %d: %s", + check_id, dump_check(buffer, sizeof(buffer), ice, check))); /* Create request */ - status = pj_stun_session_create_req(ice->tx_sess, + status = pj_stun_session_create_req(comp->stun_sess, PJ_STUN_BINDING_REQUEST, &tdata); if (status != PJ_SUCCESS) return status; @@ -826,7 +1125,8 @@ static pj_status_t perform_check(pj_ice *ice, pj_ice_checklist *clist, tdata->user_data = (void*) rd; /* Add PRIORITY */ - prio = CALC_CAND_PRIO(check->rem_type, 65535, check->comp_id); + prio = CALC_CAND_PRIO(PJ_ICE_CAND_TYPE_PEER_MAPPED, 65535, + lcand->comp_id); pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg, PJ_STUN_ATTR_PRIORITY, prio); @@ -836,42 +1136,63 @@ static pj_status_t perform_check(pj_ice *ice, pj_ice_checklist *clist, PJ_STUN_ATTR_USE_CANDIDATE); } + /* Note that USERNAME and MESSAGE-INTEGRITY will be added by the + * STUN session. + */ + /* Initiate STUN transaction to send the request */ - status = pj_stun_session_send_msg(ice->tx_sess, PJ_FALSE, - &check->rem_addr, + status = pj_stun_session_send_msg(comp->stun_sess, PJ_FALSE, + &rcand->addr, sizeof(pj_sockaddr_in), tdata); if (status != PJ_SUCCESS) return status; - check->check_state = PJ_ICE_CHECK_STATE_IN_PROGRESS; + check_set_state(ice, check, PJ_ICE_CHECK_STATE_IN_PROGRESS, PJ_SUCCESS); return PJ_SUCCESS; } -/* Start periodic check for the specified checklist */ -static pj_status_t start_periodic_check(pj_ice *ice, pj_ice_checklist *clist) + +/* Start periodic check for the specified checklist. + * This callback is called by timer on every Ta (20msec by default) + */ +static pj_status_t start_periodic_check(pj_timer_heap_t *th, + pj_timer_entry *te) { + timer_data *td; + pj_ice *ice; + pj_ice_checklist *clist; unsigned i, start_count=0; pj_status_t status; - /* Checklist state must be idle or completed */ - pj_assert(clist->state == PJ_ICE_CHECKLIST_ST_IDLE || - clist->state == PJ_ICE_CHECKLIST_ST_COMPLETED); + td = (struct timer_data*) te->user_data; + ice = td->ice; + clist = td->clist; + + pj_mutex_lock(ice->mutex); + + /* Set timer ID to FALSE first */ + te->id = PJ_FALSE; /* Set checklist state to Running */ - clist->state = PJ_ICE_CHECKLIST_ST_RUNNING; + clist_set_state(ice, clist, PJ_ICE_CHECKLIST_ST_RUNNING); - PJ_LOG(4,(ice->obj_name, "Starting checklist periodic check")); + LOG((ice->obj_name, "Starting checklist periodic check")); - /* Send STUN Binding request for checks in Waiting list */ + /* Send STUN Binding request for check with highest priority on + * Waiting state. + */ for (i=0; icount; ++i) { pj_ice_check *check = &clist->checks[i]; - if (check->check_state == PJ_ICE_CHECK_STATE_WAITING) { + if (check->state == PJ_ICE_CHECK_STATE_WAITING) { status = perform_check(ice, clist, i); - if (status != PJ_SUCCESS) + if (status != PJ_SUCCESS) { + pj_mutex_unlock(ice->mutex); return status; + } ++start_count; + break; } } @@ -882,10 +1203,12 @@ static pj_status_t start_periodic_check(pj_ice *ice, pj_ice_checklist *clist) for (i=0; icount; ++i) { pj_ice_check *check = &clist->checks[i]; - if (check->check_state == PJ_ICE_CHECK_STATE_FROZEN) { + if (check->state == PJ_ICE_CHECK_STATE_FROZEN) { status = perform_check(ice, clist, i); - if (status != PJ_SUCCESS) + if (status != PJ_SUCCESS) { + pj_mutex_unlock(ice->mutex); return status; + } ++start_count; break; @@ -897,10 +1220,18 @@ static pj_status_t start_periodic_check(pj_ice *ice, pj_ice_checklist *clist) * Set checklist state to Completed. */ if (start_count==0) { - clist->state = PJ_ICE_CHECKLIST_ST_COMPLETED; - PJ_LOG(4,(ice->obj_name, "Checklist completed")); + clist_set_state(ice, clist, PJ_ICE_CHECKLIST_ST_COMPLETED); + + } else { + /* Schedule for next timer */ + pj_time_val timeout = {0, PJ_ICE_TA_VAL}; + + te->id = PJ_TRUE; + pj_time_val_normalize(&timeout); + pj_timer_heap_schedule(th, te, &timeout); } + pj_mutex_unlock(ice->mutex); return PJ_SUCCESS; } @@ -909,36 +1240,40 @@ static pj_status_t start_periodic_check(pj_ice *ice, pj_ice_checklist *clist) PJ_DEF(pj_status_t) pj_ice_start_check(pj_ice *ice) { pj_ice_checklist *clist; + const pj_ice_cand *cand0; unsigned i; PJ_ASSERT_RETURN(ice, PJ_EINVAL); + LOG((ice->obj_name, "Starting ICE check..")); + clist = &ice->cklist; if (clist->count == 0) return PJ_EICENOCHECKLIST; /* Pickup the first pair and set the state to Waiting */ - clist->checks[0].check_state = PJ_ICE_CHECK_STATE_WAITING; + clist->checks[0].state = PJ_ICE_CHECK_STATE_WAITING; + cand0 = clist->checks[0].lcand; /* Find all of the other pairs in that check list with the same * component ID, but different foundations, and sets all of their * states to Waiting as well. */ for (i=1; icount; ++i) { - pj_ice_check *cki = &clist->checks[i]; - - if (cki->comp_id != clist->checks[0].comp_id) - continue; + const pj_ice_cand *cand1; - if (pj_strcmp(&cki->foundation, &clist->checks[0].foundation)==0) - continue; + cand1 = clist->checks[i].lcand; - clist->checks[i].check_state = PJ_ICE_CHECK_STATE_WAITING; + if (cand0->comp_id == cand1->comp_id && + pj_strcmp(&cand0->foundation, &cand1->foundation)!=0) + { + clist->checks[i].state = PJ_ICE_CHECK_STATE_WAITING; + } } /* Start periodic check */ - return start_periodic_check(ice, clist); + return start_periodic_check(ice->stun_cfg.timer_heap, &clist->timer); } @@ -955,56 +1290,8 @@ static pj_status_t on_stun_send_msg(pj_stun_session *sess, } -static pj_status_t on_stun_rx_request(pj_stun_session *sess, - const pj_uint8_t *pkt, - unsigned pkt_len, - const pj_stun_msg *msg, - const pj_sockaddr_t *src_addr, - unsigned src_addr_len) -{ - pj_stun_tx_data *tdata; - pj_status_t status; - - /* 7.2.1.2. Learning Peer Reflexive Candidates */ - PJ_TODO(LEARN_PEER_REFLEXIVE_CANDIDATES); - - /* Reject any requests except Binding request */ - if (msg->hdr.type != PJ_STUN_BINDING_REQUEST) { - status = pj_stun_session_create_response(sess, msg, - PJ_STUN_SC_BAD_REQUEST, - NULL, &tdata); - if (status != PJ_SUCCESS) - return status; - - status = pj_stun_session_send_msg(sess, PJ_TRUE, - src_addr, src_addr_len, tdata); - return status; - } - - status = pj_stun_session_create_response(sess, msg, 0, NULL, &tdata); - if (status != PJ_SUCCESS) - return status; - - status = pj_stun_msg_add_sockaddr_attr(tdata->pool, msg, - PJ_STUN_ATTR_XOR_MAPPED_ADDR, - PJ_TRUE, src_addr, src_addr_len); - - status = pj_stun_session_send_msg(sess, PJ_TRUE, - src_addr, src_addr_len, tdata); - - /* 7.2.1.3. Triggered Checks: - * Next, the agent constructs a pair whose local candidate is equal to - * the transport address on which the STUN request was received, and a - * remote candidate equal to the source transport address where the - * request came from (which may be peer-reflexive remote candidate that - * was just learned). - */ - - return status; -} - /* This callback is called when outgoing STUN request completed */ -static void on_stun_request_complete(pj_stun_session *sess, +static void on_stun_request_complete(pj_stun_session *stun_sess, pj_status_t status, pj_stun_tx_data *tdata, const pj_stun_msg *response) @@ -1012,20 +1299,32 @@ static void on_stun_request_complete(pj_stun_session *sess, struct req_data *rd = (struct req_data*) tdata->user_data; pj_ice *ice; pj_ice_check *check, *valid_check; + const pj_ice_cand *lcand; + const pj_ice_cand *rcand; pj_ice_checklist *clist; - char buffer[128]; + pj_stun_xor_mapped_addr_attr *xaddr; + char buffer[CHECK_NAME_LEN]; + unsigned i; + + PJ_UNUSED_ARG(stun_sess); ice = rd->ice; check = &rd->clist->checks[rd->ckid]; clist = rd->clist; - PJ_LOG(5,(ice->obj_name, - "Connectivity check %s for check %d: %s", - (status==PJ_SUCCESS ? "SUCCESS" : "FAILED"), rd->ckid, - dump_check(buffer, sizeof(buffer), ice, check))); + pj_mutex_lock(ice->mutex); + + lcand = check->lcand; + rcand = check->rcand; + + LOG((ice->obj_name, + "Connectivity check %s for check %s", + (status==PJ_SUCCESS ? "SUCCESS" : "FAILED"), + dump_check(buffer, sizeof(buffer), ice, check))); if (status != PJ_SUCCESS) { - check->check_state = PJ_ICE_CHECK_STATE_FAILED; + check_set_state(ice, check, PJ_ICE_CHECK_STATE_FAILED, status); + pj_mutex_unlock(ice->mutex); return; } @@ -1035,47 +1334,92 @@ static void on_stun_request_complete(pj_stun_session *sess, * the response match the source IP address and port that the Binding * Request was sent from. */ - PJ_TODO(CHECK_ICE_RESPONSE_SOURCE_ADDRESS); + PJ_TODO(ICE_CHECK_RESPONSE_SOURCE_ADDRESS); + + /* Get the STUN MAPPED-ADDRESS attribute. */ + xaddr = (pj_stun_xor_mapped_addr_attr*) + pj_stun_msg_find_attr(response, PJ_STUN_ATTR_XOR_MAPPED_ADDR,0); + if (!xaddr) { + check_set_state(ice, check, PJ_ICE_CHECK_STATE_FAILED, + PJNATH_ESTUNNOXORMAP); + pj_mutex_unlock(ice->mutex); + return; + } - /* Get the STUN MAPPED-ADDRESS attribute. If the - * transport address does not match any of the local candidates that the - * agent knows about, the mapped address represents a new candidate - a - * peer reflexive candidate + /* If the transport address returned in XOR-MAPPED-ADDRESS does not match + * any of the local candidates that the agent knows about, the mapped + * address represents a new candidate - a peer reflexive candidate. */ - PJ_TODO(CHECK_ICE_RESPONSE_SOURCE_ADDRESS2); + for (i=0; ilcand_cnt; ++i) { + if (sockaddr_cmp(&xaddr->sockaddr, &ice->lcand[i].addr) == 0) { + /* Match */ + break; + } + } + + if (i == ice->lcand_cnt) { + unsigned cand_id; + + /* Add new peer reflexive candidate */ + status = pj_ice_add_cand(ice, lcand->comp_id, + PJ_ICE_CAND_TYPE_PEER_MAPPED, + 65535, &peer_mapped_foundation, + &xaddr->sockaddr, &lcand->base_addr, NULL, + sizeof(pj_sockaddr_in), &cand_id); + if (status != PJ_SUCCESS) { + check_set_state(ice, check, PJ_ICE_CHECK_STATE_FAILED, status); + pj_mutex_unlock(ice->mutex); + return; + } + + /* Update local candidate */ + lcand = &ice->lcand[cand_id]; + } /* Sets the state of the pair that generated the check to succeeded. */ - check->check_state = PJ_ICE_CHECK_STATE_SUCCEEDED; + check_set_state(ice, check, PJ_ICE_CHECK_STATE_SUCCEEDED, PJ_SUCCESS); + /* This is a valid pair, so add this to the valid list */ valid_check = &ice->valid_list.checks[ice->valid_list.count++]; - pj_memcpy(valid_check, check, sizeof(*check)); + valid_check->lcand = lcand; + valid_check->rcand = rcand; + valid_check->prio = CALC_CHECK_PRIO(ice, lcand, rcand); + valid_check->state = PJ_ICE_CHECK_STATE_SUCCEEDED; + valid_check->nominated = (pj_stun_msg_find_attr(tdata->msg, + PJ_STUN_ATTR_USE_CANDIDATE, + 0) != NULL); + valid_check->err_code = PJ_SUCCESS; /* Sort valid_list */ sort_checklist(&ice->valid_list); - /* If the pair had a component ID of 1, the agent MUST change the * states for all other Frozen pairs for the same media stream and * same foundation, but different component IDs, to Waiting. */ - if (check->comp_id == 1) { + if (lcand->comp_id == 1) { unsigned i; + pj_bool_t unfrozen = PJ_FALSE; + for (i=0; icount; ++i) { pj_ice_check *c = &clist->checks[i]; - if (c->check_state == PJ_ICE_CHECK_STATE_FROZEN && - c->comp_id != check->comp_id && - pj_strcmp(&c->foundation, &check->foundation)==0) + if (c->state == PJ_ICE_CHECK_STATE_FROZEN && + c->lcand->comp_id != lcand->comp_id && + pj_strcmp(&c->lcand->foundation, &lcand->foundation)==0) { /* Unfreeze and start check */ - PJ_LOG(5,(ice->obj_name, "Unfreezing check %d", i)); - c->check_state = PJ_ICE_CHECK_STATE_WAITING; - perform_check(ice, clist, i); + check_set_state(ice, c, PJ_ICE_CHECK_STATE_WAITING, + PJ_SUCCESS); + unfrozen = PJ_TRUE; } } + if (unfrozen && clist->timer.id == PJ_FALSE) + start_periodic_check(ice->stun_cfg.timer_heap, &clist->timer); } + /* If the pair had a component ID equal to the number of components * for the media stream (where this is the actual number of * components being used, in cases where the number of components @@ -1094,8 +1438,151 @@ static void on_stun_request_complete(pj_stun_session *sess, PJ_TODO(UNFREEZE_OTHER_COMPONENT_ID1); } + pj_mutex_unlock(ice->mutex); } + +static pj_status_t on_stun_rx_request(pj_stun_session *sess, + const pj_uint8_t *pkt, + unsigned pkt_len, + const pj_stun_msg *msg, + const pj_sockaddr_t *src_addr, + unsigned src_addr_len) +{ + stun_data *sd; + pj_ice *ice; + pj_stun_priority_attr *ap; + pj_ice_comp *comp; + pj_ice_cand *lcand; + pj_ice_cand *rcand; + unsigned i; + pj_stun_tx_data *tdata; + pj_bool_t is_relayed; + pj_status_t status; + + PJ_UNUSED_ARG(pkt); + PJ_UNUSED_ARG(pkt_len); + + /* Only accepts Binding request */ + if (msg->hdr.type != PJ_STUN_BINDING_REQUEST) { + LOG((ice->obj_name, "Received non-Binding request, ignored")); + return PJ_SUCCESS; + } + + sd = (stun_data*) pj_stun_session_get_user_data(sess); + ice = sd->ice; + comp = sd->comp; + + pj_mutex_lock(ice->mutex); + + /* Get PRIORITY attribute */ + ap = (pj_stun_priority_attr*) + pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_PRIORITY, 0); + if (ap == 0) { + LOG((ice->obj_name, "Received Binding request with no PRIORITY")); + pj_mutex_unlock(ice->mutex); + return PJ_SUCCESS; + } + + /* For simplicity, ignore incoming requests when we don't have remote + * candidates yet. The peer agent should retransmit the STUN request + * and we'll receive it again later. + */ + if (ice->rcand_cnt == 0) { + pj_mutex_unlock(ice->mutex); + return PJ_SUCCESS; + } + + + /* Find remote candidate based on the source transport address of + * the request. + */ + for (i=0; ircand_cnt; ++i) { + if (sockaddr_cmp((const pj_sockaddr*)src_addr, &ice->rcand[i].addr)==0) + break; + } + + /* If the source transport address of the request does not match any + * existing remote candidates, it represents a new peer reflexive remote + * candidate. + */ + if (i == ice->rcand_cnt) { + rcand = &ice->rcand[ice->rcand_cnt++]; + rcand->comp_id = comp->comp_id; + rcand->type = PJ_ICE_CAND_TYPE_PEER_MAPPED; + rcand->prio = ap->value; + pj_memcpy(&rcand->addr, src_addr, src_addr_len); + + /* Foundation is random, unique from other foundation */ + rcand->foundation.ptr = pj_pool_alloc(ice->pool, 32); + rcand->foundation.slen = pj_ansi_snprintf(rcand->foundation.ptr, 32, + "f%p", + rcand->foundation.ptr); + } else { + /* Remote candidate found */ + rcand = &ice->rcand[i]; + } + + /* + * Create candidate pair for this request. + */ + /* First check if the source address is the source address of the + * STUN relay, to determine if local candidate is relayed candidate. + */ + PJ_TODO(DETERMINE_IF_REQUEST_COMES_FROM_RELAYED_CANDIDATE); + is_relayed = PJ_FALSE; + + /* Next find local candidate */ + /* Just pick up + + + + /* 7.2.1.2. Learning Peer Reflexive Candidates */ + PJ_TODO(LEARN_PEER_REFLEXIVE_CANDIDATES); + + /* Reject any requests except Binding request */ + if (msg->hdr.type != PJ_STUN_BINDING_REQUEST) { + status = pj_stun_session_create_response(sess, msg, + PJ_STUN_SC_BAD_REQUEST, + NULL, &tdata); + if (status != PJ_SUCCESS) { + pj_mutex_unlock(ice->mutex); + return status; + } + + status = pj_stun_session_send_msg(sess, PJ_TRUE, + src_addr, src_addr_len, tdata); + + pj_mutex_unlock(ice->mutex); + return status; + } + + status = pj_stun_session_create_response(sess, msg, 0, NULL, &tdata); + if (status != PJ_SUCCESS) { + pj_mutex_unlock(ice->mutex); + return status; + } + + status = pj_stun_msg_add_sockaddr_attr(tdata->pool, tdata->msg, + PJ_STUN_ATTR_XOR_MAPPED_ADDR, + PJ_TRUE, src_addr, src_addr_len); + + status = pj_stun_session_send_msg(sess, PJ_TRUE, + src_addr, src_addr_len, tdata); + + /* 7.2.1.3. Triggered Checks: + * Next, the agent constructs a pair whose local candidate is equal to + * the transport address on which the STUN request was received, and a + * remote candidate equal to the source transport address where the + * request came from (which may be peer-reflexive remote candidate that + * was just learned). + */ + + pj_mutex_unlock(ice->mutex); + return status; +} + + static pj_status_t on_stun_rx_indication(pj_stun_session *sess, const pj_uint8_t *pkt, unsigned pkt_len, @@ -1103,7 +1590,15 @@ static pj_status_t on_stun_rx_indication(pj_stun_session *sess, const pj_sockaddr_t *src_addr, unsigned src_addr_len) { + PJ_UNUSED_ARG(sess); + PJ_UNUSED_ARG(pkt); + PJ_UNUSED_ARG(pkt_len); + PJ_UNUSED_ARG(msg); + PJ_UNUSED_ARG(src_addr); + PJ_UNUSED_ARG(src_addr_len); + PJ_TODO(SUPPORT_RX_BIND_REQUEST_AS_INDICATION); + return PJ_ENOTSUP; } diff --git a/pjnath/src/pjnath/stun_auth.c b/pjnath/src/pjnath/stun_auth.c index 071019b8..2602bf8d 100644 --- a/pjnath/src/pjnath/stun_auth.c +++ b/pjnath/src/pjnath/stun_auth.c @@ -185,7 +185,8 @@ PJ_DEF(pj_status_t) pj_stun_verify_credential( const pj_uint8_t *pkt, } else if (cred->type == PJ_STUN_AUTH_CRED_DYNAMIC) { int data_type = 0; pj_status_t rc; - rc = cred->data.dyn_cred.get_password(cred->data.dyn_cred.user_data, + rc = cred->data.dyn_cred.get_password(msg, + cred->data.dyn_cred.user_data, (arealm?&arealm->value:NULL), &auser->value, pool, &data_type, &password); @@ -269,7 +270,8 @@ PJ_DEF(pj_status_t) pj_stun_verify_credential( const pj_uint8_t *pkt, pj_bool_t ok; if (cred->type == PJ_STUN_AUTH_CRED_DYNAMIC) { - ok=cred->data.dyn_cred.verify_nonce(cred->data.dyn_cred.user_data, + ok=cred->data.dyn_cred.verify_nonce(msg, + cred->data.dyn_cred.user_data, (arealm?&arealm->value:NULL), &auser->value, &anonce->value); diff --git a/pjnath/src/pjnath/stun_session.c b/pjnath/src/pjnath/stun_session.c index 9bcff549..44493120 100644 --- a/pjnath/src/pjnath/stun_session.c +++ b/pjnath/src/pjnath/stun_session.c @@ -206,14 +206,31 @@ static void on_cache_timeout(pj_timer_heap_t *timer_heap, pj_stun_msg_destroy_tdata(tdata->sess, tdata); } -static pj_str_t *get_passwd(pj_stun_session *sess) +static pj_str_t *get_passwd(pj_stun_session *sess, pj_pool_t *pool, + const pj_stun_msg *msg) { - if (sess->cred == NULL) + if (sess->cred == NULL) { return NULL; - else if (sess->cred->type == PJ_STUN_AUTH_CRED_STATIC) + } else if (sess->cred->type == PJ_STUN_AUTH_CRED_STATIC) { return &sess->cred->data.static_cred.data; - else + } else if (sess->cred->type == PJ_STUN_AUTH_CRED_DYNAMIC) { + pj_str_t realm, username, nonce; + pj_str_t *password; + void *user_data = sess->cred->data.dyn_cred.user_data; + int data_type = 0; + pj_status_t status; + + realm.slen = username.slen = nonce.slen = 0; + password = PJ_POOL_ZALLOC_T(pool, pj_str_t); + status = (*sess->cred->data.dyn_cred.get_cred)(msg, user_data, pool, + &realm, &username, + &nonce, &data_type, + password); + return password; + + } else { return NULL; + } } static pj_status_t apply_msg_options(pj_stun_session *sess, @@ -221,6 +238,10 @@ static pj_status_t apply_msg_options(pj_stun_session *sess, pj_stun_msg *msg) { pj_status_t status = 0; + pj_str_t realm, username, nonce, password; + int data_type = 0; + + realm.slen = username.slen = nonce.slen = password.slen = 0; /* The server SHOULD include a SERVER attribute in all responses */ if (sess->srv_name.slen && (PJ_STUN_IS_RESPONSE(msg->hdr.type) || @@ -238,28 +259,51 @@ static pj_status_t apply_msg_options(pj_stun_session *sess, if (sess->cred && sess->cred->type == PJ_STUN_AUTH_CRED_STATIC && !PJ_STUN_IS_ERROR_RESPONSE(msg->hdr.type)) { - const pj_str_t *username; + realm = sess->cred->data.static_cred.realm; + username = sess->cred->data.static_cred.username; + data_type = sess->cred->data.static_cred.data_type; + password = sess->cred->data.static_cred.data; + nonce = sess->cred->data.static_cred.nonce; + + } else if (sess->cred && sess->cred->type == PJ_STUN_AUTH_CRED_DYNAMIC && + !PJ_STUN_IS_ERROR_RESPONSE(msg->hdr.type)) + { + void *user_data = sess->cred->data.dyn_cred.user_data; - /* Create and add USERNAME attribute */ - username = &sess->cred->data.static_cred.username; + status = (*sess->cred->data.dyn_cred.get_cred)(msg, user_data, pool, + &realm, &username, + &nonce, &data_type, + &password); + if (status != PJ_SUCCESS) + return status; + } + + + /* Create and add USERNAME attribute */ + status = pj_stun_msg_add_string_attr(pool, msg, + PJ_STUN_ATTR_USERNAME, + &username); + PJ_ASSERT_RETURN(status==PJ_SUCCESS, status); + + /* Add REALM only when long term credential is used */ + if (realm.slen) { status = pj_stun_msg_add_string_attr(pool, msg, - PJ_STUN_ATTR_USERNAME, - username); + PJ_STUN_ATTR_REALM, + &realm); PJ_ASSERT_RETURN(status==PJ_SUCCESS, status); + } - /* Add REALM only when long term credential is used */ - if (sess->cred->data.static_cred.realm.slen) { - const pj_str_t *realm = &sess->cred->data.static_cred.realm; - status = pj_stun_msg_add_string_attr(pool, msg, - PJ_STUN_ATTR_REALM, - realm); - } + /* Add NONCE when desired */ + if (nonce.slen) { + status = pj_stun_msg_add_string_attr(pool, msg, + PJ_STUN_ATTR_NONCE, + &nonce); + } - /* Add MESSAGE-INTEGRITY attribute */ - status = pj_stun_msg_add_msgint_attr(pool, msg); - PJ_ASSERT_RETURN(status==PJ_SUCCESS, status); + /* Add MESSAGE-INTEGRITY attribute */ + status = pj_stun_msg_add_msgint_attr(pool, msg); + PJ_ASSERT_RETURN(status==PJ_SUCCESS, status); - } /* Add FINGERPRINT attribute if necessary */ if (sess->use_fingerprint) { @@ -542,7 +586,8 @@ 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, get_passwd(sess), &tdata->pkt_size); + 0, get_passwd(sess, tdata->pool, tdata->msg), + &tdata->pkt_size); if (status != PJ_SUCCESS) { pj_stun_msg_destroy_tdata(sess, tdata); pj_mutex_unlock(sess->mutex); @@ -639,18 +684,21 @@ static pj_status_t send_response(pj_stun_session *sess, unsigned out_max_len, out_len; pj_status_t status; - /* Alloc packet buffer */ - out_max_len = PJ_STUN_MAX_PKT_LEN; - out_pkt = pj_pool_alloc(pool, out_max_len); - /* Apply options */ if (!retransmission) { - apply_msg_options(sess, pool, response); + status = apply_msg_options(sess, pool, response); + if (status != PJ_SUCCESS) + return status; } + /* Alloc packet buffer */ + out_max_len = PJ_STUN_MAX_PKT_LEN; + out_pkt = pj_pool_alloc(pool, out_max_len); + /* Encode */ status = pj_stun_msg_encode(response, out_pkt, out_max_len, 0, - get_passwd(sess), &out_len); + get_passwd(sess, pool, response), + &out_len); if (status != PJ_SUCCESS) { LOG_ERR_(sess, "Error encoding message", status); return status; -- cgit v1.2.3