Skip to content

Commit df89e93

Browse files
committed
Implement HostResolver using getaddrinfo
1 parent 9c6b231 commit df89e93

File tree

2 files changed

+145
-7
lines changed

2 files changed

+145
-7
lines changed
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
package aws.smithy.kotlin.runtime.net
6+
7+
import kotlinx.coroutines.test.runTest
8+
import kotlin.test.*
9+
10+
class HostResolverTest {
11+
@Test
12+
fun testResolveLocalhost() = runTest {
13+
val addresses = HostResolver.Default.resolve("localhost")
14+
assertTrue(addresses.isNotEmpty())
15+
16+
addresses.forEach { addr ->
17+
assertEquals("localhost", addr.hostname)
18+
when (val ip = addr.address) {
19+
is IpV4Addr -> {
20+
assertEquals(4, ip.octets.size)
21+
// localhost should resolve to 127.0.0.1
22+
assertContentEquals(byteArrayOf(127, 0, 0, 1), ip.octets)
23+
}
24+
is IpV6Addr -> {
25+
assertEquals(16, ip.octets.size)
26+
// ::1 in IPv6
27+
val expectedIpv6 = ByteArray(16) { 0 }
28+
expectedIpv6[15] = 1
29+
assertContentEquals(expectedIpv6, ip.octets)
30+
}
31+
}
32+
}
33+
}
34+
35+
@Test
36+
fun testResolveIpv4Address() = runTest {
37+
val addresses = HostResolver.Default.resolve("127.0.0.1")
38+
assertTrue(addresses.isNotEmpty())
39+
40+
addresses.forEach { addr ->
41+
assertTrue(addr.address is IpV4Addr)
42+
assertContentEquals(byteArrayOf(127, 0, 0, 1), addr.address.octets)
43+
}
44+
}
45+
46+
@Test
47+
fun testResolveIpv6Address() = runTest {
48+
val addresses = HostResolver.Default.resolve("::1")
49+
assertTrue(addresses.isNotEmpty())
50+
51+
addresses.forEach { addr ->
52+
assertTrue(addr.address is IpV6Addr)
53+
val expectedBytes = ByteArray(16) { 0 }
54+
expectedBytes[15] = 1
55+
assertContentEquals(expectedBytes, addr.address.octets)
56+
}
57+
}
58+
59+
@Test
60+
fun testResolveExampleDomain() = runTest {
61+
val addresses = HostResolver.Default.resolve("example.com")
62+
assertNotNull(addresses)
63+
assertTrue(addresses.isNotEmpty())
64+
65+
addresses.forEach { addr ->
66+
assertEquals("example.com", addr.hostname)
67+
when (val ip = addr.address) {
68+
is IpV4Addr -> assertEquals(4, ip.octets.size)
69+
is IpV6Addr -> assertEquals(16, ip.octets.size)
70+
}
71+
}
72+
}
73+
74+
@Test
75+
fun testResolveInvalidDomain() = runTest {
76+
assertFails {
77+
HostResolver.Default.resolve("this-domain-definitely-does-not-exist-12345.local")
78+
}
79+
}
80+
81+
@Test
82+
fun testNoopMethods() {
83+
// Test no-op methods don't throw
84+
val dummyAddr = HostAddress("test.com", IpV4Addr(ByteArray(4)))
85+
val resolver = HostResolver.Default
86+
resolver.reportFailure(dummyAddr)
87+
resolver.purgeCache(null)
88+
resolver.purgeCache(dummyAddr)
89+
}
90+
}

runtime/runtime-core/native/src/aws/smithy/kotlin/runtime/net/HostResolverNative.kt

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,67 @@
44
*/
55
package aws.smithy.kotlin.runtime.net
66

7-
import aws.smithy.kotlin.runtime.InternalApi
7+
import kotlinx.cinterop.*
8+
import platform.posix.*
89

910
internal actual object DefaultHostResolver : HostResolver {
10-
actual override suspend fun resolve(hostname: String): List<HostAddress> {
11-
TODO("Not yet implemented")
11+
actual override suspend fun resolve(hostname: String): List<HostAddress> = memScoped {
12+
val hints = alloc<addrinfo>().apply {
13+
ai_family = AF_UNSPEC // Allow both IPv4 and IPv6
14+
ai_socktype = SOCK_STREAM // TCP stream sockets
15+
ai_flags = AI_PASSIVE // For wildcard IP address
16+
}
17+
18+
val result = allocPointerTo<addrinfo>()
19+
20+
// Perform the DNS lookup
21+
val status = getaddrinfo(hostname, null, hints.ptr, result.ptr)
22+
if (status != 0) {
23+
throw RuntimeException("Failed to resolve host $hostname: ${gai_strerror(status)?.toKString()}")
24+
}
25+
26+
val addresses = mutableListOf<HostAddress>()
27+
var current = result.value
28+
29+
while (current != null) {
30+
val sockaddr = current.pointed.ai_addr!!.pointed
31+
32+
@OptIn(UnsafeNumber::class)
33+
when (sockaddr.sa_family.toInt()) {
34+
AF_INET -> {
35+
val addr = sockaddr.reinterpret<sockaddr_in>()
36+
val ipBytes = ByteArray(4)
37+
memcpy(ipBytes.refTo(0), addr.sin_addr.ptr, 4uL)
38+
39+
addresses.add(HostAddress(
40+
hostname = hostname,
41+
address = IpV4Addr(ipBytes)
42+
))
43+
}
44+
AF_INET6 -> {
45+
val addr = sockaddr.reinterpret<sockaddr_in6>()
46+
val ipBytes = ByteArray(16)
47+
memcpy(ipBytes.refTo(0), addr.sin6_addr.ptr, 16.convert())
48+
addresses.add(HostAddress(
49+
hostname = hostname,
50+
address = IpV6Addr(ipBytes)
51+
))
52+
}
53+
}
54+
current = current.pointed.ai_next
55+
}
56+
57+
// Free the getaddrinfo results
58+
freeaddrinfo(result.value)
59+
60+
addresses
1261
}
1362

1463
actual override fun reportFailure(addr: HostAddress) {
15-
TODO("Not yet implemented")
64+
// No-op, same as JVM implementation
1665
}
1766

18-
@InternalApi
1967
actual override fun purgeCache(addr: HostAddress?) {
20-
TODO("Not yet implemented")
68+
// No-op, same as JVM implementation
2169
}
22-
}
70+
}

0 commit comments

Comments
 (0)