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()