簡(jiǎn)介
在前面的文章中刹悴,我們提到了使用netty構(gòu)建tcp和udp的客戶端向已經(jīng)公布的DNS服務(wù)器進(jìn)行域名請(qǐng)求服務(wù)咐鹤≌城眩基本的流程是借助于netty本身的NIO通道,將要查詢的信息封裝成為DNSMessage,通過(guò)netty搭建的channel發(fā)送到服務(wù)器端贷帮,然后從服務(wù)器端接受返回?cái)?shù)據(jù)戚揭,將其編碼為DNSResponse,進(jìn)行消息的處理撵枢。
那么DNS Server是否可以用netty實(shí)現(xiàn)呢民晒?
答案當(dāng)然是肯定的,但是之前也講過(guò)了DNS中有很多DnsRecordType锄禽,所以如果想實(shí)現(xiàn)全部的支持類型可能并現(xiàn)實(shí)潜必,這里我們就以最簡(jiǎn)單和最常用的A類型為例,用netty來(lái)實(shí)現(xiàn)一下DNS的TCP服務(wù)器沃但。
搭建netty服務(wù)器
因?yàn)槭荰CP請(qǐng)求磁滚,所以這里使用基于NIO的netty server服務(wù),也就是NioEventLoopGroup和NioServerSocketChannel,netty服務(wù)器的代碼如下:
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap().group(bossGroup,
workerGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new Do53ServerChannelInitializer());
final Channel channel = bootstrap.bind(dnsServerPort).channel();
channel.closeFuture().sync();
因?yàn)槭欠?wù)器宵晚,所以我們需要兩個(gè)EventLoopGroup垂攘,一個(gè)是bossGroup,一個(gè)是workerGroup淤刃。
將這兩個(gè)group傳遞給ServerBootstrap晒他,并指定channel是NioServerSocketChannel,然后添加自定義的Do53ServerChannelInitializer即可逸贾。
Do53ServerChannelInitializer中包含了netty自帶的tcp編碼解碼器和自定義的服務(wù)器端消息處理方式陨仅。
這里dnsServerPort=53,也是默認(rèn)的DNS服務(wù)器的端口值铝侵。
DNS服務(wù)器的消息處理
Do53ServerChannelInitializer是我們自定義的initializer,里面為pipline添加了消息的處理handler:
class Do53ServerChannelInitializer extends ChannelInitializer<Channel> {
@Override
protected void initChannel(Channel ch) throws Exception {
ch.pipeline().addLast(
new TcpDnsQueryDecoder(),
new TcpDnsResponseEncoder(),
new Do53ServerInboundHandler());
}
}
這里我們添加了兩個(gè)netty自帶的編碼解碼器灼伤,分別是TcpDnsQueryDecoder和TcpDnsResponseEncoder。
對(duì)于netty服務(wù)器來(lái)說(shuō)咪鲜,接收到的是ByteBuf消息狐赡,為了方便服務(wù)器端的消息讀取,需要將ByteBuf解碼為DnsQuery,這也就是TcpDnsQueryDecoder在做的事情嗜诀。
public final class TcpDnsQueryDecoder extends LengthFieldBasedFrameDecoder
TcpDnsQueryDecoder繼承自LengthFieldBasedFrameDecoder猾警,也就是以字段長(zhǎng)度來(lái)區(qū)分對(duì)象的起始位置。這和TCP查詢傳過(guò)來(lái)的數(shù)據(jù)結(jié)構(gòu)是一致的隆敢。
下面是它的decode方法:
protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
ByteBuf frame = (ByteBuf)super.decode(ctx, in);
return frame == null ? null : DnsMessageUtil.decodeDnsQuery(this.decoder, frame.slice(), new DnsQueryFactory() {
public DnsQuery newQuery(int id, DnsOpCode dnsOpCode) {
return new DefaultDnsQuery(id, dnsOpCode);
}
});
}
decode接受一個(gè)ByteBuf對(duì)象发皿,首先調(diào)用LengthFieldBasedFrameDecoder的decode方法,將真正需要解析的內(nèi)容解析出來(lái)拂蝎,然后再調(diào)用DnsMessageUtil的decodeDnsQuery方法將真正的ByteBuf內(nèi)容解碼成為DnsQuery返回穴墅。
這樣就可以在自定義的handler中處理DnsQuery消息了。
上面代碼中温自,自定義的handler叫做Do53ServerInboundHandler:
class Do53ServerInboundHandler extends SimpleChannelInboundHandler<DnsQuery>
從定義看玄货,Do53ServerInboundHandler要處理的消息就是DnsQuery。
看一下它的channelRead0方法:
protected void channelRead0(ChannelHandlerContext ctx,
DnsQuery msg) throws Exception {
DnsQuestion question = msg.recordAt(DnsSection.QUESTION);
log.info("Query is: {}", question);
ctx.writeAndFlush(newResponse(msg, question, 1000, QUERY_RESULT));
}
我們從DnsQuery的QUESTION section中拿到DnsQuestion悼泌,然后解析DnsQuestion的內(nèi)容松捉,根據(jù)DnsQuestion的內(nèi)容返回一個(gè)response給客戶端。
這里的respone是我們自定義的:
private DefaultDnsResponse newResponse(DnsQuery query,
DnsQuestion question,
long ttl, byte[]... addresses) {
DefaultDnsResponse response = new DefaultDnsResponse(query.id());
response.addRecord(DnsSection.QUESTION, question);
for (byte[] address : addresses) {
DefaultDnsRawRecord queryAnswer = new DefaultDnsRawRecord(
question.name(),
DnsRecordType.A, ttl, Unpooled.wrappedBuffer(address));
response.addRecord(DnsSection.ANSWER, queryAnswer);
}
return response;
}
上面的代碼封裝了一個(gè)新的DefaultDnsResponse對(duì)象馆里,并使用query的id作為DefaultDnsResponse的id隘世。并將question作為response的QUESEION section。
除了QUESTION section,response中還需要ANSWER section,這個(gè)ANSWER section需要填充一個(gè)DnsRecord鸠踪。
這里構(gòu)造了一個(gè)DefaultDnsRawRecord,傳入了record的name丙者,type,ttl和具體內(nèi)容营密。
最后將構(gòu)建好的DefaultDnsResponse返回械媒。
因?yàn)榭蛻舳瞬樵兊氖茿 address,按道理我們需要通過(guò)QUESTION中傳入的domain名字评汰,然后根據(jù)DNS服務(wù)器中存儲(chǔ)的記錄進(jìn)行查找纷捞,最終返回對(duì)應(yīng)域名的IP地址。
但是因?yàn)槲覀冎皇悄M的DNS服務(wù)器被去,所以并沒(méi)有真實(shí)的域名IP記錄兰绣,所以這里我們偽造了一個(gè)ip地址:
private static final byte[] QUERY_RESULT = new byte[]{46, 53, 107, 110};
然后調(diào)用Unpooled的wrappedBuffer方法,將byte數(shù)組轉(zhuǎn)換成為ByteBuf,傳入DefaultDnsRawRecord的構(gòu)造函數(shù)中编振。
這樣我們的DNS服務(wù)器就搭建好了缀辩。
DNS客戶端消息請(qǐng)求
上面我們搭建好了DNS服務(wù)器,接下來(lái)就可以使用DNS客戶端來(lái)請(qǐng)求DNS服務(wù)器了踪央。
這里我們使用之前創(chuàng)建好的netty DNS客戶端臀玄,只不過(guò)進(jìn)行少許改動(dòng),將DNS服務(wù)器的域名和IP地址替換成下面的值:
Do53TcpClient client = new Do53TcpClient();
final String dnsServer = "127.0.0.1";
final int dnsPort = 53;
final String queryDomain ="www.flydean.com";
client.startDnsClient(dnsServer,dnsPort,queryDomain);
dnsServer就填本機(jī)的IP地址畅蹂,dnsPort就是我們剛剛創(chuàng)建的默認(rèn)端口53健无。
首先運(yùn)行DNS服務(wù)器:
INFO i.n.handler.logging.LoggingHandler - [id: 0x021762f2] REGISTERED
INFO i.n.handler.logging.LoggingHandler - [id: 0x021762f2] BIND: 0.0.0.0/0.0.0.0:53
INFO i.n.handler.logging.LoggingHandler - [id: 0x021762f2, L:/0:0:0:0:0:0:0:0:53] ACTIVE
可以看到DNS服務(wù)器已經(jīng)準(zhǔn)備好了,綁定的端口是53液斜。
然后運(yùn)行上面的客戶端累贤,在客戶端可以得到下面的結(jié)果:
INFO c.f.d.Do53TcpChannelInboundHandler - question is :DefaultDnsQuestion(www.flydean.com. IN A)
INFO c.f.d.Do53TcpChannelInboundHandler - ip address is: 46.53.107.110
可以看到DNS查詢成功叠穆,并且返回了我們?cè)诜?wù)器中預(yù)設(shè)的值。
然后再看一下服務(wù)器端的輸出:
INFO i.n.handler.logging.LoggingHandler - [id: 0x021762f2, L:/0:0:0:0:0:0:0:0:53] READ: [id: 0x44d4c761, L:/127.0.0.1:53 - R:/127.0.0.1:65471]
INFO i.n.handler.logging.LoggingHandler - [id: 0x021762f2, L:/0:0:0:0:0:0:0:0:53] READ COMPLETE
INFO c.f.d.Do53ServerInboundHandler - Query is: DefaultDnsQuestion(www.flydean.com. IN A)
可以看到服務(wù)器端成功和客戶端建立了連接臼膏,并成功接收到了客戶端的查詢請(qǐng)求硼被。
總結(jié)
以上就是使用netty默認(rèn)DNS服務(wù)器端的實(shí)現(xiàn)原理和例子。因?yàn)槠邢奚酰@里只是默認(rèn)了type為A address的情況嚷硫,對(duì)其他type感興趣的朋友可以自行探索。
本文的代碼始鱼,大家可以參考: