Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
ffc0596
Add Customized Wireguard V2ray Ext
codewithtamim Aug 28, 2025
4c06cd8
Update Design & Configuration
codewithtamim Aug 28, 2025
11a73bd
Remove V2rayControl
codewithtamim Aug 28, 2025
884fc96
Update README.md
codewithtamim Aug 28, 2025
5313c90
.DS_Store
codewithtamim Aug 28, 2025
9002743
Update AUTHORS
codewithtamim Aug 28, 2025
1a499f7
Update ACKNOWLEDGEMENTS.md
codewithtamim Aug 28, 2025
92ab5f4
Delete build-v2ray.sh
codewithtamim Aug 28, 2025
c290710
Update servers.json
codewithtamim Aug 28, 2025
9c4e43d
ipv6
codewithtamim Aug 28, 2025
bdb094c
Update servers.json
codewithtamim Aug 28, 2025
de136b9
Refresh settings
codewithtamim Aug 28, 2025
614fa06
Update servers.json
codewithtamim Aug 28, 2025
b29734d
Fix port
codewithtamim Aug 30, 2025
9f752bb
Merge branch 'ivpn:develop' into fix/v2ray-obfuscation
codewithtamim Sep 3, 2025
ae8100e
Update wireguard-apple
codewithtamim Sep 5, 2025
cd5ab06
Update servers.json
codewithtamim Sep 5, 2025
b1ecb37
Update
codewithtamim Sep 5, 2025
69c8bb2
Update File
codewithtamim Sep 5, 2025
1bc79fb
Fix port bug
codewithtamim Sep 8, 2025
7c4e72e
Remove .DS_Store file from git tracking
codewithtamim Sep 8, 2025
58e71ed
Update README to mention WireGuard fork with V2Ray extension support
codewithtamim Sep 8, 2025
d057ee2
Remove Local Path
codewithtamim Sep 8, 2025
34556e8
Config Builder
codewithtamim Sep 8, 2025
e0f48eb
Update KeyChain.swift
codewithtamim Oct 6, 2025
ffe98f1
Update AppDelegate.swift
codewithtamim Oct 6, 2025
fcd7359
(chore) : Fix ipv6 configuration
codewithtamim Oct 17, 2025
825fe48
(chore) : Fix App fails to connect with V2Ray after device reboot
codewithtamim Oct 17, 2025
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
build/
DerivedData/
Frameworks/
.DS_Store

## Source / dependencies
submodules/
Expand Down
25 changes: 25 additions & 0 deletions ACKNOWLEDGEMENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -886,4 +886,29 @@ 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.

## V2Ray Core

Copyright (c) V2Ray Authors, V2Fly Community
https://github.com/v2fly/v2ray-core

V2Ray is a platform for building proxies to bypass network restrictions. It is developed and maintained by the V2Fly community.

V2Ray Core is licensed under the MIT License:

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.
4 changes: 2 additions & 2 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@

Fedir Nepyyvoda <fedir@ivpn.net>
Juraj Hilje <juraj@ivpn.net>

Tamim Hossain <tamim@thebytearray.org>

# Organizations

IVPN Limited
IVPN Limited
26 changes: 15 additions & 11 deletions IVPNClient.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
objects = {

/* Begin PBXBuildFile section */
4E46608B2E606EC4000AB900 /* libresolv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E4660892E606EC4000AB900 /* libresolv.tbd */; };
4E46608C2E606EC4000AB900 /* libresolv.9.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E46608A2E606EC4000AB900 /* libresolv.9.tbd */; };
4ED68D7A2E6F70FE009A6D96 /* V2rayConfigBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ED68D792E6F70F8009A6D96 /* V2rayConfigBuilder.swift */; };
820079F42407D96D00EC2062 /* ConnectionInfoBoxView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 820079F32407D96D00EC2062 /* ConnectionInfoBoxView.swift */; };
8201A5022354A32F008C83DB /* ErrorResultSessionNew.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8201A5012354A32F008C83DB /* ErrorResultSessionNew.swift */; };
8201A5042356536B008C83DB /* UpgradePlanViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8201A5032356536B008C83DB /* UpgradePlanViewController.swift */; };
Expand Down Expand Up @@ -46,7 +49,6 @@
821CA2D7287C5AB20067F70D /* PortViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 821CA2D6287C5AB20067F70D /* PortViewController.swift */; };
821CA2DB288039670067F70D /* PortTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 821CA2DA288039670067F70D /* PortTableViewCell.swift */; };
821CA2DF288143470067F70D /* PortRange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 821CA2DE288143470067F70D /* PortRange.swift */; };
821E35582A95DCD200AEE5C7 /* V2RayCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 821E35572A95DCD200AEE5C7 /* V2RayCore.swift */; };
821E355A2A95F77700AEE5C7 /* config.json in Resources */ = {isa = PBXBuildFile; fileRef = 821E35592A95F77700AEE5C7 /* config.json */; };
821F1C7E21FF544200107311 /* VPNServerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 821F1C7D21FF544200107311 /* VPNServerViewModel.swift */; };
821F604A240D21E3008072D7 /* ControlPanelViewController+Ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 821F6049240D21E3008072D7 /* ControlPanelViewController+Ext.swift */; };
Expand Down Expand Up @@ -74,7 +76,6 @@
822EE96C215CE0E300BE77F6 /* UserDefaults+Ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 825A43FC215CCFE70076131F /* UserDefaults+Ext.swift */; };
8232FBF42240DE19006B81D2 /* ErrorResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8232FBF32240DE19006B81D2 /* ErrorResult.swift */; };
8232FBF62240E40F006B81D2 /* Error+Ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8232FBF52240E40F006B81D2 /* Error+Ext.swift */; };
8234E0D72AB23E5C0015C9A2 /* V2RayControl.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8234E0D62AB23E5C0015C9A2 /* V2RayControl.xcframework */; };
82351FCA241FA16600E6E0FD /* InfoAlertViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82351FC9241FA16600E6E0FD /* InfoAlertViewModelTests.swift */; };
82351FCC241FBC8E00E6E0FD /* VPNStatusViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82351FCB241FBC8E00E6E0FD /* VPNStatusViewModelTests.swift */; };
82351FCE2420CE6800E6E0FD /* MapMarkerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82351FCD2420CE6800E6E0FD /* MapMarkerView.swift */; };
Expand Down Expand Up @@ -458,6 +459,9 @@

/* Begin PBXFileReference section */
042B1F5A4F38CCA3591AD441 /* Pods_openvpn_tunnel_provider.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_openvpn_tunnel_provider.framework; sourceTree = BUILT_PRODUCTS_DIR; };
4E4660892E606EC4000AB900 /* libresolv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libresolv.tbd; path = usr/lib/libresolv.tbd; sourceTree = SDKROOT; };
4E46608A2E606EC4000AB900 /* libresolv.9.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libresolv.9.tbd; path = usr/lib/libresolv.9.tbd; sourceTree = SDKROOT; };
4ED68D792E6F70F8009A6D96 /* V2rayConfigBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = V2rayConfigBuilder.swift; sourceTree = "<group>"; };
58126B967EB9B059871FD7AB /* Pods_wireguard_tunnel_provider.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_wireguard_tunnel_provider.framework; sourceTree = BUILT_PRODUCTS_DIR; };
751AB84B8432BC3E6BEDD483 /* Pods_today_extension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_today_extension.framework; sourceTree = BUILT_PRODUCTS_DIR; };
820079F32407D96D00EC2062 /* ConnectionInfoBoxView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionInfoBoxView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -493,7 +497,6 @@
821CA2D6287C5AB20067F70D /* PortViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PortViewController.swift; sourceTree = "<group>"; };
821CA2DA288039670067F70D /* PortTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PortTableViewCell.swift; sourceTree = "<group>"; };
821CA2DE288143470067F70D /* PortRange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PortRange.swift; sourceTree = "<group>"; };
821E35572A95DCD200AEE5C7 /* V2RayCore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = V2RayCore.swift; sourceTree = "<group>"; };
821E35592A95F77700AEE5C7 /* config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = config.json; sourceTree = "<group>"; };
821F1C7D21FF544200107311 /* VPNServerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNServerViewModel.swift; sourceTree = "<group>"; };
821F6049240D21E3008072D7 /* ControlPanelViewController+Ext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ControlPanelViewController+Ext.swift"; sourceTree = "<group>"; };
Expand All @@ -513,7 +516,6 @@
822EBCA72C91AB5900E708F6 /* PrivacyInfo-wireguard-tunnel-provider.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = "PrivacyInfo-wireguard-tunnel-provider.xcprivacy"; sourceTree = "<group>"; };
8232FBF32240DE19006B81D2 /* ErrorResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorResult.swift; sourceTree = "<group>"; };
8232FBF52240E40F006B81D2 /* Error+Ext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Error+Ext.swift"; sourceTree = "<group>"; };
8234E0D62AB23E5C0015C9A2 /* V2RayControl.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = V2RayControl.xcframework; path = Frameworks/V2RayControl.xcframework; sourceTree = "<group>"; };
82351FC9241FA16600E6E0FD /* InfoAlertViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoAlertViewModelTests.swift; sourceTree = "<group>"; };
82351FCB241FBC8E00E6E0FD /* VPNStatusViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNStatusViewModelTests.swift; sourceTree = "<group>"; };
82351FCD2420CE6800E6E0FD /* MapMarkerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapMarkerView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -788,6 +790,8 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
4E46608B2E606EC4000AB900 /* libresolv.tbd in Frameworks */,
4E46608C2E606EC4000AB900 /* libresolv.9.tbd in Frameworks */,
82B6052F21708575004B40E6 /* NetworkExtension.framework in Frameworks */,
82BF79622A2F8DDC00061972 /* liboqs.a in Frameworks */,
824B86E126D42A5700D0101A /* WireGuardKit in Frameworks */,
Expand Down Expand Up @@ -827,7 +831,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
8234E0D72AB23E5C0015C9A2 /* V2RayControl.xcframework in Frameworks */,
82D598C621A6A5C7000FABDE /* SystemConfiguration.framework in Frameworks */,
9CB2CE311DAF9227007A4D2D /* CoreData.framework in Frameworks */,
9C6942251DD0CBF800F9A801 /* NetworkExtension.framework in Frameworks */,
Expand All @@ -850,7 +853,8 @@
584496306C3B9383149618CE /* Frameworks */ = {
isa = PBXGroup;
children = (
8234E0D62AB23E5C0015C9A2 /* V2RayControl.xcframework */,
4E4660892E606EC4000AB900 /* libresolv.tbd */,
4E46608A2E606EC4000AB900 /* libresolv.9.tbd */,
8294BC8D22A126C900328932 /* TunnelKit.framework */,
8294BC8B22A10F4100328932 /* TunnelKit.framework */,
824F56072233FE6F00BCDD5C /* libwg-go.a */,
Expand Down Expand Up @@ -942,9 +946,9 @@
821BB5C32A7CF198005D9D64 /* V2Ray */ = {
isa = PBXGroup;
children = (
4ED68D792E6F70F8009A6D96 /* V2rayConfigBuilder.swift */,
8247C05F2A7CF54300A7C02F /* V2RayConfig.swift */,
82365E7E2AB86020006434C3 /* V2RaySettings.swift */,
821E35572A95DCD200AEE5C7 /* V2RayCore.swift */,
821E35592A95F77700AEE5C7 /* config.json */,
);
path = V2Ray;
Expand Down Expand Up @@ -2379,12 +2383,12 @@
828772FB221C28E000D5E330 /* FlagImageView.swift in Sources */,
8228C8D22B1DE906005977D3 /* PurchaseManager.swift in Sources */,
82E7880C22B0DA0D00A98D76 /* NETunnelProviderProtocol+Ext.swift in Sources */,
4ED68D7A2E6F70FE009A6D96 /* V2rayConfigBuilder.swift in Sources */,
82968A35298A98C300077E0A /* KeyChain.swift in Sources */,
82F638C2217DA89000410318 /* AddressType.swift in Sources */,
82E0E9072A90CB110008BD3F /* AdvancedViewController.swift in Sources */,
822920A02480FA3600476FC1 /* ServersSort.swift in Sources */,
826E61482428F8E60064F195 /* AccountViewController.swift in Sources */,
821E35582A95DCD200AEE5C7 /* V2RayCore.swift in Sources */,
820079F42407D96D00EC2062 /* ConnectionInfoBoxView.swift in Sources */,
821429BB22FC36100056B8FF /* ApiRequestDI.swift in Sources */,
82AB0877291A6B9C0084625A /* CustomPort+CoreDataClass.swift in Sources */,
Expand Down Expand Up @@ -3475,10 +3479,10 @@
/* Begin XCRemoteSwiftPackageReference section */
824B86DD26D42A4600D0101A /* XCRemoteSwiftPackageReference "wireguard-apple" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://git.zx2c4.com/wireguard-apple";
repositoryURL = "https://github.com/ivpn/wireguard-apple";
requirement = {
kind = revision;
revision = ccc7472fd7d1c7c19584e6a30c45a56b8ba57790;
branch = master;
kind = branch;
};
};
82968A30298A970500077E0A /* XCRemoteSwiftPackageReference "Tunnelkit" */ = {
Expand Down
6 changes: 6 additions & 0 deletions IVPNClient/Config/Config.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,12 @@ struct Config {
static let v2rayHost = "127.0.0.1"
static let v2rayPort = 16661

// HTTP/VMess/TCP (According to desktop-app)
static let v2rayTcpPort = 80

// HTTPS/VMess/QUIC (According to desktop-app)
static let v2rayQuicPort = 443

// MARK: ENV variables

static var Environment: String {
Expand Down
2 changes: 1 addition & 1 deletion IVPNClient/Config/servers.json

Large diffs are not rendered by default.

141 changes: 113 additions & 28 deletions IVPNClient/Managers/ConnectionManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -97,14 +97,6 @@ class ConnectionManager {
self.updateWireGuardLogFile()
self.reconnectAutomatically = false
}
DispatchQueue.delay(2.5) {
if UserDefaults.shared.isV2ray && !V2RayCore.shared.reconnectWithV2ray {
V2RayCore.shared.reconnectWithV2ray = true
self.reconnect()
} else {
V2RayCore.shared.reconnectWithV2ray = false
}
}
} else {
self.connected = false
}
Expand Down Expand Up @@ -147,7 +139,7 @@ class ConnectionManager {
return
}

self.vpnManager.disable(tunnelType: tunnelType) { _ in
self.vpnManager.disable(tunnelType: tunnelType) { _ in
completion()
}
}
Expand Down Expand Up @@ -249,15 +241,9 @@ class ConnectionManager {
return
}

if UserDefaults.shared.isV2ray && V2RayCore.shared.reconnectWithV2ray {
DispatchQueue.global(qos: .userInitiated).async {
let error = V2RayCore.shared.start()
if error != nil {
log(.error, message: error?.localizedDescription ?? "")
} else {
log(.info, message: "V2Ray start OK")
}
}
// Update V2Ray settings if V2Ray is enabled -> similar to android ;)
if UserDefaults.shared.isV2ray {
self.updateV2RaySettings()
}

self.vpnManager.connect(tunnelType: self.settings.connectionProtocol.tunnelType())
Expand All @@ -277,16 +263,7 @@ class ConnectionManager {
}
}

if UserDefaults.shared.isV2ray {
DispatchQueue.global(qos: .userInitiated).async {
let error = V2RayCore.shared.close()
if error != nil {
log(.error, message: error?.localizedDescription ?? "")
} else {
log(.info, message: "V2Ray stop OK")
}
}
}

}

func installOnDemandRules() {
Expand Down Expand Up @@ -505,6 +482,8 @@ class ConnectionManager {
}

func reconnect() {
log(.info, message: "Reconnecting to VPN server")

getStatus { tunnelType, status in
if status == .connected || status == .connecting {
self.reconnectAutomatically = true
Expand Down Expand Up @@ -575,6 +554,112 @@ class ConnectionManager {
}
}

// MARK: - V2Ray Settings Update

func updateV2RaySettings() {
guard UserDefaults.shared.isV2ray else {
log(.debug, message: "V2Ray obfuscation disabled, skipping settings update")
return
}

guard let currentV2RaySettings = V2RaySettings.load() else {
log(.error, message: "V2Ray base configuration not found")
return
}

if currentV2RaySettings.id.isEmpty {
log(.error, message: "V2Ray user ID is empty, authentication will fail")
return
}

guard let entryHost = getEntryHost() else {
log(.error, message: "Entry server not available, cannot configure V2Ray")
return
}

let v2rayOutboundIp = entryHost.v2ray
guard !v2rayOutboundIp.isEmpty else {
log(.error, message: "Entry host missing V2Ray configuration")
return
}

let v2rayInboundIp = entryHost.host
let v2rayInboundPort = currentV2RaySettings.singleHopInboundPort
let v2rayOutboundPort = getV2RayOutboundPort()
let v2rayDnsName = !entryHost.dnsName.isEmpty ? entryHost.dnsName : entryHost.hostName

// -> handle multi-hop conf
var finalInboundIp = v2rayInboundIp
var finalInboundPort = v2rayInboundPort

if UserDefaults.shared.isMultiHop, Application.shared.serviceStatus.isEnabled(capability: .multihop) {
if let exitHost = getExitHost() {
finalInboundIp = exitHost.host
// -> use wg inbound port for multi-hop (fix-from-android)
finalInboundPort = Application.shared.settings.connectionProtocol.port()
log(.info, message: "Multi-hop V2Ray override: inbound=\(exitHost.host):\(finalInboundPort)")
} else {
log(.error, message: "Multi-hop enabled but no exit server available")
return
}
}

if finalInboundIp.isEmpty || v2rayOutboundIp.isEmpty {
log(.error, message: "Critical V2Ray IPs are empty - inbound: '\(finalInboundIp)', outbound: '\(v2rayOutboundIp)'")
return
}

let updatedV2RaySettings = V2RaySettings(
id: currentV2RaySettings.id,
outboundIp: v2rayOutboundIp,
outboundPort: v2rayOutboundPort,
inboundIp: finalInboundIp,
inboundPort: finalInboundPort,
dnsName: v2rayDnsName,
wireguard: currentV2RaySettings.wireguard
)

updatedV2RaySettings.save()
log(.info, message: "V2Ray settings updated successfully - inbound: \(finalInboundIp):\(finalInboundPort), outbound: \(v2rayOutboundIp):\(v2rayOutboundPort)")
}

private func getEntryHost() -> Host? {
if let selectedHost = Application.shared.settings.selectedHost {
return selectedHost
}

if let firstHost = Application.shared.settings.selectedServer.hosts.first {
return firstHost
}

return nil
}

private func getExitHost() -> Host? {
if let selectedHost = Application.shared.settings.selectedExitHost {
return selectedHost
}

if let firstHost = Application.shared.settings.selectedExitServer.hosts.first {
return firstHost
}

return nil
}

private func getV2RayOutboundPort() -> Int {
let v2rayProtocol = UserDefaults.shared.v2rayProtocol

switch v2rayProtocol {
case "tcp":
return Config.v2rayTcpPort // HTTP/VMess/TCP -> desktop
case "udp":
return Config.v2rayQuicPort // HTTPS/VMess/QUIC -> desktop
default:
return settings.connectionProtocol.port()
}
}

}

extension ConnectionManager {
Expand Down
Loading