Commit 282f4783 authored by Daniel Wolf's avatar Daniel Wolf
Browse files

Merge remote-tracking branch 'origin/master'

parents 6255f0b9 d76c046d
......@@ -151,3 +151,7 @@ libs/
.idea/**/dbnavigator.xml
.idea/**/markdown-navigator-enh.xml
.idea/**/markdown-navigator.xml
.idea/jarRepositories.xml
.idea/runConfigurations.xml
.idea/misc.xml
.idea/.name
\ No newline at end of file
......@@ -3,21 +3,8 @@ image: thyrlian/android-sdk
before_script:
- chmod +x gradlew
test:
test-build-deploy:
stage: test
script:
- ./gradlew clean assembleRelease test --stacktrace || (find . -type d -wholename "**reports/tests" -exec zip -ru tests.zip {} \; && exit 1)
- find . -type d -wholename "**reports/tests" -exec zip -ru tests.zip {} \;
except:
- /^no_ci.*$/
- /^no_tests.*$/
artifacts:
when: always
paths:
- tests.zip
deploy:
stage: deploy
script:
- ./gradlew clean assembleRelease sourcesJar javadocJar
- ./gradlew publish --stacktrace
\ No newline at end of file
- ./gradlew clean assembleRelease sourcesJar test --stacktrace
- ./gradlew publish --stacktrace
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="14" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="NullableNotNullManager">
<option name="myDefaultNullable" value="android.support.annotation.Nullable" />
<option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
<option name="myNullables">
<value>
<list size="12">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
<item index="2" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
<item index="3" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
<item index="4" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
<item index="5" class="java.lang.String" itemvalue="androidx.annotation.Nullable" />
<item index="6" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNullable" />
<item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.Nullable" />
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableDecl" />
<item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableType" />
<item index="10" class="java.lang.String" itemvalue="android.annotation.Nullable" />
<item index="11" class="java.lang.String" itemvalue="com.android.annotations.Nullable" />
</list>
</value>
</option>
<option name="myNotNulls">
<value>
<list size="11">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
<item index="4" class="java.lang.String" itemvalue="androidx.annotation.NonNull" />
<item index="5" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNonNull" />
<item index="6" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.NonNull" />
<item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullDecl" />
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullType" />
<item index="9" class="java.lang.String" itemvalue="android.annotation.NonNull" />
<item index="10" class="java.lang.String" itemvalue="com.android.annotations.NonNull" />
</list>
</value>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="JDK" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
</set>
</option>
</component>
</project>
\ No newline at end of file
......@@ -24,14 +24,14 @@ ext.nexus_user = getNexusUser()
ext.nexus_password = getNexusPassword()
buildscript {
ext.ANDROID_COMPILE_SDK = (System.getenv("ANDROID_COMPILE_SDK") == null ? 29 : System.getenv("ANDROID_COMPILE_SDK")).toInteger()
ext.ANDROID_COMPILE_SDK = (System.getenv("ANDROID_COMPILE_SDK") == null ? 30 : System.getenv("ANDROID_COMPILE_SDK")).toInteger()
ext.ANDROID_TARGET_SDK = (System.getenv("ANDROID_TARGET_SDK") == null ? ANDROID_COMPILE_SDK : System.getenv("ANDROID_TARGET_SDK")).toInteger()
ext.ANDROID_MIN_SDK = (System.getenv("ANDROID_MIN_SDK") == null ? 21 : System.getenv("ANDROID_COMPILE_SDK")).toInteger()
ext.KOTLIN_VERSION = System.getenv("KOTLIN_VERSION") == null ? "1.4.10" : System.getenv("KOTLIN_VERSION")
ext.ANDROID_GRADLE_PLUGIN_VERSION = System.getenv("ANDROID_GRADLE_PLUGIN_VERSION") == null ? "4.0.1" : System.getenv("ANDROID_GRADLE_PLUGIN_VERSION")
ext.APPCOMPAT_VERSION = "1.2.0"
ext.COROUTINE_VERSION = "1.3.9"
ext.RECYCLERVIEW_VERSION = "1.1.0"
ext.KOTLIN_VERSION = System.getenv("KOTLIN_VERSION") == null ? "1.5.21" : System.getenv("KOTLIN_VERSION")
ext.ANDROID_GRADLE_PLUGIN_VERSION = System.getenv("ANDROID_GRADLE_PLUGIN_VERSION") == null ? "7.0.0" : System.getenv("ANDROID_GRADLE_PLUGIN_VERSION")
ext.APPCOMPAT_VERSION = "1.3.1"
ext.COROUTINE_VERSION = "1.5.1"
ext.RECYCLERVIEW_VERSION = "1.2.0"
ext.ROBOELECTRIC_VERSION = "4.3"
println "COMPILE SDK Version: $ANDROID_COMPILE_SDK"
......@@ -41,13 +41,11 @@ buildscript {
repositories {
google()
jcenter()
mavenCentral()
}
dependencies {
classpath "com.android.tools.build:gradle:$ANDROID_GRADLE_PLUGIN_VERSION"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$KOTLIN_VERSION"
classpath "org.jfrog.buildinfo:build-info-extractor-gradle:4.15.1"
classpath "org.jetbrains.dokka:dokka-android-gradle-plugin:0.9.18"
}
}
......@@ -55,7 +53,7 @@ allprojects {
apply plugin: 'maven-publish'
repositories {
google()
jcenter()
mavenCentral()
maven {
url 'https://nexus.frostnerd.com/repository/libs-release/'
credentials {
......
......@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-all.zip
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'org.jetbrains.dokka-android'
group = 'com.frostnerd.utilskt'
ext {
libraryGroupId = 'com.frostnerd.utilskt'
libraryArtifactId = 'vpntunnelproxy'
libraryVersion = '3.1.6'
libraryGroupId = group
libraryArtifactId = rootProject.name
libraryVersion = '3.1.17'
}
android {
......@@ -64,10 +64,10 @@ dependencies {
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk8:$KOTLIN_VERSION"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$COROUTINE_VERSION"
testImplementation 'junit:junit:4.13'
testImplementation 'junit:junit:4.13.2'
//testImplementation "org.robolectric:robolectric:$ROBOELECTRIC_VERSION"
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
configurations.all {
......@@ -79,16 +79,6 @@ task sourcesJar(type: Jar) {
classifier = 'sources'
}
task dokkaJavadoc(type: org.jetbrains.dokka.gradle.DokkaTask) {
outputFormat = 'javadoc'
outputDirectory = "$buildDir/javadoc"
}
task javadocJar(type: Jar, dependsOn: dokkaJavadoc) {
classifier = 'javadoc'
from "$buildDir/javadoc"
}
afterEvaluate {
publishing {
publications {
......@@ -101,9 +91,6 @@ afterEvaluate {
artifact (sourcesJar) {
classifier = 'sources'
}
artifact (javadocJar) {
classifier = 'javadoc'
}
pom.withXml {
def dependenciesNode = asNode().appendNode('dependencies')
......@@ -130,4 +117,4 @@ afterEvaluate {
}
}
}
publish.dependsOn("assembleRelease", "sourcesJar", "javadocJar")
\ No newline at end of file
publish.dependsOn("assembleRelease", "sourcesJar")
\ No newline at end of file
......@@ -11,7 +11,9 @@ import java.net.DatagramSocket
import java.net.Socket
import java.net.SocketException
import java.util.*
import java.util.concurrent.locks.ReentrantLock
import kotlin.collections.ArrayList
import kotlin.concurrent.withLock
/*
* Copyright (C) 2019 Daniel Wolf (Ch4t4r)
......@@ -41,11 +43,14 @@ class ForwardedPacketStore(var logger: Logger?) {
private val _nonPollableForwardedPackets:LinkedList<FutureAnswer> = LinkedList()
private val newlyForwardedPackets: ArrayList<FutureAnswer> = ArrayList()
private val newlyForwardedNonPollablePackets: ArrayList<FutureAnswer> = ArrayList()
private val newPacketsLock = ReentrantLock()
val nonPollableForwardedPackets:List<FutureAnswer> = _nonPollableForwardedPackets
val forwardedPollablePackets:List<FutureAnswer> = _forwardedPollablePackets
var hasMixedAnswerType:Boolean = true
private set
private val clearLock = Any()
private var clearing = false
val waitingAnswersSize:Int
get() = forwardedPollablePackets.size + nonPollableForwardedPackets.size + newlyForwardedPackets.size + newlyForwardedNonPollablePackets.size
fun addForwardedQuestion(token: DeviceWriteToken, socket: java.io.Closeable) {
addForwardedQuestion(SocketBasedFutureAnswer(token, socket))
......@@ -61,27 +66,33 @@ class ForwardedPacketStore(var logger: Logger?) {
@Throws(IOException::class)
fun addForwardedQuestion(futureAnswer: FutureAnswer) {
if(futureAnswer.isPollable()) newlyForwardedPackets.add(futureAnswer)
else newlyForwardedNonPollablePackets.add(futureAnswer)
newPacketsLock.withLock {
if(futureAnswer.isPollable()) newlyForwardedPackets.add(futureAnswer)
else newlyForwardedNonPollablePackets.add(futureAnswer)
}
}
fun processNewAnswers(pollable:Boolean) {
if(pollable && newlyForwardedPackets.isNotEmpty()) {
hasMixedAnswerType = newlyForwardedNonPollablePackets.isNotEmpty()
for (packet in newlyForwardedPackets) {
packet.index = _forwardedPollablePackets.size
if(packet.isPollable()) _forwardedPollablePackets.add(packet)
else _forwardedPollablePackets.add(packet)
newPacketsLock.withLock {
for (packet in newlyForwardedPackets) {
packet.index = _forwardedPollablePackets.size
if(packet.isPollable()) _forwardedPollablePackets.add(packet)
else _forwardedPollablePackets.add(packet)
}
newlyForwardedPackets.clear()
}
newlyForwardedPackets.clear()
} else if(!pollable && newlyForwardedNonPollablePackets.isNotEmpty()) {
hasMixedAnswerType = newlyForwardedPackets.isNotEmpty()
for (packet in newlyForwardedNonPollablePackets) {
packet.index = _nonPollableForwardedPackets.size
if(packet.isPollable()) _nonPollableForwardedPackets.add(packet)
else _nonPollableForwardedPackets.add(packet)
newPacketsLock.withLock {
hasMixedAnswerType = newlyForwardedPackets.isNotEmpty()
for (packet in newlyForwardedNonPollablePackets) {
packet.index = _nonPollableForwardedPackets.size
if(packet.isPollable()) _nonPollableForwardedPackets.add(packet)
else _nonPollableForwardedPackets.add(packet)
}
newlyForwardedNonPollablePackets.clear()
}
newlyForwardedNonPollablePackets.clear()
}
}
......@@ -92,26 +103,29 @@ class ForwardedPacketStore(var logger: Logger?) {
@Throws(IOException::class)
fun clear(completely:Boolean = false) {
if(clearing) return
clearing = true
logger?.fine("Clearing ForwardedPacketStore")
synchronized(clearLock) {
forwardedPollablePackets.forEach {
cleanup(it)
}
_nonPollableForwardedPackets.forEach {
cleanup(it)
}
newPacketsLock.withLock {
newlyForwardedPackets.forEach {
cleanup(it)
}
newlyForwardedNonPollablePackets.forEach {
cleanup(it)
}
_nonPollableForwardedPackets.clear()
_forwardedPollablePackets.clear()
newlyForwardedPackets.clear()
newlyForwardedNonPollablePackets.clear()
if(completely) logger = null
}
forwardedPollablePackets.forEach {
cleanup(it)
}
_nonPollableForwardedPackets.forEach {
cleanup(it)
}
_nonPollableForwardedPackets.clear()
_forwardedPollablePackets.clear()
clearing = false
if(completely) logger = null
}
}
......@@ -196,13 +210,13 @@ open class SocketBasedFutureAnswer<T>(token: DeviceWriteToken,
val inStream = DataInputStream(it.getInputStream())
val data = ByteArray(inStream.readUnsignedShort())
val actualRead = inStream.read(data)
ReceivedAnswer(data.copyOf(actualRead), socket, actualRead)
ReceivedAnswer(data.copyOf(actualRead), actualRead)
}
is DatagramSocket -> {
val datagramData = ByteArray(udpPacketSize)
val replyPacket = DatagramPacket(datagramData, datagramData.size)
it.receive(replyPacket)
ReceivedAnswer(datagramData.copyOf(replyPacket.length), socket)
ReceivedAnswer(datagramData.copyOf(replyPacket.length))
}
else -> throw IllegalStateException()
}
......@@ -251,13 +265,13 @@ abstract class SocketBasedNonPollableFutureAnswer<T>(token: DeviceWriteToken,
val inStream = DataInputStream(it.getInputStream())
val data = ByteArray(inStream.readUnsignedShort())
val actualRead = inStream.read(data)
ReceivedAnswer(data.copyOf(actualRead), socket, actualRead)
ReceivedAnswer(data.copyOf(actualRead), actualRead)
}
is DatagramSocket -> {
val datagramData = ByteArray(udpPacketSize)
val replyPacket = DatagramPacket(datagramData, datagramData.size)
it.receive(replyPacket)
ReceivedAnswer(datagramData.copyOf(replyPacket.length), socket)
ReceivedAnswer(datagramData.copyOf(replyPacket.length))
}
else -> throw IllegalStateException()
}
......@@ -287,14 +301,13 @@ abstract class SocketBasedNonPollableFutureAnswer<T>(token: DeviceWriteToken,
open class Answer internal constructor(open val bytesTransferred: Int)
data class FailedAnswer(val exception: Exception):Answer(0)
data class ReceivedAnswer(val rawResponse: ByteArray, val socket: Closeable, override val bytesTransferred: Int = rawResponse.size):Answer(bytesTransferred) {
data class ReceivedAnswer(val rawResponse: ByteArray, override val bytesTransferred: Int = rawResponse.size):Answer(bytesTransferred) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is ReceivedAnswer) return false
if (!rawResponse.contentEquals(other.rawResponse)) return false
if (socket != other.socket) return false
if (bytesTransferred != other.bytesTransferred) return false
return true
......@@ -302,8 +315,7 @@ data class ReceivedAnswer(val rawResponse: ByteArray, val socket: Closeable, ove
override fun hashCode(): Int {
var result = rawResponse.contentHashCode()
result = 31 * result + socket.hashCode()
result = 31 * result + bytesTransferred
result = 21 * result + bytesTransferred
return result
}
}
\ No newline at end of file
......@@ -80,7 +80,9 @@ class RetryingVPNTunnelProxy(packetProxy: PacketProxy,
lastCrash = System.currentTimeMillis()
retryCount++
if(retryCount < maxRetries){
runProxyWithRetry(readEnd, writeEnd)
afterCleanup {
runProxyWithRetry(readEnd, writeEnd)
}
}
else {
throw it
......
......@@ -30,6 +30,7 @@ open class TrafficStats(totalBytesRx: Long = 0,
bytesQueuedToDevice: Long = 0,
openSockets: Long = 0,
totalSockets: Long = 0) {
internal var tunnelHandle:TunnelHandle? = null
var totalBytesRx: Long = totalBytesRx
internal set
var openSockets: Long = openSockets
......@@ -55,6 +56,8 @@ open class TrafficStats(totalBytesRx: Long = 0,
var failedAnswers:Long = 0
internal set
fun memInfo() = tunnelHandle?.memInfo()
internal fun addLatency(oneLatency:Long) {
floatingAverageLatency = if(floatingAverageLatency == 0L) oneLatency
else {
......
......@@ -50,6 +50,10 @@ class TunnelHandle(proxy: Proxy,
@Volatile
internal var needsLocking:Boolean = true
fun memInfo():MemoryInfo {
return MemoryInfo(meta.size, byteOutput.size, byteOutput.sumOf { it?.size?.toLong() ?: 0 }, forwardedPacketStore?.waitingAnswersSize ?: 0)
}
fun addMeta(token: DeviceWriteToken, data: Any) {
meta[token.index] = data
}
......@@ -133,3 +137,9 @@ class TunnelHandle(proxy: Proxy,
}
class DeviceWriteToken internal constructor(internal val index:Int)
data class MemoryInfo(
val metaSize:Int,
val outputSize:Int,
val outputSizeBytes:Long,
val waitingPacketSize:Int,
)
\ No newline at end of file
......@@ -14,7 +14,10 @@ import java.io.*
import java.net.DatagramSocket
import java.net.Socket
import java.util.concurrent.locks.ReentrantLock
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
import kotlin.concurrent.withLock
import kotlin.concurrent.write
/*
* Copyright (C) 2019 Daniel Wolf (Ch4t4r)
......@@ -58,11 +61,16 @@ open class VPNTunnelProxy(val packetProxy: PacketProxy,
private var deviceOutput: FileOutputStream? = null
private var tunnelHandle: TunnelHandle? = null
private var exceptionCallbacks = mutableListOf<(Throwable) -> Unit>()
private var cleanupCallbacks = mutableListOf<() -> Unit>()
private var proxyThread:Thread? = null
private var nonPollableProxyThread:Thread? = null
@Volatile
private var polling = false
var forwardingMode:ForwardingMode = ForwardingMode.MIXED
private var proxyThreadActive:Boolean = false
private var cleanupFinalized:Boolean = false
private val clearLock = ReentrantReadWriteLock()
private var ioErrors = 0
constructor(packetProxy: PacketProxy,
trafficStats: TrafficStats = TrafficStats(0, 0),
......@@ -95,6 +103,11 @@ open class VPNTunnelProxy(val packetProxy: PacketProxy,
exceptionCallbacks.add(callback)
}
protected fun afterCleanup(callback:() -> Unit) {
if(cleanupFinalized) callback()
else cleanupCallbacks.add(callback)
}
@Throws(IOException::class, ErrnoException::class)
fun run(tunnel: ParcelFileDescriptor) {
run(tunnel, tunnel)
......@@ -103,6 +116,7 @@ open class VPNTunnelProxy(val packetProxy: PacketProxy,
open fun run(readEnd: ParcelFileDescriptor, writeEnd: ParcelFileDescriptor) {
shouldRun = true
cleanupFinalized = false
logger?.info("run() called.")
val errorStruct = createErrorPipe()
logger?.info("Creating device polls")
......@@ -119,6 +133,7 @@ open class VPNTunnelProxy(val packetProxy: PacketProxy,
logger?.info("Creating tunnel handle")
val localPacketStore = forwardedPacketStore ?: return
tunnelHandle = TunnelHandle(this, localPacketStore, trafficStats)
trafficStats.tunnelHandle = tunnelHandle
packetProxy.injectTunnelHandle(tunnelHandle!!)
logger?.info("Tunnel handle created")
......@@ -129,7 +144,10 @@ open class VPNTunnelProxy(val packetProxy: PacketProxy,
val eventsWriteBothActive = if(readEnd == writeEnd) 0
else OsConstants.POLLOUT
proxyThread = Thread {
proxyThreadActive = true
logger?.info("Proxy started on thread " + Thread.currentThread().name)
val lock = clearLock.readLock()
lock.lock()
try {
try {
while(shouldRun) {
......@@ -158,9 +176,9 @@ open class VPNTunnelProxy(val packetProxy: PacketProxy,
logger?.info("Device filed descriptors became invalid")
shouldRun = false
} else {
if (pollableAnswers.second.isNotEmpty()) processFutureAnswers(pollableAnswers.second, true)
if (deviceOutputPipe.actualEvent(OsConstants.POLLOUT)) fromLoopbackToDevice(deviceOutput) // There is data in loopback
if (deviceInputPipe.actualEvent(OsConstants.POLLIN)) fromDeviceToPacketProxy(deviceInput)
if (pollableAnswers.second.isNotEmpty()) tryIO { processFutureAnswers(pollableAnswers.second, true) }
if (deviceOutputPipe.actualEvent(OsConstants.POLLOUT)) tryIO {fromLoopbackToDevice(deviceOutput) } // There is data in loopback
if (deviceInputPipe.actualEvent(OsConstants.POLLIN)) tryIO { fromDeviceToPacketProxy(deviceInput) }
Thread.sleep(1)
}
} else if (shouldRun) {
......@@ -181,11 +199,15 @@ open class VPNTunnelProxy(val packetProxy: PacketProxy,
} finally {
logger?.info("Proxy thread ended.")
proxyThreadActive = false
lock.unlock()
finalizeCleanup()
}
}
if(forwardingMode != ForwardingMode.ONLY_POLLABLE) nonPollableProxyThread = Thread {
logger?.info("Non-pollable proxy started on thread " + Thread.currentThread().name)
val lock = clearLock.readLock()
lock.lock()
try {
while (shouldRun) {
forwardedPacketStore?.processNewAnswers(false)
......@@ -205,6 +227,8 @@ open class VPNTunnelProxy(val packetProxy: PacketProxy,
} catch (ex:java.lang.Exception) {
if (exceptionCallbacks.isEmpty()) throw ex
else errorOccurred(ex)
} finally {
lock.unlock()
}
logger?.info("Non-pollable proxy ended. Should run: $shouldRun")
}
......@@ -213,6 +237,20 @@ open class VPNTunnelProxy(val packetProxy: PacketProxy,
logger?.info("Done with run().")
}
private fun tryIO(block:() -> Unit) {
try {
block()
} catch (ex:IOException) {
if(ex is InterruptedIOException) throw ex
// Continued errors might indicate an underlying problem.
// either way, 100 is a lot and takes a lot of time to reach
// (besides, this error is again caught in another try)
if(++ioErrors >= 100) {
throw ex
}
}
}
private val packetFromDevice = ByteArray(32767)
fun fromDeviceToPacketProxy(deviceInput:InputStream) {
val readSize = deviceInput.read(packetFromDevice)
......@@ -238,13 +276,11 @@ open class VPNTunnelProxy(val packetProxy: PacketProxy,
logger?.info("stop() called")
if (!proxyStopped) {
proxyStopped = true
val wasThreadRunning = proxyThread?.isAlive ?: false
shouldRun = false
packetProxy.stop()
cleanup()
if(!wasThreadRunning) {
forwardedPacketStore?.clear(true)
forwardedPacketStore = null
if(!proxyThreadActive) {
finalizeCleanup()
}
tunnelHandle?.clear(true)
tunnelHandle = null
......@@ -255,6 +291,7 @@ open class VPNTunnelProxy(val packetProxy: PacketProxy,
private fun cleanup() {
logger?.info { "cleanup() called" }
trafficStats.tunnelHandle = null
proxyThread?.interrupt()
nonPollableProxyThread?.interrupt()
exceptionCallbacks.clear()
......@@ -262,14 +299,19 @@ open class VPNTunnelProxy(val packetProxy: PacketProxy,
}
private fun finalizeCleanup() {
proxyThread = null
nonPollableProxyThread = null
for (fileDescriptor in pipesToClose) {
fileDescriptor.tryClosing()
clearLock.write {
proxyThread = null
nonPollableProxyThread = null