From 454a36d32f8ddac7ef1cfaebc22e89b94f18dd08 Mon Sep 17 00:00:00 2001 From: sqqdfny Date: Fri, 17 Jul 2026 13:38:27 +0800 Subject: [PATCH 01/22] Fix: compilation error when only IPv4 is enabled (IPv6 disabled). --- src/address.c | 11 ++++++++++- src/address.h | 6 ++++++ src/dtls_srtp.c | 8 ++++---- src/ports.c | 10 ++++++++++ src/sctp.c | 2 +- src/socket.c | 16 ++++++++++++++++ src/stun.c | 9 ++++++++- 7 files changed, 55 insertions(+), 7 deletions(-) diff --git a/src/address.c b/src/address.c index 1618ede5..bd8cbaf2 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)); 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; 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/dtls_srtp.c b/src/dtls_srtp.c index dd546169..0e35c9ed 100644 --- a/src/dtls_srtp.c +++ b/src/dtls_srtp.c @@ -270,10 +270,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 + SRTP_MASTER_SALT_LENGTH; uint8_t *local_key, *remote_key, *local_salt, *remote_salt; if (dtls_srtp->role == DTLS_SRTP_ROLE_SERVER) { local_key = server_key; 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/sctp.c b/src/sctp.c index 644f8d8e..fe4774de 100644 --- a/src/sctp.c +++ b/src/sctp.c @@ -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); } } 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/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); From a3f46c26e9aa0c3a0a875b94cf9d622bf007d4b7 Mon Sep 17 00:00:00 2001 From: sqqdfny Date: Wed, 22 Jul 2026 13:06:27 +0800 Subject: [PATCH 02/22] =?UTF-8?q?1.=E5=A2=9E=E5=8A=A0=EF=BC=9ALIBPEER=5FUS?= =?UTF-8?q?E=5FSHARED=5FENTROPY=20=E5=AE=8F=EF=BC=8C=E7=94=A8=E4=BA=8E?= =?UTF-8?q?=E9=80=89=E6=8B=A9=E6=98=AF=E5=90=A6=E4=BD=BF=E7=94=A8=E5=85=A8?= =?UTF-8?q?=E5=B1=80=E5=85=B1=E4=BA=AB=E7=9A=84=E7=86=B5=E6=BA=90=EF=BC=88?= =?UTF-8?q?MBEDTLS=EF=BC=89=EF=BC=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/dtls_srtp.c | 11 ++++++++++- src/dtls_srtp.h | 2 ++ src/ssl_transport.c | 11 ++++++++++- src/ssl_transport.h | 2 ++ 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/dtls_srtp.c b/src/dtls_srtp.c index 0e35c9ed..9ce9ae10 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)); @@ -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); @@ -224,7 +231,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) { diff --git a/src/dtls_srtp.h b/src/dtls_srtp.h index 09d24017..d85c6c4f 100644 --- a/src/dtls_srtp.h +++ b/src/dtls_srtp.h @@ -44,7 +44,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 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; From 6755d1dc7586f2c2f02598c656df22cdd2163d31 Mon Sep 17 00:00:00 2001 From: sqqdfny Date: Thu, 23 Jul 2026 15:49:40 +0800 Subject: [PATCH 03/22] =?UTF-8?q?1.=E4=BF=AE=E5=A4=8D=EF=BC=9A=E5=B1=80?= =?UTF-8?q?=E5=9F=9F=E7=BD=91=E8=BF=9E=E6=8E=A5=E6=97=B6=EF=BC=8Cchorme=20?= =?UTF-8?q?=E8=BF=94=E5=9B=9E=E7=9A=84=20sdp=20=E4=B8=AD=E7=9A=84=E5=9C=B0?= =?UTF-8?q?=E5=9D=80=E4=B8=BA=20mDNS=20=E4=B8=BB=E6=9C=BA=E5=90=8D?= =?UTF-8?q?=EF=BC=8C=E6=AD=A4=E6=97=B6=20src/ice.c/ice=5Fcandidate=5Ffrom?= =?UTF-8?q?=5Fdescription()=20=E7=9A=84=20addrstring[]=20=E6=BA=A2?= =?UTF-8?q?=E5=87=BA=E5=AF=BC=E8=87=B4=E6=AD=BB=E6=9C=BA=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98=E3=80=82=202.=E4=BF=AE=E5=A4=8D:=20src/agent.c/agent?= =?UTF-8?q?=5Fset=5Fremote=5Fdescription()=20=E7=9A=84=20remote=5Fufrag/re?= =?UTF-8?q?mote=5Fupwd=20=E6=9C=89=E5=8F=AF=E8=83=BD=E6=BA=A2=E5=87=BA(?= =?UTF-8?q?=E6=B2=A1=E6=9C=89=E7=BB=93=E6=9D=9F=E7=AC=A6)=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98=E3=80=82=203.=E4=BF=AE=E5=A4=8D:=20src/addre?= =?UTF-8?q?ss.c/addr=5Fto=5Fstring()=20=E6=B8=85=E7=A9=BA=20buf=20?= =?UTF-8?q?=E7=9A=84=20bug=20=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/address.c | 2 +- src/agent.c | 68 +++++++++++++++++++++++++++++---------------------- src/ice.c | 5 ++-- 3 files changed, 43 insertions(+), 32 deletions(-) diff --git a/src/address.c b/src/address.c index bd8cbaf2..1f6090bb 100644 --- a/src/address.c +++ b/src/address.c @@ -49,7 +49,7 @@ int addr_from_string(const char* buf, Address* addr) { } 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: diff --git a/src/agent.c b/src/agent.c index 57de5c95..a2c5b5a9 100644 --- a/src/agent.c +++ b/src/agent.c @@ -395,36 +395,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; - } + /* + 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++; + } } - if (i == agent->remote_candidates_count) { - agent->remote_candidates_count++; - } - } } line_start = line_end + 2; diff --git a/src/ice.c b/src/ice.c index 756e961f..ea5cdf14 100644 --- a/src/ice.c +++ b/src/ice.c @@ -81,7 +81,7 @@ 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="); @@ -89,6 +89,7 @@ int ice_candidate_from_description(IceCandidate* candidate, char* description, c 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: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 if (sscanf(candidate_start, "%s %d %s %" PRIu32 " %s %" PRIu32 " typ %s", candidate->foundation, @@ -120,7 +121,7 @@ int ice_candidate_from_description(IceCandidate* candidate, char* description, c addr_set_port(&candidate->addr, port); - if (strstr(addrstring, "local") != NULL) { + if (strstr(addrstring, ".local") != NULL) { if (mdns_resolve_addr(addrstring, &candidate->addr) == 0) { LOGW("Failed to resolve mDNS address"); return -1; From 79a8501f090825ff1b9747d374be1f0f2ed3e17c Mon Sep 17 00:00:00 2001 From: sqqdfny Date: Mon, 27 Jul 2026 13:26:19 +0800 Subject: [PATCH 04/22] =?UTF-8?q?1.=E4=BF=AE=E6=94=B9:=20=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E6=89=93=E5=8D=B0=E5=AE=8F=20"#define=20LOG=5FLEVEL"?= =?UTF-8?q?=20->=20"#define=20LIBPEER=5FLOG=5FLEVEL",=20=E9=81=BF=E5=85=8D?= =?UTF-8?q?=E8=B7=9F=E4=B8=BB=E9=A1=B9=E7=9B=AE=E5=85=B6=E5=AE=83=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E7=9A=84=E7=9B=B8=E5=90=8C=E5=AE=9A=E4=B9=89=E4=BA=A7?= =?UTF-8?q?=E7=94=9F=E5=86=B2=E7=AA=81=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/config.h | 2 +- src/utils.h | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/config.h b/src/config.h index 94905a83..4b997926 100644 --- a/src/config.h +++ b/src/config.h @@ -65,7 +65,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/utils.h b/src/utils.h index 72040ae5..2481bdb2 100644 --- a/src/utils.h +++ b/src/utils.h @@ -6,18 +6,18 @@ #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 @@ -29,25 +29,25 @@ void peer_log(char* level_tag, const char* file_name, int line_number, const cha fprintf(stdout, "%s\t%s\t%d\t" fmt "\n", level_tag, __FILE__, __LINE__, ##__VA_ARGS__) #endif -#if LOG_LEVEL >= LEVEL_DEBUG +#if LIBPEER_LOG_LEVEL >= LIBPEER_LOG_LEVEL_DEBUG #define LOGD(fmt, ...) LOG_PRINT(DEBUG_TAG, fmt, ##__VA_ARGS__) #else #define LOGD(fmt, ...) #endif -#if LOG_LEVEL >= LEVEL_INFO +#if LIBPEER_LOG_LEVEL >= LIBPEER_LOG_LEVEL_INFO #define LOGI(fmt, ...) LOG_PRINT(INFO_TAG, fmt, ##__VA_ARGS__) #else #define LOGI(fmt, ...) #endif -#if LOG_LEVEL >= LEVEL_WARN +#if LIBPEER_LOG_LEVEL >= LIBPEER_LOG_LEVEL_WARN #define LOGW(fmt, ...) LOG_PRINT(WARN_TAG, fmt, ##__VA_ARGS__) #else #define LOGW(fmt, ...) #endif -#if LOG_LEVEL >= LEVEL_ERROR +#if LIBPEER_LOG_LEVEL >= LIBPEER_LOG_LEVEL_ERROR #define LOGE(fmt, ...) LOG_PRINT(ERROR_TAG, fmt, ##__VA_ARGS__) #else #define LOGE(fmt, ...) From 4a91f51e1b749e998ecab493e1fc1c53d210d2dd Mon Sep 17 00:00:00 2001 From: sqqdfny Date: Tue, 28 Jul 2026 10:36:04 +0800 Subject: [PATCH 05/22] =?UTF-8?q?1.=E4=BF=AE=E5=A4=8D:=20=E5=B1=80?= =?UTF-8?q?=E5=9F=9F=E7=BD=91=E4=B8=AD=20PC=20=E9=80=9A=E8=BF=87=20WIFI=20?= =?UTF-8?q?=E7=9B=B4=E8=BF=9E=E8=AE=BE=E5=A4=87=E6=97=B6=EF=BC=8CmDNS=20?= =?UTF-8?q?=E5=8D=8F=E8=AE=AE=E4=BC=9A=E8=A7=A3=E6=9E=90=E5=88=B0PC?= =?UTF-8?q?=E7=9A=84=E6=9C=89=E7=BA=BF=E7=BD=91=E5=8F=A3=20IP=20=E5=9C=B0?= =?UTF-8?q?=E5=9D=80=EF=BC=8C=E5=AF=BC=E8=87=B4=E8=BF=9E=E6=8E=A5=E5=BB=BA?= =?UTF-8?q?=E7=AB=8B=E5=A4=B1=E8=B4=A5=E7=9A=84=E9=97=AE=E9=A2=98=E3=80=82?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20-=20src/agent.c=20agent=5Fprocess=5F?= =?UTF-8?q?stun=5Frequest()/agent=5Fconnectivity=5Fcheck()=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20-=20src/ice.c=20=20ice=5Fcandidate=5Ffrom=5Fdescrip?= =?UTF-8?q?tion()=20=20=20=20=20=20=20=20=20-=20src/peer=5Fconnection.c=20?= =?UTF-8?q?peer=5Fconnection=5Floop()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 2.修复: peer_connection_datachannel_send() 的 sid 固定使用 0 的问题 - src/peer_connection.c peer_connection_datachannel_send() - src/sctp.c sctp_incoming_data() --- src/agent.c | 29 +++++++++++++++++++++++++++++ src/ice.c | 13 ++++++++++++- src/peer_connection.c | 17 +++++++++++++++-- src/sctp.c | 9 +++++++-- 4 files changed, 63 insertions(+), 5 deletions(-) diff --git a/src/agent.c b/src/agent.c index a2c5b5a9..e31fa48b 100644 --- a/src/agent.c +++ b/src/agent.c @@ -350,6 +350,26 @@ 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]; + } } break; default: @@ -466,6 +486,15 @@ 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; + } + if (agent->nominated_pair->state != ICE_CANDIDATE_STATE_INPROGRESS) { LOGI("nominated pair is not in progress"); return -1; diff --git a/src/ice.c b/src/ice.c index ea5cdf14..c9bf931a 100644 --- a/src/ice.c +++ b/src/ice.c @@ -122,7 +122,18 @@ 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) { + /** + * 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_connection.c b/src/peer_connection.c index c763e806..774be3ed 100644 --- a/src/peer_connection.c +++ b/src/peer_connection.c @@ -214,7 +214,12 @@ int peer_connection_send_video(PeerConnection* pc, const uint8_t* buf, size_t le } 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,7 +300,15 @@ 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)); } else if (agent_connectivity_check(&pc->agent) == 0) { STATE_CHANGED(pc, PEER_CONNECTION_CONNECTED); } diff --git a/src/sctp.c b/src/sctp.c index fe4774de..b7572a8a 100644 --- a/src/sctp.c +++ b/src/sctp.c @@ -259,13 +259,18 @@ void sctp_incoming_data(Sctp* sctp, char* buf, size_t len) { sack_chunk->a_rwnd = htonl(0x02); 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)); From d1af1e8f504f6fcf8cf01b2a23916ad3d71ee55c Mon Sep 17 00:00:00 2001 From: sqqdfny Date: Wed, 29 Jul 2026 18:11:40 +0800 Subject: [PATCH 06/22] =?UTF-8?q?1.=E4=BF=AE=E6=94=B9:=20PEER=5FCONNECTION?= =?UTF-8?q?=5FCHECKING/PEER=5FCONNECTION=5FCONNECTED=20=E4=B8=A4=E7=A7=8D?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E5=A2=9E=E5=8A=A0=E8=B6=85=E6=97=B6=E5=A4=84?= =?UTF-8?q?=E7=90=86=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/config.h | 9 +++++++++ src/peer_connection.c | 45 ++++++++++++++++++++++++++++++++++--------- 2 files changed, 45 insertions(+), 9 deletions(-) diff --git a/src/config.h b/src/config.h index 4b997926..ba33fa17 100644 --- a/src/config.h +++ b/src/config.h @@ -49,6 +49,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 diff --git a/src/peer_connection.c b/src/peer_connection.c index 774be3ed..b4646164 100644 --- a/src/peer_connection.c +++ b/src/peer_connection.c @@ -45,6 +45,8 @@ struct PeerConnection { uint32_t remote_assrc; uint32_t remote_vssrc; + + uint32_t handshake_start_time; }; static void peer_connection_outgoing_rtp_packet(uint8_t* data, size_t size, void* user_data) { @@ -300,16 +302,31 @@ int peer_connection_loop(PeerConnection* pc) { case PEER_CONNECTION_CHECKING: if (agent_select_candidate_pair(&pc->agent) < 0) { - /** - * 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)); + { + /** + * 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; @@ -325,7 +342,17 @@ int peer_connection_loop(PeerConnection* pc) { 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: From ae06efde7422e2c3ca9a0ab0a211f3aafa0deda6 Mon Sep 17 00:00:00 2001 From: sqqdfny Date: Fri, 31 Jul 2026 15:54:27 +0800 Subject: [PATCH 07/22] =?UTF-8?q?1.=E6=96=B0=E5=A2=9E:=20config.h=20?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=20SCTP=5FLOCAL=5FRWND(0x100000)=EF=BC=8Csctp?= =?UTF-8?q?.c=20=E7=9A=84=20SACK/INIT/INIT=5FACK=20=E7=BB=9F=E4=B8=80?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E8=AF=A5=E5=AE=8F=E6=9B=BF=E6=8D=A2=20magic?= =?UTF-8?q?=20number=202.=E4=BF=AE=E6=94=B9:=20sctp=5Fincoming=5Fdata()=20?= =?UTF-8?q?=E5=BE=AA=E7=8E=AF=E9=98=B2=E6=8A=A4=E2=80=94=E2=80=94length=20?= =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=E6=B8=85=E9=9B=B6=EF=BC=9Bchunk=20?= =?UTF-8?q?=E9=95=BF=E5=BA=A6=20<=20sizeof(SctpChunkCommon)=20=E6=97=B6=20?= =?UTF-8?q?break=20=E9=98=B2=E6=AD=BB=E5=BE=AA=E7=8E=AF=EF=BC=9B=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=E6=95=B0=E6=8D=AE=E5=9D=97=E6=8C=89=204=20?= =?UTF-8?q?=E5=AD=97=E8=8A=82=E5=AF=B9=E9=BD=90=E6=8E=A8=E8=BF=9B=20pos=20?= =?UTF-8?q?3.=E4=BF=AE=E6=94=B9:=20peer=5Fconnection.c=20COMPLETED=20?= =?UTF-8?q?=E7=8A=B6=E6=80=81=20agent=5Frecv=20=E6=94=B9=E4=B8=BA=E5=8D=95?= =?UTF-8?q?=E6=AC=A1=E6=9C=80=E5=A4=9A=2016=20=E4=B8=AA=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E6=8A=A5=E7=9A=84=20burst=20=E5=BE=AA=E7=8E=AF=EF=BC=8C=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=E9=81=BF=E5=85=8D=E5=8D=95=E5=B8=A7=20SAC?= =?UTF-8?q?K=20=E7=AA=81=E5=8F=91=E6=92=91=E7=88=86=20UDP=20=E6=8E=A5?= =?UTF-8?q?=E6=94=B6=E9=98=9F=E5=88=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/config.h | 4 ++++ src/peer_connection.c | 10 +++++++++- src/sctp.c | 17 ++++++++++++----- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/config.h b/src/config.h index ba33fa17..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 diff --git a/src/peer_connection.c b/src/peer_connection.c index b4646164..63b90cb5 100644 --- a/src/peer_connection.c +++ b/src/peer_connection.c @@ -19,6 +19,11 @@ 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 + struct PeerConnection { PeerConfiguration config; PeerConnectionState state; @@ -356,7 +361,10 @@ int peer_connection_loop(PeerConnection* pc) { } 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)) { diff --git a/src/sctp.c b/src/sctp.c index b7572a8a..5fd2725b 100644 --- a/src/sctp.c +++ b/src/sctp.c @@ -246,6 +246,7 @@ void sctp_incoming_data(Sctp* sctp, char* buf, size_t len) { memset(sctp->buf, 0, sizeof(sctp->buf)); while ((4 * (pos + 3) / 4) < len) { chunk_common = (SctpChunkCommon*)(buf + pos); + length = 0; // only branches that build a reply set it, otherwise nothing is sent switch (chunk_common->type) { case SCTP_DATA: { @@ -256,7 +257,7 @@ 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, sid = %u", ntohl(data_chunk->ppid), data_chunk->data[0], ntohs(data_chunk->sid)); @@ -282,7 +283,6 @@ void sctp_incoming_data(Sctp* sctp, char* buf, size_t len) { sctp->userdata, ntohs(data_chunk->sid)); } } - pos = len; // Do not handle other msg } break; case SCTP_INIT: { LOGD("SCTP_INIT"); @@ -296,7 +296,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); @@ -413,7 +413,14 @@ 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); + + if (pos < len) { + uint16_t chunk_len = ntohs(chunk_common->length); + if (chunk_len < sizeof(SctpChunkCommon)) { + break; // malformed length would stall the loop + } + pos += 4 * ((chunk_len + 3) / 4); // chunks are padded to a 4-byte boundary + } } #endif } @@ -624,7 +631,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); From 807af9a2c7f2e9ed23e4118c9f70759bebb17b58 Mon Sep 17 00:00:00 2001 From: sqqdfny Date: Tue, 4 Aug 2026 09:29:36 +0800 Subject: [PATCH 08/22] =?UTF-8?q?1.=E4=BF=AE=E5=A4=8D=E7=BC=96=E8=AF=91?= =?UTF-8?q?=E8=AD=A6=E5=91=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/peer_connection.c | 1 + src/sctp.h | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/src/peer_connection.c b/src/peer_connection.c index 63b90cb5..55ffe2bf 100644 --- a/src/peer_connection.c +++ b/src/peer_connection.c @@ -437,6 +437,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") && diff --git a/src/sctp.h b/src/sctp.h index 1116996a..bbeafe39 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, @@ -188,4 +192,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_ From 76159a6092b98c8672705bbaba2fbd093b239a45 Mon Sep 17 00:00:00 2001 From: sqqdfny Date: Tue, 4 Aug 2026 16:59:53 +0800 Subject: [PATCH 09/22] =?UTF-8?q?feat:=20sctp=20=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E5=AF=B9=20HEARTBEAT/ACK=E3=80=81SHUTDOWN/ACK/COMPLETE?= =?UTF-8?q?=E3=80=81ERROR=E3=80=81FORWARD=5FTSN=20=E7=AD=89=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E7=9A=84=E5=A4=84=E7=90=86=EF=BC=8C=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E9=95=BF=E6=97=B6=E9=97=B4=E8=BF=9E=E6=8E=A5=E5=BC=82=E5=B8=B8?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98=E3=80=82=20=20=20=20-=20peer=5Fconn?= =?UTF-8?q?ection:=20=E5=88=9D=E5=A7=8B=E5=8C=96=20state=20=E4=B8=BA=20NEW?= =?UTF-8?q?=EF=BC=8CSCTP=20=E5=85=B3=E8=81=94=E5=A4=B1=E8=B4=A5=E6=97=B6?= =?UTF-8?q?=E8=A7=A6=E5=8F=91CLOSED=EF=BC=8Cclose/=E8=B6=85=E6=97=B6?= =?UTF-8?q?=E7=BB=9F=E4=B8=80=E4=BD=BF=E7=94=A8=20STATE=5FCHANGED=20?= =?UTF-8?q?=E5=AE=8F=E9=80=9A=E7=9F=A5=E7=8A=B6=E6=80=81=E5=8F=98=E6=9B=B4?= =?UTF-8?q?=20=20=20=20-=20sctp:=20=E6=96=B0=E5=A2=9E=20HEARTBEAT/ACK?= =?UTF-8?q?=E3=80=81SHUTDOWN/ACK/COMPLETE=E3=80=81ERROR=E3=80=81FORWARD=5F?= =?UTF-8?q?TSN=20=E7=AD=89=20chunk=20=E5=AE=8C=E6=95=B4=E5=A4=84=E7=90=86?= =?UTF-8?q?=EF=BC=8CABORT=20=E6=97=B6=E7=BD=AE=E4=BD=8Dassociation=5Ffaile?= =?UTF-8?q?d=EF=BC=8C=20=20=20=20=20=20=E4=BF=AE=E5=A4=8D=20cookie=5Fecho?= =?UTF-8?q?=20=E7=A9=BA=E6=8C=87=E9=92=88=EF=BC=8C=E4=BF=AE=E5=A4=8D=20chu?= =?UTF-8?q?nk=20=E5=BE=AA=E7=8E=AF=E8=BE=B9=E7=95=8C=E8=AE=A1=E7=AE=97?= =?UTF-8?q?=EF=BC=8Ccreate=5Fassociation=20=E5=88=9D=E5=A7=8B=E5=8C=96?= =?UTF-8?q?=E5=A4=B1=E8=B4=A5=E6=A0=87=E5=BF=97=E4=BD=8D=20=20=20=20-=20sc?= =?UTF-8?q?tp.h:=20=E6=96=B0=E5=A2=9E=20association=5Ffailed=20=E5=AD=97?= =?UTF-8?q?=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/peer_connection.c | 10 ++++- src/sctp.c | 94 ++++++++++++++++++++++++++++++++++++------- src/sctp.h | 1 + 3 files changed, 90 insertions(+), 15 deletions(-) diff --git a/src/peer_connection.c b/src/peer_connection.c index 55ffe2bf..213c2eac 100644 --- a/src/peer_connection.c +++ b/src/peer_connection.c @@ -171,6 +171,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); @@ -201,7 +203,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) { @@ -397,6 +399,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); diff --git a/src/sctp.c b/src/sctp.c index 5fd2725b..bf24b672 100644 --- a/src/sctp.c +++ b/src/sctp.c @@ -242,10 +242,15 @@ 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) { @@ -328,10 +333,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: @@ -389,8 +396,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); } @@ -414,13 +483,7 @@ void sctp_incoming_data(Sctp* sctp, char* buf, size_t len) { // sctp_outgoing_data_cb(sctp, sctp->buf, SCTP_MTU, 0, 0); } - if (pos < len) { - uint16_t chunk_len = ntohs(chunk_common->length); - if (chunk_len < sizeof(SctpChunkCommon)) { - break; // malformed length would stall the loop - } - pos += 4 * ((chunk_len + 3) / 4); // chunks are padded to a 4-byte boundary - } + pos += ((chunk_len + 3) / 4) * 4; // chunks are padded to a 4-byte boundary } #endif } @@ -617,6 +680,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; diff --git a/src/sctp.h b/src/sctp.h index bbeafe39..d08cec2f 100644 --- a/src/sctp.h +++ b/src/sctp.h @@ -157,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; From 886c7cdd6342d5fb2e209d7d51e2e07c4fef942a Mon Sep 17 00:00:00 2001 From: sqqdfny Date: Tue, 4 Aug 2026 18:55:18 +0800 Subject: [PATCH 10/22] =?UTF-8?q?refactor(utils.h):=20=20=20=20-=20?= =?UTF-8?q?=E7=A7=BB=E9=99=A4=E6=97=A7=E7=9A=84=20LOG=5FREDIRECT=20?= =?UTF-8?q?=E6=9D=A1=E4=BB=B6=E5=88=86=E6=94=AF=EF=BC=8CLOG=5FPRINT=20?= =?UTF-8?q?=E9=87=8D=E5=91=BD=E5=90=8D=E4=B8=BA=20LIBPEER=5FLOG=5FPRINT?= =?UTF-8?q?=EF=BC=8C=E9=80=9A=E8=BF=87=20#ifndef=20=E5=85=81=E8=AE=B8?= =?UTF-8?q?=E5=A4=96=E9=83=A8=E8=A6=86=E7=9B=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils.h | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/utils.h b/src/utils.h index 2481bdb2..e4c6cd3e 100644 --- a/src/utils.h +++ b/src/utils.h @@ -20,35 +20,31 @@ #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 LIBPEER_LOG_LEVEL >= LIBPEER_LOG_LEVEL_DEBUG -#define LOGD(fmt, ...) LOG_PRINT(DEBUG_TAG, fmt, ##__VA_ARGS__) +#define LOGD(fmt, ...) LIBPEER_LOG_PRINT(DEBUG_TAG, fmt, ##__VA_ARGS__) #else #define LOGD(fmt, ...) #endif #if LIBPEER_LOG_LEVEL >= LIBPEER_LOG_LEVEL_INFO -#define LOGI(fmt, ...) LOG_PRINT(INFO_TAG, fmt, ##__VA_ARGS__) +#define LOGI(fmt, ...) LIBPEER_LOG_PRINT(INFO_TAG, fmt, ##__VA_ARGS__) #else #define LOGI(fmt, ...) #endif #if LIBPEER_LOG_LEVEL >= LIBPEER_LOG_LEVEL_WARN -#define LOGW(fmt, ...) LOG_PRINT(WARN_TAG, fmt, ##__VA_ARGS__) +#define LOGW(fmt, ...) LIBPEER_LOG_PRINT(WARN_TAG, fmt, ##__VA_ARGS__) #else #define LOGW(fmt, ...) #endif #if LIBPEER_LOG_LEVEL >= LIBPEER_LOG_LEVEL_ERROR -#define LOGE(fmt, ...) LOG_PRINT(ERROR_TAG, fmt, ##__VA_ARGS__) +#define LOGE(fmt, ...) LIBPEER_LOG_PRINT(ERROR_TAG, fmt, ##__VA_ARGS__) #else #define LOGE(fmt, ...) #endif From 3ddd7462deced4b54e91b9079ba1aaf0de044c83 Mon Sep 17 00:00:00 2001 From: sqqdfny Date: Fri, 7 Aug 2026 15:58:28 +0800 Subject: [PATCH 11/22] =?UTF-8?q?fix(libpeer):=20ICE=20Agent=20=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E4=BB=8E=20inbound=20STUN=20request=20=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=20peer-reflexive=20candidate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复 Android 手机浏览器 WebRTC MJPG 推流失败的问题。 问题: QEMU 做 WiFi AP 时,Android Chrome 在 SDP answer 中给出的 ICE candidate 是蜂窝数据接口 IP(10.65.209.x)而非 WiFi 分配的局域网 IP。 设备端发往这个不可达 IP 的 STUN binding request 全部 FAILED, ICE 永远卡在 checking 状态。 根因: agent_process_stun_request() 收到浏览器发来的 valid STUN binding request 后只回复 response,不标记 candidate pair 为 SUCCEEDED,也不从 source address 创建 peer-reflexive candidate。违反 RFC 8445 §7.2.5.3.2。 修复 (3 个文件): 1. address.c — 实现 addr_equal() 桩函数 - 之前始终返回 1 (TODO 未实现) - 改为对 AF_INET 比较 s_addr, AF_INET6 用 memcmp 2. agent.c — agent_process_stun_request() 新增两阶段匹配 - Phase A: 收到 STUN request 时,用 addr_equal 匹配 source address 到已有 remote candidates,匹配成功则标记 pair 为 SUCCEEDED - Phase B: 匹配失败时,从 source address 创建 PRFLX remote candidate + candidate pair(已验证可达的真实 WiFi 地址) - 现有 mDNS fallback 路径(candidate_pairs_num==0)完全不变 - agent_connectivity_check() 新增 SUCCEEDED 状态提前返回, 确保被 inbound STUN 标记 pair 能正确定向 PEER_CONNECTION_CONNECTED 3. ice.c — 补全 PRFLX 候选类型 - ice_candidate_type_preference: PRFLX = 110 (RFC 5245, HOST=126 和 SRFLX=100 之间) - ice_candidate_to_description: 添加 prflx 类型字符串 --- src/address.c | 17 +++++++++++++++-- src/agent.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/ice.c | 13 ++++++++++--- 3 files changed, 75 insertions(+), 5 deletions(-) diff --git a/src/address.c b/src/address.c index 1f6090bb..b16742d0 100644 --- a/src/address.c +++ b/src/address.c @@ -63,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/agent.c b/src/agent.c index e31fa48b..eb40690e 100644 --- a/src/agent.c +++ b/src/agent.c @@ -369,6 +369,50 @@ void agent_process_stun_request(Agent* agent, StunMessage* stun_msg, Address* ad 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; @@ -495,6 +539,12 @@ int agent_connectivity_check(Agent* agent) { 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/ice.c b/src/ice.c index c9bf931a..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: @@ -88,9 +93,11 @@ int ice_candidate_from_description(IceCandidate* candidate, char* description, c } 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: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: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, From 2714d8f50d244b5783f9b5005efefa2eec5b1aa3 Mon Sep 17 00:00:00 2001 From: sqqdfny Date: Tue, 11 Aug 2026 10:00:20 +0800 Subject: [PATCH 12/22] =?UTF-8?q?fix(webrtc):=20SCTP=5FDATA=20=E6=8E=A5?= =?UTF-8?q?=E6=94=B6=E8=B7=AF=E5=BE=84=20SACK=20=E5=85=88=E4=BA=8E=20onmes?= =?UTF-8?q?sage=20=E5=8F=91=E9=80=81=EF=BC=8C=E6=B6=88=E9=99=A4=20ping-pon?= =?UTF-8?q?g=20=E6=AD=BB=E5=BE=AA=E7=8E=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sctp_incoming_data() 处理 SCTP_DATA chunk 时,SACK 确认包构造在 共享输出缓冲区 sctp->buf 中,随后调用 sctp->onmessage() 回调。 若回调内部通过 peer_connection_datachannel_send() → sctp_outgoing_data() 发送数据(如 pong 响应),sctp_outgoing_data() 会覆写 sctp->buf。 回调返回后,代码将被覆写的缓冲区(现包含 pong 数据而非 SACK) 再次发送给对端。 浏览器 SCTP 栈收到损坏的"SACK",无法确认原始 ping 已送达, 触发重传 → 开发板再次收到 ping → 再次发送 pong → 再次收到损坏 SACK → 形成无限循环(表现:浏览器发一次 ping,持续收到无数 pong)。 修复:在 DATA_CHANNEL_PPID_DOMSTRING / _BINARY 分支中,将 SACK 的 发送移到 onmessage 回调之前,发送后置 length = 0 防止重复发送。 DCEP OPEN 分支(PPID_CONTROL)不调用 onmessage,不受影响。 同时修复 sctp_outgoing_data() 中 chunk->sid 被硬编码为 htons(0) 的缺陷,使其正确使用传入的 sid 参数。 --- src/sctp.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/sctp.c b/src/sctp.c index bf24b672..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); @@ -283,6 +283,21 @@ void sctp_incoming_data(Sctp* sctp, char* buf, size_t len) { 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)); From a715a045f89043121f1963e894c0b10f56d6a972 Mon Sep 17 00:00:00 2001 From: sqqdfny Date: Tue, 11 Aug 2026 15:55:47 +0800 Subject: [PATCH 13/22] =?UTF-8?q?fix(src/peer=5Fconnection.c):=20=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=20SDP=20=E6=97=B6=EF=BC=8C=E5=A6=82=E6=9E=9C=E6=9C=89?= =?UTF-8?q?=E5=A4=9A=E4=B8=AA=20"m=3D"=20=E8=A1=8C=EF=BC=8C=E6=B5=8F?= =?UTF-8?q?=E8=A7=88=E5=99=A8=E4=B8=8D=E5=93=8D=E5=BA=94=20STUN=20Binding?= =?UTF-8?q?=EF=BC=8C=E4=B9=9F=E4=B8=8D=E5=8F=91=E9=80=81=20STUN=20Binding?= =?UTF-8?q?=20=E7=9A=84=E9=97=AE=E9=A2=98=E3=80=82=20=20=20-=20=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=20SDP=20=E6=97=B6=EF=BC=8C=E5=A6=82=E6=9C=89=E5=A4=9A?= =?UTF-8?q?=E4=B8=AA=20"m=3D"=20=E8=A1=8C=EF=BC=8C=E6=AF=94=E5=A6=82?= =?UTF-8?q?=E8=AF=B4=E5=90=8C=E6=97=B6=E5=90=AF=E7=94=A8=20video/audio/dat?= =?UTF-8?q?achannel=EF=BC=8C=E9=9C=80=E8=A6=81=E6=8A=8A=20"a=3Dcandidate"?= =?UTF-8?q?=20=E6=94=BE=E5=88=B0=E7=AC=AC=E4=B8=80=E4=B8=AA=20"m=3D"=20?= =?UTF-8?q?=E8=A1=8C=E4=B9=8B=E5=90=8E=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/peer_connection.c | 51 +++++++++++++++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 12 deletions(-) diff --git a/src/peer_connection.c b/src/peer_connection.c index 213c2eac..7a98e50a 100644 --- a/src/peer_connection.c +++ b/src/peer_connection.c @@ -480,8 +480,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; @@ -519,19 +519,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; } @@ -540,19 +575,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); } From ddf6c5f23cda05058d7cd72d7766e1f64fef6453 Mon Sep 17 00:00:00 2001 From: sqqdfny Date: Wed, 12 Aug 2026 08:56:24 +0800 Subject: [PATCH 14/22] =?UTF-8?q?fix(libpeer):=20DTLS-SRTP=20srtp=5Fin=20?= =?UTF-8?q?=E7=A9=BA=E6=8C=87=E9=92=88=E5=B4=A9=E6=BA=83=E9=98=B2=E5=BE=A1?= =?UTF-8?q?=20+=20=E5=AF=86=E9=92=A5=E6=B4=BE=E7=94=9F=E5=A4=B1=E8=B4=A5?= =?UTF-8?q?=E6=84=9F=E7=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根因:mbedtls 3.x 的 ssl_export_keys_cb 回调签名为 void 无返回值, 若 DTLS Server 的 HELLO_VERIFY_REQUIRED 重试循环中第一次 srtp_create 成功但第二次 srtp_add_stream 失败(srtp_create 内部显式置 *session=NULL), 错误被静默吞噬。DTLS 握手报成功但 srtp_in 为 NULL, PEER_CONNECTION_COMPLETED 状态下收到 RTCP 包触发 Load Access Fault。 修复(dtls_srtp.c): - dtls_srtp_init: 显式清零 srtp_in/srtp_out,防 HELLO_VERIFY_REQUIRED 重试时旧 session 被覆写泄漏 - dtls_srtp_reset_session: srtp_dealloc 后置 NULL,消除悬垂指针 - 四个加解密函数 (decrypt_rtp/rtcp, encrypt_rtp/rtcp): 调用前加 srtp_in/srtp_out 空指针守卫,防御所有路径的空指针崩溃 - srtp_create 失败日志等级 LOGD→LOGE 修复(peer_connection.c): - PEER_CONNECTION_CONNECTED → dtls_srtp_handshake 成功后检查 dtls_srtp.state != DTLS_SRTP_STATE_CONNECTED,若密钥派生回调 静默失败则标记 PEER_CONNECTION_FAILED,触发上层 webrtc_loop_task 自动 cleanup + 重建 --- src/dtls_srtp.c | 23 ++++++++++++++++++----- src/peer_connection.c | 6 ++++++ 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/dtls_srtp.c b/src/dtls_srtp.c index 9ce9ae10..fb5f7520 100644 --- a/src/dtls_srtp.c +++ b/src/dtls_srtp.c @@ -222,6 +222,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; } @@ -310,7 +313,7 @@ static int dtls_srtp_key_derivation(DtlsSrtp* dtls_srtp, const unsigned char* ma 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; } @@ -477,6 +480,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); } @@ -516,17 +521,25 @@ int dtls_srtp_probe(uint8_t* buf) { } void dtls_srtp_decrypt_rtp_packet(DtlsSrtp* dtls_srtp, uint8_t* packet, int* bytes) { - srtp_unprotect(dtls_srtp->srtp_in, packet, bytes); + if (dtls_srtp->srtp_in) { + srtp_unprotect(dtls_srtp->srtp_in, packet, bytes); + } } void dtls_srtp_decrypt_rtcp_packet(DtlsSrtp* dtls_srtp, uint8_t* packet, int* bytes) { - srtp_unprotect_rtcp(dtls_srtp->srtp_in, packet, bytes); + if (dtls_srtp->srtp_in) { + srtp_unprotect_rtcp(dtls_srtp->srtp_in, packet, bytes); + } } void dtls_srtp_encrypt_rtp_packet(DtlsSrtp* dtls_srtp, uint8_t* packet, int* bytes) { - srtp_protect(dtls_srtp->srtp_out, packet, bytes); + if (dtls_srtp->srtp_out) { + srtp_protect(dtls_srtp->srtp_out, packet, bytes); + } } void dtls_srtp_encrypt_rctp_packet(DtlsSrtp* dtls_srtp, uint8_t* packet, int* bytes) { - srtp_protect_rtcp(dtls_srtp->srtp_out, packet, bytes); + if (dtls_srtp->srtp_out) { + srtp_protect_rtcp(dtls_srtp->srtp_out, packet, bytes); + } } diff --git a/src/peer_connection.c b/src/peer_connection.c index 7a98e50a..9145e541 100644 --- a/src/peer_connection.c +++ b/src/peer_connection.c @@ -343,6 +343,12 @@ 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); From e2c281a4b8cde520714cfde98ad7922af0524268 Mon Sep 17 00:00:00 2001 From: sqqdfny Date: Wed, 12 Aug 2026 10:02:24 +0800 Subject: [PATCH 15/22] =?UTF-8?q?fix(include):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E7=BC=96=E8=AF=91=E9=94=99=E8=AF=AF=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/peer.h | 2 +- include/peer_connection.h | 2 +- include/peer_signaling.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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 From dba1473a75a5c9c83b522850cef94df1d47af5b0 Mon Sep 17 00:00:00 2001 From: sqqdfny Date: Fri, 14 Aug 2026 18:35:00 +0800 Subject: [PATCH 16/22] =?UTF-8?q?fix(libpeer):=20H264=20RTP=20=E5=B0=81?= =?UTF-8?q?=E5=8C=85=E6=94=B9=E4=B8=BA=20per-access-unit=20=E6=97=B6?= =?UTF-8?q?=E9=97=B4=E6=88=B3=E8=AF=AD=E4=B9=89=EF=BC=88RFC=206184?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复 Chrome 播放 /webrtc-h264 流时画面仅顶部约 1/4 正常、帧率极低、 疑似只有 I-frame 被解析的问题。 根因:rtp_encoder_encode_h264 对一帧内的每个 slice NAL 单独递增 RTP timestamp 并置 marker=1。源流 x264 slices=3,一帧 3 个 slice 被接收端 按 timestamp 归为 3 个独立"帧":slice 0(画面顶部)单独可解,slice 1/2 跨 slice 引用宏块必然解错;仅 IDR 首 slice 干净,GOP 间隔 2s 呈现为 "只有 I-frame 被解析"。 修复: - h264 single/FU-A 编码路径 marker 仅帧末 NAL 末包置位(is_last), 整个 access unit 共享同一 RTP timestamp,循环结束后仅递增一次 - NAL 起始码剥离改为按 4 字节起始码精确剥离(删除原剥尾部 0x00 循环) - 默认媒体时钟步进改为 90000/15(原硬编码 90000/30,源为 15fps) - 新增 rtp_encoder_set_timestamp_increment() 与 peer_connection_set_video_timestamp_increment(),供上层按实际 pts 动态调整媒体时钟(webrtc_h264.cpp 调用) - 帧级取证日志默认关闭(#if 0) 验证:320x240@15fps slices=3 源流经 RTP/SRTP 推流,aiortc 客户端 解码 dump 帧与源视频逐字节一致(Y-PSNR 99dB);AU 结构 P 帧 [1,1,1]、IDR [7,8,5,5,5] 同 timestamp,IDR 间隔精确 180000(2s GOP); 3 次 PLI 均触发 IDR 缓存重发(idr_resends=3),恢复延迟 <1s。 --- src/dtls_srtp.h | 2 +- src/peer.c | 2 +- src/peer_connection.c | 4 +++ src/peer_connection.h | 3 +++ src/rtp.c | 60 ++++++++++++++++++++++++++----------------- src/rtp.h | 5 ++++ src/sdp.c | 8 +++--- third_party/libsrtp | 2 +- 8 files changed, 55 insertions(+), 31 deletions(-) diff --git a/src/dtls_srtp.h b/src/dtls_srtp.h index d85c6c4f..5c55e3e5 100644 --- a/src/dtls_srtp.h +++ b/src/dtls_srtp.h @@ -13,7 +13,7 @@ #include #include -#include +#include #include "address.h" 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 9145e541..1430b052 100644 --- a/src/peer_connection.c +++ b/src/peer_connection.c @@ -222,6 +222,10 @@ 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) { /** * Use the actual sid from the SCTP stream table (negotiated by the browser's DCEP OPEN), diff --git a/src/peer_connection.h b/src/peer_connection.h index b087893e..d7945e7f 100644 --- a/src/peer_connection.h +++ b/src/peer_connection.h @@ -117,6 +117,9 @@ 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); + 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/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/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/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 From 783e51f343a03e0c1ef08b63de19edb46a5b6fa6 Mon Sep 17 00:00:00 2001 From: sqqdfny Date: Tue, 18 Aug 2026 15:35:08 +0800 Subject: [PATCH 17/22] =?UTF-8?q?feat(libpeer):=20=E6=96=B0=E5=A2=9E=20RTC?= =?UTF-8?q?P=20generic=20NACK=20=E9=87=8D=E4=BC=A0=EF=BC=8C=E5=AF=86?= =?UTF-8?q?=E6=96=87=E7=BA=A7=20ring=20=E7=BC=93=E5=AD=98=EF=BC=88RFC=2045?= =?UTF-8?q?85=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 加密后 RTP 包原样缓存(含 SRTP auth tag),重发保持原 seq/ts/ssrc, 规避明文重加密被发送侧 rdbx 以 pkt_idx_old 拒绝的路径;接收侧重放 窗口对真丢失包放行、对竞态重复包丢弃 - 解析 RTCP RTPFB (fmt=1) generic NACK:FCI 取 PID + 16bit BLP 位图, 仅响应本地视频 SSRC(pc->vrtp_encoder.ssrc)的丢失包 - ring 为 struct 末尾柔性数组(nack_ring[0]),槽位数由新增 config.nack_ring_packets 决定:0 = 禁用(不缓存、不重传、不占内存); 非 0 必须为 2 的幂,create 时校验,非法直接失败 - 同余槽位以 seq 匹配实现窗口语义:seq 滑出 ring 后槽位内容必不匹配, 天然失效,无需显式清槽 - calloc 总大小溢出守卫(SIZE_MAX / sizeof(nack_ring_entry_t)), 防 RV32 下 32 位 size_t 计算回绕越界写 - 新增 peer_connection_get_nack_retransmits() 统计接口 --- src/peer_connection.c | 89 ++++++++++++++++++++++++++++++++++++++++++- src/peer_connection.h | 8 +++- 2 files changed, 95 insertions(+), 2 deletions(-) diff --git a/src/peer_connection.c b/src/peer_connection.c index 1430b052..19a990a5 100644 --- a/src/peer_connection.c +++ b/src/peer_connection.c @@ -24,6 +24,22 @@ // 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(10B) 余量 */ + +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; @@ -52,11 +68,27 @@ struct PeerConnection { uint32_t remote_vssrc; uint32_t handshake_start_time; + + uint32_t nack_retransmits; /* NACK 重传包计数 (config.nack_ring_packets==0 时恒 0) */ + + /* 柔性数组 (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); + /* 缓存密文(含 auth tag)供 NACK 原样重发;只缓存视频轨 */ + if (pc->config.nack_ring_packets > 0 && (data[1] & 0x7F) == PT_H264 && + 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); + } agent_send(&pc->agent, data, size); } @@ -91,6 +123,19 @@ 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) { + agent_send(&pc->agent, e->data, e->len); + pc->nack_retransmits++; + } +} + static void peer_connection_incoming_rtcp(PeerConnection* pc, uint8_t* buf, size_t len) { RtcpHeader* rtcp_header; size_t pos = 0; @@ -121,6 +166,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; @@ -155,12 +224,30 @@ PeerConnectionState peer_connection_get_state(PeerConnection* pc) { return pc->state; } +uint32_t peer_connection_get_nack_retransmits(PeerConnection* pc) { + return pc->nack_retransmits; +} + 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; } diff --git a/src/peer_connection.h b/src/peer_connection.h index d7945e7f..490e53a0 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; @@ -120,6 +123,9 @@ int peer_connection_send_video(PeerConnection* pc, const uint8_t* packet, size_t // 按实际视频帧间隔调整媒体时钟步进(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); + 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); From 2ec35461aeb683a8e0be3c9a5cbe60e63b191635 Mon Sep 17 00:00:00 2001 From: sqqdfny Date: Fri, 21 Aug 2026 09:28:13 +0800 Subject: [PATCH 18/22] =?UTF-8?q?feat(libpeer):=20DTLS-SRTP=20=E6=94=AF?= =?UTF-8?q?=E6=8C=81=20AEAD=5FAES=5F128=5FGCM=EF=BC=88RFC=207714=EF=BC=89?= =?UTF-8?q?=EF=BC=8C=E5=85=A5=E7=AB=99=E9=89=B4=E6=9D=83=E5=A4=B1=E8=B4=A5?= =?UTF-8?q?=E4=B8=A2=E5=BC=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 默认 profile 列表改为 [AEAD_AES_128_GCM, AES128_CM_HMAC_SHA1_80], 移除 HMAC_SHA1_32 与 NULL_HMAC 弱 profile;作 DTLS client 时此序即偏好序 - 密钥派生按协商结果分流:GCM 盐 12B(SRTP_AEAD_SALT_LEN)、材料 56B, CM 盐 14B、材料 60B(DTLS_SRTP_KEY_MATERIAL_LENGTH_GCM 宏); remote_policy_key 缓冲按上限 16+14=30B 容纳两种 profile - dtls_srtp_init 检查 mbedtls_ssl_conf_dtls_srtp_protection_profiles 返回值, 失败直接返回;握手后校验 chosen_dtls_srtp_profile,chosen==UNSET 视为协商失败返回 -1,不得带未协商密钥继续 - 加解密 API(rtp/rtcp × encrypt/decrypt)返回 int:srtp_protect/unprotect 失败返回 -1;srtp 会话未建立时透传返回 0,保持既有语义 - peer_connection:SRTP/SRTCP 入向鉴权失败丢弃(AEAD/HMAC 未过校验的字节 不进解析/解码器,防伪造反馈注入),缺口由 NACK 重传/PLI 恢复; 新增计数 srtp_auth_failures 与 peer_connection_get_srtp_auth_failures() - 出站加密失败不发送:对端必然鉴权丢弃,NACK 缓存与发送都无意义(LOGW) - NACK ring 槽注释更新:GCM 16B tag 下满长包(1300+16)恰好容纳 --- src/dtls_srtp.c | 81 ++++++++++++++++++++++++++++++++----------- src/dtls_srtp.h | 14 +++++--- src/peer_connection.c | 27 ++++++++++++--- src/peer_connection.h | 3 ++ 4 files changed, 97 insertions(+), 28 deletions(-) diff --git a/src/dtls_srtp.c b/src/dtls_srtp.c index fb5f7520..e4dd9d75 100644 --- a/src/dtls_srtp.c +++ b/src/dtls_srtp.c @@ -152,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; @@ -214,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); @@ -253,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; } @@ -285,7 +298,7 @@ static int dtls_srtp_key_derivation(DtlsSrtp* dtls_srtp, const unsigned char* ma 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 + SRTP_MASTER_SALT_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; @@ -302,11 +315,17 @@ 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; @@ -322,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; @@ -473,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; } @@ -520,26 +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) { +int dtls_srtp_decrypt_rtp_packet(DtlsSrtp* dtls_srtp, uint8_t* packet, int* bytes) { if (dtls_srtp->srtp_in) { - srtp_unprotect(dtls_srtp->srtp_in, packet, bytes); + 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) { +int dtls_srtp_decrypt_rtcp_packet(DtlsSrtp* dtls_srtp, uint8_t* packet, int* bytes) { if (dtls_srtp->srtp_in) { - srtp_unprotect_rtcp(dtls_srtp->srtp_in, packet, bytes); + 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) { +int dtls_srtp_encrypt_rtp_packet(DtlsSrtp* dtls_srtp, uint8_t* packet, int* bytes) { if (dtls_srtp->srtp_out) { - srtp_protect(dtls_srtp->srtp_out, packet, bytes); + 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) { +int dtls_srtp_encrypt_rctp_packet(DtlsSrtp* dtls_srtp, uint8_t* packet, int* bytes) { if (dtls_srtp->srtp_out) { - srtp_protect_rtcp(dtls_srtp->srtp_out, packet, bytes); + 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 5c55e3e5..2789a059 100644 --- a/src/dtls_srtp.h +++ b/src/dtls_srtp.h @@ -18,8 +18,12 @@ #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 { @@ -91,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/peer_connection.c b/src/peer_connection.c index 19a990a5..4468c34b 100644 --- a/src/peer_connection.c +++ b/src/peer_connection.c @@ -32,7 +32,7 @@ // config.nack_ring_packets 决定:0 = 禁用 (不缓存、不重传、不占内存), // 非 0 必须为 2 的幂 (peer_connection_create 校验,非法直接失败)。 -#define PEER_CONNECTION_NACK_SLOT_SIZE (CONFIG_MTU + 16) /* 密文 + SRTP auth tag(10B) 余量 */ +#define PEER_CONNECTION_NACK_SLOT_SIZE (CONFIG_MTU + 16) /* 密文 + SRTP auth tag 余量:GCM 16B tag 下满长包(1300+16)恰好容纳 */ typedef struct { uint16_t seq; @@ -70,6 +70,7 @@ struct PeerConnection { uint32_t handshake_start_time; uint32_t nack_retransmits; /* NACK 重传包计数 (config.nack_ring_packets==0 时恒 0) */ + uint32_t srtp_auth_failures; /* SRTP/SRTCP 入向鉴权失败丢包计数 */ /* 柔性数组 (GCC 零长数组扩展, 仓库既有写法, 见 async_delegation.c): * NACK 重传 ring, 槽位数 = config.nack_ring_packets (0 时分配 0 字节), @@ -79,7 +80,11 @@ struct PeerConnection { 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); + if (dtls_srtp_encrypt_rtp_packet(&pc->dtls_srtp, data, (int*)&size) != 0) { + /* 加密失败不发送:对端必然鉴权丢弃,缓存与发送都无意义 */ + LOGW("srtp_protect failed, drop outbound RTP"); + return; + } /* 缓存密文(含 auth tag)供 NACK 原样重发;只缓存视频轨 */ if (pc->config.nack_ring_packets > 0 && (data[1] & 0x7F) == PT_H264 && size <= PEER_CONNECTION_NACK_SLOT_SIZE) { @@ -228,6 +233,10 @@ 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; +} + void* peer_connection_get_sctp(PeerConnection* pc) { return &pc->sctp; } @@ -468,7 +477,12 @@ int peer_connection_loop(PeerConnection* pc) { 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)) { @@ -482,7 +496,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) { diff --git a/src/peer_connection.h b/src/peer_connection.h index 490e53a0..405b342c 100644 --- a/src/peer_connection.h +++ b/src/peer_connection.h @@ -126,6 +126,9 @@ void peer_connection_set_video_timestamp_increment(PeerConnection* pc, uint32_t // 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); + 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); From a70f8054dcbb617f2733b5073149e371ada3a70f Mon Sep 17 00:00:00 2001 From: sqqdfny Date: Thu, 27 Aug 2026 16:46:36 +0800 Subject: [PATCH 19/22] =?UTF-8?q?feat(libpeer):=20sendto=20=E5=A4=B1?= =?UTF-8?q?=E8=B4=A5=E9=80=80=E9=81=BF=E9=87=8D=E8=AF=95=20+=20RTP=20?= =?UTF-8?q?=E5=87=BA=E5=8C=85=E7=BB=9F=E8=AE=A1=EF=BC=8CFU-A=20=E6=95=B4?= =?UTF-8?q?=E5=B8=A7=E6=94=BE=E5=BC=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 rtp_packets_sent / rtp_send_failures 计数与 getter: sendto 失败(典型 ENOBUFS)在 rtp_encoder_encode_* 返回 0 的掩盖下 对上层不可见,现在于收敛点显式计数 - sendto 失败后睡眠 PEER_CONNECTION_SEND_RETRY_DELAY_MS=2ms 让出 CPU 给 TCPIP/wlan 任务排空 TX skb 池,再重试一次,仍失败计 rtp_send_failures 并丢弃;仅失败路径付出延迟,成功路径零开销 - FU-A 帧丢任意一片整帧即废:视频轨本帧首次发送失败置 video_frame_aborted,同 timestamp 剩余分片直接丢弃(等对端 PLI → IDR 重发恢复),新帧 ts 变化清除标志;避免拥塞链路上 浪费带宽并拖长推流线程持锁时间 - NACK 重传同样计入收发统计(重传失败也是拥塞信号) - NACK 缓存仍在帧放弃判断之前:已成功发送的分片仍可补发 --- src/peer_connection.c | 69 ++++++++++++++++++++++++++++++++++++++++--- src/peer_connection.h | 5 ++++ 2 files changed, 70 insertions(+), 4 deletions(-) diff --git a/src/peer_connection.c b/src/peer_connection.c index 4468c34b..82fc37c2 100644 --- a/src/peer_connection.c +++ b/src/peer_connection.c @@ -34,6 +34,10 @@ #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 视为空槽 */ @@ -72,6 +76,17 @@ struct PeerConnection { 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 分配。 */ @@ -85,8 +100,14 @@ static void peer_connection_outgoing_rtp_packet(uint8_t* data, size_t size, void LOGW("srtp_protect failed, drop outbound RTP"); return; } - /* 缓存密文(含 auth tag)供 NACK 原样重发;只缓存视频轨 */ - if (pc->config.nack_ring_packets > 0 && (data[1] & 0x7F) == PT_H264 && + + 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)]; @@ -94,7 +115,34 @@ static void peer_connection_outgoing_rtp_packet(uint8_t* data, size_t size, void e->len = (uint16_t)size; memcpy(e->data, data, size); } - agent_send(&pc->agent, 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) { @@ -136,7 +184,12 @@ static void peer_connection_nack_retransmit(PeerConnection* pc, uint16_t seq) { /* 同余槽位校验: seq 超出窗口时槽位内容必不匹配, 天然实现窗口语义 */ nack_ring_entry_t* e = &pc->nack_ring[seq & (pc->config.nack_ring_packets - 1)]; if (e->seq == seq && e->len > 0) { - agent_send(&pc->agent, e->data, e->len); + /* 重传同样计入收发统计:重传失败也是拥塞信号 */ + if (agent_send(&pc->agent, e->data, e->len) < 0) { + pc->rtp_send_failures++; + } else { + pc->rtp_packets_sent++; + } pc->nack_retransmits++; } } @@ -237,6 +290,14 @@ 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; } diff --git a/src/peer_connection.h b/src/peer_connection.h index 405b342c..d5fea3e2 100644 --- a/src/peer_connection.h +++ b/src/peer_connection.h @@ -129,6 +129,11 @@ 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); From 8daffe9fcb83e06ba5c7b4d778f6ac64f09afbe3 Mon Sep 17 00:00:00 2001 From: sqqdfny Date: Thu, 3 Sep 2026 11:21:21 +0800 Subject: [PATCH 20/22] =?UTF-8?q?chore(libpeer):=20=E7=A7=BB=E9=99=A4=20cJ?= =?UTF-8?q?SON/mbedtls/coreHTTP/coreMQTT=20=E5=B5=8C=E5=A5=97=E5=AD=90?= =?UTF-8?q?=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - third_party 子模块瘦身:删除 cJSON、mbedtls、coreHTTP、coreMQTT 四个 gitlink,.gitmodules 同步移除对应条目;保留 libsrtp、usrsctp - qemu_riscv 构建中这些依赖由主仓库自身提供(src/third_party/cJSON、 src/mbedtls,均非子模块),libpeer 不再需要各自持有的副本 - 注意:libpeer 顶层 CMakeLists.txt 第 4/5/72/81/92 行仍 include / ExternalProject 引用上述 third_party 路径——脱离 qemu_riscv 独立 构建 libpeer 需另行清理;qemu_riscv 主构建(webrtc.cmake 直接编 libpeer 源文件)不受影响 --- .gitmodules | 12 ------------ third_party/cJSON | 1 - third_party/coreHTTP | 1 - third_party/coreMQTT | 1 - third_party/mbedtls | 1 - 5 files changed, 16 deletions(-) delete mode 160000 third_party/cJSON delete mode 160000 third_party/coreHTTP delete mode 160000 third_party/coreMQTT delete mode 160000 third_party/mbedtls diff --git a/.gitmodules b/.gitmodules index ac3c5169..f60f899f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,18 +1,6 @@ [submodule "third_party/libsrtp"] path = third_party/libsrtp url = https://github.com/cisco/libsrtp.git -[submodule "third_party/cJSON"] - path = third_party/cJSON - url = https://github.com/DaveGamble/cJSON.git [submodule "third_party/usrsctp"] path = third_party/usrsctp url = https://github.com/sctplab/usrsctp.git -[submodule "third_party/mbedtls"] - path = third_party/mbedtls - url = https://github.com/Mbed-TLS/mbedtls.git -[submodule "third_party/coreHTTP"] - path = third_party/coreHTTP - url = https://github.com/FreeRTOS/coreHTTP -[submodule "third_party/coreMQTT"] - path = third_party/coreMQTT - url = https://github.com/FreeRTOS/coreMQTT diff --git a/third_party/cJSON b/third_party/cJSON deleted file mode 160000 index 203a0dec..00000000 --- a/third_party/cJSON +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 203a0dec6ff06e3842fa23a1bc9825aecf56b381 diff --git a/third_party/coreHTTP b/third_party/coreHTTP deleted file mode 160000 index b539e7ab..00000000 --- a/third_party/coreHTTP +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b539e7ab2360efde3c6361f4c2bfcc065b22d087 diff --git a/third_party/coreMQTT b/third_party/coreMQTT deleted file mode 160000 index 03290fe0..00000000 --- a/third_party/coreMQTT +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 03290fe0274e1c9cee5f3608e197daedbfdd1e1f diff --git a/third_party/mbedtls b/third_party/mbedtls deleted file mode 160000 index 1873d3bf..00000000 --- a/third_party/mbedtls +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1873d3bfc2da771672bd8e7e8f41f57e0af77f33 From 55f12e30487301a4a8ec857d352ae115f3e20a6a Mon Sep 17 00:00:00 2001 From: sqqdfny Date: Thu, 3 Sep 2026 16:57:52 +0800 Subject: [PATCH 21/22] =?UTF-8?q?feat(libpeer):=20answer=20SDP=20=E6=8C=89?= =?UTF-8?q?=20offer=20=E5=BD=92=E4=B8=80=E5=8C=96(BUNDLE=20=E7=AB=AF?= =?UTF-8?q?=E5=8F=A3/mid=20=E5=9B=9E=E6=98=BE/=E6=96=B9=E5=90=91=E5=8F=96?= =?UTF-8?q?=E5=8F=8D),H264=20PT=20=E5=8A=A8=E6=80=81=E8=A6=86=E7=9B=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - sdp.c/sdp.h: 引入 SdpGenerateParam 生成上下文。ANSWER 模式按 RFC 3264 §6/5888/8843 归一:mid 回显 offer(缺失回退语义名)、方向取反 (sendonly↔recvonly,未声明视同 sendrecv)、BUNDLE anchor(首个 m= 行) 端口 50712、其余 m= 行端口 0 并加 a=bundle-only;OFFER 模式输出与 既有字节级一致 - peer_connection.c/.h: set_remote_description 解析 offer 的 per-m-line mid/方向(a=mid/a=sendrecv 等),存 pc->remote_media; m= 前缀匹配收紧(strstr → strncmp "m=video "/"m=application "), datachannel 段不再漏解析;新增 peer_connection_set_video_payload_type()(合法范围 96-127), 同步更新发送编码器 —— answer 广告与 RTP 打包共用同一 PT - 修复 Chrome offer 的 96 为 VP8 时旧固定 PT_H264=96 直接写 answer 致 video codec 关联为空、客户端解不出帧的问题(需在 create_answer 前按 offer 的 a=rtpmap 覆盖) - outgoing RTP 视频判据由固定 PT_H264 改为 video_codec!=NONE && (data[1]&0x7F)==vrtp_encoder.type,NACK 重传 等路径随 PT 覆盖保持一致 - dtls_srtp.c: mbedtls 3.6 client + VERIFY_REQUIRED 未设 hostname 时 证书验证返回 -0x5D80 中止;answerer(client 角色)预置 hostname, 验证流程得以走完(指纹比对在 dtls_srtp_handshake 内完成,证书链 验证由回调放行) - 验证:公网 WS 信令全链路回归通过(浏览器 demo 页拉流,video codec 关联正常、推流稳定) --- src/dtls_srtp.c | 9 +++ src/peer_connection.c | 77 +++++++++++++++++--- src/peer_connection.h | 9 +++ src/sdp.c | 162 ++++++++++++++++++++++++++++++++++-------- src/sdp.h | 46 ++++++++++-- 5 files changed, 260 insertions(+), 43 deletions(-) diff --git a/src/dtls_srtp.c b/src/dtls_srtp.c index e4dd9d75..f6c781c3 100644 --- a/src/dtls_srtp.c +++ b/src/dtls_srtp.c @@ -208,6 +208,15 @@ int dtls_srtp_init(DtlsSrtp* dtls_srtp, DtlsSrtpRole role, void* user_data) { MBEDTLS_SSL_IS_CLIENT, MBEDTLS_SSL_TRANSPORT_DATAGRAM, MBEDTLS_SSL_PRESET_DEFAULT); + + /* mbedtls 3.6: client + VERIFY_REQUIRED 未设 hostname 会在验证服务器证书时 + * 返回 MBEDTLS_ERR_SSL_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME(-0x5D80)。 + * WebRTC 对等指纹比对在 dtls_srtp_handshake 内完成,证书链验证由 + * dtls_srtp_cert_verify 回调放行,hostname 仅用于让验证流程继续执行 */ + if (mbedtls_ssl_set_hostname(&dtls_srtp->ssl, "dtls_srtp") != 0) { + LOGE("mbedtls_ssl_set_hostname failed"); + return -1; + } } dtls_srtp_x509_digest(&dtls_srtp->cert, dtls_srtp->local_fingerprint); diff --git a/src/peer_connection.c b/src/peer_connection.c index 82fc37c2..91f9bf81 100644 --- a/src/peer_connection.c +++ b/src/peer_connection.c @@ -71,6 +71,9 @@ struct PeerConnection { uint32_t remote_assrc; uint32_t remote_vssrc; + SdpMediaInfo remote_media[SDP_MEDIA_COUNT]; /* offer 的 per-m-line mid/方向(pc 每会话新建 calloc,无陈旧复用) */ + int h264_pt; /* 当前会话 H264 PT,create 默认 PT_H264,可经 set_video_payload_type 覆盖 */ + uint32_t handshake_start_time; uint32_t nack_retransmits; /* NACK 重传包计数 (config.nack_ring_packets==0 时恒 0) */ @@ -101,7 +104,7 @@ static void peer_connection_outgoing_rtp_packet(uint8_t* data, size_t size, void return; } - int is_video = ((data[1] & 0x7F) == PT_H264); + int is_video = (pc->config.video_codec != CODEC_NONE) && ((data[1] & 0x7F) == (uint8_t)pc->vrtp_encoder.type); /* 缓存密文(含 auth tag)供 NACK 原样重发;只缓存视频轨。 * 必须在下方的帧放弃判断之前:本帧后续分片被丢弃时,已成功发送的 @@ -330,6 +333,8 @@ PeerConnection* peer_connection_create(PeerConfiguration* config) { pc->state = PEER_CONNECTION_NEW; + pc->h264_pt = PT_H264; + if (pc->config.audio_codec) { rtp_encoder_init(&pc->artp_encoder, pc->config.audio_codec, peer_connection_outgoing_rtp_packet, (void*)pc); @@ -383,6 +388,20 @@ void peer_connection_set_video_timestamp_increment(PeerConnection* pc, uint32_t rtp_encoder_set_timestamp_increment(&pc->vrtp_encoder, increment); } +int peer_connection_set_video_payload_type(PeerConnection* pc, int payload_type) { + if (!pc) + return -1; + /* 动态 PT 范围 96-127(rtp_packet_validate 同款判据) */ + if (payload_type < 96 || payload_type > 127) { + LOGE("invalid h264 payload type: %d", payload_type); + return -1; + } + pc->h264_pt = payload_type; + if (pc->config.video_codec == CODEC_H264) + pc->vrtp_encoder.type = (RtpPayloadType)payload_type; + return 0; +} + int peer_connection_datachannel_send(PeerConnection* pc, char* message, size_t len) { /** * Use the actual sid from the SCTP stream table (negotiated by the browser's DCEP OPEN), @@ -607,6 +626,7 @@ void peer_connection_set_remote_description(PeerConnection* pc, const char* sdp, char buf[256]; char* val_start = NULL; uint32_t* ssrc = NULL; + SdpMediaKind media_kind = SDP_MEDIA_COUNT; DtlsSrtpRole role = DTLS_SRTP_ROLE_SERVER; int is_update = 0; Agent* agent = &pc->agent; @@ -631,10 +651,22 @@ void peer_connection_set_remote_description(PeerConnection* pc, const char* sdp, is_update = 1; } - if (strstr(buf, "m=video")) { + /* 前缀匹配收紧(strstr "m=video" 可能命中行内其他子串), + * 并补 m=application 归属:旧代码不认 datachannel 段,其 mid/方向无法解析 */ + if (strncmp(buf, "m=video ", 8) == 0) { + media_kind = SDP_MEDIA_VIDEO; ssrc = &pc->remote_vssrc; - } else if (strstr(buf, "m=audio")) { + pc->remote_media[media_kind].mid[0] = '\0'; + pc->remote_media[media_kind].direction = SDP_DIR_NONE; + } else if (strncmp(buf, "m=audio ", 8) == 0) { + media_kind = SDP_MEDIA_AUDIO; ssrc = &pc->remote_assrc; + pc->remote_media[media_kind].mid[0] = '\0'; + pc->remote_media[media_kind].direction = SDP_DIR_NONE; + } else if (strncmp(buf, "m=application ", 14) == 0) { + media_kind = SDP_MEDIA_APPLICATION; + pc->remote_media[media_kind].mid[0] = '\0'; + pc->remote_media[media_kind].direction = SDP_DIR_NONE; } if ((val_start = strstr(buf, "a=ssrc:")) && ssrc) { @@ -642,6 +674,22 @@ void peer_connection_set_remote_description(PeerConnection* pc, const char* sdp, LOGD("SSRC: %" PRIu32, *ssrc); } + if (media_kind < SDP_MEDIA_COUNT) { + if ((val_start = strstr(buf, "a=mid:"))) { + strncpy(pc->remote_media[media_kind].mid, val_start + 6, SDP_MID_MAX_LEN - 1); + pc->remote_media[media_kind].mid[SDP_MID_MAX_LEN - 1] = '\0'; + } + if (strncmp(buf, "a=sendrecv", 10) == 0) { + pc->remote_media[media_kind].direction = SDP_DIR_SENDRECV; + } else if (strncmp(buf, "a=sendonly", 10) == 0) { + pc->remote_media[media_kind].direction = SDP_DIR_SENDONLY; + } else if (strncmp(buf, "a=recvonly", 10) == 0) { + pc->remote_media[media_kind].direction = SDP_DIR_RECVONLY; + } else if (strncmp(buf, "a=inactive", 10) == 0) { + pc->remote_media[media_kind].direction = SDP_DIR_INACTIVE; + } + } + start = line + 2; } @@ -661,6 +709,7 @@ static const char* peer_connection_create_sdp(PeerConnection* pc, SdpType sdp_ty char* description = (char*)pc->temp_buf; memset(pc->temp_buf, 0, sizeof(pc->temp_buf)); DtlsSrtpRole role = DTLS_SRTP_ROLE_SERVER; + SdpGenerateParam gen; pc->sctp.connected = 0; @@ -684,11 +733,21 @@ static const char* peer_connection_create_sdp(PeerConnection* pc, SdpType sdp_ty pc->dtls_srtp.udp_send = peer_connection_dtls_srtp_send; memset(pc->sdp, 0, sizeof(pc->sdp)); + + /* SDP 生成上下文:ANSWER 按 RFC 5888/8843/3264 归一(mid 回显、端口 0+bundle-only、 + * 方向取反、PT 用 pc->h264_pt),OFFER 与既有输出字节级一致 */ + memset(&gen, 0, sizeof(gen)); + gen.is_answer = (sdp_type == SDP_TYPE_ANSWER); + gen.h264_pt = pc->h264_pt; + gen.anchor_port = SDP_DATACHANNEL_PORT; + memcpy(gen.remote, pc->remote_media, sizeof(gen.remote)); + // TODO: check if we have video or audio codecs sdp_create(pc->sdp, pc->config.video_codec != CODEC_NONE, pc->config.audio_codec != CODEC_NONE, - pc->config.datachannel); + pc->config.datachannel, + &gen); agent_create_ice_credential(&pc->agent); sdp_append(pc->sdp, "a=ice-ufrag:%s", pc->agent.local_ufrag); @@ -716,7 +775,7 @@ static const char* peer_connection_create_sdp(PeerConnection* pc, SdpType sdp_ty agent_get_local_description(&pc->agent, description, sizeof(pc->temp_buf)); if (pc->config.video_codec == CODEC_H264) { - sdp_append_h264(pc->sdp); + sdp_append_h264(pc->sdp, &gen); if(0 == create_candidate_sdp_flag) { create_candidate_sdp_flag = 1; sdp_append(pc->sdp, description); @@ -725,21 +784,21 @@ static const char* peer_connection_create_sdp(PeerConnection* pc, SdpType sdp_ty switch (pc->config.audio_codec) { case CODEC_PCMA: - sdp_append_pcma(pc->sdp); + sdp_append_pcma(pc->sdp, &gen); 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); + sdp_append_pcmu(pc->sdp, &gen); 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); + sdp_append_opus(pc->sdp, &gen); if(0 == create_candidate_sdp_flag) { create_candidate_sdp_flag = 1; sdp_append(pc->sdp, description); @@ -749,7 +808,7 @@ static const char* peer_connection_create_sdp(PeerConnection* pc, SdpType sdp_ty } if (pc->config.datachannel) { - sdp_append_datachannel(pc->sdp); + sdp_append_datachannel(pc->sdp, &gen); } if(0 == create_candidate_sdp_flag) { diff --git a/src/peer_connection.h b/src/peer_connection.h index d5fea3e2..f11224d1 100644 --- a/src/peer_connection.h +++ b/src/peer_connection.h @@ -123,6 +123,15 @@ int peer_connection_send_video(PeerConnection* pc, const uint8_t* packet, size_t // 按实际视频帧间隔调整媒体时钟步进(90kHz 单位) void peer_connection_set_video_timestamp_increment(PeerConnection* pc, uint32_t increment); +/** + * @brief 覆盖当前会话 H264 动态 PT(create 时默认 PT_H264=96) + * @param[in] peer connection + * @param[in] 动态 PT 值(合法范围 96-127,非法返回 -1) + * answerer 在 create_answer 前按 offer 的 a=rtpmap 设置; + * 同步更新发送编码器,保证 SDP 广告与 RTP 打包一致。 + */ +int peer_connection_set_video_payload_type(PeerConnection* pc, int payload_type); + // NACK 重传统计 (config.nack_ring_packets==0 禁用时恒为 0) uint32_t peer_connection_get_nack_retransmits(PeerConnection* pc); diff --git a/src/sdp.c b/src/sdp.c index 3e61f8fc..901a3301 100644 --- a/src/sdp.c +++ b/src/sdp.c @@ -1,6 +1,7 @@ #include #include +#include "rtp.h" #include "sdp.h" int sdp_append(char* sdp, const char* format, ...) { @@ -26,59 +27,161 @@ void sdp_reset(char* sdp) { memset(sdp, 0, CONFIG_SDP_BUFFER_SIZE); } -void sdp_append_h264(char* sdp) { - sdp_append(sdp, "m=video 9 UDP/TLS/RTP/SAVPF 96"); +static const char* sdp_direction_name(SdpDirection direction) { + switch (direction) { + case SDP_DIR_SENDONLY: + return "sendonly"; + case SDP_DIR_RECVONLY: + return "recvonly"; + case SDP_DIR_INACTIVE: + return "inactive"; + default: + return "sendrecv"; + } +} + +/* RFC 3264 §6:answer 方向 = offer 取反(未声明视同 sendrecv) */ +static SdpDirection sdp_answer_direction(SdpDirection offer_direction) { + switch (offer_direction) { + case SDP_DIR_SENDONLY: + return SDP_DIR_RECVONLY; + case SDP_DIR_RECVONLY: + return SDP_DIR_SENDONLY; + case SDP_DIR_INACTIVE: + return SDP_DIR_INACTIVE; + default: + return SDP_DIR_SENDRECV; + } +} + +/* ANSWER 回显 offer 同 kind 的 a=mid,缺失回退语义名;OFFER 恒语义名 */ +static const char* sdp_mid_of(SdpGenerateParam* gen, SdpMediaKind kind, const char* fallback) { + if (gen->is_answer && gen->remote[kind].mid[0] != '\0') + return gen->remote[kind].mid; + return fallback; +} + +void sdp_append_h264(char* sdp, SdpGenerateParam* gen) { + int is_answer = gen->is_answer; + int is_anchor = (gen->mline_count++ == 0); + int port = 9; + const char* mid = "video"; + SdpDirection direction = SDP_DIR_SENDRECV; + + if (is_answer) { + port = is_anchor ? gen->anchor_port : 0; + mid = sdp_mid_of(gen, SDP_MEDIA_VIDEO, "video"); + direction = sdp_answer_direction(gen->remote[SDP_MEDIA_VIDEO].direction); + } + + sdp_append(sdp, "m=video %d UDP/TLS/RTP/SAVPF %d", port, gen->h264_pt); + if (is_answer && !is_anchor) + sdp_append(sdp, "a=bundle-only"); 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;packetization-mode=1"); - sdp_append(sdp, "a=rtpmap:96 H264/90000"); + sdp_append(sdp, "a=rtcp-fb:%d nack", gen->h264_pt); + sdp_append(sdp, "a=rtcp-fb:%d nack pli", gen->h264_pt); + sdp_append(sdp, "a=fmtp:%d profile-level-id=42e01f;level-asymmetry-allowed=1;packetization-mode=1", gen->h264_pt); + sdp_append(sdp, "a=rtpmap:%d H264/90000", gen->h264_pt); sdp_append(sdp, "a=ssrc:1 cname:webrtc-h264"); - sdp_append(sdp, "a=sendrecv"); - sdp_append(sdp, "a=mid:video"); + sdp_append(sdp, "a=%s", sdp_direction_name(direction)); + sdp_append(sdp, "a=mid:%s", mid); sdp_append(sdp, "a=rtcp-mux"); } -void sdp_append_pcma(char* sdp) { - sdp_append(sdp, "m=audio 9 UDP/TLS/RTP/SAVPF 8"); +void sdp_append_pcma(char* sdp, SdpGenerateParam* gen) { + int is_answer = gen->is_answer; + int is_anchor = (gen->mline_count++ == 0); + int port = 9; + const char* mid = "audio"; + SdpDirection direction = SDP_DIR_SENDRECV; + + if (is_answer) { + port = is_anchor ? gen->anchor_port : 0; + mid = sdp_mid_of(gen, SDP_MEDIA_AUDIO, "audio"); + direction = sdp_answer_direction(gen->remote[SDP_MEDIA_AUDIO].direction); + } + + sdp_append(sdp, "m=audio %d UDP/TLS/RTP/SAVPF 8", port); + if (is_answer && !is_anchor) + sdp_append(sdp, "a=bundle-only"); 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"); - sdp_append(sdp, "a=sendrecv"); - sdp_append(sdp, "a=mid:audio"); + sdp_append(sdp, "a=%s", sdp_direction_name(direction)); + sdp_append(sdp, "a=mid:%s", mid); sdp_append(sdp, "a=rtcp-mux"); } -void sdp_append_pcmu(char* sdp) { - sdp_append(sdp, "m=audio 9 UDP/TLS/RTP/SAVPF 0"); +void sdp_append_pcmu(char* sdp, SdpGenerateParam* gen) { + int is_answer = gen->is_answer; + int is_anchor = (gen->mline_count++ == 0); + int port = 9; + const char* mid = "audio"; + SdpDirection direction = SDP_DIR_SENDRECV; + + if (is_answer) { + port = is_anchor ? gen->anchor_port : 0; + mid = sdp_mid_of(gen, SDP_MEDIA_AUDIO, "audio"); + direction = sdp_answer_direction(gen->remote[SDP_MEDIA_AUDIO].direction); + } + + sdp_append(sdp, "m=audio %d UDP/TLS/RTP/SAVPF 0", port); + if (is_answer && !is_anchor) + sdp_append(sdp, "a=bundle-only"); 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"); - sdp_append(sdp, "a=sendrecv"); - sdp_append(sdp, "a=mid:audio"); + sdp_append(sdp, "a=%s", sdp_direction_name(direction)); + sdp_append(sdp, "a=mid:%s", mid); sdp_append(sdp, "a=rtcp-mux"); } -void sdp_append_opus(char* sdp) { - sdp_append(sdp, "m=audio 9 UDP/TLS/RTP/SAVPF 111"); +void sdp_append_opus(char* sdp, SdpGenerateParam* gen) { + int is_answer = gen->is_answer; + int is_anchor = (gen->mline_count++ == 0); + int port = 9; + const char* mid = "audio"; + SdpDirection direction = SDP_DIR_SENDRECV; + + if (is_answer) { + port = is_anchor ? gen->anchor_port : 0; + mid = sdp_mid_of(gen, SDP_MEDIA_AUDIO, "audio"); + direction = sdp_answer_direction(gen->remote[SDP_MEDIA_AUDIO].direction); + } + + sdp_append(sdp, "m=audio %d UDP/TLS/RTP/SAVPF 111", port); + if (is_answer && !is_anchor) + sdp_append(sdp, "a=bundle-only"); 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"); - sdp_append(sdp, "a=sendrecv"); - sdp_append(sdp, "a=mid:audio"); + sdp_append(sdp, "a=%s", sdp_direction_name(direction)); + sdp_append(sdp, "a=mid:%s", mid); sdp_append(sdp, "a=rtcp-mux"); } -void sdp_append_datachannel(char* sdp) { - sdp_append(sdp, "m=application 50712 UDP/DTLS/SCTP webrtc-datachannel"); +void sdp_append_datachannel(char* sdp, SdpGenerateParam* gen) { + int is_answer = gen->is_answer; + int is_anchor = (gen->mline_count++ == 0); + int port = SDP_DATACHANNEL_PORT; + const char* mid = "datachannel"; + + if (is_answer) { + port = is_anchor ? gen->anchor_port : 0; + mid = sdp_mid_of(gen, SDP_MEDIA_APPLICATION, "datachannel"); + } + + sdp_append(sdp, "m=application %d UDP/DTLS/SCTP webrtc-datachannel", port); + if (is_answer && !is_anchor) + sdp_append(sdp, "a=bundle-only"); sdp_append(sdp, "c=IN IP4 0.0.0.0"); - sdp_append(sdp, "a=mid:datachannel"); + sdp_append(sdp, "a=mid:%s", mid); sdp_append(sdp, "a=sctp-port:5000"); sdp_append(sdp, "a=max-message-size:262144"); } -void sdp_create(char* sdp, int b_video, int b_audio, int b_datachannel) { - char bundle[64]; +void sdp_create(char* sdp, int b_video, int b_audio, int b_datachannel, SdpGenerateParam* gen) { + char bundle[256]; sdp_append(sdp, "v=0"); sdp_append(sdp, "o=- 1495799811084970 1495799811084970 IN IP4 0.0.0.0"); sdp_append(sdp, "s=-"); @@ -92,15 +195,18 @@ void sdp_create(char* sdp, int b_video, int b_audio, int b_datachannel) { strcat(bundle, "a=group:BUNDLE"); if (b_video) { - strcat(bundle, " video"); + strcat(bundle, " "); + strcat(bundle, sdp_mid_of(gen, SDP_MEDIA_VIDEO, "video")); } if (b_audio) { - strcat(bundle, " audio"); + strcat(bundle, " "); + strcat(bundle, sdp_mid_of(gen, SDP_MEDIA_AUDIO, "audio")); } if (b_datachannel) { - strcat(bundle, " datachannel"); + strcat(bundle, " "); + strcat(bundle, sdp_mid_of(gen, SDP_MEDIA_APPLICATION, "datachannel")); } sdp_append(sdp, bundle); diff --git a/src/sdp.h b/src/sdp.h index ba85d71d..8d6d321d 100644 --- a/src/sdp.h +++ b/src/sdp.h @@ -6,21 +6,55 @@ #define SDP_ATTR_LENGTH 128 +#define SDP_MID_MAX_LEN 32 +#define SDP_DATACHANNEL_PORT 50712 + #ifndef ICE_LITE #define ICE_LITE 0 #endif -void sdp_append_h264(char* sdp); +typedef enum SdpDirection { + SDP_DIR_NONE = 0, + SDP_DIR_SENDRECV, + SDP_DIR_SENDONLY, + SDP_DIR_RECVONLY, + SDP_DIR_INACTIVE, +} SdpDirection; + +typedef enum SdpMediaKind { + SDP_MEDIA_VIDEO = 0, + SDP_MEDIA_AUDIO, + SDP_MEDIA_APPLICATION, + SDP_MEDIA_COUNT, +} SdpMediaKind; + +typedef struct SdpMediaInfo { + char mid[SDP_MID_MAX_LEN]; + SdpDirection direction; +} SdpMediaInfo; + +/* SDP 生成上下文:OFFER 按现状输出(端口 9/9/50712、语义名 mid、恒 sendrecv); + * ANSWER 按 RFC 5888/8843/3264 归一:anchor 端口 50712 + 其余 0/a=bundle-only、 + * mid 回显 offer、方向按 offer 取反 */ +typedef struct SdpGenerateParam { + int is_answer; + int h264_pt; + int anchor_port; + SdpMediaInfo remote[SDP_MEDIA_COUNT]; /* offer 的 per-m-line mid/方向 */ + int mline_count; /* 生成器内部状态,调用方 memset 清零 */ +} SdpGenerateParam; + +void sdp_append_h264(char* sdp, SdpGenerateParam* gen); -void sdp_append_pcma(char* sdp); +void sdp_append_pcma(char* sdp, SdpGenerateParam* gen); -void sdp_append_pcmu(char* sdp); +void sdp_append_pcmu(char* sdp, SdpGenerateParam* gen); -void sdp_append_opus(char* sdp); +void sdp_append_opus(char* sdp, SdpGenerateParam* gen); -void sdp_append_datachannel(char* sdp); +void sdp_append_datachannel(char* sdp, SdpGenerateParam* gen); -void sdp_create(char* sdp, int b_video, int b_audio, int b_datachannel); +void sdp_create(char* sdp, int b_video, int b_audio, int b_datachannel, SdpGenerateParam* gen); int sdp_append(char* sdp, const char* format, ...); From e83b4a5b9e90a224f5834d9d45b401d771a45705 Mon Sep 17 00:00:00 2001 From: sqqdfny Date: Fri, 4 Sep 2026 11:30:56 +0800 Subject: [PATCH 22/22] =?UTF-8?q?refactor(libpeer):=20DTLS-SRTP=20profile?= =?UTF-8?q?=20=E4=BB=85=E4=BF=9D=E7=95=99=20AEAD=5FAES=5F128=5FGCM?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - src/dtls_srtp.c: srtp_profiles 停用 SRTP_AES128_CM_HMAC_SHA1_80 (RFC 5764) 回退项,DTLS-SRTP 协商只声明 AEAD_AES_128_GCM (RFC 7714) - 板端作 DTLS client 时 profile 列表即偏好序:对端首选/仅支持 AEAD 时无影响;对端若仅支持 SHA1_80 将协商失败 - 停用行以注释保留而非删除,且上方"AEAD 优先、SHA1_80 回退"的 说明注释未同步更新 --- src/dtls_srtp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dtls_srtp.c b/src/dtls_srtp.c index f6c781c3..2fc10924 100644 --- a/src/dtls_srtp.c +++ b/src/dtls_srtp.c @@ -155,7 +155,7 @@ int dtls_srtp_init(DtlsSrtp* dtls_srtp, DtlsSrtpRole role, void* user_data) { /* 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_80, MBEDTLS_TLS_SRTP_UNSET}; dtls_srtp->role = role;