Skip to content

Commit

Permalink
init commit
Browse files Browse the repository at this point in the history
  • Loading branch information
fritzprix committed Apr 27, 2019
1 parent de3b47b commit 1fc9ec1
Show file tree
Hide file tree
Showing 8 changed files with 369 additions and 107 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/target
1 change: 1 addition & 0 deletions .idea/compiler.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

200 changes: 139 additions & 61 deletions .idea/workspace.xml

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#ROBUST DNS

## Problem Statement
> DNS setting on user's environment can cause huge impact on overall user experience. some ill performing DNS are still
widely used general user. one of widely accepted the solution for the problem is dns caching, however, it makes user device
more vulnerable to DNS poisoning. so here I propose another alternative for the problem.

## Approach : Use response arrived first
> By sending concurrent DNS query to multiple DNS provider other than system default and accepting response which arrives first can provide guarantee on max response time

## dependencies
- dnsjava
32 changes: 25 additions & 7 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,24 @@
<artifactId>robust-dns</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>jar</packaging>

<profiles>
<profile>
<id>debug</id>
</profile>
<profile>
<id>release</id>
</profile>
</profiles>

<!-- start of wagon-git plugin configuration-->
<pluginRepositories>
<pluginRepository>
<id>synergian-repo</id>
<url>https://raw.github.com/synergian/wagon-git/releases</url>
</pluginRepository>
</pluginRepositories>

<properties>
<rxjava_version>2.1.12</rxjava_version>
<lombok_version>1.16.6</lombok_version>
Expand All @@ -25,13 +43,13 @@
</licenses>

<build>
<extensions>
<extension>
<groupId>ar.com.synergian</groupId>
<artifactId>wagon-git</artifactId>
<version>0.3.1</version>
</extension>
</extensions>
<!-- <extensions>-->
<!-- <extension>-->
<!-- <groupId>ar.com.synergian</groupId>-->
<!-- <artifactId>wagon-git</artifactId>-->
<!-- <version>0.3.1</version>-->
<!-- </extension>-->
<!-- </extensions>-->
<resources>
<resource>
<directory>src/main/resources</directory>
Expand Down
178 changes: 139 additions & 39 deletions src/main/java/com/doodream/robustdns/RobustDnsResolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,66 +2,166 @@

import io.reactivex.Observable;
import io.reactivex.Single;
import io.reactivex.schedulers.Schedulers;
import org.xbill.DNS.*;

import java.io.IOException;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

public class RobustDnsResolver {

private static final String[] CREDIBLE_DNS_ADDRESS = {
"8.8.8.8",
"8.8.4.4",
"1.1.1.1",
"205.251.198.30"
};
private boolean updateOnExpire;
private long cacheTimeoutInMills;

private final ExtendedResolver resolver;
public static class DnsRecord {
InetAddress address;
long expireAt;

public DnsRecord(InetAddress resolved, long expireAt) {
address = resolved;
this.expireAt = expireAt;
}
}

private final ConcurrentHashMap<String, DnsRecord> cache = new ConcurrentHashMap<>();
private boolean cacheEnabled;
private Resolver[] rsv;

public static class Builder {
private final RobustDnsResolver resolver = new RobustDnsResolver();
private List<String> urls;
private long timeout;
private TimeUnit timeUnit;
private boolean cacheEnabled;
private long cacheTimeoutInMills;
private boolean autoUpdate;

public Builder() { }

public Builder failoverDns(String ...dnsUrls) throws UnknownHostException {
List<String> urls = new ArrayList<>();
if(dnsUrls != null && dnsUrls.length > 0) {
urls.addAll(Arrays.asList(dnsUrls));
}
this.urls = urls;
return this;
}

public Builder timeout(long timeout, TimeUnit timeUnit) {
this.timeout = timeUnit.toMillis(timeout);
this.timeUnit = timeUnit;
return this;
}

public Builder updateOnExpire(boolean enable) {
this.autoUpdate = enable;
return this;
}

public Builder cacheTimeout(long timeout, TimeUnit timeUnit) {
this.cacheTimeoutInMills = timeUnit.toMillis(timeout);
return this;
}

public RobustDnsResolver(String ...dnsUrls) throws UnknownHostException {
List<String> urls = new ArrayList<>();
if(dnsUrls != null && dnsUrls.length > 0) {
urls.addAll(Arrays.asList(dnsUrls));
public Builder cache(boolean enable) {
this.cacheEnabled = enable;
return this;
}
urls.addAll(Arrays.asList(CREDIBLE_DNS_ADDRESS));
String[] allDnsUrls = urls.toArray(new String[0]);
resolver = new ExtendedResolver(Observable.fromArray(allDnsUrls)
.map(dnsAddress -> new SimpleResolver(dnsAddress))
.toList()
.blockingGet()
.toArray(new Resolver[0]));

public RobustDnsResolver build() throws UnknownHostException {

resolver.cacheEnabled = cacheEnabled;
List<Resolver> resolvers = new ArrayList<>(Arrays.asList(Lookup.getDefaultResolver()));

if(!urls.isEmpty()) {

resolver.rsv = Observable.fromIterable(urls)
.map(url -> new SimpleResolver(url))
.cast(Resolver.class)
.toList()
.doOnSuccess(rsvs -> rsvs.addAll(resolvers))
.blockingGet()
.toArray(new Resolver[0]);
} else {
resolver.rsv = resolvers.toArray(new Resolver[0]);
}
if(timeUnit != null) {
for (Resolver resv : resolver.rsv) {
resv.setTimeout((int) timeUnit.toSeconds(timeout), (int) (timeUnit.toMillis(timeout) - (timeUnit.toSeconds(timeout) * 1000)));
}
}
resolver.updateOnExpire = autoUpdate;
resolver.cacheTimeoutInMills = cacheTimeoutInMills;
return resolver;
}

}

public RobustDnsResolver() throws UnknownHostException {
this(new String[0]);
public static Builder builder() {
return new Builder();
}

public Single<String> resolve(String name) throws IOException {
return Single.create(emitter -> {
private RobustDnsResolver() {
}

public Single<InetAddress> resolve(String name) {
if(cache.containsKey(name)) {
final DnsRecord record = cache.get(name);
if(record.expireAt > System.currentTimeMillis()) {
cache.remove(name);
if(updateOnExpire) {
resolve(name).subscribe();
}
}
return Single.just(record.address);
}
return Single.<InetAddress>create(emitter -> {
final Record question = Record.newRecord(Name.concatenate(Name.fromString(name), Name.root), Type.A, DClass.IN, TTL.MAX_VALUE);
final Message query = Message.newQuery(question);
resolver.sendAsync(query, new ResolverListener() {
@Override
public void receiveMessage(Object o, Message message) {
Record[] records = message.getSectionArray(Section.ANSWER);
if(records != null || records.length > 0) {
emitter.onSuccess(records[0].rdataToString());
} else {
emitter.onError(new UnknownHostException(String.format(Locale.ENGLISH, "%s not found", name)));
}
}

@Override
public void handleException(Object o, Exception e) {
emitter.onError(e);
}
});
});
for (Resolver resolver : rsv) {
resolver.sendAsync(query, new ResolverListener() {
@Override
public void receiveMessage(Object o, Message message) {
Record[] records = message.getSectionArray(Section.ANSWER);

if(records != null) {
if(records.length == 0) {
emitter.onError(new UnknownHostException(String.format(Locale.ENGLISH, "%s is unreachable", name)));
return;
}
try {
final InetAddress resolved = Inet4Address.getByName(records[0].rdataToString());
DnsRecord record = new DnsRecord(
resolved,
System.currentTimeMillis() + cacheTimeoutInMills
);

if(cacheEnabled) {
cache.put(name, record);
}
emitter.onSuccess(resolved);
} catch (UnknownHostException e) {
emitter.onError(e);
}
} else {
emitter.onError(new UnknownHostException(String.format(Locale.ENGLISH, "%s not found", name)));
}
}

@Override
public void handleException(Object o, Exception e) {
emitter.onError(e);
}
});
}
}).subscribeOn(Schedulers.io());
}
}
45 changes: 45 additions & 0 deletions src/test/java/com/doodream/robustdns/DnsAgingTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.doodream.robustdns;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

import java.util.Arrays;
import java.util.Collection;
import java.util.concurrent.TimeUnit;

@RunWith(Parameterized.class)
public class DnsAgingTest {
private static final String[] CREDIBLE_DNS_ADDRESS = {
"8.8.8.8",
"8.8.4.4",
"1.1.1.1",
"205.251.198.30",
"168.126.63.1"
};

private final RobustDnsResolver resolver;

@Parameterized.Parameters
public static Collection<Object> data() {
return Arrays.asList(new Object[100][]);
}

public DnsAgingTest() throws Exception {
resolver = RobustDnsResolver.builder()
.failoverDns(
"168.126.63.1",
"8.8.8.8",
"8.8.4.4",
"1.1.1.1"
)
.cache(true)
.timeout(10L, TimeUnit.SECONDS)
.build();
}

@Test(timeout = 5000L)
public void test_lookup() throws Exception{
System.out.println(resolver.resolve("www.google.com").blockingGet());
}
}

0 comments on commit 1fc9ec1

Please sign in to comment.