Skip to content

Commit

Permalink
Move IPv6 tun configuration from VPN layer to network layer (#2950)
Browse files Browse the repository at this point in the history
Task/Issue URL: https://app.asana.com/0/488551667048375/1204154532535669/f

### Description
Move the `tun` configuration for IPv6 from the VPN layer to the network layer.

See the [asana](https://app.asana.com/0/488551667048375/1204154532535669/f) task for more info

### Steps to test this PR

_Verify IPv6 configuration_
- [x] filter logcat by ipv6
- [x] install from this branch and enable ApptP
- [x] verify `Allowing IPv6 traffic through the tun interface` and `Settings IPv6 address in the tun interface` logs appear
- [x] smoke tests AppTP
  • Loading branch information
aitorvs authored Mar 10, 2023
1 parent eb5b05e commit 58a7457
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import com.duckduckgo.mobile.android.vpn.state.VpnStateMonitor.VpnStopReason
import com.duckduckgo.mobile.android.vpn.ui.notification.VpnEnabledNotificationBuilder
import dagger.android.AndroidInjection
import java.net.Inet4Address
import java.net.Inet6Address
import java.net.InetAddress
import java.util.concurrent.Executors
import javax.inject.Inject
Expand Down Expand Up @@ -291,10 +292,14 @@ class TrackerBlockingVpnService : VpnService(), CoroutineScope by MainScope() {
private suspend fun createTunnelInterface(tunnelConfig: VpnTunnelConfig) {
tunInterface = Builder().run {
tunnelConfig.addresses.forEach { addAddress(it.key, it.value) }
val tunHasIpv6Address = tunnelConfig.addresses.any { it.key is Inet6Address }

// Allow IPv6 to go through the VPN
// See https://developer.android.com/reference/android/net/VpnService.Builder#allowFamily(int) for more info as to why
allowFamily(AF_INET6)
if (tunHasIpv6Address) {
logcat { "Allowing IPv6 traffic through the tun interface" }
allowFamily(AF_INET6)
}

val dnsList = getDns(tunnelConfig.dns)

Expand All @@ -319,22 +324,28 @@ class TrackerBlockingVpnService : VpnService(), CoroutineScope by MainScope() {
}
}
}
vpnRoutes.forEach { route ->
// convert to InetAddress to later check if it's loopback
kotlin.runCatching { InetAddress.getByName(route.first) }.getOrNull()?.let {
if (!it.isLoopbackAddress) {
vpnRoutes.mapNotNull { runCatching { InetAddress.getByName(it.first) to it.second }.getOrNull() }
.filter { (it.first is Inet4Address) || (tunHasIpv6Address && it.first is Inet6Address) }
.forEach { route ->
if (!route.first.isLoopbackAddress) {
logcat { "Adding route $route" }
addRoute(route.first, route.second)
runCatching {
addRoute(route.first, route.second)
}.onFailure {
logcat(LogPriority.WARN) { "Error setting route $route: ${it.asLog()}" }
}
} else {
logcat(LogPriority.WARN) { "Tried to add loopback address $it to VPN routes" }
logcat(LogPriority.WARN) { "Tried to add loopback address $route to VPN routes" }
}
}
}
}

// Add the route for all Global Unicast Addresses. This is the IPv6 equivalent to
// IPv4 public IP addresses. They are addresses that routable in the internet
addRoute("2000::", 3)
if (tunHasIpv6Address) {
logcat { "Setting IPv6 address in the tun interface" }
addRoute("2000::", 3)
}

setBlocking(true)
// Cap the max MTU value to avoid backpressure issues in the socket
Expand All @@ -343,23 +354,25 @@ class TrackerBlockingVpnService : VpnService(), CoroutineScope by MainScope() {
configureMeteredConnection()

// Set DNS
dnsList.forEach { addr ->
if (isIpv6SupportEnabled || addr is Inet4Address) {
logcat { "Adding DNS $addr" }
runCatching {
addDnsServer(addr)
}.onFailure { t ->
logcat(LogPriority.ERROR) { "Error setting DNS $addr: ${t.asLog()}" }
if (addr.isLoopbackAddress) {
deviceShieldPixels.reportLoopbackDnsError()
} else if (addr.isAnyLocalAddress) {
deviceShieldPixels.reportAnylocalDnsError()
} else {
deviceShieldPixels.reportGeneralDnsError()
dnsList
.filter { (it is Inet4Address) || (tunHasIpv6Address && it is Inet6Address) }
.forEach { addr ->
if (isIpv6SupportEnabled || addr is Inet4Address) {
logcat { "Adding DNS $addr" }
runCatching {
addDnsServer(addr)
}.onFailure { t ->
logcat(LogPriority.ERROR) { "Error setting DNS $addr: ${t.asLog()}" }
if (addr.isLoopbackAddress) {
deviceShieldPixels.reportLoopbackDnsError()
} else if (addr.isAnyLocalAddress) {
deviceShieldPixels.reportAnylocalDnsError()
} else {
deviceShieldPixels.reportGeneralDnsError()
}
}
}
}
}

safelyAddDisallowedApps(tunnelConfig.appExclusionList.toList())

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,50 @@
package com.duckduckgo.vpn.network.api

interface VpnNetwork {
/**
* Creates the Networking layer
* * @return Returns a non-zero network handler identifier if successful or '0' otherwise
*/
fun create(): Long

/**
* Starts the networking layer
*
* @param contextId is the network handler identifier returned in [create]
* @param logLevel is the logging level
*/
fun start(contextId: Long, logLevel: VpnNetworkLog)

/**
* Runs the VPN networking layer. It will start consuming/producing packets from/to the TUN interface
*
* @param contextId is the network handler identifier returned in [create]
* @param tunfd is the TUN interface file descriptor
*/
fun run(contextId: Long, tunfd: Int)

/**
* Stops the VPN networking layer
* @param contextId is the network handler ID
*/
fun stop(contextId: Long)

/**
* Clears the VPN networking layer resource
* @param contextId is the network handler ID
*/
fun destroy(contextId: Long)

/**
* @return the MTU size
*/
fun mtu(): Int

/**
* Register a [VpnNetworkCallback] to get notified of some network events
*
* @param callback the [VpnNetworkCallback] instance or `null` to unregister previous callback
*/
fun addCallback(callback: VpnNetworkCallback?)
}

Expand Down

0 comments on commit 58a7457

Please sign in to comment.