@braedonsaunders/appkit-desk 0.6.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.
Files changed (53) hide show
  1. package/README.md +83 -1
  2. package/backend.d.ts +28 -7
  3. package/backend.d.ts.map +1 -1
  4. package/backend.js +18 -521
  5. package/backend.js.map +1 -1
  6. package/events.d.ts +3 -0
  7. package/events.d.ts.map +1 -1
  8. package/events.js.map +1 -1
  9. package/host.d.ts +32 -1
  10. package/host.d.ts.map +1 -1
  11. package/host.js +141 -23
  12. package/host.js.map +1 -1
  13. package/index.d.ts +3 -0
  14. package/index.d.ts.map +1 -1
  15. package/index.js +3 -0
  16. package/index.js.map +1 -1
  17. package/machine.d.ts +145 -0
  18. package/machine.d.ts.map +1 -0
  19. package/machine.js +518 -0
  20. package/machine.js.map +1 -0
  21. package/network.d.ts +37 -0
  22. package/network.d.ts.map +1 -0
  23. package/network.js +173 -0
  24. package/network.js.map +1 -0
  25. package/package.json +2 -2
  26. package/plan.d.ts +129 -31
  27. package/plan.d.ts.map +1 -1
  28. package/plan.js +165 -9
  29. package/plan.js.map +1 -1
  30. package/port-relay.d.ts +13 -0
  31. package/port-relay.d.ts.map +1 -0
  32. package/port-relay.js +56 -0
  33. package/port-relay.js.map +1 -0
  34. package/virtualization.d.ts +16 -0
  35. package/virtualization.d.ts.map +1 -0
  36. package/virtualization.js +148 -0
  37. package/virtualization.js.map +1 -0
  38. package/vz/Package.swift +23 -0
  39. package/vz/Sources/DeskNet/Addresses.swift +106 -0
  40. package/vz/Sources/DeskNet/DHCP.swift +111 -0
  41. package/vz/Sources/DeskNet/Fence.swift +57 -0
  42. package/vz/Sources/DeskNet/NetworkStack.swift +547 -0
  43. package/vz/Sources/DeskNet/Packets.swift +355 -0
  44. package/vz/Sources/DeskNet/Plumbing.swift +312 -0
  45. package/vz/Sources/DeskNet/Sockets.swift +221 -0
  46. package/vz/Sources/DeskNet/TCPFlow.swift +448 -0
  47. package/vz/Sources/appkit-desk-vz/Control.swift +152 -0
  48. package/vz/Sources/appkit-desk-vz/Helper.swift +505 -0
  49. package/vz/Sources/appkit-desk-vz/main.swift +38 -0
  50. package/vz/Tests/DeskNetTests/DeskNetTests.swift +426 -0
  51. package/vz/appkit-desk-vz.entitlements +8 -0
  52. package/vz/build.sh +23 -0
  53. package/vz/check/extract-boot.py +221 -0
@@ -0,0 +1,221 @@
1
+ import Darwin
2
+ import Dispatch
3
+
4
+ public struct POSIXFailure: Error, CustomStringConvertible, Sendable {
5
+ public let call: String
6
+ public let code: Int32
7
+
8
+ public init(_ call: String, _ code: Int32 = errno) {
9
+ self.call = call
10
+ self.code = code
11
+ }
12
+
13
+ public var description: String { "\(call): \(String(cString: strerror(code)))" }
14
+ }
15
+
16
+ public enum Socket {
17
+ public static func setNonBlocking(_ fd: Int32) {
18
+ let flags = fcntl(fd, F_GETFL)
19
+ _ = fcntl(fd, F_SETFL, flags | O_NONBLOCK)
20
+ }
21
+
22
+ /// Writes to a peer that has gone return EPIPE instead of killing the helper.
23
+ public static func setNoSigPipe(_ fd: Int32) {
24
+ var one: Int32 = 1
25
+ _ = setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &one, socklen_t(MemoryLayout<Int32>.size))
26
+ }
27
+
28
+ public static func setInt(_ fd: Int32, _ level: Int32, _ name: Int32, _ value: Int32) {
29
+ var value = value
30
+ _ = setsockopt(fd, level, name, &value, socklen_t(MemoryLayout<Int32>.size))
31
+ }
32
+
33
+ /// Close so the peer sees a reset rather than an orderly end of stream.
34
+ public static func abort(_ fd: Int32) {
35
+ var linger = Darwin.linger(l_onoff: 1, l_linger: 0)
36
+ _ = setsockopt(fd, SOL_SOCKET, SO_LINGER, &linger, socklen_t(MemoryLayout<Darwin.linger>.size))
37
+ }
38
+
39
+ public static func address(_ ip: IPv4, _ port: UInt16) -> sockaddr_in {
40
+ var address = sockaddr_in()
41
+ address.sin_len = UInt8(MemoryLayout<sockaddr_in>.size)
42
+ address.sin_family = sa_family_t(AF_INET)
43
+ address.sin_port = port.bigEndian
44
+ address.sin_addr = in_addr(s_addr: ip.value.bigEndian)
45
+ return address
46
+ }
47
+
48
+ /// Start a non-blocking connect. The descriptor becomes writable when it
49
+ /// settles; `pendingError` then says how.
50
+ public static func connect(_ fd: Int32, to ip: IPv4, port: UInt16) throws {
51
+ var address = address(ip, port)
52
+ let result = withUnsafePointer(to: &address) {
53
+ $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
54
+ Darwin.connect(fd, $0, socklen_t(MemoryLayout<sockaddr_in>.size))
55
+ }
56
+ }
57
+ if result != 0 && errno != EINPROGRESS { throw POSIXFailure("connect") }
58
+ }
59
+
60
+ public static func pendingError(_ fd: Int32) -> Int32 {
61
+ var error: Int32 = 0
62
+ var length = socklen_t(MemoryLayout<Int32>.size)
63
+ if getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &length) != 0 { return errno }
64
+ return error
65
+ }
66
+
67
+ /// Every IPv4 address configured on any of the Mac's interfaces.
68
+ public static func hostAddresses() -> Set<IPv4> {
69
+ var result: Set<IPv4> = []
70
+ var list: UnsafeMutablePointer<ifaddrs>?
71
+ guard getifaddrs(&list) == 0, let first = list else { return result }
72
+ defer { freeifaddrs(list) }
73
+ var cursor: UnsafeMutablePointer<ifaddrs>? = first
74
+ while let entry = cursor {
75
+ if let address = entry.pointee.ifa_addr, address.pointee.sa_family == sa_family_t(AF_INET) {
76
+ let ip = address.withMemoryRebound(to: sockaddr_in.self, capacity: 1) {
77
+ IPv4(UInt32(bigEndian: $0.pointee.sin_addr.s_addr))
78
+ }
79
+ result.insert(ip)
80
+ }
81
+ cursor = entry.pointee.ifa_next
82
+ }
83
+ return result
84
+ }
85
+
86
+ /// Read whatever is available, up to `limit` bytes. `nil` means try later;
87
+ /// an empty array means end of stream.
88
+ public static func readAvailable(_ fd: Int32, limit: Int) throws -> [UInt8]? {
89
+ var buffer = [UInt8](repeating: 0, count: limit)
90
+ let count = buffer.withUnsafeMutableBytes { Darwin.read(fd, $0.baseAddress, limit) }
91
+ if count > 0 { return Array(buffer[0..<count]) }
92
+ if count == 0 { return [] }
93
+ if errno == EAGAIN || errno == EINTR { return nil }
94
+ throw POSIXFailure("read")
95
+ }
96
+
97
+ /// Write as much of `bytes` as the descriptor takes now; returns the count.
98
+ public static func writeSome(_ fd: Int32, _ bytes: [UInt8]) throws -> Int {
99
+ if bytes.isEmpty { return 0 }
100
+ let count = bytes.withUnsafeBytes { Darwin.write(fd, $0.baseAddress, bytes.count) }
101
+ if count >= 0 { return count }
102
+ if errno == EAGAIN || errno == EINTR { return 0 }
103
+ throw POSIXFailure("write")
104
+ }
105
+ }
106
+
107
+ /// Read and write readiness for one descriptor, delivered on the main queue.
108
+ ///
109
+ /// Dispatch sources are created suspended and must be resumed before they are
110
+ /// cancelled or released, and the descriptor must outlive both. This owns all
111
+ /// of that: interest is toggled with `wantRead`/`wantWrite`, and `close()`
112
+ /// cancels both sources and closes the descriptor once both have let go of it.
113
+ @MainActor
114
+ public final class FDWatcher {
115
+ public let fd: Int32
116
+ public var onReadable: (() -> Void)?
117
+ public var onWritable: (() -> Void)?
118
+ private let readSource: DispatchSourceRead
119
+ private let writeSource: DispatchSourceWrite
120
+ private var reading = false
121
+ private var writing = false
122
+ private var closed = false
123
+
124
+ public init(fd: Int32) {
125
+ self.fd = fd
126
+ readSource = DispatchSource.makeReadSource(fileDescriptor: fd, queue: .main)
127
+ writeSource = DispatchSource.makeWriteSource(fileDescriptor: fd, queue: .main)
128
+ readSource.setEventHandler { @Sendable [weak self] in
129
+ Task { @MainActor in self?.onReadable?() }
130
+ }
131
+ writeSource.setEventHandler { @Sendable [weak self] in
132
+ Task { @MainActor in self?.onWritable?() }
133
+ }
134
+ // Close the descriptor only after both sources have been cancelled, so
135
+ // neither can be watching a number the kernel has handed to someone else.
136
+ let group = DispatchGroup()
137
+ group.enter()
138
+ group.enter()
139
+ readSource.setCancelHandler { @Sendable in group.leave() }
140
+ writeSource.setCancelHandler { @Sendable in group.leave() }
141
+ group.notify(queue: .main) { @Sendable in Darwin.close(fd) }
142
+ }
143
+
144
+ public func wantRead(_ wanted: Bool) {
145
+ guard !closed, wanted != reading else { return }
146
+ reading = wanted
147
+ if wanted { readSource.resume() } else { readSource.suspend() }
148
+ }
149
+
150
+ public func wantWrite(_ wanted: Bool) {
151
+ guard !closed, wanted != writing else { return }
152
+ writing = wanted
153
+ if wanted { writeSource.resume() } else { writeSource.suspend() }
154
+ }
155
+
156
+ public var isClosed: Bool { closed }
157
+
158
+ public func close() {
159
+ guard !closed else { return }
160
+ closed = true
161
+ onReadable = nil
162
+ onWritable = nil
163
+ if !reading { readSource.resume() }
164
+ if !writing { writeSource.resume() }
165
+ readSource.cancel()
166
+ writeSource.cancel()
167
+ }
168
+ }
169
+
170
+ /// How the network reaches the outside world. Production uses real sockets;
171
+ /// tests substitute socket pairs so a flow can be driven end to end in memory.
172
+ public protocol Dialer {
173
+ /// A non-blocking TCP socket whose connect to `ip:port` is under way.
174
+ func tcp(to ip: IPv4, port: UInt16) throws -> Int32
175
+ /// A non-blocking UDP socket connected to `ip:port`.
176
+ func udp(to ip: IPv4, port: UInt16) throws -> Int32
177
+ /// A non-blocking ICMP datagram socket, for echo.
178
+ func icmp() throws -> Int32
179
+ }
180
+
181
+ public struct SystemDialer: Dialer {
182
+ public init() {}
183
+
184
+ public func tcp(to ip: IPv4, port: UInt16) throws -> Int32 {
185
+ let fd = Darwin.socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
186
+ guard fd >= 0 else { throw POSIXFailure("socket") }
187
+ Socket.setNonBlocking(fd)
188
+ Socket.setNoSigPipe(fd)
189
+ // The guest already coalesces its writes; delaying them again here only
190
+ // adds latency to interactive streams.
191
+ Socket.setInt(fd, IPPROTO_TCP, TCP_NODELAY, 1)
192
+ do {
193
+ try Socket.connect(fd, to: ip, port: port)
194
+ } catch {
195
+ Darwin.close(fd)
196
+ throw error
197
+ }
198
+ return fd
199
+ }
200
+
201
+ public func udp(to ip: IPv4, port: UInt16) throws -> Int32 {
202
+ let fd = Darwin.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
203
+ guard fd >= 0 else { throw POSIXFailure("socket") }
204
+ Socket.setNonBlocking(fd)
205
+ do {
206
+ try Socket.connect(fd, to: ip, port: port)
207
+ } catch {
208
+ Darwin.close(fd)
209
+ throw error
210
+ }
211
+ return fd
212
+ }
213
+
214
+ public func icmp() throws -> Int32 {
215
+ // Unprivileged ICMP: macOS allows datagram ICMP sockets for echo.
216
+ let fd = Darwin.socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP)
217
+ guard fd >= 0 else { throw POSIXFailure("socket") }
218
+ Socket.setNonBlocking(fd)
219
+ return fd
220
+ }
221
+ }
@@ -0,0 +1,448 @@
1
+ import Darwin
2
+
3
+ @inline(__always) func seqLT(_ a: UInt32, _ b: UInt32) -> Bool { Int32(bitPattern: a &- b) < 0 }
4
+ @inline(__always) func seqLE(_ a: UInt32, _ b: UInt32) -> Bool { Int32(bitPattern: a &- b) <= 0 }
5
+ @inline(__always) func seqGT(_ a: UInt32, _ b: UInt32) -> Bool { Int32(bitPattern: a &- b) > 0 }
6
+
7
+ /// A TCP connection as the guest sees it: the guest's port, and the address
8
+ /// and port of whatever it believes is on the other end.
9
+ public struct FlowKey: Hashable, Sendable, CustomStringConvertible {
10
+ public let guestPort: UInt16
11
+ public let remote: IPv4
12
+ public let remotePort: UInt16
13
+
14
+ public var description: String { "guest:\(guestPort) <-> \(remote):\(remotePort)" }
15
+ }
16
+
17
+ /// One terminated TCP connection: a real TCP endpoint facing the guest, and a
18
+ /// host descriptor (an internet socket, or a client of the port socket) facing
19
+ /// the Mac. Bytes are relayed between the two with flow control in both
20
+ /// directions.
21
+ ///
22
+ /// The endpoint is deliberately small. The link is a lossless, in-memory
23
+ /// virtual wire with microsecond round trips, so it negotiates no window
24
+ /// scaling, SACK or timestamps, acknowledges every segment immediately, and
25
+ /// recovers the rare dropped frame (a full socket buffer) with go-back-N
26
+ /// retransmission on a timer. What it must get right is the state machine:
27
+ /// handshakes in both directions, half-close in both directions, resets, and
28
+ /// never holding more than a bounded amount of either side's data.
29
+ @MainActor
30
+ final class TCPFlow {
31
+ enum State: Equatable {
32
+ /// The guest sent a SYN; the host socket is still connecting.
33
+ case connecting
34
+ /// The upstream connected and the SYN-ACK went to the guest.
35
+ case synReceived
36
+ /// The Mac opened this (port socket): our SYN is with the guest.
37
+ case synSent
38
+ case established
39
+ case closed
40
+ }
41
+
42
+ static let ourMSS: UInt16 = 1460
43
+ static let receiveCapacity = 256 * 1024
44
+ static let sendCapacity = 256 * 1024
45
+ static let maximumWindow = 65_535
46
+ static let initialRTOms: UInt64 = 250
47
+ static let maximumRTOms: UInt64 = 4_000
48
+ static let maximumRetries = 10
49
+ static let handshakeRetries = 5
50
+
51
+ let key: FlowKey
52
+ let guestAddress: IPv4
53
+ let fromGuest: Bool
54
+ private unowned let stack: NetworkStack
55
+ private let host: FDWatcher
56
+ private(set) var state: State
57
+
58
+ // Toward the guest.
59
+ private let iss: UInt32
60
+ private var sndUna: UInt32
61
+ private var sndNxt: UInt32
62
+ private var sndWnd: UInt32 = 0
63
+ /// Bytes read from the host, starting at `sndUna`; the FIN is not in here.
64
+ private var sendBuffer: [UInt8] = []
65
+ private var hostEOF = false
66
+ private var finSent = false
67
+ private(set) var finAcked = false
68
+ private var guestMSS = 536
69
+
70
+ // From the guest.
71
+ private var rcvNxt: UInt32 = 0
72
+ private var toHost: [UInt8] = []
73
+ private(set) var guestFin = false
74
+ private var hostWriteShut = false
75
+ private var advertisedWindow = 0
76
+
77
+ // Timers, in the stack's milliseconds.
78
+ private var rtoDeadline: UInt64?
79
+ private var rto: UInt64 = TCPFlow.initialRTOms
80
+ private var retries = 0
81
+ private var duplicateAcks = 0
82
+
83
+ /// A flow the guest opened: answer its SYN once the upstream connects.
84
+ init(stack: NetworkStack, key: FlowKey, guest: IPv4, syn: TCPSegment, hostFD: Int32) {
85
+ self.stack = stack
86
+ self.key = key
87
+ guestAddress = guest
88
+ fromGuest = true
89
+ host = FDWatcher(fd: hostFD)
90
+ state = .connecting
91
+ iss = UInt32.random(in: 0...UInt32.max)
92
+ sndUna = iss
93
+ sndNxt = iss
94
+ rcvNxt = syn.sequence &+ 1
95
+ sndWnd = UInt32(syn.window)
96
+ guestMSS = Int(min(syn.mss ?? 536, TCPFlow.ourMSS))
97
+ host.onWritable = { [weak self] in self?.hostWritable() }
98
+ host.onReadable = { [weak self] in self?.hostReadable() }
99
+ host.wantWrite(true)
100
+ }
101
+
102
+ /// A flow the Mac opened through the port socket: send the guest a SYN.
103
+ /// `ready` is written to the client before any guest byte, once the guest
104
+ /// accepts; `early` is client data that arrived with the request.
105
+ init(stack: NetworkStack, key: FlowKey, guest: IPv4, clientFD: Int32, ready: [UInt8], early: [UInt8]) {
106
+ self.stack = stack
107
+ self.key = key
108
+ guestAddress = guest
109
+ fromGuest = false
110
+ host = FDWatcher(fd: clientFD)
111
+ state = .synSent
112
+ iss = UInt32.random(in: 0...UInt32.max)
113
+ sndUna = iss
114
+ sndNxt = iss &+ 1
115
+ sendBuffer = early
116
+ toHost = ready
117
+ host.onWritable = { [weak self] in self?.hostWritable() }
118
+ host.onReadable = { [weak self] in self?.hostReadable() }
119
+ sendControl([.syn], sequence: iss, mss: TCPFlow.ourMSS)
120
+ armRetransmit()
121
+ }
122
+
123
+ // MARK: Guest side
124
+
125
+ func receive(_ segment: TCPSegment) {
126
+ if segment.flags.contains(.rst) {
127
+ // A reset to a SYN we sent is the guest refusing the port: the client
128
+ // gets a close with no reply line, exactly like a refused vsock port.
129
+ close(resetHost: state != .synSent)
130
+ return
131
+ }
132
+ switch state {
133
+ case .closed:
134
+ return
135
+ case .connecting:
136
+ // The guest is retransmitting its SYN while the upstream connects.
137
+ return
138
+ case .synSent:
139
+ guard segment.flags.contains([.syn, .ack]), segment.acknowledgement == iss &+ 1 else {
140
+ if segment.flags.contains(.ack) { stack.sendReset(answering: segment, key: key) }
141
+ return
142
+ }
143
+ rcvNxt = segment.sequence &+ 1
144
+ sndUna = iss &+ 1
145
+ sndWnd = UInt32(segment.window)
146
+ guestMSS = Int(min(segment.mss ?? 536, TCPFlow.ourMSS))
147
+ becomeEstablished()
148
+ sendAck()
149
+ flushToHost()
150
+ pumpToGuest()
151
+ return
152
+ case .synReceived:
153
+ if segment.flags.contains(.syn) && !segment.flags.contains(.ack) {
154
+ // Our SYN-ACK was lost; the guest asked again.
155
+ sendControl([.syn, .ack], sequence: iss, mss: TCPFlow.ourMSS)
156
+ return
157
+ }
158
+ guard segment.flags.contains(.ack), segment.acknowledgement == iss &+ 1 else { return }
159
+ sndUna = iss &+ 1
160
+ sndWnd = UInt32(segment.window)
161
+ becomeEstablished()
162
+ case .established:
163
+ if segment.flags.contains(.syn) {
164
+ // A SYN on a live connection is answered with an ACK, never obeyed.
165
+ sendAck()
166
+ return
167
+ }
168
+ }
169
+
170
+ if segment.flags.contains(.ack) { processAck(segment) }
171
+ processPayload(segment)
172
+ pumpToGuest()
173
+ closeIfFinished()
174
+ }
175
+
176
+ private func becomeEstablished() {
177
+ state = .established
178
+ retries = 0
179
+ rto = TCPFlow.initialRTOms
180
+ rtoDeadline = nil
181
+ updateReadInterest()
182
+ }
183
+
184
+ private func processAck(_ segment: TCPSegment) {
185
+ let ack = segment.acknowledgement
186
+ if seqGT(ack, sndUna) && seqLE(ack, sndNxt) {
187
+ var acked = Int(ack &- sndUna)
188
+ if finSent && ack == sndNxt {
189
+ finAcked = true
190
+ acked -= 1
191
+ }
192
+ sendBuffer.removeFirst(min(acked, sendBuffer.count))
193
+ sndUna = ack
194
+ retries = 0
195
+ rto = TCPFlow.initialRTOms
196
+ duplicateAcks = 0
197
+ rtoDeadline = sndUna == sndNxt ? nil : stack.now() + rto
198
+ sndWnd = UInt32(segment.window)
199
+ updateReadInterest()
200
+ } else if ack == sndUna {
201
+ let windowChanged = UInt32(segment.window) != sndWnd
202
+ sndWnd = UInt32(segment.window)
203
+ if segment.payload.isEmpty && !windowChanged && sndNxt != sndUna && !segment.flags.contains(.fin) {
204
+ duplicateAcks += 1
205
+ if duplicateAcks == 3 { retransmitFromUna() }
206
+ }
207
+ }
208
+ }
209
+
210
+ private func processPayload(_ segment: TCPSegment) {
211
+ let hasFin = segment.flags.contains(.fin)
212
+ guard !segment.payload.isEmpty || hasFin else { return }
213
+ let offset = Int32(bitPattern: segment.sequence &- rcvNxt)
214
+ if offset > 0 || guestFin {
215
+ // Out of order (a frame before it was lost), or after the guest's FIN:
216
+ // drop it and repeat our position so the guest resends from there.
217
+ sendAck()
218
+ return
219
+ }
220
+ let skip = Int(-offset)
221
+ if skip > segment.payload.count || (skip == segment.payload.count && !hasFin) {
222
+ sendAck()
223
+ return
224
+ }
225
+ var data = Array(segment.payload[skip...])
226
+ let space = TCPFlow.receiveCapacity - toHost.count
227
+ var acceptsFin = hasFin
228
+ if data.count > space {
229
+ data = Array(data[0..<space])
230
+ acceptsFin = false
231
+ }
232
+ toHost += data
233
+ rcvNxt = rcvNxt &+ UInt32(data.count)
234
+ if acceptsFin {
235
+ guestFin = true
236
+ rcvNxt = rcvNxt &+ 1
237
+ }
238
+ sendAck()
239
+ flushToHost()
240
+ }
241
+
242
+ // MARK: Host side
243
+
244
+ private func hostWritable() {
245
+ if state == .connecting {
246
+ host.wantWrite(false)
247
+ let error = Socket.pendingError(host.fd)
248
+ if error != 0 {
249
+ stack.upstreamFailed(self, errno: error)
250
+ close(resetHost: false)
251
+ return
252
+ }
253
+ state = .synReceived
254
+ sendControl([.syn, .ack], sequence: iss, mss: TCPFlow.ourMSS)
255
+ sndNxt = iss &+ 1
256
+ armRetransmit()
257
+ return
258
+ }
259
+ flushToHost()
260
+ }
261
+
262
+ private func hostReadable() {
263
+ guard state == .established, !hostEOF else {
264
+ host.wantRead(false)
265
+ return
266
+ }
267
+ let room = TCPFlow.sendCapacity - sendBuffer.count
268
+ guard room > 0 else {
269
+ host.wantRead(false)
270
+ return
271
+ }
272
+ do {
273
+ guard let bytes = try Socket.readAvailable(host.fd, limit: min(room, 64 * 1024)) else { return }
274
+ if bytes.isEmpty {
275
+ hostEOF = true
276
+ host.wantRead(false)
277
+ } else {
278
+ sendBuffer += bytes
279
+ }
280
+ } catch {
281
+ abort()
282
+ return
283
+ }
284
+ pumpToGuest()
285
+ updateReadInterest()
286
+ closeIfFinished()
287
+ }
288
+
289
+ private func flushToHost() {
290
+ guard state == .established else { return }
291
+ let windowBefore = currentWindow()
292
+ while !toHost.isEmpty {
293
+ let written: Int
294
+ do {
295
+ written = try Socket.writeSome(host.fd, toHost)
296
+ } catch {
297
+ abort()
298
+ return
299
+ }
300
+ if written == 0 { break }
301
+ toHost.removeFirst(written)
302
+ }
303
+ host.wantWrite(!toHost.isEmpty)
304
+ if toHost.isEmpty && guestFin && !hostWriteShut {
305
+ hostWriteShut = true
306
+ _ = Darwin.shutdown(host.fd, SHUT_WR)
307
+ }
308
+ // The guest stops when our window closes; tell it once it reopens.
309
+ let windowAfter = currentWindow()
310
+ if windowBefore < Int(TCPFlow.ourMSS) && windowAfter >= Int(TCPFlow.ourMSS) {
311
+ sendAck()
312
+ }
313
+ closeIfFinished()
314
+ }
315
+
316
+ private func updateReadInterest() {
317
+ host.wantRead(state == .established && !hostEOF && sendBuffer.count < TCPFlow.sendCapacity)
318
+ }
319
+
320
+ // MARK: Sending
321
+
322
+ /// Where unsent data starts in `sendBuffer`. The FIN occupies a sequence
323
+ /// number but no byte of the buffer, so it is discounted while in flight.
324
+ private var unsentOffset: Int {
325
+ Int(sndNxt &- sndUna) - (finSent && !finAcked ? 1 : 0)
326
+ }
327
+
328
+ private func pumpToGuest() {
329
+ guard state == .established else { return }
330
+ while true {
331
+ let offset = unsentOffset
332
+ let unsent = sendBuffer.count - offset
333
+ let usable = Int(sndWnd) - Int(sndNxt &- sndUna)
334
+ guard unsent > 0, usable > 0 else { break }
335
+ let length = min(guestMSS, unsent, usable)
336
+ let payload = Array(sendBuffer[offset..<(offset + length)])
337
+ sendData(payload, sequence: sndNxt, push: length == unsent)
338
+ sndNxt = sndNxt &+ UInt32(length)
339
+ if rtoDeadline == nil { rtoDeadline = stack.now() + rto }
340
+ }
341
+ if hostEOF && !finSent && unsentOffset == sendBuffer.count {
342
+ finSent = true
343
+ sendControl([.fin, .ack], sequence: sndNxt)
344
+ sndNxt = sndNxt &+ 1
345
+ if rtoDeadline == nil { rtoDeadline = stack.now() + rto }
346
+ }
347
+ if sndWnd == 0 && sendBuffer.count > unsentOffset && rtoDeadline == nil {
348
+ // The guest's window is shut with data waiting: probe it on the timer.
349
+ rtoDeadline = stack.now() + rto
350
+ }
351
+ }
352
+
353
+ private func retransmitFromUna() {
354
+ sndNxt = sndUna
355
+ if finSent && !finAcked { finSent = false }
356
+ pumpToGuest()
357
+ }
358
+
359
+ func tick(now: UInt64) {
360
+ guard let deadline = rtoDeadline, now >= deadline, state != .closed else { return }
361
+ retries += 1
362
+ rto = min(rto * 2, TCPFlow.maximumRTOms)
363
+ rtoDeadline = now + rto
364
+ switch state {
365
+ case .synSent:
366
+ if retries > TCPFlow.handshakeRetries { return close(resetHost: false) }
367
+ sendControl([.syn], sequence: iss, mss: TCPFlow.ourMSS)
368
+ case .synReceived:
369
+ if retries > TCPFlow.handshakeRetries { return abort() }
370
+ sendControl([.syn, .ack], sequence: iss, mss: TCPFlow.ourMSS)
371
+ case .established:
372
+ if sndUna == sndNxt {
373
+ if sndWnd == 0 && sendBuffer.count > 0 {
374
+ // Zero-window probe: an old sequence number draws an ACK carrying
375
+ // the guest's current window. It never gives up on its own.
376
+ retries = 0
377
+ sendControl([.ack], sequence: sndUna &- 1)
378
+ } else {
379
+ rtoDeadline = nil
380
+ }
381
+ return
382
+ }
383
+ if retries > TCPFlow.maximumRetries { return abort() }
384
+ retransmitFromUna()
385
+ case .connecting, .closed:
386
+ rtoDeadline = nil
387
+ }
388
+ }
389
+
390
+ private func currentWindow() -> Int {
391
+ min(TCPFlow.maximumWindow, TCPFlow.receiveCapacity - toHost.count)
392
+ }
393
+
394
+ private func sendAck() {
395
+ sendControl([.ack], sequence: sndNxt)
396
+ }
397
+
398
+ private func sendControl(_ flags: TCPFlags, sequence: UInt32, mss: UInt16? = nil) {
399
+ let syn = flags.contains(.syn) && !flags.contains(.ack)
400
+ advertisedWindow = currentWindow()
401
+ stack.sendTCP(
402
+ TCPSegment(
403
+ sourcePort: key.remotePort, destinationPort: key.guestPort, sequence: sequence,
404
+ acknowledgement: syn ? 0 : rcvNxt, flags: flags, window: UInt16(advertisedWindow),
405
+ mss: mss), from: key.remote, to: guestAddress)
406
+ }
407
+
408
+ private func sendData(_ payload: [UInt8], sequence: UInt32, push: Bool) {
409
+ advertisedWindow = currentWindow()
410
+ stack.sendTCP(
411
+ TCPSegment(
412
+ sourcePort: key.remotePort, destinationPort: key.guestPort, sequence: sequence,
413
+ acknowledgement: rcvNxt, flags: push ? [.ack, .psh] : [.ack], window: UInt16(advertisedWindow),
414
+ payload: payload), from: key.remote, to: guestAddress)
415
+ }
416
+
417
+ private func armRetransmit() {
418
+ rtoDeadline = stack.now() + rto
419
+ }
420
+
421
+ // MARK: Teardown
422
+
423
+ private func closeIfFinished() {
424
+ guard state == .established, guestFin, hostWriteShut, finAcked else { return }
425
+ close(resetHost: false)
426
+ }
427
+
428
+ /// Reset both sides: something failed and neither may wait on the other.
429
+ func abort() {
430
+ guard state != .closed else { return }
431
+ if state == .established || state == .synReceived {
432
+ stack.sendTCP(
433
+ TCPSegment(
434
+ sourcePort: key.remotePort, destinationPort: key.guestPort, sequence: sndNxt,
435
+ acknowledgement: rcvNxt, flags: [.rst, .ack], window: 0), from: key.remote, to: guestAddress)
436
+ }
437
+ close(resetHost: true)
438
+ }
439
+
440
+ func close(resetHost: Bool) {
441
+ guard state != .closed else { return }
442
+ state = .closed
443
+ rtoDeadline = nil
444
+ if resetHost { Socket.abort(host.fd) }
445
+ host.close()
446
+ stack.flowClosed(self)
447
+ }
448
+ }