Skip to content

Commit d3d8e07

Browse files
committed
Apply RFC 6724 destination ordering and family preferences to DNS results.
Tighten resolver semantics and filtering for HTTP/TCP connect targets.
1 parent f6b6dbb commit d3d8e07

File tree

5 files changed

+407
-201
lines changed

5 files changed

+407
-201
lines changed
Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
/*
2+
* ====================================================================
3+
* Licensed to the Apache Software Foundation (ASF) under one
4+
* or more contributor license agreements. See the NOTICE file
5+
* distributed with this work for additional information
6+
* regarding copyright ownership. The ASF licenses this file
7+
* to you under the Apache License, Version 2.0 (the
8+
* "License"); you may not use this file except in compliance
9+
* with the License. You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing,
14+
* software distributed under the License is distributed on an
15+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16+
* KIND, either express or implied. See the License for the
17+
* specific language governing permissions and limitations
18+
* under the License.
19+
* ====================================================================
20+
*
21+
* This software consists of voluntary contributions made by many
22+
* individuals on behalf of the Apache Software Foundation. For more
23+
* information on the Apache Software Foundation, please see
24+
* <http://www.apache.org/>.
25+
*
26+
*/
27+
28+
package org.apache.hc.client5.testing;
29+
30+
import static org.junit.jupiter.api.Assertions.assertEquals;
31+
import static org.junit.jupiter.api.Assertions.assertTrue;
32+
33+
import java.net.Inet6Address;
34+
import java.net.InetAddress;
35+
import java.net.UnknownHostException;
36+
import java.util.ArrayList;
37+
import java.util.Arrays;
38+
import java.util.List;
39+
40+
import org.apache.hc.client5.http.AddressSelectingDnsResolver;
41+
import org.apache.hc.client5.http.DnsResolver;
42+
import org.apache.hc.client5.http.SystemDefaultDnsResolver;
43+
import org.apache.hc.client5.http.config.ProtocolFamilyPreference;
44+
import org.junit.jupiter.api.Assertions;
45+
import org.junit.jupiter.api.Test;
46+
import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
47+
48+
@EnabledIfSystemProperty(named = "httpclient.rfc6724.it", matches = "true")
49+
class AddressSelectingDnsResolverIT {
50+
51+
private static final String PROP_HOST = "httpclient.rfc6724.host";
52+
53+
@Test
54+
void interleave_isStableInterleavingOfDefault_forSameResolution() throws Exception {
55+
final String host = System.getProperty(PROP_HOST, "localhost");
56+
57+
final InetAddress[] resolvedOnce = SystemDefaultDnsResolver.INSTANCE.resolve(host);
58+
59+
final DnsResolver captured = new DnsResolver() {
60+
@Override
61+
public InetAddress[] resolve(final String h) throws UnknownHostException {
62+
if (!host.equals(h)) {
63+
throw new UnknownHostException(h);
64+
}
65+
return resolvedOnce.clone();
66+
}
67+
68+
@Override
69+
public String resolveCanonicalHostname(final String h) throws UnknownHostException {
70+
return SystemDefaultDnsResolver.INSTANCE.resolveCanonicalHostname(h);
71+
}
72+
};
73+
74+
final AddressSelectingDnsResolver rDefault =
75+
new AddressSelectingDnsResolver(captured, ProtocolFamilyPreference.DEFAULT);
76+
77+
final AddressSelectingDnsResolver rInterleave =
78+
new AddressSelectingDnsResolver(captured, ProtocolFamilyPreference.INTERLEAVE);
79+
80+
final InetAddress[] outDefault = rDefault.resolve(host);
81+
final InetAddress[] outInterleave = rInterleave.resolve(host);
82+
83+
// 0) Both outputs must be permutations of the captured, single resolution.
84+
Assertions.assertNotNull(outDefault);
85+
assertSameElements(Arrays.asList(resolvedOnce), Arrays.asList(outDefault));
86+
Assertions.assertNotNull(outInterleave);
87+
assertSameElements(Arrays.asList(resolvedOnce), Arrays.asList(outInterleave));
88+
89+
// 1) Same elements between DEFAULT and INTERLEAVE (no drops, no additions).
90+
assertSameElements(Arrays.asList(outDefault), Arrays.asList(outInterleave));
91+
92+
// 2) Family counts must match between DEFAULT and INTERLEAVE.
93+
final Counts cDefault = countFamilies(outDefault);
94+
final Counts cInterleave = countFamilies(outInterleave);
95+
assertEquals(cDefault.v4, cInterleave.v4);
96+
assertEquals(cDefault.v6, cInterleave.v6);
97+
98+
// 3) INTERLEAVE must be the stable interleaving of the DEFAULT-ordered list.
99+
final List<InetAddress> expected = expectedInterleaveFromBaseline(Arrays.asList(outDefault));
100+
assertEquals(expected, Arrays.asList(outInterleave));
101+
102+
// 4) If both families are present, the first 2*min(v4,v6) addresses must alternate by family.
103+
final int pairs = Math.min(cInterleave.v4, cInterleave.v6);
104+
if (pairs > 0) {
105+
assertAlternatingPrefix(outInterleave, 2 * pairs);
106+
}
107+
108+
// 5) Relative order within each family is preserved from DEFAULT.
109+
assertFamilyRelativeOrderPreserved(outDefault, outInterleave);
110+
111+
// Diagnostics for manual runs.
112+
System.out.println("Host: " + host);
113+
dump("DEFAULT", outDefault);
114+
dump("INTERLEAVE", outInterleave);
115+
}
116+
117+
private static void assertSameElements(final List<InetAddress> a, final List<InetAddress> b) {
118+
assertEquals(a.size(), b.size());
119+
120+
final List<InetAddress> remaining = new ArrayList<>(b);
121+
for (final InetAddress x : a) {
122+
assertTrue(remaining.remove(x), "Missing address: " + x);
123+
}
124+
assertTrue(remaining.isEmpty(), "Extra addresses: " + remaining);
125+
}
126+
127+
private static final class Counts {
128+
final int v4;
129+
final int v6;
130+
131+
Counts(final int v4, final int v6) {
132+
this.v4 = v4;
133+
this.v6 = v6;
134+
}
135+
}
136+
137+
private static Counts countFamilies(final InetAddress[] out) {
138+
int v4 = 0;
139+
int v6 = 0;
140+
for (final InetAddress a : out) {
141+
if (a instanceof Inet6Address) {
142+
v6++;
143+
} else {
144+
v4++;
145+
}
146+
}
147+
return new Counts(v4, v6);
148+
}
149+
150+
private static void assertAlternatingPrefix(final InetAddress[] out, final int length) {
151+
if (length <= 1) {
152+
return;
153+
}
154+
final boolean firstV6 = out[0] instanceof Inet6Address;
155+
for (int i = 0; i < length; i++) {
156+
final boolean expectV6 = firstV6 == (i % 2 == 0);
157+
final boolean isV6 = out[i] instanceof Inet6Address;
158+
assertEquals(expectV6, isV6, "Not alternating at index " + i);
159+
}
160+
}
161+
162+
private static void assertFamilyRelativeOrderPreserved(
163+
final InetAddress[] base,
164+
final InetAddress[] interleaved) {
165+
166+
final List<InetAddress> baseV6 = new ArrayList<>();
167+
final List<InetAddress> baseV4 = new ArrayList<>();
168+
for (final InetAddress a : base) {
169+
if (a instanceof Inet6Address) {
170+
baseV6.add(a);
171+
} else {
172+
baseV4.add(a);
173+
}
174+
}
175+
176+
final List<InetAddress> gotV6 = new ArrayList<>();
177+
final List<InetAddress> gotV4 = new ArrayList<>();
178+
for (final InetAddress a : interleaved) {
179+
if (a instanceof Inet6Address) {
180+
gotV6.add(a);
181+
} else {
182+
gotV4.add(a);
183+
}
184+
}
185+
186+
assertEquals(baseV6, gotV6, "IPv6 relative order changed");
187+
assertEquals(baseV4, gotV4, "IPv4 relative order changed");
188+
}
189+
190+
private static List<InetAddress> expectedInterleaveFromBaseline(final List<InetAddress> baseline) {
191+
if (baseline.size() <= 1) {
192+
return baseline;
193+
}
194+
195+
final List<InetAddress> v6 = new ArrayList<>();
196+
final List<InetAddress> v4 = new ArrayList<>();
197+
198+
for (final InetAddress a : baseline) {
199+
if (a instanceof Inet6Address) {
200+
v6.add(a);
201+
} else {
202+
v4.add(a);
203+
}
204+
}
205+
206+
if (v6.isEmpty() || v4.isEmpty()) {
207+
return baseline;
208+
}
209+
210+
final boolean startV6 = baseline.get(0) instanceof Inet6Address;
211+
212+
final List<InetAddress> out = new ArrayList<>(baseline.size());
213+
int i6 = 0;
214+
int i4 = 0;
215+
216+
while (i6 < v6.size() || i4 < v4.size()) {
217+
if (startV6) {
218+
if (i6 < v6.size()) {
219+
out.add(v6.get(i6++));
220+
}
221+
if (i4 < v4.size()) {
222+
out.add(v4.get(i4++));
223+
}
224+
} else {
225+
if (i4 < v4.size()) {
226+
out.add(v4.get(i4++));
227+
}
228+
if (i6 < v6.size()) {
229+
out.add(v6.get(i6++));
230+
}
231+
}
232+
}
233+
234+
return out;
235+
}
236+
237+
private static void dump(final String label, final InetAddress[] out) {
238+
int v4 = 0;
239+
int v6 = 0;
240+
241+
System.out.println("Preference: " + label);
242+
for (final InetAddress a : out) {
243+
if (a instanceof Inet6Address) {
244+
v6++;
245+
System.out.println(" IPv6 " + a.getHostAddress());
246+
} else {
247+
v4++;
248+
System.out.println(" IPv4 " + a.getHostAddress());
249+
}
250+
}
251+
System.out.println("Counts: IPv4=" + v4 + " IPv6=" + v6);
252+
System.out.println();
253+
}
254+
}

httpclient5-testing/src/test/java/org/apache/hc/client5/testing/ManualRfc6724ResolverIT.java

Lines changed: 0 additions & 87 deletions
This file was deleted.

0 commit comments

Comments
 (0)