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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES"
askForAppToLaunch = "Yes"
launchAutomaticallySubstyle = "2">
<BuildableProductRunnable
runnableDebuggingMode = "0">
Expand Down
55 changes: 49 additions & 6 deletions LocalDevVPN/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import Foundation
import NetworkExtension
import SwiftUI
import Darwin

import NavigationBackport

Expand All @@ -16,6 +17,30 @@ extension Bundle {
var tunnelBundleID: String { bundleIdentifier!.appending(".TunnelProv") }
}

private func getCurrentWiFiIP() -> String? {
var address: String?
var ifaddr: UnsafeMutablePointer<ifaddrs>?
guard getifaddrs(&ifaddr) == 0 else { return nil }
guard let firstAddr = ifaddr else { return nil }

for ifptr in sequence(first: firstAddr, next: { $0.pointee.ifa_next }) {
let interface = ifptr.pointee
let addrFamily = interface.ifa_addr.pointee.sa_family

if addrFamily == UInt8(AF_INET) {
let name = String(cString: interface.ifa_name)
if name == "en0" {
var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
getnameinfo(interface.ifa_addr, socklen_t(interface.ifa_addr.pointee.sa_len),
&hostname, socklen_t(hostname.count), nil, socklen_t(0), NI_NUMERICHOST)
address = String(cString: hostname)
}
}
}
freeifaddrs(ifaddr)
return address
}

// MARK: - Logging Utility

class VPNLogger: ObservableObject {
Expand Down Expand Up @@ -66,7 +91,11 @@ class TunnelManager: ObservableObject {
private var tunnelSubnetMask: String {
UserDefaults.standard.string(forKey: "TunnelSubnetMask") ?? "255.255.255.0"
}


private var useWiFiSubnet: Bool {
UserDefaults.standard.bool(forKey: "useWiFiSubnet")
}

private var tunnelBundleId: String {
Bundle.main.bundleIdentifier!.appending(".TunnelProv")
}
Expand Down Expand Up @@ -503,6 +532,7 @@ class TunnelManager: ObservableObject {
"TunnelDeviceIP": self.tunnelDeviceIp as NSObject,
"TunnelFakeIP": self.tunnelFakeIp as NSObject,
"TunnelSubnetMask": self.tunnelSubnetMask as NSObject,
"UseWiFiSubnet": self.useWiFiSubnet as NSObject,
]

do {
Expand Down Expand Up @@ -783,6 +813,14 @@ extension View {
struct StatusOverviewCard: View {
@StateObject private var tunnelManager = TunnelManager.shared
@AppStorage("TunnelDeviceIP") private var deviceIP = "10.7.0.0"
@AppStorage("useWiFiSubnet") private var useWiFiSubnet = false

private var currentIP: String {
if useWiFiSubnet, let wifiIP = getCurrentWiFiIP() {
return wifiIP
}
return deviceIP
}

var body: some View {
DashboardCard {
Expand Down Expand Up @@ -827,7 +865,7 @@ struct StatusOverviewCard: View {
private var statusTip: String {
switch tunnelManager.tunnelStatus {
case .connected:
return String(format: NSLocalizedString("connected_to_ip", comment: ""), deviceIP)
return String(format: NSLocalizedString("connected_to_ip", comment: ""), currentIP)
case .connecting:
return NSLocalizedString("ios_might_ask_you_to_allow_the_vpn", comment: "")
case .disconnecting:
Expand Down Expand Up @@ -1087,6 +1125,7 @@ struct SettingsView: View {
@AppStorage("TunnelDeviceIP") private var deviceIP = "10.7.0.0"
@AppStorage("TunnelFakeIP") private var fakeIP = "10.7.0.1"
@AppStorage("TunnelSubnetMask") private var subnetMask = "255.255.255.0"
@AppStorage("useWiFiSubnet") private var useWiFiSubnet = false
@AppStorage("autoConnect") private var autoConnect = false
@AppStorage("shownTunnelAlert") private var shownTunnelAlert = false
@StateObject private var tunnelManager = TunnelManager.shared
Expand All @@ -1106,10 +1145,14 @@ struct SettingsView: View {
}

Section(header: Text("network_configuration")) {
Group {
networkConfigRow(label: "tunnel_ip", text: $deviceIP)
networkConfigRow(label: "device_ip", text: $fakeIP)
networkConfigRow(label: "subnet_mask", text: $subnetMask)
Toggle("match_wifi_subnet", isOn: $useWiFiSubnet)

if !useWiFiSubnet {
Group {
networkConfigRow(label: "tunnel_ip", text: $deviceIP)
networkConfigRow(label: "device_ip", text: $fakeIP)
networkConfigRow(label: "subnet_mask", text: $subnetMask)
}
}
}

Expand Down
1 change: 1 addition & 0 deletions LocalDevVPN/Localization/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"auto_connect_on_launch" = "Auto Connect on Launch";
"connection_logs" = "Connection Logs";
"network_configuration" = "Network Configuration";
"match_wifi_subnet" = "Match WiFi Subnet";
"device_ip" = "Device IP";
"tunnel_ip" = "Tunnel IP";
"subnet_mask" = "Subnet Mask";
Expand Down
90 changes: 83 additions & 7 deletions TunnelProv/PacketTunnelProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//

import NetworkExtension
import Darwin

class PacketTunnelProvider: NEPacketTunnelProvider {
var tunnelDeviceIp: String = "10.7.0.0"
Expand All @@ -16,20 +17,46 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
private var fakeIpValue: UInt32 = 0

override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) -> Void) {
if let deviceIp = options?["TunnelDeviceIP"] as? String {
tunnelDeviceIp = deviceIp
}
if let fakeIp = options?["TunnelFakeIP"] as? String {
tunnelFakeIp = fakeIp
let useWifiSubnet = options?["UseWiFiSubnet"] as? Bool ?? false

if useWifiSubnet {
if let wifiInfo = getWiFiNetworkInfo() {
tunnelDeviceIp = wifiInfo.ipAddress
tunnelSubnetMask = wifiInfo.subnetMask
let fakeIpParts = wifiInfo.ipAddress.split(separator: ".")
if fakeIpParts.count == 4 {
tunnelFakeIp = "\(fakeIpParts[0]).\(fakeIpParts[1]).\(fakeIpParts[2]).\(Int(fakeIpParts[3])! + 1)"
}
}
} else {
if let deviceIp = options?["TunnelDeviceIP"] as? String {
tunnelDeviceIp = deviceIp
}
if let fakeIp = options?["TunnelFakeIP"] as? String {
tunnelFakeIp = fakeIp
}
if let subnetMask = options?["TunnelSubnetMask"] as? String {
tunnelSubnetMask = subnetMask
}
}

deviceIpValue = ipToUInt32(tunnelDeviceIp)
fakeIpValue = ipToUInt32(tunnelFakeIp)

let settings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: tunnelDeviceIp)
let ipv4 = NEIPv4Settings(addresses: [tunnelDeviceIp], subnetMasks: [tunnelSubnetMask])
ipv4.includedRoutes = [NEIPv4Route(destinationAddress: tunnelDeviceIp, subnetMask: tunnelSubnetMask)]
ipv4.excludedRoutes = [.default()]

let localSubnet = calculateNetworkAddress(ip: tunnelDeviceIp, mask: tunnelSubnetMask)
let localRoute = NEIPv4Route(destinationAddress: localSubnet, subnetMask: tunnelSubnetMask)

if useWifiSubnet {
ipv4.includedRoutes = [localRoute]
ipv4.excludedRoutes = [.default()]
} else {
ipv4.includedRoutes = [NEIPv4Route(destinationAddress: tunnelDeviceIp, subnetMask: tunnelSubnetMask)]
ipv4.excludedRoutes = [.default()]
}

settings.ipv4Settings = ipv4

setTunnelNetworkSettings(settings) { error in
Expand Down Expand Up @@ -69,4 +96,53 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
}
return (b1 << 24) | (b2 << 16) | (b3 << 8) | b4
}

private func getWiFiNetworkInfo() -> (ipAddress: String, subnetMask: String)? {
var address: String?
var subnetMask: String?

var ifaddr: UnsafeMutablePointer<ifaddrs>?
guard getifaddrs(&ifaddr) == 0 else { return nil }
guard let firstAddr = ifaddr else { return nil }

for ifptr in sequence(first: firstAddr, next: { $0.pointee.ifa_next }) {
let interface = ifptr.pointee
let addrFamily = interface.ifa_addr.pointee.sa_family

if addrFamily == UInt8(AF_INET) {
let name = String(cString: interface.ifa_name)
if name == "en0" {
var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
getnameinfo(interface.ifa_addr, socklen_t(interface.ifa_addr.pointee.sa_len),
&hostname, socklen_t(hostname.count), nil, socklen_t(0), NI_NUMERICHOST)
address = String(cString: hostname)

var maskHostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
getnameinfo(interface.ifa_netmask, socklen_t(interface.ifa_netmask.pointee.sa_len),
&maskHostname, socklen_t(maskHostname.count), nil, socklen_t(0), NI_NUMERICHOST)
subnetMask = String(cString: maskHostname)
}
}
}
freeifaddrs(ifaddr)

if let addr = address, let mask = subnetMask {
return (addr, mask)
}
return nil
}

private func calculateNetworkAddress(ip: String, mask: String) -> String {
let ipParts = ip.split(separator: ".").compactMap { UInt32($0) }
let maskParts = mask.split(separator: ".").compactMap { UInt32($0) }

guard ipParts.count == 4, maskParts.count == 4 else { return ip }

let network = (ipParts[0] & maskParts[0]) |
(ipParts[1] & maskParts[1]) |
(ipParts[2] & maskParts[2]) |
(ipParts[3] & maskParts[3])

return "\(network >> 24 & 0xFF).\(network >> 16 & 0xFF).\(network >> 8 & 0xFF).\(network & 0xFF)"
}
}