@@ -83,7 +83,7 @@ class VPNProfileCreator @Inject constructor(
83
83
" 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" ,
84
84
" 176.0.0.0/4" , " 192.0.0.0/9" , " 192.128.0.0/11" , " 192.160.0.0/13" , " 192.169.0.0/16" ,
85
85
" 192.170.0.0/15" , " 192.172.0.0/14" , " 192.176.0.0/12" , " 192.192.0.0/10" ,
86
- " 193.0.0.0/8" , " 194.0.0.0/7" , " 196.0.0.0/6" , " 200.0.0.0/5" , " 208.0.0.0/4" , " 10.255.255.0/24" , " ::0/0 "
86
+ " 193.0.0.0/8" , " 194.0.0.0/7" , " 196.0.0.0/6" , " 200.0.0.0/5" , " 208.0.0.0/4" , " ::0/0 " , " 10.255.255.0/24"
87
87
)
88
88
89
89
fun createIkEV2Profile (
@@ -412,8 +412,15 @@ class VPNProfileCreator @Inject constructor(
412
412
413
413
val reader: Reader = StringReader (configFile.content)
414
414
val bufferedReader = BufferedReader (reader)
415
- val config: Config = bufferedReader.use {
416
- Config .parse(bufferedReader)
415
+ val config: Config = try {
416
+ bufferedReader.use {
417
+ Config .parse(bufferedReader)
418
+ }
419
+ } catch (e: Exception ) {
420
+ logger.error(" Full WireGuard config parse exception: ${e.javaClass.simpleName} : ${e.message} " )
421
+ logger.error(" Stack trace: ${e.stackTrace.joinToString(" \n " )} " )
422
+ logger.error(" Config content: ${configFile.content} " )
423
+ throw e
417
424
}
418
425
interFaceBuilder.parsePrivateKey(config.getInterface().keyPair.privateKey.toBase64())
419
426
interFaceBuilder.addAddresses(config.getInterface().addresses)
@@ -426,10 +433,16 @@ class VPNProfileCreator @Inject constructor(
426
433
} else {
427
434
interFaceBuilder.addDnsServers(config.getInterface().dnsServers)
428
435
}
429
- val configWithSettings = Config .Builder ()
430
- .addPeers(config.peers)
436
+ val configWithSettings = try {
437
+ val modifiedPeers = createModifiedPeersForLanBypass(config.peers)
438
+ Config .Builder ()
439
+ .addPeers(modifiedPeers)
431
440
.setInterface(interFaceBuilder.build())
432
441
.build()
442
+ } catch (e: Exception ) {
443
+ logger.error(" Exception creating config with LAN bypass: ${e.javaClass.simpleName} : ${e.message} " )
444
+ throw e
445
+ }
433
446
val lastSelectedLocation =
434
447
LastSelectedLocation (configFile.getPrimaryKey(), nickName = configFile.name)
435
448
saveSelectedLocation(lastSelectedLocation)
@@ -576,6 +589,96 @@ class VPNProfileCreator @Inject constructor(
576
589
return builder.build()
577
590
}
578
591
592
+ private fun createModifiedPeersForLanBypass (originalPeers : List <Peer >): List <Peer > {
593
+ return if (preferencesHelper.lanByPass) {
594
+ originalPeers.mapIndexed { index, peer ->
595
+ try {
596
+ val builder = Peer .Builder ()
597
+ builder.parsePublicKey(peer.publicKey.toBase64())
598
+
599
+ // Handle endpoint safely - extract value from Optional
600
+ val endpointStr = try {
601
+ if (peer.endpoint != null && peer.endpoint.isPresent) {
602
+ val endpoint = peer.endpoint.orElse(null )
603
+ if (endpoint != null ) {
604
+ " ${endpoint.host} :${endpoint.port} "
605
+ } else {
606
+ " "
607
+ }
608
+ } else {
609
+ " "
610
+ }
611
+ } catch (e: Exception ) {
612
+ " "
613
+ }
614
+ if (endpointStr.isNotEmpty()) {
615
+ builder.parseEndpoint(endpointStr)
616
+ }
617
+
618
+ // Handle persistent keepalive - check if present first
619
+ val keepalive = try {
620
+ if (peer.persistentKeepalive != null && peer.persistentKeepalive.isPresent) {
621
+ peer.persistentKeepalive.orElse(0 )
622
+ } else {
623
+ 0
624
+ }
625
+ } catch (e: Exception ) {
626
+ 0
627
+ }
628
+ builder.setPersistentKeepalive(keepalive)
629
+
630
+ // Format allowedIPs properly - separate IPv4 and IPv6
631
+ val allowedIpsString = peer.allowedIps.joinToString(" , " )
632
+ val dnsRoutes = " "
633
+
634
+ // Split IPv4 and IPv6, only modify IPv4 part
635
+ val ipParts = allowedIpsString.split(" ," ).map { it.trim() }
636
+ val ipv4Parts = ipParts.filter { ! it.contains(" :" ) }
637
+ val ipv6Parts = ipParts.filter { it.contains(" :" ) }
638
+
639
+ // Only modify IPv4 part for LAN bypass - use IPv4-only function
640
+ val modifiedIpv4 = if (ipv4Parts.isNotEmpty()) {
641
+ modifyAllowedIpsIPv4Only(ipv4Parts.joinToString(" , " ), dnsRoutes)
642
+ } else {
643
+ " "
644
+ }
645
+
646
+ // Combine modified IPv4 with original IPv6 - clean up trailing commas
647
+ val finalAllowedIps = if (modifiedIpv4.isNotEmpty() && ipv6Parts.isNotEmpty()) {
648
+ " ${modifiedIpv4.trim(' ' , ' ,' )} , ${ipv6Parts.joinToString(" , " )} "
649
+ } else if (modifiedIpv4.isNotEmpty()) {
650
+ modifiedIpv4.trim(' ' , ' ,' )
651
+ } else if (ipv6Parts.isNotEmpty()) {
652
+ ipv6Parts.joinToString(" , " )
653
+ } else {
654
+ allowedIpsString
655
+ }
656
+
657
+ builder.parseAllowedIPs(finalAllowedIps)
658
+
659
+ // Handle pre-shared key if present - safely check Optional
660
+ try {
661
+ if (peer.preSharedKey != null && peer.preSharedKey.isPresent) {
662
+ val key = peer.preSharedKey.orElse(null )
663
+ if (key != null ) {
664
+ builder.parsePreSharedKey(key.toBase64())
665
+ }
666
+ }
667
+ } catch (e: Exception ) {
668
+ // No pre-shared key present, which is valid
669
+ }
670
+
671
+ builder.build()
672
+ } catch (e: Exception ) {
673
+ logger.error(" Exception building peer $index : ${e.javaClass.simpleName} : ${e.message} " )
674
+ throw e
675
+ }
676
+ }
677
+ } else {
678
+ originalPeers
679
+ }
680
+ }
681
+
579
682
private fun getIkev2Credentials (): Pair <String , String > {
580
683
val serverCredentials = getServerCredentials(true )
581
684
val mUsername: String
@@ -729,6 +832,34 @@ class VPNProfileCreator @Inject constructor(
729
832
return Attribute .join(output)
730
833
}
731
834
835
+ private fun modifyAllowedIpsIPv4Only (allowedIps : String , dnsRoutes : String ): String {
836
+ // Create IPv4-only array by filtering out IPv6 addresses
837
+ val ipv4OnlyNetworks = publicIpV4Array.filter { ! it.contains(" :" ) }.toTypedArray()
838
+ val ipv4PublicNetworks = HashSet (listOf (* ipv4OnlyNetworks))
839
+ val ipv4Wildcard = " 0.0.0.0/0"
840
+ val allNetworks = HashSet (listOf (ipv4Wildcard))
841
+ val input: Collection <String > = HashSet (listOf (* Attribute .split(allowedIps)))
842
+ val outputSize = input.size - allNetworks.size + ipv4PublicNetworks.size
843
+ val output: MutableCollection <String ?> = LinkedHashSet (outputSize)
844
+ var replaced = false
845
+ for (network in input) {
846
+ if (allNetworks.contains(network)) {
847
+ if (! replaced) {
848
+ for (replacement in ipv4PublicNetworks) {
849
+ if (! output.contains(replacement)) {
850
+ output.add(replacement)
851
+ }
852
+ }
853
+ replaced = true
854
+ }
855
+ } else if (! output.contains(network)) {
856
+ output.add(network)
857
+ }
858
+ }
859
+ output.addAll(listOf (* Attribute .split(dnsRoutes)))
860
+ return Attribute .join(output)
861
+ }
862
+
732
863
private fun setSplitMode (profile : VpnProfile ) {
733
864
if (preferencesHelper.splitTunnelToggle && (preferencesHelper.splitRoutingMode == PreferencesKeyConstants .EXCLUSIVE_MODE )) {
734
865
preferencesHelper.lastConnectedUsingSplit = true
0 commit comments