Skip to content

Commit 2481f39

Browse files
authored
Docker Solver: Answer all service replicas IPs (#684)
1 parent 4dbdc2a commit 2481f39

File tree

13 files changed

+207
-46
lines changed

13 files changed

+207
-46
lines changed

RELEASE-NOTES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
## 5.9.0
2+
* Answer all docker service replicas IPs, see #434.
3+
14
## 5.8.4
25
* DNS over HTTPS, see #138
36
* Option to disable a specific container to be resolved from DPS, see #596.

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
version=5.8.4-snapshot
1+
version=5.9.0-snapshot

src/main/java/com/mageddo/dns/utils/Messages.java

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import java.util.List;
77
import java.util.Optional;
88

9+
import com.mageddo.commons.Collections;
910
import com.mageddo.commons.lang.Objects;
1011
import com.mageddo.dns.Hostname;
1112
import com.mageddo.dnsproxyserver.config.Config.Entry;
@@ -123,12 +124,17 @@ public static Message aAnswer(Message query, String ip) {
123124
}
124125

125126
public static Message aAnswer(Message query, String ip, long ttl) {
127+
// FIXME EFS esse copy deveria ser feito por quem chama
128+
// o mapper para evitar várias copias sem necessidade
126129
final var res = withNoErrorResponse(copy(query));
127130
if (StringUtils.isBlank(ip)) {
128131
return res;
129132
}
130-
final var answer = new ARecord(res.getQuestion()
131-
.getName(), DClass.IN, ttl, Ips.toAddress(ip)
133+
final var answer = new ARecord(
134+
findQuestionName(query),
135+
DClass.IN,
136+
ttl,
137+
Ips.toAddress(ip)
132138
);
133139
res.addRecord(answer, Section.ANSWER);
134140
return res;
@@ -251,12 +257,12 @@ public static Message quadAnswer(Message query, String ip) {
251257
}
252258

253259
public static Message quadAnswer(final Message query, final String ip, final long ttl) {
254-
final var res = withNoErrorResponse(query.clone());
260+
final var res = withNoErrorResponse(copy(query));
255261
if (StringUtils.isBlank(ip)) {
256262
return res;
257263
}
258-
final var answer = new AAAARecord(res.getQuestion()
259-
.getName(), DClass.IN, ttl, Ips.toAddress(ip)
264+
final var answer = new AAAARecord(
265+
findQuestionName(query), DClass.IN, ttl, Ips.toAddress(ip)
260266
);
261267
res.addRecord(answer, Section.ANSWER);
262268
return res;
@@ -273,6 +279,17 @@ public static Message answer(Message query, String ip, Entry.Type type) {
273279
return answer(query, ip, type, DEFAULT_TTL);
274280
}
275281

282+
public static Message answer(Message query, List<String> ips, Entry.Type type, long ttl) {
283+
if (Collections.isEmptyOrNull(ips)) {
284+
return answer(query, (String) null, type, ttl);
285+
}
286+
Message res = query;
287+
for (final var ip : ips) {
288+
res = answer(res, ip, type, ttl);
289+
}
290+
return res;
291+
}
292+
276293
public static Message answer(Message query, String ip, Entry.Type type, long ttl) {
277294
Validate.notNull(type, "type must not be null, query=%s", toHostnameQuery(query));
278295
return switch (type) {
@@ -287,10 +304,11 @@ static Message withNoErrorResponse(Message res) {
287304
}
288305

289306
public static Message withResponseCode(Message res, int rRode) {
290-
withDefaultResponseHeaders(res);
291-
res.getHeader()
307+
final var r = withDefaultResponseHeaders(res);
308+
r
309+
.getHeader()
292310
.setRcode(rRode);
293-
return res;
311+
return r;
294312
}
295313

296314
public static int getRCode(Message m) {
@@ -368,9 +386,9 @@ public static Message authoritativeAnswer(Message query, String ip, Entry.Type t
368386
}
369387

370388
public static Message authoritativeAnswer(
371-
Message query, String ip, Entry.Type type, long ttl
389+
Message query, List<String> ips, Entry.Type type, long ttl
372390
) {
373-
return authoritative(answer(query, ip, type, ttl));
391+
return authoritative(answer(query, ips, type, ttl));
374392
}
375393

376394
public static boolean isAuthoritative(Message m) {

src/main/java/com/mageddo/dnsproxyserver/solver/AddressResolution.java

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
package com.mageddo.dnsproxyserver.solver;
22

33
import java.time.Duration;
4+
import java.util.List;
45

6+
import com.mageddo.commons.Collections;
57
import com.mageddo.commons.lang.Objects;
68
import com.mageddo.dnsproxyserver.config.Config;
79
import com.mageddo.net.IP;
810

911
import lombok.Builder;
12+
import lombok.Singular;
1013
import lombok.Value;
1114

1215
import static java.util.Objects.requireNonNullElse;
@@ -17,14 +20,11 @@ public class AddressResolution {
1720

1821
boolean hostnameMatched;
1922

20-
IP ip;
23+
@Singular
24+
List<IP> ips;
2125

2226
Duration ttl;
2327

24-
public String getIpText() {
25-
return this.ip != null ? this.ip.toText() : null;
26-
}
27-
2828
public Duration getTTL(Duration def) {
2929
return requireNonNullElse(this.ttl, def);
3030
}
@@ -38,19 +38,17 @@ public boolean isHostNameNotMatched() {
3838
}
3939

4040
public boolean hasNotIP() {
41-
return this.ip == null;
41+
return !this.hasIp();
4242
}
4343

4444
public boolean hasIp() {
45-
return this.ip != null;
45+
return Collections.isNotEmpty(this.ips);
4646
}
4747

48-
public String getIp(Config.Entry.Type type) {
48+
@SuppressWarnings("unchecked")
49+
public List<String> getIps(Config.Entry.Type type) {
4950
final var version = type.toVersion();
50-
if (this.hasNotIP() || version == null || this.ip.versionIs(version)) {
51-
return this.getIpText();
52-
}
53-
return null;
51+
return IpMapper.toText(this.ips, version);
5452
}
5553

5654
public static AddressResolution matched(IP ip) {
@@ -62,6 +60,12 @@ public static AddressResolution matched(IP ip, Integer ttl) {
6260
}
6361

6462
public static AddressResolution matched(IP ip, Duration ttl) {
63+
if (ip == null) {
64+
return builder()
65+
.hostnameMatched(true)
66+
.ttl(ttl)
67+
.build();
68+
}
6569
return builder()
6670
.hostnameMatched(true)
6771
.ip(ip)
@@ -75,4 +79,7 @@ public static AddressResolution notMatched() {
7579
.build();
7680
}
7781

82+
public String firstAsText() {
83+
return IpMapper.toText(Collections.first(this.ips));
84+
}
7885
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.mageddo.dnsproxyserver.solver;
2+
3+
import java.util.List;
4+
import java.util.Objects;
5+
6+
import com.mageddo.commons.Collections;
7+
import com.mageddo.net.IP;
8+
9+
import org.apache.commons.lang3.ObjectUtils;
10+
11+
public class IpMapper {
12+
13+
public static List<String> toText(List<IP> ips) {
14+
return Collections.mapNonNulls(ips, IpMapper::toText);
15+
}
16+
17+
public static String toText(IP ip) {
18+
return ip != null ? ip.toText() : null;
19+
}
20+
21+
public static List<String> toText(List<IP> ips, IP.Version version) {
22+
if (version == null) {
23+
return IpMapper.toText(ips);
24+
}
25+
return ObjectUtils.firstNonNull(ips, Collections.<IP>emptyList())
26+
.stream()
27+
.filter(Objects::nonNull)
28+
.filter(ip -> ip.versionIs(version))
29+
.map(IpMapper::toText)
30+
.toList();
31+
}
32+
}

src/main/java/com/mageddo/dnsproxyserver/solver/QueryResponseHandler.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public Response mapDynamicFromResolution(
5757
return HostnameEvaluator.eval(
5858
askedHost,
5959
version,
60-
hostnameQuery -> map(query, finder, hostnameQuery, type)
60+
hostnameQuery -> this.map(query, finder, hostnameQuery, type)
6161
);
6262
}
6363

@@ -89,7 +89,7 @@ public Response map(Message query, AddressResolution res, Entry.Type type) {
8989
final var ttl = res.getTTL(this.defaultTTL);
9090
final var msg = Messages.authoritativeAnswer(
9191
query,
92-
res.getIp(type),
92+
res.getIps(type),
9393
type,
9494
ttl.toSeconds()
9595
);

src/main/java/com/mageddo/dnsproxyserver/solver/docker/SolverDocker.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ public Response handle(Message query) {
3939
return null;
4040
}
4141

42-
return this.handler.mapDynamicFromResolution(query,
43-
this.containerSolvingService::findBestMatch
42+
return this.handler.mapDynamicFromResolution(
43+
query, this.containerSolvingService::findBestMatch
4444
);
4545
}
4646

src/main/java/com/mageddo/dnsproxyserver/solver/docker/application/ContainerSolvingService.java

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.mageddo.dnsproxyserver.solver.docker.application;
22

3+
import java.util.List;
34
import java.util.Map;
45
import java.util.Objects;
56
import java.util.Optional;
@@ -10,9 +11,9 @@
1011
import javax.inject.Singleton;
1112

1213
import com.mageddo.dnsproxyserver.config.application.Configs;
14+
import com.mageddo.dnsproxyserver.solver.AddressResolution;
1315
import com.mageddo.dnsproxyserver.solver.HostnameQuery;
1416
import com.mageddo.dnsproxyserver.solver.docker.Container;
15-
import com.mageddo.dnsproxyserver.solver.AddressResolution;
1617
import com.mageddo.dnsproxyserver.solver.docker.Network;
1718
import com.mageddo.dnsproxyserver.solver.docker.dataprovider.ContainerDAO;
1819
import com.mageddo.dnsproxyserver.solver.docker.dataprovider.DockerDAO;
@@ -40,24 +41,28 @@ public class ContainerSolvingService {
4041
public AddressResolution findBestMatch(HostnameQuery query) {
4142
final var stopWatch = StopWatch.createStarted();
4243
final var matchedContainers = this.containerDAO.findActiveContainersMatching(query);
43-
final var foundIp = matchedContainers
44-
.stream()
45-
.map(it -> this.findBestIpMatch(it, query.getVersion()))
46-
.filter(Objects::nonNull)
47-
.findFirst()
48-
.orElse(null);
44+
final var foundIps = this.mapToIps(matchedContainers, query);
4945
final var hostnameMatched = !matchedContainers.isEmpty();
5046
log.trace(
51-
"status=findDone, query={}, found={}, hostnameMatched={}, time={}",
52-
query, foundIp, hostnameMatched, stopWatch.getTime()
47+
"status=done, query={}, found={}, hostnameMatched={}, time={}",
48+
query, foundIps, hostnameMatched, stopWatch.getTime()
5349
);
5450
return AddressResolution
5551
.builder()
5652
.hostnameMatched(hostnameMatched)
57-
.ip(IP.of(foundIp))
53+
.ips(foundIps)
5854
.build();
5955
}
6056

57+
List<IP> mapToIps(List<Container> containers, HostnameQuery query) {
58+
return containers
59+
.stream()
60+
.map(it -> this.findBestIpMatch(it, query.getVersion()))
61+
.filter(Objects::nonNull)
62+
.map(IP::of)
63+
.toList();
64+
}
65+
6166
public String findBestIpMatch(Container c) {
6267
return this.findBestIpMatch(c, IP.Version.IPV4);
6368
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package com.mageddo.dnsproxyserver.solver;
2+
3+
import java.util.List;
4+
5+
import com.mageddo.net.IP;
6+
7+
import org.junit.jupiter.api.Test;
8+
9+
import testing.templates.IpTemplates;
10+
11+
import static org.assertj.core.api.Assertions.assertThat;
12+
13+
class IpMapperTest {
14+
15+
@Test
16+
void mustReturnAllVersionsAreMixed() {
17+
18+
final var mixed = List.of(IpTemplates.local(), IpTemplates.localIpv6());
19+
20+
final var ips = IpMapper.toText(mixed);
21+
22+
assertThat(ips)
23+
.hasSize(2)
24+
.containsExactly(
25+
"10.10.0.1", "2001:db8:1:0:0:0:0:2"
26+
);
27+
28+
}
29+
@Test
30+
void musGetOnlyIpv6() {
31+
32+
final var mixed = List.of(
33+
IpTemplates.local(),
34+
IpTemplates.localIpv6(),
35+
IpTemplates.localIpv6_3()
36+
);
37+
38+
final var ips = IpMapper.toText(mixed, IP.Version.IPV6);
39+
40+
assertThat(ips)
41+
.hasSize(2)
42+
.containsExactly(
43+
"2001:db8:1:0:0:0:0:2",
44+
"2001:db8:1:0:0:0:0:3"
45+
);
46+
47+
}
48+
}

0 commit comments

Comments
 (0)