@braedonsaunders/appkit-desk 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -0
- package/backend.d.ts +28 -7
- package/backend.d.ts.map +1 -1
- package/backend.js +18 -521
- package/backend.js.map +1 -1
- package/host.d.ts +4 -1
- package/host.d.ts.map +1 -1
- package/host.js +31 -16
- package/host.js.map +1 -1
- package/index.d.ts +2 -0
- package/index.d.ts.map +1 -1
- package/index.js +2 -0
- package/index.js.map +1 -1
- package/machine.d.ts +145 -0
- package/machine.d.ts.map +1 -0
- package/machine.js +518 -0
- package/machine.js.map +1 -0
- package/package.json +2 -2
- package/plan.d.ts +129 -31
- package/plan.d.ts.map +1 -1
- package/plan.js +165 -9
- package/plan.js.map +1 -1
- package/port-relay.d.ts +13 -0
- package/port-relay.d.ts.map +1 -0
- package/port-relay.js +56 -0
- package/port-relay.js.map +1 -0
- package/virtualization.d.ts +16 -0
- package/virtualization.d.ts.map +1 -0
- package/virtualization.js +148 -0
- package/virtualization.js.map +1 -0
- package/vz/Package.swift +23 -0
- package/vz/Sources/DeskNet/Addresses.swift +106 -0
- package/vz/Sources/DeskNet/DHCP.swift +111 -0
- package/vz/Sources/DeskNet/Fence.swift +57 -0
- package/vz/Sources/DeskNet/NetworkStack.swift +547 -0
- package/vz/Sources/DeskNet/Packets.swift +355 -0
- package/vz/Sources/DeskNet/Plumbing.swift +312 -0
- package/vz/Sources/DeskNet/Sockets.swift +221 -0
- package/vz/Sources/DeskNet/TCPFlow.swift +448 -0
- package/vz/Sources/appkit-desk-vz/Control.swift +152 -0
- package/vz/Sources/appkit-desk-vz/Helper.swift +505 -0
- package/vz/Sources/appkit-desk-vz/main.swift +38 -0
- package/vz/Tests/DeskNetTests/DeskNetTests.swift +426 -0
- package/vz/appkit-desk-vz.entitlements +8 -0
- package/vz/build.sh +23 -0
- package/vz/check/extract-boot.py +221 -0
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
/// Encoding and decoding of the frames that cross the virtual link: Ethernet,
|
|
2
|
+
/// ARP, IPv4, TCP, UDP and ICMP. Everything here is pure and works on byte
|
|
3
|
+
/// arrays, so it is tested without a guest.
|
|
4
|
+
|
|
5
|
+
public enum EtherType {
|
|
6
|
+
public static let ipv4: UInt16 = 0x0800
|
|
7
|
+
public static let arp: UInt16 = 0x0806
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
public enum IPProtocol {
|
|
11
|
+
public static let icmp: UInt8 = 1
|
|
12
|
+
public static let tcp: UInt8 = 6
|
|
13
|
+
public static let udp: UInt8 = 17
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@inline(__always) func be16(_ bytes: [UInt8], _ offset: Int) -> UInt16 {
|
|
17
|
+
UInt16(bytes[offset]) << 8 | UInt16(bytes[offset + 1])
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@inline(__always) func be32(_ bytes: [UInt8], _ offset: Int) -> UInt32 {
|
|
21
|
+
UInt32(bytes[offset]) << 24 | UInt32(bytes[offset + 1]) << 16 | UInt32(bytes[offset + 2]) << 8
|
|
22
|
+
| UInt32(bytes[offset + 3])
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@inline(__always) func put16(_ bytes: inout [UInt8], _ offset: Int, _ value: UInt16) {
|
|
26
|
+
bytes[offset] = UInt8(value >> 8)
|
|
27
|
+
bytes[offset + 1] = UInt8(value & 0xff)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@inline(__always) func put32(_ bytes: inout [UInt8], _ offset: Int, _ value: UInt32) {
|
|
31
|
+
bytes[offset] = UInt8(value >> 24)
|
|
32
|
+
bytes[offset + 1] = UInt8(value >> 16 & 0xff)
|
|
33
|
+
bytes[offset + 2] = UInt8(value >> 8 & 0xff)
|
|
34
|
+
bytes[offset + 3] = UInt8(value & 0xff)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/// The Internet checksum (RFC 1071) over `bytes`, folded and complemented.
|
|
38
|
+
public func internetChecksum(_ bytes: [UInt8], initial: UInt32 = 0) -> UInt16 {
|
|
39
|
+
var sum = initial
|
|
40
|
+
var index = 0
|
|
41
|
+
let count = bytes.count
|
|
42
|
+
while index + 1 < count {
|
|
43
|
+
sum &+= UInt32(bytes[index]) << 8 | UInt32(bytes[index + 1])
|
|
44
|
+
index += 2
|
|
45
|
+
}
|
|
46
|
+
if index < count { sum &+= UInt32(bytes[index]) << 8 }
|
|
47
|
+
while sum >> 16 != 0 { sum = (sum & 0xffff) &+ (sum >> 16) }
|
|
48
|
+
return ~UInt16(sum & 0xffff)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
func pseudoHeaderSum(source: IPv4, destination: IPv4, protocol proto: UInt8, length: Int) -> UInt32 {
|
|
52
|
+
var sum: UInt32 = 0
|
|
53
|
+
sum &+= source.value >> 16
|
|
54
|
+
sum &+= source.value & 0xffff
|
|
55
|
+
sum &+= destination.value >> 16
|
|
56
|
+
sum &+= destination.value & 0xffff
|
|
57
|
+
sum &+= UInt32(proto)
|
|
58
|
+
sum &+= UInt32(length)
|
|
59
|
+
return sum
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// MARK: - Ethernet
|
|
63
|
+
|
|
64
|
+
public struct EthernetFrame: Sendable {
|
|
65
|
+
public var destination: MAC
|
|
66
|
+
public var source: MAC
|
|
67
|
+
public var type: UInt16
|
|
68
|
+
public var payload: [UInt8]
|
|
69
|
+
|
|
70
|
+
public init(destination: MAC, source: MAC, type: UInt16, payload: [UInt8]) {
|
|
71
|
+
self.destination = destination
|
|
72
|
+
self.source = source
|
|
73
|
+
self.type = type
|
|
74
|
+
self.payload = payload
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
public init?(parsing bytes: [UInt8]) {
|
|
78
|
+
guard bytes.count >= 14 else { return nil }
|
|
79
|
+
destination = MAC(Array(bytes[0..<6]))
|
|
80
|
+
source = MAC(Array(bytes[6..<12]))
|
|
81
|
+
type = be16(bytes, 12)
|
|
82
|
+
payload = Array(bytes[14...])
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
public var encoded: [UInt8] {
|
|
86
|
+
var bytes = destination.bytes + source.bytes + [0, 0]
|
|
87
|
+
put16(&bytes, 12, type)
|
|
88
|
+
return bytes + payload
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// MARK: - ARP
|
|
93
|
+
|
|
94
|
+
public struct ARPPacket: Sendable, Equatable {
|
|
95
|
+
public static let request: UInt16 = 1
|
|
96
|
+
public static let reply: UInt16 = 2
|
|
97
|
+
|
|
98
|
+
public var operation: UInt16
|
|
99
|
+
public var senderMAC: MAC
|
|
100
|
+
public var senderIP: IPv4
|
|
101
|
+
public var targetMAC: MAC
|
|
102
|
+
public var targetIP: IPv4
|
|
103
|
+
|
|
104
|
+
public init(operation: UInt16, senderMAC: MAC, senderIP: IPv4, targetMAC: MAC, targetIP: IPv4) {
|
|
105
|
+
self.operation = operation
|
|
106
|
+
self.senderMAC = senderMAC
|
|
107
|
+
self.senderIP = senderIP
|
|
108
|
+
self.targetMAC = targetMAC
|
|
109
|
+
self.targetIP = targetIP
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
public init?(parsing bytes: [UInt8]) {
|
|
113
|
+
// Ethernet hardware (1), IPv4 protocol, 6-byte MACs, 4-byte addresses.
|
|
114
|
+
guard bytes.count >= 28, be16(bytes, 0) == 1, be16(bytes, 2) == EtherType.ipv4,
|
|
115
|
+
bytes[4] == 6, bytes[5] == 4
|
|
116
|
+
else { return nil }
|
|
117
|
+
operation = be16(bytes, 6)
|
|
118
|
+
senderMAC = MAC(Array(bytes[8..<14]))
|
|
119
|
+
senderIP = IPv4(bytes: bytes, at: 14)
|
|
120
|
+
targetMAC = MAC(Array(bytes[18..<24]))
|
|
121
|
+
targetIP = IPv4(bytes: bytes, at: 24)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
public var encoded: [UInt8] {
|
|
125
|
+
var bytes: [UInt8] = [0, 1, 0x08, 0x00, 6, 4, 0, 0]
|
|
126
|
+
put16(&bytes, 6, operation)
|
|
127
|
+
return bytes + senderMAC.bytes + senderIP.bytes + targetMAC.bytes + targetIP.bytes
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// MARK: - IPv4
|
|
132
|
+
|
|
133
|
+
public struct IPv4Packet: Sendable {
|
|
134
|
+
public var source: IPv4
|
|
135
|
+
public var destination: IPv4
|
|
136
|
+
public var proto: UInt8
|
|
137
|
+
public var ttl: UInt8
|
|
138
|
+
public var identification: UInt16
|
|
139
|
+
/// True when this is any piece of a fragmented datagram.
|
|
140
|
+
public var isFragment: Bool
|
|
141
|
+
/// The header exactly as received, for quoting in ICMP errors.
|
|
142
|
+
public var header: [UInt8]
|
|
143
|
+
public var payload: [UInt8]
|
|
144
|
+
|
|
145
|
+
public init?(parsing bytes: [UInt8]) {
|
|
146
|
+
guard bytes.count >= 20, bytes[0] >> 4 == 4 else { return nil }
|
|
147
|
+
let headerLength = Int(bytes[0] & 0x0f) * 4
|
|
148
|
+
let totalLength = Int(be16(bytes, 2))
|
|
149
|
+
guard headerLength >= 20, totalLength >= headerLength, bytes.count >= totalLength else {
|
|
150
|
+
return nil
|
|
151
|
+
}
|
|
152
|
+
let flagsAndOffset = be16(bytes, 6)
|
|
153
|
+
isFragment = flagsAndOffset & 0x2000 != 0 || flagsAndOffset & 0x1fff != 0
|
|
154
|
+
identification = be16(bytes, 4)
|
|
155
|
+
ttl = bytes[8]
|
|
156
|
+
proto = bytes[9]
|
|
157
|
+
source = IPv4(bytes: bytes, at: 12)
|
|
158
|
+
destination = IPv4(bytes: bytes, at: 16)
|
|
159
|
+
header = Array(bytes[0..<headerLength])
|
|
160
|
+
payload = Array(bytes[headerLength..<totalLength])
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/// Build a datagram with a correct header checksum and Don't Fragment set.
|
|
164
|
+
public static func encode(
|
|
165
|
+
source: IPv4, destination: IPv4, proto: UInt8, payload: [UInt8], identification: UInt16 = 0,
|
|
166
|
+
ttl: UInt8 = 64
|
|
167
|
+
) -> [UInt8] {
|
|
168
|
+
var header = [UInt8](repeating: 0, count: 20)
|
|
169
|
+
header[0] = 0x45
|
|
170
|
+
put16(&header, 2, UInt16(20 + payload.count))
|
|
171
|
+
put16(&header, 4, identification)
|
|
172
|
+
put16(&header, 6, 0x4000)
|
|
173
|
+
header[8] = ttl
|
|
174
|
+
header[9] = proto
|
|
175
|
+
put32(&header, 12, source.value)
|
|
176
|
+
put32(&header, 16, destination.value)
|
|
177
|
+
put16(&header, 10, internetChecksum(header))
|
|
178
|
+
return header + payload
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// MARK: - TCP
|
|
183
|
+
|
|
184
|
+
public struct TCPFlags: OptionSet, Sendable, Hashable {
|
|
185
|
+
public let rawValue: UInt8
|
|
186
|
+
public init(rawValue: UInt8) { self.rawValue = rawValue }
|
|
187
|
+
public static let fin = TCPFlags(rawValue: 0x01)
|
|
188
|
+
public static let syn = TCPFlags(rawValue: 0x02)
|
|
189
|
+
public static let rst = TCPFlags(rawValue: 0x04)
|
|
190
|
+
public static let psh = TCPFlags(rawValue: 0x08)
|
|
191
|
+
public static let ack = TCPFlags(rawValue: 0x10)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
public struct TCPSegment: Sendable {
|
|
195
|
+
public var sourcePort: UInt16
|
|
196
|
+
public var destinationPort: UInt16
|
|
197
|
+
public var sequence: UInt32
|
|
198
|
+
public var acknowledgement: UInt32
|
|
199
|
+
public var flags: TCPFlags
|
|
200
|
+
public var window: UInt16
|
|
201
|
+
/// The maximum segment size option, when the segment carries one.
|
|
202
|
+
public var mss: UInt16?
|
|
203
|
+
public var payload: [UInt8]
|
|
204
|
+
|
|
205
|
+
public init(
|
|
206
|
+
sourcePort: UInt16, destinationPort: UInt16, sequence: UInt32, acknowledgement: UInt32,
|
|
207
|
+
flags: TCPFlags, window: UInt16, mss: UInt16? = nil, payload: [UInt8] = []
|
|
208
|
+
) {
|
|
209
|
+
self.sourcePort = sourcePort
|
|
210
|
+
self.destinationPort = destinationPort
|
|
211
|
+
self.sequence = sequence
|
|
212
|
+
self.acknowledgement = acknowledgement
|
|
213
|
+
self.flags = flags
|
|
214
|
+
self.window = window
|
|
215
|
+
self.mss = mss
|
|
216
|
+
self.payload = payload
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
public init?(parsing bytes: [UInt8]) {
|
|
220
|
+
guard bytes.count >= 20 else { return nil }
|
|
221
|
+
let dataOffset = Int(bytes[12] >> 4) * 4
|
|
222
|
+
guard dataOffset >= 20, bytes.count >= dataOffset else { return nil }
|
|
223
|
+
sourcePort = be16(bytes, 0)
|
|
224
|
+
destinationPort = be16(bytes, 2)
|
|
225
|
+
sequence = be32(bytes, 4)
|
|
226
|
+
acknowledgement = be32(bytes, 8)
|
|
227
|
+
flags = TCPFlags(rawValue: bytes[13] & 0x1f)
|
|
228
|
+
window = be16(bytes, 14)
|
|
229
|
+
mss = nil
|
|
230
|
+
var index = 20
|
|
231
|
+
while index < dataOffset {
|
|
232
|
+
let kind = bytes[index]
|
|
233
|
+
if kind == 0 { break }
|
|
234
|
+
if kind == 1 {
|
|
235
|
+
index += 1
|
|
236
|
+
continue
|
|
237
|
+
}
|
|
238
|
+
guard index + 1 < dataOffset else { break }
|
|
239
|
+
let length = Int(bytes[index + 1])
|
|
240
|
+
guard length >= 2, index + length <= dataOffset else { break }
|
|
241
|
+
if kind == 2, length == 4 { mss = be16(bytes, index + 2) }
|
|
242
|
+
index += length
|
|
243
|
+
}
|
|
244
|
+
payload = Array(bytes[dataOffset...])
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/// The sequence space this segment occupies: its payload plus SYN and FIN.
|
|
248
|
+
public var sequenceLength: UInt32 {
|
|
249
|
+
UInt32(payload.count) + (flags.contains(.syn) ? 1 : 0) + (flags.contains(.fin) ? 1 : 0)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
public func encoded(source: IPv4, destination: IPv4) -> [UInt8] {
|
|
253
|
+
let optionBytes: [UInt8] = mss.map { [2, 4, UInt8($0 >> 8), UInt8($0 & 0xff)] } ?? []
|
|
254
|
+
let headerLength = 20 + optionBytes.count
|
|
255
|
+
var bytes = [UInt8](repeating: 0, count: 20)
|
|
256
|
+
put16(&bytes, 0, sourcePort)
|
|
257
|
+
put16(&bytes, 2, destinationPort)
|
|
258
|
+
put32(&bytes, 4, sequence)
|
|
259
|
+
put32(&bytes, 8, acknowledgement)
|
|
260
|
+
bytes[12] = UInt8(headerLength / 4) << 4
|
|
261
|
+
bytes[13] = flags.rawValue
|
|
262
|
+
put16(&bytes, 14, window)
|
|
263
|
+
bytes += optionBytes
|
|
264
|
+
bytes += payload
|
|
265
|
+
let sum = pseudoHeaderSum(
|
|
266
|
+
source: source, destination: destination, protocol: IPProtocol.tcp, length: bytes.count)
|
|
267
|
+
put16(&bytes, 16, internetChecksum(bytes, initial: sum))
|
|
268
|
+
return bytes
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// MARK: - UDP
|
|
273
|
+
|
|
274
|
+
public struct UDPDatagram: Sendable {
|
|
275
|
+
public var sourcePort: UInt16
|
|
276
|
+
public var destinationPort: UInt16
|
|
277
|
+
public var payload: [UInt8]
|
|
278
|
+
|
|
279
|
+
public init(sourcePort: UInt16, destinationPort: UInt16, payload: [UInt8]) {
|
|
280
|
+
self.sourcePort = sourcePort
|
|
281
|
+
self.destinationPort = destinationPort
|
|
282
|
+
self.payload = payload
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
public init?(parsing bytes: [UInt8]) {
|
|
286
|
+
guard bytes.count >= 8 else { return nil }
|
|
287
|
+
let length = Int(be16(bytes, 4))
|
|
288
|
+
guard length >= 8, length <= bytes.count else { return nil }
|
|
289
|
+
sourcePort = be16(bytes, 0)
|
|
290
|
+
destinationPort = be16(bytes, 2)
|
|
291
|
+
payload = Array(bytes[8..<length])
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
public func encoded(source: IPv4, destination: IPv4) -> [UInt8] {
|
|
295
|
+
var bytes = [UInt8](repeating: 0, count: 8)
|
|
296
|
+
put16(&bytes, 0, sourcePort)
|
|
297
|
+
put16(&bytes, 2, destinationPort)
|
|
298
|
+
put16(&bytes, 4, UInt16(8 + payload.count))
|
|
299
|
+
bytes += payload
|
|
300
|
+
let sum = pseudoHeaderSum(
|
|
301
|
+
source: source, destination: destination, protocol: IPProtocol.udp, length: bytes.count)
|
|
302
|
+
var checksum = internetChecksum(bytes, initial: sum)
|
|
303
|
+
// A computed zero is sent as all ones; zero means "no checksum" in UDP.
|
|
304
|
+
if checksum == 0 { checksum = 0xffff }
|
|
305
|
+
put16(&bytes, 6, checksum)
|
|
306
|
+
return bytes
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// MARK: - ICMP
|
|
311
|
+
|
|
312
|
+
public struct ICMPMessage: Sendable {
|
|
313
|
+
public static let echoReply: UInt8 = 0
|
|
314
|
+
public static let destinationUnreachable: UInt8 = 3
|
|
315
|
+
public static let echoRequest: UInt8 = 8
|
|
316
|
+
|
|
317
|
+
/// Destination-unreachable codes the stack sends.
|
|
318
|
+
public static let hostUnreachable: UInt8 = 1
|
|
319
|
+
public static let portUnreachable: UInt8 = 3
|
|
320
|
+
public static let administrativelyProhibited: UInt8 = 13
|
|
321
|
+
|
|
322
|
+
public var type: UInt8
|
|
323
|
+
public var code: UInt8
|
|
324
|
+
/// The four bytes after the checksum: identifier and sequence for echo.
|
|
325
|
+
public var rest: UInt32
|
|
326
|
+
public var payload: [UInt8]
|
|
327
|
+
|
|
328
|
+
public init(type: UInt8, code: UInt8, rest: UInt32, payload: [UInt8]) {
|
|
329
|
+
self.type = type
|
|
330
|
+
self.code = code
|
|
331
|
+
self.rest = rest
|
|
332
|
+
self.payload = payload
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
public init?(parsing bytes: [UInt8]) {
|
|
336
|
+
guard bytes.count >= 8 else { return nil }
|
|
337
|
+
type = bytes[0]
|
|
338
|
+
code = bytes[1]
|
|
339
|
+
rest = be32(bytes, 4)
|
|
340
|
+
payload = Array(bytes[8...])
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
public var identifier: UInt16 { UInt16(rest >> 16) }
|
|
344
|
+
public var sequenceNumber: UInt16 { UInt16(rest & 0xffff) }
|
|
345
|
+
|
|
346
|
+
public var encoded: [UInt8] {
|
|
347
|
+
var bytes = [UInt8](repeating: 0, count: 8)
|
|
348
|
+
bytes[0] = type
|
|
349
|
+
bytes[1] = code
|
|
350
|
+
put32(&bytes, 4, rest)
|
|
351
|
+
bytes += payload
|
|
352
|
+
put16(&bytes, 2, internetChecksum(bytes))
|
|
353
|
+
return bytes
|
|
354
|
+
}
|
|
355
|
+
}
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import Darwin
|
|
2
|
+
import Dispatch
|
|
3
|
+
|
|
4
|
+
/// The helper's end of the datagram socket pair behind the guest NIC: one
|
|
5
|
+
/// datagram is one Ethernet frame.
|
|
6
|
+
@MainActor
|
|
7
|
+
public final class DatagramLink {
|
|
8
|
+
public var onFrame: (([UInt8]) -> Void)?
|
|
9
|
+
private let socket: FDWatcher
|
|
10
|
+
|
|
11
|
+
/// The largest frame the link carries, with headroom for a guest that
|
|
12
|
+
/// offloads segmentation.
|
|
13
|
+
static let maximumFrame = 65_550
|
|
14
|
+
|
|
15
|
+
public init(fd: Int32) {
|
|
16
|
+
Socket.setNonBlocking(fd)
|
|
17
|
+
socket = FDWatcher(fd: fd)
|
|
18
|
+
socket.onReadable = { [weak self] in self?.readable() }
|
|
19
|
+
socket.wantRead(true)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/// Make a connected pair sized for bursts. Apple recommends the receiving
|
|
23
|
+
/// buffer be at least twice the sending one. Returns (helper end, VM end).
|
|
24
|
+
public static func makePair() throws -> (Int32, Int32) {
|
|
25
|
+
var fds: [Int32] = [0, 0]
|
|
26
|
+
guard socketpair(AF_UNIX, SOCK_DGRAM, 0, &fds) == 0 else { throw POSIXFailure("socketpair") }
|
|
27
|
+
for fd in fds {
|
|
28
|
+
Socket.setInt(fd, SOL_SOCKET, SO_SNDBUF, 1 << 20)
|
|
29
|
+
Socket.setInt(fd, SOL_SOCKET, SO_RCVBUF, 4 << 20)
|
|
30
|
+
}
|
|
31
|
+
return (fds[0], fds[1])
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/// Send one frame. A full buffer drops it, as a wire would; TCP recovers.
|
|
35
|
+
public func send(_ frame: [UInt8]) {
|
|
36
|
+
guard !socket.isClosed else { return }
|
|
37
|
+
_ = frame.withUnsafeBytes { Darwin.send(socket.fd, $0.baseAddress, frame.count, 0) }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private func readable() {
|
|
41
|
+
var buffer = [UInt8](repeating: 0, count: Self.maximumFrame)
|
|
42
|
+
while true {
|
|
43
|
+
let count = buffer.withUnsafeMutableBytes { Darwin.recv(socket.fd, $0.baseAddress, $0.count, 0) }
|
|
44
|
+
if count <= 0 { return }
|
|
45
|
+
onFrame?(Array(buffer[0..<count]))
|
|
46
|
+
if socket.isClosed { return }
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public func close() { socket.close() }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/// A Unix-socket listener speaking the Cloud Hypervisor hybrid-vsock preamble.
|
|
54
|
+
///
|
|
55
|
+
/// A client connects and writes `CONNECT <port>\n`. The listener hands the
|
|
56
|
+
/// connection and the port to `onRequest`, which either completes it — writes
|
|
57
|
+
/// `OK <number>\n` and carries the stream — or closes it with nothing written,
|
|
58
|
+
/// which is how a refused port presents. Anything else on the first line, or
|
|
59
|
+
/// no line within the deadline, closes the connection.
|
|
60
|
+
@MainActor
|
|
61
|
+
public final class HandshakeListener {
|
|
62
|
+
public typealias Request = (_ client: Int32, _ port: UInt32, _ early: [UInt8]) -> Void
|
|
63
|
+
|
|
64
|
+
public let path: String
|
|
65
|
+
private let listener: FDWatcher
|
|
66
|
+
private let onRequest: Request
|
|
67
|
+
private var pending: [ObjectIdentifier: PendingHandshake] = [:]
|
|
68
|
+
|
|
69
|
+
static let lineLimit = 128
|
|
70
|
+
static let deadlineSeconds = 10
|
|
71
|
+
|
|
72
|
+
public init(path: String, onRequest: @escaping Request) throws {
|
|
73
|
+
self.path = path
|
|
74
|
+
self.onRequest = onRequest
|
|
75
|
+
var address = sockaddr_un()
|
|
76
|
+
address.sun_family = sa_family_t(AF_UNIX)
|
|
77
|
+
let capacity = MemoryLayout.size(ofValue: address.sun_path)
|
|
78
|
+
let pathBytes = Array(path.utf8)
|
|
79
|
+
guard pathBytes.count < capacity else {
|
|
80
|
+
throw POSIXFailure("socket path \(path) is longer than \(capacity - 1) bytes", ENAMETOOLONG)
|
|
81
|
+
}
|
|
82
|
+
withUnsafeMutableBytes(of: &address.sun_path) { raw in
|
|
83
|
+
raw.copyBytes(from: pathBytes)
|
|
84
|
+
raw[pathBytes.count] = 0
|
|
85
|
+
}
|
|
86
|
+
address.sun_len = UInt8(MemoryLayout<sockaddr_un>.size)
|
|
87
|
+
let fd = Darwin.socket(AF_UNIX, SOCK_STREAM, 0)
|
|
88
|
+
guard fd >= 0 else { throw POSIXFailure("socket") }
|
|
89
|
+
unlink(path)
|
|
90
|
+
let bound = withUnsafePointer(to: &address) {
|
|
91
|
+
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
|
|
92
|
+
Darwin.bind(fd, $0, socklen_t(MemoryLayout<sockaddr_un>.size))
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
guard bound == 0 else {
|
|
96
|
+
let failure = POSIXFailure("bind \(path)")
|
|
97
|
+
Darwin.close(fd)
|
|
98
|
+
throw failure
|
|
99
|
+
}
|
|
100
|
+
// Only this user may drive the guest through the socket.
|
|
101
|
+
chmod(path, 0o600)
|
|
102
|
+
guard Darwin.listen(fd, 64) == 0 else {
|
|
103
|
+
let failure = POSIXFailure("listen \(path)")
|
|
104
|
+
Darwin.close(fd)
|
|
105
|
+
throw failure
|
|
106
|
+
}
|
|
107
|
+
Socket.setNonBlocking(fd)
|
|
108
|
+
listener = FDWatcher(fd: fd)
|
|
109
|
+
listener.onReadable = { [weak self] in self?.accept() }
|
|
110
|
+
listener.wantRead(true)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private func accept() {
|
|
114
|
+
while true {
|
|
115
|
+
let client = Darwin.accept(listener.fd, nil, nil)
|
|
116
|
+
if client < 0 { return }
|
|
117
|
+
Socket.setNonBlocking(client)
|
|
118
|
+
Socket.setNoSigPipe(client)
|
|
119
|
+
let handshake = PendingHandshake(fd: client)
|
|
120
|
+
let id = ObjectIdentifier(handshake)
|
|
121
|
+
pending[id] = handshake
|
|
122
|
+
handshake.onComplete = { [weak self] port, early in
|
|
123
|
+
guard let self else { return }
|
|
124
|
+
self.pending.removeValue(forKey: id)
|
|
125
|
+
if let port, let early {
|
|
126
|
+
// Hand over a duplicate; the watcher closes the original.
|
|
127
|
+
let owned = dup(client)
|
|
128
|
+
handshake.finish()
|
|
129
|
+
if owned >= 0 { self.onRequest(owned, port, early) }
|
|
130
|
+
} else {
|
|
131
|
+
handshake.finish()
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
handshake.start()
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
public func close() {
|
|
139
|
+
listener.close()
|
|
140
|
+
for handshake in pending.values { handshake.finish() }
|
|
141
|
+
pending.removeAll()
|
|
142
|
+
unlink(path)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
@MainActor
|
|
147
|
+
final class PendingHandshake {
|
|
148
|
+
var onComplete: ((UInt32?, [UInt8]?) -> Void)?
|
|
149
|
+
private let socket: FDWatcher
|
|
150
|
+
private var buffer: [UInt8] = []
|
|
151
|
+
private var timer: DispatchSourceTimer?
|
|
152
|
+
|
|
153
|
+
init(fd: Int32) {
|
|
154
|
+
socket = FDWatcher(fd: fd)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
func start() {
|
|
158
|
+
socket.onReadable = { [weak self] in self?.readable() }
|
|
159
|
+
socket.wantRead(true)
|
|
160
|
+
let timer = DispatchSource.makeTimerSource(queue: .main)
|
|
161
|
+
timer.schedule(deadline: .now() + .seconds(HandshakeListener.deadlineSeconds))
|
|
162
|
+
timer.setEventHandler { @Sendable [weak self] in Task { @MainActor in self?.complete(nil, nil) } }
|
|
163
|
+
timer.resume()
|
|
164
|
+
self.timer = timer
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private func readable() {
|
|
168
|
+
let bytes: [UInt8]?
|
|
169
|
+
do {
|
|
170
|
+
bytes = try Socket.readAvailable(socket.fd, limit: HandshakeListener.lineLimit)
|
|
171
|
+
} catch {
|
|
172
|
+
return complete(nil, nil)
|
|
173
|
+
}
|
|
174
|
+
guard let bytes else { return }
|
|
175
|
+
if bytes.isEmpty { return complete(nil, nil) }
|
|
176
|
+
buffer += bytes
|
|
177
|
+
guard let newline = buffer.firstIndex(of: 0x0a) else {
|
|
178
|
+
if buffer.count > HandshakeListener.lineLimit { complete(nil, nil) }
|
|
179
|
+
return
|
|
180
|
+
}
|
|
181
|
+
let line = String(decoding: buffer[0..<newline], as: UTF8.self)
|
|
182
|
+
let early = Array(buffer[(newline + 1)...])
|
|
183
|
+
complete(Self.parse(line), early)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/// `CONNECT <port>`, exactly. A trailing carriage return is tolerated.
|
|
187
|
+
nonisolated static func parse(_ line: String) -> UInt32? {
|
|
188
|
+
var text = Substring(line)
|
|
189
|
+
if text.hasSuffix("\r") { text = text.dropLast() }
|
|
190
|
+
let parts = text.split(separator: " ", omittingEmptySubsequences: false)
|
|
191
|
+
guard parts.count == 2, parts[0] == "CONNECT", !parts[1].isEmpty,
|
|
192
|
+
parts[1].allSatisfy({ $0.isASCII && $0.isNumber })
|
|
193
|
+
else { return nil }
|
|
194
|
+
return UInt32(parts[1])
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
private func complete(_ port: UInt32?, _ early: [UInt8]?) {
|
|
198
|
+
guard let onComplete else { return }
|
|
199
|
+
self.onComplete = nil
|
|
200
|
+
socket.wantRead(false)
|
|
201
|
+
onComplete(port, port == nil ? nil : early)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
func finish() {
|
|
205
|
+
timer?.cancel()
|
|
206
|
+
timer = nil
|
|
207
|
+
onComplete = nil
|
|
208
|
+
socket.close()
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/// Two descriptors joined into one stream, with bounded buffering and
|
|
213
|
+
/// half-close carried across in both directions.
|
|
214
|
+
@MainActor
|
|
215
|
+
public final class Relay {
|
|
216
|
+
private let left: FDWatcher
|
|
217
|
+
private let right: FDWatcher
|
|
218
|
+
private var leftToRight: [UInt8]
|
|
219
|
+
private var rightToLeft: [UInt8] = []
|
|
220
|
+
private var leftEOF = false
|
|
221
|
+
private var rightEOF = false
|
|
222
|
+
private var leftShut = false
|
|
223
|
+
private var rightShut = false
|
|
224
|
+
private var closed = false
|
|
225
|
+
private let onClose: () -> Void
|
|
226
|
+
|
|
227
|
+
static let capacity = 256 * 1024
|
|
228
|
+
|
|
229
|
+
/// `early` is data already read from `left` that must reach `right` first.
|
|
230
|
+
public init(left: Int32, right: Int32, early: [UInt8] = [], onClose: @escaping () -> Void = {}) {
|
|
231
|
+
for fd in [left, right] {
|
|
232
|
+
Socket.setNonBlocking(fd)
|
|
233
|
+
Socket.setNoSigPipe(fd)
|
|
234
|
+
}
|
|
235
|
+
self.left = FDWatcher(fd: left)
|
|
236
|
+
self.right = FDWatcher(fd: right)
|
|
237
|
+
leftToRight = early
|
|
238
|
+
self.onClose = onClose
|
|
239
|
+
self.left.onReadable = { [weak self] in self?.read(fromLeft: true) }
|
|
240
|
+
self.right.onReadable = { [weak self] in self?.read(fromLeft: false) }
|
|
241
|
+
self.left.onWritable = { [weak self] in self?.flush(toRight: false) }
|
|
242
|
+
self.right.onWritable = { [weak self] in self?.flush(toRight: true) }
|
|
243
|
+
flush(toRight: true)
|
|
244
|
+
updateInterest()
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private func read(fromLeft: Bool) {
|
|
248
|
+
let source = fromLeft ? left : right
|
|
249
|
+
let buffered = fromLeft ? leftToRight.count : rightToLeft.count
|
|
250
|
+
let room = Relay.capacity - buffered
|
|
251
|
+
guard room > 0 else { return updateInterest() }
|
|
252
|
+
let bytes: [UInt8]?
|
|
253
|
+
do {
|
|
254
|
+
bytes = try Socket.readAvailable(source.fd, limit: min(room, 64 * 1024))
|
|
255
|
+
} catch {
|
|
256
|
+
return close()
|
|
257
|
+
}
|
|
258
|
+
guard let bytes else { return }
|
|
259
|
+
if bytes.isEmpty {
|
|
260
|
+
if fromLeft { leftEOF = true } else { rightEOF = true }
|
|
261
|
+
} else if fromLeft {
|
|
262
|
+
leftToRight += bytes
|
|
263
|
+
} else {
|
|
264
|
+
rightToLeft += bytes
|
|
265
|
+
}
|
|
266
|
+
flush(toRight: fromLeft)
|
|
267
|
+
updateInterest()
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
private func flush(toRight: Bool) {
|
|
271
|
+
guard !closed else { return }
|
|
272
|
+
let target = toRight ? right : left
|
|
273
|
+
var buffer = toRight ? leftToRight : rightToLeft
|
|
274
|
+
while !buffer.isEmpty {
|
|
275
|
+
let written: Int
|
|
276
|
+
do {
|
|
277
|
+
written = try Socket.writeSome(target.fd, buffer)
|
|
278
|
+
} catch {
|
|
279
|
+
return close()
|
|
280
|
+
}
|
|
281
|
+
if written == 0 { break }
|
|
282
|
+
buffer.removeFirst(written)
|
|
283
|
+
}
|
|
284
|
+
if toRight { leftToRight = buffer } else { rightToLeft = buffer }
|
|
285
|
+
target.wantWrite(!buffer.isEmpty)
|
|
286
|
+
if buffer.isEmpty {
|
|
287
|
+
if toRight && leftEOF && !rightShut {
|
|
288
|
+
rightShut = true
|
|
289
|
+
_ = Darwin.shutdown(right.fd, SHUT_WR)
|
|
290
|
+
} else if !toRight && rightEOF && !leftShut {
|
|
291
|
+
leftShut = true
|
|
292
|
+
_ = Darwin.shutdown(left.fd, SHUT_WR)
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if leftShut && rightShut { close() }
|
|
296
|
+
updateInterest()
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
private func updateInterest() {
|
|
300
|
+
guard !closed else { return }
|
|
301
|
+
left.wantRead(!leftEOF && leftToRight.count < Relay.capacity)
|
|
302
|
+
right.wantRead(!rightEOF && rightToLeft.count < Relay.capacity)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
public func close() {
|
|
306
|
+
guard !closed else { return }
|
|
307
|
+
closed = true
|
|
308
|
+
left.close()
|
|
309
|
+
right.close()
|
|
310
|
+
onClose()
|
|
311
|
+
}
|
|
312
|
+
}
|