Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ extension TunnelStack {
///
/// Deliberately conservative: it does NOT re-resolve DNS or rebuild the mux
/// (there's no path to dial over) and does NOT force-close the app-facing TCP
/// legs. A leg riding a freed shared session (Hysteria/AnyTLS/HTTP3, or a
/// legs. A leg riding a freed shared session (Hysteria/Nowhere/AnyTLS/HTTP3, or a
/// UDP-over-mux flow) sees a graceful downlink EOF — ``MuxManager/closeAll``
/// and friends deliver no error — and winds down on its own; a leg actively
/// writing when its session drops aborts on the failed send; per-connection
Expand All @@ -132,6 +132,7 @@ extension TunnelStack {
logger.info("[VPN] Path offline/sleep: releasing upstream transports; will rebuild when it returns")

HysteriaClient.closeAll()
NowhereClient.closeAll()
AnyTLSManager.shared.closeAll()
HTTP3SessionPool.shared.closeAll()

Expand Down Expand Up @@ -241,6 +242,7 @@ extension TunnelStack {
}

HysteriaClient.closeAll()
NowhereClient.closeAll()
AnyTLSManager.shared.closeAll()
HTTP3SessionPool.shared.closeAll()

Expand Down Expand Up @@ -291,6 +293,7 @@ extension TunnelStack {
}

HysteriaClient.closeAll()
NowhereClient.closeAll()
HTTP3SessionPool.shared.closeAll()

// mux / SS sessions / flows are udpQueue-owned — close them there and
Expand Down
9 changes: 9 additions & 0 deletions Anywhere Network Extension/UDPFlow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,15 @@ class UDPFlow {
return false
}
}
if let nErr = error as? NowhereError {
switch nErr {
case .streamClosed, .authFailed, .invalidTargetLength,
.destinationTooLargeForDatagram:
return true
case .notReady, .connectionFailed:
return false
}
}
if let qErr = error as? QUICConnection.QUICError {
switch qErr {
case .closed, .streamReset, .streamClosedWithError, .handshakeFailed:
Expand Down
22 changes: 20 additions & 2 deletions Anywhere TV/TVProxyEditorViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ class TVProxyEditorViewController: UITableViewController {
private var hysteriaCC: HysteriaCongestionControl = .brutal
private var hysteriaUploadMbpsText = String(HysteriaCongestionControl.uploadMbpsDefault)
private var hysteriaDownloadMbpsText = String(HysteriaCongestionControl.downloadMbpsDefault)

// Nowhere fields
private var nowhereKey = ""

// Trojan fields
private var trojanPassword = ""
Expand Down Expand Up @@ -97,6 +100,7 @@ class TVProxyEditorViewController: UITableViewController {

private var isVLESS: Bool { selectedProtocol == .vless }
private var isHysteria: Bool { selectedProtocol == .hysteria }
private var isNowhere: Bool { selectedProtocol == .nowhere }
private var isTrojan: Bool { selectedProtocol == .trojan }
private var isAnyTLS: Bool { selectedProtocol == .anytls }
private var isShadowsocks: Bool { selectedProtocol == .shadowsocks }
Expand Down Expand Up @@ -125,6 +129,7 @@ class TVProxyEditorViewController: UITableViewController {
case tlsSNI, tlsALPN, fingerprint
case realitySNI, realityPublicKey, realityShortId
case hysteriaPassword, hysteriaCC, hysteriaUploadMbps, hysteriaDownloadMbps
case nowhereKey
case trojanPassword
case anytlsPassword
case ssPassword, ssMethod
Expand All @@ -149,6 +154,7 @@ class TVProxyEditorViewController: UITableViewController {
let protocolOptions: [(String, String)] = [
("VLESS", "vless"),
("Hysteria", "hysteria"),
("Nowhere", "nowhere"),
("Trojan", "trojan"),
("AnyTLS", "anytls"),
("Shadowsocks", "shadowsocks"),
Expand Down Expand Up @@ -182,6 +188,8 @@ class TVProxyEditorViewController: UITableViewController {
serverRows.append(.text(label: String(localized: "Upload Speed", comment: "Upload Speed for Hysteria protocol"), value: hysteriaUploadMbpsText, placeholder: String(localized: "Mbps"), key: .hysteriaUploadMbps))
serverRows.append(.text(label: String(localized: "Download Speed", comment: "Download Speed for Hysteria protocol"), value: hysteriaDownloadMbpsText, placeholder: String(localized: "Mbps"), key: .hysteriaDownloadMbps))
}
} else if isNowhere {
serverRows.append(.text(label: String(localized: "Key"), value: nowhereKey, placeholder: String(localized: "Key"), key: .nowhereKey, secure: true))
} else if isTrojan {
serverRows.append(.text(label: String(localized: "Password"), value: trojanPassword, placeholder: String(localized: "Password"), key: .trojanPassword, secure: true))
} else if isAnyTLS {
Expand Down Expand Up @@ -376,6 +384,9 @@ class TVProxyEditorViewController: UITableViewController {
}
return true
}
if isNowhere {
return !nowhereKey.isEmpty
}
if isTrojan { return !trojanPassword.isEmpty }
if isAnyTLS { return !anytlsPassword.isEmpty }
if isShadowsocks { return !ssPassword.isEmpty }
Expand Down Expand Up @@ -538,7 +549,7 @@ class TVProxyEditorViewController: UITableViewController {
case .outboundProtocol:
if let proto = OutboundProtocol(rawValue: value) {
selectedProtocol = proto
if isHysteria || isTrojan || isAnyTLS || isShadowsocks || isSOCKS5 || isSudoku || isNaive {
if isHysteria || isNowhere || isTrojan || isAnyTLS || isShadowsocks || isSOCKS5 || isSudoku || isNaive {
flow = ""
if security == "reality" { security = "none" }
}
Expand Down Expand Up @@ -575,6 +586,7 @@ class TVProxyEditorViewController: UITableViewController {
if let cc = HysteriaCongestionControl(rawValue: value) { hysteriaCC = cc }
case .hysteriaUploadMbps: hysteriaUploadMbpsText = value
case .hysteriaDownloadMbps: hysteriaDownloadMbpsText = value
case .nowhereKey: nowhereKey = value
case .trojanPassword: trojanPassword = value
case .anytlsPassword: anytlsPassword = value
case .ssPassword: ssPassword = value
Expand Down Expand Up @@ -664,6 +676,8 @@ class TVProxyEditorViewController: UITableViewController {
hysteriaCC = congestionControl
hysteriaUploadMbpsText = String(uploadMbps)
hysteriaDownloadMbpsText = String(downloadMbps)
case .nowhere(let key):
nowhereKey = key
case .trojan(let password, let tls):
trojanPassword = password
tlsSNI = tls.serverName
Expand Down Expand Up @@ -756,7 +770,7 @@ class TVProxyEditorViewController: UITableViewController {
private func save() {
guard let port = UInt16(serverPort) else { return }
let parsedUUID: UUID
if isHysteria || isTrojan || isAnyTLS || isShadowsocks || isSOCKS5 || isSudoku || isNaive {
if isHysteria || isNowhere || isTrojan || isAnyTLS || isShadowsocks || isSOCKS5 || isSudoku || isNaive {
parsedUUID = existingConfiguration?.id ?? UUID()
} else {
guard let uuid = UUID(uuidString: uuid) else { return }
Expand Down Expand Up @@ -847,6 +861,10 @@ class TVProxyEditorViewController: UITableViewController {
downloadMbps: down,
sni: existingSNI ?? bareAddress
)
case .nowhere:
outbound = .nowhere(
key: nowhereKey
)
case .trojan:
let sniValue = tlsSNI.isEmpty ? bareAddress : tlsSNI
let alpn: [String]? = tlsALPN.isEmpty ? nil : tlsALPN.split(separator: ",").map { String($0) }
Expand Down
7 changes: 7 additions & 0 deletions Anywhere.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@
"Networking/Protocols/Core/ProxyClient+AnyTLS.swift",
"Networking/Protocols/Core/ProxyClient+Hysteria.swift",
"Networking/Protocols/Core/ProxyClient+Naive.swift",
"Networking/Protocols/Core/ProxyClient+Nowhere.swift",
"Networking/Protocols/Core/ProxyClient+Shadowsocks.swift",
"Networking/Protocols/Core/ProxyClient+SOCKS5.swift",
"Networking/Protocols/Core/ProxyClient+Sudoku.swift",
Expand Down Expand Up @@ -197,6 +198,12 @@
Networking/Protocols/Hysteria/HysteriaProtocol.swift,
Networking/Protocols/Hysteria/HysteriaSession.swift,
Networking/Protocols/Hysteria/HysteriaUDPConnection.swift,
Networking/Protocols/Nowhere/NowhereClient.swift,
Networking/Protocols/Nowhere/NowhereConfiguration.swift,
Networking/Protocols/Nowhere/NowhereConnection.swift,
Networking/Protocols/Nowhere/NowhereProtocol.swift,
Networking/Protocols/Nowhere/NowhereSession.swift,
Networking/Protocols/Nowhere/NowhereUDPConnection.swift,
Networking/Protocols/Mux/MuxClient.swift,
Networking/Protocols/Mux/MuxFrame.swift,
Networking/Protocols/Mux/MuxManager.swift,
Expand Down
2 changes: 1 addition & 1 deletion Anywhere/DeepLinkManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ final class DeepLinkManager: ObservableObject {
switch url.scheme?.lowercased() {
case "anywhere":
handleAnywhereScheme(url)
case "vless", "hysteria2", "hy2", "trojan", "anytls", "ss", "quic", "sudoku":
case "vless", "hysteria2", "hy2", "nowhere", "trojan", "anytls", "ss", "quic", "sudoku":
self.url = url.absoluteString
default:
break
Expand Down
27 changes: 25 additions & 2 deletions Anywhere/Views/ProxyList/ProxyEditorView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ struct ProxyEditorView: View {
@State private var hysteriaDownloadMbpsText = String(HysteriaCongestionControl.downloadMbpsDefault)
@State private var hysteriaSNI = ""

// Nowhere fields
@State private var nowhereKey = ""

// Trojan fields
@State private var trojanPassword = ""

Expand Down Expand Up @@ -97,6 +100,7 @@ struct ProxyEditorView: View {

private var isVLESS: Bool { selectedProtocol == .vless }
private var isHysteria: Bool { selectedProtocol == .hysteria }
private var isNowhere: Bool { selectedProtocol == .nowhere }
private var isTrojan: Bool { selectedProtocol == .trojan }
private var isAnyTLS: Bool { selectedProtocol == .anytls }
private var isShadowsocks: Bool { selectedProtocol == .shadowsocks }
Expand All @@ -120,6 +124,9 @@ struct ProxyEditorView: View {
}
return true
}
if isNowhere {
return !nowhereKey.isEmpty
}
if isTrojan {
return !trojanPassword.isEmpty
}
Expand Down Expand Up @@ -166,6 +173,7 @@ struct ProxyEditorView: View {
Picker(selection: $selectedProtocol) {
Text(String("VLESS")).tag(OutboundProtocol.vless)
Text(String("Hysteria")).tag(OutboundProtocol.hysteria)
Text(String("Nowhere")).tag(OutboundProtocol.nowhere)
Text(String("Trojan")).tag(OutboundProtocol.trojan)
Text(String("AnyTLS")).tag(OutboundProtocol.anytls)
Text(String("Shadowsocks")).tag(OutboundProtocol.shadowsocks)
Expand Down Expand Up @@ -253,7 +261,16 @@ struct ProxyEditorView: View {
TextWithColorfulIcon(title: "Download Speed", comment: "Download Speed for Hysteria protocol", systemName: "arrow.down.circle.fill", foregroundColor: .white, backgroundColor: .blue)
}
}
} else if isTrojan {
} else if isNowhere {
LabeledContent {
SecureField("Key", text: $nowhereKey)
.autocorrectionDisabled()
.textInputAutocapitalization(.never)
.multilineTextAlignment(.trailing)
} label: {
TextWithColorfulIcon(title: "Key", comment: nil, systemName: "key.fill", foregroundColor: .white, backgroundColor: .green)
}
} else if isTrojan {
LabeledContent {
SecureField("Password", text: $trojanPassword)
.autocorrectionDisabled()
Expand Down Expand Up @@ -743,6 +760,8 @@ struct ProxyEditorView: View {
hysteriaUploadMbpsText = String(uploadMbps)
hysteriaDownloadMbpsText = String(downloadMbps)
hysteriaSNI = sni
case .nowhere(let key):
nowhereKey = key
case .trojan(let password, let tls):
trojanPassword = password
tlsSNI = tls.serverName
Expand Down Expand Up @@ -823,7 +842,7 @@ struct ProxyEditorView: View {
private func save() {
guard let port = UInt16(serverPort) else { return }
let parsedUUID: UUID
if isHysteria || isTrojan || isAnyTLS || isShadowsocks || isSOCKS5 || isSudoku || isNaive {
if isHysteria || isNowhere || isTrojan || isAnyTLS || isShadowsocks || isSOCKS5 || isSudoku || isNaive {
parsedUUID = self.configuration?.id ?? UUID()
} else {
guard let u = UUID(xrayString: uuid) else { return }
Expand Down Expand Up @@ -932,6 +951,10 @@ struct ProxyEditorView: View {
downloadMbps: down,
sni: sni
)
case .nowhere:
outbound = .nowhere(
key: nowhereKey
)
case .trojan:
let sni = tlsSNI.isEmpty ? bareAddress : tlsSNI
let alpn: [String]? = tlsALPN.isEmpty ? nil : tlsALPN.split(separator: ",").map { String($0) }
Expand Down
Loading