From 56a8451d07f4c61929862088ce1c80fa2b819b00 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Wed, 2 Mar 2022 14:32:24 -0500 Subject: [PATCH] Add fallback static DNS resolver. --- app/build.gradle | 9 +++ .../thoughtcrime/securesms/net/CustomDns.java | 3 + .../securesms/net/SequentialDns.java | 6 +- .../thoughtcrime/securesms/net/StaticDns.java | 35 ----------- .../thoughtcrime/securesms/net/StaticDns.kt | 22 +++++++ .../push/SignalServiceNetworkAccess.kt | 22 ++++++- .../securesms/util/NetworkUtil.java | 37 +++++++++++ app/static-ips.gradle | 8 +++ app/translations.gradle | 20 +++++- buildSrc/build.gradle | 10 +++ buildSrc/settings.gradle | 2 + .../java/org/signal/StaticIpResolver.java | 63 +++++++++++++++++++ 12 files changed, 198 insertions(+), 39 deletions(-) delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/net/StaticDns.java create mode 100644 app/src/main/java/org/thoughtcrime/securesms/net/StaticDns.kt create mode 100644 app/static-ips.gradle create mode 100644 buildSrc/build.gradle create mode 100644 buildSrc/settings.gradle create mode 100644 buildSrc/src/main/java/org/signal/StaticIpResolver.java diff --git a/app/build.gradle b/app/build.gradle index 69182dc6c..9966c6f79 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -7,6 +7,7 @@ apply from: 'translations.gradle' apply plugin: 'org.jetbrains.kotlin.android' apply plugin: 'app.cash.exhaustive' apply plugin: 'kotlin-parcelize' +apply from: 'static-ips.gradle' repositories { maven { @@ -186,6 +187,14 @@ android { buildConfigField "String[]", "SIGNAL_SFU_INTERNAL_URLS", "new String[]{\"https://sfu.test.voip.signal.org\", \"https://sfu.staging.voip.signal.org\"}" buildConfigField "String", "CONTENT_PROXY_HOST", "\"contentproxy.signal.org\"" buildConfigField "int", "CONTENT_PROXY_PORT", "443" + buildConfigField "String[]", "SIGNAL_SERVICE_IPS", service_ips + buildConfigField "String[]", "SIGNAL_STORAGE_IPS", storage_ips + buildConfigField "String[]", "SIGNAL_CDN_IPS", cdn_ips + buildConfigField "String[]", "SIGNAL_CDN2_IPS", cdn2_ips + buildConfigField "String[]", "SIGNAL_CDS_IPS", cds_ips + buildConfigField "String[]", "SIGNAL_KBS_IPS", kbs_ips + buildConfigField "String[]", "SIGNAL_SFU_IPS", sfu_ips + buildConfigField "String[]", "SIGNAL_CONTENT_PROXY_IPS", content_proxy_ips buildConfigField "String", "SIGNAL_AGENT", "\"OWA\"" buildConfigField "String", "CDSH_PUBLIC_KEY", "\"2fe57da347cd62431528daac5fbb290730fff684afc4cfc2ed90995f58cb3b74\"" buildConfigField "String", "CDSH_CODE_HASH", "\"2f79dc6c1599b71c70fc2d14f3ea2e3bc65134436eb87011c88845b137af673a\"" diff --git a/app/src/main/java/org/thoughtcrime/securesms/net/CustomDns.java b/app/src/main/java/org/thoughtcrime/securesms/net/CustomDns.java index 2882f26de..c59fb55e2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/net/CustomDns.java +++ b/app/src/main/java/org/thoughtcrime/securesms/net/CustomDns.java @@ -5,6 +5,8 @@ import androidx.annotation.NonNull; import com.annimon.stream.Stream; import org.signal.core.util.logging.Log; +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; +import org.thoughtcrime.securesms.util.NetworkUtil; import org.xbill.DNS.ARecord; import org.xbill.DNS.Lookup; import org.xbill.DNS.Record; @@ -14,6 +16,7 @@ import org.xbill.DNS.Type; import java.net.InetAddress; import java.net.UnknownHostException; +import java.util.Collections; import java.util.List; import okhttp3.Dns; diff --git a/app/src/main/java/org/thoughtcrime/securesms/net/SequentialDns.java b/app/src/main/java/org/thoughtcrime/securesms/net/SequentialDns.java index b0478a791..34d5c7cf7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/net/SequentialDns.java +++ b/app/src/main/java/org/thoughtcrime/securesms/net/SequentialDns.java @@ -3,6 +3,8 @@ package org.thoughtcrime.securesms.net; import androidx.annotation.NonNull; import org.signal.core.util.logging.Log; +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; +import org.thoughtcrime.securesms.util.NetworkUtil; import java.net.InetAddress; import java.net.UnknownHostException; @@ -36,10 +38,10 @@ public class SequentialDns implements Dns { Log.w(TAG, String.format(Locale.ENGLISH, "Didn't find any addresses for %s using %s. Continuing.", hostname, dns.getClass().getSimpleName())); } } catch (UnknownHostException e) { - Log.w(TAG, String.format(Locale.ENGLISH, "Failed to resolve %s using %s. Continuing.", hostname, dns.getClass().getSimpleName())); + Log.w(TAG, String.format(Locale.ENGLISH, "Failed to resolve %s using %s. Continuing. Network Type: %s", hostname, dns.getClass().getSimpleName(), NetworkUtil.getNetworkTypeDescriptor(ApplicationDependencies.getApplication()))); } } - Log.w(TAG, "Failed to resolve using any DNS."); + Log.w(TAG, "Failed to resolve using any DNS. Network Type: " + NetworkUtil.getNetworkTypeDescriptor(ApplicationDependencies.getApplication())); throw new UnknownHostException(hostname); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/net/StaticDns.java b/app/src/main/java/org/thoughtcrime/securesms/net/StaticDns.java deleted file mode 100644 index 18d00507f..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/net/StaticDns.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.thoughtcrime.securesms.net; - -import androidx.annotation.NonNull; - -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import okhttp3.Dns; - -/** - * A super simple {@link Dns} implementation that maps hostnames to a static IP addresses. - */ -public class StaticDns implements Dns { - - private final Map hostnameMap; - - public StaticDns(@NonNull Map hostnameMap) { - this.hostnameMap = hostnameMap; - } - - @Override - public @NonNull List lookup(@NonNull String hostname) throws UnknownHostException { - String ip = hostnameMap.get(hostname); - - if (ip != null) { - return Collections.singletonList(InetAddress.getByName(ip)); - } else { - throw new UnknownHostException(hostname); - } - - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/net/StaticDns.kt b/app/src/main/java/org/thoughtcrime/securesms/net/StaticDns.kt new file mode 100644 index 000000000..573704cfb --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/net/StaticDns.kt @@ -0,0 +1,22 @@ +package org.thoughtcrime.securesms.net + +import okhttp3.Dns +import java.net.InetAddress +import java.net.UnknownHostException + +/** + * A super simple [Dns] implementation that maps hostnames to a static IP addresses. + */ +class StaticDns(private val hostnameMap: Map>) : Dns { + + @Throws(UnknownHostException::class) + override fun lookup(hostname: String): List { + val ips = hostnameMap[hostname] + + return if (ips != null && ips.isNotEmpty()) { + listOf(InetAddress.getByName(ips.random())) + } else { + throw UnknownHostException(hostname) + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/push/SignalServiceNetworkAccess.kt b/app/src/main/java/org/thoughtcrime/securesms/push/SignalServiceNetworkAccess.kt index 13aa6ccc1..09b71dde1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/push/SignalServiceNetworkAccess.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/push/SignalServiceNetworkAccess.kt @@ -16,6 +16,7 @@ import org.thoughtcrime.securesms.net.DeviceTransferBlockingInterceptor import org.thoughtcrime.securesms.net.RemoteDeprecationDetectorInterceptor import org.thoughtcrime.securesms.net.SequentialDns import org.thoughtcrime.securesms.net.StandardUserAgentInterceptor +import org.thoughtcrime.securesms.net.StaticDns import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter import org.thoughtcrime.securesms.util.Base64 import org.whispersystems.libsignal.util.guava.Optional @@ -38,7 +39,26 @@ class SignalServiceNetworkAccess(context: Context) { private val TAG = Log.tag(SignalServiceNetworkAccess::class.java) @JvmField - val DNS: Dns = SequentialDns(Dns.SYSTEM, CustomDns("1.1.1.1")) + val DNS: Dns = SequentialDns( + Dns.SYSTEM, + CustomDns("1.1.1.1"), + StaticDns( + mapOf( + BuildConfig.SIGNAL_URL.stripProtocol() to BuildConfig.SIGNAL_SERVICE_IPS.toSet(), + BuildConfig.STORAGE_URL.stripProtocol() to BuildConfig.SIGNAL_STORAGE_IPS.toSet(), + BuildConfig.SIGNAL_CDN_URL.stripProtocol() to BuildConfig.SIGNAL_CDN_IPS.toSet(), + BuildConfig.SIGNAL_CDN2_URL.stripProtocol() to BuildConfig.SIGNAL_CDN2_IPS.toSet(), + BuildConfig.SIGNAL_CONTACT_DISCOVERY_URL.stripProtocol() to BuildConfig.SIGNAL_CDS_IPS.toSet(), + BuildConfig.SIGNAL_KEY_BACKUP_URL.stripProtocol() to BuildConfig.SIGNAL_KBS_IPS.toSet(), + BuildConfig.SIGNAL_SFU_URL.stripProtocol() to BuildConfig.SIGNAL_SFU_IPS.toSet(), + BuildConfig.CONTENT_PROXY_HOST.stripProtocol() to BuildConfig.SIGNAL_CONTENT_PROXY_IPS.toSet(), + ) + ) + ) + + private fun String.stripProtocol(): String { + return this.removePrefix("https://") + } private const val COUNTRY_CODE_EGYPT = 20 private const val COUNTRY_CODE_UAE = 971 diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/NetworkUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/NetworkUtil.java index 7189f8ea5..e16cd6c3e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/NetworkUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/NetworkUtil.java @@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.util; import android.content.Context; import android.net.ConnectivityManager; import android.net.NetworkInfo; +import android.telephony.TelephonyManager; import androidx.annotation.NonNull; @@ -37,6 +38,42 @@ public final class NetworkUtil { return useLowBandwidthCalling(context, networkAdapter) ? CallManager.BandwidthMode.LOW : CallManager.BandwidthMode.NORMAL; } + public static String getNetworkTypeDescriptor(@NonNull Context context) { + NetworkInfo info = getNetworkInfo(context); + if (info == null || !info.isConnected()) { + return "NOT CONNECTED"; + } else if (info.getType() == ConnectivityManager.TYPE_WIFI) { + return "WIFI"; + } else if (info.getType() == ConnectivityManager.TYPE_MOBILE) { + int networkType = info.getSubtype(); + switch (networkType) { + case TelephonyManager.NETWORK_TYPE_GPRS: return "MOBILE - GPRS"; + case TelephonyManager.NETWORK_TYPE_EDGE: return "MOBILE - EDGE"; + case TelephonyManager.NETWORK_TYPE_CDMA: return "MOBILE - CDMA"; + case TelephonyManager.NETWORK_TYPE_1xRTT: return "MOBILE - 1xRTT"; + case TelephonyManager.NETWORK_TYPE_IDEN: return "MOBILE - IDEN"; + case TelephonyManager.NETWORK_TYPE_GSM: return "MOBILE - GSM"; + case TelephonyManager.NETWORK_TYPE_UMTS: return "MOBILE - UMTS"; + case TelephonyManager.NETWORK_TYPE_EVDO_0: return "MOBILE - EVDO_0"; + case TelephonyManager.NETWORK_TYPE_EVDO_A: return "MOBILE - EVDO_A"; + case TelephonyManager.NETWORK_TYPE_HSDPA: return "MOBILE - HSDPA"; + case TelephonyManager.NETWORK_TYPE_HSUPA: return "MOBILE - HSUPA"; + case TelephonyManager.NETWORK_TYPE_HSPA: return "MOBILE - HSPA"; + case TelephonyManager.NETWORK_TYPE_EVDO_B: return "MOBILE - EVDO_B"; + case TelephonyManager.NETWORK_TYPE_EHRPD: return "MOBILE - EHRDP"; + case TelephonyManager.NETWORK_TYPE_HSPAP: return "MOBILE - HSPAP"; + case TelephonyManager.NETWORK_TYPE_TD_SCDMA: return "MOBILE - TD_SCDMA"; + case TelephonyManager.NETWORK_TYPE_LTE: return "MOBILE - LTE"; + case TelephonyManager.NETWORK_TYPE_IWLAN: return "MOBILE - IWLAN"; + case 19: return "MOBILE - LTE_CA"; + case TelephonyManager.NETWORK_TYPE_NR: return "MOBILE - NR"; + default: return "MOBILE - OTHER"; + } + } else { + return "UNKNOWN"; + } + } + private static boolean useLowBandwidthCalling(@NonNull Context context, @NonNull PeerConnection.AdapterType networkAdapter) { switch (SignalStore.settings().getCallBandwidthMode()) { case HIGH_ON_WIFI: diff --git a/app/static-ips.gradle b/app/static-ips.gradle new file mode 100644 index 000000000..04a35c68e --- /dev/null +++ b/app/static-ips.gradle @@ -0,0 +1,8 @@ +ext.service_ips='new String[]{"76.223.92.165","13.248.212.111"}' +ext.storage_ips='new String[]{"142.251.40.179"}' +ext.cdn_ips='new String[]{"65.8.198.46","65.8.198.39","65.8.198.54","65.8.198.118"}' +ext.cdn2_ips='new String[]{"104.18.28.74","104.18.29.74"}' +ext.cds_ips='new String[]{"20.62.208.25"}' +ext.kbs_ips='new String[]{"20.85.156.233"}' +ext.sfu_ips='new String[]{"54.152.177.76","52.6.24.145"}' +ext.content_proxy_ips='new String[]{"107.178.250.75"}' \ No newline at end of file diff --git a/app/translations.gradle b/app/translations.gradle index ee78807df..5edbe21c1 100644 --- a/app/translations.gradle +++ b/app/translations.gradle @@ -1,6 +1,7 @@ import groovy.io.FileType import groovy.transform.stc.ClosureParams import groovy.transform.stc.SimpleType +import org.signal.StaticIpResolver ext { autoResConfig = this.&autoResConfig @@ -129,8 +130,25 @@ task postTranslateQa { mustRunAfter excludeNonTranslatables } +task postTranslateIpFetch { + group 'Translate' + description 'Fetches static IPs for core hosts and writes them to static-ips.gradle' + doLast { + new File(projectDir, "static-ips.gradle").text = """ + ext.service_ips='${StaticIpResolver.resolveToBuildConfig("chat.signal.org")}' + ext.storage_ips='${StaticIpResolver.resolveToBuildConfig("storage.signal.org")}' + ext.cdn_ips='${StaticIpResolver.resolveToBuildConfig("cdn.signal.org")}' + ext.cdn2_ips='${StaticIpResolver.resolveToBuildConfig("cdn2.signal.org")}' + ext.cds_ips='${StaticIpResolver.resolveToBuildConfig("api.directory.signal.org")}' + ext.kbs_ips='${StaticIpResolver.resolveToBuildConfig("api.backup.signal.org")}' + ext.sfu_ips='${StaticIpResolver.resolveToBuildConfig("sfu.voip.signal.org")}' + ext.content_proxy_ips='${StaticIpResolver.resolveToBuildConfig("contentproxy.signal.org")}' + """.stripIndent().trim() + } +} + task translate { group 'Translate' description 'Pull translations and post-process for ellipsis, apostrophes and non-translatables.' - dependsOn pullTranslations, replaceEllipsis, cleanApostropheErrors, excludeNonTranslatables, postTranslateQa + dependsOn pullTranslations, replaceEllipsis, cleanApostropheErrors, excludeNonTranslatables, postTranslateIpFetch, postTranslateQa } diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle new file mode 100644 index 000000000..fe26224fa --- /dev/null +++ b/buildSrc/build.gradle @@ -0,0 +1,10 @@ +apply plugin: 'java-gradle-plugin' + +repositories { + google() + mavenCentral() +} + +dependencies { + implementation libs.dnsjava +} \ No newline at end of file diff --git a/buildSrc/settings.gradle b/buildSrc/settings.gradle new file mode 100644 index 000000000..4e79e5020 --- /dev/null +++ b/buildSrc/settings.gradle @@ -0,0 +1,2 @@ +enableFeaturePreview('VERSION_CATALOGS') +apply from: '../dependencies.gradle' diff --git a/buildSrc/src/main/java/org/signal/StaticIpResolver.java b/buildSrc/src/main/java/org/signal/StaticIpResolver.java new file mode 100644 index 000000000..fad2a2cd2 --- /dev/null +++ b/buildSrc/src/main/java/org/signal/StaticIpResolver.java @@ -0,0 +1,63 @@ +package org.signal; + +import org.gradle.internal.impldep.org.eclipse.jgit.annotations.NonNull; +import org.xbill.DNS.ARecord; +import org.xbill.DNS.Lookup; +import org.xbill.DNS.Record; +import org.xbill.DNS.Resolver; +import org.xbill.DNS.SimpleResolver; +import org.xbill.DNS.Type; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Arrays; + +public final class StaticIpResolver { + + private StaticIpResolver() {} + + public static String resolveToBuildConfig(String hostName) { + String[] ips = resolve(hostName); + StringBuilder builder = new StringBuilder(); + builder.append("new String[]{"); + for (int i = 0; i < ips.length; i++) { + builder.append("\"").append(ips[i]).append("\""); + if (i < ips.length - 1) { + builder.append(","); + } + } + return builder.append("}").toString(); + } + + private static String[] resolve(String hostName) { + try { + Resolver resolver = new SimpleResolver("1.1.1.1"); + Lookup lookup = doLookup(hostName); + + lookup.setResolver(resolver); + + Record[] records = lookup.run(); + + if (records != null) { + return Arrays.stream(records) + .filter(r -> r.getType() == Type.A) + .map(r -> (ARecord) r) + .map(ARecord::getAddress) + .map(InetAddress::getHostAddress) + .toArray(String[]::new); + } else { + throw new IllegalStateException("Failed to resolve host! " + hostName); + } + } catch (UnknownHostException e) { + throw new IllegalStateException("Failed to resolve host! " + hostName); + } + } + + private static @NonNull Lookup doLookup(@NonNull String hostname) throws UnknownHostException { + try { + return new Lookup(hostname); + } catch (Throwable e) { + throw new UnknownHostException(); + } + } +}