mirror of
https://github.com/shadowsocks/ShadowsocksX-NG.git
synced 2026-11-08 10:44:50 +00:00
95 lines
3.7 KiB
Swift
95 lines
3.7 KiB
Swift
/// 本地代理端点的端口语义:端口是显式用户配置,
|
||
/// 出厂默认与 Legacy 隔离(SOCKS5 11086 / HTTP 11087);系统任何路径
|
||
/// 都不静默改端口——建议端口只产出候选,必须经用户确认保存后才生效;激活
|
||
/// 是否成功以 runtime 实际绑定为准(健康门禁),本层的占用校验只是设置编辑
|
||
/// 期尽力而为的即时提示。
|
||
|
||
/// 本地代理端点;显示名与诊断报告的措辞一致。PAC 端点已移除。
|
||
enum ProxyEndpointKind: Hashable, Sendable, CaseIterable {
|
||
case socks
|
||
case http
|
||
case lanSocks
|
||
case lanHTTP
|
||
|
||
var displayName: String {
|
||
switch self {
|
||
case .socks: String(localized: "本地 SOCKS5 代理")
|
||
case .http: String(localized: "本地 HTTP 代理")
|
||
case .lanSocks: String(localized: "局域网 SOCKS 代理")
|
||
case .lanHTTP: String(localized: "局域网 HTTP 代理")
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 端口配置校验错误:点名端点与端口(错误信息要求)。
|
||
enum PortSettingError: Error, Hashable, Sendable {
|
||
case portOutOfRange(endpoint: ProxyEndpointKind, port: Int)
|
||
/// 两个端点的配置端口相同;`endpoint` 在枚举序(socks → http)中在前。
|
||
case duplicatePort(endpoint: ProxyEndpointKind, otherEndpoint: ProxyEndpointKind, port: Int)
|
||
}
|
||
|
||
/// 建议空闲端口算法:32768–65535 高位段升序扫描,跳过排除集合与不
|
||
/// 空闲端口。空闲判定由调用方注入,保证主缝可测。
|
||
enum PortSuggestion {
|
||
static let range = 32768...65535
|
||
|
||
static func firstFree(excluding excludedPorts: Set<Int>, isFree: (Int) -> Bool) -> Int? {
|
||
for candidate in range where !excludedPorts.contains(candidate) {
|
||
if isFree(candidate) { return candidate }
|
||
}
|
||
return nil
|
||
}
|
||
|
||
static func firstFreeAsync(
|
||
excluding excludedPorts: Set<Int>, isFree: (Int) async -> Bool
|
||
) async -> Int? {
|
||
for candidate in range where !excludedPorts.contains(candidate) {
|
||
if await isFree(candidate) { return candidate }
|
||
}
|
||
return nil
|
||
}
|
||
}
|
||
|
||
extension ProxyListeningConfiguration {
|
||
/// 端点的配置端口。
|
||
func configuredPort(for endpoint: ProxyEndpointKind) -> Int {
|
||
switch endpoint {
|
||
case .socks: socksPort
|
||
case .http: httpPort
|
||
case .lanSocks: lan.socksPort
|
||
case .lanHTTP: lan.httpPort
|
||
}
|
||
}
|
||
|
||
/// 端口配置校验:范围 1025–65535、四端点配置值互异。错误按固定次序
|
||
/// (socks/http 范围检查,再 socks-http 冲突)。
|
||
func portValidationErrors() -> [PortSettingError] {
|
||
var errors: [PortSettingError] = []
|
||
for endpoint in ProxyEndpointKind.allCases {
|
||
let port = configuredPort(for: endpoint)
|
||
if !ProxyPortRange.valid.contains(port) {
|
||
errors.append(.portOutOfRange(endpoint: endpoint, port: port))
|
||
}
|
||
}
|
||
for pair in Self.endpointPairs {
|
||
let first = configuredPort(for: pair.0)
|
||
if first == configuredPort(for: pair.1) {
|
||
errors.append(.duplicatePort(endpoint: pair.0, otherEndpoint: pair.1, port: first))
|
||
}
|
||
}
|
||
return errors
|
||
}
|
||
|
||
/// 为 `endpoint` 建议一个空闲端口:高位段升序首个空闲且不等于另个端点
|
||
/// 配置值的候选;整段无可用返回 nil。只产出候选,绝不替用户改配置。
|
||
func suggestedPort(for endpoint: ProxyEndpointKind, isFree: (Int) -> Bool) -> Int? {
|
||
let others = Set(ProxyEndpointKind.allCases.filter { $0 != endpoint }.map(configuredPort))
|
||
return PortSuggestion.firstFree(excluding: others, isFree: isFree)
|
||
}
|
||
|
||
private static let endpointPairs: [(ProxyEndpointKind, ProxyEndpointKind)] = [
|
||
(.socks, .http), (.socks, .lanSocks), (.socks, .lanHTTP),
|
||
(.http, .lanSocks), (.http, .lanHTTP), (.lanSocks, .lanHTTP),
|
||
]
|
||
}
|