From c656f85ba254b87fc57b3d6a2c14dcc7d3b64a64 Mon Sep 17 00:00:00 2001 From: Michael Wintermeyer Date: Tue, 12 Dec 2023 13:53:24 -0500 Subject: [PATCH 1/7] InetAddressResolver works compiled against jdk21 --- build.gradle | 4 +- .../docker/proxy/DockerProxyManager.java | 105 +++++++++++++++++- .../java.net.spi.InetAddressResolverProvider | 1 + 3 files changed, 102 insertions(+), 8 deletions(-) create mode 100644 docker-proxy-rule-core/src/main/resources/META-INF/services/java.net.spi.InetAddressResolverProvider diff --git a/build.gradle b/build.gradle index 96ffdc27..24af25fd 100644 --- a/build.gradle +++ b/build.gradle @@ -22,8 +22,8 @@ apply plugin: 'com.palantir.baseline' apply plugin: 'com.palantir.baseline-java-versions' javaVersions { - libraryTarget = 8 - runtime = 17 + libraryTarget = 21 + runtime = 21 } version gitVersion() diff --git a/docker-proxy-rule-core/src/main/java/com/palantir/docker/proxy/DockerProxyManager.java b/docker-proxy-rule-core/src/main/java/com/palantir/docker/proxy/DockerProxyManager.java index 15cd893c..8b61fad4 100644 --- a/docker-proxy-rule-core/src/main/java/com/palantir/docker/proxy/DockerProxyManager.java +++ b/docker-proxy-rule-core/src/main/java/com/palantir/docker/proxy/DockerProxyManager.java @@ -35,10 +35,16 @@ import java.net.InetAddress; import java.net.ProxySelector; import java.net.UnknownHostException; +import java.net.spi.InetAddressResolver; +import java.net.spi.InetAddressResolverProvider; import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.List; +import java.util.Optional; import java.util.function.Function; import java.util.function.UnaryOperator; +import java.util.stream.Stream; +import javax.annotation.Nullable; @SuppressWarnings("PreferSafeLoggableExceptions") abstract class DockerProxyManager> { @@ -115,21 +121,25 @@ private static File getDockerComposeFile(String networkName, String imageName) { } private void setNameService(DockerNameService nameService) { - String version = System.getProperty("java.version"); - if (version.startsWith("1.")) { + int featureVersion = Runtime.version().feature(); + if (featureVersion < 9) { getJava8NameServices().add(0, wrapNameService("sun.net.spi.nameservice.NameService", nameService, null)); - } else { + } else if (featureVersion < 21) { originalNameService = getJava9NameService(); setJava9NameService(wrapNameService("java.net.InetAddress$NameService", nameService, originalNameService)); + } else { + getJava21DockerProxyInetAddressResolverProvider(nameService); } } private void unsetNameService() { - String version = System.getProperty("java.version"); - if (version.startsWith("1.")) { + int featureVersion = Runtime.version().feature(); + if (featureVersion < 9) { getJava8NameServices().remove(0); - } else { + } else if (featureVersion < 21) { setJava9NameService(originalNameService); + } else { + unsetJava21DockerProxyInetAddressResolverProvider(); } } @@ -164,6 +174,89 @@ private static void setJava9NameService(Object newNameService) { } } + private static void getJava21DockerProxyInetAddressResolverProvider(DockerNameService dockerNameService) { + try { + DockerProxyInetAddressResolverProvider.dockerProxyInetAddressResolver = + new DockerProxyInetAddressResolver(dockerNameService); + } catch (Throwable e) { + throw new IllegalStateException("Unable to create Java 21+ InetAddressResolverProvider", e); + } + } + + private static void unsetJava21DockerProxyInetAddressResolverProvider() { + DockerProxyInetAddressResolverProvider.dockerProxyInetAddressResolver = null; + } + + public static class DockerProxyInetAddressResolverProvider extends InetAddressResolverProvider { + @Nullable + private static InetAddressResolver dockerProxyInetAddressResolver; + + @Override + public InetAddressResolver get(Configuration configuration) { + return Optional.ofNullable(dockerProxyInetAddressResolver) + .map(dockerResolver -> + new ForwardingInetAddressResolver(dockerResolver, configuration.builtinResolver())) + .orElseGet(configuration::builtinResolver); + } + + @Override + public String name() { + return "DockerProxyInetAddressResolverProvider"; + } + } + + private static final class DockerProxyInetAddressResolver implements InetAddressResolver { + private final DockerNameService dockerNameService; + + private DockerProxyInetAddressResolver(DockerNameService dockerNameService) { + this.dockerNameService = dockerNameService; + } + + @Override + public Stream lookupByName(String host, LookupPolicy _lookupPolicy) throws UnknownHostException { + return Arrays.stream(dockerNameService.lookupAllHostAddr(host)); + } + + @Override + public String lookupByAddress(byte[] addr) throws UnknownHostException { + return dockerNameService.getHostByAddr(addr); + } + } + + private static class ForwardingInetAddressResolver implements InetAddressResolver { + private final InetAddressResolver delegate; + private final InetAddressResolver fallback; + + ForwardingInetAddressResolver(InetAddressResolver delegate, InetAddressResolver fallback) { + this.delegate = delegate; + this.fallback = fallback; + } + + @Override + public Stream lookupByName(String host, LookupPolicy lookupPolicy) throws UnknownHostException { + try { + return delegate.lookupByName(host, lookupPolicy); + } catch (UnknownHostException e) { + if (fallback != null) { + return fallback.lookupByName(host, lookupPolicy); + } + throw e; + } + } + + @Override + public String lookupByAddress(byte[] addr) throws UnknownHostException { + try { + return delegate.lookupByAddress(addr); + } catch (UnknownHostException e) { + if (fallback != null) { + return fallback.lookupByAddress(addr); + } + throw e; + } + } + } + @SuppressWarnings("ProxyNonConstantType") private static Object wrapNameService(String className, Object delegate, Object fallback) { try { diff --git a/docker-proxy-rule-core/src/main/resources/META-INF/services/java.net.spi.InetAddressResolverProvider b/docker-proxy-rule-core/src/main/resources/META-INF/services/java.net.spi.InetAddressResolverProvider new file mode 100644 index 00000000..11eb908e --- /dev/null +++ b/docker-proxy-rule-core/src/main/resources/META-INF/services/java.net.spi.InetAddressResolverProvider @@ -0,0 +1 @@ +com.palantir.docker.proxy.DockerProxyManager$DockerProxyInetAddressResolverProvider From ce73a8459219005322ec57126e140cbffb7030f6 Mon Sep 17 00:00:00 2001 From: Michael Wintermeyer Date: Fri, 5 Jan 2024 16:10:48 -0500 Subject: [PATCH 2/7] Move jdk21 components into a separate module --- build.gradle | 3 +- docker-proxy-rule-core-jdk21/build.gradle | 19 +++ .../proxy/DockerProxyInetAddressResolver.java | 42 +++++ ...ockerProxyInetAddressResolverProvider.java | 36 +++++ .../proxy/ForwardingInetAddressResolver.java | 68 +++++++++ .../java.net.spi.InetAddressResolverProvider | 0 .../proxy/DockerContainerInfoUtilsTest.java | 56 +++++++ .../docker/proxy/DockerNameServiceTest.java | 107 +++++++++++++ .../docker/proxy/DockerProxySelectorTest.java | 144 ++++++++++++++++++ .../docker/proxy/DockerProxyManager.java | 98 +----------- .../docker/proxy/DockerProxySelector.java | 1 + settings.gradle | 1 + 12 files changed, 482 insertions(+), 93 deletions(-) create mode 100644 docker-proxy-rule-core-jdk21/build.gradle create mode 100644 docker-proxy-rule-core-jdk21/src/main/java/com/palantir/docker/proxy/DockerProxyInetAddressResolver.java create mode 100644 docker-proxy-rule-core-jdk21/src/main/java/com/palantir/docker/proxy/DockerProxyInetAddressResolverProvider.java create mode 100644 docker-proxy-rule-core-jdk21/src/main/java/com/palantir/docker/proxy/ForwardingInetAddressResolver.java rename {docker-proxy-rule-core => docker-proxy-rule-core-jdk21}/src/main/resources/META-INF/services/java.net.spi.InetAddressResolverProvider (100%) create mode 100644 docker-proxy-rule-core-jdk21/src/test/java/com/palantir/docker/proxy/DockerContainerInfoUtilsTest.java create mode 100644 docker-proxy-rule-core-jdk21/src/test/java/com/palantir/docker/proxy/DockerNameServiceTest.java create mode 100644 docker-proxy-rule-core-jdk21/src/test/java/com/palantir/docker/proxy/DockerProxySelectorTest.java diff --git a/build.gradle b/build.gradle index 24af25fd..e43d4a30 100644 --- a/build.gradle +++ b/build.gradle @@ -22,8 +22,7 @@ apply plugin: 'com.palantir.baseline' apply plugin: 'com.palantir.baseline-java-versions' javaVersions { - libraryTarget = 21 - runtime = 21 + libraryTarget = 11 } version gitVersion() diff --git a/docker-proxy-rule-core-jdk21/build.gradle b/docker-proxy-rule-core-jdk21/build.gradle new file mode 100644 index 00000000..ad475925 --- /dev/null +++ b/docker-proxy-rule-core-jdk21/build.gradle @@ -0,0 +1,19 @@ +apply plugin: 'com.palantir.external-publish-jar' + +dependencies { + api project(':docker-proxy-rule-core') + + testImplementation group: 'junit', name: 'junit' + testImplementation group: 'org.assertj', name: 'assertj-core' + testImplementation group: 'org.mockito', name: 'mockito-core' + testRuntimeOnly group: 'org.mockito', name: 'mockito-inline' +} + +javaVersion { + target = 21 + runtime = 21 +} + +moduleJvmArgs { + opens 'java.base/java.net' +} diff --git a/docker-proxy-rule-core-jdk21/src/main/java/com/palantir/docker/proxy/DockerProxyInetAddressResolver.java b/docker-proxy-rule-core-jdk21/src/main/java/com/palantir/docker/proxy/DockerProxyInetAddressResolver.java new file mode 100644 index 00000000..6b96ff34 --- /dev/null +++ b/docker-proxy-rule-core-jdk21/src/main/java/com/palantir/docker/proxy/DockerProxyInetAddressResolver.java @@ -0,0 +1,42 @@ +/* + * (c) Copyright 2024 Palantir Technologies Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.palantir.docker.proxy; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.net.spi.InetAddressResolver; +import java.util.Arrays; +import java.util.function.Supplier; +import java.util.stream.Stream; + +public final class DockerProxyInetAddressResolver implements InetAddressResolver { + private final Supplier dockerNameService; + + public DockerProxyInetAddressResolver(Supplier dockerNameService) { + this.dockerNameService = dockerNameService; + } + + @Override + public Stream lookupByName(String host, LookupPolicy _lookupPolicy) throws UnknownHostException { + return Arrays.stream(dockerNameService.get().lookupAllHostAddr(host)); + } + + @Override + public String lookupByAddress(byte[] addr) throws UnknownHostException { + return dockerNameService.get().getHostByAddr(addr); + } +} diff --git a/docker-proxy-rule-core-jdk21/src/main/java/com/palantir/docker/proxy/DockerProxyInetAddressResolverProvider.java b/docker-proxy-rule-core-jdk21/src/main/java/com/palantir/docker/proxy/DockerProxyInetAddressResolverProvider.java new file mode 100644 index 00000000..3b18e152 --- /dev/null +++ b/docker-proxy-rule-core-jdk21/src/main/java/com/palantir/docker/proxy/DockerProxyInetAddressResolverProvider.java @@ -0,0 +1,36 @@ +/* + * (c) Copyright 2024 Palantir Technologies Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.palantir.docker.proxy; + +import java.net.spi.InetAddressResolver; +import java.net.spi.InetAddressResolverProvider; + +public final class DockerProxyInetAddressResolverProvider extends InetAddressResolverProvider { + + @Override + public InetAddressResolver get(Configuration configuration) { + return new ForwardingInetAddressResolver( + new DockerProxyInetAddressResolver(DockerProxyManager::getDockerNameService), + configuration.builtinResolver(), + () -> DockerProxyManager.getDockerNameService() != null); + } + + @Override + public String name() { + return "DockerProxyInetAddressResolverProvider"; + } +} diff --git a/docker-proxy-rule-core-jdk21/src/main/java/com/palantir/docker/proxy/ForwardingInetAddressResolver.java b/docker-proxy-rule-core-jdk21/src/main/java/com/palantir/docker/proxy/ForwardingInetAddressResolver.java new file mode 100644 index 00000000..23ce94a1 --- /dev/null +++ b/docker-proxy-rule-core-jdk21/src/main/java/com/palantir/docker/proxy/ForwardingInetAddressResolver.java @@ -0,0 +1,68 @@ +/* + * (c) Copyright 2024 Palantir Technologies Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.palantir.docker.proxy; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.net.spi.InetAddressResolver; +import java.util.function.Supplier; +import java.util.stream.Stream; + +class ForwardingInetAddressResolver implements InetAddressResolver { + private final InetAddressResolver delegate; + private final InetAddressResolver fallback; + private final Supplier delegateEnabled; + + ForwardingInetAddressResolver( + InetAddressResolver delegate, InetAddressResolver fallback, Supplier delegateEnabled) { + this.delegate = delegate; + this.fallback = fallback; + this.delegateEnabled = delegateEnabled; + } + + @Override + public Stream lookupByName(String host, LookupPolicy lookupPolicy) throws UnknownHostException { + if (!delegateEnabled.get()) { + return fallback.lookupByName(host, lookupPolicy); + } + + try { + return delegate.lookupByName(host, lookupPolicy); + } catch (UnknownHostException e) { + if (fallback != null) { + return fallback.lookupByName(host, lookupPolicy); + } + throw e; + } + } + + @Override + public String lookupByAddress(byte[] addr) throws UnknownHostException { + if (!delegateEnabled.get()) { + return fallback.lookupByAddress(addr); + } + + try { + return delegate.lookupByAddress(addr); + } catch (UnknownHostException e) { + if (fallback != null) { + return fallback.lookupByAddress(addr); + } + throw e; + } + } +} diff --git a/docker-proxy-rule-core/src/main/resources/META-INF/services/java.net.spi.InetAddressResolverProvider b/docker-proxy-rule-core-jdk21/src/main/resources/META-INF/services/java.net.spi.InetAddressResolverProvider similarity index 100% rename from docker-proxy-rule-core/src/main/resources/META-INF/services/java.net.spi.InetAddressResolverProvider rename to docker-proxy-rule-core-jdk21/src/main/resources/META-INF/services/java.net.spi.InetAddressResolverProvider diff --git a/docker-proxy-rule-core-jdk21/src/test/java/com/palantir/docker/proxy/DockerContainerInfoUtilsTest.java b/docker-proxy-rule-core-jdk21/src/test/java/com/palantir/docker/proxy/DockerContainerInfoUtilsTest.java new file mode 100644 index 00000000..86165593 --- /dev/null +++ b/docker-proxy-rule-core-jdk21/src/test/java/com/palantir/docker/proxy/DockerContainerInfoUtilsTest.java @@ -0,0 +1,56 @@ +/* + * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.palantir.docker.proxy; + +import static com.palantir.docker.proxy.DockerContainerInfoUtils.IP_FORMAT_STRING; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.palantir.docker.compose.execution.DockerExecutable; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import org.junit.Test; + +public class DockerContainerInfoUtilsTest { + private static final String CONTAINER_ID = "container-id"; + + private final Process response = mock(Process.class); + private final DockerExecutable dockerExecutable = mock(DockerExecutable.class); + + @Test + public void getContainerIpFromIdDoesNotThrowWhenContainerIsStopped() throws IOException, InterruptedException { + when(response.getInputStream()).thenReturn(getDockerOutputForStoppedContainer()); + when(response.waitFor(anyLong(), any(TimeUnit.class))).thenReturn(true); + when(response.exitValue()).thenReturn(0); + when(dockerExecutable.execute("inspect", "--format", IP_FORMAT_STRING, CONTAINER_ID)) + .thenReturn(response); + + Optional ip = DockerContainerInfoUtils.getContainerIpFromId(dockerExecutable, CONTAINER_ID); + assertThat(ip).isNotPresent(); + } + + private static InputStream getDockerOutputForStoppedContainer() { + return new ByteArrayInputStream("\n".getBytes(StandardCharsets.UTF_8)); + } +} diff --git a/docker-proxy-rule-core-jdk21/src/test/java/com/palantir/docker/proxy/DockerNameServiceTest.java b/docker-proxy-rule-core-jdk21/src/test/java/com/palantir/docker/proxy/DockerNameServiceTest.java new file mode 100644 index 00000000..e797db11 --- /dev/null +++ b/docker-proxy-rule-core-jdk21/src/test/java/com/palantir/docker/proxy/DockerNameServiceTest.java @@ -0,0 +1,107 @@ +/* + * (c) Copyright 2017 Palantir Technologies Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.palantir.docker.proxy; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.common.net.InetAddresses; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Optional; +import org.junit.Test; + +public class DockerNameServiceTest { + private static final String HOST_NAME = "host"; + private static final String HOST_IP = "172.0.2.5"; + private static final InetAddress HOST_IP_INET = InetAddresses.forString("172.0.2.5"); + + private final DockerContainerInfo containerInfo = mock(DockerContainerInfo.class); + private final DockerNameService dockerNameService = new DockerNameService(containerInfo); + + @Test + public void shouldReturnIpOfHost() throws UnknownHostException { + when(containerInfo.getIpForHost(HOST_NAME)).thenReturn(Optional.of(HOST_IP)); + + InetAddress[] hostAddresses = dockerNameService.lookupAllHostAddr(HOST_NAME); + + assertThat(hostAddresses).containsExactly(HOST_IP_INET); + } + + @Test + public void shouldOnlyQueryTheSupplierOncePerLookupCall() throws UnknownHostException { + when(containerInfo.getIpForHost(HOST_NAME)).thenReturn(Optional.of(HOST_IP)); + + dockerNameService.lookupAllHostAddr(HOST_NAME); + + verify(containerInfo, times(1)).getIpForHost(HOST_NAME); + } + + @Test + public void shouldGetIpOfHostFromSupplierEveryTime() throws UnknownHostException { + when(containerInfo.getIpForHost(HOST_NAME)).thenReturn(Optional.of(HOST_IP)); + + dockerNameService.lookupAllHostAddr(HOST_NAME); + dockerNameService.lookupAllHostAddr(HOST_NAME); + + verify(containerInfo, times(2)).getIpForHost(HOST_NAME); + } + + @Test(expected = UnknownHostException.class) + public void shouldThrowUnknownHostExceptionWhenNoIpForHost() throws UnknownHostException { + when(containerInfo.getIpForHost(HOST_NAME)).thenReturn(Optional.empty()); + + dockerNameService.lookupAllHostAddr(HOST_NAME); + } + + @Test + public void shouldGetHostFromIp() throws UnknownHostException { + when(containerInfo.getHostForIp(HOST_IP)).thenReturn(Optional.of(HOST_NAME)); + + String host = dockerNameService.getHostByAddr(HOST_IP_INET.getAddress()); + + assertThat(host).isEqualTo(HOST_NAME); + } + + @Test + public void shouldOnlyQueryTheSupplierOncePerHostByAddrCall() throws UnknownHostException { + when(containerInfo.getHostForIp(HOST_IP)).thenReturn(Optional.of(HOST_NAME)); + + dockerNameService.getHostByAddr(HOST_IP_INET.getAddress()); + + verify(containerInfo, times(1)).getHostForIp(HOST_IP); + } + + @Test + public void shouldGetHostOfIpFromSupplierEveryTime() throws UnknownHostException { + when(containerInfo.getHostForIp(HOST_IP)).thenReturn(Optional.of(HOST_NAME)); + + dockerNameService.getHostByAddr(HOST_IP_INET.getAddress()); + dockerNameService.getHostByAddr(HOST_IP_INET.getAddress()); + + verify(containerInfo, times(2)).getHostForIp(HOST_IP); + } + + @Test(expected = UnknownHostException.class) + public void shouldThrowUnknownHostExceptionWhenNoHostForIp() throws UnknownHostException { + when(containerInfo.getHostForIp(HOST_IP)).thenReturn(Optional.empty()); + + dockerNameService.getHostByAddr(HOST_IP_INET.getAddress()); + } +} diff --git a/docker-proxy-rule-core-jdk21/src/test/java/com/palantir/docker/proxy/DockerProxySelectorTest.java b/docker-proxy-rule-core-jdk21/src/test/java/com/palantir/docker/proxy/DockerProxySelectorTest.java new file mode 100644 index 00000000..ebd1ff62 --- /dev/null +++ b/docker-proxy-rule-core-jdk21/src/test/java/com/palantir/docker/proxy/DockerProxySelectorTest.java @@ -0,0 +1,144 @@ +/* + * (c) Copyright 2017 Palantir Technologies Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.palantir.docker.proxy; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableList; +import com.google.common.net.InetAddresses; +import com.palantir.docker.compose.connection.Cluster; +import com.palantir.docker.compose.connection.Container; +import com.palantir.docker.compose.connection.ContainerCache; +import com.palantir.docker.compose.connection.DockerPort; +import com.palantir.docker.compose.connection.ImmutableCluster; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.ProxySelector; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; +import java.util.Optional; +import org.junit.Before; +import org.junit.Test; + +public class DockerProxySelectorTest { + private static final String CLUSTER_IP = "172.17.0.1"; + private static final int PROXY_EXTERNAL_PORT = 12345; + private static final InetSocketAddress PROXY_ADDRESS = + new InetSocketAddress(InetAddresses.forString(CLUSTER_IP), PROXY_EXTERNAL_PORT); + + private static final String TEST_IP = "172.17.0.5"; + private static final String TEST_HOSTNAME = "some-address"; + private static final URI TEST_IP_URI = createUriUnsafe("http://172.17.0.5"); + private static final URI TEST_HOSTNAME_URI = createUriUnsafe("http://some-address"); + + private final DockerContainerInfo containerInfo = mock(DockerContainerInfo.class); + private final ProxySelector originalProxySelector = mock(ProxySelector.class); + private final ProxySelector dockerProxySelector = + new DockerProxySelector(setupProxyContainer(), containerInfo, originalProxySelector); + + @Before + public void originalProxySelectorIsNoProxy() { + when(originalProxySelector.select(any())).thenReturn(ImmutableList.of(Proxy.NO_PROXY)); + } + + @Test + public void nonDockerAddressesShouldDelegateToPassedInSelector() { + when(containerInfo.getIpForHost(TEST_HOSTNAME)).thenReturn(Optional.empty()); + when(containerInfo.getHostForIp(TEST_HOSTNAME)).thenReturn(Optional.empty()); + + List selectedProxy = dockerProxySelector.select(TEST_HOSTNAME_URI); + + assertThat(selectedProxy).containsExactly(Proxy.NO_PROXY); + + verify(originalProxySelector, times(1)).select(TEST_HOSTNAME_URI); + } + + @Test + public void dockerAddressesShouldGoThroughAProxy() throws URISyntaxException { + when(containerInfo.getIpForHost(TEST_HOSTNAME)).thenReturn(Optional.of(TEST_IP)); + + List selectedProxy = dockerProxySelector.select(TEST_HOSTNAME_URI); + + assertThat(selectedProxy).containsExactly(new Proxy(Proxy.Type.SOCKS, PROXY_ADDRESS)); + } + + @Test + public void dockerIpsShouldGoThroughAProxy() { + when(containerInfo.getHostForIp(TEST_IP)).thenReturn(Optional.of(TEST_HOSTNAME)); + + List selectedProxy = dockerProxySelector.select(TEST_IP_URI); + + assertThat(selectedProxy).containsExactly(new Proxy(Proxy.Type.SOCKS, PROXY_ADDRESS)); + } + + @Test(expected = IllegalArgumentException.class) + public void connectionFailedShouldThrowOnNullUri() { + dockerProxySelector.connectFailed(null, PROXY_ADDRESS, new IOException()); + } + + @Test(expected = IllegalArgumentException.class) + public void connectionFailedShouldThrowOnNullAddress() { + dockerProxySelector.connectFailed(TEST_HOSTNAME_URI, null, new IOException()); + } + + @Test(expected = IllegalArgumentException.class) + public void connectionFailedShouldThrowOnNullException() { + dockerProxySelector.connectFailed(TEST_HOSTNAME_URI, PROXY_ADDRESS, null); + } + + @Test + public void connectionFailedShouldNotThrowOnValidArguments() { + dockerProxySelector.connectFailed(TEST_HOSTNAME_URI, PROXY_ADDRESS, new IOException()); + } + + @Test + public void connectionFailedShouldDelegateToPassedInSelector() { + IOException exception = new IOException(); + dockerProxySelector.connectFailed(TEST_HOSTNAME_URI, PROXY_ADDRESS, exception); + + verify(originalProxySelector, times(1)).connectFailed(TEST_HOSTNAME_URI, PROXY_ADDRESS, exception); + } + + private static Cluster setupProxyContainer() { + Container proxyContainer = mock(Container.class); + when(proxyContainer.port(DockerProxySelector.PROXY_CONTAINER_PORT)) + .thenReturn(new DockerPort(CLUSTER_IP, PROXY_EXTERNAL_PORT, DockerProxySelector.PROXY_CONTAINER_PORT)); + + ContainerCache containerCache = mock(ContainerCache.class); + when(containerCache.container(DockerProxySelector.PROXY_CONTAINER_NAME)).thenReturn(proxyContainer); + + return ImmutableCluster.builder() + .ip(CLUSTER_IP) + .containerCache(containerCache) + .build(); + } + + private static URI createUriUnsafe(String uriString) { + try { + return new URI(uriString); + } catch (URISyntaxException e) { + throw Throwables.propagate(e); + } + } +} diff --git a/docker-proxy-rule-core/src/main/java/com/palantir/docker/proxy/DockerProxyManager.java b/docker-proxy-rule-core/src/main/java/com/palantir/docker/proxy/DockerProxyManager.java index 8b61fad4..21746c81 100644 --- a/docker-proxy-rule-core/src/main/java/com/palantir/docker/proxy/DockerProxyManager.java +++ b/docker-proxy-rule-core/src/main/java/com/palantir/docker/proxy/DockerProxyManager.java @@ -35,16 +35,10 @@ import java.net.InetAddress; import java.net.ProxySelector; import java.net.UnknownHostException; -import java.net.spi.InetAddressResolver; -import java.net.spi.InetAddressResolverProvider; import java.nio.charset.StandardCharsets; -import java.util.Arrays; import java.util.List; -import java.util.Optional; import java.util.function.Function; import java.util.function.UnaryOperator; -import java.util.stream.Stream; -import javax.annotation.Nullable; @SuppressWarnings("PreferSafeLoggableExceptions") abstract class DockerProxyManager> { @@ -53,6 +47,7 @@ abstract class DockerProxyManager getJava8NameServices() { try { @@ -174,89 +173,6 @@ private static void setJava9NameService(Object newNameService) { } } - private static void getJava21DockerProxyInetAddressResolverProvider(DockerNameService dockerNameService) { - try { - DockerProxyInetAddressResolverProvider.dockerProxyInetAddressResolver = - new DockerProxyInetAddressResolver(dockerNameService); - } catch (Throwable e) { - throw new IllegalStateException("Unable to create Java 21+ InetAddressResolverProvider", e); - } - } - - private static void unsetJava21DockerProxyInetAddressResolverProvider() { - DockerProxyInetAddressResolverProvider.dockerProxyInetAddressResolver = null; - } - - public static class DockerProxyInetAddressResolverProvider extends InetAddressResolverProvider { - @Nullable - private static InetAddressResolver dockerProxyInetAddressResolver; - - @Override - public InetAddressResolver get(Configuration configuration) { - return Optional.ofNullable(dockerProxyInetAddressResolver) - .map(dockerResolver -> - new ForwardingInetAddressResolver(dockerResolver, configuration.builtinResolver())) - .orElseGet(configuration::builtinResolver); - } - - @Override - public String name() { - return "DockerProxyInetAddressResolverProvider"; - } - } - - private static final class DockerProxyInetAddressResolver implements InetAddressResolver { - private final DockerNameService dockerNameService; - - private DockerProxyInetAddressResolver(DockerNameService dockerNameService) { - this.dockerNameService = dockerNameService; - } - - @Override - public Stream lookupByName(String host, LookupPolicy _lookupPolicy) throws UnknownHostException { - return Arrays.stream(dockerNameService.lookupAllHostAddr(host)); - } - - @Override - public String lookupByAddress(byte[] addr) throws UnknownHostException { - return dockerNameService.getHostByAddr(addr); - } - } - - private static class ForwardingInetAddressResolver implements InetAddressResolver { - private final InetAddressResolver delegate; - private final InetAddressResolver fallback; - - ForwardingInetAddressResolver(InetAddressResolver delegate, InetAddressResolver fallback) { - this.delegate = delegate; - this.fallback = fallback; - } - - @Override - public Stream lookupByName(String host, LookupPolicy lookupPolicy) throws UnknownHostException { - try { - return delegate.lookupByName(host, lookupPolicy); - } catch (UnknownHostException e) { - if (fallback != null) { - return fallback.lookupByName(host, lookupPolicy); - } - throw e; - } - } - - @Override - public String lookupByAddress(byte[] addr) throws UnknownHostException { - try { - return delegate.lookupByAddress(addr); - } catch (UnknownHostException e) { - if (fallback != null) { - return fallback.lookupByAddress(addr); - } - throw e; - } - } - } - @SuppressWarnings("ProxyNonConstantType") private static Object wrapNameService(String className, Object delegate, Object fallback) { try { diff --git a/docker-proxy-rule-core/src/main/java/com/palantir/docker/proxy/DockerProxySelector.java b/docker-proxy-rule-core/src/main/java/com/palantir/docker/proxy/DockerProxySelector.java index 7ae3ff3e..308a70a6 100644 --- a/docker-proxy-rule-core/src/main/java/com/palantir/docker/proxy/DockerProxySelector.java +++ b/docker-proxy-rule-core/src/main/java/com/palantir/docker/proxy/DockerProxySelector.java @@ -35,6 +35,7 @@ public final class DockerProxySelector extends ProxySelector { private final DockerContainerInfo containerInfo; private final ProxySelector delegate; + @SuppressWarnings("DnsLookup") public DockerProxySelector(Cluster containers, DockerContainerInfo containerInfo, ProxySelector delegate) { // We can't call InetSocketAddress.createUnresolved here as some downstream libraries cannot deal with // getAddress returning null. diff --git a/settings.gradle b/settings.gradle index ccbb3ea3..0abc59ec 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,5 +1,6 @@ rootProject.name='docker-proxy-rule' include ':docker-proxy-rule-core' +include ':docker-proxy-rule-core-jdk21' include ':docker-proxy-rule-junit4' include ':docker-proxy-junit-jupiter' From 07dc39d0ec9062e0df04d65af209ee9167f4df66 Mon Sep 17 00:00:00 2001 From: Michael Wintermeyer Date: Mon, 8 Jan 2024 00:25:20 -0500 Subject: [PATCH 3/7] Upgrade byte-buddy --- versions.lock | 4 ++-- versions.props | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/versions.lock b/versions.lock index 64840004..a0e5113f 100644 --- a/versions.lock +++ b/versions.lock @@ -43,8 +43,8 @@ org.slf4j:slf4j-api:1.7.30 (2 constraints: fe2397b2) org.yaml:snakeyaml:1.27 (1 constraints: 7217fa27) [Test dependencies] -net.bytebuddy:byte-buddy:1.12.8 (1 constraints: 460b40de) -net.bytebuddy:byte-buddy-agent:1.12.8 (1 constraints: 460b40de) +net.bytebuddy:byte-buddy:1.14.11 (1 constraints: 460b40de) +net.bytebuddy:byte-buddy-agent:1.14.11 (1 constraints: 460b40de) org.assertj:assertj-core:3.22.0 (1 constraints: 39053f3b) org.junit.jupiter:junit-jupiter:5.8.2 (1 constraints: 11051e36) org.junit.jupiter:junit-jupiter-engine:5.8.2 (1 constraints: 0c0edf3b) diff --git a/versions.props b/versions.props index d1bada66..9191e6a9 100644 --- a/versions.props +++ b/versions.props @@ -6,3 +6,4 @@ org.assertj:assertj-core = 3.22.0 org.junit.jupiter:* = 5.8.2 org.mockito:* = 4.4.0 org.hamcrest:* = 2.1 +net.bytebuddy:* = 1.14.11 From 85581b51d168c29331e45dd5b2ba041a22681a97 Mon Sep 17 00:00:00 2001 From: Michael Wintermeyer Date: Mon, 8 Jan 2024 11:33:29 -0500 Subject: [PATCH 4/7] empty commit From f19e73a5ccc8dc4c8ab3f0ee6125b5517a9509c6 Mon Sep 17 00:00:00 2001 From: Michael Wintermeyer Date: Mon, 8 Jan 2024 12:06:59 -0500 Subject: [PATCH 5/7] address comments --- build.gradle | 1 + .../java/com/palantir/docker/proxy/DockerProxyManager.java | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/build.gradle b/build.gradle index e43d4a30..e6dd5faa 100644 --- a/build.gradle +++ b/build.gradle @@ -23,6 +23,7 @@ apply plugin: 'com.palantir.baseline-java-versions' javaVersions { libraryTarget = 11 + runtime = 17 } version gitVersion() diff --git a/docker-proxy-rule-core/src/main/java/com/palantir/docker/proxy/DockerProxyManager.java b/docker-proxy-rule-core/src/main/java/com/palantir/docker/proxy/DockerProxyManager.java index 21746c81..0d404cba 100644 --- a/docker-proxy-rule-core/src/main/java/com/palantir/docker/proxy/DockerProxyManager.java +++ b/docker-proxy-rule-core/src/main/java/com/palantir/docker/proxy/DockerProxyManager.java @@ -39,6 +39,7 @@ import java.util.List; import java.util.function.Function; import java.util.function.UnaryOperator; +import javax.annotation.Nullable; @SuppressWarnings("PreferSafeLoggableExceptions") abstract class DockerProxyManager> { @@ -47,6 +48,8 @@ abstract class DockerProxyManager Date: Mon, 8 Jan 2024 18:16:49 +0000 Subject: [PATCH 6/7] Add generated changelog entries --- changelog/@unreleased/pr-574.v2.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelog/@unreleased/pr-574.v2.yml diff --git a/changelog/@unreleased/pr-574.v2.yml b/changelog/@unreleased/pr-574.v2.yml new file mode 100644 index 00000000..31dfe80b --- /dev/null +++ b/changelog/@unreleased/pr-574.v2.yml @@ -0,0 +1,5 @@ +type: improvement +improvement: + description: InetAddressResolver works compiled against jdk21 + links: + - https://github.com/palantir/docker-proxy-rule/pull/574 From 87ec05867be2ca056d7913d41ed6c633f70adc98 Mon Sep 17 00:00:00 2001 From: Michael Wintermeyer Date: Mon, 8 Jan 2024 13:41:00 -0500 Subject: [PATCH 7/7] BooleanSupplier --- .../docker/proxy/ForwardingInetAddressResolver.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docker-proxy-rule-core-jdk21/src/main/java/com/palantir/docker/proxy/ForwardingInetAddressResolver.java b/docker-proxy-rule-core-jdk21/src/main/java/com/palantir/docker/proxy/ForwardingInetAddressResolver.java index 23ce94a1..e956fb17 100644 --- a/docker-proxy-rule-core-jdk21/src/main/java/com/palantir/docker/proxy/ForwardingInetAddressResolver.java +++ b/docker-proxy-rule-core-jdk21/src/main/java/com/palantir/docker/proxy/ForwardingInetAddressResolver.java @@ -19,16 +19,16 @@ import java.net.InetAddress; import java.net.UnknownHostException; import java.net.spi.InetAddressResolver; -import java.util.function.Supplier; +import java.util.function.BooleanSupplier; import java.util.stream.Stream; class ForwardingInetAddressResolver implements InetAddressResolver { private final InetAddressResolver delegate; private final InetAddressResolver fallback; - private final Supplier delegateEnabled; + private final BooleanSupplier delegateEnabled; ForwardingInetAddressResolver( - InetAddressResolver delegate, InetAddressResolver fallback, Supplier delegateEnabled) { + InetAddressResolver delegate, InetAddressResolver fallback, BooleanSupplier delegateEnabled) { this.delegate = delegate; this.fallback = fallback; this.delegateEnabled = delegateEnabled; @@ -36,7 +36,7 @@ class ForwardingInetAddressResolver implements InetAddressResolver { @Override public Stream lookupByName(String host, LookupPolicy lookupPolicy) throws UnknownHostException { - if (!delegateEnabled.get()) { + if (!delegateEnabled.getAsBoolean()) { return fallback.lookupByName(host, lookupPolicy); } @@ -52,7 +52,7 @@ public Stream lookupByName(String host, LookupPolicy lookupPolicy) @Override public String lookupByAddress(byte[] addr) throws UnknownHostException { - if (!delegateEnabled.get()) { + if (!delegateEnabled.getAsBoolean()) { return fallback.lookupByAddress(addr); }