From cddf9e061467b1ffa6c8e7b5f14a8e78e076cec7 Mon Sep 17 00:00:00 2001 From: maikebing Date: Sat, 22 Aug 2026 03:32:33 +0800 Subject: [PATCH 1/5] fix(ice): retain unresolved mDNS host candidates --- CMakeLists.txt | 7 +- src/agent.c | 16 +- src/agent.h | 3 + src/ice.c | 133 ++++++++++----- src/mdns.c | 369 ++++++++++++++++++++++++++++-------------- src/mdns.h | 5 + src/stun.c | 5 +- src/stun.h | 1 + tests/CMakeLists.txt | 4 +- tests/test_ice_mdns.c | 191 ++++++++++++++++++++++ 10 files changed, 563 insertions(+), 171 deletions(-) create mode 100644 tests/test_ice_mdns.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 83789d06..c9a1aef3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,7 +16,11 @@ if (DEFINED ENV{IDF_PATH}) return() endif() -project(peer) +project(peer C) + +set(CMAKE_C_STANDARD 99) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS ON) option(ENABLE_TESTS "Enable tests" OFF) option(BUILD_SHARED_LIBS "Build shared libraries" ON) @@ -65,6 +69,7 @@ add_subdirectory(src) add_subdirectory(examples) if(ENABLE_TESTS) + enable_testing() add_subdirectory(tests) endif() diff --git a/src/agent.c b/src/agent.c index d3a41a2d..52c2994b 100644 --- a/src/agent.c +++ b/src/agent.c @@ -365,13 +365,27 @@ int agent_send_binding_request(Agent* agent) { void agent_process_stun_request(Agent* agent, StunMessage* stun_msg, Address* addr) { StunMessage msg; StunHeader* header; + char addr_string[ADDRSTRLEN]; + switch (stun_msg->stunmethod) { case STUN_METHOD_BINDING: if (stun_msg_is_valid(stun_msg->buf, stun_msg->size, agent->local_upwd) == 0) { header = (StunHeader*)stun_msg->buf; memcpy(agent->transaction_id, header->transaction_id, sizeof(header->transaction_id)); agent_create_binding_response(agent, &msg, addr); - agent_socket_send(agent, addr, msg.buf, msg.size); + if (agent_socket_send(agent, addr, msg.buf, msg.size) >= 0) { + agent->binding_request_time = ports_get_epoch_time(); + if (stun_msg->use_candidate && agent->nominated_pair != NULL && + agent->nominated_pair->remote != NULL && + agent->nominated_pair->state == ICE_CANDIDATE_STATE_INPROGRESS) { + memcpy(&agent->nominated_pair->remote->addr, addr, sizeof(Address)); + agent->nominated_pair->remote->type = ICE_CANDIDATE_TYPE_PRFLX; + agent->nominated_pair->state = ICE_CANDIDATE_STATE_SUCCEEDED; + addr_to_string(addr, addr_string, sizeof(addr_string)); + LOGI("Accepted peer-reflexive ICE source %s:%d", addr_string, + addr->port); + } + } } break; default: diff --git a/src/agent.h b/src/agent.h index 6ac77ba2..51ff381f 100644 --- a/src/agent.h +++ b/src/agent.h @@ -75,6 +75,9 @@ int agent_send(Agent* agent, const uint8_t* buf, int len); int agent_recv(Agent* agent, uint8_t* buf, int len); +void agent_process_stun_request(Agent* agent, StunMessage* stun_msg, + Address* addr); + void agent_set_remote_description(Agent* agent, char* description); int agent_select_candidate_pair(Agent* agent); diff --git a/src/ice.c b/src/ice.c index 756e961f..f06a4785 100644 --- a/src/ice.c +++ b/src/ice.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include "ice.h" @@ -77,57 +78,105 @@ void ice_candidate_to_description(IceCandidate* candidate, char* description, in typ_raddr); } -int ice_candidate_from_description(IceCandidate* candidate, char* description, char* end) { - char* candidate_start = description; - uint32_t port; - char type[16]; - char addrstring[ADDRSTRLEN]; +static int ice_is_mdns_hostname(const char* hostname) { + size_t length; + int result = 0; - if (strncmp("a=", candidate_start, strlen("a=")) == 0) { - candidate_start += strlen("a="); - } - candidate_start += strlen("candidate:"); - - // a=candidate:448736988 1 udp 2122260223 172.17.0.1 49250 typ host generation 0 network-id 1 network-cost 50 - // a=candidate:udpcandidate 1 udp 120 192.168.1.102 8000 typ host - if (sscanf(candidate_start, "%s %d %s %" PRIu32 " %s %" PRIu32 " typ %s", - candidate->foundation, - &candidate->component, - candidate->transport, - &candidate->priority, - addrstring, - &port, - type) != 7) { - LOGE("Failed to parse ICE candidate description"); - return -1; + if (hostname != NULL) { + length = strlen(hostname); + if (length > 0 && hostname[length - 1] == '.') { + length--; + } + if (length > 6 && strncasecmp(hostname + length - 6, ".local", 6) == 0) { + result = 1; + } } - if (strncmp(candidate->transport, "UDP", 3) != 0 && strncmp(candidate->transport, "udp", 3) != 0) { - LOGE("Only UDP transport is supported"); - return -1; + return result; +} + +int ice_candidate_from_description(IceCandidate* candidate, char* description, + char* end) { + char line[512]; + char* candidate_start = line; + char type[16]; + char addrstring[ADDRSTRLEN]; + uint32_t port = 0; + size_t line_length = 0; + int parsed = 0; + int result = -1; + + if (candidate != NULL && description != NULL) { + if (end == NULL) { + end = description + strlen(description); + } + if (end >= description) { + line_length = (size_t)(end - description); + } } - if (strncmp(type, "host", 4) == 0) { - candidate->type = ICE_CANDIDATE_TYPE_HOST; - } else if (strncmp(type, "srflx", 5) == 0) { - candidate->type = ICE_CANDIDATE_TYPE_SRFLX; - } else if (strncmp(type, "relay", 5) == 0) { - candidate->type = ICE_CANDIDATE_TYPE_RELAY; - } else { - LOGE("Unknown candidate type: %s", type); - return -1; + if (line_length > 0 && line_length < sizeof(line)) { + memcpy(line, description, line_length); + line[line_length] = '\0'; + memset(candidate, 0, sizeof(*candidate)); + memset(type, 0, sizeof(type)); + memset(addrstring, 0, sizeof(addrstring)); + + if (strncmp(candidate_start, "a=", 2) == 0) { + candidate_start += 2; + } + if (strncmp(candidate_start, "candidate:", 10) == 0) { + candidate_start += 10; + parsed = sscanf(candidate_start, + "%32s %d %32s %" PRIu32 " %45s %" PRIu32 + " typ %15s", + candidate->foundation, + &candidate->component, + candidate->transport, + &candidate->priority, + addrstring, + &port, + type); + } } - addr_set_port(&candidate->addr, port); + if (parsed != 7) { + LOGE("Failed to parse ICE candidate description"); + } else if (strcasecmp(candidate->transport, "UDP") != 0) { + LOGE("Only UDP transport is supported"); + } else if (candidate->component < 1 || candidate->component > 256) { + LOGE("ICE candidate component is out of range"); + } else if (port > UINT16_MAX) { + LOGE("ICE candidate port is out of range"); + } else { + if (strcasecmp(type, "host") == 0) { + candidate->type = ICE_CANDIDATE_TYPE_HOST; + } else if (strcasecmp(type, "srflx") == 0) { + candidate->type = ICE_CANDIDATE_TYPE_SRFLX; + } else if (strcasecmp(type, "relay") == 0) { + candidate->type = ICE_CANDIDATE_TYPE_RELAY; + } else { + LOGE("Unknown candidate type: %s", type); + parsed = 0; + } - if (strstr(addrstring, "local") != NULL) { - if (mdns_resolve_addr(addrstring, &candidate->addr) == 0) { - LOGW("Failed to resolve mDNS address"); - return -1; + if (parsed == 7) { + addr_set_family(&candidate->addr, AF_INET); + addr_set_port(&candidate->addr, (uint16_t)port); + if (candidate->type == ICE_CANDIDATE_TYPE_HOST && + ice_is_mdns_hostname(addrstring)) { + if (mdns_resolve_addr(addrstring, &candidate->addr) == 0) { + LOGW("Failed to resolve mDNS address; retaining host candidate"); + } + result = 0; + } else if (addr_from_string(addrstring, &candidate->addr) != 0) { + addr_set_port(&candidate->addr, (uint16_t)port); + result = 0; + } else { + LOGE("Failed to parse ICE candidate address"); + } } - } else if (addr_from_string(addrstring, &candidate->addr) == 0) { - return -1; } - return 0; + return result; } diff --git a/src/mdns.c b/src/mdns.c index 6d7881cb..9c41328d 100644 --- a/src/mdns.c +++ b/src/mdns.c @@ -1,170 +1,289 @@ -#include +#include #include #include #include +#include #include #include + #include "address.h" +#include "mdns.h" #include "socket.h" #include "utils.h" #define MDNS_GROUP "224.0.0.251" #define MDNS_PORT 5353 - -typedef struct DnsHeader { - uint16_t id; - uint16_t flags; - uint16_t qestions; - uint16_t answer_rrs; - uint16_t authority_rrs; - uint16_t additional_rrs; -} DnsHeader; - -typedef struct DnsAnswer { - uint16_t type; - uint16_t class; - uint32_t ttl; - uint16_t length; - uint8_t data[0]; -} DnsAnswer; - -typedef struct DnsQuery { - uint16_t type; - uint16_t class; -} DnsQuery; +#define MDNS_HEADER_SIZE 12 +#define MDNS_QUERY_ATTEMPTS 3 +#define MDNS_RESPONSE_TIMEOUT_SECONDS 1 static int mdns_add_hostname(const char* hostname, uint8_t* buf, int size) { - const char *label, *dot; - int len, offset; - - offset = 0; - label = hostname; - while (*label) { - dot = strchr(label, '.'); - if (!dot) { + const char* label = hostname; + int offset = 0; + int result = -1; + int valid = 1; + + if (hostname == NULL || buf == NULL || size <= 0) { + valid = 0; + } + + while (valid && *label != '\0') { + const char* dot = strchr(label, '.'); + int label_length; + + if (dot == NULL) { dot = label + strlen(label); } - len = dot - label; - buf[offset++] = len; - memcpy(buf + offset, label, len); - offset += len; - label = *dot ? dot + 1 : dot; - } - buf[offset++] = 0x00; - return offset; -} -static int mdns_parse_answer(uint8_t* buf, int size, Address* addr, const char* hostname) { - int flags_qr, offset; - DnsHeader* header; - DnsAnswer* answer; - uint8_t name[256]; - - if (size < sizeof(DnsHeader)) { - LOGE("response too short"); - return -1; + label_length = (int)(dot - label); + if (label_length <= 0 || label_length > 63 || + offset > size - label_length - 1) { + valid = 0; + } else { + buf[offset++] = (uint8_t)label_length; + memcpy(buf + offset, label, label_length); + offset += label_length; + label = *dot != '\0' ? dot + 1 : dot; + } } - header = (DnsHeader*)buf; - flags_qr = ntohs(header->flags) >> 15; - if (flags_qr != 1) { - LOGD("flag is not a DNS response"); - return -1; + if (valid && offset < size) { + buf[offset++] = 0; + result = offset; } - buf += sizeof(DnsHeader); - offset = mdns_add_hostname(hostname, name, sizeof(name)); - // compare hostname - if (memcmp(buf, name, offset)) { - LOGI("not a mDNS response"); - return -1; + return result; +} + +/* Decode a DNS name while preserving the caller's position after compression. */ +static int mdns_decode_name(const uint8_t* packet, int size, int start, + char* name, int name_size, int* next_offset) { + int result = -1; + int offset = start; + int end_offset = -1; + int name_length = 0; + int steps = 0; + + if (packet != NULL && name != NULL && name_size > 0 && + next_offset != NULL && start >= 0 && start < size) { + name[0] = '\0'; + while (result < 0 && steps <= size && offset >= 0 && offset < size) { + int label_length = packet[offset]; + int required_length; + + steps++; + if ((label_length & 0xc0) == 0xc0) { + if (offset + 1 < size) { + int pointer = ((label_length & 0x3f) << 8) | packet[offset + 1]; + if (end_offset < 0) { + end_offset = offset + 2; + } + if (pointer < size) { + offset = pointer; + } else { + steps = size + 1; + } + } else { + steps = size + 1; + } + } else if ((label_length & 0xc0) != 0) { + steps = size + 1; + } else if (label_length == 0) { + if (end_offset < 0) { + end_offset = offset + 1; + } + name[name_length] = '\0'; + *next_offset = end_offset; + result = 0; + } else { + offset++; + required_length = name_length + label_length + + (name_length > 0 ? 1 : 0); + if (offset + label_length <= size && required_length < name_size) { + if (name_length > 0) { + name[name_length++] = '.'; + } + memcpy(name + name_length, packet + offset, label_length); + name_length += label_length; + offset += label_length; + } else { + steps = size + 1; + } + } + } } - answer = (DnsAnswer*)(buf + offset); - LOGD("type: %" PRIu16 ", class: %" PRIu16 ", ttl: %" PRIu32 ", length: %" PRIu16 "", ntohs(answer->type), ntohs(answer->class), ntohl(answer->ttl), ntohs(answer->length)); - if (ntohs(answer->length) != 4) { - LOGI("invalid length"); - return -1; + return result; +} + +static int mdns_read_u16(const uint8_t* packet, int size, int offset, + uint16_t* value) { + int result = -1; + + if (packet != NULL && value != NULL && offset >= 0 && offset + 1 < size) { + *value = (uint16_t)(((uint16_t)packet[offset] << 8) | + (uint16_t)packet[offset + 1]); + result = 0; } - memcpy(&addr->sin.sin_addr, answer->data, 4); - return 0; + return result; } -static int mdns_build_query(const char* hostname, uint8_t* buf, int size) { - int total_size, offset; - DnsHeader* dns_header; - DnsQuery* dns_query; - - total_size = sizeof(DnsHeader) + strlen(hostname) + sizeof(DnsQuery) + 2; - if (size < total_size) { - printf("buf size is not enough"); - return -1; +int mdns_parse_response(const uint8_t* packet, int size, Address* addr, + const char* hostname) { + int result = -1; + int offset = MDNS_HEADER_SIZE; + int valid = 0; + int index; + int total_records = 0; + uint16_t flags = 0; + uint16_t question_count = 0; + uint16_t answer_count = 0; + uint16_t authority_count = 0; + uint16_t additional_count = 0; + char record_name[256]; + + if (packet != NULL && addr != NULL && hostname != NULL && + size >= MDNS_HEADER_SIZE && + mdns_read_u16(packet, size, 2, &flags) == 0 && + mdns_read_u16(packet, size, 4, &question_count) == 0 && + mdns_read_u16(packet, size, 6, &answer_count) == 0 && + mdns_read_u16(packet, size, 8, &authority_count) == 0 && + mdns_read_u16(packet, size, 10, &additional_count) == 0) { + valid = 1; + if ((flags >> 15) != 1) { + valid = 0; + } + + for (index = 0; valid && index < question_count; index++) { + int next_offset = 0; + if (mdns_decode_name(packet, size, offset, record_name, + sizeof(record_name), &next_offset) != 0 || + next_offset > size - 4) { + valid = 0; + } else { + offset = next_offset + 4; + } + } + + total_records = answer_count + authority_count + additional_count; + for (index = 0; valid && result < 0 && index < total_records; index++) { + int next_offset = 0; + int data_offset = 0; + int searchable = index < answer_count || + index >= answer_count + authority_count; + uint16_t record_type = 0; + uint16_t record_class = 0; + uint16_t data_length = 0; + + if (mdns_decode_name(packet, size, offset, record_name, + sizeof(record_name), &next_offset) != 0 || + next_offset > size - 10 || + mdns_read_u16(packet, size, next_offset, &record_type) != 0 || + mdns_read_u16(packet, size, next_offset + 2, &record_class) != 0 || + mdns_read_u16(packet, size, next_offset + 8, &data_length) != 0) { + valid = 0; + } else { + data_offset = next_offset + 10; + if (data_offset > size - data_length) { + valid = 0; + } else { + if (searchable && record_type == 1 && + (record_class & 0x7fff) == 1 && data_length == 4 && + strcasecmp(record_name, hostname) == 0) { + memcpy(&addr->sin.sin_addr, packet + data_offset, 4); + addr_set_family(addr, AF_INET); + result = 0; + } + offset = data_offset + data_length; + } + } + } } - memset(buf, 0, size); - dns_header = (DnsHeader*)buf; - dns_header->qestions = 0x0100; - offset = sizeof(DnsHeader); + return result; +} + +static int mdns_build_query(const char* hostname, uint8_t* buf, int size) { + int offset = MDNS_HEADER_SIZE; + int encoded_length; + int result = -1; - // Append hostname to query - offset += mdns_add_hostname(hostname, buf + offset, size - offset); + if (hostname != NULL && buf != NULL && size >= MDNS_HEADER_SIZE) { + memset(buf, 0, size); + buf[5] = 1; + encoded_length = mdns_add_hostname(hostname, buf + offset, size - offset); + if (encoded_length > 0 && offset + encoded_length <= size - 4) { + offset += encoded_length; + buf[offset++] = 0; + buf[offset++] = 1; + buf[offset++] = 0; + buf[offset++] = 1; + result = offset; + } + } - dns_query = (DnsQuery*)(buf + offset); - dns_query->type = 0x0100; - dns_query->class = 0x0100; - return total_size; + return result; } int mdns_resolve_addr(const char* hostname, Address* addr) { - Address mcast_addr = {0}; + Address multicast_addr = {0}; UdpSocket udp_socket; uint8_t buf[256]; char addr_string[ADDRSTRLEN]; - struct timeval tv = {1, 0}; - fd_set rfds; - int maxfd, send_retry, recv_retry, size, ret; + int attempt; + int query_size; + int result = 0; - if (udp_socket_open(&udp_socket, AF_INET, MDNS_PORT) < 0) { - LOGE("Failed to create socket"); - return -1; - } + udp_socket.fd = -1; + query_size = mdns_build_query(hostname, buf, sizeof(buf)); + if (query_size < 0) { + LOGW("Invalid mDNS hostname"); + } else if (udp_socket_open(&udp_socket, AF_INET, MDNS_PORT) < 0) { + udp_socket.fd = -1; + LOGE("Failed to create mDNS socket"); + } else if (addr_from_string(MDNS_GROUP, &multicast_addr) == 0) { + LOGE("Failed to parse mDNS multicast address"); + } else if (udp_socket_add_multicast_group(&udp_socket, &multicast_addr) < 0) { + LOGE("Failed to join mDNS multicast group"); + } else { + addr_set_port(&multicast_addr, MDNS_PORT); + for (attempt = 0; result == 0 && attempt < MDNS_QUERY_ATTEMPTS; attempt++) { + struct timeval timeout = {MDNS_RESPONSE_TIMEOUT_SECONDS, 0}; + int waiting = 1; - addr_from_string(MDNS_GROUP, &mcast_addr); - addr_set_port(&mcast_addr, MDNS_PORT); - if (udp_socket_add_multicast_group(&udp_socket, &mcast_addr) < 0) { - LOGE("Failed to add multicast group"); - return -1; - } + if (udp_socket_sendto(&udp_socket, &multicast_addr, buf, query_size) < 0) { + waiting = 0; + } + while (result == 0 && waiting) { + fd_set read_fds; + int selected; - maxfd = udp_socket.fd; - FD_ZERO(&rfds); - - for (send_retry = 3; send_retry > 0; send_retry--) { - size = mdns_build_query(hostname, buf, sizeof(buf)); - udp_socket_sendto(&udp_socket, &mcast_addr, buf, size); - for (recv_retry = 5; recv_retry > 0; recv_retry--) { - FD_SET(udp_socket.fd, &rfds); - ret = select(maxfd + 1, &rfds, NULL, NULL, &tv); - - if (ret < 0) { - LOGE("select error"); - break; - } else if (ret > 0 && FD_ISSET(udp_socket.fd, &rfds)) { - ret = udp_socket_recvfrom(&udp_socket, NULL, buf, sizeof(buf)); - if (!mdns_parse_answer(buf, ret, addr, hostname)) { - addr_to_string(addr, addr_string, sizeof(addr_string)); - addr_set_family(addr, AF_INET); - LOGI("Resolved %s -> %s", hostname, addr_string); - udp_socket_close(&udp_socket); - return 1; + FD_ZERO(&read_fds); + FD_SET(udp_socket.fd, &read_fds); + selected = select(udp_socket.fd + 1, &read_fds, NULL, NULL, &timeout); + if (selected > 0 && FD_ISSET(udp_socket.fd, &read_fds)) { + int received = udp_socket_recvfrom(&udp_socket, NULL, buf, + sizeof(buf)); + if (received > 0 && + mdns_parse_response(buf, received, addr, hostname) == 0) { + addr_set_port(addr, addr->port); + addr_to_string(addr, addr_string, sizeof(addr_string)); + LOGI("Resolved %s -> %s", hostname, addr_string); + result = 1; + } } else { - LOGD("timeout"); + waiting = 0; } } } } - LOGI("Failed to resolve hostname"); - udp_socket_close(&udp_socket); - return 0; + if (udp_socket.fd >= 0) { + udp_socket_close(&udp_socket); + } + if (result == 0) { + LOGI("Failed to resolve mDNS hostname"); + } + + return result; } diff --git a/src/mdns.h b/src/mdns.h index aa844663..89e3d1ed 100644 --- a/src/mdns.h +++ b/src/mdns.h @@ -1,9 +1,14 @@ #ifndef MDNS_H_ #define MDNS_H_ +#include #include + #include "address.h" +int mdns_parse_response(const uint8_t* packet, int size, Address* addr, + const char* hostname); + int mdns_resolve_addr(const char* hostname, Address* addr); #endif // MDNS_H_ diff --git a/src/stun.c b/src/stun.c index d3872410..48e44a21 100644 --- a/src/stun.c +++ b/src/stun.c @@ -149,6 +149,7 @@ void stun_parse_msg_buf(StunMessage* msg) { } else if ((msg->stunmethod & STUN_METHOD_BINDING) == STUN_METHOD_BINDING) { msg->stunmethod = STUN_METHOD_BINDING; } + msg->use_candidate = 0; while (pos < length) { StunAttribute* attr = (StunAttribute*)(msg->buf + pos); @@ -201,7 +202,9 @@ void stun_parse_msg_buf(StunMessage* msg) { case STUN_ATTR_TYPE_PRIORITY: break; case STUN_ATTR_TYPE_USE_CANDIDATE: - // LOGD("Use Candidate"); + if (ntohs(attr->length) == 0) { + msg->use_candidate = 1; + } break; case STUN_ATTR_TYPE_FINGERPRINT: memcpy(&msg->fingerprint, attr->value, ntohs(attr->length)); diff --git a/src/stun.h b/src/stun.h index 801133b6..f2bcee3d 100644 --- a/src/stun.h +++ b/src/stun.h @@ -85,6 +85,7 @@ struct StunAttribute { struct StunMessage { StunClass stunclass; StunMethod stunmethod; + int use_candidate; uint32_t fingerprint; char message_integrity[20]; char username[128]; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6c0c6dfd..b5e6ad6d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -9,7 +9,9 @@ foreach(sourcefile ${SRCS}) string(REPLACE "${PROJECT_SOURCE_DIR}/" "" appname ${appname}) add_executable(${appname} ${sourcefile}) target_link_libraries(${appname} peer pthread) + if(appname STREQUAL "test_ice_mdns") + add_test(NAME ${appname} COMMAND ${appname}) + endif() endforeach(sourcefile ${TEST_SRCS}) target_link_libraries(test_peer_connection cjson) - diff --git a/tests/test_ice_mdns.c b/tests/test_ice_mdns.c new file mode 100644 index 00000000..1306440d --- /dev/null +++ b/tests/test_ice_mdns.c @@ -0,0 +1,191 @@ +#include +#include +#include + +#include "address.h" +#include "agent.h" +#include "ice.h" +#include "mdns.h" +#include "stun.h" + +#define TEST_HOSTNAME "00000000-0000-0000-0000-000000000000.local" + +static int failures = 0; + +#define CHECK(condition) \ + do { \ + if (!(condition)) { \ + fprintf(stderr, "check failed at %s:%d: %s\n", __FILE__, __LINE__, \ + #condition); \ + failures++; \ + } \ + } while (0) + +static int append_dns_name(uint8_t* packet, int offset, const char* hostname) { + const char* label = hostname; + + while (*label != '\0') { + const char* dot = strchr(label, '.'); + int length; + if (dot == NULL) { + dot = label + strlen(label); + } + length = (int)(dot - label); + packet[offset++] = (uint8_t)length; + memcpy(packet + offset, label, length); + offset += length; + label = *dot != '\0' ? dot + 1 : dot; + } + packet[offset++] = 0; + + return offset; +} + +static void test_compressed_additional_mdns_answer(void) { + uint8_t packet[128] = {0}; + Address addr = {0}; + char address_string[ADDRSTRLEN]; + int offset = 12; + + packet[2] = 0x84; + packet[5] = 1; + packet[11] = 1; + offset = append_dns_name(packet, offset, TEST_HOSTNAME); + packet[offset++] = 0; + packet[offset++] = 1; + packet[offset++] = 0; + packet[offset++] = 1; + packet[offset++] = 0xc0; + packet[offset++] = 0x0c; + packet[offset++] = 0; + packet[offset++] = 1; + packet[offset++] = 0x80; + packet[offset++] = 1; + packet[offset++] = 0; + packet[offset++] = 0; + packet[offset++] = 0; + packet[offset++] = 120; + packet[offset++] = 0; + packet[offset++] = 4; + packet[offset++] = 192; + packet[offset++] = 0; + packet[offset++] = 2; + packet[offset++] = 44; + + CHECK(mdns_parse_response(packet, offset, &addr, TEST_HOSTNAME) == 0); + CHECK(addr.family == AF_INET); + CHECK(addr_to_string(&addr, address_string, sizeof(address_string)) != 0); + CHECK(strcmp(address_string, "192.0.2.44") == 0); +} + +static void test_malformed_compression_is_rejected(void) { + uint8_t packet[16] = {0}; + Address addr = {0}; + + packet[2] = 0x80; + packet[5] = 1; + packet[12] = 0xc0; + packet[13] = 0x0c; + + CHECK(mdns_parse_response(packet, 14, &addr, TEST_HOSTNAME) == -1); +} + +static void test_unresolved_mdns_candidate_is_retained(void) { + char description[] = + "a=candidate:1 1 udp 2122260223 " TEST_HOSTNAME + " 54400 typ host generation 0\r\n"; + char address_string[ADDRSTRLEN]; + IceCandidate candidate; + char* end = strstr(description, "\r\n"); + + CHECK(ice_candidate_from_description(&candidate, description, end) == 0); + CHECK(candidate.type == ICE_CANDIDATE_TYPE_HOST); + CHECK(candidate.addr.family == AF_INET); + CHECK(candidate.addr.port == 54400); + CHECK(addr_to_string(&candidate.addr, address_string, + sizeof(address_string)) != 0); + CHECK(strcmp(address_string, "0.0.0.0") == 0); +} + +static void create_binding_request(StunMessage* request, const char* password, + int use_candidate) { + uint32_t priority = 0; + + memset(request, 0, sizeof(*request)); + stun_msg_create(request, STUN_CLASS_REQUEST | STUN_METHOD_BINDING); + stun_msg_write_attr(request, STUN_ATTR_TYPE_USERNAME, 9, "local:rem"); + stun_msg_write_attr(request, STUN_ATTR_TYPE_PRIORITY, sizeof(priority), + (char*)&priority); + if (use_candidate) { + stun_msg_write_attr(request, STUN_ATTR_TYPE_USE_CANDIDATE, 0, NULL); + } + stun_msg_finish(request, STUN_CREDENTIAL_SHORT_TERM, password, + strlen(password)); + stun_parse_msg_buf(request); +} + +static void reset_nominated_pair(Agent* agent, IceCandidate* remote, + IceCandidatePair* pair) { + memset(remote, 0, sizeof(*remote)); + addr_set_family(&remote->addr, AF_INET); + addr_set_port(&remote->addr, 54400); + remote->type = ICE_CANDIDATE_TYPE_HOST; + + memset(pair, 0, sizeof(*pair)); + pair->remote = remote; + pair->state = ICE_CANDIDATE_STATE_INPROGRESS; + agent->nominated_pair = pair; +} + +static void test_authenticated_nomination_learns_peer_reflexive_address(void) { + Agent agent; + Address source = {0}; + IceCandidate remote; + IceCandidatePair pair; + StunMessage request; + char address_string[ADDRSTRLEN]; + + memset(&agent, 0, sizeof(agent)); + CHECK(agent_create(&agent) == 0); + snprintf(agent.local_upwd, sizeof(agent.local_upwd), "%s", "secret"); + CHECK(addr_from_string("127.0.0.2", &source) != 0); + addr_set_port(&source, 55000); + + reset_nominated_pair(&agent, &remote, &pair); + create_binding_request(&request, "wrong-secret", 1); + CHECK(request.use_candidate == 1); + agent_process_stun_request(&agent, &request, &source); + CHECK(pair.state == ICE_CANDIDATE_STATE_INPROGRESS); + CHECK(remote.type == ICE_CANDIDATE_TYPE_HOST); + + reset_nominated_pair(&agent, &remote, &pair); + create_binding_request(&request, "secret", 0); + CHECK(request.use_candidate == 0); + agent_process_stun_request(&agent, &request, &source); + CHECK(pair.state == ICE_CANDIDATE_STATE_INPROGRESS); + CHECK(remote.type == ICE_CANDIDATE_TYPE_HOST); + + reset_nominated_pair(&agent, &remote, &pair); + create_binding_request(&request, "secret", 1); + agent_process_stun_request(&agent, &request, &source); + CHECK(pair.state == ICE_CANDIDATE_STATE_SUCCEEDED); + CHECK(remote.type == ICE_CANDIDATE_TYPE_PRFLX); + CHECK(remote.addr.port == 55000); + CHECK(addr_to_string(&remote.addr, address_string, + sizeof(address_string)) != 0); + CHECK(strcmp(address_string, "127.0.0.2") == 0); + + agent_destroy(&agent); +} + +int main(void) { + test_compressed_additional_mdns_answer(); + test_malformed_compression_is_rejected(); + test_unresolved_mdns_candidate_is_retained(); + test_authenticated_nomination_learns_peer_reflexive_address(); + + if (failures != 0) { + fprintf(stderr, "%d test checks failed\n", failures); + } + return failures == 0 ? 0 : 1; +} From e8efc4a93b4fb9bb8efec28e3dac1e3626f64088 Mon Sep 17 00:00:00 2001 From: maikebing Date: Sat, 22 Aug 2026 03:35:49 +0800 Subject: [PATCH 2/5] feat(ice): support bounded media port ranges --- src/agent.c | 50 +++++++++++++++++++++++--------- src/agent.h | 3 +- src/peer_connection.c | 32 ++++++++++++--------- src/peer_connection.h | 3 ++ src/socket.c | 8 ++++-- tests/CMakeLists.txt | 3 +- tests/test_agent.c | 2 +- tests/test_ice_mdns.c | 2 +- tests/test_port_range.c | 64 +++++++++++++++++++++++++++++++++++++++++ 9 files changed, 134 insertions(+), 33 deletions(-) create mode 100644 tests/test_port_range.c diff --git a/src/agent.c b/src/agent.c index 52c2994b..bb01e6f8 100644 --- a/src/agent.c +++ b/src/agent.c @@ -23,35 +23,57 @@ void agent_clear_candidates(Agent* agent) { agent->candidate_pairs_num = 0; } -int agent_create(Agent* agent) { - int ret; - if ((ret = udp_socket_open(&agent->udp_sockets[0], AF_INET, 0)) < 0) { +int agent_create(Agent* agent, uint16_t port_range_begin, + uint16_t port_range_end) { + uint32_t port; + int result = -1; + + agent->udp_sockets[0].fd = -1; + agent->udp_sockets[1].fd = -1; + if (port_range_begin == 0 && port_range_end == 0) { + result = udp_socket_open(&agent->udp_sockets[0], AF_INET, 0); + } else if (port_range_begin > 0 && port_range_end >= port_range_begin) { + for (port = port_range_begin; + result < 0 && port <= port_range_end; + port++) { + result = udp_socket_open(&agent->udp_sockets[0], AF_INET, (int)port); + } + } else { + LOGE("Invalid UDP port range: %u-%u", port_range_begin, port_range_end); + } + + if (result < 0) { LOGE("Failed to create UDP socket."); - return ret; + } else { + LOGI("create IPv4 UDP socket: %d", agent->udp_sockets[0].fd); } - LOGI("create IPv4 UDP socket: %d", agent->udp_sockets[0].fd); #if CONFIG_IPV6 - if ((ret = udp_socket_open(&agent->udp_sockets[1], AF_INET6, 0)) < 0) { + if (result == 0 && + (result = udp_socket_open(&agent->udp_sockets[1], AF_INET6, 0)) < 0) { LOGE("Failed to create IPv6 UDP socket."); - return ret; + udp_socket_close(&agent->udp_sockets[0]); + } else if (result == 0) { + LOGI("create IPv6 UDP socket: %d", agent->udp_sockets[1].fd); } - LOGI("create IPv6 UDP socket: %d", agent->udp_sockets[1].fd); #endif - agent_clear_candidates(agent); - memset(agent->remote_ufrag, 0, sizeof(agent->remote_ufrag)); - memset(agent->remote_upwd, 0, sizeof(agent->remote_upwd)); - return 0; + if (result == 0) { + agent_clear_candidates(agent); + memset(agent->remote_ufrag, 0, sizeof(agent->remote_ufrag)); + memset(agent->remote_upwd, 0, sizeof(agent->remote_upwd)); + } + + return result; } void agent_destroy(Agent* agent) { - if (agent->udp_sockets[0].fd > 0) { + if (agent->udp_sockets[0].fd >= 0) { udp_socket_close(&agent->udp_sockets[0]); } #if CONFIG_IPV6 - if (agent->udp_sockets[1].fd > 0) { + if (agent->udp_sockets[1].fd >= 0) { udp_socket_close(&agent->udp_sockets[1]); } #endif diff --git a/src/agent.h b/src/agent.h index 51ff381f..73536ed8 100644 --- a/src/agent.h +++ b/src/agent.h @@ -88,7 +88,8 @@ int agent_send_binding_request(Agent* agent); void agent_clear_candidates(Agent* agent); -int agent_create(Agent* agent); +int agent_create(Agent* agent, uint16_t port_range_begin, + uint16_t port_range_end); void agent_destroy(Agent* agent); diff --git a/src/peer_connection.c b/src/peer_connection.c index 91136de5..fe6e6c9d 100644 --- a/src/peer_connection.c +++ b/src/peer_connection.c @@ -164,24 +164,30 @@ PeerConnection* peer_connection_create(PeerConfiguration* config) { memcpy(&pc->config, config, sizeof(PeerConfiguration)); - agent_create(&pc->agent); + if (agent_create(&pc->agent, config->port_range_begin, + config->port_range_end) < 0) { + free(pc); + pc = NULL; + } - memset(&pc->sctp, 0, sizeof(pc->sctp)); + if (pc != NULL) { + memset(&pc->sctp, 0, sizeof(pc->sctp)); - if (pc->config.audio_codec) { - rtp_encoder_init(&pc->artp_encoder, pc->config.audio_codec, - peer_connection_outgoing_rtp_packet, (void*)pc); + if (pc->config.audio_codec) { + rtp_encoder_init(&pc->artp_encoder, pc->config.audio_codec, + peer_connection_outgoing_rtp_packet, (void*)pc); - rtp_decoder_init(&pc->artp_decoder, pc->config.audio_codec, - pc->config.onaudiotrack, pc->config.user_data); - } + rtp_decoder_init(&pc->artp_decoder, pc->config.audio_codec, + pc->config.onaudiotrack, pc->config.user_data); + } - if (pc->config.video_codec) { - rtp_encoder_init(&pc->vrtp_encoder, pc->config.video_codec, - peer_connection_outgoing_rtp_packet, (void*)pc); + if (pc->config.video_codec) { + rtp_encoder_init(&pc->vrtp_encoder, pc->config.video_codec, + peer_connection_outgoing_rtp_packet, (void*)pc); - rtp_decoder_init(&pc->vrtp_decoder, pc->config.video_codec, - pc->config.onvideotrack, pc->config.user_data); + rtp_decoder_init(&pc->vrtp_decoder, pc->config.video_codec, + pc->config.onvideotrack, pc->config.user_data); + } } return pc; diff --git a/src/peer_connection.h b/src/peer_connection.h index 600b6a57..0929f256 100644 --- a/src/peer_connection.h +++ b/src/peer_connection.h @@ -78,6 +78,9 @@ typedef struct PeerConfiguration { void (*on_request_keyframe)(void* userdata); void* user_data; + uint16_t port_range_begin; + uint16_t port_range_end; + } PeerConfiguration; typedef struct PeerConnection PeerConnection; diff --git a/src/socket.c b/src/socket.c index a910cc21..dd28ee11 100644 --- a/src/socket.c +++ b/src/socket.c @@ -60,7 +60,10 @@ int udp_socket_open(UdpSocket* udp_socket, int family, int port) { } do { - if ((ret = setsockopt(udp_socket->fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse))) < 0) { + /* mDNS shares its well-known port; ICE media ports stay exclusive. */ + if (port == 5353 && + (ret = setsockopt(udp_socket->fd, SOL_SOCKET, SO_REUSEADDR, &reuse, + sizeof(reuse))) < 0) { LOGW("reuse failed. ignore"); } @@ -94,8 +97,9 @@ int udp_socket_open(UdpSocket* udp_socket, int family, int port) { } void udp_socket_close(UdpSocket* udp_socket) { - if (udp_socket->fd > 0) { + if (udp_socket->fd >= 0) { close(udp_socket->fd); + udp_socket->fd = -1; } } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b5e6ad6d..ad6b8735 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -9,7 +9,8 @@ foreach(sourcefile ${SRCS}) string(REPLACE "${PROJECT_SOURCE_DIR}/" "" appname ${appname}) add_executable(${appname} ${sourcefile}) target_link_libraries(${appname} peer pthread) - if(appname STREQUAL "test_ice_mdns") + if(appname STREQUAL "test_ice_mdns" OR + appname STREQUAL "test_port_range") add_test(NAME ${appname} COMMAND ${appname}) endif() endforeach(sourcefile ${TEST_SRCS}) diff --git a/tests/test_agent.c b/tests/test_agent.c index b7e4e22f..2e288a28 100644 --- a/tests/test_agent.c +++ b/tests/test_agent.c @@ -26,7 +26,7 @@ int main(int argc, char* argv[]) { char description[1024]; memset(&description, 0, sizeof(description)); - agent_create(&agent); + agent_create(&agent, 0, 0); test_gather_host(&agent); test_gather_stun(&agent, stunserver); diff --git a/tests/test_ice_mdns.c b/tests/test_ice_mdns.c index 1306440d..f83dc697 100644 --- a/tests/test_ice_mdns.c +++ b/tests/test_ice_mdns.c @@ -146,7 +146,7 @@ static void test_authenticated_nomination_learns_peer_reflexive_address(void) { char address_string[ADDRSTRLEN]; memset(&agent, 0, sizeof(agent)); - CHECK(agent_create(&agent) == 0); + CHECK(agent_create(&agent, 0, 0) == 0); snprintf(agent.local_upwd, sizeof(agent.local_upwd), "%s", "secret"); CHECK(addr_from_string("127.0.0.2", &source) != 0); addr_set_port(&source, 55000); diff --git a/tests/test_port_range.c b/tests/test_port_range.c new file mode 100644 index 00000000..ca6d7202 --- /dev/null +++ b/tests/test_port_range.c @@ -0,0 +1,64 @@ +#include +#include +#include + +#include "agent.h" + +static int failures = 0; + +#define CHECK(condition) \ + do { \ + if (!(condition)) { \ + fprintf(stderr, "check failed at %s:%d: %s\n", __FILE__, __LINE__, \ + #condition); \ + failures++; \ + } \ + } while (0) + +int main(void) { + Agent blocker; + Agent ranged; + Agent duplicate; + uint16_t blocked_port; + uint16_t range_begin; + uint16_t range_end; + uint16_t selected_port = 0; + int blocker_created; + int ranged_created; + + memset(&blocker, 0, sizeof(blocker)); + memset(&ranged, 0, sizeof(ranged)); + memset(&duplicate, 0, sizeof(duplicate)); + + blocker_created = agent_create(&blocker, 0, 0) == 0; + CHECK(blocker_created); + if (blocker_created) { + blocked_port = blocker.udp_sockets[0].bind_addr.port; + if (blocked_port <= UINT16_MAX - 16) { + range_begin = blocked_port; + range_end = blocked_port + 16; + } else { + range_begin = blocked_port - 16; + range_end = blocked_port; + } + + ranged_created = agent_create(&ranged, range_begin, range_end) == 0; + CHECK(ranged_created); + if (ranged_created) { + selected_port = ranged.udp_sockets[0].bind_addr.port; + CHECK(selected_port >= range_begin); + CHECK(selected_port <= range_end); + CHECK(selected_port != blocked_port); + CHECK(agent_create(&duplicate, selected_port, selected_port) < 0); + agent_destroy(&ranged); + } + agent_destroy(&blocker); + } + + CHECK(agent_create(&duplicate, 60000, 59999) < 0); + + if (failures != 0) { + fprintf(stderr, "%d test checks failed\n", failures); + } + return failures == 0 ? 0 : 1; +} From 4a470a628c85afab30a7ac3fc45284488b8af9ef Mon Sep 17 00:00:00 2001 From: maikebing Date: Sat, 22 Aug 2026 03:39:18 +0800 Subject: [PATCH 3/5] feat(video): configure H264 WebRTC descriptions --- src/peer_connection.c | 20 +++++++- src/peer_connection.h | 8 ++++ src/sdp.c | 107 +++++++++++++++++++++++++++++++----------- src/sdp.h | 8 +++- tests/CMakeLists.txt | 3 +- tests/test_h264_sdp.c | 74 +++++++++++++++++++++++++++++ 6 files changed, 187 insertions(+), 33 deletions(-) create mode 100644 tests/test_h264_sdp.c diff --git a/src/peer_connection.c b/src/peer_connection.c index fe6e6c9d..b812fa60 100644 --- a/src/peer_connection.c +++ b/src/peer_connection.c @@ -187,6 +187,14 @@ PeerConnection* peer_connection_create(PeerConfiguration* config) { rtp_decoder_init(&pc->vrtp_decoder, pc->config.video_codec, pc->config.onvideotrack, pc->config.user_data); + if (pc->config.video_payload_type >= 96 && + pc->config.video_payload_type <= 127) { + pc->vrtp_encoder.type = + (RtpPayloadType)pc->config.video_payload_type; + } + if (pc->config.video_ssrc != 0) { + pc->vrtp_encoder.ssrc = pc->config.video_ssrc; + } } } @@ -503,7 +511,8 @@ static const char* peer_connection_create_sdp(PeerConnection* pc, SdpType sdp_ty int sdp_audio = (pc->config.audio_codec != CODEC_NONE) && (sdp_type == SDP_TYPE_OFFER || pc->remote_assrc > 0); int sdp_video = (pc->config.video_codec != CODEC_NONE) && (sdp_type == SDP_TYPE_OFFER || pc->remote_vssrc > 0); - sdp_create(pc->sdp, sdp_video, sdp_audio, pc->config.datachannel); + sdp_create(pc->sdp, sdp_video, sdp_audio, pc->config.datachannel, + pc->config.video_mid); sdp_append(pc->sdp, "a=ice-ufrag:%s", pc->agent.local_ufrag); sdp_append(pc->sdp, "a=ice-pwd:%s", pc->agent.local_upwd); @@ -513,7 +522,14 @@ static const char* peer_connection_create_sdp(PeerConnection* pc, SdpType sdp_ty if (sdp_video) { switch (pc->config.video_codec) { case CODEC_H264: - sdp_append_h264(pc->sdp); + sdp_append_h264(pc->sdp, + pc->config.video_payload_type, + pc->config.video_ssrc, + pc->config.video_mid, + pc->config.video_fmtp, + pc->config.video_send_only, + pc->config.video_stream_id, + pc->config.video_track_id); break; case CODEC_VP8: sdp_append_vp8(pc->sdp); diff --git a/src/peer_connection.h b/src/peer_connection.h index 0929f256..9ed91bd2 100644 --- a/src/peer_connection.h +++ b/src/peer_connection.h @@ -81,6 +81,14 @@ typedef struct PeerConfiguration { uint16_t port_range_begin; uint16_t port_range_end; + uint8_t video_payload_type; + uint32_t video_ssrc; + const char* video_mid; + const char* video_fmtp; + const char* video_stream_id; + const char* video_track_id; + int video_send_only; + } PeerConfiguration; typedef struct PeerConnection PeerConnection; diff --git a/src/sdp.c b/src/sdp.c index 9d7666c3..f1091e3e 100644 --- a/src/sdp.c +++ b/src/sdp.c @@ -3,6 +3,44 @@ #include "sdp.h" +#define SDP_DEFAULT_H264_FMTP \ + "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f" +#define SDP_DEFAULT_VIDEO_MID "1" +#define SDP_DEFAULT_VIDEO_STREAM_ID "libpeer-stream" +#define SDP_DEFAULT_VIDEO_TRACK_ID "webrtc-h264" + +static const char* sdp_select_token(const char* value, const char* fallback) { + size_t index = 0; + int valid = value != NULL && value[0] != '\0'; + + while (valid && value[index] != '\0') { + unsigned char current = (unsigned char)value[index]; + if (current <= 0x20 || current >= 0x7f || index >= 63) { + valid = 0; + } else { + index++; + } + } + + return valid ? value : fallback; +} + +static const char* sdp_select_line_value(const char* value, + const char* fallback) { + size_t index = 0; + int valid = value != NULL && value[0] != '\0'; + + while (valid && value[index] != '\0') { + if (value[index] == '\r' || value[index] == '\n' || index >= 255) { + valid = 0; + } else { + index++; + } + } + + return valid ? value : fallback; +} + int sdp_append(char* sdp, const char* format, ...) { va_list argptr; @@ -26,28 +64,37 @@ 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"); - 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=rtpmap:96 H264/90000"); - sdp_append(sdp, "a=ssrc:1 cname:webrtc-h264"); - sdp_append(sdp, "a=sendrecv"); - sdp_append(sdp, "a=mid:1"); - sdp_append(sdp, "a=rtcp-mux"); -} +void sdp_append_h264(char* sdp, uint8_t payload_type, uint32_t ssrc, + const char* mid, const char* fmtp, int send_only, + const char* stream_id, const char* track_id) { + const char* selected_mid = + sdp_select_token(mid, SDP_DEFAULT_VIDEO_MID); + const char* selected_fmtp = + sdp_select_line_value(fmtp, SDP_DEFAULT_H264_FMTP); + const char* selected_stream_id = + sdp_select_token(stream_id, SDP_DEFAULT_VIDEO_STREAM_ID); + const char* selected_track_id = + sdp_select_token(track_id, SDP_DEFAULT_VIDEO_TRACK_ID); + + if (payload_type < 96 || payload_type > 127) { + payload_type = 96; + } + if (ssrc == 0) { + ssrc = 1; + } -void sdp_append_vp8(char* sdp) { - sdp_append(sdp, "m=video 9 UDP/TLS/RTP/SAVPF 97"); + sdp_append(sdp, "m=video 9 UDP/TLS/RTP/SAVPF %u", payload_type); sdp_append(sdp, "c=IN IP4 0.0.0.0"); - sdp_append(sdp, "a=rtcp-fb:97 nack"); - sdp_append(sdp, "a=rtcp-fb:97 nack pli"); - sdp_append(sdp, "a=rtpmap:97 VP8/90000"); - sdp_append(sdp, "a=ssrc:1 cname:webrtc-vp8"); - sdp_append(sdp, "a=sendrecv"); - sdp_append(sdp, "a=mid:1"); + sdp_append(sdp, "a=rtcp-fb:%u nack", payload_type); + sdp_append(sdp, "a=rtcp-fb:%u nack pli", payload_type); + sdp_append(sdp, "a=fmtp:%u %s", payload_type, selected_fmtp); + sdp_append(sdp, "a=rtpmap:%u H264/90000", payload_type); + sdp_append(sdp, "a=ssrc:%u cname:%s", ssrc, selected_track_id); + sdp_append(sdp, "a=ssrc:%u msid:%s %s", ssrc, selected_stream_id, + selected_track_id); + sdp_append(sdp, "a=msid:%s %s", selected_stream_id, selected_track_id); + sdp_append(sdp, send_only ? "a=sendonly" : "a=sendrecv"); + sdp_append(sdp, "a=mid:%s", selected_mid); sdp_append(sdp, "a=rtcp-mux"); } @@ -89,30 +136,34 @@ void sdp_append_datachannel(char* sdp) { 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, + const char* video_mid) { + const char* selected_video_mid = + sdp_select_token(video_mid, SDP_DEFAULT_VIDEO_MID); + char bundle[128]; + sdp_append(sdp, "v=0"); sdp_append(sdp, "o=- 1495799811084970 1495799811084970 IN IP4 0.0.0.0"); sdp_append(sdp, "s=-"); sdp_append(sdp, "t=0 0"); - sdp_append(sdp, "a=msid-semantic: iot"); + sdp_append(sdp, "a=msid-semantic: WMS *"); #if ICE_LITE sdp_append(sdp, "a=ice-lite"); #endif - memset(bundle, 0, sizeof(bundle)); - - strcat(bundle, "a=group:BUNDLE"); + snprintf(bundle, sizeof(bundle), "%s", "a=group:BUNDLE"); if (b_datachannel) { strcat(bundle, " 0"); } if (b_video) { - strcat(bundle, " 1"); + snprintf(bundle + strlen(bundle), sizeof(bundle) - strlen(bundle), + " %s", selected_video_mid); } if (b_audio) { - strcat(bundle, " 2"); + snprintf(bundle + strlen(bundle), sizeof(bundle) - strlen(bundle), + " %s", "2"); } sdp_append(sdp, bundle); diff --git a/src/sdp.h b/src/sdp.h index 6bb65292..dccf468c 100644 --- a/src/sdp.h +++ b/src/sdp.h @@ -1,6 +1,7 @@ #ifndef SDP_H_ #define SDP_H_ +#include #include #include "config.h" @@ -10,7 +11,9 @@ #define ICE_LITE 0 #endif -void sdp_append_h264(char* sdp); +void sdp_append_h264(char* sdp, uint8_t payload_type, uint32_t ssrc, + const char* mid, const char* fmtp, int send_only, + const char* stream_id, const char* track_id); void sdp_append_vp8(char* sdp); @@ -22,7 +25,8 @@ void sdp_append_opus(char* sdp); void sdp_append_datachannel(char* sdp); -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, + const char* video_mid); int sdp_append(char* sdp, const char* format, ...); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ad6b8735..696a01da 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -10,7 +10,8 @@ foreach(sourcefile ${SRCS}) add_executable(${appname} ${sourcefile}) target_link_libraries(${appname} peer pthread) if(appname STREQUAL "test_ice_mdns" OR - appname STREQUAL "test_port_range") + appname STREQUAL "test_port_range" OR + appname STREQUAL "test_h264_sdp") add_test(NAME ${appname} COMMAND ${appname}) endif() endforeach(sourcefile ${TEST_SRCS}) diff --git a/tests/test_h264_sdp.c b/tests/test_h264_sdp.c new file mode 100644 index 00000000..924e8b8d --- /dev/null +++ b/tests/test_h264_sdp.c @@ -0,0 +1,74 @@ +#include +#include + +#include "sdp.h" + +static int failures = 0; + +#define CHECK(condition) \ + do { \ + if (!(condition)) { \ + fprintf(stderr, "check failed at %s:%d: %s\n", __FILE__, __LINE__, \ + #condition); \ + failures++; \ + } \ + } while (0) + +static void test_custom_h264_media_description(void) { + char sdp[CONFIG_SDP_BUFFER_SIZE]; + + sdp_reset(sdp); + sdp_create(sdp, 1, 0, 0, "0"); + sdp_append_h264( + sdp, 102, 0x11223344, "0", + "packetization-mode=1;profile-level-id=64001f", 1, + "camera-stream", "camera-track"); + + CHECK(strstr(sdp, "a=group:BUNDLE 0\r\n") != NULL); + CHECK(strstr(sdp, "a=msid-semantic: WMS *\r\n") != NULL); + CHECK(strstr(sdp, "m=video 9 UDP/TLS/RTP/SAVPF 102\r\n") != NULL); + CHECK(strstr(sdp, "a=rtpmap:102 H264/90000\r\n") != NULL); + CHECK(strstr(sdp, + "a=fmtp:102 packetization-mode=1;" + "profile-level-id=64001f\r\n") != NULL); + CHECK(strstr(sdp, "a=ssrc:287454020 cname:camera-track\r\n") != NULL); + CHECK(strstr(sdp, + "a=ssrc:287454020 msid:camera-stream camera-track\r\n") != + NULL); + CHECK(strstr(sdp, "a=msid:camera-stream camera-track\r\n") != NULL); + CHECK(strstr(sdp, "a=sendonly\r\n") != NULL); + CHECK(strstr(sdp, "a=mid:0\r\n") != NULL); +} + +static void test_invalid_values_use_safe_defaults(void) { + char sdp[CONFIG_SDP_BUFFER_SIZE]; + + sdp_reset(sdp); + sdp_create(sdp, 1, 0, 0, "0\r\na=ice-lite"); + sdp_append_h264(sdp, 200, 0, "0\r\na=ice-lite", + "profile-level-id=42e01f\r\na=inactive", 0, + "bad stream", "bad\r\ntrack"); + + CHECK(strstr(sdp, "a=group:BUNDLE 1\r\n") != NULL); + CHECK(strstr(sdp, "m=video 9 UDP/TLS/RTP/SAVPF 96\r\n") != NULL); + CHECK(strstr(sdp, + "a=fmtp:96 level-asymmetry-allowed=1;" + "packetization-mode=1;profile-level-id=42e01f\r\n") != NULL); + CHECK(strstr(sdp, "a=ssrc:1 cname:webrtc-h264\r\n") != NULL); + CHECK(strstr(sdp, + "a=ssrc:1 msid:libpeer-stream webrtc-h264\r\n") != NULL); + CHECK(strstr(sdp, "a=sendrecv\r\n") != NULL); + CHECK(strstr(sdp, "a=mid:1\r\n") != NULL); + CHECK(strstr(sdp, "\r\na=ice-lite\r\n") == NULL); + CHECK(strstr(sdp, "\r\na=inactive\r\n") == NULL); +} + +int main(void) { + test_custom_h264_media_description(); + test_invalid_values_use_safe_defaults(); + + if (failures != 0) { + fprintf(stderr, "%d test checks failed\n", failures); + } + return failures == 0 ? 0 : 1; +} From c99bf944d2d1586de223ef00c924cfdcf43dd549 Mon Sep 17 00:00:00 2001 From: maikebing Date: Sat, 22 Aug 2026 03:43:07 +0800 Subject: [PATCH 4/5] fix(rtp): mark the end of H264 access units --- src/peer_connection.c | 12 ++++ src/peer_connection.h | 4 ++ src/rtp.c | 83 +++++++++++++---------- tests/CMakeLists.txt | 3 +- tests/test_h264_rtp.c | 151 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 218 insertions(+), 35 deletions(-) create mode 100644 tests/test_h264_rtp.c diff --git a/src/peer_connection.c b/src/peer_connection.c index b812fa60..a6b0cf58 100644 --- a/src/peer_connection.c +++ b/src/peer_connection.c @@ -231,6 +231,18 @@ int peer_connection_send_video(PeerConnection* pc, const uint8_t* buf, size_t le return rtp_encoder_encode(&pc->vrtp_encoder, buf, len); } +int peer_connection_send_video_at(PeerConnection* pc, const uint8_t* buf, + size_t len, uint32_t timestamp) { + int result = -1; + + if (pc->state == PEER_CONNECTION_COMPLETED) { + pc->vrtp_encoder.timestamp = timestamp; + result = rtp_encoder_encode(&pc->vrtp_encoder, buf, len); + } + + return result; +} + int peer_connection_datachannel_send(PeerConnection* pc, char* message, size_t len) { return peer_connection_datachannel_send_sid(pc, message, len, 0); } diff --git a/src/peer_connection.h b/src/peer_connection.h index 9ed91bd2..e71a26be 100644 --- a/src/peer_connection.h +++ b/src/peer_connection.h @@ -125,6 +125,10 @@ 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); +/* Send one H264 access unit at an explicit 90 kHz RTP timestamp. */ +int peer_connection_send_video_at(PeerConnection* pc, const uint8_t* packet, + size_t bytes, uint32_t timestamp); + 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 21d29f71..d761d273 100644 --- a/src/rtp.c +++ b/src/rtp.c @@ -48,7 +48,9 @@ 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 marker) { RtpPacket* rtp_packet = (RtpPacket*)rtp_encoder->buf; rtp_header_init(&rtp_packet->header, rtp_encoder->type); @@ -56,22 +58,14 @@ static int rtp_encoder_encode_h264_single(RtpEncoder* rtp_encoder, uint8_t* buf, 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_header_set_marker(&rtp_packet->header); - 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); -#endif - + rtp_packet->header.markerbit = marker; memcpy(rtp_packet->payload, buf, size); rtp_encoder->on_packet(rtp_encoder->buf, size + sizeof(RtpHeader), rtp_encoder->user_data); 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 marker) { RtpPacket* rtp_packet = (RtpPacket*)rtp_encoder->buf; rtp_header_init(&rtp_packet->header, rtp_encoder->type); @@ -82,11 +76,6 @@ static int rtp_encoder_encode_h264_fu_a(RtpEncoder* rtp_encoder, uint8_t* buf, s 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; @@ -102,7 +91,7 @@ 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_header_set_marker(&rtp_packet->header); + rtp_packet->header.markerbit = marker; 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; @@ -121,41 +110,60 @@ static int rtp_encoder_encode_h264_fu_a(RtpEncoder* rtp_encoder, uint8_t* buf, s } static uint8_t* h264_find_nalu(uint8_t* buf_start, uint8_t* buf_end) { - uint8_t* p = buf_start + 2; - - while (p < buf_end) { - if (*(p - 2) == 0x00 && *(p - 1) == 0x00 && *p == 0x01) - return p + 1; - p++; + uint8_t* result = buf_end; + + if (buf_start != NULL && buf_end >= buf_start && + buf_end - buf_start >= 3) { + uint8_t* p = buf_start + 2; + while (result == buf_end && p < buf_end) { + if (*(p - 2) == 0x00 && *(p - 1) == 0x00 && *p == 0x01) { + result = p + 1; + } + p++; + } } - return buf_end; + return result; } static int rtp_encoder_encode_h264(RtpEncoder* rtp_encoder, uint8_t* buf, size_t size) { uint8_t* buf_end = buf + size; uint8_t *pstart, *pend; size_t nalu_size; + int packets_encoded = 0; + int result = -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) + if (pend != buf_end) { nalu_size--; + } - while (pstart[nalu_size - 1] == 0x00) + while (nalu_size > 0 && pstart[nalu_size - 1] == 0x00) { nalu_size--; + } - if (nalu_size <= RTP_PAYLOAD_SIZE) { - rtp_encoder_encode_h264_single(rtp_encoder, pstart, nalu_size); - - } else { - rtp_encoder_encode_h264_fu_a(rtp_encoder, pstart, nalu_size); + if (nalu_size > 0) { + int marker = pend == buf_end; + if (nalu_size <= RTP_PAYLOAD_SIZE) { + rtp_encoder_encode_h264_single(rtp_encoder, pstart, nalu_size, + marker); + } else { + rtp_encoder_encode_h264_fu_a(rtp_encoder, pstart, nalu_size, + marker); + } + packets_encoded = 1; } } - return 0; + if (packets_encoded) { + rtp_encoder->timestamp += rtp_encoder->timestamp_increment; + result = 0; + } + + return result; } static int rtp_encoder_encode_generic(RtpEncoder* rtp_encoder, uint8_t* buf, size_t size) { @@ -210,7 +218,14 @@ void rtp_encoder_init(RtpEncoder* rtp_encoder, MediaCodec codec, RtpOnPacket on_ } int rtp_encoder_encode(RtpEncoder* rtp_encoder, const uint8_t* buf, size_t size) { - return rtp_encoder->encode_func(rtp_encoder, (uint8_t*)buf, size); + int result = -1; + + if (rtp_encoder != NULL && rtp_encoder->encode_func != NULL && + buf != NULL && size > 0) { + result = rtp_encoder->encode_func(rtp_encoder, (uint8_t*)buf, size); + } + + return result; } static const uint32_t nalu_start_4bytecode = 0x01000000; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 696a01da..fa84b3a2 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -11,7 +11,8 @@ foreach(sourcefile ${SRCS}) target_link_libraries(${appname} peer pthread) if(appname STREQUAL "test_ice_mdns" OR appname STREQUAL "test_port_range" OR - appname STREQUAL "test_h264_sdp") + appname STREQUAL "test_h264_sdp" OR + appname STREQUAL "test_h264_rtp") add_test(NAME ${appname} COMMAND ${appname}) endif() endforeach(sourcefile ${TEST_SRCS}) diff --git a/tests/test_h264_rtp.c b/tests/test_h264_rtp.c new file mode 100644 index 00000000..36a2fddb --- /dev/null +++ b/tests/test_h264_rtp.c @@ -0,0 +1,151 @@ +#include +#include +#include + +#include "address.h" +#include "rtp.h" + +#define MAX_CAPTURED_PACKETS 16 + +typedef struct PacketCapture { + int count; + uint16_t sequence[MAX_CAPTURED_PACKETS]; + uint32_t timestamp[MAX_CAPTURED_PACKETS]; + uint8_t marker[MAX_CAPTURED_PACKETS]; + uint8_t payload_type[MAX_CAPTURED_PACKETS]; + uint8_t payload_first[MAX_CAPTURED_PACKETS]; + uint8_t payload_second[MAX_CAPTURED_PACKETS]; +} PacketCapture; + +static int failures = 0; + +#define CHECK(condition) \ + do { \ + if (!(condition)) { \ + fprintf(stderr, "check failed at %s:%d: %s\n", __FILE__, __LINE__, \ + #condition); \ + failures++; \ + } \ + } while (0) + +static void capture_packet(uint8_t* packet, size_t bytes, void* user_data) { + PacketCapture* capture = (PacketCapture*)user_data; + + if (capture->count < MAX_CAPTURED_PACKETS && + bytes >= sizeof(RtpHeader)) { + int index = capture->count; + RtpPacket* rtp_packet = (RtpPacket*)packet; + size_t payload_size = bytes - sizeof(RtpHeader); + + capture->sequence[index] = ntohs(rtp_packet->header.seq_number); + capture->timestamp[index] = ntohl(rtp_packet->header.timestamp); + capture->marker[index] = rtp_packet->header.markerbit; + capture->payload_type[index] = rtp_packet->header.type; + capture->payload_first[index] = + payload_size > 0 ? rtp_packet->payload[0] : 0; + capture->payload_second[index] = + payload_size > 1 ? rtp_packet->payload[1] : 0; + capture->count++; + } +} + +static void test_access_unit_uses_one_timestamp_and_final_marker(void) { + static const uint8_t access_unit[] = { + 0x00, 0x00, 0x00, 0x01, 0x09, 0x10, + 0x00, 0x00, 0x00, 0x01, 0x41, 0x11, 0x22, + 0x00, 0x00, 0x00, 0x01, 0x41, 0x33, 0x44}; + PacketCapture capture; + RtpEncoder encoder; + + memset(&capture, 0, sizeof(capture)); + memset(&encoder, 0, sizeof(encoder)); + rtp_encoder_init(&encoder, CODEC_H264, capture_packet, &capture); + encoder.timestamp = 9000; + + CHECK(rtp_encoder_encode(&encoder, access_unit, sizeof(access_unit)) == 0); + CHECK(capture.count == 3); + CHECK(capture.timestamp[0] == 9000); + CHECK(capture.timestamp[1] == 9000); + CHECK(capture.timestamp[2] == 9000); + CHECK(capture.marker[0] == 0); + CHECK(capture.marker[1] == 0); + CHECK(capture.marker[2] == 1); + CHECK(capture.sequence[0] == 0); + CHECK(capture.sequence[1] == 1); + CHECK(capture.sequence[2] == 2); + CHECK(encoder.timestamp == 12000); +} + +static void test_final_fu_a_fragment_carries_marker(void) { + uint8_t access_unit[CONFIG_MTU + 800]; + PacketCapture capture; + RtpEncoder encoder; + size_t offset = 0; + size_t index; + + memset(&capture, 0, sizeof(capture)); + memset(&encoder, 0, sizeof(encoder)); + memset(access_unit, 0x55, sizeof(access_unit)); + + access_unit[offset++] = 0; + access_unit[offset++] = 0; + access_unit[offset++] = 0; + access_unit[offset++] = 1; + access_unit[offset++] = 0x09; + access_unit[offset++] = 0x10; + access_unit[offset++] = 0; + access_unit[offset++] = 0; + access_unit[offset++] = 0; + access_unit[offset++] = 1; + access_unit[offset++] = 0x65; + for (index = offset; index < sizeof(access_unit); index++) { + access_unit[index] = (uint8_t)index; + } + + rtp_encoder_init(&encoder, CODEC_H264, capture_packet, &capture); + encoder.timestamp = 123456; + + CHECK(rtp_encoder_encode(&encoder, access_unit, sizeof(access_unit)) == 0); + CHECK(capture.count == 3); + CHECK(capture.marker[0] == 0); + CHECK(capture.marker[1] == 0); + CHECK(capture.marker[2] == 1); + CHECK((capture.payload_first[1] & 0x1f) == 28); + CHECK((capture.payload_second[1] & 0x80) != 0); + CHECK((capture.payload_second[1] & 0x40) == 0); + CHECK((capture.payload_first[2] & 0x1f) == 28); + CHECK((capture.payload_second[2] & 0x80) == 0); + CHECK((capture.payload_second[2] & 0x40) != 0); + CHECK(capture.timestamp[0] == 123456); + CHECK(capture.timestamp[1] == 123456); + CHECK(capture.timestamp[2] == 123456); + CHECK(encoder.timestamp == 126456); +} + +static void test_invalid_access_unit_does_not_advance_timestamp(void) { + static const uint8_t invalid_access_unit[] = {0x41, 0x01, 0x02}; + PacketCapture capture; + RtpEncoder encoder; + + memset(&capture, 0, sizeof(capture)); + memset(&encoder, 0, sizeof(encoder)); + rtp_encoder_init(&encoder, CODEC_H264, capture_packet, &capture); + encoder.timestamp = 777; + + CHECK(rtp_encoder_encode(&encoder, NULL, 0) < 0); + CHECK(rtp_encoder_encode(&encoder, invalid_access_unit, + sizeof(invalid_access_unit)) < 0); + CHECK(capture.count == 0); + CHECK(encoder.timestamp == 777); +} + +int main(void) { + test_access_unit_uses_one_timestamp_and_final_marker(); + test_final_fu_a_fragment_carries_marker(); + test_invalid_access_unit_does_not_advance_timestamp(); + + if (failures != 0) { + fprintf(stderr, "%d test checks failed\n", failures); + } + return failures == 0 ? 0 : 1; +} From 87dd44968904de0f4d0b9725df69e0c69e358689 Mon Sep 17 00:00:00 2001 From: maikebing Date: Mon, 24 Aug 2026 11:26:25 +0800 Subject: [PATCH 5/5] fix(security): bound ICE credentials and mDNS host parsing --- src/address.c | 2 +- src/agent.c | 59 +++++++++++++++++++++++++++++++++---------- src/ice.c | 4 +-- tests/test_ice_mdns.c | 27 ++++++++++++++++++++ 4 files changed, 75 insertions(+), 17 deletions(-) diff --git a/src/address.c b/src/address.c index 1618ede5..2f1d9b1c 100644 --- a/src/address.c +++ b/src/address.c @@ -42,7 +42,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) { case AF_INET6: return inet_ntop(AF_INET6, &addr->sin6.sin6_addr, buf, len) != NULL; diff --git a/src/agent.c b/src/agent.c index bb01e6f8..b8f744d5 100644 --- a/src/agent.c +++ b/src/agent.c @@ -464,28 +464,59 @@ void agent_set_remote_description(Agent* agent, char* description) { a=candidate:1 1 UDP 1 36.231.28.50 38143 typ srflx */ int i; + const char* ufrag_prefix = "a=ice-ufrag:"; + const char* upwd_prefix = "a=ice-pwd:"; + size_t prefix_length; + size_t value_length; 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:", 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; - } + prefix_length = strlen(ufrag_prefix); + if (strncmp(line_start, ufrag_prefix, prefix_length) == 0) { + value_length = (size_t)(line_end - line_start); + if (value_length >= prefix_length) { + value_length -= prefix_length; + } else { + value_length = 0; + } + if (value_length >= sizeof(agent->remote_ufrag)) { + value_length = sizeof(agent->remote_ufrag) - 1; + } + memcpy(agent->remote_ufrag, line_start + prefix_length, value_length); + agent->remote_ufrag[value_length] = '\0'; + + } else { + prefix_length = strlen(upwd_prefix); + if (strncmp(line_start, upwd_prefix, prefix_length) == 0) { + value_length = (size_t)(line_end - line_start); + if (value_length >= prefix_length) { + value_length -= prefix_length; + } else { + value_length = 0; + } + if (value_length >= sizeof(agent->remote_upwd)) { + value_length = sizeof(agent->remote_upwd) - 1; } - if (i == agent->remote_candidates_count) { - agent->remote_candidates_count++; + memcpy(agent->remote_upwd, line_start + prefix_length, value_length); + agent->remote_upwd[value_length] = '\0'; + + } else if (strncmp(line_start, "a=candidate:", strlen("a=candidate:")) == 0 && + agent->remote_candidates_count < AGENT_MAX_CANDIDATES) { + 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++; + } } } } diff --git a/src/ice.c b/src/ice.c index f06a4785..cb8181cd 100644 --- a/src/ice.c +++ b/src/ice.c @@ -100,7 +100,7 @@ int ice_candidate_from_description(IceCandidate* candidate, char* description, char line[512]; char* candidate_start = line; char type[16]; - char addrstring[ADDRSTRLEN]; + char addrstring[256]; uint32_t port = 0; size_t line_length = 0; int parsed = 0; @@ -128,7 +128,7 @@ int ice_candidate_from_description(IceCandidate* candidate, char* description, if (strncmp(candidate_start, "candidate:", 10) == 0) { candidate_start += 10; parsed = sscanf(candidate_start, - "%32s %d %32s %" PRIu32 " %45s %" PRIu32 + "%32s %d %32s %" PRIu32 " %255s %" PRIu32 " typ %15s", candidate->foundation, &candidate->component, diff --git a/tests/test_ice_mdns.c b/tests/test_ice_mdns.c index f83dc697..84b04a92 100644 --- a/tests/test_ice_mdns.c +++ b/tests/test_ice_mdns.c @@ -178,11 +178,38 @@ static void test_authenticated_nomination_learns_peer_reflexive_address(void) { agent_destroy(&agent); } +static void test_remote_credentials_are_bounded(void) { + Agent agent; + char description[1024]; + char long_ufrag[400]; + char long_upwd[400]; + size_t index; + + for (index = 0; index + 1 < sizeof(long_ufrag); index++) { + long_ufrag[index] = 'u'; + long_upwd[index] = 'p'; + } + long_ufrag[sizeof(long_ufrag) - 1] = '\0'; + long_upwd[sizeof(long_upwd) - 1] = '\0'; + snprintf(description, sizeof(description), + "a=ice-ufrag:%s\r\na=ice-pwd:%s\r\n", long_ufrag, long_upwd); + + memset(&agent, 0, sizeof(agent)); + CHECK(agent_create(&agent, 0, 0) == 0); + agent_set_remote_description(&agent, description); + CHECK(strlen(agent.remote_ufrag) == ICE_UFRAG_LENGTH); + CHECK(strlen(agent.remote_upwd) == ICE_UPWD_LENGTH); + CHECK(agent.remote_ufrag[ICE_UFRAG_LENGTH] == '\0'); + CHECK(agent.remote_upwd[ICE_UPWD_LENGTH] == '\0'); + agent_destroy(&agent); +} + int main(void) { test_compressed_additional_mdns_answer(); test_malformed_compression_is_rejected(); test_unresolved_mdns_candidate_is_retained(); test_authenticated_nomination_learns_peer_reflexive_address(); + test_remote_credentials_are_bounded(); if (failures != 0) { fprintf(stderr, "%d test checks failed\n", failures);