Skip to content

Commit

Permalink
Add the option to check the source address of ClientHello message on …
Browse files Browse the repository at this point in the history
…DTLS-SRTP (#4261)

* Check the source address of ClientHello message on DTLS-SRTP

* Change the return status

* Add check if source adress is available

* Modification based on comments

- The check will always be performed when ICE is use
- Check the RTP/RTPC address against the candidate when ICE is use

* Modification based on comments

* Modifications based on comment:

- Remove the enum candidate APIs
- Move the source checking for ICE
- Use the remote candidate which has received STUN request or has completed the connectivity check as a valid source
- Don't send respond to STUN request with USE-CANDIDATE when the ICE is completed.

* Missed lock release

* Modification based on comments

* Modification based on comments

- store the valid address to avoid checking with all of remote candidate
- add comment regarding the check source address in SRTP-DTLS is for non-ICE use

* Modification based on comments

- Revert the ICE behavior change since it cause the pjnath-test to fail
- Settle the valid remote address after the ICE is completed

* Modification based on comments

- Add delay before settling to a remote address after ICE is completed
- When ICE is not use, delay the handshake until remote address is available

* Minor modification

* Add PJSUA2 API
  • Loading branch information
trengginas authored Feb 13, 2025
1 parent 1e2f121 commit e533af2
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 1 deletion.
10 changes: 10 additions & 0 deletions pjmedia/include/pjmedia/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -1100,6 +1100,16 @@
# define PJMEDIA_SRTP_DTLS_OSSL_CIPHERS "DEFAULT"
#endif

/**
* Enabled this to check the source address of ClientHello message coming
* from a valid address. See PJ_ICE_SESS_CHECK_SRC_ADDR when ICE is used.
*
* Default value: 0
*/
#ifndef PJMEDIA_SRTP_DTLS_CHECK_HELLO_ADDR
# define PJMEDIA_SRTP_DTLS_CHECK_HELLO_ADDR 0
#endif


/**
* Maximum number of SRTP cryptos.
Expand Down
45 changes: 45 additions & 0 deletions pjmedia/src/pjmedia/transport_srtp_dtls.c
Original file line number Diff line number Diff line change
Expand Up @@ -1361,6 +1361,51 @@ static pj_status_t dtls_on_recv(pjmedia_transport *tp, unsigned idx,
(ds->setup == DTLS_SETUP_ACTPASS || ds->setup == DTLS_SETUP_PASSIVE))
{
pj_status_t status;

#if defined(PJMEDIA_SRTP_DTLS_CHECK_HELLO_ADDR) && \
PJMEDIA_SRTP_DTLS_CHECK_HELLO_ADDR==1

if (!ds->use_ice) {
pjmedia_transport_info info;
pj_sockaddr *src_addr;
pj_sockaddr *rem_addr;

/* Check the source address with the specified remote address from
* the SDP. At this point, if the remote address information
* is not available yet (e.g.: remote SDP has not been received),
* delay the handshake.
* Note: when ICE is used, the source address checking will be
* done in ICE session.
*/
if (!ds->rem_fingerprint.slen) {
PJ_LOG(4, (ds->base.name, "DTLS-SRTP %s delaying the handshake "
"until remote address is ready",
CHANNEL_TO_STRING(idx)));
DTLS_UNLOCK(ds);
return PJ_SUCCESS;
}
pjmedia_transport_get_info(ds->srtp->member_tp, &info);
if (idx == RTP_CHANNEL) {
rem_addr = &ds->rem_addr;
src_addr = &info.src_rtp_name;
} else {
rem_addr = &ds->rem_rtcp;
src_addr = &info.src_rtcp_name;
}

if (pj_sockaddr_cmp(src_addr, rem_addr) != 0) {
char psrc_addr[PJ_INET6_ADDRSTRLEN] = {0};

pj_sockaddr_print(src_addr, psrc_addr, sizeof(psrc_addr), 3);
PJ_LOG(4, (ds->base.name, "DTLS-SRTP %s ignoring %lu bytes, "
"from unrecognized src addr %s", CHANNEL_TO_STRING(idx),
(unsigned long)size, psrc_addr));

DTLS_UNLOCK(ds);
return PJ_SUCCESS;
}
}
#endif
ds->setup = DTLS_SETUP_PASSIVE;
status = ssl_handshake_channel(ds, idx);
if (status != PJ_SUCCESS) {
Expand Down
22 changes: 22 additions & 0 deletions pjnath/include/pjnath/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,28 @@
# define PJ_ICE_NOMINATED_CHECK_DELAY (4*PJ_STUN_RTO_VALUE)
#endif

/**
* Specify whether to check the source address of the incoming messages.
* The source address will be compared to the remote candidate which has
* a completed connectivity check or received a connectivity check.
*
* Default: 1 (yes)
*/
#ifndef PJ_ICE_SESS_CHECK_SRC_ADDR
# define PJ_ICE_SESS_CHECK_SRC_ADDR 1
#endif

/**
* If ICE source address check is enabled, any incoming data is allowed
* from all possible remote candidates until ICE is completed. Use this
* configuration to specify the time to wait before setting the remote
* address to a fix address.
*
* Defalut: 1000ms
*/
#ifndef PJ_ICE_SESS_SET_RADDR_DELAY
# define PJ_ICE_SESS_SET_RADDR_DELAY 1000
#endif

/**
* Minimum interval value to be used for sending STUN keep-alive on the ICE
Expand Down
24 changes: 24 additions & 0 deletions pjnath/include/pjnath/ice_session.h
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,12 @@ typedef struct pj_ice_sess_comp
*/
pj_stun_session *stun_sess;

/**
* The remote candidate checked address. This is expected address that
* the remote going to use.
*/
pj_sockaddr rcand_check_addr;

} pj_ice_sess_comp;


Expand Down Expand Up @@ -317,6 +323,13 @@ struct pj_ice_sess_cand
*/
pj_sockaddr rel_addr;

/**
* Indicate that remote connectivity check has been received or the check
* has been successful for this candidate. It is applicable for
* remote candidate only.
*
*/
pj_bool_t checked;
};


Expand Down Expand Up @@ -672,6 +685,15 @@ typedef struct pj_ice_sess_options
*/
pj_ice_sess_trickle trickle;

/**
* Specify whether to check the source address of the incoming messages.
* The source address will be compared to the remote candidate which has
* a completed connectivity check or received a connectivity check.
*
* Default value is PJ_ICE_SESS_CHECK_SRC_ADDR.
*/
pj_bool_t check_src_addr;

} pj_ice_sess_options;


Expand Down Expand Up @@ -705,6 +727,8 @@ struct pj_ice_sess
pj_timer_entry timer; /**< ICE timer. */
pj_timer_entry timer_end_of_cand; /**< End-of-cand timer. */
pj_ice_sess_cb cb; /**< Callback. */
pj_time_val time_completed; /**< The time when ICE
is completed. */

pj_stun_config stun_cfg; /**< STUN settings. */

Expand Down
57 changes: 56 additions & 1 deletion pjnath/src/pjnath/ice_session.c
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ PJ_DEF(void) pj_ice_sess_options_default(pj_ice_sess_options *opt)
opt->controlled_agent_want_nom_timeout =
ICE_CONTROLLED_AGENT_WAIT_NOMINATION_TIMEOUT;
opt->trickle = PJ_ICE_SESS_TRICKLE_DISABLED;
opt->check_src_addr = PJ_ICE_SESS_CHECK_SRC_ADDR;
}

/*
Expand Down Expand Up @@ -1370,6 +1371,7 @@ static void on_ice_complete(pj_ice_sess *ice, pj_status_t status)
if (!ice->is_complete) {
ice->is_complete = PJ_TRUE;
ice->ice_status = status;
pj_gettimeofday(&ice->time_completed);

pj_timer_heap_cancel_if_active(ice->stun_cfg.timer_heap, &ice->timer,
TIMER_NONE);
Expand Down Expand Up @@ -2883,6 +2885,8 @@ static void on_stun_request_complete(pj_stun_session *stun_sess,
&ice->clist, check),
(check->nominated ? " (nominated)" : " (not nominated)")));

check->rcand->checked = PJ_TRUE;

/* Get the STUN XOR-MAPPED-ADDRESS attribute. */
xaddr = (pj_stun_xor_mapped_addr_attr*)
pj_stun_msg_find_attr(response, PJ_STUN_ATTR_XOR_MAPPED_ADDR,0);
Expand Down Expand Up @@ -3141,7 +3145,6 @@ static pj_status_t on_stun_rx_request(pj_stun_session *sess,
uc_attr = (pj_stun_use_candidate_attr*)
pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_USE_CANDIDATE, 0);


/* Get ICE-CONTROLLING or ICE-CONTROLLED */
role_attr = (pj_stun_uint64_attr*)
pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_ICE_CONTROLLING, 0);
Expand Down Expand Up @@ -3447,6 +3450,8 @@ static void handle_incoming_check(pj_ice_sess *ice,
*/
c->nominated = ((rcheck->use_candidate) || c->nominated);

rcand->checked = PJ_TRUE;

if (c->state == PJ_ICE_SESS_CHECK_STATE_FROZEN ||
c->state == PJ_ICE_SESS_CHECK_STATE_WAITING)
{
Expand Down Expand Up @@ -3734,6 +3739,56 @@ PJ_DEF(pj_status_t) pj_ice_sess_on_rx_pkt(pj_ice_sess *ice,

PJ_RACE_ME(5);

if (ice->opt.check_src_addr) {
pj_bool_t check_addr = PJ_TRUE;
pj_sockaddr *raddr = &comp->rcand_check_addr;

if (!pj_sockaddr_has_addr(raddr)) {
for (i = 0; i < ice->rcand_cnt; ++i) {
if (ice->rcand[i].comp_id == comp_id &&
ice->rcand[i].checked &&
pj_sockaddr_cmp(src_addr, &ice->rcand[i].addr) == 0)
{
/* Before ICE completed, it is still allowed to receive
* data from other candidates.
*/
if (ice->is_complete) {
pj_time_val now;
pj_uint32_t duration;

pj_gettimeofday(&now);
PJ_TIME_VAL_SUB(now, ice->time_completed);
duration = PJ_TIME_VAL_MSEC(now);

if (duration > PJ_ICE_SESS_SET_RADDR_DELAY) {
char psrc_addr[PJ_INET6_ADDRSTRLEN] = {0};

if (pj_sockaddr_has_addr(src_addr)) {
pj_sockaddr_print(src_addr, psrc_addr,
sizeof(psrc_addr), 3);
}
pj_sockaddr_cp(raddr, src_addr);
PJ_LOG(4, (ice->obj_name, "Using %s as valid"
" address for component [%d]",
psrc_addr, comp_id));
}
}
check_addr = PJ_FALSE;
break;
}
}
}
if (check_addr && pj_sockaddr_cmp(src_addr, raddr) != 0) {
char paddr[PJ_INET6_ADDRSTRLEN] = {0};

pj_sockaddr_print(src_addr, paddr, sizeof(paddr), 3);
PJ_LOG(4, (ice->obj_name, "Ignoring incoming message for "
"component [%d] because source addr %s unrecognized",
comp_id, paddr));
return PJ_SUCCESS;
}
}

(*ice->cb.on_rx_data)(ice, comp_id, transport_id, pkt, pkt_size,
src_addr, src_addr_len);
status = PJ_SUCCESS;
Expand Down
9 changes: 9 additions & 0 deletions pjsip/include/pjsua2/account.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,15 @@ struct AccountNatConfig : public PersistentObject
*/
int iceWaitNominationTimeoutMsec;

/**
* Specify whether to check the source address of the incoming messages.
* The source address will be compared to the remote candidate which has
* a completed connectivity check or received a connectivity check.
*
* Default value is PJ_ICE_SESS_CHECK_SRC_ADDR.
*/
unsigned iceCheckSrcAddr;

/**
* Disable RTCP component.
*
Expand Down
5 changes: 5 additions & 0 deletions pjsip/src/pjsua2/account.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,7 @@ void AccountNatConfig::readObject(const ContainerNode &node)
NODE_READ_BOOL ( this_node, iceAggressiveNomination);
NODE_READ_UNSIGNED( this_node, iceNominatedCheckDelayMsec);
NODE_READ_INT ( this_node, iceWaitNominationTimeoutMsec);
NODE_READ_INT ( this_node, iceCheckSrcAddr);
NODE_READ_BOOL ( this_node, iceNoRtcp);
NODE_READ_BOOL ( this_node, iceAlwaysUpdate);
NODE_READ_BOOL ( this_node, turnEnabled);
Expand Down Expand Up @@ -442,6 +443,7 @@ void AccountNatConfig::writeObject(ContainerNode &node) const
NODE_WRITE_BOOL ( this_node, iceAggressiveNomination);
NODE_WRITE_UNSIGNED( this_node, iceNominatedCheckDelayMsec);
NODE_WRITE_INT ( this_node, iceWaitNominationTimeoutMsec);
NODE_WRITE_INT ( this_node, iceCheckSrcAddr);
NODE_WRITE_BOOL ( this_node, iceNoRtcp);
NODE_WRITE_BOOL ( this_node, iceAlwaysUpdate);
NODE_WRITE_BOOL ( this_node, turnEnabled);
Expand Down Expand Up @@ -671,6 +673,7 @@ void AccountConfig::toPj(pjsua_acc_config &ret) const
natConfig.iceNominatedCheckDelayMsec;
ret.ice_cfg.ice_opt.controlled_agent_want_nom_timeout =
natConfig.iceWaitNominationTimeoutMsec;
ret.ice_cfg.ice_opt.check_src_addr = natConfig.iceCheckSrcAddr;
ret.ice_cfg.ice_no_rtcp = natConfig.iceNoRtcp;
ret.ice_cfg.ice_always_update = natConfig.iceAlwaysUpdate;

Expand Down Expand Up @@ -842,6 +845,7 @@ void AccountConfig::fromPj(const pjsua_acc_config &prm,
prm.ice_cfg.ice_opt.nominated_check_delay;
natConfig.iceWaitNominationTimeoutMsec =
prm.ice_cfg.ice_opt.controlled_agent_want_nom_timeout;
natConfig.iceCheckSrcAddr = prm.ice_cfg.ice_opt.check_src_addr;
natConfig.iceNoRtcp = PJ2BOOL(prm.ice_cfg.ice_no_rtcp);
natConfig.iceAlwaysUpdate = PJ2BOOL(prm.ice_cfg.ice_always_update);
} else {
Expand All @@ -856,6 +860,7 @@ void AccountConfig::fromPj(const pjsua_acc_config &prm,
mcfg->ice_opt.nominated_check_delay;
natConfig.iceWaitNominationTimeoutMsec =
mcfg->ice_opt.controlled_agent_want_nom_timeout;
natConfig.iceCheckSrcAddr = mcfg->ice_opt.check_src_addr;
natConfig.iceNoRtcp = PJ2BOOL(mcfg->ice_no_rtcp);
natConfig.iceAlwaysUpdate = PJ2BOOL(mcfg->ice_always_update);
}
Expand Down

0 comments on commit e533af2

Please sign in to comment.