diff options
author | Benny Prijono <bennylp@teluu.com> | 2007-03-20 08:44:26 +0000 |
---|---|---|
committer | Benny Prijono <bennylp@teluu.com> | 2007-03-20 08:44:26 +0000 |
commit | 64942da06cce3089be7faa5ae609d5431d06efb8 (patch) | |
tree | 59437308405603551654518b4aa42e94611b5317 | |
parent | ddd77d225ea62a6200a6a215655a31f680b15fe8 (diff) |
--
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@1085 74dad513-b988-da41-8d7b-12977e46ad98
-rw-r--r-- | pjnath/build/pjnath.dsp | 4 | ||||
-rw-r--r-- | pjnath/include/pjnath/ice.h | 60 | ||||
-rw-r--r-- | pjnath/include/pjnath/stun_msg.h | 26 | ||||
-rw-r--r-- | pjnath/include/pjnath/stun_session.h | 2 | ||||
-rw-r--r-- | pjnath/src/pjnath/ice.c | 659 | ||||
-rw-r--r-- | pjnath/src/pjnath/stun_msg.c | 17 |
6 files changed, 693 insertions, 75 deletions
diff --git a/pjnath/build/pjnath.dsp b/pjnath/build/pjnath.dsp index 74af4bf2..9cef5642 100644 --- a/pjnath/build/pjnath.dsp +++ b/pjnath/build/pjnath.dsp @@ -41,7 +41,7 @@ RSC=rc.exe # PROP Intermediate_Dir "./output/pjnath-i386-win32-vc6-release"
# PROP Target_Dir ""
# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /c
-# ADD CPP /nologo /MD /W4 /GX /Zi /O2 /Ob2 /I "../include" /I "../../pjlib/include" /D "NDEBUG" /D "WIN32" /D "_MBCS" /D "_LIB" /D PJ_WIN32=1 /D PJ_M_I386=1 /FR /FD /c
+# ADD CPP /nologo /MD /W4 /GX /Zi /O2 /Ob2 /I "../include" /I "../../pjlib/include" /I "../../pjlib-util/include" /D "NDEBUG" /D "WIN32" /D "_MBCS" /D "_LIB" /D PJ_WIN32=1 /D PJ_M_I386=1 /FR /FD /c
# SUBTRACT CPP /YX
# ADD BASE RSC /l 0x409 /d "NDEBUG"
# ADD RSC /l 0x409 /d "NDEBUG"
@@ -65,7 +65,7 @@ LIB32=link.exe -lib # PROP Intermediate_Dir "./output/pjnath-i386-win32-vc6-debug"
# PROP Target_Dir ""
# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /YX /FD /GZ /c
-# ADD CPP /nologo /MTd /W4 /Gm /GX /ZI /Od /I "../include" /I "../../pjlib/include" /D "_DEBUG" /D "WIN32" /D "_MBCS" /D "_LIB" /D PJ_WIN32=1 /D PJ_M_I386=1 /FR /FD /GZ /c
+# ADD CPP /nologo /MTd /W4 /Gm /GX /ZI /Od /I "../include" /I "../../pjlib/include" /I "../../pjlib-util/include" /D "_DEBUG" /D "WIN32" /D "_MBCS" /D "_LIB" /D PJ_WIN32=1 /D PJ_M_I386=1 /FR /FD /GZ /c
# SUBTRACT CPP /YX
# ADD BASE RSC /l 0x409 /d "_DEBUG"
# ADD RSC /l 0x409 /d "_DEBUG"
diff --git a/pjnath/include/pjnath/ice.h b/pjnath/include/pjnath/ice.h index 3277dc77..f42a793b 100644 --- a/pjnath/include/pjnath/ice.h +++ b/pjnath/include/pjnath/ice.h @@ -24,7 +24,7 @@ * @brief ICE socket. */ #include <pjnath/types.h> -#include <pjnath/stun_endpoint.h> +#include <pjnath/stun_session.h> #include <pjlib-util/resolver.h> #include <pj/sock.h> @@ -64,9 +64,9 @@ enum pj_ice_type_pref typedef struct pj_ice pj_ice; -#define PJ_ICE_MAX_CAND 32 +#define PJ_ICE_MAX_CAND 16 #define PJ_ICE_MAX_COMP 8 - +#define PJ_ICE_MAX_CHECKS 32 /** * ICE component @@ -104,7 +104,10 @@ typedef enum pj_ice_check_state typedef struct pj_ice_check { - unsigned local_cand_id; + unsigned cand_id; + pj_uint32_t comp_id; + pj_str_t foundation; + pj_uint64_t check_prio; pj_ice_check_state check_state; @@ -127,7 +130,7 @@ typedef struct pj_ice_checklist { pj_ice_checklist_state state; unsigned count; - pj_ice_check *checks; + pj_ice_check checks[PJ_ICE_MAX_CHECKS]; } pj_ice_checklist; @@ -136,10 +139,10 @@ typedef struct pj_ice_checklist */ typedef struct pj_ice_cb { - pj_bool_t (*on_found_cand)(pj_ice *sock, - pj_ice_cand_type type, - const pj_sockaddr_t *addr, - int addr_len); + pj_status_t (*on_send_pkt)(pj_ice *ice, + const void *pkt, pj_size_t size, + const pj_sockaddr_t *dst_addr, + unsigned addr_len); } pj_ice_cb; @@ -153,6 +156,12 @@ typedef enum pj_ice_state PJ_ICE_STATE_RESV_ERROR } pj_ice_state; +typedef enum pj_ice_role +{ + PJ_ICE_ROLE_CONTROLLED, + PJ_ICE_ROLE_CONTROLLING +} pj_ice_role; + /** * ICE structure. */ @@ -164,30 +173,49 @@ struct pj_ice pj_mutex_t *mutex; int af; int sock_type; - + pj_ice_role role; pj_ice_state state; + pj_ice_cb cb; + + /* STUN credentials */ + pj_str_t tx_uname; + pj_str_t tx_pass; + pj_str_t rx_uname; + pj_str_t rx_pass; + /* Components */ unsigned comp_cnt; pj_ice_comp comp[PJ_ICE_MAX_COMP]; /* Local candidates */ - unsigned cand_cnt; - pj_ice_cand cand[PJ_ICE_MAX_CAND]; + unsigned lcand_cnt; + pj_ice_cand lcand[PJ_ICE_MAX_CAND]; + + /* Remote candidates */ + unsigned rcand_cnt; + pj_ice_cand rcand[PJ_ICE_MAX_CAND]; - /* Checklist */ + /* Checklists */ pj_ice_checklist cklist; + pj_ice_checklist valid_list; /* STUN servers */ pj_dns_resolver *resv; 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, const char *name, + pj_ice_role role, + const pj_ice_cb *cb, int af, int sock_type, pj_ice **p_ice); @@ -207,7 +235,11 @@ PJ_DECL(pj_status_t) pj_ice_add_comp(pj_ice *ice, PJ_DECL(pj_status_t) pj_ice_add_sock_comp(pj_ice *ice, unsigned comp_id, pj_sock_t sock); - +PJ_DECL(pj_status_t) pj_ice_set_credentials(pj_ice *ice, + const pj_str_t *local_ufrag, + const pj_str_t *local_pass, + const pj_str_t *remote_ufrag, + const pj_str_t *remote_pass); PJ_DECL(pj_status_t) pj_ice_start_gather(pj_ice *ice, unsigned flags); diff --git a/pjnath/include/pjnath/stun_msg.h b/pjnath/include/pjnath/stun_msg.h index ae076295..08e5672d 100644 --- a/pjnath/include/pjnath/stun_msg.h +++ b/pjnath/include/pjnath/stun_msg.h @@ -1512,7 +1512,6 @@ PJ_DECL(pj_status_t) pj_stun_binary_attr_create(pj_pool_t *pool, * if no data to be copied now. * @param length Length of data, or zero if no data is to be * copied now. - * @param p_attr Pointer to receive the attribute. * * @return PJ_SUCCESS on success or the appropriate error code. */ @@ -1522,6 +1521,31 @@ PJ_DECL(pj_status_t) pj_stun_msg_add_binary_attr(pj_pool_t *pool, const pj_uint8_t *data, unsigned length); +/** + * Create STUN empty attribute. + * + * @param pool The pool to allocate memory from. + * @param attr_type The attribute type, from #pj_stun_attr_type. + * @param p_attr Pointer to receive the attribute. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_stun_empty_attr_create(pj_pool_t *pool, + int attr_type, + pj_stun_empty_attr **p_attr); + +/** + * Create STUN empty attribute and add the attribute to the message. + * + * @param pool The pool to allocate memory from. + * @param msg The STUN message. + * @param attr_type The attribute type, from #pj_stun_attr_type. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_stun_msg_add_empty_attr(pj_pool_t *pool, + pj_stun_msg *msg, + int attr_type); /** * @} diff --git a/pjnath/include/pjnath/stun_session.h b/pjnath/include/pjnath/stun_session.h index 5f5bda30..bd28ecb6 100644 --- a/pjnath/include/pjnath/stun_session.h +++ b/pjnath/include/pjnath/stun_session.h @@ -138,6 +138,8 @@ struct pj_stun_tx_data pj_stun_session *sess; /**< The STUN session. */ pj_stun_msg *msg; /**< The STUN message. */ + void *user_data; /**< Arbitrary application data. */ + pj_stun_client_tsx *client_tsx; /**< Client STUN transaction. */ pj_uint32_t msg_magic; /**< Message magic. */ pj_uint8_t msg_key[12]; /**< Message/transaction key. */ diff --git a/pjnath/src/pjnath/ice.c b/pjnath/src/pjnath/ice.c index c92ceb5a..eb2ead7f 100644 --- a/pjnath/src/pjnath/ice.c +++ b/pjnath/src/pjnath/ice.c @@ -18,29 +18,81 @@ */ #include <pjnath/ice.h> #include <pjnath/errno.h> +#include <pj/addr_resolv.h> #include <pj/assert.h> +#include <pj/log.h> #include <pj/os.h> #include <pj/pool.h> #include <pj/string.h> +static const char *check_state_name[] = +{ + "Frozen", + "Waiting", + "In Progress", + "Succeeded", + "Failed" +}; + 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 on_stun_send_msg(pj_stun_session *sess, + const void *pkt, + pj_size_t pkt_size, + const pj_sockaddr_t *dst_addr, + unsigned addr_len); +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); +static void on_stun_request_complete(pj_stun_session *sess, + pj_status_t status, + pj_stun_tx_data *tdata, + const pj_stun_msg *response); +static pj_status_t on_stun_rx_indication(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); + +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, + 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, + 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, const char *name, + pj_ice_role role, + const pj_ice_cb *cb, int af, int sock_type, pj_ice **p_ice) { 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 && p_ice, PJ_EINVAL); + PJ_ASSERT_RETURN(cfg && cb && p_ice, PJ_EINVAL); PJ_ASSERT_RETURN(sock_type==PJ_SOCK_DGRAM || sock_type==PJ_SOCK_STREAM, PJ_EINVAL); @@ -52,6 +104,7 @@ PJ_DEF(pj_status_t) pj_ice_create(pj_stun_config *cfg, ice->pool = pool; ice->af = af; ice->sock_type = sock_type; + ice->role = role; pj_ansi_snprintf(ice->obj_name, sizeof(ice->obj_name), name, ice); @@ -63,8 +116,51 @@ PJ_DEF(pj_status_t) pj_ice_create(pj_stun_config *cfg, return status; } + pj_memcpy(&ice->cb, cb, sizeof(*cb)); + + /* 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")); + return PJ_SUCCESS; } @@ -72,6 +168,10 @@ PJ_DEF(pj_status_t) pj_ice_create(pj_stun_config *cfg, static void destroy_ice(pj_ice *ice, pj_status_t reason) { + if (reason == PJ_SUCCESS) { + PJ_LOG(4,(ice->obj_name, "Destroying ICE session")); + } + if (ice->resv_q) { pj_dns_resolver_cancel_query(ice->resv_q, PJ_FALSE); ice->resv_q = NULL; @@ -108,6 +208,9 @@ static void resolver_cb(void *user_data, pj_dns_parsed_packet *response) { pj_assert(!"Not implemented yet!"); + PJ_UNUSED_ARG(user_data); + PJ_UNUSED_ARG(status); + PJ_UNUSED_ARG(response); } PJ_DEF(pj_status_t) pj_ice_set_srv(pj_ice *ice, @@ -259,6 +362,105 @@ PJ_DEF(pj_status_t) pj_ice_add_sock_comp( pj_ice *ice, } +static pj_status_t stun_auth_get_auth(void *user_data, + pj_pool_t *pool, + pj_str_t *realm, + pj_str_t *nonce) +{ + PJ_UNUSED_ARG(user_data); + PJ_UNUSED_ARG(pool); + + realm->slen = 0; + nonce->slen = 0; + + return PJ_SUCCESS; +} + + +static pj_status_t stun_auth_get_password(void *user_data, + const pj_str_t *realm, + const pj_str_t *username, + pj_pool_t *pool, + int *data_type, + 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); + + PJ_UNUSED_ARG(realm); + PJ_UNUSED_ARG(pool); + + if (sess == ice->tx_sess) { + /* Verify username */ + if (pj_strcmp(username, &ice->tx_uname) != 0) + return -1; + *data_type = 0; + *data = ice->tx_pass; + } else { + /* 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 + * or answer for a session in-progress, and the MESSAGE-INTEGRITY + * is the output of a hash of the password and the STUN packet's + * contents. + */ + 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, + const pj_str_t *realm, + const pj_str_t *username, + const pj_str_t *nonce) +{ + /* We don't use NONCE */ + PJ_UNUSED_ARG(user_data); + PJ_UNUSED_ARG(realm); + PJ_UNUSED_ARG(username); + PJ_UNUSED_ARG(nonce); + return PJ_TRUE; +} + + +PJ_DEF(pj_status_t) pj_ice_set_credentials(pj_ice *ice, + const pj_str_t *local_ufrag, + const pj_str_t *local_pass, + const pj_str_t *remote_ufrag, + const pj_str_t *remote_pass) +{ + char buf[128]; + pj_str_t username; + + username.ptr = buf; + + PJ_ASSERT_RETURN(ice && local_ufrag && local_pass && + remote_ufrag && remote_pass, PJ_EINVAL); + PJ_ASSERT_RETURN(local_ufrag->slen + remote_ufrag->slen < + sizeof(buf), PJ_ENAMETOOLONG); + + pj_strcpy(&username, remote_ufrag); + pj_strcat2(&username, ":"); + pj_strcat(&username, local_ufrag); + + pj_strdup(ice->pool, &ice->tx_uname, &username); + pj_strdup(ice->pool, &ice->tx_pass, remote_pass); + + pj_strcpy(&username, local_ufrag); + pj_strcat2(&username, ":"); + pj_strcat(&username, remote_ufrag); + + pj_strdup(ice->pool, &ice->rx_uname, &username); + pj_strdup(ice->pool, &ice->rx_pass, local_pass); + + return PJ_SUCCESS; +} + + static pj_status_t gather_host_cands(pj_ice *ice) { unsigned i; @@ -292,11 +494,13 @@ static pj_status_t gather_host_cands(pj_ice *ice) static pj_status_t gather_mapped_cands(pj_ice *ice) { + PJ_UNUSED_ARG(ice); return PJ_ENOTSUP; } static pj_status_t gather_relayed_cands(pj_ice *ice) { + PJ_UNUSED_ARG(ice); return PJ_ENOTSUP; } @@ -305,6 +509,8 @@ PJ_DEF(pj_status_t) pj_ice_start_gather(pj_ice *ice, { pj_status_t status; + PJ_UNUSED_ARG(flags); + /* Gather host candidate */ status = gather_host_cands(ice); if (status != PJ_SUCCESS) @@ -347,32 +553,32 @@ PJ_DEF(pj_status_t) pj_ice_add_cand(pj_ice *ice, int addr_len, unsigned *p_cand_id) { - pj_ice_cand *cand; + pj_ice_cand *lcand; pj_status_t status = PJ_SUCCESS; pj_mutex_lock(ice->mutex); - if (ice->cand_cnt >= PJ_ARRAY_SIZE(ice->cand)) { + if (ice->lcand_cnt >= PJ_ARRAY_SIZE(ice->lcand)) { status = PJ_ETOOMANY; goto on_error; } - cand = &ice->cand[ice->cand_cnt]; - cand->comp_id = comp_id; - cand->type = type; - pj_strdup(ice->pool, &cand->foundation, foundation); - cand->prio = CALC_CAND_PRIO(type, local_pref, cand->comp_id); - pj_memcpy(&cand->addr, addr, addr_len); - pj_memcpy(&cand->base_addr, base_addr, addr_len); + lcand = &ice->lcand[ice->lcand_cnt]; + lcand->comp_id = comp_id; + lcand->type = type; + pj_strdup(ice->pool, &lcand->foundation, foundation); + lcand->prio = CALC_CAND_PRIO(type, local_pref, lcand->comp_id); + pj_memcpy(&lcand->addr, addr, addr_len); + pj_memcpy(&lcand->base_addr, base_addr, addr_len); if (srv_addr) - pj_memcpy(&cand->srv_addr, srv_addr, addr_len); + pj_memcpy(&lcand->srv_addr, srv_addr, addr_len); else - pj_bzero(&cand->srv_addr, sizeof(cand->srv_addr)); + pj_bzero(&lcand->srv_addr, sizeof(lcand->srv_addr)); if (p_cand_id) - *p_cand_id = ice->cand_cnt; + *p_cand_id = ice->lcand_cnt; - ++ice->cand_cnt; + ++ice->lcand_cnt; on_error: pj_mutex_unlock(ice->mutex); @@ -382,7 +588,7 @@ on_error: PJ_DEF(unsigned) pj_ice_get_cand_cnt(pj_ice *ice) { - return ice->cand_cnt; + return ice->lcand_cnt; } @@ -393,9 +599,11 @@ PJ_DEF(pj_status_t) pj_ice_enum_cands(pj_ice *ice, { unsigned i, count; + PJ_UNUSED_ARG(sort_by); + pj_mutex_lock(ice->mutex); - count = (*p_count < ice->cand_cnt) ? *p_count : ice->cand_cnt; + count = (*p_count < ice->lcand_cnt) ? *p_count : ice->lcand_cnt; for (i=0; i<count; ++i) cand_ids[i] = i; @@ -411,9 +619,9 @@ PJ_DEF(pj_status_t) pj_ice_get_cand(pj_ice *ice, pj_ice_cand **p_cand) { PJ_ASSERT_RETURN(ice && p_cand, PJ_EINVAL); - PJ_ASSERT_RETURN(cand_id <= ice->cand_cnt, PJ_EINVAL); + PJ_ASSERT_RETURN(cand_id <= ice->lcand_cnt, PJ_EINVAL); - *p_cand = &ice->cand[cand_id]; + *p_cand = &ice->lcand[cand_id]; return PJ_SUCCESS; } @@ -432,6 +640,74 @@ static pj_uint64_t CALC_CHECK_PRIO(pj_uint32_t O, pj_uint32_t A) (pj_uint64_t)2 * MAX(O, A) + (O>A ? 1 : 0); } +static const char *dump_check(char *buffer, unsigned bufsize, + const pj_ice *ice, + const pj_ice_check *check) +{ + char local_addr[128]; + int len; + + pj_ansi_strcpy(local_addr, + pj_inet_ntoa(ice->lcand[check->cand_id].addr.ipv4.sin_addr)); + + 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) + len = bufsize - 1; + + buffer[len] = '\0'; + return buffer; +} + +#if PJ_LOG_MAX_LEVEL >= 4 +static void dump_checklist(const char *title, const pj_ice *ice, + const pj_ice_checklist *clist) +{ + unsigned i; + char buffer[128]; + + PJ_LOG(4,(ice->obj_name, "%s", title)); + for (i=0; i<clist->count; ++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])); + } +} +#else +#define dump_checklist(ice, clist) +#endif + +static void sort_checklist(pj_ice_checklist *clist) +{ + unsigned i; + + for (i=0; i<clist->count-1; ++i) { + unsigned j, highest = i; + for (j=i+1; j<clist->count; ++j) { + if (clist->checks[j].check_prio > + clist->checks[highest].check_prio) + { + highest = j; + } + } + + if (highest != i) { + pj_ice_check tmp; + + pj_memcpy(&tmp, &clist->checks[i], sizeof(pj_ice_check)); + pj_memcpy(&clist->checks[i], &clist->checks[highest], + sizeof(pj_ice_check)); + pj_memcpy(&clist->checks[highest], &tmp, sizeof(pj_ice_check)); + } + } +} PJ_DEF(pj_status_t) pj_ice_create_check_list(pj_ice *ice, pj_bool_t is_remote_offer, @@ -439,39 +715,47 @@ PJ_DEF(pj_status_t) pj_ice_create_check_list(pj_ice *ice, const pj_ice_cand rem_cand[]) { pj_ice_checklist *clist; - unsigned i, j, count; + 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_mutex_lock(ice->mutex); - /* Create checklist */ + /* Save remote candidates */ + ice->rcand_cnt = 0; + for (i=0; i<rem_cand_cnt; ++i) { + pj_ice_cand *cn = &ice->rcand[ice->rcand_cnt++]; + pj_memcpy(cn, &rem_cand[i], sizeof(pj_ice_cand)); + pj_strdup(ice->pool, &cn->foundation, &rem_cand[i].foundation); + } + + /* Generate checklist */ clist = &ice->cklist; - clist->checks = pj_pool_calloc(ice->pool, - ice->cand_cnt * rem_cand_cnt, - sizeof(pj_ice_check)); - for (i=0, count=0; i<ice->cand_cnt; ++i) { + for (i=0; i<ice->lcand_cnt; ++i) { for (j=0; j<rem_cand_cnt; ++j) { - pj_ice_check *c = &clist->checks[count++]; + pj_ice_check *c = &clist->checks[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->cand[i].comp_id != rem_cand[j].comp_id || - pj_strcmp(&ice->cand[i].foundation,&rem_cand[j].foundation)==0) + if (ice->lcand[i].comp_id != rem_cand[j].comp_id || + pj_strcmp(&ice->lcand[i].foundation,&rem_cand[j].foundation)==0) { continue; } - c->local_cand_id = i; + 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->cand[i].prio); + ice->lcand[i].prio); } else { - c->check_prio = CALC_CHECK_PRIO(ice->cand[i].prio, + c->check_prio = CALC_CHECK_PRIO(ice->lcand[i].prio, rem_cand[j].prio); } @@ -485,43 +769,139 @@ PJ_DEF(pj_status_t) pj_ice_create_check_list(pj_ice *ice, } } - clist->count = count; - /* Sort checklist based on priority */ - for (i=0; i<clist->count-1; ++i) { - unsigned highest = i; - for (j=i+1; j<clist->count; ++j) { - if (clist->checks[j].check_prio > - clist->checks[highest].check_prio) - { - highest = j; - } - } - - if (highest != i) { - pj_ice_check tmp; - - pj_memcpy(&tmp, &clist->checks[i], sizeof(pj_ice_check)); - pj_memcpy(&clist->checks[i], &clist->checks[highest], - sizeof(pj_ice_check)); - pj_memcpy(&clist->checks[highest], &tmp, sizeof(pj_ice_check)); - } - } + sort_checklist(clist); /* Prune the checklist */ for (i=0; i<clist->count; ++i) { PJ_TODO(PRUNE_CHECKLIST); } + /* Log checklist */ + dump_checklist("Checklist created:", ice, clist); + pj_mutex_lock(ice->mutex); return PJ_SUCCESS; } +struct req_data +{ + pj_ice *ice; + pj_ice_checklist *clist; + unsigned ckid; +}; + +/* Perform check on the specified candidate pair */ +static pj_status_t perform_check(pj_ice *ice, pj_ice_checklist *clist, + unsigned check_id) +{ + pj_stun_tx_data *tdata; + struct req_data *rd; + pj_ice_check *check; + pj_uint32_t prio; + char buffer[128]; + pj_status_t status; + + check = &clist->checks[check_id]; + + PJ_LOG(5,(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, + PJ_STUN_BINDING_REQUEST, &tdata); + if (status != PJ_SUCCESS) + return status; + + /* Attach data to be retrieved later when STUN request transaction + * completes and on_stun_request_complete() callback is called. + */ + rd = PJ_POOL_ZALLOC_T(tdata->pool, struct req_data); + rd->ice = ice; + rd->clist = clist; + rd->ckid = check_id; + tdata->user_data = (void*) rd; + + /* Add PRIORITY */ + prio = CALC_CAND_PRIO(check->rem_type, 65535, check->comp_id); + pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg, PJ_STUN_ATTR_PRIORITY, + prio); + + /* Add USE-CANDIDATE */ + if (ice->role == PJ_ICE_ROLE_CONTROLLING) { + pj_stun_msg_add_empty_attr(tdata->pool, tdata->msg, + PJ_STUN_ATTR_USE_CANDIDATE); + } + + /* Initiate STUN transaction to send the request */ + status = pj_stun_session_send_msg(ice->tx_sess, PJ_FALSE, + &check->rem_addr, + sizeof(pj_sockaddr_in), tdata); + if (status != PJ_SUCCESS) + return status; + + check->check_state = PJ_ICE_CHECK_STATE_IN_PROGRESS; + return PJ_SUCCESS; +} + /* Start periodic check for the specified checklist */ static pj_status_t start_periodic_check(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); + + /* Set checklist state to Running */ + clist->state = PJ_ICE_CHECKLIST_ST_RUNNING; + + PJ_LOG(4,(ice->obj_name, "Starting checklist periodic check")); + + /* Send STUN Binding request for checks in Waiting list */ + for (i=0; i<clist->count; ++i) { + pj_ice_check *check = &clist->checks[i]; + + if (check->check_state == PJ_ICE_CHECK_STATE_WAITING) { + status = perform_check(ice, clist, i); + if (status != PJ_SUCCESS) + return status; + + ++start_count; + } + } + + /* If we don't have anything in Waiting state, perform check to + * highest priority pair that is in Frozen state. + */ + if (start_count==0) { + for (i=0; i<clist->count; ++i) { + pj_ice_check *check = &clist->checks[i]; + + if (check->check_state == PJ_ICE_CHECK_STATE_FROZEN) { + status = perform_check(ice, clist, i); + if (status != PJ_SUCCESS) + return status; + + ++start_count; + break; + } + } + } + + /* Cannot start check because there's no suitable candidate pair. + * Set checklist state to Completed. + */ + if (start_count==0) { + clist->state = PJ_ICE_CHECKLIST_ST_COMPLETED; + PJ_LOG(4,(ice->obj_name, "Checklist completed")); + } + + return PJ_SUCCESS; } @@ -529,8 +909,7 @@ 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; - unsigned i, comp_id; - pj_str_t fnd; + unsigned i; PJ_ASSERT_RETURN(ice, PJ_EINVAL); @@ -546,16 +925,13 @@ PJ_DEF(pj_status_t) pj_ice_start_check(pj_ice *ice) * component ID, but different foundations, and sets all of their * states to Waiting as well. */ - comp_id = ice->cand[clist->checks[0].local_cand_id].comp_id; - fnd = ice->cand[clist->checks[0].local_cand_id].foundation; - for (i=1; i<clist->count; ++i) { pj_ice_check *cki = &clist->checks[i]; - if (ice->cand[cki->local_cand_id].comp_id != comp_id) + if (cki->comp_id != clist->checks[0].comp_id) continue; - if (pj_strcmp(&ice->cand[cki->local_cand_id].foundation, &fnd)==0) + if (pj_strcmp(&cki->foundation, &clist->checks[0].foundation)==0) continue; clist->checks[i].check_state = PJ_ICE_CHECK_STATE_WAITING; @@ -564,3 +940,170 @@ PJ_DEF(pj_status_t) pj_ice_start_check(pj_ice *ice) /* Start periodic check */ return start_periodic_check(ice, clist); } + + +////////////////////////////////////////////////////////////////////////////// + +static pj_status_t on_stun_send_msg(pj_stun_session *sess, + const void *pkt, + pj_size_t pkt_size, + const pj_sockaddr_t *dst_addr, + unsigned addr_len) +{ + pj_ice *ice = (pj_ice*) pj_stun_session_get_user_data(sess); + return (*ice->cb.on_send_pkt)(ice, pkt, pkt_size, dst_addr, addr_len); +} + + +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, + pj_status_t status, + pj_stun_tx_data *tdata, + const pj_stun_msg *response) +{ + struct req_data *rd = (struct req_data*) tdata->user_data; + pj_ice *ice; + pj_ice_check *check, *valid_check; + pj_ice_checklist *clist; + char buffer[128]; + + 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))); + + if (status != PJ_SUCCESS) { + check->check_state = PJ_ICE_CHECK_STATE_FAILED; + return; + } + + /* The agent MUST check that the source IP address and port of the + * response equals the destination IP address and port that the Binding + * Request was sent to, and that the destination IP address and port of + * the response match the source IP address and port that the Binding + * Request was sent from. + */ + PJ_TODO(CHECK_ICE_RESPONSE_SOURCE_ADDRESS); + + /* 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 + */ + PJ_TODO(CHECK_ICE_RESPONSE_SOURCE_ADDRESS2); + + /* Sets the state of the pair that generated the check to succeeded. */ + check->check_state = PJ_ICE_CHECK_STATE_SUCCEEDED; + + /* 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)); + + /* 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) { + unsigned i; + for (i=0; i<clist->count; ++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) + { + /* 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); + } + } + + } + /* 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 + * signaled in the SDP differs from offerer to answerer), the agent + * MUST change the state for all other Frozen pairs for the first + * component of different media streams (and thus in different check + * lists) but the same foundation, to Waiting. + */ + else if (0) { + PJ_TODO(UNFREEZE_OTHER_COMPONENT_ID); + } + /* If the pair has any other component ID, no other pairs can be + * unfrozen. + */ + else { + PJ_TODO(UNFREEZE_OTHER_COMPONENT_ID1); + } + +} + +static pj_status_t on_stun_rx_indication(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_TODO(SUPPORT_RX_BIND_REQUEST_AS_INDICATION); + return PJ_ENOTSUP; +} + diff --git a/pjnath/src/pjnath/stun_msg.c b/pjnath/src/pjnath/stun_msg.c index b361898f..058ce833 100644 --- a/pjnath/src/pjnath/stun_msg.c +++ b/pjnath/src/pjnath/stun_msg.c @@ -873,6 +873,23 @@ pj_stun_empty_attr_create(pj_pool_t *pool, } +/* + * Create STUN empty attribute and add the attribute to the message. + */ +PJ_DEF(pj_status_t) pj_stun_msg_add_empty_attr( pj_pool_t *pool, + pj_stun_msg *msg, + int attr_type) +{ + pj_stun_empty_attr *attr = NULL; + pj_status_t status; + + status = pj_stun_empty_attr_create(pool, attr_type, &attr); + if (status != PJ_SUCCESS) + return status; + + return pj_stun_msg_add_attr(msg, &attr->hdr); +} + static pj_status_t decode_empty_attr(pj_pool_t *pool, const pj_uint8_t *buf, void **p_attr) |