前言
DNS協議作爲着互聯網客戶端-服務器通信模式得第一關,在當下每天都有成千上億上網記錄產生得當今社會,其重要性自然不可言喻。在國內比較有名得DNS服務器有電信得114.114.114.114、阿里雲得223.5.5.5,DNSPod得119.29.29.29,配置一個好的DNS服務器可以縮短請求響應時間、降低DNS劫持概率,提升上網體驗。
上面這些都是互聯網公用DNS服務器,本文博主教大家使用 Java Netty
自建DNS代理服務器,目前網上對於使用Netty自建DNS服務器得教程良莠不齊,大多沒有代理步驟,達不到博主想要得代理效果,因而創建此文。
一、自建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端口,並初始化ProxyUdp
DNS請求代理對象
@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服務器效果了