release: 1.4.4 — channel «Облако» (WebSocket via KeenDNS cloud to own Keenetic): wsgw gateway, Android 1.4.4, iOS, docs/cloud.md
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,8 @@ import WireGuardKit
|
||||
|
||||
/// PCA Phobos packet tunnel:
|
||||
/// WireGuard (userspace) -> 127.0.0.1:13255 wg-obfuscator -> 127.0.0.1:13258 pcarelay -> server (UDP or TCP).
|
||||
/// providerConfiguration: "conf" (phobos:// payload), "ruDirect" (Bool), "transport" ("auto"|"udp"|"tcp").
|
||||
/// providerConfiguration: "conf" (phobos:// payload), "ruDirect" (Bool), "transport" ("auto"|"udp"|"tcp"|"cloud"), "wsURL".
|
||||
/// "cloud": pcarelay hands packets to WsLink -> wss://<Keenetic KeenDNS subdomain>/ -> wsgw on the router -> server UDP.
|
||||
final class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
private static let wgPort = 13255, relayPort = 13258
|
||||
private static var obfuscatorStarted = false // one wg-obfuscator per process (it forks for more)
|
||||
@@ -31,7 +32,17 @@ final class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
return
|
||||
}
|
||||
|
||||
pcarelay_use_tcp(transport == "tcp" ? 1 : 0)
|
||||
let wsURL = (pc["wsURL"] as? String).flatMap { $0.isEmpty ? nil : $0 } ?? profile.wsURL
|
||||
if transport == "cloud" {
|
||||
guard let s = wsURL, let url = URL(string: s) else {
|
||||
completionHandler(NSError(domain: "PCAPhobos", code: 4, userInfo: [NSLocalizedDescriptionKey: "не задан адрес роутера (wss://…) для канала «Облако»"]))
|
||||
return
|
||||
}
|
||||
WsLink.shared.start(url)
|
||||
pcarelay_use_ws { p, n in WsLink.shared.send(p, n) }
|
||||
} else {
|
||||
pcarelay_use_tcp(transport == "tcp" ? 1 : 0)
|
||||
}
|
||||
if !Self.obfuscatorStarted {
|
||||
Self.obfuscatorStarted = true
|
||||
pcarelay_start(Int32(Self.relayPort), host, Int32(udpPort), Int32(tcpPort))
|
||||
@@ -44,8 +55,9 @@ final class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
}
|
||||
}
|
||||
|
||||
let allowed = ruDirect ? Self.ruBypassAllowedIPs() : nil
|
||||
let wgText = profile.wireGuardConfig(endpoint: "127.0.0.1:\(Self.wgPort)", allowedIPs: allowed)
|
||||
// full tunnel; Russian networks (all sizes, incl. CDN /24s) are excluded routes when ruDirect
|
||||
PhobosRoutes.excludedIPv4 = ruDirect ? Self.ruNetworks() : []
|
||||
let wgText = profile.wireGuardConfig(endpoint: "127.0.0.1:\(Self.wgPort)", allowedIPs: nil)
|
||||
let tunnelConfig: TunnelConfiguration
|
||||
do {
|
||||
tunnelConfig = try TunnelConfiguration(fromWgQuickConfig: wgText, called: "phobos")
|
||||
@@ -79,14 +91,17 @@ final class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
|
||||
/// App asks for status: {"transport": "UDP"|"TCP", "rx": bytes}
|
||||
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) {
|
||||
let status: [String: Any] = ["transport": pcarelay_is_tcp() != 0 ? "TCP" : "UDP",
|
||||
let status: [String: Any] = ["transport": pcarelay_is_ws() != 0 ? "Облако" : pcarelay_is_tcp() != 0 ? "TCP" : "UDP",
|
||||
"rx": pcarelay_rx_bytes()]
|
||||
completionHandler?(try? JSONSerialization.data(withJSONObject: status))
|
||||
}
|
||||
|
||||
private static func ruBypassAllowedIPs() -> String? {
|
||||
/// ru_bypass.txt: Russian IPv4 networks (comma-separated). IPv6 stays fully in the tunnel,
|
||||
/// otherwise blocked sites leak over carrier IPv6.
|
||||
private static func ruNetworks() -> [String] {
|
||||
guard let url = Bundle.main.url(forResource: "ru_bypass", withExtension: "txt"),
|
||||
let text = try? String(contentsOf: url, encoding: .utf8) else { return nil }
|
||||
return text.split(separator: "\n").first { !$0.hasPrefix("#") && $0.contains("/") }.map(String.init)
|
||||
let text = try? String(contentsOf: url, encoding: .utf8),
|
||||
let line = text.split(separator: "\n").first(where: { !$0.hasPrefix("#") && $0.contains("/") }) else { return [] }
|
||||
return line.split(separator: ",").map { $0.trimmingCharacters(in: .whitespaces) }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user