mirror of
https://github.com/shadowsocks/ShadowsocksX-NG.git
synced 2026-10-31 14:53:49 +00:00
refactor(runtime): drop ProxyModeError guards and silent desired-proxy skip
Make systemProxyConfiguration total so a runtime document can no longer swallow derivation via try? and silently skip desired system proxy convergence. Delete the unreachable ProxyModeError / .mode failure fact and its presentation; port validity stays at the settings store and the document contract.
This commit is contained in:
@@ -20,10 +20,10 @@ extension ProxyRuntimeController {
|
||||
}
|
||||
|
||||
/// Desired system proxy configuration:当前保存设置要求的代理值(ADR-0022)。
|
||||
/// 观察机经 init 闭包读取同一事实源。
|
||||
/// 观察机经 init 闭包读取同一事实源。`nil` 只表示尚无运行时文档。
|
||||
var desiredSystemProxyConfiguration: SystemProxyConfiguration? {
|
||||
guard let document = lastDocument else { return nil }
|
||||
return try? proxyMode.systemProxyConfiguration(
|
||||
return proxyMode.systemProxyConfiguration(
|
||||
for: document, exceptions: settings.proxyExceptionList)
|
||||
}
|
||||
|
||||
|
||||
@@ -134,7 +134,6 @@ extension SystemProxyOperationFailure {
|
||||
|
||||
enum SystemProxyFailureFacts: Equatable, Sendable {
|
||||
case operation(SystemProxyOperationFailure)
|
||||
case mode(ProxyModeError)
|
||||
case unknown
|
||||
}
|
||||
|
||||
|
||||
@@ -596,9 +596,6 @@ extension SystemProxyObserver {
|
||||
if let error = error as? SystemProxyError {
|
||||
return .operation(SystemProxyOperationFailure(error))
|
||||
}
|
||||
if let error = error as? ProxyModeError {
|
||||
return .mode(error)
|
||||
}
|
||||
return .unknown
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,6 @@ extension AppPresentation {
|
||||
private static func systemProxyFailure(_ facts: SystemProxyFailureFacts) -> String {
|
||||
switch facts {
|
||||
case .operation(let failure): return systemProxy(failure)
|
||||
case .mode(let error): return proxyMode(error)
|
||||
case .unknown: return String(localized: "系统代理未能应用")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,8 +51,6 @@ extension AppPresentation {
|
||||
return reason(error)
|
||||
case let error as CommitError:
|
||||
return commit(error)
|
||||
case let error as ProxyModeError:
|
||||
return proxyMode(error)
|
||||
case let error as PortSettingError:
|
||||
return port(error)
|
||||
case let error as ProxySettingsStoreError:
|
||||
@@ -275,13 +273,6 @@ extension AppPresentation {
|
||||
}
|
||||
}
|
||||
|
||||
static func proxyMode(_ error: ProxyModeError) -> String {
|
||||
switch error {
|
||||
case .invalidSOCKSPort: return String(localized: "SOCKS 端口无效,必须是 1025–65535 之间的整数")
|
||||
case .invalidHTTPPort: return String(localized: "HTTP 端口无效,必须是 1025–65535 之间的整数")
|
||||
}
|
||||
}
|
||||
|
||||
private static func port(_ error: PortSettingError) -> String {
|
||||
switch error {
|
||||
case .portOutOfRange(let endpoint, let portNumber):
|
||||
|
||||
@@ -2932,23 +2932,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"HTTP 端口无效,必须是 1025–65535 之间的整数": {
|
||||
"extractionState": "manual",
|
||||
"localizations": {
|
||||
"zh-Hans": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "HTTP 端口无效,必须是 1025–65535 之间的整数"
|
||||
}
|
||||
},
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Invalid HTTP port. Enter an integer between 1025 and 65535."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Legacy 导入": {
|
||||
"extractionState": "manual",
|
||||
"localizations": {
|
||||
@@ -3051,23 +3034,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"SOCKS 端口无效,必须是 1025–65535 之间的整数": {
|
||||
"extractionState": "manual",
|
||||
"localizations": {
|
||||
"zh-Hans": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "SOCKS 端口无效,必须是 1025–65535 之间的整数"
|
||||
}
|
||||
},
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Invalid SOCKS port. Enter an integer between 1025 and 65535."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"URI 链接列表文本文件写入失败": {
|
||||
"extractionState": "manual",
|
||||
"localizations": {
|
||||
|
||||
@@ -77,16 +77,12 @@ enum ProxyMode: Codable, Equatable, Hashable, Sendable {
|
||||
/// SOCKS 之外,系统 HTTP/HTTPS 代理指向恒开启的 HTTP 入站。
|
||||
/// 投影即 closed typed 配置:HTTPS 显式赋值、PAC/自动发现显式
|
||||
/// 关闭、简单主机名排除显式开启、例外列表全量给出。
|
||||
/// 端口合法性由 settings store 与运行时文档派生保证,投影对任意
|
||||
/// 文档都是全函数,保证期望系统代理配置不被静默跳过(ADR-0022)。
|
||||
func systemProxyConfiguration(
|
||||
for document: SslocalRuntimeDocument,
|
||||
exceptions: [String] = []
|
||||
) throws -> SystemProxyConfiguration {
|
||||
guard ProxyPortRange.valid.contains(document.socksPort) else {
|
||||
throw ProxyModeError.invalidSOCKSPort(document.socksPort)
|
||||
}
|
||||
guard ProxyPortRange.valid.contains(document.httpPort) else {
|
||||
throw ProxyModeError.invalidHTTPPort(document.httpPort)
|
||||
}
|
||||
) -> SystemProxyConfiguration {
|
||||
let loopback = "127.0.0.1"
|
||||
return SystemProxyConfiguration(
|
||||
socks: .init(host: loopback, port: document.socksPort),
|
||||
@@ -96,8 +92,3 @@ enum ProxyMode: Codable, Equatable, Hashable, Sendable {
|
||||
exceptions: FixedLocalProxyRanges.systemProxyExceptions(including: exceptions))
|
||||
}
|
||||
}
|
||||
|
||||
enum ProxyModeError: Error, Equatable, Sendable {
|
||||
case invalidSOCKSPort(Int)
|
||||
case invalidHTTPPort(Int)
|
||||
}
|
||||
|
||||
@@ -228,7 +228,8 @@ extension SslocalRuntimeDocument {
|
||||
var socksPort: Int { socksLocal?.localPort ?? 0 }
|
||||
var socksMode: String { socksLocal?.mode ?? "" }
|
||||
|
||||
/// HTTP 入站端口;缺 HTTP 入站时为 0(无效端口,系统代理投影层拒绝)。
|
||||
/// HTTP 入站端口;缺 HTTP 入站时为 0。完整入站组由 `isWellFormed` 在
|
||||
/// 跨进程契约缝拒绝,端口合法性由 settings store 保证。
|
||||
var httpPort: Int {
|
||||
locals.first { $0.inboundProtocol == "http" }?.localPort ?? 0
|
||||
}
|
||||
|
||||
@@ -12,10 +12,10 @@ final class ProxyModeAvailabilityTests: XCTestCase {
|
||||
[.rule, .global, .direct])
|
||||
}
|
||||
|
||||
func testDirectModeUsesLocalSOCKSAndIncludesFixedBypasses() throws {
|
||||
func testDirectModeUsesLocalSOCKSAndIncludesFixedBypasses() {
|
||||
let document = SslocalRuntimeDocument(servers: [], listen: SslocalListenSettings())
|
||||
|
||||
let configuration = try ProxyMode.direct.systemProxyConfiguration(for: document)
|
||||
let configuration = ProxyMode.direct.systemProxyConfiguration(for: document)
|
||||
|
||||
XCTAssertEqual(
|
||||
configuration.socks,
|
||||
@@ -28,11 +28,11 @@ final class ProxyModeAvailabilityTests: XCTestCase {
|
||||
FixedLocalProxyRanges.systemProxyExceptions)
|
||||
}
|
||||
|
||||
func testIPv6LANModeKeepsLocalSystemProxyEndpoints() throws {
|
||||
func testIPv6LANModeKeepsLocalSystemProxyEndpoints() {
|
||||
let listen = SslocalListenSettings(listenerMode: .allIPv6Interfaces)
|
||||
let document = SslocalRuntimeDocument(servers: [], listen: listen)
|
||||
|
||||
let configuration = try ProxyMode.direct.systemProxyConfiguration(for: document)
|
||||
let configuration = ProxyMode.direct.systemProxyConfiguration(for: document)
|
||||
|
||||
XCTAssertEqual(configuration.socks, .init(host: "127.0.0.1", port: listen.socksPort))
|
||||
XCTAssertEqual(configuration.http, .init(host: "127.0.0.1", port: listen.httpPort))
|
||||
@@ -40,10 +40,10 @@ final class ProxyModeAvailabilityTests: XCTestCase {
|
||||
|
||||
/// 全局模式:系统 SOCKS/HTTP 投影 + 固定本地例外,与直连共用
|
||||
/// 同一安全范围;公网路由由 ACL 决定,不由系统例外决定。
|
||||
func testGlobalModeUsesLocalSOCKSAndIncludesFixedBypasses() throws {
|
||||
func testGlobalModeUsesLocalSOCKSAndIncludesFixedBypasses() {
|
||||
let document = SslocalRuntimeDocument(servers: [], listen: SslocalListenSettings())
|
||||
|
||||
let configuration = try ProxyMode.global.systemProxyConfiguration(for: document)
|
||||
let configuration = ProxyMode.global.systemProxyConfiguration(for: document)
|
||||
|
||||
XCTAssertEqual(
|
||||
configuration.socks,
|
||||
|
||||
@@ -151,7 +151,7 @@ final class SystemProxyObserverTests: XCTestCase {
|
||||
isIntentEnabled: isIntentEnabled,
|
||||
isExitAvailable: { true },
|
||||
desiredConfiguration: {
|
||||
try? ProxyMode.direct.systemProxyConfiguration(
|
||||
ProxyMode.direct.systemProxyConfiguration(
|
||||
for: SslocalRuntimeDocument(servers: [], listen: SslocalListenSettings()),
|
||||
exceptions: [])
|
||||
},
|
||||
|
||||
@@ -7,7 +7,7 @@ import XCTest
|
||||
/// Closed typed 配置覆盖 HTTPS/PAC/自动发现/简单主机名与例外列表,
|
||||
/// 无条件清理计划与 helper XPC 契约(含序列化 last-writer)都在此验证。
|
||||
final class SystemProxyTests: XCTestCase {
|
||||
func testSupportedModesProjectTheSameLocalEndpoints() throws {
|
||||
func testSupportedModesProjectTheSameLocalEndpoints() {
|
||||
let document = ProxyRuntimeFixture.makeDocument(
|
||||
listenerMode: .allIPv4Interfaces, localPort: 2086)
|
||||
|
||||
@@ -17,19 +17,19 @@ final class SystemProxyTests: XCTestCase {
|
||||
https: .init(host: "127.0.0.1", port: SslocalListenSettings.defaultHTTPPort),
|
||||
exceptions: FixedLocalProxyRanges.systemProxyExceptions)
|
||||
XCTAssertEqual(
|
||||
try ProxyMode.rule.systemProxyConfiguration(for: document),
|
||||
ProxyMode.rule.systemProxyConfiguration(for: document),
|
||||
expected)
|
||||
XCTAssertEqual(
|
||||
try ProxyMode.global.systemProxyConfiguration(for: document),
|
||||
ProxyMode.global.systemProxyConfiguration(for: document),
|
||||
expected,
|
||||
"全局模式的系统例外使用固定本地范围,与 ACL 安全策略一致")
|
||||
}
|
||||
|
||||
func testIPv6LANModeKeepsSystemProxyOnLocalIPv4Loopback() throws {
|
||||
func testIPv6LANModeKeepsSystemProxyOnLocalIPv4Loopback() {
|
||||
let document = SslocalRuntimeDocument(
|
||||
servers: [], listen: SslocalListenSettings(listenerMode: .allIPv6Interfaces))
|
||||
|
||||
let configuration = try ProxyMode.rule.systemProxyConfiguration(for: document)
|
||||
let configuration = ProxyMode.rule.systemProxyConfiguration(for: document)
|
||||
|
||||
XCTAssertEqual(configuration.socks.host, "127.0.0.1")
|
||||
XCTAssertEqual(configuration.http.host, "127.0.0.1")
|
||||
@@ -51,10 +51,10 @@ final class SystemProxyTests: XCTestCase {
|
||||
XCTAssertNil(SslocalRuntimeDocument.decodeValidated(data), "每组必须同时提供 SOCKS 与 HTTP")
|
||||
}
|
||||
|
||||
func testModeProjectionCarriesClosedTypedPolicyDefaults() throws {
|
||||
func testModeProjectionCarriesClosedTypedPolicyDefaults() {
|
||||
let document = ProxyRuntimeFixture.makeDocument(localPort: 2086)
|
||||
|
||||
let configuration = try ProxyMode.rule.systemProxyConfiguration(
|
||||
let configuration = ProxyMode.rule.systemProxyConfiguration(
|
||||
for: document, exceptions: ["example.com"])
|
||||
|
||||
XCTAssertTrue(configuration.pacEnabled == false, "PAC 显式关闭")
|
||||
|
||||
@@ -61,7 +61,6 @@ final class AppPresentationTests: XCTestCase {
|
||||
.pending,
|
||||
.applied,
|
||||
.failed(.operation(.applyFailed)),
|
||||
.failed(.mode(.invalidSOCKSPort(0))),
|
||||
.failed(.operation(.helperUnavailable)),
|
||||
.failed(.unknown),
|
||||
]
|
||||
|
||||
@@ -4,4 +4,4 @@
|
||||
|
||||
Enabling the system proxy switch writes the macOS HTTP and HTTPS proxy entries in addition to the SOCKS entry (ADR-0007 had disabled the HTTP/HTTPS families and projected a SOCKS target only). All three families point at 2.0's own local inbounds: SOCKS at the SOCKS5 listener, and HTTP and HTTPS at the always-on local HTTP inbound — shadowsocks-rust serves HTTP CONNECT directly, so no Privoxy-style bridge returns. HTTPS and HTTP share one endpoint because both are served by the same CONNECT-capable inbound. Exceptions and PAC/auto-discovery suppression remain; the ownership snapshot/restore protocol retained here was superseded by ADR-0019.
|
||||
|
||||
The launch health gate already probes every configured inbound, so the HTTP endpoint must be reachable before any system proxy write happens. A runtime document without a usable HTTP inbound port is rejected by the mode projection (`invalidHTTPPort`) instead of pointing the system at an invalid port. At the time of this decision NG2 had not shipped, so no migration was planned; ADR-0019 later replaced pre-app snapshot restoration with endpoint-matched cleanup.
|
||||
The launch health gate already probes every configured inbound, so the HTTP endpoint must be reachable before any system proxy write happens. A runtime document without a usable HTTP inbound port is rejected at the runtime-document contract (`isWellFormed`) instead of pointing the system at an invalid port; the mode projection itself is total over any document. At the time of this decision NG2 had not shipped, so no migration was planned; ADR-0019 later replaced pre-app snapshot restoration with endpoint-matched cleanup.
|
||||
|
||||
Reference in New Issue
Block a user