Custom DNS Server using Netty

Viewed 2467

I have been trying to write a custom DNS server using netty. I have used the DatagramDnsQueryDecoder to parse in the incoming DNSQuery UDP packets but I cannot figure out how to send a response to resolve a domain name. I have received the DatagramDnsQuery object from the handler but cannot find a way to initialize DatagramDnsResponse and add a test DNS record and send it back to the client through DatagramDnsResponseEncoder.

Here is what i have done so far

public class DNSListen {
public static void main(String[] args) {
    final NioEventLoopGroup group = new NioEventLoopGroup();

    try {
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(group)
                .channel(NioDatagramChannel.class)
                .handler(new ChannelInitializer<NioDatagramChannel>() {
                    @Override
                    protected void initChannel(NioDatagramChannel nioDatagramChannel) throws Exception {
                        nioDatagramChannel.pipeline().addLast(new DatagramDnsQueryDecoder());
                        nioDatagramChannel.pipeline().addLast(new DatagramDnsResponseEncoder());
                        nioDatagramChannel.pipeline().addLast(new DNSMessageHandler());
                    }
                })
                .option(ChannelOption.SO_BROADCAST, true);

        ChannelFuture future = bootstrap.bind(53).sync();
        future.channel().closeFuture().sync();
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        group.shutdownGracefully();
    }

}
}

here is the Handler for DNSMessages

public class DNSMessageHandler extends SimpleChannelInboundHandler<DatagramDnsQuery> {

@Override
protected void channelRead0(ChannelHandlerContext ctx, DatagramDnsQuery dnsMessage) throws Exception {
    System.out.println(dnsMessage.content());
}

@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    cause.printStackTrace();
}
}

I get the output for multiple DNS Requests when I run this and Set the System DNS as 127.0.0.1 But I cannot find a way to send a dummy DNS Response so that the IP address is resolved for the requested domain. (I think I should initialize a DatagramDnsResponse object and write it back to the user, but is that correct?, if so how do I initialize it with a dummy IP as the resolved IP) Am I in the wrong path, can someone please direct me to the correct path . thanks.

1 Answers

It's pretty easy actualy you just need an instance of DatagramDnsResponse and add a dns record with the addRecord method thats all.

Here an example for a SRV answer:

DatagramDnsResponse response = new DatagramDnsResponse(msg.recipient(), msg.sender(), msg.id());
ByteBuf buf = Unpooled.buffer();
buf.writeShort(0); // priority
buf.writeShort(0); // weight
buf.writeShort(993); // port
encodeName("my.domain.tld", buf); // target (special encoding: https://tools.ietf.org/html/rfc1101)
response.addRecord(DnsSection.ANSWER, new DefaultDnsRawRecord("_myprotocol._tcp.domain.tld." /* requested domain */, DnsRecordType.SRV, 30 /* ttl */ , buf));
ctx.writeAndFlush(response);

Edit

To encode a name use this function (I extracted it from netty):

public void encodeName(String name, ByteBuf buf) throws Exception {
    if(".".equals(name)) {
        buf.writeByte(0);
        return;
    }

    final String[] labels = name.split("\\.");
    for (String label : labels) {
        final int labelLen = label.length();
        if (labelLen == 0)
            break;

        buf.writeByte(labelLen);
        ByteBufUtil.writeAscii(buf, label);
    }

    buf.writeByte(0);
}

To decode a SRV record you could use this snippet:

DnsRawRecord record = msg.recordAt(DnsSection.ANSWER, 0); //msg is a DnsResponse
if(record.type() == DnsRecordType.SRV) {
    ByteBuf buf = record.content();
    System.out.println("\tPriority: " + buf.readUnsignedShort());
    System.out.println("\tWeight: " + buf.readUnsignedShort());
    System.out.println("\tPort: " + buf.readUnsignedShort());
    System.out.println("\tTarget:" + DefaultDnsRecordDecoder.decodeName(buf));
}
Related