diff --git a/docs/img/ios-app.png b/docs/img/ios-app.png new file mode 100644 index 0000000..ce53f68 Binary files /dev/null and b/docs/img/ios-app.png differ diff --git a/ios/.gitignore b/ios/.gitignore new file mode 100644 index 0000000..ff2c642 --- /dev/null +++ b/ios/.gitignore @@ -0,0 +1,3 @@ +build/ +*.xcuserstate +xcuserdata/ diff --git a/ios/App/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/App/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..e13552c --- /dev/null +++ b/ios/App/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1 @@ +{"images":[{"filename":"icon-1024.png","idiom":"universal","platform":"ios","size":"1024x1024"}],"info":{"author":"xcode","version":1}} diff --git a/ios/App/Assets.xcassets/AppIcon.appiconset/icon-1024.png b/ios/App/Assets.xcassets/AppIcon.appiconset/icon-1024.png new file mode 100644 index 0000000..9237726 Binary files /dev/null and b/ios/App/Assets.xcassets/AppIcon.appiconset/icon-1024.png differ diff --git a/ios/App/Assets.xcassets/Contents.json b/ios/App/Assets.xcassets/Contents.json new file mode 100644 index 0000000..f223229 --- /dev/null +++ b/ios/App/Assets.xcassets/Contents.json @@ -0,0 +1 @@ +{"info":{"author":"xcode","version":1}} diff --git a/ios/App/ContentView.swift b/ios/App/ContentView.swift new file mode 100644 index 0000000..6228620 --- /dev/null +++ b/ios/App/ContentView.swift @@ -0,0 +1,97 @@ +import NetworkExtension +import SwiftUI + +struct ContentView: View { + @EnvironmentObject var vpn: VPNController + @State private var pasteText = "" + @State private var showImport = false + + var body: some View { + NavigationStack { + ScrollView { + VStack(spacing: 18) { + profileCard + powerButton + statusLine + optionsCard + Button { showImport = true } label: { + Label("Импорт ссылки phobos://", systemImage: "square.and.arrow.down") + .frame(maxWidth: .infinity).padding(.vertical, 12) + } + .buttonStyle(.borderedProminent).tint(.indigo) + Link(destination: URL(string: "https://speed.cloudflare.com/")!) { + Label("Проверить скорость", systemImage: "speedometer").frame(maxWidth: .infinity).padding(.vertical, 12) + } + .buttonStyle(.bordered) + if !vpn.message.isEmpty { + Text(vpn.message).font(.footnote).foregroundStyle(.secondary).multilineTextAlignment(.center) + } + } + .padding() + } + .navigationTitle("PCA Phobos VPN") + .alert("Ссылка из панели", isPresented: $showImport) { + TextField("phobos://…", text: $pasteText) + Button("Импорт") { vpn.importLink(pasteText); pasteText = "" } + Button("Из буфера") { vpn.importLink(UIPasteboard.general.string ?? "") } + Button("Отмена", role: .cancel) {} + } message: { + Text("В панели: клиент → 📱 → ссылка phobos://. Можно просто открыть ссылку на iPhone — профиль импортируется сам.") + } + } + } + + private var profileCard: some View { + VStack(alignment: .leading, spacing: 4) { + if let p = vpn.profile { + Text(vpn.name).font(.title3.bold()) + Text("Сервер: \(p.target ?? "?")").font(.subheadline).foregroundStyle(.secondary) + if let tcp = p.tcpPort { Text("Резервный TCP: порт \(tcp)").font(.caption).foregroundStyle(.secondary) } + } else { + Text("Профиль не импортирован").font(.headline) + Text("Откройте на iPhone ссылку phobos:// из панели PCA Phobos или вставьте её кнопкой ниже.") + .font(.subheadline).foregroundStyle(.secondary) + } + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding().background(.thinMaterial, in: RoundedRectangle(cornerRadius: 16)) + } + + private var powerButton: some View { + Button(action: vpn.toggle) { + Text(vpn.isOn ? "Отключить" : "Подключить") + .font(.title2.bold()).foregroundStyle(.white) + .frame(width: 180, height: 180) + .background(Circle().fill(vpn.isOn ? Color.green : Color.indigo)) + } + .padding(.top, 8) + } + + private var statusLine: some View { + let text: String = { + switch vpn.status { + case .connected: return "🟢 VPN включён" + (vpn.transport.isEmpty ? "" : " · \(vpn.transport)") + case .connecting, .reasserting: return "🟡 Подключение…" + case .disconnecting: return "Отключение…" + default: return "⚪ VPN выключен" + } + }() + return Text(text).font(.headline) + } + + private var optionsCard: some View { + VStack(alignment: .leading, spacing: 12) { + Toggle("Российские сайты — напрямую, без VPN", isOn: $vpn.ruDirect) + VStack(alignment: .leading, spacing: 6) { + Text("Канал до сервера").font(.subheadline.bold()) + Picker("Канал", selection: $vpn.transportMode) { + Text("Авто").tag("auto"); Text("UDP").tag("udp"); Text("TCP").tag("tcp") + } + .pickerStyle(.segmented) + Text("Авто: если UDP режут, через 15 секунд переходит на TCP. Изменения применяются сразу.") + .font(.caption).foregroundStyle(.secondary) + } + } + .padding().background(.thinMaterial, in: RoundedRectangle(cornerRadius: 16)) + } +} diff --git a/ios/App/Info.plist b/ios/App/Info.plist new file mode 100644 index 0000000..5b0f071 --- /dev/null +++ b/ios/App/Info.plist @@ -0,0 +1,41 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + PCA Phobos + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleURLTypes + + + CFBundleURLName + com.bobyrev.pcaphobos.link + CFBundleURLSchemes + + phobos + + + + CFBundleVersion + 1 + UILaunchScreen + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + + + diff --git a/ios/App/PCAPhobos.entitlements b/ios/App/PCAPhobos.entitlements new file mode 100644 index 0000000..ffab33e --- /dev/null +++ b/ios/App/PCAPhobos.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.developer.networking.networkextension + + packet-tunnel-provider + + + diff --git a/ios/App/PCAPhobosApp.swift b/ios/App/PCAPhobosApp.swift new file mode 100644 index 0000000..740dd56 --- /dev/null +++ b/ios/App/PCAPhobosApp.swift @@ -0,0 +1,103 @@ +import NetworkExtension +import SwiftUI + +@main +struct PCAPhobosApp: App { + @StateObject private var vpn = VPNController() + + var body: some Scene { + WindowGroup { + ContentView() + .environmentObject(vpn) + .onOpenURL { vpn.importLink($0.absoluteString) } // phobos:// from the panel + } + } +} + +/// Owns the NETunnelProviderManager: profile, options, connect/disconnect, live status. +@MainActor +final class VPNController: ObservableObject { + @Published var name = UserDefaults.standard.string(forKey: "name") ?? "" + @Published var conf = UserDefaults.standard.string(forKey: "conf") ?? "" + @Published var ruDirect = UserDefaults.standard.object(forKey: "ruDirect") as? Bool ?? true { didSet { saveOptions() } } + @Published var transportMode = UserDefaults.standard.string(forKey: "transport") ?? "auto" { didSet { saveOptions() } } + @Published var status: NEVPNStatus = .invalid + @Published var transport = "" + @Published var message = "" + + private var manager: NETunnelProviderManager? + private var timer: Timer? + + init() { + Task { await load() } + NotificationCenter.default.addObserver(forName: .NEVPNStatusDidChange, object: nil, queue: .main) { [weak self] _ in + Task { @MainActor in self?.status = self?.manager?.connection.status ?? .invalid } + } + timer = Timer.scheduledTimer(withTimeInterval: 2, repeats: true) { [weak self] _ in + Task { @MainActor in self?.pollTransport() } + } + } + + var profile: PhobosProfile? { conf.isEmpty ? nil : PhobosProfile(conf) } + var isOn: Bool { status == .connected || status == .connecting || status == .reasserting } + + func load() async { + let all = (try? await NETunnelProviderManager.loadAllFromPreferences()) ?? [] + manager = all.first + status = manager?.connection.status ?? .invalid + } + + func importLink(_ link: String) { + guard let parsed = PhobosProfile.fromLink(link) else { message = "Это не ссылка phobos:// из панели"; return } + conf = parsed.profile.raw + name = parsed.name + UserDefaults.standard.set(conf, forKey: "conf") + UserDefaults.standard.set(name, forKey: "name") + message = "Профиль «\(name)» импортирован" + Task { await save(reconnect: isOn) } + } + + private func saveOptions() { + UserDefaults.standard.set(ruDirect, forKey: "ruDirect") + UserDefaults.standard.set(transportMode, forKey: "transport") + Task { await save(reconnect: isOn) } + } + + /// Write the VPN configuration to iOS settings (asks the user once). + func save(reconnect: Bool = false) async { + guard let profile else { return } + let m = manager ?? NETunnelProviderManager() + let proto = NETunnelProviderProtocol() + proto.providerBundleIdentifier = "com.bobyrev.pcaphobos.tunnel" + proto.serverAddress = profile.targetHost ?? "Phobos" + proto.providerConfiguration = ["conf": conf, "ruDirect": ruDirect, "transport": transportMode] + m.protocolConfiguration = proto + m.localizedDescription = "PCA Phobos" + m.isEnabled = true + do { + try await m.saveToPreferences() + try await m.loadFromPreferences() + manager = m + if reconnect { m.connection.stopVPNTunnel(); try await Task.sleep(nanoseconds: 1_000_000_000); try m.connection.startVPNTunnel() } + } catch { + message = "Не удалось сохранить VPN: \(error.localizedDescription)" + } + } + + func toggle() { + Task { + if isOn { manager?.connection.stopVPNTunnel(); return } + guard profile != nil else { message = "Сначала импортируйте ссылку phobos:// из панели"; return } + await save() + do { try manager?.connection.startVPNTunnel() } catch { message = "Ошибка: \(error.localizedDescription)" } + } + } + + private func pollTransport() { + guard status == .connected, let session = manager?.connection as? NETunnelProviderSession else { transport = ""; return } + try? session.sendProviderMessage(Data()) { [weak self] data in + guard let data, let obj = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { return } + Task { @MainActor in self?.transport = obj["transport"] as? String ?? "" } + } + } +} diff --git a/ios/PCAPhobos.xcodeproj/project.pbxproj b/ios/PCAPhobos.xcodeproj/project.pbxproj new file mode 100644 index 0000000..44d583f --- /dev/null +++ b/ios/PCAPhobos.xcodeproj/project.pbxproj @@ -0,0 +1,572 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXBuildFile section */ + 008DA398D56DFBDB16D97CE1 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF14952A4263FF0E004A3F04 /* ContentView.swift */; }; + 08C4842C3185E04B2154C971 /* WireGuardKit in Frameworks */ = {isa = PBXBuildFile; productRef = FA829997FAB330F6EFA104C9 /* WireGuardKit */; }; + 11F2A6A8D281AB36B45D4603 /* TunnelConfiguration+WgQuickConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C1AA6DED9B45949864A9B7D /* TunnelConfiguration+WgQuickConfig.swift */; }; + 1C05D29ACA95548374AD6FD9 /* logging.c in Sources */ = {isa = PBXBuildFile; fileRef = 9FC04650634F03E6DB09AB6B /* logging.c */; settings = {COMPILER_FLAGS = "-Dmain=wgobf_main -w"; }; }; + 38870650A7C2B1F0927A1230 /* wg-obfuscator.c in Sources */ = {isa = PBXBuildFile; fileRef = 1FADBB2A65AF77D4ECCAFE78 /* wg-obfuscator.c */; settings = {COMPILER_FLAGS = "-Dmain=wgobf_main -w"; }; }; + 4E4255B9E0F8626F6E209B0F /* String+ArrayConversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7500F0B31797D49B5BBEC5CC /* String+ArrayConversion.swift */; }; + 56F35C68C6A387E499703CFF /* masking.c in Sources */ = {isa = PBXBuildFile; fileRef = CCE5C98CD905BDE16A8D83D4 /* masking.c */; settings = {COMPILER_FLAGS = "-Dmain=wgobf_main -w"; }; }; + 5F5D376513C0B27C45A3E7B6 /* ru_bypass.txt in Resources */ = {isa = PBXBuildFile; fileRef = 2572D9A6F822B038D0652619 /* ru_bypass.txt */; }; + 78B74F81E0042D9AD94B7A6B /* config.c in Sources */ = {isa = PBXBuildFile; fileRef = 0E5C1673464AAC3EF81DD2FE /* config.c */; settings = {COMPILER_FLAGS = "-Dmain=wgobf_main -w"; }; }; + 7D8AB66421409CBFCCF2769A /* PhobosProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98DC1765DEC5F326E2B5F9C4 /* PhobosProfile.swift */; }; + 99F5DB52833E3B5505EEA727 /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E36BA501FDCB2034F09415A /* PacketTunnelProvider.swift */; }; + B3BB8BE3409D63FEB59B5449 /* obfuscation.c in Sources */ = {isa = PBXBuildFile; fileRef = EF9115CA0D5EC52C8679A12A /* obfuscation.c */; settings = {COMPILER_FLAGS = "-Dmain=wgobf_main -w"; }; }; + C3C41D3C419E8D17F20A38B5 /* PCAPhobosApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3E470E13EF603CB26991683 /* PCAPhobosApp.swift */; }; + C6116E8B9CDFEE363BCBD2D6 /* PhobosTunnel.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = A81F96BF4131FDBB816623BE /* PhobosTunnel.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + CBC338B91B5FCE1E207B36AC /* PhobosProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98DC1765DEC5F326E2B5F9C4 /* PhobosProfile.swift */; }; + D8CCC1E6CC8164EDD4E34D25 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6359B8954E69147DA50EDB46 /* Assets.xcassets */; }; + DF6D62C526E908CA5CDDFCAE /* pcarelay.c in Sources */ = {isa = PBXBuildFile; fileRef = 211950F8776D3D14D7B9C49A /* pcarelay.c */; }; + E88105F9325A93041D617213 /* masking_stun.c in Sources */ = {isa = PBXBuildFile; fileRef = 05DD44245829A52D32B42AF8 /* masking_stun.c */; settings = {COMPILER_FLAGS = "-Dmain=wgobf_main -w"; }; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + B9875EF5DEB6362919B43F22 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = E59D1112D2B128DF6FE7A1E9 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 0FD066BC2C51A1E092914FE8; + remoteInfo = PhobosTunnel; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9FC25420ED5EF973CE140584 /* Embed Foundation Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + C6116E8B9CDFEE363BCBD2D6 /* PhobosTunnel.appex in Embed Foundation Extensions */, + ); + name = "Embed Foundation Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 027878C5E0654DB27FAE88A3 /* PCAPhobos.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = PCAPhobos.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 05DD44245829A52D32B42AF8 /* masking_stun.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = masking_stun.c; sourceTree = ""; }; + 0E5C1673464AAC3EF81DD2FE /* config.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = config.c; sourceTree = ""; }; + 1FADBB2A65AF77D4ECCAFE78 /* wg-obfuscator.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = "wg-obfuscator.c"; sourceTree = ""; }; + 211950F8776D3D14D7B9C49A /* pcarelay.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = pcarelay.c; sourceTree = ""; }; + 2572D9A6F822B038D0652619 /* ru_bypass.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = ru_bypass.txt; sourceTree = ""; }; + 2C1AA6DED9B45949864A9B7D /* TunnelConfiguration+WgQuickConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TunnelConfiguration+WgQuickConfig.swift"; sourceTree = ""; }; + 6359B8954E69147DA50EDB46 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 722DE8EFC35A88BC68637FB4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 7500F0B31797D49B5BBEC5CC /* String+ArrayConversion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+ArrayConversion.swift"; sourceTree = ""; }; + 7E66B3AD4BB22D2AB5676E10 /* PCAPhobos.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = PCAPhobos.entitlements; sourceTree = ""; }; + 8E36BA501FDCB2034F09415A /* PacketTunnelProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelProvider.swift; sourceTree = ""; }; + 98DC1765DEC5F326E2B5F9C4 /* PhobosProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhobosProfile.swift; sourceTree = ""; }; + 9FC04650634F03E6DB09AB6B /* logging.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = logging.c; sourceTree = ""; }; + A81F96BF4131FDBB816623BE /* PhobosTunnel.appex */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = "wrapper.app-extension"; path = PhobosTunnel.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + B3E470E13EF603CB26991683 /* PCAPhobosApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PCAPhobosApp.swift; sourceTree = ""; }; + CA6480536DA633DB94B6930F /* WireGuardKit */ = {isa = PBXFileReference; lastKnownFileType = folder; name = WireGuardKit; path = WireGuardKit; sourceTree = SOURCE_ROOT; }; + CCE5C98CD905BDE16A8D83D4 /* masking.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = masking.c; sourceTree = ""; }; + EF9115CA0D5EC52C8679A12A /* obfuscation.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = obfuscation.c; sourceTree = ""; }; + FF14952A4263FF0E004A3F04 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + D366EA9C7C0EC88CAE5EA47E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 08C4842C3185E04B2154C971 /* WireGuardKit in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 1EBBB94A186F7080FE3609C8 /* Products */ = { + isa = PBXGroup; + children = ( + 027878C5E0654DB27FAE88A3 /* PCAPhobos.app */, + A81F96BF4131FDBB816623BE /* PhobosTunnel.appex */, + ); + name = Products; + sourceTree = ""; + }; + 2500D202DC9E96E7BBCE29B1 /* Tunnel */ = { + isa = PBXGroup; + children = ( + 8E36BA501FDCB2034F09415A /* PacketTunnelProvider.swift */, + 211950F8776D3D14D7B9C49A /* pcarelay.c */, + 2572D9A6F822B038D0652619 /* ru_bypass.txt */, + 7500F0B31797D49B5BBEC5CC /* String+ArrayConversion.swift */, + 2C1AA6DED9B45949864A9B7D /* TunnelConfiguration+WgQuickConfig.swift */, + ); + path = Tunnel; + sourceTree = ""; + }; + 527268DF962A8B7EC120C536 /* obfuscator */ = { + isa = PBXGroup; + children = ( + 0E5C1673464AAC3EF81DD2FE /* config.c */, + 9FC04650634F03E6DB09AB6B /* logging.c */, + 05DD44245829A52D32B42AF8 /* masking_stun.c */, + CCE5C98CD905BDE16A8D83D4 /* masking.c */, + EF9115CA0D5EC52C8679A12A /* obfuscation.c */, + 1FADBB2A65AF77D4ECCAFE78 /* wg-obfuscator.c */, + ); + name = obfuscator; + path = Tunnel/obfuscator; + sourceTree = ""; + }; + D4CB33FA621F53C8F82ED115 /* Packages */ = { + isa = PBXGroup; + children = ( + CA6480536DA633DB94B6930F /* WireGuardKit */, + ); + name = Packages; + sourceTree = ""; + }; + DD1FD95C0B2DE48623872799 /* App */ = { + isa = PBXGroup; + children = ( + 6359B8954E69147DA50EDB46 /* Assets.xcassets */, + FF14952A4263FF0E004A3F04 /* ContentView.swift */, + 722DE8EFC35A88BC68637FB4 /* Info.plist */, + 7E66B3AD4BB22D2AB5676E10 /* PCAPhobos.entitlements */, + B3E470E13EF603CB26991683 /* PCAPhobosApp.swift */, + ); + path = App; + sourceTree = ""; + }; + E31449B836D353F74991D905 = { + isa = PBXGroup; + children = ( + DD1FD95C0B2DE48623872799 /* App */, + 527268DF962A8B7EC120C536 /* obfuscator */, + D4CB33FA621F53C8F82ED115 /* Packages */, + F6FC482145D1EFAD5A15F63B /* Shared */, + 2500D202DC9E96E7BBCE29B1 /* Tunnel */, + 1EBBB94A186F7080FE3609C8 /* Products */, + ); + sourceTree = ""; + }; + F6FC482145D1EFAD5A15F63B /* Shared */ = { + isa = PBXGroup; + children = ( + 98DC1765DEC5F326E2B5F9C4 /* PhobosProfile.swift */, + ); + path = Shared; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 0FD066BC2C51A1E092914FE8 /* PhobosTunnel */ = { + isa = PBXNativeTarget; + buildConfigurationList = 329F6A5A13B95B1525F94841 /* Build configuration list for PBXNativeTarget "PhobosTunnel" */; + buildPhases = ( + 173BA4C39EE769A34EBB19CB /* Sources */, + A5352C1E8C48FFB2CCEC38F3 /* Resources */, + D366EA9C7C0EC88CAE5EA47E /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = PhobosTunnel; + packageProductDependencies = ( + FA829997FAB330F6EFA104C9 /* WireGuardKit */, + ); + productName = PhobosTunnel; + productReference = A81F96BF4131FDBB816623BE /* PhobosTunnel.appex */; + productType = "com.apple.product-type.app-extension"; + }; + E1F7E69029753CF102C6CE2E /* PCAPhobos */ = { + isa = PBXNativeTarget; + buildConfigurationList = 61EF055D39FD21A29FA039D5 /* Build configuration list for PBXNativeTarget "PCAPhobos" */; + buildPhases = ( + F83DEE91B85335727CA6B39E /* Sources */, + D18596DD6D613F6BC9647644 /* Resources */, + 9FC25420ED5EF973CE140584 /* Embed Foundation Extensions */, + ); + buildRules = ( + ); + dependencies = ( + 0B334C04E5AD898D8736FFAA /* PBXTargetDependency */, + ); + name = PCAPhobos; + packageProductDependencies = ( + ); + productName = PCAPhobos; + productReference = 027878C5E0654DB27FAE88A3 /* PCAPhobos.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + E59D1112D2B128DF6FE7A1E9 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1430; + TargetAttributes = { + 0FD066BC2C51A1E092914FE8 = { + DevelopmentTeam = ""; + ProvisioningStyle = Automatic; + }; + E1F7E69029753CF102C6CE2E = { + DevelopmentTeam = ""; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = F73680B923D76C65D03B442C /* Build configuration list for PBXProject "PCAPhobos" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + Base, + en, + ); + mainGroup = E31449B836D353F74991D905; + minimizedProjectReferenceProxies = 1; + packageReferences = ( + 9F8EC62EA6724F5FA6B8BAE0 /* XCLocalSwiftPackageReference "WireGuardKit" */, + ); + preferredProjectObjectVersion = 77; + productRefGroup = 1EBBB94A186F7080FE3609C8 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + E1F7E69029753CF102C6CE2E /* PCAPhobos */, + 0FD066BC2C51A1E092914FE8 /* PhobosTunnel */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + A5352C1E8C48FFB2CCEC38F3 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5F5D376513C0B27C45A3E7B6 /* ru_bypass.txt in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D18596DD6D613F6BC9647644 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D8CCC1E6CC8164EDD4E34D25 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 173BA4C39EE769A34EBB19CB /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 99F5DB52833E3B5505EEA727 /* PacketTunnelProvider.swift in Sources */, + 7D8AB66421409CBFCCF2769A /* PhobosProfile.swift in Sources */, + 4E4255B9E0F8626F6E209B0F /* String+ArrayConversion.swift in Sources */, + 11F2A6A8D281AB36B45D4603 /* TunnelConfiguration+WgQuickConfig.swift in Sources */, + 78B74F81E0042D9AD94B7A6B /* config.c in Sources */, + 1C05D29ACA95548374AD6FD9 /* logging.c in Sources */, + 56F35C68C6A387E499703CFF /* masking.c in Sources */, + E88105F9325A93041D617213 /* masking_stun.c in Sources */, + B3BB8BE3409D63FEB59B5449 /* obfuscation.c in Sources */, + DF6D62C526E908CA5CDDFCAE /* pcarelay.c in Sources */, + 38870650A7C2B1F0927A1230 /* wg-obfuscator.c in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F83DEE91B85335727CA6B39E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 008DA398D56DFBDB16D97CE1 /* ContentView.swift in Sources */, + C3C41D3C419E8D17F20A38B5 /* PCAPhobosApp.swift in Sources */, + CBC338B91B5FCE1E207B36AC /* PhobosProfile.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 0B334C04E5AD898D8736FFAA /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 0FD066BC2C51A1E092914FE8 /* PhobosTunnel */; + targetProxy = B9875EF5DEB6362919B43F22 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 35473CF1EA2AAAC350F346D0 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = App/PCAPhobos.entitlements; + CODE_SIGN_IDENTITY = "iPhone Developer"; + INFOPLIST_FILE = App/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.bobyrev.pcaphobos; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 62739003B63EE4EE3B3FF1FB /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = Tunnel/PhobosTunnel.entitlements; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = x86_64; + HEADER_SEARCH_PATHS = ( + "$(PROJECT_DIR)/Tunnel", + "$(PROJECT_DIR)/Tunnel/obfuscator", + ); + INFOPLIST_FILE = Tunnel/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + "LIBRARY_SEARCH_PATHS[sdk=iphoneos*]" = ( + "$(PROJECT_DIR)/WireGuardGo/iphoneos", + ); + "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*]" = ( + "$(PROJECT_DIR)/WireGuardGo/iphonesimulator", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.bobyrev.pcaphobos.tunnel; + SDKROOT = iphoneos; + SWIFT_OBJC_BRIDGING_HEADER = "Tunnel/Tunnel-Bridging-Header.h"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 7ABA8220043DEF78154F9F38 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 141; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = ""; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + MARKETING_VERSION = 1.4.1; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + B5D2F9B70DD37D3A57737EED /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 141; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = ""; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "DEBUG=1", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + MARKETING_VERSION = 1.4.1; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + D0AEA32C3C7BFB88918AB206 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = Tunnel/PhobosTunnel.entitlements; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = x86_64; + HEADER_SEARCH_PATHS = ( + "$(PROJECT_DIR)/Tunnel", + "$(PROJECT_DIR)/Tunnel/obfuscator", + ); + INFOPLIST_FILE = Tunnel/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + "LIBRARY_SEARCH_PATHS[sdk=iphoneos*]" = ( + "$(PROJECT_DIR)/WireGuardGo/iphoneos", + ); + "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*]" = ( + "$(PROJECT_DIR)/WireGuardGo/iphonesimulator", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.bobyrev.pcaphobos.tunnel; + SDKROOT = iphoneos; + SWIFT_OBJC_BRIDGING_HEADER = "Tunnel/Tunnel-Bridging-Header.h"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + D42BF07983FD43EBB6D84762 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = App/PCAPhobos.entitlements; + CODE_SIGN_IDENTITY = "iPhone Developer"; + INFOPLIST_FILE = App/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.bobyrev.pcaphobos; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 329F6A5A13B95B1525F94841 /* Build configuration list for PBXNativeTarget "PhobosTunnel" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D0AEA32C3C7BFB88918AB206 /* Debug */, + 62739003B63EE4EE3B3FF1FB /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; + 61EF055D39FD21A29FA039D5 /* Build configuration list for PBXNativeTarget "PCAPhobos" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D42BF07983FD43EBB6D84762 /* Debug */, + 35473CF1EA2AAAC350F346D0 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; + F73680B923D76C65D03B442C /* Build configuration list for PBXProject "PCAPhobos" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + B5D2F9B70DD37D3A57737EED /* Debug */, + 7ABA8220043DEF78154F9F38 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; +/* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 9F8EC62EA6724F5FA6B8BAE0 /* XCLocalSwiftPackageReference "WireGuardKit" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = WireGuardKit; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + FA829997FAB330F6EFA104C9 /* WireGuardKit */ = { + isa = XCSwiftPackageProductDependency; + productName = WireGuardKit; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = E59D1112D2B128DF6FE7A1E9 /* Project object */; +} diff --git a/ios/PCAPhobos.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/PCAPhobos.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/ios/PCAPhobos.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ios/README.md b/ios/README.md new file mode 100644 index 0000000..ee9dc76 --- /dev/null +++ b/ios/README.md @@ -0,0 +1,26 @@ +# PCA Phobos VPN — iPhone + +То же, что Android-клиент: импорт `phobos://` из панели, обфускация (wg-obfuscator внутри +расширения), **российские сайты напрямую**, канал **Авто/UDP/TCP** (при блокировке UDP — сам на TCP). +Выбор отдельных приложений на iOS невозможен (Apple даёт это только через MDM) — делим по адресам. + +Устройство: +- `App/` — SwiftUI-приложение (профиль, кнопка, настройки), `Shared/PhobosProfile.swift` — разбор ссылки. +- `Tunnel/` — Packet Tunnel: WireGuardKit → 127.0.0.1:13255 wg-obfuscator (`-Dmain=wgobf_main`, один + экземпляр: iOS запрещает fork) → 127.0.0.1:13258 `pcarelay.c` → сервер по UDP или TCP (формат udptcp). +- `WireGuardKit/` — wireguard-apple (MIT), `WireGuardGo//libwg-go.a` — собран `make` (iphoneos) и + `go build -buildmode c-archive` с `-target arm64-apple-ios15.0-simulator` (симулятор). + +Сборка: `xcodegen generate` → `PCAPhobos.xcodeproj`. Проверка без телефона: `Tests/ProfileCheck`. + +## Установка на iPhone (нужен ПЛАТНЫЙ Apple Developer, $99/год) +Network Extension (packet-tunnel-provider) Apple выдаёт только платным аккаунтам — с бесплатным +Xcode не подпишет даже одно приложение. Когда аккаунт есть: +1. В `project.yml` вписать `DEVELOPMENT_TEAM: `, `xcodegen generate`. +2. Xcode → Signing: включить capability Network Extensions (Packet Tunnel) для обоих таргетов. +3. iPhone по кабелю → Run, либо Product → Archive → TestFlight (тестировщики по e-mail, 90 дней). +4. В панели клиента → 📱 → открыть ссылку `phobos://` на iPhone → «Открыть в PCA Phobos» → Подключить + → разрешить добавление VPN. + +Проверено 23.10.2026: сборка под iphoneos и симулятор, UI и импорт реальной ссылки в симуляторе. +Сам туннель в симуляторе iOS не работает — проверяется только на телефоне. diff --git a/ios/Shared/PhobosProfile.swift b/ios/Shared/PhobosProfile.swift new file mode 100644 index 0000000..05bac0c --- /dev/null +++ b/ios/Shared/PhobosProfile.swift @@ -0,0 +1,84 @@ +import Foundation + +/// Parsed phobos:// payload from the PCA Phobos panel: +/// [Interface] + [Peer] (WireGuard) + [instance] (wg-obfuscator) + "# pca-tcp = ". +struct PhobosProfile { + let raw: String + + init(_ raw: String) { self.raw = raw } + + /// phobos://#name + static func fromLink(_ link: String) -> (profile: PhobosProfile, name: String)? { + var s = link.trimmingCharacters(in: .whitespacesAndNewlines) + guard s.hasPrefix("phobos://") else { return nil } + s.removeFirst("phobos://".count) + var name = "Phobos" + if let hash = s.firstIndex(of: "#") { + name = String(s[s.index(after: hash)...]).removingPercentEncoding ?? name + s = String(s[.. [(key: String, line: String)] { + var inSection = false + var out: [(String, String)] = [] + for rawLine in raw.split(separator: "\n") { + let line = rawLine.trimmingCharacters(in: .whitespaces) + if line.hasPrefix("[") && line.hasSuffix("]") { inSection = line.lowercased() == header.lowercased(); continue } + guard inSection, !line.hasPrefix("#"), let eq = line.firstIndex(of: "=") else { continue } + let value = line[line.index(after: eq)...].trimmingCharacters(in: .whitespaces) + if value.lowercased() == "none" { continue } + out.append((line[.. String? { + lines(header).first { $0.key == key }.map { line in + line.line[line.line.index(after: line.line.firstIndex(of: "=")!)...].trimmingCharacters(in: .whitespaces) + } + } + + var target: String? { value("[instance]", "target") } + var targetHost: String? { target.map { String($0[..<($0.lastIndex(of: ":") ?? $0.endIndex)]) } } + var targetPort: Int? { target.flatMap { $0.split(separator: ":").last.flatMap { Int($0) } } } + + /// "# pca-tcp = 443" written by the panel when the server has the TCP fallback. + var tcpPort: Int? { + for rawLine in raw.split(separator: "\n") { + let line = rawLine.trimmingCharacters(in: .whitespaces) + if line.hasPrefix("#"), line.contains("pca-tcp"), let eq = line.firstIndex(of: "=") { + return Int(line[line.index(after: eq)...].trimmingCharacters(in: .whitespaces)) + } + } + return nil + } + + func obfuscatorConfig(listen: Int, target: String) -> String { + var out = "[instance]\nsource-if = 127.0.0.1\nsource-lport = \(listen)\ntarget = \(target)\n" + for item in lines("[instance]") where !["source-if", "source-lport", "target", "verbose"].contains(item.key) { + out += item.line + "\n" + } + return out + "verbose = INFO\n" + } + + func wireGuardConfig(endpoint: String, allowedIPs: String?) -> String { + var out = "[Interface]\n" + for item in lines("[Interface]") where !item.key.hasSuffix("applications") { out += item.line + "\n" } + out += "\n[Peer]\n" + for item in lines("[Peer]") { + switch item.key { + case "endpoint": out += "Endpoint = \(endpoint)\n" + case "allowedips" where allowedIPs != nil: out += "AllowedIPs = \(allowedIPs!)\n" + default: out += item.line + "\n" + } + } + return out + } +} diff --git a/ios/Tests/ProfileCheck/main.swift b/ios/Tests/ProfileCheck/main.swift new file mode 100644 index 0000000..e42aef3 --- /dev/null +++ b/ios/Tests/ProfileCheck/main.swift @@ -0,0 +1,38 @@ +// Self-check of phobos:// parsing (no device needed): +// cd ios/Tests/ProfileCheck && xcrun swiftc -o /tmp/pc ../../Shared/PhobosProfile.swift main.swift && /tmp/pc +import Foundation +let conf = """ +[Interface] +PrivateKey = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= +Address = 10.25.0.9/32 +DNS = 1.1.1.1 +MTU = 1420 + +[Peer] +PublicKey = BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB= +PresharedKey = none +Endpoint = 127.0.0.1:51820 +AllowedIPs = 0.0.0.0/0, ::/0 +PersistentKeepalive = 25 + +[instance] +source-if = 127.0.0.1 +source-lport = 51820 +target = 203.0.113.10:2083 +key = testkey +masking = STUN +verbose = INFO +idle-timeout = 300 +max-dummy = 4 +# pca-tcp = 443 +""" +let link = "phobos://" + Data(conf.utf8).base64EncodedString() + .replacingOccurrences(of: "+", with: "-").replacingOccurrences(of: "/", with: "_") + .replacingOccurrences(of: "=", with: "") + "#demo" +let (p, name) = PhobosProfile.fromLink(link)! +precondition(name == "demo" && p.targetHost == "203.0.113.10" && p.targetPort == 2083 && p.tcpPort == 443) +let obf = p.obfuscatorConfig(listen: 13255, target: "127.0.0.1:13258") +precondition(obf.contains("target = 127.0.0.1:13258") && obf.contains("key = testkey") && !obf.contains("203.0.113.10")) +let wg = p.wireGuardConfig(endpoint: "127.0.0.1:13255", allowedIPs: "1.0.0.0/8") +precondition(wg.contains("Endpoint = 127.0.0.1:13255") && wg.contains("AllowedIPs = 1.0.0.0/8") && !wg.contains("= none")) +print("profile_check ok") diff --git a/ios/Tunnel/Info.plist b/ios/Tunnel/Info.plist new file mode 100644 index 0000000..60c53ac --- /dev/null +++ b/ios/Tunnel/Info.plist @@ -0,0 +1,31 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + PhobosTunnel + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + XPC! + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + NSExtension + + NSExtensionPointIdentifier + com.apple.networkextension.packet-tunnel + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).PacketTunnelProvider + + + diff --git a/ios/Tunnel/PacketTunnelProvider.swift b/ios/Tunnel/PacketTunnelProvider.swift new file mode 100644 index 0000000..ce40613 --- /dev/null +++ b/ios/Tunnel/PacketTunnelProvider.swift @@ -0,0 +1,92 @@ +import Foundation +import NetworkExtension +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"). +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) + + private lazy var adapter = WireGuardAdapter(with: self) { _, message in NSLog("wg: %@", message) } + + override func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) { + let pc = (protocolConfiguration as? NETunnelProviderProtocol)?.providerConfiguration ?? [:] + guard let conf = pc["conf"] as? String, !conf.isEmpty else { + completionHandler(NSError(domain: "PCAPhobos", code: 1, userInfo: [NSLocalizedDescriptionKey: "нет профиля"])) + return + } + let ruDirect = pc["ruDirect"] as? Bool ?? true + let transport = pc["transport"] as? String ?? "auto" + let profile = PhobosProfile(conf) + + guard let host = profile.targetHost, let udpPort = profile.targetPort else { + completionHandler(NSError(domain: "PCAPhobos", code: 2, userInfo: [NSLocalizedDescriptionKey: "в профиле нет адреса сервера"])) + return + } + let tcpPort = profile.tcpPort ?? 0 + if transport == "tcp" && tcpPort == 0 { + completionHandler(NSError(domain: "PCAPhobos", code: 3, userInfo: [NSLocalizedDescriptionKey: "на сервере нет TCP-канала"])) + return + } + + pcarelay_use_tcp(transport == "tcp" ? 1 : 0) + if !Self.obfuscatorStarted { + Self.obfuscatorStarted = true + pcarelay_start(Int32(Self.relayPort), host, Int32(udpPort), Int32(tcpPort)) + let path = NSTemporaryDirectory() + "obf.conf" + try? profile.obfuscatorConfig(listen: Self.wgPort, target: "127.0.0.1:\(Self.relayPort)") + .write(toFile: path, atomically: true, encoding: .utf8) + Thread.detachNewThread { + var args: [UnsafeMutablePointer?] = ["wg-obfuscator", "-c", path].map { strdup($0) } + _ = wgobf_main(Int32(args.count), &args) + } + } + + let allowed = ruDirect ? Self.ruBypassAllowedIPs() : nil + let wgText = profile.wireGuardConfig(endpoint: "127.0.0.1:\(Self.wgPort)", allowedIPs: allowed) + let tunnelConfig: TunnelConfiguration + do { + tunnelConfig = try TunnelConfiguration(fromWgQuickConfig: wgText, called: "phobos") + } catch { + completionHandler(error) + return + } + adapter.start(tunnelConfiguration: tunnelConfig) { [weak self] error in + completionHandler(error) + if error == nil && transport == "auto" && tcpPort != 0 { self?.watchUdp() } + } + } + + /// Auto: nothing came back over UDP within 15 s -> UDP is cut, move the relay to TCP. + private func watchUdp() { + DispatchQueue.global().asyncAfter(deadline: .now() + 15) { + if pcarelay_is_tcp() == 0 && pcarelay_rx_bytes() == 0 { + NSLog("PCAPhobos: no UDP answer in 15 s -> TCP") + pcarelay_use_tcp(1) + } + } + } + + override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { + adapter.stop { _ in + completionHandler() + // the obfuscator thread cannot be stopped cleanly; a fresh process starts next time + exit(0) + } + } + + /// 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", + "rx": pcarelay_rx_bytes()] + completionHandler?(try? JSONSerialization.data(withJSONObject: status)) + } + + private static func ruBypassAllowedIPs() -> 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) + } +} diff --git a/ios/Tunnel/PhobosTunnel.entitlements b/ios/Tunnel/PhobosTunnel.entitlements new file mode 100644 index 0000000..ffab33e --- /dev/null +++ b/ios/Tunnel/PhobosTunnel.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.developer.networking.networkextension + + packet-tunnel-provider + + + diff --git a/ios/Tunnel/String+ArrayConversion.swift b/ios/Tunnel/String+ArrayConversion.swift new file mode 100644 index 0000000..97984f8 --- /dev/null +++ b/ios/Tunnel/String+ArrayConversion.swift @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved. + +import Foundation + +extension String { + + func splitToArray(separator: Character = ",", trimmingCharacters: CharacterSet? = nil) -> [String] { + return split(separator: separator) + .map { + if let charSet = trimmingCharacters { + return $0.trimmingCharacters(in: charSet) + } else { + return String($0) + } + } + } + +} + +extension Optional where Wrapped == String { + + func splitToArray(separator: Character = ",", trimmingCharacters: CharacterSet? = nil) -> [String] { + switch self { + case .none: + return [] + case .some(let wrapped): + return wrapped.splitToArray(separator: separator, trimmingCharacters: trimmingCharacters) + } + } + +} diff --git a/ios/Tunnel/Tunnel-Bridging-Header.h b/ios/Tunnel/Tunnel-Bridging-Header.h new file mode 100644 index 0000000..8e6dc13 --- /dev/null +++ b/ios/Tunnel/Tunnel-Bridging-Header.h @@ -0,0 +1 @@ +#include "pcarelay.h" diff --git a/ios/Tunnel/TunnelConfiguration+WgQuickConfig.swift b/ios/Tunnel/TunnelConfiguration+WgQuickConfig.swift new file mode 100644 index 0000000..d8cc11e --- /dev/null +++ b/ios/Tunnel/TunnelConfiguration+WgQuickConfig.swift @@ -0,0 +1,253 @@ +// SPDX-License-Identifier: MIT +// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved. + +import Foundation +import WireGuardKit + +extension TunnelConfiguration { + + enum ParserState { + case inInterfaceSection + case inPeerSection + case notInASection + } + + enum ParseError: Error { + case invalidLine(String.SubSequence) + case noInterface + case multipleInterfaces + case interfaceHasNoPrivateKey + case interfaceHasInvalidPrivateKey(String) + case interfaceHasInvalidListenPort(String) + case interfaceHasInvalidAddress(String) + case interfaceHasInvalidDNS(String) + case interfaceHasInvalidMTU(String) + case interfaceHasUnrecognizedKey(String) + case peerHasNoPublicKey + case peerHasInvalidPublicKey(String) + case peerHasInvalidPreSharedKey(String) + case peerHasInvalidAllowedIP(String) + case peerHasInvalidEndpoint(String) + case peerHasInvalidPersistentKeepAlive(String) + case peerHasInvalidTransferBytes(String) + case peerHasInvalidLastHandshakeTime(String) + case peerHasUnrecognizedKey(String) + case multiplePeersWithSamePublicKey + case multipleEntriesForKey(String) + } + + convenience init(fromWgQuickConfig wgQuickConfig: String, called name: String? = nil) throws { + var interfaceConfiguration: InterfaceConfiguration? + var peerConfigurations = [PeerConfiguration]() + + let lines = wgQuickConfig.split { $0.isNewline } + + var parserState = ParserState.notInASection + var attributes = [String: String]() + + for (lineIndex, line) in lines.enumerated() { + var trimmedLine: String + if let commentRange = line.range(of: "#") { + trimmedLine = String(line[.. = ["privatekey", "listenport", "address", "dns", "mtu"] + let peerSectionKeys: Set = ["publickey", "presharedkey", "allowedips", "endpoint", "persistentkeepalive"] + if parserState == .inInterfaceSection { + guard interfaceSectionKeys.contains(key) else { + throw ParseError.interfaceHasUnrecognizedKey(keyWithCase) + } + } else if parserState == .inPeerSection { + guard peerSectionKeys.contains(key) else { + throw ParseError.peerHasUnrecognizedKey(keyWithCase) + } + } + } else if lowercasedLine != "[interface]" && lowercasedLine != "[peer]" { + throw ParseError.invalidLine(line) + } + } + + let isLastLine = lineIndex == lines.count - 1 + + if isLastLine || lowercasedLine == "[interface]" || lowercasedLine == "[peer]" { + // Previous section has ended; process the attributes collected so far + if parserState == .inInterfaceSection { + let interface = try TunnelConfiguration.collate(interfaceAttributes: attributes) + guard interfaceConfiguration == nil else { throw ParseError.multipleInterfaces } + interfaceConfiguration = interface + } else if parserState == .inPeerSection { + let peer = try TunnelConfiguration.collate(peerAttributes: attributes) + peerConfigurations.append(peer) + } + } + + if lowercasedLine == "[interface]" { + parserState = .inInterfaceSection + attributes.removeAll() + } else if lowercasedLine == "[peer]" { + parserState = .inPeerSection + attributes.removeAll() + } + } + + let peerPublicKeysArray = peerConfigurations.map { $0.publicKey } + let peerPublicKeysSet = Set(peerPublicKeysArray) + if peerPublicKeysArray.count != peerPublicKeysSet.count { + throw ParseError.multiplePeersWithSamePublicKey + } + + if let interfaceConfiguration = interfaceConfiguration { + self.init(name: name, interface: interfaceConfiguration, peers: peerConfigurations) + } else { + throw ParseError.noInterface + } + } + + func asWgQuickConfig() -> String { + var output = "[Interface]\n" + output.append("PrivateKey = \(interface.privateKey.base64Key)\n") + if let listenPort = interface.listenPort { + output.append("ListenPort = \(listenPort)\n") + } + if !interface.addresses.isEmpty { + let addressString = interface.addresses.map { $0.stringRepresentation }.joined(separator: ", ") + output.append("Address = \(addressString)\n") + } + if !interface.dns.isEmpty || !interface.dnsSearch.isEmpty { + var dnsLine = interface.dns.map { $0.stringRepresentation } + dnsLine.append(contentsOf: interface.dnsSearch) + let dnsString = dnsLine.joined(separator: ", ") + output.append("DNS = \(dnsString)\n") + } + if let mtu = interface.mtu { + output.append("MTU = \(mtu)\n") + } + + for peer in peers { + output.append("\n[Peer]\n") + output.append("PublicKey = \(peer.publicKey.base64Key)\n") + if let preSharedKey = peer.preSharedKey?.base64Key { + output.append("PresharedKey = \(preSharedKey)\n") + } + if !peer.allowedIPs.isEmpty { + let allowedIPsString = peer.allowedIPs.map { $0.stringRepresentation }.joined(separator: ", ") + output.append("AllowedIPs = \(allowedIPsString)\n") + } + if let endpoint = peer.endpoint { + output.append("Endpoint = \(endpoint.stringRepresentation)\n") + } + if let persistentKeepAlive = peer.persistentKeepAlive { + output.append("PersistentKeepalive = \(persistentKeepAlive)\n") + } + } + + return output + } + + private static func collate(interfaceAttributes attributes: [String: String]) throws -> InterfaceConfiguration { + guard let privateKeyString = attributes["privatekey"] else { + throw ParseError.interfaceHasNoPrivateKey + } + guard let privateKey = PrivateKey(base64Key: privateKeyString) else { + throw ParseError.interfaceHasInvalidPrivateKey(privateKeyString) + } + var interface = InterfaceConfiguration(privateKey: privateKey) + if let listenPortString = attributes["listenport"] { + guard let listenPort = UInt16(listenPortString) else { + throw ParseError.interfaceHasInvalidListenPort(listenPortString) + } + interface.listenPort = listenPort + } + if let addressesString = attributes["address"] { + var addresses = [IPAddressRange]() + for addressString in addressesString.splitToArray(trimmingCharacters: .whitespacesAndNewlines) { + guard let address = IPAddressRange(from: addressString) else { + throw ParseError.interfaceHasInvalidAddress(addressString) + } + addresses.append(address) + } + interface.addresses = addresses + } + if let dnsString = attributes["dns"] { + var dnsServers = [DNSServer]() + var dnsSearch = [String]() + for dnsServerString in dnsString.splitToArray(trimmingCharacters: .whitespacesAndNewlines) { + if let dnsServer = DNSServer(from: dnsServerString) { + dnsServers.append(dnsServer) + } else { + dnsSearch.append(dnsServerString) + } + } + interface.dns = dnsServers + interface.dnsSearch = dnsSearch + } + if let mtuString = attributes["mtu"] { + guard let mtu = UInt16(mtuString) else { + throw ParseError.interfaceHasInvalidMTU(mtuString) + } + interface.mtu = mtu + } + return interface + } + + private static func collate(peerAttributes attributes: [String: String]) throws -> PeerConfiguration { + guard let publicKeyString = attributes["publickey"] else { + throw ParseError.peerHasNoPublicKey + } + guard let publicKey = PublicKey(base64Key: publicKeyString) else { + throw ParseError.peerHasInvalidPublicKey(publicKeyString) + } + var peer = PeerConfiguration(publicKey: publicKey) + if let preSharedKeyString = attributes["presharedkey"] { + guard let preSharedKey = PreSharedKey(base64Key: preSharedKeyString) else { + throw ParseError.peerHasInvalidPreSharedKey(preSharedKeyString) + } + peer.preSharedKey = preSharedKey + } + if let allowedIPsString = attributes["allowedips"] { + var allowedIPs = [IPAddressRange]() + for allowedIPString in allowedIPsString.splitToArray(trimmingCharacters: .whitespacesAndNewlines) { + guard let allowedIP = IPAddressRange(from: allowedIPString) else { + throw ParseError.peerHasInvalidAllowedIP(allowedIPString) + } + allowedIPs.append(allowedIP) + } + peer.allowedIPs = allowedIPs + } + if let endpointString = attributes["endpoint"] { + guard let endpoint = Endpoint(from: endpointString) else { + throw ParseError.peerHasInvalidEndpoint(endpointString) + } + peer.endpoint = endpoint + } + if let persistentKeepAliveString = attributes["persistentkeepalive"] { + guard let persistentKeepAlive = UInt16(persistentKeepAliveString) else { + throw ParseError.peerHasInvalidPersistentKeepAlive(persistentKeepAliveString) + } + peer.persistentKeepAlive = persistentKeepAlive + } + return peer + } + +} diff --git a/ios/Tunnel/obfuscator/config.c b/ios/Tunnel/obfuscator/config.c new file mode 100644 index 0000000..75c2477 --- /dev/null +++ b/ios/Tunnel/obfuscator/config.c @@ -0,0 +1,535 @@ +#include +#include +#include +#include +#include "config.h" +#include "wg-obfuscator.h" +#include "mini_argp.h" +#include "masking.h" + +// Executable name +static const char *arg0; + +/* The options we understand. */ +static const mini_argp_opt options[] = { + { "help", '?', 0 }, + { "config", 'c', 1 }, + { "source-if", 'i', 1 }, + { "source", 's', 1 }, + { "source-lport", 'p', 1 }, + { "target-if", 'o', 1 }, + { "target", 't', 1 }, + { "target-lport", 'r', 1 }, + { "key", 'k', 1 }, + { "masking", 'a', 1 }, + { "static-bindings", 'b', 1 }, + { "max-clients" , 'm', 1 }, + { "idle-timeout", 'l', 1 }, + { "in-timeout", 'n', 1 }, + { "max-dummy", 'd', 1 }, + { "fwmark", 'f', 1 }, + { "allow-clean", 'e', 0 }, + { "verbose", 'v', 1 }, + { "log-file", 'L', 1 }, + { "log-timestamps", 'T', 1 }, + { "resolve-interval", 'R', 1 }, + { 0 } +}; + +static void show_usage(void) +{ + printf("Usage: %s [options]\n%s", arg0, + " -?, --help Give this help list\n" + "\n" + "Main settings:\n" + " -c, --config= Read configuration from file\n" + " (can be used instead of the rest arguments)\n" + " -i, --source-if= Source interface to listen on\n" + " (optional, default - 0.0.0.0, e.g. all)\n" + " -p, --source-lport= Source port to listen\n" + " -t, --target=: Target IP and port\n" + " -k, --key= Obfuscation key \n" + " (required, must be 1-255 characters long)\n" + " -a, --masking= Masking type (optional, default - AUTO)\n" + " Supported values: STUN, AUTO, NONE\n" + " -b, --static-bindings=::,...\n" + " Comma-separated static bindings for two-way mode\n" + " as ::\n" + " You can also repeat this option.\n" + " -f, --fwmark= Firewall mark to set on all packets\n" + " (optional, default - 0, e.g. disabled)\n" + " -v, --verbose= Verbosity level (optional, default - INFO)\n" + " ERRORS (critical errors only)\n" + " WARNINGS (important messages)\n" + " 2 - INFO (informational messages: status messages,\n" + " connection established, etc.)\n" + " 3 - DEBUG (detailed debug messages)\n" + " 4 - TRACE (very detailed debug messages, including\n" + " packet dumps)\n" + " -L, --log-file= Write the log to this file instead of stderr\n" + " (optional, the file is reopened on SIGHUP)\n" + " -T, --log-timestamps= Prefix log lines with a timestamp\n" + " (optional, default - AUTO, e.g. only when\n" + " writing to a log file)\n" + " Supported values: AUTO, TRUE, FALSE\n" + "\n" + "Additional options:\n" + " -m, --max-clients= Maximum number of clients (default: 1024)\n" + " -l, --idle-timeout= Idle timeout in seconds (default: 300)\n" + " -n, --in-timeout= Incoming timeout in seconds (default: 0 - disabled)\n" + " -d, --max-dummy= Maximum length of dummy bytes for data packets\n" + " (default: 4)\n" + " -e, --allow-clean For servers, allow non-obfuscated incoming connections\n" + " -R, --resolve-interval=\n" + " Re-resolve target and static-binding hostnames\n" + " every N seconds (default: 0 - disabled).\n" + " SIGHUP always triggers a refresh.\n" + " If non-zero, a failed resolve at startup is retried\n" + " instead of exiting.\n"); +} + +static int parse_opt(const char *lname, char sname, const char *val, void *ctx); + +/** + * @brief Resets the configuration structure to its default values. + * + * This function clears the configuration structure by setting all fields to zero + * and resetting the verbosity level to the default value. + * + * @param config Pointer to the obfuscator_config structure to be reset. + */ +static void reset_config(obfuscator_config_t *config) +{ + if (config->static_bindings) { + free(config->static_bindings); + } + memset(config, 0, sizeof(*config)); + config->max_clients = MAX_CLIENTS_DEFAULT; + config->idle_timeout = IDLE_TIMEOUT_DEFAULT; + config->in_timeout = IN_TIMEOUT_DEFAULT; + config->max_dummy_length_data = MAX_DUMMY_LENGTH_DATA_DEFAULT; + config->log_timestamps = -1; // auto + verbose = LL_DEFAULT; +} + +/** + * Checks if the given string represents a valid integer. + * + * @param str Pointer to the null-terminated string to check. + * @return Non-zero value if the string is a valid integer, 0 otherwise. + */ +static uint8_t is_integer(const char *str) +{ + if (!str || !*str) { + return 0; // Empty string is not an integer + } + while (*str) { + if (!isdigit((unsigned char)*str)) { + return 0; // Non-digit character found + } + str++; + } + return 1; // All characters are digits +} + +/** + * Parses a boolean value from a string. + * + * @param str Pointer to the null-terminated string to parse. + * @return 1 for true values ("true", "yes", "on", "1"), + * 0 for false values ("false", "no", "off", "0"), + * -1 if the string is not a valid boolean value. + */ +static int parse_bool(const char *str) +{ + char lower[16]; + if (!str || !*str || strlen(str) >= sizeof(lower)) { + return -1; + } + strcpy(lower, str); + for (char *p = lower; *p; ++p) *p = tolower((unsigned char)*p); + if (!strcmp(lower, "true") || !strcmp(lower, "yes") || !strcmp(lower, "on") || !strcmp(lower, "1")) { + return 1; + } + if (!strcmp(lower, "false") || !strcmp(lower, "no") || !strcmp(lower, "off") || !strcmp(lower, "0")) { + return 0; + } + return -1; +} + +/** + * @brief Reads and processes the configuration file. + * + * This function opens the specified configuration file and parses its contents + * to initialize or update the application's configuration settings. + * + * @param filename The path to the configuration file to be read. + * @param config Pointer to the obfuscator_config structure where the parsed settings will be stored. + */ +static void read_config_file(const char *filename, obfuscator_config_t *config) +{ + // Read configuration from the file + uint8_t first_section = 1; // Flag to indicate if this is the first section being processed + char line[10 * 1024]; // Buffer to hold each line from the config file + + FILE *config_file = fopen(filename, "r"); + if (config_file == NULL) { + perror("Can't open config file"); + exit(EXIT_FAILURE); + } + + while (fgets(line, sizeof(line), config_file)) { + // Remove trailing newlines, carriage returns, spaces and tabs + while (strlen(line) && (line[strlen(line) - 1] == '\n' || line[strlen(line) - 1] == '\r' + || line[strlen(line) - 1] == ' ' || line[strlen(line) - 1] == '\t')) { + line[strlen(line) - 1] = 0; + } + // Remove leading spaces and tabs + while (strlen(line) && (line[0] == ' ' || line[0] == '\t')) { + memmove(line, line + 1, strlen(line)); + } + // Ignore comments + char *comment_index = strstr(line, "#"); + if (comment_index != NULL) { + *comment_index = 0; + } + // Skip empty lines or with spaces only + size_t line_len = strlen(line); + if (strspn(line, " \t\r\n") == line_len) { + continue; + } + + // It can be new section + if (line_len > 0 && line[0] == '[' && line[line_len - 1] == ']') { + if (!first_section) { + // new config, need to fork the process + pid_t pid = fork(); + if (pid < 0) { + log(LL_ERROR, "Can't fork a new instance - %s (%d)", strerror(errno), errno); + exit(EXIT_FAILURE); + } + if (pid == 0) { + // Close in the child process + fclose(config_file); + // Stop config file processing for this instance + return; + } + // Remember the instance to be able to forward signals to it + register_child_instance(pid); + } + size_t len = strlen(line) - 2; + if (len > sizeof(section_name) - 1) { + len = sizeof(section_name) - 1; + } + strncpy(section_name, line + 1, len); + section_name[len] = 0; + + // Reset all the parameters + reset_config(config); + + first_section = 0; // We have processed the first section + continue; + } + + // Parse key-value pairs + char *key = strtok(line, "="); + key = trim(key); + while (strlen(key) && (key[strlen(key) - 1] == ' ' || key[strlen(key) - 1] == '\t' || key[strlen(key) - 1] == '\r' || key[strlen(key) - 1] == '\n')) { + key[strlen(key) - 1] = 0; + } + char *value = strtok(NULL, "="); + if (value == NULL) { + log(LL_ERROR, "Invalid configuration line: %s", line); + exit(EXIT_FAILURE); + } + value = trim(value); + if (!*value) { + log(LL_ERROR, "Invalid configuration line: %s", line); + exit(EXIT_FAILURE); + } + const mini_argp_opt *o = margp_find(options, key, 0); + if (o == NULL) { + log(LL_ERROR, "Unknown configuration key: %s", key); + exit(EXIT_FAILURE); + } + if (!o->has_arg) { + // Flag options are represented as booleans in the config file + int b = parse_bool(value); + if (b < 0) { + log(LL_ERROR, "Configuration key '%s' accepts only boolean values (true/yes/on/1 or false/no/off/0)", key); + exit(EXIT_FAILURE); + } + if (!b) { + continue; + } + value = NULL; + } + parse_opt(o->long_name, o->short_name, value, config); + } + fclose(config_file); +} + +/* Parse a single option. */ +static int parse_opt(const char *lname, char sname, const char *val, void *ctx) +{ + obfuscator_config_t *config = (obfuscator_config_t *)ctx; + char val_lower[16]; + + switch (sname) + { + case '?': + // Show usage and exit + show_usage(); + exit(EXIT_SUCCESS); + case 'c': + read_config_file(val, config); + break; + case 'i': + strncpy(config->client_interface, val, sizeof(config->client_interface) - 1); + config->client_interface[sizeof(config->client_interface) - 1] = 0; // Ensure null-termination + config->client_interface_set = 1; + break; + case 'p': + if (!is_integer(val)) { + log(LL_ERROR, "Invalid source port: %s (must be an integer)", val); + exit(EXIT_FAILURE); + } + config->listen_port = atoi(val); + if (config->listen_port <= 0 || config->listen_port > 65535) { + log(LL_ERROR, "Invalid listen port: %s (must be between 1 and 65535)", val); + exit(EXIT_FAILURE); + } + config->listen_port_set = 1; + break; + case 't': + strncpy(config->forward_host_port, val, sizeof(config->forward_host_port) - 1); + config->forward_host_port[sizeof(config->forward_host_port) - 1] = 0; // Ensure null-termination + config->forward_host_port_set = 1; + break; + case 'b': + { + char *new_bindings; + if (!config->static_bindings) { + new_bindings = strdup(val); + } else { + size_t old_len = strlen(config->static_bindings); + new_bindings = realloc(config->static_bindings, old_len + strlen(val) + 2); + if (new_bindings) { + new_bindings[old_len] = ','; + strcpy(new_bindings + old_len + 1, val); + } + } + if (!new_bindings) { + log(LL_ERROR, "Out of memory while parsing static-bindings"); + exit(EXIT_FAILURE); + } + config->static_bindings = new_bindings; + config->static_bindings_set = 1; + } + break; + case 'k': + strncpy(config->xor_key, val, sizeof(config->xor_key)); + config->xor_key[sizeof(config->xor_key) - 1] = 0; // Ensure null-termination + if (strlen(config->xor_key) == 0) { + log(LL_ERROR, "XOR key cannot be empty"); + exit(EXIT_FAILURE); + } + config->xor_key_set = 1; + break; + case 'm': + if (!is_integer(val)) { + log(LL_ERROR, "Invalid maximum number of clients: %s (must be an integer)", val); + exit(EXIT_FAILURE); + } + config->max_clients = atoi(val); + if (config->max_clients <= 0) { + log(LL_ERROR, "Invalid maximum number of clients: %s (must be greater than 0)", val); + exit(EXIT_FAILURE); + } + break; + case 'l': + if (!is_integer(val)) { + log(LL_ERROR, "Invalid idle timeout: %s (must be an integer)", val); + exit(EXIT_FAILURE); + } + config->idle_timeout = atol(val); + if (config->idle_timeout <= 0) { + log(LL_ERROR, "Invalid idle timeout: %s (must be greater than 0)", val); + exit(EXIT_FAILURE); + } + config->idle_timeout *= 1000; // Convert to milliseconds + break; + case 'n': + if (!is_integer(val)) { + log(LL_ERROR, "Invalid incoming timeout: %s (must be an integer)", val); + exit(EXIT_FAILURE); + } + config->in_timeout = atol(val); + if (config->in_timeout <= 0) { + log(LL_ERROR, "Invalid incoming timeout: %s (must be greater than 0)", val); + exit(EXIT_FAILURE); + } + config->in_timeout *= 1000; // Convert to milliseconds + break; + case 'd': + if (!is_integer(val)) { + log(LL_ERROR, "Invalid maximum dummy length for data packets: %s (must be an integer)", val); + exit(EXIT_FAILURE); + } + config->max_dummy_length_data = atoi(val); + if (config->max_dummy_length_data < 0 || config->max_dummy_length_data > MAX_DUMMY_LENGTH_TOTAL) { + log(LL_ERROR, "Invalid maximum dummy length for data packets: %s (must be between 0 and %d)", val, MAX_DUMMY_LENGTH_TOTAL); + exit(EXIT_FAILURE); + } + break; + case 'e': + config->allow_clean = 1; + break; + case 'f': + // parse string with decimal and hexadecimal support +#ifdef __linux__ + { + long int v = strtol(val, NULL, 0); + if (v <= 0 || v > UINT16_MAX) { + log(LL_ERROR, "Invalid firewall mark: %s", val); + exit(EXIT_FAILURE); + } + config->fwmark = (uint16_t)v; + } +#else + log(LL_WARN, "Firewall mark is not supported on this platform"); +#endif + break; + case 'a': + { + strncpy(val_lower, val, sizeof(val_lower) - 1); + val_lower[sizeof(val_lower) - 1] = 0; + for (char *p = val_lower; *p; ++p) *p = tolower((unsigned char)*p); + if (strcmp(val_lower, "none") == 0) { + config->masking_handler = NULL; + config->masking_handler_set = 1; + break; + } + if (strcmp(val_lower, "auto") == 0) { + config->masking_handler = NULL; + config->masking_handler_set = 0; + break; + } + masking_handler_t *handler = get_masking_handler_by_name(val_lower); + if (handler == NULL) { + log(LL_ERROR, "Unknown masking type: %s", val); + exit(EXIT_FAILURE); + } + config->masking_handler = handler; + config->masking_handler_set = 1; + } + break; + case 'v': + strncpy(val_lower, val, sizeof(val_lower) - 1); + val_lower[sizeof(val_lower) - 1] = 0; + for (char *p = val_lower; *p; ++p) *p = tolower((unsigned char)*p); + if (strcmp(val_lower, "error") == 0) { + verbose = LL_ERROR; + } else if (strcmp(val_lower, "warn") == 0) { + verbose = LL_WARN; + } else if (strcmp(val_lower, "info") == 0) { + verbose = LL_INFO; + } else if (strcmp(val_lower, "debug") == 0) { + verbose = LL_DEBUG; + } else if (strcmp(val_lower, "trace") == 0) { + verbose = LL_TRACE; + } else { + // check if it's a number + if (is_integer(val)) { + verbose = atoi(val); + if (verbose < 0 || verbose > 4) { + log(LL_ERROR, "Invalid verbosity level: %s (must be one of 'ERROR', 'WARN', 'INFO', 'DEBUG', 'TRACE')", val); + exit(EXIT_FAILURE); + } + } else { + log(LL_ERROR, "Invalid verbosity level: %s (must be one of 'ERROR', 'WARN', 'INFO', 'DEBUG', 'TRACE')", val); + exit(EXIT_FAILURE); + } + } + break; + case 'L': + strncpy(config->log_file, val, sizeof(config->log_file) - 1); + config->log_file[sizeof(config->log_file) - 1] = 0; // Ensure null-termination + if (strlen(config->log_file) == 0) { + log(LL_ERROR, "Log file path cannot be empty"); + exit(EXIT_FAILURE); + } + config->log_file_set = 1; + break; + case 'T': + { + strncpy(val_lower, val, sizeof(val_lower) - 1); + val_lower[sizeof(val_lower) - 1] = 0; + for (char *p = val_lower; *p; ++p) *p = tolower((unsigned char)*p); + if (strcmp(val_lower, "auto") == 0) { + config->log_timestamps = -1; + break; + } + int b = parse_bool(val_lower); + if (b < 0) { + log(LL_ERROR, "Invalid log timestamps mode: %s (must be one of 'AUTO', 'TRUE', 'FALSE')", val); + exit(EXIT_FAILURE); + } + config->log_timestamps = b; + } + break; + case 'R': + if (!is_integer(val)) { + log(LL_ERROR, "Invalid resolve interval: %s (must be an integer)", val); + exit(EXIT_FAILURE); + } + config->resolve_interval = atol(val); + if (config->resolve_interval < 0) { + log(LL_ERROR, "Invalid resolve interval: %s (must be 0 or greater)", val); + exit(EXIT_FAILURE); + } + config->resolve_interval *= 1000; // Convert to milliseconds + break; + default: + // should never happen + return -1; + } + return 0; +} + +int parse_config(int argc, char **argv, obfuscator_config_t *config) +{ + /* Parse command line arguments */ + reset_config(config); + arg0 = argv[0]; // Save the executable name + if (argc == 1) { + fprintf(stderr, "No arguments provided, use \"%s --help\" command for usage information\n", argv[0]); + return -1; + } + if (mini_argp_parse(argc, argv, options, config, parse_opt) != 0) { + fprintf(stderr, "Failed to parse command line arguments\n"); + return -1; + } + + return 0; +} + +/** + * @brief Removes leading and trailing whitespace characters from the input string. + * + * This function modifies the input string in place by trimming any whitespace + * characters (such as spaces, tabs, or newlines) from both the beginning and end. + * + * @param s Pointer to the null-terminated string to be trimmed. + * @return Pointer to the trimmed string. + */ +char *trim(char *s) { + char *end; + // Trim leading spaces, tabs, carriage returns and newlines + while (*s && (*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n')) s++; + if (!*s) return s; + // Trim trailing spaces, tabs, carriage returns and newlines + end = s + strlen(s) - 1; + while (end > s && (*end == ' ' || *end == '\t' || *end == '\r' || *end == '\n')) *end-- = 0; + return s; +} diff --git a/ios/Tunnel/obfuscator/config.h b/ios/Tunnel/obfuscator/config.h new file mode 100644 index 0000000..1083bc5 --- /dev/null +++ b/ios/Tunnel/obfuscator/config.h @@ -0,0 +1,9 @@ +#ifndef _CONFIG_H_ +#define _CONFIG_H_ + +#include "wg-obfuscator.h" + +char *trim(char *s); +int parse_config(int argc, char **argv, obfuscator_config_t *config); + +#endif // _CONFIG_H_ diff --git a/ios/Tunnel/obfuscator/logging.c b/ios/Tunnel/obfuscator/logging.c new file mode 100644 index 0000000..3b809f1 --- /dev/null +++ b/ios/Tunnel/obfuscator/logging.c @@ -0,0 +1,258 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "wg-obfuscator.h" + +#ifndef O_BINARY +#define O_BINARY 0 +#endif + +#ifndef STDERR_FILENO +#define STDERR_FILENO 2 +#endif + +// Log files may contain client addresses, so they are not world-readable +#define LOG_FILE_MODE 0640 + +// File descriptor of the log file, -1 when logging to stderr +static int log_fd = -1; +// Path of the log file, kept to be able to reopen it on SIGHUP +static char *log_path = NULL; +// 1 if every line must be prefixed with a timestamp +static int log_timestamps = 0; + +/** + * @brief Returns the single character representing a logging level. + */ +static char level_char(int level) +{ + switch (level) { + case LL_ERROR: return 'E'; + case LL_WARN: return 'W'; + case LL_INFO: return 'I'; + case LL_DEBUG: return 'D'; + case LL_TRACE: return 'T'; + default: return '?'; + } +} + +/** + * @brief Writes the whole buffer to the descriptor, retrying on partial writes. + * + * @param fd Descriptor to write to. + * @param buf Data to write. + * @param len Number of bytes to write. + */ +static void write_all(int fd, const char *buf, size_t len) +{ + while (len) { + ssize_t written = write(fd, buf, len); + if (written <= 0) { + if (written < 0 && errno == EINTR) { + continue; + } + // Nothing sensible can be done about a broken log, drop the rest of the line + return; + } + buf += written; + len -= (size_t)written; + } +} + +/** + * @brief Appends formatted text to the buffer without ever overflowing it. + * + * @param buf Buffer to append to. + * @param size Usable size of the buffer, including the terminating zero. + * @param len Current length of the text in the buffer. + * @param fmt Format string. + * @param args Arguments for the format string. + * @return New length of the text in the buffer. + */ +static size_t append_vfmt(char *buf, size_t size, size_t len, const char *fmt, va_list args) +{ + int n; + + if (len + 1 >= size) { + return len; + } + n = vsnprintf(buf + len, size - len, fmt, args); + if (n < 0) { + return len; + } + if ((size_t)n >= size - len) { + return size - 1; // truncated + } + return len + (size_t)n; +} + +/** + * @brief Appends formatted text to the buffer without ever overflowing it. + * + * @param buf Buffer to append to. + * @param size Usable size of the buffer, including the terminating zero. + * @param len Current length of the text in the buffer. + * @param fmt Format string. + * @return New length of the text in the buffer. + */ +static size_t append_fmt(char *buf, size_t size, size_t len, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + len = append_vfmt(buf, size, len, fmt, args); + va_end(args); + + return len; +} + +/** + * @brief Writes the common line prefix (timestamp and instance name) to the buffer. + * + * @param buf Buffer to write to. + * @param size Usable size of the buffer, including the terminating zero. + * @param level Logging level of the line. + * @return Length of the prefix. + */ +static size_t log_prefix(char *buf, size_t size, int level) +{ + size_t len = 0; + + if (log_timestamps) { + struct timespec ts; + struct tm *tm; + if (clock_gettime(CLOCK_REALTIME, &ts) == 0 && (tm = localtime(&ts.tv_sec)) != NULL) { + len = strftime(buf, size, "%Y-%m-%d %H:%M:%S", tm); + len = append_fmt(buf, size, len, ".%03ld ", (long)(ts.tv_nsec / 1000000)); + } + } + + return append_fmt(buf, size, len, "[%s][%c] ", section_name, level_char(level)); +} + +/** + * @brief Terminates the line and writes it out with a single write() call. + * + * A single write() to a descriptor opened with O_APPEND keeps lines of different + * instances from interleaving when they share the same log file. + * + * @param buf Buffer holding the line, must have one spare byte for the newline. + * @param len Length of the line. + */ +static void log_emit(char *buf, size_t len) +{ + buf[len++] = '\n'; + write_all(log_fd >= 0 ? log_fd : STDERR_FILENO, buf, len); +} + +/** + * @brief Writes a single log line. + * + * This is the implementation behind the log() macro, which is the one that + * checks the verbosity level, so the line written here is unconditional. + * + * @param level Logging level of the line. + * @param fmt Format string. + */ +void log_printf(int level, const char *fmt, ...) +{ + char buf[LOG_LINE_MAX]; + va_list args; + size_t len = log_prefix(buf, sizeof(buf) - 1, level); + + va_start(args, fmt); + len = append_vfmt(buf, sizeof(buf) - 1, len, fmt, args); + va_end(args); + + log_emit(buf, len); +} + +/** + * @brief Writes a hexadecimal dump of a buffer, one or more prefixed lines. + * + * @param level Logging level of the dump. + * @param prefix Text to put right after the line prefix, e.g. a direction marker. + * @param data Data to dump. + * @param length Number of bytes to dump. + */ +void log_hexdump(int level, const char *prefix, const uint8_t *data, int length) +{ + char buf[LOG_LINE_MAX]; + int i = 0; + + if (verbose < level) { + return; + } + + // Long dumps are split into several lines, each one with its own prefix + do { + size_t len = log_prefix(buf, sizeof(buf) - 1, level); + len = append_fmt(buf, sizeof(buf) - 1, len, "%s", prefix); + while ((i < length) && (len + 3 < sizeof(buf) - 1)) { + len = append_fmt(buf, sizeof(buf) - 1, len, "%02X ", data[i++]); + } + log_emit(buf, len); + } while (i < length); +} + +/** + * @brief Initializes logging. + * + * @param path Path of the log file or NULL to keep logging to stderr. + * @param timestamps_mode 1 to force timestamps on, 0 to force them off, + * negative to enable them only when logging to a file. + */ +void log_init(const char *path, int8_t timestamps_mode) +{ + log_timestamps = (timestamps_mode >= 0) ? (timestamps_mode != 0) : (path != NULL); + + if (!path) { + return; + } + + log_path = strdup(path); + if (!log_path) { + fprintf(stderr, "Out of memory while initializing the log file\n"); + exit(EXIT_FAILURE); + } + + log_fd = open(path, O_WRONLY | O_CREAT | O_APPEND | O_BINARY, LOG_FILE_MODE); + if (log_fd < 0) { + // Logging into nowhere is worse than not starting at all + fprintf(stderr, "Can't open log file '%s' - %s (%d)\n", path, strerror(errno), errno); + exit(EXIT_FAILURE); + } + + // Make every log file start with the version, no matter the verbosity level + log_printf(LL_INFO, "Starting %s", version_string()); +} + +/** + * @brief Reopens the log file, to be called after the log has been rotated. + */ +void log_reopen(void) +{ + int fd; + + if (!log_path) { + return; + } + + fd = open(log_path, O_WRONLY | O_CREAT | O_APPEND | O_BINARY, LOG_FILE_MODE); + if (fd < 0) { + // Keep the old descriptor, losing the log completely would be worse + fprintf(stderr, "Can't reopen log file '%s' - %s (%d)\n", log_path, strerror(errno), errno); + return; + } + if (log_fd >= 0) { + close(log_fd); + } + log_fd = fd; + + log_printf(LL_INFO, "Log file reopened, %s", version_string()); +} diff --git a/ios/Tunnel/obfuscator/masking.c b/ios/Tunnel/obfuscator/masking.c new file mode 100644 index 0000000..bc9b4e0 --- /dev/null +++ b/ios/Tunnel/obfuscator/masking.c @@ -0,0 +1,170 @@ +#include +#include +#include +#include "wg-obfuscator.h" +#include "masking.h" +#include "masking_handlers.h" + +static _Thread_local struct { + int listen_sock; + struct sockaddr_in *sender_addr; + int server_sock; +} g_send_ctx; + +static ssize_t send_to_client_cb(uint8_t *buffer, int length) { + return sendto(g_send_ctx.listen_sock, buffer, length, 0, (struct sockaddr *)g_send_ctx.sender_addr, sizeof(*g_send_ctx.sender_addr)); +} + +static ssize_t send_to_server_cb(uint8_t *buffer, int length) { + return send(g_send_ctx.server_sock, buffer, length, 0); +} + +masking_handler_t * get_masking_handler_by_name(const char *name) { + if (!name) return NULL; + + for (int i = 0; masking_handlers[i]; ++i) { + char handler_name_lower[MASKING_HANDLER_NAME_LEN]; + strncpy(handler_name_lower, masking_handlers[i]->name, sizeof(handler_name_lower) - 1); + handler_name_lower[sizeof(handler_name_lower) - 1] = 0; + for (char *p = handler_name_lower; *p; ++p) *p = tolower((unsigned char)*p); + + if (strcmp(name, handler_name_lower) == 0) { + return masking_handlers[i]; + } + } + return NULL; +} + +void masking_on_handshake_req_from_client(obfuscator_config_t *config, + client_entry_t *client, + int listen_sock, + struct sockaddr_in *client_addr, + struct sockaddr_in *server_addr) { + if (!client->masking_handler || !client->masking_handler->on_handshake_req) { + return; + } + + g_send_ctx.listen_sock = listen_sock; + g_send_ctx.sender_addr = &client->client_addr; + g_send_ctx.server_sock = client->server_sock; + client->masking_handler->on_handshake_req(config, client, DIR_CLIENT_TO_SERVER, client_addr, server_addr, send_to_client_cb, send_to_server_cb); +} + +void masking_on_handshake_req_from_server(obfuscator_config_t *config, + client_entry_t *client, + int listen_sock, + struct sockaddr_in *client_addr, + struct sockaddr_in *server_addr) { + if (!client->masking_handler || !client->masking_handler->on_handshake_req) { + return; + } + + g_send_ctx.listen_sock = listen_sock; + g_send_ctx.sender_addr = &client->client_addr; + g_send_ctx.server_sock = client->server_sock; + client->masking_handler->on_handshake_req(config, client, DIR_SERVER_TO_CLIENT, server_addr, client_addr, send_to_server_cb, send_to_client_cb); +} + +int masking_unwrap_from_client(uint8_t **buffer_ptr, int length, + obfuscator_config_t *config, + client_entry_t *client, // can be NULL! + int listen_sock, + struct sockaddr_in *client_addr, + struct sockaddr_in *server_addr, + masking_handler_t **masking_handler_out) { + g_send_ctx.listen_sock = listen_sock; + g_send_ctx.sender_addr = client_addr; + g_send_ctx.server_sock = client ? client->server_sock : 0; + + if (!client && !config->masking_handler_set) { + // Brueteforce detection of masking type if no client entry and no default masking handler + for (int i = 0; masking_handlers[i]; ++i) { + // A handler may move *buffer_ptr while unwrapping. If it does not match + // (returns < 0), restore the pointer so the next handler sees the original data. + uint8_t *saved_buffer = *buffer_ptr; + int r = masking_handlers[i]->on_data_unwrap(buffer_ptr, length, config, NULL, DIR_CLIENT_TO_SERVER, client_addr, server_addr, send_to_client_cb, send_to_server_cb); + if (r < 0) { + *buffer_ptr = saved_buffer; + continue; + } + // Found a matching masking handler + log(LL_TRACE, "Autodetected masking handler for packet from %s:%d: %s", inet_ntoa(client_addr->sin_addr), ntohs(client_addr->sin_port), masking_handlers[i]->name); + if (masking_handler_out) { + *masking_handler_out = masking_handlers[i]; + } + return r; + } + + // No matching masking handler found + if (masking_handler_out) { + *masking_handler_out = NULL; + } + return length; + } + + masking_handler_t *handler = client ? client->masking_handler : config->masking_handler; + if (!handler || !handler->on_data_unwrap) { + return length; // no masking handler, nothing to do + } + + return handler->on_data_unwrap(buffer_ptr, length, config, client, DIR_CLIENT_TO_SERVER, client_addr, server_addr, send_to_client_cb, client ? send_to_server_cb : NULL); +} + +int masking_unwrap_from_server(uint8_t **buffer_ptr, int length, + obfuscator_config_t *config, + client_entry_t *client, + int listen_sock, + struct sockaddr_in *server_addr) { + if (!client->masking_handler || !client->masking_handler->on_data_unwrap) { + return length; // no masking handler, nothing to do + } + + g_send_ctx.listen_sock = listen_sock; + g_send_ctx.sender_addr = &client->client_addr; + g_send_ctx.server_sock = client->server_sock; + return client->masking_handler->on_data_unwrap(buffer_ptr, length, config, client, DIR_SERVER_TO_CLIENT, server_addr, &client->client_addr, send_to_server_cb, send_to_client_cb); +} + +int masking_data_wrap_to_client(uint8_t **buffer_ptr, int length, + obfuscator_config_t *config, + client_entry_t *client, + int listen_sock, + struct sockaddr_in *server_addr) { + if (!client->masking_handler || !client->masking_handler->on_data_wrap) { + return length; // no masking handler, nothing to do + } + + g_send_ctx.listen_sock = listen_sock; + g_send_ctx.sender_addr = &client->client_addr; + g_send_ctx.server_sock = client->server_sock; + return client->masking_handler->on_data_wrap(buffer_ptr, length, config, client, DIR_SERVER_TO_CLIENT, server_addr, &client->client_addr, send_to_server_cb, send_to_client_cb); +} + +int masking_data_wrap_to_server(uint8_t **buffer_ptr, int length, + obfuscator_config_t *config, + client_entry_t *client, + int listen_sock, + struct sockaddr_in *server_addr) { + if (!client->masking_handler || !client->masking_handler->on_data_wrap) { + return length; // no masking handler, nothing to do + } + + g_send_ctx.listen_sock = listen_sock; + g_send_ctx.sender_addr = &client->client_addr; + g_send_ctx.server_sock = client->server_sock; + return client->masking_handler->on_data_wrap(buffer_ptr, length, config, client, DIR_CLIENT_TO_SERVER, &client->client_addr, server_addr, send_to_client_cb, send_to_server_cb); +} + +void masking_on_timer(obfuscator_config_t *config, + client_entry_t *client, + int listen_sock, + struct sockaddr_in *server_addr) { + if (!client->masking_handler || !client->masking_handler->on_timer) { + return; + } + + g_send_ctx.listen_sock = listen_sock; + g_send_ctx.sender_addr = &client->client_addr; + g_send_ctx.server_sock = client->server_sock; + client->masking_handler->on_timer(config, client, &client->client_addr, server_addr, send_to_client_cb, send_to_server_cb); +} diff --git a/ios/Tunnel/obfuscator/masking.h b/ios/Tunnel/obfuscator/masking.h new file mode 100644 index 0000000..4a9cc22 --- /dev/null +++ b/ios/Tunnel/obfuscator/masking.h @@ -0,0 +1,91 @@ +#ifndef _MASKING_H_ +#define _MASKING_H_ + +#include +#include +#include "wg-obfuscator.h" + +#define MASKING_HANDLER_NAME_LEN 32 + +typedef ssize_t (*send_data_callback_t)(uint8_t *buffer, int length); + +typedef void (*masking_event_handler_t)(obfuscator_config_t *config, + client_entry_t *client, + direction_t direction, + const struct sockaddr_in *src_addr, + const struct sockaddr_in *dest_addr, + send_data_callback_t send_back_callback, + send_data_callback_t send_forward_callback); + +typedef int (*masking_data_handler_t)(uint8_t **buffer_ptr, int length, + obfuscator_config_t *config, + client_entry_t *client, + direction_t direction, + const struct sockaddr_in *src_addr, + const struct sockaddr_in *dest_addr, + send_data_callback_t send_back_callback, + send_data_callback_t send_forward_callback); + +typedef void (*masking_timer_handler_t)(obfuscator_config_t *config, + client_entry_t *client, + const struct sockaddr_in *client_addr, + const struct sockaddr_in *server_addr, + send_data_callback_t send_to_client_callback, + send_data_callback_t send_to_server_callback); + +struct masking_handler { + char name[MASKING_HANDLER_NAME_LEN]; + masking_event_handler_t on_handshake_req; + masking_data_handler_t on_data_wrap; + masking_data_handler_t on_data_unwrap; + masking_timer_handler_t on_timer; + uint32_t timer_interval_s; +}; +typedef struct masking_handler masking_handler_t; + +masking_handler_t * get_masking_handler_by_name(const char *name); + +void masking_on_handshake_req_from_client(obfuscator_config_t *config, + client_entry_t *client, + int listen_sock, + struct sockaddr_in *client_addr, + struct sockaddr_in *server_addr); + +void masking_on_handshake_req_from_server(obfuscator_config_t *config, + client_entry_t *client, + int listen_sock, + struct sockaddr_in *client_addr, + struct sockaddr_in *server_addr); + +int masking_data_wrap_to_client(uint8_t **buffer_ptr, int length, + obfuscator_config_t *config, + client_entry_t *client, + int listen_sock, + struct sockaddr_in *server_addr); + +int masking_data_wrap_to_server(uint8_t **buffer_ptr, int length, + obfuscator_config_t *config, + client_entry_t *client, + int listen_sock, + struct sockaddr_in *server_addr); + +int masking_unwrap_from_client(uint8_t **buffer_ptr, int length, + obfuscator_config_t *config, + client_entry_t *client, + int listen_sock, + struct sockaddr_in *client_addr, + struct sockaddr_in *server_addr, + masking_handler_t **masking_handler_out); + +int masking_unwrap_from_server(uint8_t **buffer_ptr, int length, + obfuscator_config_t *config, + client_entry_t *client, + int listen_sock, + struct sockaddr_in *server_addr); + +void masking_on_timer(obfuscator_config_t *config, + client_entry_t *client, + int listen_sock, + struct sockaddr_in *server_addr); + +#endif // _MASKING_H_ diff --git a/ios/Tunnel/obfuscator/masking_handlers.h b/ios/Tunnel/obfuscator/masking_handlers.h new file mode 100644 index 0000000..c7745a7 --- /dev/null +++ b/ios/Tunnel/obfuscator/masking_handlers.h @@ -0,0 +1,14 @@ +#ifndef _MASKING_HANDLERS_H_ +#define _MASKING_HANDLERS_H_ + +#include "masking.h" + +/* List of available masking handlers */ +#include "masking_stun.h" + +static masking_handler_t * const masking_handlers[] = { + &stun_masking_handler, + NULL +}; + +#endif \ No newline at end of file diff --git a/ios/Tunnel/obfuscator/masking_stun.c b/ios/Tunnel/obfuscator/masking_stun.c new file mode 100644 index 0000000..0a274b9 --- /dev/null +++ b/ios/Tunnel/obfuscator/masking_stun.c @@ -0,0 +1,298 @@ +#include +#include +#include +#include +#include +#include +#include "wg-obfuscator.h" +#include "masking.h" +#include "masking_stun.h" + +// Bytes prepended in front of the payload when wrapping data in a STUN Data Indication +// (20-byte STUN header + 4-byte attribute header). Must fit into PREBUFFER_SIZE so that +// wrapping can be done by shifting the buffer pointer instead of moving the payload. +#define STUN_WRAP_OVERHEAD 24 +_Static_assert(STUN_WRAP_OVERHEAD <= PREBUFFER_SIZE, + "PREBUFFER_SIZE is too small to hold the STUN wrap header"); + +static void rand_bytes(uint8_t* p, size_t n){ for(size_t i=0;i> 8; + b[1] = type & 0xFF; + b[2] = mlen >> 8; + b[3] = mlen & 0xFF; + memcpy(b + 4, COOKIE_BE, 4); + memcpy(b + 8, txid, 12); + return 20; +} + +static int stun_attr_xor_mapped_addr(uint8_t *b, const struct sockaddr_in *src) { + // type,len + b[0] = STUN_ATTR_XORMAPPED >> 8; + b[1] = STUN_ATTR_XORMAPPED & 0xFF; + b[2] = 0; + b[3] = 8; // len=8 (family + port + addr) + b[4] = 0; + b[5] = 0x01; // family IPv4 + memcpy(b + 6, &src->sin_port, 2); // network-order bytes + ((uint8_t*)b)[6] ^= COOKIE_BE[0]; + ((uint8_t*)b)[7] ^= COOKIE_BE[1]; + memcpy(b + 8, &src->sin_addr.s_addr, 4); // network-order bytes + ((uint8_t*)b)[8] ^= COOKIE_BE[0]; + ((uint8_t*)b)[9] ^= COOKIE_BE[1]; + ((uint8_t*)b)[10] ^= COOKIE_BE[2]; + ((uint8_t*)b)[11] ^= COOKIE_BE[3]; + return 12; // 4 hdr + 8 val +} + +/* +static int stun_attr_software(uint8_t *b, const char *s) { + uint16_t n = (uint16_t)strlen(s); + uint16_t pad = (4 - (n & 3)) & 3; + if (b) { + b[0] = STUN_ATTR_SOFTWARE >> 8; + b[1] = STUN_ATTR_SOFTWARE&0xFF; + b[2] = n>>8; + b[3] = n&0xFF; + memcpy(b+4,s,n); + if (pad) memset(b+4+n,0,pad); + } + return 4 + n + pad; +} +*/ + +static uint32_t crc32(const uint8_t *p, size_t n) { + uint32_t crc = ~0u; + for(size_t i = 0; i < n; i++){ + crc ^= p[i]; + for(int k = 0; k < 8; k++) + crc = (crc >> 1) ^ (0xEDB88320u & (-(int)(crc & 1))); + } + return ~crc; +} + +static int stun_attr_fingerprint(uint8_t *pkt, size_t cur_len) { + uint8_t *b = pkt + cur_len; + b[0] = STUN_ATTR_FINGERPR >> 8; + b[1] = STUN_ATTR_FINGERPR & 0xFF; + b[2] = 0; + b[3] = 4; + uint32_t fp = htonl(crc32(pkt, cur_len) ^ 0x5354554eu); + memcpy(b + 4, &fp, 4); + return 8; // 4 hdr + 4 val +} + +static int stun_build_binding_request(uint8_t *out) { + uint8_t txid[12]; + rand_bytes(txid, 12); + stun_write_header(out, STUN_BINDING_REQ, 0, txid); + size_t mlen = 0; + // optional SOFTWARE + //mlen += stun_attr_software(out+20+mlen, "wgo/1.0"); + mlen += stun_attr_fingerprint(out, 20 + mlen); + out[2] = (mlen >> 8) & 0xFF; + out[3] = mlen & 0xFF; + return (int)(20 + mlen); +} + +static int stun_build_binding_success(uint8_t *out, + const uint8_t txid[12], + const struct sockaddr_in *src) { + if (BUFFER_SIZE < 20 + 12 + 8) return -1; // header + XOR-MAPPED-ADDRESS + fingerprint + stun_write_header(out, STUN_BINDING_RESP, 0, (uint8_t*)txid); + size_t mlen = 0; + mlen += stun_attr_xor_mapped_addr(out + 20 + mlen, src); + // optional SOFTWARE + //mlen += stun_attr_software(out+20+mlen, "wgo/1.0"); + mlen += stun_attr_fingerprint(out, 20 + mlen); + out[2] = (mlen >> 8) & 0xFF; + out[3] = mlen & 0xFF; + return (int)(20 + mlen); +} + +static int stun_wrap(uint8_t **buf_ptr, size_t data_len) { + size_t mlen = 0; + + // The caller guarantees the payload already fits into the main buffer (it was + // received/encoded within BUFFER_SIZE), so the wrapped packet cannot overflow + // the tail of full_buffer. The attribute length field is also 16-bit only. + // Note: BUFFER_SIZE (65535) equals the maximum value of a 16-bit unsigned + // integer (0xFFFF), so this single check covers both the buffer limit and + // the STUN attribute length field limit. + if (data_len > BUFFER_SIZE) { + log(LL_WARN, "Can't wrap data in STUN, data too large (%zu bytes)", data_len); + return -ENOMEM; + } + + // Header is prepended into the prebuffer (checked at compile time via _Static_assert). + *buf_ptr -= STUN_WRAP_OVERHEAD; + uint8_t *buf = *buf_ptr; + + uint8_t txid[12]; + rand_bytes(txid,12); + mlen += stun_write_header(buf, STUN_TYPE_DATA_IND, 0, txid); + + buf[mlen] = STUN_ATTR_DATA >> 8; + buf[mlen + 1] = STUN_ATTR_DATA & 0xFF; + buf[mlen + 2] = data_len >> 8; + buf[mlen + 3] = data_len & 0xFF; + + return STUN_WRAP_OVERHEAD + data_len; +} + +static int stun_unwrap(uint8_t **buf_ptr, size_t len) { + uint8_t *buf = *buf_ptr; + if (len < 20 + 4) return -1; // header + attr + + uint16_t msg_type = (buf[0] << 8) | buf[1]; + if (msg_type != STUN_TYPE_DATA_IND) return -1; + + uint16_t msg_len = (buf[2] << 8) | buf[3]; + if (msg_len + 20 > len) return -1; + + uint16_t attr_type = (buf[20] << 8) | buf[21]; + if (attr_type != STUN_ATTR_DATA) return -1; + + uint16_t data_len = (buf[22] << 8) | buf[23]; + if (data_len + 24 > len) return -1; + + // Move the buffer pointer to avoid moving the data + *buf_ptr += 24; + + return data_len; +} + +static void stun_on_handshake_req(obfuscator_config_t *config, + client_entry_t *client, + direction_t direction, + const struct sockaddr_in *src_addr, + const struct sockaddr_in *dest_addr, + send_data_callback_t send_back_callback, + send_data_callback_t send_forward_callback) { + uint8_t buffer[128]; + int len = stun_build_binding_request(buffer); + if (len < 0) return; + + int sent = send_forward_callback(buffer, len); + if (sent < 0) { + serror("Can't send STUN binding request to %s:%d", inet_ntoa(dest_addr->sin_addr), ntohs(dest_addr->sin_port)); + } else if (sent != len) { + log(LL_WARN, "Partial send of STUN binding request to %s:%d (%d of %d bytes)", inet_ntoa(dest_addr->sin_addr), ntohs(dest_addr->sin_port), sent, len); + } else { + log(LL_TRACE, "Sent STUN binding request (%d bytes) to %s:%d", len, inet_ntoa(dest_addr->sin_addr), ntohs(dest_addr->sin_port)); + } +} + +static int stun_on_data_unwrap(uint8_t **buffer_ptr, int length, + obfuscator_config_t *config, + client_entry_t *client, + direction_t direction, + const struct sockaddr_in *src_addr, + const struct sockaddr_in *dest_addr, + send_data_callback_t send_back_callback, + send_data_callback_t send_forward_callback) { + if (!stun_check_magic(*buffer_ptr, length)) { + return -EINVAL; + } + + uint16_t stun_type = stun_peek_type(*buffer_ptr); + + switch (stun_type) { + case STUN_BINDING_REQ: { + // Received STUN Binding Request from client, send Binding Success Response + log(LL_TRACE, "Received STUN Binding Request from %s:%d", inet_ntoa(src_addr->sin_addr), ntohs(src_addr->sin_port)); + uint8_t txid[12]; + memcpy(txid, (*buffer_ptr) + 8, 12); + int resp_len = stun_build_binding_success(*buffer_ptr, txid, src_addr); + if (resp_len > 0) { + int sent = send_back_callback(*buffer_ptr, resp_len); + if (sent < 0) { + serror("sendto STUN response to %s:%d", inet_ntoa(src_addr->sin_addr), ntohs(src_addr->sin_port)); + } else if (sent != resp_len) { + log(LL_WARN, "Partial send of STUN Binding Success Response to %s:%d (%d of %d bytes)", inet_ntoa(src_addr->sin_addr), ntohs(src_addr->sin_port), sent, resp_len); + } else { + log(LL_TRACE, "Sent STUN Binding Success Response (%d bytes) to %s:%d", resp_len, inet_ntoa(src_addr->sin_addr), ntohs(src_addr->sin_port)); + } + } else { + log(LL_ERROR, "Failed to build STUN Binding Success Response"); + } + return 0; + } + case STUN_BINDING_RESP: + log(LL_TRACE, "Received STUN Binding Success Response from %s:%d, ignoring", inet_ntoa(src_addr->sin_addr), ntohs(src_addr->sin_port)); + return 0; + case STUN_TYPE_DATA_IND: + length = stun_unwrap(buffer_ptr, length); + if (length < 0) { + log(LL_DEBUG, "Failed to unwrap STUN Data Indication from %s:%d", inet_ntoa(src_addr->sin_addr), ntohs(src_addr->sin_port)); + return length; + } + log(LL_TRACE, "Unwrapped STUN Data Indication from %s:%d (%d bytes)", inet_ntoa(src_addr->sin_addr), ntohs(src_addr->sin_port), length); + return length; + default: + log(LL_DEBUG, "Received unknown STUN type %04X from %s:%d, ignoring", stun_type, inet_ntoa(src_addr->sin_addr), ntohs(src_addr->sin_port)); + return 0; + } +} + +static int stun_on_data_wrap(uint8_t **buffer_ptr, int length, + obfuscator_config_t *config, + client_entry_t *client, + direction_t direction, + const struct sockaddr_in *src_addr, + const struct sockaddr_in *dest_addr, + send_data_callback_t send_back_callback, + send_data_callback_t send_forward_callback) { + return stun_wrap(buffer_ptr, length); +} + +static void stun_on_timer(obfuscator_config_t *config, + client_entry_t *client, + const struct sockaddr_in *client_addr, + const struct sockaddr_in *server_addr, + send_data_callback_t send_to_client_callback, + send_data_callback_t send_to_server_callback) { + uint8_t buffer[128]; + int len = stun_build_binding_request(buffer); + if (len < 0) return; + + if (client->client_obfuscated) { + int sent = send_to_client_callback(buffer, len); + if (sent < 0) { + serror("STUN binding request to client"); + } else if (sent != len) { + log(LL_WARN, "Partial send of STUN binding request to client (%d of %d bytes)", sent, len); + } else { + log(LL_TRACE, "Sent STUN binding request (%d bytes) to %s:%d", len, inet_ntoa(client_addr->sin_addr), ntohs(client_addr->sin_port)); + } + } + if (client->server_obfuscated) { + int sent = send_to_server_callback(buffer, len); + if (sent < 0) { + serror("STUN binding request to server"); + } else if (sent != len) { + log(LL_WARN, "Partial send of STUN binding request to server (%d of %d bytes)", sent, len); + } else { + log(LL_TRACE, "Sent STUN binding request (%d bytes) to %s:%d", len, inet_ntoa(server_addr->sin_addr), ntohs(server_addr->sin_port)); + } + } +} + +masking_handler_t stun_masking_handler = { + .name = "STUN", + .on_handshake_req = stun_on_handshake_req, + .on_data_wrap = stun_on_data_wrap, + .on_data_unwrap = stun_on_data_unwrap, + .on_timer = stun_on_timer, + .timer_interval_s = 10, // 10 seconds +}; diff --git a/ios/Tunnel/obfuscator/masking_stun.h b/ios/Tunnel/obfuscator/masking_stun.h new file mode 100644 index 0000000..e6d906a --- /dev/null +++ b/ios/Tunnel/obfuscator/masking_stun.h @@ -0,0 +1,19 @@ +#ifndef _MASKING_STUN_H_ +#define _MASKING_STUN_H_ + +#include +#include +#include "wg-obfuscator.h" + +static const uint8_t COOKIE_BE[4] = {0x21,0x12,0xA4,0x42}; +#define STUN_TYPE_DATA_IND 0x0115 +#define STUN_BINDING_REQ 0x0001 +#define STUN_BINDING_RESP 0x0101 +#define STUN_ATTR_XORMAPPED 0x0020 +#define STUN_ATTR_SOFTWARE 0x8022 +#define STUN_ATTR_FINGERPR 0x8028 +#define STUN_ATTR_DATA 0x0013 + +extern masking_handler_t stun_masking_handler; + +#endif // _MASKING_STUN_H_ diff --git a/ios/Tunnel/obfuscator/mini_argp.h b/ios/Tunnel/obfuscator/mini_argp.h new file mode 100644 index 0000000..017c348 --- /dev/null +++ b/ios/Tunnel/obfuscator/mini_argp.h @@ -0,0 +1,82 @@ +/* mini_argp.h + * Tiny callback-based CLI parser */ + +#ifndef _MINI_ARGP_H_ +#define _MINI_ARGP_H_ +#include +#include + +typedef int (*mini_argp_cb)(const char *lname, char sname, + const char *val, void *ctx); + +typedef struct { + const char *long_name; /* "--target" or NULL */ + char short_name; /* 't' or 0 */ + int has_arg; /* 0 flag, 1 needs val */ + mini_argp_cb cb; /* your handler */ +} mini_argp_opt; + +/* internal: find desc by name */ +static const mini_argp_opt * +margp_find(const mini_argp_opt *o, char *lname, char sname) +{ + for (; o->long_name || o->short_name; o++) + if ((lname && o->long_name && !strcmp(lname,o->long_name)) || + (sname && o->short_name==sname)) + return o; + return NULL; +} + +static int mini_argp_parse(int argc, char **argv, + const mini_argp_opt *opts, + void *ctx, + mini_argp_cb cb) +{ + for (int i = 1; i < argc; ++i) { + char *arg = argv[i]; + + /* long option: --foo or --foo=bar */ + if (!strncmp(arg, "--", 2)) { + char *name = arg + 2, *val = NULL; + if ((val = strchr(name, '='))) { *val = 0; ++val; } + + const mini_argp_opt *o = margp_find(opts, name, 0); + if (!o) { fprintf(stderr,"unknown --%s\n", name); return -1; } + if (o->has_arg && !val) { + if (++i == argc) { fprintf(stderr,"--%s needs value\n",name);return -1; } + val = argv[i]; + } + if (!o->has_arg) val = NULL; + mini_argp_cb to_call = o->cb ? o->cb : cb; + if (to_call(o->long_name, o->short_name, val, ctx)) return -1; + } + + /* short(s): -a, -fVAL, -xzvf */ + else if (arg[0]=='-' && arg[1]) { + for (size_t pos=1; arg[pos]; ++pos) { + char c = arg[pos]; + const mini_argp_opt *o = margp_find(opts,NULL,c); + if (!o){ fprintf(stderr,"unknown -%c\n",c); return -1;} + const char *val = NULL; + + if (o->has_arg) { + if (arg[pos+1]) val = &arg[pos+1]; /* -fVAL */ + else if (++i < argc) val = argv[i]; /* -f VAL */ + else { fprintf(stderr,"-%c needs value\n",c); return -1;} + /* Stop further char-scanning: the rest of the arg is the value */ + pos = strlen(arg)-1; + } + mini_argp_cb to_call = o->cb ? o->cb : cb; + if (to_call(o->long_name, o->short_name, val, ctx)) return -1; + } + } + + /* positional not allowed */ + else { + fprintf(stderr,"unexpected arg: %s\n",arg); return -1; + } + } + return 0; +} + +#endif/* _MINI_ARGP_H_ */ diff --git a/ios/Tunnel/obfuscator/obfuscation.c b/ios/Tunnel/obfuscator/obfuscation.c new file mode 100644 index 0000000..20b4aa4 --- /dev/null +++ b/ios/Tunnel/obfuscator/obfuscation.c @@ -0,0 +1,130 @@ +#include +#include +#include +#include "wg-obfuscator.h" +#include "obfuscation.h" + +static uint8_t crc_a[256]; // step(crc, 0): contribution of the current CRC state +static uint8_t crc_b[256]; // step(0, inbyte): contribution of the input byte +static uint8_t tables_ready = 0; + +typedef struct { + uint8_t *data; // cached keystream bytes, NULL until first allocation + int valid; // number of valid bytes in data + int cap; // allocated capacity of data + uint8_t crc; // CRC state right after the 'valid' bytes +} keystream_row_t; + +static keystream_row_t rows[256]; + +// Single step of the original bit-by-bit CRC8 update, kept only to build the tables. +static uint8_t crc8_step(uint8_t crc, uint8_t inbyte) { + for (uint8_t j = 0; j < 8; j++) { + uint8_t mix = (crc ^ inbyte) & 0x01; + crc >>= 1; + if (mix) { + crc ^= 0x8C; + } + inbyte >>= 1; + } + return crc; +} + +static void ensure_tables(void) { + if (tables_ready) { + return; + } + for (int i = 0; i < 256; i++) { + crc_a[i] = crc8_step((uint8_t)i, 0); + crc_b[i] = crc8_step(0, (uint8_t)i); + } + tables_ready = 1; +} + +// Ensure the given row holds at least 'need' valid keystream bytes (capped at +// KEYSTREAM_ROW_MAX). On allocation failure the row is left as large as it could +// grow; the caller then computes the missing tail on the fly. +static void grow_row(keystream_row_t *row, int cls, int need, const char *key, int key_length) { + if (need > KEYSTREAM_ROW_MAX) { + need = KEYSTREAM_ROW_MAX; + } + if (row->valid >= need) { + return; + } + if (row->cap < need) { + int newcap = (need + 255) & ~255; // round up to a 256-byte chunk + if (newcap > KEYSTREAM_ROW_MAX) { + newcap = KEYSTREAM_ROW_MAX; + } + uint8_t *p = realloc(row->data, newcap); + if (!p) { + return; // keep whatever we already have; tail handled by caller + } + row->data = p; + row->cap = newcap; + } + + uint8_t crc = row->crc; + int ki = row->valid % key_length; + for (int i = row->valid; i < need; i++) { + uint8_t inbyte = (uint8_t)(key[ki] + cls + key_length); + crc = crc_a[crc] ^ crc_b[inbyte]; + row->data[i] = crc; + if (++ki == key_length) { + ki = 0; + } + } + row->valid = need; + row->crc = crc; +} + +// XOR 'n' keystream bytes into dst, 8 bytes at a time. memcpy avoids both +// strict-aliasing violations and unaligned-access assumptions. +static void xor_block(uint8_t *dst, const uint8_t *ks, int n) { + int i = 0; + for (; i + 8 <= n; i += 8) { + uint64_t a, b; + memcpy(&a, dst + i, 8); + memcpy(&b, ks + i, 8); + a ^= b; + memcpy(dst + i, &a, 8); + } + for (; i < n; i++) { + dst[i] ^= ks[i]; + } +} + +void xor_data(uint8_t *buffer, int length, char *key, int key_length) { + if (length <= 0) { + return; + } + ensure_tables(); + + int cls = length & 0xFF; + keystream_row_t *row = &rows[cls]; + + int cached = (length <= KEYSTREAM_ROW_MAX) ? length : KEYSTREAM_ROW_MAX; + if (row->valid < cached) { + grow_row(row, cls, cached, key, key_length); + } + + int n = (length < row->valid) ? length : row->valid; + if (n > 0) { + xor_block(buffer, row->data, n); + } + + // Tail beyond the cache limit (or beyond what we could allocate): compute on + // the fly, continuing the CRC chain from the last cached state. + if (n < length) { + uint8_t crc = row->crc; + int ki = n % key_length; + for (int i = n; i < length; i++) { + uint8_t inbyte = (uint8_t)(key[ki] + cls + key_length); + crc = crc_a[crc] ^ crc_b[inbyte]; + buffer[i] ^= crc; + if (++ki == key_length) { + ki = 0; + } + } + } +} diff --git a/ios/Tunnel/obfuscator/obfuscation.h b/ios/Tunnel/obfuscator/obfuscation.h new file mode 100644 index 0000000..e69a41a --- /dev/null +++ b/ios/Tunnel/obfuscator/obfuscation.h @@ -0,0 +1,139 @@ +#ifndef _OBFUSCATION_H_ +#define _OBFUSCATION_H_ + +#include + +// Current obfuscation version +#define OBFUSCATION_VERSION 1 + +// Maximum length (in bytes) of a single cached keystream row. +// The keystream depends only on (key, length mod 256), so it is cached in 256 +// lazily-grown rows. Packets longer than this limit are still handled correctly: +// the part beyond the limit is computed on the fly. Worst-case cache memory is +// 256 * KEYSTREAM_ROW_MAX bytes. 2048 covers a typical MTU plus masking overhead. +#define KEYSTREAM_ROW_MAX 2048 + +// WireGuard packet types +#define WG_TYPE_HANDSHAKE 0x01 +#define WG_TYPE_HANDSHAKE_RESP 0x02 +#define WG_TYPE_COOKIE 0x03 +#define WG_TYPE_DATA 0x04 + +#define WG_TYPE(data) ((uint32_t)(data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24))) +#ifndef MIN +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif + +/** + * Checks if the given data is obfuscated. + * + * @param data Pointer to the data buffer to check. + * @return uint8_t Returns a non-zero value if the data is obfuscated, 0 otherwise. + */ +static inline uint8_t is_obfuscated(uint8_t *data) { + uint32_t packet_type = WG_TYPE(data); + return !(packet_type >= 1 && packet_type <= 4); +} + +/** + * @brief XORs the data in the given buffer with the key-derived keystream. + * + * The keystream is a per-byte CRC8 value derived from the key, the byte position + * and the packet length; it depends only on (key, length mod 256), so it is + * cached (see obfuscation.c). The on-the-wire result is byte-for-byte identical + * to the original bit-by-bit implementation. + * + * @param buffer Pointer to the data buffer to be XORed. + * @param length Length of the data buffer in bytes. + * @param key Pointer to the key used for XOR operation. + * @param key_length Length of the key in bytes. + */ +void xor_data(uint8_t *buffer, int length, char *key, int key_length); + +/** + * @brief Encodes the given buffer using the specified key and version. + * + * This function applies an encoding algorithm to the input buffer using the provided key and version. + * WARNING: buffer must be at least 4 bytes long and aligned to 4 bytes. + * + * @param buffer Pointer to the data buffer to encode. + * @param length Length of the data buffer in bytes. + * @param key Pointer to the key used for encoding. + * @param key_length Length of the key in bytes. + * @param version Encoding version to use. + * @param max_dummy_length_data Maximum length of dummy data for data packets. + * @return 0 on success, or a negative value on error. + */ +static inline int encode(uint8_t *buffer, int length, char *key, int key_length, uint8_t version, int max_dummy_length_data) { + if (version >= 1) { + uint32_t packet_type = WG_TYPE(buffer); + // Add some randomness to the packet + uint8_t rnd = 1 + (rand() % 255); + buffer[0] ^= rnd; // Xor the first byte to a random value + buffer[1] = rnd; // Set the second byte to a random value + // Add dummy data to the packet + if (length < MAX_DUMMY_LENGTH_TOTAL) { + uint16_t dummy_length = 0; + uint16_t max_dummy_length = MAX_DUMMY_LENGTH_TOTAL - length; + switch (packet_type) { + case WG_TYPE_HANDSHAKE: + case WG_TYPE_HANDSHAKE_RESP: + // length to MAX_DUMMY_LENGTH_HANDSHAKE + dummy_length = rand() % MIN(max_dummy_length, MAX_DUMMY_LENGTH_HANDSHAKE); + break; + case WG_TYPE_COOKIE: + case WG_TYPE_DATA: + // length to MAX_DUMMY_LENGTH_HANDSHAKE + if (max_dummy_length_data) { + dummy_length = rand() % MIN(max_dummy_length, max_dummy_length_data); + } + break; + default: + //assert(0); + break; + } + buffer[2] = dummy_length & 0xFF; // Set the dummy length in the packet + buffer[3] = dummy_length >> 8; // Set the dummy length in + if (dummy_length > 0) { + int i = length; + length += dummy_length; + for (; i < length; ++i) { + buffer[i] = 0xFF; // Fill with FFs, random data is not needed + } + } + } + } + + xor_data(buffer, length, key, key_length); + + return length; +} + +/** + * Decodes the given buffer using the provided key. + * + * WARNING: buffer must be at least 4 bytes long and aligned to 4 bytes. + * + * @param buffer Pointer to the input buffer to decode. + * @param length Length of the input buffer. + * @param key Pointer to the key used for decoding. + * @param key_length Length of the key. + * @param version_out Pointer to a variable where the decoded version will be stored. + * @return Length of the decoded data (smaller than or equal to the input length). + */ +static inline int decode(uint8_t *buffer, int length, char *key, int key_length, uint8_t *version_out) { + xor_data(buffer, length, key, key_length); + + if (!is_obfuscated(buffer)) { + // Looks like an old version + *version_out = 0; + return length; + } + + buffer[0] ^= buffer[1]; // Restore the first byte by XORing it with the second byte + length -= (uint16_t)(buffer[2] | (buffer[3] << 8)); // Remove dummy data length from the packet + buffer[1] = buffer[2] = buffer[3] = 0; // Reset the dummy length field to 0 + return length; +} + +#endif // _OBFUSCATION_H_ \ No newline at end of file diff --git a/ios/Tunnel/obfuscator/uthash.h b/ios/Tunnel/obfuscator/uthash.h new file mode 100644 index 0000000..06c2eeb --- /dev/null +++ b/ios/Tunnel/obfuscator/uthash.h @@ -0,0 +1,1137 @@ +/* +Copyright (c) 2003-2025, Troy D. Hanson https://troydhanson.github.io/uthash/ +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef UTHASH_H +#define UTHASH_H + +#define UTHASH_VERSION 2.3.0 + +#include /* memcmp, memset, strlen */ +#include /* ptrdiff_t */ +#include /* exit */ + +#if defined(HASH_NO_STDINT) && HASH_NO_STDINT +/* The user doesn't have , and must figure out their own way + to provide definitions for uint8_t and uint32_t. */ +#else +#include /* uint8_t, uint32_t */ +#endif + +/* These macros use decltype or the earlier __typeof GNU extension. + As decltype is only available in newer compilers (VS2010 or gcc 4.3+ + when compiling c++ source) this code uses whatever method is needed + or, for VS2008 where neither is available, uses casting workarounds. */ +#if !defined(DECLTYPE) && !defined(NO_DECLTYPE) +#if defined(_MSC_VER) /* MS compiler */ +#if _MSC_VER >= 1600 && defined(__cplusplus) /* VS2010 or newer in C++ mode */ +#define DECLTYPE(x) (decltype(x)) +#else /* VS2008 or older (or VS2010 in C mode) */ +#define NO_DECLTYPE +#endif +#elif defined(__MCST__) /* Elbrus C Compiler */ +#define DECLTYPE(x) (__typeof(x)) +#elif defined(__BORLANDC__) || defined(__ICCARM__) || defined(__LCC__) || defined(__WATCOMC__) +#define NO_DECLTYPE +#else /* GNU, Sun and other compilers */ +#define DECLTYPE(x) (__typeof(x)) +#endif +#endif + +#ifdef NO_DECLTYPE +#define DECLTYPE(x) +#define DECLTYPE_ASSIGN(dst,src) \ +do { \ + char **_da_dst = (char**)(&(dst)); \ + *_da_dst = (char*)(src); \ +} while (0) +#else +#define DECLTYPE_ASSIGN(dst,src) \ +do { \ + (dst) = DECLTYPE(dst)(src); \ +} while (0) +#endif + +#ifndef uthash_malloc +#define uthash_malloc(sz) malloc(sz) /* malloc fcn */ +#endif +#ifndef uthash_free +#define uthash_free(ptr,sz) free(ptr) /* free fcn */ +#endif +#ifndef uthash_bzero +#define uthash_bzero(a,n) memset(a,'\0',n) +#endif +#ifndef uthash_strlen +#define uthash_strlen(s) strlen(s) +#endif + +#ifndef HASH_FUNCTION +#define HASH_FUNCTION(keyptr,keylen,hashv) HASH_JEN(keyptr, keylen, hashv) +#endif + +#ifndef HASH_KEYCMP +#define HASH_KEYCMP(a,b,n) memcmp(a,b,n) +#endif + +#ifndef uthash_noexpand_fyi +#define uthash_noexpand_fyi(tbl) /* can be defined to log noexpand */ +#endif +#ifndef uthash_expand_fyi +#define uthash_expand_fyi(tbl) /* can be defined to log expands */ +#endif + +#ifndef HASH_NONFATAL_OOM +#define HASH_NONFATAL_OOM 0 +#endif + +#if HASH_NONFATAL_OOM +/* malloc failures can be recovered from */ + +#ifndef uthash_nonfatal_oom +#define uthash_nonfatal_oom(obj) do {} while (0) /* non-fatal OOM error */ +#endif + +#define HASH_RECORD_OOM(oomed) do { (oomed) = 1; } while (0) +#define IF_HASH_NONFATAL_OOM(x) x + +#else +/* malloc failures result in lost memory, hash tables are unusable */ + +#ifndef uthash_fatal +#define uthash_fatal(msg) exit(-1) /* fatal OOM error */ +#endif + +#define HASH_RECORD_OOM(oomed) uthash_fatal("out of memory") +#define IF_HASH_NONFATAL_OOM(x) + +#endif + +/* initial number of buckets */ +#define HASH_INITIAL_NUM_BUCKETS 32U /* initial number of buckets */ +#define HASH_INITIAL_NUM_BUCKETS_LOG2 5U /* lg2 of initial number of buckets */ +#define HASH_BKT_CAPACITY_THRESH 10U /* expand when bucket count reaches */ + +/* calculate the element whose hash handle address is hhp */ +#define ELMT_FROM_HH(tbl,hhp) ((void*)(((char*)(hhp)) - ((tbl)->hho))) +/* calculate the hash handle from element address elp */ +#define HH_FROM_ELMT(tbl,elp) ((UT_hash_handle*)(void*)(((char*)(elp)) + ((tbl)->hho))) + +#define HASH_ROLLBACK_BKT(hh, head, itemptrhh) \ +do { \ + struct UT_hash_handle *_hd_hh_item = (itemptrhh); \ + unsigned _hd_bkt; \ + HASH_TO_BKT(_hd_hh_item->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ + (head)->hh.tbl->buckets[_hd_bkt].count++; \ + _hd_hh_item->hh_next = NULL; \ + _hd_hh_item->hh_prev = NULL; \ +} while (0) + +#define HASH_VALUE(keyptr,keylen,hashv) \ +do { \ + HASH_FUNCTION(keyptr, keylen, hashv); \ +} while (0) + +#define HASH_FIND_BYHASHVALUE(hh,head,keyptr,keylen,hashval,out) \ +do { \ + (out) = NULL; \ + if (head) { \ + unsigned _hf_bkt; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _hf_bkt); \ + if (HASH_BLOOM_TEST((head)->hh.tbl, hashval)) { \ + HASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[ _hf_bkt ], keyptr, keylen, hashval, out); \ + } \ + } \ +} while (0) + +#define HASH_FIND(hh,head,keyptr,keylen,out) \ +do { \ + (out) = NULL; \ + if (head) { \ + unsigned _hf_hashv; \ + HASH_VALUE(keyptr, keylen, _hf_hashv); \ + HASH_FIND_BYHASHVALUE(hh, head, keyptr, keylen, _hf_hashv, out); \ + } \ +} while (0) + +#ifdef HASH_BLOOM +#define HASH_BLOOM_BITLEN (1UL << HASH_BLOOM) +#define HASH_BLOOM_BYTELEN (HASH_BLOOM_BITLEN/8UL) + (((HASH_BLOOM_BITLEN%8UL)!=0UL) ? 1UL : 0UL) +#define HASH_BLOOM_MAKE(tbl,oomed) \ +do { \ + (tbl)->bloom_nbits = HASH_BLOOM; \ + (tbl)->bloom_bv = (uint8_t*)uthash_malloc(HASH_BLOOM_BYTELEN); \ + if (!(tbl)->bloom_bv) { \ + HASH_RECORD_OOM(oomed); \ + } else { \ + uthash_bzero((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ + (tbl)->bloom_sig = HASH_BLOOM_SIGNATURE; \ + } \ +} while (0) + +#define HASH_BLOOM_FREE(tbl) \ +do { \ + uthash_free((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ +} while (0) + +#define HASH_BLOOM_BITSET(bv,idx) (bv[(idx)/8U] |= (1U << ((idx)%8U))) +#define HASH_BLOOM_BITTEST(bv,idx) ((bv[(idx)/8U] & (1U << ((idx)%8U))) != 0) + +#define HASH_BLOOM_ADD(tbl,hashv) \ + HASH_BLOOM_BITSET((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) + +#define HASH_BLOOM_TEST(tbl,hashv) \ + HASH_BLOOM_BITTEST((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) + +#else +#define HASH_BLOOM_MAKE(tbl,oomed) +#define HASH_BLOOM_FREE(tbl) +#define HASH_BLOOM_ADD(tbl,hashv) +#define HASH_BLOOM_TEST(tbl,hashv) 1 +#define HASH_BLOOM_BYTELEN 0U +#endif + +#define HASH_MAKE_TABLE(hh,head,oomed) \ +do { \ + (head)->hh.tbl = (UT_hash_table*)uthash_malloc(sizeof(UT_hash_table)); \ + if (!(head)->hh.tbl) { \ + HASH_RECORD_OOM(oomed); \ + } else { \ + uthash_bzero((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head)->hh.tbl->tail = &((head)->hh); \ + (head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS; \ + (head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2; \ + (head)->hh.tbl->hho = (char*)(&(head)->hh) - (char*)(head); \ + (head)->hh.tbl->buckets = (UT_hash_bucket*)uthash_malloc( \ + HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ + (head)->hh.tbl->signature = HASH_SIGNATURE; \ + if (!(head)->hh.tbl->buckets) { \ + HASH_RECORD_OOM(oomed); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + } else { \ + uthash_bzero((head)->hh.tbl->buckets, \ + HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ + HASH_BLOOM_MAKE((head)->hh.tbl, oomed); \ + IF_HASH_NONFATAL_OOM( \ + if (oomed) { \ + uthash_free((head)->hh.tbl->buckets, \ + HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + } \ + ) \ + } \ + } \ +} while (0) + +#define HASH_REPLACE_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,replaced,cmpfcn) \ +do { \ + (replaced) = NULL; \ + HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ + if (replaced) { \ + HASH_DELETE(hh, head, replaced); \ + } \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn); \ +} while (0) + +#define HASH_REPLACE_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add,replaced) \ +do { \ + (replaced) = NULL; \ + HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ + if (replaced) { \ + HASH_DELETE(hh, head, replaced); \ + } \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add); \ +} while (0) + +#define HASH_REPLACE(hh,head,fieldname,keylen_in,add,replaced) \ +do { \ + unsigned _hr_hashv; \ + HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ + HASH_REPLACE_BYHASHVALUE(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced); \ +} while (0) + +#define HASH_REPLACE_INORDER(hh,head,fieldname,keylen_in,add,replaced,cmpfcn) \ +do { \ + unsigned _hr_hashv; \ + HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ + HASH_REPLACE_BYHASHVALUE_INORDER(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced, cmpfcn); \ +} while (0) + +#define HASH_APPEND_LIST(hh, head, add) \ +do { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail); \ + (head)->hh.tbl->tail->next = (add); \ + (head)->hh.tbl->tail = &((add)->hh); \ +} while (0) + +#define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \ +do { \ + do { \ + if (cmpfcn(DECLTYPE(head)(_hs_iter), add) > 0) { \ + break; \ + } \ + } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ +} while (0) + +#ifdef NO_DECLTYPE +#undef HASH_AKBI_INNER_LOOP +#define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \ +do { \ + char *_hs_saved_head = (char*)(head); \ + do { \ + DECLTYPE_ASSIGN(head, _hs_iter); \ + if (cmpfcn(head, add) > 0) { \ + DECLTYPE_ASSIGN(head, _hs_saved_head); \ + break; \ + } \ + DECLTYPE_ASSIGN(head, _hs_saved_head); \ + } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ +} while (0) +#endif + +#if HASH_NONFATAL_OOM + +#define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \ +do { \ + if (!(oomed)) { \ + unsigned _ha_bkt; \ + (head)->hh.tbl->num_items++; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ + HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ + if (oomed) { \ + HASH_ROLLBACK_BKT(hh, head, &(add)->hh); \ + HASH_DELETE_HH(hh, head, &(add)->hh); \ + (add)->hh.tbl = NULL; \ + uthash_nonfatal_oom(add); \ + } else { \ + HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ + HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ + } \ + } else { \ + (add)->hh.tbl = NULL; \ + uthash_nonfatal_oom(add); \ + } \ +} while (0) + +#else + +#define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \ +do { \ + unsigned _ha_bkt; \ + (head)->hh.tbl->num_items++; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ + HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ + HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ + HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ +} while (0) + +#endif + + +#define HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh,head,keyptr,keylen_in,hashval,add,cmpfcn) \ +do { \ + IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \ + (add)->hh.hashv = (hashval); \ + (add)->hh.key = (char*) (keyptr); \ + (add)->hh.keylen = (unsigned) (keylen_in); \ + if (!(head)) { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = NULL; \ + HASH_MAKE_TABLE(hh, add, _ha_oomed); \ + IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \ + (head) = (add); \ + IF_HASH_NONFATAL_OOM( } ) \ + } else { \ + void *_hs_iter = (head); \ + (add)->hh.tbl = (head)->hh.tbl; \ + HASH_AKBI_INNER_LOOP(hh, head, add, cmpfcn); \ + if (_hs_iter) { \ + (add)->hh.next = _hs_iter; \ + if (((add)->hh.prev = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev)) { \ + HH_FROM_ELMT((head)->hh.tbl, (add)->hh.prev)->next = (add); \ + } else { \ + (head) = (add); \ + } \ + HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev = (add); \ + } else { \ + HASH_APPEND_LIST(hh, head, add); \ + } \ + } \ + HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ + HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE_INORDER"); \ +} while (0) + +#define HASH_ADD_KEYPTR_INORDER(hh,head,keyptr,keylen_in,add,cmpfcn) \ +do { \ + unsigned _hs_hashv; \ + HASH_VALUE(keyptr, keylen_in, _hs_hashv); \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, keyptr, keylen_in, _hs_hashv, add, cmpfcn); \ +} while (0) + +#define HASH_ADD_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,cmpfcn) \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn) + +#define HASH_ADD_INORDER(hh,head,fieldname,keylen_in,add,cmpfcn) \ + HASH_ADD_KEYPTR_INORDER(hh, head, &((add)->fieldname), keylen_in, add, cmpfcn) + +#define HASH_ADD_KEYPTR_BYHASHVALUE(hh,head,keyptr,keylen_in,hashval,add) \ +do { \ + IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \ + (add)->hh.hashv = (hashval); \ + (add)->hh.key = (const void*) (keyptr); \ + (add)->hh.keylen = (unsigned) (keylen_in); \ + if (!(head)) { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = NULL; \ + HASH_MAKE_TABLE(hh, add, _ha_oomed); \ + IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \ + (head) = (add); \ + IF_HASH_NONFATAL_OOM( } ) \ + } else { \ + (add)->hh.tbl = (head)->hh.tbl; \ + HASH_APPEND_LIST(hh, head, add); \ + } \ + HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ + HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE"); \ +} while (0) + +#define HASH_ADD_KEYPTR(hh,head,keyptr,keylen_in,add) \ +do { \ + unsigned _ha_hashv; \ + HASH_VALUE(keyptr, keylen_in, _ha_hashv); \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, keyptr, keylen_in, _ha_hashv, add); \ +} while (0) + +#define HASH_ADD_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add) \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add) + +#define HASH_ADD(hh,head,fieldname,keylen_in,add) \ + HASH_ADD_KEYPTR(hh, head, &((add)->fieldname), keylen_in, add) + +#define HASH_TO_BKT(hashv,num_bkts,bkt) \ +do { \ + bkt = ((hashv) & ((num_bkts) - 1U)); \ +} while (0) + +/* delete "delptr" from the hash table. + * "the usual" patch-up process for the app-order doubly-linked-list. + * The use of _hd_hh_del below deserves special explanation. + * These used to be expressed using (delptr) but that led to a bug + * if someone used the same symbol for the head and deletee, like + * HASH_DELETE(hh,users,users); + * We want that to work, but by changing the head (users) below + * we were forfeiting our ability to further refer to the deletee (users) + * in the patch-up process. Solution: use scratch space to + * copy the deletee pointer, then the latter references are via that + * scratch pointer rather than through the repointed (users) symbol. + */ +#define HASH_DELETE(hh,head,delptr) \ + HASH_DELETE_HH(hh, head, &(delptr)->hh) + +#define HASH_DELETE_HH(hh,head,delptrhh) \ +do { \ + const struct UT_hash_handle *_hd_hh_del = (delptrhh); \ + if ((_hd_hh_del->prev == NULL) && (_hd_hh_del->next == NULL)) { \ + HASH_BLOOM_FREE((head)->hh.tbl); \ + uthash_free((head)->hh.tbl->buckets, \ + (head)->hh.tbl->num_buckets * sizeof(struct UT_hash_bucket)); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head) = NULL; \ + } else { \ + unsigned _hd_bkt; \ + if (_hd_hh_del == (head)->hh.tbl->tail) { \ + (head)->hh.tbl->tail = HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev); \ + } \ + if (_hd_hh_del->prev != NULL) { \ + HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev)->next = _hd_hh_del->next; \ + } else { \ + DECLTYPE_ASSIGN(head, _hd_hh_del->next); \ + } \ + if (_hd_hh_del->next != NULL) { \ + HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->next)->prev = _hd_hh_del->prev; \ + } \ + HASH_TO_BKT(_hd_hh_del->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ + HASH_DEL_IN_BKT((head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del); \ + (head)->hh.tbl->num_items--; \ + } \ + HASH_FSCK(hh, head, "HASH_DELETE_HH"); \ +} while (0) + +/* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */ +#define HASH_FIND_STR(head,findstr,out) \ +do { \ + unsigned _uthash_hfstr_keylen = (unsigned)uthash_strlen(findstr); \ + HASH_FIND(hh, head, findstr, _uthash_hfstr_keylen, out); \ +} while (0) +#define HASH_ADD_STR(head,strfield,add) \ +do { \ + unsigned _uthash_hastr_keylen = (unsigned)uthash_strlen((add)->strfield); \ + HASH_ADD(hh, head, strfield[0], _uthash_hastr_keylen, add); \ +} while (0) +#define HASH_REPLACE_STR(head,strfield,add,replaced) \ +do { \ + unsigned _uthash_hrstr_keylen = (unsigned)uthash_strlen((add)->strfield); \ + HASH_REPLACE(hh, head, strfield[0], _uthash_hrstr_keylen, add, replaced); \ +} while (0) +#define HASH_FIND_INT(head,findint,out) \ + HASH_FIND(hh,head,findint,sizeof(int),out) +#define HASH_ADD_INT(head,intfield,add) \ + HASH_ADD(hh,head,intfield,sizeof(int),add) +#define HASH_REPLACE_INT(head,intfield,add,replaced) \ + HASH_REPLACE(hh,head,intfield,sizeof(int),add,replaced) +#define HASH_FIND_PTR(head,findptr,out) \ + HASH_FIND(hh,head,findptr,sizeof(void *),out) +#define HASH_ADD_PTR(head,ptrfield,add) \ + HASH_ADD(hh,head,ptrfield,sizeof(void *),add) +#define HASH_REPLACE_PTR(head,ptrfield,add,replaced) \ + HASH_REPLACE(hh,head,ptrfield,sizeof(void *),add,replaced) +#define HASH_DEL(head,delptr) \ + HASH_DELETE(hh,head,delptr) + +/* HASH_FSCK checks hash integrity on every add/delete when HASH_DEBUG is defined. + * This is for uthash developer only; it compiles away if HASH_DEBUG isn't defined. + */ +#ifdef HASH_DEBUG +#include /* fprintf, stderr */ +#define HASH_OOPS(...) do { fprintf(stderr, __VA_ARGS__); exit(-1); } while (0) +#define HASH_FSCK(hh,head,where) \ +do { \ + struct UT_hash_handle *_thh; \ + if (head) { \ + unsigned _bkt_i; \ + unsigned _count = 0; \ + char *_prev; \ + for (_bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; ++_bkt_i) { \ + unsigned _bkt_count = 0; \ + _thh = (head)->hh.tbl->buckets[_bkt_i].hh_head; \ + _prev = NULL; \ + while (_thh) { \ + if (_prev != (char*)(_thh->hh_prev)) { \ + HASH_OOPS("%s: invalid hh_prev %p, actual %p\n", \ + (where), (void*)_thh->hh_prev, (void*)_prev); \ + } \ + _bkt_count++; \ + _prev = (char*)(_thh); \ + _thh = _thh->hh_next; \ + } \ + _count += _bkt_count; \ + if ((head)->hh.tbl->buckets[_bkt_i].count != _bkt_count) { \ + HASH_OOPS("%s: invalid bucket count %u, actual %u\n", \ + (where), (head)->hh.tbl->buckets[_bkt_i].count, _bkt_count); \ + } \ + } \ + if (_count != (head)->hh.tbl->num_items) { \ + HASH_OOPS("%s: invalid hh item count %u, actual %u\n", \ + (where), (head)->hh.tbl->num_items, _count); \ + } \ + _count = 0; \ + _prev = NULL; \ + _thh = &(head)->hh; \ + while (_thh) { \ + _count++; \ + if (_prev != (char*)_thh->prev) { \ + HASH_OOPS("%s: invalid prev %p, actual %p\n", \ + (where), (void*)_thh->prev, (void*)_prev); \ + } \ + _prev = (char*)ELMT_FROM_HH((head)->hh.tbl, _thh); \ + _thh = (_thh->next ? HH_FROM_ELMT((head)->hh.tbl, _thh->next) : NULL); \ + } \ + if (_count != (head)->hh.tbl->num_items) { \ + HASH_OOPS("%s: invalid app item count %u, actual %u\n", \ + (where), (head)->hh.tbl->num_items, _count); \ + } \ + } \ +} while (0) +#else +#define HASH_FSCK(hh,head,where) +#endif + +/* When compiled with -DHASH_EMIT_KEYS, length-prefixed keys are emitted to + * the descriptor to which this macro is defined for tuning the hash function. + * The app can #include to get the prototype for write(2). */ +#ifdef HASH_EMIT_KEYS +#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) \ +do { \ + unsigned _klen = fieldlen; \ + write(HASH_EMIT_KEYS, &_klen, sizeof(_klen)); \ + write(HASH_EMIT_KEYS, keyptr, (unsigned long)fieldlen); \ +} while (0) +#else +#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) +#endif + +/* The Bernstein hash function, used in Perl prior to v5.6. Note (x<<5+x)=x*33. */ +#define HASH_BER(key,keylen,hashv) \ +do { \ + unsigned _hb_keylen = (unsigned)keylen; \ + const unsigned char *_hb_key = (const unsigned char*)(key); \ + (hashv) = 0; \ + while (_hb_keylen-- != 0U) { \ + (hashv) = (((hashv) << 5) + (hashv)) + *_hb_key++; \ + } \ +} while (0) + + +/* SAX/FNV/OAT/JEN hash functions are macro variants of those listed at + * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx + * (archive link: https://archive.is/Ivcan ) + */ +#define HASH_SAX(key,keylen,hashv) \ +do { \ + unsigned _sx_i; \ + const unsigned char *_hs_key = (const unsigned char*)(key); \ + hashv = 0; \ + for (_sx_i=0; _sx_i < keylen; _sx_i++) { \ + hashv ^= (hashv << 5) + (hashv >> 2) + _hs_key[_sx_i]; \ + } \ +} while (0) +/* FNV-1a variation */ +#define HASH_FNV(key,keylen,hashv) \ +do { \ + unsigned _fn_i; \ + const unsigned char *_hf_key = (const unsigned char*)(key); \ + (hashv) = 2166136261U; \ + for (_fn_i=0; _fn_i < keylen; _fn_i++) { \ + hashv = hashv ^ _hf_key[_fn_i]; \ + hashv = hashv * 16777619U; \ + } \ +} while (0) + +#define HASH_OAT(key,keylen,hashv) \ +do { \ + unsigned _ho_i; \ + const unsigned char *_ho_key=(const unsigned char*)(key); \ + hashv = 0; \ + for(_ho_i=0; _ho_i < keylen; _ho_i++) { \ + hashv += _ho_key[_ho_i]; \ + hashv += (hashv << 10); \ + hashv ^= (hashv >> 6); \ + } \ + hashv += (hashv << 3); \ + hashv ^= (hashv >> 11); \ + hashv += (hashv << 15); \ +} while (0) + +#define HASH_JEN_MIX(a,b,c) \ +do { \ + a -= b; a -= c; a ^= ( c >> 13 ); \ + b -= c; b -= a; b ^= ( a << 8 ); \ + c -= a; c -= b; c ^= ( b >> 13 ); \ + a -= b; a -= c; a ^= ( c >> 12 ); \ + b -= c; b -= a; b ^= ( a << 16 ); \ + c -= a; c -= b; c ^= ( b >> 5 ); \ + a -= b; a -= c; a ^= ( c >> 3 ); \ + b -= c; b -= a; b ^= ( a << 10 ); \ + c -= a; c -= b; c ^= ( b >> 15 ); \ +} while (0) + +#define HASH_JEN(key,keylen,hashv) \ +do { \ + unsigned _hj_i,_hj_j,_hj_k; \ + unsigned const char *_hj_key=(unsigned const char*)(key); \ + hashv = 0xfeedbeefu; \ + _hj_i = _hj_j = 0x9e3779b9u; \ + _hj_k = (unsigned)(keylen); \ + while (_hj_k >= 12U) { \ + _hj_i += (_hj_key[0] + ( (unsigned)_hj_key[1] << 8 ) \ + + ( (unsigned)_hj_key[2] << 16 ) \ + + ( (unsigned)_hj_key[3] << 24 ) ); \ + _hj_j += (_hj_key[4] + ( (unsigned)_hj_key[5] << 8 ) \ + + ( (unsigned)_hj_key[6] << 16 ) \ + + ( (unsigned)_hj_key[7] << 24 ) ); \ + hashv += (_hj_key[8] + ( (unsigned)_hj_key[9] << 8 ) \ + + ( (unsigned)_hj_key[10] << 16 ) \ + + ( (unsigned)_hj_key[11] << 24 ) ); \ + \ + HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ + \ + _hj_key += 12; \ + _hj_k -= 12U; \ + } \ + hashv += (unsigned)(keylen); \ + switch ( _hj_k ) { \ + case 11: hashv += ( (unsigned)_hj_key[10] << 24 ); /* FALLTHROUGH */ \ + case 10: hashv += ( (unsigned)_hj_key[9] << 16 ); /* FALLTHROUGH */ \ + case 9: hashv += ( (unsigned)_hj_key[8] << 8 ); /* FALLTHROUGH */ \ + case 8: _hj_j += ( (unsigned)_hj_key[7] << 24 ); /* FALLTHROUGH */ \ + case 7: _hj_j += ( (unsigned)_hj_key[6] << 16 ); /* FALLTHROUGH */ \ + case 6: _hj_j += ( (unsigned)_hj_key[5] << 8 ); /* FALLTHROUGH */ \ + case 5: _hj_j += _hj_key[4]; /* FALLTHROUGH */ \ + case 4: _hj_i += ( (unsigned)_hj_key[3] << 24 ); /* FALLTHROUGH */ \ + case 3: _hj_i += ( (unsigned)_hj_key[2] << 16 ); /* FALLTHROUGH */ \ + case 2: _hj_i += ( (unsigned)_hj_key[1] << 8 ); /* FALLTHROUGH */ \ + case 1: _hj_i += _hj_key[0]; /* FALLTHROUGH */ \ + default: ; \ + } \ + HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ +} while (0) + +/* The Paul Hsieh hash function */ +#undef get16bits +#if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) \ + || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__) +#define get16bits(d) (*((const uint16_t *) (d))) +#endif + +#if !defined (get16bits) +#define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8) \ + +(uint32_t)(((const uint8_t *)(d))[0]) ) +#endif +#define HASH_SFH(key,keylen,hashv) \ +do { \ + unsigned const char *_sfh_key=(unsigned const char*)(key); \ + uint32_t _sfh_tmp, _sfh_len = (uint32_t)keylen; \ + \ + unsigned _sfh_rem = _sfh_len & 3U; \ + _sfh_len >>= 2; \ + hashv = 0xcafebabeu; \ + \ + /* Main loop */ \ + for (;_sfh_len > 0U; _sfh_len--) { \ + hashv += get16bits (_sfh_key); \ + _sfh_tmp = ((uint32_t)(get16bits (_sfh_key+2)) << 11) ^ hashv; \ + hashv = (hashv << 16) ^ _sfh_tmp; \ + _sfh_key += 2U*sizeof (uint16_t); \ + hashv += hashv >> 11; \ + } \ + \ + /* Handle end cases */ \ + switch (_sfh_rem) { \ + case 3: hashv += get16bits (_sfh_key); \ + hashv ^= hashv << 16; \ + hashv ^= (uint32_t)(_sfh_key[sizeof (uint16_t)]) << 18; \ + hashv += hashv >> 11; \ + break; \ + case 2: hashv += get16bits (_sfh_key); \ + hashv ^= hashv << 11; \ + hashv += hashv >> 17; \ + break; \ + case 1: hashv += *_sfh_key; \ + hashv ^= hashv << 10; \ + hashv += hashv >> 1; \ + break; \ + default: ; \ + } \ + \ + /* Force "avalanching" of final 127 bits */ \ + hashv ^= hashv << 3; \ + hashv += hashv >> 5; \ + hashv ^= hashv << 4; \ + hashv += hashv >> 17; \ + hashv ^= hashv << 25; \ + hashv += hashv >> 6; \ +} while (0) + +/* iterate over items in a known bucket to find desired item */ +#define HASH_FIND_IN_BKT(tbl,hh,head,keyptr,keylen_in,hashval,out) \ +do { \ + if ((head).hh_head != NULL) { \ + DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (head).hh_head)); \ + } else { \ + (out) = NULL; \ + } \ + while ((out) != NULL) { \ + if ((out)->hh.hashv == (hashval) && (out)->hh.keylen == (keylen_in)) { \ + if (HASH_KEYCMP((out)->hh.key, keyptr, keylen_in) == 0) { \ + break; \ + } \ + } \ + if ((out)->hh.hh_next != NULL) { \ + DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (out)->hh.hh_next)); \ + } else { \ + (out) = NULL; \ + } \ + } \ +} while (0) + +/* add an item to a bucket */ +#define HASH_ADD_TO_BKT(head,hh,addhh,oomed) \ +do { \ + UT_hash_bucket *_ha_head = &(head); \ + _ha_head->count++; \ + (addhh)->hh_next = _ha_head->hh_head; \ + (addhh)->hh_prev = NULL; \ + if (_ha_head->hh_head != NULL) { \ + _ha_head->hh_head->hh_prev = (addhh); \ + } \ + _ha_head->hh_head = (addhh); \ + if ((_ha_head->count >= ((_ha_head->expand_mult + 1U) * HASH_BKT_CAPACITY_THRESH)) \ + && !(addhh)->tbl->noexpand) { \ + HASH_EXPAND_BUCKETS(addhh,(addhh)->tbl, oomed); \ + IF_HASH_NONFATAL_OOM( \ + if (oomed) { \ + HASH_DEL_IN_BKT(head,addhh); \ + } \ + ) \ + } \ +} while (0) + +/* remove an item from a given bucket */ +#define HASH_DEL_IN_BKT(head,delhh) \ +do { \ + UT_hash_bucket *_hd_head = &(head); \ + _hd_head->count--; \ + if (_hd_head->hh_head == (delhh)) { \ + _hd_head->hh_head = (delhh)->hh_next; \ + } \ + if ((delhh)->hh_prev) { \ + (delhh)->hh_prev->hh_next = (delhh)->hh_next; \ + } \ + if ((delhh)->hh_next) { \ + (delhh)->hh_next->hh_prev = (delhh)->hh_prev; \ + } \ +} while (0) + +/* Bucket expansion has the effect of doubling the number of buckets + * and redistributing the items into the new buckets. Ideally the + * items will distribute more or less evenly into the new buckets + * (the extent to which this is true is a measure of the quality of + * the hash function as it applies to the key domain). + * + * With the items distributed into more buckets, the chain length + * (item count) in each bucket is reduced. Thus by expanding buckets + * the hash keeps a bound on the chain length. This bounded chain + * length is the essence of how a hash provides constant time lookup. + * + * The calculation of tbl->ideal_chain_maxlen below deserves some + * explanation. First, keep in mind that we're calculating the ideal + * maximum chain length based on the *new* (doubled) bucket count. + * In fractions this is just n/b (n=number of items,b=new num buckets). + * Since the ideal chain length is an integer, we want to calculate + * ceil(n/b). We don't depend on floating point arithmetic in this + * hash, so to calculate ceil(n/b) with integers we could write + * + * ceil(n/b) = (n/b) + ((n%b)?1:0) + * + * and in fact a previous version of this hash did just that. + * But now we have improved things a bit by recognizing that b is + * always a power of two. We keep its base 2 log handy (call it lb), + * so now we can write this with a bit shift and logical AND: + * + * ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0) + * + */ +#define HASH_EXPAND_BUCKETS(hh,tbl,oomed) \ +do { \ + unsigned _he_bkt; \ + unsigned _he_bkt_i; \ + struct UT_hash_handle *_he_thh, *_he_hh_nxt; \ + UT_hash_bucket *_he_new_buckets, *_he_newbkt; \ + _he_new_buckets = (UT_hash_bucket*)uthash_malloc( \ + sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ + if (!_he_new_buckets) { \ + HASH_RECORD_OOM(oomed); \ + } else { \ + uthash_bzero(_he_new_buckets, \ + sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ + (tbl)->ideal_chain_maxlen = \ + ((tbl)->num_items >> ((tbl)->log2_num_buckets+1U)) + \ + ((((tbl)->num_items & (((tbl)->num_buckets*2U)-1U)) != 0U) ? 1U : 0U); \ + (tbl)->nonideal_items = 0; \ + for (_he_bkt_i = 0; _he_bkt_i < (tbl)->num_buckets; _he_bkt_i++) { \ + _he_thh = (tbl)->buckets[ _he_bkt_i ].hh_head; \ + while (_he_thh != NULL) { \ + _he_hh_nxt = _he_thh->hh_next; \ + HASH_TO_BKT(_he_thh->hashv, (tbl)->num_buckets * 2U, _he_bkt); \ + _he_newbkt = &(_he_new_buckets[_he_bkt]); \ + if (++(_he_newbkt->count) > (tbl)->ideal_chain_maxlen) { \ + (tbl)->nonideal_items++; \ + if (_he_newbkt->count > _he_newbkt->expand_mult * (tbl)->ideal_chain_maxlen) { \ + _he_newbkt->expand_mult++; \ + } \ + } \ + _he_thh->hh_prev = NULL; \ + _he_thh->hh_next = _he_newbkt->hh_head; \ + if (_he_newbkt->hh_head != NULL) { \ + _he_newbkt->hh_head->hh_prev = _he_thh; \ + } \ + _he_newbkt->hh_head = _he_thh; \ + _he_thh = _he_hh_nxt; \ + } \ + } \ + uthash_free((tbl)->buckets, (tbl)->num_buckets * sizeof(struct UT_hash_bucket)); \ + (tbl)->num_buckets *= 2U; \ + (tbl)->log2_num_buckets++; \ + (tbl)->buckets = _he_new_buckets; \ + (tbl)->ineff_expands = ((tbl)->nonideal_items > ((tbl)->num_items >> 1)) ? \ + ((tbl)->ineff_expands+1U) : 0U; \ + if ((tbl)->ineff_expands > 1U) { \ + (tbl)->noexpand = 1; \ + uthash_noexpand_fyi(tbl); \ + } \ + uthash_expand_fyi(tbl); \ + } \ +} while (0) + + +/* This is an adaptation of Simon Tatham's O(n log(n)) mergesort */ +/* Note that HASH_SORT assumes the hash handle name to be hh. + * HASH_SRT was added to allow the hash handle name to be passed in. */ +#define HASH_SORT(head,cmpfcn) HASH_SRT(hh,head,cmpfcn) +#define HASH_SRT(hh,head,cmpfcn) \ +do { \ + unsigned _hs_i; \ + unsigned _hs_looping,_hs_nmerges,_hs_insize,_hs_psize,_hs_qsize; \ + struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail; \ + if (head != NULL) { \ + _hs_insize = 1; \ + _hs_looping = 1; \ + _hs_list = &((head)->hh); \ + while (_hs_looping != 0U) { \ + _hs_p = _hs_list; \ + _hs_list = NULL; \ + _hs_tail = NULL; \ + _hs_nmerges = 0; \ + while (_hs_p != NULL) { \ + _hs_nmerges++; \ + _hs_q = _hs_p; \ + _hs_psize = 0; \ + for (_hs_i = 0; _hs_i < _hs_insize; ++_hs_i) { \ + _hs_psize++; \ + _hs_q = ((_hs_q->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ + if (_hs_q == NULL) { \ + break; \ + } \ + } \ + _hs_qsize = _hs_insize; \ + while ((_hs_psize != 0U) || ((_hs_qsize != 0U) && (_hs_q != NULL))) { \ + if (_hs_psize == 0U) { \ + _hs_e = _hs_q; \ + _hs_q = ((_hs_q->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ + _hs_qsize--; \ + } else if ((_hs_qsize == 0U) || (_hs_q == NULL)) { \ + _hs_e = _hs_p; \ + if (_hs_p != NULL) { \ + _hs_p = ((_hs_p->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ + } \ + _hs_psize--; \ + } else if ((cmpfcn( \ + DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_p)), \ + DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_q)) \ + )) <= 0) { \ + _hs_e = _hs_p; \ + if (_hs_p != NULL) { \ + _hs_p = ((_hs_p->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ + } \ + _hs_psize--; \ + } else { \ + _hs_e = _hs_q; \ + _hs_q = ((_hs_q->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ + _hs_qsize--; \ + } \ + if ( _hs_tail != NULL ) { \ + _hs_tail->next = ((_hs_e != NULL) ? \ + ELMT_FROM_HH((head)->hh.tbl, _hs_e) : NULL); \ + } else { \ + _hs_list = _hs_e; \ + } \ + if (_hs_e != NULL) { \ + _hs_e->prev = ((_hs_tail != NULL) ? \ + ELMT_FROM_HH((head)->hh.tbl, _hs_tail) : NULL); \ + } \ + _hs_tail = _hs_e; \ + } \ + _hs_p = _hs_q; \ + } \ + if (_hs_tail != NULL) { \ + _hs_tail->next = NULL; \ + } \ + if (_hs_nmerges <= 1U) { \ + _hs_looping = 0; \ + (head)->hh.tbl->tail = _hs_tail; \ + DECLTYPE_ASSIGN(head, ELMT_FROM_HH((head)->hh.tbl, _hs_list)); \ + } \ + _hs_insize *= 2U; \ + } \ + HASH_FSCK(hh, head, "HASH_SRT"); \ + } \ +} while (0) + +/* This function selects items from one hash into another hash. + * The end result is that the selected items have dual presence + * in both hashes. There is no copy of the items made; rather + * they are added into the new hash through a secondary hash + * hash handle that must be present in the structure. */ +#define HASH_SELECT(hh_dst, dst, hh_src, src, cond) \ +do { \ + unsigned _src_bkt, _dst_bkt; \ + void *_last_elt = NULL, *_elt; \ + UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh=NULL; \ + ptrdiff_t _dst_hho = ((char*)(&(dst)->hh_dst) - (char*)(dst)); \ + if ((src) != NULL) { \ + for (_src_bkt=0; _src_bkt < (src)->hh_src.tbl->num_buckets; _src_bkt++) { \ + for (_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head; \ + _src_hh != NULL; \ + _src_hh = _src_hh->hh_next) { \ + _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh); \ + if (cond(_elt)) { \ + IF_HASH_NONFATAL_OOM( int _hs_oomed = 0; ) \ + _dst_hh = (UT_hash_handle*)(void*)(((char*)_elt) + _dst_hho); \ + _dst_hh->key = _src_hh->key; \ + _dst_hh->keylen = _src_hh->keylen; \ + _dst_hh->hashv = _src_hh->hashv; \ + _dst_hh->prev = _last_elt; \ + _dst_hh->next = NULL; \ + if (_last_elt_hh != NULL) { \ + _last_elt_hh->next = _elt; \ + } \ + if ((dst) == NULL) { \ + DECLTYPE_ASSIGN(dst, _elt); \ + HASH_MAKE_TABLE(hh_dst, dst, _hs_oomed); \ + IF_HASH_NONFATAL_OOM( \ + if (_hs_oomed) { \ + uthash_nonfatal_oom(_elt); \ + (dst) = NULL; \ + continue; \ + } \ + ) \ + } else { \ + _dst_hh->tbl = (dst)->hh_dst.tbl; \ + } \ + HASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, _dst_bkt); \ + HASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt], hh_dst, _dst_hh, _hs_oomed); \ + (dst)->hh_dst.tbl->num_items++; \ + IF_HASH_NONFATAL_OOM( \ + if (_hs_oomed) { \ + HASH_ROLLBACK_BKT(hh_dst, dst, _dst_hh); \ + HASH_DELETE_HH(hh_dst, dst, _dst_hh); \ + _dst_hh->tbl = NULL; \ + uthash_nonfatal_oom(_elt); \ + continue; \ + } \ + ) \ + HASH_BLOOM_ADD(_dst_hh->tbl, _dst_hh->hashv); \ + _last_elt = _elt; \ + _last_elt_hh = _dst_hh; \ + } \ + } \ + } \ + } \ + HASH_FSCK(hh_dst, dst, "HASH_SELECT"); \ +} while (0) + +#define HASH_CLEAR(hh,head) \ +do { \ + if ((head) != NULL) { \ + HASH_BLOOM_FREE((head)->hh.tbl); \ + uthash_free((head)->hh.tbl->buckets, \ + (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket)); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head) = NULL; \ + } \ +} while (0) + +#define HASH_OVERHEAD(hh,head) \ + (((head) != NULL) ? ( \ + (size_t)(((head)->hh.tbl->num_items * sizeof(UT_hash_handle)) + \ + ((head)->hh.tbl->num_buckets * sizeof(UT_hash_bucket)) + \ + sizeof(UT_hash_table) + \ + (HASH_BLOOM_BYTELEN))) : 0U) + +#ifdef NO_DECLTYPE +#define HASH_ITER(hh,head,el,tmp) \ +for(((el)=(head)), ((*(char**)(&(tmp)))=(char*)((head!=NULL)?(head)->hh.next:NULL)); \ + (el) != NULL; ((el)=(tmp)), ((*(char**)(&(tmp)))=(char*)((tmp!=NULL)?(tmp)->hh.next:NULL))) +#else +#define HASH_ITER(hh,head,el,tmp) \ +for(((el)=(head)), ((tmp)=DECLTYPE(el)((head!=NULL)?(head)->hh.next:NULL)); \ + (el) != NULL; ((el)=(tmp)), ((tmp)=DECLTYPE(el)((tmp!=NULL)?(tmp)->hh.next:NULL))) +#endif + +/* obtain a count of items in the hash */ +#define HASH_COUNT(head) HASH_CNT(hh,head) +#define HASH_CNT(hh,head) ((head != NULL)?((head)->hh.tbl->num_items):0U) + +typedef struct UT_hash_bucket { + struct UT_hash_handle *hh_head; + unsigned count; + + /* expand_mult is normally set to 0. In this situation, the max chain length + * threshold is enforced at its default value, HASH_BKT_CAPACITY_THRESH. (If + * the bucket's chain exceeds this length, bucket expansion is triggered). + * However, setting expand_mult to a non-zero value delays bucket expansion + * (that would be triggered by additions to this particular bucket) + * until its chain length reaches a *multiple* of HASH_BKT_CAPACITY_THRESH. + * (The multiplier is simply expand_mult+1). The whole idea of this + * multiplier is to reduce bucket expansions, since they are expensive, in + * situations where we know that a particular bucket tends to be overused. + * It is better to let its chain length grow to a longer yet-still-bounded + * value, than to do an O(n) bucket expansion too often. + */ + unsigned expand_mult; + +} UT_hash_bucket; + +/* random signature used only to find hash tables in external analysis */ +#define HASH_SIGNATURE 0xa0111fe1u +#define HASH_BLOOM_SIGNATURE 0xb12220f2u + +typedef struct UT_hash_table { + UT_hash_bucket *buckets; + unsigned num_buckets, log2_num_buckets; + unsigned num_items; + struct UT_hash_handle *tail; /* tail hh in app order, for fast append */ + ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */ + + /* in an ideal situation (all buckets used equally), no bucket would have + * more than ceil(#items/#buckets) items. that's the ideal chain length. */ + unsigned ideal_chain_maxlen; + + /* nonideal_items is the number of items in the hash whose chain position + * exceeds the ideal chain maxlen. these items pay the penalty for an uneven + * hash distribution; reaching them in a chain traversal takes >ideal steps */ + unsigned nonideal_items; + + /* ineffective expands occur when a bucket doubling was performed, but + * afterward, more than half the items in the hash had nonideal chain + * positions. If this happens on two consecutive expansions we inhibit any + * further expansion, as it's not helping; this happens when the hash + * function isn't a good fit for the key domain. When expansion is inhibited + * the hash will still work, albeit no longer in constant time. */ + unsigned ineff_expands, noexpand; + + uint32_t signature; /* used only to find hash tables in external analysis */ +#ifdef HASH_BLOOM + uint32_t bloom_sig; /* used only to test bloom exists in external analysis */ + uint8_t *bloom_bv; + uint8_t bloom_nbits; +#endif + +} UT_hash_table; + +typedef struct UT_hash_handle { + struct UT_hash_table *tbl; + void *prev; /* prev element in app order */ + void *next; /* next element in app order */ + struct UT_hash_handle *hh_prev; /* previous hh in bucket order */ + struct UT_hash_handle *hh_next; /* next hh in bucket order */ + const void *key; /* ptr to enclosing struct's key */ + unsigned keylen; /* enclosing struct's key len */ + unsigned hashv; /* result of hash-fcn(key) */ +} UT_hash_handle; + +#endif /* UTHASH_H */ diff --git a/ios/Tunnel/obfuscator/wg-obfuscator.c b/ios/Tunnel/obfuscator/wg-obfuscator.c new file mode 100644 index 0000000..9470d38 --- /dev/null +++ b/ios/Tunnel/obfuscator/wg-obfuscator.c @@ -0,0 +1,1320 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "wg-obfuscator.h" +#include "config.h" +#include "obfuscation.h" +#include "uthash.h" +#include "masking.h" + +// Verbosity level +int verbose = LL_DEFAULT; +// Section name (for multiple instances) +char section_name[256] = DEFAULT_INSTANCE_NAME; +// Listening socket for receiving data from the clients +static int listen_sock = 0; +// Hash table for client connections +static client_entry_t *conn_table = NULL; +// PIDs of the other instances, filled by the parent process only +static pid_t *child_pids = NULL; +static int child_pids_count = 0; +// Set by the SIGHUP handler, the log file is reopened from the main loop +static volatile sig_atomic_t log_reopen_pending = 0; + +// Hostname re-resolve: the blocking getaddrinfo() runs in a helper thread +#define RESOLVE_TAG_TARGET (-1) +typedef struct { + int32_t tag; // RESOLVE_TAG_TARGET or index into resolve_bindings + int32_t err; // getaddrinfo() error, 0 on success + uint32_t addr; // IPv4 address in network byte order +} resolve_result_t; + +static int resolve_wake_rd = -1; +static int resolve_wake_wr = -1; +static int resolve_result_rd = -1; +static int resolve_result_wr = -1; +static char resolve_target_host[256]; +static int resolve_target_is_name = 0; +static long resolve_interval_ms = 0; +static client_entry_t **resolve_bindings = NULL; +static int resolve_bindings_count = 0; + +#ifdef USE_EPOLL + static int epfd = 0; +#endif + +/** + * @brief Handles incoming signals for the application. + * + * This function is registered as a signal handler and is invoked when the process + * receives a signal. The specific actions taken depend on the signal received. + * + * @param signal The signal number received by the process. + */ +static void signal_handler(int signal) { + client_entry_t *current_entry, *tmp; + + // Close all connections and clean up + if (listen_sock) { + close(listen_sock); + } + HASH_ITER(hh, conn_table, current_entry, tmp) { + if (current_entry->server_sock) { + close(current_entry->server_sock); + } + HASH_DEL(conn_table, current_entry); + free(current_entry); + } +#ifdef USE_EPOLL + if (epfd) { + close(epfd); + } +#endif + log(LL_INFO, "Stopped."); + exit(signal != -1 ? EXIT_SUCCESS : EXIT_FAILURE); +} +#define FAILURE() signal_handler(-1) + +/** + * @brief Returns 1 if the string is a dotted IPv4 address, 0 otherwise. + */ +static int is_ipv4_literal(const char *host) +{ + struct in_addr tmp; + return host && *host && inet_pton(AF_INET, host, &tmp) == 1; +} + +/** + * @brief Resolves a hostname to an IPv4 address. + * + * @param host Hostname or IPv4 literal. + * @param out Filled with the first IPv4 address on success. + * @return 0 on success, a getaddrinfo() error code otherwise. + */ +static int resolve_hostname(const char *host, struct in_addr *out) +{ + struct addrinfo hints = { + .ai_family = AF_INET, + .ai_socktype = SOCK_DGRAM, + }; + struct addrinfo *addr = NULL; + int err = getaddrinfo(host, NULL, &hints, &addr); + if (err != 0 || addr == NULL) { + if (addr) { + freeaddrinfo(addr); + } + return err ? err : EAI_NONAME; + } + *out = ((struct sockaddr_in *)addr->ai_addr)->sin_addr; + freeaddrinfo(addr); + return 0; +} + +/** + * @brief Resolves a hostname at startup. + * + * On failure: if interval_ms is 0, returns the error immediately. Otherwise + * retries every interval_ms until the name resolves, so the daemon can start + * before the network or DNS is up. Sleep is interruptible by SIGINT/SIGTERM. + * + * @param host Hostname or IPv4 literal. + * @param out Filled with the first IPv4 address on success. + * @param interval_ms Retry interval in milliseconds, 0 to fail immediately. + * @param what Short label for the log, e.g. "target" or "static binding". + * @return 0 on success, a getaddrinfo() error code if interval_ms is 0 and resolve failed. + */ +static int resolve_hostname_startup(const char *host, struct in_addr *out, long interval_ms, const char *what) +{ + while (1) { + int err = resolve_hostname(host, out); + if (err == 0) { + return 0; + } + if (interval_ms <= 0) { + return err; + } + log(LL_WARN, "Can't resolve %s hostname '%s': %s, retrying in %ld seconds", + what, host, gai_strerror(err), interval_ms / 1000); + struct timespec ts = { + .tv_sec = interval_ms / 1000, + .tv_nsec = (interval_ms % 1000) * 1000000L, + }; + while (nanosleep(&ts, &ts) < 0 && errno == EINTR) { + } + } +} + +/** + * @brief Sends one resolve result to the main thread. The write is at most + * PIPE_BUF bytes, so it is atomic and cannot interleave with another result. + */ +static void resolve_send_result(int32_t tag, int32_t err, uint32_t addr) +{ + resolve_result_t r = { .tag = tag, .err = err, .addr = addr }; + const char *p = (const char *)&r; + size_t left = sizeof(r); + while (left) { + ssize_t n = write(resolve_result_wr, p, left); + if (n < 0) { + if (errno == EINTR) { + continue; + } + return; + } + p += n; + left -= (size_t)n; + } +} + +static void resolve_one(int32_t tag, const char *host) +{ + struct in_addr addr; + int err = resolve_hostname(host, &addr); + resolve_send_result(tag, err, err ? 0 : addr.s_addr); +} + +/** + * @brief Resolver thread: waits for SIGHUP (wake pipe) or the refresh interval, + * then re-resolves every non-literal hostname. Never calls log(). + */ +static void *resolve_thread_fn(void *arg) +{ + (void)arg; + struct pollfd pfd = { .fd = resolve_wake_rd, .events = POLLIN }; + while (1) { + int timeout = resolve_interval_ms > 0 ? (int)resolve_interval_ms : -1; + int ret = poll(&pfd, 1, timeout); + if (ret < 0) { + if (errno == EINTR) { + continue; + } + continue; + } + if (ret > 0 && (pfd.revents & POLLIN)) { + char buf[64]; + while (read(resolve_wake_rd, buf, sizeof(buf)) > 0) { + } + } + if (resolve_target_is_name) { + resolve_one(RESOLVE_TAG_TARGET, resolve_target_host); + } + for (int i = 0; i < resolve_bindings_count; i++) { + if (resolve_bindings[i] && resolve_bindings[i]->bind_host[0]) { + resolve_one(i, resolve_bindings[i]->bind_host); + } + } + } + return NULL; +} + +/** + * @brief Wakes the resolver thread. Safe to call from a signal handler. + */ +static void resolve_wake(void) +{ + char c = 1; + if (resolve_wake_wr < 0) { + return; + } + if (write(resolve_wake_wr, &c, 1) < 0 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) { + // Nothing useful can be done from a signal handler + } +} + +/** + * @brief Applies one re-resolve result on the main thread. + */ +static void apply_resolve_result(const resolve_result_t *r, struct sockaddr_in *forward_addr) +{ + if (r->err != 0) { + const char *name = "?"; + if (r->tag == RESOLVE_TAG_TARGET) { + name = resolve_target_host; + } else if (r->tag >= 0 && r->tag < resolve_bindings_count && resolve_bindings[r->tag]) { + name = resolve_bindings[r->tag]->bind_host; + } + log(LL_WARN, "Can't re-resolve hostname '%s': %s", name, gai_strerror(r->err)); + return; + } + + if (r->tag == RESOLVE_TAG_TARGET) { + if (forward_addr->sin_addr.s_addr == r->addr) { + return; + } + char old_ip[INET_ADDRSTRLEN], new_ip[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &forward_addr->sin_addr, old_ip, sizeof(old_ip)); + forward_addr->sin_addr.s_addr = r->addr; + inet_ntop(AF_INET, &forward_addr->sin_addr, new_ip, sizeof(new_ip)); + log(LL_INFO, "Target address changed: %s -> %s", old_ip, new_ip); + + client_entry_t *e, *tmp; + HASH_ITER(hh, conn_table, e, tmp) { + if (connect(e->server_sock, (struct sockaddr *)forward_addr, sizeof(*forward_addr)) < 0) { + serror_level(LL_WARN, "Failed to update target address for client %s:%d", + inet_ntoa(e->client_addr.sin_addr), ntohs(e->client_addr.sin_port)); + } + } + return; + } + + if (r->tag < 0 || r->tag >= resolve_bindings_count) { + return; + } + client_entry_t *entry = resolve_bindings[r->tag]; + if (!entry || !entry->is_static) { + return; + } + if (entry->client_addr.sin_addr.s_addr == r->addr) { + return; + } + + struct sockaddr_in new_addr = entry->client_addr; + new_addr.sin_addr.s_addr = r->addr; + + client_entry_t *collision = NULL; + HASH_FIND(hh, conn_table, &new_addr, sizeof(new_addr), collision); + if (collision && collision != entry) { + log(LL_WARN, "Static binding '%s' re-resolved to %s:%d, but that address is already in use", + entry->bind_host, inet_ntoa(new_addr.sin_addr), ntohs(new_addr.sin_port)); + return; + } + + char old_ip[INET_ADDRSTRLEN], new_ip[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &entry->client_addr.sin_addr, old_ip, sizeof(old_ip)); + inet_ntop(AF_INET, &new_addr.sin_addr, new_ip, sizeof(new_ip)); + + HASH_DEL(conn_table, entry); + entry->client_addr.sin_addr.s_addr = r->addr; + HASH_ADD(hh, conn_table, client_addr, sizeof(entry->client_addr), entry); + log(LL_INFO, "Static binding '%s' address changed: %s -> %s", entry->bind_host, old_ip, new_ip); +} + +/** + * @brief Reads every queued re-resolve result. Each write is atomic, so one + * read of sizeof(resolve_result_t) is one message. + */ +static void drain_resolve_results(struct sockaddr_in *forward_addr) +{ + resolve_result_t r; + while (1) { + ssize_t n = read(resolve_result_rd, &r, sizeof(r)); + if (n < 0) { + if (errno == EINTR) { + continue; + } + if (errno == EAGAIN || errno == EWOULDBLOCK) { + break; + } + serror_level(LL_WARN, "Failed to read hostname re-resolve result"); + break; + } + if (n == 0) { + break; + } + if (n != (ssize_t)sizeof(r)) { + log(LL_WARN, "Short read from hostname resolver (%zd bytes)", n); + break; + } + apply_resolve_result(&r, forward_addr); + } +} + +/** + * @brief Starts the resolver thread if there is at least one hostname to refresh. + */ +static void resolve_start_thread(void) +{ + int wake[2], result[2]; + pthread_t thread; + + if (!resolve_target_is_name && resolve_bindings_count == 0) { + return; + } + + if (pipe(wake) < 0 || pipe(result) < 0) { + log(LL_WARN, "Can't create pipes for hostname re-resolve, periodic refresh disabled"); + return; + } + resolve_wake_rd = wake[0]; + resolve_wake_wr = wake[1]; + resolve_result_rd = result[0]; + resolve_result_wr = result[1]; + + fcntl(resolve_wake_rd, F_SETFL, O_NONBLOCK); + fcntl(resolve_wake_wr, F_SETFL, O_NONBLOCK); + fcntl(resolve_result_rd, F_SETFL, O_NONBLOCK); + + if (pthread_create(&thread, NULL, resolve_thread_fn, NULL) != 0) { + log(LL_WARN, "Can't start hostname resolver thread, periodic refresh disabled"); + close(resolve_wake_rd); + close(resolve_wake_wr); + close(resolve_result_rd); + close(resolve_result_wr); + resolve_wake_rd = resolve_wake_wr = resolve_result_rd = resolve_result_wr = -1; + return; + } + pthread_detach(thread); + + if (resolve_interval_ms > 0) { + log(LL_INFO, "Re-resolving hostnames every %ld seconds (and on SIGHUP)", resolve_interval_ms / 1000); + } else { + log(LL_INFO, "Re-resolving hostnames on SIGHUP"); + } +} + +/** + * @brief Remembers the PID of a forked instance. + * + * Called by the configuration parser for every additional section, so that the + * parent process can forward signals to all the instances. + * + * @param pid PID of the forked instance. + */ +void register_child_instance(pid_t pid) +{ + pid_t *new_pids = realloc(child_pids, (child_pids_count + 1) * sizeof(pid_t)); + if (!new_pids) { + log(LL_WARN, "Out of memory, instance with PID %ld will not receive forwarded signals", (long)pid); + return; + } + child_pids = new_pids; + child_pids[child_pids_count++] = pid; +} + +#ifdef SIGHUP +/** + * @brief Handles SIGHUP: schedules reopening of the log file. + * + * The actual reopening is done by the main loop, the handler only sets a flag + * and forwards the signal to the instances of the other configuration sections. + * + * @param sig Signal number received by the process. + */ +static void sighup_handler(int sig) +{ + (void)sig; + log_reopen_pending = 1; + resolve_wake(); + // The list is filled before the handler is installed, so it can't change here + for (int i = 0; i < child_pids_count; i++) { + kill(child_pids[i], SIGHUP); + } +} +#endif + +/** + * @brief Creates a new client_entry_t structure and initializes it with the provided client and forward addresses. + * + * @param config Pointer to the obfuscator configuration structure. + * @param client_addr Pointer to a struct sockaddr_in representing the client's address. + * @param forward_addr Pointer to a struct sockaddr_in representing the address to which traffic should be forwarded. + * @return Pointer to the newly created client_entry_t structure, or NULL on failure. + */ +static client_entry_t * new_client_entry(obfuscator_config_t *config, struct sockaddr_in *client_addr, struct sockaddr_in *forward_addr) { + if (HASH_COUNT(conn_table) >= config->max_clients) { + log(LL_ERROR, "Maximum number of clients reached (%d), cannot add new client", config->max_clients); + return NULL; + } + client_entry_t * client_entry = malloc(sizeof(client_entry_t)); + if (!client_entry) { + log(LL_ERROR, "Failed to allocate memory for client entry"); + return NULL; + } + memset(client_entry, 0, sizeof(client_entry_t)); + // Set default version (latest) + client_entry->version = OBFUSCATION_VERSION; + // Set the client address + memcpy(&client_entry->client_addr, client_addr, sizeof(client_entry->client_addr)); + // Create a socket for the server connection + client_entry->server_sock = socket(AF_INET, SOCK_DGRAM, 0); + // TODO: add client address to log + if (client_entry->server_sock < 0) { + serror("Failed to create server socket for client"); + free(client_entry); + return NULL; + } +#ifdef __linux__ + // Set "Don't Fragment" flag + int optval = 1; + if (setsockopt(client_entry->server_sock, IPPROTO_IP, IP_MTU_DISCOVER, &optval, sizeof(optval)) < 0) { + serror("Failed to set 'don't fragment' flag for client"); + close(client_entry->server_sock); + free(client_entry); + return NULL; + } + if (config->fwmark) { + if (setsockopt(client_entry->server_sock, SOL_SOCKET, SO_MARK, &config->fwmark, sizeof(config->fwmark)) < 0) { + log(LL_WARN, "Failed to set 'firewall mark' for client: %s", strerror(errno)); + } + } +#endif + // Set the server address to the specified one + connect(client_entry->server_sock, (struct sockaddr *)forward_addr, sizeof(*forward_addr)); + // Get the assigned port number + socklen_t our_addr_len = sizeof(client_entry->our_addr); + if (getsockname(client_entry->server_sock, (struct sockaddr *)&client_entry->our_addr, &our_addr_len) == -1) { + serror("Failed to get socket port number"); + close(client_entry->server_sock); + free(client_entry); + return NULL; + } + +#ifdef USE_EPOLL + struct epoll_event e = { + .events = EPOLLIN, + .data.ptr = client_entry + }; + if (epoll_ctl(epfd, EPOLL_CTL_ADD, client_entry->server_sock, &e) != 0) { + serror("epoll_ctl for client socket"); + close(client_entry->server_sock); + free(client_entry); + return NULL; + } +#endif + + HASH_ADD(hh, conn_table, client_addr, sizeof(*client_addr), client_entry); + + log(LL_DEBUG, "Added binding: %s:%d:%d", + inet_ntoa(client_entry->client_addr.sin_addr), ntohs(client_entry->client_addr.sin_port), + ntohs(client_entry->our_addr.sin_port)); + + return client_entry; +} + +/** + * @brief Creates a new static client entry. + * + * This function allocates and initializes a new client_entry_t structure + * using the provided client and forward addresses, as well as the specified local port. + * + * @param config Pointer to the obfuscator configuration structure. + * @param client_addr Pointer to a sockaddr_in structure representing the client's address. + * @param forward_addr Pointer to a sockaddr_in structure representing the address to forward to. + * @param local_port The local port number to connect to the server. + * @param bind_host Original hostname from the configuration, stored if it is not an IPv4 literal. + * @return Pointer to the newly created client_entry_t structure, or NULL on failure. + */ +static client_entry_t * new_client_entry_static(obfuscator_config_t *config, struct sockaddr_in *client_addr, struct sockaddr_in *forward_addr, uint16_t local_port, const char *bind_host) { + if (HASH_COUNT(conn_table) >= config->max_clients) { + log(LL_ERROR, "Maximum number of clients reached (%d), cannot add new client", config->max_clients); + return NULL; + } + + // Check if such client already exists + client_entry_t *existing_entry; + HASH_FIND(hh, conn_table, client_addr, sizeof(*client_addr), existing_entry); + if (existing_entry) { + log(LL_ERROR, "Binding with client %s:%d already exists", + inet_ntoa(client_addr->sin_addr), ntohs(client_addr->sin_port)); + return NULL; + } + + client_entry_t * client_entry = malloc(sizeof(client_entry_t)); + if (!client_entry) { + log(LL_ERROR, "Failed to allocate memory for client entry"); + return NULL; + } + memset(client_entry, 0, sizeof(client_entry_t)); + // Set default version (latest) + client_entry->version = OBFUSCATION_VERSION; + // default masking type + client_entry->masking_handler = config->masking_handler; + // Set the client address + memcpy(&client_entry->client_addr, client_addr, sizeof(client_entry->client_addr)); + // Create a socket for the server connection + client_entry->server_sock = socket(AF_INET, SOCK_DGRAM, 0); + if (client_entry->server_sock < 0) { + serror("Failed to create server socket for client"); + free(client_entry); + return NULL; + } + // Bind the socket to the specified local port + client_entry->our_addr.sin_family = AF_INET; + // TODO: ability to bind to a specific address + client_entry->our_addr.sin_addr.s_addr = INADDR_ANY; + client_entry->our_addr.sin_port = htons(local_port); + // Set the local port number + if (bind(client_entry->server_sock, (struct sockaddr *)&client_entry->our_addr, sizeof(client_entry->our_addr)) < 0) { + serror("Failed to bind server socket to %s:%d", + inet_ntoa(client_entry->our_addr.sin_addr), local_port); + close(client_entry->server_sock); + free(client_entry); + return NULL; + } +#ifdef __linux__ + // Set "Don't Fragment" flag + int optval = 1; + if (setsockopt(client_entry->server_sock, IPPROTO_IP, IP_MTU_DISCOVER, &optval, sizeof(optval)) < 0) { + serror("Failed to set 'don't fragment' flag for client %s:%d", + inet_ntoa(client_entry->client_addr.sin_addr), local_port); + close(client_entry->server_sock); + free(client_entry); + return NULL; + } + if (config->fwmark) { + if (setsockopt(client_entry->server_sock, SOL_SOCKET, SO_MARK, &config->fwmark, sizeof(config->fwmark)) < 0) { + log(LL_WARN, "Failed to set 'firewall mark' for client %s:%d: %s", + inet_ntoa(client_entry->client_addr.sin_addr), ntohs(client_entry->client_addr.sin_port), strerror(errno)); + } + } + +#endif + // Set the server address to the specified one + connect(client_entry->server_sock, (struct sockaddr *)forward_addr, sizeof(*forward_addr)); + +#ifdef USE_EPOLL + struct epoll_event e = { + .events = EPOLLIN, + .data.ptr = client_entry + }; + if (epoll_ctl(epfd, EPOLL_CTL_ADD, client_entry->server_sock, &e) != 0) { + serror("epoll_ctl for client socket"); + close(client_entry->server_sock); + free(client_entry); + return NULL; + } +#endif + + client_entry->is_static = 1; + if (bind_host && !is_ipv4_literal(bind_host)) { + strncpy(client_entry->bind_host, bind_host, sizeof(client_entry->bind_host) - 1); + } + + HASH_ADD(hh, conn_table, client_addr, sizeof(*client_addr), client_entry); + + return client_entry; +} + +#ifndef USE_EPOLL +static client_entry_t *find_by_server_sock(int fd) { + client_entry_t *e, *tmp; + HASH_ITER(hh, conn_table, e, tmp) { + if (e->server_sock == fd) return e; + } + return NULL; +} +#endif + +/** + * @brief Prints the version information of the program. + * + * This function outputs the current version of the application to the standard output. + * Typically used to inform users about the build or release version. + */ +const char *version_string(void) { +#ifdef COMMIT +#ifndef ARCH + return "WireGuard Obfuscator (commit " COMMIT " @ " WG_OBFUSCATOR_GIT_REPO ")"; +#else + return "WireGuard Obfuscator (commit " COMMIT " @ " WG_OBFUSCATOR_GIT_REPO ") (" ARCH ")"; +#endif +#else +#ifndef ARCH + return "WireGuard Obfuscator v" WG_OBFUSCATOR_VERSION; +#else + return "WireGuard Obfuscator v" WG_OBFUSCATOR_VERSION " (" ARCH ")"; +#endif +#endif +} + +void print_version(void) { + fprintf(stderr, "Starting %s\n", version_string()); +} + +int main(int argc, char *argv[]) { + obfuscator_config_t config = {0}; + struct sockaddr_in + listen_addr, // Address for listening socket, for receiving data from the client + forward_addr; // Address for forwarding socket, for sending data to the server + uint8_t full_buffer[BUFFER_SIZE + PREBUFFER_SIZE]; + char target_host[256] = {0}; + int target_port = -1; + int key_length = 0; + in_addr_t s_listen_addr_client = INADDR_ANY; + long now, last_cleanup_time = 0; + struct addrinfo *addr; + int err; + struct addrinfo hints = { // for getaddrinfo + .ai_family = AF_INET, // IPv4 + .ai_socktype = SOCK_DGRAM, // UDP + }; + + print_version(); + + if (parse_config(argc, argv, &config) != 0) { + exit(EXIT_FAILURE); + } + + /* Start writing to the log file before validating the rest of the parameters, + so that configuration errors are logged there too */ + log_init(config.log_file_set ? config.log_file : NULL, config.log_timestamps); + +#ifdef USE_EPOLL + struct epoll_event events[MAX_EVENTS]; +#else + struct pollfd pollfds[config.max_clients + 2]; +#endif + + /* Check the parameters */ + // Check the listening port + if (!config.listen_port_set) { + log(LL_ERROR, "'source-lport' is not set in the configuration file"); + exit(EXIT_FAILURE); + } + + // Check the key + key_length = strlen(config.xor_key); + if (!config.xor_key_set || key_length == 0) { + log(LL_ERROR, "'key' is not set in the configuration file"); + exit(EXIT_FAILURE); + } + + // Check the target host and port + if (!config.forward_host_port_set) { + log(LL_ERROR, "'target' is not set"); + exit(EXIT_FAILURE); + } else { + char *port_delimiter = strchr(config.forward_host_port, ':'); + if (port_delimiter == NULL) { + log(LL_ERROR, "Invalid target host:port format: %s", config.forward_host_port); + exit(EXIT_FAILURE); + } + *port_delimiter = 0; + strncpy(target_host, config.forward_host_port, sizeof(target_host) - 1); + target_host[sizeof(target_host) - 1] = 0; // Ensure null-termination + target_port = atoi(port_delimiter + 1); + if (target_port <= 0) { + log(LL_ERROR, "Invalid target port: %s", port_delimiter + 1); + exit(EXIT_FAILURE); + } + } + + // 'allow-clean' is incompatible with static bindings: for a static binding + // there is no way to know in advance whether the client's traffic must be obfuscated + if (config.allow_clean && config.static_bindings_set) { + log(LL_ERROR, "'allow-clean' cannot be used together with 'static-bindings'"); + exit(EXIT_FAILURE); + } + + // Check the client interface + if (config.client_interface_set) { + s_listen_addr_client = inet_addr(config.client_interface); + if (s_listen_addr_client == INADDR_NONE) { + err = getaddrinfo(config.client_interface, NULL, &hints, &addr); + if (err != 0 || addr == NULL) { + log(LL_ERROR, "Invalid source interface '%s': %s", config.client_interface, gai_strerror(err)); + exit(EXIT_FAILURE); + } + s_listen_addr_client = ((struct sockaddr_in *)addr->ai_addr)->sin_addr.s_addr; + freeaddrinfo(addr); + } + } + + /* Set up signal handlers */ + signal(SIGINT, signal_handler); + signal(SIGTERM, signal_handler); +#ifdef SIGHUP + signal(SIGHUP, sighup_handler); +#endif + + /* Create listening socket */ + if ((listen_sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + serror("Can't create source socket to listen"); + exit(EXIT_FAILURE); + } + +#ifdef __linux__ + /* Set "Don't Fragment" flag */ + int optval = 1; + if (setsockopt(listen_sock, IPPROTO_IP, IP_MTU_DISCOVER, &optval, sizeof(optval)) < 0) { + serror("Failed to set 'don't fragment' flag for listening socket"); + FAILURE(); + } + if (config.fwmark) { + if (setsockopt(listen_sock, SOL_SOCKET, SO_MARK, &config.fwmark, sizeof(config.fwmark)) < 0) { + log(LL_WARN, "Failed to set 'firewall mark' for listening socket: %s", strerror(errno)); + } + } +#endif + + /* Bind the listening socket to the specified address and port */ + memset(&listen_addr, 0, sizeof(listen_addr)); + listen_addr.sin_family = AF_INET; + listen_addr.sin_addr.s_addr = s_listen_addr_client; + listen_addr.sin_port = htons(config.listen_port); + if (bind(listen_sock, (struct sockaddr *)&listen_addr, sizeof(listen_addr)) < 0) { + serror("Failed to bind source socket to %s:%d", + inet_ntoa(listen_addr.sin_addr), ntohs(listen_addr.sin_port)); + FAILURE(); + } + log(LL_INFO, "Listening on port %s:%d for source", inet_ntoa(listen_addr.sin_addr), ntohs(listen_addr.sin_port)); + + if (config.masking_handler_set) { + log(LL_INFO, "Using masking type: %s", config.masking_handler ? config.masking_handler->name : "none"); + } + + if (config.allow_clean) { + log(LL_INFO, "Non-obfuscated (clean) clients are allowed, their traffic will be forwarded as is"); + } + + /* Use epoll for events if enabled */ +#ifdef USE_EPOLL + epfd = epoll_create1(0); + if (epfd < 0) { + serror("epoll_create1"); + FAILURE(); + } + { + struct epoll_event ev = { + .events = EPOLLIN, + .data.fd = listen_sock + }; + if (epoll_ctl(epfd, EPOLL_CTL_ADD, listen_sock, &ev) != 0) { + serror("epoll_ctl for listen_sock"); + FAILURE(); + } + } +#endif + + /* Set up forward address */ + memset(&forward_addr, 0, sizeof(forward_addr)); + forward_addr.sin_family = AF_INET; + resolve_interval_ms = config.resolve_interval; + err = resolve_hostname_startup(target_host, &forward_addr.sin_addr, resolve_interval_ms, "target"); + if (err != 0) { + log(LL_ERROR, "Can't resolve hostname '%s': %s", target_host, gai_strerror(err)); + FAILURE(); + } + log(LL_DEBUG, "Resolved target hostname '%s' to %s", target_host, inet_ntoa(forward_addr.sin_addr)); + if (target_port > 65535) { + log(LL_ERROR, "Invalid target port: %d", target_port); + FAILURE(); + } + forward_addr.sin_port = htons(target_port); + log(LL_INFO, "Target: %s:%d", target_host, target_port); + strncpy(resolve_target_host, target_host, sizeof(resolve_target_host) - 1); + resolve_target_is_name = !is_ipv4_literal(target_host); + + /* Add static bindings if provided */ + if (config.static_bindings) { + // Parse static bindings + char *binding = strtok(config.static_bindings, ","); + while (binding) { + // Trim leading and trailing spaces + binding = trim(binding); + char *colon1 = strchr(binding, ':'); + if (!colon1) { + log(LL_ERROR, "Invalid static binding format: %s", binding); + exit(EXIT_FAILURE); + } + char *colon2 = strchr(colon1 + 1, ':'); + if (!colon2) { + log(LL_ERROR, "Invalid static binding format: %s", binding); + exit(EXIT_FAILURE); + } + *colon1 = 0; + *colon2 = 0; + + struct sockaddr_in client_addr = {0}; + client_addr.sin_family = AF_INET; + err = resolve_hostname_startup(binding, &client_addr.sin_addr, resolve_interval_ms, "static binding"); + if (err != 0) { + log(LL_ERROR, "Can't resolve hostname '%s' for static binding '%s:%s:%s': %s", + binding, binding, colon1 + 1, colon2 + 1, gai_strerror(err)); + FAILURE(); + } + log(LL_DEBUG, "Resolved static binding hostname '%s' to %s", binding, inet_ntoa(client_addr.sin_addr)); + int remote_port = atoi(colon1 + 1); + if (remote_port <= 0 || remote_port > 65535) { + log(LL_ERROR, "Invalid port '%s' for static binding '%s:%s:%s'", + colon1 + 1, binding, colon1 + 1, colon2 + 1); + FAILURE(); + } + int local_port = atoi(colon2 + 1); + if (local_port <= 0 || local_port > 65535) { + log(LL_ERROR, "Invalid port '%s' for static binding '%s:%s:%s'", + colon2 + 1, binding, colon1 + 1, colon2 + 1); + FAILURE(); + } + client_addr.sin_port = htons(remote_port); + + if (!new_client_entry_static(&config, &client_addr, &forward_addr, local_port, binding)) { + log(LL_ERROR, "Failed to create static binding: %s:%s:%s", + binding, colon1 + 1, colon2 + 1); + FAILURE(); + } + + log(LL_INFO, "Added static binding: %s:%d <-> %d:obfuscator:%d <-> %s:%d", + binding, remote_port, config.listen_port, + local_port, target_host, target_port); + + binding = strtok(NULL, ","); + } + free(config.static_bindings); + config.static_bindings = NULL; + } + + { + client_entry_t *e, *tmp; + int n = 0; + HASH_ITER(hh, conn_table, e, tmp) { + if (e->is_static && e->bind_host[0]) { + n++; + } + } + if (n > 0) { + resolve_bindings = calloc((size_t)n, sizeof(*resolve_bindings)); + if (!resolve_bindings) { + log(LL_WARN, "Out of memory, static binding hostnames will not be re-resolved"); + } else { + HASH_ITER(hh, conn_table, e, tmp) { + if (e->is_static && e->bind_host[0]) { + resolve_bindings[resolve_bindings_count++] = e; + } + } + } + } + resolve_start_thread(); +#ifdef USE_EPOLL + if (resolve_result_rd >= 0) { + struct epoll_event ev = { + .events = EPOLLIN, + .data.fd = resolve_result_rd + }; + if (epoll_ctl(epfd, EPOLL_CTL_ADD, resolve_result_rd, &ev) != 0) { + serror("epoll_ctl for hostname resolver"); + FAILURE(); + } + } +#endif + } + + log(LL_INFO, "WireGuard obfuscator successfully started"); + + /* Main loop */ + while (1) { + // Reopen the log file after it has been rotated, requested by SIGHUP + if (log_reopen_pending) { + log_reopen_pending = 0; + log_reopen(); + } + + // Using epoll or poll to wait for events +#ifdef USE_EPOLL + int events_n = epoll_wait(epfd, events, MAX_EVENTS, POLL_TIMEOUT); + if (events_n < 0) { + if (errno == EINTR) { + // Interrupted by a signal, e.g. SIGHUP + continue; + } + serror("epoll_wait"); + FAILURE(); + } +#else + int nfds = 0; + pollfds[nfds].fd = listen_sock; + pollfds[nfds].events = POLLIN; + nfds++; + if (resolve_result_rd >= 0) { + pollfds[nfds].fd = resolve_result_rd; + pollfds[nfds].events = POLLIN; + nfds++; + } + client_entry_t *entry, *tmp_entry; + HASH_ITER(hh, conn_table, entry, tmp_entry) { + if (nfds >= config.max_clients + 2) { + log(LL_DEBUG, "Too many clients, cannot add more"); + break; + } + pollfds[nfds].fd = entry->server_sock; + pollfds[nfds].events = POLLIN; + nfds++; + } + int ret = poll(pollfds, nfds, POLL_TIMEOUT); + if (ret < 0) { + if (errno == EINTR) { + // Interrupted by a signal, e.g. SIGHUP + continue; + } + serror("poll"); + FAILURE(); + } +#endif + + // Get the current time + struct timespec now_ts; + clock_gettime(CLOCK_MONOTONIC, &now_ts); + now = now_ts.tv_sec * 1000 + now_ts.tv_nsec / 1000000; + +#ifdef USE_EPOLL + for (int e = 0; e < events_n; e++) { + struct epoll_event *event = &events[e]; + if (resolve_result_rd >= 0 && event->data.fd == resolve_result_rd) { + drain_resolve_results(&forward_addr); + continue; + } + if (event->data.fd == listen_sock) { +#else + for (int e = 0; e < nfds; e++) if (pollfds[e].revents & POLLIN) { + if (resolve_result_rd >= 0 && pollfds[e].fd == resolve_result_rd) { + drain_resolve_results(&forward_addr); + continue; + } + if (pollfds[e].fd == listen_sock) { +#endif + /* *** Handle incoming data from the clients *** */ + uint8_t *buffer = full_buffer + PREBUFFER_SIZE; + struct sockaddr_in sender_addr = {0}; + socklen_t sender_addr_len = sizeof(sender_addr); + int length = recvfrom(listen_sock, buffer, BUFFER_SIZE, MSG_TRUNC | MSG_DONTWAIT, (struct sockaddr *)&sender_addr, &sender_addr_len); + if (length < 0) { + // A readiness notification does not guarantee that there is something + // to read by the time we get here, so an empty socket is not an error + if (errno != EAGAIN && errno != EWOULDBLOCK) { + serror_level(LL_DEBUG, "recvfrom client"); + } + continue; + } + if (length > BUFFER_SIZE) { + log(LL_DEBUG, "Received packet from %s:%d is too large (%d bytes), while buffer size is %d bytes, ignoring", + inet_ntoa(sender_addr.sin_addr), ntohs(sender_addr.sin_port), length, BUFFER_SIZE); + continue; + } + + // Find the client entry if any + client_entry_t *client_entry; + HASH_FIND(hh, conn_table, &sender_addr, sizeof(sender_addr), client_entry); + + uint8_t obfuscated = length >= 4 && is_obfuscated(buffer); + // Is it masked packet maybe? + masking_handler_t *masking_handler = config.masking_handler; + if (obfuscated) { + length = masking_unwrap_from_client(&buffer, length, &config, client_entry, listen_sock, &sender_addr, &forward_addr, &masking_handler); + if (length <= 0) { + // Nothing to do + continue; + } + } + // Check the length + if (length < 4) { + log(LL_DEBUG, "Received too short packet from %s:%d (%d bytes), ignoring", inet_ntoa(sender_addr.sin_addr), ntohs(sender_addr.sin_port), length); + continue; + } + + uint8_t version = client_entry ? client_entry->version : OBFUSCATION_VERSION; + + if (verbose >= LL_TRACE) { + log(LL_TRACE, "Received %d bytes from %s:%d to %s:%d (known=%s, obfuscated=%s)", + length, + inet_ntoa(sender_addr.sin_addr), ntohs(sender_addr.sin_port), + target_host, target_port, + client_entry ? "yes" : "no", obfuscated ? "yes" : "no"); + log_hexdump(LL_TRACE, obfuscated ? "X->: " : "O->: ", buffer, length); + } + + if (obfuscated) { + // decode + int original_length = length; + length = decode(buffer, length, config.xor_key, key_length, &version); + if (length < 4 || length > original_length) { + log(LL_DEBUG, "Failed to decode packet from %s:%d (original_length=%d, decoded_length=%d)", + inet_ntoa(sender_addr.sin_addr), ntohs(sender_addr.sin_port), original_length, length); + continue; + } + } + + // Is it handshake? + if (WG_TYPE(buffer) == WG_TYPE_HANDSHAKE) { + log(LL_DEBUG, "Received WireGuard handshake from %s:%d to %s:%d (%d bytes, obfuscated=%s)", + inet_ntoa(sender_addr.sin_addr), ntohs(sender_addr.sin_port), + target_host, target_port, + length, + obfuscated ? "yes" : "no"); + + if (!client_entry) { + client_entry = new_client_entry(&config, &sender_addr, &forward_addr); + if (!client_entry) { + continue; + } + client_entry->last_activity_time = now; + client_entry->last_incoming_time = 0; + client_entry->masking_handler = masking_handler; + } + if (config.allow_clean) { + // Remember whether this client speaks plain WireGuard, + // its traffic will be forwarded as is in both directions + if (!obfuscated && !client_entry->client_clean) { + log(LL_INFO, "Client %s:%d is not obfuscated, forwarding its traffic as is", + inet_ntoa(sender_addr.sin_addr), ntohs(sender_addr.sin_port)); + } + client_entry->client_clean = !obfuscated; + if (client_entry->client_clean) { + // No masking for clean clients + client_entry->masking_handler = NULL; + } + } + if (!obfuscated && !client_entry->client_clean) { + masking_on_handshake_req_from_client(&config, client_entry, listen_sock, &sender_addr, &forward_addr); + } + client_entry->handshake_direction = DIR_CLIENT_TO_SERVER; + client_entry->last_handshake_request_time = now; + } + // Is it handshake response? + else if (WG_TYPE(buffer) == WG_TYPE_HANDSHAKE_RESP) { + if (!client_entry) { + log(LL_DEBUG, "Received WireGuard handshake response from %s:%d, but no connection entry found for this client", + inet_ntoa(sender_addr.sin_addr), ntohs(sender_addr.sin_port)); + continue; + } + + log(LL_DEBUG, "Received WireGuard handshake response from %s:%d to %s:%d (%d bytes, obfuscated=%s)", + inet_ntoa(client_entry->client_addr.sin_addr), ntohs(client_entry->client_addr.sin_port), + target_host, target_port, + length, obfuscated ? "yes" : "no"); + + // Check handshake timeout + if (now - client_entry->last_handshake_request_time > HANDSHAKE_TIMEOUT) { + log(LL_DEBUG, "Ignoring WireGuard handshake response, handshake timeout"); + continue; + } + + if (client_entry->handshake_direction != DIR_SERVER_TO_CLIENT) { + log(LL_DEBUG, "Received handshake response from %s:%d to %s:%d, but the handshake direction is not set to server-to-client", + inet_ntoa(sender_addr.sin_addr), ntohs(sender_addr.sin_port), + target_host, target_port); + continue;; + } + + log(!client_entry->handshaked ? LL_INFO : LL_DEBUG, "Handshake established with %s:%d to %s:%d (reverse)", + inet_ntoa(sender_addr.sin_addr), ntohs(sender_addr.sin_port), + target_host, target_port); + client_entry->handshaked = 1; + client_entry->client_obfuscated = obfuscated; + client_entry->server_obfuscated = !obfuscated; + client_entry->last_handshake_time = now; + } + // If it's not a handshake or handshake response, connection is not established yet + else if (!client_entry || !client_entry->handshaked) { + log(LL_DEBUG, "Ignoring data (packet type #%u) from %s:%d to %s:%d until the handshake is completed", + WG_TYPE(buffer), + inet_ntoa(sender_addr.sin_addr), ntohs(sender_addr.sin_port), + target_host, target_port); + continue; + } + + // Version downgrade check + if (version < client_entry->version) { + log(LL_WARN, "Client %s:%d uses old obfuscation version, downgrading from %d to %d", inet_ntoa(sender_addr.sin_addr), ntohs(sender_addr.sin_port), + client_entry->version, version); + client_entry->version = version; + } + + if (!obfuscated && !client_entry->client_clean) { + // If the packet is not obfuscated, we need to encode it + length = encode(buffer, length, config.xor_key, key_length, client_entry->version, config.max_dummy_length_data); + if (length < 4) { + log(LL_ERROR, "Failed to encode packet from %s:%d (too short, length=%d)", + inet_ntoa(sender_addr.sin_addr), ntohs(sender_addr.sin_port), length); + continue; + } + length = masking_data_wrap_to_server(&buffer, length, &config, client_entry, listen_sock, &forward_addr); + } + + log_hexdump(LL_TRACE, (!obfuscated && !client_entry->client_clean) ? "X->: " : "O->: ", buffer, length); + + length = send(client_entry->server_sock, buffer, length, 0); + if (length < 0) { + serror_level(LL_DEBUG, "sendto %s:%d", target_host, target_port); + continue; + } + client_entry->last_activity_time = now; + } else { // if (event->data.fd == listen_sock) + /* *** Handle data from the server *** */ +#ifdef USE_EPOLL + client_entry_t *client_entry = event->data.ptr; +#else + client_entry_t *client_entry = find_by_server_sock(pollfds[e].fd); +#endif + uint8_t *buffer = full_buffer + PREBUFFER_SIZE; + int length = recv(client_entry->server_sock, buffer, BUFFER_SIZE, MSG_TRUNC | MSG_DONTWAIT); + if (length < 0) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + serror_level(LL_DEBUG, "recv from server"); + } + continue; + } + if (length > BUFFER_SIZE) { + log(LL_DEBUG, "Received packet from %s:%d is too large (%d bytes), while buffer size is %d bytes, ignoring", + target_host, target_port, length, BUFFER_SIZE); + continue; + } + uint8_t obfuscated = length >= 4 && is_obfuscated(buffer); + if (obfuscated) { + // Is it masked packet maybe? + length = masking_unwrap_from_server(&buffer, length, &config, client_entry, listen_sock, &forward_addr); + if (length <= 0) { + // Nothing to do + continue; + } + } + // Check the length + if (length < 4) { + log(LL_DEBUG, "Received too short packet from %s:%d (%d bytes), ignoring", target_host, target_port, length); + continue; + } + + uint8_t version = client_entry->version; + + if (verbose >= LL_TRACE) { + log(LL_TRACE, "Received %d bytes from %s:%d to %s:%d (obfuscated=%s)", + length, + target_host, target_port, + inet_ntoa(client_entry->client_addr.sin_addr), ntohs(client_entry->client_addr.sin_port), + obfuscated ? "yes" : "no"); + log_hexdump(LL_TRACE, obfuscated ? "<-X: " : "<-O: ", buffer, length); + } + + if (obfuscated) { + // decode + int original_length = length; + length = decode(buffer, length, config.xor_key, key_length, &version); + if (length < 4 || length > original_length) { + log(LL_DEBUG, "Failed to decode packet from %s:%d (original_length=%d, decoded_length=%d)", target_host, target_port, original_length, length); + continue; + } + } + + // Is it handshake? + if (WG_TYPE(buffer) == WG_TYPE_HANDSHAKE) { + log(LL_DEBUG, "Received WireGuard handshake from %s:%d to %s:%d (%d bytes, obfuscated=%s)", + target_host, target_port, + inet_ntoa(client_entry->client_addr.sin_addr), ntohs(client_entry->client_addr.sin_port), + length, + obfuscated ? "yes" : "no"); + if (!obfuscated && !client_entry->client_clean) { + // Send STUN binding request before the obfuscated handshake + masking_on_handshake_req_from_server(&config, client_entry, listen_sock, &client_entry->client_addr, &forward_addr); + } + client_entry->handshake_direction = DIR_SERVER_TO_CLIENT; + client_entry->last_handshake_request_time = now; + } + // Is it handshake response? + else if (WG_TYPE(buffer) == WG_TYPE_HANDSHAKE_RESP) { + log(LL_DEBUG, "Received WireGuard handshake response from %s:%d to %s:%d (%d bytes, obfuscated=%s)", + target_host, target_port, + inet_ntoa(client_entry->client_addr.sin_addr), ntohs(client_entry->client_addr.sin_port), + length, obfuscated ? "yes" : "no"); + + // Check handshake timeout + if (now - client_entry->last_handshake_request_time > HANDSHAKE_TIMEOUT) { + log(LL_DEBUG, "Ignoring WireGuard handshake response, handshake timeout"); + continue; + } + + if (client_entry->handshake_direction != DIR_CLIENT_TO_SERVER) { + log(LL_DEBUG, "Received handshake response from %s:%d to %s:%d, but the handshake direction is not set to client-to-server", + target_host, target_port, + inet_ntoa(client_entry->client_addr.sin_addr), ntohs(client_entry->client_addr.sin_port)); + continue; + } + + log(!client_entry->handshaked ? LL_INFO : LL_DEBUG, "Handshake established with %s:%d to %s:%d (direct)", + inet_ntoa(client_entry->client_addr.sin_addr), ntohs(client_entry->client_addr.sin_port), + target_host, target_port); + if (!client_entry->handshaked && client_entry->masking_handler && !config.masking_handler_set) { + log(LL_INFO, "Autodetected masking handler for client %s:%d: %s", inet_ntoa(client_entry->client_addr.sin_addr), ntohs(client_entry->client_addr.sin_port), client_entry->masking_handler->name); + } + client_entry->handshaked = 1; + client_entry->client_obfuscated = !obfuscated && !client_entry->client_clean; + client_entry->server_obfuscated = obfuscated; + client_entry->last_handshake_time = now; + } + // If it's not a handshake or handshake response, connection is not established yet + else if (!client_entry->handshaked) { + log(LL_DEBUG, "Ignoring response (packet type #%u) from %s:%d to %s:%d until the handshake is completed", + WG_TYPE(buffer), + target_host, target_port, + inet_ntoa(client_entry->client_addr.sin_addr), ntohs(client_entry->client_addr.sin_port)); + continue; + } + + // Version downgrade check + if (version < client_entry->version) { + log(LL_WARN, "Server %s:%d uses old obfuscation version, downgrading from %d to %d", + target_host, target_port, client_entry->version, version); + client_entry->version = version; + } + + if (!obfuscated && !client_entry->client_clean) { + // If the packet is not obfuscated, we need to encode it + length = encode(buffer, length, config.xor_key, key_length, client_entry->version, config.max_dummy_length_data); + if (length < 4) { + log(LL_ERROR, "Failed to encode packet from %s:%d", target_host, target_port); + continue; + } + length = masking_data_wrap_to_client(&buffer, length, &config, client_entry, listen_sock, &forward_addr); + } + + log_hexdump(LL_TRACE, (!obfuscated && !client_entry->client_clean) ? "<-X: " : "<-O: ", buffer, length); + + // Send the response back to the original client + length = sendto(listen_sock, buffer, length, 0, (struct sockaddr *)&client_entry->client_addr, sizeof(client_entry->client_addr)); + if (length < 0) { + serror_level(LL_DEBUG, "sendto %s:%d", inet_ntoa(client_entry->client_addr.sin_addr), ntohs(client_entry->client_addr.sin_port)); + continue; + } + client_entry->last_activity_time = now; + client_entry->last_incoming_time = now; + } // if (event->data.fd != listen_sock) + } // for (int e = 0; e < events_n; e++) + + if (now - last_cleanup_time >= ITERATE_INTERVAL) { + client_entry_t *current_entry, *tmp_current_entry; + // Iterate over all client entries + HASH_ITER(hh, conn_table, current_entry, tmp_current_entry) { + // Check if the entry is idle for too long + uint8_t idle = now - current_entry->last_activity_time >= config.idle_timeout; + uint8_t incoming_timeout = config.in_timeout > 0 && now - current_entry->last_incoming_time >= config.in_timeout; + uint8_t handshake_timeout = !current_entry->handshaked && now - current_entry->last_activity_time >= HANDSHAKE_TIMEOUT; + if ((idle || incoming_timeout || handshake_timeout) && !current_entry->is_static) { // Do not remove static entries + // Remove old entry + if (idle) { + log(LL_INFO, "Removing idle client %s:%d", inet_ntoa(current_entry->client_addr.sin_addr), ntohs(current_entry->client_addr.sin_port)); + } else if (incoming_timeout) { + log(LL_INFO, "Removing client %s:%d due to incoming timeout", inet_ntoa(current_entry->client_addr.sin_addr), ntohs(current_entry->client_addr.sin_port)); + } else if (handshake_timeout) { + log(LL_DEBUG, "Removing client %s:%d due to handshake timeout", inet_ntoa(current_entry->client_addr.sin_addr), ntohs(current_entry->client_addr.sin_port)); + } +#ifdef USE_EPOLL + epoll_ctl(epfd, EPOLL_CTL_DEL, current_entry->server_sock, NULL); +#endif + close(current_entry->server_sock); + HASH_DEL(conn_table, current_entry); + free(current_entry); + continue; + } + + // Check if we need to call masking timer + if (current_entry->masking_handler && current_entry->masking_handler->timer_interval_s > 0 + && now - current_entry->last_masking_timer_time >= current_entry->masking_handler->timer_interval_s * 1000) { + current_entry->last_masking_timer_time = now; + masking_on_timer(&config, current_entry, listen_sock, &forward_addr); + } + } + // Update the last cleanup time + last_cleanup_time = now; + } + } // while (1) + + // You should never reach this point, but just in case + return 0; +} diff --git a/ios/Tunnel/obfuscator/wg-obfuscator.h b/ios/Tunnel/obfuscator/wg-obfuscator.h new file mode 100644 index 0000000..ca9c7a6 --- /dev/null +++ b/ios/Tunnel/obfuscator/wg-obfuscator.h @@ -0,0 +1,135 @@ +#ifndef _WG_OBFUSCATOR_H_ +#define _WG_OBFUSCATOR_H_ + +#include +#include +#include +#include +#include "uthash.h" + +// on Linux, use epoll for better performance +#ifdef __linux__ +#define USE_EPOLL +#endif + +#ifdef USE_EPOLL +#include +#define MAX_EVENTS 1024 +#else +#include +#endif + +#define WG_OBFUSCATOR_VERSION "1.6" +#define WG_OBFUSCATOR_GIT_REPO "https://github.com/ClusterM/wg-obfuscator" + +#define LL_DEFAULT LL_INFO // default logging level + +// Main parameters +// TODO: make these configurable via command line arguments or config file +#define BUFFER_SIZE 65535 // size of the buffer for receiving data from the clients and server +#define PREBUFFER_SIZE 1024 // size of the additional buffer size before the main buffer +#define POLL_TIMEOUT 5000 // in milliseconds +#define HANDSHAKE_TIMEOUT 5000 // in milliseconds +#define ITERATE_INTERVAL 1000 // in milliseconds +#define MAX_DUMMY_LENGTH_TOTAL 1024 // maximum length of a packet after dummy data extension +#define MAX_DUMMY_LENGTH_HANDSHAKE 512 // maximum length of dummy data for handshake packets + +#define MAX_CLIENTS_DEFAULT 1024 // maximum number of clients +#define IDLE_TIMEOUT_DEFAULT 300000 // in milliseconds +#define IN_TIMEOUT_DEFAULT 0 // in milliseconds +#define MAX_DUMMY_LENGTH_DATA_DEFAULT 4 // maximum length of dummy data for data packets + +// Default instance name +#define DEFAULT_INSTANCE_NAME "main" + +// Logging levels +#define LL_ERROR 0 +#define LL_WARN 1 +#define LL_INFO 2 +#define LL_DEBUG 3 +#define LL_TRACE 4 + +// Maximum length of a single log line, longer hex dumps are split into several lines +#define LOG_LINE_MAX 16384 + +#define log(level, fmt, ...) do { if (verbose >= (level)) log_printf((level), fmt, ##__VA_ARGS__); } while (0) +#define serror_level(level, fmt, ...) log(level, fmt " - %s (%d)", ##__VA_ARGS__, strerror(errno), errno) +#define serror(fmt, ...) serror_level(LL_ERROR, fmt, ##__VA_ARGS__) + +// Direction of... something +typedef enum { + DIR_CLIENT_TO_SERVER = 0, + DIR_SERVER_TO_CLIENT = 1, +} direction_t; + +struct masking_handler; // forward declaration +typedef struct masking_handler masking_handler_t; + +// Structure to hold obfuscator configuration +typedef struct { + int listen_port; // Listening port for the obfuscator + char forward_host_port[256]; // Host and port to forward the data to + char xor_key[256]; // Key for obfuscation + char client_interface[256]; // Client interface as a string + char *static_bindings; // Static bindings as a string + int max_clients; // Maximum number of clients + long idle_timeout; // Idle timeout in milliseconds + long in_timeout; // Incoming timeout in milliseconds + int max_dummy_length_data; // Maximum length of dummy data for data packets + uint32_t fwmark; // Firewall mark + masking_handler_t *masking_handler; // Masking handler to use + uint8_t allow_clean; // 1 if non-obfuscated (clean) clients are allowed, 0 otherwise + char log_file[512]; // Path of the log file + int8_t log_timestamps; // 1 to force timestamps on, 0 to force them off, -1 for auto + long resolve_interval; // Hostname re-resolve interval in milliseconds, 0 to disable periodic refresh + + uint8_t log_file_set; // 1 if the log file is set, 0 otherwise + uint8_t listen_port_set; // 1 if the listen port is set, 0 otherwise + uint8_t forward_host_port_set; // 1 if the forward host and port are set, 0 otherwise + uint8_t xor_key_set; // 1 if the XOR key is set, 0 otherwise + uint8_t client_interface_set; // 1 if the client interface is set, 0 otherwise + uint8_t static_bindings_set; // 1 if the static bindings are set, 0 otherwise + uint8_t masking_handler_set; // 1 if the masking handler is set, 0 otherwise +} obfuscator_config_t; + +// Structure to hold client connection information +typedef struct { + struct sockaddr_in client_addr; // client address and port (key for the hash table) + struct sockaddr_in our_addr; // our address and port on the server connection + long last_activity_time; // last time we received data from/to this client + long last_incoming_time; // last time we received data from the remote server + long last_handshake_request_time; // last time we received a handshake request from/to this client + long last_handshake_time; // last time we received a handshake response from/to this client + long last_masking_timer_time; // last time we called the masking timer handler for this client + int server_sock; // socket for the connection to the server + uint8_t version; // obfuscation version + masking_handler_t *masking_handler; // masking handler in use + uint8_t handshaked : 1; // 1 if the handshake is complete, 0 otherwise + uint8_t handshake_direction : 1; // 1 if the handshake is from client to server, 0 if from server to client + uint8_t client_obfuscated : 1; // 1 if the client is obfuscated, 0 otherwise + uint8_t server_obfuscated : 1; // 1 if the server is obfuscated, 0 otherwise + uint8_t client_clean : 1; // 1 if the client speaks plain (non-obfuscated) WireGuard, traffic is passed through as is (allow-clean mode) + uint8_t is_static : 1; // 1 if this is a static binding entry, 0 otherwise + char bind_host[256]; // Original hostname of a static binding, empty if the address is a literal or the entry is dynamic + UT_hash_handle hh; +} client_entry_t; + +// Verbosity level +extern int verbose; +// Section name (for multiple instances) +extern char section_name[256]; + +const char *version_string(void); +void print_version(void); +void register_child_instance(pid_t pid); + +void log_init(const char *path, int8_t timestamps_mode); +void log_reopen(void); +#ifdef __GNUC__ +void log_printf(int level, const char *fmt, ...) __attribute__((format(printf, 2, 3))); +#else +void log_printf(int level, const char *fmt, ...); +#endif +void log_hexdump(int level, const char *prefix, const uint8_t *data, int length); + +#endif diff --git a/ios/Tunnel/pcarelay.c b/ios/Tunnel/pcarelay.c new file mode 100644 index 0000000..013b630 --- /dev/null +++ b/ios/Tunnel/pcarelay.c @@ -0,0 +1,118 @@ +/* + * pcarelay — in-process transport switch for the iOS packet tunnel. + * wg-obfuscator (one instance, iOS forbids fork) sends to 127.0.0.1:; + * we forward either over UDP to the server's obfuscator port or over TCP to + * the server's udptcp (frames: 2-byte length + packet). Switch with pcarelay_use_tcp(). + */ +#include "pcarelay.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static atomic_int use_tcp = 0; +static atomic_ullong rx_bytes = 0; +static char g_host[256], g_udp_port[8], g_tcp_port[8]; +static int g_local_port; + +static int dial(int socktype, const char *port) { + struct addrinfo hints = {0}, *res, *r; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = socktype; + if (getaddrinfo(g_host, port, &hints, &res) != 0) return -1; + int s = -1; + for (r = res; r; r = r->ai_next) { + s = socket(r->ai_family, r->ai_socktype, 0); + if (s < 0) continue; + if (connect(s, r->ai_addr, r->ai_addrlen) == 0) break; + close(s); s = -1; + } + freeaddrinfo(res); + if (s >= 0 && socktype == SOCK_STREAM) { + int one = 1; + setsockopt(s, IPPROTO_TCP, TCP_NODELAY, &one, sizeof one); + setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, &one, sizeof one); + setsockopt(s, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof one); + } + return s; +} + +static int read_full(int fd, unsigned char *b, size_t n) { + size_t got = 0; + while (got < n) { ssize_t r = read(fd, b + got, n - got); if (r <= 0) return -1; got += (size_t)r; } + return 0; +} + +static void *loop(void *arg) { + (void)arg; + struct sockaddr_in la = {0}; + la.sin_family = AF_INET; + la.sin_port = htons((uint16_t)g_local_port); + la.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + int local = socket(AF_INET, SOCK_DGRAM, 0); + if (local < 0 || bind(local, (struct sockaddr *)&la, sizeof la) < 0) return NULL; + int udp = -1, tcp = -1; + struct sockaddr_storage peer; socklen_t pl = 0; + unsigned char buf[65537]; + time_t last_dial = 0; + for (;;) { + int want_tcp = atomic_load(&use_tcp); + if (!want_tcp && tcp >= 0) { close(tcp); tcp = -1; } + if (want_tcp && udp >= 0) { close(udp); udp = -1; } + if (!want_tcp && udp < 0 && time(NULL) != last_dial) { last_dial = time(NULL); udp = dial(SOCK_DGRAM, g_udp_port); } + int up = want_tcp ? tcp : udp; + struct pollfd p[2] = {{local, POLLIN, 0}, {up, POLLIN, 0}}; + poll(p, up >= 0 ? 2 : 1, 1000); + if (p[0].revents & POLLIN) { + pl = sizeof peer; + ssize_t n = recvfrom(local, buf, 65535, 0, (struct sockaddr *)&peer, &pl); + if (n <= 0) continue; + if (want_tcp) { + if (tcp < 0 && time(NULL) != last_dial) { last_dial = time(NULL); tcp = dial(SOCK_STREAM, g_tcp_port); } + if (tcp >= 0) { + unsigned char h[2] = {(unsigned char)(n >> 8), (unsigned char)n}; + if (send(tcp, h, 2, 0) != 2 || send(tcp, buf, (size_t)n, 0) != n) { close(tcp); tcp = -1; } + } + } else if (udp >= 0) { + send(udp, buf, (size_t)n, 0); + } + } + if (up >= 0 && (p[1].revents & (POLLIN | POLLHUP | POLLERR))) { + ssize_t n; + if (want_tcp) { + unsigned char h[2]; + if (read_full(tcp, h, 2) < 0) { close(tcp); tcp = -1; continue; } + n = (h[0] << 8) | h[1]; + if (n == 0 || read_full(tcp, buf, (size_t)n) < 0) { close(tcp); tcp = -1; continue; } + } else { + n = recv(udp, buf, 65535, 0); + if (n <= 0) { close(udp); udp = -1; continue; } + } + atomic_fetch_add(&rx_bytes, (unsigned long long)n); + if (pl) sendto(local, buf, (size_t)n, 0, (struct sockaddr *)&peer, pl); + } + } +} + +int pcarelay_start(int local_port, const char *host, int udp_port, int tcp_port) { + g_local_port = local_port; + snprintf(g_host, sizeof g_host, "%s", host); + snprintf(g_udp_port, sizeof g_udp_port, "%d", udp_port); + snprintf(g_tcp_port, sizeof g_tcp_port, "%d", tcp_port); + pthread_t t; + if (pthread_create(&t, NULL, loop, NULL) != 0) return -1; + pthread_detach(t); + return 0; +} + +void pcarelay_use_tcp(int on) { atomic_store(&use_tcp, on ? 1 : 0); } +int pcarelay_is_tcp(void) { return atomic_load(&use_tcp); } +unsigned long long pcarelay_rx_bytes(void) { return atomic_load(&rx_bytes); } diff --git a/ios/Tunnel/pcarelay.h b/ios/Tunnel/pcarelay.h new file mode 100644 index 0000000..d92f4cc --- /dev/null +++ b/ios/Tunnel/pcarelay.h @@ -0,0 +1,14 @@ +#ifndef PCARELAY_H +#define PCARELAY_H + +/* Start the transport switch thread: obfuscator -> 127.0.0.1:local_port -> host via UDP or TCP. */ +int pcarelay_start(int local_port, const char *host, int udp_port, int tcp_port); +void pcarelay_use_tcp(int on); +int pcarelay_is_tcp(void); +/* Bytes received back from the server on any path (auto-detects a dead UDP). */ +unsigned long long pcarelay_rx_bytes(void); + +/* wg-obfuscator compiled in with -Dmain=wgobf_main (one instance per process). */ +int wgobf_main(int argc, char **argv); + +#endif diff --git a/ios/Tunnel/ru_bypass.txt b/ios/Tunnel/ru_bypass.txt new file mode 100644 index 0000000..9fbcc82 --- /dev/null +++ b/ios/Tunnel/ru_bypass.txt @@ -0,0 +1,2 @@ +# AllowedIPs = Internet minus Russian networks (/20 and larger, ipdeny 2026-10-23) + 10.25.0.0/16 +0.0.0.0/7, 2.0.0.0/11, 2.32.0.0/12, 2.48.0.0/13, 2.56.0.0/14, 2.64.0.0/12, 2.80.0.0/13, 2.88.0.0/14, 2.96.0.0/11, 2.128.0.0/9, 3.0.0.0/8, 4.0.0.0/8, 5.0.0.0/15, 5.2.0.0/19, 5.2.64.0/18, 5.2.128.0/17, 5.4.0.0/14, 5.8.16.0/20, 5.8.32.0/20, 5.8.64.0/18, 5.8.128.0/19, 5.8.176.0/20, 5.8.240.0/20, 5.9.0.0/16, 5.10.0.0/16, 5.11.0.0/18, 5.11.80.0/20, 5.11.96.0/19, 5.11.128.0/17, 5.12.0.0/14, 5.20.0.0/15, 5.22.0.0/16, 5.23.0.0/18, 5.23.64.0/19, 5.23.112.0/20, 5.23.128.0/17, 5.24.0.0/14, 5.28.0.0/20, 5.28.32.0/19, 5.28.64.0/18, 5.28.128.0/17, 5.29.0.0/16, 5.30.0.0/15, 5.32.0.0/15, 5.34.0.0/16, 5.35.32.0/20, 5.35.64.0/20, 5.35.96.0/20, 5.35.160.0/19, 5.35.192.0/18, 5.36.0.0/14, 5.40.0.0/15, 5.42.128.0/17, 5.43.0.0/17, 5.43.160.0/19, 5.43.192.0/18, 5.44.16.0/20, 5.44.32.0/20, 5.44.64.0/18, 5.44.128.0/17, 5.45.0.0/17, 5.45.128.0/18, 5.46.0.0/15, 5.48.0.0/14, 5.52.0.0/16, 5.53.0.0/20, 5.53.32.0/19, 5.53.64.0/18, 5.53.128.0/17, 5.54.0.0/15, 5.56.0.0/13, 5.64.0.0/13, 5.72.0.0/14, 5.76.0.0/16, 5.77.32.0/19, 5.77.64.0/18, 5.77.128.0/17, 5.78.0.0/16, 5.79.0.0/17, 5.80.0.0/12, 5.96.0.0/14, 5.100.0.0/18, 5.100.128.0/17, 5.101.48.0/20, 5.101.96.0/19, 5.101.128.0/18, 5.101.208.0/20, 5.102.0.0/15, 5.104.0.0/19, 5.104.64.0/18, 5.104.128.0/17, 5.105.0.0/16, 5.106.0.0/15, 5.108.0.0/14, 5.112.0.0/12, 5.132.0.0/14, 5.144.0.0/18, 5.144.80.0/20, 5.144.128.0/17, 5.145.0.0/17, 5.145.128.0/18, 5.146.0.0/15, 5.148.0.0/16, 5.149.0.0/17, 5.149.128.0/20, 5.149.160.0/19, 5.149.192.0/20, 5.149.224.0/19, 5.150.0.0/15, 5.152.0.0/16, 5.153.0.0/17, 5.153.192.0/18, 5.154.0.0/15, 5.156.0.0/15, 5.158.0.0/18, 5.158.64.0/19, 5.158.128.0/17, 5.159.0.0/18, 5.159.64.0/19, 5.159.112.0/20, 5.159.128.0/17, 5.160.0.0/14, 5.168.0.0/14, 5.172.32.0/19, 5.172.64.0/18, 5.172.128.0/17, 5.173.0.0/16, 5.174.0.0/16, 5.175.0.0/18, 5.175.64.0/19, 5.175.128.0/17, 5.176.0.0/13, 5.184.0.0/15, 5.186.0.0/16, 5.187.0.0/18, 5.187.96.0/19, 5.187.128.0/17, 5.188.0.0/19, 5.188.32.0/20, 5.188.64.0/18, 5.188.128.0/18, 5.188.208.0/20, 5.188.224.0/19, 5.189.128.0/18, 5.189.192.0/19, 5.190.0.0/15, 5.192.0.0/14, 5.196.0.0/15, 5.198.0.0/16, 5.199.0.0/17, 5.199.128.0/18, 5.199.224.0/19, 5.200.0.0/19, 5.200.64.0/18, 5.200.128.0/17, 5.201.0.0/16, 5.202.0.0/15, 5.204.0.0/15, 5.206.128.0/17, 5.207.0.0/16, 5.208.0.0/12, 5.224.0.0/15, 5.226.0.0/16, 5.229.0.0/16, 5.230.0.0/15, 5.232.0.0/13, 5.240.0.0/13, 5.248.0.0/16, 5.249.0.0/17, 5.249.128.0/18, 5.249.224.0/19, 5.250.0.0/15, 5.252.0.0/15, 5.254.0.0/17, 5.254.128.0/18, 5.254.192.0/19, 5.255.0.0/17, 5.255.128.0/18, 6.0.0.0/7, 8.0.0.0/7, 11.0.0.0/8, 12.0.0.0/6, 16.0.0.0/6, 20.0.0.0/7, 22.0.0.0/8, 23.0.0.0/10, 23.64.0.0/11, 23.96.0.0/13, 23.104.0.0/16, 23.105.0.0/17, 23.105.128.0/18, 23.105.192.0/19, 23.106.0.0/15, 23.108.0.0/15, 23.110.0.0/16, 23.111.0.0/19, 23.111.64.0/19, 23.111.128.0/18, 23.111.224.0/19, 23.112.0.0/12, 23.128.0.0/9, 24.0.0.0/6, 28.0.0.0/7, 30.0.0.0/8, 31.0.0.0/15, 31.2.0.0/16, 31.3.0.0/20, 31.3.32.0/19, 31.3.64.0/18, 31.3.128.0/17, 31.4.0.0/15, 31.6.0.0/18, 31.6.64.0/19, 31.6.128.0/17, 31.7.0.0/17, 31.7.128.0/18, 31.7.192.0/19, 31.7.240.0/20, 31.9.0.0/16, 31.10.0.0/18, 31.10.128.0/17, 31.11.0.0/16, 31.12.0.0/16, 31.13.0.0/19, 31.13.64.0/18, 31.13.128.0/17, 31.14.0.0/16, 31.15.0.0/18, 31.15.64.0/20, 31.15.96.0/19, 31.15.128.0/17, 31.16.0.0/14, 31.20.0.0/15, 31.22.0.0/16, 31.24.0.0/14, 31.28.64.0/19, 31.28.128.0/18, 31.29.0.0/17, 31.30.0.0/16, 31.31.0.0/17, 31.31.128.0/18, 31.31.208.0/20, 31.31.224.0/19, 31.32.0.0/13, 31.40.0.0/19, 31.40.96.0/20, 31.40.192.0/18, 31.41.16.0/20, 31.41.32.0/19, 31.41.64.0/18, 31.41.128.0/17, 31.42.0.0/19, 31.42.48.0/20, 31.42.64.0/19, 31.42.112.0/20, 31.42.128.0/18, 31.43.0.0/17, 31.43.128.0/18, 31.43.224.0/19, 31.44.0.0/19, 31.44.32.0/20, 31.44.64.0/20, 31.44.96.0/19, 31.44.128.0/19, 31.44.160.0/20, 31.44.192.0/19, 31.44.224.0/20, 31.45.0.0/16, 31.46.0.0/16, 31.47.0.0/18, 31.47.64.0/19, 31.47.96.0/20, 31.47.192.0/18, 31.48.0.0/12, 31.64.0.0/10, 31.128.0.0/19, 31.128.48.0/20, 31.128.64.0/18, 31.128.160.0/19, 31.128.224.0/19, 31.129.64.0/19, 31.129.112.0/20, 31.129.160.0/19, 31.129.224.0/19, 31.130.96.0/20, 31.130.160.0/19, 31.130.192.0/18, 31.131.0.0/18, 31.131.80.0/20, 31.131.96.0/19, 31.131.128.0/20, 31.131.160.0/19, 31.131.224.0/19, 31.132.0.0/18, 31.132.64.0/19, 31.132.192.0/20, 31.133.0.0/19, 31.133.80.0/20, 31.133.96.0/19, 31.133.128.0/18, 31.133.192.0/19, 31.134.0.0/20, 31.134.48.0/20, 31.134.64.0/18, 31.134.192.0/19, 31.135.0.0/19, 31.135.128.0/18, 31.135.192.0/19, 31.136.0.0/13, 31.144.0.0/12, 31.160.0.0/15, 31.164.0.0/14, 31.168.0.0/14, 31.172.0.0/17, 31.172.128.0/18, 31.172.224.0/19, 31.174.0.0/15, 31.176.0.0/16, 31.177.0.0/18, 31.177.96.0/19, 31.177.128.0/17, 31.178.0.0/15, 31.182.0.0/15, 31.184.0.0/17, 31.184.128.0/18, 31.184.192.0/20, 31.184.240.0/20, 31.185.0.0/16, 31.186.0.0/18, 31.186.80.0/20, 31.186.96.0/19, 31.186.160.0/19, 31.186.192.0/18, 31.187.0.0/16, 31.188.0.0/14, 31.192.0.0/17, 31.192.192.0/18, 31.193.0.0/16, 31.194.0.0/15, 31.196.0.0/14, 31.200.0.0/17, 31.200.128.0/18, 31.200.208.0/20, 31.200.240.0/20, 31.201.0.0/16, 31.202.0.0/15, 31.204.64.0/19, 31.204.112.0/20, 31.204.128.0/19, 31.204.192.0/18, 31.205.0.0/16, 31.206.0.0/16, 31.207.0.0/18, 31.207.80.0/20, 31.207.96.0/19, 31.208.0.0/15, 31.210.0.0/17, 31.210.128.0/18, 31.210.224.0/19, 31.211.128.0/17, 31.212.0.0/14, 31.216.0.0/17, 31.216.128.0/19, 31.216.176.0/20, 31.216.192.0/18, 31.217.0.0/16, 31.218.0.0/15, 31.220.0.0/17, 31.220.128.0/19, 31.220.192.0/18, 31.221.0.0/16, 31.222.0.0/15, 31.224.0.0/11, 32.0.0.0/6, 36.0.0.0/8, 37.0.0.0/16, 37.1.80.0/20, 37.1.96.0/19, 37.1.144.0/20, 37.1.160.0/19, 37.1.192.0/18, 37.2.0.0/15, 37.4.0.0/14, 37.8.0.0/17, 37.8.128.0/20, 37.8.160.0/19, 37.8.192.0/18, 37.9.16.0/20, 37.9.32.0/19, 37.9.128.0/20, 37.9.160.0/19, 37.9.192.0/18, 37.10.0.0/15, 37.12.0.0/14, 37.16.0.0/15, 37.18.0.0/16, 37.19.0.0/19, 37.19.64.0/18, 37.19.128.0/17, 37.24.0.0/14, 37.28.0.0/17, 37.28.128.0/19, 37.28.192.0/18, 37.29.128.0/17, 37.30.0.0/15, 37.32.0.0/12, 37.48.0.0/16, 37.49.0.0/17, 37.49.128.0/19, 37.49.224.0/19, 37.50.0.0/15, 37.52.0.0/14, 37.56.0.0/14, 37.60.0.0/17, 37.60.128.0/18, 37.60.192.0/20, 37.60.224.0/19, 37.61.0.0/16, 37.62.0.0/15, 37.64.0.0/13, 37.72.0.0/18, 37.72.96.0/19, 37.72.128.0/17, 37.73.0.0/16, 37.74.0.0/16, 37.75.0.0/17, 37.75.128.0/18, 37.75.208.0/20, 37.75.224.0/19, 37.76.0.0/17, 37.76.192.0/18, 37.77.0.0/16, 37.80.0.0/12, 37.96.0.0/15, 37.98.0.0/17, 37.98.128.0/19, 37.98.192.0/19, 37.98.224.0/20, 37.99.0.0/16, 37.100.0.0/14, 37.104.0.0/14, 37.108.0.0/15, 37.110.160.0/19, 37.110.192.0/19, 37.110.240.0/20, 37.111.0.0/16, 37.114.0.0/20, 37.114.32.0/19, 37.114.64.0/18, 37.114.128.0/17, 37.115.0.0/16, 37.116.0.0/14, 37.120.0.0/15, 37.122.128.0/17, 37.123.0.0/16, 37.124.0.0/14, 37.128.0.0/17, 37.128.128.0/18, 37.128.192.0/19, 37.128.224.0/20, 37.129.0.0/16, 37.130.0.0/16, 37.131.0.0/17, 37.131.128.0/18, 37.131.224.0/19, 37.132.0.0/14, 37.136.0.0/15, 37.138.0.0/16, 37.139.0.0/19, 37.139.64.0/19, 37.139.112.0/20, 37.139.128.0/18, 37.140.192.0/18, 37.141.0.0/16, 37.142.0.0/16, 37.143.0.0/20, 37.143.32.0/19, 37.143.64.0/19, 37.143.112.0/20, 37.143.128.0/17, 37.148.0.0/14, 37.152.0.0/16, 37.153.64.0/18, 37.153.128.0/17, 37.154.0.0/15, 37.156.0.0/16, 37.157.0.0/20, 37.157.32.0/19, 37.157.64.0/18, 37.157.128.0/17, 37.158.0.0/15, 37.160.0.0/12, 37.176.0.0/13, 37.184.0.0/14, 37.188.64.0/18, 37.188.128.0/17, 37.189.0.0/16, 37.190.128.0/17, 37.191.0.0/16, 37.196.0.0/14, 37.200.0.0/18, 37.200.80.0/20, 37.200.96.0/19, 37.200.128.0/17, 37.201.0.0/16, 37.202.0.0/15, 37.205.0.0/18, 37.205.96.0/19, 37.205.128.0/17, 37.206.0.0/15, 37.208.0.0/18, 37.208.80.0/20, 37.208.96.0/19, 37.208.128.0/17, 37.209.0.0/17, 37.209.128.0/18, 37.209.192.0/19, 37.209.240.0/20, 37.210.0.0/15, 37.212.0.0/14, 37.216.0.0/14, 37.220.0.0/17, 37.220.128.0/19, 37.220.160.0/20, 37.220.192.0/18, 37.221.0.0/16, 37.222.0.0/15, 37.224.0.0/14, 37.228.0.0/18, 37.228.64.0/20, 37.228.96.0/19, 37.228.128.0/17, 37.229.0.0/16, 37.230.0.0/15, 37.232.0.0/17, 37.233.0.0/16, 37.234.0.0/15, 37.236.0.0/14, 37.240.0.0/12, 38.0.0.0/7, 40.0.0.0/6, 44.0.0.0/7, 46.1.0.0/16, 46.2.0.0/15, 46.4.0.0/14, 46.8.0.0/13, 46.16.0.0/14, 46.20.0.0/18, 46.20.80.0/20, 46.20.96.0/19, 46.20.128.0/19, 46.20.160.0/20, 46.20.192.0/18, 46.21.0.0/18, 46.21.80.0/20, 46.21.96.0/19, 46.21.128.0/17, 46.22.0.0/19, 46.22.32.0/20, 46.22.64.0/18, 46.22.128.0/18, 46.22.192.0/19, 46.22.224.0/20, 46.23.0.0/17, 46.23.128.0/20, 46.23.160.0/20, 46.23.192.0/18, 46.24.0.0/14, 46.28.0.0/15, 46.30.0.0/19, 46.30.48.0/20, 46.30.64.0/18, 46.30.128.0/17, 46.31.0.0/16, 46.32.0.0/18, 46.32.96.0/19, 46.32.128.0/17, 46.33.0.0/16, 46.34.0.0/16, 46.35.0.0/17, 46.35.128.0/18, 46.35.192.0/19, 46.36.32.0/19, 46.36.64.0/18, 46.36.128.0/17, 46.37.0.0/17, 46.37.160.0/19, 46.37.192.0/18, 46.38.64.0/19, 46.38.128.0/17, 46.39.64.0/18, 46.39.128.0/18, 46.39.192.0/19, 46.40.0.0/16, 46.41.0.0/18, 46.41.128.0/17, 46.42.64.0/18, 46.42.192.0/18, 46.43.0.0/17, 46.43.128.0/18, 46.44.64.0/18, 46.44.128.0/17, 46.45.64.0/18, 46.45.128.0/18, 46.46.64.0/18, 46.46.192.0/18, 46.47.64.0/18, 46.47.128.0/18, 46.49.0.0/16, 46.50.0.0/17, 46.51.0.0/16, 46.52.0.0/17, 46.53.0.0/16, 46.54.0.0/15, 46.56.0.0/14, 46.60.0.0/16, 46.62.128.0/17, 46.63.0.0/17, 46.64.0.0/13, 46.74.0.0/15, 46.76.0.0/14, 46.80.0.0/12, 46.96.0.0/13, 46.104.0.0/14, 46.108.0.0/15, 46.110.0.0/16, 46.112.0.0/12, 46.128.0.0/13, 46.136.0.0/15, 46.139.0.0/16, 46.140.0.0/14, 46.144.0.0/15, 46.148.0.0/19, 46.148.32.0/20, 46.148.64.0/19, 46.148.112.0/20, 46.148.144.0/20, 46.148.160.0/20, 46.149.0.0/19, 46.149.48.0/20, 46.149.64.0/18, 46.149.144.0/20, 46.149.160.0/19, 46.149.192.0/19, 46.149.240.0/20, 46.150.0.0/17, 46.150.192.0/19, 46.150.240.0/20, 46.151.16.0/20, 46.151.32.0/19, 46.151.64.0/18, 46.151.128.0/20, 46.151.160.0/19, 46.151.192.0/18, 46.152.0.0/14, 46.156.0.0/15, 46.160.64.0/18, 46.161.64.0/18, 46.161.192.0/18, 46.162.64.0/18, 46.162.128.0/17, 46.163.0.0/17, 46.163.192.0/18, 46.164.0.0/17, 46.164.128.0/18, 46.165.64.0/18, 46.165.128.0/17, 46.166.0.0/18, 46.166.128.0/18, 46.167.0.0/18, 46.167.128.0/17, 46.168.0.0/14, 46.172.64.0/19, 46.172.128.0/19, 46.172.224.0/19, 46.173.16.0/20, 46.173.64.0/18, 46.173.128.0/19, 46.173.160.0/20, 46.173.192.0/20, 46.173.224.0/19, 46.174.0.0/16, 46.175.0.0/17, 46.175.128.0/18, 46.175.224.0/19, 46.176.0.0/14, 46.182.0.0/15, 46.184.0.0/15, 46.186.0.0/16, 46.187.128.0/17, 46.188.128.0/17, 46.189.0.0/16, 46.190.0.0/16, 46.191.0.0/17, 46.192.0.0/11, 46.224.0.0/14, 46.228.16.0/20, 46.228.32.0/19, 46.228.80.0/20, 46.228.128.0/17, 46.229.0.0/18, 46.229.80.0/20, 46.229.112.0/20, 46.229.144.0/20, 46.229.160.0/20, 46.229.192.0/20, 46.229.224.0/19, 46.230.0.0/15, 46.232.0.0/16, 46.233.0.0/17, 46.233.128.0/18, 46.234.0.0/15, 46.236.0.0/17, 46.236.192.0/18, 46.237.64.0/18, 46.237.128.0/17, 46.238.0.0/15, 46.240.0.0/16, 46.241.128.0/17, 46.242.128.0/17, 46.243.0.0/16, 46.244.0.0/14, 46.248.0.0/16, 46.249.32.0/19, 46.249.64.0/18, 46.249.128.0/17, 46.250.0.0/19, 46.250.96.0/19, 46.250.160.0/19, 46.250.192.0/18, 46.251.0.0/18, 46.251.96.0/19, 46.251.128.0/17, 46.252.0.0/18, 46.252.64.0/19, 46.252.96.0/20, 46.252.128.0/19, 46.252.176.0/20, 46.252.192.0/19, 46.252.224.0/20, 46.253.0.0/16, 46.254.0.0/20, 46.254.32.0/19, 46.254.64.0/18, 46.254.128.0/17, 46.255.0.0/16, 47.0.0.0/8, 48.0.0.0/7, 50.0.0.0/8, 51.0.0.0/9, 51.128.0.0/10, 51.192.0.0/11, 51.224.0.0/12, 51.240.0.0/13, 51.248.0.0/15, 51.251.0.0/16, 51.252.0.0/14, 52.0.0.0/6, 56.0.0.0/6, 60.0.0.0/7, 62.0.0.0/14, 62.4.0.0/16, 62.5.0.0/17, 62.6.0.0/15, 62.8.0.0/13, 62.16.0.0/19, 62.16.64.0/19, 62.16.128.0/17, 62.17.0.0/16, 62.18.0.0/15, 62.20.0.0/14, 62.24.0.0/13, 62.32.0.0/16, 62.34.0.0/15, 62.36.0.0/14, 62.40.0.0/13, 62.48.0.0/13, 62.56.0.0/14, 62.60.0.0/16, 62.61.32.0/19, 62.61.64.0/18, 62.61.128.0/17, 62.62.0.0/16, 62.63.0.0/18, 62.63.128.0/17, 62.64.32.0/19, 62.64.64.0/18, 62.64.128.0/17, 62.65.0.0/16, 62.66.0.0/15, 62.68.0.0/17, 62.68.160.0/19, 62.68.192.0/18, 62.69.32.0/19, 62.69.64.0/18, 62.69.128.0/17, 62.70.0.0/15, 62.72.0.0/14, 62.76.224.0/20, 62.77.0.0/16, 62.78.0.0/19, 62.78.64.0/20, 62.78.96.0/19, 62.78.128.0/17, 62.79.0.0/16, 62.80.0.0/14, 62.84.0.0/18, 62.84.64.0/19, 62.84.96.0/20, 62.84.128.0/17, 62.85.0.0/16, 62.86.0.0/15, 62.88.0.0/16, 62.89.0.0/17, 62.89.128.0/18, 62.90.0.0/15, 62.92.0.0/14, 62.96.0.0/13, 62.104.0.0/16, 62.105.64.0/18, 62.105.160.0/19, 62.105.192.0/18, 62.106.0.0/18, 62.106.64.0/19, 62.106.128.0/17, 62.107.0.0/16, 62.108.0.0/16, 62.109.32.0/19, 62.109.64.0/18, 62.109.128.0/19, 62.109.192.0/18, 62.110.0.0/15, 62.112.0.0/18, 62.112.64.0/19, 62.112.128.0/17, 62.113.0.0/19, 62.113.128.0/17, 62.114.0.0/15, 62.116.0.0/16, 62.117.0.0/18, 62.117.128.0/17, 62.119.0.0/16, 62.120.0.0/13, 62.128.0.0/14, 62.132.0.0/16, 62.133.0.0/17, 62.133.128.0/19, 62.133.192.0/18, 62.134.0.0/15, 62.136.0.0/14, 62.140.0.0/17, 62.140.128.0/18, 62.140.192.0/19, 62.141.0.0/18, 62.141.128.0/17, 62.142.0.0/15, 62.144.0.0/14, 62.148.32.0/19, 62.148.64.0/18, 62.148.160.0/19, 62.148.192.0/19, 62.149.0.0/16, 62.150.0.0/15, 62.152.0.0/19, 62.152.96.0/19, 62.152.128.0/18, 62.152.224.0/19, 62.153.0.0/16, 62.154.0.0/15, 62.156.0.0/14, 62.160.0.0/14, 62.164.0.0/16, 62.165.64.0/18, 62.165.128.0/17, 62.166.0.0/15, 62.168.0.0/17, 62.168.128.0/18, 62.168.192.0/19, 62.169.0.0/16, 62.170.0.0/15, 62.172.0.0/16, 62.173.0.0/17, 62.173.160.0/19, 62.173.192.0/18, 62.174.0.0/15, 62.176.32.0/19, 62.176.64.0/18, 62.176.128.0/17, 62.177.0.0/19, 62.177.64.0/18, 62.177.128.0/17, 62.178.0.0/15, 62.180.0.0/16, 62.181.0.0/19, 62.181.64.0/18, 62.181.128.0/17, 62.182.0.0/18, 62.182.80.0/20, 62.182.96.0/19, 62.182.144.0/20, 62.182.160.0/19, 62.182.192.0/18, 62.183.128.0/17, 62.184.0.0/13, 62.192.0.0/19, 62.192.64.0/18, 62.192.128.0/18, 62.192.192.0/19, 62.193.0.0/16, 62.194.0.0/15, 62.196.0.0/14, 62.200.0.0/14, 62.204.0.0/16, 62.205.0.0/17, 62.205.128.0/19, 62.205.192.0/18, 62.206.0.0/15, 62.208.0.0/14, 62.212.0.0/16, 62.213.128.0/17, 62.214.0.0/15, 62.216.0.0/16, 62.217.0.0/17, 62.217.128.0/19, 62.217.192.0/18, 62.218.0.0/15, 62.220.0.0/19, 62.220.64.0/18, 62.220.128.0/17, 62.221.0.0/16, 62.222.0.0/15, 62.224.0.0/14, 62.228.0.0/15, 62.230.0.0/16, 62.231.32.0/19, 62.231.64.0/18, 62.231.128.0/19, 62.231.192.0/18, 62.232.0.0/13, 62.240.0.0/14, 62.244.0.0/16, 62.245.0.0/19, 62.245.64.0/18, 62.245.128.0/17, 62.246.0.0/15, 62.248.0.0/16, 62.249.0.0/17, 62.249.160.0/19, 62.249.192.0/18, 62.250.0.0/15, 62.252.0.0/14, 63.0.0.0/8, 64.0.0.0/5, 72.0.0.0/6, 76.0.0.0/8, 77.0.0.0/11, 77.32.0.0/15, 77.36.0.0/16, 77.37.0.0/17, 77.38.0.0/16, 77.39.128.0/17, 77.40.128.0/17, 77.42.0.0/16, 77.43.0.0/17, 77.44.0.0/16, 77.45.0.0/17, 77.46.0.0/15, 77.48.0.0/15, 77.52.0.0/14, 77.56.0.0/13, 77.64.0.0/15, 77.66.0.0/17, 77.67.0.0/16, 77.68.0.0/14, 77.72.0.0/17, 77.72.128.0/18, 77.72.192.0/19, 77.72.224.0/20, 77.73.0.0/16, 77.74.0.0/16, 77.75.16.0/20, 77.75.32.0/19, 77.75.64.0/18, 77.75.128.0/17, 77.76.0.0/15, 77.78.0.0/16, 77.79.0.0/17, 77.79.192.0/18, 77.80.0.0/15, 77.83.0.0/16, 77.84.0.0/14, 77.88.64.0/18, 77.88.128.0/17, 77.89.0.0/16, 77.90.0.0/15, 77.92.0.0/16, 77.93.0.0/18, 77.93.64.0/19, 77.93.128.0/17, 77.94.0.0/19, 77.94.48.0/20, 77.94.64.0/19, 77.94.128.0/19, 77.94.224.0/19, 77.95.0.0/16, 77.96.0.0/13, 77.104.0.0/16, 77.105.0.0/17, 77.105.128.0/19, 77.105.160.0/20, 77.105.192.0/18, 77.106.128.0/18, 77.107.0.0/16, 77.108.0.0/18, 77.108.128.0/18, 77.109.0.0/16, 77.110.0.0/15, 77.112.0.0/13, 77.120.0.0/16, 77.121.0.0/19, 77.121.64.0/18, 77.121.128.0/17, 77.122.0.0/16, 77.123.0.0/17, 77.123.128.0/18, 77.124.0.0/14, 77.128.0.0/10, 77.192.0.0/12, 77.208.0.0/13, 77.216.0.0/14, 77.220.0.0/19, 77.220.64.0/18, 77.220.192.0/18, 77.221.0.0/17, 77.221.128.0/18, 77.221.224.0/19, 77.222.0.0/19, 77.222.64.0/19, 77.222.128.0/17, 77.223.0.0/18, 77.223.128.0/17, 77.224.0.0/13, 77.232.64.0/18, 77.232.160.0/19, 77.232.192.0/18, 77.233.32.0/19, 77.233.64.0/18, 77.233.128.0/19, 77.233.224.0/19, 77.234.32.0/19, 77.234.64.0/18, 77.234.128.0/18, 77.234.224.0/19, 77.235.0.0/17, 77.235.128.0/18, 77.235.224.0/19, 77.236.0.0/19, 77.236.96.0/19, 77.236.128.0/18, 77.236.192.0/19, 77.237.0.0/16, 77.238.0.0/17, 77.238.160.0/19, 77.238.192.0/18, 77.239.0.0/17, 77.239.128.0/18, 77.239.224.0/19, 77.240.0.0/17, 77.240.128.0/20, 77.240.176.0/20, 77.240.192.0/18, 77.241.0.0/20, 77.241.48.0/20, 77.241.64.0/18, 77.241.128.0/20, 77.241.160.0/19, 77.241.192.0/19, 77.241.224.0/20, 77.242.16.0/20, 77.242.32.0/19, 77.242.64.0/19, 77.242.112.0/20, 77.242.128.0/17, 77.243.16.0/20, 77.243.32.0/19, 77.243.64.0/19, 77.243.128.0/18, 77.243.192.0/19, 77.243.224.0/20, 77.244.0.0/20, 77.244.32.0/19, 77.244.80.0/20, 77.244.96.0/19, 77.244.128.0/19, 77.244.176.0/20, 77.244.192.0/20, 77.244.224.0/19, 77.245.0.0/18, 77.245.64.0/19, 77.245.96.0/20, 77.245.128.0/19, 77.245.176.0/20, 77.245.224.0/19, 77.246.0.0/17, 77.246.128.0/18, 77.246.192.0/19, 77.246.240.0/20, 77.247.0.0/17, 77.247.144.0/20, 77.247.176.0/20, 77.247.192.0/18, 77.248.0.0/13, 78.0.0.0/12, 78.16.0.0/13, 78.24.0.0/16, 78.25.0.0/18, 78.25.192.0/18, 78.26.0.0/15, 78.28.0.0/16, 78.29.128.0/17, 78.30.0.0/17, 78.30.128.0/18, 78.31.0.0/16, 78.32.0.0/14, 78.38.0.0/15, 78.40.0.0/16, 78.41.0.0/18, 78.41.64.0/19, 78.41.112.0/20, 78.41.128.0/17, 78.42.0.0/15, 78.44.0.0/14, 78.48.0.0/12, 78.64.0.0/12, 78.80.0.0/16, 78.82.0.0/15, 78.84.0.0/16, 78.86.0.0/15, 78.88.0.0/13, 78.96.0.0/13, 78.104.0.0/15, 78.108.0.0/18, 78.108.96.0/19, 78.108.128.0/18, 78.108.208.0/20, 78.108.224.0/19, 78.109.0.0/19, 78.109.48.0/20, 78.109.64.0/19, 78.109.96.0/20, 78.109.144.0/20, 78.109.160.0/19, 78.109.192.0/18, 78.110.0.0/19, 78.110.32.0/20, 78.110.64.0/18, 78.110.128.0/20, 78.110.160.0/19, 78.110.192.0/19, 78.110.224.0/20, 78.111.0.0/17, 78.111.128.0/20, 78.111.160.0/19, 78.111.192.0/19, 78.111.224.0/20, 78.112.0.0/12, 78.128.0.0/14, 78.132.0.0/17, 78.133.0.0/16, 78.134.0.0/15, 78.136.0.0/17, 78.136.128.0/18, 78.136.240.0/20, 78.137.0.0/16, 78.138.0.0/17, 78.138.192.0/18, 78.139.0.0/18, 78.139.128.0/18, 78.140.64.0/18, 78.140.128.0/18, 78.140.240.0/20, 78.141.0.0/16, 78.142.0.0/15, 78.144.0.0/13, 78.152.0.0/16, 78.153.32.0/19, 78.153.64.0/18, 78.153.160.0/19, 78.153.192.0/18, 78.154.0.0/16, 78.155.0.0/17, 78.155.128.0/19, 78.155.224.0/19, 78.156.0.0/17, 78.156.128.0/18, 78.156.192.0/19, 78.157.0.0/17, 78.157.128.0/18, 78.157.192.0/19, 78.158.0.0/17, 78.158.128.0/18, 78.158.224.0/19, 78.159.0.0/17, 78.159.128.0/18, 78.159.192.0/19, 78.160.0.0/11, 78.192.0.0/10, 79.0.0.0/10, 79.64.0.0/11, 79.96.0.0/13, 79.106.0.0/15, 79.108.0.0/15, 79.110.0.0/17, 79.110.128.0/18, 79.110.192.0/19, 79.110.224.0/20, 79.112.0.0/13, 79.120.128.0/17, 79.121.0.0/16, 79.122.0.0/17, 79.123.0.0/16, 79.124.0.0/15, 79.126.128.0/17, 79.127.0.0/16, 79.128.0.0/14, 79.132.0.0/18, 79.132.64.0/19, 79.132.128.0/17, 79.133.0.0/18, 79.133.96.0/19, 79.133.160.0/19, 79.133.192.0/18, 79.134.32.0/19, 79.134.96.0/19, 79.134.128.0/18, 79.134.224.0/19, 79.135.0.0/18, 79.135.96.0/19, 79.135.128.0/18, 79.135.192.0/19, 79.136.0.0/17, 79.137.0.0/17, 79.137.160.0/19, 79.138.0.0/16, 79.139.0.0/17, 79.140.0.0/20, 79.140.32.0/19, 79.140.80.0/20, 79.140.112.0/20, 79.140.128.0/18, 79.140.192.0/19, 79.140.224.0/20, 79.141.0.0/19, 79.141.32.0/20, 79.141.80.0/20, 79.141.96.0/19, 79.141.128.0/18, 79.141.192.0/20, 79.141.224.0/19, 79.142.0.0/20, 79.142.32.0/19, 79.142.64.0/19, 79.142.112.0/20, 79.142.128.0/19, 79.142.176.0/20, 79.142.192.0/18, 79.143.16.0/20, 79.143.32.0/19, 79.143.80.0/20, 79.143.96.0/19, 79.143.128.0/18, 79.143.192.0/19, 79.143.240.0/20, 79.144.0.0/12, 79.160.0.0/14, 79.166.0.0/15, 79.168.0.0/14, 79.172.128.0/17, 79.173.0.0/18, 79.173.128.0/17, 79.174.0.0/19, 79.174.96.0/19, 79.174.128.0/17, 79.175.64.0/18, 79.175.128.0/17, 79.176.0.0/12, 79.192.0.0/10, 80.0.0.0/10, 80.64.0.0/18, 80.64.64.0/19, 80.64.112.0/20, 80.64.128.0/19, 80.64.176.0/20, 80.64.192.0/18, 80.65.0.0/20, 80.65.32.0/19, 80.65.64.0/18, 80.65.128.0/17, 80.66.0.0/18, 80.66.96.0/19, 80.66.128.0/20, 80.66.160.0/19, 80.66.192.0/18, 80.67.0.0/19, 80.67.32.0/20, 80.67.64.0/18, 80.67.128.0/18, 80.67.192.0/20, 80.67.224.0/20, 80.68.16.0/20, 80.68.32.0/19, 80.68.80.0/20, 80.68.96.0/19, 80.68.128.0/18, 80.68.192.0/19, 80.68.224.0/20, 80.69.0.0/17, 80.69.128.0/20, 80.69.160.0/20, 80.69.192.0/18, 80.70.0.0/18, 80.70.64.0/19, 80.70.112.0/20, 80.70.128.0/18, 80.70.192.0/19, 80.70.240.0/20, 80.71.0.0/17, 80.71.128.0/19, 80.71.192.0/20, 80.71.224.0/20, 80.72.0.0/18, 80.72.64.0/19, 80.72.96.0/20, 80.72.128.0/18, 80.72.192.0/20, 80.72.240.0/20, 80.73.0.0/20, 80.73.32.0/19, 80.73.96.0/19, 80.73.128.0/19, 80.73.176.0/20, 80.73.208.0/20, 80.73.224.0/19, 80.74.0.0/16, 80.75.0.0/18, 80.75.64.0/20, 80.75.96.0/19, 80.75.144.0/20, 80.75.160.0/19, 80.75.192.0/18, 80.76.0.0/18, 80.76.64.0/19, 80.76.112.0/20, 80.76.144.0/20, 80.76.160.0/20, 80.76.192.0/19, 80.77.0.0/17, 80.77.128.0/19, 80.77.176.0/20, 80.77.192.0/18, 80.78.0.0/18, 80.78.64.0/19, 80.78.128.0/18, 80.78.208.0/20, 80.78.224.0/20, 80.79.0.0/18, 80.79.80.0/20, 80.79.96.0/19, 80.79.128.0/19, 80.79.160.0/20, 80.79.192.0/19, 80.79.224.0/20, 80.80.0.0/18, 80.80.64.0/19, 80.80.128.0/18, 80.80.208.0/20, 80.80.224.0/19, 80.81.0.0/17, 80.81.128.0/18, 80.81.192.0/20, 80.81.224.0/19, 80.82.0.0/19, 80.82.64.0/20, 80.82.96.0/19, 80.82.128.0/19, 80.82.192.0/18, 80.83.0.0/17, 80.83.128.0/18, 80.83.208.0/20, 80.84.0.0/18, 80.84.64.0/19, 80.84.96.0/20, 80.84.128.0/17, 80.85.0.0/17, 80.85.128.0/20, 80.85.160.0/20, 80.85.192.0/18, 80.86.0.0/16, 80.87.0.0/18, 80.87.64.0/19, 80.87.112.0/20, 80.87.128.0/20, 80.87.160.0/19, 80.87.208.0/20, 80.87.224.0/19, 80.88.0.0/19, 80.88.32.0/20, 80.88.64.0/18, 80.88.128.0/17, 80.89.16.0/20, 80.89.32.0/19, 80.89.64.0/18, 80.89.160.0/19, 80.89.208.0/20, 80.89.224.0/19, 80.90.0.0/18, 80.90.80.0/20, 80.90.96.0/20, 80.90.128.0/19, 80.90.160.0/20, 80.90.192.0/19, 80.90.224.0/20, 80.91.0.0/20, 80.91.32.0/19, 80.91.64.0/18, 80.91.128.0/18, 80.91.208.0/20, 80.91.224.0/19, 80.92.48.0/20, 80.92.64.0/19, 80.92.112.0/20, 80.92.128.0/19, 80.92.176.0/20, 80.92.192.0/20, 80.92.224.0/19, 80.93.0.0/19, 80.93.32.0/20, 80.93.64.0/19, 80.93.112.0/20, 80.93.128.0/19, 80.93.160.0/20, 80.93.192.0/18, 80.94.0.0/16, 80.95.0.0/19, 80.95.48.0/20, 80.95.64.0/18, 80.95.128.0/17, 80.96.0.0/12, 80.112.0.0/15, 80.114.0.0/16, 80.115.0.0/17, 80.115.128.0/18, 80.115.224.0/19, 80.116.0.0/14, 80.120.0.0/13, 80.128.0.0/10, 80.192.0.0/11, 80.224.0.0/13, 80.232.0.0/15, 80.234.128.0/17, 80.235.0.0/16, 80.236.0.0/16, 80.237.128.0/17, 80.238.0.0/15, 80.240.0.0/19, 80.240.64.0/18, 80.240.128.0/18, 80.240.192.0/20, 80.240.224.0/20, 80.241.0.0/16, 80.242.0.0/18, 80.242.112.0/20, 80.242.128.0/17, 80.243.16.0/20, 80.243.32.0/19, 80.243.80.0/20, 80.243.96.0/19, 80.243.128.0/17, 80.244.0.0/19, 80.244.48.0/20, 80.244.64.0/18, 80.244.128.0/18, 80.244.192.0/19, 80.244.240.0/20, 80.245.0.0/18, 80.245.64.0/19, 80.245.96.0/20, 80.245.128.0/18, 80.245.192.0/19, 80.245.224.0/20, 80.246.0.0/18, 80.246.96.0/19, 80.246.128.0/18, 80.246.192.0/20, 80.246.224.0/20, 80.247.0.0/19, 80.247.48.0/20, 80.247.64.0/19, 80.247.128.0/19, 80.247.160.0/20, 80.247.192.0/18, 80.248.0.0/19, 80.248.32.0/20, 80.248.64.0/20, 80.248.96.0/19, 80.248.128.0/20, 80.248.160.0/19, 80.248.192.0/18, 80.249.0.0/17, 80.249.128.0/19, 80.249.160.0/20, 80.249.208.0/20, 80.249.224.0/19, 80.250.0.0/18, 80.250.96.0/19, 80.250.128.0/19, 80.250.192.0/20, 80.250.240.0/20, 80.251.0.0/19, 80.251.32.0/20, 80.251.64.0/19, 80.251.96.0/20, 80.251.144.0/20, 80.251.160.0/19, 80.251.192.0/19, 80.251.240.0/20, 80.252.0.0/20, 80.252.32.0/19, 80.252.64.0/18, 80.252.160.0/19, 80.252.192.0/18, 80.253.32.0/19, 80.253.64.0/18, 80.253.128.0/18, 80.253.192.0/19, 80.253.240.0/20, 80.254.0.0/20, 80.254.32.0/20, 80.254.64.0/19, 80.254.128.0/17, 80.255.0.0/20, 80.255.32.0/19, 80.255.64.0/20, 80.255.96.0/19, 80.255.160.0/20, 80.255.192.0/18, 81.0.0.0/16, 81.1.0.0/17, 81.2.64.0/18, 81.2.128.0/17, 81.3.0.0/17, 81.3.192.0/18, 81.4.0.0/17, 81.4.128.0/18, 81.5.0.0/18, 81.5.128.0/17, 81.6.0.0/15, 81.8.0.0/16, 81.9.128.0/17, 81.10.0.0/15, 81.12.0.0/16, 81.13.128.0/17, 81.14.0.0/15, 81.16.0.0/18, 81.16.64.0/20, 81.16.96.0/19, 81.16.128.0/17, 81.17.16.0/20, 81.17.32.0/19, 81.17.64.0/18, 81.17.128.0/20, 81.17.160.0/19, 81.17.192.0/18, 81.18.0.0/18, 81.18.64.0/19, 81.18.96.0/20, 81.18.160.0/19, 81.18.192.0/18, 81.19.0.0/18, 81.19.96.0/19, 81.19.144.0/20, 81.19.160.0/19, 81.19.192.0/18, 81.20.0.0/18, 81.20.64.0/19, 81.20.112.0/20, 81.20.128.0/19, 81.20.176.0/20, 81.20.208.0/20, 81.20.224.0/19, 81.21.0.0/16, 81.22.16.0/20, 81.22.32.0/20, 81.22.64.0/18, 81.22.128.0/17, 81.23.16.0/20, 81.23.32.0/19, 81.23.64.0/19, 81.23.128.0/20, 81.23.208.0/20, 81.23.224.0/19, 81.24.0.0/18, 81.24.64.0/20, 81.24.96.0/20, 81.24.144.0/20, 81.24.160.0/20, 81.24.192.0/18, 81.25.16.0/20, 81.25.32.0/20, 81.25.64.0/18, 81.25.128.0/17, 81.26.0.0/18, 81.26.64.0/20, 81.26.96.0/19, 81.26.144.0/20, 81.26.160.0/20, 81.26.192.0/18, 81.27.0.0/19, 81.27.32.0/20, 81.27.64.0/18, 81.27.128.0/20, 81.27.160.0/19, 81.27.192.0/19, 81.27.224.0/20, 81.28.0.0/17, 81.28.128.0/19, 81.28.192.0/20, 81.28.224.0/19, 81.29.0.0/18, 81.29.64.0/19, 81.29.96.0/20, 81.29.144.0/20, 81.29.160.0/19, 81.29.192.0/18, 81.30.0.0/19, 81.30.32.0/20, 81.30.64.0/19, 81.30.96.0/20, 81.30.128.0/19, 81.30.160.0/20, 81.30.224.0/19, 81.31.0.0/16, 81.32.0.0/11, 81.64.0.0/12, 81.80.0.0/13, 81.88.0.0/18, 81.88.64.0/20, 81.88.96.0/20, 81.88.128.0/18, 81.88.192.0/20, 81.88.224.0/19, 81.89.0.0/18, 81.89.80.0/20, 81.89.96.0/20, 81.89.128.0/19, 81.89.160.0/20, 81.89.192.0/18, 81.90.16.0/20, 81.90.32.0/19, 81.90.64.0/18, 81.90.128.0/18, 81.90.192.0/20, 81.90.224.0/19, 81.91.0.0/19, 81.91.64.0/18, 81.91.128.0/17, 81.92.0.0/15, 81.94.0.0/20, 81.94.48.0/20, 81.94.64.0/18, 81.94.160.0/19, 81.94.192.0/18, 81.95.0.0/20, 81.95.48.0/20, 81.95.64.0/18, 81.95.144.0/20, 81.95.160.0/19, 81.95.192.0/20, 81.95.224.0/19, 81.96.0.0/11, 81.128.0.0/11, 81.160.0.0/16, 81.161.0.0/20, 81.161.48.0/20, 81.161.64.0/18, 81.161.128.0/18, 81.161.192.0/20, 81.161.224.0/19, 81.162.48.0/20, 81.162.64.0/19, 81.162.128.0/17, 81.163.0.0/20, 81.163.64.0/20, 81.163.112.0/20, 81.163.192.0/19, 81.164.0.0/14, 81.168.0.0/13, 81.178.0.0/15, 81.180.0.0/14, 81.184.0.0/13, 81.192.0.0/15, 81.194.0.0/16, 81.196.0.0/14, 81.200.32.0/19, 81.200.64.0/20, 81.200.96.0/20, 81.200.128.0/20, 81.200.160.0/19, 81.200.192.0/20, 81.200.224.0/20, 81.201.0.0/20, 81.201.32.0/19, 81.201.80.0/20, 81.201.96.0/19, 81.201.128.0/18, 81.201.192.0/19, 81.201.224.0/20, 81.202.0.0/15, 81.204.0.0/14, 81.208.0.0/15, 81.210.0.0/16, 81.211.128.0/17, 81.212.0.0/14, 81.216.0.0/14, 81.220.0.0/15, 81.223.0.0/16, 81.224.0.0/11, 82.0.0.0/10, 82.64.0.0/11, 82.96.0.0/17, 82.96.128.0/18, 82.97.0.0/16, 82.98.0.0/15, 82.100.0.0/14, 82.104.0.0/13, 82.112.64.0/18, 82.112.128.0/19, 82.112.160.0/20, 82.112.192.0/18, 82.113.0.0/16, 82.114.32.0/19, 82.114.64.0/19, 82.114.160.0/19, 82.114.192.0/19, 82.115.0.0/16, 82.116.64.0/18, 82.116.128.0/17, 82.117.0.0/18, 82.117.96.0/19, 82.117.128.0/19, 82.117.192.0/18, 82.118.0.0/17, 82.118.160.0/19, 82.118.192.0/18, 82.119.0.0/17, 82.119.160.0/19, 82.119.192.0/18, 82.120.0.0/13, 82.128.0.0/13, 82.136.0.0/16, 82.137.0.0/17, 82.137.192.0/18, 82.138.64.0/18, 82.138.128.0/17, 82.139.0.0/16, 82.140.0.0/18, 82.140.128.0/18, 82.141.0.0/16, 82.142.0.0/17, 82.142.192.0/18, 82.143.0.0/16, 82.144.0.0/18, 82.144.96.0/19, 82.144.128.0/17, 82.145.0.0/16, 82.146.0.0/19, 82.146.64.0/18, 82.146.128.0/17, 82.147.0.0/18, 82.147.128.0/17, 82.148.0.0/20, 82.148.32.0/19, 82.148.64.0/18, 82.148.128.0/17, 82.149.0.0/17, 82.149.160.0/19, 82.149.224.0/19, 82.150.0.0/16, 82.151.0.0/18, 82.151.64.0/19, 82.151.128.0/18, 82.151.224.0/19, 82.152.0.0/13, 82.160.0.0/15, 82.163.0.0/16, 82.164.0.0/14, 82.168.0.0/13, 82.176.0.0/15, 82.178.0.0/16, 82.180.0.0/14, 82.184.0.0/13, 82.192.0.0/16, 82.193.0.0/17, 82.193.160.0/19, 82.193.192.0/18, 82.194.0.0/17, 82.194.128.0/19, 82.194.192.0/19, 82.195.32.0/19, 82.195.64.0/18, 82.195.128.0/17, 82.196.0.0/18, 82.196.96.0/19, 82.196.160.0/19, 82.196.192.0/18, 82.197.0.0/16, 82.198.32.0/19, 82.198.64.0/18, 82.198.128.0/19, 82.198.192.0/18, 82.199.0.0/18, 82.199.64.0/19, 82.199.128.0/17, 82.200.128.0/17, 82.201.0.0/16, 82.202.0.0/17, 82.202.176.0/20, 82.203.0.0/16, 82.204.0.0/17, 82.205.0.0/16, 82.206.0.0/15, 82.208.0.0/18, 82.208.128.0/17, 82.209.0.0/18, 82.209.128.0/17, 82.210.0.0/15, 82.212.0.0/14, 82.216.0.0/13, 82.224.0.0/11, 83.0.0.0/10, 83.64.0.0/14, 83.68.0.0/19, 83.68.64.0/18, 83.68.128.0/17, 83.69.32.0/19, 83.69.128.0/19, 83.70.0.0/15, 83.72.0.0/13, 83.80.0.0/12, 83.96.0.0/14, 83.100.0.0/15, 83.102.0.0/17, 83.103.0.0/16, 83.104.0.0/13, 83.112.0.0/12, 83.128.0.0/13, 83.136.0.0/15, 83.138.0.0/16, 83.139.0.0/17, 83.139.192.0/18, 83.140.0.0/15, 83.142.0.0/16, 83.143.0.0/17, 83.143.128.0/18, 83.143.208.0/20, 83.143.224.0/19, 83.144.0.0/15, 83.146.0.0/18, 83.146.128.0/17, 83.147.0.0/16, 83.148.0.0/16, 83.149.64.0/18, 83.149.128.0/18, 83.150.0.0/16, 83.151.16.0/20, 83.151.32.0/19, 83.151.64.0/18, 83.151.128.0/17, 83.152.0.0/13, 83.160.0.0/14, 83.164.0.0/15, 83.166.0.0/18, 83.166.64.0/19, 83.166.128.0/18, 83.166.192.0/19, 83.167.32.0/19, 83.167.128.0/17, 83.168.0.0/16, 83.169.0.0/17, 83.169.128.0/18, 83.170.0.0/16, 83.171.0.0/18, 83.171.128.0/17, 83.172.64.0/18, 83.172.128.0/17, 83.173.0.0/16, 83.174.0.0/17, 83.174.128.0/18, 83.175.0.0/16, 83.176.0.0/12, 83.192.0.0/12, 83.208.0.0/13, 83.216.0.0/16, 83.217.64.0/18, 83.217.128.0/18, 83.217.224.0/19, 83.218.0.0/16, 83.219.32.0/19, 83.219.64.0/18, 83.219.160.0/19, 83.219.192.0/19, 83.220.0.0/19, 83.220.96.0/19, 83.220.128.0/19, 83.220.192.0/19, 83.221.32.0/19, 83.221.64.0/18, 83.221.128.0/18, 83.221.224.0/19, 83.222.0.0/20, 83.222.32.0/19, 83.222.128.0/18, 83.222.224.0/19, 83.223.0.0/16, 83.224.0.0/14, 83.228.0.0/16, 83.229.0.0/17, 83.230.0.0/15, 83.232.0.0/15, 83.235.0.0/16, 83.236.0.0/16, 83.238.0.0/16, 83.240.0.0/15, 83.242.0.0/17, 83.243.0.0/16, 83.244.0.0/15, 83.246.0.0/17, 83.247.0.0/16, 83.248.0.0/13, 84.0.0.0/12, 84.16.0.0/17, 84.16.160.0/19, 84.16.192.0/18, 84.17.32.0/19, 84.17.64.0/18, 84.17.128.0/18, 84.17.192.0/19, 84.18.0.0/18, 84.18.64.0/19, 84.18.128.0/17, 84.19.0.0/16, 84.20.0.0/16, 84.21.0.0/18, 84.21.96.0/19, 84.21.128.0/18, 84.21.192.0/19, 84.22.0.0/17, 84.22.160.0/19, 84.22.224.0/19, 84.23.0.0/19, 84.23.64.0/18, 84.23.128.0/17, 84.24.0.0/13, 84.32.0.0/14, 84.36.0.0/15, 84.38.0.0/17, 84.38.128.0/19, 84.38.160.0/20, 84.38.192.0/18, 84.39.0.0/17, 84.39.128.0/18, 84.39.192.0/19, 84.39.224.0/20, 84.40.0.0/15, 84.42.128.0/17, 84.43.0.0/16, 84.44.0.0/14, 84.48.0.0/15, 84.50.0.0/16, 84.51.0.0/18, 84.51.128.0/18, 84.51.224.0/19, 84.52.0.0/18, 84.52.128.0/17, 84.53.0.0/17, 84.53.128.0/18, 84.54.0.0/17, 84.54.128.0/18, 84.55.0.0/16, 84.56.0.0/13, 84.64.0.0/10, 84.128.0.0/10, 84.192.0.0/13, 84.200.0.0/16, 84.201.0.0/17, 84.201.192.0/19, 84.201.224.0/20, 84.202.0.0/15, 84.205.0.0/16, 84.206.0.0/15, 84.208.0.0/12, 84.224.0.0/13, 84.232.0.0/14, 84.236.0.0/16, 84.237.128.0/17, 84.238.0.0/15, 84.240.0.0/15, 84.242.0.0/17, 84.242.128.0/18, 84.243.0.0/16, 84.244.64.0/18, 84.244.128.0/17, 84.245.0.0/16, 84.246.0.0/15, 84.248.0.0/14, 84.252.0.0/17, 84.252.144.0/20, 84.252.192.0/18, 84.253.0.0/18, 84.253.128.0/17, 84.254.0.0/17, 84.254.128.0/18, 84.255.0.0/16, 85.0.0.0/13, 85.8.0.0/14, 85.12.0.0/17, 85.12.128.0/18, 85.13.0.0/16, 85.14.0.0/16, 85.15.0.0/18, 85.15.192.0/18, 85.16.0.0/14, 85.20.0.0/16, 85.22.0.0/15, 85.24.0.0/15, 85.26.0.0/17, 85.27.0.0/16, 85.28.64.0/18, 85.28.128.0/18, 85.29.0.0/16, 85.30.0.0/17, 85.30.128.0/18, 85.31.0.0/18, 85.31.64.0/19, 85.31.96.0/20, 85.31.128.0/17, 85.32.0.0/11, 85.64.0.0/12, 85.80.0.0/13, 85.88.0.0/17, 85.88.128.0/19, 85.88.192.0/18, 85.89.0.0/18, 85.89.64.0/19, 85.89.128.0/17, 85.90.0.0/18, 85.90.64.0/19, 85.90.128.0/17, 85.91.0.0/17, 85.91.128.0/18, 85.91.224.0/19, 85.92.32.0/19, 85.92.64.0/18, 85.92.128.0/17, 85.93.0.0/19, 85.93.64.0/18, 85.93.160.0/19, 85.93.192.0/18, 85.94.64.0/18, 85.94.128.0/17, 85.95.0.0/17, 85.95.192.0/18, 85.96.0.0/12, 85.112.0.0/19, 85.112.64.0/19, 85.112.128.0/17, 85.113.0.0/19, 85.113.64.0/18, 85.113.160.0/19, 85.113.224.0/19, 85.114.32.0/19, 85.114.96.0/19, 85.114.128.0/19, 85.114.192.0/18, 85.115.0.0/17, 85.115.128.0/19, 85.115.192.0/19, 85.116.0.0/18, 85.116.64.0/19, 85.116.128.0/17, 85.117.0.0/18, 85.117.96.0/19, 85.117.128.0/17, 85.118.0.0/15, 85.120.0.0/13, 85.128.0.0/13, 85.136.0.0/14, 85.144.0.0/13, 85.152.0.0/14, 85.156.0.0/15, 85.158.0.0/16, 85.159.0.0/19, 85.159.48.0/20, 85.159.64.0/18, 85.159.128.0/17, 85.160.0.0/13, 85.168.0.0/14, 85.176.0.0/12, 85.192.64.0/18, 85.192.192.0/18, 85.193.0.0/18, 85.193.64.0/20, 85.193.96.0/19, 85.193.128.0/17, 85.194.0.0/15, 85.196.0.0/15, 85.198.0.0/18, 85.198.80.0/20, 85.198.112.0/20, 85.198.128.0/17, 85.199.0.0/16, 85.200.0.0/15, 85.202.16.0/20, 85.202.32.0/19, 85.202.64.0/18, 85.202.144.0/20, 85.202.160.0/20, 85.202.192.0/19, 85.203.0.0/16, 85.204.0.0/14, 85.208.0.0/12, 85.224.0.0/13, 85.232.0.0/18, 85.232.64.0/19, 85.232.128.0/17, 85.233.0.0/18, 85.233.96.0/19, 85.233.160.0/19, 85.233.192.0/18, 85.234.64.0/19, 85.234.128.0/17, 85.235.0.0/19, 85.235.64.0/18, 85.235.128.0/19, 85.235.224.0/19, 85.236.32.0/19, 85.236.64.0/18, 85.236.128.0/19, 85.236.192.0/18, 85.237.0.0/19, 85.237.64.0/18, 85.237.128.0/17, 85.238.0.0/16, 85.239.0.0/19, 85.239.64.0/18, 85.239.128.0/17, 85.240.0.0/13, 85.248.0.0/16, 85.250.0.0/15, 85.252.0.0/14, 86.0.0.0/11, 86.32.0.0/12, 86.48.0.0/13, 86.56.0.0/14, 86.60.0.0/15, 86.62.0.0/18, 86.62.128.0/17, 86.63.0.0/16, 86.64.0.0/11, 86.96.0.0/14, 86.100.0.0/15, 86.103.0.0/16, 86.104.0.0/14, 86.108.0.0/16, 86.109.0.0/17, 86.109.128.0/18, 86.109.224.0/19, 86.110.32.0/19, 86.110.64.0/19, 86.110.128.0/19, 86.110.192.0/20, 86.110.224.0/19, 86.111.32.0/19, 86.111.64.0/18, 86.111.128.0/17, 86.112.0.0/12, 86.128.0.0/9, 87.0.0.0/10, 87.64.0.0/13, 87.72.0.0/14, 87.76.16.0/20, 87.76.32.0/19, 87.76.64.0/18, 87.76.128.0/17, 87.77.0.0/16, 87.78.0.0/15, 87.80.0.0/12, 87.96.0.0/14, 87.100.0.0/15, 87.102.0.0/16, 87.103.0.0/17, 87.104.0.0/13, 87.112.0.0/14, 87.116.0.0/16, 87.117.64.0/18, 87.117.192.0/18, 87.118.0.0/17, 87.118.128.0/18, 87.119.0.0/17, 87.119.128.0/18, 87.119.192.0/19, 87.120.0.0/13, 87.128.0.0/10, 87.192.0.0/11, 87.224.0.0/17, 87.225.128.0/17, 87.226.0.0/17, 87.227.0.0/16, 87.228.128.0/17, 87.229.0.0/17, 87.230.0.0/15, 87.232.0.0/14, 87.236.0.0/20, 87.236.32.0/19, 87.236.64.0/18, 87.236.128.0/17, 87.237.0.0/16, 87.238.0.0/15, 87.240.64.0/18, 87.240.192.0/18, 87.241.0.0/17, 87.241.128.0/18, 87.242.0.0/18, 87.242.128.0/17, 87.243.0.0/16, 87.244.64.0/18, 87.244.128.0/17, 87.245.0.0/17, 87.245.208.0/20, 87.245.224.0/19, 87.246.0.0/15, 87.248.0.0/17, 87.248.128.0/18, 87.248.192.0/19, 87.249.64.0/18, 87.249.128.0/18, 87.250.32.0/19, 87.250.64.0/18, 87.250.128.0/18, 87.251.0.0/18, 87.251.128.0/19, 87.251.192.0/18, 87.252.0.0/16, 87.253.32.0/19, 87.253.64.0/18, 87.253.128.0/18, 87.253.224.0/19, 87.254.0.0/17, 87.254.160.0/19, 87.254.192.0/18, 87.255.32.0/19, 87.255.64.0/18, 87.255.128.0/18, 87.255.192.0/19, 88.0.0.0/10, 88.64.0.0/12, 88.80.0.0/19, 88.80.64.0/18, 88.80.128.0/17, 88.81.0.0/19, 88.81.64.0/18, 88.81.128.0/18, 88.81.224.0/19, 88.82.0.0/18, 88.82.96.0/19, 88.82.128.0/19, 88.82.192.0/18, 88.83.0.0/17, 88.83.128.0/18, 88.83.224.0/19, 88.84.0.0/17, 88.84.128.0/18, 88.84.224.0/19, 88.85.0.0/17, 88.85.128.0/19, 88.85.224.0/19, 88.86.0.0/18, 88.86.96.0/19, 88.86.128.0/18, 88.86.224.0/19, 88.87.0.0/18, 88.87.96.0/19, 88.87.128.0/17, 88.88.0.0/13, 88.96.0.0/11, 88.128.0.0/14, 88.132.0.0/15, 88.134.0.0/16, 88.135.16.0/20, 88.135.32.0/20, 88.135.64.0/20, 88.135.96.0/20, 88.135.128.0/17, 88.136.0.0/13, 88.144.0.0/15, 88.146.0.0/16, 88.147.0.0/17, 88.148.0.0/14, 88.152.0.0/13, 88.160.0.0/11, 88.192.0.0/13, 88.200.0.0/17, 88.201.0.0/17, 88.202.0.0/15, 88.204.128.0/17, 88.205.0.0/17, 88.206.128.0/17, 88.207.0.0/16, 88.208.0.0/14, 88.212.0.0/17, 88.212.128.0/18, 88.213.0.0/16, 88.214.0.0/16, 88.215.0.0/17, 88.215.192.0/18, 88.216.0.0/13, 88.224.0.0/11, 89.0.0.0/12, 89.16.0.0/19, 89.16.64.0/19, 89.16.128.0/17, 89.17.0.0/19, 89.17.64.0/18, 89.17.128.0/17, 89.18.0.0/17, 89.18.160.0/19, 89.18.192.0/18, 89.19.0.0/17, 89.19.128.0/19, 89.19.224.0/19, 89.20.48.0/20, 89.20.64.0/19, 89.20.160.0/19, 89.20.192.0/18, 89.21.0.0/17, 89.21.160.0/19, 89.21.192.0/18, 89.22.0.0/20, 89.22.32.0/19, 89.22.64.0/18, 89.22.192.0/19, 89.23.64.0/19, 89.23.112.0/20, 89.23.224.0/19, 89.24.0.0/14, 89.28.0.0/15, 89.30.0.0/16, 89.31.0.0/18, 89.31.64.0/20, 89.31.96.0/19, 89.31.128.0/17, 89.32.0.0/11, 89.64.0.0/11, 89.96.0.0/13, 89.104.0.0/18, 89.104.96.0/19, 89.104.128.0/17, 89.105.0.0/17, 89.105.160.0/19, 89.105.192.0/18, 89.106.0.0/16, 89.107.0.0/18, 89.107.64.0/19, 89.107.96.0/20, 89.107.128.0/18, 89.107.208.0/20, 89.107.224.0/19, 89.108.0.0/18, 89.108.128.0/17, 89.109.64.0/18, 89.110.64.0/18, 89.110.128.0/17, 89.111.0.0/17, 89.111.192.0/18, 89.114.0.0/15, 89.116.0.0/14, 89.120.0.0/13, 89.128.0.0/12, 89.144.0.0/16, 89.145.0.0/17, 89.145.160.0/19, 89.145.192.0/18, 89.146.0.0/15, 89.148.0.0/17, 89.148.128.0/18, 89.149.0.0/16, 89.150.0.0/16, 89.151.0.0/17, 89.151.192.0/18, 89.152.0.0/13, 89.160.0.0/15, 89.162.0.0/16, 89.163.128.0/17, 89.164.0.0/14, 89.168.0.0/16, 89.169.32.0/19, 89.169.96.0/19, 89.170.0.0/15, 89.172.0.0/15, 89.174.0.0/16, 89.176.0.0/15, 89.180.0.0/14, 89.184.32.0/19, 89.184.64.0/18, 89.184.128.0/18, 89.184.192.0/19, 89.185.0.0/18, 89.185.96.0/19, 89.185.128.0/17, 89.186.0.0/17, 89.186.128.0/18, 89.186.192.0/19, 89.187.0.0/16, 89.188.0.0/18, 89.188.64.0/19, 89.188.128.0/19, 89.188.192.0/19, 89.189.32.0/19, 89.189.64.0/19, 89.189.192.0/18, 89.190.0.0/17, 89.190.128.0/18, 89.190.192.0/19, 89.191.0.0/17, 89.191.128.0/18, 89.191.192.0/19, 89.191.224.0/20, 89.192.0.0/13, 89.200.0.0/14, 89.204.128.0/17, 89.205.0.0/16, 89.206.0.0/16, 89.207.0.0/19, 89.207.48.0/20, 89.207.80.0/20, 89.207.96.0/19, 89.207.128.0/17, 89.208.0.0/20, 89.208.48.0/20, 89.208.112.0/20, 89.208.128.0/20, 89.208.176.0/20, 89.208.240.0/20, 89.209.0.0/16, 89.210.0.0/15, 89.212.0.0/14, 89.216.0.0/14, 89.220.0.0/16, 89.221.0.0/20, 89.221.32.0/20, 89.221.64.0/18, 89.221.128.0/18, 89.221.208.0/20, 89.221.224.0/19, 89.222.0.0/17, 89.223.0.0/19, 89.223.80.0/20, 89.223.112.0/20, 89.223.128.0/17, 89.224.0.0/13, 89.232.0.0/18, 89.232.160.0/19, 89.233.0.0/16, 89.234.0.0/16, 89.235.0.0/17, 89.235.192.0/18, 89.236.0.0/16, 89.237.64.0/18, 89.237.128.0/17, 89.238.0.0/16, 89.239.0.0/17, 89.239.192.0/18, 89.240.0.0/13, 89.248.0.0/18, 89.248.64.0/19, 89.248.96.0/20, 89.248.128.0/19, 89.248.160.0/20, 89.248.208.0/20, 89.248.224.0/19, 89.249.0.0/20, 89.249.32.0/19, 89.249.64.0/19, 89.249.96.0/20, 89.249.176.0/20, 89.249.192.0/19, 89.250.32.0/19, 89.250.64.0/18, 89.250.128.0/20, 89.250.176.0/20, 89.250.192.0/20, 89.250.240.0/20, 89.251.0.0/18, 89.251.80.0/20, 89.251.112.0/20, 89.251.128.0/20, 89.251.176.0/20, 89.251.192.0/18, 89.252.0.0/18, 89.252.128.0/17, 89.253.64.0/18, 89.253.128.0/18, 89.254.0.0/17, 89.254.128.0/18, 89.255.0.0/18, 89.255.128.0/17, 90.0.0.0/9, 90.128.0.0/12, 90.144.0.0/14, 90.148.0.0/15, 90.152.0.0/15, 90.154.128.0/17, 90.155.0.0/17, 90.156.0.0/17, 90.156.144.0/20, 90.156.160.0/20, 90.156.192.0/19, 90.157.128.0/17, 90.158.0.0/15, 90.160.0.0/12, 90.176.0.0/13, 90.184.0.0/14, 90.190.0.0/15, 90.192.0.0/10, 91.0.0.0/10, 91.64.0.0/13, 91.72.0.0/14, 91.80.0.0/12, 91.96.0.0/14, 91.100.0.0/15, 91.102.0.0/16, 91.103.0.0/17, 91.103.128.0/18, 91.103.208.0/20, 91.103.224.0/19, 91.104.0.0/16, 91.105.0.0/17, 91.105.192.0/18, 91.106.0.0/16, 91.107.64.0/19, 91.107.128.0/17, 91.108.0.0/16, 91.109.0.0/18, 91.109.96.0/19, 91.109.160.0/19, 91.109.192.0/18, 91.110.0.0/15, 91.112.0.0/13, 91.120.0.0/15, 91.123.0.0/20, 91.123.32.0/19, 91.123.64.0/20, 91.123.96.0/19, 91.123.128.0/17, 91.124.0.0/14, 91.128.0.0/14, 91.132.0.0/16, 91.133.32.0/19, 91.133.64.0/18, 91.133.128.0/17, 91.134.0.0/16, 91.135.0.0/17, 91.135.128.0/20, 91.135.160.0/19, 91.135.192.0/20, 91.135.224.0/19, 91.136.0.0/14, 91.140.0.0/15, 91.142.0.0/18, 91.142.64.0/20, 91.142.96.0/19, 91.142.128.0/20, 91.142.160.0/19, 91.142.192.0/18, 91.143.64.0/18, 91.143.176.0/20, 91.143.192.0/18, 91.144.0.0/17, 91.144.192.0/18, 91.145.0.0/16, 91.146.64.0/18, 91.146.128.0/17, 91.147.64.0/18, 91.147.128.0/17, 91.148.0.0/16, 91.149.0.0/18, 91.149.128.0/17, 91.150.0.0/16, 91.151.0.0/19, 91.151.48.0/20, 91.151.64.0/18, 91.151.128.0/19, 91.151.160.0/20, 91.151.208.0/20, 91.151.224.0/19, 91.152.0.0/13, 91.160.0.0/12, 91.176.0.0/13, 91.184.0.0/17, 91.184.128.0/18, 91.184.192.0/19, 91.184.240.0/20, 91.185.0.0/19, 91.185.96.0/19, 91.185.128.0/18, 91.185.192.0/19, 91.186.0.0/18, 91.186.64.0/19, 91.186.128.0/17, 91.187.0.0/16, 91.188.0.0/17, 91.188.128.0/19, 91.188.192.0/18, 91.189.0.0/16, 91.190.0.0/18, 91.190.80.0/20, 91.190.96.0/20, 91.190.128.0/17, 91.191.0.0/17, 91.191.128.0/18, 91.191.192.0/19, 91.192.0.0/13, 91.200.0.0/14, 91.204.0.0/18, 91.204.64.0/19, 91.204.112.0/20, 91.204.128.0/17, 91.205.0.0/16, 91.206.0.0/15, 91.208.0.0/15, 91.210.0.0/17, 91.210.128.0/18, 91.210.208.0/20, 91.210.224.0/19, 91.211.0.0/16, 91.212.0.0/15, 91.214.0.0/16, 91.215.0.0/17, 91.215.128.0/18, 91.215.208.0/20, 91.215.224.0/19, 91.216.0.0/15, 91.218.0.0/17, 91.218.144.0/20, 91.218.160.0/19, 91.218.192.0/18, 91.219.0.0/16, 91.220.0.0/14, 91.224.0.0/12, 91.240.0.0/16, 91.241.0.0/17, 91.241.160.0/19, 91.241.192.0/18, 91.242.0.0/17, 91.242.128.0/19, 91.242.160.0/20, 91.242.192.0/18, 91.243.0.0/19, 91.243.64.0/19, 91.243.112.0/20, 91.243.128.0/18, 91.243.192.0/19, 91.244.0.0/18, 91.244.64.0/20, 91.244.96.0/19, 91.244.128.0/19, 91.244.176.0/20, 91.244.192.0/18, 91.245.0.0/19, 91.245.64.0/18, 91.245.160.0/19, 91.245.192.0/18, 91.246.0.0/18, 91.246.64.0/19, 91.246.96.0/20, 91.246.128.0/17, 91.247.32.0/19, 91.247.64.0/19, 91.247.144.0/20, 91.247.160.0/19, 91.247.208.0/20, 91.247.224.0/19, 91.248.0.0/13, 92.0.0.0/11, 92.32.0.0/14, 92.36.128.0/17, 92.37.0.0/17, 92.38.0.0/17, 92.38.128.0/18, 92.38.208.0/20, 92.39.0.0/18, 92.39.80.0/20, 92.39.112.0/20, 92.39.144.0/20, 92.39.160.0/19, 92.39.192.0/20, 92.39.240.0/20, 92.40.0.0/13, 92.48.0.0/16, 92.49.0.0/17, 92.49.192.0/18, 92.50.0.0/17, 92.51.48.0/20, 92.51.64.0/18, 92.51.128.0/17, 92.52.0.0/16, 92.53.0.0/18, 92.53.128.0/17, 92.54.0.0/18, 92.54.128.0/17, 92.55.64.0/18, 92.55.128.0/17, 92.56.0.0/14, 92.60.0.0/18, 92.60.64.0/20, 92.60.96.0/19, 92.60.128.0/17, 92.61.0.0/20, 92.61.32.0/19, 92.61.64.0/18, 92.61.128.0/17, 92.62.0.0/17, 92.62.128.0/20, 92.62.160.0/19, 92.62.192.0/18, 92.63.0.0/18, 92.63.80.0/20, 92.63.112.0/20, 92.63.128.0/17, 92.64.0.0/11, 92.96.0.0/14, 92.102.0.0/15, 92.104.0.0/13, 92.112.0.0/13, 92.120.0.0/14, 92.128.0.0/10, 92.192.0.0/11, 92.224.0.0/12, 92.240.0.0/17, 92.240.144.0/20, 92.240.160.0/19, 92.240.224.0/19, 92.241.32.0/19, 92.241.64.0/19, 92.241.128.0/19, 92.241.192.0/19, 92.242.128.0/17, 92.243.0.0/18, 92.243.64.0/19, 92.243.128.0/19, 92.243.192.0/18, 92.244.0.0/17, 92.244.128.0/18, 92.244.192.0/19, 92.245.0.0/19, 92.245.64.0/18, 92.245.128.0/17, 92.246.0.0/17, 92.246.128.0/19, 92.246.224.0/19, 92.247.0.0/16, 92.248.0.0/17, 92.249.0.0/16, 92.250.0.0/15, 92.252.0.0/17, 92.253.0.0/16, 92.254.0.0/16, 92.255.0.0/20, 92.255.32.0/19, 92.255.64.0/19, 93.0.0.0/10, 93.64.0.0/13, 93.72.0.0/14, 93.76.0.0/16, 93.77.0.0/17, 93.77.128.0/19, 93.77.192.0/18, 93.78.0.0/15, 93.82.0.0/15, 93.84.0.0/14, 93.88.16.0/20, 93.88.32.0/19, 93.88.64.0/18, 93.88.144.0/20, 93.88.192.0/20, 93.88.224.0/19, 93.89.0.0/17, 93.89.128.0/19, 93.89.160.0/20, 93.89.192.0/18, 93.90.0.0/19, 93.90.48.0/20, 93.90.64.0/20, 93.90.112.0/20, 93.90.128.0/18, 93.90.192.0/20, 93.91.16.0/20, 93.91.32.0/19, 93.91.64.0/19, 93.91.96.0/20, 93.91.128.0/19, 93.91.176.0/20, 93.91.192.0/19, 93.91.240.0/20, 93.92.0.0/18, 93.92.64.0/20, 93.92.96.0/19, 93.92.128.0/18, 93.92.208.0/20, 93.92.224.0/19, 93.93.0.0/16, 93.94.0.0/17, 93.94.128.0/20, 93.94.160.0/19, 93.94.192.0/18, 93.95.0.0/17, 93.95.128.0/19, 93.95.176.0/20, 93.95.192.0/18, 93.96.0.0/14, 93.101.0.0/16, 93.102.0.0/15, 93.104.0.0/13, 93.112.0.0/13, 93.120.0.0/17, 93.121.0.0/16, 93.122.0.0/16, 93.123.0.0/17, 93.124.128.0/17, 93.125.0.0/16, 93.126.0.0/15, 93.128.0.0/12, 93.144.0.0/13, 93.152.0.0/16, 93.153.0.0/17, 93.154.0.0/15, 93.156.0.0/16, 93.157.0.0/17, 93.157.128.0/19, 93.157.176.0/20, 93.157.192.0/19, 93.157.224.0/20, 93.158.0.0/17, 93.158.192.0/19, 93.158.224.0/20, 93.159.0.0/16, 93.160.0.0/13, 93.168.0.0/14, 93.172.0.0/15, 93.174.0.0/19, 93.174.32.0/20, 93.174.64.0/18, 93.174.128.0/17, 93.175.32.0/19, 93.175.64.0/18, 93.175.128.0/17, 93.176.0.0/16, 93.177.64.0/18, 93.177.128.0/17, 93.178.0.0/18, 93.178.128.0/17, 93.179.0.0/16, 93.180.64.0/18, 93.180.128.0/17, 93.181.0.0/17, 93.181.128.0/18, 93.182.64.0/18, 93.182.128.0/17, 93.183.0.0/18, 93.183.80.0/20, 93.183.96.0/19, 93.183.128.0/17, 93.184.0.0/17, 93.184.128.0/19, 93.184.176.0/20, 93.184.192.0/18, 93.185.0.0/20, 93.185.32.0/19, 93.185.64.0/18, 93.185.128.0/19, 93.185.160.0/20, 93.185.208.0/20, 93.185.224.0/19, 93.186.0.0/19, 93.186.32.0/20, 93.186.64.0/18, 93.186.128.0/18, 93.186.192.0/19, 93.186.240.0/20, 93.187.0.0/17, 93.187.128.0/19, 93.187.160.0/20, 93.187.192.0/18, 93.188.0.0/14, 93.192.0.0/10, 94.0.0.0/12, 94.16.0.0/15, 94.18.0.0/16, 94.20.0.0/14, 94.24.0.0/17, 94.26.0.0/17, 94.26.192.0/19, 94.27.0.0/16, 94.29.128.0/17, 94.30.0.0/16, 94.31.0.0/17, 94.32.0.0/13, 94.40.0.0/16, 94.42.0.0/15, 94.44.0.0/16, 94.45.32.0/19, 94.45.64.0/18, 94.45.128.0/18, 94.45.224.0/19, 94.46.0.0/15, 94.48.0.0/15, 94.52.0.0/14, 94.56.0.0/13, 94.64.0.0/13, 94.72.64.0/18, 94.72.128.0/17, 94.73.0.0/17, 94.73.128.0/18, 94.74.0.0/16, 94.75.64.0/18, 94.75.192.0/18, 94.76.0.0/16, 94.77.192.0/18, 94.78.0.0/17, 94.78.128.0/18, 94.79.64.0/18, 94.79.128.0/17, 94.80.0.0/12, 94.96.0.0/14, 94.100.0.0/18, 94.100.64.0/20, 94.100.96.0/19, 94.100.128.0/19, 94.100.160.0/20, 94.100.208.0/20, 94.100.224.0/19, 94.101.0.0/18, 94.101.80.0/20, 94.101.96.0/19, 94.101.128.0/17, 94.102.0.0/20, 94.102.32.0/19, 94.102.64.0/19, 94.102.96.0/20, 94.102.128.0/19, 94.102.160.0/20, 94.102.192.0/18, 94.103.0.0/17, 94.103.128.0/18, 94.103.192.0/19, 94.103.240.0/20, 94.104.0.0/13, 94.112.0.0/13, 94.120.0.0/14, 94.124.0.0/17, 94.124.128.0/18, 94.124.208.0/20, 94.124.224.0/19, 94.125.16.0/20, 94.125.32.0/19, 94.125.64.0/18, 94.125.128.0/17, 94.126.0.0/16, 94.127.0.0/17, 94.127.128.0/20, 94.127.160.0/19, 94.127.192.0/19, 94.127.224.0/20, 94.128.0.0/13, 94.136.0.0/17, 94.136.128.0/18, 94.136.224.0/19, 94.137.96.0/19, 94.137.128.0/18, 94.138.32.0/19, 94.138.64.0/18, 94.138.160.0/19, 94.138.192.0/18, 94.139.0.0/18, 94.139.64.0/19, 94.139.128.0/18, 94.139.192.0/19, 94.139.224.0/20, 94.140.0.0/17, 94.140.160.0/19, 94.141.0.0/19, 94.141.64.0/19, 94.141.96.0/20, 94.141.128.0/19, 94.141.192.0/18, 94.142.32.0/19, 94.142.64.0/18, 94.142.128.0/17, 94.143.0.0/19, 94.143.32.0/20, 94.143.64.0/18, 94.143.128.0/17, 94.144.0.0/13, 94.152.0.0/15, 94.154.0.0/18, 94.154.96.0/19, 94.154.128.0/17, 94.155.0.0/16, 94.156.0.0/15, 94.158.16.0/20, 94.158.48.0/20, 94.158.64.0/19, 94.158.128.0/19, 94.158.176.0/20, 94.158.208.0/20, 94.158.224.0/19, 94.159.128.0/17, 94.160.0.0/12, 94.176.0.0/14, 94.182.0.0/15, 94.184.0.0/14, 94.188.128.0/17, 94.189.0.0/16, 94.190.128.0/17, 94.191.0.0/16, 94.192.0.0/14, 94.196.0.0/15, 94.198.0.0/16, 94.199.0.0/18, 94.199.80.0/20, 94.199.96.0/19, 94.199.128.0/17, 94.200.0.0/13, 94.208.0.0/12, 94.224.0.0/14, 94.228.0.0/18, 94.228.64.0/19, 94.228.96.0/20, 94.228.128.0/19, 94.228.176.0/20, 94.228.208.0/20, 94.228.224.0/20, 94.229.0.0/20, 94.229.32.0/19, 94.229.64.0/18, 94.229.128.0/18, 94.229.192.0/19, 94.230.16.0/20, 94.230.48.0/20, 94.230.64.0/19, 94.230.96.0/20, 94.230.144.0/20, 94.230.176.0/20, 94.230.192.0/19, 94.230.224.0/20, 94.231.0.0/20, 94.231.32.0/19, 94.231.64.0/19, 94.231.96.0/20, 94.231.176.0/20, 94.231.192.0/18, 94.232.16.0/20, 94.232.32.0/20, 94.232.64.0/18, 94.232.144.0/20, 94.232.160.0/19, 94.232.192.0/18, 94.234.0.0/15, 94.236.0.0/16, 94.237.0.0/17, 94.238.0.0/15, 94.240.0.0/18, 94.240.128.0/17, 94.241.64.0/18, 94.241.128.0/18, 94.242.48.0/20, 94.242.64.0/18, 94.242.192.0/18, 94.243.64.0/18, 94.244.0.0/16, 94.245.0.0/17, 94.245.192.0/18, 94.246.0.0/16, 94.247.0.0/17, 94.247.128.0/18, 94.247.192.0/19, 94.247.224.0/20, 94.248.0.0/15, 94.250.0.0/16, 94.251.128.0/17, 94.252.0.0/16, 94.253.128.0/17, 94.254.0.0/16, 94.255.128.0/17, 95.0.0.0/12, 95.16.0.0/13, 95.33.0.0/16, 95.34.0.0/15, 95.36.0.0/16, 95.38.0.0/15, 95.40.0.0/13, 95.48.0.0/14, 95.56.0.0/13, 95.64.0.0/17, 95.65.0.0/16, 95.66.0.0/17, 95.67.0.0/17, 95.68.0.0/17, 95.69.0.0/16, 95.70.128.0/17, 95.74.0.0/15, 95.76.0.0/15, 95.80.0.0/18, 95.80.128.0/17, 95.81.0.0/17, 95.81.128.0/18, 95.82.0.0/17, 95.82.128.0/18, 95.83.192.0/18, 95.84.64.0/18, 95.85.0.0/16, 95.86.0.0/17, 95.86.128.0/18, 95.87.0.0/16, 95.88.0.0/13, 95.96.0.0/13, 95.104.0.0/17, 95.105.128.0/17, 95.107.128.0/17, 95.108.0.0/17, 95.109.0.0/16, 95.110.128.0/17, 95.111.0.0/16, 95.112.0.0/12, 95.128.0.0/16, 95.129.0.0/17, 95.129.128.0/19, 95.129.176.0/20, 95.129.192.0/18, 95.130.0.0/16, 95.131.16.0/20, 95.131.32.0/19, 95.131.64.0/18, 95.131.128.0/17, 95.132.0.0/14, 95.136.0.0/16, 95.137.128.0/17, 95.138.0.0/17, 95.138.128.0/18, 95.138.192.0/19, 95.140.0.0/20, 95.140.32.0/19, 95.140.64.0/18, 95.140.128.0/20, 95.140.160.0/19, 95.140.192.0/18, 95.141.16.0/20, 95.141.32.0/19, 95.141.64.0/18, 95.141.128.0/19, 95.141.160.0/20, 95.141.208.0/20, 95.141.240.0/20, 95.142.0.0/19, 95.142.48.0/20, 95.142.64.0/18, 95.142.128.0/18, 95.142.208.0/20, 95.142.224.0/19, 95.143.32.0/19, 95.143.64.0/19, 95.143.96.0/20, 95.143.128.0/19, 95.143.160.0/20, 95.143.192.0/20, 95.143.224.0/19, 95.144.0.0/13, 95.152.64.0/18, 95.152.128.0/17, 95.153.0.0/17, 95.154.0.0/18, 95.154.192.0/18, 95.155.0.0/16, 95.156.0.0/18, 95.156.128.0/17, 95.157.0.0/16, 95.158.0.0/17, 95.158.128.0/18, 95.159.0.0/17, 95.159.192.0/18, 95.160.0.0/16, 95.161.0.0/17, 95.161.128.0/20, 95.161.176.0/20, 95.161.192.0/20, 95.161.224.0/19, 95.162.0.0/16, 95.163.144.0/20, 95.163.160.0/20, 95.163.192.0/20, 95.164.0.0/16, 95.166.0.0/16, 95.168.0.0/16, 95.169.0.0/18, 95.169.64.0/19, 95.169.160.0/19, 95.169.192.0/18, 95.170.0.0/18, 95.170.64.0/19, 95.170.192.0/18, 95.171.32.0/19, 95.171.64.0/18, 95.171.128.0/18, 95.171.192.0/19, 95.172.0.0/19, 95.172.64.0/19, 95.172.160.0/19, 95.172.192.0/18, 95.173.0.0/17, 95.173.160.0/19, 95.173.192.0/18, 95.174.0.0/17, 95.174.128.0/18, 95.174.224.0/19, 95.175.0.0/17, 95.175.128.0/18, 95.175.192.0/19, 95.176.0.0/15, 95.178.0.0/16, 95.179.128.0/17, 95.180.0.0/16, 95.181.128.0/19, 95.181.176.0/20, 95.181.208.0/20, 95.181.224.0/19, 95.182.0.0/19, 95.182.64.0/18, 95.182.128.0/17, 95.183.0.0/20, 95.183.48.0/20, 95.183.128.0/17, 95.184.0.0/14, 95.192.0.0/12, 95.208.0.0/14, 95.212.0.0/16, 95.214.0.0/15, 95.216.0.0/14, 95.222.0.0/15, 95.224.0.0/11, 96.0.0.0/6, 100.0.0.0/10, 100.128.0.0/9, 101.0.0.0/8, 102.0.0.0/7, 104.0.0.0/6, 108.0.0.0/8, 109.0.0.0/11, 109.32.0.0/12, 109.48.0.0/13, 109.56.0.0/14, 109.60.0.0/17, 109.61.0.0/17, 109.62.0.0/17, 109.63.0.0/17, 109.64.0.0/14, 109.68.0.0/16, 109.69.0.0/20, 109.69.32.0/19, 109.69.64.0/18, 109.69.128.0/17, 109.70.0.0/16, 109.71.0.0/17, 109.71.128.0/18, 109.71.208.0/20, 109.71.224.0/19, 109.72.0.0/18, 109.72.80.0/20, 109.72.96.0/19, 109.72.144.0/20, 109.72.160.0/19, 109.72.192.0/19, 109.73.16.0/20, 109.73.48.0/20, 109.73.64.0/18, 109.73.128.0/18, 109.73.208.0/20, 109.73.224.0/19, 109.74.0.0/18, 109.74.64.0/19, 109.74.96.0/20, 109.74.144.0/20, 109.74.176.0/20, 109.74.192.0/20, 109.74.224.0/19, 109.75.0.0/17, 109.75.144.0/20, 109.75.160.0/19, 109.75.208.0/20, 109.75.224.0/20, 109.76.0.0/14, 109.80.0.0/13, 109.88.0.0/14, 109.92.0.0/15, 109.94.32.0/19, 109.94.96.0/19, 109.94.128.0/19, 109.94.160.0/20, 109.94.208.0/20, 109.94.224.0/19, 109.95.0.0/18, 109.95.64.0/19, 109.95.112.0/20, 109.95.128.0/18, 109.95.192.0/20, 109.95.224.0/19, 109.96.0.0/13, 109.104.0.0/16, 109.105.0.0/18, 109.105.96.0/19, 109.105.128.0/19, 109.105.192.0/18, 109.106.0.0/17, 109.106.160.0/19, 109.106.224.0/19, 109.107.0.0/16, 109.108.0.0/19, 109.108.64.0/18, 109.108.128.0/17, 109.109.0.0/17, 109.109.128.0/18, 109.109.192.0/20, 109.109.224.0/19, 109.110.0.0/19, 109.110.96.0/19, 109.110.128.0/17, 109.111.32.0/19, 109.111.96.0/19, 109.111.192.0/18, 109.112.0.0/13, 109.120.64.0/18, 109.120.128.0/19, 109.120.176.0/20, 109.120.192.0/18, 109.121.0.0/16, 109.122.0.0/16, 109.123.0.0/17, 109.123.192.0/18, 109.124.128.0/18, 109.125.0.0/16, 109.126.64.0/18, 109.126.128.0/18, 109.127.0.0/17, 109.127.192.0/18, 109.128.0.0/11, 109.160.0.0/16, 109.161.128.0/17, 109.162.0.0/15, 109.164.0.0/16, 109.165.128.0/17, 109.166.0.0/16, 109.167.0.0/17, 109.168.0.0/17, 109.169.0.0/17, 109.170.128.0/17, 109.171.128.0/17, 109.172.0.0/20, 109.172.80.0/20, 109.172.128.0/17, 109.173.128.0/17, 109.174.128.0/17, 109.175.0.0/16, 109.176.0.0/13, 109.185.0.0/16, 109.186.0.0/16, 109.189.0.0/16, 109.190.0.0/16, 109.192.0.0/15, 109.196.0.0/20, 109.196.32.0/19, 109.196.80.0/20, 109.196.96.0/19, 109.196.128.0/18, 109.196.224.0/19, 109.197.0.0/20, 109.197.32.0/19, 109.197.64.0/19, 109.197.96.0/20, 109.197.160.0/19, 109.197.208.0/20, 109.197.224.0/19, 109.198.0.0/18, 109.198.128.0/19, 109.199.0.0/16, 109.200.0.0/18, 109.200.64.0/19, 109.200.160.0/19, 109.200.192.0/18, 109.201.0.0/18, 109.201.128.0/18, 109.201.224.0/19, 109.202.64.0/18, 109.202.128.0/19, 109.202.192.0/18, 109.203.0.0/17, 109.203.128.0/18, 109.203.224.0/19, 109.204.0.0/15, 109.206.0.0/17, 109.206.160.0/19, 109.206.192.0/18, 109.207.16.0/20, 109.207.32.0/19, 109.207.64.0/20, 109.207.96.0/19, 109.207.128.0/19, 109.207.160.0/20, 109.207.192.0/19, 109.207.240.0/20, 109.208.0.0/12, 109.224.0.0/16, 109.225.64.0/18, 109.225.128.0/18, 109.226.0.0/18, 109.226.128.0/18, 109.227.0.0/17, 109.227.128.0/18, 109.228.0.0/16, 109.229.0.0/19, 109.229.160.0/19, 109.229.192.0/19, 109.230.0.0/17, 109.230.192.0/18, 109.231.0.0/16, 109.232.0.0/16, 109.233.0.0/17, 109.233.128.0/18, 109.233.192.0/19, 109.233.240.0/20, 109.234.0.0/16, 109.235.0.0/17, 109.235.128.0/18, 109.235.192.0/20, 109.235.224.0/19, 109.236.0.0/18, 109.236.80.0/20, 109.236.112.0/20, 109.236.128.0/18, 109.236.192.0/20, 109.237.16.0/20, 109.237.32.0/19, 109.237.64.0/19, 109.237.112.0/20, 109.237.128.0/20, 109.237.160.0/19, 109.237.192.0/19, 109.237.240.0/20, 109.238.0.0/18, 109.238.64.0/19, 109.238.112.0/20, 109.238.128.0/18, 109.238.208.0/20, 109.238.224.0/20, 109.239.0.0/17, 109.239.128.0/18, 109.239.192.0/20, 109.239.224.0/19, 109.240.0.0/13, 109.248.0.0/14, 109.253.0.0/16, 109.255.0.0/16, 110.0.0.0/8, 111.0.0.0/10, 111.64.0.0/12, 111.80.0.0/13, 111.88.0.0/18, 111.88.64.0/19, 111.88.128.0/20, 111.88.160.0/19, 111.88.224.0/20, 111.89.0.0/16, 111.90.0.0/15, 111.92.0.0/14, 111.96.0.0/11, 111.128.0.0/9, 112.0.0.0/5, 120.0.0.0/6, 124.0.0.0/7, 126.0.0.0/8, 128.0.0.0/18, 128.0.64.0/20, 128.0.96.0/19, 128.0.144.0/20, 128.0.160.0/19, 128.0.192.0/18, 128.1.0.0/16, 128.2.0.0/15, 128.4.0.0/14, 128.8.0.0/13, 128.16.0.0/12, 128.32.0.0/11, 128.64.0.0/14, 128.76.0.0/14, 128.80.0.0/12, 128.96.0.0/11, 128.128.0.0/13, 128.136.0.0/14, 128.140.0.0/17, 128.140.128.0/19, 128.140.176.0/20, 128.140.192.0/18, 128.141.0.0/16, 128.142.0.0/15, 128.144.0.0/12, 128.160.0.0/11, 128.192.0.0/13, 128.200.0.0/14, 128.204.128.0/19, 128.204.192.0/18, 128.205.0.0/16, 128.206.0.0/15, 128.208.0.0/12, 128.224.0.0/11, 129.0.0.0/10, 129.64.0.0/11, 129.96.0.0/14, 129.100.0.0/16, 129.101.0.0/18, 129.101.96.0/19, 129.101.128.0/17, 129.102.0.0/15, 129.104.0.0/13, 129.112.0.0/12, 129.128.0.0/9, 130.0.0.0/11, 130.32.0.0/12, 130.48.0.0/16, 130.49.0.0/17, 130.49.128.0/19, 130.49.160.0/20, 130.50.0.0/15, 130.52.0.0/14, 130.56.0.0/13, 130.64.0.0/10, 130.128.0.0/10, 130.192.0.0/16, 130.193.0.0/19, 130.193.64.0/18, 130.193.128.0/17, 130.194.0.0/15, 130.196.0.0/14, 130.200.0.0/13, 130.208.0.0/12, 130.224.0.0/12, 130.240.0.0/13, 130.248.0.0/14, 130.252.0.0/15, 130.254.0.0/16, 130.255.0.0/19, 130.255.64.0/18, 130.255.144.0/20, 130.255.160.0/19, 130.255.192.0/18, 131.0.0.0/8, 132.0.0.0/7, 134.0.0.0/18, 134.0.64.0/19, 134.0.112.0/20, 134.0.128.0/17, 134.1.0.0/16, 134.2.0.0/15, 134.4.0.0/14, 134.8.0.0/13, 134.16.0.0/15, 134.18.0.0/16, 134.19.0.0/17, 134.19.128.0/20, 134.19.160.0/19, 134.19.192.0/18, 134.20.0.0/14, 134.24.0.0/13, 134.32.0.0/11, 134.64.0.0/12, 134.80.0.0/13, 134.88.0.0/15, 134.90.0.0/17, 134.90.128.0/19, 134.90.192.0/18, 134.91.0.0/16, 134.92.0.0/14, 134.96.0.0/11, 134.128.0.0/10, 134.192.0.0/11, 134.224.0.0/12, 134.240.0.0/13, 134.248.0.0/14, 134.252.0.0/15, 134.254.0.0/16, 134.255.0.0/17, 134.255.160.0/19, 134.255.192.0/18, 135.0.0.0/10, 135.64.0.0/11, 135.96.0.0/13, 135.104.0.0/15, 135.106.0.0/17, 135.106.128.0/20, 135.107.0.0/16, 135.108.0.0/14, 135.112.0.0/12, 135.128.0.0/9, 136.0.0.0/9, 136.128.0.0/11, 136.160.0.0/13, 136.168.0.0/16, 136.169.0.0/17, 136.170.0.0/15, 136.172.0.0/14, 136.176.0.0/12, 136.192.0.0/11, 136.224.0.0/13, 136.232.0.0/15, 136.234.112.0/20, 136.234.128.0/17, 136.235.0.0/16, 136.236.0.0/14, 136.240.0.0/12, 137.0.0.0/12, 137.16.0.0/14, 137.20.0.0/15, 137.22.0.0/17, 137.22.128.0/20, 137.22.160.0/19, 137.22.192.0/18, 137.23.0.0/16, 137.24.0.0/13, 137.32.0.0/11, 137.64.0.0/10, 137.128.0.0/9, 138.0.0.0/12, 138.16.0.0/17, 138.16.128.0/20, 138.17.0.0/16, 138.18.0.0/15, 138.20.0.0/14, 138.24.0.0/13, 138.32.0.0/11, 138.64.0.0/10, 138.128.0.0/9, 139.0.0.0/11, 139.32.0.0/13, 139.40.0.0/14, 139.44.0.0/16, 139.45.0.0/17, 139.45.128.0/18, 139.45.192.0/19, 139.46.0.0/15, 139.48.0.0/12, 139.64.0.0/11, 139.96.0.0/14, 139.100.0.0/17, 139.100.128.0/19, 139.100.176.0/20, 139.101.0.0/16, 139.102.0.0/15, 139.104.0.0/13, 139.112.0.0/12, 139.128.0.0/9, 140.0.0.0/8, 141.0.0.0/13, 141.8.0.0/17, 141.8.192.0/18, 141.9.0.0/16, 141.10.0.0/15, 141.12.0.0/14, 141.16.0.0/12, 141.32.0.0/11, 141.64.0.0/11, 141.96.0.0/13, 141.104.0.0/16, 141.105.0.0/19, 141.105.48.0/20, 141.105.64.0/18, 141.105.128.0/17, 141.106.0.0/15, 141.108.0.0/14, 141.112.0.0/12, 141.128.0.0/14, 141.132.0.0/16, 141.133.0.0/17, 141.133.128.0/19, 141.133.160.0/20, 141.134.0.0/15, 141.136.0.0/13, 141.144.0.0/12, 141.160.0.0/11, 141.192.0.0/10, 142.0.0.0/7, 144.0.0.0/9, 144.128.0.0/10, 144.192.0.0/13, 144.200.0.0/14, 144.204.0.0/15, 144.207.0.0/16, 144.208.0.0/12, 144.224.0.0/11, 145.0.0.0/9, 145.128.0.0/10, 145.192.0.0/11, 145.224.0.0/12, 145.240.0.0/13, 145.248.0.0/16, 145.250.0.0/15, 145.252.0.0/15, 145.254.0.0/16, 145.255.48.0/20, 145.255.64.0/18, 145.255.128.0/17, 146.0.0.0/10, 146.64.0.0/15, 146.66.0.0/17, 146.66.128.0/19, 146.66.208.0/20, 146.66.224.0/19, 146.67.0.0/16, 146.68.0.0/14, 146.72.0.0/13, 146.80.0.0/12, 146.96.0.0/11, 146.128.0.0/11, 146.160.0.0/12, 146.176.0.0/13, 146.184.0.0/16, 146.185.0.0/17, 146.185.128.0/18, 146.185.208.0/20, 146.185.224.0/19, 146.186.0.0/15, 146.188.0.0/14, 146.192.0.0/11, 146.224.0.0/12, 146.240.0.0/13, 146.248.0.0/14, 146.252.0.0/15, 146.254.0.0/16, 146.255.0.0/17, 146.255.128.0/18, 146.255.224.0/19, 147.0.0.0/11, 147.32.0.0/13, 147.40.0.0/14, 147.44.0.0/16, 147.46.0.0/15, 147.48.0.0/12, 147.64.0.0/10, 147.128.0.0/9, 148.0.0.0/8, 149.0.0.0/11, 149.32.0.0/12, 149.48.0.0/13, 149.56.0.0/14, 149.60.0.0/15, 149.62.32.0/19, 149.62.64.0/18, 149.62.128.0/17, 149.63.0.0/16, 149.64.0.0/10, 149.128.0.0/12, 149.144.0.0/13, 149.152.0.0/15, 149.154.0.0/17, 149.154.144.0/20, 149.154.160.0/19, 149.154.192.0/18, 149.155.0.0/16, 149.156.0.0/14, 149.160.0.0/11, 149.192.0.0/11, 149.224.0.0/12, 149.240.0.0/13, 149.248.0.0/14, 149.252.0.0/15, 149.254.0.0/16, 149.255.32.0/19, 149.255.64.0/18, 149.255.128.0/17, 150.0.0.0/8, 151.0.64.0/18, 151.0.128.0/17, 151.1.0.0/16, 151.2.0.0/15, 151.4.0.0/14, 151.8.0.0/13, 151.16.0.0/12, 151.32.0.0/11, 151.64.0.0/10, 151.128.0.0/10, 151.192.0.0/11, 151.224.0.0/13, 151.232.0.0/14, 151.236.0.0/18, 151.236.128.0/17, 151.237.0.0/16, 151.238.0.0/15, 151.240.0.0/13, 151.248.0.0/18, 151.248.64.0/19, 151.248.96.0/20, 151.248.128.0/17, 151.249.0.0/16, 151.250.0.0/15, 151.252.0.0/18, 151.252.96.0/19, 151.252.128.0/17, 151.253.0.0/16, 151.254.0.0/15, 152.0.0.0/8, 153.0.0.0/11, 153.32.0.0/12, 153.48.0.0/15, 153.50.0.0/16, 153.51.0.0/19, 153.51.128.0/19, 153.51.192.0/18, 153.52.0.0/14, 153.56.0.0/13, 153.64.0.0/13, 153.72.0.0/14, 153.76.0.0/17, 153.76.128.0/19, 153.76.176.0/20, 153.76.192.0/18, 153.77.0.0/16, 153.78.0.0/16, 153.79.0.0/17, 153.79.128.0/19, 153.79.160.0/20, 153.79.192.0/18, 153.80.0.0/17, 153.80.128.0/19, 153.80.160.0/20, 153.80.192.0/19, 153.81.0.0/16, 153.82.0.0/15, 153.84.0.0/14, 153.88.0.0/13, 153.96.0.0/11, 153.128.0.0/9, 154.0.0.0/8, 155.0.0.0/9, 155.128.0.0/10, 155.192.0.0/12, 155.208.0.0/14, 155.212.0.0/17, 155.212.144.0/20, 155.212.208.0/20, 155.212.224.0/19, 155.213.0.0/16, 155.214.0.0/15, 155.216.0.0/13, 155.224.0.0/11, 156.0.0.0/8, 157.0.0.0/12, 157.16.0.0/14, 157.20.0.0/15, 157.22.0.0/17, 157.22.128.0/19, 157.22.208.0/20, 157.22.224.0/19, 157.23.0.0/16, 157.24.0.0/13, 157.32.0.0/11, 157.64.0.0/10, 157.128.0.0/11, 157.160.0.0/12, 157.176.0.0/13, 157.184.0.0/15, 157.187.0.0/16, 157.188.0.0/14, 157.192.0.0/10, 158.0.0.0/11, 158.32.0.0/13, 158.40.0.0/14, 158.44.0.0/15, 158.47.0.0/16, 158.48.0.0/12, 158.64.0.0/10, 158.128.0.0/11, 158.161.0.0/16, 158.162.0.0/15, 158.164.0.0/14, 158.168.0.0/13, 158.176.0.0/12, 158.192.0.0/11, 158.224.0.0/12, 158.240.0.0/13, 158.248.0.0/15, 158.251.0.0/16, 158.252.0.0/15, 158.254.0.0/16, 158.255.0.0/17, 158.255.192.0/18, 159.0.0.0/10, 159.64.0.0/12, 159.80.0.0/13, 159.88.0.0/14, 159.92.0.0/16, 159.94.0.0/15, 159.96.0.0/11, 159.128.0.0/10, 159.192.0.0/15, 159.194.0.0/17, 159.194.128.0/18, 159.194.192.0/20, 159.195.0.0/16, 159.196.0.0/14, 159.200.0.0/17, 159.200.192.0/18, 159.201.0.0/16, 159.202.0.0/15, 159.204.0.0/14, 159.208.0.0/12, 159.224.0.0/12, 159.240.0.0/13, 159.248.0.0/14, 159.252.0.0/15, 159.254.0.0/16, 159.255.32.0/19, 159.255.128.0/17, 160.0.0.0/14, 160.4.0.0/16, 160.5.0.0/17, 160.5.128.0/18, 160.5.192.0/20, 160.5.224.0/19, 160.6.0.0/15, 160.8.0.0/13, 160.16.0.0/12, 160.32.0.0/11, 160.64.0.0/10, 160.128.0.0/9, 161.0.0.0/10, 161.64.0.0/11, 161.96.0.0/13, 161.104.0.0/20, 161.104.64.0/19, 161.104.112.0/20, 161.104.128.0/17, 161.105.0.0/16, 161.106.0.0/15, 161.108.0.0/14, 161.112.0.0/12, 161.128.0.0/9, 162.0.0.0/7, 164.0.0.0/9, 164.128.0.0/10, 164.192.0.0/12, 164.208.0.0/14, 164.212.0.0/15, 164.214.0.0/16, 164.215.0.0/18, 164.215.96.0/19, 164.215.128.0/17, 164.216.0.0/13, 164.224.0.0/11, 165.0.0.0/8, 166.0.0.0/7, 168.0.0.0/10, 168.64.0.0/11, 168.96.0.0/12, 168.112.0.0/16, 168.113.0.0/17, 168.113.208.0/20, 168.113.224.0/20, 168.114.0.0/15, 168.116.0.0/14, 168.120.0.0/13, 168.128.0.0/10, 168.192.0.0/12, 168.208.0.0/13, 168.216.0.0/14, 168.220.0.0/15, 168.222.0.0/17, 168.222.144.0/20, 168.222.160.0/19, 168.222.208.0/20, 168.222.224.0/19, 168.223.0.0/16, 168.224.0.0/11, 169.0.0.0/9, 169.128.0.0/10, 169.192.0.0/11, 169.224.0.0/12, 169.240.0.0/13, 169.248.0.0/14, 169.252.0.0/15, 169.255.0.0/16, 170.0.0.0/7, 172.0.0.0/12, 172.32.0.0/11, 172.64.0.0/10, 172.128.0.0/9, 173.0.0.0/8, 174.0.0.0/7, 176.0.0.0/13, 176.8.0.0/14, 176.12.0.0/18, 176.12.80.0/20, 176.12.96.0/19, 176.12.128.0/17, 176.13.0.0/16, 176.16.0.0/12, 176.32.0.0/17, 176.32.160.0/20, 176.32.192.0/18, 176.33.0.0/16, 176.34.0.0/15, 176.36.0.0/14, 176.40.0.0/13, 176.52.128.0/17, 176.53.0.0/17, 176.53.128.0/18, 176.53.208.0/20, 176.54.0.0/15, 176.56.32.0/19, 176.56.64.0/18, 176.56.128.0/17, 176.57.0.0/17, 176.57.128.0/18, 176.57.192.0/20, 176.57.224.0/19, 176.58.0.0/19, 176.58.64.0/18, 176.58.128.0/17, 176.60.0.0/15, 176.62.0.0/18, 176.62.128.0/19, 176.62.160.0/20, 176.62.192.0/19, 176.62.224.0/20, 176.63.0.0/16, 176.64.0.0/16, 176.65.0.0/19, 176.65.64.0/19, 176.65.128.0/17, 176.66.0.0/15, 176.68.0.0/14, 176.72.0.0/14, 176.76.0.0/16, 176.77.128.0/17, 176.78.0.0/15, 176.80.0.0/12, 176.96.32.0/19, 176.96.64.0/18, 176.96.128.0/17, 176.97.0.0/19, 176.97.48.0/20, 176.97.64.0/19, 176.97.112.0/20, 176.97.128.0/19, 176.97.192.0/18, 176.98.0.0/17, 176.98.160.0/19, 176.98.192.0/18, 176.99.16.0/20, 176.99.32.0/19, 176.100.0.0/18, 176.100.160.0/19, 176.100.192.0/19, 176.100.224.0/20, 176.101.16.0/20, 176.101.32.0/19, 176.101.64.0/19, 176.101.128.0/18, 176.101.192.0/19, 176.102.32.0/19, 176.102.64.0/18, 176.102.128.0/17, 176.103.0.0/18, 176.103.64.0/19, 176.103.112.0/20, 176.103.128.0/17, 176.104.0.0/17, 176.104.176.0/20, 176.104.224.0/19, 176.105.0.0/16, 176.106.0.0/20, 176.106.32.0/19, 176.106.96.0/19, 176.106.128.0/20, 176.106.160.0/19, 176.106.192.0/19, 176.106.224.0/20, 176.107.16.0/20, 176.107.32.0/19, 176.107.64.0/18, 176.107.128.0/18, 176.107.192.0/20, 176.108.0.0/17, 176.108.128.0/20, 176.108.224.0/20, 176.109.48.0/20, 176.109.128.0/20, 176.109.160.0/20, 176.109.192.0/19, 176.110.0.0/18, 176.110.96.0/19, 176.110.144.0/20, 176.110.208.0/20, 176.111.0.0/18, 176.111.80.0/20, 176.111.96.0/19, 176.111.128.0/17, 176.112.0.0/19, 176.112.64.0/19, 176.112.112.0/20, 176.112.144.0/20, 176.112.176.0/20, 176.113.32.0/20, 176.113.64.0/18, 176.113.128.0/18, 176.113.192.0/19, 176.114.0.0/20, 176.114.48.0/20, 176.114.96.0/20, 176.114.224.0/19, 176.115.0.0/19, 176.115.80.0/20, 176.115.96.0/20, 176.115.160.0/19, 176.115.224.0/19, 176.116.0.0/19, 176.116.32.0/20, 176.116.64.0/18, 176.116.128.0/19, 176.116.192.0/20, 176.116.224.0/20, 176.117.48.0/20, 176.117.64.0/19, 176.117.96.0/20, 176.117.160.0/19, 176.118.0.0/20, 176.118.32.0/19, 176.118.80.0/20, 176.118.96.0/20, 176.118.128.0/18, 176.118.192.0/20, 176.119.0.0/17, 176.119.128.0/19, 176.119.192.0/18, 176.120.16.0/20, 176.120.32.0/19, 176.120.64.0/20, 176.120.96.0/19, 176.120.128.0/19, 176.120.160.0/20, 176.121.0.0/17, 176.121.224.0/19, 176.122.0.0/20, 176.122.32.0/20, 176.122.80.0/20, 176.122.96.0/19, 176.122.128.0/17, 176.123.0.0/17, 176.123.176.0/20, 176.123.208.0/20, 176.123.224.0/19, 176.124.32.0/19, 176.124.64.0/18, 176.124.128.0/18, 176.124.224.0/19, 176.125.0.0/18, 176.125.224.0/19, 176.126.32.0/19, 176.126.64.0/18, 176.126.128.0/17, 176.127.0.0/16, 176.128.0.0/10, 176.198.0.0/15, 176.200.0.0/13, 176.216.0.0/14, 176.220.0.0/16, 176.221.32.0/19, 176.221.64.0/18, 176.221.128.0/17, 176.222.0.0/17, 176.222.128.0/18, 176.222.208.0/20, 176.222.224.0/20, 176.223.0.0/16, 176.224.0.0/15, 176.226.0.0/17, 176.227.0.0/17, 176.227.128.0/19, 176.227.160.0/20, 176.227.192.0/18, 176.228.0.0/14, 176.232.0.0/13, 176.240.0.0/16, 176.241.0.0/17, 176.241.128.0/18, 176.241.224.0/19, 176.242.0.0/15, 176.244.0.0/14, 176.248.0.0/13, 177.0.0.0/8, 178.0.0.0/12, 178.16.0.0/18, 178.16.64.0/20, 178.16.96.0/19, 178.16.128.0/17, 178.17.0.0/17, 178.17.144.0/20, 178.17.160.0/20, 178.17.208.0/20, 178.17.224.0/19, 178.18.16.0/20, 178.18.32.0/19, 178.18.64.0/19, 178.18.112.0/20, 178.18.128.0/18, 178.18.192.0/20, 178.18.224.0/19, 178.19.0.0/17, 178.19.128.0/18, 178.19.192.0/19, 178.19.224.0/20, 178.20.0.0/14, 178.24.0.0/13, 178.32.0.0/15, 178.36.0.0/14, 178.40.0.0/14, 178.48.0.0/16, 178.50.0.0/15, 178.52.0.0/14, 178.56.0.0/16, 178.57.64.0/20, 178.57.128.0/18, 178.58.0.0/15, 178.60.0.0/14, 178.72.0.0/18, 178.72.192.0/18, 178.73.0.0/16, 178.74.0.0/18, 178.74.192.0/18, 178.75.128.0/17, 178.76.0.0/17, 178.76.128.0/18, 178.77.0.0/16, 178.78.64.0/18, 178.78.128.0/17, 178.79.0.0/16, 178.80.0.0/12, 178.96.0.0/11, 178.128.0.0/16, 178.130.32.0/20, 178.130.64.0/18, 178.130.144.0/20, 178.130.160.0/19, 178.130.192.0/18, 178.131.0.0/16, 178.132.0.0/14, 178.136.0.0/14, 178.142.0.0/15, 178.144.0.0/13, 178.152.0.0/15, 178.154.0.0/17, 178.155.128.0/17, 178.156.0.0/16, 178.157.0.0/17, 178.157.192.0/18, 178.158.128.0/17, 178.159.0.0/20, 178.159.32.0/20, 178.159.64.0/20, 178.159.96.0/20, 178.159.128.0/17, 178.160.0.0/16, 178.161.0.0/17, 178.162.128.0/17, 178.163.128.0/17, 178.164.0.0/15, 178.166.0.0/17, 178.167.128.0/17, 178.168.0.0/16, 178.169.0.0/19, 178.169.128.0/17, 178.170.0.0/16, 178.171.128.0/17, 178.172.0.0/16, 178.173.128.0/17, 178.174.0.0/15, 178.180.0.0/14, 178.188.0.0/14, 178.192.0.0/13, 178.200.0.0/14, 178.208.0.0/18, 178.208.96.0/19, 178.208.160.0/19, 178.208.192.0/19, 178.209.0.0/18, 178.209.64.0/19, 178.209.128.0/18, 178.210.96.0/19, 178.210.128.0/17, 178.211.32.0/19, 178.211.64.0/18, 178.211.128.0/19, 178.211.192.0/18, 178.212.0.0/16, 178.213.0.0/20, 178.213.32.0/19, 178.213.64.0/18, 178.213.128.0/18, 178.213.208.0/20, 178.213.224.0/20, 178.214.0.0/19, 178.214.64.0/18, 178.214.128.0/19, 178.214.192.0/19, 178.215.0.0/18, 178.215.128.0/20, 178.215.160.0/19, 178.215.192.0/18, 178.216.0.0/18, 178.216.80.0/20, 178.216.96.0/19, 178.216.128.0/17, 178.217.0.0/19, 178.217.32.0/20, 178.217.80.0/20, 178.217.96.0/19, 178.217.128.0/17, 178.218.64.0/20, 178.218.128.0/18, 178.218.192.0/20, 178.218.224.0/19, 178.219.0.0/17, 178.219.128.0/19, 178.219.224.0/20, 178.220.0.0/14, 178.224.0.0/13, 178.232.0.0/15, 178.235.0.0/16, 178.236.0.0/20, 178.236.32.0/19, 178.236.64.0/18, 178.236.144.0/20, 178.236.160.0/19, 178.236.192.0/20, 178.236.224.0/20, 178.237.0.0/20, 178.237.32.0/19, 178.237.64.0/18, 178.237.128.0/19, 178.237.160.0/20, 178.237.208.0/20, 178.237.224.0/20, 178.238.0.0/20, 178.238.32.0/19, 178.238.64.0/19, 178.238.96.0/20, 178.238.128.0/17, 178.239.0.0/16, 178.240.0.0/13, 178.248.0.0/16, 178.249.0.0/18, 178.249.80.0/20, 178.249.96.0/19, 178.249.128.0/17, 178.250.0.0/15, 178.252.0.0/18, 178.252.128.0/18, 178.253.0.0/16, 178.254.0.0/15, 179.0.0.0/8, 180.0.0.0/6, 184.0.0.0/8, 185.0.0.0/10, 185.64.0.0/12, 185.80.0.0/14, 185.84.0.0/15, 185.86.0.0/18, 185.86.64.0/19, 185.86.96.0/20, 185.86.128.0/17, 185.87.0.0/16, 185.88.0.0/13, 185.96.0.0/11, 185.128.0.0/9, 186.0.0.0/7, 188.0.32.0/19, 188.0.64.0/18, 188.0.128.0/19, 188.0.224.0/19, 188.1.0.0/16, 188.2.0.0/15, 188.4.0.0/14, 188.8.0.0/13, 188.20.0.0/14, 188.24.0.0/13, 188.33.0.0/16, 188.34.0.0/16, 188.36.0.0/14, 188.40.0.0/15, 188.42.0.0/16, 188.44.0.0/19, 188.44.64.0/19, 188.44.128.0/17, 188.45.0.0/16, 188.46.0.0/15, 188.48.0.0/12, 188.64.0.0/14, 188.68.0.0/20, 188.68.32.0/19, 188.68.64.0/20, 188.68.160.0/20, 188.68.224.0/19, 188.69.0.0/16, 188.70.0.0/15, 188.72.0.0/16, 188.73.0.0/17, 188.73.192.0/18, 188.74.0.0/16, 188.75.64.0/18, 188.75.128.0/18, 188.76.0.0/14, 188.80.0.0/13, 188.88.0.0/14, 188.92.0.0/16, 188.93.0.0/20, 188.93.32.0/20, 188.93.64.0/18, 188.93.128.0/17, 188.94.0.0/17, 188.94.128.0/19, 188.94.176.0/20, 188.94.192.0/18, 188.95.0.0/16, 188.96.0.0/12, 188.112.0.0/17, 188.112.128.0/18, 188.113.64.0/18, 188.113.192.0/18, 188.114.64.0/18, 188.114.128.0/18, 188.114.224.0/19, 188.115.0.0/16, 188.116.0.0/17, 188.116.192.0/18, 188.117.0.0/16, 188.118.0.0/15, 188.120.0.0/19, 188.120.32.0/20, 188.120.64.0/18, 188.120.128.0/18, 188.120.192.0/19, 188.121.0.0/16, 188.122.0.0/17, 188.122.128.0/19, 188.122.192.0/19, 188.123.0.0/19, 188.123.64.0/18, 188.123.128.0/18, 188.123.192.0/19, 188.124.0.0/19, 188.124.48.0/20, 188.124.64.0/19, 188.124.128.0/17, 188.125.0.0/16, 188.126.0.0/19, 188.126.64.0/18, 188.126.128.0/17, 188.127.0.0/17, 188.127.128.0/18, 188.127.192.0/19, 188.127.240.0/20, 188.128.128.0/17, 188.129.0.0/16, 188.130.0.0/15, 188.132.0.0/16, 188.133.0.0/17, 188.134.128.0/17, 188.135.0.0/16, 188.136.0.0/14, 188.140.0.0/15, 188.142.0.0/16, 188.143.0.0/17, 188.144.0.0/12, 188.160.0.0/15, 188.163.0.0/16, 188.164.0.0/17, 188.164.128.0/19, 188.164.192.0/18, 188.165.0.0/16, 188.166.0.0/15, 188.169.0.0/16, 188.171.0.0/16, 188.172.0.0/14, 188.176.0.0/13, 188.184.0.0/15, 188.188.0.0/15, 188.190.0.0/17, 188.190.160.0/19, 188.190.224.0/19, 188.191.32.0/19, 188.191.64.0/20, 188.191.96.0/20, 188.191.128.0/19, 188.191.176.0/20, 188.191.192.0/18, 188.192.0.0/11, 188.224.0.0/16, 188.225.128.0/17, 188.226.128.0/17, 188.227.48.0/20, 188.227.80.0/20, 188.227.96.0/20, 188.227.128.0/17, 188.228.0.0/14, 188.236.0.0/14, 188.240.0.0/15, 188.244.16.0/20, 188.244.48.0/20, 188.244.64.0/18, 188.245.0.0/16, 188.246.0.0/17, 188.246.128.0/19, 188.246.192.0/18, 188.247.0.0/19, 188.247.64.0/18, 188.247.128.0/17, 188.248.0.0/14, 188.252.0.0/15, 188.254.128.0/17, 188.255.128.0/17, 189.0.0.0/8, 190.0.0.0/7, 192.0.0.0/9, 192.128.0.0/11, 192.160.0.0/13, 192.169.0.0/16, 192.170.0.0/15, 192.172.0.0/14, 192.176.0.0/12, 192.192.0.0/10, 193.0.0.0/9, 193.128.0.0/12, 193.144.0.0/14, 193.148.0.0/15, 193.150.0.0/16, 193.151.0.0/17, 193.151.128.0/18, 193.151.224.0/19, 193.152.0.0/13, 193.160.0.0/11, 193.192.0.0/17, 193.192.160.0/19, 193.192.192.0/18, 193.193.0.0/16, 193.194.0.0/15, 193.196.0.0/14, 193.200.0.0/13, 193.208.0.0/12, 193.224.0.0/13, 193.234.0.0/15, 193.236.0.0/14, 193.240.0.0/12, 194.0.0.0/13, 194.8.0.0/17, 194.8.128.0/19, 194.8.192.0/18, 194.9.0.0/17, 194.9.128.0/18, 194.9.192.0/19, 194.9.240.0/20, 194.10.0.0/15, 194.12.0.0/19, 194.12.32.0/20, 194.12.64.0/19, 194.12.128.0/17, 194.13.0.0/16, 194.14.0.0/15, 194.16.0.0/12, 194.32.0.0/12, 194.48.0.0/13, 194.56.0.0/15, 194.58.0.0/18, 194.58.64.0/19, 194.58.128.0/19, 194.58.192.0/18, 194.59.0.0/16, 194.60.0.0/14, 194.64.0.0/15, 194.66.0.0/16, 194.67.80.0/20, 194.67.192.0/19, 194.68.0.0/14, 194.72.0.0/13, 194.80.0.0/14, 194.85.240.0/20, 194.86.0.0/15, 194.88.0.0/13, 194.96.0.0/13, 194.104.0.0/16, 194.105.0.0/17, 194.105.128.0/18, 194.105.224.0/19, 194.106.0.0/15, 194.108.0.0/14, 194.112.0.0/12, 194.128.0.0/14, 194.132.0.0/15, 194.134.0.0/16, 194.135.0.0/17, 194.135.128.0/18, 194.135.192.0/20, 194.135.240.0/20, 194.136.0.0/13, 194.144.0.0/14, 194.148.0.0/15, 194.150.0.0/17, 194.150.160.0/19, 194.150.192.0/18, 194.151.0.0/16, 194.152.0.0/15, 194.154.0.0/18, 194.154.96.0/19, 194.154.128.0/17, 194.155.0.0/16, 194.156.0.0/14, 194.160.0.0/12, 194.176.0.0/13, 194.184.0.0/15, 194.187.0.0/16, 194.188.0.0/15, 194.191.0.0/16, 194.192.0.0/11, 194.224.0.0/15, 194.227.0.0/16, 194.228.0.0/14, 194.232.0.0/13, 194.240.0.0/14, 194.244.0.0/15, 194.246.0.0/16, 194.247.0.0/17, 194.247.160.0/19, 194.247.192.0/18, 194.248.0.0/13, 195.0.0.0/15, 195.2.0.0/18, 195.2.80.0/20, 195.2.96.0/19, 195.2.128.0/17, 195.3.0.0/16, 195.4.0.0/16, 195.5.0.0/17, 195.5.160.0/19, 195.5.192.0/18, 195.6.0.0/16, 195.7.0.0/17, 195.7.128.0/19, 195.7.192.0/18, 195.8.0.0/16, 195.10.0.0/15, 195.12.0.0/18, 195.12.96.0/19, 195.12.128.0/17, 195.13.0.0/16, 195.14.0.0/19, 195.14.64.0/18, 195.14.128.0/17, 195.15.0.0/16, 195.16.0.0/19, 195.16.64.0/19, 195.16.128.0/17, 195.17.0.0/16, 195.18.0.0/19, 195.18.64.0/18, 195.18.128.0/17, 195.20.0.0/14, 195.24.0.0/15, 195.26.0.0/17, 195.26.128.0/19, 195.26.192.0/18, 195.27.0.0/16, 195.28.0.0/19, 195.28.64.0/18, 195.28.128.0/17, 195.29.0.0/16, 195.30.0.0/15, 195.32.0.0/15, 195.34.64.0/18, 195.34.128.0/18, 195.34.192.0/19, 195.35.0.0/16, 195.36.0.0/15, 195.38.0.0/19, 195.38.64.0/18, 195.38.128.0/17, 195.39.0.0/16, 195.40.0.0/15, 195.42.0.0/18, 195.42.96.0/19, 195.42.128.0/19, 195.42.192.0/18, 195.43.0.0/16, 195.44.0.0/15, 195.46.0.0/18, 195.46.64.0/19, 195.46.128.0/19, 195.46.192.0/18, 195.47.0.0/16, 195.48.0.0/16, 195.49.0.0/17, 195.49.128.0/18, 195.49.208.0/20, 195.49.224.0/19, 195.50.0.0/15, 195.52.0.0/15, 195.54.32.0/19, 195.54.64.0/18, 195.54.128.0/18, 195.54.224.0/19, 195.55.0.0/16, 195.56.0.0/15, 195.58.32.0/19, 195.58.64.0/18, 195.58.128.0/17, 195.59.0.0/16, 195.60.0.0/14, 195.64.0.0/17, 195.64.128.0/18, 195.64.224.0/19, 195.65.0.0/16, 195.66.0.0/15, 195.68.0.0/17, 195.68.192.0/18, 195.69.0.0/16, 195.70.0.0/17, 195.70.128.0/18, 195.70.224.0/19, 195.71.0.0/16, 195.72.0.0/17, 195.72.128.0/18, 195.72.192.0/19, 195.73.0.0/16, 195.74.0.0/15, 195.76.0.0/14, 195.80.0.0/13, 195.88.0.0/15, 195.90.0.0/17, 195.90.192.0/18, 195.91.0.0/17, 195.92.0.0/15, 195.94.0.0/17, 195.94.128.0/18, 195.94.192.0/19, 195.95.0.0/16, 195.96.0.0/18, 195.96.96.0/19, 195.96.128.0/19, 195.96.192.0/18, 195.97.0.0/16, 195.98.0.0/19, 195.98.96.0/19, 195.98.128.0/19, 195.98.192.0/18, 195.99.0.0/16, 195.100.0.0/14, 195.104.0.0/13, 195.112.0.0/18, 195.112.64.0/19, 195.112.128.0/18, 195.112.192.0/19, 195.113.0.0/16, 195.114.0.0/15, 195.116.0.0/14, 195.120.0.0/15, 195.122.0.0/17, 195.122.128.0/18, 195.122.192.0/19, 195.123.0.0/16, 195.124.0.0/14, 195.128.0.0/19, 195.128.32.0/20, 195.128.96.0/19, 195.128.128.0/17, 195.129.0.0/16, 195.130.0.0/16, 195.132.0.0/16, 195.133.0.0/17, 195.133.128.0/18, 195.133.192.0/19, 195.134.0.0/15, 195.136.0.0/13, 195.144.0.0/17, 195.144.128.0/18, 195.145.0.0/16, 195.146.0.0/18, 195.146.96.0/19, 195.146.128.0/17, 195.147.0.0/16, 195.148.0.0/15, 195.150.0.0/16, 195.152.0.0/13, 195.160.0.0/16, 195.162.0.0/19, 195.162.64.0/18, 195.162.128.0/17, 195.163.0.0/16, 195.164.0.0/15, 195.166.0.0/17, 195.166.128.0/19, 195.166.192.0/18, 195.167.0.0/16, 195.168.0.0/15, 195.170.0.0/19, 195.170.64.0/18, 195.170.128.0/18, 195.171.0.0/16, 195.172.0.0/14, 195.176.0.0/15, 195.178.0.0/17, 195.178.128.0/18, 195.178.224.0/19, 195.179.0.0/16, 195.180.0.0/15, 195.182.0.0/17, 195.182.160.0/19, 195.182.192.0/18, 195.183.0.0/16, 195.184.0.0/17, 195.184.128.0/18, 195.184.224.0/19, 195.185.0.0/16, 195.186.0.0/15, 195.188.0.0/15, 195.190.0.0/18, 195.190.64.0/19, 195.190.128.0/17, 195.191.0.0/16, 195.192.0.0/13, 195.200.0.0/14, 195.204.0.0/15, 195.206.0.0/19, 195.206.64.0/18, 195.206.128.0/17, 195.207.0.0/16, 195.210.0.0/17, 195.210.192.0/18, 195.211.0.0/16, 195.212.0.0/14, 195.216.0.0/15, 195.218.0.0/17, 195.219.0.0/16, 195.220.0.0/15, 195.222.0.0/17, 195.222.192.0/18, 195.223.0.0/16, 195.224.0.0/14, 195.228.0.0/15, 195.230.0.0/18, 195.230.96.0/19, 195.230.128.0/17, 195.231.0.0/16, 195.232.0.0/14, 195.236.0.0/15, 195.238.0.0/16, 195.240.0.0/12, 196.0.0.0/6, 200.0.0.0/7, 202.0.0.0/8, 203.0.0.0/10, 203.64.0.0/12, 203.80.0.0/16, 203.81.0.0/17, 203.81.128.0/18, 203.81.192.0/20, 203.81.224.0/19, 203.82.0.0/15, 203.84.0.0/14, 203.88.0.0/13, 203.96.0.0/11, 203.128.0.0/9, 204.0.0.0/6, 208.0.0.0/6, 212.0.0.0/18, 212.0.96.0/19, 212.0.128.0/17, 212.1.0.0/17, 212.1.128.0/18, 212.1.192.0/19, 212.2.0.0/16, 212.3.0.0/17, 212.3.160.0/19, 212.3.192.0/18, 212.4.0.0/16, 212.5.0.0/18, 212.5.128.0/19, 212.5.192.0/19, 212.6.32.0/19, 212.6.64.0/18, 212.6.128.0/17, 212.7.0.0/18, 212.7.64.0/19, 212.7.128.0/18, 212.7.192.0/19, 212.8.0.0/15, 212.10.0.0/16, 212.11.0.0/17, 212.11.160.0/19, 212.11.192.0/18, 212.12.32.0/19, 212.12.96.0/19, 212.12.128.0/17, 212.13.32.0/19, 212.13.64.0/19, 212.13.128.0/17, 212.14.0.0/17, 212.14.128.0/19, 212.14.224.0/19, 212.15.0.0/19, 212.15.64.0/19, 212.15.128.0/17, 212.16.32.0/19, 212.16.64.0/18, 212.16.128.0/18, 212.16.224.0/19, 212.17.32.0/19, 212.17.64.0/18, 212.17.128.0/17, 212.18.0.0/16, 212.19.32.0/19, 212.19.64.0/18, 212.19.128.0/17, 212.20.64.0/18, 212.20.128.0/17, 212.21.32.0/19, 212.21.64.0/18, 212.21.128.0/17, 212.22.0.0/18, 212.22.96.0/19, 212.22.128.0/17, 212.23.0.0/18, 212.23.96.0/19, 212.23.128.0/17, 212.24.0.0/19, 212.24.64.0/18, 212.24.128.0/17, 212.25.0.0/16, 212.26.0.0/17, 212.26.128.0/18, 212.26.192.0/19, 212.27.0.0/16, 212.28.0.0/15, 212.30.0.0/17, 212.30.192.0/18, 212.31.0.0/16, 212.32.0.0/17, 212.32.128.0/18, 212.32.224.0/19, 212.33.32.0/19, 212.33.64.0/18, 212.33.128.0/18, 212.33.192.0/19, 212.34.0.0/19, 212.34.64.0/19, 212.34.128.0/17, 212.35.0.0/17, 212.35.128.0/19, 212.35.192.0/18, 212.36.0.0/17, 212.36.128.0/18, 212.36.192.0/19, 212.37.0.0/16, 212.38.0.0/18, 212.38.64.0/19, 212.38.128.0/17, 212.39.0.0/16, 212.40.0.0/17, 212.40.128.0/18, 212.40.224.0/19, 212.41.64.0/18, 212.41.128.0/17, 212.42.0.0/19, 212.42.64.0/18, 212.42.128.0/17, 212.43.0.0/16, 212.44.0.0/18, 212.44.96.0/19, 212.44.160.0/19, 212.44.192.0/18, 212.45.32.0/19, 212.45.64.0/18, 212.45.128.0/18, 212.45.192.0/19, 212.46.32.0/19, 212.46.64.0/18, 212.46.128.0/18, 212.47.0.0/16, 212.48.0.0/19, 212.48.64.0/18, 212.48.160.0/19, 212.48.240.0/20, 212.49.0.0/18, 212.49.64.0/19, 212.49.128.0/17, 212.50.0.0/15, 212.52.0.0/16, 212.53.0.0/19, 212.53.64.0/18, 212.53.128.0/17, 212.54.0.0/16, 212.55.0.0/18, 212.55.64.0/19, 212.55.128.0/17, 212.56.0.0/16, 212.57.0.0/18, 212.57.64.0/19, 212.57.192.0/18, 212.58.0.0/17, 212.58.128.0/18, 212.58.224.0/19, 212.59.0.0/18, 212.59.64.0/19, 212.59.128.0/17, 212.60.0.0/14, 212.64.0.0/16, 212.65.0.0/18, 212.65.96.0/19, 212.65.128.0/17, 212.66.0.0/19, 212.66.48.0/20, 212.66.64.0/18, 212.66.128.0/17, 212.67.32.0/19, 212.67.64.0/18, 212.67.128.0/17, 212.68.0.0/16, 212.69.0.0/18, 212.69.64.0/19, 212.69.128.0/17, 212.70.0.0/15, 212.72.0.0/16, 212.73.0.0/18, 212.73.64.0/19, 212.73.128.0/17, 212.74.0.0/17, 212.74.128.0/18, 212.75.0.0/17, 212.75.160.0/19, 212.76.0.0/17, 212.76.192.0/18, 212.77.0.0/17, 212.77.160.0/19, 212.77.192.0/18, 212.78.0.0/15, 212.80.0.0/15, 212.82.0.0/16, 212.83.32.0/19, 212.83.64.0/18, 212.83.128.0/17, 212.84.0.0/14, 212.88.0.0/15, 212.90.0.0/16, 212.91.0.0/17, 212.91.128.0/18, 212.91.224.0/19, 212.92.0.0/17, 212.92.192.0/18, 212.93.0.0/16, 212.94.32.0/19, 212.94.64.0/19, 212.94.128.0/17, 212.95.0.0/16, 212.96.0.0/18, 212.96.64.0/19, 212.96.128.0/18, 212.96.224.0/19, 212.97.0.0/16, 212.98.0.0/15, 212.100.0.0/17, 212.100.160.0/19, 212.100.192.0/18, 212.101.0.0/16, 212.102.0.0/16, 212.103.0.0/18, 212.103.64.0/19, 212.103.128.0/17, 212.104.0.0/18, 212.104.96.0/19, 212.104.128.0/17, 212.105.0.0/16, 212.106.0.0/19, 212.106.64.0/18, 212.106.128.0/17, 212.107.0.0/17, 212.107.128.0/18, 212.108.0.0/16, 212.109.32.0/19, 212.109.64.0/18, 212.109.128.0/18, 212.109.224.0/19, 212.110.0.0/16, 212.111.0.0/18, 212.111.80.0/20, 212.111.96.0/19, 212.111.128.0/17, 212.112.0.0/16, 212.113.0.0/18, 212.113.64.0/19, 212.113.128.0/18, 212.113.192.0/19, 212.114.0.0/15, 212.116.0.0/18, 212.116.64.0/19, 212.116.128.0/17, 212.117.0.0/16, 212.118.0.0/16, 212.119.0.0/18, 212.119.64.0/19, 212.119.128.0/19, 212.120.0.0/17, 212.120.128.0/19, 212.120.192.0/18, 212.121.0.0/16, 212.122.32.0/19, 212.122.64.0/18, 212.122.128.0/17, 212.123.0.0/16, 212.124.32.0/19, 212.124.64.0/19, 212.124.128.0/17, 212.125.0.0/16, 212.126.0.0/15, 212.128.0.0/16, 212.129.0.0/18, 212.129.64.0/19, 212.129.128.0/17, 212.130.0.0/15, 212.132.0.0/14, 212.136.0.0/13, 212.144.0.0/13, 212.152.0.0/19, 212.152.64.0/18, 212.152.128.0/17, 212.153.0.0/16, 212.154.0.0/15, 212.156.0.0/15, 212.158.0.0/17, 212.158.128.0/19, 212.158.176.0/20, 212.158.192.0/18, 212.159.0.0/16, 212.160.0.0/14, 212.165.0.0/16, 212.166.0.0/15, 212.168.0.0/13, 212.177.0.0/16, 212.178.0.0/15, 212.180.0.0/14, 212.184.0.0/14, 212.188.128.0/17, 212.189.0.0/16, 212.190.0.0/15, 212.192.0.0/19, 212.192.208.0/20, 212.192.240.0/20, 212.193.0.0/19, 212.194.0.0/15, 212.196.0.0/14, 212.200.0.0/13, 212.208.0.0/13, 212.216.0.0/14, 212.221.0.0/16, 212.222.0.0/15, 212.224.0.0/13, 212.232.16.0/20, 212.232.64.0/18, 212.232.128.0/17, 212.233.0.0/16, 212.234.0.0/15, 212.236.0.0/14, 212.240.0.0/13, 212.248.128.0/17, 212.249.0.0/16, 212.250.0.0/15, 212.252.0.0/14, 213.0.0.0/14, 213.4.0.0/16, 213.5.0.0/18, 213.5.64.0/19, 213.5.112.0/20, 213.5.128.0/17, 213.6.0.0/15, 213.8.0.0/13, 213.16.0.0/13, 213.25.0.0/16, 213.26.0.0/16, 213.27.128.0/17, 213.28.0.0/14, 213.32.0.0/16, 213.33.0.0/17, 213.34.0.0/15, 213.36.0.0/14, 213.40.0.0/13, 213.48.0.0/13, 213.56.0.0/15, 213.58.0.0/16, 213.59.112.0/20, 213.60.0.0/14, 213.64.0.0/13, 213.72.0.0/14, 213.76.0.0/15, 213.78.0.0/16, 213.79.32.0/19, 213.79.128.0/17, 213.80.0.0/17, 213.81.0.0/16, 213.82.0.0/15, 213.84.0.0/16, 213.86.0.0/16, 213.88.128.0/17, 213.89.0.0/16, 213.90.0.0/15, 213.92.0.0/14, 213.96.0.0/13, 213.104.0.0/14, 213.108.0.0/17, 213.108.128.0/18, 213.108.192.0/20, 213.108.224.0/19, 213.109.32.0/20, 213.109.64.0/18, 213.109.128.0/17, 213.110.64.0/18, 213.110.128.0/18, 213.110.224.0/20, 213.111.0.0/16, 213.112.0.0/12, 213.128.32.0/19, 213.128.64.0/18, 213.128.128.0/18, 213.128.224.0/19, 213.129.0.0/19, 213.129.64.0/19, 213.129.128.0/17, 213.130.0.0/16, 213.131.32.0/19, 213.131.64.0/18, 213.131.128.0/17, 213.132.0.0/18, 213.132.96.0/19, 213.132.128.0/17, 213.133.0.0/16, 213.134.0.0/17, 213.134.128.0/18, 213.134.224.0/19, 213.135.0.0/18, 213.135.160.0/19, 213.135.192.0/18, 213.136.0.0/16, 213.137.0.0/17, 213.137.128.0/18, 213.137.192.0/19, 213.138.0.0/18, 213.138.96.0/19, 213.138.128.0/18, 213.138.208.0/20, 213.138.224.0/19, 213.139.0.0/16, 213.140.0.0/18, 213.140.64.0/19, 213.140.128.0/18, 213.140.192.0/19, 213.141.0.0/17, 213.141.160.0/19, 213.141.192.0/19, 213.142.0.0/19, 213.142.64.0/18, 213.142.128.0/18, 213.142.224.0/19, 213.143.0.0/16, 213.144.0.0/16, 213.145.64.0/18, 213.145.128.0/17, 213.146.0.0/16, 213.147.0.0/19, 213.147.64.0/18, 213.147.128.0/18, 213.147.224.0/19, 213.148.0.0/17, 213.148.128.0/19, 213.148.192.0/18, 213.149.32.0/19, 213.149.64.0/18, 213.149.128.0/17, 213.150.0.0/18, 213.150.96.0/19, 213.150.128.0/17, 213.151.32.0/19, 213.151.64.0/18, 213.151.128.0/17, 213.152.0.0/17, 213.152.160.0/19, 213.152.192.0/18, 213.153.0.0/16, 213.154.0.0/17, 213.154.128.0/19, 213.154.192.0/18, 213.155.0.0/17, 213.155.128.0/18, 213.155.224.0/19, 213.156.0.0/17, 213.156.128.0/18, 213.156.224.0/19, 213.157.0.0/16, 213.158.32.0/19, 213.158.64.0/18, 213.158.128.0/17, 213.159.0.0/17, 213.159.128.0/18, 213.159.224.0/19, 213.160.0.0/14, 213.164.0.0/16, 213.165.0.0/17, 213.165.128.0/18, 213.165.224.0/19, 213.166.0.0/16, 213.167.0.0/19, 213.167.64.0/18, 213.167.128.0/18, 213.167.224.0/19, 213.168.0.0/19, 213.168.64.0/18, 213.168.160.0/19, 213.168.192.0/18, 213.169.0.0/16, 213.170.0.0/18, 213.170.128.0/17, 213.171.64.0/18, 213.171.128.0/17, 213.172.32.0/19, 213.172.64.0/18, 213.172.128.0/17, 213.173.0.0/16, 213.174.0.0/15, 213.176.0.0/17, 213.176.128.0/18, 213.176.192.0/19, 213.177.0.0/18, 213.177.64.0/19, 213.177.128.0/17, 213.178.0.0/19, 213.178.64.0/18, 213.178.128.0/17, 213.179.0.0/16, 213.180.0.0/17, 213.180.128.0/18, 213.180.224.0/19, 213.181.32.0/19, 213.181.64.0/18, 213.181.128.0/17, 213.182.0.0/17, 213.182.128.0/19, 213.182.192.0/18, 213.183.0.0/18, 213.183.64.0/19, 213.183.128.0/18, 213.183.192.0/19, 213.184.0.0/17, 213.184.160.0/19, 213.184.192.0/18, 213.185.0.0/16, 213.186.0.0/16, 213.187.0.0/18, 213.187.64.0/19, 213.187.128.0/17, 213.188.0.0/16, 213.189.0.0/17, 213.189.128.0/18, 213.190.0.0/17, 213.190.128.0/18, 213.190.192.0/19, 213.191.32.0/19, 213.191.64.0/18, 213.191.128.0/17, 213.192.0.0/16, 213.193.32.0/19, 213.193.64.0/18, 213.193.128.0/17, 213.194.0.0/15, 213.196.0.0/14, 213.200.0.0/13, 213.208.0.0/17, 213.208.128.0/19, 213.208.160.0/20, 213.208.192.0/18, 213.209.0.0/16, 213.210.0.0/18, 213.210.128.0/17, 213.211.0.0/16, 213.212.0.0/14, 213.216.0.0/15, 213.218.0.0/16, 213.219.0.0/17, 213.219.128.0/18, 213.219.240.0/20, 213.220.0.0/16, 213.221.64.0/18, 213.221.128.0/17, 213.222.0.0/17, 213.222.128.0/18, 213.222.192.0/19, 213.223.0.0/16, 213.224.0.0/14, 213.228.0.0/18, 213.228.128.0/17, 213.229.0.0/16, 213.230.0.0/15, 213.232.0.0/15, 213.234.32.0/19, 213.234.64.0/18, 213.234.128.0/18, 213.235.0.0/16, 213.236.0.0/14, 213.240.0.0/16, 213.241.0.0/17, 213.241.128.0/18, 213.242.64.0/18, 213.242.128.0/18, 213.243.0.0/18, 213.243.128.0/17, 213.244.0.0/15, 213.246.0.0/16, 213.247.0.0/17, 213.248.64.0/18, 213.248.128.0/17, 213.249.0.0/16, 213.250.0.0/16, 213.251.0.0/17, 213.251.128.0/18, 213.252.0.0/18, 213.252.128.0/17, 213.253.0.0/16, 213.254.0.0/15, 214.0.0.0/7, 216.0.0.0/8, 217.0.0.0/13, 217.8.0.0/18, 217.8.64.0/20, 217.8.96.0/19, 217.8.128.0/18, 217.8.192.0/19, 217.8.240.0/20, 217.9.0.0/18, 217.9.64.0/20, 217.9.96.0/19, 217.9.128.0/20, 217.9.160.0/19, 217.9.192.0/18, 217.10.0.0/19, 217.10.48.0/20, 217.10.64.0/18, 217.10.128.0/17, 217.11.0.0/16, 217.12.0.0/19, 217.12.48.0/20, 217.12.80.0/20, 217.12.112.0/20, 217.12.128.0/18, 217.12.192.0/19, 217.12.224.0/20, 217.13.0.0/17, 217.13.128.0/18, 217.13.192.0/20, 217.13.224.0/19, 217.14.0.0/20, 217.14.32.0/20, 217.14.64.0/18, 217.14.128.0/18, 217.14.208.0/20, 217.14.224.0/20, 217.15.0.0/20, 217.15.32.0/20, 217.15.64.0/18, 217.15.160.0/19, 217.15.208.0/20, 217.15.224.0/19, 217.16.0.0/20, 217.16.32.0/19, 217.16.64.0/18, 217.16.128.0/17, 217.17.0.0/18, 217.17.64.0/19, 217.17.96.0/20, 217.17.128.0/19, 217.17.192.0/18, 217.18.0.0/17, 217.18.160.0/19, 217.18.192.0/18, 217.19.0.0/18, 217.19.64.0/19, 217.19.128.0/17, 217.20.0.0/18, 217.20.96.0/19, 217.20.128.0/20, 217.20.160.0/19, 217.20.192.0/18, 217.21.0.0/18, 217.21.64.0/19, 217.21.112.0/20, 217.21.128.0/18, 217.21.192.0/20, 217.21.224.0/19, 217.22.0.0/17, 217.22.128.0/19, 217.22.176.0/20, 217.22.192.0/18, 217.23.0.0/20, 217.23.32.0/19, 217.23.96.0/19, 217.23.144.0/20, 217.23.160.0/20, 217.23.192.0/18, 217.24.0.0/18, 217.24.64.0/19, 217.24.96.0/20, 217.24.128.0/19, 217.24.160.0/20, 217.24.192.0/18, 217.25.0.0/18, 217.25.64.0/20, 217.25.96.0/19, 217.25.128.0/20, 217.25.160.0/19, 217.25.192.0/20, 217.25.240.0/20, 217.26.32.0/19, 217.26.64.0/18, 217.26.128.0/17, 217.27.0.0/17, 217.27.144.0/20, 217.27.160.0/19, 217.27.192.0/18, 217.28.0.0/17, 217.28.128.0/18, 217.28.192.0/20, 217.28.240.0/20, 217.29.0.0/19, 217.29.32.0/20, 217.29.64.0/20, 217.29.96.0/19, 217.29.128.0/19, 217.29.160.0/20, 217.29.192.0/18, 217.30.0.0/17, 217.30.128.0/18, 217.30.192.0/19, 217.30.224.0/20, 217.31.0.0/16, 217.32.0.0/11, 217.64.0.0/17, 217.64.144.0/20, 217.64.160.0/19, 217.64.192.0/18, 217.65.16.0/20, 217.65.32.0/19, 217.65.64.0/20, 217.65.96.0/19, 217.65.128.0/18, 217.65.192.0/20, 217.65.224.0/19, 217.66.0.0/20, 217.66.32.0/19, 217.66.96.0/19, 217.66.128.0/20, 217.66.160.0/19, 217.66.192.0/18, 217.67.16.0/20, 217.67.32.0/19, 217.67.64.0/19, 217.67.96.0/20, 217.67.128.0/19, 217.67.160.0/20, 217.67.192.0/18, 217.68.0.0/16, 217.69.0.0/17, 217.69.144.0/20, 217.69.160.0/19, 217.69.224.0/19, 217.70.0.0/20, 217.70.32.0/19, 217.70.64.0/19, 217.70.128.0/17, 217.71.0.0/17, 217.71.144.0/20, 217.71.176.0/20, 217.71.192.0/19, 217.71.240.0/20, 217.72.0.0/17, 217.72.128.0/20, 217.72.160.0/19, 217.72.192.0/18, 217.73.0.0/18, 217.73.64.0/19, 217.73.96.0/20, 217.73.128.0/18, 217.73.208.0/20, 217.73.224.0/19, 217.74.0.0/19, 217.74.48.0/20, 217.74.64.0/19, 217.74.96.0/20, 217.74.128.0/19, 217.74.176.0/20, 217.74.192.0/19, 217.74.224.0/20, 217.75.0.0/16, 217.76.0.0/19, 217.76.48.0/20, 217.76.64.0/18, 217.76.128.0/19, 217.76.160.0/20, 217.76.192.0/18, 217.77.0.0/19, 217.77.32.0/20, 217.77.64.0/18, 217.77.128.0/17, 217.78.0.0/17, 217.78.128.0/19, 217.78.160.0/20, 217.78.192.0/18, 217.79.32.0/20, 217.79.64.0/18, 217.79.128.0/18, 217.79.192.0/19, 217.79.240.0/20, 217.80.0.0/12, 217.96.0.0/13, 217.104.0.0/15, 217.108.0.0/14, 217.112.48.0/20, 217.112.64.0/18, 217.112.128.0/17, 217.113.0.0/18, 217.113.64.0/19, 217.113.96.0/20, 217.113.128.0/17, 217.114.32.0/19, 217.114.64.0/18, 217.114.128.0/20, 217.114.160.0/20, 217.114.192.0/19, 217.115.0.0/18, 217.115.64.0/20, 217.115.96.0/19, 217.115.128.0/19, 217.115.160.0/20, 217.115.192.0/18, 217.116.0.0/19, 217.116.32.0/20, 217.116.64.0/18, 217.116.160.0/19, 217.116.192.0/18, 217.117.0.0/18, 217.117.64.0/20, 217.117.96.0/20, 217.117.128.0/19, 217.117.160.0/20, 217.117.192.0/19, 217.117.224.0/20, 217.118.0.0/18, 217.118.96.0/19, 217.118.128.0/19, 217.118.160.0/20, 217.118.192.0/18, 217.119.0.0/20, 217.119.32.0/19, 217.119.64.0/20, 217.119.96.0/19, 217.119.128.0/17, 217.120.0.0/13, 217.128.0.0/12, 217.144.0.0/17, 217.144.128.0/19, 217.144.176.0/20, 217.144.192.0/18, 217.145.0.0/20, 217.145.32.0/19, 217.145.64.0/18, 217.145.128.0/20, 217.145.160.0/19, 217.145.192.0/18, 217.146.0.0/19, 217.146.48.0/20, 217.146.64.0/18, 217.146.128.0/17, 217.147.0.0/20, 217.147.32.0/19, 217.147.64.0/18, 217.147.128.0/17, 217.148.0.0/19, 217.148.32.0/20, 217.148.64.0/18, 217.148.128.0/18, 217.148.224.0/19, 217.149.0.0/20, 217.149.32.0/19, 217.149.64.0/18, 217.149.128.0/19, 217.149.160.0/20, 217.149.192.0/18, 217.150.64.0/18, 217.150.128.0/18, 217.150.208.0/20, 217.150.224.0/19, 217.151.0.0/20, 217.151.32.0/19, 217.151.80.0/20, 217.151.96.0/19, 217.151.144.0/20, 217.151.160.0/19, 217.151.192.0/19, 217.151.240.0/20, 217.152.0.0/13, 217.160.0.0/13, 217.168.0.0/18, 217.168.80.0/20, 217.168.96.0/19, 217.168.128.0/17, 217.169.0.0/18, 217.169.64.0/20, 217.169.96.0/19, 217.169.128.0/17, 217.170.0.0/18, 217.170.96.0/20, 217.170.128.0/18, 217.170.192.0/20, 217.170.224.0/19, 217.171.16.0/20, 217.171.32.0/19, 217.171.64.0/18, 217.171.128.0/17, 217.172.0.0/20, 217.172.32.0/19, 217.172.64.0/18, 217.172.128.0/17, 217.173.0.0/20, 217.173.32.0/19, 217.173.80.0/20, 217.173.96.0/19, 217.173.128.0/17, 217.174.16.0/20, 217.174.32.0/19, 217.174.64.0/19, 217.174.112.0/20, 217.174.128.0/19, 217.174.192.0/18, 217.175.48.0/20, 217.175.64.0/18, 217.175.160.0/19, 217.175.192.0/18, 217.176.0.0/12, 217.192.0.0/15, 217.194.0.0/17, 217.194.128.0/18, 217.194.192.0/19, 217.194.224.0/20, 217.195.0.0/18, 217.195.112.0/20, 217.195.128.0/18, 217.195.192.0/20, 217.195.224.0/19, 217.196.0.0/18, 217.196.64.0/19, 217.196.112.0/20, 217.196.128.0/17, 217.197.16.0/20, 217.197.32.0/19, 217.197.64.0/19, 217.197.96.0/20, 217.197.128.0/18, 217.197.208.0/20, 217.198.16.0/20, 217.198.32.0/19, 217.198.64.0/20, 217.198.96.0/19, 217.198.128.0/19, 217.198.176.0/20, 217.198.192.0/18, 217.199.0.0/17, 217.199.128.0/18, 217.199.192.0/20, 217.199.240.0/20, 217.200.0.0/13, 217.208.0.0/12, 217.224.0.0/11, 218.0.0.0/7, 220.0.0.0/6, 10.25.0.0/16 diff --git a/ios/WireGuardGo/iphoneos/libwg-go.a b/ios/WireGuardGo/iphoneos/libwg-go.a new file mode 100644 index 0000000..2fef8a5 Binary files /dev/null and b/ios/WireGuardGo/iphoneos/libwg-go.a differ diff --git a/ios/WireGuardGo/iphonesimulator/libwg-go.a b/ios/WireGuardGo/iphonesimulator/libwg-go.a new file mode 100644 index 0000000..268f2d2 Binary files /dev/null and b/ios/WireGuardGo/iphonesimulator/libwg-go.a differ diff --git a/ios/WireGuardKit/COPYING b/ios/WireGuardKit/COPYING new file mode 100644 index 0000000..af11a81 --- /dev/null +++ b/ios/WireGuardKit/COPYING @@ -0,0 +1,19 @@ +Copyright © 2018-2023 WireGuard LLC. All Rights Reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/ios/WireGuardKit/Package.swift b/ios/WireGuardKit/Package.swift new file mode 100644 index 0000000..4d4e07d --- /dev/null +++ b/ios/WireGuardKit/Package.swift @@ -0,0 +1,15 @@ +// swift-tools-version:5.5 +// Vendored WireGuardKit (github.com/WireGuard/wireguard-apple, MIT). libwg-go.a is +// prebuilt in ../WireGuardGo// — the app target adds it to LIBRARY_SEARCH_PATHS. +import PackageDescription + +let package = Package( + name: "WireGuardKit", + platforms: [.iOS(.v15)], + products: [.library(name: "WireGuardKit", targets: ["WireGuardKit"])], + targets: [ + .target(name: "WireGuardKit", dependencies: ["WireGuardKitGo", "WireGuardKitC"]), + .target(name: "WireGuardKitC", publicHeadersPath: "."), + .target(name: "WireGuardKitGo", publicHeadersPath: ".", linkerSettings: [.linkedLibrary("wg-go")]), + ] +) diff --git a/ios/WireGuardKit/Sources/WireGuardKit/Array+ConcurrentMap.swift b/ios/WireGuardKit/Sources/WireGuardKit/Array+ConcurrentMap.swift new file mode 100644 index 0000000..13309c6 --- /dev/null +++ b/ios/WireGuardKit/Sources/WireGuardKit/Array+ConcurrentMap.swift @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved. + +import Foundation + +extension Array { + + /// Returns an array containing the results of mapping the given closure over the sequence’s + /// elements concurrently. + /// + /// - Parameters: + /// - queue: The queue for performing concurrent computations. + /// If the given queue is serial, the values are mapped in a serial fashion. + /// Pass `nil` to perform computations on the current queue. + /// - transform: the block to perform concurrent computations over the given element. + /// - Returns: an array of concurrently computed values. + func concurrentMap(queue: DispatchQueue?, _ transform: (Element) -> U) -> [U] { + var result = [U?](repeating: nil, count: self.count) + let resultQueue = DispatchQueue(label: "ConcurrentMapQueue") + + let execute = queue?.sync ?? { $0() } + + execute { + DispatchQueue.concurrentPerform(iterations: self.count) { index in + let value = transform(self[index]) + resultQueue.sync { + result[index] = value + } + } + } + + return result.map { $0! } + } +} diff --git a/ios/WireGuardKit/Sources/WireGuardKit/DNSResolver.swift b/ios/WireGuardKit/Sources/WireGuardKit/DNSResolver.swift new file mode 100644 index 0000000..cf4fde6 --- /dev/null +++ b/ios/WireGuardKit/Sources/WireGuardKit/DNSResolver.swift @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: MIT +// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved. + +import Network +import Foundation + +enum DNSResolver {} + +extension DNSResolver { + + /// Concurrent queue used for DNS resolutions + private static let resolverQueue = DispatchQueue(label: "DNSResolverQueue", qos: .default, attributes: .concurrent) + + static func resolveSync(endpoints: [Endpoint?]) -> [Result?] { + let isAllEndpointsAlreadyResolved = endpoints.allSatisfy { maybeEndpoint -> Bool in + return maybeEndpoint?.hasHostAsIPAddress() ?? true + } + + if isAllEndpointsAlreadyResolved { + return endpoints.map { endpoint in + return endpoint.map { .success($0) } + } + } + + return endpoints.concurrentMap(queue: resolverQueue) { endpoint -> Result? in + guard let endpoint = endpoint else { return nil } + + if endpoint.hasHostAsIPAddress() { + return .success(endpoint) + } else { + return Result { try DNSResolver.resolveSync(endpoint: endpoint) } + .mapError { error -> DNSResolutionError in + // swiftlint:disable:next force_cast + return error as! DNSResolutionError + } + } + } + } + + private static func resolveSync(endpoint: Endpoint) throws -> Endpoint { + guard case .name(let name, _) = endpoint.host else { + return endpoint + } + + var hints = addrinfo() + hints.ai_flags = AI_ALL // We set this to ALL so that we get v4 addresses even on DNS64 networks + hints.ai_family = AF_UNSPEC + hints.ai_socktype = SOCK_DGRAM + hints.ai_protocol = IPPROTO_UDP + + var resultPointer: UnsafeMutablePointer? + defer { + resultPointer.flatMap { freeaddrinfo($0) } + } + + let errorCode = getaddrinfo(name, "\(endpoint.port)", &hints, &resultPointer) + if errorCode != 0 { + throw DNSResolutionError(errorCode: errorCode, address: name) + } + + var ipv4Address: IPv4Address? + var ipv6Address: IPv6Address? + + var next: UnsafeMutablePointer? = resultPointer + let iterator = AnyIterator { () -> addrinfo? in + let result = next?.pointee + next = result?.ai_next + return result + } + + for addrInfo in iterator { + if let maybeIpv4Address = IPv4Address(addrInfo: addrInfo) { + ipv4Address = maybeIpv4Address + break // If we found an IPv4 address, we can stop + } else if let maybeIpv6Address = IPv6Address(addrInfo: addrInfo) { + ipv6Address = maybeIpv6Address + continue // If we already have an IPv6 address, we can skip this one + } + } + + // We prefer an IPv4 address over an IPv6 address + if let ipv4Address = ipv4Address { + return Endpoint(host: .ipv4(ipv4Address), port: endpoint.port) + } else if let ipv6Address = ipv6Address { + return Endpoint(host: .ipv6(ipv6Address), port: endpoint.port) + } else { + // Must never happen + fatalError() + } + } +} + +extension Endpoint { + func withReresolvedIP() throws -> Endpoint { + #if os(iOS) + let hostname: String + switch host { + case .name(let name, _): + hostname = name + case .ipv4(let address): + hostname = "\(address)" + case .ipv6(let address): + hostname = "\(address)" + @unknown default: + fatalError() + } + + var hints = addrinfo() + hints.ai_family = AF_UNSPEC + hints.ai_socktype = SOCK_DGRAM + hints.ai_protocol = IPPROTO_UDP + hints.ai_flags = 0 // We set this to zero so that we actually resolve this using DNS64 + + var result: UnsafeMutablePointer? + defer { + result.flatMap { freeaddrinfo($0) } + } + + let errorCode = getaddrinfo(hostname, "\(self.port)", &hints, &result) + if errorCode != 0 { + throw DNSResolutionError(errorCode: errorCode, address: hostname) + } + + let addrInfo = result!.pointee + if let ipv4Address = IPv4Address(addrInfo: addrInfo) { + return Endpoint(host: .ipv4(ipv4Address), port: port) + } else if let ipv6Address = IPv6Address(addrInfo: addrInfo) { + return Endpoint(host: .ipv6(ipv6Address), port: port) + } else { + fatalError() + } + #elseif os(macOS) + return self + #else + #error("Unimplemented") + #endif + } +} + +/// An error type describing DNS resolution error +public struct DNSResolutionError: LocalizedError { + public let errorCode: Int32 + public let address: String + + init(errorCode: Int32, address: String) { + self.errorCode = errorCode + self.address = address + } + + public var errorDescription: String? { + return String(cString: gai_strerror(errorCode)) + } +} diff --git a/ios/WireGuardKit/Sources/WireGuardKit/DNSServer.swift b/ios/WireGuardKit/Sources/WireGuardKit/DNSServer.swift new file mode 100644 index 0000000..eaf10f8 --- /dev/null +++ b/ios/WireGuardKit/Sources/WireGuardKit/DNSServer.swift @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved. + +import Foundation +import Network + +public struct DNSServer { + public let address: IPAddress + + public init(address: IPAddress) { + self.address = address + } +} + +extension DNSServer: Equatable { + public static func == (lhs: DNSServer, rhs: DNSServer) -> Bool { + return lhs.address.rawValue == rhs.address.rawValue + } +} + +extension DNSServer { + public var stringRepresentation: String { + return "\(address)" + } + + public init?(from addressString: String) { + if let addr = IPv4Address(addressString) { + address = addr + } else if let addr = IPv6Address(addressString) { + address = addr + } else { + return nil + } + } +} diff --git a/ios/WireGuardKit/Sources/WireGuardKit/Endpoint.swift b/ios/WireGuardKit/Sources/WireGuardKit/Endpoint.swift new file mode 100644 index 0000000..da49088 --- /dev/null +++ b/ios/WireGuardKit/Sources/WireGuardKit/Endpoint.swift @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: MIT +// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved. + +import Foundation +import Network + +public struct Endpoint { + public let host: NWEndpoint.Host + public let port: NWEndpoint.Port + + public init(host: NWEndpoint.Host, port: NWEndpoint.Port) { + self.host = host + self.port = port + } +} + +extension Endpoint: Equatable { + public static func == (lhs: Endpoint, rhs: Endpoint) -> Bool { + return lhs.host == rhs.host && lhs.port == rhs.port + } +} + +extension Endpoint: Hashable { + public func hash(into hasher: inout Hasher) { + hasher.combine(host) + hasher.combine(port) + } +} + +extension Endpoint { + public var stringRepresentation: String { + switch host { + case .name(let hostname, _): + return "\(hostname):\(port)" + case .ipv4(let address): + return "\(address):\(port)" + case .ipv6(let address): + return "[\(address)]:\(port)" + @unknown default: + fatalError() + } + } + + public init?(from string: String) { + // Separation of host and port is based on 'parse_endpoint' function in + // https://git.zx2c4.com/wireguard-tools/tree/src/config.c + guard !string.isEmpty else { return nil } + let startOfPort: String.Index + let hostString: String + if string.first! == "[" { + // Look for IPv6-style endpoint, like [::1]:80 + let startOfHost = string.index(after: string.startIndex) + guard let endOfHost = string.dropFirst().firstIndex(of: "]") else { return nil } + let afterEndOfHost = string.index(after: endOfHost) + if afterEndOfHost == string.endIndex { return nil } + guard string[afterEndOfHost] == ":" else { return nil } + startOfPort = string.index(after: afterEndOfHost) + hostString = String(string[startOfHost ..< endOfHost]) + } else { + // Look for an IPv4-style endpoint, like 127.0.0.1:80 + guard let endOfHost = string.firstIndex(of: ":") else { return nil } + startOfPort = string.index(after: endOfHost) + hostString = String(string[string.startIndex ..< endOfHost]) + } + guard let endpointPort = NWEndpoint.Port(String(string[startOfPort ..< string.endIndex])) else { return nil } + let invalidCharacterIndex = hostString.unicodeScalars.firstIndex { char in + return !CharacterSet.urlHostAllowed.contains(char) + } + guard invalidCharacterIndex == nil else { return nil } + host = NWEndpoint.Host(hostString) + port = endpointPort + } +} + +extension Endpoint { + public func hasHostAsIPAddress() -> Bool { + switch host { + case .name: + return false + case .ipv4: + return true + case .ipv6: + return true + @unknown default: + fatalError() + } + } + + public func hostname() -> String? { + switch host { + case .name(let hostname, _): + return hostname + case .ipv4: + return nil + case .ipv6: + return nil + @unknown default: + fatalError() + } + } +} diff --git a/ios/WireGuardKit/Sources/WireGuardKit/IPAddress+AddrInfo.swift b/ios/WireGuardKit/Sources/WireGuardKit/IPAddress+AddrInfo.swift new file mode 100644 index 0000000..7fe1fa7 --- /dev/null +++ b/ios/WireGuardKit/Sources/WireGuardKit/IPAddress+AddrInfo.swift @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved. + +import Foundation +import Network + +extension IPv4Address { + init?(addrInfo: addrinfo) { + guard addrInfo.ai_family == AF_INET else { return nil } + + let addressData = addrInfo.ai_addr.withMemoryRebound(to: sockaddr_in.self, capacity: 1) { ptr -> Data in + return Data(bytes: &ptr.pointee.sin_addr, count: MemoryLayout.size) + } + + self.init(addressData) + } +} + +extension IPv6Address { + init?(addrInfo: addrinfo) { + guard addrInfo.ai_family == AF_INET6 else { return nil } + + let addressData = addrInfo.ai_addr.withMemoryRebound(to: sockaddr_in6.self, capacity: 1) { ptr -> Data in + return Data(bytes: &ptr.pointee.sin6_addr, count: MemoryLayout.size) + } + + self.init(addressData) + } +} diff --git a/ios/WireGuardKit/Sources/WireGuardKit/IPAddressRange.swift b/ios/WireGuardKit/Sources/WireGuardKit/IPAddressRange.swift new file mode 100644 index 0000000..60430af --- /dev/null +++ b/ios/WireGuardKit/Sources/WireGuardKit/IPAddressRange.swift @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: MIT +// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved. + +import Foundation +import Network + +public struct IPAddressRange { + public let address: IPAddress + public let networkPrefixLength: UInt8 + + init(address: IPAddress, networkPrefixLength: UInt8) { + self.address = address + self.networkPrefixLength = networkPrefixLength + } +} + +extension IPAddressRange: Equatable { + public static func == (lhs: IPAddressRange, rhs: IPAddressRange) -> Bool { + return lhs.address.rawValue == rhs.address.rawValue && lhs.networkPrefixLength == rhs.networkPrefixLength + } +} + +extension IPAddressRange: Hashable { + public func hash(into hasher: inout Hasher) { + hasher.combine(address.rawValue) + hasher.combine(networkPrefixLength) + } +} + +extension IPAddressRange { + public var stringRepresentation: String { + return "\(address)/\(networkPrefixLength)" + } + + public init?(from string: String) { + guard let parsed = IPAddressRange.parseAddressString(string) else { return nil } + address = parsed.0 + networkPrefixLength = parsed.1 + } + + private static func parseAddressString(_ string: String) -> (IPAddress, UInt8)? { + let endOfIPAddress = string.lastIndex(of: "/") ?? string.endIndex + let addressString = String(string[string.startIndex ..< endOfIPAddress]) + let address: IPAddress + if let addr = IPv4Address(addressString) { + address = addr + } else if let addr = IPv6Address(addressString) { + address = addr + } else { + return nil + } + + let maxNetworkPrefixLength: UInt8 = address is IPv4Address ? 32 : 128 + var networkPrefixLength: UInt8 + if endOfIPAddress < string.endIndex { // "/" was located + let indexOfNetworkPrefixLength = string.index(after: endOfIPAddress) + guard indexOfNetworkPrefixLength < string.endIndex else { return nil } + let networkPrefixLengthSubstring = string[indexOfNetworkPrefixLength ..< string.endIndex] + guard let npl = UInt8(networkPrefixLengthSubstring) else { return nil } + networkPrefixLength = min(npl, maxNetworkPrefixLength) + } else { + networkPrefixLength = maxNetworkPrefixLength + } + + return (address, networkPrefixLength) + } + + public func subnetMask() -> IPAddress { + if address is IPv4Address { + let mask = networkPrefixLength > 0 ? ~UInt32(0) << (32 - networkPrefixLength) : UInt32(0) + let bytes = Data([ + UInt8(truncatingIfNeeded: mask >> 24), + UInt8(truncatingIfNeeded: mask >> 16), + UInt8(truncatingIfNeeded: mask >> 8), + UInt8(truncatingIfNeeded: mask >> 0) + ]) + return IPv4Address(bytes)! + } + if address is IPv6Address { + var bytes = Data(repeating: 0, count: 16) + for i in 0..> 24) + bytes[i + 1] = UInt8(truncatingIfNeeded: mask >> 16) + bytes[i + 2] = UInt8(truncatingIfNeeded: mask >> 8) + bytes[i + 3] = UInt8(truncatingIfNeeded: mask >> 0) + } + return IPv6Address(bytes)! + } + fatalError() + } + + public func maskedAddress() -> IPAddress { + let subnet = subnetMask().rawValue + var masked = Data(address.rawValue) + if subnet.count != masked.count { + fatalError() + } + for i in 0.. Bool { + let lhsAddresses = lhs.addresses.filter { $0.address is IPv4Address } + lhs.addresses.filter { $0.address is IPv6Address } + let rhsAddresses = rhs.addresses.filter { $0.address is IPv4Address } + rhs.addresses.filter { $0.address is IPv6Address } + + return lhs.privateKey == rhs.privateKey && + lhsAddresses == rhsAddresses && + lhs.listenPort == rhs.listenPort && + lhs.mtu == rhs.mtu && + lhs.dns == rhs.dns && + lhs.dnsSearch == rhs.dnsSearch + } +} diff --git a/ios/WireGuardKit/Sources/WireGuardKit/PacketTunnelSettingsGenerator.swift b/ios/WireGuardKit/Sources/WireGuardKit/PacketTunnelSettingsGenerator.swift new file mode 100644 index 0000000..3658956 --- /dev/null +++ b/ios/WireGuardKit/Sources/WireGuardKit/PacketTunnelSettingsGenerator.swift @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: MIT +// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved. + +import Foundation +import Network +import NetworkExtension + +#if SWIFT_PACKAGE +import WireGuardKitC +#endif + +/// A type alias for `Result` type that holds a tuple with source and resolved endpoint. +typealias EndpointResolutionResult = Result<(Endpoint, Endpoint), DNSResolutionError> + +class PacketTunnelSettingsGenerator { + let tunnelConfiguration: TunnelConfiguration + let resolvedEndpoints: [Endpoint?] + + init(tunnelConfiguration: TunnelConfiguration, resolvedEndpoints: [Endpoint?]) { + self.tunnelConfiguration = tunnelConfiguration + self.resolvedEndpoints = resolvedEndpoints + } + + func endpointUapiConfiguration() -> (String, [EndpointResolutionResult?]) { + var resolutionResults = [EndpointResolutionResult?]() + var wgSettings = "" + + assert(tunnelConfiguration.peers.count == resolvedEndpoints.count) + for (peer, resolvedEndpoint) in zip(self.tunnelConfiguration.peers, self.resolvedEndpoints) { + wgSettings.append("public_key=\(peer.publicKey.hexKey)\n") + + let result = resolvedEndpoint.map(Self.reresolveEndpoint) + if case .success((_, let resolvedEndpoint)) = result { + if case .name = resolvedEndpoint.host { assert(false, "Endpoint is not resolved") } + wgSettings.append("endpoint=\(resolvedEndpoint.stringRepresentation)\n") + } + resolutionResults.append(result) + } + + return (wgSettings, resolutionResults) + } + + func uapiConfiguration() -> (String, [EndpointResolutionResult?]) { + var resolutionResults = [EndpointResolutionResult?]() + var wgSettings = "" + wgSettings.append("private_key=\(tunnelConfiguration.interface.privateKey.hexKey)\n") + if let listenPort = tunnelConfiguration.interface.listenPort { + wgSettings.append("listen_port=\(listenPort)\n") + } + if !tunnelConfiguration.peers.isEmpty { + wgSettings.append("replace_peers=true\n") + } + assert(tunnelConfiguration.peers.count == resolvedEndpoints.count) + for (peer, resolvedEndpoint) in zip(self.tunnelConfiguration.peers, self.resolvedEndpoints) { + wgSettings.append("public_key=\(peer.publicKey.hexKey)\n") + if let preSharedKey = peer.preSharedKey?.hexKey { + wgSettings.append("preshared_key=\(preSharedKey)\n") + } + + let result = resolvedEndpoint.map(Self.reresolveEndpoint) + if case .success((_, let resolvedEndpoint)) = result { + if case .name = resolvedEndpoint.host { assert(false, "Endpoint is not resolved") } + wgSettings.append("endpoint=\(resolvedEndpoint.stringRepresentation)\n") + } + resolutionResults.append(result) + + let persistentKeepAlive = peer.persistentKeepAlive ?? 0 + wgSettings.append("persistent_keepalive_interval=\(persistentKeepAlive)\n") + if !peer.allowedIPs.isEmpty { + wgSettings.append("replace_allowed_ips=true\n") + peer.allowedIPs.forEach { wgSettings.append("allowed_ip=\($0.stringRepresentation)\n") } + } + } + return (wgSettings, resolutionResults) + } + + func generateNetworkSettings() -> NEPacketTunnelNetworkSettings { + /* iOS requires a tunnel endpoint, whereas in WireGuard it's valid for + * a tunnel to have no endpoint, or for there to be many endpoints, in + * which case, displaying a single one in settings doesn't really + * make sense. So, we fill it in with this placeholder, which is not + * a valid IP address that will actually route over the Internet. + */ + let networkSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "127.0.0.1") + + if !tunnelConfiguration.interface.dnsSearch.isEmpty || !tunnelConfiguration.interface.dns.isEmpty { + let dnsServerStrings = tunnelConfiguration.interface.dns.map { $0.stringRepresentation } + let dnsSettings = NEDNSSettings(servers: dnsServerStrings) + dnsSettings.searchDomains = tunnelConfiguration.interface.dnsSearch + if !tunnelConfiguration.interface.dns.isEmpty { + dnsSettings.matchDomains = [""] // All DNS queries must first go through the tunnel's DNS + } + networkSettings.dnsSettings = dnsSettings + } + + let mtu = tunnelConfiguration.interface.mtu ?? 0 + + /* 0 means automatic MTU. In theory, we should just do + * `networkSettings.tunnelOverheadBytes = 80` but in + * practice there are too many broken networks out there. + * Instead set it to 1280. Boohoo. Maybe someday we'll + * add a nob, maybe, or iOS will do probing for us. + */ + if mtu == 0 { + #if os(iOS) + networkSettings.mtu = NSNumber(value: 1280) + #elseif os(macOS) + networkSettings.tunnelOverheadBytes = 80 + #else + #error("Unimplemented") + #endif + } else { + networkSettings.mtu = NSNumber(value: mtu) + } + + let (ipv4Addresses, ipv6Addresses) = addresses() + let (ipv4IncludedRoutes, ipv6IncludedRoutes) = includedRoutes() + + let ipv4Settings = NEIPv4Settings(addresses: ipv4Addresses.map { $0.destinationAddress }, subnetMasks: ipv4Addresses.map { $0.destinationSubnetMask }) + ipv4Settings.includedRoutes = ipv4IncludedRoutes + networkSettings.ipv4Settings = ipv4Settings + + let ipv6Settings = NEIPv6Settings(addresses: ipv6Addresses.map { $0.destinationAddress }, networkPrefixLengths: ipv6Addresses.map { $0.destinationNetworkPrefixLength }) + ipv6Settings.includedRoutes = ipv6IncludedRoutes + networkSettings.ipv6Settings = ipv6Settings + + return networkSettings + } + + private func addresses() -> ([NEIPv4Route], [NEIPv6Route]) { + var ipv4Routes = [NEIPv4Route]() + var ipv6Routes = [NEIPv6Route]() + for addressRange in tunnelConfiguration.interface.addresses { + if addressRange.address is IPv4Address { + ipv4Routes.append(NEIPv4Route(destinationAddress: "\(addressRange.address)", subnetMask: "\(addressRange.subnetMask())")) + } else if addressRange.address is IPv6Address { + /* Big fat ugly hack for broken iOS networking stack: the smallest prefix that will have + * any effect on iOS is a /120, so we clamp everything above to /120. This is potentially + * very bad, if various network parameters were actually relying on that subnet being + * intentionally small. TODO: talk about this with upstream iOS devs. + */ + ipv6Routes.append(NEIPv6Route(destinationAddress: "\(addressRange.address)", networkPrefixLength: NSNumber(value: min(120, addressRange.networkPrefixLength)))) + } + } + return (ipv4Routes, ipv6Routes) + } + + private func includedRoutes() -> ([NEIPv4Route], [NEIPv6Route]) { + var ipv4IncludedRoutes = [NEIPv4Route]() + var ipv6IncludedRoutes = [NEIPv6Route]() + + for addressRange in tunnelConfiguration.interface.addresses { + if addressRange.address is IPv4Address { + let route = NEIPv4Route(destinationAddress: "\(addressRange.maskedAddress())", subnetMask: "\(addressRange.subnetMask())") + route.gatewayAddress = "\(addressRange.address)" + ipv4IncludedRoutes.append(route) + } else if addressRange.address is IPv6Address { + let route = NEIPv6Route(destinationAddress: "\(addressRange.maskedAddress())", networkPrefixLength: NSNumber(value: addressRange.networkPrefixLength)) + route.gatewayAddress = "\(addressRange.address)" + ipv6IncludedRoutes.append(route) + } + } + + for peer in tunnelConfiguration.peers { + for addressRange in peer.allowedIPs { + if addressRange.address is IPv4Address { + ipv4IncludedRoutes.append(NEIPv4Route(destinationAddress: "\(addressRange.address)", subnetMask: "\(addressRange.subnetMask())")) + } else if addressRange.address is IPv6Address { + ipv6IncludedRoutes.append(NEIPv6Route(destinationAddress: "\(addressRange.address)", networkPrefixLength: NSNumber(value: addressRange.networkPrefixLength))) + } + } + } + return (ipv4IncludedRoutes, ipv6IncludedRoutes) + } + + private class func reresolveEndpoint(endpoint: Endpoint) -> EndpointResolutionResult { + return Result { (endpoint, try endpoint.withReresolvedIP()) } + .mapError { error -> DNSResolutionError in + // swiftlint:disable:next force_cast + return error as! DNSResolutionError + } + } +} diff --git a/ios/WireGuardKit/Sources/WireGuardKit/PeerConfiguration.swift b/ios/WireGuardKit/Sources/WireGuardKit/PeerConfiguration.swift new file mode 100644 index 0000000..bc9276f --- /dev/null +++ b/ios/WireGuardKit/Sources/WireGuardKit/PeerConfiguration.swift @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT +// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved. + +import Foundation + +public struct PeerConfiguration { + public var publicKey: PublicKey + public var preSharedKey: PreSharedKey? + public var allowedIPs = [IPAddressRange]() + public var endpoint: Endpoint? + public var persistentKeepAlive: UInt16? + public var rxBytes: UInt64? + public var txBytes: UInt64? + public var lastHandshakeTime: Date? + + public init(publicKey: PublicKey) { + self.publicKey = publicKey + } +} + +extension PeerConfiguration: Equatable { + public static func == (lhs: PeerConfiguration, rhs: PeerConfiguration) -> Bool { + return lhs.publicKey == rhs.publicKey && + lhs.preSharedKey == rhs.preSharedKey && + Set(lhs.allowedIPs) == Set(rhs.allowedIPs) && + lhs.endpoint == rhs.endpoint && + lhs.persistentKeepAlive == rhs.persistentKeepAlive + } +} + +extension PeerConfiguration: Hashable { + public func hash(into hasher: inout Hasher) { + hasher.combine(publicKey) + hasher.combine(preSharedKey) + hasher.combine(Set(allowedIPs)) + hasher.combine(endpoint) + hasher.combine(persistentKeepAlive) + + } +} diff --git a/ios/WireGuardKit/Sources/WireGuardKit/PrivateKey.swift b/ios/WireGuardKit/Sources/WireGuardKit/PrivateKey.swift new file mode 100644 index 0000000..79d389d --- /dev/null +++ b/ios/WireGuardKit/Sources/WireGuardKit/PrivateKey.swift @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: MIT +// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved. + +import Foundation + +#if SWIFT_PACKAGE +import WireGuardKitC +#endif + +/// The class describing a private key used by WireGuard. +public class PrivateKey: BaseKey { + /// Derived public key + public var publicKey: PublicKey { + return rawValue.withUnsafeBytes { (privateKeyBufferPointer: UnsafeRawBufferPointer) -> PublicKey in + var publicKeyData = Data(repeating: 0, count: Int(WG_KEY_LEN)) + let privateKeyBytes = privateKeyBufferPointer.baseAddress!.assumingMemoryBound(to: UInt8.self) + + publicKeyData.withUnsafeMutableBytes { (publicKeyBufferPointer: UnsafeMutableRawBufferPointer) in + let publicKeyBytes = publicKeyBufferPointer.baseAddress!.assumingMemoryBound(to: UInt8.self) + curve25519_derive_public_key(publicKeyBytes, privateKeyBytes) + } + + return PublicKey(rawValue: publicKeyData)! + } + } + + /// Initialize new private key + convenience public init() { + var privateKeyData = Data(repeating: 0, count: Int(WG_KEY_LEN)) + privateKeyData.withUnsafeMutableBytes { (rawBufferPointer: UnsafeMutableRawBufferPointer) in + let privateKeyBytes = rawBufferPointer.baseAddress!.assumingMemoryBound(to: UInt8.self) + curve25519_generate_private_key(privateKeyBytes) + } + self.init(rawValue: privateKeyData)! + } +} + +/// The class describing a public key used by WireGuard. +public class PublicKey: BaseKey {} + +/// The class describing a pre-shared key used by WireGuard. +public class PreSharedKey: BaseKey {} + +/// The base key implementation. Should not be used directly. +public class BaseKey: RawRepresentable, Equatable, Hashable { + /// Raw key representation + public let rawValue: Data + + /// Hex encoded representation + public var hexKey: String { + return rawValue.withUnsafeBytes { (rawBufferPointer: UnsafeRawBufferPointer) -> String in + let inBytes = rawBufferPointer.baseAddress!.assumingMemoryBound(to: UInt8.self) + var outBytes = [CChar](repeating: 0, count: Int(WG_KEY_LEN_HEX)) + key_to_hex(&outBytes, inBytes) + return String(cString: outBytes, encoding: .ascii)! + } + } + + /// Base64 encoded representation + public var base64Key: String { + return rawValue.withUnsafeBytes { (rawBufferPointer: UnsafeRawBufferPointer) -> String in + let inBytes = rawBufferPointer.baseAddress!.assumingMemoryBound(to: UInt8.self) + var outBytes = [CChar](repeating: 0, count: Int(WG_KEY_LEN_BASE64)) + key_to_base64(&outBytes, inBytes) + return String(cString: outBytes, encoding: .ascii)! + } + } + + /// Initialize the key with existing raw representation + required public init?(rawValue: Data) { + if rawValue.count == WG_KEY_LEN { + self.rawValue = rawValue + } else { + return nil + } + } + + /// Initialize the key with hex representation + public convenience init?(hexKey: String) { + var bytes = Data(repeating: 0, count: Int(WG_KEY_LEN)) + let success = bytes.withUnsafeMutableBytes { (bufferPointer: UnsafeMutableRawBufferPointer) -> Bool in + return key_from_hex(bufferPointer.baseAddress!.assumingMemoryBound(to: UInt8.self), hexKey) + } + if success { + self.init(rawValue: bytes) + } else { + return nil + } + } + + /// Initialize the key with base64 representation + public convenience init?(base64Key: String) { + var bytes = Data(repeating: 0, count: Int(WG_KEY_LEN)) + let success = bytes.withUnsafeMutableBytes { (bufferPointer: UnsafeMutableRawBufferPointer) -> Bool in + return key_from_base64(bufferPointer.baseAddress!.assumingMemoryBound(to: UInt8.self), base64Key) + } + if success { + self.init(rawValue: bytes) + } else { + return nil + } + } + + public static func == (lhs: BaseKey, rhs: BaseKey) -> Bool { + return lhs.rawValue.withUnsafeBytes { (lhsBytes: UnsafeRawBufferPointer) -> Bool in + return rhs.rawValue.withUnsafeBytes { (rhsBytes: UnsafeRawBufferPointer) -> Bool in + return key_eq( + lhsBytes.baseAddress!.assumingMemoryBound(to: UInt8.self), + rhsBytes.baseAddress!.assumingMemoryBound(to: UInt8.self) + ) + } + } + } +} diff --git a/ios/WireGuardKit/Sources/WireGuardKit/TunnelConfiguration.swift b/ios/WireGuardKit/Sources/WireGuardKit/TunnelConfiguration.swift new file mode 100644 index 0000000..a59ba2f --- /dev/null +++ b/ios/WireGuardKit/Sources/WireGuardKit/TunnelConfiguration.swift @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved. + +import Foundation + +public final class TunnelConfiguration { + public var name: String? + public var interface: InterfaceConfiguration + public let peers: [PeerConfiguration] + + public init(name: String?, interface: InterfaceConfiguration, peers: [PeerConfiguration]) { + self.interface = interface + self.peers = peers + self.name = name + + let peerPublicKeysArray = peers.map { $0.publicKey } + let peerPublicKeysSet = Set(peerPublicKeysArray) + if peerPublicKeysArray.count != peerPublicKeysSet.count { + fatalError("Two or more peers cannot have the same public key") + } + } +} + +extension TunnelConfiguration: Equatable { + public static func == (lhs: TunnelConfiguration, rhs: TunnelConfiguration) -> Bool { + return lhs.name == rhs.name && + lhs.interface == rhs.interface && + Set(lhs.peers) == Set(rhs.peers) + } +} diff --git a/ios/WireGuardKit/Sources/WireGuardKit/WireGuardAdapter.swift b/ios/WireGuardKit/Sources/WireGuardKit/WireGuardAdapter.swift new file mode 100644 index 0000000..f7be19b --- /dev/null +++ b/ios/WireGuardKit/Sources/WireGuardKit/WireGuardAdapter.swift @@ -0,0 +1,487 @@ +// SPDX-License-Identifier: MIT +// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved. + +import Foundation +import NetworkExtension + +#if SWIFT_PACKAGE +import WireGuardKitGo +import WireGuardKitC +#endif + +public enum WireGuardAdapterError: Error { + /// Failure to locate tunnel file descriptor. + case cannotLocateTunnelFileDescriptor + + /// Failure to perform an operation in such state. + case invalidState + + /// Failure to resolve endpoints. + case dnsResolution([DNSResolutionError]) + + /// Failure to set network settings. + case setNetworkSettings(Error) + + /// Failure to start WireGuard backend. + case startWireGuardBackend(Int32) +} + +/// Enum representing internal state of the `WireGuardAdapter` +private enum State { + /// The tunnel is stopped + case stopped + + /// The tunnel is up and running + case started(_ handle: Int32, _ settingsGenerator: PacketTunnelSettingsGenerator) + + /// The tunnel is temporarily shutdown due to device going offline + case temporaryShutdown(_ settingsGenerator: PacketTunnelSettingsGenerator) +} + +public class WireGuardAdapter { + public typealias LogHandler = (WireGuardLogLevel, String) -> Void + + /// Network routes monitor. + private var networkMonitor: NWPathMonitor? + + /// Packet tunnel provider. + private weak var packetTunnelProvider: NEPacketTunnelProvider? + + /// Log handler closure. + private let logHandler: LogHandler + + /// Private queue used to synchronize access to `WireGuardAdapter` members. + private let workQueue = DispatchQueue(label: "WireGuardAdapterWorkQueue") + + /// Adapter state. + private var state: State = .stopped + + /// Tunnel device file descriptor. + private var tunnelFileDescriptor: Int32? { + var ctlInfo = ctl_info() + withUnsafeMutablePointer(to: &ctlInfo.ctl_name) { + $0.withMemoryRebound(to: CChar.self, capacity: MemoryLayout.size(ofValue: $0.pointee)) { + _ = strcpy($0, "com.apple.net.utun_control") + } + } + for fd: Int32 in 0...1024 { + var addr = sockaddr_ctl() + var ret: Int32 = -1 + var len = socklen_t(MemoryLayout.size(ofValue: addr)) + withUnsafeMutablePointer(to: &addr) { + $0.withMemoryRebound(to: sockaddr.self, capacity: 1) { + ret = getpeername(fd, $0, &len) + } + } + if ret != 0 || addr.sc_family != AF_SYSTEM { + continue + } + if ctlInfo.ctl_id == 0 { + ret = ioctl(fd, CTLIOCGINFO, &ctlInfo) + if ret != 0 { + continue + } + } + if addr.sc_id == ctlInfo.ctl_id { + return fd + } + } + return nil + } + + /// Returns a WireGuard version. + class var backendVersion: String { + guard let ver = wgVersion() else { return "unknown" } + let str = String(cString: ver) + free(UnsafeMutableRawPointer(mutating: ver)) + return str + } + + /// Returns the tunnel device interface name, or nil on error. + /// - Returns: String. + public var interfaceName: String? { + guard let tunnelFileDescriptor = self.tunnelFileDescriptor else { return nil } + + var buffer = [UInt8](repeating: 0, count: Int(IFNAMSIZ)) + + return buffer.withUnsafeMutableBufferPointer { mutableBufferPointer in + guard let baseAddress = mutableBufferPointer.baseAddress else { return nil } + + var ifnameSize = socklen_t(IFNAMSIZ) + let result = getsockopt( + tunnelFileDescriptor, + 2 /* SYSPROTO_CONTROL */, + 2 /* UTUN_OPT_IFNAME */, + baseAddress, + &ifnameSize) + + if result == 0 { + return String(cString: baseAddress) + } else { + return nil + } + } + } + + // MARK: - Initialization + + /// Designated initializer. + /// - Parameter packetTunnelProvider: an instance of `NEPacketTunnelProvider`. Internally stored + /// as a weak reference. + /// - Parameter logHandler: a log handler closure. + public init(with packetTunnelProvider: NEPacketTunnelProvider, logHandler: @escaping LogHandler) { + self.packetTunnelProvider = packetTunnelProvider + self.logHandler = logHandler + + setupLogHandler() + } + + deinit { + // Force remove logger to make sure that no further calls to the instance of this class + // can happen after deallocation. + wgSetLogger(nil, nil) + + // Cancel network monitor + networkMonitor?.cancel() + + // Shutdown the tunnel + if case .started(let handle, _) = self.state { + wgTurnOff(handle) + } + } + + // MARK: - Public methods + + /// Returns a runtime configuration from WireGuard. + /// - Parameter completionHandler: completion handler. + public func getRuntimeConfiguration(completionHandler: @escaping (String?) -> Void) { + workQueue.async { + guard case .started(let handle, _) = self.state else { + completionHandler(nil) + return + } + + if let settings = wgGetConfig(handle) { + completionHandler(String(cString: settings)) + free(settings) + } else { + completionHandler(nil) + } + } + } + + /// Start the tunnel tunnel. + /// - Parameters: + /// - tunnelConfiguration: tunnel configuration. + /// - completionHandler: completion handler. + public func start(tunnelConfiguration: TunnelConfiguration, completionHandler: @escaping (WireGuardAdapterError?) -> Void) { + workQueue.async { + guard case .stopped = self.state else { + completionHandler(.invalidState) + return + } + + let networkMonitor = NWPathMonitor() + networkMonitor.pathUpdateHandler = { [weak self] path in + self?.didReceivePathUpdate(path: path) + } + networkMonitor.start(queue: self.workQueue) + + do { + let settingsGenerator = try self.makeSettingsGenerator(with: tunnelConfiguration) + try self.setNetworkSettings(settingsGenerator.generateNetworkSettings()) + + let (wgConfig, resolutionResults) = settingsGenerator.uapiConfiguration() + self.logEndpointResolutionResults(resolutionResults) + + self.state = .started( + try self.startWireGuardBackend(wgConfig: wgConfig), + settingsGenerator + ) + self.networkMonitor = networkMonitor + completionHandler(nil) + } catch let error as WireGuardAdapterError { + networkMonitor.cancel() + completionHandler(error) + } catch { + fatalError() + } + } + } + + /// Stop the tunnel. + /// - Parameter completionHandler: completion handler. + public func stop(completionHandler: @escaping (WireGuardAdapterError?) -> Void) { + workQueue.async { + switch self.state { + case .started(let handle, _): + wgTurnOff(handle) + + case .temporaryShutdown: + break + + case .stopped: + completionHandler(.invalidState) + return + } + + self.networkMonitor?.cancel() + self.networkMonitor = nil + + self.state = .stopped + + completionHandler(nil) + } + } + + /// Update runtime configuration. + /// - Parameters: + /// - tunnelConfiguration: tunnel configuration. + /// - completionHandler: completion handler. + public func update(tunnelConfiguration: TunnelConfiguration, completionHandler: @escaping (WireGuardAdapterError?) -> Void) { + workQueue.async { + if case .stopped = self.state { + completionHandler(.invalidState) + return + } + + // Tell the system that the tunnel is going to reconnect using new WireGuard + // configuration. + // This will broadcast the `NEVPNStatusDidChange` notification to the GUI process. + self.packetTunnelProvider?.reasserting = true + defer { + self.packetTunnelProvider?.reasserting = false + } + + do { + let settingsGenerator = try self.makeSettingsGenerator(with: tunnelConfiguration) + try self.setNetworkSettings(settingsGenerator.generateNetworkSettings()) + + switch self.state { + case .started(let handle, _): + let (wgConfig, resolutionResults) = settingsGenerator.uapiConfiguration() + self.logEndpointResolutionResults(resolutionResults) + + wgSetConfig(handle, wgConfig) + #if os(iOS) + wgDisableSomeRoamingForBrokenMobileSemantics(handle) + #endif + + self.state = .started(handle, settingsGenerator) + + case .temporaryShutdown: + self.state = .temporaryShutdown(settingsGenerator) + + case .stopped: + fatalError() + } + + completionHandler(nil) + } catch let error as WireGuardAdapterError { + completionHandler(error) + } catch { + fatalError() + } + } + } + + // MARK: - Private methods + + /// Setup WireGuard log handler. + private func setupLogHandler() { + let context = Unmanaged.passUnretained(self).toOpaque() + wgSetLogger(context) { context, logLevel, message in + guard let context = context, let message = message else { return } + + let unretainedSelf = Unmanaged.fromOpaque(context) + .takeUnretainedValue() + + let swiftString = String(cString: message).trimmingCharacters(in: .newlines) + let tunnelLogLevel = WireGuardLogLevel(rawValue: logLevel) ?? .verbose + + unretainedSelf.logHandler(tunnelLogLevel, swiftString) + } + } + + /// Set network tunnel configuration. + /// This method ensures that the call to `setTunnelNetworkSettings` does not time out, as in + /// certain scenarios the completion handler given to it may not be invoked by the system. + /// + /// - Parameters: + /// - networkSettings: an instance of type `NEPacketTunnelNetworkSettings`. + /// - Throws: an error of type `WireGuardAdapterError`. + /// - Returns: `PacketTunnelSettingsGenerator`. + private func setNetworkSettings(_ networkSettings: NEPacketTunnelNetworkSettings) throws { + var systemError: Error? + let condition = NSCondition() + + // Activate the condition + condition.lock() + defer { condition.unlock() } + + self.packetTunnelProvider?.setTunnelNetworkSettings(networkSettings) { error in + systemError = error + condition.signal() + } + + // Packet tunnel's `setTunnelNetworkSettings` times out in certain + // scenarios & never calls the given callback. + let setTunnelNetworkSettingsTimeout: TimeInterval = 5 // seconds + + if condition.wait(until: Date().addingTimeInterval(setTunnelNetworkSettingsTimeout)) { + if let systemError = systemError { + throw WireGuardAdapterError.setNetworkSettings(systemError) + } + } else { + self.logHandler(.error, "setTunnelNetworkSettings timed out after 5 seconds; proceeding anyway") + } + } + + /// Resolve peers of the given tunnel configuration. + /// - Parameter tunnelConfiguration: tunnel configuration. + /// - Throws: an error of type `WireGuardAdapterError`. + /// - Returns: The list of resolved endpoints. + private func resolvePeers(for tunnelConfiguration: TunnelConfiguration) throws -> [Endpoint?] { + let endpoints = tunnelConfiguration.peers.map { $0.endpoint } + let resolutionResults = DNSResolver.resolveSync(endpoints: endpoints) + let resolutionErrors = resolutionResults.compactMap { result -> DNSResolutionError? in + if case .failure(let error) = result { + return error + } else { + return nil + } + } + assert(endpoints.count == resolutionResults.count) + guard resolutionErrors.isEmpty else { + throw WireGuardAdapterError.dnsResolution(resolutionErrors) + } + + let resolvedEndpoints = resolutionResults.map { result -> Endpoint? in + // swiftlint:disable:next force_try + return try! result?.get() + } + + return resolvedEndpoints + } + + /// Start WireGuard backend. + /// - Parameter wgConfig: WireGuard configuration + /// - Throws: an error of type `WireGuardAdapterError` + /// - Returns: tunnel handle + private func startWireGuardBackend(wgConfig: String) throws -> Int32 { + guard let tunnelFileDescriptor = self.tunnelFileDescriptor else { + throw WireGuardAdapterError.cannotLocateTunnelFileDescriptor + } + + let handle = wgTurnOn(wgConfig, tunnelFileDescriptor) + if handle < 0 { + throw WireGuardAdapterError.startWireGuardBackend(handle) + } + #if os(iOS) + wgDisableSomeRoamingForBrokenMobileSemantics(handle) + #endif + return handle + } + + /// Resolves the hostnames in the given tunnel configuration and return settings generator. + /// - Parameter tunnelConfiguration: an instance of type `TunnelConfiguration`. + /// - Throws: an error of type `WireGuardAdapterError`. + /// - Returns: an instance of type `PacketTunnelSettingsGenerator`. + private func makeSettingsGenerator(with tunnelConfiguration: TunnelConfiguration) throws -> PacketTunnelSettingsGenerator { + return PacketTunnelSettingsGenerator( + tunnelConfiguration: tunnelConfiguration, + resolvedEndpoints: try self.resolvePeers(for: tunnelConfiguration) + ) + } + + /// Log DNS resolution results. + /// - Parameter resolutionErrors: an array of type `[DNSResolutionError]`. + private func logEndpointResolutionResults(_ resolutionResults: [EndpointResolutionResult?]) { + for case .some(let result) in resolutionResults { + switch result { + case .success((let sourceEndpoint, let resolvedEndpoint)): + if sourceEndpoint.host == resolvedEndpoint.host { + self.logHandler(.verbose, "DNS64: mapped \(sourceEndpoint.host) to itself.") + } else { + self.logHandler(.verbose, "DNS64: mapped \(sourceEndpoint.host) to \(resolvedEndpoint.host)") + } + case .failure(let resolutionError): + self.logHandler(.error, "Failed to resolve endpoint \(resolutionError.address): \(resolutionError.errorDescription ?? "(nil)")") + } + } + } + + /// Helper method used by network path monitor. + /// - Parameter path: new network path + private func didReceivePathUpdate(path: Network.NWPath) { + self.logHandler(.verbose, "Network change detected with \(path.status) route and interface order \(path.availableInterfaces)") + + #if os(macOS) + if case .started(let handle, _) = self.state { + wgBumpSockets(handle) + } + #elseif os(iOS) + switch self.state { + case .started(let handle, let settingsGenerator): + if path.status.isSatisfiable { + let (wgConfig, resolutionResults) = settingsGenerator.endpointUapiConfiguration() + self.logEndpointResolutionResults(resolutionResults) + + wgSetConfig(handle, wgConfig) + wgDisableSomeRoamingForBrokenMobileSemantics(handle) + wgBumpSockets(handle) + } else { + self.logHandler(.verbose, "Connectivity offline, pausing backend.") + + self.state = .temporaryShutdown(settingsGenerator) + wgTurnOff(handle) + } + + case .temporaryShutdown(let settingsGenerator): + guard path.status.isSatisfiable else { return } + + self.logHandler(.verbose, "Connectivity online, resuming backend.") + + do { + try self.setNetworkSettings(settingsGenerator.generateNetworkSettings()) + + let (wgConfig, resolutionResults) = settingsGenerator.uapiConfiguration() + self.logEndpointResolutionResults(resolutionResults) + + self.state = .started( + try self.startWireGuardBackend(wgConfig: wgConfig), + settingsGenerator + ) + } catch { + self.logHandler(.error, "Failed to restart backend: \(error.localizedDescription)") + } + + case .stopped: + // no-op + break + } + #else + #error("Unsupported") + #endif + } +} + +/// A enum describing WireGuard log levels defined in `api-apple.go`. +public enum WireGuardLogLevel: Int32 { + case verbose = 0 + case error = 1 +} + +private extension Network.NWPath.Status { + /// Returns `true` if the path is potentially satisfiable. + var isSatisfiable: Bool { + switch self { + case .requiresConnection, .satisfied: + return true + case .unsatisfied: + return false + @unknown default: + return true + } + } +} diff --git a/ios/WireGuardKit/Sources/WireGuardKitC/WireGuardKitC.h b/ios/WireGuardKit/Sources/WireGuardKitC/WireGuardKitC.h new file mode 100644 index 0000000..1b8974d --- /dev/null +++ b/ios/WireGuardKit/Sources/WireGuardKitC/WireGuardKitC.h @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved. + +#include +#include "key.h" +#include "x25519.h" + +/* From */ +#define CTLIOCGINFO 0xc0644e03UL +struct ctl_info { + u_int32_t ctl_id; + char ctl_name[96]; +}; +struct sockaddr_ctl { + u_char sc_len; + u_char sc_family; + u_int16_t ss_sysaddr; + u_int32_t sc_id; + u_int32_t sc_unit; + u_int32_t sc_reserved[5]; +}; diff --git a/ios/WireGuardKit/Sources/WireGuardKitC/key.c b/ios/WireGuardKit/Sources/WireGuardKitC/key.c new file mode 100644 index 0000000..84e7f16 --- /dev/null +++ b/ios/WireGuardKit/Sources/WireGuardKitC/key.c @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2015-2019 Jason A. Donenfeld . All Rights Reserved. + * + * This is a specialized constant-time base64/hex implementation that resists side-channel attacks. + */ + +#include +#include "key.h" + +static inline void encode_base64(char dest[static 4], const uint8_t src[static 3]) +{ + const uint8_t input[] = { (src[0] >> 2) & 63, ((src[0] << 4) | (src[1] >> 4)) & 63, ((src[1] << 2) | (src[2] >> 6)) & 63, src[2] & 63 }; + + for (unsigned int i = 0; i < 4; ++i) + dest[i] = input[i] + 'A' + + (((25 - input[i]) >> 8) & 6) + - (((51 - input[i]) >> 8) & 75) + - (((61 - input[i]) >> 8) & 15) + + (((62 - input[i]) >> 8) & 3); + +} + +void key_to_base64(char base64[static WG_KEY_LEN_BASE64], const uint8_t key[static WG_KEY_LEN]) +{ + unsigned int i; + + for (i = 0; i < WG_KEY_LEN / 3; ++i) + encode_base64(&base64[i * 4], &key[i * 3]); + encode_base64(&base64[i * 4], (const uint8_t[]){ key[i * 3 + 0], key[i * 3 + 1], 0 }); + base64[WG_KEY_LEN_BASE64 - 2] = '='; + base64[WG_KEY_LEN_BASE64 - 1] = '\0'; +} + +static inline int decode_base64(const char src[static 4]) +{ + int val = 0; + + for (unsigned int i = 0; i < 4; ++i) + val |= (-1 + + ((((('A' - 1) - src[i]) & (src[i] - ('Z' + 1))) >> 8) & (src[i] - 64)) + + ((((('a' - 1) - src[i]) & (src[i] - ('z' + 1))) >> 8) & (src[i] - 70)) + + ((((('0' - 1) - src[i]) & (src[i] - ('9' + 1))) >> 8) & (src[i] + 5)) + + ((((('+' - 1) - src[i]) & (src[i] - ('+' + 1))) >> 8) & 63) + + ((((('/' - 1) - src[i]) & (src[i] - ('/' + 1))) >> 8) & 64) + ) << (18 - 6 * i); + return val; +} + +bool key_from_base64(uint8_t key[static WG_KEY_LEN], const char *base64) +{ + unsigned int i; + volatile uint8_t ret = 0; + int val; + + if (strlen(base64) != WG_KEY_LEN_BASE64 - 1 || base64[WG_KEY_LEN_BASE64 - 2] != '=') + return false; + + for (i = 0; i < WG_KEY_LEN / 3; ++i) { + val = decode_base64(&base64[i * 4]); + ret |= (uint32_t)val >> 31; + key[i * 3 + 0] = (val >> 16) & 0xff; + key[i * 3 + 1] = (val >> 8) & 0xff; + key[i * 3 + 2] = val & 0xff; + } + val = decode_base64((const char[]){ base64[i * 4 + 0], base64[i * 4 + 1], base64[i * 4 + 2], 'A' }); + ret |= ((uint32_t)val >> 31) | (val & 0xff); + key[i * 3 + 0] = (val >> 16) & 0xff; + key[i * 3 + 1] = (val >> 8) & 0xff; + + return 1 & ((ret - 1) >> 8); +} + +void key_to_hex(char hex[static WG_KEY_LEN_HEX], const uint8_t key[static WG_KEY_LEN]) +{ + unsigned int i; + + for (i = 0; i < WG_KEY_LEN; ++i) { + hex[i * 2] = 87U + (key[i] >> 4) + ((((key[i] >> 4) - 10U) >> 8) & ~38U); + hex[i * 2 + 1] = 87U + (key[i] & 0xf) + ((((key[i] & 0xf) - 10U) >> 8) & ~38U); + } + hex[i * 2] = '\0'; +} + +bool key_from_hex(uint8_t key[static WG_KEY_LEN], const char *hex) +{ + uint8_t c, c_acc, c_alpha0, c_alpha, c_num0, c_num, c_val; + volatile uint8_t ret = 0; + + if (strlen(hex) != WG_KEY_LEN_HEX - 1) + return false; + + for (unsigned int i = 0; i < WG_KEY_LEN_HEX - 1; i += 2) { + c = (uint8_t)hex[i]; + c_num = c ^ 48U; + c_num0 = (c_num - 10U) >> 8; + c_alpha = (c & ~32U) - 55U; + c_alpha0 = ((c_alpha - 10U) ^ (c_alpha - 16U)) >> 8; + ret |= ((c_num0 | c_alpha0) - 1) >> 8; + c_val = (c_num0 & c_num) | (c_alpha0 & c_alpha); + c_acc = c_val * 16U; + + c = (uint8_t)hex[i + 1]; + c_num = c ^ 48U; + c_num0 = (c_num - 10U) >> 8; + c_alpha = (c & ~32U) - 55U; + c_alpha0 = ((c_alpha - 10U) ^ (c_alpha - 16U)) >> 8; + ret |= ((c_num0 | c_alpha0) - 1) >> 8; + c_val = (c_num0 & c_num) | (c_alpha0 & c_alpha); + key[i / 2] = c_acc | c_val; + } + + return 1 & ((ret - 1) >> 8); +} + +bool key_eq(const uint8_t key1[static WG_KEY_LEN], const uint8_t key2[static WG_KEY_LEN]) +{ + volatile uint8_t acc = 0; + for (unsigned int i = 0; i < WG_KEY_LEN; ++i) { + acc |= key1[i] ^ key2[i]; + asm volatile("" : "=r"(acc) : "0"(acc)); + } + return 1 & ((acc - 1) >> 8); +} diff --git a/ios/WireGuardKit/Sources/WireGuardKitC/key.h b/ios/WireGuardKit/Sources/WireGuardKitC/key.h new file mode 100644 index 0000000..5353ade --- /dev/null +++ b/ios/WireGuardKit/Sources/WireGuardKitC/key.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (C) 2015-2019 Jason A. Donenfeld . All Rights Reserved. + */ + +#ifndef KEY_H +#define KEY_H + +#include +#include + +#define WG_KEY_LEN (32) +#define WG_KEY_LEN_BASE64 (45) +#define WG_KEY_LEN_HEX (65) + +void key_to_base64(char base64[static WG_KEY_LEN_BASE64], const uint8_t key[static WG_KEY_LEN]); +bool key_from_base64(uint8_t key[static WG_KEY_LEN], const char *base64); + +void key_to_hex(char hex[static WG_KEY_LEN_HEX], const uint8_t key[static WG_KEY_LEN]); +bool key_from_hex(uint8_t key[static WG_KEY_LEN], const char *hex); + +bool key_eq(const uint8_t key1[static WG_KEY_LEN], const uint8_t key2[static WG_KEY_LEN]); + +#endif diff --git a/ios/WireGuardKit/Sources/WireGuardKitC/module.modulemap b/ios/WireGuardKit/Sources/WireGuardKitC/module.modulemap new file mode 100644 index 0000000..26b45bf --- /dev/null +++ b/ios/WireGuardKit/Sources/WireGuardKitC/module.modulemap @@ -0,0 +1,4 @@ +module WireGuardKitC { + umbrella header "WireGuardKitC.h" + export * +} diff --git a/ios/WireGuardKit/Sources/WireGuardKitC/x25519.c b/ios/WireGuardKit/Sources/WireGuardKitC/x25519.c new file mode 100644 index 0000000..7793299 --- /dev/null +++ b/ios/WireGuardKit/Sources/WireGuardKitC/x25519.c @@ -0,0 +1,178 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2015-2019 Jason A. Donenfeld . All Rights Reserved. + * + * Curve25519 ECDH functions, based on TweetNaCl but cleaned up. + */ + +#include +#include +#include +#include + +#include "x25519.h" + +typedef int64_t fe[16]; + +static inline void carry(fe o) +{ + int i; + + for (i = 0; i < 16; ++i) { + o[(i + 1) % 16] += (i == 15 ? 38 : 1) * (o[i] >> 16); + o[i] &= 0xffff; + } +} + +static inline void cswap(fe p, fe q, int b) +{ + int i; + int64_t t, c = ~(b - 1); + + for (i = 0; i < 16; ++i) { + t = c & (p[i] ^ q[i]); + p[i] ^= t; + q[i] ^= t; + } +} + +static inline void pack(uint8_t *o, const fe n) +{ + int i, j, b; + fe m, t; + + memcpy(t, n, sizeof(t)); + carry(t); + carry(t); + carry(t); + for (j = 0; j < 2; ++j) { + m[0] = t[0] - 0xffed; + for (i = 1; i < 15; ++i) { + m[i] = t[i] - 0xffff - ((m[i - 1] >> 16) & 1); + m[i - 1] &= 0xffff; + } + m[15] = t[15] - 0x7fff - ((m[14] >> 16) & 1); + b = (m[15] >> 16) & 1; + m[14] &= 0xffff; + cswap(t, m, 1 - b); + } + for (i = 0; i < 16; ++i) { + o[2 * i] = t[i] & 0xff; + o[2 * i + 1] = t[i] >> 8; + } +} + +static inline void unpack(fe o, const uint8_t *n) +{ + int i; + + for (i = 0; i < 16; ++i) + o[i] = n[2 * i] + ((int64_t)n[2 * i + 1] << 8); + o[15] &= 0x7fff; +} + +static inline void add(fe o, const fe a, const fe b) +{ + int i; + + for (i = 0; i < 16; ++i) + o[i] = a[i] + b[i]; +} + +static inline void subtract(fe o, const fe a, const fe b) +{ + int i; + + for (i = 0; i < 16; ++i) + o[i] = a[i] - b[i]; +} + +static inline void multmod(fe o, const fe a, const fe b) +{ + int i, j; + int64_t t[31] = { 0 }; + + for (i = 0; i < 16; ++i) { + for (j = 0; j < 16; ++j) + t[i + j] += a[i] * b[j]; + } + for (i = 0; i < 15; ++i) + t[i] += 38 * t[i + 16]; + memcpy(o, t, sizeof(fe)); + carry(o); + carry(o); +} + +static inline void invert(fe o, const fe i) +{ + fe c; + int a; + + memcpy(c, i, sizeof(c)); + for (a = 253; a >= 0; --a) { + multmod(c, c, c); + if (a != 2 && a != 4) + multmod(c, c, i); + } + memcpy(o, c, sizeof(fe)); +} + +static void curve25519_shared_secret(uint8_t shared_secret[32], const uint8_t private_key[32], const uint8_t public_key[32]) +{ + static const fe a24 = { 0xdb41, 1 }; + uint8_t z[32]; + int64_t r; + int i; + fe a = { 1 }, b, c = { 0 }, d = { 1 }, e, f, x; + + memcpy(z, private_key, sizeof(z)); + + z[31] = (z[31] & 127) | 64; + z[0] &= 248; + + unpack(x, public_key); + memcpy(b, x, sizeof(b)); + + for (i = 254; i >= 0; --i) { + r = (z[i >> 3] >> (i & 7)) & 1; + cswap(a, b, (int)r); + cswap(c, d, (int)r); + add(e, a, c); + subtract(a, a, c); + add(c, b, d); + subtract(b, b, d); + multmod(d, e, e); + multmod(f, a, a); + multmod(a, c, a); + multmod(c, b, e); + add(e, a, c); + subtract(a, a, c); + multmod(b, a, a); + subtract(c, d, f); + multmod(a, c, a24); + add(a, a, d); + multmod(c, c, a); + multmod(a, d, f); + multmod(d, b, x); + multmod(b, e, e); + cswap(a, b, (int)r); + cswap(c, d, (int)r); + } + invert(c, c); + multmod(a, a, c); + pack(shared_secret, a); +} + +void curve25519_derive_public_key(uint8_t public_key[32], const uint8_t private_key[32]) +{ + static const uint8_t basepoint[32] = { 9 }; + + curve25519_shared_secret(public_key, private_key, basepoint); +} + +void curve25519_generate_private_key(uint8_t private_key[32]) +{ + assert(CCRandomGenerateBytes(private_key, 32) == kCCSuccess); + private_key[31] = (private_key[31] & 127) | 64; + private_key[0] &= 248; +} diff --git a/ios/WireGuardKit/Sources/WireGuardKitC/x25519.h b/ios/WireGuardKit/Sources/WireGuardKitC/x25519.h new file mode 100644 index 0000000..7d8440d --- /dev/null +++ b/ios/WireGuardKit/Sources/WireGuardKitC/x25519.h @@ -0,0 +1,7 @@ +#ifndef X25519_H +#define X25519_H + +void curve25519_derive_public_key(unsigned char public_key[32], const unsigned char private_key[32]); +void curve25519_generate_private_key(unsigned char private_key[32]); + +#endif diff --git a/ios/WireGuardKit/Sources/WireGuardKitGo/dummy.c b/ios/WireGuardKit/Sources/WireGuardKitGo/dummy.c new file mode 100644 index 0000000..d15abba --- /dev/null +++ b/ios/WireGuardKit/Sources/WireGuardKitGo/dummy.c @@ -0,0 +1 @@ +// Empty diff --git a/ios/WireGuardKit/Sources/WireGuardKitGo/module.modulemap b/ios/WireGuardKit/Sources/WireGuardKitGo/module.modulemap new file mode 100644 index 0000000..2ca3916 --- /dev/null +++ b/ios/WireGuardKit/Sources/WireGuardKitGo/module.modulemap @@ -0,0 +1,5 @@ +module WireGuardKitGo { + umbrella header "wireguard.h" + link "wg-go" + export * +} diff --git a/ios/WireGuardKit/Sources/WireGuardKitGo/wireguard.h b/ios/WireGuardKit/Sources/WireGuardKitGo/wireguard.h new file mode 100644 index 0000000..fdb66c2 --- /dev/null +++ b/ios/WireGuardKit/Sources/WireGuardKitGo/wireguard.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2018-2023 WireGuard LLC. All Rights Reserved. + */ + +#ifndef WIREGUARD_H +#define WIREGUARD_H + +#include +#include +#include + +typedef void(*logger_fn_t)(void *context, int level, const char *msg); +extern void wgSetLogger(void *context, logger_fn_t logger_fn); +extern int wgTurnOn(const char *settings, int32_t tun_fd); +extern void wgTurnOff(int handle); +extern int64_t wgSetConfig(int handle, const char *settings); +extern char *wgGetConfig(int handle); +extern void wgBumpSockets(int handle); +extern void wgDisableSomeRoamingForBrokenMobileSemantics(int handle); +extern const char *wgVersion(); + +#endif diff --git a/ios/project.yml b/ios/project.yml new file mode 100644 index 0000000..4c671c5 --- /dev/null +++ b/ios/project.yml @@ -0,0 +1,79 @@ +# xcodegen spec: `xcodegen generate` -> PCAPhobos.xcodeproj +# Signing: set DEVELOPMENT_TEAM (paid Apple Developer account — Network Extension needs it). +name: PCAPhobos +options: + bundleIdPrefix: com.bobyrev + deploymentTarget: + iOS: "16.0" +packages: + WireGuardKit: + path: WireGuardKit +settings: + base: + MARKETING_VERSION: "1.4.1" + CURRENT_PROJECT_VERSION: "141" + DEVELOPMENT_TEAM: "" + CODE_SIGN_STYLE: Automatic +targets: + PCAPhobos: + type: application + platform: iOS + sources: + - App + - Shared + resources: + - App/Assets.xcassets + dependencies: + - target: PhobosTunnel + entitlements: + path: App/PCAPhobos.entitlements + properties: + com.apple.developer.networking.networkextension: [packet-tunnel-provider] + info: + path: App/Info.plist + properties: + CFBundleDisplayName: PCA Phobos + UILaunchScreen: {} + UISupportedInterfaceOrientations: [UIInterfaceOrientationPortrait] + CFBundleURLTypes: + - CFBundleURLName: com.bobyrev.pcaphobos.link + CFBundleURLSchemes: [phobos] + settings: + base: + PRODUCT_BUNDLE_IDENTIFIER: com.bobyrev.pcaphobos + ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon + PhobosTunnel: + type: app-extension + platform: iOS + sources: + - Tunnel/PacketTunnelProvider.swift + - Tunnel/TunnelConfiguration+WgQuickConfig.swift + - Tunnel/String+ArrayConversion.swift + - Tunnel/pcarelay.c + - Shared + - path: Tunnel/ru_bypass.txt + buildPhase: resources + - path: Tunnel/obfuscator + compilerFlags: ["-Dmain=wgobf_main", "-w"] + excludes: ["*.h"] + dependencies: + - package: WireGuardKit + entitlements: + path: Tunnel/PhobosTunnel.entitlements + properties: + com.apple.developer.networking.networkextension: [packet-tunnel-provider] + info: + path: Tunnel/Info.plist + properties: + CFBundleDisplayName: PhobosTunnel + NSExtension: + NSExtensionPointIdentifier: com.apple.networkextension.packet-tunnel + NSExtensionPrincipalClass: $(PRODUCT_MODULE_NAME).PacketTunnelProvider + settings: + base: + PRODUCT_BUNDLE_IDENTIFIER: com.bobyrev.pcaphobos.tunnel + SWIFT_OBJC_BRIDGING_HEADER: Tunnel/Tunnel-Bridging-Header.h + HEADER_SEARCH_PATHS: ["$(PROJECT_DIR)/Tunnel", "$(PROJECT_DIR)/Tunnel/obfuscator"] + "LIBRARY_SEARCH_PATHS[sdk=iphoneos*]": ["$(PROJECT_DIR)/WireGuardGo/iphoneos"] + "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*]": ["$(PROJECT_DIR)/WireGuardGo/iphonesimulator"] + "EXCLUDED_ARCHS[sdk=iphonesimulator*]": x86_64