У нас есть требование к VpnService в нашем приложении, который в основном разрешает трафик только на адреса из белого списка и от него и отбрасывает остальной трафик, а также позволяет время от времени обходить определенный адрес. Текущая реализация, которую мы имеем, основана на https://github.com/julian-klode/dns66/b ... d.java#L93 и адаптирована к также использовать CloudFlare в качестве DNS, когда пользователь использует сотовую связь вместо Wi-Fi.
Зависимость CloudFlare была добавлена, поскольку, по словам создателя нашего сервиса, они не могли обеспечить ту же функциональность при использовании сотовый. Однако наше приложение для iOS прекрасно справляется с этой задачей без каких-либо сторонних зависимостей, поэтому я задался вопросом, возможно ли что-то подобное и на Android с помощью встроенных инструментов.
Я наткнулся на официальные документы Google по адресу https://android.googlesource.com/platfo ... oid/toyvpn и мне было интересно, есть ли что-то вроде это работает независимо от типа сети.
Для справки, вот наша тема и сервис:
class SecureThread(private val vpnService: VpnService, private val context: Context) : Runnable {
companion object {
var allowBypass = false
}
private var dnsServer: InetAddress? = null
private var fileDescriptor: ParcelFileDescriptor? = null
private var thread: Thread? = null
private var inputStream: SecureFileInputStream? = null
private var whitelistedHosts = setOf(
"sample.com",
"second.io"
)
private val secureBuilder = SecureBuilder()
fun startThread() {
SecureService.status = SecureService.Status.STARTING
Timber.i("Starting Vpn Thread")
thread = Thread(this, "some label").apply {
start()
SecureService.status = SecureService.Status.RUNNING
}
}
fun stopThread() {
SecureService.status = SecureService.Status.STOPPING
thread?.interrupt()
inputStream?.interrupt()
thread?.join(2000)
thread = null
SecureService.status = SecureService.Status.STOPPED
}
override fun run() {
try {
var retryTimeout = SecureService.MIN_RETRY_TIME
while (true) {
try {
runVpn()
break
} catch (e: Exception) {
}
Thread.sleep(retryTimeout.toLong() * 1000)
retryTimeout = if (retryTimeout < SecureService.MAX_RETRY_TIME) {
retryTimeout * 2
} else {
retryTimeout
}
}
} catch (e: Exception) {
}
}
private fun runVpn() {
configure()
val stream = SecureFileInputStream(fileDescriptor!!.fileDescriptor)
inputStream = stream
val packet = ByteArray(32767)
val executor =
ThreadPoolExecutor(0, 32, 60L, TimeUnit.SECONDS, SynchronousQueue())
try {
while (true) {
val length = try {
inputStream!!.read(packet)
} catch (e: Exception) {
return
}
val readPacket = packet.copyOfRange(0, length)
val outFd = FileOutputStream(fileDescriptor!!.fileDescriptor)
val dnsSocket = DatagramSocket()
vpnService.protect(dnsSocket)
try {
executor.execute {
handleDnsRequest(readPacket, dnsSocket, outFd)
}
} catch (e: RejectedExecutionException) {
}
}
} finally {
executor.shutdownNow()
fileDescriptor!!.close()
fileDescriptor = null
}
}
private fun handleDnsRequest(
packet: ByteArray,
dnsSocket: DatagramSocket,
outStream: FileOutputStream
) {
try {
val parsedPacket = IpV4Packet.newPacket(packet, 0, packet.size)
if (parsedPacket.payload !is UdpPacket) {
return
}
val dnsRawData = (parsedPacket.payload as UdpPacket).payload.rawData
val dnsMsg = Message(dnsRawData)
if (dnsMsg.question == null) {
return
}
val dnsQueryName = dnsMsg.question.name.toString(true)
val response: ByteArray =
if (whitelistedHosts.any { dnsQueryName.contains(it) } || allowBypass) {
Timber.i("Secure:: pass request with $dnsQueryName, bypass: $allowBypass")
val outPacket = DatagramPacket(dnsRawData, 0, dnsRawData.size, dnsServer!!, 53)
try {
dnsSocket.send(outPacket)
} catch (e: Exception) {
throw e
}
val datagramData = ByteArray(1024)
val replyPacket = DatagramPacket(datagramData, datagramData.size)
dnsSocket.receive(replyPacket)
datagramData
} else {
Timber.i("Secure:: block request with $dnsQueryName")
dnsMsg.header.setFlag(Flags.QR.toInt())
dnsMsg.addRecord(
ARecord(
dnsMsg.question.name,
dnsMsg.question.dClass,
10.toLong(),
Inet4Address.getLocalHost()
), Section.ANSWER
)
dnsMsg.toWire()
}
val udpOutPacket = parsedPacket.payload as UdpPacket
val ipOutPacket = IpV4Packet.Builder(parsedPacket)
.srcAddr(parsedPacket.header.dstAddr)
.dstAddr(parsedPacket.header.srcAddr)
.correctChecksumAtBuild(true)
.correctLengthAtBuild(true)
.payloadBuilder(
UdpPacket.Builder(udpOutPacket)
.srcPort(udpOutPacket.header.dstPort)
.dstPort(udpOutPacket.header.srcPort)
.srcAddr(parsedPacket.header.dstAddr)
.dstAddr(parsedPacket.header.srcAddr)
.correctChecksumAtBuild(true)
.correctLengthAtBuild(true)
.payloadBuilder(
UnknownPacket.Builder()
.rawData(response)
)
).build()
try {
outStream.write(ipOutPacket.rawData)
} catch (e: Exception) {
throw e
}
} catch (e: Exception) {
} finally {
dnsSocket.close()
outStream.close()
}
}
private fun configure() {
dnsServer = InetUtils.getDnsServers(vpnService as Context)
val builder = secureBuilder.configure(vpnService, vpnService.Builder())
fileDescriptor = builder
.setSession("session label").establish()
}
}
Сервис:
class SecureService : VpnService(), Handler.Callback {
private var vpnThread: SecureThread = SecureThread(this, this)
private val connectivityChangedReceiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
onConnectivityChanged(intent)
}
}
private val stopBr: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent) {
if (STOP_MESSAGE == intent.action) {
Timber.i("Secure: Stopping")
onRevoke()
}
}
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Timber.i("Secure:: Start command")
startVpn()
registerReceiver(connectivityChangedReceiver, IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION))
return Service.START_STICKY
}
override fun handleMessage(message: Message): Boolean {
return true
}
override fun onCreate() {
super.onCreate()
LocalBroadcastManager.getInstance(this).registerReceiver(
stopBr, IntentFilter(STOP_MESSAGE)
)
}
override fun onRevoke() {
stopVpn()
super.onRevoke()
}
override fun onDestroy() {
super.onDestroy()
stopVpn()
}
private fun startVpn() {
log("Stopping VPN service")
status = Status.STARTING
restartVpn()
}
private fun restartVpn() {
log("Restarting VPN service")
vpnThread.stopThread()
vpnThread.startThread()
}
private fun stopVpn() {
log("Stopping VPN service")
vpnThread.stopThread()
try {
unregisterReceiver(connectivityChangedReceiver)
} catch (_: IllegalArgumentException) {
}
status = Status.STOPPED
stopSelf()
}
private fun waitForNetwork() {
status = Status.WAITING_FOR_NETWORK
vpnThread.stopThread()
}
private fun onConnectivityChanged(intent: Intent) {
if (intent.getIntExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, 0) == ConnectivityManager.TYPE_VPN) {
return
}
if (intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false)) {
log("No internet connection")
waitForNetwork()
} else {
status = Status.RECONNECTING
restartVpn()
}
}
private fun log(text: String) {
Timber.i("${TAG}$text")
}
companion object {
private const val TAG = "SecureService:: "
const val MIN_RETRY_TIME = 5
const val MAX_RETRY_TIME = 120
const val VPN_PERMISSION_REQUEST_CODE = 666
const val STOP_MESSAGE = "STOP"
var status = Status.STOPPED
}
enum class Status {
STARTING,
RUNNING,
STOPPING,
STOPPED,
RECONNECTING,
RECONNECTING_NETWORK_ERROR,
WAITING_FOR_NETWORK,
}
enum class SecureMessage {
ConnectivityChanged,
}
}
Подробнее здесь: https://stackoverflow.com/questions/790 ... ype-wifi-c
Наблюдение за исходящими/входящими сетевыми пакетами относительно типа соединения (WiFi/сотовая связь) ⇐ Android
-
- Похожие темы
- Ответы
- Просмотры
- Последнее сообщение
-
-
Может ли OS.ListDir повесить с сетевыми дисками? Какой системный вызов он использует?
Anonymous » » в форуме Python - 0 Ответы
- 2 Просмотры
-
Последнее сообщение Anonymous
-
-
-
Использование if(!empty) с несколькими переменными, не входящими в массив
Anonymous » » в форуме Php - 0 Ответы
- 4 Просмотры
-
Последнее сообщение Anonymous
-