Skip to content

Commit a6a4539

Browse files
authored
Add VirtualSocket resolver (#1815)
Motivation: Users should be able to connect to Virtual Sockets. To do these they need a VSOCK target and resolver. Modifications: - Add a VSOCK target and resolver - Update the registry to include it by default Result: Can resolve VSOCK targets
1 parent 97b09fa commit a6a4539

File tree

3 files changed

+95
-4
lines changed

3 files changed

+95
-4
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright 2024, gRPC Authors All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import GRPCCore
18+
19+
extension ResolvableTargets {
20+
/// A resolvable target for Virtual Socket addresses.
21+
///
22+
/// ``VirtualSocket`` addresses can be resolved by the ``NameResolvers/VirtualSocket``
23+
/// resolver which creates a single ``Endpoint`` for target address.
24+
public struct VirtualSocket: ResolvableTarget {
25+
public var address: SocketAddress.VirtualSocket
26+
27+
public init(address: SocketAddress.VirtualSocket) {
28+
self.address = address
29+
}
30+
}
31+
}
32+
33+
extension ResolvableTarget where Self == ResolvableTargets.VirtualSocket {
34+
/// Creates a new resolvable Virtual Socket target.
35+
/// - Parameters:
36+
/// - contextID: The context ID ('cid') of the service.
37+
/// - port: The port to connect to.
38+
public static func vsock(
39+
contextID: SocketAddress.VirtualSocket.ContextID,
40+
port: SocketAddress.VirtualSocket.Port
41+
) -> Self {
42+
let address = SocketAddress.VirtualSocket(contextID: contextID, port: port)
43+
return ResolvableTargets.VirtualSocket(address: address)
44+
}
45+
}
46+
47+
@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
48+
extension NameResolvers {
49+
/// A ``NameResolverFactory`` for ``ResolvableTargets/VirtualSocket`` targets.
50+
///
51+
/// The name resolver for a given target always produces the same values, with a single endpoint.
52+
/// This resolver doesn't support fetching service configuration.
53+
public struct VirtualSocket: NameResolverFactory {
54+
public typealias Target = ResolvableTargets.VirtualSocket
55+
56+
public init() {}
57+
58+
public func resolver(for target: Target) -> NameResolver {
59+
let endpoint = Endpoint(addresses: [.vsock(target.address)])
60+
let resolutionResult = NameResolutionResult(endpoints: [endpoint], serviceConfiguration: nil)
61+
return NameResolver(names: .constant(resolutionResult), updateMode: .pull)
62+
}
63+
}
64+
}

Sources/GRPCHTTP2Core/Client/Resolver/NameResolverRegistry.swift

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@
2828
/// // type `CustomResolver.ResolvableTarget`.
2929
/// registry.registerFactory(CustomResolver())
3030
///
31-
/// // Remove the Unix Domain Socket and VSOCK resolvers, if they exist.
31+
/// // Remove the Unix Domain Socket and Virtual Socket resolvers, if they exist.
3232
/// registry.removeFactory(ofType: NameResolvers.UnixDomainSocket.self)
33-
/// registry.removeFactory(ofType: NameResolvers.VSOCK.self)
33+
/// registry.removeFactory(ofType: NameResolvers.VirtualSocket.self)
3434
///
3535
/// // Resolve an IPv4 target
3636
/// if let resolver = registry.makeResolver(for: .ipv4(host: "localhost", port: 80)) {
@@ -43,6 +43,7 @@ public struct NameResolverRegistry {
4343
case ipv4(NameResolvers.IPv4)
4444
case ipv6(NameResolvers.IPv6)
4545
case unix(NameResolvers.UnixDomainSocket)
46+
case vsock(NameResolvers.VirtualSocket)
4647
case other(any NameResolverFactory)
4748

4849
init(_ factory: some NameResolverFactory) {
@@ -52,6 +53,8 @@ public struct NameResolverRegistry {
5253
self = .ipv6(ipv6)
5354
} else if let unix = factory as? NameResolvers.UnixDomainSocket {
5455
self = .unix(unix)
56+
} else if let vsock = factory as? NameResolvers.VirtualSocket {
57+
self = .vsock(vsock)
5558
} else {
5659
self = .other(factory)
5760
}
@@ -65,6 +68,8 @@ public struct NameResolverRegistry {
6568
return factory.makeResolverIfCompatible(target)
6669
case .unix(let factory):
6770
return factory.makeResolverIfCompatible(target)
71+
case .vsock(let factory):
72+
return factory.makeResolverIfCompatible(target)
6873
case .other(let factory):
6974
return factory.makeResolverIfCompatible(target)
7075
}
@@ -78,6 +83,8 @@ public struct NameResolverRegistry {
7883
return factory.isCompatible(withTarget: target)
7984
case .unix(let factory):
8085
return factory.isCompatible(withTarget: target)
86+
case .vsock(let factory):
87+
return factory.isCompatible(withTarget: target)
8188
case .other(let factory):
8289
return factory.isCompatible(withTarget: target)
8390
}
@@ -91,6 +98,8 @@ public struct NameResolverRegistry {
9198
return NameResolvers.IPv6.self == factoryType
9299
case .unix:
93100
return NameResolvers.UnixDomainSocket.self == factoryType
101+
case .vsock:
102+
return NameResolvers.VirtualSocket.self == factoryType
94103
case .other(let factory):
95104
return type(of: factory) == factoryType
96105
}
@@ -109,12 +118,14 @@ public struct NameResolverRegistry {
109118
/// The default resolvers include:
110119
/// - ``NameResolvers/IPv4``,
111120
/// - ``NameResolvers/IPv6``,
112-
/// - ``NameResolvers/UnixDomainSocket``.
121+
/// - ``NameResolvers/UnixDomainSocket``,
122+
/// - ``NameResolvers/VirtualSocket``.
113123
public static var defaults: Self {
114124
var resolvers = NameResolverRegistry()
115125
resolvers.registerFactory(NameResolvers.IPv4())
116126
resolvers.registerFactory(NameResolvers.IPv6())
117127
resolvers.registerFactory(NameResolvers.UnixDomainSocket())
128+
resolvers.registerFactory(NameResolvers.VirtualSocket())
118129
return resolvers
119130
}
120131

Tests/GRPCHTTP2CoreTests/Client/Resolver/NameResolverRegistryTests.swift

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,8 @@ final class NameResolverRegistryTests: XCTestCase {
135135
XCTAssert(resolvers.containsFactory(ofType: NameResolvers.IPv4.self))
136136
XCTAssert(resolvers.containsFactory(ofType: NameResolvers.IPv6.self))
137137
XCTAssert(resolvers.containsFactory(ofType: NameResolvers.UnixDomainSocket.self))
138-
XCTAssertEqual(resolvers.count, 3)
138+
XCTAssert(resolvers.containsFactory(ofType: NameResolvers.VirtualSocket.self))
139+
XCTAssertEqual(resolvers.count, 4)
139140
}
140141

141142
func testMakeResolver() {
@@ -252,4 +253,19 @@ final class NameResolverRegistryTests: XCTestCase {
252253
XCTAssertNil(result.serviceConfiguration)
253254
}
254255
}
256+
257+
func testVSOCKResolver() async throws {
258+
let factory = NameResolvers.VirtualSocket()
259+
let resolver = factory.resolver(for: .vsock(contextID: .any, port: .any))
260+
261+
XCTAssertEqual(resolver.updateMode, .pull)
262+
263+
// The VSOCK resolver always returns the same values.
264+
var iterator = resolver.names.makeAsyncIterator()
265+
for _ in 0 ..< 1000 {
266+
let result = try await XCTUnwrapAsync { try await iterator.next() }
267+
XCTAssertEqual(result.endpoints, [Endpoint(addresses: [.vsock(contextID: .any, port: .any)])])
268+
XCTAssertNil(result.serviceConfiguration)
269+
}
270+
}
255271
}

0 commit comments

Comments
 (0)