没错,请求DNS服务器还可以使用UDP协议
简介
之前我们讲到了如何在netty中构建client向DNS服务器进行域名解析请求。使用的是最常见的TCP协议,也叫做Do53/TCP。
事实上除了TCP协议之外,DNS服务器还接收UDP协议。这个协议叫做DNS-over-UDP/53,简称("Do53")。
本文将会一步一步带领大家在netty中搭建使用UDP的DNS客户端。
搭建netty客户端
因为这里使用的UDP协议,netty为UDP协议提供了专门的channel叫做NioDatagramChannel。EventLoopGroup还是可以使用常用的NioEventLoopGroup,这样我们搭建netty客户端的代码和常用的NIO UDP代码没有太大的区别,如下所示:
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();
这里的EventLoopGroup使用的是NioEventLoopGroup,作为client端Bootstrap的group。
因为要使用UDP协议进行传输,所以这里的channel使用的是NioDatagramChannel。
设置好channel之后,传入我们自定义的handler,netty client就搭建完毕了。
因为是UDP,所以这里没有使用TCP中的connect方法,而是使用bind方法来获得channel。
Do53UdpChannelInitializer中包含了netty提供的UDP DNS的编码解码器,还有自定义的消息处理器,我们会在后面的章节中详细进行介绍。
在netty中发送DNS查询请求
搭建好netty客户端之后,接下来就是使用客户端发送DNS查询消息了。
先看具体的查询代码:
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();
}
查询的逻辑是先构建UDP的DnsQuery请求包,然后将这请求包写入到channel中,然后等待消息处理完毕。
DnsQuery之前我们已经介绍过了,他是netty中所有DNS查询的基础类。
public interface DnsQuery extends DnsMessage
DnsQuery的子类有两个,分别是DatagramDnsQuery和DefaultDnsQuery。这两个实现类一个表示UDP协议的查询,一个表示TCP协议的查询。
我们看下UDP协议的DatagramDnsQuery具体定义:
public class DatagramDnsQuery extends DefaultDnsQuery implements AddressedEnvelope<DatagramDnsQuery, InetSocketAddress>
可以看到DatagramDnsQuery不仅仅继承自DefaultDnsQuery,还实现了AddressedEnvelope接口。
AddressedEnvelope是netty中UDP包的定义,所以要想在netty中发送基于UDP协议的数据包,就必须实现AddressedEnvelope中定义的方法。
作为一个UDP数据包,除了基本的DNS查询中所需要的id和opCode之外,还需要提供两个额外的地址,分别是sender和recipient:
private final InetSocketAddress sender;
private final InetSocketAddress recipient;
所以DatagramDnsQuery的构造函数可以接收4个参数:
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;
}
}
这里recipient和sender不能同时为空。
在上面的代码中,我们构建DatagramDnsQuery时,传入了服务器的InetSocketAddress:
final String dnsServer = "223.5.5.5";
final int dnsPort = 53;
InetSocketAddress addr = new InetSocketAddress(dnsServer, dnsPort);
并且随机生成了一个ID。然后调用setRecord方法填充查询的数据。
.setRecord(DnsSection.QUESTION,
new DefaultDnsQuestion(queryDomain, DnsRecordType.A));
DnsSection有4个,分别是:
QUESTION,
ANSWER,
AUTHORITY,
ADDITIONAL;
这里是查询操作,所以需要设置DnsSection.QUESTION。它的值是一个DnsQuestion:
public class DefaultDnsQuestion extends AbstractDnsRecord implements DnsQuestion
在这个查询中,我们传入了要查询的domain值:www.flydean.com,还有查询的类型A:address,表示的是域名的IP地址。
DNS消息的处理
在Do53UdpChannelInitializer中为pipline添加了netty提供的UDP编码解码器和自定义的消息处理器:
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负责将DnsQuery编码成为DatagramPacket,从而可以在NioDatagramChannel中进行传输。
public class DatagramDnsQueryEncoder extends MessageToMessageEncoder<AddressedEnvelope<DnsQuery, InetSocketAddress>> {
DatagramDnsQueryEncoder继承自MessageToMessageEncoder,要编码的对象是AddressedEnvelope,也就是我们构建的DatagramDnsQuery。
看一下它里面最核心的encode方法:
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));
}
基本思路就是从AddressedEnvelope中取出recipient和DnsQuery,然后调用encoder.encode方法将DnsQuery进行编码,最后将这些数据封装到DatagramPacket中。
这里的encoder是一个DnsQueryEncoder实例,专门用来编码DnsQuery对象。
DatagramDnsResponseDecoder负责将接受到的DatagramPacket对象解码成为DnsResponse供后续的自定义程序读取使用:
public class DatagramDnsResponseDecoder extends MessageToMessageDecoder<DatagramPacket>
看一下它的decode方法:
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);
}
}
上面的decode方法实际上调用了DnsResponseDecoder的decode方法进行解码操作。
最后就是自定义的Do53UdpChannelInboundHandler用来进行消息的读取和解析:
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())));
}
}
}
自定义handler接受的是一个DatagramDnsResponse对象,处理逻辑也很简单,首先读取msg中的QUESTION,并打印出来。
然后读取msg中的ANSWER字段,如果ANSWER的类型是A address,那么就调用NetUtil.bytesToIpAddress方法将其转换成为IP地址输出。
最后我们可能得到下面的输出:
question is :DefaultDnsQuestion(www.flydean.com. IN A)
49.112.38.167
总结
以上就是在netty中使用UDP协议进行DNS查询的详细讲解。
本文的代码,大家可以参考:
更多内容请参考 http://www.flydean.com/55-netty-dns-over-udp/
最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!
欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!
没错,请求DNS服务器还可以使用UDP协议的更多相关文章
- 手把手教你在netty中使用TCP协议请求DNS服务器
目录 简介 DNS传输协议简介 DNS的IP地址 Do53/TCP在netty中的使用 搭建DNS netty client 发送DNS查询消息 DNS查询的消息处理 总结 简介 DNS的全称doma ...
- netty系列之: 在netty中使用 tls 协议请求 DNS 服务器
目录 简介 支持DoT的DNS服务器 搭建支持DoT的netty客户端 TLS的客户端请求 总结 简介 在前面的文章中我们讲过了如何在netty中构造客户端分别使用tcp和udp协议向DNS服务器请求 ...
- Tftp文件传输服务器(基于UDP协议)
一个简单的UDP服务端与客户端 服务端: from socket import * #创建套接字 udp_server = socket(AF_INET,SOCK_DGRAM) msg_server ...
- DNS服务器的配置与应用: BIND9 的安装与配置
3. BIND9 的安装与配置 3.1 bind简介 BIND (Berkeley Internet Name Domain)是Domain Name System (DNS) 协议的一个实现,提供了 ...
- IP地址、子网掩码、网关、DNS服务器
1. IP地址 IP是英文Internet Protocol的缩写,意思是"网络之间互连的协议",也就是为计算机网络相互连接进行通信而设计的协议.在因特网中,它是能使连接到网上的所 ...
- udp协议与tcp协议
TCP协议与UDP协议支持的应用协议 TCP支持的应用协议主要有:Telnet.FTP.SMTP等: UDP支持的应用层协议主要有:NFS(网络文件系统).SNMP(简单网络管理协议).DNS(主域名 ...
- 在Debian上用Bind 配置DNS服务器
1 什么是DNS 初学者可能不理解DNS到底是什么,干什么用.我是在1998年大学毕业时才听说这个词的.那时我在聊天室碰到潍坊信息港的一个网管,我恬不知耻地说我也是个网管,他说也维护DNS吗?我说,D ...
- 网络编程——TCP协议、UDP协议、socket套接字、粘包问题以及解决方法
网络编程--TCP协议.UDP协议.socket套接字.粘包问题以及解决方法 TCP协议(流式协议) 当应用程序想通过TCP协议实现远程通信时,彼此之间必须先建立双向通信通道,基于该双向通道实现数 ...
- Linux服务器架设篇,DNS服务器(一),基础知识
一.端口 DNS监听端口 注意: DNS通常是以UDP协议来进行数据传输协议的,但是若没有办法查询到完整的信息是.DNS的daemon是named,它会启动TCP和UDP的53端口,所以启用DSN服务 ...
随机推荐
- 高级IPC DBus
What is IPC IPC [Inter-Process Communication] 进程间通信,指至少两个进程或线程间传送数据或信号的一些技术或方法.在Linux/Unix中,提供了许多IPC ...
- deepin安装jdk配置环境
下载一个jdk压缩包https://download.oracle.com/java/18/latest/jdk-18_linux-x64_bin.tar.gz 这个包,不用安装,下下来,直接解压,然 ...
- 防火墙之ipset表应用
简介 由于公司线上生产环境firewalld默认都是关闭的,所以只能依赖iptables的表来方便管理IP黑白名单. 提示线上生产环境不要随便重启iptables,会导致docker已经映射好的关系表 ...
- VS.NET启动显示ID为XXXX的进程当前未运行
解决办法:在启动项目根目录下用文本编辑器打开Web项目下的{X}.csproj文件,然后查找 <WebProjectProperties>,将这一对标签之间的内容全部删除,然后再打开项目就 ...
- 139_Power BI之某制造企业HR相关数据年度复盘
博客:www.jiaopengzi.com 焦棚子的文章目录 请点击下载附件 一.背景 最近在忙一个关于制造企业HR年度数据复盘分析:数据已脱敏. 先来看看效果. 1.视频效果 [video widt ...
- git指令使用
仓库为空,本地创建git项目之后提交到仓库中1.创建项目文件夹(本地git仓库)2.在项目文件夹中右键:选择Git Bash3.初始化项目:git init -- 会出现一个.git的隐藏文件夹4.将 ...
- .NET C#基础(5):结构体 - 高性能代码的基石
0. 文章目的 本文面向有一定.NET C#基础知识的学习者,介绍C#中结构体定义.使用以及特点. 1. 阅读基础 了解C#基本语法 了解.NET中的栈与托管堆 2. 值类型 2.1 .N ...
- Jmeter跨线程组获取token(彻底解决因格式token报错)
将token设置为全局变量网上方式方法千千万,但是你就是获取token会失败,不是提示格式错误,就是提示无法获取token,没安装Jmeter的可以查看前两期教程,有详细介绍 在工作当中如果仅仅是一个 ...
- torch.nn.MSELoss()函数解读
转载自:https://www.cnblogs.com/tingtin/p/13902325.html
- Django 学习记录(AcWing)
Django 2.1 搭建文件结构 前面的都是配置基本步骤,不需要理解,其他Django项目同样步骤操作: 接下来用Django-admin新建一个Django项目: django-admin sta ...