From e641569a7c590a6802e604a170727ecc01fbb4b3 Mon Sep 17 00:00:00 2001 From: Dmitry Verenitsin Date: Tue, 28 Apr 2026 22:29:11 +0500 Subject: [PATCH] [core] Use switch_stun_ipv6_t for STUN IPv6 write paths. Route IPv6 writes in `switch_stun_packet_attribute_add_binded_address` and `switch_stun_packet_attribute_add_xor_binded_address` through `switch_stun_ipv6_t` (16-byte `address[]`) instead of `switch_stun_ip_t` (4-byte `uint32_t address`). Add IPv4/IPv6 unit tests for both encoders. --- src/switch_stun.c | 52 +++------ tests/unit/Makefile.am | 1 + tests/unit/conf_stun/freeswitch.xml | 73 ++++++++++++ tests/unit/switch_stun.c | 172 ++++++++++++++++++++++++++++ 4 files changed, 265 insertions(+), 33 deletions(-) create mode 100644 tests/unit/conf_stun/freeswitch.xml create mode 100644 tests/unit/switch_stun.c diff --git a/src/switch_stun.c b/src/switch_stun.c index 35c9daed91e..1c0027e1a1f 100644 --- a/src/switch_stun.c +++ b/src/switch_stun.c @@ -485,31 +485,24 @@ SWITCH_DECLARE(switch_stun_packet_t *) switch_stun_packet_build_header(switch_st SWITCH_DECLARE(uint8_t) switch_stun_packet_attribute_add_binded_address(switch_stun_packet_t *packet, char *ipstr, uint16_t port, int family) { switch_stun_packet_attribute_t *attribute; - switch_stun_ip_t *ip; attribute = (switch_stun_packet_attribute_t *) ((uint8_t *) & packet->first_attribute + ntohs(packet->header.length)); attribute->type = htons(SWITCH_STUN_ATTR_XOR_MAPPED_ADDRESS); if (family == AF_INET6) { + switch_stun_ipv6_t *ipv6 = (switch_stun_ipv6_t *) attribute->value; + attribute->length = htons(20); + ipv6->family = 2; + ipv6->port = htons(port ^ (STUN_MAGIC_COOKIE >> 16)); + inet_pton(AF_INET6, ipstr, ipv6->address); } else { - attribute->length = htons(8); - } - - ip = (switch_stun_ip_t *) attribute->value; - - ip->port = htons(port ^ (STUN_MAGIC_COOKIE >> 16)); + switch_stun_ip_t *ip = (switch_stun_ip_t *) attribute->value; - if (family == AF_INET6) { - ip->family = 2; - } else { + attribute->length = htons(8); ip->family = 1; - } - - if (family == AF_INET6) { - inet_pton(AF_INET6, ipstr, (struct in6_addr *) &ip->address); - } else { - inet_pton(AF_INET, ipstr, (int *) &ip->address); + ip->port = htons(port ^ (STUN_MAGIC_COOKIE >> 16)); + inet_pton(AF_INET, ipstr, &ip->address); } packet->header.length += htons(sizeof(switch_stun_packet_attribute_t)) + attribute->length; @@ -519,32 +512,25 @@ SWITCH_DECLARE(uint8_t) switch_stun_packet_attribute_add_binded_address(switch_s SWITCH_DECLARE(uint8_t) switch_stun_packet_attribute_add_xor_binded_address(switch_stun_packet_t *packet, char *ipstr, uint16_t port, int family) { switch_stun_packet_attribute_t *attribute; - switch_stun_ip_t *ip; attribute = (switch_stun_packet_attribute_t *) ((uint8_t *) & packet->first_attribute + ntohs(packet->header.length)); attribute->type = htons(SWITCH_STUN_ATTR_XOR_MAPPED_ADDRESS); if (family == AF_INET6) { + switch_stun_ipv6_t *ipv6 = (switch_stun_ipv6_t *) attribute->value; + attribute->length = htons(20); + ipv6->family = 2; + ipv6->port = htons(port ^ (STUN_MAGIC_COOKIE >> 16)); + inet_pton(AF_INET6, ipstr, ipv6->address); + v6_xor(ipv6->address, (uint8_t *)packet->header.id); } else { - attribute->length = htons(8); - } - - ip = (switch_stun_ip_t *) attribute->value; - - ip->port = htons(port ^ (STUN_MAGIC_COOKIE >> 16)); + switch_stun_ip_t *ip = (switch_stun_ip_t *) attribute->value; - if (family == AF_INET6) { - ip->family = 2; - } else { + attribute->length = htons(8); ip->family = 1; - } - - if (family == AF_INET6) { - inet_pton(AF_INET6, ipstr, (struct in6_addr *) &ip->address); - v6_xor((uint8_t *)&ip->address, (uint8_t *)packet->header.id); - } else { - inet_pton(AF_INET, ipstr, (int *) &ip->address); + ip->port = htons(port ^ (STUN_MAGIC_COOKIE >> 16)); + inet_pton(AF_INET, ipstr, &ip->address); ip->address = htonl(ntohl(ip->address) ^ STUN_MAGIC_COOKIE); } diff --git a/tests/unit/Makefile.am b/tests/unit/Makefile.am index 719152d6dfd..2f83bca68f6 100644 --- a/tests/unit/Makefile.am +++ b/tests/unit/Makefile.am @@ -3,6 +3,7 @@ include $(top_srcdir)/build/modmake.rulesam noinst_PROGRAMS = switch_event switch_hash switch_ivr_originate switch_utils switch_core switch_console switch_vpx switch_core_file \ switch_ivr_play_say switch_core_codec switch_rtp switch_xml noinst_PROGRAMS += switch_core_video switch_core_db switch_vad switch_packetizer switch_core_session test_sofia switch_ivr_async switch_core_asr switch_log +noinst_PROGRAMS += switch_stun noinst_PROGRAMS += test_tts_format noinst_PROGRAMS+= switch_hold switch_sip diff --git a/tests/unit/conf_stun/freeswitch.xml b/tests/unit/conf_stun/freeswitch.xml new file mode 100644 index 00000000000..c9ad71b4df7 --- /dev/null +++ b/tests/unit/conf_stun/freeswitch.xml @@ -0,0 +1,73 @@ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + + +
+
diff --git a/tests/unit/switch_stun.c b/tests/unit/switch_stun.c new file mode 100644 index 00000000000..675333c029b --- /dev/null +++ b/tests/unit/switch_stun.c @@ -0,0 +1,172 @@ +/* +* FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application +* Copyright (C) 2005-2026, Anthony Minessale II +* +* Version: MPL 1.1 +* +* The contents of this file are subject to the Mozilla Public License Version +* 1.1 (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* http://www.mozilla.org/MPL/ +* +* Software distributed under the License is distributed on an "AS IS" basis, +* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +* for the specific language governing rights and limitations under the +* License. +* +* The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application +* +* The Initial Developer of the Original Code is +* Anthony Minessale II +* Portions created by the Initial Developer are Copyright (C) +* the Initial Developer. All Rights Reserved. +* +* Contributor(s): +* Dmitry Verenitsin +* +* switch_stun.c -- tests STUN (https://www.rfc-editor.org/rfc/rfc5389). +*/ + + +#include +#include +#include + +FST_CORE_BEGIN("./conf_stun") +{ +FST_SUITE_BEGIN(switch_stun) +{ +FST_SETUP_BEGIN() +{ +} +FST_SETUP_END() + +FST_TEARDOWN_BEGIN() +{ +} +FST_TEARDOWN_END() + + FST_TEST_BEGIN(test_stun_add_binded_address_ipv6) + { + /* + * Encode an IPv6 XOR-MAPPED-ADDRESS attribute and verify the + * attribute type, length, address family, and the raw 16-byte + * address payload at its expected offset inside the value. + */ + uint8_t buf[512]; + switch_stun_packet_t *packet; + switch_stun_packet_attribute_t *attr; + const char *ipv6_str = "2001:db8::dead:beef"; + uint8_t expected[16]; + uint8_t *value_bytes; + + memset(buf, 0, sizeof(buf)); + packet = switch_stun_packet_build_header(SWITCH_STUN_BINDING_RESPONSE, NULL, buf); + fst_xcheck(inet_pton(AF_INET6, ipv6_str, expected) == 1, "test IPv6 literal parses"); + + switch_stun_packet_attribute_add_binded_address(packet, (char *)ipv6_str, 12345, AF_INET6); + + attr = (switch_stun_packet_attribute_t *)packet->first_attribute; + fst_xcheck(ntohs(attr->type) == SWITCH_STUN_ATTR_XOR_MAPPED_ADDRESS, "attribute type is XOR_MAPPED_ADDRESS"); + fst_xcheck(ntohs(attr->length) == 20, "attribute length is 20 for IPv6"); + + /* Attribute value layout: wasted(1) + family(1) + port(2) + address(16). */ + value_bytes = (uint8_t *)attr->value; + fst_xcheck(value_bytes[1] == 2, "attribute family byte is 2 for IPv6"); + fst_xcheck(memcmp(value_bytes + 4, expected, 16) == 0, "16-byte IPv6 address written at offset 4 of attribute value"); + } + FST_TEST_END() + + FST_TEST_BEGIN(test_stun_add_xor_binded_address_ipv6) + { + /* + * Encode then decode an IPv6 XOR-MAPPED-ADDRESS attribute and + * confirm the round-trip recovers the original IPv6 string — + * the write path must XOR the address with the transaction ID + * symmetrically to the read path. + */ + uint8_t buf[512]; + switch_stun_packet_t *packet; + switch_stun_packet_attribute_t *attr; + const char *ipv6_str = "2001:db8::dead:beef"; + char out_ip[64] = { 0 }; + uint16_t out_port = 0; + + memset(buf, 0, sizeof(buf)); + packet = switch_stun_packet_build_header(SWITCH_STUN_BINDING_RESPONSE, NULL, buf); + + switch_stun_packet_attribute_add_xor_binded_address(packet, (char *)ipv6_str, 12345, AF_INET6); + + attr = (switch_stun_packet_attribute_t *)packet->first_attribute; + fst_xcheck(ntohs(attr->type) == SWITCH_STUN_ATTR_XOR_MAPPED_ADDRESS, "attribute type is XOR_MAPPED_ADDRESS"); + fst_xcheck(ntohs(attr->length) == 20, "attribute length is 20 for IPv6"); + + switch_stun_packet_attribute_get_xor_mapped_address(attr, &packet->header, out_ip, sizeof(out_ip), &out_port); + fst_check_string_equals(out_ip, ipv6_str); + } + FST_TEST_END() + + FST_TEST_BEGIN(test_stun_add_binded_address_ipv4) + { + /* + * Encode an IPv4 XOR-MAPPED-ADDRESS attribute and verify the + * attribute type, length, address family, and the raw 4-byte + * address payload at its expected offset inside the value. + */ + uint8_t buf[512]; + switch_stun_packet_t *packet; + switch_stun_packet_attribute_t *attr; + const char *ipv4_str = "192.0.2.42"; + uint8_t expected[4]; + uint8_t *value_bytes; + + memset(buf, 0, sizeof(buf)); + packet = switch_stun_packet_build_header(SWITCH_STUN_BINDING_RESPONSE, NULL, buf); + fst_xcheck(inet_pton(AF_INET, ipv4_str, expected) == 1, "test IPv4 literal parses"); + + switch_stun_packet_attribute_add_binded_address(packet, (char *)ipv4_str, 12345, AF_INET); + + attr = (switch_stun_packet_attribute_t *)packet->first_attribute; + fst_xcheck(ntohs(attr->type) == SWITCH_STUN_ATTR_XOR_MAPPED_ADDRESS, "attribute type is XOR_MAPPED_ADDRESS"); + fst_xcheck(ntohs(attr->length) == 8, "attribute length is 8 for IPv4"); + + /* Attribute value layout: wasted(1) + family(1) + port(2) + address(4). */ + value_bytes = (uint8_t *)attr->value; + fst_xcheck(value_bytes[1] == 1, "attribute family byte is 1 for IPv4"); + fst_xcheck(memcmp(value_bytes + 4, expected, 4) == 0, "4-byte IPv4 address written at offset 4 of attribute value"); + } + FST_TEST_END() + + FST_TEST_BEGIN(test_stun_add_xor_binded_address_ipv4) + { + /* + * Encode then decode an IPv4 XOR-MAPPED-ADDRESS attribute and + * confirm the round-trip recovers the original IPv4 string — + * the write path must XOR the address with the magic cookie + * symmetrically to the read path. + */ + uint8_t buf[512]; + switch_stun_packet_t *packet; + switch_stun_packet_attribute_t *attr; + const char *ipv4_str = "192.0.2.42"; + char out_ip[64] = { 0 }; + uint16_t out_port = 0; + + memset(buf, 0, sizeof(buf)); + packet = switch_stun_packet_build_header(SWITCH_STUN_BINDING_RESPONSE, NULL, buf); + + switch_stun_packet_attribute_add_xor_binded_address(packet, (char *)ipv4_str, 12345, AF_INET); + + attr = (switch_stun_packet_attribute_t *)packet->first_attribute; + fst_xcheck(ntohs(attr->type) == SWITCH_STUN_ATTR_XOR_MAPPED_ADDRESS, "attribute type is XOR_MAPPED_ADDRESS"); + fst_xcheck(ntohs(attr->length) == 8, "attribute length is 8 for IPv4"); + + switch_stun_packet_attribute_get_xor_mapped_address(attr, &packet->header, out_ip, sizeof(out_ip), &out_port); + fst_check_string_equals(out_ip, ipv4_str); + } + FST_TEST_END() +} +FST_SUITE_END() +} +FST_CORE_END() +