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

android: reproducibility #1848

Merged
merged 4 commits into from
Jan 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .github/workflows/build-nym-vpn-android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,18 @@ jobs:
- name: Setup Android SDK
uses: android-actions/setup-android@v3

- name: Setup Android SDK
- name: Setup NDK
uses: nttld/setup-ndk@v1
id: setup-ndk
with:
ndk-version: r25c
add-to-path: false

- name: Set env
shell: bash
run: |
echo "ANDROID_NDK_HOME=${{ steps.setup-ndk.outputs.ndk-path }}" >> $GITHUB_ENV
echo "NDK_TOOLCHAIN_DIR=${{ steps.setup-ndk.outputs.ndk-path }}/toolchains/llvm/prebuilt/linux-x86_64/bin" >> $GITHUB_ENV

- name: Grant execute permission for gradlew
run: chmod +x gradlew
Expand Down
9 changes: 8 additions & 1 deletion .github/workflows/build-nym-vpn-core-android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,18 @@ jobs:
run: |
rustup target install aarch64-linux-android

- name: Setup Android SDK
- name: Setup NDK
uses: nttld/setup-ndk@v1
id: setup-ndk
with:
ndk-version: r25c
add-to-path: false

- name: Set env
shell: bash
run: |
echo "ANDROID_NDK_HOME=${{ steps.setup-ndk.outputs.ndk-path }}" >> $GITHUB_ENV
echo "NDK_TOOLCHAIN_DIR=${{ steps.setup-ndk.outputs.ndk-path }}/toolchains/llvm/prebuilt/linux-x86_64/bin" >> $GITHUB_ENV

- name: Install cargo deps
run: |
Expand Down
15 changes: 9 additions & 6 deletions .github/workflows/publish-nym-vpn-android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,6 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
with:
sparse-checkout: |
nym-vpn-android

- name: Install system dependencies
run: |
Expand All @@ -86,7 +83,7 @@ jobs:
- name: Set version release notes
if: ${{ inputs.release_type == 'release' }}
run: |
RELEASE_NOTES="$(cat ${{ github.workspace }}/nym-vpn-android/fastlane/metadata/android/en-US/changelogs/${{ env.VERSION_CODE }}.txt)"
RELEASE_NOTES="$(cat ${{ github.workspace }}/fastlane/metadata/android/en-US/changelogs/${{ env.VERSION_CODE }}.txt)"
echo "RELEASE_NOTES<<EOF" >> $GITHUB_ENV
echo "$RELEASE_NOTES" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
Expand Down Expand Up @@ -214,12 +211,18 @@ jobs:
- name: Setup Android SDK
uses: android-actions/setup-android@v3

- name: Setup Android SDK
- name: Setup NDK
uses: nttld/setup-ndk@v1
id: setup-ndk
with:
ndk-version: r25c
link-to-sdk: true
add-to-path: false

- name: Set env
shell: bash
run: |
echo "ANDROID_NDK_HOME=${{ steps.setup-ndk.outputs.ndk-path }}" >> $GITHUB_ENV
echo "NDK_TOOLCHAIN_DIR=${{ steps.setup-ndk.outputs.ndk-path }}/toolchains/llvm/prebuilt/linux-x86_64/bin" >> $GITHUB_ENV

- name: Grant execute permission for gradlew
run: chmod +x gradlew
Expand Down
File renamed without changes.
2 changes: 2 additions & 0 deletions fastlane/metadata/android/en-US/changelogs/11700.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
What's new:
- Reproducible builds
3 changes: 2 additions & 1 deletion nym-vpn-android/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,5 @@ app/release/output.json
app/signing.properties
*.so
!libjnidispatch.so
nym-vpn-client/src/main/assets/licenses_rust.json
core/src/main/assets/licenses_rust.json

11 changes: 10 additions & 1 deletion nym-vpn-android/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ android {
generateLocaleConfig = true
}

// reproducibility
dependenciesInfo {
// Disables dependency metadata when building APKs.
includeInApk = false
// Disables dependency metadata when building Android App Bundles.
includeInBundle = false
}

defaultConfig {
applicationId = Constants.APP_ID
minSdk = Constants.MIN_SDK
Expand Down Expand Up @@ -71,6 +79,7 @@ android {
isDebuggable = false
isMinifyEnabled = true
isShrinkResources = true
vcsInfo.include = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro",
Expand Down Expand Up @@ -163,7 +172,7 @@ android {

dependencies {

implementation(project(":nym-vpn-client"))
implementation(project(":core"))
implementation(project(":logcat-util"))
implementation(libs.androidx.lifecycle.process)
coreLibraryDesugaring(libs.com.android.tools.desugar)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package net.nymtech.nymvpn.module

import android.content.Context
import android.os.Build
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
Expand All @@ -12,8 +11,6 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import net.nymtech.logcatutil.LogReader
import net.nymtech.logcatutil.LogcatReader
import net.nymtech.nymvpn.BuildConfig
import net.nymtech.nymvpn.NymVpn
import net.nymtech.nymvpn.data.GatewayRepository
import net.nymtech.nymvpn.manager.shortcut.DynamicShortcutManager
import net.nymtech.nymvpn.manager.shortcut.ShortcutManager
Expand All @@ -26,13 +23,11 @@ import net.nymtech.nymvpn.service.gateway.NymApiLibService
import net.nymtech.nymvpn.service.gateway.NymApiService
import net.nymtech.nymvpn.service.notification.NotificationService
import net.nymtech.nymvpn.service.notification.VpnAlertNotifications
import net.nymtech.nymvpn.util.Constants
import net.nymtech.nymvpn.util.FileUtils
import net.nymtech.nymvpn.util.extensions.isAndroidTV
import net.nymtech.nymvpn.util.extensions.toUserAgent
import net.nymtech.vpn.NymApi
import net.nymtech.vpn.backend.Backend
import net.nymtech.vpn.backend.NymBackend
import nym_vpn_lib.UserAgent
import javax.inject.Singleton

@InstallIn(SingletonComponent::class)
Expand All @@ -48,15 +43,9 @@ object AppModule {
@Singleton
@Provides
fun provideNymApi(@IoDispatcher dispatcher: CoroutineDispatcher, @ApplicationContext context: Context): NymApi {
val platform = if (context.isAndroidTV()) "AndroidTV" else "Android"
return NymApi(
dispatcher,
UserAgent(
Constants.APP_PROJECT_NAME,
BuildConfig.VERSION_NAME,
"$platform; ${Build.VERSION.SDK_INT}; ${NymVpn.getCPUArchitecture()}; ${BuildConfig.FLAVOR}",
BuildConfig.COMMIT_HASH,
),
context.toUserAgent(),
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package net.nymtech.nymvpn.service.gateway

import net.nymtech.vpn.NymApi
import net.nymtech.vpn.backend.Tunnel
import net.nymtech.vpn.model.Country
import nym_vpn_lib.GatewayType
import nym_vpn_lib.NetworkEnvironment
import nym_vpn_lib.SystemMessage
import javax.inject.Inject

Expand All @@ -16,11 +14,7 @@ class NymApiLibService @Inject constructor(
return nymApi.getGatewayCountries(type)
}

override suspend fun getEnvironment(environment: Tunnel.Environment): NetworkEnvironment {
return nymApi.getEnvironment(environment)
}

override suspend fun getSystemMessages(environment: Tunnel.Environment): List<SystemMessage> {
return nymApi.getSystemMessages(environment)
override suspend fun getSystemMessages(): List<SystemMessage> {
return nymApi.getSystemMessages()
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
package net.nymtech.nymvpn.service.gateway

import net.nymtech.vpn.backend.Tunnel
import net.nymtech.vpn.model.Country
import nym_vpn_lib.GatewayType
import nym_vpn_lib.NetworkEnvironment
import nym_vpn_lib.SystemMessage

interface NymApiService {
suspend fun getCountries(type: GatewayType): Set<Country>
suspend fun getEnvironment(environment: Tunnel.Environment): NetworkEnvironment
suspend fun getSystemMessages(environment: Tunnel.Environment): List<SystemMessage>
suspend fun getSystemMessages(): List<SystemMessage>
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import net.nymtech.nymvpn.service.tunnel.model.BackendUiEvent
import net.nymtech.nymvpn.service.tunnel.model.MixnetConnectionState
import net.nymtech.nymvpn.util.extensions.requestTileServiceStateUpdate
import net.nymtech.nymvpn.util.extensions.toMB
import net.nymtech.nymvpn.util.extensions.toUserAgent
import net.nymtech.nymvpn.util.extensions.toUserMessage
import net.nymtech.vpn.backend.Backend
import net.nymtech.vpn.backend.Tunnel
Expand Down Expand Up @@ -83,7 +84,7 @@ class NymTunnelManager @Inject constructor(
backendEvent = ::onBackendEvent,
credentialMode = settingsRepository.isCredentialMode(),
)
backend.get().start(tunnel, fromBackground)
backend.get().start(tunnel, fromBackground, context.toUserAgent())
}.onFailure {
if (it is NymVpnInitializeException) {
when (it) {
Expand Down Expand Up @@ -144,7 +145,7 @@ class NymTunnelManager @Inject constructor(

override suspend fun getAccountLinks(): AccountLinks? {
return try {
backend.get().getAccountLinks(settingsRepository.getEnvironment())
backend.get().getAccountLinks()
} catch (_: Exception) {
null
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,7 @@ constructor(

private suspend fun checkSystemMessages() {
runCatching {
val env = settingsRepository.getEnvironment()
val messages = nymApiService.getSystemMessages(env)
val messages = nymApiService.getSystemMessages()
messages.firstOrNull()?.let {
_systemMessage.emit(it)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@ import android.widget.Toast
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.TextUnit
import net.nymtech.nymvpn.BuildConfig
import net.nymtech.nymvpn.NymVpn
import net.nymtech.nymvpn.NymVpn.Companion.instance
import net.nymtech.nymvpn.R
import net.nymtech.nymvpn.service.android.tile.VpnQuickTile
import net.nymtech.nymvpn.util.Constants
import net.nymtech.vpn.model.Country
import nym_vpn_lib.UserAgent
import timber.log.Timber
import java.util.Locale

Expand Down Expand Up @@ -170,3 +173,13 @@ fun Context.launchAppSettings() {
fun Context.isAndroidTV(): Boolean {
return packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
}

fun Context.toUserAgent(): UserAgent {
val platform = if (isAndroidTV()) "AndroidTV" else "Android"
return UserAgent(
Constants.APP_PROJECT_NAME,
BuildConfig.VERSION_NAME,
"$platform; ${Build.VERSION.SDK_INT}; ${NymVpn.getCPUArchitecture()}; ${BuildConfig.FLAVOR}",
BuildConfig.COMMIT_HASH,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,5 +95,6 @@ fun VpnException.toUserMessage(context: Context): String {
is VpnException.RequestZkNym -> "Failed to request nyms"
is VpnException.UpdateAccountEndpointFailure -> "Account endpoint failure"
is VpnException.UpdateDeviceEndpointFailure -> "Device endpoint failure"
is VpnException.StatisticsRecipient -> "Statistics recipient error"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ android {
disable.add("UnsafeOptInUsageError")
}

android {
ndkVersion = "25.2.9519653"
}

namespace = "${Constants.NAMESPACE}.${Constants.VPN_LIB_NAME}"
compileSdk = Constants.COMPILE_SDK

Expand Down Expand Up @@ -90,9 +94,14 @@ dependencies {

implementation(libs.kotlinx.serialization)
implementation(libs.timber)
implementation(libs.jna)
implementation(libs.relinker)

implementation(libs.jna) {
artifact {
type = "aar"
}
}

testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
Expand All @@ -109,7 +118,8 @@ tasks.register<Exec>(Constants.BUILD_LIB_TASK) {
commandLine("echo", "Skipping library build")
return@register
}
val ndkPath = android.sdkDirectory.resolve("ndk").listFilesOrdered().lastOrNull()?.path ?: System.getenv("ANDROID_NDK_HOME")
// prefer system for reproducible builds
val ndkPath = System.getenv("ANDROID_NDK_HOME") ?: android.sdkDirectory.resolve("ndk").listFilesOrdered().lastOrNull()?.path
commandLine("echo", "NDK HOME: $ndkPath")
val script = "${projectDir.path}/src/main/scripts/build-libs.sh"
// TODO find a better way to limit builds
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,16 @@ package net.nymtech.vpn

import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
import net.nymtech.vpn.backend.Tunnel
import net.nymtech.vpn.model.Country
import nym_vpn_lib.GatewayType
import nym_vpn_lib.NetworkEnvironment
import nym_vpn_lib.SystemMessage
import nym_vpn_lib.UserAgent
import nym_vpn_lib.fetchEnvironment
import nym_vpn_lib.fetchSystemMessages
import nym_vpn_lib.getGatewayCountries

class NymApi(
private val ioDispatcher: CoroutineDispatcher,
private val userAgent: UserAgent,
) {

suspend fun getGatewayCountries(type: GatewayType): Set<Country> {
return withContext(ioDispatcher) {
getGatewayCountries(type, userAgent, null).map {
Expand All @@ -25,15 +20,9 @@ class NymApi(
}
}

suspend fun getEnvironment(environment: Tunnel.Environment): NetworkEnvironment {
return withContext(ioDispatcher) {
fetchEnvironment(environment.networkName())
}
}

suspend fun getSystemMessages(environment: Tunnel.Environment): List<SystemMessage> {
suspend fun getSystemMessages(): List<SystemMessage> {
return withContext(ioDispatcher) {
fetchSystemMessages(environment.networkName())
nym_vpn_lib.getSystemMessages()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,23 @@ package net.nymtech.vpn.backend

import nym_vpn_lib.AccountLinks
import nym_vpn_lib.AccountStateSummary
import nym_vpn_lib.UserAgent

interface Backend {

suspend fun init(environment: Tunnel.Environment, credentialMode: Boolean?)

suspend fun getAccountSummary(): AccountStateSummary

suspend fun getAccountLinks(environment: Tunnel.Environment): AccountLinks
suspend fun getAccountLinks(): AccountLinks

suspend fun storeMnemonic(credential: String)

suspend fun isMnemonicStored(): Boolean

suspend fun removeMnemonic()

suspend fun start(tunnel: Tunnel, background: Boolean)
suspend fun start(tunnel: Tunnel, background: Boolean, userAgent: UserAgent)

suspend fun stop()

Expand Down
Loading
Loading