Add AWG module

This commit is contained in:
albexk 2023-11-26 13:07:31 +03:00
parent 91f44fb394
commit 9297f877c4
11 changed files with 295 additions and 43 deletions

View file

@ -0,0 +1,18 @@
plugins {
id(libs.plugins.android.library.get().pluginId)
id(libs.plugins.kotlin.android.get().pluginId)
}
kotlin {
jvmToolchain(17)
}
android {
namespace = "org.amnezia.vpn.protocol.awg"
}
dependencies {
compileOnly(project(":utils"))
compileOnly(project(":protocolApi"))
implementation(project(":wireguard"))
}

View file

@ -0,0 +1,79 @@
package org.amnezia.vpn.protocol.awg
import org.amnezia.vpn.protocol.wireguard.Wireguard
import org.json.JSONObject
/**
* Config example:
* {
* "protocol": "awg",
* "description": "Server 1",
* "dns1": "1.1.1.1",
* "dns2": "1.0.0.1",
* "hostName": "100.100.100.0",
* "splitTunnelSites": [
* ],
* "splitTunnelType": 0,
* "awg_config_data": {
* "H1": "969537490",
* "H2": "481688153",
* "H3": "2049399200",
* "H4": "52029755",
* "Jc": "3",
* "Jmax": "1000",
* "Jmin": "50",
* "S1": "49",
* "S2": "60",
* "client_ip": "10.8.1.1",
* "hostName": "100.100.100.0",
* "port": 12345,
* "client_pub_key": "clientPublicKeyBase64",
* "client_priv_key": "privateKeyBase64",
* "psk_key": "presharedKeyBase64",
* "server_pub_key": "publicKeyBase64",
* "config": "[Interface]
* Address = 10.8.1.1/32
* DNS = 1.1.1.1, 1.0.0.1
* PrivateKey = privateKeyBase64
* Jc = 3
* Jmin = 50
* Jmax = 1000
* S1 = 49
* S2 = 60
* H1 = 969537490
* H2 = 481688153
* H3 = 2049399200
* H4 = 52029755
*
* [Peer]
* PublicKey = publicKeyBase64
* PresharedKey = presharedKeyBase64
* AllowedIPs = 0.0.0.0/0, ::/0
* Endpoint = 100.100.100.0:12345
* PersistentKeepalive = 25
* "
* }
* }
*/
class Awg : Wireguard() {
override val ifName: String = "awg0"
override fun parseConfig(config: JSONObject): AwgConfig {
val configDataJson = config.getJSONObject("awg_config_data")
val configData = parseConfigData(configDataJson.getString("config"))
return AwgConfig.build {
configureWireguard(wireguardConfigBuilder(configData))
configData["Jc"]?.let { setJc(it.toInt()) }
configData["Jmin"]?.let { setJmin(it.toInt()) }
configData["Jmax"]?.let { setJmax(it.toInt()) }
configData["S1"]?.let { setS1(it.toInt()) }
configData["S2"]?.let { setS2(it.toInt()) }
configData["H1"]?.let { setH1(it.toLong()) }
configData["H2"]?.let { setH2(it.toLong()) }
configData["H3"]?.let { setH3(it.toLong()) }
configData["H4"]?.let { setH4(it.toLong()) }
}
}
}

View file

@ -0,0 +1,115 @@
package org.amnezia.vpn.protocol.awg
import org.amnezia.vpn.protocol.BadConfigException
import org.amnezia.vpn.protocol.wireguard.WireguardConfig
class AwgConfig private constructor(
wireguardConfigBuilder: WireguardConfig.Builder,
val jc: Int,
val jmin: Int,
val jmax: Int,
val s1: Int,
val s2: Int,
val h1: Long,
val h2: Long,
val h3: Long,
val h4: Long
) : WireguardConfig(wireguardConfigBuilder) {
private constructor(builder: Builder) : this(
builder.wireguardConfigBuilder,
builder.jc,
builder.jmin,
builder.jmax,
builder.s1,
builder.s2,
builder.h1,
builder.h2,
builder.h3,
builder.h4
)
override fun toWgUserspaceString(): String = with(StringBuilder()) {
append(super.toWgUserspaceString())
appendLine("jc=$jc")
appendLine("jmin=$jmin")
appendLine("jmax=$jmax")
appendLine("s1=$s1")
appendLine("s2=$s2")
appendLine("h1=$h1")
appendLine("h2=$h2")
appendLine("h3=$h3")
appendLine("h4=$h4")
return this.toString()
}
class Builder {
internal lateinit var wireguardConfigBuilder: WireguardConfig.Builder
private set
private var _jc: Int? = null
internal var jc: Int
get() = _jc ?: throw BadConfigException("AWG: parameter jc is undefined")
private set(value) { _jc = value}
private var _jmin: Int? = null
internal var jmin: Int
get() = _jmin ?: throw BadConfigException("AWG: parameter jmin is undefined")
private set(value) { _jmin = value}
private var _jmax: Int? = null
internal var jmax: Int
get() = _jmax ?: throw BadConfigException("AWG: parameter jmax is undefined")
private set(value) { _jmax = value}
private var _s1: Int? = null
internal var s1: Int
get() = _s1 ?: throw BadConfigException("AWG: parameter s1 is undefined")
private set(value) { _s1 = value}
private var _s2: Int? = null
internal var s2: Int
get() = _s2 ?: throw BadConfigException("AWG: parameter s2 is undefined")
private set(value) { _s2 = value}
private var _h1: Long? = null
internal var h1: Long
get() = _h1 ?: throw BadConfigException("AWG: parameter h1 is undefined")
private set(value) { _h1 = value}
private var _h2: Long? = null
internal var h2: Long
get() = _h2 ?: throw BadConfigException("AWG: parameter h2 is undefined")
private set(value) { _h2 = value}
private var _h3: Long? = null
internal var h3: Long
get() = _h3 ?: throw BadConfigException("AWG: parameter h3 is undefined")
private set(value) { _h3 = value}
private var _h4: Long? = null
internal var h4: Long
get() = _h4 ?: throw BadConfigException("AWG: parameter h4 is undefined")
private set(value) { _h4 = value}
fun configureWireguard(block: WireguardConfig.Builder.() -> Unit) = apply {
wireguardConfigBuilder = WireguardConfig.Builder().apply(block)
}
fun setJc(jc: Int) = apply { this.jc = jc }
fun setJmin(jmin: Int) = apply { this.jmin = jmin }
fun setJmax(jmax: Int) = apply { this.jmax = jmax }
fun setS1(s1: Int) = apply { this.s1 = s1 }
fun setS2(s2: Int) = apply { this.s2 = s2 }
fun setH1(h1: Long) = apply { this.h1 = h1 }
fun setH2(h2: Long) = apply { this.h2 = h2 }
fun setH3(h3: Long) = apply { this.h3 = h3 }
fun setH4(h4: Long) = apply { this.h4 = h4 }
fun build(): AwgConfig = AwgConfig(this)
}
companion object {
inline fun build(block: Builder.() -> Unit): AwgConfig = Builder().apply(block).build()
}
}

View file

@ -87,6 +87,7 @@ dependencies {
implementation(project(":utils"))
implementation(project(":protocolApi"))
implementation(project(":wireguard"))
implementation(project(":awg"))
implementation(libs.androidx.core)
implementation(libs.androidx.activity)
implementation(libs.androidx.security.crypto)

View file

@ -14,4 +14,4 @@ android {
dependencies {
compileOnly(project(":utils"))
implementation(libs.androidx.annotation)
}
}

View file

@ -18,17 +18,17 @@ private const val TAG = "Protocol"
const val VPN_SESSION_NAME = "AmneziaVPN"
abstract class Protocol(protected val context: Context) {
open lateinit var config: ProtocolConfig
abstract class Protocol {
abstract val statistics: Statistics
abstract fun initialize()
abstract fun initialize(context: Context)
abstract fun parseConfig(config: JSONObject)
abstract fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean)
protected open fun buildVpnInterface(vpnBuilder: Builder) {
abstract fun stopVpn()
protected open fun buildVpnInterface(config: ProtocolConfig, vpnBuilder: Builder) {
vpnBuilder.setSession(VPN_SESSION_NAME)
vpnBuilder.allowFamily(OsConstants.AF_INET)
vpnBuilder.allowFamily(OsConstants.AF_INET6)
@ -49,10 +49,6 @@ abstract class Protocol(protected val context: Context) {
vpnBuilder.setMetered(false)
}
abstract fun startVpn(vpnBuilder: Builder, protect: (Int) -> Boolean)
abstract fun stopVpn()
companion object {
private fun extractLibrary(context: Context, libraryName: String, destination: File): Boolean {
Log.d(TAG, "Extracting library: $libraryName")

View file

@ -5,7 +5,7 @@ import android.os.Build
import androidx.annotation.RequiresApi
import java.net.InetAddress
data class ProtocolConfig(
open class ProtocolConfig protected constructor(
val addresses: Set<InetNetwork>,
val dnsServers: Set<InetAddress>,
val routes: Set<InetNetwork>,
@ -15,7 +15,7 @@ data class ProtocolConfig(
val mtu: Int
) {
private constructor(builder: Builder) : this(
protected constructor(builder: Builder) : this(
builder.addresses,
builder.dnsServers,
builder.routes,
@ -49,6 +49,7 @@ data class ProtocolConfig(
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
fun excludeRoute(route: InetNetwork) = apply { this.excludedRoutes += route }
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
fun excludeRoutes(routes: List<InetNetwork>) = apply { this.excludedRoutes += routes }

View file

@ -33,6 +33,7 @@ include(":qt")
include(":utils")
include(":protocolApi")
include(":wireguard")
include(":awg")
// get values from gradle or local properties
val androidBuildToolsVersion: String by gradleProperties

View file

@ -35,6 +35,7 @@ import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTING
import org.amnezia.vpn.protocol.Statistics
import org.amnezia.vpn.protocol.Status
import org.amnezia.vpn.protocol.VpnStartException
import org.amnezia.vpn.protocol.awg.Awg
import org.amnezia.vpn.protocol.putStatistics
import org.amnezia.vpn.protocol.putStatus
import org.amnezia.vpn.protocol.wireguard.Wireguard
@ -54,6 +55,7 @@ class AmneziaVpnService : VpnService() {
private lateinit var mainScope: CoroutineScope
private var isServiceBound = false
private var protocol: Protocol? = null
private val protocolCache = mutableMapOf<String, Protocol>()
private var protocolState = MutableStateFlow(DISCONNECTED)
private val isConnected
@ -272,11 +274,7 @@ class AmneziaVpnService : VpnService() {
disconnectionJob = null
protocol = getProtocol(config.getString("protocol"))
protocol?.let { protocol ->
protocol.initialize()
protocol.parseConfig(config)
protocol.startVpn(Builder(), ::protect)
}
protocol?.startVpn(config, Builder(), ::protect)
protocolState.value = CONNECTED
}
}
@ -302,10 +300,12 @@ class AmneziaVpnService : VpnService() {
}
private fun getProtocol(protocolName: String): Protocol =
when (protocolName) {
"wireguard" -> Wireguard(applicationContext)
else -> throw IllegalArgumentException("Failed to load $protocolName protocol")
}
protocolCache[protocolName]
?: when (protocolName) {
"wireguard" -> Wireguard()
"awg" -> Awg()
else -> throw IllegalArgumentException("Failed to load $protocolName protocol")
}.apply { initialize(applicationContext) }
/**
* Utils methods

View file

@ -9,17 +9,51 @@ import org.amnezia.vpn.protocol.InetEndpoint
import org.amnezia.vpn.protocol.InetNetwork
import org.amnezia.vpn.protocol.Protocol
import org.amnezia.vpn.protocol.Statistics
import org.amnezia.vpn.protocol.VPN_SESSION_NAME
import org.amnezia.vpn.protocol.VpnStartException
import org.amnezia.vpn.protocol.parseInetAddress
import org.json.JSONObject
/**
* Config example:
* {
* "protocol": "wireguard",
* "description": "Server 1",
* "dns1": "1.1.1.1",
* "dns2": "1.0.0.1",
* "hostName": "100.100.100.0",
* "splitTunnelSites": [
* ],
* "splitTunnelType": 0,
* "wireguard_config_data": {
* "client_ip": "10.8.1.1",
* "hostName": "100.100.100.0",
* "port": 12345,
* "client_pub_key": "clientPublicKeyBase64",
* "client_priv_key": "privateKeyBase64",
* "psk_key": "presharedKeyBase64",
* "server_pub_key": "publicKeyBase64",
* "config": "[Interface]
* Address = 10.8.1.1/32
* DNS = 1.1.1.1, 1.0.0.1
* PrivateKey = privateKeyBase64
*
* [Peer]
* PublicKey = publicKeyBase64
* PresharedKey = presharedKeyBase64
* AllowedIPs = 0.0.0.0/0, ::/0
* Endpoint = 100.100.100.0:12345
* PersistentKeepalive = 25
* "
* }
* }
*/
private const val TAG = "Wireguard"
class Wireguard(context: Context) : Protocol(context) {
open class Wireguard : Protocol() {
private var tunnelHandle: Int = -1
private lateinit var wireguardConfig: WireguardConfig
protected open val ifName: String = "amn0"
override val statistics: Statistics
get() {
@ -40,14 +74,23 @@ class Wireguard(context: Context) : Protocol(context) {
}
}
override fun initialize() {
override fun initialize(context: Context) {
loadSharedLibrary(context, "wg-go")
}
override fun parseConfig(config: JSONObject) {
override fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean) {
val wireguardConfig = parseConfig(config)
start(wireguardConfig, vpnBuilder, protect)
}
protected open fun parseConfig(config: JSONObject): WireguardConfig {
val configDataJson = config.getJSONObject("wireguard_config_data")
val configData = parseConfigData(configDataJson.getString("config"))
wireguardConfig = WireguardConfig.build {
return WireguardConfig.build(wireguardConfigBuilder(configData))
}
protected fun wireguardConfigBuilder(configData: Map<String, String>): WireguardConfig.Builder.() -> Unit =
{
configureBaseProtocol(true) {
configData["Address"]?.let { addAddress(InetNetwork.parse(it)) }
configData["DNS"]?.split(",")?.map { dns ->
@ -64,10 +107,8 @@ class Wireguard(context: Context) : Protocol(context) {
configData["PublicKey"]?.let { setPublicKeyHex(it.base64ToHex()) }
configData["PresharedKey"]?.let { setPreSharedKeyHex(it.base64ToHex()) }
}
this.config = wireguardConfig.baseProtocolConfig
}
private fun parseConfigData(data: String): Map<String, String> {
protected fun parseConfigData(data: String): Map<String, String> {
val parsedData = TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER)
data.lineSequence()
.filter { it.isNotEmpty() && !it.startsWith('[') }
@ -78,20 +119,20 @@ class Wireguard(context: Context) : Protocol(context) {
return parsedData
}
override fun startVpn(vpnBuilder: Builder, protect: (Int) -> Boolean) {
private fun start(config: WireguardConfig, vpnBuilder: Builder, protect: (Int) -> Boolean) {
if (tunnelHandle != -1) {
Log.w(TAG, "Tunnel already up")
return
}
buildVpnInterface(vpnBuilder)
buildVpnInterface(config, vpnBuilder)
vpnBuilder.establish().use { tunFd ->
if (tunFd == null) {
throw VpnStartException("Create VPN interface: permission not granted or revoked")
}
Log.v(TAG, "Wg-go backend ${GoBackend.wgVersion()}")
tunnelHandle = GoBackend.wgTurnOn(VPN_SESSION_NAME, tunFd.detachFd(), wireguardConfig.toWgUserspaceString())
tunnelHandle = GoBackend.wgTurnOn(ifName, tunFd.detachFd(), config.toWgUserspaceString())
}
if (tunnelHandle < 0) {

View file

@ -6,17 +6,17 @@ import org.amnezia.vpn.protocol.ProtocolConfig
internal const val WIREGUARD_DEFAULT_MTU = 1280
data class WireguardConfig(
val baseProtocolConfig: ProtocolConfig,
open class WireguardConfig protected constructor(
protocolConfigBuilder: ProtocolConfig.Builder,
val endpoint: InetEndpoint,
val persistentKeepalive: Int,
val publicKeyHex: String,
val preSharedKeyHex: String,
val privateKeyHex: String
) {
) : ProtocolConfig(protocolConfigBuilder) {
private constructor(builder: Builder) : this(
builder.baseProtocolConfig,
protected constructor(builder: Builder) : this(
builder.protocolConfigBuilder,
builder.endpoint,
builder.persistentKeepalive,
builder.publicKeyHex,
@ -24,11 +24,11 @@ data class WireguardConfig(
builder.privateKeyHex
)
fun toWgUserspaceString(): String = with(StringBuilder()) {
open fun toWgUserspaceString(): String = with(StringBuilder()) {
appendLine("private_key=$privateKeyHex")
appendLine("replace_peers=true")
appendLine("public_key=$publicKeyHex")
baseProtocolConfig.routes.forEach { route ->
routes.forEach { route ->
appendLine("allowed_ip=$route")
}
appendLine("endpoint=$endpoint")
@ -39,7 +39,7 @@ data class WireguardConfig(
}
class Builder {
internal lateinit var baseProtocolConfig: ProtocolConfig
internal lateinit var protocolConfigBuilder: ProtocolConfig.Builder
private set
internal lateinit var endpoint: InetEndpoint
@ -58,7 +58,7 @@ data class WireguardConfig(
private set
fun configureBaseProtocol(blockingMode: Boolean, block: ProtocolConfig.Builder.() -> Unit) = apply {
baseProtocolConfig = ProtocolConfig.Builder(blockingMode).apply(block).build()
protocolConfigBuilder = ProtocolConfig.Builder(blockingMode).apply(block)
}
fun setEndpoint(endpoint: InetEndpoint) = apply { this.endpoint = endpoint }