Skip to content

Commit 8a64d7c

Browse files
tladesignzsyphyr
authored andcommitted
Issue guardianproject#1193: Added support for mixing bridge types with custom bridges: Meek, Obfs4 and Webtunnel can now be used all in parallel.
1 parent 1fb0350 commit 8a64d7c

File tree

8 files changed

+147
-35
lines changed

8 files changed

+147
-35
lines changed

Diff for: app-tv/build.gradle

+7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
plugins {
2+
alias(libs.plugins.kotlin.android)
3+
}
14
apply from: "../commons.gradle"
25

36
android {
@@ -41,6 +44,9 @@ android {
4144
archivesBaseName = "Orbot-TV-$versionName"
4245
}
4346
}
47+
kotlinOptions {
48+
jvmTarget = '17'
49+
}
4450
}
4551

4652
configurations {
@@ -65,6 +71,7 @@ dependencies {
6571
implementation(libs.androidx.palette)
6672
implementation(libs.androidx.recyclerview)
6773
implementation(libs.apl.appintro)
74+
implementation libs.androidx.core.ktx
6875

6976
androidTestImplementation(libs.fastlane.screengrab)
7077
}

Diff for: app/src/main/java/org/torproject/android/CustomBridgeBottomSheet.kt

+1-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class CustomBridgeBottomSheet(private val callbacks: ConnectionHelperCallbacks)
1212
OrbotBottomSheetDialogFragment() {
1313
companion object {
1414
const val TAG = "CustomBridgeBottomSheet"
15-
private const val bridgeStatement = "obfs4"
15+
private val bridgeStatement = Regex("(obfs4|meek|webtunnel)")
1616
}
1717

1818
private lateinit var btnAction: Button
@@ -42,5 +42,4 @@ class CustomBridgeBottomSheet(private val callbacks: ConnectionHelperCallbacks)
4242
btnAction.isEnabled =
4343
!(etBridges.text.isEmpty() || !etBridges.text.contains(bridgeStatement))
4444
}
45-
4645
}

Diff for: build.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Top-level build file where you can add configuration options common to all sub-projects/modules.
22
plugins {
33
id "com.android.application" version "8.8.0" apply false
4-
id "org.jetbrains.kotlin.android" version "2.0.0" apply false
4+
id "org.jetbrains.kotlin.android" version "2.1.0" apply false
55
}

Diff for: gradle/libs.versions.toml

+6-3
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ androidx-leanback-tab = "1.1.0-beta01"
1313
androidx-localbroadcast = "1.1.0"
1414
androidx-palette = "1.0.0"
1515
androidx-preference = "1.2.1"
16-
androidx-recyclerview = "1.3.2"
16+
androidx-recyclerview = "1.4.0"
1717
androidx-work = "2.10.0"
1818
apl-appintro = "v4.2.3"
1919
espresso-core = "3.6.1"
@@ -23,11 +23,12 @@ guardian-jtorctl = "0.4.8.13"
2323
junit = "4.13.2"
2424
junit-version = "1.2.1"
2525
navigation-fragment-ktx = "2.8.5"
26-
retrofit = "2.9.0"
27-
rootbeer-lib = "0.1.0"
26+
retrofit = "2.11.0"
27+
rootbeer-lib = "0.1.1"
2828
tor-android = "0.4.8.13"
2929
pcap-core = "1.8.2"
3030
pcap-factory = "1.8.2"
31+
kotlin = "2.1.0"
3132

3233
[libraries]
3334
android-material = { group = "com.google.android.material", name = "material", version.ref = "android-material" }
@@ -64,3 +65,5 @@ rootbeer-lib = { module = "com.scottyab:rootbeer-lib", version.ref = "rootbeer-l
6465
tor-android = { group = "info.guardianproject", name = "tor-android", version.ref = "tor-android" }
6566
pcap-core = { group = "org.pcap4j", name = "pcap4j-core", version.ref = "pcap-core" }
6667
pcap-factory = { group = "org.pcap4j", name = "pcap4j-packetfactory-static", version.ref = "pcap-factory" }
68+
[plugins]
69+
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }

Diff for: intentintegrator/build.gradle

+7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
plugins {
2+
alias(libs.plugins.kotlin.android)
3+
}
14
apply plugin: "com.android.library"
25

36
android {
@@ -21,8 +24,12 @@ android {
2124
sourceCompatibility = JavaVersion.VERSION_17
2225
targetCompatibility = JavaVersion.VERSION_17
2326
}
27+
kotlinOptions {
28+
jvmTarget = '17'
29+
}
2430
}
2531

2632
dependencies {
2733
implementation(libs.androidx.appcompat)
34+
implementation libs.androidx.core.ktx
2835
}

Diff for: orbotservice/build.gradle

+7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
plugins {
2+
alias(libs.plugins.kotlin.android)
3+
}
14
apply plugin: "com.android.library"
25

36
android {
@@ -36,6 +39,9 @@ android {
3639
textReport false
3740
xmlReport false
3841
}
42+
kotlinOptions {
43+
jvmTarget = '17'
44+
}
3945
}
4046

4147
dependencies {
@@ -54,4 +60,5 @@ dependencies {
5460
implementation(libs.pcap.factory)
5561

5662
// implementation(files("../libs/geoip.jar"))
63+
implementation libs.androidx.core.ktx
5764
}

Diff for: orbotservice/src/main/java/org/torproject/android/service/OrbotService.java

+33-29
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import net.freehaven.tor.control.TorControlCommands;
4040
import net.freehaven.tor.control.TorControlConnection;
4141

42+
import org.torproject.android.service.util.Bridge;
4243
import org.torproject.android.service.util.CustomTorResourceInstaller;
4344
import org.torproject.android.service.util.PowerConnectionReceiver;
4445
import org.torproject.android.service.util.Prefs;
@@ -137,16 +138,6 @@ public void stopped(String s, Exception e) {
137138

138139
return mIptProxy;
139140
}
140-
/**
141-
* @param bridgeList bridges that were manually entered into Orbot settings
142-
* @return Array with each bridge as an element, no whitespace entries see issue #289...
143-
*/
144-
private static String[] parseBridgesFromSettings(String bridgeList) {
145-
// this regex replaces lines that only contain whitespace with an empty String
146-
bridgeList = bridgeList.trim().replaceAll("(?m)^[ \t]*\r?\n", "");
147-
return bridgeList.split("\\n");
148-
}
149-
150141
public void debug(String msg) {
151142
Log.d(TAG, msg);
152143

@@ -299,12 +290,12 @@ private void stopTorAsync(boolean showNotification) {
299290
// todo this needs to handle a lot of different cases that haven't been defined yet
300291
// todo particularly this is true for the smart connection case...
301292
if (connectionPathway.startsWith(Prefs.PATHWAY_SNOWFLAKE) || Prefs.getPrefSmartTrySnowflake()) {
302-
// mIptProxy.stop
303293
mIptProxy.stop(IPtProxy.Snowflake);
304-
} else if (connectionPathway.equals(Prefs.PATHWAY_CUSTOM) || Prefs.getPrefSmartTryObfs4() != null) {
305-
// IPtProxy.stopLyrebird();
294+
}
295+
else if (connectionPathway.equals(Prefs.PATHWAY_CUSTOM) || Prefs.getPrefSmartTryObfs4() != null) {
296+
mIptProxy.stop(IPtProxy.MeekLite);
306297
mIptProxy.stop(IPtProxy.Obfs4);
307-
298+
mIptProxy.stop(IPtProxy.Webtunnel);
308299
}
309300

310301
stopTor();
@@ -1204,7 +1195,7 @@ private StringBuffer processSettingsImpl(StringBuffer extraLines) throws IOExcep
12041195
if (pathway.startsWith(Prefs.PATHWAY_SNOWFLAKE) || Prefs.getPrefSmartTrySnowflake()) {
12051196
extraLines = processSettingsImplSnowflake(extraLines);
12061197
} else if (pathway.equals(Prefs.PATHWAY_CUSTOM) || Prefs.getPrefSmartTryObfs4() != null) {
1207-
extraLines = processSettingsImplObfs4(extraLines);
1198+
extraLines = processSettingsLyrebird(extraLines);
12081199
}
12091200
}
12101201
var fileGeoIP = new File(appBinHome, GEOIP_ASSET_KEY);
@@ -1274,20 +1265,32 @@ private StringBuffer processSettingsImplSnowflake(StringBuffer extraLines) {
12741265
return extraLines;
12751266
}
12761267

1277-
private StringBuffer processSettingsImplObfs4(StringBuffer extraLines) {
1278-
Log.d(TAG, "in obfs4 torrc config");
1279-
extraLines.append("ClientTransportPlugin obfs4 socks5 127.0.0.1:" + mIptProxy.port(IPtProxy.Obfs4)).append('\n');
1268+
private StringBuffer processSettingsLyrebird(StringBuffer extraLines) {
1269+
Log.d(TAG, "in Lyrebird torrc config");
1270+
1271+
var customBridges = getCustomBridges();
12801272

1281-
var bridgeList = "";
1282-
if (Prefs.getConnectionPathway().equals(Prefs.PATHWAY_CUSTOM)) {
1283-
bridgeList = Prefs.getBridgesList();
1284-
} else bridgeList = Prefs.getPrefSmartTryObfs4();
1285-
var customBridges = parseBridgesFromSettings(bridgeList);
1286-
for (var b : customBridges)
1273+
for (String transport : Bridge.getTransports(customBridges)) {
1274+
extraLines
1275+
.append(String.format(Locale.US, "ClientTransportPlugin %s socks5 127.0.0.1:%d",
1276+
transport, mIptProxy.port(transport)))
1277+
.append('\n');
1278+
}
1279+
1280+
for (var b : customBridges) {
12871281
extraLines.append("Bridge ").append(b).append("\n");
1282+
}
1283+
12881284
return extraLines;
12891285
}
12901286

1287+
private List<Bridge> getCustomBridges() {
1288+
return Bridge.parseBridges(
1289+
Prefs.getConnectionPathway().equals(Prefs.PATHWAY_CUSTOM)
1290+
? Prefs.getBridgesList()
1291+
: Prefs.getPrefSmartTryObfs4());
1292+
}
1293+
12911294
private StringBuffer processSettingsImplDirectPathway(StringBuffer extraLines) {
12921295
var prefs = Prefs.getSharedPrefs(getApplicationContext());
12931296
extraLines.append("UseBridges 0").append('\n');
@@ -1528,11 +1531,12 @@ public void run() {
15281531
} else if (connectionPathway.equals(Prefs.PATHWAY_SNOWFLAKE_AMP)) {
15291532
startSnowflakeClientAmpRendezvous();
15301533
} else if (connectionPathway.equals(Prefs.PATHWAY_CUSTOM) || Prefs.getPrefSmartTryObfs4() != null) {
1531-
//IPtProxy.startLyrebird("DEBUG", false, false, null);
1532-
try {
1533-
mIptProxy.start(IPtProxy.Obfs4,"");
1534-
} catch (Exception e) {
1535-
throw new RuntimeException(e);
1534+
for (var transport : Bridge.getTransports(getCustomBridges())) {
1535+
try {
1536+
mIptProxy.start(transport, "");
1537+
} catch (Exception e) {
1538+
throw new RuntimeException(e);
1539+
}
15361540
}
15371541
}
15381542
startTor();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package org.torproject.android.service.util
2+
3+
/**
4+
* Parser for bridge lines.
5+
*/
6+
@Suppress("MemberVisibilityCanBePrivate", "unused")
7+
class Bridge(var raw: String) {
8+
9+
val rawPieces
10+
get() = raw.split(" ")
11+
12+
val transport
13+
get() = rawPieces.firstOrNull()
14+
15+
val address
16+
get() = rawPieces.getOrNull(1)
17+
18+
val ip
19+
get() = address?.split(":")?.firstOrNull()
20+
21+
val port
22+
get() = address?.split(":")?.lastOrNull()?.toInt()
23+
24+
val fingerprint1
25+
get() = rawPieces.getOrNull(2)
26+
27+
val fingerprint2
28+
get() = rawPieces.firstOrNull { it.startsWith("fingerprint=") }
29+
?.split("=")?.lastOrNull()
30+
31+
val url
32+
get() = rawPieces.firstOrNull { it.startsWith("url=") }
33+
?.split("=")?.lastOrNull()
34+
35+
val front
36+
get() = rawPieces.firstOrNull { it.startsWith("front=") }
37+
?.split("=")?.lastOrNull()
38+
39+
val fronts
40+
get() = rawPieces.firstOrNull { it.startsWith("fronts=") }
41+
?.split("=")?.lastOrNull()?.split(",")?.filter { it.isNotEmpty() }
42+
43+
val cert
44+
get() = rawPieces.firstOrNull { it.startsWith("cert=") }
45+
?.split("=")?.lastOrNull()
46+
47+
val iatMode
48+
get() = rawPieces.firstOrNull { it.startsWith("iat-mode=") }
49+
?.split("=")?.lastOrNull()
50+
51+
val ice
52+
get() = rawPieces.firstOrNull { it.startsWith("ice=") }
53+
?.split("=")?.lastOrNull()
54+
55+
val utlsImitate
56+
get() = rawPieces.firstOrNull { it.startsWith("utls-imitate=") }
57+
?.split("=")?.lastOrNull()
58+
59+
val ver
60+
get() = rawPieces.firstOrNull { it.startsWith("ver=") }
61+
?.split("=")?.lastOrNull()
62+
63+
64+
override fun toString(): String {
65+
return raw
66+
}
67+
68+
companion object {
69+
70+
@JvmStatic
71+
fun parseBridges(bridges: String): List<Bridge> {
72+
return bridges
73+
.split("\n")
74+
.mapNotNull {
75+
val b = it.trim()
76+
if (b.isNotEmpty()) Bridge(b) else null
77+
}
78+
}
79+
80+
@JvmStatic
81+
fun getTransports(bridges: List<Bridge>): Set<String> {
82+
return bridges.mapNotNull { it.transport }.toSet()
83+
}
84+
}
85+
}

0 commit comments

Comments
 (0)