-
Notifications
You must be signed in to change notification settings - Fork 921
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Respect TTL of a DNS record for proxy config #5960
base: main
Are you sure you want to change the base?
Respect TTL of a DNS record for proxy config #5960
Conversation
Hi, @chickenchickenlove! Thanks for submitting this PR. There is a limitation in the current patch:
To address this, we need to ensure TTL is respected and refresh the address once TTL expires. final ProxyConfig proxyConfig = factory.proxyConfigSelector().select(protocol, endpoint);
final Future<InetSocketAddress> resolveFuture = addressResolverGroup.getResolver(
ctx.eventLoop().withoutContext()).resolve(proxyConfig.proxyAddress());
if (resolveFuture.isSuccess()) {
...
} else {
resolveFuture.addListener(...);
} Because we resolve the address internally, we can now accept unresolved addresses as well:
Please, let me know if you need any further information. 😉 |
This reverts commit 417bf3b.
…kenlove/armeria into 241027-proxy-config
@@ -215,10 +219,34 @@ private void acquireConnectionAndExecute0(ClientRequestContext ctx, Endpoint end | |||
} | |||
} | |||
|
|||
private ProxyConfig getProxyConfig(SessionProtocol protocol, Endpoint endpoint) { | |||
private ProxyConfig getProxyConfig(SessionProtocol protocol, Endpoint endpoint, ClientRequestContext ctx) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have to recreate ProxyConfig
with the new proxy address because the address is resolved:
if (proxyConfig.proxyAddress() != null) {
final Future<InetSocketAddress> resolveFuture = addressResolverGroup.getResolver(
ctx.eventLoop().withoutContext()).resolve(proxyConfig.proxyAddress());
resolveFuture.addListener(future -> {
if (future.isSuccess()) {
final ProxyConfig newProxyConfig = proxyConfig.withNewProxyAddress(
(InetSocketAddress) future.get());
...
}
}
}
Because getting the ProxyConfig
is now asynchronous, we have to change the signature of this method.
We have two options:
- Returning
CompletableFuture<ProxyConfig>
instead ofProxyConfig
// in HttpClientDelegate.execute() try { final CompletableFuture<ProxyConfig> future = getProxyConfig(protocol, endpoint, ctx); future.handle(....) } catch (Throwable t) { return earlyFailedResponse(t, ctx); }
- Using a callback that is executed after resolution like we do for resolving address
private void resolveProxyConfig(..., BiConsumer<@Nullable ProxyConig, @Nullable Throwable> onComplete) { final ProxyConfig proxyConfig = factory.proxyConfigSelector().select(protocol, endpoint); requireNonNull(proxyConfig, "proxyConfig"); ... }
Please let me know if you need further information. 😉
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@minwoox , Thanks for your comments 🙇♂️
I made a new commit with following second option!
Because ProxyConfig
will be resolved in other thread, so the method maybeHAProxy()
will get capturedProxyAddress
instead of ServiceRequestContext
.
Also, res
instance will be returned right away even if future don't completed.
So I used the method earlyFailed(...)
instead of earlyfailedResponse(...)
.
When you have time, Please take another look 🙇♂️
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good overall, left some minor comments 🙇
core/src/main/java/com/linecorp/armeria/client/HttpClientDelegate.java
Outdated
Show resolved
Hide resolved
final ProxyConfig newProxyConfig = proxyConfig.withNewProxyAddress(resolvedAddress); | ||
onComplete.accept(maybeHAProxy(newProxyConfig, capturedProxiedAddresses), null); | ||
} else { | ||
logger.warn("Resolved address is invalid or unresolved: {}. " + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't understand this case - are you concerned that the resolution succeeds but the InetAddress
does not exist?
Would it make sense to just align error handling with dns resolution for the normal code path (not using proxy)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for your comments!
After take a look RefreshingAddressResolver
, RefreshingAddressResolver
returns InetSocketAddress
when succeed.
As you mentioned before, it is unnecessary codes.
Therefore, I delete this codes.
@@ -40,6 +40,11 @@ public InetSocketAddress proxyAddress() { | |||
return null; | |||
} | |||
|
|||
@Override | |||
public ProxyConfig withNewProxyAddress(InetSocketAddress newProxyAddress) { | |||
return new DirectProxyConfig(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit; it may make more sense to throw an exception instead
* Returns a new proxy address instance that respects DNS TTL. | ||
* @param newProxyAddress the inet socket address | ||
*/ | ||
public abstract ProxyConfig withNewProxyAddress(InetSocketAddress newProxyAddress); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit; I believe that currently only resolved InetSocketAddress
can be used to create a ProxyConfig
.
Can you add some integration tests to confirm the current code works correctly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jrhee17 Thanks for your comments. 🙇♂️
I added two integration test codes for this feature.
Due to restrictions on mocking, narrow Access specifier
, I couldn't use a spy
instance to validate it or inject DNSResolver
as well.
That's why I made a new class called DNSResolverFacadeUtils
class 😅 .
As @minwoox mentioned, we don't need to check if InetSocketAddress
is resolved, since it will be resolved when HttpClientDelegate
calls execute()
. After removing checkArgument(...)
, I was finally able to write some integration test cases... 😅
When you have time, please take another look. 🙇♂️
core/src/main/java/com/linecorp/armeria/client/HttpClientDelegate.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/client/HttpClientDelegate.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/client/HttpClientDelegate.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/client/proxy/DirectProxyConfig.java
Outdated
Show resolved
Hide resolved
return this.sourceAddress == null ? new HAProxyConfig(proxyAddress) | ||
: new HAProxyConfig(proxyAddress, this.sourceAddress); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The input is ignored.
return this.sourceAddress == null ? new HAProxyConfig(proxyAddress) | |
: new HAProxyConfig(proxyAddress, this.sourceAddress); | |
requireNonNull(newProxyAddress, "newProxyAddress"); | |
return this.sourceAddress == null ? new HAProxyConfig(newProxyAddress) | |
: new HAProxyConfig(newProxyAddress, this.sourceAddress); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment wasn't addressed. Was it intended? Let me know if I'm missing something or misunderstood.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm really sorry about this. 🙇♂️
I missed it.
Now, I fixed it!
core/src/main/java/com/linecorp/armeria/client/proxy/ProxyConfig.java
Outdated
Show resolved
Hide resolved
@@ -44,19 +44,6 @@ void testEqualityAndHashCode(ProxyConfig config) { | |||
assertThat(equalProxyConfigs.get(0).hashCode()).isEqualTo(config.hashCode()); | |||
} | |||
|
|||
@Test | |||
void testUnresolvedProxyAddress() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we revive this test and fix it to check if ProxyConfig
is successfully created with unresolved addresses?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for your comments 🙇♂️
Good point, I didn't expect it needed to be kept at all. 😓
I revived this test and fixed it!
@ikhoon nim, thanks for your comments! 🙇♂️ |
core/src/main/java/com/linecorp/armeria/client/HttpClientDelegate.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/client/HttpClientDelegate.java
Outdated
Show resolved
Hide resolved
@@ -215,24 +224,48 @@ private void acquireConnectionAndExecute0(ClientRequestContext ctx, Endpoint end | |||
} | |||
} | |||
|
|||
private ProxyConfig getProxyConfig(SessionProtocol protocol, Endpoint endpoint) { | |||
private void resolveProxyConfig(SessionProtocol protocol, Endpoint endpoint, ClientRequestContext ctx, | |||
BiConsumer<@Nullable ProxyConfig, @Nullable Throwable> onComplete) { | |||
final ProxyConfig proxyConfig = factory.proxyConfigSelector().select(protocol, endpoint); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be efficient if we could skip the resolution process if proxyConfig.proxyAddress()
was created with IP addresses.
return Objects.hash(proxyAddress, sourceAddress); | ||
return hash(proxyAddress, sourceAddress); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Revert?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have been reverted!
core/src/main/java/com/linecorp/armeria/client/HttpClientDelegate.java
Outdated
Show resolved
Hide resolved
|
||
final InetAddress inetAddress = socketAddress.getAddress(); | ||
// If hostname and host address are the same, it was created with an IP address | ||
return socketAddress.getHostString().equals(inetAddress.getHostAddress()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ikhoon nim
I removed this code because of test fail.
final InetAddress inetAddress = socketAddress.getAddress();
if (inetAddress.isAnyLocalAddress()) {
return false; // Wildcard address
}
If IP is wildcard address, addressResolver
start to resolve wildcard address like 0.0.0.0
, ::
.
In that case, The addressResolver
try to resolve 0.0.0.0
and throws UnknownHostException
.
The error message is Caused by: java.net.UnknownHostException: Failed to resolve '0.0.0.0.akadns.net.' [A(1)] after 2 queries
.
Please refer to HAProxyClientIntegrationTest#testConnectionFailure()
. It's failed test reason described above.
I think that we don't need to resolve either 0.0.0.0
or ::
.
Also, I think 0.0.0.0
and ::
are fixed IP address.
What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agreed. Thanks for fixing that.
core/src/test/java/com/linecorp/armeria/internal/common/util/IpAddrUtilTest.java
Show resolved
Hide resolved
@ikhoon nim, thanks for your clean up commit. The one is fixed. When you have time, Please take another look. 🙇♂️ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, @chickenchickenlove!
checkArgument(sourceAddress.getAddress().getClass() == proxyAddress.getAddress().getClass(), | ||
"sourceAddress and proxyAddress should be the same type"); | ||
} else { | ||
logger.warn("Either the source or proxy address could not be resolved. " + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think of removing this warning? This message doesn't seem useful, and I'm unsure if warn
is a proper level.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree. I removed it!
Motivation:
From now on, armeria client ignores DNS TTL.
Thus the client will keep sending the request to the old proxy.
Modifications:
InetSocketAddress
mutable to support a feature which update DNS.xxxProxyConfig
(lastUpdatedTime, Schedulers)refreshInterval
JVM doesn't consider DNS TTL as default.
Therefore, client has a intent to DNS update,
IMHO, Client should configure JVM options.
Result: