diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index a8eca1d..a541536 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -8,8 +8,6 @@ - - @@ -78,15 +76,5 @@ - diff --git a/android/app/src/main/java/com/masterdns/vpn/dns/FakeDnsInterceptor.kt b/android/app/src/main/java/com/masterdns/vpn/dns/FakeDnsInterceptor.kt deleted file mode 100644 index 830530a..0000000 --- a/android/app/src/main/java/com/masterdns/vpn/dns/FakeDnsInterceptor.kt +++ /dev/null @@ -1,251 +0,0 @@ -package com.masterdns.vpn.dns - -import android.util.Log -import java.io.InputStream -import java.io.OutputStream -import java.net.InetSocketAddress -import java.net.ServerSocket -import java.net.Socket - -/** - * SOCKS5 proxy that intercepts connections to fake IPs and replaces them - * with real hostnames before forwarding to the upstream SOCKS5 proxy. - */ -class FakeDnsInterceptor( - private val listenPort: Int, - private val upstreamHost: String, - private val upstreamPort: Int, - private val fakeDnsServer: FakeDnsServer -) { - private var serverSocket: ServerSocket? = null - @Volatile private var running = false - - companion object { - private const val TAG = "FakeDnsInterceptor" - private const val BUFFER_SIZE = 8192 - } - - fun start() { - if (running) return - running = true - - Thread { - try { - serverSocket = ServerSocket() - serverSocket?.reuseAddress = true - serverSocket?.bind(InetSocketAddress("127.0.0.1", listenPort)) - Log.i(TAG, "Fake DNS interceptor started on port $listenPort") - - while (running) { - try { - val client = serverSocket?.accept() ?: break - Thread { handleClient(client) }.apply { - name = "FakeDnsInterceptor-Client" - isDaemon = true - }.start() - } catch (e: Exception) { - if (running) { - Log.e(TAG, "Error accepting client: ${e.message}") - } - } - } - } catch (e: Exception) { - if (running) { - Log.e(TAG, "Fake DNS interceptor error", e) - } - } finally { - serverSocket?.close() - } - }.apply { - name = "FakeDnsInterceptor" - isDaemon = true - }.start() - } - - fun stop() { - running = false - serverSocket?.close() - serverSocket = null - } - - private fun handleClient(client: Socket) { - var upstream: Socket? = null - try { - client.soTimeout = 30000 // 30 second timeout - val input = client.getInputStream() - val output = client.getOutputStream() - - // Read SOCKS5 greeting - val version = input.read() - if (version != 5) { - Log.w(TAG, "Invalid SOCKS version: $version") - return - } - - val nmethods = input.read() - if (nmethods <= 0) return - - val methods = ByteArray(nmethods) - input.read(methods) - - // Send "no authentication required" - output.write(byteArrayOf(5, 0)) - output.flush() - - // Read SOCKS5 request - val requestHeader = ByteArray(4) - if (input.read(requestHeader) != 4) return - - if (requestHeader[1].toInt() != 1) { // Only support CONNECT - Log.w(TAG, "Unsupported SOCKS command: ${requestHeader[1]}") - output.write(byteArrayOf(5, 7, 0, 1, 0, 0, 0, 0, 0, 0)) // Command not supported - return - } - - val addressType = requestHeader[3].toInt() - val targetHost: String - val targetPort: Int - - when (addressType) { - 1 -> { // IPv4 - val addr = ByteArray(4) - if (input.read(addr) != 4) return - - val fakeIp = addr.joinToString(".") { (it.toInt() and 0xFF).toString() } - - // Check if this is a fake IP and resolve to real hostname - targetHost = fakeDnsServer.getHostnameForFakeIp(fakeIp) ?: fakeIp - - val portBytes = ByteArray(2) - if (input.read(portBytes) != 2) return - targetPort = ((portBytes[0].toInt() and 0xFF) shl 8) or (portBytes[1].toInt() and 0xFF) - - if (fakeIp != targetHost) { - Log.d(TAG, "Intercepted fake IP: $fakeIp -> $targetHost:$targetPort") - } - } - 3 -> { // Domain name - val len = input.read() - if (len <= 0) return - - val domain = ByteArray(len) - if (input.read(domain) != len) return - targetHost = String(domain, Charsets.UTF_8) - - val portBytes = ByteArray(2) - if (input.read(portBytes) != 2) return - targetPort = ((portBytes[0].toInt() and 0xFF) shl 8) or (portBytes[1].toInt() and 0xFF) - } - else -> { - Log.w(TAG, "Unsupported address type: $addressType") - output.write(byteArrayOf(5, 8, 0, 1, 0, 0, 0, 0, 0, 0)) // Address type not supported - return - } - } - - // Connect to upstream SOCKS5 proxy - upstream = Socket() - upstream.connect(InetSocketAddress(upstreamHost, upstreamPort), 5000) - upstream.soTimeout = 30000 - - val upInput = upstream.getInputStream() - val upOutput = upstream.getOutputStream() - - // SOCKS5 handshake with upstream - upOutput.write(byteArrayOf(5, 1, 0)) - upOutput.flush() - - val upGreeting = ByteArray(2) - if (upInput.read(upGreeting) != 2) { - output.write(byteArrayOf(5, 1, 0, 1, 0, 0, 0, 0, 0, 0)) // General failure - return - } - - // Send CONNECT request with real hostname to upstream - val hostBytes = targetHost.toByteArray(Charsets.UTF_8) - val request = ByteArray(7 + hostBytes.size) - request[0] = 5 // Version - request[1] = 1 // CONNECT - request[2] = 0 // Reserved - request[3] = 3 // Domain name - request[4] = hostBytes.size.toByte() - System.arraycopy(hostBytes, 0, request, 5, hostBytes.size) - request[5 + hostBytes.size] = (targetPort shr 8).toByte() - request[6 + hostBytes.size] = (targetPort and 0xFF).toByte() - - upOutput.write(request) - upOutput.flush() - - // Read upstream response - val upResponse = ByteArray(4) - if (upInput.read(upResponse) != 4) { - output.write(byteArrayOf(5, 1, 0, 1, 0, 0, 0, 0, 0, 0)) // General failure - return - } - - // Read bound address - when (upResponse[3].toInt()) { - 1 -> upInput.read(ByteArray(6)) // IPv4 + port - 3 -> { - val len = upInput.read() - if (len > 0) upInput.read(ByteArray(len + 2)) - } - 4 -> upInput.read(ByteArray(18)) // IPv6 + port - } - - // Check if upstream connection succeeded - if (upResponse[1].toInt() != 0) { - Log.w(TAG, "Upstream SOCKS5 error: ${upResponse[1]}") - output.write(byteArrayOf(5, upResponse[1], 0, 1, 0, 0, 0, 0, 0, 0)) - return - } - - // Send success response to client - output.write(byteArrayOf(5, 0, 0, 1, 0, 0, 0, 0, 0, 0)) - output.flush() - - // Relay data bidirectionally - val clientToUpstream = Thread { - relay(input, upOutput, "client->upstream") - upstream.shutdownOutput() - }.apply { - name = "Relay-C2U" - isDaemon = true - } - - val upstreamToClient = Thread { - relay(upInput, output, "upstream->client") - client.shutdownOutput() - }.apply { - name = "Relay-U2C" - isDaemon = true - } - - clientToUpstream.start() - upstreamToClient.start() - - clientToUpstream.join() - upstreamToClient.join() - - } catch (e: Exception) { - Log.e(TAG, "Error handling client: ${e.message}") - } finally { - try { client.close() } catch (_: Exception) {} - try { upstream?.close() } catch (_: Exception) {} - } - } - - private fun relay(input: InputStream, output: OutputStream, direction: String) { - try { - val buffer = ByteArray(BUFFER_SIZE) - while (true) { - val n = input.read(buffer) - if (n <= 0) break - output.write(buffer, 0, n) - output.flush() - } - } catch (e: Exception) { - // Connection closed or error - } - } -} diff --git a/android/app/src/main/java/com/masterdns/vpn/dns/FakeDnsServer.kt b/android/app/src/main/java/com/masterdns/vpn/dns/FakeDnsServer.kt deleted file mode 100644 index 81e16b3..0000000 --- a/android/app/src/main/java/com/masterdns/vpn/dns/FakeDnsServer.kt +++ /dev/null @@ -1,188 +0,0 @@ -package com.masterdns.vpn.dns - -import android.util.Log -import java.net.DatagramPacket -import java.net.DatagramSocket -import java.net.InetAddress -import java.nio.ByteBuffer -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.atomic.AtomicInteger - -/** - * Fake DNS server that returns fake IPs from 198.18.0.0/16 range. - * IMPORTANT: Must bind to 0.0.0.0 (not 10.0.0.1) to receive packets from Android - */ -class FakeDnsServer( - private val listenPort: Int = 53 -) { - - private var socket: DatagramSocket? = null - @Volatile private var running = false - private val fakeIpCounter = AtomicInteger(1) - private val hostnameToFakeIp = ConcurrentHashMap() - private val fakeIpToHostname = ConcurrentHashMap() - - companion object { - private const val TAG = "FakeDnsServer" - private const val FAKE_IP_PREFIX = "198.18" - private const val MAX_FAKE_IPS = 65535 - } - - fun start() { - if (running) return - running = true - - Thread { - try { - // Bind to 0.0.0.0 (all interfaces) to receive packets from VPN interface - socket = DatagramSocket(listenPort, InetAddress.getByName("0.0.0.0")) - socket?.reuseAddress = true - Log.i(TAG, "Fake DNS server started on 0.0.0.0:$listenPort") - - val buffer = ByteArray(512) - while (running) { - try { - val packet = DatagramPacket(buffer, buffer.size) - socket?.receive(packet) - - Log.d(TAG, "Received DNS query from ${packet.address}:${packet.port}") - - val query = buffer.copyOf(packet.length) - val response = processQuery(query) - - if (response != null) { - val responsePacket = DatagramPacket( - response, response.size, - packet.address, packet.port - ) - socket?.send(responsePacket) - Log.d(TAG, "Sent DNS response to ${packet.address}:${packet.port}") - } - } catch (e: Exception) { - if (running) { - Log.e(TAG, "Error processing packet: ${e.message}") - } - } - } - } catch (e: Exception) { - if (running) { - Log.e(TAG, "Fake DNS server error", e) - } - } finally { - socket?.close() - } - }.apply { - name = "FakeDnsServer" - isDaemon = true - }.start() - } - - fun stop() { - running = false - socket?.close() - socket = null - } - - private fun processQuery(query: ByteArray): ByteArray? { - try { - // Parse DNS query - val hostname = parseDnsQuery(query) ?: return null - - // Generate or retrieve fake IP - val fakeIp = getFakeIpForHostname(hostname) - - Log.i(TAG, "DNS query: $hostname -> $fakeIp") - - // Build DNS response - return buildDnsResponse(query, fakeIp) - } catch (e: Exception) { - Log.e(TAG, "Error processing DNS query", e) - return null - } - } - - private fun parseDnsQuery(query: ByteArray): String? { - if (query.size < 12) return null - - var pos = 12 // Skip DNS header - val labels = mutableListOf() - - while (pos < query.size) { - val len = query[pos].toInt() and 0xFF - if (len == 0) break - if (len > 63) return null // Invalid label length - - pos++ - if (pos + len > query.size) return null - - val label = String(query, pos, len, Charsets.UTF_8) - labels.add(label) - pos += len - } - - return if (labels.isNotEmpty()) labels.joinToString(".") else null - } - - private fun getFakeIpForHostname(hostname: String): String { - return hostnameToFakeIp.getOrPut(hostname) { - val counter = fakeIpCounter.getAndIncrement() - if (counter > MAX_FAKE_IPS) { - // Wrap around if we exceed the range - fakeIpCounter.set(1) - } - val octet3 = (counter shr 8) and 0xFF - val octet4 = counter and 0xFF - val fakeIp = "$FAKE_IP_PREFIX.$octet3.$octet4" - fakeIpToHostname[fakeIp] = hostname - Log.d(TAG, "Mapped $hostname -> $fakeIp") - fakeIp - } - } - - fun getHostnameForFakeIp(fakeIp: String): String? { - return fakeIpToHostname[fakeIp] - } - - fun getMappingCount(): Int = hostnameToFakeIp.size - - private fun buildDnsResponse(query: ByteArray, fakeIp: String): ByteArray { - val response = ByteBuffer.allocate(512) - - // Copy query - response.put(query) - response.position(0) - - // Modify flags: QR=1 (response), AA=1 (authoritative) - val flags = response.getShort(2).toInt() - response.putShort(2, ((flags or 0x8400) and 0xFFFF).toShort()) - - // Set answer count to 1 - response.putShort(6, 1) - - // Position at end of query - response.position(query.size) - - // Add answer section - // Name pointer to question (0xC00C) - response.putShort(0xC00C.toShort()) - - // Type A (1), Class IN (1) - response.putShort(1) - response.putShort(1) - - // TTL (60 seconds) - response.putInt(60) - - // Data length (4 bytes for IPv4) - response.putShort(4) - - // IP address - val ipParts = fakeIp.split(".") - ipParts.forEach { response.put(it.toInt().toByte()) } - - val result = ByteArray(response.position()) - response.position(0) - response.get(result) - return result - } -} diff --git a/android/app/src/main/java/com/masterdns/vpn/service/BootReceiver.kt b/android/app/src/main/java/com/masterdns/vpn/service/BootReceiver.kt deleted file mode 100644 index 0b94f80..0000000 --- a/android/app/src/main/java/com/masterdns/vpn/service/BootReceiver.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.masterdns.vpn.service - -/* - * TODO(auto-connect): keep this receiver dormant until the app has an explicit - * auto-connect-on-boot setting and a permission-safe startup flow. - * - * Intended shape: - * - * class BootReceiver : BroadcastReceiver() { - * override fun onReceive(context: Context, intent: Intent) { - * if (intent.action != Intent.ACTION_BOOT_COMPLETED) return - * // Check user setting, VPN permission, and selected profile. - * // Start MasterDnsVpnService only when all prerequisites are satisfied. - * } - * } - */ diff --git a/mobile/mobile.go b/mobile/mobile.go index 996ea85..ab601e9 100644 --- a/mobile/mobile.go +++ b/mobile/mobile.go @@ -20,7 +20,6 @@ import ( "masterdnsvpn-go/internal/client" "masterdnsvpn-go/internal/config" - "masterdnsvpn-go/internal/version" "masterdnsvpn-go/mobile/tun" "github.com/xjasonlyu/tun2socks/v2/engine" @@ -285,24 +284,6 @@ func IsRunning() bool { return running } -// GetVersion returns the build version string of the Go core. -func GetVersion() string { - return version.GetVersion() -} - -// GetListenAddress returns the configured listen address (e.g., "127.0.0.1:18000"). -// Returns empty string if client is not initialized. -func GetListenAddress() string { - mu.Lock() - defer mu.Unlock() - if vpnClient == nil { - return "" - } - // The listen address comes from the config, not directly exposed. - // We'll return it via the config the user provides. - return "" -} - // StartTunBridge starts the TUN bridge with DNS interception using FakeDNS proxy. func StartTunBridge(tunFd int64, mtu int64, socksAddr string) error { proxyAddr, err := tun.StartFakeDNSProxy(socksAddr) @@ -359,13 +340,6 @@ func StopTunBridge() { tun.StopFakeDNSProxy() } -// IsTunBridgeRunning returns true if the DNS-aware TUN bridge is active. -func IsTunBridgeRunning() bool { - mu.Lock() - defer mu.Unlock() - return tunBridgeRunning -} - // GetTunBandwidth returns upload/download counters from the TUN bridge. func GetTunBandwidth() *Bandwidth { up := atomic.LoadInt64(&trackedUp) @@ -375,18 +349,3 @@ func GetTunBandwidth() *Bandwidth { Down: down, } } - -// GetDNSMapping resolves a fake IP to hostname when available. -func GetDNSMapping(fakeIP string) string { - return tun.GetDNSMapping(fakeIP) -} - -// GetDNSMappingCount returns the number of active fake DNS mappings. -func GetDNSMappingCount() int { - return tun.GetDNSMappingCount() -} - -// GetTunVersion returns the TUN bridge module version. -func GetTunVersion() string { - return tun.GetVersion() -} diff --git a/mobile/tun/tun_api.go b/mobile/tun/tun_api.go index 3169cda..0a44954 100644 --- a/mobile/tun/tun_api.go +++ b/mobile/tun/tun_api.go @@ -6,10 +6,6 @@ import ( "sync" ) -func GetVersion() string { - return "1.0.0-fakedns-proxy" -} - var ( bridgeMu sync.Mutex activeProxy *FakeDNSProxy @@ -65,11 +61,6 @@ func IsFakeDNSProxyRunning() bool { return activeProxy != nil } -func GetTunBandwidth() (up int64, down int64) { - // tun2socks engine handles bandwidth stats, this is dummy now - return 0, 0 -} - func GetDNSMapping(fakeIP string) string { bridgeMu.Lock() defer bridgeMu.Unlock()