feat: 完成 M2 mosh(LAN + tsnet 端到端验证)

- mosh(blinksh/ios C++)+protobuf-lite 交叉编译为 MoshCore.xcframework
  (CommonCrypto 后端、含 arm64 模拟器 slice;绕 autotools 用 CMake 直编 + 手写 config.h)
- tsnet 桥新增 UDP relay(StartMoshRelay:loopback↔tsnet.Dial 逐包搬运)
- MoshSession(pipe↔FILE*/pthread/SIGWINCH)+ MoshConnectScanner(TXCore,+4 单测=39)
- SSHTerminalModel 编排:SSH 引导 mosh-server→解析 MOSH CONNECT→relay→mosh 接管
- LAN 直连 + mosh-over-tsnet 均端到端验证通过,R1(tsnet UDP 数据报语义) 证伪

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
kid
2026-07-24 16:14:40 +08:00
parent aa92d0e676
commit 32520f09e0
1232 changed files with 728138 additions and 37 deletions

View File

@@ -18,14 +18,26 @@ let package = Package(
.binaryTarget(name: "CSSHCore", path: "../../artifacts/CSSHCore.xcframework"),
// + modulemap `import CSSH` CSSHCore
.target(name: "CSSH", publicHeadersPath: "include"),
// mosh(C++) + protobuf-lite vendor/build/build-mosh.shlibrary-only
.binaryTarget(name: "MoshCore", path: "../../artifacts/MoshCore.xcframework"),
// + modulemap `import CMosh` mosh_main MoshCore
.target(name: "CMosh", publicHeadersPath: "include"),
.target(
name: "TXTransport",
dependencies: [
"CSSH",
"CSSHCore",
"CMosh",
"MoshCore",
.product(name: "TXCore", package: "TXCore"),
],
swiftSettings: [.swiftLanguageMode(.v6)]
swiftSettings: [.swiftLanguageMode(.v6)],
// mosh C++ libc++ compressor.cc zlib libz
// CommonCrypto libSystem Apple
linkerSettings: [
.linkedLibrary("c++"),
.linkedLibrary("z"),
]
),
]
)

View File

@@ -0,0 +1,4 @@
module CMosh {
header "moshiosbridge.h"
export *
}

View File

@@ -0,0 +1,17 @@
/* CMosh — Swift 可 import 的 mosh 客户端入口头(库符号来自 MoshCore.xcframework
* 对应 vendor/mosh/src/frontend/moshiosbridge.h保持签名一致。
* mosh_main 是阻塞式主循环:读 f_in(用户输入字节)、写 f_out(终端输出字节)
* 经 UDP 连 ip:portmosh SSPAES-128-OCBwindow_size 供 SIGWINCH resize。 */
#ifndef CMOSH_MOSHIOSBRIDGE_H
#define CMOSH_MOSHIOSBRIDGE_H
#include <stdio.h>
#include <sys/ioctl.h>
#if __cplusplus
extern "C"
#endif
int mosh_main(FILE *f_in, FILE *f_out, struct winsize *window_size,
const char *ip, const char *port, const char *key, const char *predict_mode);
#endif /* CMOSH_MOSHIOSBRIDGE_H */

View File

@@ -0,0 +1,3 @@
// CMosh 是 mosh_main 头文件 + modulemap 的载体 target供 `import CMosh`
// 实际库符号来自 MoshCore.xcframework静态库。此处仅需一个非空翻译单元。
typedef int cmosh_shim_placeholder;

View File

@@ -0,0 +1,162 @@
import Foundation
import CMosh
/// MoshSession vendored mosh MoshCore.xcframework `mosh_main`
///
/// `mosh_main` `FILE*`
/// - Swift `send(_:)` inPipe[1] mosh `fileno(f_in)=inPipe[0]`
/// - mosh `f_out=outPipe[1]` 线 outPipe[0] `onBytes`
/// - resize `winsize` `pthread_kill(SIGWINCH)`mosh select
///
/// ip:port mosh-over-tsnet `127.0.0.1:<relay >`
/// TsnetBridge MoshRelaymosh tailnet
public final class MoshSession: @unchecked Sendable {
/// mosh libghostty
public var onBytes: (@Sendable ([UInt8]) -> Void)?
/// mosh_main 0=退
public var onClosed: (@Sendable (Int32) -> Void)?
private let ip: String
private let port: String
private let key: String
private let predictMode: String
private let winPtr = UnsafeMutablePointer<winsize>.allocate(capacity: 1)
private let lock = NSLock()
private var inWriteFD: Int32 = -1 // Swift
private var outReadFD: Int32 = -1 // Swift
private var moshThread: pthread_t?
private var started = false
private var closed = false
/// - Parameters:
/// - ip/port: mosh-server tsnet loopback relay
/// - key: `mosh-server new` MOSH_KEYbase64
/// - predictMode: always | never | adaptive | experimental adaptive
public init(ip: String, port: Int, key: String, cols: Int, rows: Int, predictMode: String = "adaptive") {
self.ip = ip
self.port = String(port)
self.key = key
self.predictMode = predictMode
winPtr.pointee = winsize(ws_row: UInt16(rows), ws_col: UInt16(cols), ws_xpixel: 0, ws_ypixel: 0)
}
deinit {
winPtr.deallocate()
}
/// pipe线 mosh 线
public func start() {
lock.lock()
if started { lock.unlock(); return }
started = true
lock.unlock()
// pipe SIGPIPE EPIPE
signal(SIGPIPE, SIG_IGN)
// mosh UTF-8 native locale is_utf8_locale() exit(1)
setenv("LANG", "en_US.UTF-8", 1)
setenv("LC_ALL", "en_US.UTF-8", 1)
var inPipe: [Int32] = [-1, -1]
var outPipe: [Int32] = [-1, -1]
_ = inPipe.withUnsafeMutableBufferPointer { pipe($0.baseAddress) }
_ = outPipe.withUnsafeMutableBufferPointer { pipe($0.baseAddress) }
lock.lock()
inWriteFD = inPipe[1]
outReadFD = outPipe[0]
lock.unlock()
startReader(fd: outPipe[0])
startMosh(inReadFD: inPipe[0], outWriteFD: outPipe[1])
}
///
public func send(_ bytes: [UInt8]) {
lock.lock(); let fd = inWriteFD; lock.unlock()
guard fd >= 0, !bytes.isEmpty else { return }
var off = 0
bytes.withUnsafeBytes { buf in
while off < buf.count {
let n = write(fd, buf.baseAddress!.advanced(by: off), buf.count - off)
if n <= 0 { break }
off += n
}
}
}
/// moshSIGWINCH process_resize
public func resize(cols: Int, rows: Int) {
winPtr.pointee.ws_col = UInt16(cols)
winPtr.pointee.ws_row = UInt16(rows)
lock.lock(); let tid = moshThread; lock.unlock()
if let tid { pthread_kill(tid, SIGWINCH) }
}
/// pipemosh select EOF/EPIPE线
public func close() {
lock.lock()
if closed { lock.unlock(); return }
closed = true
let w = inWriteFD, r = outReadFD
inWriteFD = -1; outReadFD = -1
lock.unlock()
if w >= 0 { Foundation.close(w) }
if r >= 0 { Foundation.close(r) }
}
// MARK: -
private func setMoshThread(_ tid: pthread_t) { lock.lock(); moshThread = tid; lock.unlock() }
private func handleExit(_ rc: Int32) {
lock.lock(); moshThread = nil; lock.unlock()
onClosed?(rc)
}
/// mosh 线fdopen FILE* mosh_main Foundation.Thread
/// pthread_create + Unmanaged Swift6 SendNonSendable pass
/// 线 pthread_self() tid SIGWINCH
private func startMosh(inReadFD: Int32, outWriteFD: Int32) {
let ip = self.ip, port = self.port, key = self.key, predict = self.predictMode
let winPtr = self.winPtr
let t = Thread { [weak self] in
self?.setMoshThread(pthread_self())
guard let fin = fdopen(inReadFD, "r"), let fout = fdopen(outWriteFD, "w") else {
self?.handleExit(-1); return
}
setvbuf(fout, nil, _IONBF, 0) // mosh fwrite out_fd
let rc = ip.withCString { ipC in
port.withCString { portC in
key.withCString { keyC in
predict.withCString { prC in
mosh_main(fin, fout, winPtr, ipC, portC, keyC, prC)
}
}
}
}
fflush(fout)
self?.handleExit(rc)
}
t.name = "mosh-main"
t.stackSize = 2 * 1024 * 1024
t.start()
}
/// 线 mosh onBytes
private func startReader(fd: Int32) {
let t = Thread { [weak self] in
var buf = [UInt8](repeating: 0, count: 16384)
while true {
let n = buf.withUnsafeMutableBytes { read(fd, $0.baseAddress, $0.count) }
if n <= 0 { break }
let chunk = Array(buf[0..<n])
self?.onBytes?(chunk)
}
}
t.name = "mosh-reader"
t.stackSize = 512 * 1024
t.start()
}
}