Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@
import com.google.devtools.build.lib.vfs.Path;
import io.grpc.CallCredentials;
import io.grpc.ClientInterceptor;
import io.grpc.HttpConnectProxiedSocketAddress;
import io.grpc.ManagedChannel;
import io.grpc.ProxiedSocketAddress;
import io.grpc.ProxyDetector;
import io.grpc.auth.MoreCallCredentials;
import io.grpc.netty.GrpcSslContexts;
import io.grpc.netty.NegotiationType;
Expand All @@ -52,6 +55,8 @@
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.URI;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -204,11 +209,115 @@ private static NettyChannelBuilder newNettyChannelBuilder(String targetUrl, Stri
return NettyChannelBuilder.forTarget(targetUrl).defaultLoadBalancingPolicy("round_robin");
}

if (!proxy.startsWith("unix:")) {
throw new IOException("Remote proxy unsupported: " + proxy);
if (proxy.startsWith("unix:")) {
// Unix domain socket proxy
return newUnixNettyChannelBuilder(proxy).overrideAuthority(targetUrl);
}

return newUnixNettyChannelBuilder(proxy).overrideAuthority(targetUrl);
// HTTP proxy - parse the proxy URL and create a ProxyDetector
InetSocketAddress proxyAddress = parseHttpProxyAddress(proxy);
String proxyUsername = parseHttpProxyUsername(proxy);
String proxyPassword = parseHttpProxyPassword(proxy);

ProxyDetector proxyDetector =
createHttpProxyDetector(proxyAddress, proxyUsername, proxyPassword);

return NettyChannelBuilder.forTarget(targetUrl)
.defaultLoadBalancingPolicy("round_robin")
.proxyDetector(proxyDetector);
}

/**
* Parses an HTTP proxy address from a proxy URL string.
*
* @param proxy the proxy URL (e.g., "http://user:pass@proxy.example.com:8080")
* @return the proxy InetSocketAddress
* @throws IOException if the proxy URL is invalid
*/
private static InetSocketAddress parseHttpProxyAddress(String proxy) throws IOException {
try {
// Remove credentials from the URL for parsing
String cleanProxy = proxy.replaceFirst("://[^@]+@", "://");
URI proxyUri = new URI(cleanProxy.startsWith("http") ? cleanProxy : "http://" + cleanProxy);
String host = proxyUri.getHost();
int port = proxyUri.getPort();
if (port == -1) {
port = proxyUri.getScheme().equals("https") ? 443 : 80;
}
if (host == null) {
throw new IOException("Invalid proxy URL (no host): " + proxy);
}
return new InetSocketAddress(host, port);
} catch (Exception e) {
throw new IOException("Invalid proxy URL: " + proxy, e);
}
}

/** Parses the username from a proxy URL, or returns null if not present. */
@Nullable
private static String parseHttpProxyUsername(String proxy) {
int atIndex = proxy.indexOf('@');
if (atIndex == -1) {
return null;
}
int schemeEnd = proxy.indexOf("://");
if (schemeEnd == -1) {
schemeEnd = -3; // Adjust for no scheme
}
String userInfo = proxy.substring(schemeEnd + 3, atIndex);
int colonIndex = userInfo.indexOf(':');
if (colonIndex == -1) {
return userInfo;
}
return userInfo.substring(0, colonIndex);
}

/** Parses the password from a proxy URL, or returns null if not present. */
@Nullable
private static String parseHttpProxyPassword(String proxy) {
int atIndex = proxy.indexOf('@');
if (atIndex == -1) {
return null;
}
int schemeEnd = proxy.indexOf("://");
if (schemeEnd == -1) {
schemeEnd = -3;
}
String userInfo = proxy.substring(schemeEnd + 3, atIndex);
int colonIndex = userInfo.indexOf(':');
if (colonIndex == -1) {
return null;
}
return userInfo.substring(colonIndex + 1);
}

/**
* Creates a ProxyDetector for HTTP CONNECT proxy.
*
* @param proxyAddress the proxy server address
* @param username optional proxy username
* @param password optional proxy password
* @return a ProxyDetector that routes all connections through the specified proxy
*/
private static ProxyDetector createHttpProxyDetector(
InetSocketAddress proxyAddress, @Nullable String username, @Nullable String password) {
return new ProxyDetector() {
@Nullable
@Override
public ProxiedSocketAddress proxyFor(SocketAddress targetServerAddress) {
if (!(targetServerAddress instanceof InetSocketAddress)) {
return null;
}
HttpConnectProxiedSocketAddress.Builder builder =
HttpConnectProxiedSocketAddress.newBuilder()
.setProxyAddress(proxyAddress)
.setTargetAddress((InetSocketAddress) targetServerAddress);
if (username != null && password != null) {
builder.setUsername(username).setPassword(password);
}
return builder.build();
}
};
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,20 @@
import com.google.common.base.Ascii;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.authandtls.AuthAndTLSOptions;
import com.google.devtools.build.lib.remote.common.RemoteCacheClient;
import com.google.devtools.build.lib.remote.disk.DiskCacheClient;
import com.google.devtools.build.lib.remote.http.HttpCacheClient;
import com.google.devtools.build.lib.remote.options.RemoteOptions;
import com.google.devtools.build.lib.remote.util.DigestUtil;
import com.google.devtools.build.lib.remote.util.RemoteProxyHelper;
import com.google.devtools.build.lib.remote.util.RemoteProxyHelper.ProxyInfo;
import com.google.devtools.build.lib.vfs.Path;
import io.netty.channel.unix.DomainSocketAddress;
import java.io.IOException;
import java.net.URI;
import java.util.Map;
import javax.annotation.Nullable;

/** A factory class for providing a {@link CombinedCacheClient}. */
Expand All @@ -50,11 +54,25 @@ public static CombinedCacheClient create(
DigestUtil digestUtil,
RemoteRetrier retrier)
throws IOException {
return create(
options, creds, authAndTlsOptions, workingDirectory, digestUtil, retrier, ImmutableMap.of());
}

public static CombinedCacheClient create(
RemoteOptions options,
@Nullable Credentials creds,
AuthAndTLSOptions authAndTlsOptions,
Path workingDirectory,
DigestUtil digestUtil,
RemoteRetrier retrier,
Map<String, String> clientEnv)
throws IOException {
Preconditions.checkNotNull(workingDirectory, "workingDirectory");
RemoteCacheClient httpCacheClient = null;
DiskCacheClient diskCacheClient = null;
if (isHttpCache(options)) {
httpCacheClient = createHttp(options, creds, authAndTlsOptions, digestUtil, retrier);
httpCacheClient =
createHttp(options, creds, authAndTlsOptions, digestUtil, retrier, clientEnv);
}
if (isDiskCache(options)) {
diskCacheClient =
Expand All @@ -77,7 +95,8 @@ private static RemoteCacheClient createHttp(
Credentials creds,
AuthAndTLSOptions authAndTlsOptions,
DigestUtil digestUtil,
RemoteRetrier retrier) {
RemoteRetrier retrier,
Map<String, String> clientEnv) {
Preconditions.checkNotNull(options.remoteCache, "remoteCache");

try {
Expand All @@ -88,6 +107,7 @@ private static RemoteCacheClient createHttp(

if (options.remoteProxy != null) {
if (options.remoteProxy.startsWith("unix:")) {
// Unix domain socket proxy
return HttpCacheClient.create(
new DomainSocketAddress(options.remoteProxy.replaceFirst("^unix:", "")),
uri,
Expand All @@ -100,19 +120,57 @@ private static RemoteCacheClient createHttp(
creds,
authAndTlsOptions);
} else {
throw new Exception("Remote cache proxy unsupported: " + options.remoteProxy);
// HTTP proxy from flag (e.g., http://proxy:8080)
ProxyInfo proxyInfo = RemoteProxyHelper.parseProxyAddress(options.remoteProxy);
if (proxyInfo.hasProxy()) {
return HttpCacheClient.create(
uri,
Math.toIntExact(options.remoteTimeout.toSeconds()),
options.remoteMaxConnections,
options.remoteVerifyDownloads,
ImmutableList.copyOf(options.remoteHeaders),
digestUtil,
retrier,
creds,
authAndTlsOptions,
proxyInfo.address(),
proxyInfo.username(),
proxyInfo.password());
} else {
throw new Exception("Invalid remote cache proxy: " + options.remoteProxy);
}
}
} else {
return HttpCacheClient.create(
uri,
Math.toIntExact(options.remoteTimeout.toSeconds()),
options.remoteMaxConnections,
options.remoteVerifyDownloads,
ImmutableList.copyOf(options.remoteHeaders),
digestUtil,
retrier,
creds,
authAndTlsOptions);
// No explicit proxy flag - check environment variables (HTTPS_PROXY, HTTP_PROXY)
RemoteProxyHelper proxyHelper = new RemoteProxyHelper(clientEnv);
ProxyInfo proxyInfo = proxyHelper.createProxyIfNeeded(uri);
if (proxyInfo.hasProxy()) {
return HttpCacheClient.create(
uri,
Math.toIntExact(options.remoteTimeout.toSeconds()),
options.remoteMaxConnections,
options.remoteVerifyDownloads,
ImmutableList.copyOf(options.remoteHeaders),
digestUtil,
retrier,
creds,
authAndTlsOptions,
proxyInfo.address(),
proxyInfo.username(),
proxyInfo.password());
} else {
// Direct connection
return HttpCacheClient.create(
uri,
Math.toIntExact(options.remoteTimeout.toSeconds()),
options.remoteMaxConnections,
options.remoteVerifyDownloads,
ImmutableList.copyOf(options.remoteHeaders),
digestUtil,
retrier,
creds,
authAndTlsOptions);
}
}
} catch (Exception e) {
throw new RuntimeException(e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,8 @@ private void initHttpAndDiskCache(
Preconditions.checkNotNull(env.getWorkingDirectory(), "workingDirectory"),
digestUtil,
new RemoteRetrier(
remoteOptions, HTTP_RESULT_CLASSIFIER, retryScheduler, circuitBreaker));
remoteOptions, HTTP_RESULT_CLASSIFIER, retryScheduler, circuitBreaker),
env.getClientEnv());
} catch (IOException e) {
handleInitFailure(env, e, Code.CACHE_INIT_FAILURE);
return;
Expand Down
Loading