mirror of
https://github.com/amnezia-vpn/amneziawg-android.git
synced 2026-07-03 14:05:13 +02:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9eafc7231d | |||
| c1af1fb8d6 | |||
| 2e9f9d3650 | |||
| f207d7bfd7 | |||
| e71423f46d | |||
| 511397af2f | |||
| 6d654aff91 | |||
| 5bffd56583 | |||
| 6d295a9475 | |||
| c906e7c1c7 | |||
| bebf19aaf6 | |||
| cef17bac38 | |||
| 1f748dc917 | |||
| d6f973d620 | |||
| 5c8f568453 | |||
| d771ee3fc6 | |||
| d52f8c4783 | |||
| 3221335fd8 | |||
| 8cb862d336 | |||
| adc770bb69 | |||
| 7921d15247 | |||
| a96d4072bf | |||
| 161d885ba8 | |||
| f7dc83ae8e | |||
| 2f0782a967 | |||
| 8c8c640270 | |||
| f9af162bed |
+2
-1
@@ -14,4 +14,5 @@ build/
|
||||
*.dex
|
||||
*.iml
|
||||
*.jks
|
||||
keystore.properties
|
||||
gradlew.bat
|
||||
maint/
|
||||
|
||||
+6
-3
@@ -1,3 +1,6 @@
|
||||
[submodule "tunnel/tools/wireguard-tools"]
|
||||
path = tunnel/tools/wireguard-tools
|
||||
url = https://git.zx2c4.com/wireguard-tools
|
||||
[submodule "tunnel/tools/amneziawg-tools"]
|
||||
path = tunnel/tools/amneziawg-tools
|
||||
url = https://github.com/amnezia-vpn/amneziawg-tools
|
||||
[submodule "tunnel/tools/elf-cleaner"]
|
||||
path = tunnel/tools/elf-cleaner
|
||||
url = https://github.com/termux/termux-elf-cleaner
|
||||
|
||||
Generated
-472
@@ -1,472 +0,0 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<option name="AUTODETECT_INDENTS" value="false" />
|
||||
<option name="LINE_SEPARATOR" value=" " />
|
||||
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
|
||||
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
|
||||
<option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
|
||||
<value />
|
||||
</option>
|
||||
<option name="IMPORT_LAYOUT_TABLE">
|
||||
<value>
|
||||
<package name="android" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="com" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="junit" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="net" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="org" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="java" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="javax" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="" withSubpackages="true" static="true" />
|
||||
<emptyLine />
|
||||
</value>
|
||||
</option>
|
||||
<JavaCodeStyleSettings>
|
||||
<option name="GENERATE_FINAL_LOCALS" value="true" />
|
||||
<option name="GENERATE_FINAL_PARAMETERS" value="true" />
|
||||
<option name="INSERT_INNER_CLASS_IMPORTS" value="true" />
|
||||
<option name="IMPORT_LAYOUT_TABLE">
|
||||
<value>
|
||||
<package name="android" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="com" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="junit" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="net" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="org" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="java" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="javax" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="" withSubpackages="true" static="true" />
|
||||
<emptyLine />
|
||||
</value>
|
||||
</option>
|
||||
</JavaCodeStyleSettings>
|
||||
<JetCodeStyleSettings>
|
||||
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
|
||||
<value>
|
||||
<package name="kotlinx.android.synthetic" withSubpackages="true" static="false" />
|
||||
</value>
|
||||
</option>
|
||||
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="10" />
|
||||
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="10" />
|
||||
</JetCodeStyleSettings>
|
||||
<XML>
|
||||
<option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
|
||||
</XML>
|
||||
<codeStyleSettings language="JAVA">
|
||||
<option name="METHOD_ANNOTATION_WRAP" value="0" />
|
||||
<option name="FIELD_ANNOTATION_WRAP" value="0" />
|
||||
<arrangement>
|
||||
<groups />
|
||||
<rules>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<FIELD>true</FIELD>
|
||||
<FINAL>true</FINAL>
|
||||
<PUBLIC>true</PUBLIC>
|
||||
<STATIC>true</STATIC>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<FIELD>true</FIELD>
|
||||
<FINAL>true</FINAL>
|
||||
<PROTECTED>true</PROTECTED>
|
||||
<STATIC>true</STATIC>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<FIELD>true</FIELD>
|
||||
<FINAL>true</FINAL>
|
||||
<PACKAGE_PRIVATE>true</PACKAGE_PRIVATE>
|
||||
<STATIC>true</STATIC>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<FIELD>true</FIELD>
|
||||
<FINAL>true</FINAL>
|
||||
<PRIVATE>true</PRIVATE>
|
||||
<STATIC>true</STATIC>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<FIELD>true</FIELD>
|
||||
<PUBLIC>true</PUBLIC>
|
||||
<STATIC>true</STATIC>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<FIELD>true</FIELD>
|
||||
<PROTECTED>true</PROTECTED>
|
||||
<STATIC>true</STATIC>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<FIELD>true</FIELD>
|
||||
<PACKAGE_PRIVATE>true</PACKAGE_PRIVATE>
|
||||
<STATIC>true</STATIC>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<FIELD>true</FIELD>
|
||||
<PRIVATE>true</PRIVATE>
|
||||
<STATIC>true</STATIC>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<INITIALIZER_BLOCK>true</INITIALIZER_BLOCK>
|
||||
<STATIC>true</STATIC>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<FIELD>true</FIELD>
|
||||
<FINAL>true</FINAL>
|
||||
<PUBLIC>true</PUBLIC>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<FIELD>true</FIELD>
|
||||
<FINAL>true</FINAL>
|
||||
<PROTECTED>true</PROTECTED>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<FIELD>true</FIELD>
|
||||
<FINAL>true</FINAL>
|
||||
<PACKAGE_PRIVATE>true</PACKAGE_PRIVATE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<FIELD>true</FIELD>
|
||||
<FINAL>true</FINAL>
|
||||
<PRIVATE>true</PRIVATE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<FIELD>true</FIELD>
|
||||
<PUBLIC>true</PUBLIC>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<FIELD>true</FIELD>
|
||||
<PROTECTED>true</PROTECTED>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<FIELD>true</FIELD>
|
||||
<PACKAGE_PRIVATE>true</PACKAGE_PRIVATE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<FIELD>true</FIELD>
|
||||
<PRIVATE>true</PRIVATE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<FIELD>true</FIELD>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<INITIALIZER_BLOCK>true</INITIALIZER_BLOCK>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<CONSTRUCTOR>true</CONSTRUCTOR>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<METHOD>true</METHOD>
|
||||
<STATIC>true</STATIC>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<METHOD>true</METHOD>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<ENUM>true</ENUM>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<INTERFACE>true</INTERFACE>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<CLASS>true</CLASS>
|
||||
<STATIC>true</STATIC>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<CLASS>true</CLASS>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
</rules>
|
||||
</arrangement>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="XML">
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
</indentOptions>
|
||||
<arrangement>
|
||||
<rules>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:android</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:id</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>style</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>ANDROID_ATTRIBUTE_ORDER</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
</rules>
|
||||
</arrangement>
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
||||
Generated
-5
@@ -1,5 +0,0 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</state>
|
||||
</component>
|
||||
Generated
-6
@@ -1,6 +0,0 @@
|
||||
<component name="CopyrightManager">
|
||||
<copyright>
|
||||
<option name="notice" value="Copyright © &#36;today.year WireGuard LLC. All Rights Reserved. SPDX-License-Identifier: Apache-2.0" />
|
||||
<option name="myName" value="Default" />
|
||||
</copyright>
|
||||
</component>
|
||||
Generated
-3
@@ -1,3 +0,0 @@
|
||||
<component name="CopyrightManager">
|
||||
<settings default="Default" />
|
||||
</component>
|
||||
Generated
-527
@@ -1,527 +0,0 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0" is_locked="false">
|
||||
<option name="myName" value="Default" />
|
||||
<inspection_tool class="AbstractClassNamingConvention" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="AbstractClassWithoutAbstractMethods" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AccessToNonThreadSafeStaticFieldFromInstance" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="nonThreadSafeClasses">
|
||||
<value />
|
||||
</option>
|
||||
<option name="nonThreadSafeTypes" value="" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="AndroidLintIconExpectedSize" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AndroidLintNegativeMargin" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AndroidLintTypographyQuotes" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AnnotationNamingConvention" enabled="false" level="WARNING" enabled_by_default="false">
|
||||
<option name="m_regex" value="[A-Z][A-Za-z\d]*" />
|
||||
<option name="m_minLength" value="8" />
|
||||
<option name="m_maxLength" value="64" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="AnonymousClassVariableHidesContainingMethodVariable" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AnonymousInnerClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AnonymousInnerClassMayBeStatic" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ApiName" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="ApiNamespace" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="ApiParameter" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="ArrayEquality" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ArrayIssues" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="AssignmentOrReturnOfFieldWithMutableTypeMerged" />
|
||||
<inspection_tool class="AssignmentToCatchBlockParameter" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AssignmentToCollectionFieldFromParameter" enabled="false" level="WARNING" enabled_by_default="false">
|
||||
<option name="ignorePrivateMethods" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="AssignmentToDateFieldFromParameter" enabled="false" level="WARNING" enabled_by_default="false">
|
||||
<option name="ignorePrivateMethods" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="AssignmentToForLoopParameter" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_checkForeachParameters" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="AssignmentToLambdaParameter" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AssignmentToSuperclassField" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AssignmentUsedAsCondition" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AutoCloseableResource" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="METHOD_MATCHER_CONFIG" value="java.util.Formatter,format,java.io.Writer,append,com.google.common.base.Preconditions,checkNotNull,org.hibernate.Session,close,java.io.PrintWriter,printf,java.util.HashMap,put,java.util.Map,put" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="BadExceptionCaught" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="exceptionsString" value="" />
|
||||
<option name="exceptions">
|
||||
<value />
|
||||
</option>
|
||||
</inspection_tool>
|
||||
<inspection_tool class="BadOddness" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="BooleanExpressionMayBeConditional" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="BooleanParameter" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="onlyReportMultiple" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="CallToSimpleGetterInClass" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreGetterCallsOnOtherObjects" value="false" />
|
||||
<option name="onlyReportPrivateGetter" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="CallToStringConcatCanBeReplacedByOperator" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CannotResolve" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="CastConflictsWithInstanceof" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CatchMayIgnoreException" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreCatchBlocksWithComments" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ChainedEquality" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ClassInitializer" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ClassNameDiffersFromFileName" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ClassNamingConvention" enabled="false" level="WARNING" enabled_by_default="false">
|
||||
<option name="m_regex" value="[A-Z][A-Za-z\d]*" />
|
||||
<option name="m_minLength" value="8" />
|
||||
<option name="m_maxLength" value="64" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ClassReferencesSubclass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ClassWithOnlyPrivateConstructors" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ComparableImplementedButEqualsNotOverridden" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CompareToUsesNonFinalVariable" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ConditionalExpression" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="ConditionalExpressionWithIdenticalBranches" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ConstExpressionRequired" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="ConstantNamingConvention" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="onlyCheckImmutables" value="false" />
|
||||
<option name="m_regex" value="[A-Z][A-Z_\d]*" />
|
||||
<option name="m_minLength" value="0" />
|
||||
<option name="m_maxLength" value="0" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ConstantValueVariableUse" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ConstructionIsNotAllowed" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="Constructor" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="Convert2streamapi" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="REPLACE_TRIVIAL_FOREACH" value="true" />
|
||||
<option name="SUGGEST_FOREACH" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ConvertAnnotations" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="CovariantEquals" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="DeclareCollectionAsInterface" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreLocalVariables" value="false" />
|
||||
<option name="ignorePrivateMethodsAndFields" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="DefaultNotLastCaseInSwitch" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="DerivedFunctionsReturnTypeMismatch" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="DeserializableClassInSecureContext" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="DoubleBraceInitialization" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="DoubleCheckedLocking" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreOnVolatileVariables" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="DoubleLiteralMayBeFloatLiteral" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="DuplicateAlternationBranch" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="DuplicateBooleanBranch" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="DuplicateDeclarations" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="DynamicRegexReplaceableByCompiledPattern" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ElementOnlyUsedFromTestCode" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="EmptyClass" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignorableAnnotations">
|
||||
<value />
|
||||
</option>
|
||||
<option name="ignoreClassWithParameterization" value="true" />
|
||||
<option name="ignoreThrowables" value="true" />
|
||||
<option name="commentsAreContent" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="EmptySynchronizedStatement" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="EnumSwitchStatementWhichMissesCases" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreSwitchStatementsWithDefault" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="EnumeratedClassNamingConvention" enabled="false" level="WARNING" enabled_by_default="false">
|
||||
<option name="m_regex" value="[A-Z][A-Za-z\d]*" />
|
||||
<option name="m_minLength" value="8" />
|
||||
<option name="m_maxLength" value="64" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="EnumeratedConstantNamingConvention" enabled="false" level="WARNING" enabled_by_default="false">
|
||||
<option name="m_regex" value="[A-Z][A-Z_\d]*" />
|
||||
<option name="m_minLength" value="5" />
|
||||
<option name="m_maxLength" value="32" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="EnumerationCanBeIteration" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="EqualityOperatorComparesObjects" enabled="true" level="INFORMATION" enabled_by_default="true" />
|
||||
<inspection_tool class="EqualsAndHashcode" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="EqualsCalledOnEnumConstant" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="EqualsUsesNonFinalVariable" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="EscapedMetaCharacter" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="ExceptionFromCatchWhichDoesntWrap" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreGetMessage" value="false" />
|
||||
<option name="ignoreCantWrap" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ExtendsThrowable" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ExtendsUtilityClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="FallthruInSwitchStatement" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="FieldAccessedSynchronizedAndUnsynchronized" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="countGettersAndSetters" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="FieldMayBeFinal" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="FieldMayBeStatic" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="FieldMustBeInitialized" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="FinalMethodInFinalClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="FloatingPointEquality" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ForLoopReplaceableByWhile" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreLoopsWithoutConditions" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="FrequentlyUsedInheritorInspection" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
||||
<inspection_tool class="FullJavaName" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="FullMethodName" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="FunctionParameterCountMismatch" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="FuseStreamOperations" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="GrFieldAlreadyDefined" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="GroovyAnnotationNamingConvention" enabled="false" level="WARNING" enabled_by_default="false">
|
||||
<option name="m_regex" value="[A-Z][A-Za-z\d]*" />
|
||||
<option name="m_minLength" value="8" />
|
||||
<option name="m_maxLength" value="64" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="GroovyClassNamingConvention" enabled="false" level="WARNING" enabled_by_default="false">
|
||||
<option name="m_regex" value="[A-Z][A-Za-z\d]*" />
|
||||
<option name="m_minLength" value="8" />
|
||||
<option name="m_maxLength" value="64" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="GroovyEnumerationNamingConvention" enabled="false" level="WARNING" enabled_by_default="false">
|
||||
<option name="m_regex" value="[A-Z][A-Za-z\d]*" />
|
||||
<option name="m_minLength" value="8" />
|
||||
<option name="m_maxLength" value="64" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="GroovyInterfaceNamingConvention" enabled="false" level="WARNING" enabled_by_default="false">
|
||||
<option name="m_regex" value="[A-Z][A-Za-z\d]*" />
|
||||
<option name="m_minLength" value="8" />
|
||||
<option name="m_maxLength" value="64" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="HashCodeUsesNonFinalVariable" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="HtmlTagCanBeJavadocTag" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="IOResource" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoredTypesString" value="java.io.ByteArrayOutputStream,java.io.ByteArrayInputStream,java.io.StringBufferInputStream,java.io.CharArrayWriter,java.io.CharArrayReader,java.io.StringWriter,java.io.StringReader" />
|
||||
<option name="insideTryAllowed" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="IfMayBeConditional" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="IfStatementWithIdenticalBranches" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="IgnoreResultOfCall" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_reportAllNonLibraryCalls" value="false" />
|
||||
<option name="callCheckString" value="java.io.File,.*,java.io.InputStream,read|skip|available|markSupported,java.io.Reader,read|skip|ready|markSupported,java.lang.Boolean,.*,java.lang.Byte,.*,java.lang.Character,.*,java.lang.Double,.*,java.lang.Float,.*,java.lang.Integer,.*,java.lang.Long,.*,java.lang.Math,.*,java.lang.Object,equals|hashCode|toString,java.lang.Short,.*,java.lang.StrictMath,.*,java.lang.String,.*,java.math.BigInteger,.*,java.math.BigDecimal,.*,java.net.InetAddress,.*,java.net.URI,.*,java.util.UUID,.*,java.util.regex.Matcher,pattern|toMatchResult|start|end|group|groupCount|matches|find|lookingAt|quoteReplacement|replaceAll|replaceFirst|regionStart|regionEnd|hasTransparantBounds|hasAnchoringBounds|hitEnd|requireEnd,java.util.regex.Pattern,.*,java.util.stream.BaseStream,.*" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ImplicitSubclassInspection" enabled="false" level="ERROR" enabled_by_default="false" />
|
||||
<inspection_tool class="IncompatibleTypes" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="InitializerIssues" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="InnerClassReferencedViaSubclass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="InnerClassVariableHidesOuterClassVariable" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreInvisibleFields" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="InstanceMethodNamingConvention" enabled="false" level="WARNING" enabled_by_default="false">
|
||||
<option name="m_regex" value="[a-z][A-Za-z\d]*" />
|
||||
<option name="m_minLength" value="4" />
|
||||
<option name="m_maxLength" value="32" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="InstanceVariableNamingConvention" enabled="false" level="WARNING" enabled_by_default="false">
|
||||
<option name="m_regex" value="m_[a-z][A-Za-z\d]*" />
|
||||
<option name="m_minLength" value="5" />
|
||||
<option name="m_maxLength" value="32" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="InstanceofCatchParameter" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="InstanceofThis" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="InstantiationOfUtilityClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="IntLiteralMayBeLongLiteral" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="IntegerTypeRequired" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="InterfaceMayBeAnnotatedFunctional" enabled="true" level="INFORMATION" enabled_by_default="true" />
|
||||
<inspection_tool class="InterfaceNamingConvention" enabled="false" level="WARNING" enabled_by_default="false">
|
||||
<option name="m_regex" value="[A-Z][A-Za-z\d]*" />
|
||||
<option name="m_minLength" value="8" />
|
||||
<option name="m_maxLength" value="64" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="InvalidParameterAnnotations" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="IteratorNextDoesNotThrowNoSuchElementException" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="JUnit3MethodNamingConvention" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="JUnit4MethodNamingConvention" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="JUnitAbstractTestClassNamingConvention" enabled="false" level="WARNING" enabled_by_default="false">
|
||||
<option name="m_regex" value="[A-Z][A-Za-z\d]*TestCase" />
|
||||
<option name="m_minLength" value="12" />
|
||||
<option name="m_maxLength" value="64" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="JUnitTestClassNamingConvention" enabled="false" level="WARNING" enabled_by_default="false">
|
||||
<option name="m_regex" value="[A-Z][A-Za-z\d]*Test" />
|
||||
<option name="m_minLength" value="8" />
|
||||
<option name="m_maxLength" value="64" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="Java8ArraySetAll" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="Java9CollectionFactory" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="JavaRequiresAutoModule" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="JavadocHtmlLint" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="KeySetIterationMayUseEntrySet" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="LambdaCanBeMethodCall" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="LambdaParameterHidingMemberVariable" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="LengthOneStringsInConcatenation" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="LimitedScopeInnerClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ListIndexOfReplaceableByContains" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="LiteralAsArgToStringEquals" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="LocalCanBeFinal" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="REPORT_VARIABLES" value="true" />
|
||||
<option name="REPORT_PARAMETERS" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="LocalVariableHidingMemberVariable" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreInvisibleFields" value="true" />
|
||||
<option name="m_ignoreStaticMethods" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="LoopWithImplicitTerminationCondition" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MagicNumber" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreInitialCapacity" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="MapReplaceableByEnumMap" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MemberVisibility" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="MemberVisibilityCanPrivate" enabled="true" level="INFO" enabled_by_default="true" />
|
||||
<inspection_tool class="MethodMayBeStatic" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_onlyPrivateOrFinal" value="false" />
|
||||
<option name="m_ignoreEmptyMethods" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="MethodMayBeSynchronized" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MethodName" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="MethodOnlyUsedFromInnerClass" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreMethodsAccessedFromAnonymousClass" value="false" />
|
||||
<option name="ignoreStaticMethodsFromNonStaticInnerClass" value="false" />
|
||||
<option name="onlyReportStaticMethods" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="MethodOverloadsParentMethod" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="reportIncompatibleParameters" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="MethodOverridesInaccessibleMethodOfSuper" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MethodOverridesStaticMethod" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MethodParameterType" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="MethodReturnType" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="MissingDeprecatedAnnotation" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MissingOverrideAnnotation" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreObjectMethods" value="false" />
|
||||
<option name="ignoreAnonymousClassMethods" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="MissortedModifiers" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_requireAnnotationsFirst" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="MoveFieldAssignmentToInitializer" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
||||
<inspection_tool class="MultipleDeclaration" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreForLoopDeclarations" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="MultipleTopLevelClassesInFile" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MultipleVariablesInDeclaration" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NamedResource" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="NativeMethodNamingConvention" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="NegatedConditional" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreNegatedNullComparison" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="NegatedConditionalExpression" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NegatedEqualityExpression" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NegatedIfElse" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreNegatedNullComparison" value="true" />
|
||||
<option name="m_ignoreNegatedZeroComparison" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="NegativelyNamedBooleanVariable" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NestedSynchronizedStatement" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NewClassNamingConventionMerged" />
|
||||
<inspection_tool class="NewExceptionWithoutArguments" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NewGroovyClassNamingConventionMerged" />
|
||||
<inspection_tool class="NewMethodNamingConventionMerged" />
|
||||
<inspection_tool class="NoDefaultBaseConstructor" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="NonAsciiCharacters" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="CHECK_FOR_FILES_CONTAINING_BOM" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="NonExceptionNameEndsWithException" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonFinalFieldInEnum" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonFinalFieldOfException" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonFinalStaticVariableUsedInClassInitialization" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonFinalUtilityClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonProtectedConstructorInAbstractClass" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreNonPublicClasses" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="NonShortCircuitBoolean" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonSynchronizedMethodOverridesSynchronizedMethod" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonThreadSafeLazyInitialization" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NoopMethodInAbstractClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NotAssignable" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="NullThrown" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ObjectEquality" enabled="false" level="WARNING" enabled_by_default="false">
|
||||
<option name="m_ignoreEnums" value="true" />
|
||||
<option name="m_ignoreClassObjects" value="false" />
|
||||
<option name="m_ignorePrivateConstructors" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ObjectInstantiationInEqualsHashCode" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ObjectToString" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ObsoleteCollection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreRequiredObsoleteCollectionTypes" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="OctalEscape" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="OptionalContainsCollection" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="OverlyStrongTypeCast" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreInMatchingInstanceof" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="OverriddenMethodCallDuringObjectConstruction" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PackageInfoWithoutPackage" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PointerTypeRequired" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="ProblematicVarargsMethodOverride" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ProtectedField" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ProtectedInnerClass" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreEnums" value="false" />
|
||||
<option name="ignoreInterfaces" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ProtectedMemberInFinalClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PublicConstructorInNonPublicClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PublicField" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreEnums" value="false" />
|
||||
<option name="ignorableAnnotations">
|
||||
<value />
|
||||
</option>
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PublicFieldAccessedInSynchronizedContext" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="RandomDoubleForRandomInteger" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="RedundantFieldInitialization" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="RedundantImplements" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreSerializable" value="false" />
|
||||
<option name="ignoreCloneable" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="RedundantMethodOverride" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="RedundantThrowsDeclaration" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="RepeatedSpace" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ReplaceAssignmentWithOperatorAssignment" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreLazyOperators" value="true" />
|
||||
<option name="ignoreObscureOperators" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ReplaceCallWithComparison" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="RequireNonNull" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
||||
<inspection_tool class="ResourceParameter" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="RestSignature" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="ResultOfObjectAllocationIgnored" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ReturnOfCollectionField" enabled="false" level="WARNING" enabled_by_default="false">
|
||||
<option name="ignorePrivateMethods" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ReturnOfDateField" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="ScalarTypeRequired" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="SerializableClassInSecureContext" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SetReplaceableByEnumSet" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SimplifiableAnnotation" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SimplifiableEqualsExpression" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SingleCharAlternation" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SizeReplaceableByIsEmpty" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="StaticCallOnSubclass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="StaticFieldReferenceOnSubclass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="StaticImport" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="allowedClasses">
|
||||
<set>
|
||||
<option value="org.junit.Assert" />
|
||||
</set>
|
||||
</option>
|
||||
</inspection_tool>
|
||||
<inspection_tool class="StaticInheritance" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="StaticMethodNamingConvention" enabled="false" level="WARNING" enabled_by_default="false">
|
||||
<option name="m_regex" value="[a-z][A-Za-z\d]*" />
|
||||
<option name="m_minLength" value="4" />
|
||||
<option name="m_maxLength" value="32" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="StaticNonFinalField" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="StaticVariableNamingConvention" enabled="false" level="WARNING" enabled_by_default="false">
|
||||
<option name="checkMutableFinals" value="false" />
|
||||
<option name="m_regex" value="s_[a-z][A-Za-z\d]*" />
|
||||
<option name="m_minLength" value="5" />
|
||||
<option name="m_maxLength" value="32" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="StaticnessMismatch" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="StringBufferToStringInConcatenation" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="StringConcatenationInFormatCall" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="StringConcatenationMissingWhitespace" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="StringEqualsCharSequence" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="StringEqualsEmptyString" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="StringReplaceableByStringBuffer" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="onlyWarnOnLoop" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="SubtractionInCompareTo" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SuperClassHasFrequentlyUsedInheritors" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SuspiciousArrayCast" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SuspiciousIndentAfterControlStatement" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SwitchStatementWithConfusingDeclaration" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SynchronizeOnLock" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SynchronizeOnThis" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SynchronizedMethod" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_includeNativeMethods" value="true" />
|
||||
<option name="ignoreSynchronizedSuperMethods" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="SynchronizedOnLiteralObject" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="TemplateArgumentsIssues" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="TestMethodWithoutAssertion" enabled="false" level="WARNING" enabled_by_default="false">
|
||||
<option name="assertionMethods" value="org.junit.Assert,assert.*|fail.*,junit.framework.Assert,assert.*|fail.*,org.junit.jupiter.api.Assertions,assert.*|fail.*,org.mockito.Mockito,verify.*,org.mockito.InOrder,verify,org.junit.rules.ExpectedException,expect.*,org.hamcrest.MatcherAssert,assertThat" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="TestNGMethodNamingConvention" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="ThisEscapedInConstructor" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ThrowCaughtLocally" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreRethrownExceptions" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ThrowsRuntimeException" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="TooBroadScope" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_allowConstructorAsInitializer" value="false" />
|
||||
<option name="m_onlyLookAtBlocks" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="TrivialMethodReference" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="TrivialStringConcatenation" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="TypeMayBeWeakened" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="useRighthandTypeAsWeakestTypeInAssignments" value="true" />
|
||||
<option name="useParameterizedTypeForCollectionMethods" value="true" />
|
||||
<option name="doNotWeakenToJavaLangObject" value="true" />
|
||||
<option name="onlyWeakentoInterface" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="TypeParameterExtendsFinalClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="TypeParameterNamingConvention" enabled="false" level="WARNING" enabled_by_default="false">
|
||||
<option name="m_regex" value="[A-Z][A-Za-z\d]*" />
|
||||
<option name="m_minLength" value="1" />
|
||||
<option name="m_maxLength" value="1" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="UnaryPlus" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnnecessarilyQualifiedInnerClassAccess" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreReferencesNeedingImport" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="UnnecessaryBlockStatement" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreSwitchBranches" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="UnnecessaryConstantArrayCreationExpression" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnnecessaryConstructor" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnnecessaryExplicitNumericCast" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnnecessaryFullyQualifiedName" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreJavadoc" value="false" />
|
||||
<option name="ignoreInModuleStatements" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="UnnecessaryInheritDoc" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnnecessaryJavaDocLink" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreInlineLinkToSuper" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="UnnecessaryQualifierForThis" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnnecessarySuperConstructor" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnnecessarySuperQualifier" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnnecessaryThis" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnnecessaryToStringCall" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnnecessaryUnaryMinus" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnsecureRandomNumberGeneration" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnusedCatchParameter" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreCatchBlocksWithComments" value="false" />
|
||||
<option name="m_ignoreTestCases" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="UnusedImport" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="UpperCaseFieldNameNotConstant" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UseOfClone" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UseOfObsoleteDateTimeApi" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UtilityClassWithPublicConstructor" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UtilityClassWithoutPrivateConstructor" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignorableAnnotations">
|
||||
<value />
|
||||
</option>
|
||||
<option name="ignoreClassesWithOnlyMain" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="WeakerAccess" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="SUGGEST_PACKAGE_LOCAL_FOR_MEMBERS" value="false" />
|
||||
<option name="SUGGEST_PACKAGE_LOCAL_FOR_TOP_CLASSES" value="true" />
|
||||
<option name="SUGGEST_PRIVATE_FOR_INNERS" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="WhileLoopSpinsOnField" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreNonEmtpyLoops" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ZeroLengthArrayInitialization" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="unused" enabled="true" level="WARNING" enabled_by_default="true" klass="packageLocal" inner_class="protected" field="protected" method="protected" parameter="protected">
|
||||
<option name="LOCAL_VARIABLE" value="true" />
|
||||
<option name="FIELD" value="true" />
|
||||
<option name="METHOD" value="true" />
|
||||
<option name="CLASS" value="true" />
|
||||
<option name="PARAMETER" value="true" />
|
||||
<option name="REPORT_PARAMETER_FOR_PUBLIC_METHODS" value="false" />
|
||||
<option name="ADD_MAINS_TO_ENTRIES" value="true" />
|
||||
<option name="ADD_APPLET_TO_ENTRIES" value="true" />
|
||||
<option name="ADD_SERVLET_TO_ENTRIES" value="true" />
|
||||
<option name="ADD_NONJAVA_TO_ENTRIES" value="true" />
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="PROJECT_PROFILE" value="Default" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
||||
@@ -1,34 +1,15 @@
|
||||
# Android GUI for [WireGuard](https://www.wireguard.com/)
|
||||
# Android GUI for [AmneziaWG](https://amnezia.org/learn-more/31_amneziawg)
|
||||
|
||||
**[Download from the Play Store](https://play.google.com/store/apps/details?id=com.wireguard.android)**
|
||||
**[Download from the Play Store](https://play.google.com/store/apps/details?id=org.amnezia.awg)**
|
||||
|
||||
This is an Android GUI for [WireGuard](https://www.wireguard.com/). It [opportunistically uses the kernel implementation](https://git.zx2c4.com/android_kernel_wireguard/about/), and falls back to using the non-root [userspace implementation](https://git.zx2c4.com/wireguard-go/about/).
|
||||
This is an Android GUI for [AmneziaWG](https://amnezia.org/learn-more/31_amneziawg).
|
||||
|
||||
## Building
|
||||
|
||||
```
|
||||
$ git clone --recurse-submodules https://git.zx2c4.com/wireguard-android
|
||||
$ cd wireguard-android
|
||||
$ git clone --recurse-submodules https://github.com/amnezia-vpn/amneziawg-android
|
||||
$ cd amneziawg-android
|
||||
$ ./gradlew assembleRelease
|
||||
```
|
||||
|
||||
## Embedding
|
||||
|
||||
The tunnel library is [on JCenter](https://bintray.com/wireguard/wireguard-android/wireguard-android/_latestVersion), alongside [extensive class library documentation](https://javadoc.io/doc/com.wireguard.android/tunnel).
|
||||
|
||||
```
|
||||
implementation 'com.wireguard.android:tunnel:$wireguardTunnelVersion'
|
||||
```
|
||||
|
||||
The library makes use of Java 8 features, so be sure to support those in your gradle configuration:
|
||||
|
||||
```
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
```
|
||||
|
||||
## Translating
|
||||
|
||||
Please help us translate the app into several languages on [our translation platform](https://crowdin.com/project/WireGuard).
|
||||
macOS users may need [flock(1)](https://github.com/discoteq/flock).
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
buildscript {
|
||||
ext {
|
||||
agpVersion = '3.6.1'
|
||||
annotationsVersion = '1.1.0'
|
||||
appcompatVersion = '1.1.0'
|
||||
bintrayPluginVersion = '1.8.4'
|
||||
biometricVersion = '1.0.1'
|
||||
cardviewVersion = '1.0.0'
|
||||
collectionVersion = '1.1.0'
|
||||
constraintLayoutVersion = '1.1.3'
|
||||
coordinatorLayoutVersion = '1.1.0'
|
||||
coreKtxVersion = '1.2.0'
|
||||
coroutinesVersion = '1.3.5'
|
||||
eddsaVersion = '0.3.0'
|
||||
fragmentVersion = '1.2.3'
|
||||
jsr305Version = '3.0.2'
|
||||
junitVersion = '4.13'
|
||||
kotlinVersion = '1.3.71'
|
||||
materialComponentsVersion = '1.1.0'
|
||||
mavenPluginVersion = '2.1'
|
||||
preferenceVersion = '1.1.0'
|
||||
streamsupportVersion = '1.7.2'
|
||||
threetenabpVersion = '1.2.3'
|
||||
zxingEmbeddedVersion = '3.6.0'
|
||||
|
||||
groupName = 'com.wireguard.android'
|
||||
}
|
||||
dependencies {
|
||||
classpath "com.android.tools.build:gradle:$agpVersion"
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
|
||||
classpath "com.github.dcendents:android-maven-gradle-plugin:$mavenPluginVersion"
|
||||
classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:$bintrayPluginVersion"
|
||||
}
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
|
||||
tasks {
|
||||
wrapper {
|
||||
gradleVersion = "6.2.2"
|
||||
distributionType = Wrapper.DistributionType.ALL
|
||||
}
|
||||
}
|
||||
|
||||
apply from: "version.gradle"
|
||||
@@ -0,0 +1,6 @@
|
||||
plugins {
|
||||
alias(libs.plugins.android.application) apply false
|
||||
alias(libs.plugins.android.library) apply false
|
||||
alias(libs.plugins.kotlin.android) apply false
|
||||
alias(libs.plugins.kotlin.kapt) apply false
|
||||
}
|
||||
+62
-22
@@ -1,33 +1,73 @@
|
||||
# Project-wide Gradle settings.
|
||||
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
android.enableJetifier=true
|
||||
android.useAndroidX=true
|
||||
org.gradle.jvmargs=-Xmx1536m
|
||||
amneziawgVersionCode=7
|
||||
amneziawgVersionName=1.1.4
|
||||
amneziawgPackageName=org.amnezia.awg
|
||||
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
org.gradle.parallel=true
|
||||
org.gradle.configureondemand=true
|
||||
org.gradle.caching=true
|
||||
|
||||
# R8 Full mode
|
||||
android.enableR8.fullMode=true
|
||||
# Enable Kotlin incremental compilation
|
||||
kotlin.incremental=true
|
||||
|
||||
# https://jakewharton.com/increased-accuracy-of-aapt2-keep-rules/
|
||||
android.useMinimalKeepRules=true
|
||||
# Enable AndroidX support
|
||||
android.useAndroidX=true
|
||||
|
||||
# Enable rudimentary R class namespacing where each library only contains
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs=-Xmx1536m
|
||||
|
||||
# Turn off AP discovery in compile path to enable compile avoidance
|
||||
kapt.include.compile.classpath=false
|
||||
|
||||
# Enable non-transitive R class namespacing where each library only contains
|
||||
# references to the resources it declares instead of declarations plus all
|
||||
# transitive dependency references.
|
||||
android.namespacedRClass=true
|
||||
android.nonTransitiveRClass=true
|
||||
|
||||
# Experimental AGP flags
|
||||
# Generate compile-time only R class for app modules.
|
||||
android.enableAppCompileTimeRClass=true
|
||||
# Keep AAPT2 daemons alive between incremental builds.
|
||||
android.keepWorkerActionServicesBetweenBuilds=true
|
||||
# Make R fields non-final to improve build speeds.
|
||||
# http://tools.android.com/tips/non-constant-fields
|
||||
android.nonFinalResIds=true
|
||||
# Enable the newly refactored resource shrinker.
|
||||
android.experimental.enableNewResourceShrinker=true
|
||||
# Enable precise shrinking in the new resource shrinker.
|
||||
android.experimental.enableNewResourceShrinker.preciseShrinking=true
|
||||
# Generate manifest class as a .class directly rather than a Java source file.
|
||||
android.generateManifestClass=true
|
||||
# Generate the text map of source sets and absolute paths to allow
|
||||
# generating relative paths from absolute paths later in the build.
|
||||
android.experimental.enableSourceSetPathsMap=true
|
||||
# Use relative paths for better Gradle caching of library build tasks
|
||||
android.experimental.cacheCompileLibResources=true
|
||||
|
||||
# Default Android build features
|
||||
# Disable BuildConfig generation by default
|
||||
android.defaults.buildfeatures.buildconfig=false
|
||||
# Disable AIDL stub generation by default
|
||||
android.defaults.buildfeatures.aidl=false
|
||||
# Disable RenderScript compilation by default
|
||||
android.defaults.buildfeatures.renderscript=false
|
||||
# Disable resource values generation by default in libraries
|
||||
android.defaults.buildfeatures.resvalues=false
|
||||
# Disable shader compilation by default
|
||||
android.defaults.buildfeatures.shaders=false
|
||||
# Disable Android resource processing by default
|
||||
android.library.defaults.buildfeatures.androidresources=false
|
||||
|
||||
# Suppress warnings for some features that aren't yet stabilized
|
||||
android.suppressUnsupportedOptionWarnings=android.enableR8.fullMode,android.useMinimalKeepRules,android.namespacedRClass,android.suppressUnsupportedOptionWarnings
|
||||
android.suppressUnsupportedOptionWarnings=android.keepWorkerActionServicesBetweenBuilds,\
|
||||
android.experimental.enableNewResourceShrinker.preciseShrinking,\
|
||||
android.enableAppCompileTimeRClass,\
|
||||
android.suppressUnsupportedOptionWarnings
|
||||
|
||||
# OSSRH sometimes struggles with slow deployments, so this makes Gradle
|
||||
# more tolerant to those delays.
|
||||
systemProp.org.gradle.internal.http.connectionTimeout=500000
|
||||
systemProp.org.gradle.internal.http.socketTimeout=500000
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
[versions]
|
||||
agp = "8.3.0"
|
||||
kotlin = "1.9.20"
|
||||
|
||||
[libraries]
|
||||
androidx-activity-ktx = "androidx.activity:activity-ktx:1.8.2"
|
||||
androidx-annotation = "androidx.annotation:annotation:1.7.1"
|
||||
androidx-appcompat = "androidx.appcompat:appcompat:1.6.1"
|
||||
androidx-biometric = "androidx.biometric:biometric:1.1.0"
|
||||
androidx-collection = "androidx.collection:collection:1.4.0"
|
||||
androidx-constraintlayout = "androidx.constraintlayout:constraintlayout:2.1.4"
|
||||
androidx-coordinatorlayout = "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
|
||||
androidx-core-ktx = "androidx.core:core-ktx:1.12.0"
|
||||
androidx-datastore-preferences = "androidx.datastore:datastore-preferences:1.0.0"
|
||||
androidx-fragment-ktx = "androidx.fragment:fragment-ktx:1.6.2"
|
||||
androidx-lifecycle-runtime-ktx = "androidx.lifecycle:lifecycle-runtime-ktx:2.7.0"
|
||||
androidx-preference-ktx = "androidx.preference:preference-ktx:1.2.1"
|
||||
desugarJdkLibs = "com.android.tools:desugar_jdk_libs:2.0.4"
|
||||
google-material = "com.google.android.material:material:1.11.0"
|
||||
jsr305 = "com.google.code.findbugs:jsr305:3.0.2"
|
||||
junit = "junit:junit:4.13.2"
|
||||
kotlinx-coroutines-android = "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3"
|
||||
zxing-android-embedded = "com.journeyapps:zxing-android-embedded:4.3.0"
|
||||
|
||||
[plugins]
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
android-library = { id = "com.android.library", version.ref = "agp" }
|
||||
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||
kotlin-kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" }
|
||||
Vendored
BIN
Binary file not shown.
+1
-1
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env sh
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -17,78 +17,111 @@
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
@@ -97,87 +130,120 @@ Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
include ':tunnel'
|
||||
include ':ui'
|
||||
@@ -0,0 +1,34 @@
|
||||
import com.android.build.api.dsl.SettingsExtension
|
||||
|
||||
pluginManagement {
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UnstableApiUsage")
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id("com.android.settings") version "8.3.0"
|
||||
}
|
||||
|
||||
rootProject.name = "amneziawg-android"
|
||||
|
||||
include(":tunnel")
|
||||
include(":ui")
|
||||
|
||||
configure<SettingsExtension> {
|
||||
buildToolsVersion = "34.0.0"
|
||||
compileSdk = 34
|
||||
minSdk = 24
|
||||
ndkVersion = "26.1.10909125"
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
version wireguardVersionName
|
||||
group groupName
|
||||
|
||||
android {
|
||||
buildToolsVersion '29.0.3'
|
||||
compileSdkVersion 29
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 29
|
||||
versionCode wireguardVersionCode
|
||||
versionName wireguardVersionName
|
||||
}
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path 'tools/CMakeLists.txt'
|
||||
}
|
||||
}
|
||||
testOptions.unitTests.all {
|
||||
testLogging {
|
||||
events 'passed', 'skipped', 'failed', 'standardOut', 'standardError'
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments "-DANDROID_PACKAGE_NAME=${groupName}", "-DGRADLE_USER_HOME=${project.gradle.gradleUserHomeDir}"
|
||||
}
|
||||
}
|
||||
}
|
||||
debug {
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments "-DANDROID_PACKAGE_NAME=${groupName}.debug", "-DGRADLE_USER_HOME=${project.gradle.gradleUserHomeDir}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api "net.sourceforge.streamsupport:android-retrostreams:$streamsupportVersion"
|
||||
implementation "androidx.annotation:annotation:$annotationsVersion"
|
||||
implementation "androidx.collection:collection:$collectionVersion"
|
||||
implementation "com.google.code.findbugs:jsr305:$jsr305Version"
|
||||
implementation "com.jakewharton.threetenabp:threetenabp:$threetenabpVersion"
|
||||
implementation "net.i2p.crypto:eddsa:$eddsaVersion"
|
||||
testImplementation "junit:junit:$junitVersion"
|
||||
}
|
||||
|
||||
apply from: "publish.gradle"
|
||||
@@ -0,0 +1,61 @@
|
||||
@file:Suppress("UnstableApiUsage")
|
||||
|
||||
import org.gradle.api.tasks.testing.logging.TestLogEvent
|
||||
|
||||
val pkg: String = providers.gradleProperty("amneziawgPackageName").get()
|
||||
val cmakeAndroidPackageName: String = providers.environmentVariable("ANDROID_PACKAGE_NAME").getOrElse(pkg)
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.android.library)
|
||||
}
|
||||
|
||||
android {
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
namespace = "${pkg}.tunnel"
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path("tools/CMakeLists.txt")
|
||||
}
|
||||
}
|
||||
testOptions.unitTests.all {
|
||||
it.testLogging { events(TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED) }
|
||||
}
|
||||
buildTypes {
|
||||
all {
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
targets("libwg-go.so", "libwg.so", "libwg-quick.so")
|
||||
arguments("-DGRADLE_USER_HOME=${project.gradle.gradleUserHomeDir}")
|
||||
}
|
||||
}
|
||||
}
|
||||
release {
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments("-DANDROID_PACKAGE_NAME=${cmakeAndroidPackageName}")
|
||||
}
|
||||
}
|
||||
}
|
||||
debug {
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments("-DANDROID_PACKAGE_NAME=${cmakeAndroidPackageName}.debug")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
lint {
|
||||
disable += "LongLogTag"
|
||||
disable += "NewApi"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.androidx.annotation)
|
||||
implementation(libs.androidx.collection)
|
||||
compileOnly(libs.jsr305)
|
||||
testImplementation(libs.junit)
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
apply plugin: 'com.github.dcendents.android-maven'
|
||||
apply plugin: 'com.jfrog.bintray'
|
||||
|
||||
install {
|
||||
repositories.mavenInstaller {
|
||||
pom.project {
|
||||
name 'WireGuard Tunnel Library'
|
||||
description 'Embeddable tunnel library for WireGuard for Android'
|
||||
url 'https://www.wireguard.com/'
|
||||
|
||||
packaging 'aar'
|
||||
groupId groupName
|
||||
artifactId 'tunnel'
|
||||
version wireguardVersionName
|
||||
|
||||
licenses {
|
||||
license {
|
||||
name 'The Apache Software License, Version 2.0'
|
||||
url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
|
||||
distribution 'repo'
|
||||
}
|
||||
}
|
||||
scm {
|
||||
connection 'scm:git:https://git.zx2c4.com/wireguard-android'
|
||||
url 'https://git.zx2c4.com/wireguard-android'
|
||||
}
|
||||
organization {
|
||||
name 'WireGuard'
|
||||
url 'https://www.wireguard.com/'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bintray {
|
||||
user = hasProperty('BINTRAY_USER') ? getProperty('BINTRAY_USER') : System.getenv('BINTRAY_USER')
|
||||
key = hasProperty('BINTRAY_KEY') ? getProperty('BINTRAY_KEY') : System.getenv('BINTRAY_KEY')
|
||||
|
||||
configurations = [ 'archives' ]
|
||||
|
||||
pkg {
|
||||
repo = 'wireguard-android'
|
||||
name = 'wireguard-android'
|
||||
userOrg = 'wireguard'
|
||||
licenses = [ 'Apache-2.0' ]
|
||||
vcsUrl = 'https://git.zx2c4.com/wireguard-android'
|
||||
publish = true
|
||||
|
||||
version {
|
||||
name = wireguardVersionName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
android.libraryVariants.all { variant ->
|
||||
if (variant.name == 'release') {
|
||||
task javadoc(type: Javadoc) {
|
||||
source = variant.javaCompileProvider.get().source
|
||||
classpath = files((android.bootClasspath.join(File.pathSeparator)))
|
||||
classpath += variant.javaCompileProvider.get().classpath
|
||||
title = 'Embeddable WireGuard Tunnel for Android v$wireguardVersionName'
|
||||
}
|
||||
task javadocJar(type: Jar, dependsOn: javadoc) {
|
||||
archiveClassifier = 'javadoc'
|
||||
from javadoc.destinationDir
|
||||
}
|
||||
task sourcesJar(type: Jar) {
|
||||
archiveClassifier = 'sources'
|
||||
from android.sourceSets.main.java.srcDirs
|
||||
}
|
||||
artifacts {
|
||||
archives sourcesJar
|
||||
archives javadocJar
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,15 @@
|
||||
<!--
|
||||
~ Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
~ Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
~ SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.wireguard.android.tunnel">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<application>
|
||||
<service
|
||||
android:name="com.wireguard.android.backend.GoBackend$VpnService"
|
||||
android:permission="android.permission.BIND_VPN_SERVICE">
|
||||
android:name="org.amnezia.awg.backend.GoBackend$VpnService"
|
||||
android:permission="android.permission.BIND_VPN_SERVICE"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="android.net.VpnService" />
|
||||
</intent-filter>
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2020 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.backend;
|
||||
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
|
||||
@NonNullForAll
|
||||
public final class BackendException extends Exception {
|
||||
private final Object[] format;
|
||||
private final Reason reason;
|
||||
|
||||
public BackendException(final Reason reason, final Object... format) {
|
||||
this.reason = reason;
|
||||
this.format = format;
|
||||
}
|
||||
|
||||
public Object[] getFormat() {
|
||||
return format;
|
||||
}
|
||||
|
||||
public Reason getReason() {
|
||||
return reason;
|
||||
}
|
||||
|
||||
public enum Reason {
|
||||
UNKNOWN_KERNEL_MODULE_NAME,
|
||||
WG_QUICK_CONFIG_ERROR_CODE,
|
||||
TUNNEL_MISSING_CONFIG,
|
||||
VPN_NOT_AUTHORIZED,
|
||||
UNABLE_TO_START_VPN,
|
||||
TUN_CREATION_ERROR,
|
||||
GO_ACTIVATION_ERROR_CODE
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2020 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.backend;
|
||||
|
||||
import android.os.SystemClock;
|
||||
import android.util.Pair;
|
||||
|
||||
import com.wireguard.crypto.Key;
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@NonNullForAll
|
||||
public class Statistics {
|
||||
private final Map<Key, Pair<Long, Long>> peerBytes = new HashMap<>();
|
||||
private long lastTouched = SystemClock.elapsedRealtime();
|
||||
|
||||
Statistics() {
|
||||
}
|
||||
|
||||
void add(final Key key, final long rx, final long tx) {
|
||||
peerBytes.put(key, Pair.create(rx, tx));
|
||||
lastTouched = SystemClock.elapsedRealtime();
|
||||
}
|
||||
|
||||
public boolean isStale() {
|
||||
return SystemClock.elapsedRealtime() - lastTouched > 900;
|
||||
}
|
||||
|
||||
public long peerRx(final Key peer) {
|
||||
if (!peerBytes.containsKey(peer))
|
||||
return 0;
|
||||
return peerBytes.get(peer).first;
|
||||
}
|
||||
|
||||
public long peerTx(final Key peer) {
|
||||
if (!peerBytes.containsKey(peer))
|
||||
return 0;
|
||||
return peerBytes.get(peer).second;
|
||||
}
|
||||
|
||||
public Key[] peers() {
|
||||
return peerBytes.keySet().toArray(new Key[0]);
|
||||
}
|
||||
|
||||
public long totalRx() {
|
||||
long rx = 0;
|
||||
for (final Pair<Long, Long> val : peerBytes.values()) {
|
||||
rx += val.first;
|
||||
}
|
||||
return rx;
|
||||
}
|
||||
|
||||
public long totalTx() {
|
||||
long tx = 0;
|
||||
for (final Pair<Long, Long> val : peerBytes.values()) {
|
||||
tx += val.second;
|
||||
}
|
||||
return tx;
|
||||
}
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2019-2020 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.system.OsConstants;
|
||||
import android.util.Base64;
|
||||
|
||||
import com.wireguard.android.util.RootShell.RootShellException;
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
|
||||
import net.i2p.crypto.eddsa.EdDSAEngine;
|
||||
import net.i2p.crypto.eddsa.EdDSAPublicKey;
|
||||
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
|
||||
import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec;
|
||||
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.InvalidParameterException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Signature;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
@NonNullForAll
|
||||
@SuppressWarnings("MagicNumber")
|
||||
public class ModuleLoader {
|
||||
private static final String MODULE_LIST_URL = "https://download.wireguard.com/android-module/modules.txt.sig";
|
||||
private static final String MODULE_NAME = "wireguard-%s.ko";
|
||||
private static final String MODULE_PUBLIC_KEY_BASE64 = "RWRmHuT9PSqtwfsLtEx+QS06BJtLgFYteL9WCNjH7yuyu5Y1DieSN7If";
|
||||
private static final String MODULE_URL = "https://download.wireguard.com/android-module/%s";
|
||||
private final File moduleDir;
|
||||
private final RootShell rootShell;
|
||||
private final File tmpDir;
|
||||
private final String userAgent;
|
||||
|
||||
public ModuleLoader(final Context context, final RootShell rootShell, final String userAgent) {
|
||||
moduleDir = new File(context.getCacheDir(), "kmod");
|
||||
tmpDir = new File(context.getCacheDir(), "tmp");
|
||||
this.rootShell = rootShell;
|
||||
this.userAgent = userAgent;
|
||||
}
|
||||
|
||||
public static boolean isModuleLoaded() {
|
||||
return new File("/sys/module/wireguard").exists();
|
||||
}
|
||||
|
||||
public Integer download() throws IOException, RootShellException, NoSuchAlgorithmException {
|
||||
final List<String> output = new ArrayList<>();
|
||||
rootShell.run(output, "sha256sum /proc/version|cut -d ' ' -f 1");
|
||||
if (output.size() != 1 || output.get(0).length() != 64)
|
||||
throw new InvalidParameterException("Invalid sha256 of /proc/version");
|
||||
final String moduleName = String.format(MODULE_NAME, output.get(0));
|
||||
HttpURLConnection connection = (HttpURLConnection) new URL(MODULE_LIST_URL).openConnection();
|
||||
connection.setRequestProperty("User-Agent", userAgent);
|
||||
connection.connect();
|
||||
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK)
|
||||
throw new IOException("Hash list could not be found");
|
||||
final byte[] input = new byte[1024 * 1024 * 3 /* 3MiB */];
|
||||
int len;
|
||||
try (final InputStream inputStream = connection.getInputStream()) {
|
||||
len = inputStream.read(input);
|
||||
}
|
||||
if (len <= 0)
|
||||
throw new IOException("Hash list was empty");
|
||||
final Map<String, Sha256Digest> modules = verifySignedHashes(new String(input, 0, len, StandardCharsets.UTF_8));
|
||||
if (modules == null)
|
||||
throw new InvalidParameterException("The signature did not verify or invalid hash list format");
|
||||
if (!modules.containsKey(moduleName))
|
||||
return OsConstants.ENOENT;
|
||||
connection = (HttpURLConnection) new URL(String.format(MODULE_URL, moduleName)).openConnection();
|
||||
connection.setRequestProperty("User-Agent", userAgent);
|
||||
connection.connect();
|
||||
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK)
|
||||
throw new IOException("Module file could not be found, despite being on hash list");
|
||||
|
||||
tmpDir.mkdirs();
|
||||
moduleDir.mkdir();
|
||||
File tempFile = null;
|
||||
try {
|
||||
tempFile = File.createTempFile("UNVERIFIED-", null, tmpDir);
|
||||
final MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
try (final InputStream inputStream = connection.getInputStream();
|
||||
final FileOutputStream outputStream = new FileOutputStream(tempFile)) {
|
||||
int total = 0;
|
||||
while ((len = inputStream.read(input)) > 0) {
|
||||
total += len;
|
||||
if (total > 1024 * 1024 * 15 /* 15 MiB */)
|
||||
throw new IOException("File too big");
|
||||
outputStream.write(input, 0, len);
|
||||
digest.update(input, 0, len);
|
||||
}
|
||||
outputStream.getFD().sync();
|
||||
}
|
||||
if (!Arrays.equals(digest.digest(), modules.get(moduleName).bytes))
|
||||
throw new IOException("Incorrect file hash");
|
||||
|
||||
if (!tempFile.renameTo(new File(moduleDir, moduleName)))
|
||||
throw new IOException("Unable to rename to final destination");
|
||||
} finally {
|
||||
if (tempFile != null)
|
||||
tempFile.delete();
|
||||
}
|
||||
return OsConstants.EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
public void loadModule() throws IOException, RootShellException {
|
||||
rootShell.run(null, String.format("insmod \"%s/wireguard-$(sha256sum /proc/version|cut -d ' ' -f 1).ko\"", moduleDir.getAbsolutePath()));
|
||||
}
|
||||
|
||||
public boolean moduleMightExist() {
|
||||
return moduleDir.exists() && moduleDir.isDirectory();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Map<String, Sha256Digest> verifySignedHashes(final String signifyDigest) {
|
||||
final byte[] publicKeyBytes = Base64.decode(MODULE_PUBLIC_KEY_BASE64, Base64.DEFAULT);
|
||||
|
||||
if (publicKeyBytes == null || publicKeyBytes.length != 32 + 10 || publicKeyBytes[0] != 'E' || publicKeyBytes[1] != 'd')
|
||||
return null;
|
||||
|
||||
final String[] lines = signifyDigest.split("\n", 3);
|
||||
if (lines.length != 3)
|
||||
return null;
|
||||
if (!lines[0].startsWith("untrusted comment: "))
|
||||
return null;
|
||||
|
||||
final byte[] signatureBytes = Base64.decode(lines[1], Base64.DEFAULT);
|
||||
if (signatureBytes == null || signatureBytes.length != 64 + 10)
|
||||
return null;
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
if (signatureBytes[i] != publicKeyBytes[i])
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
final EdDSAParameterSpec parameterSpec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519);
|
||||
final Signature signature = new EdDSAEngine(MessageDigest.getInstance(parameterSpec.getHashAlgorithm()));
|
||||
final byte[] rawPublicKeyBytes = new byte[32];
|
||||
System.arraycopy(publicKeyBytes, 10, rawPublicKeyBytes, 0, 32);
|
||||
signature.initVerify(new EdDSAPublicKey(new EdDSAPublicKeySpec(rawPublicKeyBytes, parameterSpec)));
|
||||
signature.update(lines[2].getBytes(StandardCharsets.UTF_8));
|
||||
if (!signature.verify(signatureBytes, 10, 64))
|
||||
return null;
|
||||
} catch (final Exception ignored) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final Map<String, Sha256Digest> hashes = new HashMap<>();
|
||||
for (final String line : lines[2].split("\n")) {
|
||||
final String[] components = line.split(" {2}", 2);
|
||||
if (components.length != 2)
|
||||
return null;
|
||||
try {
|
||||
hashes.put(components[1], new Sha256Digest(components[0]));
|
||||
} catch (final Exception ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return hashes;
|
||||
}
|
||||
|
||||
private static final class Sha256Digest {
|
||||
private final byte[] bytes;
|
||||
|
||||
private Sha256Digest(final String hex) {
|
||||
if (hex.length() != 64)
|
||||
throw new InvalidParameterException("SHA256 hashes must be 32 bytes long");
|
||||
bytes = new byte[32];
|
||||
for (int i = 0; i < 32; ++i)
|
||||
bytes[i] = (byte) Integer.parseInt(hex.substring(i * 2, i * 2 + 2), 16);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,356 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.config;
|
||||
|
||||
import com.wireguard.config.BadConfigException.Location;
|
||||
import com.wireguard.config.BadConfigException.Reason;
|
||||
import com.wireguard.config.BadConfigException.Section;
|
||||
import com.wireguard.crypto.Key;
|
||||
import com.wireguard.crypto.KeyFormatException;
|
||||
import com.wireguard.crypto.KeyPair;
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import java9.util.Lists;
|
||||
import java9.util.Optional;
|
||||
import java9.util.stream.Collectors;
|
||||
import java9.util.stream.StreamSupport;
|
||||
|
||||
/**
|
||||
* Represents the configuration for a WireGuard interface (an [Interface] block). Interfaces must
|
||||
* have a private key (used to initialize a {@code KeyPair}), and may optionally have several other
|
||||
* attributes.
|
||||
* <p>
|
||||
* Instances of this class are immutable.
|
||||
*/
|
||||
@NonNullForAll
|
||||
public final class Interface {
|
||||
private static final int MAX_UDP_PORT = 65535;
|
||||
private static final int MIN_UDP_PORT = 0;
|
||||
|
||||
private final Set<InetNetwork> addresses;
|
||||
private final Set<InetAddress> dnsServers;
|
||||
private final Set<String> excludedApplications;
|
||||
private final KeyPair keyPair;
|
||||
private final Optional<Integer> listenPort;
|
||||
private final Optional<Integer> mtu;
|
||||
|
||||
private Interface(final Builder builder) {
|
||||
// Defensively copy to ensure immutability even if the Builder is reused.
|
||||
addresses = Collections.unmodifiableSet(new LinkedHashSet<>(builder.addresses));
|
||||
dnsServers = Collections.unmodifiableSet(new LinkedHashSet<>(builder.dnsServers));
|
||||
excludedApplications = Collections.unmodifiableSet(new LinkedHashSet<>(builder.excludedApplications));
|
||||
keyPair = Objects.requireNonNull(builder.keyPair, "Interfaces must have a private key");
|
||||
listenPort = builder.listenPort;
|
||||
mtu = builder.mtu;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an series of "KEY = VALUE" lines into an {@code Interface}. Throws
|
||||
* {@link ParseException} if the input is not well-formed or contains unknown attributes.
|
||||
*
|
||||
* @param lines An iterable sequence of lines, containing at least a private key attribute
|
||||
* @return An {@code Interface} with all of the attributes from {@code lines} set
|
||||
*/
|
||||
public static Interface parse(final Iterable<? extends CharSequence> lines)
|
||||
throws BadConfigException {
|
||||
final Builder builder = new Builder();
|
||||
for (final CharSequence line : lines) {
|
||||
final Attribute attribute = Attribute.parse(line).orElseThrow(() ->
|
||||
new BadConfigException(Section.INTERFACE, Location.TOP_LEVEL,
|
||||
Reason.SYNTAX_ERROR, line));
|
||||
switch (attribute.getKey().toLowerCase(Locale.ENGLISH)) {
|
||||
case "address":
|
||||
builder.parseAddresses(attribute.getValue());
|
||||
break;
|
||||
case "dns":
|
||||
builder.parseDnsServers(attribute.getValue());
|
||||
break;
|
||||
case "excludedapplications":
|
||||
builder.parseExcludedApplications(attribute.getValue());
|
||||
break;
|
||||
case "listenport":
|
||||
builder.parseListenPort(attribute.getValue());
|
||||
break;
|
||||
case "mtu":
|
||||
builder.parseMtu(attribute.getValue());
|
||||
break;
|
||||
case "privatekey":
|
||||
builder.parsePrivateKey(attribute.getValue());
|
||||
break;
|
||||
default:
|
||||
throw new BadConfigException(Section.INTERFACE, Location.TOP_LEVEL,
|
||||
Reason.UNKNOWN_ATTRIBUTE, attribute.getKey());
|
||||
}
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (!(obj instanceof Interface))
|
||||
return false;
|
||||
final Interface other = (Interface) obj;
|
||||
return addresses.equals(other.addresses)
|
||||
&& dnsServers.equals(other.dnsServers)
|
||||
&& excludedApplications.equals(other.excludedApplications)
|
||||
&& keyPair.equals(other.keyPair)
|
||||
&& listenPort.equals(other.listenPort)
|
||||
&& mtu.equals(other.mtu);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set of IP addresses assigned to the interface.
|
||||
*
|
||||
* @return a set of {@link InetNetwork}s
|
||||
*/
|
||||
public Set<InetNetwork> getAddresses() {
|
||||
// The collection is already immutable.
|
||||
return addresses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set of DNS servers associated with the interface.
|
||||
*
|
||||
* @return a set of {@link InetAddress}es
|
||||
*/
|
||||
public Set<InetAddress> getDnsServers() {
|
||||
// The collection is already immutable.
|
||||
return dnsServers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set of applications excluded from using the interface.
|
||||
*
|
||||
* @return a set of package names
|
||||
*/
|
||||
public Set<String> getExcludedApplications() {
|
||||
// The collection is already immutable.
|
||||
return excludedApplications;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the public/private key pair used by the interface.
|
||||
*
|
||||
* @return a key pair
|
||||
*/
|
||||
public KeyPair getKeyPair() {
|
||||
return keyPair;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the UDP port number that the WireGuard interface will listen on.
|
||||
*
|
||||
* @return a UDP port number, or {@code Optional.empty()} if none is configured
|
||||
*/
|
||||
public Optional<Integer> getListenPort() {
|
||||
return listenPort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the MTU used for the WireGuard interface.
|
||||
*
|
||||
* @return the MTU, or {@code Optional.empty()} if none is configured
|
||||
*/
|
||||
public Optional<Integer> getMtu() {
|
||||
return mtu;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 1;
|
||||
hash = 31 * hash + addresses.hashCode();
|
||||
hash = 31 * hash + dnsServers.hashCode();
|
||||
hash = 31 * hash + excludedApplications.hashCode();
|
||||
hash = 31 * hash + keyPair.hashCode();
|
||||
hash = 31 * hash + listenPort.hashCode();
|
||||
hash = 31 * hash + mtu.hashCode();
|
||||
return hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the {@code Interface} into a string suitable for debugging purposes. The {@code
|
||||
* Interface} is identified by its public key and (if set) the port used for its UDP socket.
|
||||
*
|
||||
* @return A concise single-line identifier for the {@code Interface}
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder sb = new StringBuilder("(Interface ");
|
||||
sb.append(keyPair.getPublicKey().toBase64());
|
||||
listenPort.ifPresent(lp -> sb.append(" @").append(lp));
|
||||
sb.append(')');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the {@code Interface} into a string suitable for inclusion in a {@code wg-quick}
|
||||
* configuration file.
|
||||
*
|
||||
* @return The {@code Interface} represented as a series of "Key = Value" lines
|
||||
*/
|
||||
public String toWgQuickString() {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
if (!addresses.isEmpty())
|
||||
sb.append("Address = ").append(Attribute.join(addresses)).append('\n');
|
||||
if (!dnsServers.isEmpty()) {
|
||||
final List<String> dnsServerStrings = StreamSupport.stream(dnsServers)
|
||||
.map(InetAddress::getHostAddress)
|
||||
.collect(Collectors.toUnmodifiableList());
|
||||
sb.append("DNS = ").append(Attribute.join(dnsServerStrings)).append('\n');
|
||||
}
|
||||
if (!excludedApplications.isEmpty())
|
||||
sb.append("ExcludedApplications = ").append(Attribute.join(excludedApplications)).append('\n');
|
||||
listenPort.ifPresent(lp -> sb.append("ListenPort = ").append(lp).append('\n'));
|
||||
mtu.ifPresent(m -> sb.append("MTU = ").append(m).append('\n'));
|
||||
sb.append("PrivateKey = ").append(keyPair.getPrivateKey().toBase64()).append('\n');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the {@code Interface} for use with the WireGuard cross-platform userspace API.
|
||||
* Note that not all attributes are included in this representation.
|
||||
*
|
||||
* @return the {@code Interface} represented as a series of "KEY=VALUE" lines
|
||||
*/
|
||||
public String toWgUserspaceString() {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append("private_key=").append(keyPair.getPrivateKey().toHex()).append('\n');
|
||||
listenPort.ifPresent(lp -> sb.append("listen_port=").append(lp).append('\n'));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnusedReturnValue")
|
||||
public static final class Builder {
|
||||
// Defaults to an empty set.
|
||||
private final Set<InetNetwork> addresses = new LinkedHashSet<>();
|
||||
// Defaults to an empty set.
|
||||
private final Set<InetAddress> dnsServers = new LinkedHashSet<>();
|
||||
// Defaults to an empty set.
|
||||
private final Set<String> excludedApplications = new LinkedHashSet<>();
|
||||
// No default; must be provided before building.
|
||||
@Nullable private KeyPair keyPair;
|
||||
// Defaults to not present.
|
||||
private Optional<Integer> listenPort = Optional.empty();
|
||||
// Defaults to not present.
|
||||
private Optional<Integer> mtu = Optional.empty();
|
||||
|
||||
public Builder addAddress(final InetNetwork address) {
|
||||
addresses.add(address);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addAddresses(final Collection<InetNetwork> addresses) {
|
||||
this.addresses.addAll(addresses);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addDnsServer(final InetAddress dnsServer) {
|
||||
dnsServers.add(dnsServer);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addDnsServers(final Collection<? extends InetAddress> dnsServers) {
|
||||
this.dnsServers.addAll(dnsServers);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Interface build() throws BadConfigException {
|
||||
if (keyPair == null)
|
||||
throw new BadConfigException(Section.INTERFACE, Location.PRIVATE_KEY,
|
||||
Reason.MISSING_ATTRIBUTE, null);
|
||||
return new Interface(this);
|
||||
}
|
||||
|
||||
public Builder excludeApplication(final String application) {
|
||||
excludedApplications.add(application);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder excludeApplications(final Collection<String> applications) {
|
||||
excludedApplications.addAll(applications);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder parseAddresses(final CharSequence addresses) throws BadConfigException {
|
||||
try {
|
||||
for (final String address : Attribute.split(addresses))
|
||||
addAddress(InetNetwork.parse(address));
|
||||
return this;
|
||||
} catch (final ParseException e) {
|
||||
throw new BadConfigException(Section.INTERFACE, Location.ADDRESS, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder parseDnsServers(final CharSequence dnsServers) throws BadConfigException {
|
||||
try {
|
||||
for (final String dnsServer : Attribute.split(dnsServers))
|
||||
addDnsServer(InetAddresses.parse(dnsServer));
|
||||
return this;
|
||||
} catch (final ParseException e) {
|
||||
throw new BadConfigException(Section.INTERFACE, Location.DNS, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder parseExcludedApplications(final CharSequence apps) {
|
||||
return excludeApplications(Lists.of(Attribute.split(apps)));
|
||||
}
|
||||
|
||||
public Builder parseListenPort(final String listenPort) throws BadConfigException {
|
||||
try {
|
||||
return setListenPort(Integer.parseInt(listenPort));
|
||||
} catch (final NumberFormatException e) {
|
||||
throw new BadConfigException(Section.INTERFACE, Location.LISTEN_PORT, listenPort, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder parseMtu(final String mtu) throws BadConfigException {
|
||||
try {
|
||||
return setMtu(Integer.parseInt(mtu));
|
||||
} catch (final NumberFormatException e) {
|
||||
throw new BadConfigException(Section.INTERFACE, Location.MTU, mtu, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder parsePrivateKey(final String privateKey) throws BadConfigException {
|
||||
try {
|
||||
return setKeyPair(new KeyPair(Key.fromBase64(privateKey)));
|
||||
} catch (final KeyFormatException e) {
|
||||
throw new BadConfigException(Section.INTERFACE, Location.PRIVATE_KEY, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder setKeyPair(final KeyPair keyPair) {
|
||||
this.keyPair = keyPair;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setListenPort(final int listenPort) throws BadConfigException {
|
||||
if (listenPort < MIN_UDP_PORT || listenPort > MAX_UDP_PORT)
|
||||
throw new BadConfigException(Section.INTERFACE, Location.LISTEN_PORT,
|
||||
Reason.INVALID_VALUE, String.valueOf(listenPort));
|
||||
this.listenPort = listenPort == 0 ? Optional.empty() : Optional.of(listenPort);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setMtu(final int mtu) throws BadConfigException {
|
||||
if (mtu < 0)
|
||||
throw new BadConfigException(Section.INTERFACE, Location.LISTEN_PORT,
|
||||
Reason.INVALID_VALUE, String.valueOf(mtu));
|
||||
this.mtu = mtu == 0 ? Optional.empty() : Optional.of(mtu);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.amnezia.awg;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public class GoBackend {
|
||||
@Nullable
|
||||
public static native String awgGetConfig(int handle);
|
||||
|
||||
public static native int awgGetSocketV4(int handle);
|
||||
|
||||
public static native int awgGetSocketV6(int handle);
|
||||
|
||||
public static native void awgTurnOff(int handle);
|
||||
|
||||
public static native int awgTurnOn(String ifName, int tunFd, String settings);
|
||||
|
||||
public static native String awgVersion();
|
||||
}
|
||||
+29
-26
@@ -1,21 +1,21 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.backend;
|
||||
package org.amnezia.awg.backend;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import com.wireguard.android.backend.BackendException.Reason;
|
||||
import com.wireguard.android.backend.Tunnel.State;
|
||||
import com.wireguard.android.util.RootShell;
|
||||
import com.wireguard.android.util.ToolsInstaller;
|
||||
import com.wireguard.config.Config;
|
||||
import com.wireguard.crypto.Key;
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
import org.amnezia.awg.backend.BackendException.Reason;
|
||||
import org.amnezia.awg.backend.Tunnel.State;
|
||||
import org.amnezia.awg.util.RootShell;
|
||||
import org.amnezia.awg.util.ToolsInstaller;
|
||||
import org.amnezia.awg.config.Config;
|
||||
import org.amnezia.awg.crypto.Key;
|
||||
import org.amnezia.awg.util.NonNullForAll;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
@@ -32,42 +32,45 @@ import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import java9.util.stream.Collectors;
|
||||
import java9.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* WireGuard backend that uses {@code wg-quick} to implement tunnel configuration.
|
||||
* Implementation of {@link Backend} that uses the kernel module and {@code awg-quick} to provide
|
||||
* AmneziaWG tunnels.
|
||||
*/
|
||||
|
||||
@NonNullForAll
|
||||
public final class WgQuickBackend implements Backend {
|
||||
private static final String TAG = "WireGuard/WgQuickBackend";
|
||||
public final class AwgQuickBackend implements Backend {
|
||||
private static final String TAG = "AmneziaWG/AwgQuickBackend";
|
||||
private final File localTemporaryDir;
|
||||
private final RootShell rootShell;
|
||||
private final Map<Tunnel, Config> runningConfigs = new HashMap<>();
|
||||
private final ToolsInstaller toolsInstaller;
|
||||
private boolean multipleTunnels;
|
||||
|
||||
public WgQuickBackend(final Context context, final RootShell rootShell, final ToolsInstaller toolsInstaller) {
|
||||
public AwgQuickBackend(final Context context, final RootShell rootShell, final ToolsInstaller toolsInstaller) {
|
||||
localTemporaryDir = new File(context.getCacheDir(), "tmp");
|
||||
this.rootShell = rootShell;
|
||||
this.toolsInstaller = toolsInstaller;
|
||||
}
|
||||
|
||||
public static boolean hasKernelSupport() {
|
||||
return new File("/sys/module/amneziawg").exists();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getRunningTunnelNames() {
|
||||
final List<String> output = new ArrayList<>();
|
||||
// Don't throw an exception here or nothing will show up in the UI.
|
||||
try {
|
||||
toolsInstaller.ensureToolsAvailable();
|
||||
if (rootShell.run(output, "wg show interfaces") != 0 || output.isEmpty())
|
||||
if (rootShell.run(output, "awg show interfaces") != 0 || output.isEmpty())
|
||||
return Collections.emptySet();
|
||||
} catch (final Exception e) {
|
||||
Log.w(TAG, "Unable to enumerate running tunnels", e);
|
||||
return Collections.emptySet();
|
||||
}
|
||||
// wg puts all interface names on the same line. Split them into separate elements.
|
||||
return Stream.of(output.get(0).split(" ")).collect(Collectors.toUnmodifiableSet());
|
||||
// awg puts all interface names on the same line. Split them into separate elements.
|
||||
return Set.of(output.get(0).split(" "));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -80,17 +83,17 @@ public final class WgQuickBackend implements Backend {
|
||||
final Statistics stats = new Statistics();
|
||||
final Collection<String> output = new ArrayList<>();
|
||||
try {
|
||||
if (rootShell.run(output, String.format("wg show '%s' transfer", tunnel.getName())) != 0)
|
||||
if (rootShell.run(output, String.format("awg show '%s' dump", tunnel.getName())) != 0)
|
||||
return stats;
|
||||
} catch (final Exception ignored) {
|
||||
return stats;
|
||||
}
|
||||
for (final String line : output) {
|
||||
final String[] parts = line.split("\\t");
|
||||
if (parts.length != 3)
|
||||
if (parts.length != 8)
|
||||
continue;
|
||||
try {
|
||||
stats.add(Key.fromBase64(parts[0]), Long.parseLong(parts[1]), Long.parseLong(parts[2]));
|
||||
stats.add(Key.fromBase64(parts[0]), Long.parseLong(parts[5]), Long.parseLong(parts[6]), Long.parseLong(parts[4]) * 1000);
|
||||
} catch (final Exception ignored) {
|
||||
}
|
||||
}
|
||||
@@ -100,7 +103,7 @@ public final class WgQuickBackend implements Backend {
|
||||
@Override
|
||||
public String getVersion() throws Exception {
|
||||
final List<String> output = new ArrayList<>();
|
||||
if (rootShell.run(output, "cat /sys/module/wireguard/version") != 0 || output.isEmpty())
|
||||
if (rootShell.run(output, "cat /sys/module/amneziawg/version") != 0 || output.isEmpty())
|
||||
throw new BackendException(Reason.UNKNOWN_KERNEL_MODULE_NAME);
|
||||
return output.get(0);
|
||||
}
|
||||
@@ -170,17 +173,17 @@ public final class WgQuickBackend implements Backend {
|
||||
|
||||
final File tempFile = new File(localTemporaryDir, tunnel.getName() + ".conf");
|
||||
try (final FileOutputStream stream = new FileOutputStream(tempFile, false)) {
|
||||
stream.write(config.toWgQuickString().getBytes(StandardCharsets.UTF_8));
|
||||
stream.write(config.toAwgQuickString().getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
String command = String.format("wg-quick %s '%s'",
|
||||
String command = String.format("awg-quick %s '%s'",
|
||||
state.toString().toLowerCase(Locale.ENGLISH), tempFile.getAbsolutePath());
|
||||
if (state == State.UP)
|
||||
command = "cat /sys/module/wireguard/version && " + command;
|
||||
command = "cat /sys/module/amneziawg/version && " + command;
|
||||
final int result = rootShell.run(null, command);
|
||||
// noinspection ResultOfMethodCallIgnored
|
||||
tempFile.delete();
|
||||
if (result != 0)
|
||||
throw new BackendException(Reason.WG_QUICK_CONFIG_ERROR_CODE, result);
|
||||
throw new BackendException(Reason.AWG_QUICK_CONFIG_ERROR_CODE, result);
|
||||
|
||||
if (state == State.UP)
|
||||
runningConfigs.put(tunnel, config);
|
||||
+9
-6
@@ -1,19 +1,19 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.backend;
|
||||
package org.amnezia.awg.backend;
|
||||
|
||||
import com.wireguard.config.Config;
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
import org.amnezia.awg.config.Config;
|
||||
import org.amnezia.awg.util.NonNullForAll;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Interface for implementations of the WireGuard secure network tunnel.
|
||||
* Interface for implementations of the AmneziaWG secure network tunnel.
|
||||
*/
|
||||
|
||||
@NonNullForAll
|
||||
@@ -30,6 +30,7 @@ public interface Backend {
|
||||
*
|
||||
* @param tunnel The tunnel to examine the state of.
|
||||
* @return The state of the tunnel.
|
||||
* @throws Exception Exception raised when retrieving tunnel's state.
|
||||
*/
|
||||
Tunnel.State getState(Tunnel tunnel) throws Exception;
|
||||
|
||||
@@ -39,6 +40,7 @@ public interface Backend {
|
||||
*
|
||||
* @param tunnel The tunnel to retrieve statistics for.
|
||||
* @return The statistics for the tunnel.
|
||||
* @throws Exception Exception raised when retrieving statistics.
|
||||
*/
|
||||
Statistics getStatistics(Tunnel tunnel) throws Exception;
|
||||
|
||||
@@ -46,7 +48,7 @@ public interface Backend {
|
||||
* Determine version of underlying backend.
|
||||
*
|
||||
* @return The version of the backend.
|
||||
* @throws Exception
|
||||
* @throws Exception Exception raised while retrieving version.
|
||||
*/
|
||||
String getVersion() throws Exception;
|
||||
|
||||
@@ -59,6 +61,7 @@ public interface Backend {
|
||||
* {@code TOGGLE}.
|
||||
* @param config The configuration for this tunnel, may be null if state is {@code DOWN}.
|
||||
* @return The updated state of the tunnel.
|
||||
* @throws Exception Exception raised while changing state.
|
||||
*/
|
||||
Tunnel.State setState(Tunnel tunnel, Tunnel.State state, @Nullable Config config) throws Exception;
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.amnezia.awg.backend;
|
||||
|
||||
import org.amnezia.awg.util.NonNullForAll;
|
||||
|
||||
/**
|
||||
* A subclass of {@link Exception} that encapsulates the reasons for a failure originating in
|
||||
* implementations of {@link Backend}.
|
||||
*/
|
||||
@NonNullForAll
|
||||
public final class BackendException extends Exception {
|
||||
private final Object[] format;
|
||||
private final Reason reason;
|
||||
|
||||
/**
|
||||
* Public constructor for BackendException.
|
||||
*
|
||||
* @param reason The {@link Reason} which caused this exception to be thrown
|
||||
* @param format Format string values used when converting exceptions to user-facing strings.
|
||||
*/
|
||||
public BackendException(final Reason reason, final Object... format) {
|
||||
this.reason = reason;
|
||||
this.format = format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the format string values associated with the instance.
|
||||
*
|
||||
* @return Array of {@link Object} for string formatting purposes
|
||||
*/
|
||||
public Object[] getFormat() {
|
||||
return format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the reason for this exception.
|
||||
*
|
||||
* @return Associated {@link Reason} for this exception.
|
||||
*/
|
||||
public Reason getReason() {
|
||||
return reason;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enum class containing all known reasons for why a {@link BackendException} might be thrown.
|
||||
*/
|
||||
public enum Reason {
|
||||
UNKNOWN_KERNEL_MODULE_NAME,
|
||||
AWG_QUICK_CONFIG_ERROR_CODE,
|
||||
TUNNEL_MISSING_CONFIG,
|
||||
VPN_NOT_AUTHORIZED,
|
||||
UNABLE_TO_START_VPN,
|
||||
TUN_CREATION_ERROR,
|
||||
GO_ACTIVATION_ERROR_CODE,
|
||||
DNS_RESOLUTION_FAILURE,
|
||||
}
|
||||
}
|
||||
+147
-44
@@ -1,25 +1,27 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.backend;
|
||||
package org.amnezia.awg.backend;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.system.OsConstants;
|
||||
import android.util.Log;
|
||||
|
||||
import com.wireguard.android.backend.BackendException.Reason;
|
||||
import com.wireguard.android.backend.Tunnel.State;
|
||||
import com.wireguard.android.util.SharedLibraryLoader;
|
||||
import com.wireguard.config.Config;
|
||||
import com.wireguard.config.InetNetwork;
|
||||
import com.wireguard.config.Peer;
|
||||
import com.wireguard.crypto.Key;
|
||||
import com.wireguard.crypto.KeyFormatException;
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
import org.amnezia.awg.backend.BackendException.Reason;
|
||||
import org.amnezia.awg.backend.Tunnel.State;
|
||||
import org.amnezia.awg.util.SharedLibraryLoader;
|
||||
import org.amnezia.awg.config.Config;
|
||||
import org.amnezia.awg.config.InetEndpoint;
|
||||
import org.amnezia.awg.config.InetNetwork;
|
||||
import org.amnezia.awg.config.Peer;
|
||||
import org.amnezia.awg.crypto.Key;
|
||||
import org.amnezia.awg.crypto.KeyFormatException;
|
||||
import org.amnezia.awg.util.NonNullForAll;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.Collections;
|
||||
@@ -33,9 +35,16 @@ import java.util.concurrent.TimeoutException;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.collection.ArraySet;
|
||||
|
||||
import static org.amnezia.awg.GoBackend.*;
|
||||
|
||||
/**
|
||||
* Implementation of {@link Backend} that uses the amneziawg-go userspace implementation to provide
|
||||
* AmneziaWG tunnels.
|
||||
*/
|
||||
@NonNullForAll
|
||||
public final class GoBackend implements Backend {
|
||||
private static final String TAG = "WireGuard/GoBackend";
|
||||
private static final int DNS_RESOLUTION_RETRIES = 10;
|
||||
private static final String TAG = "AmneziaWG/GoBackend";
|
||||
@Nullable private static AlwaysOnCallback alwaysOnCallback;
|
||||
private static GhettoCompletableFuture<VpnService> vpnService = new GhettoCompletableFuture<>();
|
||||
private final Context context;
|
||||
@@ -43,27 +52,33 @@ public final class GoBackend implements Backend {
|
||||
@Nullable private Tunnel currentTunnel;
|
||||
private int currentTunnelHandle = -1;
|
||||
|
||||
/**
|
||||
* Public constructor for GoBackend.
|
||||
*
|
||||
* @param context An Android {@link Context}
|
||||
*/
|
||||
public GoBackend(final Context context) {
|
||||
SharedLibraryLoader.loadSharedLibrary(context, "wg-go");
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a {@link AlwaysOnCallback} to be invoked when {@link VpnService} is started by the
|
||||
* system's Always-On VPN mode.
|
||||
*
|
||||
* @param cb Callback to be invoked
|
||||
*/
|
||||
public static void setAlwaysOnCallback(final AlwaysOnCallback cb) {
|
||||
alwaysOnCallback = cb;
|
||||
}
|
||||
|
||||
private static native String wgGetConfig(int handle);
|
||||
|
||||
private static native int wgGetSocketV4(int handle);
|
||||
|
||||
private static native int wgGetSocketV6(int handle);
|
||||
|
||||
private static native void wgTurnOff(int handle);
|
||||
|
||||
private static native int wgTurnOn(String ifName, int tunFd, String settings);
|
||||
|
||||
private static native String wgVersion();
|
||||
|
||||
/**
|
||||
* Method to get the names of running tunnels.
|
||||
*
|
||||
* @return A set of string values denoting names of running tunnels.
|
||||
*/
|
||||
@Override
|
||||
public Set<String> getRunningTunnelNames() {
|
||||
if (currentTunnel != null) {
|
||||
@@ -74,27 +89,42 @@ public final class GoBackend implements Backend {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the associated {@link State} for a given {@link Tunnel}.
|
||||
*
|
||||
* @param tunnel The tunnel to examine the state of.
|
||||
* @return {@link State} associated with the given tunnel.
|
||||
*/
|
||||
@Override
|
||||
public State getState(final Tunnel tunnel) {
|
||||
return currentTunnel == tunnel ? State.UP : State.DOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the associated {@link Statistics} for a given {@link Tunnel}.
|
||||
*
|
||||
* @param tunnel The tunnel to retrieve statistics for.
|
||||
* @return {@link Statistics} associated with the given tunnel.
|
||||
*/
|
||||
@Override
|
||||
public Statistics getStatistics(final Tunnel tunnel) {
|
||||
final Statistics stats = new Statistics();
|
||||
if (tunnel != currentTunnel) {
|
||||
if (tunnel != currentTunnel || currentTunnelHandle == -1)
|
||||
return stats;
|
||||
final String config = awgGetConfig(currentTunnelHandle);
|
||||
if (config == null)
|
||||
return stats;
|
||||
}
|
||||
final String config = wgGetConfig(currentTunnelHandle);
|
||||
Key key = null;
|
||||
long rx = 0;
|
||||
long tx = 0;
|
||||
long latestHandshakeMSec = 0;
|
||||
for (final String line : config.split("\\n")) {
|
||||
if (line.startsWith("public_key=")) {
|
||||
if (key != null)
|
||||
stats.add(key, rx, tx);
|
||||
stats.add(key, rx, tx, latestHandshakeMSec);
|
||||
rx = 0;
|
||||
tx = 0;
|
||||
latestHandshakeMSec = 0;
|
||||
try {
|
||||
key = Key.fromHex(line.substring(11));
|
||||
} catch (final KeyFormatException ignored) {
|
||||
@@ -116,18 +146,49 @@ public final class GoBackend implements Backend {
|
||||
} catch (final NumberFormatException ignored) {
|
||||
tx = 0;
|
||||
}
|
||||
} else if (line.startsWith("last_handshake_time_sec=")) {
|
||||
if (key == null)
|
||||
continue;
|
||||
try {
|
||||
latestHandshakeMSec += Long.parseLong(line.substring(24)) * 1000;
|
||||
} catch (final NumberFormatException ignored) {
|
||||
latestHandshakeMSec = 0;
|
||||
}
|
||||
} else if (line.startsWith("last_handshake_time_nsec=")) {
|
||||
if (key == null)
|
||||
continue;
|
||||
try {
|
||||
latestHandshakeMSec += Long.parseLong(line.substring(25)) / 1000000;
|
||||
} catch (final NumberFormatException ignored) {
|
||||
latestHandshakeMSec = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (key != null)
|
||||
stats.add(key, rx, tx);
|
||||
stats.add(key, rx, tx, latestHandshakeMSec);
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the version of the underlying amneziawg-go library.
|
||||
*
|
||||
* @return {@link String} value of the version of the amneziawg-go library.
|
||||
*/
|
||||
@Override
|
||||
public String getVersion() {
|
||||
return wgVersion();
|
||||
return awgVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the state of a given {@link Tunnel}, optionally applying a given {@link Config}.
|
||||
*
|
||||
* @param tunnel The tunnel to control the state of.
|
||||
* @param state The new state for this tunnel. Must be {@code UP}, {@code DOWN}, or
|
||||
* {@code TOGGLE}.
|
||||
* @param config The configuration for this tunnel, may be null if state is {@code DOWN}.
|
||||
* @return {@link State} of the tunnel after state changes are applied.
|
||||
* @throws Exception Exception raised while changing tunnel state.
|
||||
*/
|
||||
@Override
|
||||
public State setState(final Tunnel tunnel, State state, @Nullable final Config config) throws Exception {
|
||||
final State originalState = getState(tunnel);
|
||||
@@ -166,8 +227,10 @@ public final class GoBackend implements Backend {
|
||||
throw new BackendException(Reason.VPN_NOT_AUTHORIZED);
|
||||
|
||||
final VpnService service;
|
||||
if (!vpnService.isDone())
|
||||
startVpnService();
|
||||
if (!vpnService.isDone()) {
|
||||
Log.d(TAG, "Requesting to start VpnService");
|
||||
context.startService(new Intent(context, VpnService.class));
|
||||
}
|
||||
|
||||
try {
|
||||
service = vpnService.get(2, TimeUnit.SECONDS);
|
||||
@@ -183,8 +246,27 @@ public final class GoBackend implements Backend {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
dnsRetry: for (int i = 0; i < DNS_RESOLUTION_RETRIES; ++i) {
|
||||
// Pre-resolve IPs so they're cached when building the userspace string
|
||||
for (final Peer peer : config.getPeers()) {
|
||||
final InetEndpoint ep = peer.getEndpoint().orElse(null);
|
||||
if (ep == null)
|
||||
continue;
|
||||
if (ep.getResolved().orElse(null) == null) {
|
||||
if (i < DNS_RESOLUTION_RETRIES - 1) {
|
||||
Log.w(TAG, "DNS host \"" + ep.getHost() + "\" failed to resolve; trying again");
|
||||
Thread.sleep(1000);
|
||||
continue dnsRetry;
|
||||
} else
|
||||
throw new BackendException(Reason.DNS_RESOLUTION_FAILURE, ep.getHost());
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Build config
|
||||
final String goConfig = config.toWgUserspaceString();
|
||||
final String goConfig = config.toAwgUserspaceString();
|
||||
|
||||
// Create the vpn tunnel with android API
|
||||
final VpnService.Builder builder = service.getBuilder();
|
||||
@@ -193,15 +275,31 @@ public final class GoBackend implements Backend {
|
||||
for (final String excludedApplication : config.getInterface().getExcludedApplications())
|
||||
builder.addDisallowedApplication(excludedApplication);
|
||||
|
||||
for (final String includedApplication : config.getInterface().getIncludedApplications())
|
||||
builder.addAllowedApplication(includedApplication);
|
||||
|
||||
for (final InetNetwork addr : config.getInterface().getAddresses())
|
||||
builder.addAddress(addr.getAddress(), addr.getMask());
|
||||
|
||||
for (final InetAddress addr : config.getInterface().getDnsServers())
|
||||
builder.addDnsServer(addr.getHostAddress());
|
||||
|
||||
for (final String dnsSearchDomain : config.getInterface().getDnsSearchDomains())
|
||||
builder.addSearchDomain(dnsSearchDomain);
|
||||
|
||||
boolean sawDefaultRoute = false;
|
||||
for (final Peer peer : config.getPeers()) {
|
||||
for (final InetNetwork addr : peer.getAllowedIps())
|
||||
for (final InetNetwork addr : peer.getAllowedIps()) {
|
||||
if (addr.getMask() == 0)
|
||||
sawDefaultRoute = true;
|
||||
builder.addRoute(addr.getAddress(), addr.getMask());
|
||||
}
|
||||
}
|
||||
|
||||
// "Kill-switch" semantics
|
||||
if (!(sawDefaultRoute && config.getPeers().size() == 1)) {
|
||||
builder.allowFamily(OsConstants.AF_INET);
|
||||
builder.allowFamily(OsConstants.AF_INET6);
|
||||
}
|
||||
|
||||
builder.setMtu(config.getInterface().getMtu().orElse(1280));
|
||||
@@ -215,8 +313,8 @@ public final class GoBackend implements Backend {
|
||||
try (final ParcelFileDescriptor tun = builder.establish()) {
|
||||
if (tun == null)
|
||||
throw new BackendException(Reason.TUN_CREATION_ERROR);
|
||||
Log.d(TAG, "Go backend v" + wgVersion());
|
||||
currentTunnelHandle = wgTurnOn(tunnel.getName(), tun.detachFd(), goConfig);
|
||||
Log.d(TAG, "Go backend " + awgVersion());
|
||||
currentTunnelHandle = awgTurnOn(tunnel.getName(), tun.detachFd(), goConfig);
|
||||
}
|
||||
if (currentTunnelHandle < 0)
|
||||
throw new BackendException(Reason.GO_ACTIVATION_ERROR_CODE, currentTunnelHandle);
|
||||
@@ -224,28 +322,30 @@ public final class GoBackend implements Backend {
|
||||
currentTunnel = tunnel;
|
||||
currentConfig = config;
|
||||
|
||||
service.protect(wgGetSocketV4(currentTunnelHandle));
|
||||
service.protect(wgGetSocketV6(currentTunnelHandle));
|
||||
service.protect(awgGetSocketV4(currentTunnelHandle));
|
||||
service.protect(awgGetSocketV6(currentTunnelHandle));
|
||||
} else {
|
||||
if (currentTunnelHandle == -1) {
|
||||
Log.w(TAG, "Tunnel already down");
|
||||
return;
|
||||
}
|
||||
|
||||
wgTurnOff(currentTunnelHandle);
|
||||
int handleToClose = currentTunnelHandle;
|
||||
currentTunnel = null;
|
||||
currentTunnelHandle = -1;
|
||||
currentConfig = null;
|
||||
awgTurnOff(handleToClose);
|
||||
try {
|
||||
vpnService.get(0, TimeUnit.NANOSECONDS).stopSelf();
|
||||
} catch (final TimeoutException ignored) { }
|
||||
}
|
||||
|
||||
tunnel.onStateChange(state);
|
||||
}
|
||||
|
||||
private void startVpnService() {
|
||||
Log.d(TAG, "Requesting to start VpnService");
|
||||
context.startService(new Intent(context, VpnService.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for {@link GoBackend} that is invoked when {@link VpnService} is started by the
|
||||
* system's Always-On VPN mode.
|
||||
*/
|
||||
public interface AlwaysOnCallback {
|
||||
void alwaysOnTriggered();
|
||||
}
|
||||
@@ -279,6 +379,9 @@ public final class GoBackend implements Backend {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link android.net.VpnService} implementation for {@link GoBackend}
|
||||
*/
|
||||
public static class VpnService extends android.net.VpnService {
|
||||
@Nullable private GoBackend owner;
|
||||
|
||||
@@ -298,7 +401,7 @@ public final class GoBackend implements Backend {
|
||||
final Tunnel tunnel = owner.currentTunnel;
|
||||
if (tunnel != null) {
|
||||
if (owner.currentTunnelHandle != -1)
|
||||
wgTurnOff(owner.currentTunnelHandle);
|
||||
awgTurnOff(owner.currentTunnelHandle);
|
||||
owner.currentTunnel = null;
|
||||
owner.currentTunnelHandle = -1;
|
||||
owner.currentConfig = null;
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.amnezia.awg.backend;
|
||||
|
||||
import android.os.SystemClock;
|
||||
|
||||
import org.amnezia.awg.crypto.Key;
|
||||
import org.amnezia.awg.util.NonNullForAll;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Class representing transfer statistics for a {@link Tunnel} instance.
|
||||
*/
|
||||
@NonNullForAll
|
||||
public class Statistics {
|
||||
public record PeerStats(long rxBytes, long txBytes, long latestHandshakeEpochMillis) { }
|
||||
private final Map<Key, PeerStats> stats = new HashMap<>();
|
||||
private long lastTouched = SystemClock.elapsedRealtime();
|
||||
|
||||
Statistics() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a peer and its current stats to the internal map.
|
||||
*
|
||||
* @param key An AmneziaWG public key bound to a particular peer
|
||||
* @param rxBytes The received traffic for the {@link org.amnezia.config.Peer} referenced by
|
||||
* the provided {@link Key}. This value is in bytes
|
||||
* @param txBytes The transmitted traffic for the {@link org.amnezia.config.Peer} referenced by
|
||||
* the provided {@link Key}. This value is in bytes.
|
||||
* @param latestHandshake The timestamp of the latest handshake for the {@link org.amnezia.config.Peer}
|
||||
* referenced by the provided {@link Key}. The value is in epoch milliseconds.
|
||||
*/
|
||||
void add(final Key key, final long rxBytes, final long txBytes, final long latestHandshake) {
|
||||
stats.put(key, new PeerStats(rxBytes, txBytes, latestHandshake));
|
||||
lastTouched = SystemClock.elapsedRealtime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the statistics are stale, indicating the need for the {@link Backend} to update them.
|
||||
*
|
||||
* @return boolean indicating if the current statistics instance has stale values.
|
||||
*/
|
||||
public boolean isStale() {
|
||||
return SystemClock.elapsedRealtime() - lastTouched > 900;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the statistics for the {@link org.amnezia.config.Peer} referenced by the provided {@link Key}
|
||||
*
|
||||
* @param peer A {@link Key} representing a {@link org.amnezia.config.Peer}.
|
||||
* @return a {@link PeerStats} representing various statistics about this peer.
|
||||
*/
|
||||
@Nullable
|
||||
public PeerStats peer(final Key peer) {
|
||||
return stats.get(peer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of peers being tracked by this instance.
|
||||
*
|
||||
* @return An array of {@link Key} instances representing AmneziaWG
|
||||
* {@link org.amnezia.config.Peer}s
|
||||
*/
|
||||
public Key[] peers() {
|
||||
return stats.keySet().toArray(new Key[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total received traffic by all the peers being tracked by this instance
|
||||
*
|
||||
* @return a long representing the number of bytes received by the peers being tracked.
|
||||
*/
|
||||
public long totalRx() {
|
||||
long rx = 0;
|
||||
for (final PeerStats val : stats.values()) {
|
||||
rx += val.rxBytes;
|
||||
}
|
||||
return rx;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total transmitted traffic by all the peers being tracked by this instance
|
||||
*
|
||||
* @return a long representing the number of bytes transmitted by the peers being tracked.
|
||||
*/
|
||||
public long totalTx() {
|
||||
long tx = 0;
|
||||
for (final PeerStats val : stats.values()) {
|
||||
tx += val.txBytes;
|
||||
}
|
||||
return tx;
|
||||
}
|
||||
}
|
||||
+13
-4
@@ -1,16 +1,16 @@
|
||||
/*
|
||||
* Copyright © 2020 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.backend;
|
||||
package org.amnezia.awg.backend;
|
||||
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
import org.amnezia.awg.util.NonNullForAll;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Represents a WireGuard tunnel.
|
||||
* Represents an AmneziaWG tunnel.
|
||||
*/
|
||||
|
||||
@NonNullForAll
|
||||
@@ -36,11 +36,20 @@ public interface Tunnel {
|
||||
*/
|
||||
void onStateChange(State newState);
|
||||
|
||||
/**
|
||||
* Enum class to represent all possible states of a {@link Tunnel}.
|
||||
*/
|
||||
enum State {
|
||||
DOWN,
|
||||
TOGGLE,
|
||||
UP;
|
||||
|
||||
/**
|
||||
* Get the state of a {@link Tunnel}
|
||||
*
|
||||
* @param running boolean indicating if the tunnel is running.
|
||||
* @return State of the tunnel based on whether or not it is running.
|
||||
*/
|
||||
public static State of(final boolean running) {
|
||||
return running ? UP : DOWN;
|
||||
}
|
||||
+4
-5
@@ -1,18 +1,17 @@
|
||||
/*
|
||||
* Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.config;
|
||||
package org.amnezia.awg.config;
|
||||
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
import org.amnezia.awg.util.NonNullForAll;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import java9.util.Optional;
|
||||
|
||||
@NonNullForAll
|
||||
public final class Attribute {
|
||||
private static final Pattern LINE_PATTERN = Pattern.compile("(\\w+)\\s*=\\s*([^\\s#][^#]*)");
|
||||
+15
-5
@@ -1,12 +1,12 @@
|
||||
/*
|
||||
* Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.config;
|
||||
package org.amnezia.awg.config;
|
||||
|
||||
import com.wireguard.crypto.KeyFormatException;
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
import org.amnezia.awg.crypto.KeyFormatException;
|
||||
import org.amnezia.awg.util.NonNullForAll;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
@@ -72,12 +72,22 @@ public class BadConfigException extends Exception {
|
||||
DNS("DNS"),
|
||||
ENDPOINT("Endpoint"),
|
||||
EXCLUDED_APPLICATIONS("ExcludedApplications"),
|
||||
INCLUDED_APPLICATIONS("IncludedApplications"),
|
||||
LISTEN_PORT("ListenPort"),
|
||||
MTU("MTU"),
|
||||
PERSISTENT_KEEPALIVE("PersistentKeepalive"),
|
||||
PRE_SHARED_KEY("PresharedKey"),
|
||||
PRIVATE_KEY("PrivateKey"),
|
||||
PUBLIC_KEY("PublicKey");
|
||||
PUBLIC_KEY("PublicKey"),
|
||||
JUNK_PACKET_COUNT("JunkPacketCount"),
|
||||
JUNK_PACKET_MIN_SIZE("JunkPacketMinSize"),
|
||||
JUNK_PACKET_MAX_SIZE("JunkPacketMaxSize"),
|
||||
INIT_PACKET_JUNK_SIZE("InitPacketJunkSize"),
|
||||
RESPONSE_PACKET_JUNK_SIZE("ResponsePacketJunkSize"),
|
||||
INIT_PACKET_MAGIC_HEADER("InitPacketMagicHeader"),
|
||||
RESPONSE_PACKET_MAGIC_HEADER("ResponsePacketMagicHeader"),
|
||||
UNDERLOAD_PACKET_MAGIC_HEADER("UnderloadPacketMagicHeader"),
|
||||
TRANSPORT_PACKET_MAGIC_HEADER("TransportPacketMagicHeader");
|
||||
|
||||
private final String name;
|
||||
|
||||
+18
-20
@@ -1,14 +1,14 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.config;
|
||||
package org.amnezia.awg.config;
|
||||
|
||||
import com.wireguard.config.BadConfigException.Location;
|
||||
import com.wireguard.config.BadConfigException.Reason;
|
||||
import com.wireguard.config.BadConfigException.Section;
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
import org.amnezia.awg.config.BadConfigException.Location;
|
||||
import org.amnezia.awg.config.BadConfigException.Reason;
|
||||
import org.amnezia.awg.config.BadConfigException.Section;
|
||||
import org.amnezia.awg.util.NonNullForAll;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
@@ -17,15 +17,13 @@ import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Represents the contents of a wg-quick configuration file, made up of one or more "Interface"
|
||||
* Represents the contents of a awg-quick configuration file, made up of one or more "Interface"
|
||||
* sections (combined together), and zero or more "Peer" sections (treated individually).
|
||||
* <p>
|
||||
* Instances of this class are immutable.
|
||||
@@ -46,7 +44,7 @@ public final class Config {
|
||||
* {@link BadConfigException} if the input is not well-formed or contains data that cannot
|
||||
* be parsed.
|
||||
*
|
||||
* @param stream a stream of UTF-8 text that is interpreted as a WireGuard configuration
|
||||
* @param stream a stream of UTF-8 text that is interpreted as an AmneziaWG configuration
|
||||
* @return a {@code Config} instance representing the supplied configuration
|
||||
*/
|
||||
public static Config parse(final InputStream stream)
|
||||
@@ -59,7 +57,7 @@ public final class Config {
|
||||
* {@link BadConfigException} if the input is not well-formed or contains data that cannot
|
||||
* be parsed.
|
||||
*
|
||||
* @param reader a BufferedReader of UTF-8 text that is interpreted as a WireGuard configuration
|
||||
* @param reader a BufferedReader of UTF-8 text that is interpreted as an AmneziaWG configuration
|
||||
* @return a {@code Config} instance representing the supplied configuration
|
||||
*/
|
||||
public static Config parse(final BufferedReader reader)
|
||||
@@ -157,37 +155,37 @@ public final class Config {
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the {@code Config} into a string suitable for use as a {@code wg-quick}
|
||||
* Converts the {@code Config} into a string suitable for use as a {@code awg-quick}
|
||||
* configuration file.
|
||||
*
|
||||
* @return the {@code Config} represented as one [Interface] and zero or more [Peer] sections
|
||||
*/
|
||||
public String toWgQuickString() {
|
||||
public String toAwgQuickString() {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append("[Interface]\n").append(interfaze.toWgQuickString());
|
||||
sb.append("[Interface]\n").append(interfaze.toAwgQuickString());
|
||||
for (final Peer peer : peers)
|
||||
sb.append("\n[Peer]\n").append(peer.toWgQuickString());
|
||||
sb.append("\n[Peer]\n").append(peer.toAwgQuickString());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the {@code Config} for use with the WireGuard cross-platform userspace API.
|
||||
* Serializes the {@code Config} for use with the AmneziaWG cross-platform userspace API.
|
||||
*
|
||||
* @return the {@code Config} represented as a series of "key=value" lines
|
||||
*/
|
||||
public String toWgUserspaceString() {
|
||||
public String toAwgUserspaceString() {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append(interfaze.toWgUserspaceString());
|
||||
sb.append(interfaze.toAwgUserspaceString());
|
||||
sb.append("replace_peers=true\n");
|
||||
for (final Peer peer : peers)
|
||||
sb.append(peer.toWgUserspaceString());
|
||||
sb.append(peer.toAwgUserspaceString());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnusedReturnValue")
|
||||
public static final class Builder {
|
||||
// Defaults to an empty set.
|
||||
private final Set<Peer> peers = new LinkedHashSet<>();
|
||||
private final ArrayList<Peer> peers = new ArrayList<>();
|
||||
// No default; must be provided before building.
|
||||
@Nullable private Interface interfaze;
|
||||
|
||||
+14
-3
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.config;
|
||||
package org.amnezia.awg.config;
|
||||
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
import org.amnezia.awg.util.NonNullForAll;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.Inet4Address;
|
||||
@@ -23,6 +23,7 @@ import androidx.annotation.Nullable;
|
||||
public final class InetAddresses {
|
||||
@Nullable private static final Method PARSER_METHOD;
|
||||
private static final Pattern WONT_TOUCH_RESOLVER = Pattern.compile("^(((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:)))(%.+)?)|((?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))$");
|
||||
private static final Pattern VALID_HOSTNAME = Pattern.compile("^(?=.{1,255}$)[0-9A-Za-z](?:(?:[0-9A-Za-z]|-){0,61}[0-9A-Za-z])?(?:\\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|-){0,61}[0-9A-Za-z])?)*\\.?$");
|
||||
|
||||
static {
|
||||
Method m = null;
|
||||
@@ -38,6 +39,16 @@ public final class InetAddresses {
|
||||
private InetAddresses() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether input is a valid DNS hostname.
|
||||
*
|
||||
* @param maybeHostname a string that is possibly a DNS hostname
|
||||
* @return whether or not maybeHostname is a valid DNS hostname
|
||||
*/
|
||||
public static boolean isHostname(final CharSequence maybeHostname) {
|
||||
return VALID_HOSTNAME.matcher(maybeHostname).matches();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a numeric IPv4 or IPv6 address without performing any DNS lookups.
|
||||
*
|
||||
+8
-9
@@ -1,28 +1,27 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.config;
|
||||
package org.amnezia.awg.config;
|
||||
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
|
||||
import org.threeten.bp.Duration;
|
||||
import org.threeten.bp.Instant;
|
||||
import org.amnezia.awg.util.NonNullForAll;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import java9.util.Optional;
|
||||
|
||||
|
||||
/**
|
||||
* An external endpoint (host and port) used to connect to a WireGuard {@link Peer}.
|
||||
* An external endpoint (host and port) used to connect to an AmneziaWG {@link Peer}.
|
||||
* <p>
|
||||
* Instances of this class are externally immutable.
|
||||
*/
|
||||
@@ -49,7 +48,7 @@ public final class InetEndpoint {
|
||||
throw new ParseException(InetEndpoint.class, endpoint, "Forbidden characters");
|
||||
final URI uri;
|
||||
try {
|
||||
uri = new URI("wg://" + endpoint);
|
||||
uri = new URI("awg://" + endpoint);
|
||||
} catch (final URISyntaxException e) {
|
||||
throw new ParseException(InetEndpoint.class, endpoint, e);
|
||||
}
|
||||
+3
-3
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.config;
|
||||
package org.amnezia.awg.config;
|
||||
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
import org.amnezia.awg.util.NonNullForAll;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.InetAddress;
|
||||
@@ -0,0 +1,750 @@
|
||||
/*
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.amnezia.awg.config;
|
||||
|
||||
import org.amnezia.awg.config.BadConfigException.Location;
|
||||
import org.amnezia.awg.config.BadConfigException.Reason;
|
||||
import org.amnezia.awg.config.BadConfigException.Section;
|
||||
import org.amnezia.awg.crypto.Key;
|
||||
import org.amnezia.awg.crypto.KeyFormatException;
|
||||
import org.amnezia.awg.crypto.KeyPair;
|
||||
import org.amnezia.awg.util.NonNullForAll;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Represents the configuration for an AmneziaWG interface (an [Interface] block). Interfaces must
|
||||
* have a private key (used to initialize a {@code KeyPair}), and may optionally have several other
|
||||
* attributes.
|
||||
* <p>
|
||||
* Instances of this class are immutable.
|
||||
*/
|
||||
@NonNullForAll
|
||||
public final class Interface {
|
||||
private static final int MAX_UDP_PORT = 65535;
|
||||
private static final int MIN_UDP_PORT = 0;
|
||||
|
||||
private final Set<InetNetwork> addresses;
|
||||
private final Set<InetAddress> dnsServers;
|
||||
private final Set<String> dnsSearchDomains;
|
||||
private final Set<String> excludedApplications;
|
||||
private final Set<String> includedApplications;
|
||||
private final KeyPair keyPair;
|
||||
private final Optional<Integer> listenPort;
|
||||
private final Optional<Integer> mtu;
|
||||
private final Optional<Integer> junkPacketCount;
|
||||
private final Optional<Integer> junkPacketMinSize;
|
||||
private final Optional<Integer> junkPacketMaxSize;
|
||||
private final Optional<Integer> initPacketJunkSize;
|
||||
private final Optional<Integer> responsePacketJunkSize;
|
||||
private final Optional<Long> initPacketMagicHeader;
|
||||
private final Optional<Long> responsePacketMagicHeader;
|
||||
private final Optional<Long> underloadPacketMagicHeader;
|
||||
private final Optional<Long> transportPacketMagicHeader;
|
||||
|
||||
private Interface(final Builder builder) {
|
||||
// Defensively copy to ensure immutability even if the Builder is reused.
|
||||
addresses = Collections.unmodifiableSet(new LinkedHashSet<>(builder.addresses));
|
||||
dnsServers = Collections.unmodifiableSet(new LinkedHashSet<>(builder.dnsServers));
|
||||
dnsSearchDomains = Collections.unmodifiableSet(new LinkedHashSet<>(builder.dnsSearchDomains));
|
||||
excludedApplications = Collections.unmodifiableSet(new LinkedHashSet<>(builder.excludedApplications));
|
||||
includedApplications = Collections.unmodifiableSet(new LinkedHashSet<>(builder.includedApplications));
|
||||
keyPair = Objects.requireNonNull(builder.keyPair, "Interfaces must have a private key");
|
||||
listenPort = builder.listenPort;
|
||||
mtu = builder.mtu;
|
||||
junkPacketCount = builder.junkPacketCount;
|
||||
junkPacketMinSize = builder.junkPacketMinSize;
|
||||
junkPacketMaxSize = builder.junkPacketMaxSize;
|
||||
initPacketJunkSize = builder.initPacketJunkSize;
|
||||
responsePacketJunkSize = builder.responsePacketJunkSize;
|
||||
initPacketMagicHeader = builder.initPacketMagicHeader;
|
||||
responsePacketMagicHeader = builder.responsePacketMagicHeader;
|
||||
underloadPacketMagicHeader = builder.underloadPacketMagicHeader;
|
||||
transportPacketMagicHeader = builder.transportPacketMagicHeader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an series of "KEY = VALUE" lines into an {@code Interface}. Throws
|
||||
* {@link ParseException} if the input is not well-formed or contains unknown attributes.
|
||||
*
|
||||
* @param lines An iterable sequence of lines, containing at least a private key attribute
|
||||
* @return An {@code Interface} with all of the attributes from {@code lines} set
|
||||
*/
|
||||
public static Interface parse(final Iterable<? extends CharSequence> lines)
|
||||
throws BadConfigException {
|
||||
final Builder builder = new Builder();
|
||||
for (final CharSequence line : lines) {
|
||||
final Attribute attribute = Attribute.parse(line).orElseThrow(() ->
|
||||
new BadConfigException(Section.INTERFACE, Location.TOP_LEVEL,
|
||||
Reason.SYNTAX_ERROR, line));
|
||||
switch (attribute.getKey().toLowerCase(Locale.ENGLISH)) {
|
||||
case "address":
|
||||
builder.parseAddresses(attribute.getValue());
|
||||
break;
|
||||
case "dns":
|
||||
builder.parseDnsServers(attribute.getValue());
|
||||
break;
|
||||
case "excludedapplications":
|
||||
builder.parseExcludedApplications(attribute.getValue());
|
||||
break;
|
||||
case "includedapplications":
|
||||
builder.parseIncludedApplications(attribute.getValue());
|
||||
break;
|
||||
case "listenport":
|
||||
builder.parseListenPort(attribute.getValue());
|
||||
break;
|
||||
case "mtu":
|
||||
builder.parseMtu(attribute.getValue());
|
||||
break;
|
||||
case "privatekey":
|
||||
builder.parsePrivateKey(attribute.getValue());
|
||||
break;
|
||||
case "jc":
|
||||
builder.parseJunkPacketCount(attribute.getValue());
|
||||
break;
|
||||
case "jmin":
|
||||
builder.parseJunkPacketMinSize(attribute.getValue());
|
||||
break;
|
||||
case "jmax":
|
||||
builder.parseJunkPacketMaxSize(attribute.getValue());
|
||||
break;
|
||||
case "s1":
|
||||
builder.parseInitPacketJunkSize(attribute.getValue());
|
||||
break;
|
||||
case "s2":
|
||||
builder.parseResponsePacketJunkSize(attribute.getValue());
|
||||
break;
|
||||
case "h1":
|
||||
builder.parseInitPacketMagicHeader(attribute.getValue());
|
||||
break;
|
||||
case "h2":
|
||||
builder.parseResponsePacketMagicHeader(attribute.getValue());
|
||||
break;
|
||||
case "h3":
|
||||
builder.parseUnderloadPacketMagicHeader(attribute.getValue());
|
||||
break;
|
||||
case "h4":
|
||||
builder.parseTransportPacketMagicHeader(attribute.getValue());
|
||||
break;
|
||||
default:
|
||||
throw new BadConfigException(Section.INTERFACE, Location.TOP_LEVEL,
|
||||
Reason.UNKNOWN_ATTRIBUTE, attribute.getKey());
|
||||
}
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (!(obj instanceof Interface))
|
||||
return false;
|
||||
final Interface other = (Interface) obj;
|
||||
return addresses.equals(other.addresses)
|
||||
&& dnsServers.equals(other.dnsServers)
|
||||
&& dnsSearchDomains.equals(other.dnsSearchDomains)
|
||||
&& excludedApplications.equals(other.excludedApplications)
|
||||
&& includedApplications.equals(other.includedApplications)
|
||||
&& keyPair.equals(other.keyPair)
|
||||
&& listenPort.equals(other.listenPort)
|
||||
&& mtu.equals(other.mtu)
|
||||
&& junkPacketCount.equals(other.junkPacketCount)
|
||||
&& junkPacketMinSize.equals(other.junkPacketMinSize)
|
||||
&& junkPacketMaxSize.equals(other.junkPacketMaxSize)
|
||||
&& initPacketJunkSize.equals(other.initPacketJunkSize)
|
||||
&& responsePacketJunkSize.equals(other.responsePacketJunkSize)
|
||||
&& initPacketMagicHeader.equals(other.initPacketMagicHeader)
|
||||
&& responsePacketMagicHeader.equals(other.responsePacketMagicHeader)
|
||||
&& underloadPacketMagicHeader.equals(other.underloadPacketMagicHeader)
|
||||
&& transportPacketMagicHeader.equals(other.transportPacketMagicHeader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set of IP addresses assigned to the interface.
|
||||
*
|
||||
* @return a set of {@link InetNetwork}s
|
||||
*/
|
||||
public Set<InetNetwork> getAddresses() {
|
||||
// The collection is already immutable.
|
||||
return addresses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set of DNS servers associated with the interface.
|
||||
*
|
||||
* @return a set of {@link InetAddress}es
|
||||
*/
|
||||
public Set<InetAddress> getDnsServers() {
|
||||
// The collection is already immutable.
|
||||
return dnsServers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set of DNS search domains associated with the interface.
|
||||
*
|
||||
* @return a set of strings
|
||||
*/
|
||||
public Set<String> getDnsSearchDomains() {
|
||||
// The collection is already immutable.
|
||||
return dnsSearchDomains;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set of applications excluded from using the interface.
|
||||
*
|
||||
* @return a set of package names
|
||||
*/
|
||||
public Set<String> getExcludedApplications() {
|
||||
// The collection is already immutable.
|
||||
return excludedApplications;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set of applications included exclusively for using the interface.
|
||||
*
|
||||
* @return a set of package names
|
||||
*/
|
||||
public Set<String> getIncludedApplications() {
|
||||
// The collection is already immutable.
|
||||
return includedApplications;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the public/private key pair used by the interface.
|
||||
*
|
||||
* @return a key pair
|
||||
*/
|
||||
public KeyPair getKeyPair() {
|
||||
return keyPair;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the UDP port number that the AmneziaWG interface will listen on.
|
||||
*
|
||||
* @return a UDP port number, or {@code Optional.empty()} if none is configured
|
||||
*/
|
||||
public Optional<Integer> getListenPort() {
|
||||
return listenPort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the MTU used for the AmneziaWG interface.
|
||||
*
|
||||
* @return the MTU, or {@code Optional.empty()} if none is configured
|
||||
*/
|
||||
public Optional<Integer> getMtu() {
|
||||
return mtu;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the junkPacketCount used for the AmneziaWG interface.
|
||||
*
|
||||
* @return the junkPacketCount, or {@code Optional.empty()} if none is configured
|
||||
*/
|
||||
public Optional<Integer> getJunkPacketCount() {
|
||||
return junkPacketCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the junkPacketMinSize used for the AmneziaWG interface.
|
||||
*
|
||||
* @return the junkPacketMinSize, or {@code Optional.empty()} if none is configured
|
||||
*/
|
||||
public Optional<Integer> getJunkPacketMinSize() {
|
||||
return junkPacketMinSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the junkPacketMaxSize used for the AmneziaWG interface.
|
||||
*
|
||||
* @return the junkPacketMaxSize, or {@code Optional.empty()} if none is configured
|
||||
*/
|
||||
public Optional<Integer> getJunkPacketMaxSize() {
|
||||
return junkPacketMaxSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the initPacketJunkSize used for the AmneziaWG interface.
|
||||
*
|
||||
* @return the initPacketJunkSize, or {@code Optional.empty()} if none is configured
|
||||
*/
|
||||
public Optional<Integer> getInitPacketJunkSize() {
|
||||
return initPacketJunkSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the responsePacketJunkSize used for the AmneziaWG interface.
|
||||
*
|
||||
* @return the responsePacketJunkSize, or {@code Optional.empty()} if none is configured
|
||||
*/
|
||||
public Optional<Integer> getResponsePacketJunkSize() {
|
||||
return responsePacketJunkSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the initPacketMagicHeader used for the AmneziaWG interface.
|
||||
*
|
||||
* @return the initPacketMagicHeader, or {@code Optional.empty()} if none is configured
|
||||
*/
|
||||
public Optional<Long> getInitPacketMagicHeader() {
|
||||
return initPacketMagicHeader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the responsePacketMagicHeader used for the AmneziaWG interface.
|
||||
*
|
||||
* @return the responsePacketMagicHeader, or {@code Optional.empty()} if none is configured
|
||||
*/
|
||||
public Optional<Long> getResponsePacketMagicHeader() {
|
||||
return responsePacketMagicHeader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the underloadPacketMagicHeader used for the AmneziaWG interface.
|
||||
*
|
||||
* @return the underloadPacketMagicHeader, or {@code Optional.empty()} if none is configured
|
||||
*/
|
||||
public Optional<Long> getUnderloadPacketMagicHeader() {
|
||||
return underloadPacketMagicHeader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the transportPacketMagicHeader used for the AmneziaWG interface.
|
||||
*
|
||||
* @return the transportPacketMagicHeader, or {@code Optional.empty()} if none is configured
|
||||
*/
|
||||
public Optional<Long> getTransportPacketMagicHeader() {
|
||||
return transportPacketMagicHeader;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 1;
|
||||
hash = 31 * hash + addresses.hashCode();
|
||||
hash = 31 * hash + dnsServers.hashCode();
|
||||
hash = 31 * hash + excludedApplications.hashCode();
|
||||
hash = 31 * hash + includedApplications.hashCode();
|
||||
hash = 31 * hash + keyPair.hashCode();
|
||||
hash = 31 * hash + listenPort.hashCode();
|
||||
hash = 31 * hash + mtu.hashCode();
|
||||
hash = 31 * hash + junkPacketCount.hashCode();
|
||||
hash = 31 * hash + junkPacketMinSize.hashCode();
|
||||
hash = 31 * hash + junkPacketMaxSize.hashCode();
|
||||
hash = 31 * hash + initPacketJunkSize.hashCode();
|
||||
hash = 31 * hash + responsePacketJunkSize.hashCode();
|
||||
hash = 31 * hash + initPacketMagicHeader.hashCode();
|
||||
hash = 31 * hash + responsePacketMagicHeader.hashCode();
|
||||
hash = 31 * hash + underloadPacketMagicHeader.hashCode();
|
||||
hash = 31 * hash + transportPacketMagicHeader.hashCode();
|
||||
return hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the {@code Interface} into a string suitable for debugging purposes. The {@code
|
||||
* Interface} is identified by its public key and (if set) the port used for its UDP socket.
|
||||
*
|
||||
* @return A concise single-line identifier for the {@code Interface}
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder sb = new StringBuilder("(Interface ");
|
||||
sb.append(keyPair.getPublicKey().toBase64());
|
||||
listenPort.ifPresent(lp -> sb.append(" @").append(lp));
|
||||
sb.append(')');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the {@code Interface} into a string suitable for inclusion in a {@code awg-quick}
|
||||
* configuration file.
|
||||
*
|
||||
* @return The {@code Interface} represented as a series of "Key = Value" lines
|
||||
*/
|
||||
public String toAwgQuickString() {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
if (!addresses.isEmpty())
|
||||
sb.append("Address = ").append(Attribute.join(addresses)).append('\n');
|
||||
if (!dnsServers.isEmpty()) {
|
||||
final List<String> dnsServerStrings = dnsServers.stream().map(InetAddress::getHostAddress).collect(Collectors.toList());
|
||||
dnsServerStrings.addAll(dnsSearchDomains);
|
||||
sb.append("DNS = ").append(Attribute.join(dnsServerStrings)).append('\n');
|
||||
}
|
||||
if (!excludedApplications.isEmpty())
|
||||
sb.append("ExcludedApplications = ").append(Attribute.join(excludedApplications)).append('\n');
|
||||
if (!includedApplications.isEmpty())
|
||||
sb.append("IncludedApplications = ").append(Attribute.join(includedApplications)).append('\n');
|
||||
listenPort.ifPresent(lp -> sb.append("ListenPort = ").append(lp).append('\n'));
|
||||
mtu.ifPresent(m -> sb.append("MTU = ").append(m).append('\n'));
|
||||
junkPacketCount.ifPresent(jc -> sb.append("Jc = ").append(jc).append('\n'));
|
||||
junkPacketMinSize.ifPresent(jmin -> sb.append("Jmin = ").append(jmin).append('\n'));
|
||||
junkPacketMaxSize.ifPresent(jmax -> sb.append("Jmax = ").append(jmax).append('\n'));
|
||||
initPacketJunkSize.ifPresent(s1 -> sb.append("S1 = ").append(s1).append('\n'));
|
||||
responsePacketJunkSize.ifPresent(s2 -> sb.append("S2 = ").append(s2).append('\n'));
|
||||
initPacketMagicHeader.ifPresent(h1 -> sb.append("H1 = ").append(h1).append('\n'));
|
||||
responsePacketMagicHeader.ifPresent(h2 -> sb.append("H2 = ").append(h2).append('\n'));
|
||||
underloadPacketMagicHeader.ifPresent(h3 -> sb.append("H3 = ").append(h3).append('\n'));
|
||||
transportPacketMagicHeader.ifPresent(h4 -> sb.append("H4 = ").append(h4).append('\n'));
|
||||
sb.append("PrivateKey = ").append(keyPair.getPrivateKey().toBase64()).append('\n');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the {@code Interface} for use with the AmneziaWG cross-platform userspace API.
|
||||
* Note that not all attributes are included in this representation.
|
||||
*
|
||||
* @return the {@code Interface} represented as a series of "KEY=VALUE" lines
|
||||
*/
|
||||
public String toAwgUserspaceString() {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append("private_key=").append(keyPair.getPrivateKey().toHex()).append('\n');
|
||||
listenPort.ifPresent(lp -> sb.append("listen_port=").append(lp).append('\n'));
|
||||
junkPacketCount.ifPresent(jc -> sb.append("jc=").append(jc).append('\n'));
|
||||
junkPacketMinSize.ifPresent(jmin -> sb.append("jmin=").append(jmin).append('\n'));
|
||||
junkPacketMaxSize.ifPresent(jmax -> sb.append("jmax=").append(jmax).append('\n'));
|
||||
initPacketJunkSize.ifPresent(s1 -> sb.append("s1=").append(s1).append('\n'));
|
||||
responsePacketJunkSize.ifPresent(s2 -> sb.append("s2=").append(s2).append('\n'));
|
||||
initPacketMagicHeader.ifPresent(h1 -> sb.append("h1=").append(h1).append('\n'));
|
||||
responsePacketMagicHeader.ifPresent(h2 -> sb.append("h2=").append(h2).append('\n'));
|
||||
underloadPacketMagicHeader.ifPresent(h3 -> sb.append("h3=").append(h3).append('\n'));
|
||||
transportPacketMagicHeader.ifPresent(h4 -> sb.append("h4=").append(h4).append('\n'));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnusedReturnValue")
|
||||
public static final class Builder {
|
||||
// Defaults to an empty set.
|
||||
private final Set<InetNetwork> addresses = new LinkedHashSet<>();
|
||||
// Defaults to an empty set.
|
||||
private final Set<InetAddress> dnsServers = new LinkedHashSet<>();
|
||||
// Defaults to an empty set.
|
||||
private final Set<String> dnsSearchDomains = new LinkedHashSet<>();
|
||||
// Defaults to an empty set.
|
||||
private final Set<String> excludedApplications = new LinkedHashSet<>();
|
||||
// Defaults to an empty set.
|
||||
private final Set<String> includedApplications = new LinkedHashSet<>();
|
||||
// No default; must be provided before building.
|
||||
@Nullable private KeyPair keyPair;
|
||||
// Defaults to not present.
|
||||
private Optional<Integer> listenPort = Optional.empty();
|
||||
// Defaults to not present.
|
||||
private Optional<Integer> mtu = Optional.empty();
|
||||
// Defaults to not present.
|
||||
private Optional<Integer> junkPacketCount = Optional.empty();
|
||||
// Defaults to not present.
|
||||
private Optional<Integer> junkPacketMinSize = Optional.empty();
|
||||
// Defaults to not present.
|
||||
private Optional<Integer> junkPacketMaxSize = Optional.empty();
|
||||
// Defaults to not present.
|
||||
private Optional<Integer> initPacketJunkSize = Optional.empty();
|
||||
// Defaults to not present.
|
||||
private Optional<Integer> responsePacketJunkSize = Optional.empty();
|
||||
// Defaults to not present.
|
||||
private Optional<Long> initPacketMagicHeader = Optional.empty();
|
||||
// Defaults to not present.
|
||||
private Optional<Long> responsePacketMagicHeader = Optional.empty();
|
||||
// Defaults to not present.
|
||||
private Optional<Long> underloadPacketMagicHeader = Optional.empty();
|
||||
// Defaults to not present.
|
||||
private Optional<Long> transportPacketMagicHeader = Optional.empty();
|
||||
|
||||
|
||||
public Builder addAddress(final InetNetwork address) {
|
||||
addresses.add(address);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addAddresses(final Collection<InetNetwork> addresses) {
|
||||
this.addresses.addAll(addresses);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addDnsServer(final InetAddress dnsServer) {
|
||||
dnsServers.add(dnsServer);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addDnsServers(final Collection<? extends InetAddress> dnsServers) {
|
||||
this.dnsServers.addAll(dnsServers);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addDnsSearchDomain(final String dnsSearchDomain) {
|
||||
dnsSearchDomains.add(dnsSearchDomain);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addDnsSearchDomains(final Collection<String> dnsSearchDomains) {
|
||||
this.dnsSearchDomains.addAll(dnsSearchDomains);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Interface build() throws BadConfigException {
|
||||
if (keyPair == null)
|
||||
throw new BadConfigException(Section.INTERFACE, Location.PRIVATE_KEY,
|
||||
Reason.MISSING_ATTRIBUTE, null);
|
||||
if (!includedApplications.isEmpty() && !excludedApplications.isEmpty())
|
||||
throw new BadConfigException(Section.INTERFACE, Location.INCLUDED_APPLICATIONS,
|
||||
Reason.INVALID_KEY, null);
|
||||
return new Interface(this);
|
||||
}
|
||||
|
||||
public Builder excludeApplication(final String application) {
|
||||
excludedApplications.add(application);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder excludeApplications(final Collection<String> applications) {
|
||||
excludedApplications.addAll(applications);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder includeApplication(final String application) {
|
||||
includedApplications.add(application);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder includeApplications(final Collection<String> applications) {
|
||||
includedApplications.addAll(applications);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder parseAddresses(final CharSequence addresses) throws BadConfigException {
|
||||
try {
|
||||
for (final String address : Attribute.split(addresses))
|
||||
addAddress(InetNetwork.parse(address));
|
||||
return this;
|
||||
} catch (final ParseException e) {
|
||||
throw new BadConfigException(Section.INTERFACE, Location.ADDRESS, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder parseDnsServers(final CharSequence dnsServers) throws BadConfigException {
|
||||
try {
|
||||
for (final String dnsServer : Attribute.split(dnsServers)) {
|
||||
try {
|
||||
addDnsServer(InetAddresses.parse(dnsServer));
|
||||
} catch (final ParseException e) {
|
||||
if (e.getParsingClass() != InetAddress.class || !InetAddresses.isHostname(dnsServer))
|
||||
throw e;
|
||||
addDnsSearchDomain(dnsServer);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
} catch (final ParseException e) {
|
||||
throw new BadConfigException(Section.INTERFACE, Location.DNS, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder parseExcludedApplications(final CharSequence apps) {
|
||||
return excludeApplications(List.of(Attribute.split(apps)));
|
||||
}
|
||||
|
||||
public Builder parseIncludedApplications(final CharSequence apps) {
|
||||
return includeApplications(List.of(Attribute.split(apps)));
|
||||
}
|
||||
|
||||
public Builder parseListenPort(final String listenPort) throws BadConfigException {
|
||||
try {
|
||||
return setListenPort(Integer.parseInt(listenPort));
|
||||
} catch (final NumberFormatException e) {
|
||||
throw new BadConfigException(Section.INTERFACE, Location.LISTEN_PORT, listenPort, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder parseMtu(final String mtu) throws BadConfigException {
|
||||
try {
|
||||
return setMtu(Integer.parseInt(mtu));
|
||||
} catch (final NumberFormatException e) {
|
||||
throw new BadConfigException(Section.INTERFACE, Location.MTU, mtu, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder parseJunkPacketCount(final String junkPacketCount) throws BadConfigException {
|
||||
try {
|
||||
return setJunkPacketCount(Integer.parseInt(junkPacketCount));
|
||||
} catch (final NumberFormatException e) {
|
||||
throw new BadConfigException(Section.INTERFACE, Location.JUNK_PACKET_COUNT, junkPacketCount, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder parseJunkPacketMinSize(final String junkPacketMinSize) throws BadConfigException {
|
||||
try {
|
||||
return setJunkPacketMinSize(Integer.parseInt(junkPacketMinSize));
|
||||
} catch (final NumberFormatException e) {
|
||||
throw new BadConfigException(Section.INTERFACE, Location.JUNK_PACKET_MIN_SIZE, junkPacketMinSize, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder parseJunkPacketMaxSize(final String junkPacketMaxSize) throws BadConfigException {
|
||||
try {
|
||||
return setJunkPacketMaxSize(Integer.parseInt(junkPacketMaxSize));
|
||||
} catch (final NumberFormatException e) {
|
||||
throw new BadConfigException(Section.INTERFACE, Location.JUNK_PACKET_MAX_SIZE, junkPacketMaxSize, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder parseInitPacketJunkSize(final String initPacketJunkSize) throws BadConfigException {
|
||||
try {
|
||||
return setInitPacketJunkSize(Integer.parseInt(initPacketJunkSize));
|
||||
} catch (final NumberFormatException e) {
|
||||
throw new BadConfigException(Section.INTERFACE, Location.INIT_PACKET_JUNK_SIZE, initPacketJunkSize, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder parseResponsePacketJunkSize(final String responsePacketJunkSize) throws BadConfigException {
|
||||
try {
|
||||
return setResponsePacketJunkSize(Integer.parseInt(responsePacketJunkSize));
|
||||
} catch (final NumberFormatException e) {
|
||||
throw new BadConfigException(Section.INTERFACE, Location.RESPONSE_PACKET_JUNK_SIZE, responsePacketJunkSize, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder parseInitPacketMagicHeader(final String initPacketMagicHeader) throws BadConfigException {
|
||||
try {
|
||||
return setInitPacketMagicHeader(Long.parseLong(initPacketMagicHeader));
|
||||
} catch (final NumberFormatException e) {
|
||||
throw new BadConfigException(Section.INTERFACE, Location.INIT_PACKET_MAGIC_HEADER, initPacketMagicHeader, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder parseResponsePacketMagicHeader(final String responsePacketMagicHeader) throws BadConfigException {
|
||||
try {
|
||||
|
||||
return setResponsePacketMagicHeader(Long.parseLong(responsePacketMagicHeader));
|
||||
} catch (final NumberFormatException e) {
|
||||
throw new BadConfigException(Section.INTERFACE, Location.RESPONSE_PACKET_MAGIC_HEADER, responsePacketMagicHeader, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder parseUnderloadPacketMagicHeader(final String underloadPacketMagicHeader) throws BadConfigException {
|
||||
try {
|
||||
return setUnderloadPacketMagicHeader(Long.parseLong(underloadPacketMagicHeader));
|
||||
} catch (final NumberFormatException e) {
|
||||
throw new BadConfigException(Section.INTERFACE, Location.UNDERLOAD_PACKET_MAGIC_HEADER, underloadPacketMagicHeader, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder parseTransportPacketMagicHeader(final String transportPacketMagicHeader) throws BadConfigException {
|
||||
try {
|
||||
return setTransportPacketMagicHeader(Long.parseLong(transportPacketMagicHeader));
|
||||
} catch (final NumberFormatException e) {
|
||||
throw new BadConfigException(Section.INTERFACE, Location.TRANSPORT_PACKET_MAGIC_HEADER, transportPacketMagicHeader, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder parsePrivateKey(final String privateKey) throws BadConfigException {
|
||||
try {
|
||||
return setKeyPair(new KeyPair(Key.fromBase64(privateKey)));
|
||||
} catch (final KeyFormatException e) {
|
||||
throw new BadConfigException(Section.INTERFACE, Location.PRIVATE_KEY, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder setKeyPair(final KeyPair keyPair) {
|
||||
this.keyPair = keyPair;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setListenPort(final int listenPort) throws BadConfigException {
|
||||
if (listenPort < MIN_UDP_PORT || listenPort > MAX_UDP_PORT)
|
||||
throw new BadConfigException(Section.INTERFACE, Location.LISTEN_PORT,
|
||||
Reason.INVALID_VALUE, String.valueOf(listenPort));
|
||||
this.listenPort = listenPort == 0 ? Optional.empty() : Optional.of(listenPort);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setMtu(final int mtu) throws BadConfigException {
|
||||
if (mtu < 0)
|
||||
throw new BadConfigException(Section.INTERFACE, Location.MTU,
|
||||
Reason.INVALID_VALUE, String.valueOf(mtu));
|
||||
this.mtu = mtu == 0 ? Optional.empty() : Optional.of(mtu);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setJunkPacketCount(final int junkPacketCount) throws BadConfigException {
|
||||
if (junkPacketCount < 0)
|
||||
throw new BadConfigException(Section.INTERFACE, Location.JUNK_PACKET_COUNT,
|
||||
Reason.INVALID_VALUE, String.valueOf(junkPacketCount));
|
||||
this.junkPacketCount = junkPacketCount == 0 ? Optional.empty() : Optional.of(junkPacketCount);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setJunkPacketMinSize(final int junkPacketMinSize) throws BadConfigException {
|
||||
if (junkPacketMinSize < 0)
|
||||
throw new BadConfigException(Section.INTERFACE, Location.JUNK_PACKET_MIN_SIZE,
|
||||
Reason.INVALID_VALUE, String.valueOf(junkPacketMinSize));
|
||||
this.junkPacketMinSize = junkPacketMinSize == 0 ? Optional.empty() : Optional.of(junkPacketMinSize);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setJunkPacketMaxSize(final int junkPacketMaxSize) throws BadConfigException {
|
||||
if (junkPacketMaxSize < 0)
|
||||
throw new BadConfigException(Section.INTERFACE, Location.JUNK_PACKET_MAX_SIZE,
|
||||
Reason.INVALID_VALUE, String.valueOf(junkPacketMaxSize));
|
||||
this.junkPacketMaxSize = junkPacketMaxSize == 0 ? Optional.empty() : Optional.of(junkPacketMaxSize);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setInitPacketJunkSize(final int initPacketJunkSize) throws BadConfigException {
|
||||
if (initPacketJunkSize < 0)
|
||||
throw new BadConfigException(Section.INTERFACE, Location.INIT_PACKET_JUNK_SIZE,
|
||||
Reason.INVALID_VALUE, String.valueOf(initPacketJunkSize));
|
||||
this.initPacketJunkSize = initPacketJunkSize == 0 ? Optional.empty() : Optional.of(initPacketJunkSize);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setResponsePacketJunkSize(final int responsePacketJunkSize) throws BadConfigException {
|
||||
if (responsePacketJunkSize < 0)
|
||||
throw new BadConfigException(Section.INTERFACE, Location.RESPONSE_PACKET_JUNK_SIZE,
|
||||
Reason.INVALID_VALUE, String.valueOf(responsePacketJunkSize));
|
||||
this.responsePacketJunkSize = responsePacketJunkSize == 0 ? Optional.empty() : Optional.of(responsePacketJunkSize);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setInitPacketMagicHeader(final long initPacketMagicHeader) throws BadConfigException {
|
||||
if (initPacketMagicHeader < 0)
|
||||
throw new BadConfigException(Section.INTERFACE, Location.INIT_PACKET_MAGIC_HEADER,
|
||||
Reason.INVALID_VALUE, String.valueOf(initPacketMagicHeader));
|
||||
this.initPacketMagicHeader = initPacketMagicHeader == 0 ? Optional.empty() : Optional.of(initPacketMagicHeader);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setResponsePacketMagicHeader(final long responsePacketMagicHeader) throws BadConfigException {
|
||||
if (responsePacketMagicHeader < 0)
|
||||
throw new BadConfigException(Section.INTERFACE, Location.RESPONSE_PACKET_MAGIC_HEADER,
|
||||
Reason.INVALID_VALUE, String.valueOf(responsePacketMagicHeader));
|
||||
this.responsePacketMagicHeader = responsePacketMagicHeader == 0 ? Optional.empty() : Optional.of(responsePacketMagicHeader);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setUnderloadPacketMagicHeader(final long underloadPacketMagicHeader) throws BadConfigException {
|
||||
if (underloadPacketMagicHeader < 0)
|
||||
throw new BadConfigException(Section.INTERFACE, Location.UNDERLOAD_PACKET_MAGIC_HEADER,
|
||||
Reason.INVALID_VALUE, String.valueOf(underloadPacketMagicHeader));
|
||||
this.underloadPacketMagicHeader = underloadPacketMagicHeader == 0 ? Optional.empty() : Optional.of(underloadPacketMagicHeader);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setTransportPacketMagicHeader(final long transportPacketMagicHeader) throws BadConfigException {
|
||||
if (transportPacketMagicHeader < 0)
|
||||
throw new BadConfigException(Section.INTERFACE, Location.TRANSPORT_PACKET_MAGIC_HEADER,
|
||||
Reason.INVALID_VALUE, String.valueOf(transportPacketMagicHeader));
|
||||
this.transportPacketMagicHeader = transportPacketMagicHeader == 0 ? Optional.empty() : Optional.of(transportPacketMagicHeader);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.config;
|
||||
package org.amnezia.awg.config;
|
||||
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
import org.amnezia.awg.util.NonNullForAll;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
+15
-15
@@ -1,29 +1,29 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.config;
|
||||
package org.amnezia.awg.config;
|
||||
|
||||
import com.wireguard.config.BadConfigException.Location;
|
||||
import com.wireguard.config.BadConfigException.Reason;
|
||||
import com.wireguard.config.BadConfigException.Section;
|
||||
import com.wireguard.crypto.Key;
|
||||
import com.wireguard.crypto.KeyFormatException;
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
import org.amnezia.awg.config.BadConfigException.Location;
|
||||
import org.amnezia.awg.config.BadConfigException.Reason;
|
||||
import org.amnezia.awg.config.BadConfigException.Section;
|
||||
import org.amnezia.awg.crypto.Key;
|
||||
import org.amnezia.awg.crypto.KeyFormatException;
|
||||
import org.amnezia.awg.util.NonNullForAll;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import java9.util.Optional;
|
||||
|
||||
/**
|
||||
* Represents the configuration for a WireGuard peer (a [Peer] block). Peers must have a public key,
|
||||
* Represents the configuration for an AmneziaWG peer (a [Peer] block). Peers must have a public key,
|
||||
* and may optionally have several other attributes.
|
||||
* <p>
|
||||
* Instances of this class are immutable.
|
||||
@@ -168,12 +168,12 @@ public final class Peer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the {@code Peer} into a string suitable for inclusion in a {@code wg-quick}
|
||||
* Converts the {@code Peer} into a string suitable for inclusion in a {@code awg-quick}
|
||||
* configuration file.
|
||||
*
|
||||
* @return the {@code Peer} represented as a series of "Key = Value" lines
|
||||
*/
|
||||
public String toWgQuickString() {
|
||||
public String toAwgQuickString() {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
if (!allowedIps.isEmpty())
|
||||
sb.append("AllowedIPs = ").append(Attribute.join(allowedIps)).append('\n');
|
||||
@@ -185,12 +185,12 @@ public final class Peer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the {@code Peer} for use with the WireGuard cross-platform userspace API. Note
|
||||
* Serializes the {@code Peer} for use with the AmneziaWG cross-platform userspace API. Note
|
||||
* that not all attributes are included in this representation.
|
||||
*
|
||||
* @return the {@code Peer} represented as a series of "key=value" lines
|
||||
*/
|
||||
public String toWgUserspaceString() {
|
||||
public String toAwgUserspaceString() {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
// The order here is important: public_key signifies the beginning of a new peer.
|
||||
sb.append("public_key=").append(publicKey.toHex()).append('\n');
|
||||
@@ -204,7 +204,7 @@ public final class Peer {
|
||||
|
||||
@SuppressWarnings("UnusedReturnValue")
|
||||
public static final class Builder {
|
||||
// See wg(8)
|
||||
// See awg(8)
|
||||
private static final int MAX_PERSISTENT_KEEPALIVE = 65535;
|
||||
|
||||
// Defaults to an empty set.
|
||||
+6
-6
@@ -1,21 +1,21 @@
|
||||
/*
|
||||
* Copyright © 2016 Southern Storm Software, Pty Ltd.
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.crypto;
|
||||
package org.amnezia.awg.crypto;
|
||||
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
import org.amnezia.awg.util.NonNullForAll;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Implementation of the Curve25519 elliptic curve algorithm.
|
||||
* Implementation of Curve25519 ECDH.
|
||||
* <p>
|
||||
* This implementation was imported to WireGuard from noise-java:
|
||||
* This implementation was imported to AmneziaWG from noise-java:
|
||||
* https://github.com/rweather/noise-java
|
||||
* <p>
|
||||
* This implementation is based on that from arduinolibs:
|
||||
@@ -28,7 +28,7 @@ import androidx.annotation.Nullable;
|
||||
*/
|
||||
@SuppressWarnings({"MagicNumber", "NonConstantFieldWithUpperCaseName", "SuspiciousNameCombination"})
|
||||
@NonNullForAll
|
||||
final class Curve25519 {
|
||||
public final class Curve25519 {
|
||||
// Numbers modulo 2^255 - 19 are broken up into ten 26-bit words.
|
||||
private static final int NUM_LIMBS_255BIT = 10;
|
||||
private static final int NUM_LIMBS_510BIT = 20;
|
||||
+12
-12
@@ -1,19 +1,19 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.crypto;
|
||||
package org.amnezia.awg.crypto;
|
||||
|
||||
import com.wireguard.crypto.KeyFormatException.Type;
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
import org.amnezia.awg.crypto.KeyFormatException.Type;
|
||||
import org.amnezia.awg.util.NonNullForAll;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Represents a WireGuard public or private key. This class uses specialized constant-time base64
|
||||
* Represents an AmneziaWG public or private key. This class uses specialized constant-time base64
|
||||
* and hexadecimal codec implementations that resist side-channel attacks.
|
||||
* <p>
|
||||
* Instances of this class are immutable.
|
||||
@@ -83,10 +83,10 @@ public final class Key {
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a WireGuard public or private key from its base64 string representation. This
|
||||
* Decodes an AmneziaWG public or private key from its base64 string representation. This
|
||||
* function throws a {@link KeyFormatException} if the source string is not well-formed.
|
||||
*
|
||||
* @param str the base64 string representation of a WireGuard key
|
||||
* @param str the base64 string representation of an AmneziaWG key
|
||||
* @return the decoded key encapsulated in an immutable container
|
||||
*/
|
||||
public static Key fromBase64(final String str) throws KeyFormatException {
|
||||
@@ -120,10 +120,10 @@ public final class Key {
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a WireGuard public or private key in an immutable container. This function throws a
|
||||
* Wraps an AmneziaWG public or private key in an immutable container. This function throws a
|
||||
* {@link KeyFormatException} if the source data is not the correct length.
|
||||
*
|
||||
* @param bytes an array of bytes containing a WireGuard key in binary format
|
||||
* @param bytes an array of bytes containing an AmneziaWG key in binary format
|
||||
* @return the key encapsulated in an immutable container
|
||||
*/
|
||||
public static Key fromBytes(final byte[] bytes) throws KeyFormatException {
|
||||
@@ -133,10 +133,10 @@ public final class Key {
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a WireGuard public or private key from its hexadecimal string representation. This
|
||||
* Decodes an AmneziaWG public or private key from its hexadecimal string representation. This
|
||||
* function throws a {@link KeyFormatException} if the source string is not well-formed.
|
||||
*
|
||||
* @param str the hexadecimal string representation of a WireGuard key
|
||||
* @param str the hexadecimal string representation of an AmneziaWG key
|
||||
* @return the decoded key encapsulated in an immutable container
|
||||
*/
|
||||
public static Key fromHex(final String str) throws KeyFormatException {
|
||||
@@ -269,7 +269,7 @@ public final class Key {
|
||||
}
|
||||
|
||||
/**
|
||||
* The supported formats for encoding a WireGuard key.
|
||||
* The supported formats for encoding an AmneziaWG key.
|
||||
*/
|
||||
public enum Format {
|
||||
BASE64(44),
|
||||
+3
-3
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.crypto;
|
||||
package org.amnezia.awg.crypto;
|
||||
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
import org.amnezia.awg.util.NonNullForAll;
|
||||
|
||||
/**
|
||||
* An exception thrown when attempting to parse an invalid key (too short, too long, or byte
|
||||
+4
-4
@@ -1,14 +1,14 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.crypto;
|
||||
package org.amnezia.awg.crypto;
|
||||
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
import org.amnezia.awg.util.NonNullForAll;
|
||||
|
||||
/**
|
||||
* Represents a Curve25519 key pair as used by WireGuard.
|
||||
* Represents a Curve25519 key pair as used by AmneziaWG.
|
||||
* <p>
|
||||
* Instances of this class are immutable.
|
||||
*/
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.util;
|
||||
package org.amnezia.awg.util;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
+10
-7
@@ -1,15 +1,15 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.util;
|
||||
package org.amnezia.awg.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import com.wireguard.android.util.RootShell.RootShellException.Reason;
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
import org.amnezia.awg.util.RootShell.RootShellException.Reason;
|
||||
import org.amnezia.awg.util.NonNullForAll;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
@@ -29,7 +29,7 @@ import androidx.annotation.Nullable;
|
||||
@NonNullForAll
|
||||
public class RootShell {
|
||||
private static final String SU = "su";
|
||||
private static final String TAG = "WireGuard/RootShell";
|
||||
private static final String TAG = "AmneziaWG/RootShell";
|
||||
|
||||
private final File localBinaryDir;
|
||||
private final File localTemporaryDir;
|
||||
@@ -43,8 +43,11 @@ public class RootShell {
|
||||
public RootShell(final Context context) {
|
||||
localBinaryDir = new File(context.getCodeCacheDir(), "bin");
|
||||
localTemporaryDir = new File(context.getCacheDir(), "tmp");
|
||||
preamble = String.format("export CALLING_PACKAGE=%s PATH=\"%s:$PATH\" TMPDIR='%s'; id -u\n",
|
||||
context.getPackageName(), localBinaryDir, localTemporaryDir);
|
||||
final String packageName = context.getPackageName();
|
||||
if (packageName.contains("'"))
|
||||
throw new RuntimeException("Impossibly invalid package name contains a single quote");
|
||||
preamble = String.format("export CALLING_PACKAGE='%s' PATH=\"%s:$PATH\" TMPDIR='%s'; magisk --sqlite \"UPDATE policies SET notification=0, logging=0 WHERE uid=%d\" >/dev/null 2>&1; id -u\n",
|
||||
packageName, localBinaryDir, localTemporaryDir, android.os.Process.myUid());
|
||||
}
|
||||
|
||||
private static boolean isExecutableInPath(final String name) {
|
||||
+4
-4
@@ -1,15 +1,15 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.util;
|
||||
package org.amnezia.awg.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
import org.amnezia.awg.util.NonNullForAll;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
@@ -27,7 +27,7 @@ import androidx.annotation.RestrictTo.Scope;
|
||||
@NonNullForAll
|
||||
@RestrictTo(Scope.LIBRARY_GROUP)
|
||||
public final class SharedLibraryLoader {
|
||||
private static final String TAG = "WireGuard/SharedLibraryLoader";
|
||||
private static final String TAG = "AmneziaWG/SharedLibraryLoader";
|
||||
|
||||
private SharedLibraryLoader() {
|
||||
}
|
||||
+16
-16
@@ -1,16 +1,16 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.util;
|
||||
package org.amnezia.awg.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.system.OsConstants;
|
||||
import android.util.Log;
|
||||
|
||||
import com.wireguard.android.util.RootShell.RootShellException;
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
import org.amnezia.awg.util.RootShell.RootShellException;
|
||||
import org.amnezia.awg.util.NonNullForAll;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
@@ -23,7 +23,7 @@ import androidx.annotation.RestrictTo;
|
||||
import androidx.annotation.RestrictTo.Scope;
|
||||
|
||||
/**
|
||||
* Helper to install WireGuard tools to the system partition.
|
||||
* Helper to install AmneziaWG tools to the system partition.
|
||||
*/
|
||||
|
||||
@NonNullForAll
|
||||
@@ -33,13 +33,13 @@ public final class ToolsInstaller {
|
||||
public static final int NO = 0x2;
|
||||
public static final int SYSTEM = 0x8;
|
||||
public static final int YES = 0x1;
|
||||
private static final String[] EXECUTABLES = {"wg", "wg-quick"};
|
||||
private static final String[] EXECUTABLES = {"awg", "awg-quick"};
|
||||
private static final File[] INSTALL_DIRS = {
|
||||
new File("/system/xbin"),
|
||||
new File("/system/bin"),
|
||||
};
|
||||
@Nullable private static final File INSTALL_DIR = getInstallDir();
|
||||
private static final String TAG = "WireGuard/ToolsInstaller";
|
||||
private static final String TAG = "AmneziaWG/ToolsInstaller";
|
||||
|
||||
private final Context context;
|
||||
private final File localBinaryDir;
|
||||
@@ -100,7 +100,7 @@ public final class ToolsInstaller {
|
||||
"Tools were already extracted into our private binary dir");
|
||||
areToolsAvailable = true;
|
||||
} catch (final IOException e) {
|
||||
Log.e(TAG, "The wg and wg-quick tools are not available", e);
|
||||
Log.e(TAG, "The awg and awg-quick tools are not available", e);
|
||||
areToolsAvailable = false;
|
||||
}
|
||||
}
|
||||
@@ -134,8 +134,8 @@ public final class ToolsInstaller {
|
||||
|
||||
@RestrictTo(Scope.LIBRARY_GROUP)
|
||||
public int install() throws RootShellException, IOException {
|
||||
if (!context.getPackageName().startsWith("com.wireguard."))
|
||||
throw new SecurityException("The tools may only be installed system-wide from the main WireGuard app.");
|
||||
if (!context.getPackageName().startsWith("org.amnezia."))
|
||||
throw new SecurityException("The tools may only be installed system-wide from the main AmneziaWG app.");
|
||||
return willInstallAsMagiskModule() ? installMagisk() : installSystem();
|
||||
}
|
||||
|
||||
@@ -143,12 +143,12 @@ public final class ToolsInstaller {
|
||||
extract();
|
||||
final StringBuilder script = new StringBuilder("set -ex; ");
|
||||
|
||||
script.append("trap 'rm -rf /sbin/.magisk/img/wireguard' INT TERM EXIT; ");
|
||||
script.append(String.format("rm -rf /sbin/.magisk/img/wireguard/; mkdir -p /sbin/.magisk/img/wireguard%s; ", INSTALL_DIR));
|
||||
script.append("printf 'name=WireGuard Command Line Tools\nversion=1.0\nversionCode=1\nauthor=zx2c4\ndescription=Command line tools for WireGuard\nminMagisk=1500\n' > /sbin/.magisk/img/wireguard/module.prop; ");
|
||||
script.append("touch /sbin/.magisk/img/wireguard/auto_mount; ");
|
||||
script.append("trap 'rm -rf /data/adb/modules/amneziawg' INT TERM EXIT; ");
|
||||
script.append(String.format("rm -rf /data/adb/modules/amneziawg/; mkdir -p /data/adb/modules/amneziawg%s; ", INSTALL_DIR));
|
||||
script.append("printf 'id=amneziawg\nname=AmneziaWG Command Line Tools\nversion=1.0\nversionCode=1\nauthor=amnezia\ndescription=Command line tools for AmneziaWG\nminMagisk=1500\n' > /data/adb/modules/amneziawg/module.prop; ");
|
||||
script.append("touch /data/adb/modules/amneziawg/auto_mount; ");
|
||||
for (final String name : EXECUTABLES) {
|
||||
final File destination = new File("/sbin/.magisk/img/wireguard" + INSTALL_DIR, name);
|
||||
final File destination = new File("/data/adb/modules/amneziawg" + INSTALL_DIR, name);
|
||||
script.append(String.format("cp '%s' '%s'; chmod 755 '%s'; chcon 'u:object_r:system_file:s0' '%s' || true; ",
|
||||
new File(localBinaryDir, name), destination, destination, destination));
|
||||
}
|
||||
@@ -191,7 +191,7 @@ public final class ToolsInstaller {
|
||||
synchronized (lock) {
|
||||
if (installAsMagiskModule == null) {
|
||||
try {
|
||||
installAsMagiskModule = rootShell.run(null, "[ -d /sbin/.magisk/mirror -a -d /sbin/.magisk/img -a ! -f /cache/.disable_magisk ]") == OsConstants.EXIT_SUCCESS;
|
||||
installAsMagiskModule = rootShell.run(null, "[ -d /data/adb/modules -a ! -f /cache/.disable_magisk ]") == OsConstants.EXIT_SUCCESS;
|
||||
} catch (final Exception ignored) {
|
||||
installAsMagiskModule = false;
|
||||
}
|
||||
+5
-5
@@ -1,13 +1,13 @@
|
||||
/*
|
||||
* Copyright © 2020 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.config;
|
||||
package org.amnezia.awg.config;
|
||||
|
||||
import com.wireguard.config.BadConfigException.Location;
|
||||
import com.wireguard.config.BadConfigException.Reason;
|
||||
import com.wireguard.config.BadConfigException.Section;
|
||||
import org.amnezia.awg.config.BadConfigException.Location;
|
||||
import org.amnezia.awg.config.BadConfigException.Reason;
|
||||
import org.amnezia.awg.config.BadConfigException.Section;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
/*
|
||||
* Copyright © 2020 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.config;
|
||||
package org.amnezia.awg.config;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[Interface]
|
||||
Address = 192.0.2.2/32,2001:db8:ffff:ffff:ffff:ffff:ffff:ffff/128
|
||||
DNS = 192.0.2.0,yes
|
||||
DNS = 192.0.2.0,invalid_value
|
||||
PrivateKey = TFlmmEUC7V7VtiDYLKsbP5rySTKLIZq1yn8lMqK83wo=
|
||||
[Peer]
|
||||
AllowedIPs = 0.0.0.0/0, ::0/0
|
||||
|
||||
+28
-18
@@ -1,34 +1,44 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||
# Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
cmake_minimum_required(VERSION 3.4.1)
|
||||
project("AmneziaWG")
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}")
|
||||
add_link_options(LINKER:--build-id=none)
|
||||
add_compile_options(-Wall -Werror)
|
||||
|
||||
# Work around https://github.com/android-ndk/ndk/issues/602
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=gold")
|
||||
|
||||
add_executable(libwg-quick.so wireguard-tools/src/wg-quick/android.c ndk-compat/compat.c)
|
||||
target_compile_options(libwg-quick.so PUBLIC -O3 -std=gnu11 -Wall -include ${CMAKE_CURRENT_SOURCE_DIR}/ndk-compat/compat.h -DWG_PACKAGE_NAME=\"${ANDROID_PACKAGE_NAME}\")
|
||||
add_executable(libwg-quick.so amneziawg-tools/src/wg-quick/android.c ndk-compat/compat.c)
|
||||
target_compile_options(libwg-quick.so PUBLIC -std=gnu11 -include ${CMAKE_CURRENT_SOURCE_DIR}/ndk-compat/compat.h -DAWG_PACKAGE_NAME=\"${ANDROID_PACKAGE_NAME}\")
|
||||
target_link_libraries(libwg-quick.so -ldl)
|
||||
|
||||
file(GLOB WG_SOURCES wireguard-tools/src/*.c ndk-compat/compat.c)
|
||||
file(GLOB WG_SOURCES amneziawg-tools/src/*.c ndk-compat/compat.c)
|
||||
add_executable(libwg.so ${WG_SOURCES})
|
||||
target_include_directories(libwg.so PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/wireguard-tools/src/uapi/" "${CMAKE_CURRENT_SOURCE_DIR}/wireguard-tools/src/")
|
||||
target_compile_options(libwg.so PUBLIC -O3 -std=gnu11 -D_GNU_SOURCE -include ${CMAKE_CURRENT_SOURCE_DIR}/ndk-compat/compat.h -DHAVE_VISIBILITY_HIDDEN -DRUNSTATEDIR=\"/data/data/${ANDROID_PACKAGE_NAME}/cache\")
|
||||
target_include_directories(libwg.so PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/amneziawg-tools/src/uapi/linux/" "${CMAKE_CURRENT_SOURCE_DIR}/amneziawg-tools/src/")
|
||||
target_compile_options(libwg.so PUBLIC -std=gnu11 -include ${CMAKE_CURRENT_SOURCE_DIR}/ndk-compat/compat.h -DRUNSTATEDIR=\"/data/data/${ANDROID_PACKAGE_NAME}/cache\")
|
||||
|
||||
add_custom_target(libwg-go.so WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/libwg-go" COMMENT "Building wireguard-go" VERBATIM COMMAND make
|
||||
add_custom_target(libwg-go.so WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/libwg-go" COMMENT "Building amneziawg-go" VERBATIM COMMAND "${ANDROID_HOST_PREBUILTS}/bin/make"
|
||||
ANDROID_ARCH_NAME=${ANDROID_ARCH_NAME}
|
||||
ANDROID_C_COMPILER=${ANDROID_C_COMPILER}
|
||||
ANDROID_TOOLCHAIN_ROOT=${ANDROID_TOOLCHAIN_ROOT}
|
||||
ANDROID_LLVM_TRIPLE=${ANDROID_LLVM_TRIPLE}
|
||||
ANDROID_SYSROOT=${ANDROID_SYSROOT}
|
||||
ANDROID_PACKAGE_NAME=${ANDROID_PACKAGE_NAME}
|
||||
GRADLE_USER_HOME=${GRADLE_USER_HOME}
|
||||
CFLAGS=${CMAKE_C_FLAGS}\ -Wno-unused-command-line-argument
|
||||
LDFLAGS=${CMAKE_SHARED_LINKER_FLAGS}\ -fuse-ld=gold
|
||||
CC=${CMAKE_C_COMPILER}
|
||||
CFLAGS=${CMAKE_C_FLAGS}
|
||||
LDFLAGS=${CMAKE_SHARED_LINKER_FLAGS}
|
||||
SYSROOT=${CMAKE_SYSROOT}
|
||||
TARGET=${CMAKE_C_COMPILER_TARGET}
|
||||
DESTDIR=${CMAKE_LIBRARY_OUTPUT_DIRECTORY}
|
||||
BUILDDIR=${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/../generated-src
|
||||
)
|
||||
# Hack to make it actually build as part of the default target
|
||||
add_dependencies(libwg.so libwg-go.so)
|
||||
|
||||
# Strip unwanted ELF sections to prevent DT_FLAGS_1 warnings on old Android versions
|
||||
file(GLOB ELF_CLEANER_SOURCES elf-cleaner/*.c elf-cleaner/*.cpp)
|
||||
add_custom_target(elf-cleaner COMMENT "Building elf-cleaner" VERBATIM COMMAND cc
|
||||
-O2 -DPACKAGE_NAME="elf-cleaner" -DPACKAGE_VERSION="" -DCOPYRIGHT=""
|
||||
-o "${CMAKE_CURRENT_BINARY_DIR}/elf-cleaner" ${ELF_CLEANER_SOURCES}
|
||||
)
|
||||
add_custom_command(TARGET libwg.so POST_BUILD VERBATIM COMMAND "${CMAKE_CURRENT_BINARY_DIR}/elf-cleaner"
|
||||
--api-level "${ANDROID_NATIVE_API_LEVEL}" "$<TARGET_FILE:libwg.so>")
|
||||
add_dependencies(libwg.so elf-cleaner)
|
||||
add_custom_command(TARGET libwg-quick.so POST_BUILD VERBATIM COMMAND "${CMAKE_CURRENT_BINARY_DIR}/elf-cleaner"
|
||||
--api-level "${ANDROID_NATIVE_API_LEVEL}" "$<TARGET_FILE:libwg-quick.so>")
|
||||
add_dependencies(libwg-quick.so elf-cleaner)
|
||||
|
||||
Submodule
+1
Submodule tunnel/tools/amneziawg-tools added at 5c6ffd6168
Submodule
+1
Submodule tunnel/tools/elf-cleaner added at 7efc050906
@@ -1,6 +1,6 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
# Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
BUILDDIR ?= $(CURDIR)/build
|
||||
DESTDIR ?= $(CURDIR)/out
|
||||
@@ -12,25 +12,26 @@ NDK_GO_ARCH_MAP_arm64 := arm64
|
||||
NDK_GO_ARCH_MAP_mips := mipsx
|
||||
NDK_GO_ARCH_MAP_mips64 := mips64x
|
||||
|
||||
CLANG_FLAGS := --target=$(ANDROID_LLVM_TRIPLE) --gcc-toolchain=$(ANDROID_TOOLCHAIN_ROOT) --sysroot=$(ANDROID_SYSROOT)
|
||||
export CGO_CFLAGS := $(CLANG_FLAGS) $(CFLAGS)
|
||||
export CGO_LDFLAGS := $(CLANG_FLAGS) $(LDFLAGS)
|
||||
export CC := $(ANDROID_C_COMPILER)
|
||||
comma := ,
|
||||
CLANG_FLAGS := --target=$(TARGET) --sysroot=$(SYSROOT)
|
||||
export CGO_CFLAGS := $(CLANG_FLAGS) $(subst -mthumb,-marm,$(CFLAGS))
|
||||
export CGO_LDFLAGS := $(CLANG_FLAGS) $(patsubst -Wl$(comma)--build-id=%,-Wl$(comma)--build-id=none,$(LDFLAGS)) -Wl,-soname=libwg-go.so
|
||||
export GOARCH := $(NDK_GO_ARCH_MAP_$(ANDROID_ARCH_NAME))
|
||||
export GOOS := android
|
||||
export CGO_ENABLED := 1
|
||||
|
||||
GO_VERSION := 1.14.1
|
||||
GO_VERSION := 1.24.2
|
||||
GO_PLATFORM := $(shell uname -s | tr '[:upper:]' '[:lower:]')-$(NDK_GO_ARCH_MAP_$(shell uname -m))
|
||||
GO_TARBALL := go$(GO_VERSION).$(GO_PLATFORM).tar.gz
|
||||
GO_HASH_darwin-amd64 := 6632f9d53fd95632e431e8c34295349cca3f0a124e3a28b760ae5c42b32816e3
|
||||
GO_HASH_linux-amd64 := 2f49eb17ce8b48c680cdb166ffd7389702c0dec6effa090c324804a5cac8a7f8
|
||||
GO_HASH_darwin-amd64 := 238d9c065d09ff6af229d2e3b8b5e85e688318d69f4006fb85a96e41c216ea83
|
||||
GO_HASH_darwin-arm64 := b70f8b3c5b4ccb0ad4ffa5ee91cd38075df20fdbd953a1daedd47f50fbcff47a
|
||||
GO_HASH_linux-amd64 := 68097bd680839cbc9d464a0edce4f7c333975e27a90246890e9f1078c7e702ad
|
||||
|
||||
default: $(DESTDIR)/libwg-go.so
|
||||
|
||||
$(GRADLE_USER_HOME)/caches/golang/$(GO_TARBALL):
|
||||
mkdir -p "$(dir $@)"
|
||||
flock -x "$@.lock" -c ' \
|
||||
flock "$@.lock" -c ' \
|
||||
[ -f "$@" ] && exit 0; \
|
||||
curl -o "$@.tmp" "https://dl.google.com/go/$(GO_TARBALL)" && \
|
||||
echo "$(GO_HASH_$(GO_PLATFORM)) $@.tmp" | sha256sum -c && \
|
||||
@@ -38,7 +39,7 @@ $(GRADLE_USER_HOME)/caches/golang/$(GO_TARBALL):
|
||||
|
||||
$(BUILDDIR)/go-$(GO_VERSION)/.prepared: $(GRADLE_USER_HOME)/caches/golang/$(GO_TARBALL)
|
||||
mkdir -p "$(dir $@)"
|
||||
flock -x "$@.lock" -c ' \
|
||||
flock "$@.lock" -c ' \
|
||||
[ -f "$@" ] && exit 0; \
|
||||
tar -C "$(dir $@)" --strip-components=1 -xzf "$^" && \
|
||||
patch -p1 -f -N -r- -d "$(dir $@)" < goruntime-boottime-over-monotonic.diff && \
|
||||
@@ -46,6 +47,6 @@ $(BUILDDIR)/go-$(GO_VERSION)/.prepared: $(GRADLE_USER_HOME)/caches/golang/$(GO_T
|
||||
|
||||
$(DESTDIR)/libwg-go.so: export PATH := $(BUILDDIR)/go-$(GO_VERSION)/bin/:$(PATH)
|
||||
$(DESTDIR)/libwg-go.so: $(BUILDDIR)/go-$(GO_VERSION)/.prepared go.mod
|
||||
go build -tags linux -ldflags="-X golang.zx2c4.com/wireguard/ipc.socketDirectory=/data/data/$(ANDROID_PACKAGE_NAME)/cache/wireguard" -v -trimpath -o "$@" -buildmode c-shared
|
||||
go build -tags linux -ldflags="-X github.com/amnezia-vpn/amneziawg-go/ipc.socketDirectory=/data/data/$(ANDROID_PACKAGE_NAME)/cache/amneziawg -buildid=" -v -trimpath -buildvcs=false -o "$@" -buildmode c-shared
|
||||
|
||||
.DELETE_ON_ERROR:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Copyright (C) 2017-2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* Copyright © 2017-2022 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package main
|
||||
@@ -10,30 +10,39 @@ package main
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"golang.org/x/sys/unix"
|
||||
"golang.zx2c4.com/wireguard/device"
|
||||
"golang.zx2c4.com/wireguard/ipc"
|
||||
"golang.zx2c4.com/wireguard/tun"
|
||||
"bytes"
|
||||
"log"
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/amnezia-vpn/amneziawg-go/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/device"
|
||||
"github.com/amnezia-vpn/amneziawg-go/ipc"
|
||||
"github.com/amnezia-vpn/amneziawg-go/tun"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type AndroidLogger struct {
|
||||
level C.int
|
||||
interfaceName string
|
||||
level C.int
|
||||
tag *C.char
|
||||
}
|
||||
|
||||
func (l AndroidLogger) Write(p []byte) (int, error) {
|
||||
C.__android_log_write(l.level, C.CString("WireGuard/GoBackend/"+l.interfaceName), C.CString(string(p)))
|
||||
return len(p), nil
|
||||
func cstring(s string) *C.char {
|
||||
b, err := unix.BytePtrFromString(s)
|
||||
if err != nil {
|
||||
b := [1]C.char{}
|
||||
return &b[0]
|
||||
}
|
||||
return (*C.char)(unsafe.Pointer(b))
|
||||
}
|
||||
|
||||
func (l AndroidLogger) Printf(format string, args ...interface{}) {
|
||||
C.__android_log_write(l.level, l.tag, cstring(fmt.Sprintf(format, args...)))
|
||||
}
|
||||
|
||||
type TunnelHandle struct {
|
||||
@@ -44,7 +53,6 @@ type TunnelHandle struct {
|
||||
var tunnelHandles map[int32]TunnelHandle
|
||||
|
||||
func init() {
|
||||
device.RoamingDisabled = true
|
||||
tunnelHandles = make(map[int32]TunnelHandle)
|
||||
signals := make(chan os.Signal)
|
||||
signal.Notify(signals, unix.SIGUSR2)
|
||||
@@ -54,52 +62,52 @@ func init() {
|
||||
select {
|
||||
case <-signals:
|
||||
n := runtime.Stack(buf, true)
|
||||
if n == len(buf) {
|
||||
n--
|
||||
}
|
||||
buf[n] = 0
|
||||
C.__android_log_write(C.ANDROID_LOG_ERROR, C.CString("WireGuard/GoBackend/Stacktrace"), (*C.char)(unsafe.Pointer(&buf[0])))
|
||||
C.__android_log_write(C.ANDROID_LOG_ERROR, cstring("AmneziaWG/Stacktrace"), (*C.char)(unsafe.Pointer(&buf[0])))
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
//export wgTurnOn
|
||||
func wgTurnOn(ifnameRef string, tunFd int32, settings string) int32 {
|
||||
interfaceName := string([]byte(ifnameRef))
|
||||
|
||||
//export awgTurnOn
|
||||
func awgTurnOn(interfaceName string, tunFd int32, settings string) int32 {
|
||||
tag := cstring("AmneziaWG/" + interfaceName)
|
||||
logger := &device.Logger{
|
||||
Debug: log.New(&AndroidLogger{level: C.ANDROID_LOG_DEBUG, interfaceName: interfaceName}, "", 0),
|
||||
Info: log.New(&AndroidLogger{level: C.ANDROID_LOG_INFO, interfaceName: interfaceName}, "", 0),
|
||||
Error: log.New(&AndroidLogger{level: C.ANDROID_LOG_ERROR, interfaceName: interfaceName}, "", 0),
|
||||
Verbosef: AndroidLogger{level: C.ANDROID_LOG_DEBUG, tag: tag}.Printf,
|
||||
Errorf: AndroidLogger{level: C.ANDROID_LOG_ERROR, tag: tag}.Printf,
|
||||
}
|
||||
|
||||
logger.Debug.Println("Debug log enabled")
|
||||
|
||||
tun, name, err := tun.CreateUnmonitoredTUNFromFD(int(tunFd))
|
||||
if err != nil {
|
||||
unix.Close(int(tunFd))
|
||||
logger.Error.Println(err)
|
||||
logger.Errorf("CreateUnmonitoredTUNFromFD: %v", err)
|
||||
return -1
|
||||
}
|
||||
|
||||
logger.Info.Println("Attaching to interface", name)
|
||||
device := device.NewDevice(tun, logger)
|
||||
logger.Verbosef("Attaching to interface %v", name)
|
||||
device := device.NewDevice(tun, conn.NewStdNetBind(), logger)
|
||||
|
||||
setError := device.IpcSetOperation(bufio.NewReader(strings.NewReader(settings)))
|
||||
if setError != nil {
|
||||
err = device.IpcSet(settings)
|
||||
if err != nil {
|
||||
unix.Close(int(tunFd))
|
||||
logger.Error.Println(setError)
|
||||
logger.Errorf("IpcSet: %v", err)
|
||||
return -1
|
||||
}
|
||||
device.DisableSomeRoamingForBrokenMobileSemantics()
|
||||
|
||||
var uapi net.Listener
|
||||
|
||||
uapiFile, err := ipc.UAPIOpen(name)
|
||||
if err != nil {
|
||||
logger.Error.Println(err)
|
||||
logger.Errorf("UAPIOpen: %v", err)
|
||||
} else {
|
||||
uapi, err = ipc.UAPIListen(name, uapiFile)
|
||||
if err != nil {
|
||||
uapiFile.Close()
|
||||
logger.Error.Println(err)
|
||||
logger.Errorf("UAPIListen: %v", err)
|
||||
} else {
|
||||
go func() {
|
||||
for {
|
||||
@@ -113,8 +121,14 @@ func wgTurnOn(ifnameRef string, tunFd int32, settings string) int32 {
|
||||
}
|
||||
}
|
||||
|
||||
device.Up()
|
||||
logger.Info.Println("Device started")
|
||||
err = device.Up()
|
||||
if err != nil {
|
||||
logger.Errorf("Unable to bring up device: %v", err)
|
||||
uapiFile.Close()
|
||||
device.Close()
|
||||
return -1
|
||||
}
|
||||
logger.Verbosef("Device started")
|
||||
|
||||
var i int32
|
||||
for i = 0; i < math.MaxInt32; i++ {
|
||||
@@ -123,15 +137,17 @@ func wgTurnOn(ifnameRef string, tunFd int32, settings string) int32 {
|
||||
}
|
||||
}
|
||||
if i == math.MaxInt32 {
|
||||
unix.Close(int(tunFd))
|
||||
logger.Errorf("Unable to find empty handle")
|
||||
uapiFile.Close()
|
||||
device.Close()
|
||||
return -1
|
||||
}
|
||||
tunnelHandles[i] = TunnelHandle{device: device, uapi: uapi}
|
||||
return i
|
||||
}
|
||||
|
||||
//export wgTurnOff
|
||||
func wgTurnOff(tunnelHandle int32) {
|
||||
//export awgTurnOff
|
||||
func awgTurnOff(tunnelHandle int32) {
|
||||
handle, ok := tunnelHandles[tunnelHandle]
|
||||
if !ok {
|
||||
return
|
||||
@@ -143,51 +159,69 @@ func wgTurnOff(tunnelHandle int32) {
|
||||
handle.device.Close()
|
||||
}
|
||||
|
||||
//export wgGetSocketV4
|
||||
func wgGetSocketV4(tunnelHandle int32) int32 {
|
||||
//export awgGetSocketV4
|
||||
func awgGetSocketV4(tunnelHandle int32) int32 {
|
||||
handle, ok := tunnelHandles[tunnelHandle]
|
||||
if !ok {
|
||||
return -1
|
||||
}
|
||||
fd, err := handle.device.PeekLookAtSocketFd4()
|
||||
bind, _ := handle.device.Bind().(conn.PeekLookAtSocketFd)
|
||||
if bind == nil {
|
||||
return -1
|
||||
}
|
||||
fd, err := bind.PeekLookAtSocketFd4()
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
return int32(fd)
|
||||
}
|
||||
|
||||
//export wgGetSocketV6
|
||||
func wgGetSocketV6(tunnelHandle int32) int32 {
|
||||
//export awgGetSocketV6
|
||||
func awgGetSocketV6(tunnelHandle int32) int32 {
|
||||
handle, ok := tunnelHandles[tunnelHandle]
|
||||
if !ok {
|
||||
return -1
|
||||
}
|
||||
fd, err := handle.device.PeekLookAtSocketFd6()
|
||||
bind, _ := handle.device.Bind().(conn.PeekLookAtSocketFd)
|
||||
if bind == nil {
|
||||
return -1
|
||||
}
|
||||
fd, err := bind.PeekLookAtSocketFd6()
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
return int32(fd)
|
||||
}
|
||||
|
||||
//export wgGetConfig
|
||||
func wgGetConfig(tunnelHandle int32) *C.char {
|
||||
//export awgGetConfig
|
||||
func awgGetConfig(tunnelHandle int32) *C.char {
|
||||
handle, ok := tunnelHandles[tunnelHandle]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
settings := new(bytes.Buffer)
|
||||
writer := bufio.NewWriter(settings)
|
||||
err := handle.device.IpcGetOperation(writer)
|
||||
settings, err := handle.device.IpcGet()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
writer.Flush()
|
||||
return C.CString(settings.String())
|
||||
return C.CString(settings)
|
||||
}
|
||||
|
||||
//export wgVersion
|
||||
func wgVersion() *C.char {
|
||||
return C.CString(device.WireGuardGoVersion)
|
||||
//export awgVersion
|
||||
func awgVersion() *C.char {
|
||||
info, ok := debug.ReadBuildInfo()
|
||||
if !ok {
|
||||
return C.CString("unknown")
|
||||
}
|
||||
for _, dep := range info.Deps {
|
||||
if dep.Path == "github.com/amnezia-vpn/amneziawg-go" {
|
||||
parts := strings.Split(dep.Version, "-")
|
||||
if len(parts) == 3 && len(parts[2]) == 12 {
|
||||
return C.CString(parts[2][:7])
|
||||
}
|
||||
return C.CString(dep.Version)
|
||||
}
|
||||
}
|
||||
return C.CString("unknown")
|
||||
}
|
||||
|
||||
func main() {}
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
module golang.zx2c4.com/wireguard/android
|
||||
module github.com/amnezia-vpn/amneziawg-android
|
||||
|
||||
go 1.13
|
||||
go 1.24.4
|
||||
|
||||
require (
|
||||
golang.org/x/crypto v0.0.0-20200320181102-891825fb96df // indirect
|
||||
golang.org/x/net v0.0.0-20200320220750-118fecf932d8 // indirect
|
||||
golang.org/x/sys v0.0.0-20200321134203-328b4cd54aae
|
||||
golang.zx2c4.com/wireguard v0.0.20200320
|
||||
github.com/amnezia-vpn/amneziawg-go v0.2.15
|
||||
golang.org/x/sys v0.33.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/tevino/abool v1.2.0 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
golang.org/x/crypto v0.39.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 // indirect
|
||||
golang.org/x/net v0.41.0 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||
)
|
||||
|
||||
@@ -1,19 +1,30 @@
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200320181102-891825fb96df h1:lDWgvUvNnaTnNBc/dwOty86cFeKoKWbwy2wQj0gIxbU=
|
||||
golang.org/x/crypto v0.0.0-20200320181102-891825fb96df/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20191003171128-d98b1b443823/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200320220750-118fecf932d8 h1:1+zQlQqEEhUeStBTi653GZAnAuivZq/2hz+Iz+OP7rg=
|
||||
golang.org/x/net v0.0.0-20200320220750-118fecf932d8/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200321134203-328b4cd54aae h1:3tcmuaB7wwSZtelmiv479UjUB+vviwABz7a133ZwOKQ=
|
||||
golang.org/x/sys v0.0.0-20200321134203-328b4cd54aae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.zx2c4.com/wireguard v0.0.20200320 h1:1vE6zVeO7fix9cJX1Z9ZQ+ikPIIx7vIyU0o0tLDD88g=
|
||||
golang.zx2c4.com/wireguard v0.0.20200320/go.mod h1:lDian4Sw4poJ04SgHh35nzMVwGSYlPumkdnHcucAQoY=
|
||||
github.com/amnezia-vpn/amneziawg-go v0.2.15 h1:hQnFOJJHXrInorORe3JJwiAD55m1an81EGSW3AdbT74=
|
||||
github.com/amnezia-vpn/amneziawg-go v0.2.15/go.mod h1:nRkPpIzjCxMW8pZKXTRkpqAQVlmFJdVOGkeQSC7wbms=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
||||
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tevino/abool v1.2.0 h1:heAkClL8H6w+mK5md9dzsuohKeXHUpY7Vw0ZCKW+huA=
|
||||
github.com/tevino/abool v1.2.0/go.mod h1:qc66Pna1RiIsPa7O4Egxxs9OqkuxDX55zznh9K07Tzg=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 h1:Di6/M8l0O2lCLc6VVRWhgCiApHV8MnQurBnFSHsQtNY=
|
||||
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
||||
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
|
||||
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489 h1:ze1vwAdliUAr68RQ5NtufWaXaOg8WUO2OACzEV+TNdE=
|
||||
gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489/go.mod h1:10sU+Uh5KKNv1+2x2A0Gvzt8FjD3ASIhorV3YsauXhk=
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
From e44f456f1d0e429e08afed64a161175ff493f3ac Mon Sep 17 00:00:00 2001
|
||||
From 61f3ae8298d1c503cbc31539e0f3a73446c7db9d Mon Sep 17 00:00:00 2001
|
||||
From: "Jason A. Donenfeld" <Jason@zx2c4.com>
|
||||
Date: Wed, 27 Feb 2019 05:05:44 +0100
|
||||
Subject: [PATCH] runtime: use CLOCK_BOOTTIME in nanotime on Linux
|
||||
Date: Tue, 21 Mar 2023 15:33:56 +0100
|
||||
Subject: [PATCH] [release-branch.go1.20] runtime: use CLOCK_BOOTTIME in
|
||||
nanotime on Linux
|
||||
|
||||
This makes timers account for having expired while a computer was
|
||||
asleep, which is quite common on mobile devices. Note that BOOTTIME is
|
||||
@@ -21,17 +22,17 @@ Change-Id: I7b2a6ca0c5bc5fce57ec0eeafe7b68270b429321
|
||||
src/runtime/sys_linux_amd64.s | 2 +-
|
||||
src/runtime/sys_linux_arm.s | 4 ++--
|
||||
src/runtime/sys_linux_arm64.s | 4 ++--
|
||||
src/runtime/sys_linux_mips64x.s | 2 +-
|
||||
src/runtime/sys_linux_mips64x.s | 4 ++--
|
||||
src/runtime/sys_linux_mipsx.s | 2 +-
|
||||
src/runtime/sys_linux_ppc64x.s | 2 +-
|
||||
src/runtime/sys_linux_s390x.s | 2 +-
|
||||
8 files changed, 11 insertions(+), 11 deletions(-)
|
||||
8 files changed, 12 insertions(+), 12 deletions(-)
|
||||
|
||||
diff --git a/src/runtime/sys_linux_386.s b/src/runtime/sys_linux_386.s
|
||||
index 1b28098ad9..46b7071ed8 100644
|
||||
index 12a294153d..17e3524b40 100644
|
||||
--- a/src/runtime/sys_linux_386.s
|
||||
+++ b/src/runtime/sys_linux_386.s
|
||||
@@ -317,13 +317,13 @@ noswitch:
|
||||
@@ -352,13 +352,13 @@ noswitch:
|
||||
|
||||
LEAL 8(SP), BX // &ts (struct timespec)
|
||||
MOVL BX, 4(SP)
|
||||
@@ -48,20 +49,20 @@ index 1b28098ad9..46b7071ed8 100644
|
||||
INVOKE_SYSCALL
|
||||
|
||||
diff --git a/src/runtime/sys_linux_amd64.s b/src/runtime/sys_linux_amd64.s
|
||||
index 58d3bc54b4..4bb9bde3d0 100644
|
||||
index c7a89ba536..01f0a6a26e 100644
|
||||
--- a/src/runtime/sys_linux_amd64.s
|
||||
+++ b/src/runtime/sys_linux_amd64.s
|
||||
@@ -293,7 +293,7 @@ noswitch:
|
||||
MOVQ runtime·vdsoClockgettimeSym(SB), AX
|
||||
CMPQ AX, $0
|
||||
JEQ fallback
|
||||
@@ -255,7 +255,7 @@ noswitch:
|
||||
SUBQ $16, SP // Space for results
|
||||
ANDQ $~15, SP // Align for C code
|
||||
|
||||
- MOVL $1, DI // CLOCK_MONOTONIC
|
||||
+ MOVL $7, DI // CLOCK_BOOTTIME
|
||||
LEAQ 0(SP), SI
|
||||
CALL AX
|
||||
MOVQ 0(SP), AX // sec
|
||||
MOVQ runtime·vdsoClockgettimeSym(SB), AX
|
||||
CMPQ AX, $0
|
||||
diff --git a/src/runtime/sys_linux_arm.s b/src/runtime/sys_linux_arm.s
|
||||
index e103da56dc..0b872b90a6 100644
|
||||
index 7b8c4f0e04..9798a1334e 100644
|
||||
--- a/src/runtime/sys_linux_arm.s
|
||||
+++ b/src/runtime/sys_linux_arm.s
|
||||
@@ -11,7 +11,7 @@
|
||||
@@ -73,20 +74,20 @@ index e103da56dc..0b872b90a6 100644
|
||||
|
||||
// for EABI, as we don't support OABI
|
||||
#define SYS_BASE 0x0
|
||||
@@ -345,7 +345,7 @@ noswitch:
|
||||
SUB $24, R13 // Space for results
|
||||
BIC $0x7, R13 // Align for C code
|
||||
@@ -374,7 +374,7 @@ finish:
|
||||
|
||||
// func nanotime1() int64
|
||||
TEXT runtime·nanotime1(SB),NOSPLIT,$12-8
|
||||
- MOVW $CLOCK_MONOTONIC, R0
|
||||
+ MOVW $CLOCK_BOOTTIME, R0
|
||||
MOVW $8(R13), R1 // timespec
|
||||
MOVW runtime·vdsoClockgettimeSym(SB), R2
|
||||
CMP $0, R2
|
||||
MOVW $spec-12(SP), R1 // timespec
|
||||
|
||||
MOVW runtime·vdsoClockgettimeSym(SB), R4
|
||||
diff --git a/src/runtime/sys_linux_arm64.s b/src/runtime/sys_linux_arm64.s
|
||||
index b9588cec30..e444d50df4 100644
|
||||
index 38ff6ac330..6b819c5441 100644
|
||||
--- a/src/runtime/sys_linux_arm64.s
|
||||
+++ b/src/runtime/sys_linux_arm64.s
|
||||
@@ -13,7 +13,7 @@
|
||||
@@ -14,7 +14,7 @@
|
||||
#define AT_FDCWD -100
|
||||
|
||||
#define CLOCK_REALTIME 0
|
||||
@@ -95,7 +96,7 @@ index b9588cec30..e444d50df4 100644
|
||||
|
||||
#define SYS_exit 93
|
||||
#define SYS_read 63
|
||||
@@ -297,7 +297,7 @@ noswitch:
|
||||
@@ -338,7 +338,7 @@ noswitch:
|
||||
BIC $15, R1
|
||||
MOVD R1, RSP
|
||||
|
||||
@@ -105,10 +106,10 @@ index b9588cec30..e444d50df4 100644
|
||||
CBZ R2, fallback
|
||||
|
||||
diff --git a/src/runtime/sys_linux_mips64x.s b/src/runtime/sys_linux_mips64x.s
|
||||
index 723cfe43d9..edd7a195eb 100644
|
||||
index 47f2da524d..a8b387f193 100644
|
||||
--- a/src/runtime/sys_linux_mips64x.s
|
||||
+++ b/src/runtime/sys_linux_mips64x.s
|
||||
@@ -278,7 +278,7 @@ noswitch:
|
||||
@@ -326,7 +326,7 @@ noswitch:
|
||||
AND $~15, R1 // Align for C code
|
||||
MOVV R1, R29
|
||||
|
||||
@@ -117,11 +118,20 @@ index 723cfe43d9..edd7a195eb 100644
|
||||
MOVV $0(R29), R5
|
||||
|
||||
MOVV runtime·vdsoClockgettimeSym(SB), R25
|
||||
@@ -336,7 +336,7 @@ noswitch:
|
||||
// see walltime for detail
|
||||
BEQ R2, R0, finish
|
||||
MOVV R0, runtime·vdsoClockgettimeSym(SB)
|
||||
- MOVW $1, R4 // CLOCK_MONOTONIC
|
||||
+ MOVW $7, R4 // CLOCK_BOOTTIME
|
||||
MOVV $0(R29), R5
|
||||
JMP fallback
|
||||
|
||||
diff --git a/src/runtime/sys_linux_mipsx.s b/src/runtime/sys_linux_mipsx.s
|
||||
index 15893a7a28..f3edf9a83a 100644
|
||||
index 5e6b6c1504..7f5fd2a80e 100644
|
||||
--- a/src/runtime/sys_linux_mipsx.s
|
||||
+++ b/src/runtime/sys_linux_mipsx.s
|
||||
@@ -235,7 +235,7 @@ TEXT runtime·walltime1(SB),NOSPLIT,$8-12
|
||||
@@ -243,7 +243,7 @@ TEXT runtime·walltime(SB),NOSPLIT,$8-12
|
||||
RET
|
||||
|
||||
TEXT runtime·nanotime1(SB),NOSPLIT,$8-8
|
||||
@@ -131,31 +141,31 @@ index 15893a7a28..f3edf9a83a 100644
|
||||
MOVW $SYS_clock_gettime, R2
|
||||
SYSCALL
|
||||
diff --git a/src/runtime/sys_linux_ppc64x.s b/src/runtime/sys_linux_ppc64x.s
|
||||
index 8629fe3233..2402e2623a 100644
|
||||
index d0427a4807..05ee9fede9 100644
|
||||
--- a/src/runtime/sys_linux_ppc64x.s
|
||||
+++ b/src/runtime/sys_linux_ppc64x.s
|
||||
@@ -233,7 +233,7 @@ fallback:
|
||||
JMP finish
|
||||
@@ -298,7 +298,7 @@ fallback:
|
||||
JMP return
|
||||
|
||||
TEXT runtime·nanotime1(SB),NOSPLIT,$16
|
||||
TEXT runtime·nanotime1(SB),NOSPLIT,$16-8
|
||||
- MOVD $1, R3 // CLOCK_MONOTONIC
|
||||
+ MOVD $7, R3 // CLOCK_BOOTTIME
|
||||
|
||||
MOVD R1, R15 // R15 is unchanged by C code
|
||||
MOVD g_m(g), R21 // R21 = m
|
||||
diff --git a/src/runtime/sys_linux_s390x.s b/src/runtime/sys_linux_s390x.s
|
||||
index c15a1d5364..f52c4d5098 100644
|
||||
index 1448670b91..7d2ee3231c 100644
|
||||
--- a/src/runtime/sys_linux_s390x.s
|
||||
+++ b/src/runtime/sys_linux_s390x.s
|
||||
@@ -207,7 +207,7 @@ TEXT runtime·walltime1(SB),NOSPLIT,$16
|
||||
@@ -296,7 +296,7 @@ fallback:
|
||||
RET
|
||||
|
||||
TEXT runtime·nanotime1(SB),NOSPLIT,$16
|
||||
- MOVW $1, R2 // CLOCK_MONOTONIC
|
||||
+ MOVW $7, R2 // CLOCK_BOOTTIME
|
||||
MOVD $tp-16(SP), R3
|
||||
MOVW $SYS_clock_gettime, R1
|
||||
SYSCALL
|
||||
TEXT runtime·nanotime1(SB),NOSPLIT,$32-8
|
||||
- MOVW $1, R2 // CLOCK_MONOTONIC
|
||||
+ MOVW $7, R2 // CLOCK_BOOTTIME
|
||||
|
||||
MOVD R15, R7 // Backup stack pointer
|
||||
|
||||
--
|
||||
2.25.1
|
||||
2.17.1
|
||||
|
||||
|
||||
+19
-19
@@ -1,6 +1,6 @@
|
||||
/* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Copyright © 2017-2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* Copyright © 2017-2021 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
*/
|
||||
|
||||
#include <jni.h>
|
||||
@@ -8,20 +8,20 @@
|
||||
#include <string.h>
|
||||
|
||||
struct go_string { const char *str; long n; };
|
||||
extern int wgTurnOn(struct go_string ifname, int tun_fd, struct go_string settings);
|
||||
extern void wgTurnOff(int handle);
|
||||
extern int wgGetSocketV4(int handle);
|
||||
extern int wgGetSocketV6(int handle);
|
||||
extern char *wgGetConfig(int handle);
|
||||
extern char *wgVersion();
|
||||
extern int awgTurnOn(struct go_string ifname, int tun_fd, struct go_string settings);
|
||||
extern void awgTurnOff(int handle);
|
||||
extern int awgGetSocketV4(int handle);
|
||||
extern int awgGetSocketV6(int handle);
|
||||
extern char *awgGetConfig(int handle);
|
||||
extern char *awgVersion();
|
||||
|
||||
JNIEXPORT jint JNICALL Java_com_wireguard_android_backend_GoBackend_wgTurnOn(JNIEnv *env, jclass c, jstring ifname, jint tun_fd, jstring settings)
|
||||
JNIEXPORT jint JNICALL Java_org_amnezia_awg_GoBackend_awgTurnOn(JNIEnv *env, jclass c, jstring ifname, jint tun_fd, jstring settings)
|
||||
{
|
||||
const char *ifname_str = (*env)->GetStringUTFChars(env, ifname, 0);
|
||||
size_t ifname_len = (*env)->GetStringUTFLength(env, ifname);
|
||||
const char *settings_str = (*env)->GetStringUTFChars(env, settings, 0);
|
||||
size_t settings_len = (*env)->GetStringUTFLength(env, settings);
|
||||
int ret = wgTurnOn((struct go_string){
|
||||
int ret = awgTurnOn((struct go_string){
|
||||
.str = ifname_str,
|
||||
.n = ifname_len
|
||||
}, tun_fd, (struct go_string){
|
||||
@@ -33,25 +33,25 @@ JNIEXPORT jint JNICALL Java_com_wireguard_android_backend_GoBackend_wgTurnOn(JNI
|
||||
return ret;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_com_wireguard_android_backend_GoBackend_wgTurnOff(JNIEnv *env, jclass c, jint handle)
|
||||
JNIEXPORT void JNICALL Java_org_amnezia_awg_GoBackend_awgTurnOff(JNIEnv *env, jclass c, jint handle)
|
||||
{
|
||||
wgTurnOff(handle);
|
||||
awgTurnOff(handle);
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL Java_com_wireguard_android_backend_GoBackend_wgGetSocketV4(JNIEnv *env, jclass c, jint handle)
|
||||
JNIEXPORT jint JNICALL Java_org_amnezia_awg_GoBackend_awgGetSocketV4(JNIEnv *env, jclass c, jint handle)
|
||||
{
|
||||
return wgGetSocketV4(handle);
|
||||
return awgGetSocketV4(handle);
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL Java_com_wireguard_android_backend_GoBackend_wgGetSocketV6(JNIEnv *env, jclass c, jint handle)
|
||||
JNIEXPORT jint JNICALL Java_org_amnezia_awg_GoBackend_awgGetSocketV6(JNIEnv *env, jclass c, jint handle)
|
||||
{
|
||||
return wgGetSocketV6(handle);
|
||||
return awgGetSocketV6(handle);
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL Java_com_wireguard_android_backend_GoBackend_wgGetConfig(JNIEnv *env, jclass c, jint handle)
|
||||
JNIEXPORT jstring JNICALL Java_org_amnezia_awg_GoBackend_awgGetConfig(JNIEnv *env, jclass c, jint handle)
|
||||
{
|
||||
jstring ret;
|
||||
char *config = wgGetConfig(handle);
|
||||
char *config = awgGetConfig(handle);
|
||||
if (!config)
|
||||
return NULL;
|
||||
ret = (*env)->NewStringUTF(env, config);
|
||||
@@ -59,10 +59,10 @@ JNIEXPORT jstring JNICALL Java_com_wireguard_android_backend_GoBackend_wgGetConf
|
||||
return ret;
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL Java_com_wireguard_android_backend_GoBackend_wgVersion(JNIEnv *env, jclass c)
|
||||
JNIEXPORT jstring JNICALL Java_org_amnezia_awg_GoBackend_awgVersion(JNIEnv *env, jclass c)
|
||||
{
|
||||
jstring ret;
|
||||
char *version = wgVersion();
|
||||
char *version = awgVersion();
|
||||
if (!version)
|
||||
return NULL;
|
||||
ret = (*env)->NewStringUTF(env, version);
|
||||
|
||||
@@ -1,64 +1,12 @@
|
||||
/* SPDX-License-Identifier: BSD
|
||||
*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
*
|
||||
*/
|
||||
|
||||
#define FILE_IS_EMPTY
|
||||
|
||||
#if defined(__ANDROID_API__) && __ANDROID_API__ < 18
|
||||
#undef FILE_IS_EMPTY
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
ssize_t getdelim(char **buf, size_t *bufsiz, int delimiter, FILE *fp)
|
||||
{
|
||||
char *ptr, *eptr;
|
||||
|
||||
if (*buf == NULL || *bufsiz == 0) {
|
||||
*bufsiz = BUFSIZ;
|
||||
if ((*buf = malloc(*bufsiz)) == NULL)
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (ptr = *buf, eptr = *buf + *bufsiz;;) {
|
||||
int c = fgetc(fp);
|
||||
if (c == -1) {
|
||||
if (feof(fp)) {
|
||||
ssize_t diff = (ssize_t)(ptr - *buf);
|
||||
if (diff != 0) {
|
||||
*ptr = '\0';
|
||||
return diff;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
*ptr++ = c;
|
||||
if (c == delimiter) {
|
||||
*ptr = '\0';
|
||||
return ptr - *buf;
|
||||
}
|
||||
if (ptr + 2 >= eptr) {
|
||||
char *nbuf;
|
||||
size_t nbufsiz = *bufsiz * 2;
|
||||
ssize_t d = ptr - *buf;
|
||||
if ((nbuf = realloc(*buf, nbufsiz)) == NULL)
|
||||
return -1;
|
||||
*buf = nbuf;
|
||||
*bufsiz = nbufsiz;
|
||||
eptr = nbuf + nbufsiz;
|
||||
ptr = nbuf + d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ssize_t getline(char **buf, size_t *bufsiz, FILE *fp)
|
||||
{
|
||||
return getdelim(buf, bufsiz, '\n', fp);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(__ANDROID_API__) && __ANDROID_API__ < 24
|
||||
#if defined(__ANDROID_MIN_SDK_VERSION__) && __ANDROID_MIN_SDK_VERSION__ < 24
|
||||
#undef FILE_IS_EMPTY
|
||||
#include <string.h>
|
||||
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
/* SPDX-License-Identifier: BSD
|
||||
*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
*
|
||||
*/
|
||||
|
||||
#if defined(__ANDROID_API__) && __ANDROID_API__ < 18
|
||||
#include <stdio.h>
|
||||
ssize_t getdelim(char **buf, size_t *bufsiz, int delimiter, FILE *fp);
|
||||
ssize_t getline(char **buf, size_t *bufsiz, FILE *fp);
|
||||
#endif
|
||||
|
||||
#if defined(__ANDROID_API__) && __ANDROID_API__ < 24
|
||||
#if defined(__ANDROID_MIN_SDK_VERSION__) && __ANDROID_MIN_SDK_VERSION__ < 24
|
||||
char *strchrnul(const char *s, int c);
|
||||
#endif
|
||||
|
||||
|
||||
Submodule tunnel/tools/wireguard-tools deleted from 828ffc88cd
@@ -1,87 +0,0 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
|
||||
version wireguardVersionName
|
||||
group groupName
|
||||
|
||||
// Create a variable called keystorePropertiesFile, and initialize it to your
|
||||
// keystore.properties file, in the rootProject folder.
|
||||
final def keystorePropertiesFile = rootProject.file("keystore.properties")
|
||||
|
||||
android {
|
||||
buildToolsVersion '29.0.3'
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
}
|
||||
compileSdkVersion 29
|
||||
dataBinding.enabled true
|
||||
viewBinding.enabled true
|
||||
defaultConfig {
|
||||
applicationId 'com.wireguard.android'
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 29
|
||||
versionCode wireguardVersionCode
|
||||
versionName wireguardVersionName
|
||||
buildConfigField 'int', 'MIN_SDK_VERSION', "$minSdkVersion.apiLevel"
|
||||
}
|
||||
// If the keystore file exists
|
||||
if (keystorePropertiesFile.exists()) {
|
||||
// Initialize a new Properties() object called keystoreProperties.
|
||||
final def keystoreProperties = new Properties()
|
||||
|
||||
// Load your keystore.properties file into the keystoreProperties object.
|
||||
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
|
||||
|
||||
signingConfigs {
|
||||
release {
|
||||
keyAlias keystoreProperties['keyAlias']
|
||||
keyPassword keystoreProperties['keyPassword']
|
||||
storeFile rootProject.file(keystoreProperties['storeFile'])
|
||||
storePassword keystoreProperties['storePassword']
|
||||
}
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
if (keystorePropertiesFile.exists()) signingConfig signingConfigs.release
|
||||
minifyEnabled true
|
||||
proguardFiles "proguard-android-optimize.txt", "proguard-rules.pro"
|
||||
}
|
||||
debug {
|
||||
applicationIdSuffix ".debug"
|
||||
versionNameSuffix "-debug"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(":tunnel")
|
||||
implementation "androidx.annotation:annotation:$annotationsVersion"
|
||||
implementation "androidx.appcompat:appcompat:$appcompatVersion"
|
||||
implementation "androidx.cardview:cardview:$cardviewVersion"
|
||||
implementation "androidx.constraintlayout:constraintlayout:$constraintLayoutVersion"
|
||||
implementation "androidx.coordinatorlayout:coordinatorlayout:$coordinatorLayoutVersion"
|
||||
implementation "androidx.biometric:biometric:$biometricVersion"
|
||||
implementation "androidx.core:core-ktx:$coreKtxVersion"
|
||||
implementation "androidx.databinding:databinding-runtime:$agpVersion"
|
||||
implementation "androidx.fragment:fragment:$fragmentVersion"
|
||||
implementation "androidx.preference:preference:$preferenceVersion"
|
||||
implementation "com.google.android.material:material:$materialComponentsVersion"
|
||||
implementation "com.journeyapps:zxing-android-embedded:$zxingEmbeddedVersion"
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion"
|
||||
implementation "net.sourceforge.streamsupport:android-retrofuture:$streamsupportVersion"
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
options.compilerArgs << '-Xlint:unchecked'
|
||||
options.deprecation = true
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
@file:Suppress("UnstableApiUsage")
|
||||
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
|
||||
val pkg: String = providers.gradleProperty("amneziawgPackageName").get()
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
alias(libs.plugins.kotlin.kapt)
|
||||
}
|
||||
|
||||
android {
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
dataBinding = true
|
||||
viewBinding = true
|
||||
}
|
||||
namespace = pkg
|
||||
defaultConfig {
|
||||
applicationId = pkg
|
||||
targetSdk = 34
|
||||
versionCode = providers.gradleProperty("amneziawgVersionCode").get().toInt()
|
||||
versionName = providers.gradleProperty("amneziawgVersionName").get()
|
||||
buildConfigField("int", "MIN_SDK_VERSION", minSdk.toString())
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
isCoreLibraryDesugaringEnabled = true
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
proguardFiles("proguard-android-optimize.txt")
|
||||
packaging {
|
||||
resources {
|
||||
excludes += "DebugProbesKt.bin"
|
||||
excludes += "kotlin-tooling-metadata.json"
|
||||
excludes += "META-INF/*.version"
|
||||
}
|
||||
}
|
||||
}
|
||||
debug {
|
||||
applicationIdSuffix = ".debug"
|
||||
versionNameSuffix = "-debug"
|
||||
}
|
||||
create("googleplay") {
|
||||
initWith(getByName("release"))
|
||||
matchingFallbacks += "release"
|
||||
}
|
||||
}
|
||||
androidResources {
|
||||
generateLocaleConfig = true
|
||||
}
|
||||
lint {
|
||||
disable += "LongLogTag"
|
||||
warning += "MissingTranslation"
|
||||
warning += "ImpliedQuantity"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":tunnel"))
|
||||
implementation(libs.androidx.activity.ktx)
|
||||
implementation(libs.androidx.annotation)
|
||||
implementation(libs.androidx.appcompat)
|
||||
implementation(libs.androidx.constraintlayout)
|
||||
implementation(libs.androidx.coordinatorlayout)
|
||||
implementation(libs.androidx.biometric)
|
||||
implementation(libs.androidx.core.ktx)
|
||||
implementation(libs.androidx.fragment.ktx)
|
||||
implementation(libs.androidx.preference.ktx)
|
||||
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||
implementation(libs.androidx.datastore.preferences)
|
||||
implementation(libs.google.material)
|
||||
implementation(libs.zxing.android.embedded)
|
||||
implementation(libs.kotlinx.coroutines.android)
|
||||
coreLibraryDesugaring(libs.desugarJdkLibs)
|
||||
}
|
||||
|
||||
tasks.withType<JavaCompile>().configureEach {
|
||||
options.compilerArgs.add("-Xlint:unchecked")
|
||||
options.isDeprecation = true
|
||||
}
|
||||
|
||||
tasks.withType<KotlinCompile>().configureEach {
|
||||
compilerOptions.jvmTarget.set(JvmTarget.JVM_17)
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
-allowaccessmodification
|
||||
-dontpreverify
|
||||
-dontusemixedcaseclassnames
|
||||
-dontskipnonpubliclibraryclasses
|
||||
-dontobfuscate
|
||||
-verbose
|
||||
|
||||
-keepattributes *Annotation*
|
||||
|
||||
Vendored
-6
@@ -1,6 +0,0 @@
|
||||
# Squelch all warnings, they're harmless but ProGuard
|
||||
# escalates them as errors.
|
||||
-dontwarn sun.misc.Unsafe
|
||||
|
||||
# Retain some information to keep stacktraces usable
|
||||
-keepattributes SourceFile,LineNumberTable
|
||||
@@ -3,18 +3,18 @@
|
||||
"names": [
|
||||
{
|
||||
"names": [
|
||||
{ "name": "wg0" },
|
||||
{ "name": "wg1" },
|
||||
{ "name": "wg2" },
|
||||
{ "name": "wg3" },
|
||||
{ "name": "wg4" },
|
||||
{ "name": "wg5" },
|
||||
{ "name": "wg6" },
|
||||
{ "name": "wg7" },
|
||||
{ "name": "wg8" },
|
||||
{ "name": "wg9" },
|
||||
{ "name": "wg10" },
|
||||
{ "name": "wg11" }
|
||||
{ "name": "awg0" },
|
||||
{ "name": "awg1" },
|
||||
{ "name": "awg2" },
|
||||
{ "name": "awg3" },
|
||||
{ "name": "awg4" },
|
||||
{ "name": "awg5" },
|
||||
{ "name": "awg6" },
|
||||
{ "name": "awg7" },
|
||||
{ "name": "awg8" },
|
||||
{ "name": "awg9" },
|
||||
{ "name": "awg10" },
|
||||
{ "name": "awg11" }
|
||||
],
|
||||
"checked": [
|
||||
{ "checked": true },
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">WireGuard β</string>
|
||||
<string name="app_name" translatable="false">AmneziaWG β</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.wireguard.android"
|
||||
android:installLocation="internalOnly">
|
||||
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission
|
||||
android:name="android.permission.SYSTEM_ALERT_WINDOW"
|
||||
android:minSdkVersion="34" />
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="28"
|
||||
tools:ignore="ScopedStorage" />
|
||||
|
||||
<uses-feature
|
||||
android:name="android.hardware.touchscreen"
|
||||
@@ -15,6 +20,9 @@
|
||||
<uses-feature
|
||||
android:name="android.software.leanback"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera.any"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera"
|
||||
android:required="false" />
|
||||
@@ -30,6 +38,7 @@
|
||||
android:name=".Application"
|
||||
android:allowBackup="false"
|
||||
android:banner="@mipmap/banner"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
@@ -38,14 +47,16 @@
|
||||
|
||||
<activity
|
||||
android:name=".activity.TunnelToggleActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:theme="@style/NoBackgroundTheme" />
|
||||
|
||||
<activity android:name=".activity.MainActivity">
|
||||
<activity
|
||||
android:name=".activity.MainActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
@@ -53,6 +64,16 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".activity.TvMainActivity"
|
||||
android:exported="true"
|
||||
android:theme="@style/TvTheme">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".activity.SettingsActivity"
|
||||
android:label="@string/settings"
|
||||
@@ -70,6 +91,7 @@
|
||||
|
||||
<activity
|
||||
android:name=".activity.LogViewerActivity"
|
||||
android:exported="false"
|
||||
android:label="@string/log_viewer_title">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
@@ -82,7 +104,9 @@
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true" />
|
||||
|
||||
<receiver android:name=".BootShutdownReceiver">
|
||||
<receiver
|
||||
android:name=".BootShutdownReceiver"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.ACTION_SHUTDOWN" />
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
@@ -91,16 +115,18 @@
|
||||
|
||||
<receiver
|
||||
android:name=".model.TunnelManager$IntentReceiver"
|
||||
android:exported="true"
|
||||
android:permission="${applicationId}.permission.CONTROL_TUNNELS">
|
||||
<intent-filter>
|
||||
<action android:name="com.wireguard.android.action.REFRESH_TUNNEL_STATES" />
|
||||
<action android:name="com.wireguard.android.action.SET_TUNNEL_UP" />
|
||||
<action android:name="com.wireguard.android.action.SET_TUNNEL_DOWN" />
|
||||
<action android:name="org.amnezia.awg.action.REFRESH_TUNNEL_STATES" />
|
||||
<action android:name="org.amnezia.awg.action.SET_TUNNEL_UP" />
|
||||
<action android:name="org.amnezia.awg.action.SET_TUNNEL_DOWN" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<service
|
||||
android:name=".QuickTileService"
|
||||
android:exported="true"
|
||||
android:icon="@drawable/ic_tile"
|
||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
|
||||
|
||||
@@ -111,6 +137,21 @@
|
||||
<meta-data
|
||||
android:name="android.service.quicksettings.ACTIVE_TILE"
|
||||
android:value="false" />
|
||||
|
||||
<meta-data
|
||||
android:name="android.service.quicksettings.TOGGLEABLE_TILE"
|
||||
android:value="true" />
|
||||
</service>
|
||||
|
||||
<meta-data
|
||||
android:name="android.content.APP_RESTRICTIONS"
|
||||
android:resource="@xml/app_restrictions" />
|
||||
</application>
|
||||
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
|
||||
@@ -1,159 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
|
||||
import android.os.AsyncTask
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.os.StrictMode
|
||||
import android.os.StrictMode.VmPolicy
|
||||
import android.util.Log
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.wireguard.android.backend.Backend
|
||||
import com.wireguard.android.backend.GoBackend
|
||||
import com.wireguard.android.backend.WgQuickBackend
|
||||
import com.wireguard.android.configStore.FileConfigStore
|
||||
import com.wireguard.android.model.TunnelManager
|
||||
import com.wireguard.android.util.AsyncWorker
|
||||
import com.wireguard.android.util.ExceptionLoggers
|
||||
import com.wireguard.android.util.ModuleLoader
|
||||
import com.wireguard.android.util.RootShell
|
||||
import com.wireguard.android.util.ToolsInstaller
|
||||
import java9.util.concurrent.CompletableFuture
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.Locale
|
||||
|
||||
class Application : android.app.Application(), OnSharedPreferenceChangeListener {
|
||||
private val futureBackend = CompletableFuture<Backend>()
|
||||
private lateinit var asyncWorker: AsyncWorker
|
||||
private var backend: Backend? = null
|
||||
private lateinit var moduleLoader: ModuleLoader
|
||||
private lateinit var rootShell: RootShell
|
||||
private lateinit var sharedPreferences: SharedPreferences
|
||||
private lateinit var toolsInstaller: ToolsInstaller
|
||||
private lateinit var tunnelManager: TunnelManager
|
||||
|
||||
override fun attachBaseContext(context: Context) {
|
||||
super.attachBaseContext(context)
|
||||
if (BuildConfig.MIN_SDK_VERSION > Build.VERSION.SDK_INT) {
|
||||
val intent = Intent(Intent.ACTION_MAIN)
|
||||
intent.addCategory(Intent.CATEGORY_HOME)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
startActivity(intent)
|
||||
System.exit(0)
|
||||
}
|
||||
if (BuildConfig.DEBUG) {
|
||||
StrictMode.setVmPolicy(VmPolicy.Builder().detectAll().penaltyLog().build())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
Log.i(TAG, USER_AGENT)
|
||||
super.onCreate()
|
||||
asyncWorker = AsyncWorker(AsyncTask.SERIAL_EXECUTOR, Handler(Looper.getMainLooper()))
|
||||
rootShell = RootShell(applicationContext)
|
||||
toolsInstaller = ToolsInstaller(applicationContext, rootShell)
|
||||
moduleLoader = ModuleLoader(applicationContext, rootShell, USER_AGENT)
|
||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(applicationContext)
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
AppCompatDelegate.setDefaultNightMode(
|
||||
if (sharedPreferences.getBoolean("dark_theme", false)) AppCompatDelegate.MODE_NIGHT_YES else AppCompatDelegate.MODE_NIGHT_NO)
|
||||
} else {
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
|
||||
}
|
||||
tunnelManager = TunnelManager(FileConfigStore(applicationContext))
|
||||
tunnelManager.onCreate()
|
||||
asyncWorker.supplyAsync(Companion::getBackend).thenAccept { futureBackend.complete(it) }
|
||||
sharedPreferences.registerOnSharedPreferenceChangeListener(this)
|
||||
}
|
||||
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
|
||||
if ("multiple_tunnels" == key && backend != null && backend is WgQuickBackend)
|
||||
(backend as WgQuickBackend).setMultipleTunnels(sharedPreferences.getBoolean(key, false))
|
||||
}
|
||||
|
||||
override fun onTerminate() {
|
||||
sharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
|
||||
super.onTerminate()
|
||||
}
|
||||
|
||||
companion object {
|
||||
val USER_AGENT = String.format(Locale.ENGLISH, "WireGuard/%s (Android %d; %s; %s; %s %s; %s)", BuildConfig.VERSION_NAME, Build.VERSION.SDK_INT, if (Build.SUPPORTED_ABIS.isNotEmpty()) Build.SUPPORTED_ABIS[0] else "unknown ABI", Build.BOARD, Build.MANUFACTURER, Build.MODEL, Build.FINGERPRINT)
|
||||
private const val TAG = "WireGuard/Application"
|
||||
private lateinit var weakSelf: WeakReference<Application>
|
||||
|
||||
@JvmStatic
|
||||
fun get(): Application {
|
||||
return weakSelf.get()!!
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getAsyncWorker() = get().asyncWorker
|
||||
|
||||
@JvmStatic
|
||||
fun getBackend(): Backend {
|
||||
val app = get()
|
||||
synchronized(app.futureBackend) {
|
||||
if (app.backend == null) {
|
||||
var backend: Backend? = null
|
||||
var didStartRootShell = false
|
||||
if (!ModuleLoader.isModuleLoaded() && app.moduleLoader.moduleMightExist()) {
|
||||
try {
|
||||
app.rootShell.start()
|
||||
didStartRootShell = true
|
||||
app.moduleLoader.loadModule()
|
||||
} catch (ignored: Exception) {
|
||||
}
|
||||
}
|
||||
if (!app.sharedPreferences.getBoolean("disable_kernel_module", false) && ModuleLoader.isModuleLoaded()) {
|
||||
try {
|
||||
if (!didStartRootShell)
|
||||
app.rootShell.start()
|
||||
val wgQuickBackend = WgQuickBackend(app.applicationContext, app.rootShell, app.toolsInstaller)
|
||||
wgQuickBackend.setMultipleTunnels(app.sharedPreferences.getBoolean("multiple_tunnels", false))
|
||||
backend = wgQuickBackend
|
||||
} catch (ignored: Exception) {
|
||||
}
|
||||
}
|
||||
if (backend == null) {
|
||||
backend = GoBackend(app.applicationContext)
|
||||
GoBackend.setAlwaysOnCallback { get().tunnelManager.restoreState(true).whenComplete(ExceptionLoggers.D) }
|
||||
}
|
||||
app.backend = backend
|
||||
}
|
||||
return app.backend!!
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getBackendAsync() = get().futureBackend
|
||||
|
||||
@JvmStatic
|
||||
fun getModuleLoader() = get().moduleLoader
|
||||
|
||||
@JvmStatic
|
||||
fun getRootShell() = get().rootShell
|
||||
|
||||
@JvmStatic
|
||||
fun getSharedPreferences() = get().sharedPreferences
|
||||
|
||||
@JvmStatic
|
||||
fun getToolsInstaller() = get().toolsInstaller
|
||||
|
||||
@JvmStatic
|
||||
fun getTunnelManager() = get().tunnelManager
|
||||
}
|
||||
|
||||
init {
|
||||
weakSelf = WeakReference(this)
|
||||
}
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.activity
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.SparseArray
|
||||
import android.view.MenuItem
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import com.wireguard.android.Application
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.backend.WgQuickBackend
|
||||
import com.wireguard.android.util.ModuleLoader
|
||||
import java.util.ArrayList
|
||||
import java.util.Arrays
|
||||
|
||||
/**
|
||||
* Interface for changing application-global persistent settings.
|
||||
*/
|
||||
class SettingsActivity : ThemeChangeAwareActivity() {
|
||||
private val permissionRequestCallbacks = SparseArray<(permissions: Array<String>, granted: IntArray) -> Unit>()
|
||||
private var permissionRequestCounter = 0
|
||||
|
||||
fun ensurePermissions(permissions: Array<String>, cb: (permissions: Array<String>, granted: IntArray) -> Unit) {
|
||||
val needPermissions: MutableList<String> = ArrayList(permissions.size)
|
||||
permissions.forEach {
|
||||
if (ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED) {
|
||||
needPermissions.add(it)
|
||||
}
|
||||
}
|
||||
if (needPermissions.isEmpty()) {
|
||||
val granted = IntArray(permissions.size)
|
||||
Arrays.fill(granted, PackageManager.PERMISSION_GRANTED)
|
||||
cb.invoke(permissions, granted)
|
||||
return
|
||||
}
|
||||
val idx = permissionRequestCounter++
|
||||
permissionRequestCallbacks.put(idx, cb)
|
||||
ActivityCompat.requestPermissions(this,
|
||||
needPermissions.toTypedArray(), idx)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
if (supportFragmentManager.findFragmentById(android.R.id.content) == null) {
|
||||
supportFragmentManager.beginTransaction()
|
||||
.add(android.R.id.content, SettingsFragment())
|
||||
.commit()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
if (item.itemId == android.R.id.home) {
|
||||
finish()
|
||||
return true
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int,
|
||||
permissions: Array<String>,
|
||||
grantResults: IntArray) {
|
||||
val f = permissionRequestCallbacks[requestCode]
|
||||
if (f != null) {
|
||||
permissionRequestCallbacks.remove(requestCode)
|
||||
f.invoke(permissions, grantResults)
|
||||
}
|
||||
}
|
||||
|
||||
class SettingsFragment : PreferenceFragmentCompat() {
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, key: String?) {
|
||||
addPreferencesFromResource(R.xml.preferences)
|
||||
preferenceScreen.initialExpandedChildrenCount = 4
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
val darkTheme = preferenceManager.findPreference<Preference>("dark_theme")
|
||||
darkTheme?.parent?.removePreference(darkTheme)
|
||||
--preferenceScreen.initialExpandedChildrenCount
|
||||
}
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
val remoteApps = preferenceManager.findPreference<Preference>("allow_remote_control_intents")
|
||||
remoteApps?.parent?.removePreference(remoteApps)
|
||||
}
|
||||
val wgQuickOnlyPrefs = arrayOf(
|
||||
preferenceManager.findPreference("tools_installer"),
|
||||
preferenceManager.findPreference("restore_on_boot"),
|
||||
preferenceManager.findPreference<Preference>("multiple_tunnels")
|
||||
).filterNotNull()
|
||||
wgQuickOnlyPrefs.forEach { it.isVisible = false }
|
||||
Application.getBackendAsync().thenAccept { backend ->
|
||||
if (backend is WgQuickBackend) {
|
||||
++preferenceScreen.initialExpandedChildrenCount
|
||||
wgQuickOnlyPrefs.forEach { it.isVisible = true }
|
||||
} else {
|
||||
wgQuickOnlyPrefs.forEach { it.parent?.removePreference(it) }
|
||||
}
|
||||
}
|
||||
preferenceManager.findPreference<Preference>("log_viewer")?.setOnPreferenceClickListener {
|
||||
startActivity(Intent(requireContext(), LogViewerActivity::class.java))
|
||||
true
|
||||
}
|
||||
val moduleInstaller = preferenceManager.findPreference<Preference>("module_downloader")
|
||||
val kernelModuleDisabler = preferenceManager.findPreference<Preference>("kernel_module_disabler")
|
||||
moduleInstaller?.isVisible = false
|
||||
if (ModuleLoader.isModuleLoaded()) {
|
||||
moduleInstaller?.parent?.removePreference(moduleInstaller)
|
||||
} else {
|
||||
kernelModuleDisabler?.parent?.removePreference(kernelModuleDisabler)
|
||||
Application.getAsyncWorker().runAsync(Application.getRootShell()::start).whenComplete { _, e ->
|
||||
if (e == null)
|
||||
moduleInstaller?.isVisible = true
|
||||
else
|
||||
moduleInstaller?.parent?.removePreference(moduleInstaller)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.activity
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import com.wireguard.android.Application
|
||||
|
||||
abstract class ThemeChangeAwareActivity : AppCompatActivity(), OnSharedPreferenceChangeListener {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
Application.getSharedPreferences().registerOnSharedPreferenceChangeListener(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
Application.getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this)
|
||||
}
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
|
||||
when (key) {
|
||||
"dark_theme" -> {
|
||||
AppCompatDelegate.setDefaultNightMode(if (sharedPreferences.getBoolean(key, false)) {
|
||||
AppCompatDelegate.MODE_NIGHT_YES
|
||||
} else {
|
||||
AppCompatDelegate.MODE_NIGHT_NO
|
||||
})
|
||||
recreate()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.activity
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.service.quicksettings.TileService
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.wireguard.android.Application
|
||||
import com.wireguard.android.QuickTileService
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.backend.Tunnel
|
||||
import com.wireguard.android.util.ErrorMessages
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.N)
|
||||
class TunnelToggleActivity : AppCompatActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val tunnel = Application.getTunnelManager().lastUsedTunnel ?: return
|
||||
tunnel.setStateAsync(Tunnel.State.TOGGLE).whenComplete { _, t ->
|
||||
TileService.requestListeningState(this, ComponentName(this, QuickTileService::class.java))
|
||||
onToggleFinished(t)
|
||||
finishAffinity()
|
||||
}
|
||||
}
|
||||
|
||||
private fun onToggleFinished(throwable: Throwable?) {
|
||||
if (throwable == null) return
|
||||
val error = ErrorMessages[throwable]
|
||||
val message = getString(R.string.toggle_error, error)
|
||||
Log.e(TAG, message, throwable)
|
||||
Toast.makeText(this, message, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "WireGuard/TunnelToggleActivity"
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.fragment
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.wireguard.android.Application
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.databinding.AppListDialogFragmentBinding
|
||||
import com.wireguard.android.databinding.ObservableKeyedArrayList
|
||||
import com.wireguard.android.model.ApplicationData
|
||||
import com.wireguard.android.util.ErrorMessages
|
||||
|
||||
class AppListDialogFragment : DialogFragment() {
|
||||
private val appData: ObservableKeyedArrayList<String, ApplicationData> = ObservableKeyedArrayList()
|
||||
private var currentlyExcludedApps = emptyList<String>()
|
||||
|
||||
private fun loadData() {
|
||||
val activity = activity ?: return
|
||||
val pm = activity.packageManager
|
||||
Application.getAsyncWorker().supplyAsync<List<ApplicationData>> {
|
||||
val launcherIntent = Intent(Intent.ACTION_MAIN, null)
|
||||
launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER)
|
||||
val resolveInfos = pm.queryIntentActivities(launcherIntent, 0)
|
||||
val applicationData: MutableList<ApplicationData> = ArrayList()
|
||||
resolveInfos.forEach {
|
||||
val packageName = it.activityInfo.packageName
|
||||
applicationData.add(ApplicationData(it.loadIcon(pm), it.loadLabel(pm).toString(), packageName, currentlyExcludedApps.contains(packageName)))
|
||||
}
|
||||
applicationData.sortWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.name })
|
||||
applicationData
|
||||
}.whenComplete { data, throwable ->
|
||||
if (data != null) {
|
||||
appData.clear()
|
||||
appData.addAll(data)
|
||||
} else {
|
||||
val error = ErrorMessages[throwable]
|
||||
val message = activity.getString(R.string.error_fetching_apps, error)
|
||||
Toast.makeText(activity, message, Toast.LENGTH_LONG).show()
|
||||
dismissAllowingStateLoss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val excludedApps = requireArguments().getStringArrayList(KEY_EXCLUDED_APPS)
|
||||
currentlyExcludedApps = (excludedApps ?: emptyList())
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val alertDialogBuilder = AlertDialog.Builder(requireActivity())
|
||||
alertDialogBuilder.setTitle(R.string.excluded_applications)
|
||||
val binding = AppListDialogFragmentBinding.inflate(requireActivity().layoutInflater, null, false)
|
||||
binding.executePendingBindings()
|
||||
alertDialogBuilder.setView(binding.root)
|
||||
alertDialogBuilder.setPositiveButton(R.string.set_exclusions) { _, _ -> setExclusionsAndDismiss() }
|
||||
alertDialogBuilder.setNegativeButton(R.string.cancel) { dialog, _ -> dialog.dismiss() }
|
||||
alertDialogBuilder.setNeutralButton(R.string.toggle_all) { _, _ -> }
|
||||
binding.fragment = this
|
||||
binding.appData = appData
|
||||
loadData()
|
||||
val dialog = alertDialogBuilder.create()
|
||||
dialog.setOnShowListener {
|
||||
dialog.getButton(DialogInterface.BUTTON_NEUTRAL).setOnClickListener {
|
||||
val selectedItems = appData
|
||||
.filter { it.isExcludedFromTunnel }
|
||||
|
||||
val excludeAll = selectedItems.isEmpty()
|
||||
appData.forEach {
|
||||
it.isExcludedFromTunnel = excludeAll
|
||||
}
|
||||
}
|
||||
}
|
||||
return dialog
|
||||
}
|
||||
|
||||
private fun setExclusionsAndDismiss() {
|
||||
val excludedApps: MutableList<String> = ArrayList()
|
||||
for (data in appData) {
|
||||
if (data.isExcludedFromTunnel) {
|
||||
excludedApps.add(data.packageName)
|
||||
}
|
||||
}
|
||||
(targetFragment as AppExclusionListener?)!!.onExcludedAppsSelected(excludedApps)
|
||||
dismiss()
|
||||
}
|
||||
|
||||
interface AppExclusionListener {
|
||||
fun onExcludedAppsSelected(excludedApps: List<String>)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val KEY_EXCLUDED_APPS = "excludedApps"
|
||||
fun <T> newInstance(excludedApps: ArrayList<String?>?, target: T): AppListDialogFragment where T : Fragment?, T : AppExclusionListener? {
|
||||
val extras = Bundle()
|
||||
extras.putStringArrayList(KEY_EXCLUDED_APPS, excludedApps)
|
||||
val fragment = AppListDialogFragment()
|
||||
fragment.setTargetFragment(target, 0)
|
||||
fragment.arguments = extras
|
||||
return fragment
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.fragment
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.databinding.DataBindingUtil
|
||||
import androidx.databinding.ViewDataBinding
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.wireguard.android.Application
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.activity.BaseActivity
|
||||
import com.wireguard.android.activity.BaseActivity.OnSelectedTunnelChangedListener
|
||||
import com.wireguard.android.backend.Backend
|
||||
import com.wireguard.android.backend.GoBackend
|
||||
import com.wireguard.android.backend.Tunnel
|
||||
import com.wireguard.android.databinding.TunnelDetailFragmentBinding
|
||||
import com.wireguard.android.databinding.TunnelListItemBinding
|
||||
import com.wireguard.android.model.ObservableTunnel
|
||||
import com.wireguard.android.util.ErrorMessages
|
||||
|
||||
/**
|
||||
* Base class for fragments that need to know the currently-selected tunnel. Only does anything when
|
||||
* attached to a `BaseActivity`.
|
||||
*/
|
||||
abstract class BaseFragment : Fragment(), OnSelectedTunnelChangedListener {
|
||||
private var baseActivity: BaseActivity? = null
|
||||
private var pendingTunnel: ObservableTunnel? = null
|
||||
private var pendingTunnelUp: Boolean? = null
|
||||
protected var selectedTunnel: ObservableTunnel?
|
||||
get() = baseActivity?.selectedTunnel
|
||||
protected set(tunnel) {
|
||||
baseActivity?.selectedTunnel = tunnel
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
if (requestCode == REQUEST_CODE_VPN_PERMISSION) {
|
||||
if (pendingTunnel != null && pendingTunnelUp != null) setTunnelStateWithPermissionsResult(pendingTunnel!!, pendingTunnelUp!!)
|
||||
pendingTunnel = null
|
||||
pendingTunnelUp = null
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
if (context is BaseActivity) {
|
||||
baseActivity = context
|
||||
baseActivity?.addOnSelectedTunnelChangedListener(this)
|
||||
} else {
|
||||
baseActivity = null
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDetach() {
|
||||
baseActivity?.removeOnSelectedTunnelChangedListener(this)
|
||||
baseActivity = null
|
||||
super.onDetach()
|
||||
}
|
||||
|
||||
fun setTunnelState(view: View, checked: Boolean) {
|
||||
val tunnel = when (val binding = DataBindingUtil.findBinding<ViewDataBinding>(view)) {
|
||||
is TunnelDetailFragmentBinding -> binding.tunnel
|
||||
is TunnelListItemBinding -> binding.item
|
||||
else -> return
|
||||
} ?: return
|
||||
Application.getBackendAsync().thenAccept { backend: Backend? ->
|
||||
if (backend is GoBackend) {
|
||||
val intent = GoBackend.VpnService.prepare(view.context)
|
||||
if (intent != null) {
|
||||
pendingTunnel = tunnel
|
||||
pendingTunnelUp = checked
|
||||
startActivityForResult(intent, REQUEST_CODE_VPN_PERMISSION)
|
||||
return@thenAccept
|
||||
}
|
||||
}
|
||||
setTunnelStateWithPermissionsResult(tunnel, checked)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setTunnelStateWithPermissionsResult(tunnel: ObservableTunnel, checked: Boolean) {
|
||||
tunnel.setStateAsync(Tunnel.State.of(checked)).whenComplete { _, throwable ->
|
||||
if (throwable == null) return@whenComplete
|
||||
val error = ErrorMessages[throwable]
|
||||
val messageResId = if (checked) R.string.error_up else R.string.error_down
|
||||
val message = requireContext().getString(messageResId, error)
|
||||
val view = view
|
||||
if (view != null)
|
||||
Snackbar.make(view, message, Snackbar.LENGTH_LONG)
|
||||
.setAnchorView(view.findViewById<View>(R.id.create_fab))
|
||||
.show()
|
||||
else
|
||||
Toast.makeText(requireContext(), message, Toast.LENGTH_LONG).show()
|
||||
Log.e(TAG, message, throwable)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val REQUEST_CODE_VPN_PERMISSION = 23491
|
||||
private const val TAG = "WireGuard/BaseFragment"
|
||||
}
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.fragment
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.databinding.DataBindingUtil
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.backend.Tunnel
|
||||
import com.wireguard.android.databinding.TunnelDetailFragmentBinding
|
||||
import com.wireguard.android.databinding.TunnelDetailPeerBinding
|
||||
import com.wireguard.android.model.ObservableTunnel
|
||||
import com.wireguard.android.widget.EdgeToEdge.setUpRoot
|
||||
import com.wireguard.android.widget.EdgeToEdge.setUpScrollingContent
|
||||
import java.util.Timer
|
||||
import java.util.TimerTask
|
||||
|
||||
/**
|
||||
* Fragment that shows details about a specific tunnel.
|
||||
*/
|
||||
class TunnelDetailFragment : BaseFragment() {
|
||||
private var binding: TunnelDetailFragmentBinding? = null
|
||||
private var lastState = Tunnel.State.TOGGLE
|
||||
private var timer: Timer? = null
|
||||
|
||||
private fun formatBytes(bytes: Long): String {
|
||||
val context = requireContext()
|
||||
return when {
|
||||
bytes < 1024 -> context.getString(R.string.transfer_bytes, bytes)
|
||||
bytes < 1024 * 1024 -> context.getString(R.string.transfer_kibibytes, bytes / 1024.0)
|
||||
bytes < 1024 * 1024 * 1024 -> context.getString(R.string.transfer_mibibytes, bytes / (1024.0 * 1024.0))
|
||||
bytes < 1024 * 1024 * 1024 * 1024L -> context.getString(R.string.transfer_gibibytes, bytes / (1024.0 * 1024.0 * 1024.0))
|
||||
else -> context.getString(R.string.transfer_tibibytes, bytes / (1024.0 * 1024.0 * 1024.0) / 1024.0)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setHasOptionsMenu(true)
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.tunnel_detail, menu)
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?): View? {
|
||||
super.onCreateView(inflater, container, savedInstanceState)
|
||||
binding = TunnelDetailFragmentBinding.inflate(inflater, container, false)
|
||||
binding?.apply {
|
||||
executePendingBindings()
|
||||
setUpRoot(root as ViewGroup)
|
||||
setUpScrollingContent(root as ViewGroup, null)
|
||||
}
|
||||
return binding!!.root
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
binding = null
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
timer = Timer()
|
||||
timer!!.scheduleAtFixedRate(object : TimerTask() {
|
||||
override fun run() {
|
||||
updateStats()
|
||||
}
|
||||
}, 0, 1000)
|
||||
}
|
||||
|
||||
override fun onSelectedTunnelChanged(oldTunnel: ObservableTunnel?, newTunnel: ObservableTunnel?) {
|
||||
binding ?: return
|
||||
binding!!.tunnel = newTunnel
|
||||
if (newTunnel == null) binding!!.config = null else newTunnel.configAsync.thenAccept { config -> binding!!.config = config }
|
||||
lastState = Tunnel.State.TOGGLE
|
||||
updateStats()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
if (timer != null) {
|
||||
timer!!.cancel()
|
||||
timer = null
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewStateRestored(savedInstanceState: Bundle?) {
|
||||
binding ?: return
|
||||
binding!!.fragment = this
|
||||
onSelectedTunnelChanged(null, selectedTunnel)
|
||||
super.onViewStateRestored(savedInstanceState)
|
||||
}
|
||||
|
||||
private fun updateStats() {
|
||||
if (binding == null || !isResumed) return
|
||||
val tunnel = binding!!.tunnel ?: return
|
||||
val state = tunnel.state
|
||||
if (state != Tunnel.State.UP && lastState == state) return
|
||||
lastState = state
|
||||
tunnel.statisticsAsync.whenComplete { statistics, throwable ->
|
||||
if (throwable != null) {
|
||||
for (i in 0 until binding!!.peersLayout.childCount) {
|
||||
val peer: TunnelDetailPeerBinding = DataBindingUtil.getBinding(binding!!.peersLayout.getChildAt(i))
|
||||
?: continue
|
||||
peer.transferLabel.visibility = View.GONE
|
||||
peer.transferText.visibility = View.GONE
|
||||
}
|
||||
return@whenComplete
|
||||
}
|
||||
for (i in 0 until binding!!.peersLayout.childCount) {
|
||||
val peer: TunnelDetailPeerBinding = DataBindingUtil.getBinding(binding!!.peersLayout.getChildAt(i))
|
||||
?: continue
|
||||
val publicKey = peer.item!!.publicKey
|
||||
val rx = statistics.peerRx(publicKey)
|
||||
val tx = statistics.peerTx(publicKey)
|
||||
if (rx == 0L && tx == 0L) {
|
||||
peer.transferLabel.visibility = View.GONE
|
||||
peer.transferText.visibility = View.GONE
|
||||
continue
|
||||
}
|
||||
peer.transferText.text = requireContext().getString(R.string.transfer_rx_tx, formatBytes(rx), formatBytes(tx))
|
||||
peer.transferLabel.visibility = View.VISIBLE
|
||||
peer.transferText.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,271 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.fragment
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.text.InputType
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.WindowManager
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.EditText
|
||||
import android.widget.Toast
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.wireguard.android.Application
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.backend.Tunnel
|
||||
import com.wireguard.android.databinding.TunnelEditorFragmentBinding
|
||||
import com.wireguard.android.fragment.AppListDialogFragment.AppExclusionListener
|
||||
import com.wireguard.android.model.ObservableTunnel
|
||||
import com.wireguard.android.util.BiometricAuthenticator
|
||||
import com.wireguard.android.util.ErrorMessages
|
||||
import com.wireguard.android.viewmodel.ConfigProxy
|
||||
import com.wireguard.android.widget.EdgeToEdge.setUpRoot
|
||||
import com.wireguard.android.widget.EdgeToEdge.setUpScrollingContent
|
||||
import com.wireguard.config.Config
|
||||
|
||||
/**
|
||||
* Fragment for editing a WireGuard configuration.
|
||||
*/
|
||||
class TunnelEditorFragment : BaseFragment(), AppExclusionListener {
|
||||
private var haveShownKeys = false
|
||||
private var binding: TunnelEditorFragmentBinding? = null
|
||||
private var tunnel: ObservableTunnel? = null
|
||||
private fun onConfigLoaded(config: Config) {
|
||||
binding?.config = ConfigProxy(config)
|
||||
}
|
||||
|
||||
private fun onConfigSaved(savedTunnel: Tunnel, throwable: Throwable?) {
|
||||
val message: String
|
||||
if (throwable == null) {
|
||||
message = getString(R.string.config_save_success, savedTunnel.name)
|
||||
Log.d(TAG, message)
|
||||
Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show()
|
||||
onFinished()
|
||||
} else {
|
||||
val error = ErrorMessages[throwable]
|
||||
message = getString(R.string.config_save_error, savedTunnel.name, error)
|
||||
Log.e(TAG, message, throwable)
|
||||
binding?.let {
|
||||
Snackbar.make(it.mainContainer, message, Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setHasOptionsMenu(true)
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.config_editor, menu)
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?): View? {
|
||||
super.onCreateView(inflater, container, savedInstanceState)
|
||||
binding = TunnelEditorFragmentBinding.inflate(inflater, container, false)
|
||||
binding?.apply {
|
||||
executePendingBindings()
|
||||
setUpRoot(root as ViewGroup)
|
||||
setUpScrollingContent(mainContainer, null)
|
||||
privateKeyTextLayout.setEndIconOnClickListener { config?.`interface`?.generateKeyPair() }
|
||||
}
|
||||
return binding?.root
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
activity?.window?.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
||||
binding = null
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
override fun onExcludedAppsSelected(excludedApps: List<String>) {
|
||||
requireNotNull(binding) { "Tried to set excluded apps while no view was loaded" }
|
||||
binding!!.config!!.`interface`.excludedApplications.apply {
|
||||
clear()
|
||||
addAll(excludedApps)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onFinished() {
|
||||
// Hide the keyboard; it rarely goes away on its own.
|
||||
val activity = activity ?: return
|
||||
val focusedView = activity.currentFocus
|
||||
if (focusedView != null) {
|
||||
val inputManager = activity.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
|
||||
inputManager?.hideSoftInputFromWindow(focusedView.windowToken,
|
||||
InputMethodManager.HIDE_NOT_ALWAYS)
|
||||
}
|
||||
// Tell the activity to finish itself or go back to the detail view.
|
||||
activity.runOnUiThread {
|
||||
// TODO(smaeul): Remove this hack when fixing the Config ViewModel
|
||||
// The selected tunnel has to actually change, but we have to remember this one.
|
||||
val savedTunnel = tunnel
|
||||
if (savedTunnel === selectedTunnel) selectedTunnel = null
|
||||
selectedTunnel = savedTunnel
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
if (item.itemId == R.id.menu_action_save) {
|
||||
binding ?: return false
|
||||
val newConfig = try {
|
||||
binding!!.config!!.resolve()
|
||||
} catch (e: Exception) {
|
||||
val error = ErrorMessages[e]
|
||||
val tunnelName = if (tunnel == null) binding!!.name else tunnel!!.name
|
||||
val message = getString(R.string.config_save_error, tunnelName, error)
|
||||
Log.e(TAG, message, e)
|
||||
Snackbar.make(binding!!.mainContainer, error, Snackbar.LENGTH_LONG).show()
|
||||
return false
|
||||
}
|
||||
when {
|
||||
tunnel == null -> {
|
||||
Log.d(TAG, "Attempting to create new tunnel " + binding!!.name)
|
||||
val manager = Application.getTunnelManager()
|
||||
manager.create(binding!!.name!!, newConfig).whenComplete(this::onTunnelCreated)
|
||||
}
|
||||
tunnel!!.name != binding!!.name -> {
|
||||
Log.d(TAG, "Attempting to rename tunnel to " + binding!!.name)
|
||||
tunnel!!.setNameAsync(binding!!.name!!).whenComplete { _, t -> onTunnelRenamed(tunnel!!, newConfig, t) }
|
||||
}
|
||||
else -> {
|
||||
Log.d(TAG, "Attempting to save config of " + tunnel!!.name)
|
||||
tunnel!!.setConfigAsync(newConfig)
|
||||
.whenComplete { _, t -> onConfigSaved(tunnel!!, t) }
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
fun onRequestSetExcludedApplications(view: View?) {
|
||||
if (binding != null) {
|
||||
val excludedApps = ArrayList(binding!!.config!!.`interface`.excludedApplications)
|
||||
val fragment = AppListDialogFragment.newInstance(excludedApps, this)
|
||||
fragment.show(parentFragmentManager, null)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
if (binding != null) outState.putParcelable(KEY_LOCAL_CONFIG, binding!!.config)
|
||||
outState.putString(KEY_ORIGINAL_NAME, if (tunnel == null) null else tunnel!!.name)
|
||||
super.onSaveInstanceState(outState)
|
||||
}
|
||||
|
||||
override fun onSelectedTunnelChanged(oldTunnel: ObservableTunnel?,
|
||||
newTunnel: ObservableTunnel?) {
|
||||
tunnel = newTunnel
|
||||
if (binding == null) return
|
||||
binding!!.config = ConfigProxy()
|
||||
if (tunnel != null) {
|
||||
binding!!.name = tunnel!!.name
|
||||
tunnel!!.configAsync.thenAccept(this::onConfigLoaded)
|
||||
} else {
|
||||
binding!!.name = ""
|
||||
}
|
||||
}
|
||||
|
||||
private fun onTunnelCreated(newTunnel: ObservableTunnel, throwable: Throwable?) {
|
||||
val message: String
|
||||
if (throwable == null) {
|
||||
tunnel = newTunnel
|
||||
message = getString(R.string.tunnel_create_success, tunnel!!.name)
|
||||
Log.d(TAG, message)
|
||||
Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show()
|
||||
onFinished()
|
||||
} else {
|
||||
val error = ErrorMessages[throwable]
|
||||
message = getString(R.string.tunnel_create_error, error)
|
||||
Log.e(TAG, message, throwable)
|
||||
binding?.let {
|
||||
Snackbar.make(it.mainContainer, message, Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onTunnelRenamed(renamedTunnel: ObservableTunnel, newConfig: Config,
|
||||
throwable: Throwable?) {
|
||||
val message: String
|
||||
if (throwable == null) {
|
||||
message = getString(R.string.tunnel_rename_success, renamedTunnel.name)
|
||||
Log.d(TAG, message)
|
||||
// Now save the rest of configuration changes.
|
||||
Log.d(TAG, "Attempting to save config of renamed tunnel " + tunnel!!.name)
|
||||
renamedTunnel.setConfigAsync(newConfig).whenComplete { _, t -> onConfigSaved(renamedTunnel, t) }
|
||||
} else {
|
||||
val error = ErrorMessages[throwable]
|
||||
message = getString(R.string.tunnel_rename_error, error)
|
||||
Log.e(TAG, message, throwable)
|
||||
binding?.let {
|
||||
Snackbar.make(it.mainContainer, message, Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewStateRestored(savedInstanceState: Bundle?) {
|
||||
binding ?: return
|
||||
binding!!.fragment = this
|
||||
if (savedInstanceState == null) {
|
||||
onSelectedTunnelChanged(null, selectedTunnel)
|
||||
} else {
|
||||
tunnel = selectedTunnel
|
||||
val config: ConfigProxy = savedInstanceState.getParcelable(KEY_LOCAL_CONFIG)!!
|
||||
val originalName = savedInstanceState.getString(KEY_ORIGINAL_NAME)
|
||||
if (tunnel != null && tunnel!!.name != originalName) onSelectedTunnelChanged(null, tunnel) else binding!!.config = config
|
||||
}
|
||||
super.onViewStateRestored(savedInstanceState)
|
||||
}
|
||||
|
||||
private var showingAuthenticator = false
|
||||
|
||||
fun onKeyClick(view: View) = onKeyFocusChange(view, true)
|
||||
|
||||
fun onKeyFocusChange(view: View, isFocused: Boolean) {
|
||||
if (!isFocused || showingAuthenticator) return
|
||||
val edit = view as? EditText ?: return
|
||||
if (!haveShownKeys && edit.text.isNotEmpty()) {
|
||||
showingAuthenticator = true
|
||||
BiometricAuthenticator.authenticate(R.string.biometric_prompt_private_key_title, this) {
|
||||
showingAuthenticator = false
|
||||
when (it) {
|
||||
is BiometricAuthenticator.Result.Success, is BiometricAuthenticator.Result.HardwareUnavailableOrDisabled -> {
|
||||
haveShownKeys = true
|
||||
showPrivateKey(edit)
|
||||
}
|
||||
is BiometricAuthenticator.Result.Failure -> {
|
||||
Snackbar.make(
|
||||
binding!!.mainContainer,
|
||||
it.message,
|
||||
Snackbar.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
showPrivateKey(edit)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showPrivateKey(edit: EditText) {
|
||||
activity?.window?.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
||||
edit.inputType = InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS or InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val KEY_LOCAL_CONFIG = "local_config"
|
||||
private const val KEY_ORIGINAL_NAME = "original_name"
|
||||
private const val TAG = "WireGuard/TunnelEditorFragment"
|
||||
}
|
||||
}
|
||||
@@ -1,402 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.fragment
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.content.res.Resources
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.provider.OpenableColumns
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.zxing.integration.android.IntentIntegrator
|
||||
import com.wireguard.android.Application
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.databinding.ObservableKeyedRecyclerViewAdapter.RowConfigurationHandler
|
||||
import com.wireguard.android.databinding.TunnelListFragmentBinding
|
||||
import com.wireguard.android.databinding.TunnelListItemBinding
|
||||
import com.wireguard.android.fragment.ConfigNamingDialogFragment.Companion.newInstance
|
||||
import com.wireguard.android.model.ObservableTunnel
|
||||
import com.wireguard.android.util.ErrorMessages
|
||||
import com.wireguard.android.widget.EdgeToEdge.setUpFAB
|
||||
import com.wireguard.android.widget.EdgeToEdge.setUpRoot
|
||||
import com.wireguard.android.widget.EdgeToEdge.setUpScrollingContent
|
||||
import com.wireguard.android.widget.MultiselectableRelativeLayout
|
||||
import com.wireguard.config.Config
|
||||
import java9.util.concurrent.CompletableFuture
|
||||
import java.io.BufferedReader
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.InputStreamReader
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.util.ArrayList
|
||||
import java.util.HashSet
|
||||
import java.util.Locale
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
|
||||
/**
|
||||
* Fragment containing a list of known WireGuard tunnels. It allows creating and deleting tunnels.
|
||||
*/
|
||||
class TunnelListFragment : BaseFragment() {
|
||||
private val actionModeListener = ActionModeListener()
|
||||
private var actionMode: ActionMode? = null
|
||||
private var binding: TunnelListFragmentBinding? = null
|
||||
private fun importTunnel(configText: String) {
|
||||
try {
|
||||
// Ensure the config text is parseable before proceeding…
|
||||
Config.parse(ByteArrayInputStream(configText.toByteArray(StandardCharsets.UTF_8)))
|
||||
|
||||
// Config text is valid, now create the tunnel…
|
||||
newInstance(configText).show(parentFragmentManager, null)
|
||||
} catch (e: Exception) {
|
||||
onTunnelImportFinished(emptyList(), listOf<Throwable>(e))
|
||||
}
|
||||
}
|
||||
|
||||
private fun importTunnel(uri: Uri?) {
|
||||
val activity = activity
|
||||
if (activity == null || uri == null) {
|
||||
return
|
||||
}
|
||||
val contentResolver = activity.contentResolver
|
||||
|
||||
val futureTunnels = ArrayList<CompletableFuture<ObservableTunnel>>()
|
||||
val throwables = ArrayList<Throwable>()
|
||||
Application.getAsyncWorker().supplyAsync {
|
||||
val columns = arrayOf(OpenableColumns.DISPLAY_NAME)
|
||||
var name = ""
|
||||
contentResolver.query(uri, columns, null, null, null)?.use { cursor ->
|
||||
if (cursor.moveToFirst() && !cursor.isNull(0)) {
|
||||
name = cursor.getString(0)
|
||||
}
|
||||
}
|
||||
if (name.isEmpty()) {
|
||||
name = Uri.decode(uri.lastPathSegment)
|
||||
}
|
||||
var idx = name.lastIndexOf('/')
|
||||
if (idx >= 0) {
|
||||
require(idx < name.length - 1) { resources.getString(R.string.illegal_filename_error, name) }
|
||||
name = name.substring(idx + 1)
|
||||
}
|
||||
val isZip = name.toLowerCase(Locale.ROOT).endsWith(".zip")
|
||||
if (name.toLowerCase(Locale.ROOT).endsWith(".conf")) {
|
||||
name = name.substring(0, name.length - ".conf".length)
|
||||
} else {
|
||||
require(isZip) { resources.getString(R.string.bad_extension_error) }
|
||||
}
|
||||
|
||||
if (isZip) {
|
||||
ZipInputStream(contentResolver.openInputStream(uri)).use { zip ->
|
||||
val reader = BufferedReader(InputStreamReader(zip, StandardCharsets.UTF_8))
|
||||
var entry: ZipEntry?
|
||||
while (true) {
|
||||
entry = zip.nextEntry ?: break
|
||||
name = entry.name
|
||||
idx = name.lastIndexOf('/')
|
||||
if (idx >= 0) {
|
||||
if (idx >= name.length - 1) {
|
||||
continue
|
||||
}
|
||||
name = name.substring(name.lastIndexOf('/') + 1)
|
||||
}
|
||||
if (name.toLowerCase(Locale.ROOT).endsWith(".conf")) {
|
||||
name = name.substring(0, name.length - ".conf".length)
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
try {
|
||||
Config.parse(reader)
|
||||
} catch (e: Exception) {
|
||||
throwables.add(e)
|
||||
null
|
||||
}?.let {
|
||||
futureTunnels.add(Application.getTunnelManager().create(name, it).toCompletableFuture())
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
futureTunnels.add(
|
||||
Application.getTunnelManager().create(
|
||||
name,
|
||||
Config.parse(contentResolver.openInputStream(uri)!!)
|
||||
).toCompletableFuture()
|
||||
)
|
||||
}
|
||||
|
||||
if (futureTunnels.isEmpty()) {
|
||||
if (throwables.size == 1) {
|
||||
throw throwables[0]
|
||||
} else {
|
||||
require(throwables.isNotEmpty()) { resources.getString(R.string.no_configs_error) }
|
||||
}
|
||||
}
|
||||
CompletableFuture.allOf(*futureTunnels.toTypedArray())
|
||||
}.whenComplete { future, exception ->
|
||||
if (exception != null) {
|
||||
onTunnelImportFinished(emptyList(), listOf(exception))
|
||||
} else {
|
||||
future.whenComplete { _, _ ->
|
||||
val tunnels = mutableListOf<ObservableTunnel>()
|
||||
for (futureTunnel in futureTunnels) {
|
||||
val tunnel: ObservableTunnel? = try {
|
||||
futureTunnel.getNow(null)
|
||||
} catch (e: Exception) {
|
||||
throwables.add(e)
|
||||
null
|
||||
}
|
||||
|
||||
if (tunnel != null) {
|
||||
tunnels.add(tunnel)
|
||||
}
|
||||
}
|
||||
onTunnelImportFinished(tunnels, throwables)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
if (savedInstanceState != null) {
|
||||
val checkedItems = savedInstanceState.getIntegerArrayList(CHECKED_ITEMS)
|
||||
if (checkedItems != null) {
|
||||
for (i in checkedItems) actionModeListener.setItemChecked(i, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
when (requestCode) {
|
||||
REQUEST_IMPORT -> {
|
||||
if (resultCode == Activity.RESULT_OK && data != null) importTunnel(data.data)
|
||||
return
|
||||
}
|
||||
IntentIntegrator.REQUEST_CODE -> {
|
||||
val result = IntentIntegrator.parseActivityResult(requestCode, resultCode, data)
|
||||
if (result != null && result.contents != null) {
|
||||
importTunnel(result.contents)
|
||||
}
|
||||
return
|
||||
}
|
||||
else -> super.onActivityResult(requestCode, resultCode, data)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?): View? {
|
||||
super.onCreateView(inflater, container, savedInstanceState)
|
||||
binding = TunnelListFragmentBinding.inflate(inflater, container, false)
|
||||
binding?.apply {
|
||||
createFab.setOnClickListener {
|
||||
val bottomSheet = AddTunnelsSheet()
|
||||
bottomSheet.setTargetFragment(fragment, REQUEST_TARGET_FRAGMENT)
|
||||
bottomSheet.show(parentFragmentManager, "BOTTOM_SHEET")
|
||||
}
|
||||
executePendingBindings()
|
||||
setUpRoot(root as ViewGroup)
|
||||
setUpFAB(createFab)
|
||||
setUpScrollingContent(tunnelList, createFab)
|
||||
}
|
||||
return binding!!.root
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
binding = null
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
outState.putIntegerArrayList(CHECKED_ITEMS, actionModeListener.getCheckedItems())
|
||||
}
|
||||
|
||||
override fun onSelectedTunnelChanged(oldTunnel: ObservableTunnel?, newTunnel: ObservableTunnel?) {
|
||||
binding ?: return
|
||||
Application.getTunnelManager().tunnels.thenAccept { tunnels ->
|
||||
if (newTunnel != null) viewForTunnel(newTunnel, tunnels).setSingleSelected(true)
|
||||
if (oldTunnel != null) viewForTunnel(oldTunnel, tunnels).setSingleSelected(false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onTunnelDeletionFinished(count: Int, throwable: Throwable?) {
|
||||
val message: String
|
||||
if (throwable == null) {
|
||||
message = resources.getQuantityString(R.plurals.delete_success, count, count)
|
||||
} else {
|
||||
val error = ErrorMessages[throwable]
|
||||
message = resources.getQuantityString(R.plurals.delete_error, count, count, error)
|
||||
Log.e(TAG, message, throwable)
|
||||
}
|
||||
showSnackbar(message)
|
||||
}
|
||||
|
||||
private fun onTunnelImportFinished(tunnels: List<ObservableTunnel>, throwables: Collection<Throwable>) {
|
||||
var message = ""
|
||||
for (throwable in throwables) {
|
||||
val error = ErrorMessages[throwable]
|
||||
message = getString(R.string.import_error, error)
|
||||
Log.e(TAG, message, throwable)
|
||||
}
|
||||
if (tunnels.size == 1 && throwables.isEmpty())
|
||||
message = getString(R.string.import_success, tunnels[0].name)
|
||||
else if (tunnels.isEmpty() && throwables.size == 1)
|
||||
else if (throwables.isEmpty())
|
||||
message = resources.getQuantityString(R.plurals.import_total_success,
|
||||
tunnels.size, tunnels.size)
|
||||
else if (!throwables.isEmpty())
|
||||
message = resources.getQuantityString(R.plurals.import_partial_success,
|
||||
tunnels.size + throwables.size,
|
||||
tunnels.size, tunnels.size + throwables.size)
|
||||
showSnackbar(message)
|
||||
}
|
||||
|
||||
override fun onViewStateRestored(savedInstanceState: Bundle?) {
|
||||
super.onViewStateRestored(savedInstanceState)
|
||||
binding ?: return
|
||||
binding!!.fragment = this
|
||||
Application.getTunnelManager().tunnels.thenAccept { tunnels -> binding!!.tunnels = tunnels }
|
||||
val parent = this
|
||||
binding!!.rowConfigurationHandler = object : RowConfigurationHandler<TunnelListItemBinding, ObservableTunnel> {
|
||||
override fun onConfigureRow(binding: TunnelListItemBinding, item: ObservableTunnel, position: Int) {
|
||||
binding.fragment = parent
|
||||
binding.root.setOnClickListener {
|
||||
if (actionMode == null) {
|
||||
selectedTunnel = item
|
||||
} else {
|
||||
actionModeListener.toggleItemChecked(position)
|
||||
}
|
||||
}
|
||||
binding.root.setOnLongClickListener {
|
||||
actionModeListener.toggleItemChecked(position)
|
||||
true
|
||||
}
|
||||
if (actionMode != null)
|
||||
(binding.root as MultiselectableRelativeLayout).setMultiSelected(actionModeListener.checkedItems.contains(position))
|
||||
else
|
||||
(binding.root as MultiselectableRelativeLayout).setSingleSelected(selectedTunnel == item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showSnackbar(message: CharSequence) {
|
||||
binding?.let {
|
||||
Snackbar.make(it.mainContainer, message, Snackbar.LENGTH_LONG)
|
||||
.setAnchorView(it.createFab)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun viewForTunnel(tunnel: ObservableTunnel, tunnels: List<*>): MultiselectableRelativeLayout {
|
||||
return binding!!.tunnelList.findViewHolderForAdapterPosition(tunnels.indexOf(tunnel))!!.itemView as MultiselectableRelativeLayout
|
||||
}
|
||||
|
||||
private inner class ActionModeListener : ActionMode.Callback {
|
||||
val checkedItems: MutableCollection<Int> = HashSet()
|
||||
private var resources: Resources? = null
|
||||
|
||||
fun getCheckedItems(): ArrayList<Int> {
|
||||
return ArrayList(checkedItems)
|
||||
}
|
||||
|
||||
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.menu_action_delete -> {
|
||||
val copyCheckedItems = HashSet(checkedItems)
|
||||
Application.getTunnelManager().tunnels.thenAccept { tunnels ->
|
||||
val tunnelsToDelete = ArrayList<ObservableTunnel>()
|
||||
for (position in copyCheckedItems) tunnelsToDelete.add(tunnels[position])
|
||||
val futures = tunnelsToDelete.map { it.delete().toCompletableFuture() }.toTypedArray()
|
||||
CompletableFuture.allOf(*futures)
|
||||
.thenApply { futures.size }
|
||||
.whenComplete(this@TunnelListFragment::onTunnelDeletionFinished)
|
||||
}
|
||||
checkedItems.clear()
|
||||
mode.finish()
|
||||
true
|
||||
}
|
||||
R.id.menu_action_select_all -> {
|
||||
Application.getTunnelManager().tunnels.thenAccept { tunnels ->
|
||||
for (i in 0 until tunnels.size) {
|
||||
setItemChecked(i, true)
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
|
||||
actionMode = mode
|
||||
if (activity != null) {
|
||||
resources = activity!!.resources
|
||||
}
|
||||
mode.menuInflater.inflate(R.menu.tunnel_list_action_mode, menu)
|
||||
binding!!.tunnelList.adapter!!.notifyDataSetChanged()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onDestroyActionMode(mode: ActionMode) {
|
||||
actionMode = null
|
||||
resources = null
|
||||
checkedItems.clear()
|
||||
binding!!.tunnelList.adapter!!.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
|
||||
updateTitle(mode)
|
||||
return false
|
||||
}
|
||||
|
||||
fun setItemChecked(position: Int, checked: Boolean) {
|
||||
if (checked) {
|
||||
checkedItems.add(position)
|
||||
} else {
|
||||
checkedItems.remove(position)
|
||||
}
|
||||
val adapter = if (binding == null) null else binding!!.tunnelList.adapter
|
||||
if (actionMode == null && !checkedItems.isEmpty() && activity != null) {
|
||||
(activity as AppCompatActivity?)!!.startSupportActionMode(this)
|
||||
} else if (actionMode != null && checkedItems.isEmpty()) {
|
||||
actionMode!!.finish()
|
||||
}
|
||||
adapter?.notifyItemChanged(position)
|
||||
updateTitle(actionMode)
|
||||
}
|
||||
|
||||
fun toggleItemChecked(position: Int) {
|
||||
setItemChecked(position, !checkedItems.contains(position))
|
||||
}
|
||||
|
||||
private fun updateTitle(mode: ActionMode?) {
|
||||
if (mode == null) {
|
||||
return
|
||||
}
|
||||
val count = checkedItems.size
|
||||
if (count == 0) {
|
||||
mode.title = ""
|
||||
} else {
|
||||
mode.title = resources!!.getQuantityString(R.plurals.delete_title, count, count)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val REQUEST_IMPORT = 1
|
||||
private const val REQUEST_TARGET_FRAGMENT = 2
|
||||
private const val CHECKED_ITEMS = "CHECKED_ITEMS"
|
||||
private const val TAG = "WireGuard/TunnelListFragment"
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.model
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import androidx.databinding.BaseObservable
|
||||
import androidx.databinding.Bindable
|
||||
import com.wireguard.android.BR
|
||||
import com.wireguard.android.databinding.Keyed
|
||||
|
||||
class ApplicationData(val icon: Drawable, val name: String, val packageName: String, isExcludedFromTunnel: Boolean) : BaseObservable(), Keyed<String> {
|
||||
override val key = name
|
||||
|
||||
@get:Bindable
|
||||
var isExcludedFromTunnel = isExcludedFromTunnel
|
||||
set(value) {
|
||||
field = value
|
||||
notifyPropertyChanged(BR.excludedFromTunnel)
|
||||
}
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.model
|
||||
|
||||
import androidx.databinding.BaseObservable
|
||||
import androidx.databinding.Bindable
|
||||
import com.wireguard.android.BR
|
||||
import com.wireguard.android.backend.Statistics
|
||||
import com.wireguard.android.backend.Tunnel
|
||||
import com.wireguard.android.databinding.Keyed
|
||||
import com.wireguard.android.util.ExceptionLoggers
|
||||
import com.wireguard.config.Config
|
||||
import java9.util.concurrent.CompletableFuture
|
||||
import java9.util.concurrent.CompletionStage
|
||||
|
||||
/**
|
||||
* Encapsulates the volatile and nonvolatile state of a WireGuard tunnel.
|
||||
*/
|
||||
class ObservableTunnel internal constructor(
|
||||
private val manager: TunnelManager,
|
||||
private var name: String,
|
||||
config: Config?,
|
||||
state: Tunnel.State
|
||||
) : BaseObservable(), Keyed<String>, Tunnel {
|
||||
override val key = name
|
||||
|
||||
@Bindable
|
||||
override fun getName() = name
|
||||
|
||||
fun setNameAsync(name: String): CompletionStage<String> = if (name != this.name)
|
||||
manager.setTunnelName(this, name)
|
||||
else
|
||||
CompletableFuture.completedFuture(this.name)
|
||||
|
||||
fun onNameChanged(name: String): String {
|
||||
this.name = name
|
||||
notifyPropertyChanged(BR.name)
|
||||
return name
|
||||
}
|
||||
|
||||
|
||||
@get:Bindable
|
||||
var state = state
|
||||
private set
|
||||
|
||||
override fun onStateChange(newState: Tunnel.State) {
|
||||
onStateChanged(newState)
|
||||
}
|
||||
|
||||
fun onStateChanged(state: Tunnel.State): Tunnel.State {
|
||||
if (state != Tunnel.State.UP) onStatisticsChanged(null)
|
||||
this.state = state
|
||||
notifyPropertyChanged(BR.state)
|
||||
return state
|
||||
}
|
||||
|
||||
fun setStateAsync(state: Tunnel.State): CompletionStage<Tunnel.State> = if (state != this.state)
|
||||
manager.setTunnelState(this, state)
|
||||
else
|
||||
CompletableFuture.completedFuture(this.state)
|
||||
|
||||
|
||||
@get:Bindable
|
||||
var config = config
|
||||
get() {
|
||||
if (field == null)
|
||||
manager.getTunnelConfig(this).whenComplete(ExceptionLoggers.E)
|
||||
return field
|
||||
}
|
||||
private set
|
||||
|
||||
val configAsync: CompletionStage<Config>
|
||||
get() = if (config == null)
|
||||
manager.getTunnelConfig(this)
|
||||
else
|
||||
CompletableFuture.completedFuture(config)
|
||||
|
||||
fun setConfigAsync(config: Config): CompletionStage<Config> = if (config != this.config)
|
||||
manager.setTunnelConfig(this, config)
|
||||
else
|
||||
CompletableFuture.completedFuture(this.config)
|
||||
|
||||
fun onConfigChanged(config: Config?): Config? {
|
||||
this.config = config
|
||||
notifyPropertyChanged(BR.config)
|
||||
return config
|
||||
}
|
||||
|
||||
|
||||
@get:Bindable
|
||||
var statistics: Statistics? = null
|
||||
get() {
|
||||
if (field == null || field!!.isStale)
|
||||
manager.getTunnelStatistics(this).whenComplete(ExceptionLoggers.E)
|
||||
return field
|
||||
}
|
||||
private set
|
||||
|
||||
val statisticsAsync: CompletionStage<Statistics>
|
||||
get() = if (statistics == null || statistics!!.isStale)
|
||||
manager.getTunnelStatistics(this)
|
||||
else
|
||||
CompletableFuture.completedFuture(statistics)
|
||||
|
||||
fun onStatisticsChanged(statistics: Statistics?): Statistics? {
|
||||
this.statistics = statistics
|
||||
notifyPropertyChanged(BR.statistics)
|
||||
return statistics
|
||||
}
|
||||
|
||||
|
||||
fun delete(): CompletionStage<Void> = manager.delete(this)
|
||||
}
|
||||
@@ -1,236 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.model
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import androidx.databinding.BaseObservable
|
||||
import androidx.databinding.Bindable
|
||||
import com.wireguard.android.Application.Companion.get
|
||||
import com.wireguard.android.Application.Companion.getAsyncWorker
|
||||
import com.wireguard.android.Application.Companion.getBackend
|
||||
import com.wireguard.android.Application.Companion.getSharedPreferences
|
||||
import com.wireguard.android.Application.Companion.getTunnelManager
|
||||
import com.wireguard.android.BR
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.backend.Statistics
|
||||
import com.wireguard.android.backend.Tunnel
|
||||
import com.wireguard.android.configStore.ConfigStore
|
||||
import com.wireguard.android.databinding.ObservableSortedKeyedArrayList
|
||||
import com.wireguard.android.util.ExceptionLoggers
|
||||
import com.wireguard.config.Config
|
||||
import java9.util.concurrent.CompletableFuture
|
||||
import java9.util.concurrent.CompletionStage
|
||||
import java.util.ArrayList
|
||||
|
||||
/**
|
||||
* Maintains and mediates changes to the set of available WireGuard tunnels,
|
||||
*/
|
||||
class TunnelManager(private val configStore: ConfigStore) : BaseObservable() {
|
||||
val tunnels = CompletableFuture<ObservableSortedKeyedArrayList<String, ObservableTunnel>>()
|
||||
private val context: Context = get()
|
||||
private val delayedLoadRestoreTunnels = ArrayList<CompletableFuture<Void>>()
|
||||
private val tunnelMap: ObservableSortedKeyedArrayList<String, ObservableTunnel> = ObservableSortedKeyedArrayList(TunnelComparator)
|
||||
private var haveLoaded = false
|
||||
|
||||
private fun addToList(name: String, config: Config?, state: Tunnel.State): ObservableTunnel? {
|
||||
val tunnel = ObservableTunnel(this, name, config, state)
|
||||
tunnelMap.add(tunnel)
|
||||
return tunnel
|
||||
}
|
||||
|
||||
fun create(name: String, config: Config?): CompletionStage<ObservableTunnel> {
|
||||
if (Tunnel.isNameInvalid(name))
|
||||
return CompletableFuture.failedFuture(IllegalArgumentException(context.getString(R.string.tunnel_error_invalid_name)))
|
||||
if (tunnelMap.containsKey(name))
|
||||
return CompletableFuture.failedFuture(IllegalArgumentException(context.getString(R.string.tunnel_error_already_exists, name)))
|
||||
return getAsyncWorker().supplyAsync { configStore.create(name, config!!) }.thenApply { addToList(name, it, Tunnel.State.DOWN) }
|
||||
}
|
||||
|
||||
fun delete(tunnel: ObservableTunnel): CompletionStage<Void> {
|
||||
val originalState = tunnel.state
|
||||
val wasLastUsed = tunnel == lastUsedTunnel
|
||||
// Make sure nothing touches the tunnel.
|
||||
if (wasLastUsed)
|
||||
lastUsedTunnel = null
|
||||
tunnelMap.remove(tunnel)
|
||||
return getAsyncWorker().runAsync {
|
||||
if (originalState == Tunnel.State.UP)
|
||||
getBackend().setState(tunnel, Tunnel.State.DOWN, null)
|
||||
try {
|
||||
configStore.delete(tunnel.name)
|
||||
} catch (e: Exception) {
|
||||
if (originalState == Tunnel.State.UP)
|
||||
getBackend().setState(tunnel, Tunnel.State.UP, tunnel.config)
|
||||
throw e
|
||||
}
|
||||
}.whenComplete { _, e ->
|
||||
if (e == null)
|
||||
return@whenComplete
|
||||
// Failure, put the tunnel back.
|
||||
tunnelMap.add(tunnel)
|
||||
if (wasLastUsed)
|
||||
lastUsedTunnel = tunnel
|
||||
}
|
||||
}
|
||||
|
||||
@get:Bindable
|
||||
@SuppressLint("ApplySharedPref")
|
||||
var lastUsedTunnel: ObservableTunnel? = null
|
||||
private set(value) {
|
||||
if (value == field) return
|
||||
field = value
|
||||
notifyPropertyChanged(BR.lastUsedTunnel)
|
||||
if (value != null)
|
||||
getSharedPreferences().edit().putString(KEY_LAST_USED_TUNNEL, value.name).commit()
|
||||
else
|
||||
getSharedPreferences().edit().remove(KEY_LAST_USED_TUNNEL).commit()
|
||||
}
|
||||
|
||||
fun getTunnelConfig(tunnel: ObservableTunnel): CompletionStage<Config> = getAsyncWorker()
|
||||
.supplyAsync { configStore.load(tunnel.name) }.thenApply(tunnel::onConfigChanged)
|
||||
|
||||
|
||||
fun onCreate() {
|
||||
getAsyncWorker().supplyAsync { configStore.enumerate() }
|
||||
.thenAcceptBoth(getAsyncWorker().supplyAsync { getBackend().runningTunnelNames }, this::onTunnelsLoaded)
|
||||
.whenComplete(ExceptionLoggers.E)
|
||||
}
|
||||
|
||||
private fun onTunnelsLoaded(present: Iterable<String>, running: Collection<String>) {
|
||||
for (name in present)
|
||||
addToList(name, null, if (running.contains(name)) Tunnel.State.UP else Tunnel.State.DOWN)
|
||||
val lastUsedName = getSharedPreferences().getString(KEY_LAST_USED_TUNNEL, null)
|
||||
if (lastUsedName != null)
|
||||
lastUsedTunnel = tunnelMap[lastUsedName]
|
||||
var toComplete: Array<CompletableFuture<Void>>
|
||||
synchronized(delayedLoadRestoreTunnels) {
|
||||
haveLoaded = true
|
||||
toComplete = delayedLoadRestoreTunnels.toTypedArray()
|
||||
delayedLoadRestoreTunnels.clear()
|
||||
}
|
||||
restoreState(true).whenComplete { v: Void?, t: Throwable? ->
|
||||
for (f in toComplete) {
|
||||
if (t == null)
|
||||
f.complete(v)
|
||||
else
|
||||
f.completeExceptionally(t)
|
||||
}
|
||||
}
|
||||
tunnels.complete(tunnelMap)
|
||||
}
|
||||
|
||||
fun refreshTunnelStates() {
|
||||
getAsyncWorker().supplyAsync { getBackend().runningTunnelNames }
|
||||
.thenAccept { running: Set<String> -> for (tunnel in tunnelMap) tunnel.onStateChanged(if (running.contains(tunnel.name)) Tunnel.State.UP else Tunnel.State.DOWN) }
|
||||
.whenComplete(ExceptionLoggers.E)
|
||||
}
|
||||
|
||||
fun restoreState(force: Boolean): CompletionStage<Void> {
|
||||
if (!force && !getSharedPreferences().getBoolean(KEY_RESTORE_ON_BOOT, false))
|
||||
return CompletableFuture.completedFuture(null)
|
||||
synchronized(delayedLoadRestoreTunnels) {
|
||||
if (!haveLoaded) {
|
||||
val f = CompletableFuture<Void>()
|
||||
delayedLoadRestoreTunnels.add(f)
|
||||
return f
|
||||
}
|
||||
}
|
||||
val previouslyRunning = getSharedPreferences().getStringSet(KEY_RUNNING_TUNNELS, null)
|
||||
?: return CompletableFuture.completedFuture(null)
|
||||
return CompletableFuture.allOf(*tunnelMap.filter { previouslyRunning.contains(it.name) }.map { setTunnelState(it, Tunnel.State.UP).toCompletableFuture() }.toTypedArray())
|
||||
}
|
||||
|
||||
@SuppressLint("ApplySharedPref")
|
||||
fun saveState() {
|
||||
getSharedPreferences().edit().putStringSet(KEY_RUNNING_TUNNELS, tunnelMap.filter { it.state == Tunnel.State.UP }.map { it.name }.toSet()).commit()
|
||||
}
|
||||
|
||||
fun setTunnelConfig(tunnel: ObservableTunnel, config: Config): CompletionStage<Config> = getAsyncWorker().supplyAsync {
|
||||
getBackend().setState(tunnel, tunnel.state, config)
|
||||
configStore.save(tunnel.name, config)
|
||||
}.thenApply { tunnel.onConfigChanged(it) }
|
||||
|
||||
fun setTunnelName(tunnel: ObservableTunnel, name: String): CompletionStage<String> {
|
||||
if (Tunnel.isNameInvalid(name))
|
||||
return CompletableFuture.failedFuture(IllegalArgumentException(context.getString(R.string.tunnel_error_invalid_name)))
|
||||
if (tunnelMap.containsKey(name)) {
|
||||
return CompletableFuture.failedFuture(IllegalArgumentException(context.getString(R.string.tunnel_error_already_exists, name)))
|
||||
}
|
||||
val originalState = tunnel.state
|
||||
val wasLastUsed = tunnel == lastUsedTunnel
|
||||
// Make sure nothing touches the tunnel.
|
||||
if (wasLastUsed)
|
||||
lastUsedTunnel = null
|
||||
tunnelMap.remove(tunnel)
|
||||
return getAsyncWorker().supplyAsync {
|
||||
if (originalState == Tunnel.State.UP)
|
||||
getBackend().setState(tunnel, Tunnel.State.DOWN, null)
|
||||
configStore.rename(tunnel.name, name)
|
||||
val newName = tunnel.onNameChanged(name)
|
||||
if (originalState == Tunnel.State.UP)
|
||||
getBackend().setState(tunnel, Tunnel.State.UP, tunnel.config)
|
||||
newName
|
||||
}.whenComplete { _, e ->
|
||||
// On failure, we don't know what state the tunnel might be in. Fix that.
|
||||
if (e != null)
|
||||
getTunnelState(tunnel)
|
||||
// Add the tunnel back to the manager, under whatever name it thinks it has.
|
||||
tunnelMap.add(tunnel)
|
||||
if (wasLastUsed)
|
||||
lastUsedTunnel = tunnel
|
||||
}
|
||||
}
|
||||
|
||||
fun setTunnelState(tunnel: ObservableTunnel, state: Tunnel.State): CompletionStage<Tunnel.State> = tunnel.configAsync
|
||||
.thenCompose { getAsyncWorker().supplyAsync { getBackend().setState(tunnel, state, it) } }
|
||||
.whenComplete { newState, e ->
|
||||
// Ensure onStateChanged is always called (failure or not), and with the correct state.
|
||||
tunnel.onStateChanged(if (e == null) newState else tunnel.state)
|
||||
if (e == null && newState == Tunnel.State.UP)
|
||||
lastUsedTunnel = tunnel
|
||||
saveState()
|
||||
}
|
||||
|
||||
class IntentReceiver : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent?) {
|
||||
val manager = getTunnelManager()
|
||||
if (intent == null) return
|
||||
val action = intent.action ?: return
|
||||
if ("com.wireguard.android.action.REFRESH_TUNNEL_STATES" == action) {
|
||||
manager.refreshTunnelStates()
|
||||
return
|
||||
}
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || !getSharedPreferences().getBoolean("allow_remote_control_intents", false))
|
||||
return
|
||||
val state: Tunnel.State
|
||||
state = when (action) {
|
||||
"com.wireguard.android.action.SET_TUNNEL_UP" -> Tunnel.State.UP
|
||||
"com.wireguard.android.action.SET_TUNNEL_DOWN" -> Tunnel.State.DOWN
|
||||
else -> return
|
||||
}
|
||||
val tunnelName = intent.getStringExtra("tunnel") ?: return
|
||||
manager.tunnels.thenAccept {
|
||||
val tunnel = it[tunnelName] ?: return@thenAccept
|
||||
manager.setTunnelState(tunnel, state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getTunnelState(tunnel: ObservableTunnel): CompletionStage<Tunnel.State> = getAsyncWorker()
|
||||
.supplyAsync { getBackend().getState(tunnel) }.thenApply(tunnel::onStateChanged)
|
||||
|
||||
fun getTunnelStatistics(tunnel: ObservableTunnel): CompletionStage<Statistics> = getAsyncWorker()
|
||||
.supplyAsync { getBackend().getStatistics(tunnel) }.thenApply(tunnel::onStatisticsChanged)
|
||||
|
||||
companion object {
|
||||
private const val KEY_LAST_USED_TUNNEL = "last_used_tunnel"
|
||||
private const val KEY_RESTORE_ON_BOOT = "restore_on_boot"
|
||||
private const val KEY_RUNNING_TUNNELS = "enabled_configs"
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2020 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.preference
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.util.AttributeSet
|
||||
import androidx.preference.Preference
|
||||
import com.wireguard.android.Application
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.activity.SettingsActivity
|
||||
import com.wireguard.android.backend.Tunnel
|
||||
import com.wireguard.android.backend.WgQuickBackend
|
||||
import java9.util.concurrent.CompletableFuture
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
class KernelModuleDisablerPreference(context: Context, attrs: AttributeSet?) : Preference(context, attrs) {
|
||||
private var state = if (Application.getBackend() is WgQuickBackend) State.ENABLED else State.DISABLED
|
||||
|
||||
override fun getSummary() = context.getString(state.summaryResourceId)
|
||||
|
||||
override fun getTitle() = context.getString(state.titleResourceId)
|
||||
|
||||
@SuppressLint("ApplySharedPref")
|
||||
override fun onClick() {
|
||||
if (state == State.DISABLED) {
|
||||
setState(State.ENABLING)
|
||||
Application.getSharedPreferences().edit().putBoolean("disable_kernel_module", false).commit()
|
||||
} else if (state == State.ENABLED) {
|
||||
setState(State.DISABLING)
|
||||
Application.getSharedPreferences().edit().putBoolean("disable_kernel_module", true).commit()
|
||||
}
|
||||
Application.getAsyncWorker().runAsync {
|
||||
Application.getTunnelManager().tunnels.thenApply { observableTunnels ->
|
||||
val downings = observableTunnels.map { it.setStateAsync(Tunnel.State.DOWN).toCompletableFuture() }.toTypedArray()
|
||||
CompletableFuture.allOf(*downings).thenRun {
|
||||
val restartIntent = Intent(context, SettingsActivity::class.java)
|
||||
restartIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
restartIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
Application.get().startActivity(restartIntent)
|
||||
exitProcess(0)
|
||||
}
|
||||
}.join()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setState(state: State) {
|
||||
if (this.state == state) return
|
||||
this.state = state
|
||||
if (isEnabled != state.shouldEnableView) isEnabled = state.shouldEnableView
|
||||
notifyChanged()
|
||||
}
|
||||
|
||||
private enum class State(val titleResourceId: Int, val summaryResourceId: Int, val shouldEnableView: Boolean) {
|
||||
ENABLED(R.string.module_disabler_enabled_title, R.string.module_disabler_enabled_summary, true),
|
||||
DISABLED(R.string.module_disabler_disabled_title, R.string.module_disabler_disabled_summary, true),
|
||||
ENABLING(R.string.module_disabler_disabled_title, R.string.success_application_will_restart, false),
|
||||
DISABLING(R.string.module_disabler_enabled_title, R.string.success_application_will_restart, false);
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2019 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.preference
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.system.OsConstants
|
||||
import android.util.AttributeSet
|
||||
import android.widget.Toast
|
||||
import androidx.preference.Preference
|
||||
import com.wireguard.android.Application
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.activity.SettingsActivity
|
||||
import com.wireguard.android.util.ErrorMessages
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
class ModuleDownloaderPreference(context: Context, attrs: AttributeSet?) : Preference(context, attrs) {
|
||||
private var state = State.INITIAL
|
||||
|
||||
override fun getSummary() = context.getString(state.messageResourceId)
|
||||
|
||||
override fun getTitle() = context.getString(R.string.module_installer_title)
|
||||
|
||||
override fun onClick() {
|
||||
setState(State.WORKING)
|
||||
Application.getAsyncWorker().supplyAsync(Application.getModuleLoader()::download).whenComplete(this::onDownloadResult)
|
||||
}
|
||||
|
||||
@SuppressLint("ApplySharedPref")
|
||||
private fun onDownloadResult(result: Int, throwable: Throwable?) {
|
||||
when {
|
||||
throwable != null -> {
|
||||
setState(State.FAILURE)
|
||||
Toast.makeText(context, ErrorMessages[throwable], Toast.LENGTH_LONG).show()
|
||||
}
|
||||
result == OsConstants.ENOENT -> setState(State.NOTFOUND)
|
||||
result == OsConstants.EXIT_SUCCESS -> {
|
||||
setState(State.SUCCESS)
|
||||
Application.getSharedPreferences().edit().remove("disable_kernel_module").commit()
|
||||
Application.getAsyncWorker().runAsync {
|
||||
val restartIntent = Intent(context, SettingsActivity::class.java)
|
||||
restartIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
restartIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
Application.get().startActivity(restartIntent)
|
||||
exitProcess(0)
|
||||
}
|
||||
}
|
||||
else -> setState(State.FAILURE)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setState(state: State) {
|
||||
if (this.state == state) return
|
||||
this.state = state
|
||||
if (isEnabled != state.shouldEnableView) isEnabled = state.shouldEnableView
|
||||
notifyChanged()
|
||||
}
|
||||
|
||||
private enum class State(val messageResourceId: Int, val shouldEnableView: Boolean) {
|
||||
INITIAL(R.string.module_installer_initial, true),
|
||||
FAILURE(R.string.module_installer_error, true),
|
||||
WORKING(R.string.module_installer_working, false),
|
||||
SUCCESS(R.string.success_application_will_restart, false),
|
||||
NOTFOUND(R.string.module_installer_not_found, false);
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.preference
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import androidx.preference.Preference
|
||||
import com.wireguard.android.Application
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.util.ToolsInstaller
|
||||
|
||||
/**
|
||||
* Preference implementing a button that asynchronously runs `ToolsInstaller` and displays the
|
||||
* result as the preference summary.
|
||||
*/
|
||||
class ToolsInstallerPreference(context: Context, attrs: AttributeSet?) : Preference(context, attrs) {
|
||||
private var state = State.INITIAL
|
||||
|
||||
override fun getSummary() = context.getString(state.messageResourceId)
|
||||
|
||||
override fun getTitle() = context.getString(R.string.tools_installer_title)
|
||||
|
||||
override fun onAttached() {
|
||||
super.onAttached()
|
||||
Application.getAsyncWorker().supplyAsync(Application.getToolsInstaller()::areInstalled).whenComplete(this::onCheckResult)
|
||||
}
|
||||
|
||||
private fun onCheckResult(state: Int, throwable: Throwable?) {
|
||||
when {
|
||||
throwable != null || state == ToolsInstaller.ERROR -> setState(State.INITIAL)
|
||||
state and ToolsInstaller.YES == ToolsInstaller.YES -> setState(State.ALREADY)
|
||||
state and (ToolsInstaller.MAGISK or ToolsInstaller.NO) == ToolsInstaller.MAGISK or ToolsInstaller.NO -> setState(State.INITIAL_MAGISK)
|
||||
state and (ToolsInstaller.SYSTEM or ToolsInstaller.NO) == ToolsInstaller.SYSTEM or ToolsInstaller.NO -> setState(State.INITIAL_SYSTEM)
|
||||
else -> setState(State.INITIAL)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onClick() {
|
||||
setState(State.WORKING)
|
||||
Application.getAsyncWorker().supplyAsync { Application.getToolsInstaller().install() }.whenComplete { result: Int, throwable: Throwable? -> onInstallResult(result, throwable) }
|
||||
}
|
||||
|
||||
private fun onInstallResult(result: Int, throwable: Throwable?) {
|
||||
when {
|
||||
throwable != null -> setState(State.FAILURE)
|
||||
result and (ToolsInstaller.YES or ToolsInstaller.MAGISK) == ToolsInstaller.YES or ToolsInstaller.MAGISK -> setState(State.SUCCESS_MAGISK)
|
||||
result and (ToolsInstaller.YES or ToolsInstaller.SYSTEM) == ToolsInstaller.YES or ToolsInstaller.SYSTEM -> setState(State.SUCCESS_SYSTEM)
|
||||
else -> setState(State.FAILURE)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setState(state: State) {
|
||||
if (this.state == state) return
|
||||
this.state = state
|
||||
if (isEnabled != state.shouldEnableView) isEnabled = state.shouldEnableView
|
||||
notifyChanged()
|
||||
}
|
||||
|
||||
private enum class State(val messageResourceId: Int, val shouldEnableView: Boolean) {
|
||||
INITIAL(R.string.tools_installer_initial, true),
|
||||
ALREADY(R.string.tools_installer_already, false),
|
||||
FAILURE(R.string.tools_installer_failure, true),
|
||||
WORKING(R.string.tools_installer_working, false),
|
||||
INITIAL_SYSTEM(R.string.tools_installer_initial_system, true),
|
||||
SUCCESS_SYSTEM(R.string.tools_installer_success_system, false),
|
||||
INITIAL_MAGISK(R.string.tools_installer_initial_magisk, true),
|
||||
SUCCESS_MAGISK(R.string.tools_installer_success_magisk, false);
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.preference
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.util.AttributeSet
|
||||
import androidx.preference.Preference
|
||||
import com.wireguard.android.Application
|
||||
import com.wireguard.android.BuildConfig
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.backend.Backend
|
||||
import com.wireguard.android.backend.GoBackend
|
||||
import com.wireguard.android.backend.WgQuickBackend
|
||||
import java.util.Locale
|
||||
|
||||
class VersionPreference(context: Context, attrs: AttributeSet?) : Preference(context, attrs) {
|
||||
private var versionSummary: String? = null
|
||||
|
||||
override fun getSummary() = versionSummary
|
||||
|
||||
override fun getTitle() = context.getString(R.string.version_title, BuildConfig.VERSION_NAME)
|
||||
|
||||
override fun onClick() {
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.data = Uri.parse("https://www.wireguard.com/")
|
||||
try {
|
||||
context.startActivity(intent)
|
||||
} catch (_: ActivityNotFoundException) {
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private fun getBackendPrettyName(context: Context, backend: Backend) = when (backend) {
|
||||
is WgQuickBackend -> context.getString(R.string.type_name_kernel_module)
|
||||
is GoBackend -> context.getString(R.string.type_name_go_userspace)
|
||||
else -> ""
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
Application.getBackendAsync().thenAccept { backend ->
|
||||
versionSummary = getContext().getString(R.string.version_summary_checking, getBackendPrettyName(context, backend).toLowerCase(Locale.ENGLISH))
|
||||
Application.getAsyncWorker().supplyAsync(backend::getVersion).whenComplete { version, exception ->
|
||||
versionSummary = if (exception == null)
|
||||
getContext().getString(R.string.version_summary, getBackendPrettyName(context, backend), version)
|
||||
else
|
||||
getContext().getString(R.string.version_summary_unknown, getBackendPrettyName(context, backend).toLowerCase(Locale.ENGLISH))
|
||||
notifyChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.preference
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.util.AttributeSet
|
||||
import android.util.Log
|
||||
import androidx.preference.Preference
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.wireguard.android.Application
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.model.ObservableTunnel
|
||||
import com.wireguard.android.util.BiometricAuthenticator
|
||||
import com.wireguard.android.util.DownloadsFileSaver
|
||||
import com.wireguard.android.util.ErrorMessages
|
||||
import com.wireguard.android.util.FragmentUtils
|
||||
import java9.util.concurrent.CompletableFuture
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
/**
|
||||
* Preference implementing a button that asynchronously exports config zips.
|
||||
*/
|
||||
class ZipExporterPreference(context: Context, attrs: AttributeSet?) : Preference(context, attrs) {
|
||||
private var exportedFilePath: String? = null
|
||||
|
||||
private fun exportZip() {
|
||||
Application.getTunnelManager().tunnels.thenAccept(this::exportZip)
|
||||
}
|
||||
|
||||
private fun exportZip(tunnels: List<ObservableTunnel>) {
|
||||
val futureConfigs = tunnels.map { it.configAsync.toCompletableFuture() }.toTypedArray()
|
||||
if (futureConfigs.isEmpty()) {
|
||||
exportZipComplete(null, IllegalArgumentException(
|
||||
context.getString(R.string.no_tunnels_error)))
|
||||
return
|
||||
}
|
||||
CompletableFuture.allOf(*futureConfigs)
|
||||
.whenComplete { _, exception ->
|
||||
Application.getAsyncWorker().supplyAsync {
|
||||
if (exception != null) throw exception
|
||||
val outputFile = DownloadsFileSaver.save(context, "wireguard-export.zip", "application/zip", true)
|
||||
try {
|
||||
ZipOutputStream(outputFile.outputStream).use { zip ->
|
||||
for (i in futureConfigs.indices) {
|
||||
zip.putNextEntry(ZipEntry(tunnels[i].name + ".conf"))
|
||||
zip.write(futureConfigs[i].getNow(null)!!.toWgQuickString().toByteArray(StandardCharsets.UTF_8))
|
||||
}
|
||||
zip.closeEntry()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
outputFile.delete()
|
||||
throw e
|
||||
}
|
||||
outputFile.fileName
|
||||
}.whenComplete(this::exportZipComplete)
|
||||
}
|
||||
}
|
||||
|
||||
private fun exportZipComplete(filePath: String?, throwable: Throwable?) {
|
||||
if (throwable != null) {
|
||||
val error = ErrorMessages[throwable]
|
||||
val message = context.getString(R.string.zip_export_error, error)
|
||||
Log.e(TAG, message, throwable)
|
||||
Snackbar.make(
|
||||
FragmentUtils.getPrefActivity(this).findViewById(android.R.id.content),
|
||||
message, Snackbar.LENGTH_LONG).show()
|
||||
isEnabled = true
|
||||
} else {
|
||||
exportedFilePath = filePath
|
||||
notifyChanged()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getSummary() = if (exportedFilePath == null) context.getString(R.string.zip_export_summary) else context.getString(R.string.zip_export_success, exportedFilePath)
|
||||
|
||||
override fun getTitle() = context.getString(R.string.zip_export_title)
|
||||
|
||||
override fun onClick() {
|
||||
val prefActivity = FragmentUtils.getPrefActivity(this)
|
||||
val fragment = prefActivity.supportFragmentManager.fragments.first()
|
||||
BiometricAuthenticator.authenticate(R.string.biometric_prompt_zip_exporter_title, fragment) {
|
||||
when (it) {
|
||||
// When we have successful authentication, or when there is no biometric hardware available.
|
||||
is BiometricAuthenticator.Result.Success, is BiometricAuthenticator.Result.HardwareUnavailableOrDisabled -> {
|
||||
prefActivity.ensurePermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE)) { _, grantResults ->
|
||||
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
isEnabled = false
|
||||
exportZip()
|
||||
}
|
||||
}
|
||||
}
|
||||
is BiometricAuthenticator.Result.Failure -> {
|
||||
Snackbar.make(
|
||||
prefActivity.findViewById(android.R.id.content),
|
||||
it.message,
|
||||
Snackbar.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "WireGuard/ZipExporterPreference"
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2017-2020 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.util
|
||||
|
||||
import android.os.Handler
|
||||
import java9.util.concurrent.CompletableFuture
|
||||
import java9.util.concurrent.CompletionStage
|
||||
import java.util.concurrent.Executor
|
||||
|
||||
/**
|
||||
* Helper class for running asynchronous tasks and ensuring they are completed on the main thread.
|
||||
*/
|
||||
|
||||
class AsyncWorker(private val executor: Executor, private val handler: Handler) {
|
||||
|
||||
fun runAsync(run: () -> Unit): CompletionStage<Void> {
|
||||
val future = CompletableFuture<Void>()
|
||||
executor.execute {
|
||||
try {
|
||||
run()
|
||||
handler.post { future.complete(null) }
|
||||
} catch (t: Throwable) {
|
||||
handler.post { future.completeExceptionally(t) }
|
||||
}
|
||||
}
|
||||
return future
|
||||
}
|
||||
|
||||
fun <T> supplyAsync(get: () -> T?): CompletionStage<T> {
|
||||
val future = CompletableFuture<T>()
|
||||
executor.execute {
|
||||
try {
|
||||
val result = get()
|
||||
handler.post { future.complete(result) }
|
||||
} catch (t: Throwable) {
|
||||
handler.post { future.completeExceptionally(t) }
|
||||
}
|
||||
}
|
||||
return future
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2020 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.util
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.KeyguardManager
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.util.Log
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.biometric.BiometricConstants
|
||||
import androidx.biometric.BiometricManager
|
||||
import androidx.biometric.BiometricPrompt
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.wireguard.android.R
|
||||
|
||||
|
||||
object BiometricAuthenticator {
|
||||
private const val TAG = "WireGuard/BiometricAuthenticator"
|
||||
private val handler = Handler()
|
||||
|
||||
sealed class Result {
|
||||
data class Success(val cryptoObject: BiometricPrompt.CryptoObject?) : Result()
|
||||
data class Failure(val code: Int?, val message: CharSequence) : Result()
|
||||
object HardwareUnavailableOrDisabled : Result()
|
||||
object Cancelled : Result()
|
||||
}
|
||||
|
||||
@SuppressLint("PrivateApi")
|
||||
private fun isPinEnabled(context: Context): Boolean {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
|
||||
return context.getSystemService<KeyguardManager>()!!.isDeviceSecure
|
||||
return try {
|
||||
val lockUtilsClass = Class.forName("com.android.internal.widget.LockPatternUtils")
|
||||
val lockUtils = lockUtilsClass.getConstructor(Context::class.java).newInstance(context)
|
||||
val method = lockUtilsClass.getMethod("isLockScreenDisabled")
|
||||
!(method.invoke(lockUtils) as Boolean)
|
||||
} catch (e: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun authenticate(
|
||||
@StringRes dialogTitleRes: Int,
|
||||
fragment: Fragment,
|
||||
callback: (Result) -> Unit
|
||||
) {
|
||||
val authCallback = object : BiometricPrompt.AuthenticationCallback() {
|
||||
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
|
||||
super.onAuthenticationError(errorCode, errString)
|
||||
Log.d(TAG, "BiometricAuthentication error: errorCode=$errorCode, msg=$errString")
|
||||
callback(when (errorCode) {
|
||||
BiometricConstants.ERROR_CANCELED, BiometricConstants.ERROR_USER_CANCELED,
|
||||
BiometricConstants.ERROR_NEGATIVE_BUTTON -> {
|
||||
Result.Cancelled
|
||||
}
|
||||
BiometricConstants.ERROR_HW_NOT_PRESENT, BiometricConstants.ERROR_HW_UNAVAILABLE,
|
||||
BiometricConstants.ERROR_NO_BIOMETRICS, BiometricConstants.ERROR_NO_DEVICE_CREDENTIAL -> {
|
||||
Result.HardwareUnavailableOrDisabled
|
||||
}
|
||||
else -> Result.Failure(errorCode, fragment.getString(R.string.biometric_auth_error_reason, errString))
|
||||
})
|
||||
}
|
||||
|
||||
override fun onAuthenticationFailed() {
|
||||
super.onAuthenticationFailed()
|
||||
callback(Result.Failure(null, fragment.getString(R.string.biometric_auth_error)))
|
||||
}
|
||||
|
||||
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
|
||||
super.onAuthenticationSucceeded(result)
|
||||
callback(Result.Success(result.cryptoObject))
|
||||
}
|
||||
}
|
||||
val biometricPrompt = BiometricPrompt(fragment, { handler.post(it) }, authCallback)
|
||||
val promptInfo = BiometricPrompt.PromptInfo.Builder()
|
||||
.setTitle(fragment.getString(dialogTitleRes))
|
||||
.setDeviceCredentialAllowed(true)
|
||||
.build()
|
||||
if (BiometricManager.from(fragment.requireContext()).canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS || isPinEnabled(fragment.requireContext())) {
|
||||
biometricPrompt.authenticate(promptInfo)
|
||||
} else {
|
||||
callback(Result.HardwareUnavailableOrDisabled)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2019 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.util
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.provider.MediaStore
|
||||
import android.provider.MediaStore.MediaColumns
|
||||
import com.wireguard.android.R
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.OutputStream
|
||||
|
||||
object DownloadsFileSaver {
|
||||
@Throws(Exception::class)
|
||||
fun save(context: Context, name: String, mimeType: String?, overwriteExisting: Boolean) = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
val contentResolver = context.contentResolver
|
||||
if (overwriteExisting)
|
||||
contentResolver.delete(MediaStore.Downloads.EXTERNAL_CONTENT_URI, String.format("%s = ?", MediaColumns.DISPLAY_NAME), arrayOf(name))
|
||||
val contentValues = ContentValues()
|
||||
contentValues.put(MediaColumns.DISPLAY_NAME, name)
|
||||
contentValues.put(MediaColumns.MIME_TYPE, mimeType)
|
||||
val contentUri = contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)
|
||||
?: throw IOException(context.getString(R.string.create_downloads_file_error))
|
||||
val contentStream = contentResolver.openOutputStream(contentUri)
|
||||
?: throw IOException(context.getString(R.string.create_downloads_file_error))
|
||||
@Suppress("DEPRECATION") var cursor = contentResolver.query(contentUri, arrayOf(MediaColumns.DATA), null, null, null)
|
||||
var path: String? = null
|
||||
if (cursor != null) {
|
||||
try {
|
||||
if (cursor.moveToFirst())
|
||||
path = cursor.getString(0)
|
||||
} finally {
|
||||
cursor.close()
|
||||
}
|
||||
}
|
||||
if (path == null) {
|
||||
path = "Download/"
|
||||
cursor = contentResolver.query(contentUri, arrayOf(MediaColumns.DISPLAY_NAME), null, null, null)
|
||||
if (cursor != null) {
|
||||
try {
|
||||
if (cursor.moveToFirst())
|
||||
path += cursor.getString(0)
|
||||
} finally {
|
||||
cursor.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
DownloadsFile(context, contentStream, path, contentUri)
|
||||
} else {
|
||||
@Suppress("DEPRECATION") val path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
||||
val file = File(path, name)
|
||||
if (!path.isDirectory && !path.mkdirs())
|
||||
throw IOException(context.getString(R.string.create_output_dir_error))
|
||||
DownloadsFile(context, FileOutputStream(file), file.absolutePath, null)
|
||||
}
|
||||
|
||||
class DownloadsFile(private val context: Context, val outputStream: OutputStream, val fileName: String, private val uri: Uri?) {
|
||||
fun delete() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
|
||||
context.contentResolver.delete(uri!!, null, null)
|
||||
else
|
||||
File(fileName).delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.util
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.os.RemoteException
|
||||
import com.wireguard.android.Application.Companion.get
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.backend.BackendException
|
||||
import com.wireguard.android.util.RootShell.RootShellException
|
||||
import com.wireguard.config.BadConfigException
|
||||
import com.wireguard.config.InetEndpoint
|
||||
import com.wireguard.config.InetNetwork
|
||||
import com.wireguard.config.ParseException
|
||||
import com.wireguard.crypto.Key
|
||||
import com.wireguard.crypto.KeyFormatException
|
||||
import java.net.InetAddress
|
||||
|
||||
object ErrorMessages {
|
||||
private val BCE_REASON_MAP = mapOf(
|
||||
BadConfigException.Reason.INVALID_KEY to R.string.bad_config_reason_invalid_key,
|
||||
BadConfigException.Reason.INVALID_NUMBER to R.string.bad_config_reason_invalid_number,
|
||||
BadConfigException.Reason.INVALID_VALUE to R.string.bad_config_reason_invalid_value,
|
||||
BadConfigException.Reason.MISSING_ATTRIBUTE to R.string.bad_config_reason_missing_attribute,
|
||||
BadConfigException.Reason.MISSING_SECTION to R.string.bad_config_reason_missing_section,
|
||||
BadConfigException.Reason.SYNTAX_ERROR to R.string.bad_config_reason_syntax_error,
|
||||
BadConfigException.Reason.UNKNOWN_ATTRIBUTE to R.string.bad_config_reason_unknown_attribute,
|
||||
BadConfigException.Reason.UNKNOWN_SECTION to R.string.bad_config_reason_unknown_section
|
||||
)
|
||||
private val BE_REASON_MAP = mapOf(
|
||||
BackendException.Reason.UNKNOWN_KERNEL_MODULE_NAME to R.string.module_version_error,
|
||||
BackendException.Reason.WG_QUICK_CONFIG_ERROR_CODE to R.string.tunnel_config_error,
|
||||
BackendException.Reason.TUNNEL_MISSING_CONFIG to R.string.no_config_error,
|
||||
BackendException.Reason.VPN_NOT_AUTHORIZED to R.string.vpn_not_authorized_error,
|
||||
BackendException.Reason.UNABLE_TO_START_VPN to R.string.vpn_start_error,
|
||||
BackendException.Reason.TUN_CREATION_ERROR to R.string.tun_create_error,
|
||||
BackendException.Reason.GO_ACTIVATION_ERROR_CODE to R.string.tunnel_on_error
|
||||
)
|
||||
private val KFE_FORMAT_MAP = mapOf(
|
||||
Key.Format.BASE64 to R.string.key_length_explanation_base64,
|
||||
Key.Format.BINARY to R.string.key_length_explanation_binary,
|
||||
Key.Format.HEX to R.string.key_length_explanation_hex
|
||||
)
|
||||
private val KFE_TYPE_MAP = mapOf(
|
||||
KeyFormatException.Type.CONTENTS to R.string.key_contents_error,
|
||||
KeyFormatException.Type.LENGTH to R.string.key_length_error
|
||||
)
|
||||
private val PE_CLASS_MAP = mapOf(
|
||||
InetAddress::class.java to R.string.parse_error_inet_address,
|
||||
InetEndpoint::class.java to R.string.parse_error_inet_endpoint,
|
||||
InetNetwork::class.java to R.string.parse_error_inet_network,
|
||||
Int::class.java to R.string.parse_error_integer
|
||||
)
|
||||
private val RSE_REASON_MAP = mapOf(
|
||||
RootShellException.Reason.NO_ROOT_ACCESS to R.string.error_root,
|
||||
RootShellException.Reason.SHELL_MARKER_COUNT_ERROR to R.string.shell_marker_count_error,
|
||||
RootShellException.Reason.SHELL_EXIT_STATUS_READ_ERROR to R.string.shell_exit_status_read_error,
|
||||
RootShellException.Reason.SHELL_START_ERROR to R.string.shell_start_error,
|
||||
RootShellException.Reason.CREATE_BIN_DIR_ERROR to R.string.create_bin_dir_error,
|
||||
RootShellException.Reason.CREATE_TEMP_DIR_ERROR to R.string.create_temp_dir_error
|
||||
)
|
||||
|
||||
operator fun get(throwable: Throwable?): String {
|
||||
val resources = get().resources
|
||||
if (throwable == null) return resources.getString(R.string.unknown_error)
|
||||
val rootCause = rootCause(throwable)
|
||||
return when {
|
||||
rootCause is BadConfigException -> {
|
||||
val reason = getBadConfigExceptionReason(resources, rootCause)
|
||||
val context = if (rootCause.location == BadConfigException.Location.TOP_LEVEL) {
|
||||
resources.getString(R.string.bad_config_context_top_level, rootCause.section.getName())
|
||||
} else {
|
||||
resources.getString(R.string.bad_config_context, rootCause.section.getName(), rootCause.location.getName())
|
||||
}
|
||||
val explanation = getBadConfigExceptionExplanation(resources, rootCause)
|
||||
resources.getString(R.string.bad_config_error, reason, context) + explanation
|
||||
}
|
||||
rootCause is BackendException -> {
|
||||
resources.getString(BE_REASON_MAP.getValue(rootCause.reason), *rootCause.format)
|
||||
}
|
||||
rootCause is RootShellException -> {
|
||||
resources.getString(RSE_REASON_MAP.getValue(rootCause.reason), *rootCause.format)
|
||||
}
|
||||
rootCause.message != null -> {
|
||||
rootCause.message!!
|
||||
}
|
||||
else -> {
|
||||
val errorType = rootCause.javaClass.simpleName
|
||||
resources.getString(R.string.generic_error, errorType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getBadConfigExceptionExplanation(resources: Resources,
|
||||
bce: BadConfigException): String {
|
||||
if (bce.cause is KeyFormatException) {
|
||||
val kfe = bce.cause as KeyFormatException?
|
||||
if (kfe!!.type == KeyFormatException.Type.LENGTH) return resources.getString(KFE_FORMAT_MAP.getValue(kfe.format))
|
||||
} else if (bce.cause is ParseException) {
|
||||
val pe = bce.cause as ParseException?
|
||||
if (pe!!.message != null) return ": ${pe.message}"
|
||||
} else if (bce.location == BadConfigException.Location.LISTEN_PORT) {
|
||||
return resources.getString(R.string.bad_config_explanation_udp_port)
|
||||
} else if (bce.location == BadConfigException.Location.MTU) {
|
||||
return resources.getString(R.string.bad_config_explanation_positive_number)
|
||||
} else if (bce.location == BadConfigException.Location.PERSISTENT_KEEPALIVE) {
|
||||
return resources.getString(R.string.bad_config_explanation_pka)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
private fun getBadConfigExceptionReason(resources: Resources,
|
||||
bce: BadConfigException): String {
|
||||
if (bce.cause is KeyFormatException) {
|
||||
val kfe = bce.cause as KeyFormatException?
|
||||
return resources.getString(KFE_TYPE_MAP.getValue(kfe!!.type))
|
||||
} else if (bce.cause is ParseException) {
|
||||
val pe = bce.cause as ParseException?
|
||||
val type = resources.getString((if (PE_CLASS_MAP.containsKey(pe!!.parsingClass)) PE_CLASS_MAP[pe.parsingClass] else R.string.parse_error_generic)!!)
|
||||
return resources.getString(R.string.parse_error_reason, type, pe.text)
|
||||
}
|
||||
return resources.getString(BCE_REASON_MAP.getValue(bce.reason), bce.text)
|
||||
}
|
||||
|
||||
private fun rootCause(throwable: Throwable): Throwable {
|
||||
var cause = throwable
|
||||
while (cause.cause != null) {
|
||||
if (cause is BadConfigException || cause is BackendException ||
|
||||
cause is RootShellException) break
|
||||
val nextCause = cause.cause!!
|
||||
if (nextCause is RemoteException) break
|
||||
cause = nextCause
|
||||
}
|
||||
return cause
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.util
|
||||
|
||||
import android.util.Log
|
||||
import java9.util.function.BiConsumer
|
||||
|
||||
/**
|
||||
* Helpers for logging exceptions from asynchronous tasks. These can be passed to
|
||||
* `CompletionStage.whenComplete()` at the end of an asynchronous future chain.
|
||||
*/
|
||||
enum class ExceptionLoggers(private val priority: Int) : BiConsumer<Any?, Throwable?> {
|
||||
D(Log.DEBUG), E(Log.ERROR);
|
||||
|
||||
override fun accept(result: Any?, throwable: Throwable?) {
|
||||
if (throwable != null)
|
||||
Log.println(Log.ERROR, TAG, Log.getStackTraceString(throwable))
|
||||
else if (priority <= Log.DEBUG)
|
||||
Log.println(priority, TAG, "Future completed successfully")
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "WireGuard/ExceptionLoggers"
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2020 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.util
|
||||
|
||||
import android.content.Context
|
||||
import android.util.TypedValue
|
||||
import androidx.annotation.AttrRes
|
||||
|
||||
fun Context.resolveAttribute(@AttrRes attrRes: Int): Int {
|
||||
val typedValue = TypedValue()
|
||||
theme.resolveAttribute(attrRes, typedValue, true)
|
||||
return typedValue.data
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.util
|
||||
|
||||
import android.view.ContextThemeWrapper
|
||||
import androidx.preference.Preference
|
||||
import com.wireguard.android.activity.SettingsActivity
|
||||
|
||||
object FragmentUtils {
|
||||
fun getPrefActivity(preference: Preference): SettingsActivity {
|
||||
val context = preference.context
|
||||
if (context is ContextThemeWrapper) {
|
||||
if (context is SettingsActivity) {
|
||||
return context
|
||||
}
|
||||
}
|
||||
throw IllegalStateException("Failed to resolve SettingsActivity")
|
||||
}
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.viewmodel
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import androidx.databinding.BaseObservable
|
||||
import androidx.databinding.Bindable
|
||||
import androidx.databinding.ObservableArrayList
|
||||
import androidx.databinding.ObservableList
|
||||
import com.wireguard.android.BR
|
||||
import com.wireguard.config.Attribute
|
||||
import com.wireguard.config.BadConfigException
|
||||
import com.wireguard.config.Interface
|
||||
import com.wireguard.crypto.Key
|
||||
import com.wireguard.crypto.KeyFormatException
|
||||
import com.wireguard.crypto.KeyPair
|
||||
|
||||
class InterfaceProxy : BaseObservable, Parcelable {
|
||||
@get:Bindable
|
||||
val excludedApplications: ObservableList<String> = ObservableArrayList()
|
||||
|
||||
@get:Bindable
|
||||
var addresses: String = ""
|
||||
set(value) {
|
||||
field = value
|
||||
notifyPropertyChanged(BR.addresses)
|
||||
}
|
||||
|
||||
@get:Bindable
|
||||
var dnsServers: String = ""
|
||||
set(value) {
|
||||
field = value
|
||||
notifyPropertyChanged(BR.dnsServers)
|
||||
}
|
||||
|
||||
@get:Bindable
|
||||
var listenPort: String = ""
|
||||
set(value) {
|
||||
field = value
|
||||
notifyPropertyChanged(BR.listenPort)
|
||||
}
|
||||
|
||||
@get:Bindable
|
||||
var mtu: String = ""
|
||||
set(value) {
|
||||
field = value
|
||||
notifyPropertyChanged(BR.mtu)
|
||||
}
|
||||
|
||||
@get:Bindable
|
||||
var privateKey: String = ""
|
||||
set(value) {
|
||||
field = value
|
||||
notifyPropertyChanged(BR.privateKey)
|
||||
notifyPropertyChanged(BR.publicKey)
|
||||
}
|
||||
|
||||
@get:Bindable
|
||||
val publicKey: String
|
||||
get() = try {
|
||||
KeyPair(Key.fromBase64(privateKey)).publicKey.toBase64()
|
||||
} catch (ignored: KeyFormatException) {
|
||||
""
|
||||
}
|
||||
|
||||
private constructor(parcel: Parcel) {
|
||||
addresses = parcel.readString() ?: ""
|
||||
dnsServers = parcel.readString() ?: ""
|
||||
parcel.readStringList(excludedApplications)
|
||||
listenPort = parcel.readString() ?: ""
|
||||
mtu = parcel.readString() ?: ""
|
||||
privateKey = parcel.readString() ?: ""
|
||||
}
|
||||
|
||||
constructor(other: Interface) {
|
||||
addresses = Attribute.join(other.addresses)
|
||||
val dnsServerStrings = other.dnsServers.map { it.hostAddress }
|
||||
dnsServers = Attribute.join(dnsServerStrings)
|
||||
excludedApplications.addAll(other.excludedApplications)
|
||||
listenPort = other.listenPort.map { it.toString() }.orElse("")
|
||||
mtu = other.mtu.map { it.toString() }.orElse("")
|
||||
val keyPair = other.keyPair
|
||||
privateKey = keyPair.privateKey.toBase64()
|
||||
}
|
||||
|
||||
constructor()
|
||||
|
||||
override fun describeContents() = 0
|
||||
|
||||
fun generateKeyPair() {
|
||||
val keyPair = KeyPair()
|
||||
privateKey = keyPair.privateKey.toBase64()
|
||||
notifyPropertyChanged(BR.privateKey)
|
||||
notifyPropertyChanged(BR.publicKey)
|
||||
}
|
||||
|
||||
@Throws(BadConfigException::class)
|
||||
fun resolve(): Interface {
|
||||
val builder = Interface.Builder()
|
||||
if (addresses.isNotEmpty()) builder.parseAddresses(addresses)
|
||||
if (dnsServers.isNotEmpty()) builder.parseDnsServers(dnsServers)
|
||||
if (excludedApplications.isNotEmpty()) builder.excludeApplications(excludedApplications)
|
||||
if (listenPort.isNotEmpty()) builder.parseListenPort(listenPort)
|
||||
if (mtu.isNotEmpty()) builder.parseMtu(mtu)
|
||||
if (privateKey.isNotEmpty()) builder.parsePrivateKey(privateKey)
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||
dest.writeString(addresses)
|
||||
dest.writeString(dnsServers)
|
||||
dest.writeStringList(excludedApplications)
|
||||
dest.writeString(listenPort)
|
||||
dest.writeString(mtu)
|
||||
dest.writeString(privateKey)
|
||||
}
|
||||
|
||||
private class InterfaceProxyCreator : Parcelable.Creator<InterfaceProxy> {
|
||||
override fun createFromParcel(parcel: Parcel): InterfaceProxy {
|
||||
return InterfaceProxy(parcel)
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<InterfaceProxy?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmField
|
||||
val CREATOR: Parcelable.Creator<InterfaceProxy> = InterfaceProxyCreator()
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2020 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.widget
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.marginBottom
|
||||
import androidx.core.view.marginLeft
|
||||
import androidx.core.view.marginRight
|
||||
import androidx.core.view.marginTop
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.core.view.updatePadding
|
||||
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
|
||||
/**
|
||||
* A utility for edge-to-edge display. It provides several features needed to make the app
|
||||
* displayed edge-to-edge on Android Q with gestural navigation.
|
||||
*/
|
||||
|
||||
object EdgeToEdge {
|
||||
@JvmStatic
|
||||
fun setUpRoot(root: ViewGroup) {
|
||||
root.systemUiVisibility =
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun setUpScrollingContent(scrollingContent: ViewGroup, fab: ExtendedFloatingActionButton?) {
|
||||
val originalPaddingLeft = scrollingContent.paddingLeft
|
||||
val originalPaddingRight = scrollingContent.paddingRight
|
||||
val originalPaddingBottom = scrollingContent.paddingBottom
|
||||
|
||||
val fabPaddingBottom = fab?.height ?: 0
|
||||
|
||||
val originalMarginTop = scrollingContent.marginTop
|
||||
|
||||
scrollingContent.setOnApplyWindowInsetsListener { _, windowInsets ->
|
||||
scrollingContent.updatePadding(
|
||||
left = originalPaddingLeft + windowInsets.systemWindowInsetLeft,
|
||||
right = originalPaddingRight + windowInsets.systemWindowInsetRight,
|
||||
bottom = originalPaddingBottom + fabPaddingBottom + windowInsets.systemWindowInsetBottom
|
||||
)
|
||||
scrollingContent.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
topMargin = originalMarginTop + windowInsets.systemWindowInsetTop
|
||||
}
|
||||
windowInsets
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun setUpFAB(fab: ExtendedFloatingActionButton) {
|
||||
val originalMarginLeft = fab.marginLeft
|
||||
val originalMarginRight = fab.marginRight
|
||||
val originalMarginBottom = fab.marginBottom
|
||||
fab.setOnApplyWindowInsetsListener { _, windowInsets ->
|
||||
fab.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
leftMargin = originalMarginLeft + windowInsets.systemWindowInsetLeft
|
||||
rightMargin = originalMarginRight + windowInsets.systemWindowInsetRight
|
||||
bottomMargin = originalMarginBottom + windowInsets.systemWindowInsetBottom
|
||||
}
|
||||
windowInsets
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2020 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.widget
|
||||
|
||||
import android.content.Context
|
||||
import android.text.Editable
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.util.AttributeSet
|
||||
import com.google.android.material.R
|
||||
import com.google.android.material.textfield.TextInputEditText
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
|
||||
class MonkeyedTextInputEditText @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = R.attr.editTextStyle) : TextInputEditText(context, attrs, defStyleAttr) {
|
||||
@Override
|
||||
override fun getText(): Editable? {
|
||||
val text = super.getText()
|
||||
if (!text.isNullOrEmpty())
|
||||
return text
|
||||
/* We want this expression in TextInputLayout.java to be true if there's a hint set:
|
||||
* final boolean hasText = editText != null && !TextUtils.isEmpty(editText.getText());
|
||||
* But for everyone else it should return the real value, so we check the caller.
|
||||
*/
|
||||
if (!hint.isNullOrEmpty() && Thread.currentThread().stackTrace[3].className == TextInputLayout::class.qualifiedName)
|
||||
return SpannableStringBuilder(hint)
|
||||
return text
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package org.amnezia.awg
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.StrictMode
|
||||
import android.os.StrictMode.ThreadPolicy
|
||||
import android.os.StrictMode.VmPolicy
|
||||
import android.util.Log
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.preferencesDataStoreFile
|
||||
import com.google.android.material.color.DynamicColors
|
||||
import org.amnezia.awg.backend.Backend
|
||||
import org.amnezia.awg.backend.GoBackend
|
||||
import org.amnezia.awg.backend.AwgQuickBackend
|
||||
import org.amnezia.awg.configStore.FileConfigStore
|
||||
import org.amnezia.awg.model.TunnelManager
|
||||
import org.amnezia.awg.util.RootShell
|
||||
import org.amnezia.awg.util.ToolsInstaller
|
||||
import org.amnezia.awg.util.UserKnobs
|
||||
import org.amnezia.awg.util.applicationScope
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.Locale
|
||||
|
||||
class Application : android.app.Application() {
|
||||
private val futureBackend = CompletableDeferred<Backend>()
|
||||
private val coroutineScope = CoroutineScope(Job() + Dispatchers.Main.immediate)
|
||||
private var backend: Backend? = null
|
||||
private lateinit var rootShell: RootShell
|
||||
private lateinit var preferencesDataStore: DataStore<Preferences>
|
||||
private lateinit var toolsInstaller: ToolsInstaller
|
||||
private lateinit var tunnelManager: TunnelManager
|
||||
|
||||
override fun attachBaseContext(context: Context) {
|
||||
super.attachBaseContext(context)
|
||||
if (BuildConfig.MIN_SDK_VERSION > Build.VERSION.SDK_INT) {
|
||||
val intent = Intent(Intent.ACTION_MAIN)
|
||||
intent.addCategory(Intent.CATEGORY_HOME)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
startActivity(intent)
|
||||
System.exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun determineBackend(): Backend {
|
||||
var backend: Backend? = null
|
||||
if (UserKnobs.enableKernelModule.first() && AwgQuickBackend.hasKernelSupport()) {
|
||||
try {
|
||||
rootShell.start()
|
||||
val awgQuickBackend = AwgQuickBackend(applicationContext, rootShell, toolsInstaller)
|
||||
awgQuickBackend.setMultipleTunnels(UserKnobs.multipleTunnels.first())
|
||||
backend = awgQuickBackend
|
||||
UserKnobs.multipleTunnels.onEach {
|
||||
awgQuickBackend.setMultipleTunnels(it)
|
||||
}.launchIn(coroutineScope)
|
||||
} catch (ignored: Exception) {
|
||||
}
|
||||
}
|
||||
if (backend == null) {
|
||||
backend = GoBackend(applicationContext)
|
||||
GoBackend.setAlwaysOnCallback { get().applicationScope.launch { get().tunnelManager.restoreState(true) } }
|
||||
}
|
||||
return backend
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
Log.i(TAG, USER_AGENT)
|
||||
super.onCreate()
|
||||
DynamicColors.applyToActivitiesIfAvailable(this)
|
||||
rootShell = RootShell(applicationContext)
|
||||
toolsInstaller = ToolsInstaller(applicationContext, rootShell)
|
||||
preferencesDataStore = PreferenceDataStoreFactory.create { applicationContext.preferencesDataStoreFile("settings") }
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
runBlocking {
|
||||
AppCompatDelegate.setDefaultNightMode(if (UserKnobs.darkTheme.first()) AppCompatDelegate.MODE_NIGHT_YES else AppCompatDelegate.MODE_NIGHT_NO)
|
||||
}
|
||||
UserKnobs.darkTheme.onEach {
|
||||
val newMode = if (it) {
|
||||
AppCompatDelegate.MODE_NIGHT_YES
|
||||
} else {
|
||||
AppCompatDelegate.MODE_NIGHT_NO
|
||||
}
|
||||
if (AppCompatDelegate.getDefaultNightMode() != newMode) {
|
||||
AppCompatDelegate.setDefaultNightMode(newMode)
|
||||
}
|
||||
}.launchIn(coroutineScope)
|
||||
} else {
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
|
||||
}
|
||||
tunnelManager = TunnelManager(FileConfigStore(applicationContext))
|
||||
tunnelManager.onCreate()
|
||||
coroutineScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
backend = determineBackend()
|
||||
futureBackend.complete(backend!!)
|
||||
} catch (e: Throwable) {
|
||||
Log.e(TAG, Log.getStackTraceString(e))
|
||||
}
|
||||
}
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
StrictMode.setVmPolicy(VmPolicy.Builder().detectAll().penaltyLog().build())
|
||||
StrictMode.setThreadPolicy(ThreadPolicy.Builder().detectAll().penaltyLog().build())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTerminate() {
|
||||
coroutineScope.cancel()
|
||||
super.onTerminate()
|
||||
}
|
||||
|
||||
companion object {
|
||||
val USER_AGENT = String.format(Locale.ENGLISH, "AmneziaWG/%s (Android %d; %s; %s; %s %s; %s)", BuildConfig.VERSION_NAME, Build.VERSION.SDK_INT, if (Build.SUPPORTED_ABIS.isNotEmpty()) Build.SUPPORTED_ABIS[0] else "unknown ABI", Build.BOARD, Build.MANUFACTURER, Build.MODEL, Build.FINGERPRINT)
|
||||
private const val TAG = "AmneziaWG/Application"
|
||||
private lateinit var weakSelf: WeakReference<Application>
|
||||
|
||||
fun get(): Application {
|
||||
return weakSelf.get()!!
|
||||
}
|
||||
|
||||
suspend fun getBackend() = get().futureBackend.await()
|
||||
|
||||
fun getRootShell() = get().rootShell
|
||||
|
||||
fun getPreferencesDataStore() = get().preferencesDataStore
|
||||
|
||||
fun getToolsInstaller() = get().toolsInstaller
|
||||
|
||||
fun getTunnelManager() = get().tunnelManager
|
||||
|
||||
fun getCoroutineScope() = get().coroutineScope
|
||||
}
|
||||
|
||||
init {
|
||||
weakSelf = WeakReference(this)
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user