@braedonsaunders/appkit-desk 0.7.0 → 0.9.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 +126 -1
- package/backend.d.ts +32 -7
- package/backend.d.ts.map +1 -1
- package/backend.js +107 -524
- package/backend.js.map +1 -1
- package/host.d.ts +27 -1
- package/host.d.ts.map +1 -1
- package/host.js +47 -19
- 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 +154 -0
- package/machine.d.ts.map +1 -0
- package/machine.js +573 -0
- package/machine.js.map +1 -0
- package/package.json +2 -2
- package/plan.d.ts +192 -31
- package/plan.d.ts.map +1 -1
- package/plan.js +298 -12
- 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 +156 -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 +550 -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,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
|
+
}
|