Commit 2bf954f1 authored by Daniel Wolf's avatar Daniel Wolf
Browse files

Current state (no proper commit messages for now)

parent 4cb0fda7
[submodule "AndroidUtils"]
path = AndroidUtils
url = https://git.frostnerd.com/AndroidApps/AndroidUtils.git
AndroidUtils @ b266d866
Subproject commit b266d86639fa9c93b02f88281f2289e880e2d646
apply plugin: 'com.android.application'
android {
compileSdkVersion 26
buildToolsVersion "26.0.2"
compileSdkVersion 27
buildToolsVersion "27.0.0"
defaultConfig {
applicationId "com.frostnerd.dnsserver"
minSdkVersion 14
targetSdkVersion 26
targetSdkVersion 27
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
......@@ -31,6 +31,12 @@ dependencies {
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:26.0.2'
compile 'com.android.support:appcompat-v7:27.0.0'
compile 'org.pcap4j:pcap4j-core:1.7.1'
compile 'org.pcap4j:pcap4j-packetfactory-static:1.7.1'
compile 'de.measite.minidns:minidns-core:0.2.2'
compile project(path: ':AndroidUtils:library')
testCompile 'junit:junit:4.12'
compile 'com.android.support:cardview-v7:27.0.0'
compile 'com.android.support:recyclerview-v7:27.0.0'
}
......@@ -23,3 +23,13 @@
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
# This dnsjava class uses old Sun API
-dontnote org.xbill.DNS.spi.DNSJavaNameServiceDescriptor
-dontwarn org.xbill.DNS.spi.DNSJavaNameServiceDescriptor
-dontwarn java.awt.*
-dontwarn org.slf4j.*
# See http://stackoverflow.com/questions/5701126, happens in dnsjava
-optimizations !code/allocation/variable
-keep class android.support.v7.widget.SearchView { *; }
-keepattributes SourceFile,LineNumberTable
\ No newline at end of file
......@@ -2,6 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.frostnerd.dnsserver">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
......@@ -9,7 +11,7 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<activity android:name=".activities.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
......
package com.frostnerd.dnsserver;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
package com.frostnerd.dnsserver.activities;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import com.frostnerd.dnsserver.R;
import com.frostnerd.dnsserver.server.DNSServer;
import com.frostnerd.dnsserver.server.UDPServer;
import com.frostnerd.dnsserver.util.IPPortPair;
import java.util.ArrayList;
import java.util.List;
import de.measite.minidns.Record;
/**
* Copyright Daniel Wolf 2017
* All rights reserved.
* Code may NOT be used without proper permission, neither in binary nor in source form.
* All redistributions of this software in source code must retain this copyright header
* All redistributions of this software in binary form must visibly inform users about usage of this software
* <p>
* development@frostnerd.com
*/
public class MainActivity extends AppCompatActivity {
private ListView list;
private ArrayAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
list = findViewById(R.id.list);
final List<String> obj = new ArrayList<>();
list.setAdapter(adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, obj));
DNSServer.Configuration configuration = new DNSServer.Configuration.Builder().setLogUpstreamQueryResults(false)
.setQueryListener(new DNSServer.QueryListener() {
@Override
public void queryReceived(final String host, final String query, final Record.TYPE type, boolean forwardedToUpstream) {
runOnUiThread(new Runnable() {
@Override
public void run() {
adapter.add(host + " ---> " + query + "(" + type + ")");
System.out.println("Received Query: " + query + " from " + host + " (" + type + ")");
}
});
}
}).build();
new Thread(new UDPServer(this, 5301, new IPPortPair("8.8.8.8", 53, false), configuration)).start();
}
}
package com.frostnerd.dnsserver.server;
import android.content.Context;
import com.frostnerd.dnsserver.util.DNSResolver;
import com.frostnerd.dnsserver.util.IPPortPair;
import com.frostnerd.dnsserver.util.QueryLogger;
import com.frostnerd.dnsserver.util.Util;
import java.net.InetAddress;
import java.net.UnknownHostException;
import de.measite.minidns.Record;
/**
* Copyright Daniel Wolf 2017
* All rights reserved.
* Code may NOT be used without proper permission, neither in binary nor in source form.
* All redistributions of this software in source code must retain this copyright header
* All redistributions of this software in binary form must visibly inform users about usage of this software
* <p>
* development@frostnerd.com
*/
public abstract class DNSServer implements Runnable {
protected DNSResolver resolver;
protected int port;
protected Configuration configuration;
protected IPPortPair upstreamServer;
protected InetAddress upstreamServerAddress;
protected QueryLogger queryLogger;
protected QueryListener queryListener;
public DNSServer(Context context, int port, IPPortPair upstreamServer, final Configuration configuration) {
this.resolver = Util.getDnsResolver(context);
this.port = port;
this.configuration = configuration;
this.upstreamServer = upstreamServer;
try {
this.upstreamServerAddress = InetAddress.getByName(upstreamServer.getAddress());
} catch (UnknownHostException ignored) {}
if(configuration.shouldLogQueries() || configuration.shouldLogForwardedQuery() ||
configuration.shouldLogUpstreamQueryResults())
queryLogger = new QueryLogger(context);
if(configuration.shouldLogQueries() || configuration.shouldLogForwardedQuery() ||
configuration.shouldLogUpstreamQueryResults() || configuration.getQueryListener() != null){
if((configuration.shouldLogQueries() || configuration.shouldLogForwardedQuery()) && configuration.getQueryListener() != null){
queryListener = new QueryListener() {
@Override
public void queryReceived(String host, String query, Record.TYPE type, boolean forwardedToUpstream) {
if(configuration.shouldLogQueries() || configuration.shouldLogForwardedQuery())
queryLogger.logQuery(host,query, type, forwardedToUpstream);
configuration.getQueryListener().queryReceived(host, query, type, forwardedToUpstream);
}
};
}else if(configuration.getQueryListener() != null){
queryListener = new QueryListener() {
@Override
public void queryReceived(String host, String query, Record.TYPE type, boolean forwardedToUpstream) {
configuration.getQueryListener().queryReceived(host, query, type, forwardedToUpstream);
}
};
}else{
queryListener = new QueryListener() {
@Override
public void queryReceived(String host, String query, Record.TYPE type, boolean forwardedToUpstream) {
if(configuration.shouldLogQueries() || configuration.shouldLogForwardedQuery())
queryLogger.logQuery(host,query, type, forwardedToUpstream);
}
};
}
}else{
queryListener = new QueryListener() {
@Override
public void queryReceived(String host, String query, Record.TYPE type, boolean forwardedToUpstream) {
// - Dust -
}
};
}
System.out.println("Listing on port " + port);
}
public abstract void stopServer();
public void stop() {
stopServer();
resolver.cleanup();
if(queryLogger != null)queryLogger.cleanup();
resolver = null;
queryLogger = null;
}
public static class Configuration{
private final boolean logQueries, upstreamUDP, logForwardedQuery, logUpstreamQueryResults, resolveLocal;
private final ErrorListener errorListener;
private final QueryListener queryListener;
private Configuration(boolean logQueries, ErrorListener errorListener, boolean upstreamUDP,
boolean logForwardedQuery, boolean logUpstreamQueryResults, QueryListener listener,
boolean resolveLocal){
this.logQueries = logQueries;
this.errorListener = errorListener;
this.upstreamUDP = upstreamUDP;
this.logForwardedQuery = logForwardedQuery;
this.logUpstreamQueryResults = logUpstreamQueryResults;
this.queryListener = listener;
this.resolveLocal = resolveLocal;
}
public boolean shouldLogQueries() {
return logQueries;
}
public boolean shouldLogForwardedQuery() {
return logForwardedQuery;
}
public boolean shouldLogUpstreamQueryResults() {
return logUpstreamQueryResults;
}
public boolean isUpstreamUDP() {
return upstreamUDP;
}
public ErrorListener getErrorListener() {
return errorListener;
}
public QueryListener getQueryListener() {
return queryListener;
}
public boolean shouldResolveLocal() {
return resolveLocal;
}
public static class Builder {
private boolean logQueries;
private DNSServer.ErrorListener errorListener;
private boolean upstreamUDP, logForwardedQuery, logUpstreamQueryResults, resolveLocal;
private QueryListener queryListener;
public Builder setLogQueries(boolean logQueries) {
this.logQueries = logQueries;
return this;
}
public Builder setErrorListener(DNSServer.ErrorListener errorListener) {
this.errorListener = errorListener;
return this;
}
public Builder setUpstreamUDP(boolean upstreamUDP) {
this.upstreamUDP = upstreamUDP;
return this;
}
public Builder setLogForwardedQuery(boolean logForwardedQuery){
this.logForwardedQuery = logForwardedQuery;
return this;
}
public Builder setResolveLocal(boolean resolveLocal) {
this.resolveLocal = resolveLocal;
return this;
}
public Builder setLogUpstreamQueryResults(boolean logUpstreamQueryResults) {
this.logUpstreamQueryResults = logUpstreamQueryResults;
return this;
}
public Builder setQueryListener(QueryListener queryListener) {
this.queryListener = queryListener;
return this;
}
public DNSServer.Configuration build() {
return new DNSServer.Configuration(logQueries, errorListener, upstreamUDP,
logForwardedQuery, logUpstreamQueryResults, queryListener, resolveLocal);
}
}
}
public interface ErrorListener{
public void onError(Exception e);
}
public interface QueryListener{
public void queryReceived(String host, String query, Record.TYPE type, boolean forwardedToUpstream);
}
}
package com.frostnerd.dnsserver.server;
/**
* Created by Daniel on 25.10.2017.
*/
public class TCPServer {
}
package com.frostnerd.dnsserver.server;
import android.content.Context;
import com.frostnerd.dnsserver.util.DNSResolver;
import com.frostnerd.dnsserver.util.IPPortPair;
import com.frostnerd.utils.general.SortUtil;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import de.measite.minidns.Record;
import de.measite.minidns.record.Data;
/**
* Copyright Daniel Wolf 2017
* All rights reserved.
* Code may NOT be used without proper permission, neither in binary nor in source form.
* All redistributions of this software in source code must retain this copyright header
* All redistributions of this software in binary form must visibly inform users about usage of this software
* <p>
* development@frostnerd.com
*/
public class UDPServer extends DNSServer{
private boolean stop = false;
private DatagramSocket serverSocket;
private HashMap<ExpiredPacket, Query> futureAnswers = new HashMap<>();
public UDPServer(Context context, int port, IPPortPair upstreamServer, Configuration configuration) {
super(context, port, upstreamServer, configuration);
}
@Override
public void run() {
try {
serverSocket = new DatagramSocket(port);
//System.out.println("Listening on: " + serverSocket.getLocalSocketAddress());
byte[] data = new byte[32767];
DatagramPacket packet;
DNSResolver.DNSResolveResult result;
while(!stop){
packet = new DatagramPacket(data, data.length);
//System.out.println("Waiting for packet.");
serverSocket.receive(packet);
//System.out.println("Received a packet from " + packet.getAddress().getHostAddress());
result = resolver.handlePossiblePacket(data, configuration.shouldResolveLocal());
if(result != null){
if(result.isUpstreamAnswer()){
//System.out.println("Received upstream answer");
if(configuration.shouldLogUpstreamQueryResults()){
for(Record<? extends Data> record: result.getMessage().answerSection){
queryLogger.logUpstreamQueryResult(result.getQuery(),
record.getPayload().toString(),record.type);
}
}
byte[] dnsData = result.getMessage().toArray();
Map.Entry<ExpiredPacket, Query> entry;
for(Iterator<Map.Entry<ExpiredPacket, Query>> iterator = futureAnswers.entrySet().iterator(); iterator.hasNext();){
entry = iterator.next();
//System.out.println("Someone waits for " + entry.getValue().getQuery() +
// ", we have " + result.getQuery() + " equals: " + entry.getValue().getQuery().equalsIgnoreCase(result.getQuery()));
if(entry.getValue().getQuery().equalsIgnoreCase(result.getQuery()) &&
containsType(result.getMessage().answerSection, entry.getValue().getType())){
//System.out.println("Answering to " + entry.getKey().getAddress());
serverSocket.send(new DatagramPacket(dnsData, dnsData.length,
entry.getKey().getAddress(), entry.getKey().getPort()));
iterator.remove();
}
}
}else if(result.shouldForwardQuery()){
queryListener.queryReceived(packet.getAddress().getHostAddress(), result.getQuery(), result.getType(), true);
//System.out.println("Forwarding question for " + result.getQuery() + " of type: " + result.getType());
futureAnswers.put(new ExpiredPacket(packet), new Query(result));
serverSocket.send(new DatagramPacket(packet.getData(), packet.getLength(), upstreamServerAddress, upstreamServer.getPort()));
}else{
queryListener.queryReceived(packet.getAddress().getHostAddress(), result.getQuery(), result.getType(), false);
byte[] dnsData = result.getMessage().toArray();
packet = new DatagramPacket(dnsData, dnsData.length, packet.getAddress(), packet.getPort());
serverSocket.send(packet);
}
}else System.out.println("RESULT NULL");
}
} catch (Exception e) {
if(configuration.getErrorListener() != null)configuration.getErrorListener().onError(e);
e.printStackTrace();
}
}
private boolean containsType(List<Record<? extends Data>> records, Record.TYPE type){
for(Record<? extends Data> record: records){
//System.out.println("Waiting for type: " + record.getPayload().getType() + ", given: " + type);
if(record.getPayload().getType() == type)return true;
}
return false;
}
@Override
public void stopServer() {
stop = true;
serverSocket.close();
futureAnswers.clear();
serverSocket = null;
upstreamServerAddress = null;
futureAnswers = null;
}
private class ExpiredPacket{
private InetAddress address;
private int port;
public ExpiredPacket(DatagramPacket packet){
this.address = packet.getAddress();
this.port = packet.getPort();
}
public InetAddress getAddress() {
return address;
}
public int getPort() {
return port;
}
@Override
public String toString() {
return address + ":" + port;
}
}
private class Query{
private Record.TYPE type;
private String query;
public Query(DNSResolver.DNSResolveResult result){
this(result.getType(), result.getQuery());
}
public Query(Record.TYPE type, String query) {
this.type = type;
this.query = query;
}
public Record.TYPE getType() {
return type;
}
public String getQuery() {
return query;
}
}
}
package com.frostnerd.dnsserver.util;
import android.content.Context;
import android.database.Cursor;
import android.text.TextUtils;
import java.io.IOException;
import java.io.InputStream;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.util.Arrays;
import de.measite.minidns.DNSMessage;
import de.measite.minidns.Record;
import de.measite.minidns.record.A;
import de.measite.minidns.record.AAAA;
import de.measite.minidns.record.Data;
/**
* Copyright Daniel Wolf 2017
* All rights reserved.
* Code may NOT be used without proper permission, neither in binary nor in source form.
* All redistributions of this software in source code must retain this copyright header
* All redistributions of this software in binary form must visibly inform users about usage of this software
* <p>
* development@frostnerd.com
*/
public class DNSResolver {
private static final String WILDCARD_QUERY_RANDOM =
"SELECT Target FROM DNSRules WHERE IPv6=? AND Wildcard=1 AND ? REGEXP Domain ORDER BY RANDOM() LIMIT 1";
private static final String WILDCARD_QUERY_FIRST =
"SELECT Target FROM DNSRules WHERE IPv6=? AND Wildcard=1 AND ? REGEXP Domain LIMIT 1";
private static final String NON_WILDCARD_QUERY = "SELECT Target FROM DNSRules WHERE Domain=? AND IPv6=? AND Wildcard=0";
private static final String SUM_WILDCARD_QUERY = "SELECT SUM(Wildcard) FROM DNSRules";
private DatabaseHelper db;
private int wildcardCount;
public DNSResolver(Context context) {
db = Util.getDatabaseHelper(context);
Cursor cursor = db.getReadableDatabase().rawQuery(SUM_WILDCARD_QUERY, null);
if (cursor.moveToFirst()) {
wildcardCount = cursor.getInt(0);
}
cursor.close();
}
public void cleanup(){
db.close();
db = null;
}
public String resolve(String host, boolean ipv6) {
return resolve(host, ipv6, true);
}
public String resolve(String host, boolean ipv6, boolean allowWildcard) {
String res;
res = resolveNonWildcard(host, ipv6);
if (res == null && allowWildcard && wildcardCount != 0) {
res = resolveWildcard(host, ipv6, false);
}
return res;
}
public String resolveNonWildcard(String host, boolean ipv6) {
String result = null;
Cursor cursor = db.getReadableDatabase().rawQuery(NON_WILDCARD_QUERY,
new String[]{host, ipv6 ? "1" : "0"});
if (cursor.moveToFirst()) {
result = cursor.getString(0);
}
cursor.close();
return result;
}
public String resolveWildcard(String host, boolean ipv6, boolean matchFirst) {
String result = null;
Cursor cursor = db.getReadableDatabase().rawQuery(matchFirst ? WILDCARD_QUERY_FIRST : WILDCARD_QUERY_RANDOM,
new String[]{ipv6 ? "1" : "0", host});
if (cursor.moveToFirst()) {
result =