diff --git a/include/peer.h b/include/peer.h index 23ce91f2..5069f4e2 120000 --- a/include/peer.h +++ b/include/peer.h @@ -1 +1 @@ -../src/peer.h \ No newline at end of file +#include "../src/peer.h" \ No newline at end of file diff --git a/include/peer_connection.h b/include/peer_connection.h index 15c1e0f4..b5335606 120000 --- a/include/peer_connection.h +++ b/include/peer_connection.h @@ -1 +1 @@ -../src/peer_connection.h \ No newline at end of file +#include "../src/peer_connection.h" \ No newline at end of file diff --git a/include/peer_signaling.h b/include/peer_signaling.h index f783b7be..b5765e1f 120000 --- a/include/peer_signaling.h +++ b/include/peer_signaling.h @@ -1 +1 @@ -../src/peer_signaling.h \ No newline at end of file +#include "../src/peer_signaling.h" \ No newline at end of file diff --git a/src/address.c b/src/address.c index 1618ede5..b16742d0 100644 --- a/src/address.c +++ b/src/address.c @@ -7,9 +7,11 @@ void addr_set_family(Address* addr, int family) { switch (family) { +#if CONFIG_USE_IPV6 case AF_INET6: addr->family = AF_INET6; break; +#endif case AF_INET: default: addr->family = AF_INET; @@ -20,9 +22,11 @@ void addr_set_family(Address* addr, int family) { void addr_set_port(Address* addr, uint16_t port) { addr->port = port; switch (addr->family) { +#if CONFIG_USE_IPV6 case AF_INET6: addr->sin6.sin6_port = htons(port); break; +#endif case AF_INET: default: addr->sin.sin_port = htons(port); @@ -34,18 +38,23 @@ int addr_from_string(const char* buf, Address* addr) { if (inet_pton(AF_INET, buf, &(addr->sin.sin_addr)) == 1) { addr_set_family(addr, AF_INET); return 1; - } else if (inet_pton(AF_INET6, buf, &(addr->sin6.sin6_addr)) == 1) { + } +#if CONFIG_USE_IPV6 + else if (inet_pton(AF_INET6, buf, &(addr->sin6.sin6_addr)) == 1) { addr_set_family(addr, AF_INET6); return 1; } +#endif return 0; } int addr_to_string(const Address* addr, char* buf, size_t len) { - memset(buf, 0, sizeof(len)); + memset(buf, 0, len); switch (addr->family) { +#if CONFIG_USE_IPV6 case AF_INET6: return inet_ntop(AF_INET6, &addr->sin6.sin6_addr, buf, len) != NULL; +#endif case AF_INET: default: return inet_ntop(AF_INET, &addr->sin.sin_addr, buf, len) != NULL; @@ -54,6 +63,19 @@ int addr_to_string(const Address* addr, char* buf, size_t len) { } int addr_equal(const Address* a, const Address* b) { - // TODO - return 1; + if (!a || !b) return 0; + if (a->family != b->family) return 0; + if (a->port != b->port) return 0; + + switch (a->family) { + case AF_INET: + return a->sin.sin_addr.s_addr == b->sin.sin_addr.s_addr; +#if CONFIG_USE_IPV6 + case AF_INET6: + return memcmp(&a->sin6.sin6_addr, &b->sin6.sin6_addr, + sizeof(struct in6_addr)) == 0; +#endif + default: + return 0; + } } diff --git a/src/address.h b/src/address.h index 6f8eb781..283015bc 100644 --- a/src/address.h +++ b/src/address.h @@ -10,12 +10,18 @@ #endif #include +#if CONFIG_USE_IPV6 #define ADDRSTRLEN INET6_ADDRSTRLEN +#else +#define ADDRSTRLEN INET_ADDRSTRLEN +#endif typedef struct Address { uint8_t family; struct sockaddr_in sin; +#if CONFIG_USE_IPV6 struct sockaddr_in6 sin6; +#endif uint16_t port; } Address; diff --git a/src/agent.c b/src/agent.c index 57de5c95..eb40690e 100644 --- a/src/agent.c +++ b/src/agent.c @@ -350,6 +350,70 @@ void agent_process_stun_request(Agent* agent, StunMessage* stun_msg, Address* ad agent_create_binding_response(agent, &msg, addr); agent_socket_send(agent, addr, msg.buf, msg.size); agent->binding_request_time = ports_get_epoch_time(); + + /** + * When there are no candidate pairs (e.g., the browser's mDNS hostname cannot be resolved), + * create a candidate pair from the UDP source address of the STUN request. + * Mark it as FROZEN; later, the standard ICE procedure will select the pair, + * send USE‑CANDIDATE, and establish connectivity. + */ + if (agent->candidate_pairs_num == 0 && agent->local_candidates_count > 0) { + memcpy(&agent->remote_candidates[0].addr, addr, sizeof(Address)); + agent->remote_candidates[0].type = ICE_CANDIDATE_TYPE_HOST; + agent->remote_candidates[0].addr.port = addr->port; + agent->remote_candidates_count = 1; + agent->candidate_pairs[0].local = &agent->local_candidates[0]; + agent->candidate_pairs[0].remote = &agent->remote_candidates[0]; + agent->candidate_pairs[0].state = ICE_CANDIDATE_STATE_FROZEN; + agent->candidate_pairs[0].priority = agent->local_candidates[0].priority; + agent->candidate_pairs[0].conncheck = 0; + agent->candidate_pairs_num = 1; + agent->nominated_pair = &agent->candidate_pairs[0]; + } else { + int found = 0; + + /* Phase A: match source address to existing remote candidates */ + for (int i = 0; i < agent->candidate_pairs_num; i++) { + if (addr_equal(&agent->candidate_pairs[i].remote->addr, addr)) { + agent->candidate_pairs[i].state = ICE_CANDIDATE_STATE_SUCCEEDED; + agent->nominated_pair = &agent->candidate_pairs[i]; + agent->selected_pair = &agent->candidate_pairs[i]; + LOGD("ICE pair %d SUCCEEDED via inbound STUN request", i); + found = 1; + break; + } + } + + /* Phase B: source differs from SDP — create peer-reflexive candidate */ + if (!found + && agent->remote_candidates_count < AGENT_MAX_CANDIDATES + && agent->local_candidates_count > 0) { + IceCandidate *prflx = + &agent->remote_candidates[agent->remote_candidates_count]; + ice_candidate_create(prflx, agent->remote_candidates_count, + ICE_CANDIDATE_TYPE_PRFLX, addr); + agent->remote_candidates_count++; + + for (int j = 0; j < agent->local_candidates_count; j++) { + if (agent->local_candidates[j].addr.family == addr->family + && agent->candidate_pairs_num < AGENT_MAX_CANDIDATE_PAIRS) { + int idx = agent->candidate_pairs_num; + agent->candidate_pairs[idx].local = &agent->local_candidates[j]; + agent->candidate_pairs[idx].remote = prflx; + agent->candidate_pairs[idx].priority = + agent->local_candidates[j].priority + prflx->priority; + agent->candidate_pairs[idx].state = ICE_CANDIDATE_STATE_SUCCEEDED; + agent->candidate_pairs[idx].conncheck = 0; + agent->nominated_pair = &agent->candidate_pairs[idx]; + agent->selected_pair = &agent->candidate_pairs[idx]; + agent->candidate_pairs_num++; + LOGI("ICE: created PRFLX candidate pair from inbound STUN"); + found = 1; + break; + } + } + } + } } break; default: @@ -395,36 +459,46 @@ int agent_recv(Agent* agent, uint8_t* buf, int len) { } void agent_set_remote_description(Agent* agent, char* description) { - /* - a=ice-ufrag:Iexb - a=ice-pwd:IexbSoY7JulyMbjKwISsG9 - a=candidate:1 1 UDP 1 36.231.28.50 38143 typ srflx - */ - int i; - - LOGD("Set remote description:\n%s", description); - - char* line_start = description; - char* line_end = NULL; - - while ((line_end = strstr(line_start, "\r\n")) != NULL) { - if (strncmp(line_start, "a=ice-ufrag:", strlen("a=ice-ufrag:")) == 0) { - strncpy(agent->remote_ufrag, line_start + strlen("a=ice-ufrag:"), line_end - line_start - strlen("a=ice-ufrag:")); - - } else if (strncmp(line_start, "a=ice-pwd:", strlen("a=ice-pwd:")) == 0) { - strncpy(agent->remote_upwd, line_start + strlen("a=ice-pwd:"), line_end - line_start - strlen("a=ice-pwd:")); - - } else if (strncmp(line_start, "a=candidate:", strlen("a=candidate:")) == 0) { - if (ice_candidate_from_description(&agent->remote_candidates[agent->remote_candidates_count], line_start, line_end) == 0) { - for (i = 0; i < agent->remote_candidates_count; i++) { - if (strcmp(agent->remote_candidates[i].foundation, agent->remote_candidates[agent->remote_candidates_count].foundation) == 0) { - break; - } - } - if (i == agent->remote_candidates_count) { - agent->remote_candidates_count++; + /* + a=ice-ufrag:Iexb + a=ice-pwd:IexbSoY7JulyMbjKwISsG9 + a=candidate:1 1 UDP 1 36.231.28.50 38143 typ srflx + */ + int i; + + LOGD("Set remote description:\n%s", description); + + char* line_start = description; + char* line_end = NULL; + agent->remote_ufrag[0] = '\0'; + agent->remote_upwd[0] = '\0'; + while ((line_end = strstr(line_start, "\r\n")) != NULL) { + if (strncmp(line_start, "a=ice-ufrag:", sizeof("a=ice-ufrag:") - 1) == 0) { + line_start += sizeof("a=ice-ufrag:") - 1; + size_t len = line_end - line_start; + len = len >= sizeof(agent->remote_ufrag) ? (sizeof(agent->remote_ufrag) - 1) : len; + strncpy(agent->remote_ufrag, line_start, len); + agent->remote_ufrag[len] = '\0'; + } else + if (strncmp(line_start, "a=ice-pwd:", sizeof("a=ice-pwd:") - 1) == 0) { + line_start += sizeof("a=ice-pwd:") - 1; + size_t len = line_end - line_start; + len = len >= sizeof(agent->remote_upwd) ? (sizeof(agent->remote_upwd) - 1) : len; + strncpy(agent->remote_upwd, line_start, len); + agent->remote_upwd[len] = '\0'; + } else + if (strncmp(line_start, "a=candidate:", sizeof("a=candidate:") - 1) == 0) { + + if (ice_candidate_from_description(&agent->remote_candidates[agent->remote_candidates_count], line_start, line_end) == 0) { + for (i = 0; i < agent->remote_candidates_count; i++) { + if (strcmp(agent->remote_candidates[i].foundation, agent->remote_candidates[agent->remote_candidates_count].foundation) == 0) { + break; + } + } + if (i == agent->remote_candidates_count) { + agent->remote_candidates_count++; + } } - } } line_start = line_end + 2; @@ -456,6 +530,21 @@ int agent_connectivity_check(Agent* agent) { uint8_t buf[1400]; StunMessage msg; + if (agent->nominated_pair == NULL) { + /** + * No candidate pairs yet (all mDNS attempts failed), + * only receive and process STUN requests actively sent by the browser. + */ + agent_recv(agent, buf, sizeof(buf)); + return -1; + } + + /* Handle pair already marked SUCCEEDED by agent_process_stun_request */ + if (agent->nominated_pair->state == ICE_CANDIDATE_STATE_SUCCEEDED) { + agent->selected_pair = agent->nominated_pair; + return 0; + } + if (agent->nominated_pair->state != ICE_CANDIDATE_STATE_INPROGRESS) { LOGI("nominated pair is not in progress"); return -1; diff --git a/src/config.h b/src/config.h index 94905a83..c6e1a65c 100644 --- a/src/config.h +++ b/src/config.h @@ -7,6 +7,10 @@ #define SCTP_MTU (1200) #define CONFIG_MTU (1300) +// Advertised receiver window. Incoming DATA is dispatched to the user callback +// immediately without buffering, so the window is always fully available. +#define SCTP_LOCAL_RWND (0x100000) + #ifndef CONFIG_USE_LWIP #define CONFIG_USE_LWIP 0 #endif @@ -49,6 +53,15 @@ #define CONFIG_TLS_READ_TIMEOUT 3000 #endif +#ifndef CONFIG_CHECKING_TIMEOUT +// 默认的 PEER_CONNECTION_CHECKING 状态超时为 15S +#define CONFIG_CHECKING_TIMEOUT 15000 +#endif + +#ifndef CONFIG_DTLS_HANDSHAKE_TIMEOUT +#define CONFIG_DTLS_HANDSHAKE_TIMEOUT 30000 +#endif + #ifndef CONFIG_KEEPALIVE_TIMEOUT #define CONFIG_KEEPALIVE_TIMEOUT 10000 #endif @@ -65,7 +78,7 @@ // empty will use first active interface #define CONFIG_IFACE_PREFIX "" -// #define LOG_LEVEL LEVEL_DEBUG +// #define LIBPEER_LOG_LEVEL LIBPEER_LOG_LEVEL_DEBUG #ifndef LOG_REDIRECT #define LOG_REDIRECT 0 #endif diff --git a/src/dtls_srtp.c b/src/dtls_srtp.c index dd546169..e4dd9d75 100644 --- a/src/dtls_srtp.c +++ b/src/dtls_srtp.c @@ -15,6 +15,11 @@ #include "socket.h" #include "utils.h" +/* 当未启用共享熵源时,LIBPEER_ENTROPY_CTX 回退到原有的结构体字段指针 */ +#ifndef LIBPEER_ENTROPY_CTX +#define LIBPEER_ENTROPY_CTX (&dtls_srtp->entropy) +#endif + int dtls_srtp_udp_send(void* ctx, const uint8_t* buf, size_t len) { DtlsSrtp* dtls_srtp = (DtlsSrtp*)ctx; UdpSocket* udp_socket = (UdpSocket*)dtls_srtp->user_data; @@ -85,7 +90,7 @@ static int dtls_srtp_selfsign_cert(DtlsSrtp* dtls_srtp) { return -1; } - mbedtls_ctr_drbg_seed(&dtls_srtp->ctr_drbg, mbedtls_entropy_func, &dtls_srtp->entropy, (const unsigned char*)pers, strlen(pers)); + mbedtls_ctr_drbg_seed(&dtls_srtp->ctr_drbg, mbedtls_entropy_func, LIBPEER_ENTROPY_CTX, (const unsigned char*)pers, strlen(pers)); #if CONFIG_DTLS_USE_ECDSA mbedtls_pk_setup(&dtls_srtp->pkey, mbedtls_pk_info_from_type(MBEDTLS_PK_ECKEY)); @@ -147,10 +152,10 @@ static void dtls_srtp_debug(void* ctx, int level, const char* file, int line, co int dtls_srtp_init(DtlsSrtp* dtls_srtp, DtlsSrtpRole role, void* user_data) { static const mbedtls_ssl_srtp_profile default_profiles[] = { + /* AEAD_AES_128_GCM (RFC 7714) 优先,SRTP_AES128_CM_HMAC_SHA1_80 (RFC 5764) 回退。 + * 板端作 DTLS client 时此顺序即偏好序;作 server 时选择由客户端优先级决定 */ + MBEDTLS_TLS_SRTP_AEAD_AES_128_GCM, MBEDTLS_TLS_SRTP_AES128_CM_HMAC_SHA1_80, - MBEDTLS_TLS_SRTP_AES128_CM_HMAC_SHA1_32, - MBEDTLS_TLS_SRTP_NULL_HMAC_SHA1_80, - MBEDTLS_TLS_SRTP_NULL_HMAC_SHA1_32, MBEDTLS_TLS_SRTP_UNSET}; dtls_srtp->role = role; @@ -164,7 +169,9 @@ int dtls_srtp_init(DtlsSrtp* dtls_srtp, DtlsSrtpRole role, void* user_data) { mbedtls_x509_crt_init(&dtls_srtp->cert); mbedtls_pk_init(&dtls_srtp->pkey); +#ifndef LIBPEER_USE_SHARED_ENTROPY mbedtls_entropy_init(&dtls_srtp->entropy); +#endif mbedtls_ctr_drbg_init(&dtls_srtp->ctr_drbg); #if CONFIG_MBEDTLS_DEBUG mbedtls_debug_set_threshold(3); @@ -207,7 +214,10 @@ int dtls_srtp_init(DtlsSrtp* dtls_srtp, DtlsSrtpRole role, void* user_data) { LOGD("local fingerprint: %s", dtls_srtp->local_fingerprint); - mbedtls_ssl_conf_dtls_srtp_protection_profiles(&dtls_srtp->conf, default_profiles); + if (mbedtls_ssl_conf_dtls_srtp_protection_profiles(&dtls_srtp->conf, default_profiles) != 0) { + LOGE("mbedtls_ssl_conf_dtls_srtp_protection_profiles failed"); + return -1; + } mbedtls_ssl_conf_srtp_mki_value_supported(&dtls_srtp->conf, MBEDTLS_SSL_DTLS_SRTP_MKI_UNSUPPORTED); @@ -215,6 +225,9 @@ int dtls_srtp_init(DtlsSrtp* dtls_srtp, DtlsSrtpRole role, void* user_data) { mbedtls_ssl_setup(&dtls_srtp->ssl, &dtls_srtp->conf); + dtls_srtp->srtp_in = NULL; + dtls_srtp->srtp_out = NULL; + return 0; } @@ -224,7 +237,9 @@ void dtls_srtp_deinit(DtlsSrtp* dtls_srtp) { mbedtls_x509_crt_free(&dtls_srtp->cert); mbedtls_pk_free(&dtls_srtp->pkey); +#ifndef LIBPEER_USE_SHARED_ENTROPY mbedtls_entropy_free(&dtls_srtp->entropy); +#endif mbedtls_ctr_drbg_free(&dtls_srtp->ctr_drbg); if (dtls_srtp->role == DTLS_SRTP_ROLE_SERVER) { @@ -241,9 +256,19 @@ static int dtls_srtp_key_derivation(DtlsSrtp* dtls_srtp, const unsigned char* ma int ret; const char* dtls_srtp_label = "EXTRACTOR-dtls_srtp"; uint8_t key_material[DTLS_SRTP_KEY_MATERIAL_LENGTH]; + + /* 双 profile:密钥/盐长度取决于协商结果。use_srtp 扩展在 Hello 阶段已定, + * Finished 阶段的密钥导出回调触发时结果可读 */ + mbedtls_dtls_srtp_info derivation_negotiation_result; + mbedtls_ssl_get_dtls_srtp_negotiation_result(&dtls_srtp->ssl, &derivation_negotiation_result); + const int is_gcm = + (derivation_negotiation_result.chosen_dtls_srtp_profile == MBEDTLS_TLS_SRTP_AEAD_AES_128_GCM); + const size_t salt_len = is_gcm ? (size_t)SRTP_MASTER_SALT_LENGTH_GCM : (size_t)SRTP_MASTER_SALT_LENGTH; + const size_t material_len = is_gcm ? (size_t)DTLS_SRTP_KEY_MATERIAL_LENGTH_GCM : (size_t)DTLS_SRTP_KEY_MATERIAL_LENGTH; + // Export keying material if ((ret = mbedtls_ssl_tls_prf(tls_prf_type, master_secret, secret_len, dtls_srtp_label, - randbytes, randbytes_len, key_material, sizeof(key_material))) != 0) { + randbytes, randbytes_len, key_material, material_len)) != 0) { LOGE("mbedtls_ssl_tls_prf failed(%d)", ret); return ret; } @@ -270,10 +295,10 @@ static int dtls_srtp_key_derivation(DtlsSrtp* dtls_srtp, const unsigned char* ma printf("\n"); #endif - const uint8_t* client_key = key_material; - const uint8_t* server_key = client_key + SRTP_MASTER_KEY_LENGTH; - const uint8_t* client_salt = server_key + SRTP_MASTER_KEY_LENGTH; - const uint8_t* server_salt = client_salt + SRTP_MASTER_SALT_LENGTH; + uint8_t* client_key = key_material; + uint8_t* server_key = client_key + SRTP_MASTER_KEY_LENGTH; + uint8_t* client_salt = server_key + SRTP_MASTER_KEY_LENGTH; + uint8_t* server_salt = client_salt + salt_len; uint8_t *local_key, *remote_key, *local_salt, *remote_salt; if (dtls_srtp->role == DTLS_SRTP_ROLE_SERVER) { local_key = server_key; @@ -290,18 +315,24 @@ static int dtls_srtp_key_derivation(DtlsSrtp* dtls_srtp, const unsigned char* ma memset(&dtls_srtp->remote_policy, 0, sizeof(dtls_srtp->remote_policy)); - srtp_crypto_policy_set_rtp_default(&dtls_srtp->remote_policy.rtp); - srtp_crypto_policy_set_rtcp_default(&dtls_srtp->remote_policy.rtcp); + if (is_gcm) { + /* RFC 7714:RTP/SRTCP 统一 16B tag(SRTCP 强制 16B) */ + srtp_crypto_policy_set_aes_gcm_128_16_auth(&dtls_srtp->remote_policy.rtp); + srtp_crypto_policy_set_aes_gcm_128_16_auth(&dtls_srtp->remote_policy.rtcp); + } else { + srtp_crypto_policy_set_rtp_default(&dtls_srtp->remote_policy.rtp); + srtp_crypto_policy_set_rtcp_default(&dtls_srtp->remote_policy.rtcp); + } memcpy(dtls_srtp->remote_policy_key, remote_key, SRTP_MASTER_KEY_LENGTH); - memcpy(dtls_srtp->remote_policy_key + SRTP_MASTER_KEY_LENGTH, remote_salt, SRTP_MASTER_SALT_LENGTH); + memcpy(dtls_srtp->remote_policy_key + SRTP_MASTER_KEY_LENGTH, remote_salt, salt_len); dtls_srtp->remote_policy.ssrc.type = ssrc_any_inbound; dtls_srtp->remote_policy.key = dtls_srtp->remote_policy_key; dtls_srtp->remote_policy.next = NULL; if (srtp_create(&dtls_srtp->srtp_in, &dtls_srtp->remote_policy) != srtp_err_status_ok) { - LOGD("Error creating inbound SRTP session for component"); + LOGE("Error creating inbound SRTP session for component"); return -1; } @@ -310,11 +341,16 @@ static int dtls_srtp_key_derivation(DtlsSrtp* dtls_srtp, const unsigned char* ma // derive outbounds keys memset(&dtls_srtp->local_policy, 0, sizeof(dtls_srtp->local_policy)); - srtp_crypto_policy_set_rtp_default(&dtls_srtp->local_policy.rtp); - srtp_crypto_policy_set_rtcp_default(&dtls_srtp->local_policy.rtcp); + if (is_gcm) { + srtp_crypto_policy_set_aes_gcm_128_16_auth(&dtls_srtp->local_policy.rtp); + srtp_crypto_policy_set_aes_gcm_128_16_auth(&dtls_srtp->local_policy.rtcp); + } else { + srtp_crypto_policy_set_rtp_default(&dtls_srtp->local_policy.rtp); + srtp_crypto_policy_set_rtcp_default(&dtls_srtp->local_policy.rtcp); + } memcpy(dtls_srtp->local_policy_key, local_key, SRTP_MASTER_KEY_LENGTH); - memcpy(dtls_srtp->local_policy_key + SRTP_MASTER_KEY_LENGTH, local_salt, SRTP_MASTER_SALT_LENGTH); + memcpy(dtls_srtp->local_policy_key + SRTP_MASTER_KEY_LENGTH, local_salt, salt_len); dtls_srtp->local_policy.ssrc.type = ssrc_any_outbound; dtls_srtp->local_policy.key = dtls_srtp->local_policy_key; @@ -461,6 +497,19 @@ int dtls_srtp_handshake(DtlsSrtp* dtls_srtp, Address* addr) { mbedtls_dtls_srtp_info dtls_srtp_negotiation_result; mbedtls_ssl_get_dtls_srtp_negotiation_result(&dtls_srtp->ssl, &dtls_srtp_negotiation_result); + if (ret == 0) { + if (dtls_srtp_negotiation_result.chosen_dtls_srtp_profile == MBEDTLS_TLS_SRTP_AEAD_AES_128_GCM) { + LOGI("DTLS-SRTP negotiated profile: AEAD_AES_128_GCM (RFC 7714)"); + } else if (dtls_srtp_negotiation_result.chosen_dtls_srtp_profile == MBEDTLS_TLS_SRTP_AES128_CM_HMAC_SHA1_80) { + LOGI("DTLS-SRTP negotiated profile: SRTP_AES128_CM_HMAC_SHA1_80 (fallback)"); + } else { + /* 无共同 use_srtp profile(chosen==UNSET 等):不得带未协商密钥继续 */ + LOGE("DTLS-SRTP no profile negotiated (chosen=0x%04x)", + dtls_srtp_negotiation_result.chosen_dtls_srtp_profile); + return -1; + } + } + return ret; } @@ -468,6 +517,8 @@ void dtls_srtp_reset_session(DtlsSrtp* dtls_srtp) { if (dtls_srtp->state == DTLS_SRTP_STATE_CONNECTED) { srtp_dealloc(dtls_srtp->srtp_in); srtp_dealloc(dtls_srtp->srtp_out); + dtls_srtp->srtp_in = NULL; + dtls_srtp->srtp_out = NULL; mbedtls_ssl_session_reset(&dtls_srtp->ssl); } @@ -506,18 +557,30 @@ int dtls_srtp_probe(uint8_t* buf) { return (buf[0] == 0x17); } -void dtls_srtp_decrypt_rtp_packet(DtlsSrtp* dtls_srtp, uint8_t* packet, int* bytes) { - srtp_unprotect(dtls_srtp->srtp_in, packet, bytes); +int dtls_srtp_decrypt_rtp_packet(DtlsSrtp* dtls_srtp, uint8_t* packet, int* bytes) { + if (dtls_srtp->srtp_in) { + return (srtp_unprotect(dtls_srtp->srtp_in, packet, bytes) == srtp_err_status_ok) ? 0 : -1; + } + return 0; } -void dtls_srtp_decrypt_rtcp_packet(DtlsSrtp* dtls_srtp, uint8_t* packet, int* bytes) { - srtp_unprotect_rtcp(dtls_srtp->srtp_in, packet, bytes); +int dtls_srtp_decrypt_rtcp_packet(DtlsSrtp* dtls_srtp, uint8_t* packet, int* bytes) { + if (dtls_srtp->srtp_in) { + return (srtp_unprotect_rtcp(dtls_srtp->srtp_in, packet, bytes) == srtp_err_status_ok) ? 0 : -1; + } + return 0; } -void dtls_srtp_encrypt_rtp_packet(DtlsSrtp* dtls_srtp, uint8_t* packet, int* bytes) { - srtp_protect(dtls_srtp->srtp_out, packet, bytes); +int dtls_srtp_encrypt_rtp_packet(DtlsSrtp* dtls_srtp, uint8_t* packet, int* bytes) { + if (dtls_srtp->srtp_out) { + return (srtp_protect(dtls_srtp->srtp_out, packet, bytes) == srtp_err_status_ok) ? 0 : -1; + } + return 0; } -void dtls_srtp_encrypt_rctp_packet(DtlsSrtp* dtls_srtp, uint8_t* packet, int* bytes) { - srtp_protect_rtcp(dtls_srtp->srtp_out, packet, bytes); +int dtls_srtp_encrypt_rctp_packet(DtlsSrtp* dtls_srtp, uint8_t* packet, int* bytes) { + if (dtls_srtp->srtp_out) { + return (srtp_protect_rtcp(dtls_srtp->srtp_out, packet, bytes) == srtp_err_status_ok) ? 0 : -1; + } + return 0; } diff --git a/src/dtls_srtp.h b/src/dtls_srtp.h index 09d24017..2789a059 100644 --- a/src/dtls_srtp.h +++ b/src/dtls_srtp.h @@ -13,13 +13,17 @@ #include #include -#include +#include #include "address.h" #define SRTP_MASTER_KEY_LENGTH 16 +/* 双 profile:CM (RFC 5764) 盐(salt) 14B,GCM (RFC 7714) 盐 12B。 + * 无后缀宏为 CM 值(= 缓冲上限),key 缓冲按上限 16+14=30B 容纳两种 profile */ #define SRTP_MASTER_SALT_LENGTH 14 +#define SRTP_MASTER_SALT_LENGTH_GCM SRTP_AEAD_SALT_LEN /* 12, 来自 libsrtp srtp.h */ #define DTLS_SRTP_KEY_MATERIAL_LENGTH 60 +#define DTLS_SRTP_KEY_MATERIAL_LENGTH_GCM (2 * (SRTP_MASTER_KEY_LENGTH + SRTP_MASTER_SALT_LENGTH_GCM)) #define DTLS_SRTP_FINGERPRINT_LENGTH 160 typedef enum DtlsSrtpRole { @@ -44,7 +48,9 @@ typedef struct DtlsSrtp { mbedtls_ssl_cookie_ctx cookie_ctx; mbedtls_x509_crt cert; mbedtls_pk_context pkey; +#ifndef LIBPEER_USE_SHARED_ENTROPY mbedtls_entropy_context entropy; +#endif mbedtls_ctr_drbg_context ctr_drbg; // SRTP @@ -89,12 +95,14 @@ void dtls_srtp_sctp_to_dtls(DtlsSrtp* dtls_srtp, uint8_t* packet, int bytes); int dtls_srtp_probe(uint8_t* buf); -void dtls_srtp_decrypt_rtp_packet(DtlsSrtp* dtls_srtp, uint8_t* packet, int* bytes); +/* 返回 0 成功,-1 加密/鉴权失败(调用方必须丢弃该包,不得送解码器/网络)。 + * srtp 会话未建立时返回 0(透传,保持既有语义) */ +int dtls_srtp_decrypt_rtp_packet(DtlsSrtp* dtls_srtp, uint8_t* packet, int* bytes); -void dtls_srtp_decrypt_rtcp_packet(DtlsSrtp* dtls_srtp, uint8_t* packet, int* bytes); +int dtls_srtp_decrypt_rtcp_packet(DtlsSrtp* dtls_srtp, uint8_t* packet, int* bytes); -void dtls_srtp_encrypt_rtp_packet(DtlsSrtp* dtls_srtp, uint8_t* packet, int* bytes); +int dtls_srtp_encrypt_rtp_packet(DtlsSrtp* dtls_srtp, uint8_t* packet, int* bytes); -void dtls_srtp_encrypt_rctp_packet(DtlsSrtp* dtls_srtp, uint8_t* packet, int* bytes); +int dtls_srtp_encrypt_rctp_packet(DtlsSrtp* dtls_srtp, uint8_t* packet, int* bytes); #endif // DTLS_SRTP_H_ diff --git a/src/ice.c b/src/ice.c index 756e961f..cea37844 100644 --- a/src/ice.c +++ b/src/ice.c @@ -15,6 +15,8 @@ static uint8_t ice_candidate_type_preference(IceCandidateType type) { switch (type) { case ICE_CANDIDATE_TYPE_HOST: return 126; + case ICE_CANDIDATE_TYPE_PRFLX: + return 110; /* RFC 5245: peer reflexive between HOST(126) and SRFLX(100) */ case ICE_CANDIDATE_TYPE_SRFLX: return 100; case ICE_CANDIDATE_TYPE_RELAY: @@ -60,6 +62,9 @@ void ice_candidate_to_description(IceCandidate* candidate, char* description, in case ICE_CANDIDATE_TYPE_SRFLX: snprintf(typ_raddr, sizeof(typ_raddr), "srflx raddr %s rport %d", addr_string, candidate->raddr.port); break; + case ICE_CANDIDATE_TYPE_PRFLX: + snprintf(typ_raddr, sizeof(typ_raddr), "prflx"); + break; case ICE_CANDIDATE_TYPE_RELAY: snprintf(typ_raddr, sizeof(typ_raddr), "relay raddr %s rport %d", addr_string, candidate->raddr.port); default: @@ -81,15 +86,18 @@ int ice_candidate_from_description(IceCandidate* candidate, char* description, c char* candidate_start = description; uint32_t port; char type[16]; - char addrstring[ADDRSTRLEN]; + char addrstring[256]; // 局域网会使用 mDNS 主机名 if (strncmp("a=", candidate_start, strlen("a=")) == 0) { candidate_start += strlen("a="); } candidate_start += strlen("candidate:"); - // a=candidate:448736988 1 udp 2122260223 172.17.0.1 49250 typ host generation 0 network-id 1 network-cost 50 - // a=candidate:udpcandidate 1 udp 120 192.168.1.102 8000 typ host +// a=candidate:448736988 1 udp 2122260223 172.17.0.1 49250 typ host generation 0 network-id 1 network-cost 50 +// a=candidate:3989800143 1 udp 2113937151 48c82aba-d349-4784-a733-404f193524f5.local 64630 typ host generation 0 network-cost 999 +// a=candidate:udpcandidate 1 udp 120 192.168.1.102 8000 typ host +// a=candidate:1623718428 1 udp 2113937151 10.65.209.95 55476 typ host generation 0 network-cost 999 +// a=candidate:69123048 1 udp 2113939711 240e:469:246:4066:6c3e:6cff:fefd:58dd 43249 typ host generation 0 network-cost 999 if (sscanf(candidate_start, "%s %d %s %" PRIu32 " %s %" PRIu32 " typ %s", candidate->foundation, &candidate->component, @@ -120,8 +128,19 @@ int ice_candidate_from_description(IceCandidate* candidate, char* description, c addr_set_port(&candidate->addr, port); - if (strstr(addrstring, "local") != NULL) { - if (mdns_resolve_addr(addrstring, &candidate->addr) == 0) { + if (strstr(addrstring, ".local") != NULL) { + /** + * In a LAN environment, Chrome assigns a local domain name like uuid.local. + * If the device is a Wi‑Fi AP and the PC is directly connected via Wi‑Fi, + * while the PC's wired network interface is also connected to another network, + * an mDNS query will return the IP of the wired interface. + * In this case, mdns_resolve_addr() will have 3 retries × 5 receive attempts × 1s timeout + * = up to 15 seconds, and it will definitely fail. + */ +#if CONFIG_USE_MDNS + if (mdns_resolve_addr(addrstring, &candidate->addr) == 0) +#endif + { LOGW("Failed to resolve mDNS address"); return -1; } diff --git a/src/peer.c b/src/peer.c index f24bf9df..7d4cd98c 100644 --- a/src/peer.c +++ b/src/peer.c @@ -1,4 +1,4 @@ -#include +#include #include #include #include diff --git a/src/peer_connection.c b/src/peer_connection.c index c763e806..82fc37c2 100644 --- a/src/peer_connection.c +++ b/src/peer_connection.c @@ -19,6 +19,31 @@ pc->state = curr_state; \ } +// Max datagrams drained per peer_connection_loop() call. A single video frame +// generates dozens of SACKs; draining one per call throttles the peer to the +// loop period and overflows the UDP receive queue. +#define PEER_CONNECTION_RECV_BURST 16 + +// ================= NACK 重传缓存 (RFC 4585 generic NACK) ================= +// 只缓存"加密后"的 RTP 包原样重发:明文重加密会被 libsrtp 发送侧 rdbx 以 +// pkt_idx_old 拒绝,且同 key+index 重加密违反 SRTP 使用规范;重传包保持原 +// seq/ts/ssrc/auth-tag,接收侧重放窗口对真丢失包放行、对竞态重复包丢弃。 +// Ring 为 struct PeerConnection 末尾的柔性数组,槽位数运行时由 +// config.nack_ring_packets 决定:0 = 禁用 (不缓存、不重传、不占内存), +// 非 0 必须为 2 的幂 (peer_connection_create 校验,非法直接失败)。 + +#define PEER_CONNECTION_NACK_SLOT_SIZE (CONFIG_MTU + 16) /* 密文 + SRTP auth tag 余量:GCM 16B tag 下满长包(1300+16)恰好容纳 */ + +/* sendto 失败(典型 ENOBUFS)后退避重试的间隔:让出 CPU 给 TCPIP/wlan 任务 + * 排空 TX skb 池。仅失败路径付出该延迟,成功路径零开销。 */ +#define PEER_CONNECTION_SEND_RETRY_DELAY_MS 2 + +typedef struct { + uint16_t seq; + uint16_t len; /* len==0 视为空槽 */ + uint8_t data[PEER_CONNECTION_NACK_SLOT_SIZE]; +} nack_ring_entry_t; + struct PeerConnection { PeerConfiguration config; PeerConnectionState state; @@ -45,12 +70,79 @@ struct PeerConnection { uint32_t remote_assrc; uint32_t remote_vssrc; + + uint32_t handshake_start_time; + + uint32_t nack_retransmits; /* NACK 重传包计数 (config.nack_ring_packets==0 时恒 0) */ + uint32_t srtp_auth_failures; /* SRTP/SRTCP 入向鉴权失败丢包计数 */ + + /* RTP 出包统计:成功投递到 socket 的包数与最终丢弃(重试后仍失败)的包数。 + * sendto 失败(典型 ENOBUFS)在 rtp_encoder_encode_* 返回 0 的掩盖下 + * 对上层不可见,必须在此收敛点显式计数。 */ + uint32_t rtp_packets_sent; + uint32_t rtp_send_failures; + + /* 拥塞帧放弃:FU-A 帧丢一片整帧即废。视频轨本帧首次发送失败后, + * 同 timestamp 的剩余分片直接丢弃(等待对端 PLI → IDR 重发恢复)。 */ + uint32_t last_video_ts; + int video_frame_aborted; + + /* 柔性数组 (GCC 零长数组扩展, 仓库既有写法, 见 async_delegation.c): + * NACK 重传 ring, 槽位数 = config.nack_ring_packets (0 时分配 0 字节), + * 必须是 struct 最后一个成员, 随 create 一次性 calloc 分配。 */ + nack_ring_entry_t nack_ring[0]; }; static void peer_connection_outgoing_rtp_packet(uint8_t* data, size_t size, void* user_data) { PeerConnection* pc = (PeerConnection*)user_data; - dtls_srtp_encrypt_rtp_packet(&pc->dtls_srtp, data, (int*)&size); - agent_send(&pc->agent, data, size); + if (dtls_srtp_encrypt_rtp_packet(&pc->dtls_srtp, data, (int*)&size) != 0) { + /* 加密失败不发送:对端必然鉴权丢弃,缓存与发送都无意义 */ + LOGW("srtp_protect failed, drop outbound RTP"); + return; + } + + int is_video = ((data[1] & 0x7F) == PT_H264); + + /* 缓存密文(含 auth tag)供 NACK 原样重发;只缓存视频轨。 + * 必须在下方的帧放弃判断之前:本帧后续分片被丢弃时,已成功发送的 + * 分片仍可通过 NACK 补发(虽然整帧已不可解,重传主要用于对端 PLI 前 + * 的真实丢包场景,缓存成本极低) */ + if (pc->config.nack_ring_packets > 0 && is_video && + size <= PEER_CONNECTION_NACK_SLOT_SIZE) { + uint16_t seq = ((uint16_t)data[2] << 8) | data[3]; + nack_ring_entry_t* e = &pc->nack_ring[seq & (pc->config.nack_ring_packets - 1)]; + e->seq = seq; + e->len = (uint16_t)size; + memcpy(e->data, data, size); + } + + if (is_video) { + uint32_t ts = ntohl(((RtpHeader*)data)->timestamp); + if (ts != pc->last_video_ts) { + /* 新帧开始,清除上一帧的放弃标志 */ + pc->last_video_ts = ts; + pc->video_frame_aborted = 0; + } + /* FU-A 帧丢任意一片整帧即废:已放弃的帧剩余分片不再发送, + * 避免在拥塞链路上浪费带宽并拖长推流线程持锁时间 */ + if (pc->video_frame_aborted) { + return; + } + } + + if (agent_send(&pc->agent, data, size) < 0) { + /* sendto 失败(典型 ENOBUFS):退避一次让 wlan 排空 TX skb 池再重试, + * 仍失败即丢弃。失败路径才付出延迟,最多一次、2ms 封顶,不累积 */ + ports_sleep_ms(PEER_CONNECTION_SEND_RETRY_DELAY_MS); + if (agent_send(&pc->agent, data, size) < 0) { + pc->rtp_send_failures++; + if (is_video) { + pc->video_frame_aborted = 1; + } + return; + } + } + pc->rtp_packets_sent++; } static int peer_connection_dtls_srtp_recv(void* ctx, unsigned char* buf, size_t len) { @@ -84,6 +176,24 @@ static int peer_connection_dtls_srtp_send(void* ctx, const uint8_t* buf, size_t return agent_send(&pc->agent, buf, len); } +static void peer_connection_nack_retransmit(PeerConnection* pc, uint16_t seq) { + /* nack_ring_packets==0 (未启用重传) 早退 */ + if (pc->config.nack_ring_packets == 0) { + return; + } + /* 同余槽位校验: seq 超出窗口时槽位内容必不匹配, 天然实现窗口语义 */ + nack_ring_entry_t* e = &pc->nack_ring[seq & (pc->config.nack_ring_packets - 1)]; + if (e->seq == seq && e->len > 0) { + /* 重传同样计入收发统计:重传失败也是拥塞信号 */ + if (agent_send(&pc->agent, e->data, e->len) < 0) { + pc->rtp_send_failures++; + } else { + pc->rtp_packets_sent++; + } + pc->nack_retransmits++; + } +} + static void peer_connection_incoming_rtcp(PeerConnection* pc, uint8_t* buf, size_t len) { RtcpHeader* rtcp_header; size_t pos = 0; @@ -114,6 +224,30 @@ static void peer_connection_incoming_rtcp(PeerConnection* pc, uint8_t* buf, size if ((fmt == 1 || fmt == 4) && pc->config.on_request_keyframe) { pc->config.on_request_keyframe(pc->config.user_data); } + break; + } + case RTCP_RTPFB: { + int fmt = rtcp_header->rc; + LOGD("RTCP_RTPFB %d", fmt); + /* RFC 4585 generic NACK (fmt=1): FCI = PID(2B) + BLP(2B), + * 位于 pos+12 (RtcpHeader 4B + sender SSRC 4B + media SSRC 4B) */ + if (fmt == 1 && pos + 16 <= len) { + const uint8_t* fci = buf + pos + 12; + uint32_t media = ((uint32_t)buf[pos + 8] << 24) | ((uint32_t)buf[pos + 9] << 16) | + ((uint32_t)buf[pos + 10] << 8) | buf[pos + 11]; + /* 只响应视频轨的 NACK: media SSRC == 本地视频 SSRC */ + if (media == pc->vrtp_encoder.ssrc) { + uint16_t base = ((uint16_t)fci[0] << 8) | fci[1]; + uint16_t blp = ((uint16_t)fci[2] << 8) | fci[3]; + peer_connection_nack_retransmit(pc, base); + for (int i = 0; i < 16; i++) { + if (blp & (1u << i)) { + peer_connection_nack_retransmit(pc, (uint16_t)(base + i + 1)); + } + } + } + } + break; } default: break; @@ -148,12 +282,42 @@ PeerConnectionState peer_connection_get_state(PeerConnection* pc) { return pc->state; } +uint32_t peer_connection_get_nack_retransmits(PeerConnection* pc) { + return pc->nack_retransmits; +} + +uint32_t peer_connection_get_srtp_auth_failures(PeerConnection* pc) { + return pc->srtp_auth_failures; +} + +uint32_t peer_connection_get_rtp_packets_sent(PeerConnection* pc) { + return pc->rtp_packets_sent; +} + +uint32_t peer_connection_get_rtp_send_failures(PeerConnection* pc) { + return pc->rtp_send_failures; +} + void* peer_connection_get_sctp(PeerConnection* pc) { return &pc->sctp; } PeerConnection* peer_connection_create(PeerConfiguration* config) { - PeerConnection* pc = calloc(1, sizeof(PeerConnection)); + uint32_t n = config->nack_ring_packets; + + /* n=0 合法(禁用重传); 非 0 必须为 2 的幂 —— n & (n-1) 对 n=0 也为 0 */ + if (n & (n - 1)) { + LOGE("nack_ring_packets must be a power of 2 (got %" PRIu32 ")", n); + return NULL; + } + /* RV32 下 size_t 为 32 位: n=2^30 时 1320*n 精确回绕为 0, + * calloc 会成功而 ring 越界写, 必须守卫 */ + if ((size_t)n > SIZE_MAX / sizeof(nack_ring_entry_t)) { + LOGE("nack_ring_packets too large (got %" PRIu32 ")", n); + return NULL; + } + + PeerConnection* pc = calloc(1, sizeof(PeerConnection) + (size_t)n * sizeof(nack_ring_entry_t)); if (!pc) { return NULL; } @@ -164,6 +328,8 @@ PeerConnection* peer_connection_create(PeerConfiguration* config) { memset(&pc->sctp, 0, sizeof(pc->sctp)); + pc->state = PEER_CONNECTION_NEW; + if (pc->config.audio_codec) { rtp_encoder_init(&pc->artp_encoder, pc->config.audio_codec, peer_connection_outgoing_rtp_packet, (void*)pc); @@ -194,7 +360,7 @@ void peer_connection_destroy(PeerConnection* pc) { } void peer_connection_close(PeerConnection* pc) { - pc->state = PEER_CONNECTION_CLOSED; + STATE_CHANGED(pc, PEER_CONNECTION_CLOSED); } int peer_connection_send_audio(PeerConnection* pc, const uint8_t* buf, size_t len) { @@ -213,8 +379,17 @@ int peer_connection_send_video(PeerConnection* pc, const uint8_t* buf, size_t le return rtp_encoder_encode(&pc->vrtp_encoder, buf, len); } +void peer_connection_set_video_timestamp_increment(PeerConnection* pc, uint32_t increment) { + rtp_encoder_set_timestamp_increment(&pc->vrtp_encoder, increment); +} + int peer_connection_datachannel_send(PeerConnection* pc, char* message, size_t len) { - return peer_connection_datachannel_send_sid(pc, message, len, 0); + /** + * Use the actual sid from the SCTP stream table (negotiated by the browser's DCEP OPEN), + * instead of hardcoding 0. + */ + uint16_t sid = (pc->sctp.stream_count > 0) ? pc->sctp.stream_table[0].sid : 0; + return peer_connection_datachannel_send_sid(pc, message, len, sid); } int peer_connection_datachannel_send_sid(PeerConnection* pc, char* message, size_t len, uint16_t sid) { @@ -295,8 +470,31 @@ int peer_connection_loop(PeerConnection* pc) { case PEER_CONNECTION_CHECKING: if (agent_select_candidate_pair(&pc->agent) < 0) { - STATE_CHANGED(pc, PEER_CONNECTION_FAILED); + { + /** + * No candidate pairs (browser's mDNS hostname cannot be resolved). + * Do not directly mark as FAILED; still receive STUN requests — + * agent_process_stun_request will create a FROZEN candidate pair from the UDP source address, + * and in the next loop iteration the standard procedure will select the pair, + * send USE‑CANDIDATE, and establish connectivity. + */ + uint8_t buf[1400]; + agent_recv(&pc->agent, buf, sizeof(buf)); + } + if (pc->agent.candidate_pairs_num == 0) { + /** + * No candidate pairs, wait for the browser to send STUN. + * A 15-second timeout prevents failure to exit the + * PEER_CONNECTION_CHECKING state after the browser disconnects. + */ + if (pc->agent.binding_request_time == 0) { + pc->agent.binding_request_time = ports_get_epoch_time(); + } else if ((ports_get_epoch_time() - pc->agent.binding_request_time) > CONFIG_CHECKING_TIMEOUT) { + STATE_CHANGED(pc, PEER_CONNECTION_FAILED); + } + } } else if (agent_connectivity_check(&pc->agent) == 0) { + pc->handshake_start_time = ports_get_epoch_time(); STATE_CHANGED(pc, PEER_CONNECTION_CONNECTED); } break; @@ -306,22 +504,46 @@ int peer_connection_loop(PeerConnection* pc) { if (dtls_srtp_handshake(&pc->dtls_srtp, NULL) == 0) { LOGD("DTLS-SRTP handshake done"); + if (pc->dtls_srtp.state != DTLS_SRTP_STATE_CONNECTED) { + LOGW("DTLS-SRTP key derivation failed"); + STATE_CHANGED(pc, PEER_CONNECTION_FAILED); + break; + } + if (pc->config.datachannel) { LOGI("SCTP create socket"); sctp_create_association(&pc->sctp, &pc->dtls_srtp); pc->sctp.userdata = pc->config.user_data; } + /** + * Reset the keepalive base timestamp to prevent an + * immediate timeout upon entering PEER_CONNECTION_COMPLETED. + */ + pc->agent.binding_request_time = ports_get_epoch_time(); STATE_CHANGED(pc, PEER_CONNECTION_COMPLETED); + } else { + if ((ports_get_epoch_time() - pc->handshake_start_time) > CONFIG_DTLS_HANDSHAKE_TIMEOUT) { + LOGW("handshake timeout"); + STATE_CHANGED(pc, PEER_CONNECTION_FAILED); + } } break; case PEER_CONNECTION_COMPLETED: - if ((pc->agent_ret = agent_recv(&pc->agent, pc->agent_buf, sizeof(pc->agent_buf))) > 0) { + for (int i = 0; i < PEER_CONNECTION_RECV_BURST; i++) { + if ((pc->agent_ret = agent_recv(&pc->agent, pc->agent_buf, sizeof(pc->agent_buf))) <= 0) { + break; + } LOGD("agent_recv %d", pc->agent_ret); if (rtcp_probe(pc->agent_buf, pc->agent_ret)) { LOGD("Got RTCP packet"); - dtls_srtp_decrypt_rtcp_packet(&pc->dtls_srtp, pc->agent_buf, &pc->agent_ret); + if (dtls_srtp_decrypt_rtcp_packet(&pc->dtls_srtp, pc->agent_buf, &pc->agent_ret) != 0) { + /* 鉴权失败丢弃:AEAD/HMAC 未过校验的字节不得进入解析(防伪造反馈注入) */ + LOGW("SRTCP auth failed, drop"); + pc->srtp_auth_failures++; + continue; + } peer_connection_incoming_rtcp(pc, pc->agent_buf, pc->agent_ret); } else if (dtls_srtp_probe(pc->agent_buf)) { @@ -335,7 +557,12 @@ int peer_connection_loop(PeerConnection* pc) { } else if (rtp_packet_validate(pc->agent_buf, pc->agent_ret)) { LOGD("Got RTP packet"); - dtls_srtp_decrypt_rtp_packet(&pc->dtls_srtp, pc->agent_buf, &pc->agent_ret); + if (dtls_srtp_decrypt_rtp_packet(&pc->dtls_srtp, pc->agent_buf, &pc->agent_ret) != 0) { + /* 鉴权失败丢弃,绝不送解码器;缺口由 NACK 重传/PLI 关键帧恢复 */ + LOGW("SRTP auth failed, drop"); + pc->srtp_auth_failures++; + continue; + } ssrc = rtp_get_ssrc(pc->agent_buf); if (ssrc == pc->remote_assrc) { @@ -349,6 +576,12 @@ int peer_connection_loop(PeerConnection* pc) { } } + if (pc->config.datachannel && pc->sctp.association_failed) { + LOGI("SCTP association failed"); + STATE_CHANGED(pc, PEER_CONNECTION_CLOSED); + break; + } + if (CONFIG_KEEPALIVE_TIMEOUT > 0 && (ports_get_epoch_time() - pc->agent.binding_request_time) > CONFIG_KEEPALIVE_TIMEOUT) { LOGI("binding request timeout"); STATE_CHANGED(pc, PEER_CONNECTION_CLOSED); @@ -389,6 +622,7 @@ void peer_connection_set_remote_description(PeerConnection* pc, const char* sdp, if (strstr(buf, "a=fingerprint")) { strncpy(pc->dtls_srtp.remote_fingerprint, buf + 22, DTLS_SRTP_FINGERPRINT_LENGTH); + pc->dtls_srtp.remote_fingerprint[DTLS_SRTP_FINGERPRINT_LENGTH - 1] = '\0'; } if (strstr(buf, "a=ice-ufrag") && @@ -423,8 +657,8 @@ void peer_connection_set_remote_description(PeerConnection* pc, const char* sdp, } static const char* peer_connection_create_sdp(PeerConnection* pc, SdpType sdp_type) { + uint8_t create_candidate_sdp_flag = 0; char* description = (char*)pc->temp_buf; - memset(pc->temp_buf, 0, sizeof(pc->temp_buf)); DtlsSrtpRole role = DTLS_SRTP_ROLE_SERVER; @@ -462,19 +696,54 @@ static const char* peer_connection_create_sdp(PeerConnection* pc, SdpType sdp_ty sdp_append(pc->sdp, "a=fingerprint:sha-256 %s", pc->dtls_srtp.local_fingerprint); sdp_append(pc->sdp, peer_connection_dtls_role_setup_value(role)); + /* 虽然从 RFC 8840 (SDP for BUNDLE) 和 RFC 5245 (ICE) 的标准来看,Session-level candidate 在语法上是合法的, + * 但在实际的 WebRTC 工程实践中,Media-level candidate 的兼容性和成功率远高于 Session-level。 + * starfan 20260811: 上面两行为网上查找的结论,下面为实测的结果。 + * a=candidate 必须放在第一个 m= 段段之后作为 + * 否则 Chrome 在 第一个 m= 段 段找不到 candidate 会一直等 + * trickle ICE,从不发起 connectivity check(不发 STUN,也不回 STUN)。 + */ + pc->b_local_description_created = 1; + + agent_gather_candidate(&pc->agent, NULL, NULL, NULL); // host address + for (int i = 0; i < sizeof(pc->config.ice_servers) / sizeof(pc->config.ice_servers[0]); ++i) { + if (pc->config.ice_servers[i].urls) { + LOGI("ice server: %s", pc->config.ice_servers[i].urls); + agent_gather_candidate(&pc->agent, pc->config.ice_servers[i].urls, pc->config.ice_servers[i].username, pc->config.ice_servers[i].credential); + } + } + + agent_get_local_description(&pc->agent, description, sizeof(pc->temp_buf)); + if (pc->config.video_codec == CODEC_H264) { sdp_append_h264(pc->sdp); + if(0 == create_candidate_sdp_flag) { + create_candidate_sdp_flag = 1; + sdp_append(pc->sdp, description); + } } switch (pc->config.audio_codec) { case CODEC_PCMA: sdp_append_pcma(pc->sdp); + if(0 == create_candidate_sdp_flag) { + create_candidate_sdp_flag = 1; + sdp_append(pc->sdp, description); + } break; case CODEC_PCMU: sdp_append_pcmu(pc->sdp); + if(0 == create_candidate_sdp_flag) { + create_candidate_sdp_flag = 1; + sdp_append(pc->sdp, description); + } break; case CODEC_OPUS: sdp_append_opus(pc->sdp); + if(0 == create_candidate_sdp_flag) { + create_candidate_sdp_flag = 1; + sdp_append(pc->sdp, description); + } default: break; } @@ -483,19 +752,11 @@ static const char* peer_connection_create_sdp(PeerConnection* pc, SdpType sdp_ty sdp_append_datachannel(pc->sdp); } - pc->b_local_description_created = 1; - - agent_gather_candidate(&pc->agent, NULL, NULL, NULL); // host address - for (int i = 0; i < sizeof(pc->config.ice_servers) / sizeof(pc->config.ice_servers[0]); ++i) { - if (pc->config.ice_servers[i].urls) { - LOGI("ice server: %s", pc->config.ice_servers[i].urls); - agent_gather_candidate(&pc->agent, pc->config.ice_servers[i].urls, pc->config.ice_servers[i].username, pc->config.ice_servers[i].credential); - } + if(0 == create_candidate_sdp_flag) { + create_candidate_sdp_flag = 1; + sdp_append(pc->sdp, description); } - agent_get_local_description(&pc->agent, description, sizeof(pc->temp_buf)); - sdp_append(pc->sdp, description); - if (pc->onicecandidate) { pc->onicecandidate(pc->sdp, pc->config.user_data); } diff --git a/src/peer_connection.h b/src/peer_connection.h index b087893e..d5fea3e2 100644 --- a/src/peer_connection.h +++ b/src/peer_connection.h @@ -56,7 +56,7 @@ typedef enum MediaCodec { CODEC_MJPEG, // not implemented yet /* Audio */ - CODEC_OPUS, // not implemented yet + CODEC_OPUS, // SDP + RTP passthrough only, no Opus codec/packetizer CODEC_PCMA, CODEC_PCMU, @@ -81,6 +81,9 @@ typedef struct PeerConfiguration { void (*on_request_keyframe)(void* userdata); void* user_data; + /* NACK 重传 ring 槽位数: 0 = 禁用; 非 0 必须为 2 的幂 (非法时 create 失败) */ + uint32_t nack_ring_packets; + } PeerConfiguration; typedef struct PeerConnection PeerConnection; @@ -117,6 +120,20 @@ int peer_connection_send_audio(PeerConnection* pc, const uint8_t* packet, size_t int peer_connection_send_video(PeerConnection* pc, const uint8_t* packet, size_t bytes); +// 按实际视频帧间隔调整媒体时钟步进(90kHz 单位) +void peer_connection_set_video_timestamp_increment(PeerConnection* pc, uint32_t increment); + +// NACK 重传统计 (config.nack_ring_packets==0 禁用时恒为 0) +uint32_t peer_connection_get_nack_retransmits(PeerConnection* pc); + +// SRTP/SRTCP 入向鉴权失败丢包计数 +uint32_t peer_connection_get_srtp_auth_failures(PeerConnection* pc); + +// RTP 出包统计: 成功投递包数与最终丢弃(重试后仍失败)包数 +uint32_t peer_connection_get_rtp_packets_sent(PeerConnection* pc); + +uint32_t peer_connection_get_rtp_send_failures(PeerConnection* pc); + void peer_connection_set_remote_description(PeerConnection* pc, const char* sdp, SdpType sdp_type); void peer_connection_set_local_description(PeerConnection* pc, const char* sdp, SdpType sdp_type); diff --git a/src/ports.c b/src/ports.c index 2346f609..a482ee9b 100644 --- a/src/ports.c +++ b/src/ports.c @@ -29,6 +29,7 @@ int ports_get_host_addr(Address* addr, const char* iface_prefix) { int i; for (netif = netif_list; netif != NULL; netif = netif->next) { switch (addr->family) { +#if CONFIG_USE_IPV6 case AF_INET6: for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) { if (!ip6_addr_isany(netif_ip6_addr(netif, i))) { @@ -38,10 +39,15 @@ int ports_get_host_addr(Address* addr, const char* iface_prefix) { } } break; +#endif case AF_INET: default: if (!ip_addr_isany(&netif->ip_addr)) { + #if CONFIG_USE_IPV6 memcpy(&addr->sin.sin_addr, &netif->ip_addr.u_addr.ip4, 4); + #else + memcpy(&addr->sin.sin_addr, &netif->ip_addr, 4); + #endif ret = 1; } break; @@ -89,9 +95,11 @@ int ports_get_host_addr(Address* addr, const char* iface_prefix) { } switch (ifa->ifa_addr->sa_family) { +#if CONFIG_USE_IPV6 case AF_INET6: memcpy(&addr->sin6, ifa->ifa_addr, sizeof(struct sockaddr_in6)); break; +#endif case AF_INET: default: memcpy(&addr->sin, ifa->ifa_addr, sizeof(struct sockaddr_in)); @@ -124,9 +132,11 @@ int ports_resolve_addr(const char* host, Address* addr) { for (p = res; p != NULL; p = p->ai_next) { if (p->ai_family == addr->family) { switch (addr->family) { +#if CONFIG_USE_IPV6 case AF_INET6: memcpy(&addr->sin6, p->ai_addr, sizeof(struct sockaddr_in6)); break; +#endif case AF_INET: default: memcpy(&addr->sin, p->ai_addr, sizeof(struct sockaddr_in)); diff --git a/src/rtp.c b/src/rtp.c index 0a3136ad..7e7a6c4c 100644 --- a/src/rtp.c +++ b/src/rtp.c @@ -43,26 +43,23 @@ uint32_t rtp_get_ssrc(uint8_t* packet) { return ntohl(rtp_header->ssrc); } -static int rtp_encoder_encode_h264_single(RtpEncoder* rtp_encoder, uint8_t* buf, size_t size) { +static int rtp_encoder_encode_h264_single(RtpEncoder* rtp_encoder, uint8_t* buf, size_t size, int is_last) { RtpPacket* rtp_packet = (RtpPacket*)rtp_encoder->buf; rtp_packet->header.version = 2; rtp_packet->header.padding = 0; rtp_packet->header.extension = 0; rtp_packet->header.csrccount = 0; - rtp_packet->header.markerbit = 0; + // marker 表示 access unit 结束:仅帧末 NAL 置位(RFC 6184 5.1) + rtp_packet->header.markerbit = is_last; rtp_packet->header.type = rtp_encoder->type; rtp_packet->header.seq_number = htons(rtp_encoder->seq_number++); rtp_packet->header.timestamp = htonl(rtp_encoder->timestamp); rtp_packet->header.ssrc = htonl(rtp_encoder->ssrc); - // I frame and P frame - if ((*buf & 0x1f) == 0x05 || (*buf & 0x1f) == 0x01) { - rtp_packet->header.markerbit = 1; - rtp_encoder->timestamp += rtp_encoder->timestamp_increment; - } #if 0 - LOGI("markbit: %d, timestamp: %d, nalu type: %d", rtp_packet->header.markerbit, rtp_encoder->timestamp, buf[0] & 0x1f); + LOGI("h264 nalu: type=%d size=%d ts=%u seq=%u marker=%d", buf[0] & 0x1f, (int)size, + rtp_encoder->timestamp, rtp_encoder->seq_number, rtp_packet->header.markerbit); #endif memcpy(rtp_packet->payload, buf, size); @@ -70,7 +67,7 @@ static int rtp_encoder_encode_h264_single(RtpEncoder* rtp_encoder, uint8_t* buf, return 0; } -static int rtp_encoder_encode_h264_fu_a(RtpEncoder* rtp_encoder, uint8_t* buf, size_t size) { +static int rtp_encoder_encode_h264_fu_a(RtpEncoder* rtp_encoder, uint8_t* buf, size_t size, int is_last) { RtpPacket* rtp_packet = (RtpPacket*)rtp_encoder->buf; rtp_packet->header.version = 2; @@ -83,14 +80,10 @@ static int rtp_encoder_encode_h264_fu_a(RtpEncoder* rtp_encoder, uint8_t* buf, s rtp_packet->header.ssrc = htonl(rtp_encoder->ssrc); uint8_t type = buf[0] & 0x1f; uint8_t nri = (buf[0] & 0x60) >> 5; + size_t total_size = size; buf = buf + 1; size = size - 1; - // increase timestamp if I, P frame - if (type == 0x05 || type == 0x01) { - rtp_encoder->timestamp += rtp_encoder->timestamp_increment; - } - NaluHeader* fu_indicator = (NaluHeader*)rtp_packet->payload; FuHeader* fu_header = (FuHeader*)rtp_packet->payload + sizeof(NaluHeader); fu_header->s = 1; @@ -105,7 +98,12 @@ static int rtp_encoder_encode_h264_fu_a(RtpEncoder* rtp_encoder, uint8_t* buf, s if (size <= FU_PAYLOAD_SIZE) { fu_header->e = 1; - rtp_packet->header.markerbit = 1; + // 末片 + 帧末 NAL 才置 marker + rtp_packet->header.markerbit = is_last; +#if 0 + LOGI("h264 nalu: type=%d size=%d ts=%u seq=%u marker=%d", type, (int)total_size, + rtp_encoder->timestamp, rtp_encoder->seq_number, rtp_packet->header.markerbit); +#endif memcpy(rtp_packet->payload + sizeof(NaluHeader) + sizeof(FuHeader), buf, size); rtp_encoder->on_packet(rtp_encoder->buf, size + sizeof(RtpHeader) + sizeof(NaluHeader) + sizeof(FuHeader), rtp_encoder->user_data); break; @@ -139,25 +137,39 @@ static int rtp_encoder_encode_h264(RtpEncoder* rtp_encoder, uint8_t* buf, size_t uint8_t* buf_end = buf + size; uint8_t *pstart, *pend; size_t nalu_size; + int sent = 0; +#if 0 + LOGI("h264 frame: %d bytes, ts=%u", (int)size, rtp_encoder->timestamp); +#endif + + // 一帧(access unit)含多个 NAL(本流 slices=3):所有 NAL 共享同一 RTP timestamp, + // marker 仅在帧末 NAL 的最后一个包置位(RFC 6184 5.1) for (pstart = h264_find_nalu(buf, buf_end); pstart < buf_end; pstart = pend) { pend = h264_find_nalu(pstart, buf_end); nalu_size = pend - pstart; - if (pend != buf_end) - nalu_size--; - - while (pstart[nalu_size - 1] == 0x00) - nalu_size--; + // h264_find_nalu 匹配 4 字节起始码(00 00 00 01)的尾 3 字节并返回 01 之后一位, + // 故非末 NAL 的 nalu_size 含尾随 3 字节 00 00 00(demux 恒写 4 字节起始码),精确剥离 + if (pend != buf_end) { + if (nalu_size <= 3) + continue; + nalu_size -= 3; + } + sent = 1; if (nalu_size <= RTP_PAYLOAD_SIZE) { - rtp_encoder_encode_h264_single(rtp_encoder, pstart, nalu_size); - + rtp_encoder_encode_h264_single(rtp_encoder, pstart, nalu_size, pend == buf_end); } else { - rtp_encoder_encode_h264_fu_a(rtp_encoder, pstart, nalu_size); + rtp_encoder_encode_h264_fu_a(rtp_encoder, pstart, nalu_size, pend == buf_end); } } + // timestamp 按帧递增一次(与 generic 路径"每次调用一帧"语义一致), + // 帧内多 NAL 包共享同一媒体时间戳 + if (sent) + rtp_encoder->timestamp += rtp_encoder->timestamp_increment; + return 0; } @@ -190,7 +202,7 @@ void rtp_encoder_init(RtpEncoder* rtp_encoder, MediaCodec codec, RtpOnPacket on_ case CODEC_H264: rtp_encoder->type = PT_H264; rtp_encoder->ssrc = SSRC_H264; - rtp_encoder->timestamp_increment = 90000 / 30; // 30 FPS. + rtp_encoder->timestamp_increment = 90000 / 15; // 15 FPS,实际由 set_timestamp_increment 按 pts 动态调整 rtp_encoder->encode_func = rtp_encoder_encode_h264; break; case CODEC_PCMA: diff --git a/src/rtp.h b/src/rtp.h index 4f946145..1858c8f8 100644 --- a/src/rtp.h +++ b/src/rtp.h @@ -99,6 +99,11 @@ void rtp_encoder_init(RtpEncoder* rtp_encoder, MediaCodec codec, RtpOnPacket on_ int rtp_encoder_encode(RtpEncoder* rtp_encoder, const uint8_t* data, size_t size); +// 按实际帧间隔动态调整媒体时钟步进(90kHz 单位) +static inline void rtp_encoder_set_timestamp_increment(RtpEncoder* rtp_encoder, uint32_t increment) { + rtp_encoder->timestamp_increment = increment; +} + void rtp_decoder_init(RtpDecoder* rtp_decoder, MediaCodec codec, RtpOnPacket on_packet, void* user_data); int rtp_decoder_decode(RtpDecoder* rtp_decoder, const uint8_t* data, size_t size); diff --git a/src/sctp.c b/src/sctp.c index 644f8d8e..77c3c45f 100644 --- a/src/sctp.c +++ b/src/sctp.c @@ -124,7 +124,7 @@ int sctp_outgoing_data(Sctp* sctp, char* buf, size_t len, SctpDataPpid ppid, uin chunk->type = SCTP_DATA; chunk->iube = 0x06; - chunk->sid = htons(0); + chunk->sid = htons(sid); chunk->sqn = htons(sqn++); chunk->ppid = htonl(ppid); @@ -194,7 +194,7 @@ void sctp_parse_data_channel_open(Sctp* sctp, uint16_t sid, char* data, size_t l // Add stream mapping sctp_add_stream_mapping(sctp, label_str, sid); char ack = DATA_CHANNEL_ACK; - sctp_outgoing_data(sctp, &ack, 1, DATA_CHANNEL_PPID_CONTROL, sid); + sctp_outgoing_data(sctp, &ack, 1, (SctpDataPpid)DATA_CHANNEL_PPID_CONTROL, sid); } } @@ -242,11 +242,17 @@ void sctp_incoming_data(Sctp* sctp, char* buf, size_t len) { return; } - // prepare outgoing packet - memset(sctp->buf, 0, sizeof(sctp->buf)); - while ((4 * (pos + 3) / 4) < len) { + while (pos + sizeof(SctpChunkCommon) <= len) { + memset(sctp->buf, 0, sizeof(sctp->buf)); chunk_common = (SctpChunkCommon*)(buf + pos); + uint16_t chunk_len = ntohs(chunk_common->length); + if (chunk_len < sizeof(SctpChunkCommon) || pos + chunk_len > len) { + break; + } + + length = 0; // only branches that build a reply set it, otherwise nothing is sent + switch (chunk_common->type) { case SCTP_DATA: { SctpDataChunk* data_chunk = (SctpDataChunk*)(buf + pos); @@ -256,28 +262,47 @@ void sctp_incoming_data(Sctp* sctp, char* buf, size_t len) { sack_chunk->common.flags = 0x00; sack_chunk->common.length = htons(16); sack_chunk->cumulative_tsn_ack = data_chunk->tsn; - sack_chunk->a_rwnd = htonl(0x02); + sack_chunk->a_rwnd = htonl(SCTP_LOCAL_RWND); length = ntohs(sack_chunk->common.length) + sizeof(SctpHeader); - LOGD("SCTP_DATA. ppid = %ld, data = %.2x", ntohl(data_chunk->ppid), data_chunk->data[0]); + LOGD("SCTP_DATA. ppid = %ld, data = %.2x, sid = %u", ntohl(data_chunk->ppid), data_chunk->data[0], ntohs(data_chunk->sid)); if (ntohl(data_chunk->ppid) == DATA_CHANNEL_PPID_CONTROL && data_chunk->data[0] == DATA_CHANNEL_OPEN) { + uint16_t browser_sid = ntohs(data_chunk->sid); + sctp->stream_count = 1; + sctp->stream_table[0].sid = browser_sid; + sctp->stream_table[0].label[0] = '0'; + LOGD("DCEP OPEN from sid=%u, saving", browser_sid); data_chunk = (SctpDataChunk*)sack_chunk->blocks; data_chunk->type = SCTP_DATA; data_chunk->iube = 0x03; data_chunk->tsn = htonl(sctp->tsn++); - data_chunk->sid = htons(0); + data_chunk->sid = htons(browser_sid); data_chunk->sqn = htons(0); data_chunk->ppid = htonl(DATA_CHANNEL_PPID_CONTROL); data_chunk->length = htons(1 + sizeof(SctpDataChunk)); data_chunk->data[0] = DATA_CHANNEL_ACK; length += ntohs(data_chunk->length); } else if (ntohl(data_chunk->ppid) == DATA_CHANNEL_PPID_DOMSTRING || ntohl(data_chunk->ppid) == DATA_CHANNEL_PPID_BINARY) { + /* Send SACK BEFORE calling onmessage — onmessage may invoke + * sctp_outgoing_data() which overwrites sctp->buf, corrupting the + * SACK. If the peer never sees a valid SACK it retransmits the + * payload, creating an infinite ping-pong loop. */ + out_packet->header.source_port = htons(sctp->local_port); + out_packet->header.destination_port = htons(sctp->remote_port); + out_packet->header.verification_tag = sctp->verification_tag; + out_packet->header.checksum = 0x00; + { + size_t sack_len = (4 * ((length + 3) / 4)); + out_packet->header.checksum = sctp_get_checksum(sctp, sctp->buf, sack_len); + dtls_srtp_write(sctp->dtls_srtp, sctp->buf, sack_len); + } + length = 0; /* SACK sent; don't resend below */ + if (sctp->onmessage) { sctp->onmessage((char*)data_chunk->data, ntohs(data_chunk->length) - sizeof(SctpDataChunk), sctp->userdata, ntohs(data_chunk->sid)); } } - pos = len; // Do not handle other msg } break; case SCTP_INIT: { LOGD("SCTP_INIT"); @@ -291,7 +316,7 @@ void sctp_incoming_data(Sctp* sctp, char* buf, size_t len) { init_ack->common.flags = 0x00; init_ack->common.length = htons(20 + 8); init_ack->initiate_tag = htonl(0x12345678); - init_ack->a_rwnd = htonl(0x100000); + init_ack->a_rwnd = htonl(SCTP_LOCAL_RWND); init_ack->number_of_outbound_streams = 0xffff; init_ack->number_of_inbound_streams = 0xffff; init_ack->initial_tsn = htonl(sctp->tsn); @@ -323,10 +348,12 @@ void sctp_incoming_data(Sctp* sctp, char* buf, size_t len) { cookie_echo->common.type = SCTP_COOKIE_ECHO; cookie_echo->common.flags = 0x00; - // cookie echo: type + flag + length (4 bytes) + cookie - cookie_echo->common.length = htons(ntohs(param->length)); - // param: type + length (4 bytes) + cookie - memcpy(cookie_echo->cookie, param->value, ntohs(param->length) - 4); + if(param) { + // cookie echo: type + flag + length (4 bytes) + cookie + cookie_echo->common.length = htons(ntohs(param->length)); + // param: type + length (4 bytes) + cookie + memcpy(cookie_echo->cookie, param->value, ntohs(param->length) - 4); + } length = ntohs(cookie_echo->common.length) + sizeof(SctpHeader); } break; case SCTP_SACK: @@ -384,8 +411,70 @@ void sctp_incoming_data(Sctp* sctp, char* buf, size_t len) { } break; } + case SCTP_HEARTBEAT: { + if (chunk_len <= sizeof(sctp->buf)) { + SctpChunkCommon* hb_ack = (SctpChunkCommon*)out_packet->chunks; + memcpy(hb_ack, chunk_common, chunk_len); + hb_ack->type = SCTP_HEARTBEAT_ACK; + length = chunk_len + sizeof(SctpHeader); + } + } break; + case SCTP_HEARTBEAT_ACK: + break; + case SCTP_SHUTDOWN: { + SctpChunkCommon* shut_ack = (SctpChunkCommon*)out_packet->chunks; + shut_ack->type = SCTP_SHUTDOWN_ACK; + shut_ack->flags = 0x00; + shut_ack->length = htons(4); + length = sizeof(SctpHeader) + sizeof(SctpChunkCommon); + sctp->connected = 0; + sctp->association_failed = 1; + if (sctp->onclose) { + sctp->onclose(sctp->userdata); + } + } break; + case SCTP_SHUTDOWN_ACK: { + SctpChunkCommon* shut_comp = (SctpChunkCommon*)out_packet->chunks; + shut_comp->type = SCTP_SHUTDOWN_COMPLETE; + shut_comp->flags = 0x00; + shut_comp->length = htons(4); + length = sizeof(SctpHeader) + sizeof(SctpChunkCommon); + sctp->connected = 0; + sctp->association_failed = 1; + if (sctp->onclose) { + sctp->onclose(sctp->userdata); + } + } break; + case SCTP_SHUTDOWN_COMPLETE: + sctp->connected = 0; + sctp->association_failed = 1; + if (sctp->onclose) { + sctp->onclose(sctp->userdata); + } + break; + case SCTP_ERROR: { + if (chunk_len > sizeof(SctpChunkCommon)) { + size_t cause_pos = pos + sizeof(SctpChunkCommon); + size_t cause_end = pos + chunk_len; + while (cause_pos + 4 <= cause_end) { + uint16_t cause_code = ntohs(*(uint16_t*)(buf + cause_pos)); + uint16_t cause_length = ntohs(*(uint16_t*)(buf + cause_pos + 2)); + if (cause_length < 4 || cause_pos + cause_length > cause_end) break; + LOGW("SCTP_ERROR cause_code=0x%04x", cause_code); + cause_pos += ((cause_length + 3) / 4) * 4; + } + } + } break; + case SCTP_FORWARD_TSN: { + SctpForwardTsnChunk* fwd = (SctpForwardTsnChunk*)(buf + pos); + uint32_t new_cumulative_tsn = ntohl(fwd->new_cumulative_tsn); + if (new_cumulative_tsn >= sctp->tsn) { + sctp->tsn = new_cumulative_tsn + 1; + } + } break; case SCTP_ABORT: sctp->connected = 0; + sctp->association_failed = 1; if (sctp->onclose) { sctp->onclose(sctp->userdata); } @@ -408,7 +497,8 @@ void sctp_incoming_data(Sctp* sctp, char* buf, size_t len) { dtls_srtp_write(sctp->dtls_srtp, sctp->buf, length); // sctp_outgoing_data_cb(sctp, sctp->buf, SCTP_MTU, 0, 0); } - pos += ntohs(chunk_common->length); + + pos += ((chunk_len + 3) / 4) * 4; // chunks are padded to a 4-byte boundary } #endif } @@ -605,6 +695,9 @@ int sctp_create_association(Sctp* sctp, DtlsSrtp* dtls_srtp) { sctp->sock = sock; #else // send SCTP_INIT + sctp->connected = 0; + sctp->association_failed = 0; + int length = 0; SctpInitChunk* init_chunk; SctpHeader* header; @@ -619,7 +712,7 @@ int sctp_create_association(Sctp* sctp, DtlsSrtp* dtls_srtp) { init_chunk->common.flags = 0x00; init_chunk->common.length = htons(20); init_chunk->initiate_tag = htonl(0x12345678); - init_chunk->a_rwnd = htonl(0x100000); + init_chunk->a_rwnd = htonl(SCTP_LOCAL_RWND); init_chunk->number_of_outbound_streams = 0xffff; init_chunk->number_of_inbound_streams = 0xffff; init_chunk->initial_tsn = htonl(sctp->tsn); diff --git a/src/sctp.h b/src/sctp.h index 1116996a..d08cec2f 100644 --- a/src/sctp.h +++ b/src/sctp.h @@ -5,6 +5,10 @@ #include "dtls_srtp.h" #include "utils.h" +#ifdef __cplusplus +extern "C" { +#endif + typedef enum DecpMsgType { DATA_CHANNEL_OPEN = 0x03, @@ -153,6 +157,7 @@ typedef struct Sctp { int local_port; int remote_port; int connected; + uint8_t association_failed; uint32_t verification_tag; uint32_t tsn; DtlsSrtp* dtls_srtp; @@ -188,4 +193,8 @@ void sctp_onopen(Sctp* sctp, void (*onopen)(void* userdata)); void sctp_onclose(Sctp* sctp, void (*onclose)(void* userdata)); +#ifdef __cplusplus +} +#endif + #endif // SCTP_H_ diff --git a/src/sdp.c b/src/sdp.c index b7661a4c..3e61f8fc 100644 --- a/src/sdp.c +++ b/src/sdp.c @@ -31,7 +31,7 @@ void sdp_append_h264(char* sdp) { sdp_append(sdp, "c=IN IP4 0.0.0.0"); sdp_append(sdp, "a=rtcp-fb:96 nack"); sdp_append(sdp, "a=rtcp-fb:96 nack pli"); - sdp_append(sdp, "a=fmtp:96 profile-level-id=42e01f;level-asymmetry-allowed=1"); + sdp_append(sdp, "a=fmtp:96 profile-level-id=42e01f;level-asymmetry-allowed=1;packetization-mode=1"); sdp_append(sdp, "a=rtpmap:96 H264/90000"); sdp_append(sdp, "a=ssrc:1 cname:webrtc-h264"); sdp_append(sdp, "a=sendrecv"); @@ -40,7 +40,7 @@ void sdp_append_h264(char* sdp) { } void sdp_append_pcma(char* sdp) { - sdp_append(sdp, "m=audio 9 UDP/TLS/RTP/SAVP 8"); + sdp_append(sdp, "m=audio 9 UDP/TLS/RTP/SAVPF 8"); sdp_append(sdp, "c=IN IP4 0.0.0.0"); sdp_append(sdp, "a=rtpmap:8 PCMA/8000"); sdp_append(sdp, "a=ssrc:4 cname:webrtc-pcma"); @@ -50,7 +50,7 @@ void sdp_append_pcma(char* sdp) { } void sdp_append_pcmu(char* sdp) { - sdp_append(sdp, "m=audio 9 UDP/TLS/RTP/SAVP 0"); + sdp_append(sdp, "m=audio 9 UDP/TLS/RTP/SAVPF 0"); sdp_append(sdp, "c=IN IP4 0.0.0.0"); sdp_append(sdp, "a=rtpmap:0 PCMU/8000"); sdp_append(sdp, "a=ssrc:5 cname:webrtc-pcmu"); @@ -60,7 +60,7 @@ void sdp_append_pcmu(char* sdp) { } void sdp_append_opus(char* sdp) { - sdp_append(sdp, "m=audio 9 UDP/TLS/RTP/SAVP 111"); + sdp_append(sdp, "m=audio 9 UDP/TLS/RTP/SAVPF 111"); sdp_append(sdp, "c=IN IP4 0.0.0.0"); sdp_append(sdp, "a=rtpmap:111 opus/48000/2"); sdp_append(sdp, "a=ssrc:6 cname:webrtc-opus"); diff --git a/src/socket.c b/src/socket.c index 6f7d8ef2..64b1c0b5 100644 --- a/src/socket.c +++ b/src/socket.c @@ -35,6 +35,7 @@ int udp_socket_open(UdpSocket* udp_socket, int family, int port) { udp_socket->bind_addr.family = family; switch (family) { +#if CONFIG_USE_IPV6 case AF_INET6: udp_socket->fd = socket(AF_INET6, SOCK_DGRAM, 0); udp_socket->bind_addr.sin6.sin6_family = AF_INET6; @@ -44,6 +45,7 @@ int udp_socket_open(UdpSocket* udp_socket, int family, int port) { sa = (struct sockaddr*)&udp_socket->bind_addr.sin6; sock_len = sizeof(struct sockaddr_in6); break; +#endif case AF_INET: default: udp_socket->fd = socket(AF_INET, SOCK_DGRAM, 0); @@ -82,9 +84,11 @@ int udp_socket_open(UdpSocket* udp_socket, int family, int port) { } switch (udp_socket->bind_addr.family) { +#if CONFIG_USE_IPV6 case AF_INET6: udp_socket->bind_addr.port = ntohs(udp_socket->bind_addr.sin6.sin6_port); break; +#endif case AF_INET: default: udp_socket->bind_addr.port = ntohs(udp_socket->bind_addr.sin.sin_port); @@ -111,11 +115,13 @@ int udp_socket_sendto(UdpSocket* udp_socket, Address* addr, const uint8_t* buf, } switch (addr->family) { +#if CONFIG_USE_IPV6 case AF_INET6: addr->sin6.sin6_family = AF_INET6; sa = (struct sockaddr*)&addr->sin6; sock_len = sizeof(struct sockaddr_in6); break; +#endif case AF_INET: default: addr->sin.sin_family = AF_INET; @@ -133,7 +139,9 @@ int udp_socket_sendto(UdpSocket* udp_socket, Address* addr, const uint8_t* buf, } int udp_socket_recvfrom(UdpSocket* udp_socket, Address* addr, uint8_t* buf, int len) { +#if CONFIG_USE_IPV6 struct sockaddr_in6 sin6; +#endif struct sockaddr_in sin; struct sockaddr* sa; socklen_t sock_len; @@ -145,11 +153,13 @@ int udp_socket_recvfrom(UdpSocket* udp_socket, Address* addr, uint8_t* buf, int } switch (udp_socket->bind_addr.family) { +#if CONFIG_USE_IPV6 case AF_INET6: sin6.sin6_family = AF_INET6; sa = (struct sockaddr*)&sin6; sock_len = sizeof(struct sockaddr_in6); break; +#endif case AF_INET: default: sin.sin_family = AF_INET; @@ -165,11 +175,13 @@ int udp_socket_recvfrom(UdpSocket* udp_socket, Address* addr, uint8_t* buf, int if (addr) { switch (udp_socket->bind_addr.family) { +#if CONFIG_USE_IPV6 case AF_INET6: addr->family = AF_INET6; addr->port = htons(sin6.sin6_port); memcpy(&addr->sin6, &sin6, sizeof(struct sockaddr_in6)); break; +#endif case AF_INET: default: addr->family = AF_INET; @@ -185,9 +197,11 @@ int udp_socket_recvfrom(UdpSocket* udp_socket, Address* addr, uint8_t* buf, int int tcp_socket_open(TcpSocket* tcp_socket, int family) { tcp_socket->bind_addr.family = family; switch (family) { +#if CONFIG_USE_IPV6 case AF_INET6: tcp_socket->fd = socket(AF_INET6, SOCK_STREAM, 0); break; +#endif case AF_INET: default: tcp_socket->fd = socket(AF_INET, SOCK_STREAM, 0); @@ -213,11 +227,13 @@ int tcp_socket_connect(TcpSocket* tcp_socket, Address* addr) { } switch (addr->family) { +#if CONFIG_USE_IPV6 case AF_INET6: addr->sin6.sin6_family = AF_INET6; sa = (struct sockaddr*)&addr->sin6; sock_len = sizeof(struct sockaddr_in6); break; +#endif case AF_INET: default: addr->sin.sin_family = AF_INET; diff --git a/src/ssl_transport.c b/src/ssl_transport.c index 9847616f..45711eb0 100644 --- a/src/ssl_transport.c +++ b/src/ssl_transport.c @@ -14,6 +14,11 @@ #include "ssl_transport.h" #include "utils.h" +/* 当未启用共享熵源时,LIBPEER_ENTROPY_CTX 回退到原有的结构体字段指针 */ +#ifndef LIBPEER_ENTROPY_CTX +#define LIBPEER_ENTROPY_CTX (&net_ctx->entropy) +#endif + static int ssl_transport_mbedtls_recv_timeout(void* ctx, unsigned char* buf, size_t len, uint32_t timeout) { int ret; fd_set read_fds; @@ -54,9 +59,11 @@ int ssl_transport_connect(NetworkContext_t* net_ctx, mbedtls_ssl_config_init(&net_ctx->conf); // mbedtls_x509_crt_init(&net_ctx->cacert); mbedtls_ctr_drbg_init(&net_ctx->ctr_drbg); +#ifndef LIBPEER_USE_SHARED_ENTROPY mbedtls_entropy_init(&net_ctx->entropy); +#endif - if ((ret = mbedtls_ctr_drbg_seed(&net_ctx->ctr_drbg, mbedtls_entropy_func, &net_ctx->entropy, + if ((ret = mbedtls_ctr_drbg_seed(&net_ctx->ctr_drbg, mbedtls_entropy_func, LIBPEER_ENTROPY_CTX, (const unsigned char*)pers, strlen(pers))) != 0) { return -1; } @@ -119,7 +126,9 @@ void ssl_transport_disconnect(NetworkContext_t* net_ctx) { mbedtls_ssl_config_free(&net_ctx->conf); // mbedtls_x509_crt_free(&net_ctx->cacert); mbedtls_ctr_drbg_free(&net_ctx->ctr_drbg); +#ifndef LIBPEER_USE_SHARED_ENTROPY mbedtls_entropy_free(&net_ctx->entropy); +#endif mbedtls_ssl_free(&net_ctx->ssl); tcp_socket_close(&net_ctx->tcp_socket); diff --git a/src/ssl_transport.h b/src/ssl_transport.h index fe794dc0..894d946c 100644 --- a/src/ssl_transport.h +++ b/src/ssl_transport.h @@ -14,7 +14,9 @@ struct NetworkContext { TcpSocket tcp_socket; mbedtls_ssl_context ssl; +#ifndef LIBPEER_USE_SHARED_ENTROPY mbedtls_entropy_context entropy; +#endif mbedtls_ctr_drbg_context ctr_drbg; mbedtls_ssl_config conf; mbedtls_x509_crt cacert; diff --git a/src/stun.c b/src/stun.c index d3872410..55f5d488 100644 --- a/src/stun.c +++ b/src/stun.c @@ -65,9 +65,11 @@ int stun_set_mapped_address(char* value, uint8_t* mask, Address* addr) { uint32_t* val32 = (uint32_t*)(value + 4); uint16_t* val16 = (uint16_t*)(value + 4); uint32_t* addr32 = (uint32_t*)(&addr->sin.sin_addr); +#if CONFIG_USE_IPV6 uint16_t* addr16 = (uint16_t*)(&addr->sin6.sin6_addr); - +#endif switch (addr->family) { +#if CONFIG_USE_IPV6 case AF_INET6: *family = STUN_FAMILY_IPV6; for (i = 0; i < 8; i++) { @@ -75,6 +77,7 @@ int stun_set_mapped_address(char* value, uint8_t* mask, Address* addr) { } ret = 20; break; +#endif case AF_INET: default: *family = STUN_FAMILY_IPV4; @@ -96,17 +99,21 @@ void stun_get_mapped_address(char* value, uint8_t* mask, Address* addr) { int i; char addr_string[ADDRSTRLEN]; uint32_t* addr32 = (uint32_t*)&addr->sin.sin_addr; +#if CONFIG_USE_IPV6 uint16_t* addr16 = (uint16_t*)&addr->sin6.sin6_addr; +#endif uint8_t family = value[1]; uint16_t port; switch (family) { +#if CONFIG_USE_IPV6 case STUN_FAMILY_IPV6: addr_set_family(addr, AF_INET6); for (i = 0; i < 8; i++) { addr16[i] = (*(uint16_t*)(value + 4 + 2 * i) ^ *(uint16_t*)(mask + 2 * i)); } break; +#endif case STUN_FAMILY_IPV4: default: addr_set_family(addr, AF_INET); diff --git a/src/utils.h b/src/utils.h index 72040ae5..e4c6cd3e 100644 --- a/src/utils.h +++ b/src/utils.h @@ -6,49 +6,45 @@ #include #include "config.h" -#define LEVEL_ERROR 0x00 -#define LEVEL_WARN 0x01 -#define LEVEL_INFO 0x02 -#define LEVEL_DEBUG 0x03 +#define LIBPEER_LOG_LEVEL_ERROR 0x00 +#define LIBPEER_LOG_LEVEL_WARN 0x01 +#define LIBPEER_LOG_LEVEL_INFO 0x02 +#define LIBPEER_LOG_LEVEL_DEBUG 0x03 #define ERROR_TAG "ERROR" #define WARN_TAG "WARN" #define INFO_TAG "INFO" #define DEBUG_TAG "DEBUG" -#ifndef LOG_LEVEL -#define LOG_LEVEL LEVEL_INFO +#ifndef LIBPEER_LOG_LEVEL +#define LIBPEER_LOG_LEVEL LIBPEER_LOG_LEVEL_INFO #endif -#if LOG_REDIRECT -void peer_log(char* level_tag, const char* file_name, int line_number, const char* fmt, ...); -#define LOG_PRINT(level_tag, fmt, ...) \ - peer_log(level_tag, __FILE__, __LINE__, fmt, ##__VA_ARGS__) -#else -#define LOG_PRINT(level_tag, fmt, ...) \ +#ifndef LIBPEER_LOG_PRINT +#define LIBPEER_LOG_PRINT(level_tag, fmt, ...) \ fprintf(stdout, "%s\t%s\t%d\t" fmt "\n", level_tag, __FILE__, __LINE__, ##__VA_ARGS__) #endif -#if LOG_LEVEL >= LEVEL_DEBUG -#define LOGD(fmt, ...) LOG_PRINT(DEBUG_TAG, fmt, ##__VA_ARGS__) +#if LIBPEER_LOG_LEVEL >= LIBPEER_LOG_LEVEL_DEBUG +#define LOGD(fmt, ...) LIBPEER_LOG_PRINT(DEBUG_TAG, fmt, ##__VA_ARGS__) #else #define LOGD(fmt, ...) #endif -#if LOG_LEVEL >= LEVEL_INFO -#define LOGI(fmt, ...) LOG_PRINT(INFO_TAG, fmt, ##__VA_ARGS__) +#if LIBPEER_LOG_LEVEL >= LIBPEER_LOG_LEVEL_INFO +#define LOGI(fmt, ...) LIBPEER_LOG_PRINT(INFO_TAG, fmt, ##__VA_ARGS__) #else #define LOGI(fmt, ...) #endif -#if LOG_LEVEL >= LEVEL_WARN -#define LOGW(fmt, ...) LOG_PRINT(WARN_TAG, fmt, ##__VA_ARGS__) +#if LIBPEER_LOG_LEVEL >= LIBPEER_LOG_LEVEL_WARN +#define LOGW(fmt, ...) LIBPEER_LOG_PRINT(WARN_TAG, fmt, ##__VA_ARGS__) #else #define LOGW(fmt, ...) #endif -#if LOG_LEVEL >= LEVEL_ERROR -#define LOGE(fmt, ...) LOG_PRINT(ERROR_TAG, fmt, ##__VA_ARGS__) +#if LIBPEER_LOG_LEVEL >= LIBPEER_LOG_LEVEL_ERROR +#define LOGE(fmt, ...) LIBPEER_LOG_PRINT(ERROR_TAG, fmt, ##__VA_ARGS__) #else #define LOGE(fmt, ...) #endif diff --git a/third_party/libsrtp b/third_party/libsrtp index 90d05bf8..24b3bf8f 160000 --- a/third_party/libsrtp +++ b/third_party/libsrtp @@ -1 +1 @@ -Subproject commit 90d05bf8980d16e4ac3f16c19b77e296c4bc207b +Subproject commit 24b3bf8f19b6f5ab4cd2bcceb4f4064efca86fd5