Праграма працавала на Android 9 і памірае на тым жа тэлефоне пасля абнаўлення да Android 10. Ваш профіль не чапаны, сертыфікаты тыя ж, а збой адбываецца да любога аўтэнтыфікацыйнага трафіку. Logcat спачатку паказвае нулявы паказальнік, а на некалькі радкоў вышэй стаіць Cannot run program ...: error=13, Permission denied. Гэтае паведамленне пра выкананне файла, а не пра чытанне вашай канфігурацыі.

Што насамрэч адмаўляе памылка 13

Android 10, узровень API 29, забараніў праграмам выконваць файлы, якія яны запісалі ў сваю каталог даных. Правіла накіравана на сховішча, якое спачатку даступна для запісу, а потым для выканання, — менавіта тое, што стварае запускальнік, які капіруе з assets. Старэйшыя зборкі ядра ics-openvpn распакоўваюць з APK выканальны файл openvpn пры першым запуску і стартуюць яго праз ProcessBuilder. Распакоўка па-ранейшаму паспяховая; адмаўляецца менавіта выкананне. Праграма, якая нацэлена на API 29, наследуе правіла, нават калі працуе на прыладзе, якая выйшла са старэйшай версіяй, таму абнаўленне можа зламаць зборку, якую ніхто не чапаў.

У адмове няма нічога спецыфічнага для адной назвы каталога. Файл, створаны праграмай у яе каталозе даных, лічыцца невыканальным незалежна ад шляху, таму getFilesDir(), getCacheDir() і ўласны падкаталог правальваюцца аднолькава.

Аддзяліце збой дапаможніка ад праблемы профілю

Чытайце лог вакол запуску працэсу, а не гартайце зверху. Шуканая сігнатура — радок error=13, а трасіроўка стэка над ім звычайна называе метад запускальніка, які выклікаў ProcessBuilder:

adb logcat -d | grep -n -i -E "cannot run program|permission denied|startOpenVPN"

Калі дапаможнік так і не стартаваў, вы не ўбачыце ніякага вываду логу OpenVPN: ні перамовы шыфраў, ні прасунутых маршрутаў, ні налады DNS. Калі ж гэтыя радкі з’яўляюцца, а збой прыходзіць пазней, бінарнік запусціўся, і праблема ў профілі, серверы або ўзроўні маршрутызацыі. Гэтае адзінае адрозненне зэканоміць шмат часу, які інакш сышоў бы на праўку файлаў .ovpn, якія ніколі не былі вінаватыя. Яно таксама падказвае, якое з двух выпраўленняў ніжэй вам сапраўды патрэбна.

Праверце таксама ўзровень API на прыладзе, якую тэстуеце, а не той, што ў вашай канфігурацыі зборкі. Вобраз эмулятара на API 28 нічога не ўзнаўляе, бо абмежаванне дзейнічае толькі пачынаючы з 29. Адначасова праверце мэтавы SDK у файле зборкі: менавіта яго павышэнне прыцягвае гэтае правіла.

adb shell getprop ro.build.version.sdk

Чаму капіраванне бінарніка ў іншае месца не дапамагае

Відавочны абыходны шлях — запісаць дапаможнік у іншы каталог, даступны для запісу, — правальваецца з той жа прычыны, што і арыгінал. Android па-ранейшаму дазваляе выконваць агульныя бібліятэкі, усталяваныя ў каталог натыўных бібліятэк праграмы, бо іх пакуюць усталёўшчык, а не запісваюць падчас выканання. Менавіта на гэты каталог і павінен паказваць запускальнік. Усё, што вы распакоўваеце самі падчас выканання, як бы вы гэта ні назвалі, застаецца ў катэгорыі забароненых.

Знешняе сховішча яшчэ горш: яно змантавана з noexec, і дапаможнік, змешчаны там, не можа быць запушчаны ніводнай праграмай. Ачыстка даных праграмы таксама не вяртае старыя паводзіны, бо наступны запуск проста стварае тую ж невыканальную копію.

Накіруйце запускальнік на ўпакаваную бібліятэку

Пераназавіце дапаможнік у назву агульнай бібліятэкі і пастаўце яго ў jniLibs для кожнага ABI, які вы падтрымліваеце, а затым хай запускальнік вяртае шлях усталяванай бібліятэкі пачынаючы з API 28:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
    return new File(context.getApplicationInfo().nativeLibraryDir,
                    "libovpnexec.so").getPath();
}

Бібліятэка мусіць распакоўвацца падчас усталявання, каб гэты шлях змяшчаў рэальны файл, таму ўсталюйце android:extractNativeLibs="true" на элеменце <application>. Калі злівальнік маніфеста прынясе іншае значэнне, дадайце tools:replace="android:extractNativeLibs" побач. даведнік па элеменце application апісвае, што кантралюе гэты атрыбут.

Сцеражыцеся дубліката лакальнай зменнай, калі ўручную ўносіце праўку ў запускальнік. Старэйшыя крыніцы аб’яўляюць nativeAPI пазней у тым жа метадзе, а другое аб’яўленне — гэта памылка кампіляцыі, а не часу выканання, таму зборка правальваецца хутка і памылку лёгка заўважыць. Трымайце спіс ABI ў блоку Gradle ў адпаведнасці з каталогамі, якія вы дадаяце.

Зрабіце артэфакт выпуску адпаведным тэставай зборцы

Адладачны APK можа падняць тунель, тады як bundle выпуску ўсё яшчэ падае, бо bundle пакуюць натыўныя бібліятэкі ў іншай форме і можа аддаваць іх сціснутымі з архіва. Калі правальваюцца толькі зборкі з bundle, дадайце гэта ў gradle.properties і перабудуйце:

android.bundle.enableUncompressedNativeLibs=false

Затым пацвердзіце, што ўсталяваны пакет паведамляе чаканы ABI, і паўтарыце праверку на 32-бітнай і 64-бітнай прыладзе, калі вы пастаўляеце абедзве. Кіраўнік пакетаў называе выбраны ABI, таму прылада, якая адкацілася да 32 біт, з’явіцца тут, а не ў справаздачы пра збой:

adb shell dumpsys package com.example.openvpn | grep -i primaryCpuAbi

Адсутная бібліятэка для аднаго ABI ўзнаўляе зыходную памылку доступу толькі на гэтай прыладзе, таму адзін паспяховы тэставы тэлефон не з’яўляецца доказам. Пастаўляйце сцяг маніфеста, назву бібліятэкі і наладу bundle разам; змена аднаго і забыццё астатніх вяртае вас да таго ж збою. Доўгатэрміновае выпраўленне — перайсці на ядро, якое падтрымліваецца, а не насіць латку вечна.

Падобныя артыкулы