diff --git a/LazyDNS.puml b/LazyDNS.puml
new file mode 100644
index 0000000..8c3708d
--- /dev/null
+++ b/LazyDNS.puml
@@ -0,0 +1,61 @@
+
+
+@startuml
+
+title 动态DNS
+
+actor 访客 as User
+
+package "服务端(公网)" as dns_server_{
+ component [服务端私有网络A]{
+ [mysql:(172.1.1.4:3306)] as dns_server_remote_local_
+ [clickhouse:(172.1.1.5:3306)]
+ }
+ component [服务端私有网络B]{
+ [mysql:(172.1.2.4:3306)]
+ [clickhouse:(172.1.2.5:3306)]
+ }
+
+}
+
+package "客户端(私有网络)" as dns_remote_local_{
+
+ component [客户端私有网络A]{
+ [mysql:(162.1.1.4:3306)] as dns_client_remote_local_
+ [clickhouse:(162.1.1.5:3306)]
+ }
+ component [客户端私有网络B]{
+ [mysql:(162.1.2.4:3306)]
+ [clickhouse:(162.1.2.5:3306)]
+ }
+}
+
+
+package "客户端(用户本地)" as dns_local_ {
+
+}
+
+
+
+note "用户本地网络" as local_net_
+note "服务端网络" as server_net_
+note "客户端私有网络" as remote_net_
+
+'(User) .... local_condition_
+'local_condition_ ... (target)
+
+[User] ...right...> dns_local_: DNS连接到本地
+dns_local_ ...right...> local_net_: 访问本地网络(本地DNS)
+dns_local_ ...up...> dns_server_remote_local_: 远程DNS解析
+
+dns_server_ ...up...> server_net_: server本地的网络
+dns_server_ ...down...> dns_remote_local_: 访问的地址在远程的客户端中
+dns_remote_local_ ...down...> dns_client_remote_local_: 远程客户端所在的私有网络
+dns_remote_local_ ...down...> remote_net_: 远程客户端中的其他网络
+
+
+
+
+
+@enduml
+
diff --git a/pom.xml b/pom.xml
index 548f817..322864c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -23,6 +23,7 @@
wu-lazy-cloud-heartbeat-server-cluster
wu-lazy-cloud-heartbeat-client
wu-lazy-cloud-heartbeat-common
+ wu-lazy-cloud-heartbeat-dns
wu-lazy-cloud-heartbeat-start
diff --git a/wu-lazy-cloud-heartbeat-dns/pom.xml b/wu-lazy-cloud-heartbeat-dns/pom.xml
new file mode 100644
index 0000000..23892b8
--- /dev/null
+++ b/wu-lazy-cloud-heartbeat-dns/pom.xml
@@ -0,0 +1,59 @@
+
+
+
+ top.wu2020
+ wu-lazy-cloud-network
+ 1.3.0-JDK17-SNAPSHOT
+
+
+ 4.0.0
+
+ wu-lazy-cloud-heartbeat-dns
+ 云上心跳服务dns
+
+
+ 17
+ 17
+
+
+
+
+ top.wu2020
+ wu-framework-web
+
+
+
+ top.wu2020
+ wu-lazy-cloud-heartbeat-common
+
+
+
+ mysql
+ mysql-connector-java
+ 8.0.33
+
+
+
+ top.wu2020
+ wu-database-lazy-plus-starter
+
+
+ com.alibaba
+ fastjson
+ 2.0.50
+
+
+ io.swagger.core.v3
+ swagger-annotations-jakarta
+ 2.2.21
+
+
+ top.wu2020
+ wu-framework-lazy-orm-spring-starter
+
+
+
+
+
\ No newline at end of file
diff --git a/wu-lazy-cloud-heartbeat-dns/src/main/java/org/framework/lazy/cloud/network/heartbeat/dns/EnableDnsAutoConfiguration.java b/wu-lazy-cloud-heartbeat-dns/src/main/java/org/framework/lazy/cloud/network/heartbeat/dns/EnableDnsAutoConfiguration.java
new file mode 100644
index 0000000..5a6f52a
--- /dev/null
+++ b/wu-lazy-cloud-heartbeat-dns/src/main/java/org/framework/lazy/cloud/network/heartbeat/dns/EnableDnsAutoConfiguration.java
@@ -0,0 +1,12 @@
+package org.framework.lazy.cloud.network.heartbeat.dns;
+
+import org.springframework.context.annotation.ComponentScan;
+import org.wu.framework.lazy.orm.core.stereotype.LazyScan;
+
+@LazyScan(scanBasePackages = {
+ "org.framework.lazy.cloud.network.heartbeat.dns.standalone.infrastructure.entity",
+ "org.framework.lazy.cloud.network.heartbeat.dns.cluster.infrastructure.entity"
+})
+@ComponentScan(basePackages = "org.framework.lazy.cloud.network.heartbeat.dns")
+public class EnableDnsAutoConfiguration {
+}
diff --git a/wu-lazy-cloud-heartbeat-dns/src/main/java/org/framework/lazy/cloud/network/heartbeat/dns/config/DnsAutoConfiguration.java b/wu-lazy-cloud-heartbeat-dns/src/main/java/org/framework/lazy/cloud/network/heartbeat/dns/config/DnsAutoConfiguration.java
new file mode 100644
index 0000000..dc850f3
--- /dev/null
+++ b/wu-lazy-cloud-heartbeat-dns/src/main/java/org/framework/lazy/cloud/network/heartbeat/dns/config/DnsAutoConfiguration.java
@@ -0,0 +1,11 @@
+package org.framework.lazy.cloud.network.heartbeat.dns.config;
+
+
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.context.annotation.Role;
+
+
+@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+public class DnsAutoConfiguration {
+
+}
diff --git a/wu-lazy-cloud-heartbeat-dns/src/main/java/org/framework/lazy/cloud/network/heartbeat/dns/config/DnsFlowConfiguration.java b/wu-lazy-cloud-heartbeat-dns/src/main/java/org/framework/lazy/cloud/network/heartbeat/dns/config/DnsFlowConfiguration.java
new file mode 100644
index 0000000..be33aa7
--- /dev/null
+++ b/wu-lazy-cloud-heartbeat-dns/src/main/java/org/framework/lazy/cloud/network/heartbeat/dns/config/DnsFlowConfiguration.java
@@ -0,0 +1,15 @@
+package org.framework.lazy.cloud.network.heartbeat.dns.config;
+
+import org.framework.lazy.cloud.network.heartbeat.common.adapter.ChannelFlowAdapter;
+import org.framework.lazy.cloud.network.heartbeat.common.advanced.flow.HandleChannelFlowAdvanced;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.context.annotation.Role;
+
+/**
+ * @see ChannelFlowAdapter
+ * @see HandleChannelFlowAdvanced
+ */
+@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+public class DnsFlowConfiguration {
+
+}
diff --git a/wu-lazy-cloud-heartbeat-dns/src/main/java/org/framework/lazy/cloud/network/heartbeat/dns/context/NettyDnsSocketApplicationListener.java b/wu-lazy-cloud-heartbeat-dns/src/main/java/org/framework/lazy/cloud/network/heartbeat/dns/context/NettyDnsSocketApplicationListener.java
new file mode 100644
index 0000000..8fcaa55
--- /dev/null
+++ b/wu-lazy-cloud-heartbeat-dns/src/main/java/org/framework/lazy/cloud/network/heartbeat/dns/context/NettyDnsSocketApplicationListener.java
@@ -0,0 +1,25 @@
+package org.framework.lazy.cloud.network.heartbeat.dns.context;
+
+import lombok.extern.slf4j.Slf4j;
+import org.framework.lazy.cloud.network.heartbeat.common.context.SocketApplicationListener;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+public class NettyDnsSocketApplicationListener implements SocketApplicationListener {
+
+ /**
+ * 运行
+ *
+ * @throws InterruptedException
+ */
+ @Override
+ public void doRunning() throws Exception {
+
+ }
+
+ @Override
+ public void destroy() throws Exception {
+
+ }
+}
\ No newline at end of file
diff --git a/wu-lazy-cloud-heartbeat-dns/src/main/java/org/framework/lazy/cloud/network/heartbeat/dns/init/InitDnsSocket.java b/wu-lazy-cloud-heartbeat-dns/src/main/java/org/framework/lazy/cloud/network/heartbeat/dns/init/InitDnsSocket.java
new file mode 100644
index 0000000..0ee461b
--- /dev/null
+++ b/wu-lazy-cloud-heartbeat-dns/src/main/java/org/framework/lazy/cloud/network/heartbeat/dns/init/InitDnsSocket.java
@@ -0,0 +1,22 @@
+package org.framework.lazy.cloud.network.heartbeat.dns.init;
+
+
+import lombok.extern.slf4j.Slf4j;
+import org.framework.lazy.cloud.network.heartbeat.dns.context.NettyDnsSocketApplicationListener;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+
+
+/**
+ * description 初始化服务端
+ *
+ * @author 吴佳伟
+ * @date 2023/09/12 18:22
+ */
+@Slf4j
+@Configuration
+@Import({NettyDnsSocketApplicationListener.class})
+public class InitDnsSocket {
+
+
+}
diff --git a/wu-lazy-cloud-heartbeat-dns/src/main/resources/META-INF/spring.factories b/wu-lazy-cloud-heartbeat-dns/src/main/resources/META-INF/spring.factories
new file mode 100644
index 0000000..f9b6772
--- /dev/null
+++ b/wu-lazy-cloud-heartbeat-dns/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,6 @@
+# Auto Configure
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+org.framework.lazy.cloud.network.heartbeat.dns.EnableDnsAutoConfiguration,\
+org.framework.lazy.cloud.network.heartbeat.dns.config.DnsAutoConfiguration,\
+org.framework.lazy.cloud.network.heartbeat.dns.init.InitDnsSocket,\
+org.framework.lazy.cloud.network.heartbeat.dns.config.DnsFlowConfiguration
diff --git a/wu-lazy-cloud-heartbeat-dns/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/wu-lazy-cloud-heartbeat-dns/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000..70b5eaa
--- /dev/null
+++ b/wu-lazy-cloud-heartbeat-dns/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1,4 @@
+org.framework.lazy.cloud.network.heartbeat.dns.EnableDnsAutoConfiguration
+org.framework.lazy.cloud.network.heartbeat.dns.config.DnsAutoConfiguration
+org.framework.lazy.cloud.network.heartbeat.dns.init.InitDnsSocket
+org.framework.lazy.cloud.network.heartbeat.dns.config.DnsFlowConfiguration
diff --git a/wu-lazy-cloud-heartbeat-dns/src/test/java/org/framework/lazy/cloud/network/heartbeat/dns/DnsServer.java b/wu-lazy-cloud-heartbeat-dns/src/test/java/org/framework/lazy/cloud/network/heartbeat/dns/DnsServer.java
new file mode 100644
index 0000000..3e69d6c
--- /dev/null
+++ b/wu-lazy-cloud-heartbeat-dns/src/test/java/org/framework/lazy/cloud/network/heartbeat/dns/DnsServer.java
@@ -0,0 +1,52 @@
+package org.framework.lazy.cloud.network.heartbeat.dns;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.DatagramChannel;
+import io.netty.channel.socket.nio.NioDatagramChannel;
+import io.netty.handler.codec.dns.DatagramDnsQueryDecoder;
+import io.netty.handler.codec.dns.DatagramDnsResponseEncoder;
+
+public class DnsServer {
+
+ private final int port;
+
+ public DnsServer(int port) {
+ this.port = port;
+ }
+
+ public void run() throws Exception {
+ EventLoopGroup group = new NioEventLoopGroup();
+ try {
+ Bootstrap b = new Bootstrap();
+ b.group(group)
+ .channel(NioDatagramChannel.class)
+ .option(ChannelOption.SO_BROADCAST, true)
+ .handler(new ChannelInitializer() {
+ @Override
+ protected void initChannel(DatagramChannel ch) throws Exception {
+ ch.pipeline().addLast(
+ new DatagramDnsQueryDecoder(),
+ new DatagramDnsResponseEncoder(),
+ new DnsServerHandler()
+ );
+ }
+ });
+
+ // 绑定端口
+ ChannelFuture f = b.bind(port).sync();
+ System.out.println("DNS server started and listening on port " + port);
+ f.channel().closeFuture().await();
+ } finally {
+ group.shutdownGracefully();
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ new DnsServer(53).run();
+ }
+}
\ No newline at end of file
diff --git a/wu-lazy-cloud-heartbeat-dns/src/test/java/org/framework/lazy/cloud/network/heartbeat/dns/DnsServerHandler.java b/wu-lazy-cloud-heartbeat-dns/src/test/java/org/framework/lazy/cloud/network/heartbeat/dns/DnsServerHandler.java
new file mode 100644
index 0000000..a7775e9
--- /dev/null
+++ b/wu-lazy-cloud-heartbeat-dns/src/test/java/org/framework/lazy/cloud/network/heartbeat/dns/DnsServerHandler.java
@@ -0,0 +1,37 @@
+package org.framework.lazy.cloud.network.heartbeat.dns;
+
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.SimpleChannelInboundHandler;
+import io.netty.handler.codec.dns.*;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class DnsServerHandler extends SimpleChannelInboundHandler {
+
+ @Override
+ protected void channelRead0(ChannelHandlerContext ctx, DatagramDnsQuery query) throws Exception {
+ log.info("query:{}", query);
+ // 创建DNS响应
+ DatagramDnsResponse response = new DatagramDnsResponse(query.recipient(), query.sender(), query.id());
+ // 处理每个查询问题
+ for (int i = 0; i < query.count(DnsSection.QUESTION); i++) {
+ DnsQuestion question = query.recordAt(DnsSection.QUESTION, i);
+ response.addRecord(DnsSection.QUESTION, question);
+
+ // 简单示例:返回一个固定的A记录
+ if (question.type() == DnsRecordType.A) {
+ DefaultDnsRawRecord answer = new DefaultDnsRawRecord(
+ question.name(), DnsRecordType.A, 3600,
+ io.netty.buffer.Unpooled.wrappedBuffer(new byte[]{127, 0, 0, 1}));
+ }
+ }
+ // 发送响应
+ ctx.writeAndFlush(response);
+ }
+
+ @Override
+ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+ cause.printStackTrace();
+ ctx.close();
+ }
+}
\ No newline at end of file
diff --git a/wu-lazy-cloud-heartbeat-dns/src/test/java/org/framework/lazy/cloud/network/heartbeat/dns/demo/DnsServer.java b/wu-lazy-cloud-heartbeat-dns/src/test/java/org/framework/lazy/cloud/network/heartbeat/dns/demo/DnsServer.java
new file mode 100644
index 0000000..055cf4e
--- /dev/null
+++ b/wu-lazy-cloud-heartbeat-dns/src/test/java/org/framework/lazy/cloud/network/heartbeat/dns/demo/DnsServer.java
@@ -0,0 +1,108 @@
+package org.framework.lazy.cloud.network.heartbeat.dns.demo;
+
+
+import com.github.xiaoymin.knife4j.core.util.StrUtil;
+import io.netty.bootstrap.Bootstrap;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.*;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.DatagramChannel;
+import io.netty.channel.socket.nio.NioDatagramChannel;
+import io.netty.handler.codec.dns.*;
+import io.netty.util.AttributeKey;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+
+@Slf4j
+public final class DnsServer {
+
+ private static final List BLACK_LIST_DOMAIN = new ArrayList<>();
+
+ static {
+
+ String s;
+ try (InputStream is = DnsServer.class.getClassLoader().getResourceAsStream("black_list.txt");
+ BufferedReader br = new BufferedReader(new InputStreamReader(is))) {
+ while (StrUtil.isNotBlank(s = br.readLine())) {
+ BLACK_LIST_DOMAIN.add(s);
+ }
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ ProxyUdp proxyUdp = new ProxyUdp();
+ proxyUdp.init();
+ final int[] num = {0};
+ final NioEventLoopGroup group = new NioEventLoopGroup();
+ Bootstrap bootstrap = new Bootstrap();
+ bootstrap.group(group).channel(NioDatagramChannel.class)
+ .handler(new ChannelInitializer() {
+ @Override
+ protected void initChannel(NioDatagramChannel nioDatagramChannel) {
+ nioDatagramChannel.pipeline().addLast(new DatagramDnsQueryDecoder());
+ nioDatagramChannel.pipeline().addLast(new SimpleChannelInboundHandler() {
+
+ @Override
+ protected void channelRead0(ChannelHandlerContext ctx, DatagramDnsQuery msg) {
+ try {
+ DefaultDnsQuestion dnsQuestion = msg.recordAt(DnsSection.QUESTION);
+ String name = dnsQuestion.name();
+ log.info(name + ++num[0]);
+ Channel channel = ctx.channel();
+ int id = msg.id();
+ channel.attr(AttributeKey.valueOf(String.valueOf(id))).set(msg);
+ if (BLACK_LIST_DOMAIN.contains(name)) {
+ DnsQuestion question = msg.recordAt(DnsSection.QUESTION);
+ DatagramDnsResponse dnsResponse = getDatagramDnsResponse(msg, id, question);
+ channel.writeAndFlush(dnsResponse);
+ return;
+ }
+ proxyUdp.send(name, msg.id(), channel);
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ }
+ }
+
+ private DatagramDnsResponse getDatagramDnsResponse(DatagramDnsQuery msg, int id, DnsQuestion question) {
+ DatagramDnsResponse dnsResponse = new DatagramDnsResponse(msg.recipient(), msg.sender(), id);
+ dnsResponse.addRecord(DnsSection.QUESTION, question);
+
+ // just print the IP after query
+ DefaultDnsRawRecord queryAnswer = new DefaultDnsRawRecord(
+ question.name(),
+ DnsRecordType.A, 600, Unpooled.wrappedBuffer(new byte[]{(byte) 192, (byte) 168, 1, 1}));
+ dnsResponse.addRecord(DnsSection.ANSWER, queryAnswer);
+ return dnsResponse;
+ }
+
+ @Override
+ public void exceptionCaught(ChannelHandlerContext ctx, Throwable e) {
+ log.error(e.getMessage(), e);
+ }
+ });
+ nioDatagramChannel.pipeline().addLast(new DatagramDnsResponseEncoder());
+
+ }
+ }).option(ChannelOption.SO_BROADCAST, true);
+
+ int port = 53;
+ ChannelFuture future = bootstrap.bind(port).addListener(future1 -> {
+ log.info("server listening port:{}", port);
+ });
+
+ future.channel().closeFuture().addListener(future1 -> {
+ if (future.isSuccess()) {
+ log.info(future.channel().toString());
+ }
+ });
+ }
+}
+
diff --git a/wu-lazy-cloud-heartbeat-dns/src/test/java/org/framework/lazy/cloud/network/heartbeat/dns/demo/ProxyUdp.java b/wu-lazy-cloud-heartbeat-dns/src/test/java/org/framework/lazy/cloud/network/heartbeat/dns/demo/ProxyUdp.java
new file mode 100644
index 0000000..ac4f377
--- /dev/null
+++ b/wu-lazy-cloud-heartbeat-dns/src/test/java/org/framework/lazy/cloud/network/heartbeat/dns/demo/ProxyUdp.java
@@ -0,0 +1,80 @@
+package org.framework.lazy.cloud.network.heartbeat.dns.demo;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.*;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.DatagramChannel;
+import io.netty.channel.socket.nio.NioDatagramChannel;
+import io.netty.handler.codec.dns.*;
+import io.netty.util.AttributeKey;
+import lombok.extern.slf4j.Slf4j;
+
+import java.net.InetSocketAddress;
+
+@Slf4j
+class ProxyUdp {
+ private Channel localChannel;
+ private Channel proxyChannel;
+
+ public void init() throws InterruptedException {
+ EventLoopGroup proxyGroup = new NioEventLoopGroup();
+ Bootstrap b = new Bootstrap();
+ b.group(proxyGroup)
+ .channel(NioDatagramChannel.class)
+ .handler(new ChannelInitializer() {
+ @Override
+ protected void initChannel(DatagramChannel ch) {
+ ChannelPipeline p = ch.pipeline();
+ p.addLast(new DatagramDnsQueryEncoder())
+ .addLast(new DatagramDnsResponseDecoder())
+ .addLast(new SimpleChannelInboundHandler() {
+ @Override
+ public void channelActive(ChannelHandlerContext ctx) {
+ log.info(ctx.channel().toString());
+ }
+
+ @Override
+ protected void channelRead0(ChannelHandlerContext ctx, DatagramDnsResponse msg) {
+ DatagramDnsQuery dnsQuery = localChannel.attr(AttributeKey.valueOf(String.valueOf(msg.id()))).get();
+ DnsQuestion question = msg.recordAt(DnsSection.QUESTION);
+ DatagramDnsResponse dnsResponse = new DatagramDnsResponse(dnsQuery.recipient(), dnsQuery.sender(), msg.id());
+ dnsResponse.addRecord(DnsSection.QUESTION, question);
+
+ for (int i = 0, count = msg.count(DnsSection.ANSWER); i < count; i++) {
+ DnsRecord record = msg.recordAt(DnsSection.ANSWER, i);
+ if (record.type() == DnsRecordType.A) {
+ // just print the IP after query
+ DnsRawRecord raw = (DnsRawRecord) record;
+ DefaultDnsRawRecord queryAnswer = new DefaultDnsRawRecord(
+ question.name(),
+ DnsRecordType.A, 600, Unpooled.wrappedBuffer(ByteBufUtil.getBytes(raw.content())));
+ dnsResponse.addRecord(DnsSection.ANSWER, queryAnswer);
+ }
+ }
+
+ localChannel.writeAndFlush(dnsResponse);
+ }
+
+ @Override
+ public void exceptionCaught(ChannelHandlerContext ctx, Throwable e) {
+ log.error(e.getMessage(), e);
+ }
+ });
+
+ }
+ });
+ proxyChannel = b.bind(0).sync().addListener(future1 -> {
+ log.info("绑定成功");
+ }).channel();
+ }
+
+ public void send(String domain, int id, Channel localChannel) {
+ this.localChannel = localChannel;
+ DnsQuery query = new DatagramDnsQuery(null, new InetSocketAddress("114.114.114.114", 53), id).setRecord(
+ DnsSection.QUESTION,
+ new DefaultDnsQuestion(domain, DnsRecordType.A));
+ this.proxyChannel.writeAndFlush(query);
+ }
+}
\ No newline at end of file