commit e5ea15a06480d4905c5d0f580cb9573cff30267a Author: matrixy Date: Sat Apr 27 21:21:26 2019 +0800 初始提交 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..38a56dd --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +target +.idea +*.iml +*test* diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..90eeef8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ +Copyright [2019] [matrixy] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..b7fd26c --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +## DNS-Cheater +自建DNS服务器,用来做点什么好呢? \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..0ee22b4 --- /dev/null +++ b/pom.xml @@ -0,0 +1,121 @@ + + + 4.0.0 + + cn.org.hentai + dns-cheater + 1.0-SNAPSHOT + + + org.springframework.boot + spring-boot-starter-parent + 1.5.9.RELEASE + + + + + org.springframework.boot + spring-boot-starter-websocket + + + javax + javaee-api + 7.0 + provided + + + junit + junit + 3.8.1 + test + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-freemarker + 1.4.1.RELEASE + + + org.springframework.boot + spring-boot-starter-jdbc + + + org.xerial + sqlite-jdbc + 3.21.0.1 + + + com.google.code.gson + gson + 2.5 + + + org.apache.httpcomponents + httpcore + 4.4.8 + + + org.apache.httpcomponents + httpclient + 4.5.4 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + true + cn.org.hentai.dns.app.DNSCheaterApp + + + + + repackage + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.1.0 + + + + shade + + + + + .xxoo + + + META-INF/spring.handlers + + + META-INF/spring.schemas + + + + cn.org.hentai.server.app.ServerApp + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/cn/org/hentai/dns/app/DNSCheaterApp.java b/src/main/java/cn/org/hentai/dns/app/DNSCheaterApp.java new file mode 100644 index 0000000..85eed4e --- /dev/null +++ b/src/main/java/cn/org/hentai/dns/app/DNSCheaterApp.java @@ -0,0 +1,42 @@ +package cn.org.hentai.dns.app; + +import cn.org.hentai.dns.util.BeanUtils; +import cn.org.hentai.dns.util.Configs; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.core.env.Environment; +import org.sqlite.SQLiteDataSource; + +import javax.sql.DataSource; + +/** + * Created by matrixy on 2019/4/19. + */ +@ComponentScan(value = {"cn.org.hentai"}) +@EnableAutoConfiguration +@SpringBootApplication +public class DNSCheaterApp +{ + @Autowired + private Environment env; + + public static void main(String[] args) throws Exception + { + ApplicationContext context = SpringApplication.run(DNSCheaterApp.class, args); + BeanUtils.init(context); + Configs.init("/application.properties"); + } + + @Bean + public DataSource dataSource() + { + SQLiteDataSource dataSource = new SQLiteDataSource(); + dataSource.setUrl(env.getProperty("spring.datasource.url")); + return dataSource; + } +} diff --git a/src/main/java/cn/org/hentai/dns/cache/CacheManager.java b/src/main/java/cn/org/hentai/dns/cache/CacheManager.java new file mode 100644 index 0000000..1658d65 --- /dev/null +++ b/src/main/java/cn/org/hentai/dns/cache/CacheManager.java @@ -0,0 +1,43 @@ +package cn.org.hentai.dns.cache; + +import cn.org.hentai.dns.dns.entity.ResourceRecord; + +/** + * Created by matrixy on 2019/4/23. + * LRC+k缓存实现 + */ +public final class CacheManager +{ + LRU cachePool = null; + + public ResourceRecord[] get(String key) + { + return cachePool.get(key); + } + + public void put(String key, ResourceRecord[] records) + { + cachePool.put(key, records); + } + + static volatile CacheManager instance; + private CacheManager() + { + cachePool = new LRU(4096 * 100); + } + + public static CacheManager getInstance() + { + if (instance == null) + { + synchronized (CacheManager.class) + { + if (instance == null) + { + instance = new CacheManager(); + } + } + } + return instance; + } +} diff --git a/src/main/java/cn/org/hentai/dns/cache/LRU.java b/src/main/java/cn/org/hentai/dns/cache/LRU.java new file mode 100644 index 0000000..85e5bf7 --- /dev/null +++ b/src/main/java/cn/org/hentai/dns/cache/LRU.java @@ -0,0 +1,48 @@ +package cn.org.hentai.dns.cache; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Created by matrixy on 2019/4/23. + */ +public class LRU +{ + private static final float hashLoadFactory = 0.75f; + private LinkedHashMap map; + private int cacheSize; + + public LRU(int cacheSize) + { + this.cacheSize = cacheSize; + int capacity = (int) Math.ceil(cacheSize / hashLoadFactory) + 1; + map = new LinkedHashMap(capacity, hashLoadFactory, true) + { + @Override + protected boolean removeEldestEntry(Map.Entry eldest) + { + return size() > LRU.this.cacheSize; + } + }; + } + + public synchronized V get(K key) + { + return map.get(key); + } + + public synchronized void put(K key, V value) + { + map.put(key, value); + } + + public synchronized void clear() + { + map.clear(); + } + + public synchronized int usedSize() + { + return map.size(); + } +} \ No newline at end of file diff --git a/src/main/java/cn/org/hentai/dns/dns/NameResolveWorker.java b/src/main/java/cn/org/hentai/dns/dns/NameResolveWorker.java new file mode 100644 index 0000000..97fbac7 --- /dev/null +++ b/src/main/java/cn/org/hentai/dns/dns/NameResolveWorker.java @@ -0,0 +1,104 @@ +package cn.org.hentai.dns.dns; + +import cn.org.hentai.dns.cache.CacheManager; +import cn.org.hentai.dns.dns.coder.SimpleMessageDecoder; +import cn.org.hentai.dns.dns.coder.SimpleMessageEncoder; +import cn.org.hentai.dns.dns.entity.*; +import cn.org.hentai.dns.util.ByteUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; + +/** + * Created by matrixy on 2019/4/19. + */ +public class NameResolveWorker extends Thread +{ + static Logger logger = LoggerFactory.getLogger(NameResolveWorker.class); + + NameServer nameServer; + ArrayList questions = new ArrayList(100); + ArrayList answers = new ArrayList(); + + public NameResolveWorker(NameServer nameServer) + { + this.nameServer = nameServer; + } + + private void getAndResolve() throws Exception + { + Request request = this.nameServer.takeRequest(); + if (request == null) return; + + // 消息包解码 + Message msg = SimpleMessageDecoder.decode(request.packet); + logger.debug("decode: TransactionId: {}, Flags: {}, Questions: {}, AnswerRRs: {}, AuthorityRRs: {}, AdditionalRRs: {}", msg.transactionId, Integer.toBinaryString(msg.flags), msg.questions, msg.answerRRs, msg.authorityRRs, msg.additionalRRs); + logger.debug("decode flags: QR: {}, OP: {}, AA: {}, TC: {}, RD: {}, RA: {}, RCode: {}", msg.isQuestion(), msg.getOperateType(), msg.isAuthorityAnswer(), msg.isTruncateable(), msg.isRecursiveExpected(), msg.isRecursively(), msg.getReturnCode()); + + if (msg.isQuestion() == false) + { + logger.debug("skip current question: " + ByteUtils.toString(request.packet.nextBytes())); + logger.error("要不是我读错了,嗯,只有可能是读错了。。。"); + // return; + } + + // 遍历每一个要查询的域名 + request.packet.seek(12); + int len = 0; + questions.clear(); + for (int i = 0; i < msg.questions; i++) + { + StringBuilder name = new StringBuilder(64); + while ((len = request.packet.nextByte() & 0xff) > 0) + { + name.append(new String(request.packet.nextBytes(len))); + name.append('.'); + } + int queryType = request.packet.nextShort() & 0xffff; + int queryClass = request.packet.nextShort() & 0xffff; + questions.add(new Question(name.toString(), queryType)); + } + + // 依次处理,一般来说,都是单个查询的吧,只有自己写程序才有可能会有批量查询的情况 + if (questions.size() > 1) throw new RuntimeException("multiple name resolve unsupported"); + CacheManager cacheManager = CacheManager.getInstance(); + for (Question question : questions) + { + logger.debug("resolve: name = {}, type = {}", question.name, question.type); + if (question.type != Message.TYPE_A && question.type != Message.TYPE_AAAA) + { + logger.error("unsupported query type: {}", question.type); + continue; + } + ResourceRecord[] answers = cacheManager.get(question.name); + // 规则引擎决定了什么东西? + if (answers == null) + { + // 交给递归解析线程去上游服务器解析 + } + else + { + // 返回结果 + logger.debug("resolved: name = {}, answer = {}", question.name, answers[0].ipv4); + byte[] resp = SimpleMessageEncoder.encode(msg, question, answers); + this.nameServer.putResponse(new Response(request.remoteAddress, resp)); + } + } + } + + public void run() + { + while (!this.isInterrupted()) + { + try + { + getAndResolve(); + } + catch(Exception e) + { + logger.error("domain name resolve error", e); + } + } + } +} diff --git a/src/main/java/cn/org/hentai/dns/dns/NameServer.java b/src/main/java/cn/org/hentai/dns/dns/NameServer.java new file mode 100644 index 0000000..508f7ec --- /dev/null +++ b/src/main/java/cn/org/hentai/dns/dns/NameServer.java @@ -0,0 +1,146 @@ +package cn.org.hentai.dns.dns; + +import cn.org.hentai.dns.dns.entity.Request; +import cn.org.hentai.dns.dns.entity.Response; +import cn.org.hentai.dns.util.ByteUtils; +import cn.org.hentai.dns.util.Configs; +import cn.org.hentai.dns.util.Packet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.DatagramChannel; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.util.Iterator; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Created by matrixy on 2019/4/19. + */ +public class NameServer extends Thread +{ + static Logger logger = LoggerFactory.getLogger(NameServer.class); + + NameResolveWorker[] resolveWorkers = null; + ArrayBlockingQueue queries = null; + ArrayBlockingQueue responses = null; + + AtomicLong totalQueryCount = new AtomicLong(0); + + public NameServer() + { + this.setName("nameserver-thread"); + this.resolveWorkers = new NameResolveWorker[Runtime.getRuntime().availableProcessors() * 2]; + this.queries = new ArrayBlockingQueue(65535); + this.responses = new ArrayBlockingQueue(65535); + for (int i = 0; i < this.resolveWorkers.length; i++) + { + this.resolveWorkers[i] = new NameResolveWorker(this); + this.resolveWorkers[i].setName("name-resolve-worker-" + i); + this.resolveWorkers[i].start(); + } + } + + public void run() + { + DatagramChannel datagramChannel = null; + try + { + int port = Configs.getInt("dns.port", 53); + Selector selector = Selector.open(); + + datagramChannel = DatagramChannel.open(); + datagramChannel.socket().bind(new InetSocketAddress(port)); + datagramChannel.configureBlocking(false); + datagramChannel.register(selector, SelectionKey.OP_READ); + + logger.info("NameServer started at: {}", port); + + datagramChannel.configureBlocking(false); + ByteBuffer buffer = ByteBuffer.allocate(1024); + + datagramChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE); + while (!this.isInterrupted()) + { + selector.select(); + Iterator iterator = selector.selectedKeys().iterator(); + while (iterator.hasNext()) + { + SelectionKey selectionKey = (SelectionKey) iterator.next(); + if (selectionKey.isReadable()) + { + buffer.clear(); + SocketAddress addr = datagramChannel.receive(buffer); + buffer.flip(); + byte[] message = new byte[buffer.limit()]; + buffer.get(message, 0, message.length); + + logger.debug("received: from = {}, length = {}, ", addr.toString(), message.length); + queries.put(new Request(addr, Packet.create(message))); + totalQueryCount.addAndGet(1); + } + while (selectionKey.isWritable()) + { + if (responses.size() == 0) break; + Response response = responses.poll(); + if (response != null) + { + buffer.clear(); + buffer.put(response.packet); + buffer.flip(); + datagramChannel.send(buffer, response.remoteAddress); + logger.debug("send: to = {}, length = {}", response.remoteAddress, response.packet.length); + } + } + } + } + } + catch (Exception ex) + { + logger.error("nameserver receive error", ex); + } + finally + { + try { datagramChannel.close(); } catch(Exception e) { } + logger.info("NameServer app exited..."); + System.exit(1); + } + } + + public Request takeRequest() + { + try + { + return queries.take(); + } + catch(Exception ex) + { + return null; + } + } + + public boolean putResponse(Response response) + { + try + { + responses.put(response); + return true; + } + catch (InterruptedException e) + { + return false; + } + } + + public static void main(String[] args) throws Exception + { + new NameServer().start(); + } +} diff --git a/src/main/java/cn/org/hentai/dns/dns/coder/SimpleMessageDecoder.java b/src/main/java/cn/org/hentai/dns/dns/coder/SimpleMessageDecoder.java new file mode 100644 index 0000000..ee06d9e --- /dev/null +++ b/src/main/java/cn/org/hentai/dns/dns/coder/SimpleMessageDecoder.java @@ -0,0 +1,24 @@ +package cn.org.hentai.dns.dns.coder; + +import cn.org.hentai.dns.dns.entity.Message; +import cn.org.hentai.dns.util.ByteUtils; +import cn.org.hentai.dns.util.Packet; + +/** + * Created by matrixy on 2019/4/19. + * 查询消息包解码器 + */ +public final class SimpleMessageDecoder +{ + public static Message decode(Packet packet) + { + Message msg = new Message(); + msg.transactionId = packet.nextShort() & 0xffff; + msg.flags = packet.nextShort() & 0xffff; + msg.questions = packet.nextShort() & 0xffff; + msg.answerRRs = packet.nextShort() & 0xffff; + msg.authorityRRs = packet.nextShort() & 0xffff; + msg.additionalRRs = packet.nextShort() & 0xffff; + return msg; + } +} diff --git a/src/main/java/cn/org/hentai/dns/dns/coder/SimpleMessageEncoder.java b/src/main/java/cn/org/hentai/dns/dns/coder/SimpleMessageEncoder.java new file mode 100644 index 0000000..9766541 --- /dev/null +++ b/src/main/java/cn/org/hentai/dns/dns/coder/SimpleMessageEncoder.java @@ -0,0 +1,84 @@ +package cn.org.hentai.dns.dns.coder; + +import cn.org.hentai.dns.dns.entity.Message; +import cn.org.hentai.dns.dns.entity.Question; +import cn.org.hentai.dns.dns.entity.ResourceRecord; +import cn.org.hentai.dns.util.ByteUtils; +import cn.org.hentai.dns.util.Packet; + +import static cn.org.hentai.dns.dns.entity.Message.TYPE_A; +import static cn.org.hentai.dns.dns.entity.Message.TYPE_AAAA; + +/** + * Created by matrixy on 2019/4/23. + */ +public final class SimpleMessageEncoder +{ + // 创建回应消息包 + public static byte[] encode(Message query, Question question, ResourceRecord[] answers) + { + Packet packet = Packet.create(1024); + + // 会话标识 + packet.addShort((short)(query.transactionId & 0xffff)); + + // 标志,始终为成功应答 + packet.addShort((short)0x8000); + + // 数量 + packet.addShort((short)0x01).addShort((short)answers.length).addShort((short)0x00).addShort((short)0x00); + + // Queries区域,这里只处理单域名查询的情况 + packet.addBytes(encodeName(question.name)); + packet.addShort((short)question.type); // 查询类型 + packet.addShort((short)0x01); // 查询类,始终为01 + + // Resource Record列表 + for (int i = 0; i < answers.length; i++) + { + ResourceRecord answer = answers[i]; + + // 始终指向查询区的名称 + packet.addShort((short)0xc00c); + packet.addShort((short)answer.type); + packet.addShort((short)0x01); + packet.addInt(answer.ttl); + packet.addShort((short)answer.dlen); + if (answer.type == TYPE_A) + { + packet.addInt(answer.ipv4); + } + else if (answer.type == TYPE_AAAA) + { + packet.addBytes(answer.ipv6.getAddress()); + } + else + { + packet.addBytes(answer.data); + } + } + + return packet.getBytes(); + } + + private static byte[] encodeName(String queryName) + { + byte[] nameBytes = new byte[queryName.length() + 1]; + int lastHeadIndex = 0, s = 0; + for (int i = 0, k = 1; i < queryName.length(); i++, k++) + { + char chr = queryName.charAt(i); + if (chr == '.') + { + nameBytes[lastHeadIndex] = (byte)s; + s = 0; + lastHeadIndex = i + 1; + continue; + } + nameBytes[k] = (byte)chr; + s += 1; + } + nameBytes[lastHeadIndex] = (byte)s; + return nameBytes; + } +} diff --git a/src/main/java/cn/org/hentai/dns/dns/entity/Message.java b/src/main/java/cn/org/hentai/dns/dns/entity/Message.java new file mode 100644 index 0000000..88d2abc --- /dev/null +++ b/src/main/java/cn/org/hentai/dns/dns/entity/Message.java @@ -0,0 +1,79 @@ +package cn.org.hentai.dns.dns.entity; + +import java.net.InetSocketAddress; + +/** + * Created by matrixy on 2019/4/19. + */ +public class Message +{ + public static final int QR_QUESTION = 0x00; // QR类型:问询 + public static final int QR_RESPONSE = 0x01; // QR类型:响应 + + public static final int OP_STANDARD_QUERY = 0x00; // OP类型:标准查询 + public static final int OP_REVERSE_QUERY = 0x01; // OP类型:反向查询 + public static final int OP_STATUS_QUER = 0x02; // OP类型,服务器状态查询 + + public static final int RCODE_SUCCESS = 0x00; // 返回类型:成功 + public static final int RCODE_INVALID_NAME = 0x01; // 返回类型:错误的名字 + public static final int RCODE_SERVER_FAILURE = 0x02; // 返回类型:服务器错误 + + public static final int TYPE_A = 1; // 由域名获得IPv4地址 + public static final int TYPE_NS = 2; // 查询域名服务器 + public static final int TYPE_CNAME = 5; // 查询规范名称/别名 + public static final int TYPE_SOA = 6; // 开始授权 + public static final int TYPE_WKS = 11; // 熟知服务 + public static final int TYPE_PTR = 12; // 把IP地址转换成域名 + public static final int TYPE_HINFO = 13; // 主机信息 + public static final int TYPE_MX = 15; // 邮件交换 + public static final int TYPE_AAAA = 28; // 由域名获得IPv6地址 + public static final int TYPE_AXFR = 252; // 传送整个区的请求 + public static final int TYPE_ANY = 255; // 对所有记录的请求 + + public int transactionId; // 会话标识 + public int flags; // 标志 + public int questions; // 问题数 + public int answerRRs; // 回答资源记录数 + public int authorityRRs; // 授权资源记录数 + public int additionalRRs; // 附加资源记录数 + + public Message() + { + + } + + public int getReturnCode() + { + return flags & 0x0f; + } + + public boolean isRecursively() + { + return ((flags >> 7) & 0x01) == 1; + } + + public boolean isRecursiveExpected() + { + return ((flags >> 8) & 0x01) == 1; + } + + public boolean isTruncateable() + { + return ((flags >> 9) & 0x01) == 1; + } + + public boolean isAuthorityAnswer() + { + return ((flags >> 10) & 0x01) == 1; + } + + public int getOperateType() + { + return (flags >> 11) & 0x0f; + } + + public boolean isQuestion() + { + return (flags & 0x8000) == 0; + } +} diff --git a/src/main/java/cn/org/hentai/dns/dns/entity/Question.java b/src/main/java/cn/org/hentai/dns/dns/entity/Question.java new file mode 100644 index 0000000..967ee2f --- /dev/null +++ b/src/main/java/cn/org/hentai/dns/dns/entity/Question.java @@ -0,0 +1,16 @@ +package cn.org.hentai.dns.dns.entity; + +/** + * Created by matrixy on 2019/4/19. + */ +public class Question +{ + public String name; + public int type; + // public int class; // 因为关键字的原因,就不设置此字段了,反正也都是固定值 + public Question(String name, int type) + { + this.name = name; + this.type = type; + } +} diff --git a/src/main/java/cn/org/hentai/dns/dns/entity/Request.java b/src/main/java/cn/org/hentai/dns/dns/entity/Request.java new file mode 100644 index 0000000..5f6a46a --- /dev/null +++ b/src/main/java/cn/org/hentai/dns/dns/entity/Request.java @@ -0,0 +1,22 @@ +package cn.org.hentai.dns.dns.entity; + +import cn.org.hentai.dns.util.Packet; + +import java.net.DatagramPacket; +import java.net.InetAddress; +import java.net.SocketAddress; + +/** + * Created by matrixy on 2019/4/24. + */ +public class Request +{ + public Packet packet; + public SocketAddress remoteAddress; + + public Request(SocketAddress remoteAddress, Packet packet) + { + this.packet = packet; + this.remoteAddress = remoteAddress; + } +} diff --git a/src/main/java/cn/org/hentai/dns/dns/entity/ResourceRecord.java b/src/main/java/cn/org/hentai/dns/dns/entity/ResourceRecord.java new file mode 100644 index 0000000..d9312bb --- /dev/null +++ b/src/main/java/cn/org/hentai/dns/dns/entity/ResourceRecord.java @@ -0,0 +1,68 @@ +package cn.org.hentai.dns.dns.entity; + +import cn.org.hentai.dns.util.ByteUtils; + +import java.net.Inet6Address; + +/** + * Created by matrixy on 2019/4/23. + * 响应消息的资源记录,一般为域名所对应的IP或CNAME等 + */ +public class ResourceRecord +{ + public String name; + public int type; + // public int class; // 固定值就不声明了 + public int ttl; + public int dlen; + public int ipv4; // 如果type == TYPE_A,则使用IP作为data,省得转来转去 + public Inet6Address ipv6; // 同上 + public byte[] data; + + public ResourceRecord(String name, int type, int ttl, int ipv4) + { + this.name = name; + this.type = type; + this.ttl = ttl; + this.ipv4 = ipv4; + this.dlen = 4; + } + + public ResourceRecord(String name, int type, int ttl, Inet6Address ipv6) + { + this.name = name; + this.type = type; + this.ttl = ttl; + this.ipv6 = ipv6; + this.dlen = 16; + } + + public ResourceRecord(String name, int type, int ttl, byte[] data) + { + this.name = name; + this.type = type; + this.ttl = ttl; + this.data = data; + this.dlen = data.length; + } + + public String getAnswerString() + { + if (type == Message.TYPE_A) + { + int a = (ipv4 >> 24) & 0xff, + b = (ipv4 >> 16) & 0xff, + c = (ipv4 >> 8) & 0xff, + d = ipv4 & 0xff; + return a + "." + b + "." + c + "." + d; + } + else if (type == Message.TYPE_AAAA) + { + return ipv6.toString(); + } + else + { + return ByteUtils.toString(data); + } + } +} diff --git a/src/main/java/cn/org/hentai/dns/dns/entity/Response.java b/src/main/java/cn/org/hentai/dns/dns/entity/Response.java new file mode 100644 index 0000000..b8065e8 --- /dev/null +++ b/src/main/java/cn/org/hentai/dns/dns/entity/Response.java @@ -0,0 +1,20 @@ +package cn.org.hentai.dns.dns.entity; + +import cn.org.hentai.dns.util.Packet; + +import java.net.SocketAddress; + +/** + * Created by matrixy on 2019/4/25. + */ +public class Response +{ + public SocketAddress remoteAddress; + public byte[] packet; + + public Response(SocketAddress remoteAddress, byte[] packet) + { + this.packet = packet; + this.remoteAddress = remoteAddress; + } +} diff --git a/src/main/java/cn/org/hentai/dns/rex/Rule.java b/src/main/java/cn/org/hentai/dns/rex/Rule.java new file mode 100644 index 0000000..6f8528a --- /dev/null +++ b/src/main/java/cn/org/hentai/dns/rex/Rule.java @@ -0,0 +1,159 @@ +package cn.org.hentai.dns.rex; + +import cn.org.hentai.dns.util.ByteUtils; + +import java.io.Serializable; +import java.net.Inet4Address; + +/** + * Created by matrixy on 2019/4/25. + */ +public class Rule implements Serializable +{ + Long id; + Long ipFrom; + Long ipTo; + Integer timeFrom; + Integer timeTo; + Integer netmask; + String matchMode; + String name; + + public boolean matches(int now, byte[] addr, String domainName) + { + // 时间段,07:34:11 -> 08:01:01 + // Inet4Address.getLoopbackAddress(); + long ip = addr[0] << 24 | addr[1] << 16 | addr[2] << 8 | addr[3]; + if (ipFrom != null && ip < ipFrom) return false; + if (ipTo != null && ip > ipTo) return false; + + // 时间 + if (timeFrom != null && now < timeFrom) return false; + if (timeTo != null && now > timeTo) return false; + + // 域名匹配 + if ("prefix".equals(matchMode)) return domainName.startsWith(name); + else if ("suffix".equals(matchMode)) return domainName.endsWith(name); + else return domainName.indexOf(name) > -1; + } + + public String getMatchMode() { + return matchMode; + } + + public Rule setMatchMode(String matchMode) { + this.matchMode = matchMode; + return this; + } + + public Long getId() { + return id; + } + + public Integer getNetmask() { + return netmask; + } + + public Rule setNetmask(Integer netmask) { + this.netmask = netmask; + return this; + } + + public Rule setId(Long id) { + this.id = id; + return this; + } + + public Long getIpFrom() { + return ipFrom; + } + + public Rule setIpFrom(Long ipFrom) { + this.ipFrom = ipFrom; + return this; + } + + public Long getIpTo() { + return ipTo; + } + + public Rule setIpTo(Long ipTo) { + this.ipTo = ipTo; + return this; + } + + public Integer getTimeFrom() { + return timeFrom; + } + + public Rule setTimeFrom(Integer timeFrom) { + this.timeFrom = timeFrom; + return this; + } + + public Integer getTimeTo() { + return timeTo; + } + + public Rule setTimeTo(Integer timeTo) { + this.timeTo = timeTo; + return this; + } + + public String getName() { + return name; + } + + public Rule setName(String name) { + this.name = name; + return this; + } + + @Override + public String toString() { + return "Rule{" + + "id=" + id + + ", ipFrom=" + ipFrom + + ", ipTo=" + ipTo + + ", timeFrom=" + timeFrom + + ", timeTo=" + timeTo + + ", netmask=" + netmask + + ", matchMode='" + matchMode + '\'' + + ", name='" + name + '\'' + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Rule)) return false; + + Rule rule = (Rule) o; + + if (getId() != null ? !getId().equals(rule.getId()) : rule.getId() != null) return false; + if (getIpFrom() != null ? !getIpFrom().equals(rule.getIpFrom()) : rule.getIpFrom() != null) return false; + if (getIpTo() != null ? !getIpTo().equals(rule.getIpTo()) : rule.getIpTo() != null) return false; + if (getTimeFrom() != null ? !getTimeFrom().equals(rule.getTimeFrom()) : rule.getTimeFrom() != null) + return false; + if (getTimeTo() != null ? !getTimeTo().equals(rule.getTimeTo()) : rule.getTimeTo() != null) return false; + if (getNetmask() != null ? !getNetmask().equals(rule.getNetmask()) : rule.getNetmask() != null) return false; + if (getMatchMode() != null ? !getMatchMode().equals(rule.getMatchMode()) : rule.getMatchMode() != null) + return false; + if (getName() != null ? !getName().equals(rule.getName()) : rule.getName() != null) return false; + + return true; + } + + @Override + public int hashCode() { + int result = getId() != null ? getId().hashCode() : 0; + result = 31 * result + (getIpFrom() != null ? getIpFrom().hashCode() : 0); + result = 31 * result + (getIpTo() != null ? getIpTo().hashCode() : 0); + result = 31 * result + (getTimeFrom() != null ? getTimeFrom().hashCode() : 0); + result = 31 * result + (getTimeTo() != null ? getTimeTo().hashCode() : 0); + result = 31 * result + (getNetmask() != null ? getNetmask().hashCode() : 0); + result = 31 * result + (getMatchMode() != null ? getMatchMode().hashCode() : 0); + result = 31 * result + (getName() != null ? getName().hashCode() : 0); + return result; + } +} diff --git a/src/main/java/cn/org/hentai/dns/util/BeanUtils.java b/src/main/java/cn/org/hentai/dns/util/BeanUtils.java new file mode 100644 index 0000000..ff59932 --- /dev/null +++ b/src/main/java/cn/org/hentai/dns/util/BeanUtils.java @@ -0,0 +1,27 @@ +package cn.org.hentai.dns.util; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.context.ApplicationContext; + +/** + * Created by Expect on 2018/1/25. + */ +public final class BeanUtils +{ + private static BeanFactory beanFactory; + + public static void init(ApplicationContext context) + { + BeanUtils.beanFactory = context; + } + + public static T create(Class serviceClass) + { + return (T)beanFactory.getBean(serviceClass); + } + + public static void destroy(Object bean) + { + // ... + } +} diff --git a/src/main/java/cn/org/hentai/dns/util/ByteUtils.java b/src/main/java/cn/org/hentai/dns/util/ByteUtils.java new file mode 100644 index 0000000..c9c704c --- /dev/null +++ b/src/main/java/cn/org/hentai/dns/util/ByteUtils.java @@ -0,0 +1,149 @@ +package cn.org.hentai.dns.util; + +/** + * Created by matrixy on 2017/8/22. + */ +public final class ByteUtils +{ + public static byte[] parse(String hexString) + { + String[] hexes = hexString.split(" "); + byte[] data = new byte[hexes.length]; + for (int i = 0; i < hexes.length; i++) data[i] = (byte)(Integer.parseInt(hexes[i], 16) & 0xff); + return data; + } + + public static void dump(byte[] data) + { + for (int i = 0, l = data.length; i < l; ) + { + String ascii = ""; + int k = 0, f = 0; + for (; k < 16; k++) + { + if (k + i < l) + { + f++; + byte d = data[i + k]; + String hex = Integer.toHexString(d & 0xff).toUpperCase(); + if (hex.length() == 1) hex = "0" + hex; + if (d >= 0x20 && d < 127) ascii += (char)d; + else ascii += '.'; + System.out.print(hex); + } + else + { + System.out.print(' '); + System.out.print(' '); + } + + if (k % 4 == 3) System.out.print(" "); + else System.out.print(' '); + } + i += f; + System.out.println(ascii); + } + } + + public static String toString(byte[] data) + { + return toString(data, data.length); + } + + public static String toString(byte[] buff, int length) + { + StringBuffer sb = new StringBuffer(length * 2); + for (int i = 0; i < length; i++) + { + if ((buff[i] & 0xff) < 0x10) sb.append('0'); + sb.append(Integer.toHexString(buff[i] & 0xff).toUpperCase()); + sb.append(' '); + } + return sb.toString(); + } + + public static boolean getBit(int val, int pos) + { + return getBit(new byte[] { + (byte)((val >> 0) & 0xff), + (byte)((val >> 8) & 0xff), + (byte)((val >> 16) & 0xff), + (byte)((val >> 24) & 0xff) + }, pos); + } + + public static int reverse(int val) + { + byte[] bytes = toBytes(val); + byte[] ret = new byte[4]; + for (int i = 0; i < 4; i++) ret[i] = bytes[3 - i]; + return toInt(ret); + } + + public static int toInt(byte[] bytes) + { + int val = 0; + for (int i = 0; i < 4; i++) val |= (bytes[i] & 0xff) << ((3 - i) * 8); + return val; + } + + public static byte[] toBytes(int val) + { + byte[] bytes = new byte[4]; + for (int i = 0; i < 4; i++) + { + bytes[i] = (byte)(val >> ((3 - i) * 8) & 0xff); + } + return bytes; + } + + public static byte[] toBytes(long val) + { + byte[] bytes = new byte[8]; + for (int i = 0; i < 8; i++) + { + bytes[i] = (byte)(val >> ((7 - i) * 8) & 0xff); + } + return bytes; + } + + public static int getInt(byte[] data, int offset, int length) + { + int val = 0; + for (int i = 0; i < length; i++) val |= (data[offset + i] & 0xff) << ((length - i - 1) * 8); + return val; + } + + public static long getLong(byte[] data, int offset, int length) + { + long val = 0; + for (int i = 0; i < length; i++) val |= ((long)data[offset + i] & 0xff) << ((length - i - 1) * 8); + return val; + } + + public static boolean getBit(byte[] data, int pos) + { + return ((data[pos / 8] >> (pos % 8)) & 0x01) == 0x01; + } + + public static byte[] concat(byte[]...byteArrays) + { + int len = 0, index = 0; + for (int i = 0; i < byteArrays.length; i++) len += byteArrays[i].length; + byte[] buff = new byte[len]; + for (int i = 0; i < byteArrays.length; i++) + { + System.arraycopy(byteArrays[i], 0, buff, index, byteArrays[i].length); + index += byteArrays[i].length; + } + return buff; + } + + public static boolean compare(byte[] data1, byte[] data2) + { + if (data1.length != data2.length) return false; + for (int i = 0; i < data1.length; i++) + if ((data1[i] & 0xff) != (data2[i] & 0xff)) return false; + return true; + } +} diff --git a/src/main/java/cn/org/hentai/dns/util/Configs.java b/src/main/java/cn/org/hentai/dns/util/Configs.java new file mode 100644 index 0000000..30a0724 --- /dev/null +++ b/src/main/java/cn/org/hentai/dns/util/Configs.java @@ -0,0 +1,48 @@ +package cn.org.hentai.dns.util; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Properties; + +/** + * Created by matrixy on 2017/8/14. + */ +public final class Configs +{ + static Properties properties = new Properties(); + + public static void init(String configFilePath) + { + try + { + File file = new File((configFilePath.startsWith("/") ? "." : "") + configFilePath); + if (file.exists()) properties.load(new FileInputStream(file)); + else properties.load(Configs.class.getResourceAsStream(configFilePath)); + } + catch (IOException e) + { + e.printStackTrace(); + } + } + + public static String get(String key) + { + Object val = properties.get(key); + if (null == val) return null; + else return String.valueOf(val).trim(); + } + + public static String get(String key, String defaultVal) + { + Object val = properties.get(key); + if (null == val) return defaultVal; + else return String.valueOf(val).trim(); + } + + public static int getInt(String key, int defaultVal) + { + String val = get(key, String.valueOf(defaultVal)); + return Integer.parseInt(val); + } +} diff --git a/src/main/java/cn/org/hentai/dns/util/Packet.java b/src/main/java/cn/org/hentai/dns/util/Packet.java new file mode 100644 index 0000000..1fd37a0 --- /dev/null +++ b/src/main/java/cn/org/hentai/dns/util/Packet.java @@ -0,0 +1,202 @@ +package cn.org.hentai.dns.util; + +/** + * Created by matrixy on 2018/4/14. + */ +public class Packet +{ + int size = 0; + int offset = 0; + int maxSize = 0; + public byte[] data; + + private Packet() + { + // do nothing here.. + } + + public static Packet create(int length) + { + Packet p = new Packet(); + p.data = new byte[length]; + p.size = 0; + p.maxSize = length; + return p; + } + + public static Packet create(byte[] data) + { + Packet p = new Packet(); + p.data = data; + p.size = data.length; + p.maxSize = data.length; + return p; + } + + public int size() + { + return this.size; + } + + public Packet addByte(byte b) + { + this.data[size++] = b; + return this; + } + + public Packet addShort(short s) + { + this.data[size++] = (byte)((s >> 8) & 0xff); + this.data[size++] = (byte)(s & 0xff); + return this; + } + + public Packet addInt(int i) + { + this.data[size++] = (byte)((i >> 24) & 0xff); + this.data[size++] = (byte)((i >> 16) & 0xff); + this.data[size++] = (byte)((i >> 8) & 0xff); + this.data[size++] = (byte)(i & 0xff); + return this; + } + + public Packet addLong(long l) + { + this.data[size++] = (byte)((l >> 56) & 0xff); + this.data[size++] = (byte)((l >> 48) & 0xff); + this.data[size++] = (byte)((l >> 40) & 0xff); + this.data[size++] = (byte)((l >> 32) & 0xff); + this.data[size++] = (byte)((l >> 24) & 0xff); + this.data[size++] = (byte)((l >> 16) & 0xff); + this.data[size++] = (byte)((l >> 8) & 0xff); + this.data[size++] = (byte)(l & 0xff); + return this; + } + + public Packet addBytes(byte[] b) + { + System.arraycopy(b, 0, this.data, size, b.length); + size += b.length; + return this; + } + + public Packet addBytes(byte[] b, int offset, int length) + { + System.arraycopy(b, offset, this.data, size, length); + size += length; + return this; + } + + public Packet reset() + { + this.offset = 0; + this.size = 0; + return this; + } + + public Packet rewind() + { + this.offset = 0; + return this; + } + + public int offset() + { + return this.offset; + } + + public byte nextByte() + { + return this.data[offset++]; + } + + public short nextShort() + { + return (short)(((this.data[offset++] & 0xff) << 8) | (this.data[offset++] & 0xff)); + } + + public int nextInt() + { + return (this.data[offset++] & 0xff) << 24 | (this.data[offset++] & 0xff) << 16 | (this.data[offset++] & 0xff) << 8 | (this.data[offset++] & 0xff); + } + + public long nextLong() + { + return ((long)this.data[offset++] & 0xff) << 56 + | ((long)this.data[offset++] & 0xff) << 48 + | ((long)this.data[offset++] & 0xff) << 40 + | ((long)this.data[offset++] & 0xff) << 32 + | ((long)this.data[offset++] & 0xff) << 24 + | ((long)this.data[offset++] & 0xff) << 16 + | ((long)this.data[offset++] & 0xff) << 8 + | ((long)this.data[offset++] & 0xff); + } + + public byte[] nextBytes(int length) + { + byte[] buf = new byte[length]; + System.arraycopy(this.data, offset, buf, 0, length); + offset += length; + return buf; + } + + public byte[] nextBytes() + { + byte[] buf = new byte[this.size - this.offset]; + System.arraycopy(this.data, offset, buf, 0, buf.length); + offset += buf.length; + return buf; + } + + public byte get(int position) + { + return this.data[position]; + } + + public byte[] get(int offset, int length) + { + byte[] buf = new byte[length]; + System.arraycopy(this.data, offset, buf, 0, length); + return buf; + } + + public Packet skip(int offset) + { + this.offset += offset; + return this; + } + + public Packet seek(int index) + { + this.offset = index; + return this; + } + + public boolean hasMoreBytes() + { + return this.offset < this.size; + } + + public byte[] getBytes() + { + if (size == maxSize) return this.data; + else + { + byte[] buff = new byte[size]; + System.arraycopy(this.data, 0, buff, 0, size); + return buff; + } + } + + /** + * 复制len个字节,到dest的offset位置处 + * @param dest + * @param offset + * @param len + */ + public void copyBytes(byte[] dest, int offset, int len) + { + System.arraycopy(this.data, this.offset, dest, offset, len); + this.offset += len; + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..dca35ad --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,32 @@ + +server.remotePort=80 + +dns.remotePort = 53 +dns.upstream.server = 180.76.76.76 + +# 趨ftlļ· +spring.freemarker.template-loader-path=classpath:/templates + +# 趨̬ļ·js,css +spring.mvc.static-path-pattern=/static/** + +# ļϴС +spring.http.multipart.maxFileSize=1024MB +spring.http.multipart.maxRequestSize=1024MB + +# sqlite url +spring.datasource.url=jdbc:sqlite:nameserver-cheater.sqlite + +log4j.rootLogger = debug,ServerDailyRollingFile,stdout + +log4j.appender.ServerDailyRollingFile = org.apache.log4j.DailyRollingFileAppender +log4j.appender.ServerDailyRollingFile.DatePattern = '.'yyyy-MM-dd +log4j.appender.ServerDailyRollingFile.File = cheater.log +log4j.appender.ServerDailyRollingFile.layout = org.apache.log4j.PatternLayout +log4j.appender.ServerDailyRollingFile.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} [%t] %-5p [%c] - %m%n +log4j.appender.ServerDailyRollingFile.Append = true + +log4j.appender.stdout = org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout = org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern = %d yyyy-MM-dd HH:mm:ss %p [%c] %m%n +log4j.cn.org.hentai = debug \ No newline at end of file