diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/event/TunnelErrorEvent.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/event/TunnelErrorEvent.kt index 8e6fc6c8..d219e6b8 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/event/TunnelErrorEvent.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/event/TunnelErrorEvent.kt @@ -7,13 +7,30 @@ sealed interface TunnelErrorEvent { data class StateConflict(val tunnelId: Int, val message: String) : TunnelErrorEvent - data class InternalFailure(val tunnelId: Int?, val message: String) : TunnelErrorEvent + data class InternalFailure(val tunnelId: Int, val message: String) : TunnelErrorEvent + + data class Socks5PortUnavailable(val tunnelId: Int, val port: Int) : TunnelErrorEvent + + data class HttpPortUnavailable(val tunnelId: Int, val port: Int) : TunnelErrorEvent companion object { - fun from(throwable: Throwable, id: Int?): TunnelErrorEvent { + fun from(throwable: Throwable, id: Int): TunnelErrorEvent { return when (throwable) { - is BackendException.StateConflict -> StateConflict(id ?: -1, throwable.message) - is BackendException.Unauthorized -> InternalFailure(id, "Unauthorized") + is BackendException.StateConflict -> { + StateConflict(id, throwable.message) + } + is BackendException.Unauthorized -> { + VpnPermissionDenied(id) + } + is BackendException.InternalError -> { + InternalFailure(id, throwable.message) + } + is BackendException.Socks5PortUnavailable -> { + Socks5PortUnavailable(id, throwable.port) + } + is BackendException.HttpPortUnavailable -> { + HttpPortUnavailable(id, throwable.port) + } else -> InternalFailure(id, throwable.message ?: "Unknown") } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/event/TunnelEventDispatcher.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/event/TunnelEventDispatcher.kt index a09b8020..11aeb885 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/event/TunnelEventDispatcher.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/event/TunnelEventDispatcher.kt @@ -61,6 +61,14 @@ class TunnelEventDispatcher(private val notificationManager: TunnelNotificationS is TunnelErrorEvent.InternalFailure -> { notificationManager.showError(error.message) } + + is TunnelErrorEvent.Socks5PortUnavailable -> { + notificationManager.showSocks5PortUnavailable(error.port) + } + + is TunnelErrorEvent.HttpPortUnavailable -> { + notificationManager.showHttpPortUnavailable(error.port) + } } } .launchIn(scope) diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/notification/AndroidTunnelNotificationService.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/notification/AndroidTunnelNotificationService.kt index 85485fa0..9de46f0a 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/notification/AndroidTunnelNotificationService.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/notification/AndroidTunnelNotificationService.kt @@ -150,6 +150,20 @@ class AndroidTunnelNotificationService( showError(context.getString(R.string.error_root_denied)) } + override suspend fun showSocks5PortUnavailable(port: Int) { + val context = notificationService.context + val message = context.getString(R.string.error_socks5_port_unavailable, port) + + showError(message) + } + + override suspend fun showHttpPortUnavailable(port: Int) { + val context = notificationService.context + val message = context.getString(R.string.error_http_port_unavailable, port) + + showError(message) + } + override suspend fun showError(message: String) { val notification = diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/notification/TunnelNotificationService.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/notification/TunnelNotificationService.kt index c48c97c7..042d2da1 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/notification/TunnelNotificationService.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/notification/TunnelNotificationService.kt @@ -16,6 +16,10 @@ interface TunnelNotificationService { suspend fun showStateConflict(tunnelId: Int) + suspend fun showSocks5PortUnavailable(port: Int) + + suspend fun showHttpPortUnavailable(port: Int) + suspend fun showRootShellAccess() suspend fun showError(message: String) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 023af939..92e66c44 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -567,4 +567,7 @@ Pre/Post script support Tunnel name cannot be empty More options + + SOCKS5 port %1$d is already in use.\nPlease choose a different port. + HTTP listener port %1$d is already in use.\nPlease choose a different port. diff --git a/tunnel/src/main/java/com/zaneschepke/tunnel/ProxyBackend.kt b/tunnel/src/main/java/com/zaneschepke/tunnel/ProxyBackend.kt index 26d69af6..46a755ca 100644 --- a/tunnel/src/main/java/com/zaneschepke/tunnel/ProxyBackend.kt +++ b/tunnel/src/main/java/com/zaneschepke/tunnel/ProxyBackend.kt @@ -10,8 +10,6 @@ internal object ProxyBackend { external fun awgUpdateProxyTunnelPeers(handle: Int, settings: String): Int - external fun awgStopProxy() - external fun awgTurnProxyTunnelOff(handle: Int) external fun awgGetProxyConfig(handle: Int): String diff --git a/tunnel/src/main/java/com/zaneschepke/tunnel/backend/WireGuardTunnelEngine.kt b/tunnel/src/main/java/com/zaneschepke/tunnel/backend/WireGuardTunnelEngine.kt index 2855282b..686686e8 100644 --- a/tunnel/src/main/java/com/zaneschepke/tunnel/backend/WireGuardTunnelEngine.kt +++ b/tunnel/src/main/java/com/zaneschepke/tunnel/backend/WireGuardTunnelEngine.kt @@ -41,6 +41,24 @@ internal class WireGuardTunnelEngine( } is BackendMode.Proxy.Standard -> { val proxyConfig = mode.proxyConfig + + proxyConfig.socks5?.port?.let { port -> + if (!isPortAvailable(port)) { + throw BackendException.Socks5PortUnavailable( + "SOCKS5 port $port is already in use.", + port, + ) + } + } + + proxyConfig.http?.port?.let { port -> + if (!isPortAvailable(port)) { + throw BackendException.HttpPortUnavailable( + "HTTP listener port $port is already in use.", + port, + ) + } + } startProxyTunnel(ifName, config, proxyConfig, false) } is BackendMode.Vpn -> { @@ -61,6 +79,15 @@ internal class WireGuardTunnelEngine( ) } + private fun isPortAvailable(port: Int): Boolean { + if (port !in 1..65_535) return false + return try { + ServerSocket(port).use { true } + } catch (e: IOException) { + false + } + } + private fun buildConfig(mode: BackendMode): Pair { var removedPeerEndpoint = false return mode.config.copy( diff --git a/tunnel/src/main/java/com/zaneschepke/tunnel/util/BackendException.kt b/tunnel/src/main/java/com/zaneschepke/tunnel/util/BackendException.kt index 215be876..8f244726 100644 --- a/tunnel/src/main/java/com/zaneschepke/tunnel/util/BackendException.kt +++ b/tunnel/src/main/java/com/zaneschepke/tunnel/util/BackendException.kt @@ -6,4 +6,8 @@ sealed class BackendException : Exception() { class InternalError(override val message: String) : BackendException() class Unauthorized(override val message: String) : BackendException() + + class Socks5PortUnavailable(override val message: String, val port: Int) : BackendException() + + class HttpPortUnavailable(override val message: String, val port: Int) : BackendException() } diff --git a/tunnel/tools/libwg-go/proxy/proxy.go b/tunnel/tools/libwg-go/proxy/proxy.go index ce471b5f..bc126717 100644 --- a/tunnel/tools/libwg-go/proxy/proxy.go +++ b/tunnel/tools/libwg-go/proxy/proxy.go @@ -9,6 +9,7 @@ import ( "net" "sync" "syscall" + "time" "github.com/amnezia-vpn/amneziawg-go/conn" "github.com/amnezia-vpn/amneziawg-go/device" @@ -21,8 +22,7 @@ import ( import "C" var ( - ctx context.Context - cancelFunc context.CancelFunc + cancelFuncs map[int32]context.CancelFunc tag string virtualTunnelHandles map[int32]*wireproxyawg.VirtualTun ) @@ -30,6 +30,7 @@ var ( func init() { tag = "AwgProxy" virtualTunnelHandles = make(map[int32]*wireproxyawg.VirtualTun) + cancelFuncs = make(map[int32]context.CancelFunc) } //export awgStartProxy @@ -134,13 +135,14 @@ func awgStartProxy(interfaceName string, config string, uapiPath string, bypass virtualTunnelHandles[handle] = virtualTun // Create cancellable context - ctx, cancelFunc = context.WithCancel(context.Background()) + tunnelCtx, tunnelCancel := context.WithCancel(context.Background()) + cancelFuncs[handle] = tunnelCancel // Spawn all routines with context for _, spawner := range conf.Routines { shared.LogDebug(tag, "Spawning routine..") go func(s wireproxyawg.RoutineSpawner) { - if err := s.SpawnRoutine(ctx, virtualTun); err != nil { + if err := s.SpawnRoutine(tunnelCtx, virtualTun); err != nil { shared.LogError(tag, "Routine failed: %v", err) } }(spawner) @@ -195,24 +197,6 @@ func awgGetProxyConfig(tunnelHandle int32) *C.char { return C.CString(settings) } -//export awgStopProxy -func awgStopProxy() { - if cancelFunc != nil { - shared.LogDebug(tag, "Stopping proxy routines..") - cancelFunc() - cancelFunc = nil - } - handles := make([]int32, 0, len(virtualTunnelHandles)) - for h := range virtualTunnelHandles { - handles = append(handles, h) - } - for _, handle := range handles { - awgTurnProxyTunnelOff(handle) - } - virtualTunnelHandles = make(map[int32]*wireproxyawg.VirtualTun) - shared.LogDebug(tag, "Proxy fully reset: %d handles closed", len(handles)) -} - // control hook to bypass sockets func protectControlFunc(network, address string, c syscall.RawConn) error { var opErr error @@ -239,6 +223,12 @@ func awgTurnProxyTunnelOff(virtualTunnelHandle int32) { } shared.LogDebug(tag, "Tearing down tunnel %d", virtualTunnelHandle) + if cancel, exists := cancelFuncs[virtualTunnelHandle]; exists { + cancel() + delete(cancelFuncs, virtualTunnelHandle) + time.Sleep(50 * time.Millisecond) + } + // Close UAPI listener and underlying file if virtualTun.Uapi != nil { virtualTun.Uapi.Close() diff --git a/tunnel/tools/libwg-go/proxy/proxy_jni.c b/tunnel/tools/libwg-go/proxy/proxy_jni.c index fd91182a..747b0eda 100644 --- a/tunnel/tools/libwg-go/proxy/proxy_jni.c +++ b/tunnel/tools/libwg-go/proxy/proxy_jni.c @@ -76,11 +76,6 @@ JNIEXPORT jint JNICALL Java_com_zaneschepke_tunnel_ProxyBackend_awgStartProxy(JN return ret; } -JNIEXPORT void JNICALL Java_com_zaneschepke_tunnel_ProxyBackend_awgStopProxy(JNIEnv *env, jclass c) -{ - awgStopProxy(); -} - JNIEXPORT void JNICALL Java_com_zaneschepke_tunnel_ProxyBackend_awgTurnProxyTunnelOff(JNIEnv *env, jclass c, jint handle) { awgTurnProxyTunnelOff(handle);