mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-23 12:38:33 +00:00
Add system HTTP proxy support to libsignal-net.
Co-authored-by: Cody Henthorne <cody@signal.org>
This commit is contained in:
committed by
Cody Henthorne
parent
8e880fe117
commit
74c6e76808
@@ -97,6 +97,7 @@ class InstrumentationApplicationDependencyProvider(val application: Application,
|
|||||||
networkInterceptors = emptyList(),
|
networkInterceptors = emptyList(),
|
||||||
dns = Optional.of(SignalServiceNetworkAccess.DNS),
|
dns = Optional.of(SignalServiceNetworkAccess.DNS),
|
||||||
signalProxy = Optional.empty(),
|
signalProxy = Optional.empty(),
|
||||||
|
systemHttpProxy = Optional.empty(),
|
||||||
zkGroupServerPublicParams = Base64.decode(BuildConfig.ZKGROUP_SERVER_PUBLIC_PARAMS),
|
zkGroupServerPublicParams = Base64.decode(BuildConfig.ZKGROUP_SERVER_PUBLIC_PARAMS),
|
||||||
genericServerPublicParams = Base64.decode(BuildConfig.GENERIC_SERVER_PUBLIC_PARAMS),
|
genericServerPublicParams = Base64.decode(BuildConfig.GENERIC_SERVER_PUBLIC_PARAMS),
|
||||||
backupServerPublicParams = Base64.decode(BuildConfig.BACKUP_SERVER_PUBLIC_PARAMS),
|
backupServerPublicParams = Base64.decode(BuildConfig.BACKUP_SERVER_PUBLIC_PARAMS),
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import okhttp3.OkHttpClient
|
|||||||
import org.signal.core.util.billing.BillingApi
|
import org.signal.core.util.billing.BillingApi
|
||||||
import org.signal.core.util.concurrent.DeadlockDetector
|
import org.signal.core.util.concurrent.DeadlockDetector
|
||||||
import org.signal.core.util.concurrent.LatestValueObservable
|
import org.signal.core.util.concurrent.LatestValueObservable
|
||||||
|
import org.signal.core.util.orNull
|
||||||
import org.signal.core.util.resettableLazy
|
import org.signal.core.util.resettableLazy
|
||||||
import org.signal.libsignal.net.Network
|
import org.signal.libsignal.net.Network
|
||||||
import org.signal.libsignal.zkgroup.profiles.ClientZkProfileOperations
|
import org.signal.libsignal.zkgroup.profiles.ClientZkProfileOperations
|
||||||
@@ -16,6 +17,7 @@ import org.thoughtcrime.securesms.components.TypingStatusSender
|
|||||||
import org.thoughtcrime.securesms.crypto.storage.SignalServiceDataStoreImpl
|
import org.thoughtcrime.securesms.crypto.storage.SignalServiceDataStoreImpl
|
||||||
import org.thoughtcrime.securesms.database.DatabaseObserver
|
import org.thoughtcrime.securesms.database.DatabaseObserver
|
||||||
import org.thoughtcrime.securesms.database.PendingRetryReceiptCache
|
import org.thoughtcrime.securesms.database.PendingRetryReceiptCache
|
||||||
|
import org.thoughtcrime.securesms.dependencies.AppDependencies.authWebSocket
|
||||||
import org.thoughtcrime.securesms.groups.GroupsV2Authorization
|
import org.thoughtcrime.securesms.groups.GroupsV2Authorization
|
||||||
import org.thoughtcrime.securesms.jobmanager.JobManager
|
import org.thoughtcrime.securesms.jobmanager.JobManager
|
||||||
import org.thoughtcrime.securesms.megaphone.MegaphoneRepository
|
import org.thoughtcrime.securesms.megaphone.MegaphoneRepository
|
||||||
@@ -378,6 +380,16 @@ object AppDependencies {
|
|||||||
networkModule.openConnections()
|
networkModule.openConnections()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onSystemHttpProxyChange(host: String?, port: Int?): Boolean {
|
||||||
|
val currentSystemProxy = signalServiceNetworkAccess.getConfiguration().systemHttpProxy.orNull()
|
||||||
|
return if (currentSystemProxy?.host != host || currentSystemProxy?.port != port) {
|
||||||
|
resetNetwork()
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
interface Provider {
|
interface Provider {
|
||||||
fun providePushServiceSocket(signalServiceConfiguration: SignalServiceConfiguration, groupsV2Operations: GroupsV2Operations): PushServiceSocket
|
fun providePushServiceSocket(signalServiceConfiguration: SignalServiceConfiguration, groupsV2Operations: GroupsV2Operations): PushServiceSocket
|
||||||
fun provideGroupsV2Operations(signalServiceConfiguration: SignalServiceConfiguration): GroupsV2Operations
|
fun provideGroupsV2Operations(signalServiceConfiguration: SignalServiceConfiguration): GroupsV2Operations
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.app.Application
|
|||||||
import android.app.Service
|
import android.app.Service
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.net.ProxyInfo
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import androidx.annotation.VisibleForTesting
|
import androidx.annotation.VisibleForTesting
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
@@ -86,16 +87,29 @@ class IncomingMessageObserver(private val context: Application, private val auth
|
|||||||
|
|
||||||
private val lock: ReentrantLock = ReentrantLock()
|
private val lock: ReentrantLock = ReentrantLock()
|
||||||
private val connectionNecessarySemaphore = Semaphore(0)
|
private val connectionNecessarySemaphore = Semaphore(0)
|
||||||
private val networkConnectionListener = NetworkConnectionListener(context) { isNetworkUnavailable ->
|
private var previousProxyInfo: ProxyInfo? = null
|
||||||
lock.withLock {
|
private val networkConnectionListener = NetworkConnectionListener(
|
||||||
AppDependencies.libsignalNetwork.onNetworkChange()
|
context,
|
||||||
if (isNetworkUnavailable()) {
|
{ isNetworkUnavailable ->
|
||||||
Log.w(TAG, "Lost network connection. Resetting the drained state.")
|
lock.withLock {
|
||||||
decryptionDrained = false
|
AppDependencies.libsignalNetwork.onNetworkChange()
|
||||||
|
if (isNetworkUnavailable()) {
|
||||||
|
Log.w(TAG, "Lost network connection. Resetting the drained state.")
|
||||||
|
decryptionDrained = false
|
||||||
|
}
|
||||||
|
connectionNecessarySemaphore.release()
|
||||||
}
|
}
|
||||||
connectionNecessarySemaphore.release()
|
},
|
||||||
|
{ proxyInfo ->
|
||||||
|
if (proxyInfo != previousProxyInfo) {
|
||||||
|
val networkReset = AppDependencies.onSystemHttpProxyChange(proxyInfo?.host, proxyInfo?.port)
|
||||||
|
if (networkReset) {
|
||||||
|
Log.i(TAG, "System proxy configuration changed, network reset.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
previousProxyInfo = proxyInfo
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
|
|
||||||
private val messageContentProcessor = MessageContentProcessor(context)
|
private val messageContentProcessor = MessageContentProcessor(context)
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
import android.net.ConnectivityManager
|
import android.net.ConnectivityManager
|
||||||
|
import android.net.LinkProperties
|
||||||
import android.net.Network
|
import android.net.Network
|
||||||
|
import android.net.ProxyInfo
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import org.signal.core.util.logging.Log
|
import org.signal.core.util.logging.Log
|
||||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint
|
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint
|
||||||
@@ -24,7 +26,7 @@ import org.thoughtcrime.securesms.util.ServiceUtil
|
|||||||
* API 28+ only runs on lost networks, so it provides a conditional that's always true because that is guaranteed by the call site.
|
* API 28+ only runs on lost networks, so it provides a conditional that's always true because that is guaranteed by the call site.
|
||||||
* Earlier versions use [NetworkConstraint.isMet] to query the current network state upon receiving the broadcast.
|
* Earlier versions use [NetworkConstraint.isMet] to query the current network state upon receiving the broadcast.
|
||||||
*/
|
*/
|
||||||
class NetworkConnectionListener(private val context: Context, private val onNetworkLost: (() -> Boolean) -> Unit) {
|
class NetworkConnectionListener(private val context: Context, private val onNetworkLost: (() -> Boolean) -> Unit, private val onProxySettingsChanged: ((ProxyInfo?) -> Unit)) {
|
||||||
companion object {
|
companion object {
|
||||||
private val TAG = Log.tag(NetworkConnectionListener::class.java)
|
private val TAG = Log.tag(NetworkConnectionListener::class.java)
|
||||||
}
|
}
|
||||||
@@ -55,6 +57,12 @@ class NetworkConnectionListener(private val context: Context, private val onNetw
|
|||||||
Log.d(TAG, "ConnectivityManager.NetworkCallback onLost()")
|
Log.d(TAG, "ConnectivityManager.NetworkCallback onLost()")
|
||||||
onNetworkLost { true }
|
onNetworkLost { true }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onLinkPropertiesChanged(network: Network, linkProperties: LinkProperties) {
|
||||||
|
super.onLinkPropertiesChanged(network, linkProperties)
|
||||||
|
Log.d(TAG, "ConnectivityManager.NetworkCallback onLinkPropertiesChanged()")
|
||||||
|
onProxySettingsChanged(linkProperties.httpProxy)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val connectionReceiver = object : BroadcastReceiver() {
|
private val connectionReceiver = object : BroadcastReceiver() {
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package org.thoughtcrime.securesms.push
|
package org.thoughtcrime.securesms.push
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.net.ConnectivityManager
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import com.google.i18n.phonenumbers.PhoneNumberUtil
|
import com.google.i18n.phonenumbers.PhoneNumberUtil
|
||||||
import okhttp3.CipherSuite
|
import okhttp3.CipherSuite
|
||||||
import okhttp3.ConnectionSpec
|
import okhttp3.ConnectionSpec
|
||||||
@@ -20,6 +23,7 @@ import org.thoughtcrime.securesms.net.SequentialDns
|
|||||||
import org.thoughtcrime.securesms.net.StandardUserAgentInterceptor
|
import org.thoughtcrime.securesms.net.StandardUserAgentInterceptor
|
||||||
import org.thoughtcrime.securesms.net.StaticDns
|
import org.thoughtcrime.securesms.net.StaticDns
|
||||||
import org.whispersystems.signalservice.api.push.TrustStore
|
import org.whispersystems.signalservice.api.push.TrustStore
|
||||||
|
import org.whispersystems.signalservice.internal.configuration.HttpProxy
|
||||||
import org.whispersystems.signalservice.internal.configuration.SignalCdnUrl
|
import org.whispersystems.signalservice.internal.configuration.SignalCdnUrl
|
||||||
import org.whispersystems.signalservice.internal.configuration.SignalCdsiUrl
|
import org.whispersystems.signalservice.internal.configuration.SignalCdsiUrl
|
||||||
import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration
|
import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration
|
||||||
@@ -28,6 +32,7 @@ import org.whispersystems.signalservice.internal.configuration.SignalStorageUrl
|
|||||||
import org.whispersystems.signalservice.internal.configuration.SignalSvr2Url
|
import org.whispersystems.signalservice.internal.configuration.SignalSvr2Url
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.Optional
|
import java.util.Optional
|
||||||
|
import android.net.Proxy as AndroidProxy
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides a [SignalServiceConfiguration] to be used with our service layer.
|
* Provides a [SignalServiceConfiguration] to be used with our service layer.
|
||||||
@@ -133,6 +138,28 @@ class SignalServiceNetworkAccess(context: Context) {
|
|||||||
.build()
|
.build()
|
||||||
|
|
||||||
private val APP_CONNECTION_SPEC = ConnectionSpec.MODERN_TLS
|
private val APP_CONNECTION_SPEC = ConnectionSpec.MODERN_TLS
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
private fun getSystemHttpProxy(context: Context): HttpProxy? {
|
||||||
|
return if (Build.VERSION.SDK_INT >= 23) {
|
||||||
|
val connectivityManager = ContextCompat.getSystemService(context, ConnectivityManager::class.java) ?: return null
|
||||||
|
|
||||||
|
connectivityManager
|
||||||
|
.activeNetwork
|
||||||
|
?.let { connectivityManager.getLinkProperties(it)?.httpProxy }
|
||||||
|
?.takeIf { !it.exclusionList.contains(BuildConfig.SIGNAL_URL.stripProtocol()) }
|
||||||
|
?.let { proxy -> HttpProxy(proxy.host, proxy.port) }
|
||||||
|
} else {
|
||||||
|
val host: String? = AndroidProxy.getHost(context)
|
||||||
|
val port: Int = AndroidProxy.getPort(context)
|
||||||
|
|
||||||
|
if (host != null) {
|
||||||
|
HttpProxy(host, port)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val serviceTrustStore: TrustStore = SignalServiceTrustStore(context)
|
private val serviceTrustStore: TrustStore = SignalServiceTrustStore(context)
|
||||||
@@ -187,6 +214,7 @@ class SignalServiceNetworkAccess(context: Context) {
|
|||||||
networkInterceptors = interceptors,
|
networkInterceptors = interceptors,
|
||||||
dns = Optional.of(DNS),
|
dns = Optional.of(DNS),
|
||||||
signalProxy = Optional.empty(),
|
signalProxy = Optional.empty(),
|
||||||
|
systemHttpProxy = Optional.empty(),
|
||||||
zkGroupServerPublicParams = zkGroupServerPublicParams,
|
zkGroupServerPublicParams = zkGroupServerPublicParams,
|
||||||
genericServerPublicParams = genericServerPublicParams,
|
genericServerPublicParams = genericServerPublicParams,
|
||||||
backupServerPublicParams = backupServerPublicParams,
|
backupServerPublicParams = backupServerPublicParams,
|
||||||
@@ -246,6 +274,7 @@ class SignalServiceNetworkAccess(context: Context) {
|
|||||||
networkInterceptors = interceptors,
|
networkInterceptors = interceptors,
|
||||||
dns = Optional.of(DNS),
|
dns = Optional.of(DNS),
|
||||||
signalProxy = if (SignalStore.proxy.isProxyEnabled) Optional.ofNullable(SignalStore.proxy.proxy) else Optional.empty(),
|
signalProxy = if (SignalStore.proxy.isProxyEnabled) Optional.ofNullable(SignalStore.proxy.proxy) else Optional.empty(),
|
||||||
|
systemHttpProxy = Optional.ofNullable(getSystemHttpProxy(context)),
|
||||||
zkGroupServerPublicParams = zkGroupServerPublicParams,
|
zkGroupServerPublicParams = zkGroupServerPublicParams,
|
||||||
genericServerPublicParams = genericServerPublicParams,
|
genericServerPublicParams = genericServerPublicParams,
|
||||||
backupServerPublicParams = backupServerPublicParams,
|
backupServerPublicParams = backupServerPublicParams,
|
||||||
@@ -316,6 +345,7 @@ class SignalServiceNetworkAccess(context: Context) {
|
|||||||
networkInterceptors = interceptors,
|
networkInterceptors = interceptors,
|
||||||
dns = Optional.of(DNS),
|
dns = Optional.of(DNS),
|
||||||
signalProxy = Optional.empty(),
|
signalProxy = Optional.empty(),
|
||||||
|
systemHttpProxy = Optional.empty(),
|
||||||
zkGroupServerPublicParams = zkGroupServerPublicParams,
|
zkGroupServerPublicParams = zkGroupServerPublicParams,
|
||||||
genericServerPublicParams = genericServerPublicParams,
|
genericServerPublicParams = genericServerPublicParams,
|
||||||
backupServerPublicParams = backupServerPublicParams,
|
backupServerPublicParams = backupServerPublicParams,
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package org.whispersystems.signalservice.internal.configuration
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP Proxy configuration from Android OS configuration.
|
||||||
|
*/
|
||||||
|
class HttpProxy(val host: String, val port: Int)
|
||||||
@@ -17,6 +17,7 @@ data class SignalServiceConfiguration(
|
|||||||
val networkInterceptors: List<Interceptor>,
|
val networkInterceptors: List<Interceptor>,
|
||||||
val dns: Optional<Dns>,
|
val dns: Optional<Dns>,
|
||||||
val signalProxy: Optional<SignalProxy>,
|
val signalProxy: Optional<SignalProxy>,
|
||||||
|
val systemHttpProxy: Optional<HttpProxy>,
|
||||||
val zkGroupServerPublicParams: ByteArray,
|
val zkGroupServerPublicParams: ByteArray,
|
||||||
val genericServerPublicParams: ByteArray,
|
val genericServerPublicParams: ByteArray,
|
||||||
val backupServerPublicParams: ByteArray,
|
val backupServerPublicParams: ByteArray,
|
||||||
|
|||||||
@@ -30,16 +30,27 @@ fun Network.transformAndSetRemoteConfig(remoteConfig: Map<String, Any>) {
|
|||||||
* Helper method to apply settings from the SignalServiceConfiguration.
|
* Helper method to apply settings from the SignalServiceConfiguration.
|
||||||
*/
|
*/
|
||||||
fun Network.applyConfiguration(config: SignalServiceConfiguration) {
|
fun Network.applyConfiguration(config: SignalServiceConfiguration) {
|
||||||
val proxy = config.signalProxy.orNull()
|
val signalProxy = config.signalProxy.orNull()
|
||||||
|
val systemHttpProxy = config.systemHttpProxy.orNull()
|
||||||
|
|
||||||
if (proxy == null) {
|
when {
|
||||||
this.clearProxy()
|
(signalProxy != null) -> {
|
||||||
} else {
|
try {
|
||||||
try {
|
this.setProxy(signalProxy.host, signalProxy.port)
|
||||||
this.setProxy(proxy.host, proxy.port)
|
} catch (e: IOException) {
|
||||||
} catch (e: IOException) {
|
Log.e(TAG, "Invalid proxy configuration set! Failing connections until changed.")
|
||||||
Log.e(TAG, "Invalid proxy configuration set! Failing connections until changed.")
|
this.setInvalidProxy()
|
||||||
this.setInvalidProxy()
|
}
|
||||||
|
}
|
||||||
|
(systemHttpProxy != null) -> {
|
||||||
|
try {
|
||||||
|
this.setProxy("http", systemHttpProxy.host, systemHttpProxy.port, "", "")
|
||||||
|
} catch (e: IOException) {
|
||||||
|
// The Android settings screen where this is set explicitly calls out that apps are allowed to
|
||||||
|
// ignore the HTTP Proxy setting, so if using the specified proxy would cause us to break, let's
|
||||||
|
// try just ignoring it and seeing if that still lets us connect.
|
||||||
|
Log.w(TAG, "Failed to set system HTTP proxy, ignoring and continuing...")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user