前言
DNS協議作為著互聯網客戶端-服務器通信模式得第一關,在當下每天都有成千上億上網記錄產生得當今社會,其重要性自然不可言喻,在國內比較有名得DNS服務器有電信得114.114.114.114、阿里云得223.5.5.5,DNSPod得119.29.29.29,配置一個好的DNS服務器可以縮短請求回應時間、降低DNS劫持概率,提升上網體驗,
上面這些都是互聯網公用DNS服務器,本文博主教大家使用 Java Netty 自建DNS代理服務器,目前網上對于使用Netty自建DNS服務器得教程良莠不齊,大多沒有代理步驟,達不到博主想要得代理效果,因而創建此文,覺得本文有幫助得可以關注博主github
- https://github.com/wayn111
一、自建DNS代理服務器有哪些優勢
- 域名控制:對于特定域名可以自由控制訪問權限(屏蔽對特定網站訪問)
- 域名記錄:記錄局域網內各個主機得域名訪問(記錄員工上網記錄)
- 配置內網域名:通過自建DNS服務器可以配置內網域名,節約成本
- DNS負載均衡:通過自建DNS服務器可以輕松實作對于訪問域名得負載均衡配置
- ...
二、自建DNS代理服務器代碼
- 添加域名黑名單檔案,
resources檔案夾下添加black_list.txt檔案
google.com.
facebook.com.
初始化 BLACK_LIST_DOMAIN
private static final List<String> 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);
}
}
- 使用
UDP協議系結本機53埠,并初始化ProxyUdpDNS請求代理物件
@Slf4j
public final class DnsServer {
private static final List<String> BLACK_LIST_DOMAIN = new ArrayList<>();
static {
...
}
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<NioDatagramChannel>() {
@Override
protected void initChannel(NioDatagramChannel nioDatagramChannel) {
nioDatagramChannel.pipeline().addLast(...);
}
}).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());
}
});
}
}
- 給
nioDatagramChannel.pipeline()添加ChannelHandler
nioDatagramChannel.pipeline().addLast(new DatagramDnsQueryDecoder());
nioDatagramChannel.pipeline().addLast(new SimpleChannelInboundHandler<DatagramDnsQuery>() {
@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.<DatagramDnsQuery>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);
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());
在 new SimpleChannelInboundHandler<DatagramDnsQuery>() 中 決議客戶端DNS查詢報文, 獲取訪問域名資訊,如果訪問域名在黑名單中,則通過 getDatagramDnsResponse() 直接回傳 192.168.1.1 的DNS回應報文,反之則通過 proxyUdp 物件轉發DNS查詢,
ProxyUdp作為DNS查詢代理類會通過send(String domain, int id, Channel serverChannel)方法傳入DnsServer類收到的訪問域名、DNS事務ID、serverChannel,隨后包裝訪問域名請求DNS服務器114.114.114.114,最后通過new SimpleChannelInboundHandler<DatagramDnsResponse>()將收到的DNS回應報文通過上一步傳入得serverChannel輸出到客戶端,
@Slf4j
class ProxyUdp {
private Channel serverChannel;
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<DatagramChannel>() {
@Override
protected void initChannel(DatagramChannel ch) {
ChannelPipeline p = ch.pipeline();
p.addLast(new DatagramDnsQueryEncoder())
.addLast(new DatagramDnsResponseDecoder())
.addLast(new SimpleChannelInboundHandler<DatagramDnsResponse>() {
@Override
public void channelActive(ChannelHandlerContext ctx) {
log.info(ctx.channel().toString());
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, DatagramDnsResponse msg) {
DatagramDnsQuery dnsQuery = localChannel.attr(AttributeKey.<DatagramDnsQuery>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);
}
}
serverChannel.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 serverChannel) {
this.serverChannel = serverChannel;
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);
}
}
- 自建DNS服務器全部代碼
@Slf4j
public final class DnsServer {
private static final List<String> 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<NioDatagramChannel>() {
@Override
protected void initChannel(NioDatagramChannel nioDatagramChannel) {
nioDatagramChannel.pipeline().addLast(new DatagramDnsQueryDecoder());
nioDatagramChannel.pipeline().addLast(new SimpleChannelInboundHandler<DatagramDnsQuery>() {
@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.<DatagramDnsQuery>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 = 553;
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());
}
});
}
}
@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<DatagramChannel>() {
@Override
protected void initChannel(DatagramChannel ch) {
ChannelPipeline p = ch.pipeline();
p.addLast(new DatagramDnsQueryEncoder())
.addLast(new DatagramDnsResponseDecoder())
.addLast(new SimpleChannelInboundHandler<DatagramDnsResponse>() {
@Override
public void channelActive(ChannelHandlerContext ctx) {
log.info(ctx.channel().toString());
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, DatagramDnsResponse msg) {
DatagramDnsQuery dnsQuery = localChannel.attr(AttributeKey.<DatagramDnsQuery>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);
}
}
三、本地測驗
- 修改本機DNS設定(win11),修改首選、備選DNS地址為127.0.0.1
- 打開命令列工具,執行DNS快取清除命令
ipconfig/flushdns
自此就可以打開瀏覽器訪問常用網站,看是否能正常訪問,來驗證自建的DNS服務器效果了
參考資料
- 用 Node.js 手寫一個 DNS 服務器
- DNS中有哪些值得學習的優秀設計
- netty dns example
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/541472.html
標籤:其他
上一篇:Python筆記(2)——串列一:串列簡介(Python編程:從入門到實踐)
下一篇:[做題計劃1~10] 雜題亂選
