From 236fada43f610b910490f7e0c216ac9aa3d9480c Mon Sep 17 00:00:00 2001 From: Donat Zenichev Date: Fri, 18 Feb 2022 11:44:14 +0100 Subject: [PATCH] core: add support of ICE media options to SDP parser In the current implementation only parsing of ICE candidate attributes is supported, which makes it Possible to work with ICE candidates and parameters specifically related per candidate, but makes it Impossible to work with ICE options of the media stream level (ICE options which have an impact on the whole specific media stream). ICE candidate attributes and ICE media options have different dedication. In order to target this matter, a new linked list is introduced to store ICE media stream options. Additionally, now when parsing media stream's attributes, a couple of new helper functions have been introduced to properly extract and store ICE media options: - extract_ice_option() - add_sdp_ice_opt() The following extraction approaches are supported: - multi-valued ICE options attribute (more than one value per a= header) - one value per attribute (multiple a= headers with ICE options present) Also now the way how a "zeroed" on-hold is detected is improved, in case of the ICE (re)negotiation (when the connection address is equal to '0.0.0.0', the media port is equal to '9' and ice-option 'trickle' is present) the SDP is Not considered as an on-hold case, and it clearly detects it's the ICE (re)negotiation (RFC 8840), which for e.g. leads to a proper work of other side modules' functions (which use SDP parser's data structures), such as 'is_audio_on_hold()' from the textops.so module. --- src/core/parser/sdp/sdp.c | 59 ++++++++++++++++++++------ src/core/parser/sdp/sdp.h | 40 ++++++++++++------ src/core/parser/sdp/sdp_helpr_funcs.c | 60 +++++++++++++++++++++++++++ src/core/parser/sdp/sdp_helpr_funcs.h | 1 + 4 files changed, 136 insertions(+), 24 deletions(-) diff --git a/src/core/parser/sdp/sdp.c b/src/core/parser/sdp/sdp.c index c3c94c37daa..eddb3988db1 100644 --- a/src/core/parser/sdp/sdp.c +++ b/src/core/parser/sdp/sdp.c @@ -38,9 +38,6 @@ #define SDP_USE_PKG_MEM 0 #define SDP_USE_SHM_MEM 1 -#define HOLD_IP_STR "0.0.0.0" -#define HOLD_IP_LEN 7 - /** * Creates and initialize a new sdp_info structure */ @@ -371,7 +368,8 @@ static int parse_sdp_session(str *sdp_body, int session_num, str *cnt_disp, sdp_ sdp_session_cell_t *session; sdp_stream_cell_t *stream; sdp_payload_attr_t *payload_attr; - int parse_payload_attr; + sdp_ice_opt_t *ice_opt; /* media lvl ice options */ + int parse_payload_attr, ice_trickle = 0; str fmtp_string; str remote_candidates = {"a:remote-candidates:", 20}; @@ -582,10 +580,12 @@ static int parse_sdp_session(str *sdp_body, int session_num, str *cnt_disp, sdp_ payload_attr = (sdp_payload_attr_t*)get_sdp_payload4payload(stream, &rtp_payload); set_sdp_payload_fmtp(payload_attr, &fmtp_string); } else if (parse_payload_attr && extract_candidate(&tmpstr1, stream) == 0) { - a1p += 2; + a1p += 2; + } else if (parse_payload_attr && extract_ice_option(&tmpstr1, stream) == 0) { + a1p += 2; } else if (parse_payload_attr && extract_field(&tmpstr1, &stream->remote_candidates, remote_candidates) == 0) { - a1p += 2; + a1p += 2; } else if (extract_accept_types(&tmpstr1, &stream->accept_types) == 0) { a1p = stream->accept_types.s + stream->accept_types.len; } else if (extract_accept_wrapped_types(&tmpstr1, &stream->accept_wrapped_types) == 0) { @@ -606,14 +606,49 @@ static int parse_sdp_session(str *sdp_body, int session_num, str *cnt_disp, sdp_ /* Let's detect if the media is on hold by checking * the good old "0.0.0.0" connection address */ if (!stream->is_on_hold) { + /* But, exclude the cases with ICE trickle re-negotiation (RFC8840), + * which are not the on hold (RFC2543) case actually */ + ice_opt = stream->ice_opt; + while(ice_opt) + { + if (ice_opt->option.len == ICE_OPT_TRICKLE_LEN && + strncmp(ice_opt->option.s, ICE_OPT_TRICKLE_STR, ICE_OPT_TRICKLE_LEN)==0) { + ice_trickle = 1; + ice_opt = NULL; /* break */ + } else { + ice_opt = ice_opt->next; + } + } + + /* SDP stream level */ if (stream->ip_addr.s && stream->ip_addr.len) { - if (stream->ip_addr.len == HOLD_IP_LEN && - strncmp(stream->ip_addr.s, HOLD_IP_STR, HOLD_IP_LEN)==0) - stream->is_on_hold = RFC2543_HOLD; + if (stream->pf == AF_INET && + stream->ip_addr.len == HOLD_IP_LEN && + strncmp(stream->ip_addr.s, HOLD_IP_STR, HOLD_IP_LEN)==0) { + + /* make sure it's not ICE trickle re-negotiation */ + if (ice_trickle && + stream->port.len==HOLD_PORT_ICE_TRICKLE_LEN && /* port=9 */ + strncmp(stream->port.s,HOLD_PORT_ICE_TRICKLE_STR,HOLD_PORT_ICE_TRICKLE_LEN)==0) + LM_DBG("Not a zeroed on-hold (RFC2543), because is ICE re-negotiaion (RFC8840)\n"); + else + stream->is_on_hold = RFC2543_HOLD; + } + + /* SDP session level */ } else if (session->ip_addr.s && session->ip_addr.len) { - if (session->ip_addr.len == HOLD_IP_LEN && - strncmp(session->ip_addr.s, HOLD_IP_STR, HOLD_IP_LEN)==0) - stream->is_on_hold = RFC2543_HOLD; + if (session->pf == AF_INET && + session->ip_addr.len == HOLD_IP_LEN && + strncmp(session->ip_addr.s, HOLD_IP_STR, HOLD_IP_LEN)==0) { + + /* make sure it's not ICE trickle re-negotiation */ + if (ice_trickle && + stream->port.len==HOLD_PORT_ICE_TRICKLE_LEN && /* port=9 */ + strncmp(stream->port.s,HOLD_PORT_ICE_TRICKLE_STR,HOLD_PORT_ICE_TRICKLE_LEN)==0) + LM_DBG("Not a zeroed on-hold (RFC2543), because is ICE re-negotiaion (RFC8840)\n"); + else + stream->is_on_hold = RFC2543_HOLD; + } } } ++stream_num; diff --git a/src/core/parser/sdp/sdp.h b/src/core/parser/sdp/sdp.h index d8c1b15b66b..417cb2b4c01 100644 --- a/src/core/parser/sdp/sdp.h +++ b/src/core/parser/sdp/sdp.h @@ -36,6 +36,16 @@ #define RFC2543_HOLD 1 #define RFC3264_HOLD 2 +#define HOLD_IP_STR "0.0.0.0" +#define HOLD_IP_LEN 7 + +#define HOLD_PORT_ICE_TRICKLE_STR "9" +#define HOLD_PORT_ICE_TRICKLE_LEN 1 + +#define ICE_OPTIONS "a=ice-options:" +#define ICE_OPT_TRICKLE_STR "trickle" +#define ICE_OPT_TRICKLE_LEN 7 + typedef struct sdp_payload_attr { struct sdp_payload_attr *next; int payload_num; /**< payload index inside stream */ @@ -46,15 +56,20 @@ typedef struct sdp_payload_attr { str fmtp_string; } sdp_payload_attr_t; +typedef struct sdp_ice_opt { + struct sdp_ice_opt *next; + str option; /* for e.g. 'trickle', 'rtp+ecn' */ +} sdp_ice_opt_t; + typedef struct sdp_ice_attr { - struct sdp_ice_attr *next; - str foundation; - unsigned int component_id; - str transport; - str connection_addr; - str port; - str candidate_type; - int candidateType; /* ICE_HOST/ICE_SRFLX/ICE_PRFLX/ICE_RELAY/ICE_UNKNOWN */ + struct sdp_ice_attr *next; + str foundation; + unsigned int component_id; + str transport; + str connection_addr; + str port; + str candidate_type; + int candidateType; /* ICE_HOST/ICE_SRFLX/ICE_PRFLX/ICE_RELAY/ICE_UNKNOWN */ } sdp_ice_attr_t; typedef struct sdp_stream_cell { @@ -88,10 +103,11 @@ typedef struct sdp_stream_cell { str raw_stream; /**< fast access to raw stream string */ struct sdp_payload_attr **p_payload_attr; /**< fast access pointers to payloads */ struct sdp_payload_attr *payload_attr; - int ice_attrs_num; /**< number of ICE attrs inside a stream */ - /* add fast access pointers to ice attributes if you need them */ - sdp_ice_attr_t *ice_attr; - str remote_candidates; /**< ICE a:remote-candidates */ + int ice_attrs_num; /* number of candidate ICE attrs inside a stream */ + sdp_ice_attr_t *ice_attr; /* add fast access pointers to ice candidate attributes */ + int ice_opt_num; /* number of media level ICE options inside the stream */ + sdp_ice_opt_t *ice_opt; /* add fast access pointers to media level ICE options */ + str remote_candidates; /* ICE a:remote-candidates */ } sdp_stream_cell_t; typedef struct sdp_session_cell { diff --git a/src/core/parser/sdp/sdp_helpr_funcs.c b/src/core/parser/sdp/sdp_helpr_funcs.c index 9b7a442e790..9fae1f7448f 100644 --- a/src/core/parser/sdp/sdp_helpr_funcs.c +++ b/src/core/parser/sdp/sdp_helpr_funcs.c @@ -279,6 +279,26 @@ static inline sdp_ice_attr_t *add_sdp_ice(sdp_stream_cell_t* _stream) return ice_attr; } +static inline sdp_ice_opt_t *add_sdp_ice_opt(sdp_stream_cell_t* _stream) +{ + sdp_ice_opt_t *ice_opt; + int len; + + len = sizeof(sdp_ice_opt_t); + ice_opt = (sdp_ice_opt_t *)pkg_malloc(len); + if (ice_opt == NULL) { + PKG_MEM_ERROR; + return NULL; + } + memset( ice_opt, 0, len); + + /* Insert the new ice option */ + ice_opt->next = _stream->ice_opt; + _stream->ice_opt = ice_opt; + _stream->ice_opt_num++; + + return ice_opt; +} int extract_candidate(str *body, sdp_stream_cell_t *stream) { @@ -343,6 +363,46 @@ int extract_field(str *body, str *value, str field) return 0; } +int extract_ice_option(str *body, sdp_stream_cell_t *stream) +{ + sdp_ice_opt_t *ice_opt; + + char * ptr_src; + int max_options = 10; /* protection - max options can be listed in one line */ + int length = 0; /* each option length */ + + /* a=ice-options: */ + if ((body->len < 14) || (strncasecmp(body->s, ICE_OPTIONS, 14) != 0)) + return -1; + + ptr_src = body->s + 14; + if (ptr_src == 32) ptr_src++; /* if starts with a space, skip it */ + + /* identify all existing ICE options, if they are listed in one row */ + while (*ptr_src && *ptr_src != '\r' && *ptr_src != '\n' && max_options-->0) + { + while (*ptr_src != 32 && *ptr_src && *ptr_src != '\r' && *ptr_src != '\n') + { + length++; + ptr_src++; + } + + ice_opt = add_sdp_ice_opt(stream); + if (ice_opt == NULL) { + LM_ERR("failed to add ice option\n"); + return -1; + } + + ice_opt->option.s = ptr_src-length; + ice_opt->option.len = length; + trim_len(ice_opt->option.len, ice_opt->option.s, ice_opt->option); + + length = 0; + if (*ptr_src == 32) ptr_src++; /* skip space */ + } + + return 0; +} int extract_ptime(str *body, str *ptime) { diff --git a/src/core/parser/sdp/sdp_helpr_funcs.h b/src/core/parser/sdp/sdp_helpr_funcs.h index 406f57b1659..3c4c7d78a26 100644 --- a/src/core/parser/sdp/sdp_helpr_funcs.h +++ b/src/core/parser/sdp/sdp_helpr_funcs.h @@ -52,6 +52,7 @@ int extract_mediaip(str *body, str *mediaip, int *pf, char *line); int extract_media_attr(str *body, str *mediamedia, str *mediaport, str *mediatransport, str *mediapayload, int *is_rtp); int extract_bwidth(str *body, str *bwtype, str *bwwitdth); int extract_candidate(str *body, sdp_stream_cell_t *stream); +int extract_ice_option(str *body, sdp_stream_cell_t *stream); int extract_sess_version(str* oline, str* sess_version); /* RFC3605 attributes */