From de907c697b0e8f54ed43bae6f320c2e8a32ce8ae Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 29 Oct 2026 10:35:04 +0100 Subject: [PATCH] Match temporary IPv6 flags using canonical address bytes --- .../utun/chat/data/InterfaceAddressFlags.kt | 13 +++++++++++ .../chat/data/InterfaceAddressFlagsTest.kt | 23 +++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 tools/chatgui-android/app/src/main/java/com/utun/chat/data/InterfaceAddressFlags.kt create mode 100644 tools/chatgui-android/app/src/test/java/com/utun/chat/data/InterfaceAddressFlagsTest.kt diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/InterfaceAddressFlags.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/InterfaceAddressFlags.kt new file mode 100644 index 00000000..2aba324f --- /dev/null +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/InterfaceAddressFlags.kt @@ -0,0 +1,13 @@ +package com.utun.chat.data + +/** /proc/net/if_inet6 использует 32 hex-цифры без сжатия нулей; IFA_F_TEMPORARY=0x20. + * Сопоставляется пара «интерфейс + 16 байтов адреса», прочие адреса/интерфейсы не меняют результат. */ +internal fun temporaryIpv6Address(ifName: String, address: ByteArray, rows: Sequence): Boolean { + require(address.size == 16) { "IPv6 address must contain 16 bytes" } + val hex = address.joinToString("") { (it.toInt() and 255).toString(16).padStart(2, '0') } + return rows.any { line -> + val fields = line.trim().split(Regex("\\s+"), limit = 6) + fields.size == 6 && fields[0].equals(hex, ignoreCase = true) && fields[5] == ifName && + fields[4].toIntOrNull(16)?.let { it and 0x20 != 0 } == true + } +} diff --git a/tools/chatgui-android/app/src/test/java/com/utun/chat/data/InterfaceAddressFlagsTest.kt b/tools/chatgui-android/app/src/test/java/com/utun/chat/data/InterfaceAddressFlagsTest.kt new file mode 100644 index 00000000..9979a4f4 --- /dev/null +++ b/tools/chatgui-android/app/src/test/java/com/utun/chat/data/InterfaceAddressFlagsTest.kt @@ -0,0 +1,23 @@ +package com.utun.chat.data + +import java.net.InetAddress +import org.junit.Assert.* +import org.junit.Test + +class InterfaceAddressFlagsTest { + @Test fun compressedIpv6MustMatchKernelBytesRatherThanPaddingConcatenatedGroups() { + val address = InetAddress.getByName("2001:db8::1234").address + assertTrue(temporaryIpv6Address("wlan0", address, sequenceOf("20010db8000000000000000000001234 02 40 00 20 wlan0"))) + } + @Test fun anotherInterfaceCannotMarkTheCurrentAddressTemporary() { + val address = InetAddress.getByName("2001:db8::1234").address + val rows = listOf("20010db8000000000000000000001234 02 40 00 20 mobile0", + "20010db8000000000000000000001234 03 40 00 80 wlan0") + assertFalse(temporaryIpv6Address("wlan0", address, rows.asSequence())) + } + @Test fun stableAddressDoesNotInheritFlagsFromAnotherAddressOnTheSameInterface() { + val rows = listOf("20010db8000000000000000000001234 02 40 00 20 wlan0", + "20010DB8000000000000000000005678 02 40 00 80 wlan0") + assertFalse(temporaryIpv6Address("wlan0", InetAddress.getByName("2001:db8::5678").address, rows.asSequence())) + } +}