mirror of
https://github.com/yggdrasil-network/yggdrasil-android.git
synced 2025-09-08 23:35:08 +03:00
Merge ca6df89289
into 222d9d90bc
This commit is contained in:
commit
f58b66bcdd
1 changed files with 226 additions and 7 deletions
|
@ -12,8 +12,13 @@ import androidx.preference.PreferenceManager
|
||||||
import eu.neilalexander.yggdrasil.YggStateReceiver.Companion.YGG_STATE_INTENT
|
import eu.neilalexander.yggdrasil.YggStateReceiver.Companion.YGG_STATE_INTENT
|
||||||
import mobile.Yggdrasil
|
import mobile.Yggdrasil
|
||||||
import org.json.JSONArray
|
import org.json.JSONArray
|
||||||
|
import java.io.FileDescriptor
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
|
import java.net.DatagramPacket
|
||||||
|
import java.net.DatagramSocket
|
||||||
|
import java.net.InetAddress
|
||||||
|
import java.net.Inet6Address
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
|
|
||||||
|
@ -35,6 +40,7 @@ open class PacketTunnelProvider: VpnService() {
|
||||||
private var started = AtomicBoolean()
|
private var started = AtomicBoolean()
|
||||||
|
|
||||||
private lateinit var config: ConfigurationProxy
|
private lateinit var config: ConfigurationProxy
|
||||||
|
private var customDnsPort: Int = 0
|
||||||
|
|
||||||
private var readerThread: Thread? = null
|
private var readerThread: Thread? = null
|
||||||
private var writerThread: Thread? = null
|
private var writerThread: Thread? = null
|
||||||
|
@ -114,6 +120,26 @@ open class PacketTunnelProvider: VpnService() {
|
||||||
yggdrasil.startJSON(config.getJSONByteArray())
|
yggdrasil.startJSON(config.getJSONByteArray())
|
||||||
|
|
||||||
val address = yggdrasil.addressString
|
val address = yggdrasil.addressString
|
||||||
|
var hasCustomDns = false
|
||||||
|
|
||||||
|
val preferences = PreferenceManager.getDefaultSharedPreferences(this.baseContext)
|
||||||
|
val serverString = preferences.getString(KEY_DNS_SERVERS, "")
|
||||||
|
|
||||||
|
// First, check if we have custom DNS servers to determine bypass behavior
|
||||||
|
if (serverString!!.isNotEmpty()) {
|
||||||
|
val servers = serverString.split(",")
|
||||||
|
servers.forEach { server ->
|
||||||
|
val trimmedServer = server.trim()
|
||||||
|
if (trimmedServer.startsWith("127.0.0.1:")) {
|
||||||
|
hasCustomDns = true
|
||||||
|
// Extract port number from 127.0.0.1:5353 format
|
||||||
|
val portString = trimmedServer.substring(10) // Remove "127.0.0.1:"
|
||||||
|
customDnsPort = portString.toIntOrNull() ?: 5353
|
||||||
|
Log.i(TAG, "Found custom IPv4 DNS server: $trimmedServer, port: $customDnsPort")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val builder = Builder()
|
val builder = Builder()
|
||||||
.addAddress(address, 7)
|
.addAddress(address, 7)
|
||||||
.addRoute("200::", 7)
|
.addRoute("200::", 7)
|
||||||
|
@ -125,7 +151,18 @@ open class PacketTunnelProvider: VpnService() {
|
||||||
// and we can't use DNS with Yggdrasil addresses.
|
// and we can't use DNS with Yggdrasil addresses.
|
||||||
.addRoute("2000::", 128)
|
.addRoute("2000::", 128)
|
||||||
.allowFamily(OsConstants.AF_INET)
|
.allowFamily(OsConstants.AF_INET)
|
||||||
.allowBypass()
|
|
||||||
|
// Only allow bypass if no custom DNS servers
|
||||||
|
if (!hasCustomDns) {
|
||||||
|
builder.allowBypass()
|
||||||
|
Log.d(TAG, "Allowing VPN bypass - no custom DNS")
|
||||||
|
} else {
|
||||||
|
Log.i(TAG, "Not allowing VPN bypass - forcing DNS through VPN")
|
||||||
|
// Add route only for our dummy DNS server
|
||||||
|
builder.addRoute("198.18.0.1", 32) // Private IPv4 DNS (dummy)
|
||||||
|
}
|
||||||
|
|
||||||
|
builder
|
||||||
.setBlocking(true)
|
.setBlocking(true)
|
||||||
.setMtu(yggdrasil.mtu.toInt())
|
.setMtu(yggdrasil.mtu.toInt())
|
||||||
.setSession("Yggdrasil")
|
.setSession("Yggdrasil")
|
||||||
|
@ -137,14 +174,20 @@ open class PacketTunnelProvider: VpnService() {
|
||||||
builder.setMetered(false)
|
builder.setMetered(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
val preferences = PreferenceManager.getDefaultSharedPreferences(this.baseContext)
|
// Now add the actual DNS servers
|
||||||
val serverString = preferences.getString(KEY_DNS_SERVERS, "")
|
if (serverString.isNotEmpty()) {
|
||||||
if (serverString!!.isNotEmpty()) {
|
|
||||||
val servers = serverString.split(",")
|
val servers = serverString.split(",")
|
||||||
if (servers.isNotEmpty()) {
|
if (servers.isNotEmpty()) {
|
||||||
servers.forEach {
|
servers.forEach { server ->
|
||||||
Log.i(TAG, "Using DNS server $it")
|
val trimmedServer = server.trim()
|
||||||
builder.addDnsServer(it)
|
if (trimmedServer.startsWith("127.0.0.1:")) {
|
||||||
|
// Add only private DNS as dummy server to intercept
|
||||||
|
builder.addDnsServer("198.18.0.1") // Private IPv4 DNS (dummy)
|
||||||
|
Log.i(TAG, "Added dummy DNS server 198.18.0.1 to intercept for 127.0.0.1:$customDnsPort")
|
||||||
|
} else {
|
||||||
|
Log.i(TAG, "Using standard DNS server $trimmedServer")
|
||||||
|
builder.addDnsServer(trimmedServer)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -333,6 +376,37 @@ open class PacketTunnelProvider: VpnService() {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
val n = readerStream.read(b)
|
val n = readerStream.read(b)
|
||||||
|
|
||||||
|
if (n > 0) {
|
||||||
|
if (n > 20) {
|
||||||
|
val version = (b[0].toInt() and 0xF0) shr 4
|
||||||
|
|
||||||
|
if (version == 4 && n >= 20) {
|
||||||
|
val protocol = b[9].toInt() and 0xFF
|
||||||
|
val srcIP = String.format("%d.%d.%d.%d",
|
||||||
|
b[12].toInt() and 0xFF, b[13].toInt() and 0xFF,
|
||||||
|
b[14].toInt() and 0xFF, b[15].toInt() and 0xFF)
|
||||||
|
val dstIP = String.format("%d.%d.%d.%d",
|
||||||
|
b[16].toInt() and 0xFF, b[17].toInt() and 0xFF,
|
||||||
|
b[18].toInt() and 0xFF, b[19].toInt() and 0xFF)
|
||||||
|
|
||||||
|
if (protocol == 17 && n >= 28) { // UDP
|
||||||
|
val ipHeaderLength = (b[0].toInt() and 0x0F) * 4
|
||||||
|
val srcPort = ((b[ipHeaderLength].toInt() and 0xFF) shl 8) or
|
||||||
|
(b[ipHeaderLength + 1].toInt() and 0xFF)
|
||||||
|
val destPort = ((b[ipHeaderLength + 2].toInt() and 0xFF) shl 8) or
|
||||||
|
(b[ipHeaderLength + 3].toInt() and 0xFF)
|
||||||
|
|
||||||
|
if (destPort == 53 && customDnsPort > 0) {
|
||||||
|
// Forward DNS query to custom server and inject response
|
||||||
|
forwardDnsQuery(b, n, ipHeaderLength + 8, true, srcIP, srcPort)
|
||||||
|
continue@reads // Skip normal processing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
yggdrasil.sendBuffer(b, n.toLong())
|
yggdrasil.sendBuffer(b, n.toLong())
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.i(TAG, "Error in sendBuffer: $e")
|
Log.i(TAG, "Error in sendBuffer: $e")
|
||||||
|
@ -344,4 +418,149 @@ open class PacketTunnelProvider: VpnService() {
|
||||||
readerStream = null
|
readerStream = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun forwardDnsQuery(packet: ByteArray, packetLength: Int, dnsPayloadOffset: Int, isIPv4: Boolean, srcIP: String, srcPort: Int) {
|
||||||
|
try {
|
||||||
|
// Extract DNS payload
|
||||||
|
val dnsPayloadLength = packetLength - dnsPayloadOffset
|
||||||
|
val dnsPayload = ByteArray(dnsPayloadLength)
|
||||||
|
System.arraycopy(packet, dnsPayloadOffset, dnsPayload, 0, dnsPayloadLength)
|
||||||
|
|
||||||
|
|
||||||
|
// Forward to custom DNS server at 127.0.0.1:customDnsPort
|
||||||
|
thread {
|
||||||
|
var socket: DatagramSocket? = null
|
||||||
|
try {
|
||||||
|
|
||||||
|
// Create socket with no specific binding - let system choose any available port on any interface
|
||||||
|
socket = DatagramSocket()
|
||||||
|
socket.soTimeout = 1000 // 1 second timeout
|
||||||
|
|
||||||
|
|
||||||
|
val address = InetAddress.getByName("127.0.0.1")
|
||||||
|
val outPacket = DatagramPacket(dnsPayload, dnsPayload.size, address, customDnsPort)
|
||||||
|
|
||||||
|
socket.send(outPacket)
|
||||||
|
|
||||||
|
// Wait for response
|
||||||
|
val responseBuffer = ByteArray(1024)
|
||||||
|
val responsePacket = DatagramPacket(responseBuffer, responseBuffer.size)
|
||||||
|
|
||||||
|
socket.receive(responsePacket)
|
||||||
|
|
||||||
|
val responseData = ByteArray(responsePacket.length)
|
||||||
|
System.arraycopy(responseBuffer, 0, responseData, 0, responsePacket.length)
|
||||||
|
|
||||||
|
|
||||||
|
// Inject response back into VPN tunnel (IPv4 only)
|
||||||
|
injectDnsResponse(responseData, true, srcIP, srcPort)
|
||||||
|
|
||||||
|
} catch (e: java.net.SocketTimeoutException) {
|
||||||
|
Log.e(TAG, "Timeout waiting for DNS response from 127.0.0.1:$customDnsPort")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Error forwarding DNS query: $e")
|
||||||
|
} finally {
|
||||||
|
socket?.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Error in forwardDnsQuery: $e")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun injectDnsResponse(dnsResponse: ByteArray, isIPv4: Boolean, originalSrcIP: String, originalSrcPort: Int) {
|
||||||
|
try {
|
||||||
|
val writerStream = writerStream ?: return
|
||||||
|
|
||||||
|
|
||||||
|
// Create IPv4 UDP packet with DNS response from 198.18.0.1:53 back to client
|
||||||
|
val responsePacket = createIPv4UdpPacket(dnsResponse, "198.18.0.1", 53, originalSrcIP, originalSrcPort)
|
||||||
|
if (responsePacket.isNotEmpty()) {
|
||||||
|
writerStream.write(responsePacket)
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "Failed to create response packet")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Error injecting DNS response: $e")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createIPv4UdpPacket(payload: ByteArray, srcIP: String, srcPort: Int, dstIP: String, dstPort: Int): ByteArray {
|
||||||
|
val totalLength = 20 + 8 + payload.size // IP header + UDP header + payload
|
||||||
|
val packet = ByteArray(totalLength)
|
||||||
|
|
||||||
|
// IPv4 Header (20 bytes)
|
||||||
|
packet[0] = 0x45 // Version 4, Header length 5 (20 bytes)
|
||||||
|
packet[1] = 0x00 // Type of Service
|
||||||
|
packet[2] = (totalLength shr 8).toByte() // Total length high byte
|
||||||
|
packet[3] = (totalLength and 0xFF).toByte() // Total length low byte
|
||||||
|
packet[4] = 0x00 // Identification high byte
|
||||||
|
packet[5] = 0x00 // Identification low byte
|
||||||
|
packet[6] = 0x40 // Flags: Don't fragment
|
||||||
|
packet[7] = 0x00 // Fragment offset
|
||||||
|
packet[8] = 64 // TTL
|
||||||
|
packet[9] = 17 // Protocol: UDP
|
||||||
|
packet[10] = 0x00 // Header checksum (will calculate)
|
||||||
|
packet[11] = 0x00 // Header checksum
|
||||||
|
|
||||||
|
// Source IP
|
||||||
|
val srcBytes = srcIP.split(".").map { it.toInt().toByte() }
|
||||||
|
packet[12] = srcBytes[0]
|
||||||
|
packet[13] = srcBytes[1]
|
||||||
|
packet[14] = srcBytes[2]
|
||||||
|
packet[15] = srcBytes[3]
|
||||||
|
|
||||||
|
// Destination IP
|
||||||
|
val dstBytes = dstIP.split(".").map { it.toInt().toByte() }
|
||||||
|
packet[16] = dstBytes[0]
|
||||||
|
packet[17] = dstBytes[1]
|
||||||
|
packet[18] = dstBytes[2]
|
||||||
|
packet[19] = dstBytes[3]
|
||||||
|
|
||||||
|
// Calculate IP header checksum
|
||||||
|
val checksum = calculateIPv4Checksum(packet, 0, 20)
|
||||||
|
packet[10] = (checksum shr 8).toByte()
|
||||||
|
packet[11] = (checksum and 0xFF).toByte()
|
||||||
|
|
||||||
|
// UDP Header (8 bytes)
|
||||||
|
val udpLength = 8 + payload.size
|
||||||
|
packet[20] = (srcPort shr 8).toByte() // Source port high byte
|
||||||
|
packet[21] = (srcPort and 0xFF).toByte() // Source port low byte
|
||||||
|
packet[22] = (dstPort shr 8).toByte() // Dest port high byte
|
||||||
|
packet[23] = (dstPort and 0xFF).toByte() // Dest port low byte
|
||||||
|
packet[24] = (udpLength shr 8).toByte() // UDP length high byte
|
||||||
|
packet[25] = (udpLength and 0xFF).toByte() // UDP length low byte
|
||||||
|
packet[26] = 0x00 // UDP checksum (optional for IPv4)
|
||||||
|
packet[27] = 0x00 // UDP checksum
|
||||||
|
|
||||||
|
// Copy payload
|
||||||
|
System.arraycopy(payload, 0, packet, 28, payload.size)
|
||||||
|
|
||||||
|
return packet
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun calculateIPv4Checksum(data: ByteArray, offset: Int, length: Int): Int {
|
||||||
|
var sum = 0L
|
||||||
|
var i = offset
|
||||||
|
|
||||||
|
// Sum all 16-bit words
|
||||||
|
while (i < offset + length - 1) {
|
||||||
|
sum += ((data[i].toInt() and 0xFF) shl 8) + (data[i + 1].toInt() and 0xFF)
|
||||||
|
i += 2
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add odd byte if present
|
||||||
|
if (i < offset + length) {
|
||||||
|
sum += (data[i].toInt() and 0xFF) shl 8
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add carry bits
|
||||||
|
while (sum shr 16 != 0L) {
|
||||||
|
sum = (sum and 0xFFFF) + (sum shr 16)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (sum.inv() and 0xFFFF).toInt()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue