Introduction
Earlier we talked about how to build a client in netty to make a domain name resolution request to the DNS server. The most common TCP protocol is used, also known as Do53/TCP.
In fact, in addition to the TCP protocol, the DNS server also accepts the UDP protocol. This protocol is called DNS-over-UDP/53, or ("Do53") for short.
This article will guide you step by step to build a DNS client using UDP in netty.
Build netty client
Because of the UDP protocol used here, netty provides a special channel for the UDP protocol called NioDatagramChannel. EventLoopGroup can still use the commonly used NioEventLoopGroup, so that the code for building the netty client is not much different from the commonly used NIO UDP code, as shown below:
EventLoopGroup group = new NioEventLoopGroup();
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioDatagramChannel.class)
.handler(new Do53UdpChannelInitializer());
final Channel ch = b.bind(0).sync().channel();
The EventLoopGroup here uses NioEventLoopGroup, which is the group of Bootstrap on the client side.
Because the UDP protocol is used for transmission, the channel here uses NioDatagramChannel.
After setting up the channel, pass in our custom handler, and the netty client is built.
Because it is UDP, the connect method in TCP is not used here, but the bind method is used to obtain the channel.
Do53UdpChannelInitializer includes the UDP DNS codec provided by netty, as well as a custom message processor, which we will introduce in detail in the following chapters.
Send DNS query request in netty
After building the netty client, the next step is to use the client to send DNS query messages.
First look at the specific query code:
int randomID = (int) (System.currentTimeMillis() / 1000);
DnsQuery query = new DatagramDnsQuery(null, addr, randomID).setRecord(
DnsSection.QUESTION,
new DefaultDnsQuestion(queryDomain, DnsRecordType.A));
ch.writeAndFlush(query).sync();
boolean result = ch.closeFuture().await(10, TimeUnit.SECONDS);
if (!result) {
log.error("DNS查询失败");
ch.close().sync();
}
The logic of the query is to first construct the UDP DnsQuery request packet, then write the request packet to the channel, and then wait for the message to be processed.
We have already introduced DnsQuery before, it is the base class for all DNS queries in netty.
public interface DnsQuery extends DnsMessage
There are two subclasses of DnsQuery, DatagramDnsQuery and DefaultDnsQuery. One of these two implementation classes represents the query of the UDP protocol, and the other represents the query of the TCP protocol.
Let's look at the specific definition of the DatagramDnsQuery of the UDP protocol:
public class DatagramDnsQuery extends DefaultDnsQuery implements AddressedEnvelope<DatagramDnsQuery, InetSocketAddress>
It can be seen that DatagramDnsQuery not only inherits from DefaultDnsQuery, but also implements the AddressedEnvelope interface.
AddressedEnvelope is the definition of UDP packets in netty, so if you want to send packets based on UDP protocol in netty, you must implement the methods defined in AddressedEnvelope.
As a UDP packet, in addition to the id and opCode required in the basic DNS query, it also needs to provide two additional addresses, sender and recipient:
private final InetSocketAddress sender;
private final InetSocketAddress recipient;
So the constructor of DatagramDnsQuery can receive 4 parameters:
public DatagramDnsQuery(InetSocketAddress sender, InetSocketAddress recipient, int id, DnsOpCode opCode) {
super(id, opCode);
if (recipient == null && sender == null) {
throw new NullPointerException("recipient and sender");
} else {
this.sender = sender;
this.recipient = recipient;
}
}
Here recipient and sender cannot be null at the same time.
In the code above, we pass in the server's InetSocketAddress when building the DatagramDnsQuery:
final String dnsServer = "223.5.5.5";
final int dnsPort = 53;
InetSocketAddress addr = new InetSocketAddress(dnsServer, dnsPort);
And an ID is randomly generated. Then call the setRecord method to populate the query data.
.setRecord(DnsSection.QUESTION,
new DefaultDnsQuestion(queryDomain, DnsRecordType.A));
There are 4 DnsSections, namely:
QUESTION,
ANSWER,
AUTHORITY,
ADDITIONAL;
Here is the query operation, so you need to set DnsSection.QUESTION. Its value is a DnsQuestion:
public class DefaultDnsQuestion extends AbstractDnsRecord implements DnsQuestion
In this query, we pass in the domain value to be queried: www.flydean.com, and the query type A: address, which represents the IP address of the domain name.
Handling of DNS messages
In Do53UdpChannelInitializer, the UDP codec provided by netty and the custom message processor are added to the pipline:
class Do53UdpChannelInitializer extends ChannelInitializer<DatagramChannel> {
@Override
protected void initChannel(DatagramChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(new DatagramDnsQueryEncoder())
.addLast(new DatagramDnsResponseDecoder())
.addLast(new Do53UdpChannelInboundHandler());
}
}
DatagramDnsQueryEncoder is responsible for encoding DnsQuery into DatagramPacket, which can be transmitted in NioDatagramChannel.
public class DatagramDnsQueryEncoder extends MessageToMessageEncoder<AddressedEnvelope<DnsQuery, InetSocketAddress>> {
DatagramDnsQueryEncoder inherits from MessageToMessageEncoder, and the object to be encoded is AddressedEnvelope, which is the DatagramDnsQuery we built.
Take a look at the core encode method in it:
protected void encode(ChannelHandlerContext ctx, AddressedEnvelope<DnsQuery, InetSocketAddress> in, List<Object> out) throws Exception {
InetSocketAddress recipient = (InetSocketAddress)in.recipient();
DnsQuery query = (DnsQuery)in.content();
ByteBuf buf = this.allocateBuffer(ctx, in);
boolean success = false;
try {
this.encoder.encode(query, buf);
success = true;
} finally {
if (!success) {
buf.release();
}
}
out.add(new DatagramPacket(buf, recipient, (InetSocketAddress)null));
}
The basic idea is to take out the recipient and DnsQuery from the AddressedEnvelope, then call the encoder.encode method to encode the DnsQuery, and finally encapsulate the data into the DatagramPacket.
The encoder here is an instance of DnsQueryEncoder, which is specially used to encode DnsQuery objects.
DatagramDnsResponseDecoder is responsible for decoding the received DatagramPacket object into DnsResponse for subsequent reading by custom programs:
public class DatagramDnsResponseDecoder extends MessageToMessageDecoder<DatagramPacket>
Take a look at its decode method:
protected void decode(ChannelHandlerContext ctx, DatagramPacket packet, List<Object> out) throws Exception {
try {
out.add(this.decodeResponse(ctx, packet));
} catch (IndexOutOfBoundsException var5) {
throw new CorruptedFrameException("Unable to decode response", var5);
}
}
The decode method above actually calls the decode method of DnsResponseDecoder for decoding.
Finally, the custom Do53UdpChannelInboundHandler is used to read and parse messages:
private static void readMsg(DatagramDnsResponse msg) {
if (msg.count(DnsSection.QUESTION) > 0) {
DnsQuestion question = msg.recordAt(DnsSection.QUESTION, 0);
log.info("question is :{}", 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) {
//A记录用来指定主机名或者域名对应的IP地址
DnsRawRecord raw = (DnsRawRecord) record;
System.out.println(NetUtil.bytesToIpAddress(ByteBufUtil.getBytes(raw.content())));
}
}
}
The custom handler accepts a DatagramDnsResponse object, and the processing logic is also very simple. First, read the QUESTION in msg and print it.
Then read the ANSWER field in msg, if the type of ANSWER is A address, then call NetUtil.bytesToIpAddress method to convert it into IP address output.
In the end we might get the following output:
question is :DefaultDnsQuestion(www.flydean.com. IN A)
49.112.38.167
Summarize
The above is a detailed explanation of using UDP protocol for DNS query in netty.
The code of this article, you can refer to:
For more information, please refer to http://www.flydean.com/55-netty-dns-over-udp/
The most popular interpretation, the most profound dry goods, the most concise tutorials, and many tricks you don't know are waiting for you to discover!
Welcome to pay attention to my official account: "Program those things", understand technology, understand you better!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。