Skip to content
This repository has been archived by the owner on Nov 8, 2023. It is now read-only.

Commit

Permalink
Fix several HTTP proxy issues with multinetworking.
Browse files Browse the repository at this point in the history
1. Send PROXY_CHANGE_ACTION broadcast when any network's proxy changes,
   not just the default network.
2. When a process is bound to a particular Network, update the proxy
   system properties to those for the bound Network, and keep them
   updated when PROXY_CHANGE_ACTION broadcasts are received.
3. Make Network.openConnection() use the proxy for the Network.

bug:17905627
bug:17420465
bug:18144582

(cherry-pick of https://android-review.googlesource.com/#/c/115170)

Change-Id: Ia2819985e6108a8c121e74c683a5646becfd0a97
  • Loading branch information
JensenPaul committed Dec 10, 2014
1 parent 8b33cf4 commit e0bef71
Show file tree
Hide file tree
Showing 7 changed files with 139 additions and 42 deletions.
13 changes: 11 additions & 2 deletions core/java/android/app/ActivityThread.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.hardware.display.DisplayManagerGlobal;
import android.net.ConnectivityManager;
import android.net.IConnectivityManager;
import android.net.LinkProperties;
import android.net.Network;
import android.net.Proxy;
import android.net.ProxyInfo;
import android.net.Uri;
Expand Down Expand Up @@ -839,7 +842,13 @@ public void clearDnsCache() {
}

public void setHttpProxy(String host, String port, String exclList, Uri pacFileUrl) {
Proxy.setHttpProxySystemProperty(host, port, exclList, pacFileUrl);
final Network network = ConnectivityManager.getProcessDefaultNetwork();
if (network != null) {
Proxy.setHttpProxySystemProperty(
ConnectivityManager.from(getSystemContext()).getDefaultProxy());
} else {
Proxy.setHttpProxySystemProperty(host, port, exclList, pacFileUrl);
}
}

public void processInBackground() {
Expand Down Expand Up @@ -4430,7 +4439,7 @@ private void handleBindApplication(AppBindData data) {
// crash if we can't get it.
IConnectivityManager service = IConnectivityManager.Stub.asInterface(b);
try {
ProxyInfo proxyInfo = service.getProxy();
final ProxyInfo proxyInfo = service.getDefaultProxy();
Proxy.setHttpProxySystemProperty(proxyInfo);
} catch (RemoteException e) {}
}
Expand Down
47 changes: 38 additions & 9 deletions core/java/android/net/ConnectivityManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,13 @@ public class ConnectivityManager {
public static final int NETID_UNSET = 0;

private final IConnectivityManager mService;
/**
* A kludge to facilitate static access where a Context pointer isn't available, like in the
* case of the static set/getProcessDefaultNetwork methods and from the Network class.
* TODO: Remove this after deprecating the static methods in favor of non-static methods or
* methods that take a Context argument.
*/
private static ConnectivityManager sInstance;

private INetworkManagementService mNMService;

Expand Down Expand Up @@ -1397,6 +1404,7 @@ public boolean isDefaultNetworkActive() {
*/
public ConnectivityManager(IConnectivityManager service) {
mService = checkNotNull(service, "missing IConnectivityManager");
sInstance = this;
}

/** {@hide} */
Expand All @@ -1418,6 +1426,18 @@ public static final void enforceTetherChangePermission(Context context) {
}
}

/**
* @deprecated - use getSystemService. This is a kludge to support static access in certain
* situations where a Context pointer is unavailable.
* @hide
*/
public static ConnectivityManager getInstance() {
if (sInstance == null) {
throw new IllegalStateException("No ConnectivityManager yet constructed");
}
return sInstance;
}

/**
* Get the set of tetherable, available interfaces. This list is limited by
* device configuration and current interface existence.
Expand Down Expand Up @@ -1749,20 +1769,26 @@ public ProxyInfo getGlobalProxy() {
}

/**
* Get the HTTP proxy settings for the current default network. Note that
* if a global proxy is set, it will override any per-network setting.
* Get the current default HTTP proxy settings. If a global proxy is set it will be returned,
* otherwise if this process is bound to a {@link Network} using
* {@link #setProcessDefaultNetwork} then that {@code Network}'s proxy is returned, otherwise
* the default network's proxy is returned.
*
* @return the {@link ProxyInfo} for the current HTTP proxy, or {@code null} if no
* HTTP proxy is active.
*
* <p>This method requires the call to hold the permission
* {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
* {@hide}
* @deprecated Deprecated in favor of {@link #getLinkProperties}
* @hide
*/
public ProxyInfo getProxy() {
public ProxyInfo getDefaultProxy() {
final Network network = getProcessDefaultNetwork();
if (network != null) {
final ProxyInfo globalProxy = getGlobalProxy();
if (globalProxy != null) return globalProxy;
final LinkProperties lp = getLinkProperties(network);
if (lp != null) return lp.getHttpProxy();
return null;
}
try {
return mService.getProxy();
return mService.getDefaultProxy();
} catch (RemoteException e) {
return null;
}
Expand Down Expand Up @@ -2475,6 +2501,9 @@ public static boolean setProcessDefaultNetwork(Network network) {
return true;
}
if (NetworkUtils.bindProcessToNetwork(netId)) {
// Set HTTP proxy system properties to match network.
// TODO: Deprecate this static method and replace it with a non-static version.
Proxy.setHttpProxySystemProperty(getInstance().getDefaultProxy());
// Must flush DNS cache as new network may have different DNS resolutions.
InetAddress.clearDnsCache();
// Must flush socket pool as idle sockets will be bound to previous network and may
Expand Down
2 changes: 1 addition & 1 deletion core/java/android/net/IConnectivityManager.aidl
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ interface IConnectivityManager

void setGlobalProxy(in ProxyInfo p);

ProxyInfo getProxy();
ProxyInfo getDefaultProxy();

void setDataDependency(int networkType, boolean met);

Expand Down
37 changes: 34 additions & 3 deletions core/java/android/net/Network.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.ProxySelector;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
Expand Down Expand Up @@ -244,16 +245,46 @@ public InetAddress[] getAllByName(String host) throws UnknownHostException {
* @see java.net.URL#openConnection()
*/
public URLConnection openConnection(URL url) throws IOException {
final ConnectivityManager cm = ConnectivityManager.getInstance();
// TODO: Should this be optimized to avoid fetching the global proxy for every request?
ProxyInfo proxyInfo = cm.getGlobalProxy();
if (proxyInfo == null) {
// TODO: Should this be optimized to avoid fetching LinkProperties for every request?
final LinkProperties lp = cm.getLinkProperties(this);
if (lp != null) proxyInfo = lp.getHttpProxy();
}
java.net.Proxy proxy = null;
if (proxyInfo != null) {
proxy = proxyInfo.makeProxy();
} else {
proxy = java.net.Proxy.NO_PROXY;
}
return openConnection(url, proxy);
}

/**
* Opens the specified {@link URL} on this {@code Network}, such that all traffic will be sent
* on this Network. The URL protocol must be {@code HTTP} or {@code HTTPS}.
*
* @param proxy the proxy through which the connection will be established.
* @return a {@code URLConnection} to the resource referred to by this URL.
* @throws MalformedURLException if the URL protocol is not HTTP or HTTPS.
* @throws IllegalArgumentException if the argument proxy is null.
* @throws IOException if an error occurs while opening the connection.
* @see java.net.URL#openConnection()
* @hide
*/
public URLConnection openConnection(URL url, java.net.Proxy proxy) throws IOException {
if (proxy == null) throw new IllegalArgumentException("proxy is null");
maybeInitHttpClient();
String protocol = url.getProtocol();
OkHttpClient client;
// TODO: HttpHandler creates OkHttpClients that share the default ResponseCache.
// Could this cause unexpected behavior?
// TODO: Should the network's proxy be specified?
if (protocol.equals("http")) {
client = HttpHandler.createHttpOkHttpClient(null /* proxy */);
client = HttpHandler.createHttpOkHttpClient(proxy);
} else if (protocol.equals("https")) {
client = HttpsHandler.createHttpsOkHttpClient(null /* proxy */);
client = HttpsHandler.createHttpsOkHttpClient(proxy);
} else {
// OkHttpClient only supports HTTP and HTTPS and returns a null URLStreamHandler if
// passed another protocol.
Expand Down
3 changes: 2 additions & 1 deletion core/java/android/net/ProxyInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,8 @@ public String toString() {
if (!Uri.EMPTY.equals(mPacFileUrl)) {
sb.append("PAC Script: ");
sb.append(mPacFileUrl);
} else if (mHost != null) {
}
if (mHost != null) {
sb.append("[");
sb.append(mHost);
sb.append("] ");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,10 @@
import android.graphics.Bitmap;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.net.Proxy;
import android.net.ProxyInfo;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
Expand Down Expand Up @@ -96,26 +94,10 @@ protected void onCreate(Bundle savedInstanceState) {
done(CAPTIVE_PORTAL_APP_RETURN_WANTED_AS_IS);
}

final ConnectivityManager cm = ConnectivityManager.from(this);
final Network network = new Network(mNetId);
ConnectivityManager.setProcessDefaultNetwork(network);

// Set HTTP proxy system properties to those of the selected Network.
final LinkProperties lp = ConnectivityManager.from(this).getLinkProperties(network);
if (lp != null) {
final ProxyInfo proxyInfo = lp.getHttpProxy();
String host = "";
String port = "";
String exclList = "";
Uri pacFileUrl = Uri.EMPTY;
if (proxyInfo != null) {
host = proxyInfo.getHost();
port = Integer.toString(proxyInfo.getPort());
exclList = proxyInfo.getExclusionListAsString();
pacFileUrl = proxyInfo.getPacFileUrl();
}
Proxy.setHttpProxySystemProperty(host, port, exclList, pacFileUrl);
Log.v(TAG, "Set proxy system properties to " + proxyInfo);
}
// Also initializes proxy system properties.
cm.setProcessDefaultNetwork(network);

// Proxy system properties must be initialized before setContentView is called because
// setContentView initializes the WebView logic which in turn reads the system properties.
Expand All @@ -124,8 +106,7 @@ protected void onCreate(Bundle savedInstanceState) {
getActionBar().setDisplayShowHomeEnabled(false);

// Exit app if Network disappears.
final NetworkCapabilities networkCapabilities =
ConnectivityManager.from(this).getNetworkCapabilities(network);
final NetworkCapabilities networkCapabilities = cm.getNetworkCapabilities(network);
if (networkCapabilities == null) {
finish();
return;
Expand All @@ -140,7 +121,7 @@ public void onLost(Network lostNetwork) {
for (int transportType : networkCapabilities.getTransportTypes()) {
builder.addTransportType(transportType);
}
ConnectivityManager.from(this).registerNetworkCallback(builder.build(), mNetworkCallback);
cm.registerNetworkCallback(builder.build(), mNetworkCallback);

final WebView myWebView = (WebView) findViewById(R.id.webview);
myWebView.clearCache(true);
Expand Down
50 changes: 48 additions & 2 deletions services/core/java/com/android/server/ConnectivityService.java
Original file line number Diff line number Diff line change
Expand Up @@ -2618,7 +2618,7 @@ public void reportBadNetwork(Network network) {
}
}

public ProxyInfo getProxy() {
public ProxyInfo getDefaultProxy() {
// this information is already available as a world read/writable jvm property
// so this API change wouldn't have a benifit. It also breaks the passing
// of proxy info to all the JVMs.
Expand All @@ -2630,6 +2630,34 @@ public ProxyInfo getProxy() {
}
}

// Convert empty ProxyInfo's to null as null-checks are used to determine if proxies are present
// (e.g. if mGlobalProxy==null fall back to network-specific proxy, if network-specific
// proxy is null then there is no proxy in place).
private ProxyInfo canonicalizeProxyInfo(ProxyInfo proxy) {
if (proxy != null && TextUtils.isEmpty(proxy.getHost())
&& (proxy.getPacFileUrl() == null || Uri.EMPTY.equals(proxy.getPacFileUrl()))) {
proxy = null;
}
return proxy;
}

// ProxyInfo equality function with a couple modifications over ProxyInfo.equals() to make it
// better for determining if a new proxy broadcast is necessary:
// 1. Canonicalize empty ProxyInfos to null so an empty proxy compares equal to null so as to
// avoid unnecessary broadcasts.
// 2. Make sure all parts of the ProxyInfo's compare true, including the host when a PAC URL
// is in place. This is important so legacy PAC resolver (see com.android.proxyhandler)
// changes aren't missed. The legacy PAC resolver pretends to be a simple HTTP proxy but
// actually uses the PAC to resolve; this results in ProxyInfo's with PAC URL, host and port
// all set.
private boolean proxyInfoEqual(ProxyInfo a, ProxyInfo b) {
a = canonicalizeProxyInfo(a);
b = canonicalizeProxyInfo(b);
// ProxyInfo.equals() doesn't check hosts when PAC URLs are present, but we need to check
// hosts even when PAC URLs are present to account for the legacy PAC resolver.
return Objects.equals(a, b) && (a == null || Objects.equals(a.getHost(), b.getHost()));
}

public void setGlobalProxy(ProxyInfo proxyProperties) {
enforceConnectivityInternalPermission();

Expand Down Expand Up @@ -2747,6 +2775,20 @@ private void handleApplyDefaultProxy(ProxyInfo proxy) {
}
}

// If the proxy has changed from oldLp to newLp, resend proxy broadcast with default proxy.
// This method gets called when any network changes proxy, but the broadcast only ever contains
// the default proxy (even if it hasn't changed).
// TODO: Deprecate the broadcast extras as they aren't necessarily applicable in a multi-network
// world where an app might be bound to a non-default network.
private void updateProxy(LinkProperties newLp, LinkProperties oldLp, NetworkAgentInfo nai) {
ProxyInfo newProxyInfo = newLp == null ? null : newLp.getHttpProxy();
ProxyInfo oldProxyInfo = oldLp == null ? null : oldLp.getHttpProxy();

if (!proxyInfoEqual(newProxyInfo, oldProxyInfo)) {
sendProxyBroadcast(getDefaultProxy());
}
}

private void handleDeprecatedGlobalHttpProxy() {
String proxy = Settings.Global.getString(mContext.getContentResolver(),
Settings.Global.HTTP_PROXY);
Expand Down Expand Up @@ -3660,7 +3702,11 @@ private void updateLinkProperties(NetworkAgentInfo networkAgent, LinkProperties
updateDnses(newLp, oldLp, netId, flushDns, useDefaultDns);

updateClat(newLp, oldLp, networkAgent);
if (isDefaultNetwork(networkAgent)) handleApplyDefaultProxy(newLp.getHttpProxy());
if (isDefaultNetwork(networkAgent)) {
handleApplyDefaultProxy(newLp.getHttpProxy());
} else {
updateProxy(newLp, oldLp, networkAgent);
}
// TODO - move this check to cover the whole function
if (!Objects.equals(newLp, oldLp)) {
notifyIfacesChanged();
Expand Down

0 comments on commit e0bef71

Please sign in to comment.