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[.. = ["address", "allowedips", "dns"]
+ if let presentValue = attributes[key] {
+ if keysWithMultipleEntriesAllowed.contains(key) {
+ attributes[key] = presentValue + "," + value
+ } else {
+ throw ParseError.multipleEntriesForKey(keyWithCase)
+ }
+ } else {
+ attributes[key] = value
+ }
+ let interfaceSectionKeys: Set = ["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