Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add negative leak test for Android #6691

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

niklasberglund
Copy link
Collaborator

@niklasberglund niklasberglund commented Aug 28, 2024

MVP of "negative leak" - traffic is sent to a host and the tests check if traffic leak outside the tunnel.

Note that there's an issue with timestamps from the packets in the parsed packet capture now, this must be resolved before this PR is merged. So the PR might still be updated.


This change is Reviewable

@niklasberglund niklasberglund added the Android Issues related to Android label Aug 28, 2024
@niklasberglund niklasberglund self-assigned this Aug 28, 2024
Copy link

linear bot commented Aug 28, 2024

Copy link
Contributor

@Rawa Rawa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 4 of 11 files at r1.
Reviewable status: 3 of 11 files reviewed, 25 unresolved discussions (waiting on @niklasberglund)


android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/interactor/AppInteractor.kt line 90 at r1 (raw file):

    }

    fun extractOutIpAddress(): String {

Out is implicit from extract


android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/interactor/AppInteractor.kt line 100 at r1 (raw file):

                .text

        val extractedIpAddress = outString.split(" ")[1].split(" ")[0]

Why do we have two split twice here? I believe the second split is unnecessary. Also you already have a extractOutIpAddress() that can be reused.


android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/interactor/AppInteractor.kt line 144 at r1 (raw file):

    private fun String.extractOutIpAddress(): String {
        return split(" ")[1].split(" ")[0]

Same here, .split(" ")[0] should be removable.


android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/interactor/AppInteractor.kt line 147 at r1 (raw file):

    }

    private fun String.separateInIpAddress(): String {

👍 Good that this is private, since String is a commonly used type so we don't want to pollute the scope of String.


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/LeakTest.kt line 33 at r1 (raw file):

            val trafficGenerator = TrafficGenerator(targetIpAddress, 80)

            val session = packetCapture.startCapture()

Maybe the api of a capture can be more intuitive. Right now the user must startCapture and stopCapture, another alternative would be something like a scope. Without going into to much code could probably look like something like this:

packetCapture.createSession {
    // Create session starts with running startCapture()

    // Do stuff where we want to capture the packages

    // Once this lambda is complete stop the capture session and return the result.
}

android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/LeakTest.kt line 35 at r1 (raw file):

            val session = packetCapture.startCapture()
            packetCaptureClient.sendStartCaptureRequest(session)
            trafficGenerator.startGeneratingUDPTraffic(100)

Reminder to use Duration


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/LeakTest.kt line 49 at r1 (raw file):

            device.findObjectWithTimeout(By.text("Disconnect")).click()
            Thread.sleep(2000)

Feels a bit blunt to do Thread.sleep, we should be able to use delay() instead


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/LeakTest.kt line 53 at r1 (raw file):

            trafficGenerator.stopGeneratingUDPTraffic()
            packetCaptureClient.sendStopCaptureRequest(session)
            val streamCollection = packetCapture.stopCapture(session)

It feels a bit redundant where we have to stop twice, doesn't really feel necessary.


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/LeakTest.kt line 55 at r1 (raw file):

            val streamCollection = packetCapture.stopCapture(session)
            val connectedThroughRelayStartEndDatePair =
                streamCollection.getConnectedThroughRelayStartEndDate(relayIpAddress)

This variable and function looks like something taken out of ObjectiveC 😅 Try and create a more consice and readable function and variable name.


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/Networking.kt line 10 at r1 (raw file):

    companion object {
        fun getIPAddress(): String {
            NetworkInterface.getNetworkInterfaces()?.toList()?.map { networkInterface ->

As mentioned before, it could probably be worth using !! if there is something we don't expect to fail, or that is completely fatal. It should also fail the test.


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/Networking.kt line 14 at r1 (raw file):

                    ?.toList()
                    ?.find { !it.isLoopbackAddress && it is Inet4Address }
                    ?.let {

A bit over use of let, assign to val


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/PacketCapture.kt line 22 at r1 (raw file):

import kotlinx.serialization.json.put

data class PacketCaptureSession(val identifier: String = UUID.randomUUID().toString())

Not sure of the data structures of things here but this sounds more like a SessionId with its current content. Also if it is a UUID, let it be a UUID. Also if it is just an identifier we should consider using a value class instead


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/PacketCapture.kt line 33 at r1 (raw file):

    }

    suspend fun stopCapture(session: PacketCaptureSession): StreamCollection {

Do we care if this fail? Can we really always guarantee that we will return a StreamCollection?


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/PacketCapture.kt line 43 at r1 (raw file):

class PacketCaptureClient {
    private val baseUrl = "http://8.8.8.8"

Hard-coded string, if we want to store this it should probably be a const val and if it is supposed to be with this class it should be in a companion object otherwise it would create one string for each instance of the class.


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/PacketCapture.kt line 44 at r1 (raw file):

class PacketCaptureClient {
    private val baseUrl = "http://8.8.8.8"
    private val client =

Preferable we want to inject as much dependencies through the constructor as possible. Not sure of the requirements in this case, e.g isLenient and prettyPrint but we should consider providing this client through the constructor.


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/PacketCapture.kt line 57 at r1 (raw file):

    suspend fun sendStartCaptureRequest(session: PacketCaptureSession) {
        val jsonBody = buildJsonObject { put("label", session.identifier) }

We should be able to create a data structure representing this message and just serializing that. E.g

data class StartCaptureRequest(
    val label: PacketCaptureSession
)

and then just serialize it as JSON. Then you don't have to have a hard-coded label in the function.


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/PacketCapture.kt line 61 at r1 (raw file):

        Logger.v("Sending start capture request with body: $jsonBody.toString()")

        val response =

Shouldn't the result be returned some how? Or what happens if this request fails?


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/PacketCapture.kt line 69 at r1 (raw file):

    suspend fun sendStopCaptureRequest(session: PacketCaptureSession) {
        val response = client.post("$baseUrl/stop-capture/${session.identifier}")

Same here responseis never used. A caller is blind to what happens if something goes wrong.


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/PacketCapture.kt line 73 at r1 (raw file):

    suspend fun sendGetCapturedPacketsRequest(session: PacketCaptureSession): HttpResponse {
        val testDeviceIpAddress = Networking.getIPAddress()

Can we rely on getIPAddress()?


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/PacketCapture.kt line 86 at r1 (raw file):

    @SerialName("tcp") TCP("tcp"),
    @SerialName("udp") UDP("udp"),
    @SerialName("icmp") ICMP("icmp")

Do we need the value strings? Do they serve a purpose?


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/PacketCapture.kt line 97 at r1 (raw file):

    val packets: List<Packet>
) {
    @Contextual var startDate: Date = packets.first().date

As mentioned in our talk, as a general rule data class = no var


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/StreamCollection.kt line 6 at r1 (raw file):

import org.junit.jupiter.api.fail

class StreamCollection {

Feels like this class is pretty useless. It is basically just wrapper around List.


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/StreamCollection.kt line 7 at r1 (raw file):

class StreamCollection {
    private var streams: List<Stream> = emptyList()

Avoid use of var


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/StreamCollection.kt line 19 at r1 (raw file):

    }

    fun getConnectedThroughRelayStartEndDate(relayIpAddress: String): Pair<Date, Date> {

Pair is not very descriptive, there should probably be a better type for this. Date range or similar.
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.ranges/range-to.html


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/StreamCollection.kt line 32 at r1 (raw file):

        }

        for (stream in matchingStreams) {

Not sure exactly what the logic below does, but it feels like it could be simplified a fair bit. If possible we should avoid using

  var startDate: Date? = null
  var endDate: Date? = null

If we can chain streams and fine this out it would be better.


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/TrafficGenerator.kt line 27 at r1 (raw file):

        val packet = DatagramPacket(data, data.size, address, destinationPort)

        timer = Timer()

Never used timer, but it looks a bit weird. There is probably a more intuitive way to do this with coroutines. E.g

val job = yourScope.launch {
    // setup with creating packages
    while(true) {
        socket.send(packet)
        delay(interval)
    }
}

// and then stop
job.cancel()

The entire thing can then be just made to an extension function on a scope to start sending traffic within a scope.

Copy link
Contributor

@Pururun Pururun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 2 of 11 files at r1, 6 of 6 files at r2, all commit messages.
Reviewable status: all files reviewed, 26 unresolved discussions (waiting on @niklasberglund)


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/StreamCollection.kt line 88 at r2 (raw file):

    fun dontAllowTrafficFromTestDevice(toHost: String) {
        streams.forEach { stream ->

Since we get all packages from all streams in a bunch of places, maybe it could be useful to have some kind of allPackages() helper function?

@niklasberglund niklasberglund force-pushed the add-mvp-for-negative-leak-test-droid-824 branch 6 times, most recently from 44aca84 to 9ea2479 Compare September 9, 2024 08:36
@niklasberglund niklasberglund marked this pull request as ready for review September 9, 2024 08:38
Copy link
Collaborator Author

@niklasberglund niklasberglund left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: 1 of 19 files reviewed, 26 unresolved discussions (waiting on @Pururun and @Rawa)


android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/interactor/AppInteractor.kt line 100 at r1 (raw file):

Previously, Rawa (David Göransson) wrote…

Why do we have two split twice here? I believe the second split is unnecessary. Also you already have a extractOutIpAddress() that can be reused.

Removed


android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/interactor/AppInteractor.kt line 144 at r1 (raw file):

Previously, Rawa (David Göransson) wrote…

Same here, .split(" ")[0] should be removable.

Removed


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/LeakTest.kt line 33 at r1 (raw file):

Previously, Rawa (David Göransson) wrote…

Maybe the api of a capture can be more intuitive. Right now the user must startCapture and stopCapture, another alternative would be something like a scope. Without going into to much code could probably look like something like this:

packetCapture.createSession {
    // Create session starts with running startCapture()

    // Do stuff where we want to capture the packages

    // Once this lambda is complete stop the capture session and return the result.
}

Packet capture session and traffic generation session are now scope based


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/LeakTest.kt line 35 at r1 (raw file):

Previously, Rawa (David Göransson) wrote…

Reminder to use Duration

Switched to Duration


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/LeakTest.kt line 49 at r1 (raw file):

Previously, Rawa (David Göransson) wrote…

Feels a bit blunt to do Thread.sleep, we should be able to use delay() instead

Changed to using delay


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/LeakTest.kt line 53 at r1 (raw file):

Previously, Rawa (David Göransson) wrote…

It feels a bit redundant where we have to stop twice, doesn't really feel necessary.

The capture code was a bit messy before, have been tidied up and using a scoped function instead now


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/LeakTest.kt line 55 at r1 (raw file):

Previously, Rawa (David Göransson) wrote…

This variable and function looks like something taken out of ObjectiveC 😅 Try and create a more consice and readable function and variable name.

This function is no longer needed so it has been removed, but I have probably introduced some other long names 😅


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/PacketCapture.kt line 22 at r1 (raw file):

Previously, Rawa (David Göransson) wrote…

Not sure of the data structures of things here but this sounds more like a SessionId with its current content. Also if it is a UUID, let it be a UUID. Also if it is just an identifier we should consider using a value class instead

It is now a UUID


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/PacketCapture.kt line 33 at r1 (raw file):

Previously, Rawa (David Göransson) wrote…

Do we care if this fail? Can we really always guarantee that we will return a StreamCollection?

Have added error handling - failing test if there's an error response


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/PacketCapture.kt line 57 at r1 (raw file):

Previously, Rawa (David Göransson) wrote…

We should be able to create a data structure representing this message and just serializing that. E.g

data class StartCaptureRequest(
    val label: PacketCaptureSession
)

and then just serialize it as JSON. Then you don't have to have a hard-coded label in the function.

Ah yes. Hmm now PacketCaptureSession has been removed, using UUID directly instead.


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/PacketCapture.kt line 61 at r1 (raw file):

Previously, Rawa (David Göransson) wrote…

Shouldn't the result be returned some how? Or what happens if this request fails?

Nothing should be returned, but error handling has been added now


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/PacketCapture.kt line 69 at r1 (raw file):

Previously, Rawa (David Göransson) wrote…

Same here responseis never used. A caller is blind to what happens if something goes wrong.

Nothing should be returned, but error handling has been added now


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/PacketCapture.kt line 86 at r1 (raw file):

Previously, Rawa (David Göransson) wrote…

Do we need the value strings? Do they serve a purpose?

No, removed


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/PacketCapture.kt line 97 at r1 (raw file):

Previously, Rawa (David Göransson) wrote…

As mentioned in our talk, as a general rule data class = no var

Changed


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/TrafficGenerator.kt line 27 at r1 (raw file):

Previously, Rawa (David Göransson) wrote…

Never used timer, but it looks a bit weird. There is probably a more intuitive way to do this with coroutines. E.g

val job = yourScope.launch {
    // setup with creating packages
    while(true) {
        socket.send(packet)
        delay(interval)
    }
}

// and then stop
job.cancel()

The entire thing can then be just made to an extension function on a scope to start sending traffic within a scope.

Nice, turned this into a job 👍


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/StreamCollection.kt line 6 at r1 (raw file):

Previously, Rawa (David Göransson) wrote…

Feels like this class is pretty useless. It is basically just wrapper around List.

This class has been removed


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/StreamCollection.kt line 7 at r1 (raw file):

Previously, Rawa (David Göransson) wrote…

Avoid use of var

This class has been removed


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/StreamCollection.kt line 19 at r1 (raw file):

Previously, Rawa (David Göransson) wrote…

Pair is not very descriptive, there should probably be a better type for this. Date range or similar.
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.ranges/range-to.html

This class has been removed


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/StreamCollection.kt line 32 at r1 (raw file):

Previously, Rawa (David Göransson) wrote…

Not sure exactly what the logic below does, but it feels like it could be simplified a fair bit. If possible we should avoid using

  var startDate: Date? = null
  var endDate: Date? = null

If we can chain streams and fine this out it would be better.

This function is no longer used and has been removed


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/StreamCollection.kt line 88 at r2 (raw file):

Previously, Pururun (Jonatan Rhodin) wrote…

Since we get all packages from all streams in a bunch of places, maybe it could be useful to have some kind of allPackages() helper function?

This function has been removed

Pururun
Pururun previously approved these changes Sep 9, 2024
Copy link
Contributor

@Pururun Pururun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 2 of 13 files at r3, 16 of 16 files at r4, all commit messages.
Reviewable status: all files reviewed, 25 unresolved discussions (waiting on @niklasberglund and @Rawa)

Copy link
Collaborator Author

@niklasberglund niklasberglund left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: 18 of 19 files reviewed, 25 unresolved discussions (waiting on @Pururun and @Rawa)


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/Networking.kt line 10 at r1 (raw file):

Previously, Rawa (David Göransson) wrote…

As mentioned before, it could probably be worth using !! if there is something we don't expect to fail, or that is completely fatal. It should also fail the test.

Turned out these weren't nullable 😯


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/Networking.kt line 14 at r1 (raw file):

Previously, Rawa (David Göransson) wrote…

A bit over use of let, assign to val

Changed

Copy link
Contributor

@Rawa Rawa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: 18 of 19 files reviewed, 27 unresolved discussions (waiting on @niklasberglund and @Pururun)


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/LeakTest.kt line 49 at r1 (raw file):

Previously, niklasberglund (Niklas Berglund) wrote…

Changed to using delay

There is still Thread.sleep here


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/LeakTest.kt line 78 at r5 (raw file):

            lateinit var captureResult: PacketCaptureResult
            val connectTime = DateTime.now()
            TrafficGenerator(targetIpAddress, targetPort).generateTraffic(10.milliseconds) {

Shouldn't this be the other way around? Now we would start generating traffic and then start capturing, thus we might miss some packets until the capture has started. Also then we don't have to do:

lateinit var captureResult: PacketCaptureResult

android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/LeakTest.kt line 93 at r5 (raw file):

            val capturedStreams = captureResult.streams
            val capturedPcap = captureResult.pcap

This is not used by anything right now, should it be removed?


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/LeakTest.kt line 95 at r5 (raw file):

            val capturedPcap = captureResult.pcap

            // Attachment.saveAttachment("capture.pcap", "should-leak", capturedPcap)

Should this still be here?


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/LeakTest.kt line 152 at r5 (raw file):

            // val pcapContent = packetCapture.getPcap()
            // Attachment.saveAttachment("capture.pcap", "should-leak", pcapContent.toByteArray())

Remove?


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/Networking.kt line 10 at r1 (raw file):

Previously, niklasberglund (Niklas Berglund) wrote…

Turned out these weren't nullable 😯

They can be null, Enumeration<NetworkInterface>!. The ! notifies you can Kotlin can determine nullability since it invokes java code.


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/Networking.kt line 7 at r5 (raw file):

import org.junit.Assert.fail

class Networking {

Do we have a usage for this class? Otherwise getIpAddress can just be a function straight away


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/PacketCapture.kt line 22 at r1 (raw file):

Previously, niklasberglund (Niklas Berglund) wrote…

It is now a UUID

Not sure if you missed to push latest, but It is still a String here.

Edit: Saw that you said further down that you've removed this class, I think it shouldn't be removed, we should use a value class containing a UUID, it is more clean and less error prone.


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/PacketCapture.kt line 55 at r5 (raw file):

    }

    suspend fun capturePackets(block: suspend () -> Unit): PacketCaptureResult = runBlocking {

Is it intented to use runBlocking here? To wait in the test case?


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/PacketCapture.kt line 60 at r5 (raw file):

        stopCapture()
        val parsedCapture = getParsedCapture()
        val pcapString = getPcap()

Maybe there should be a private finishCapture(): PackertCaptureResult that does all these 3 things?


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/PacketCapture.kt line 137 at r5 (raw file):

data class Host(val ipAddress: String, val port: Int)

object PacketSerializer : KSerializer<List<Packet>> {

Is this really needed? Feels like you should be able to let serializationx do this for you? Or was it the boolean thing causing this to be the case?


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/PacketCapture.kt line 182 at r5 (raw file):

            destinationAddressAndPort.split(":").first(),
            destinationAddressAndPort.split(":").last().toInt(),
        )

We should create a function on Host.Companion that allows you to create the type from a String. So you can do like this:
Host.fromString(destinationAddressAndPort)


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/PacketCapture.kt line 191 at r5 (raw file):

    @Contextual val rxEndDate: DateTime? = packets.lastOrNull { !it.fromPeer }?.date

    fun getTxPackets(): List<TxPacket> = packets.filterIsInstance<TxPacket>()

generally one would avoid "get" it is very java like.

fun txPackets()
fun rxPackets()

or if it sees a lot of reuse:

val txPackets by lazy { packets.filterIsInstance<TxPacket>() }
val rxPackets by lazy { packets.filterIsInstance<RxPacket>() }

android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/PacketCapture.kt line 197 at r5 (raw file):

@Serializable
sealed class Packet {

sealed interface instead of sealed class


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/TrafficGenerator.kt line 32 at r5 (raw file):

                        "TrafficGenerator sending UDP packet to $destinationHost:$destinationPort"
                    )
                    delay(interval.toLong(DurationUnit.MILLISECONDS))

I believe you can use delay with a Duration?

Copy link
Contributor

@Rawa Rawa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 1 of 1 files at r5, all commit messages.
Reviewable status: all files reviewed, 27 unresolved discussions (waiting on @niklasberglund)

Copy link
Contributor

@Rawa Rawa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: all files reviewed, 25 unresolved discussions (waiting on @niklasberglund)


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/PacketCapture.kt line 57 at r1 (raw file):

Previously, niklasberglund (Niklas Berglund) wrote…

Ah yes. Hmm now PacketCaptureSession has been removed, using UUID directly instead.

Still, present, we should avoid using buildJsonObject and instead use serialize from kotlinx.serialization.


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/PacketCapture.kt line 32 at r5 (raw file):

class PacketCapture {
    private val client = PacketCaptureClient()
    private var sessionUUID = UUID.randomUUID()

Should not var right?


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/PacketCapture.kt line 96 at r5 (raw file):

        val jsonBody = buildJsonObject { put("label", sessionUUID.toString()) }

        Logger.v("Sending start capture request with body: $jsonBody.toString()")

You are not invoking toString()


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/PacketCapture.kt line 106 at r5 (raw file):

    suspend fun sendStopCaptureRequest(sessionUUID: UUID) {
        val response = httpClient.post("$baseUrl/stop-capture/${sessionUUID.toString()}")

Remove response if we don't use it.

Copy link
Contributor

@Rawa Rawa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: all files reviewed, 26 unresolved discussions (waiting on @niklasberglund)


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/PacketCapture.kt line 53 at r5 (raw file):

        Logger.v("PCAP content: ${pcapContent.body<String>()}")
        return pcapContent.body<String>()
    }

Consider

private suspend fun getPcap() = client.sendGetPcapFileRequest(sessionUUID).pcapContent.body<String>().also {
        Logger.v("PCAP content: $it")
    }
private suspend fun getPcap(): String {
    return client.sendGetPcapFileRequest(sessionUUID)
                          .pcapContent.body<String>()
                          .also {
                                Logger.v("PCAP content: $it")
                           }
    }

@niklasberglund niklasberglund force-pushed the add-mvp-for-negative-leak-test-droid-824 branch 2 times, most recently from d5b61cc to dd33077 Compare September 16, 2024 13:44
Copy link
Collaborator Author

@niklasberglund niklasberglund left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: 15 of 19 files reviewed, 26 unresolved discussions (waiting on @Pururun and @Rawa)


android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/interactor/AppInteractor.kt line 90 at r1 (raw file):

Previously, Rawa (David Göransson) wrote…

Out is implicit from extract

"Out" refers to the out IP address


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/LeakTest.kt line 49 at r1 (raw file):

Previously, Rawa (David Göransson) wrote…

There is still Thread.sleep here

Replaced with delay


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/LeakTest.kt line 78 at r5 (raw file):

Previously, Rawa (David Göransson) wrote…

Shouldn't this be the other way around? Now we would start generating traffic and then start capturing, thus we might miss some packets until the capture has started. Also then we don't have to do:

lateinit var captureResult: PacketCaptureResult

Doing it the other way around now


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/LeakTest.kt line 93 at r5 (raw file):

Previously, Rawa (David Göransson) wrote…

This is not used by anything right now, should it be removed?

See answer below https://reviewable.io/reviews/mullvad/mullvadvpn-app/6691#-O6RGIMC6kToCq8sLzIi


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/LeakTest.kt line 95 at r5 (raw file):

Previously, Rawa (David Göransson) wrote…

Should this still be here?

Indecisive. There's a separate Linear issue for storing PCAP files, but most of that ticket has actually been implemented in this PR already. https://linear.app/mullvad/issue/DROID-1315/store-pcap-files-from-packet-captures-on-device

I'm leaning towards implementing this issue as well in this PR. How do you think?


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/Networking.kt line 7 at r5 (raw file):

Previously, Rawa (David Göransson) wrote…

Do we have a usage for this class? Otherwise getIpAddress can just be a function straight away

Good point, but I'm anticipating that this class will grow. For iOS end to end tests there's a "Networking" class which is quite big https://github.com/mullvad/mullvadvpn-app/blob/dc5544701738ad3fab4738bc9314ef95e8d6731e/ios/MullvadVPNUITests/Networking/Networking.swift


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/PacketCapture.kt line 22 at r1 (raw file):

Previously, Rawa (David Göransson) wrote…

Not sure if you missed to push latest, but It is still a String here.

Edit: Saw that you said further down that you've removed this class, I think it shouldn't be removed, we should use a value class containing a UUID, it is more clean and less error prone.

Turned this into a UUID and passing around PacketCatpureSession data class objects instead of UUID


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/PacketCapture.kt line 43 at r1 (raw file):

Previously, Rawa (David Göransson) wrote…

Hard-coded string, if we want to store this it should probably be a const val and if it is supposed to be with this class it should be in a companion object otherwise it would create one string for each instance of the class.

Moved to companion object


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/PacketCapture.kt line 44 at r1 (raw file):

Previously, Rawa (David Göransson) wrote…

Preferable we want to inject as much dependencies through the constructor as possible. Not sure of the requirements in this case, e.g isLenient and prettyPrint but we should consider providing this client through the constructor.

It looks like they cannot be set through the constructor in this case unless I'm missing something, but it's good to know. I'll keep this in mind.


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/PacketCapture.kt line 57 at r1 (raw file):

Previously, Rawa (David Göransson) wrote…

Still, present, we should avoid using buildJsonObject and instead use serialize from kotlinx.serialization.

Replaced with a serializable data class


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/PacketCapture.kt line 73 at r1 (raw file):

Previously, Rawa (David Göransson) wrote…

Can we rely on getIPAddress()?

I think so? 😊


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/PacketCapture.kt line 32 at r5 (raw file):

Previously, Rawa (David Göransson) wrote…

Should not var right?

Yes, changed to val


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/PacketCapture.kt line 96 at r5 (raw file):

Previously, Rawa (David Göransson) wrote…

You are not invoking toString()

Oops 😄 Now it's being invoked.


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/PacketCapture.kt line 106 at r5 (raw file):

Previously, Rawa (David Göransson) wrote…

Remove response if we don't use it.

Removed


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/PacketCapture.kt line 137 at r5 (raw file):

Previously, Rawa (David Göransson) wrote…

Is this really needed? Feels like you should be able to let serializationx do this for you? Or was it the boolean thing causing this to be the case?

Myea, I'm not 100% sure but from what I have read the polymorphism offered out of the box is to separate between classes with @SerialName("expected-type-value-here") which maps based on the value of a type attribute. We could request a type attribute to be added, but maybe that would be something that can be improved later?


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/PacketCapture.kt line 182 at r5 (raw file):

Previously, Rawa (David Göransson) wrote…

We should create a function on Host.Companion that allows you to create the type from a String. So you can do like this:
Host.fromString(destinationAddressAndPort)

Yes that's much better. Using this approach now. Is fromString the common convention for naming a factory method or should it have some other name?


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/PacketCapture.kt line 191 at r5 (raw file):

Previously, Rawa (David Göransson) wrote…

generally one would avoid "get" it is very java like.

fun txPackets()
fun rxPackets()

or if it sees a lot of reuse:

val txPackets by lazy { packets.filterIsInstance<TxPacket>() }
val rxPackets by lazy { packets.filterIsInstance<RxPacket>() }

Renamed


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/PacketCapture.kt line 197 at r5 (raw file):

Previously, Rawa (David Göransson) wrote…

sealed interface instead of sealed class

Changed


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/TrafficGenerator.kt line 32 at r5 (raw file):

Previously, Rawa (David Göransson) wrote…

I believe you can use delay with a Duration?

True, changed 👍

@niklasberglund niklasberglund force-pushed the add-mvp-for-negative-leak-test-droid-824 branch from dd33077 to 9f2c1d3 Compare September 16, 2024 13:45
@niklasberglund niklasberglund force-pushed the add-mvp-for-negative-leak-test-droid-824 branch from 9f2c1d3 to f5d96a5 Compare September 19, 2024 14:50
Copy link
Collaborator Author

@niklasberglund niklasberglund left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: 13 of 19 files reviewed, 26 unresolved discussions (waiting on @Pururun and @Rawa)


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/Networking.kt line 10 at r1 (raw file):

Previously, Rawa (David Göransson) wrote…

They can be null, Enumeration<NetworkInterface>!. The ! notifies you can Kotlin can determine nullability since it invokes java code.

Not-null asserting getNetworkInterfaces()


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/PacketCapture.kt line 53 at r5 (raw file):

Previously, Rawa (David Göransson) wrote…

Consider

private suspend fun getPcap() = client.sendGetPcapFileRequest(sessionUUID).pcapContent.body<String>().also {
        Logger.v("PCAP content: $it")
    }
private suspend fun getPcap(): String {
    return client.sendGetPcapFileRequest(sessionUUID)
                          .pcapContent.body<String>()
                          .also {
                                Logger.v("PCAP content: $it")
                           }
    }

Nice suggestion 👍 Actually the the PCAP content was just debug logging which should've been removed, so I removed it now.


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/PacketCapture.kt line 55 at r5 (raw file):

Previously, Rawa (David Göransson) wrote…

Is it intented to use runBlocking here? To wait in the test case?

It is intended, I believe it is required because suspend functions are invoked? Or is there some other better way?


android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/PacketCapture.kt line 60 at r5 (raw file):

Previously, Rawa (David Göransson) wrote…

Maybe there should be a private finishCapture(): PackertCaptureResult that does all these 3 things?

This is already in the relatively low level class PacketCapture so I think adding an abstraction layer might make it difficult to follow. The capturePackets block function is already an abstraction.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Android Issues related to Android
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants