Skip to content

KTOR-8490 Add InetSocketAddress::resolveAddress #4857

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 25 commits into from
May 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
51008f7
KTOR-8490: Add SocketAddress::address
OmniacDev May 14, 2025
88f2c57
Merge branch 'ktorio:main' into KTOR-8490
OmniacDev May 14, 2025
d55eea2
add method doc
OmniacDev May 14, 2025
bf73a08
Merge remote-tracking branch 'origin/KTOR-8490' into KTOR-8490
OmniacDev May 14, 2025
b702275
Merge branch 'main' into KTOR-8490
OmniacDev May 16, 2025
8bb2a60
api
OmniacDev May 16, 2025
628f9d9
Merge remote-tracking branch 'origin/KTOR-8490' into KTOR-8490
OmniacDev May 16, 2025
61a98c9
codestyle
OmniacDev May 16, 2025
2140e0f
Merge branch 'main' into KTOR-8490
OmniacDev May 20, 2025
da0ae28
Merge branch 'main' into KTOR-8490
OmniacDev May 27, 2025
ef24f44
use native address resolution
OmniacDev May 28, 2025
289cc5d
Merge branch 'main' into KTOR-8490
OmniacDev May 28, 2025
bac820e
update api
OmniacDev May 28, 2025
902eaef
extract parsing into internal functions
OmniacDev May 29, 2025
63875ea
skip intermediate list creation
OmniacDev May 29, 2025
6878da8
improve parsing impl
OmniacDev May 29, 2025
2159e77
ip string parse tests
OmniacDev May 29, 2025
879c600
ip string parse tests
OmniacDev May 29, 2025
db8521b
ip string parse tests
OmniacDev May 29, 2025
186724b
only parse IPv6 group if it's a hex string
OmniacDev May 29, 2025
33d95aa
additional IPv6 test edge cases
OmniacDev May 29, 2025
0c220f2
Merge branch 'main' into KTOR-8490
OmniacDev May 29, 2025
15b18d6
code style
OmniacDev May 29, 2025
d460eb6
Merge remote-tracking branch 'origin/KTOR-8490' into KTOR-8490
OmniacDev May 29, 2025
ea11009
fix IPv6 parsing, use toUShort instead of toInt
OmniacDev May 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ktor-network/api/ktor-network.api
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ public final class io/ktor/network/sockets/InetSocketAddress : io/ktor/network/s
public final fun getHostname ()Ljava/lang/String;
public final fun getPort ()I
public fun hashCode ()I
public final fun resolveAddress ()[B
public fun toString ()Ljava/lang/String;
}

Expand Down
1 change: 1 addition & 0 deletions ktor-network/api/ktor-network.klib.api
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ final class io.ktor.network.sockets/InetSocketAddress : io.ktor.network.sockets/
final fun copy(kotlin/String = ..., kotlin/Int = ...): io.ktor.network.sockets/InetSocketAddress // io.ktor.network.sockets/InetSocketAddress.copy|copy(kotlin.String;kotlin.Int){}[0]
final fun equals(kotlin/Any?): kotlin/Boolean // io.ktor.network.sockets/InetSocketAddress.equals|equals(kotlin.Any?){}[0]
final fun hashCode(): kotlin/Int // io.ktor.network.sockets/InetSocketAddress.hashCode|hashCode(){}[0]
final fun resolveAddress(): kotlin/ByteArray? // io.ktor.network.sockets/InetSocketAddress.resolveAddress|resolveAddress(){}[0]
final fun toString(): kotlin/String // io.ktor.network.sockets/InetSocketAddress.toString|toString(){}[0]
}

Expand Down
12 changes: 12 additions & 0 deletions ktor-network/common/src/io/ktor/network/sockets/SocketAddress.kt
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,18 @@ public expect class InetSocketAddress(
*/
public val port: Int

/**
* Returns the raw IP address bytes of this socket address.
*
* The returned array is 4 bytes for IPv4 addresses and 16 bytes for IPv6 addresses.
* Returns null if the address cannot be resolved or is not a valid IP address.
*
* Always returns null for wasm/js targets.
*
* [Report a problem](https://ktor.io/feedback/?fqname=io.ktor.network.sockets.InetSocketAddress.address)
*/
public fun resolveAddress(): ByteArray?

/**
* The hostname of the socket address.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,7 @@
package io.ktor.network.sockets

internal actual fun isUnixSocketSupported(): Boolean = false

internal actual fun InetSocketAddress.platformResolveAddress(): ByteArray? {
return null
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ public actual class InetSocketAddress internal constructor(

public actual val port: Int get() = address.port

public actual fun resolveAddress(): ByteArray? = address.address?.address

public actual constructor(hostname: String, port: Int) :
this(java.net.InetSocketAddress(hostname, port))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ public actual class InetSocketAddress actual constructor(
public actual val hostname: String,
public actual val port: Int
) : SocketAddress() {
public actual fun resolveAddress(): ByteArray? {
return platformResolveAddress()
}

/**
* Create a copy of [InetSocketAddress].
*
Expand Down Expand Up @@ -100,3 +104,5 @@ public actual class UnixSocketAddress actual constructor(
}

internal expect fun isUnixSocketSupported(): Boolean

internal expect fun InetSocketAddress.platformResolveAddress(): ByteArray?
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright 2014-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package io.ktor.network.sockets

import io.ktor.network.util.NativeIPv4SocketAddress
import io.ktor.network.util.NativeIPv6SocketAddress
import io.ktor.network.util.parseIPv4String
import io.ktor.network.util.parseIPv6String
import io.ktor.network.util.resolve

internal actual fun InetSocketAddress.platformResolveAddress(): ByteArray? {
return this.resolve().firstOrNull()?.let {
when (it) {
is NativeIPv4SocketAddress -> parseIPv4String(it.ipString)
is NativeIPv6SocketAddress -> parseIPv6String(it.ipString)
else -> null
}
}
}
45 changes: 45 additions & 0 deletions ktor-network/posix/src/io/ktor/network/util/SocketAddressUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,48 @@ internal fun SocketAddress.resolve(): List<NativeSocketAddress> = when (this) {
is InetSocketAddress -> getAddressInfo(hostname, port)
is UnixSocketAddress -> listOf(NativeUnixSocketAddress(AF_UNIX.convert(), path))
}

internal fun parseIPv4String(ipString: String): ByteArray? {
return try {
val octets = ipString.split('.').also {
require(it.size == 4) { "Invalid IPv4 string: $ipString" }
}

octets.map { it.toUByte().toByte() }.toByteArray()
} catch (_: Throwable) {
null
}
}

internal fun parseIPv6String(ipString: String): ByteArray? {
return try {
val groups = if ("::" in ipString) {
val parts = ipString.split("::", limit = 2)

val groups = Pair(
if (parts[0].isNotEmpty()) parts[0].split(':') else emptyList(),
if (parts.size > 1 && parts[1].isNotEmpty()) parts[1].split(':') else emptyList()
)

val totalGroups = groups.first.size + groups.second.size
val emptyGroups = 8 - totalGroups

groups.first + List(emptyGroups) { "0" } + groups.second
} else {
ipString.split(':').also {
require(it.size == 8) { "Invalid IPv6 string: $ipString" }
}
}

groups.flatMap {
val int = if (it.matches(Regex("^[0-9a-fA-f]+$"))) {
it.toUShort(16)
} else {
return null
}.toInt()
listOf((int shr 8).toByte(), int.toByte())
}.toByteArray()
} catch (_: Throwable) {
null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* Copyright 2014-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package io.ktor.network.sockets.tests

import io.ktor.network.util.*
import kotlin.test.Test
import kotlin.test.assertContentEquals
import kotlin.test.assertNull

class IPStringParseTest {
@Test
fun testParseIPv4String() {
val valid = listOf(
"0.0.0.0" to byteArrayOf(0, 0, 0, 0),
"127.0.0.1" to byteArrayOf(127, 0, 0, 1),
"255.255.255.255" to byteArrayOf(255.toByte(), 255.toByte(), 255.toByte(), 255.toByte()),
)

val invalid = listOf(
"-1.0.0.0",
"not an IPv4 string",
"256.0.0.0",
"300",
"::"
)

valid.forEach {
val parsed = parseIPv4String(it.first)
assertContentEquals(it.second, parsed)
}

invalid.forEach {
val parsed = parseIPv4String(it)
assertNull(parsed, "Original: $it, Parsed: ${parsed.contentToString()}")
}
}

@Test
fun testParseIPv6String() {
val valid = listOf(
"::" to ByteArray(16) { 0 },
"2001:0db8:85a3::8a2e:0370:7334" to byteArrayOf(
0x20, 0x01, 0x0d, 0xb8.toByte(), 0x85.toByte(), 0xa3.toByte(), 0x00, 0x00,
0x00, 0x00, 0x8a.toByte(), 0x2e, 0x03, 0x70, 0x73, 0x34
),
"fe80::1" to byteArrayOf(
0xfe.toByte(), 0x80.toByte(), 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01
),
"::1" to byteArrayOf(
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01
),
"2001:db8::ff00:42:8329" to byteArrayOf(
0x20, 0x01, 0x0d, 0xb8.toByte(), 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xff.toByte(), 0x00, 0x00, 0x42, 0x83.toByte(), 0x29
)
)

val invalid = listOf(
":",
"0:0:1",
"not an IPv6 string",
"255.255.255.255",
"-1::0",
":::",
"",
"12345::1",
"g::1",
)

valid.forEach {
val parsed = parseIPv6String(it.first)
assertContentEquals(it.second, parsed)
}

invalid.forEach {
val parsed = parseIPv6String(it)
assertNull(parsed, "Original: $it, Parsed: ${parsed.contentToString()}")
}
}
}