Skip to content

Instantly share code, notes, and snippets.

@23Yong
Last active October 24, 2023 15:54
Show Gist options
  • Save 23Yong/c7aa6030b73e519bd1e5b2fc7b3e6aa1 to your computer and use it in GitHub Desktop.
Save 23Yong/c7aa6030b73e519bd1e5b2fc7b3e6aa1 to your computer and use it in GitHub Desktop.
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class DnsClient {
public void send(DatagramSocket datagramSocket, QType qType) throws Exception {
// 1.1.1.1 ip주소로 다음의 udp datagram을 전송한다.
datagramSocket.connect(InetAddress.getByName("168.126.63.1"), 53);
byte[] dnsQuery = null;
if (qType == QType.A) {
dnsQuery = createATypeDnsQuery();
} else if (qType == QType.MX) {
dnsQuery = createMXTypeDnsQuery();
}
// naver.com에 대한 ip주소 (A type record) 를 요청
datagramSocket.send(new DatagramPacket(dnsQuery, dnsQuery.length));
}
private byte[] createATypeDnsQuery() {
// create udp datagram to send to dns server
return new byte[]{
// Header Section
// 트랜잭션 아이디 (transaction id)
0x00, 0x01,
// |QR| Opcode |AA|TC|RD|RA|Z|RCODE|
// 0 0 0 0 0 0 0 0
0x00, 0x00, // QR, Opcode, AA, TC, RD, RA, Z, RCODE
// QDCOUNT
0x00, 0x01,
// ANCOUNT
0x00, 0x00,
// NSCOUNT
0x00, 0x00,
// ARCOUNT
0x00, 0x00,
// Question Section
// 질의 이름 QNAME(query name) 5naver3com0
// 0x05, 0x6e, 0x61, 0x76, 0x65, 0x72,
// 0x03, 0x63, 0x6f, 0x6d,
// 0x00,
// 질의 이름 QNAME(query name) 6google3com0
0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
0x03, 0x63, 0x6f, 0x6d,
0x00,
// 질의 타입 QTYPE(query type) A
0x00, 0x01, // A
// 질의 클래스 QCLASS(query class) IN(1)
0x00, 0x01
};
}
private byte[] createMXTypeDnsQuery() {
// create udp datagram to send to dns server
return new byte[]{
// Header Section
// 트랜잭션 아이디 (transaction id)
0x00, 0x01,
// |QR| Opcode |AA|TC|RD|RA|Z|RCODE|
// 0 0 0 0 0 0 0 0
0x00, 0x00, // QR, Opcode, AA, TC, RD, RA, Z, RCODE
// QDCOUNT
0x00, 0x01,
// ANCOUNT
0x00, 0x00,
// NSCOUNT
0x00, 0x00,
// ARCOUNT
0x00, 0x00,
// Question Section
// 질의 이름 QNAME(query name) 5naver3com0
0x05, 0x6e, 0x61, 0x76, 0x65, 0x72,
0x03, 0x63, 0x6f, 0x6d,
0x00,
// 질의 이름 QNAME(query name) 6google3com0
// 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
// 0x03, 0x63, 0x6f, 0x6d,
// 0x00,
// 질의 타입 QTYPE(query type) MX
0x00, 0x0f, // MX
// 질의 클래스 QCLASS(query class) IN(1)
0x00, 0x01
};
}
public byte[] receive(DatagramSocket datagramSocket) throws IOException {
byte[] bytes = new byte[2048];
DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length);
datagramSocket.receive(datagramPacket);
return bytes;
}
}
import java.net.DatagramSocket;
public class Main {
private static final DnsClient dnsClient = new DnsClient();
private static final ResponseParser responseParser = new ResponseParser();
public static void main(String[] args) throws Exception {
sendATypeRequest();
sendMXTypeRequest();
}
private static void sendATypeRequest() throws Exception {
var datagramSocket = new DatagramSocket();
// dns 서버에 요청을 보낸다.
dnsClient.send(datagramSocket, QType.A);
// 응답을 받는다.
byte[] receive = dnsClient.receive(datagramSocket);
responseParser.printPretty(receive);
}
private static void sendMXTypeRequest() throws Exception {
var datagramSocket = new DatagramSocket();
// dns 서버에 요청을 보낸다.
dnsClient.send(datagramSocket, QType.MX);
// 응답을 받는다.
byte[] receive = dnsClient.receive(datagramSocket);
responseParser.printPretty(receive);
}
}
public class ParserUtils {
public static String bytesToHex(byte... bytes) {
var sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
public static String parseName(byte[] bytes, int start) {
var sb = new StringBuilder();
for (int i = start; i < bytes.length; i++) {
if (bytes[i] == 0) { // QNAME 00 으로 종료
break;
}
if (checkMessageCompression(bytes[i])) {
int pointerOffset = Integer.parseInt(bytesToHex(bytes[i], bytes[i + 1]).substring(2), 16);
sb.append(parseName(bytes, pointerOffset));
break;
}
int length = bytes[i];
if (length > 0) {
for (int j = i + 1; j < i + 1 + length; j++) {
sb.append((char) bytes[j]);
}
sb.append(".");
i += length;
}
}
return sb.toString();
}
public static int parseNameLength(byte[] bytes, int start) {
int length = 0;
for (int i = start; i < bytes.length; i++) {
if (bytes[i] == 0) { // QNAME 00 으로 종료
break;
}
length++;
}
return length;
}
public static boolean checkMessageCompression(byte b) {
return (b & 0b11000000) == 0b11000000;
}
}
import java.util.Arrays;
public enum QType {
A(1) {
@Override
public void printRData(byte[] bytes, int startIdx, int rDataLength) {
System.out.println("address: " + RDataUtils.parseA(bytes, startIdx, rDataLength));
}
},
NS(2) {
@Override
public void printRData(byte[] bytes, int startIdx, int rDataLength) {
System.out.println("name server: " + RDataUtils.parseNS(bytes, startIdx, rDataLength));
}
},
MX(15) {
@Override
public void printRData(byte[] bytes, int startIdx, int rDataLength) {
int preference = bytes[startIdx] << 8 | bytes[startIdx + 1];
System.out.println("preference: " + preference);
String exchange = RDataUtils.parseMxExchange(bytes, startIdx + 2, rDataLength);
System.out.println("exchange: " + exchange);
}
},
UNKNOWN(-1) {
@Override
public void printRData(byte[] bytes, int startIdx, int rDataLength) {
System.out.println("UNKNOWN");
}
};
private final int value;
QType(int value) {
this.value = value;
}
public static String parseType(byte[] bytes, int start) {
int typeValue = bytes[start + 1] & 0xff;
return Arrays.stream(QType.values())
.filter(qType -> qType.value == typeValue)
.findFirst()
.orElse(UNKNOWN)
.name();
}
public static QType of(byte[] bytes, int start) {
int typeValue = bytes[start + 1] & 0xff;
return Arrays.stream(QType.values())
.filter(qType -> qType.value == typeValue)
.findFirst()
.orElse(UNKNOWN);
}
public abstract void printRData(byte[] bytes, int startIdx, int rDataLength);
}
public class RDataUtils {
public static String parseA(byte[] bytes, int startIdx, int rDataLength) {
var sb = new StringBuilder();
for (int i = startIdx; i < startIdx + rDataLength; i++) {
int value = bytes[i] & 0xff;
sb.append(value).append(".");
}
return sb.toString();
}
public static String parseNS(byte[] bytes, int startIdx, int rDataLength) {
var sb = new StringBuilder();
for (int i = startIdx + 1; i < startIdx + rDataLength; i++) {
if (ParserUtils.checkMessageCompression(bytes[i])) {
int pointerOffset = Integer.parseInt(ParserUtils.bytesToHex(bytes[i], bytes[i + 1]).substring(2), 16);
sb.append(".").append(ParserUtils.parseName(bytes, pointerOffset));
break;
}
sb.append((char) bytes[i]);
}
return sb.toString();
}
public static String parseMxExchange(byte[] bytes, int startIdx, int rDataLength) {
var sb = new StringBuilder();
for (int i = startIdx + 1; i < startIdx + rDataLength; i++) {
if (ParserUtils.checkMessageCompression(bytes[i])) {
int pointerOffset = Integer.parseInt(ParserUtils.bytesToHex(bytes[i], bytes[i + 1]).substring(2), 16);
sb.append(".").append(ParserUtils.parseName(bytes, pointerOffset));
break;
}
sb.append((char) bytes[i]);
}
return sb.toString();
}
}
response from naver.com
Header Section
00 01 -> 트랜잭션 아이디 (transaction id)
80 80 -> |QR| Opcode |AA|TC|RD|RA|Z|RCODE|
00 01 -> QDCOUNT
00 04 -> ANCOUNT
00 00 -> NSCOUNT
00 00 -> ARCOUNT
*
Question Section
// QNAME (5naver3com0)
05 6e 61 76 65 72 -> naver
03 63 6f 6d -> com
00
00 01 -> 질의 타입 (query type) A type(1)
00 01 -> 질의 클래스 (query class) IN(1)
*
Answer Section
c0 0c -> offset 12
00 01 -> TYPE : A type(1)
00 01 -> CLASS : IN(1)
00 00 00 b0 -> TTL : 0xb0 -> 176
00 04 -> RDLENGTH : 4
df 82 c8 6b -> RDATA : 223 130 200 107
*
c0 0c -> offset 12
00 01 -> TYPE: A type(1)
00 01 -> CLASS : IN(1)
00 00 00 b0 -> TTL : 0xb0 -> 176
00 04 -> RDLENGTH : 4
df 82 c3 5f -> RDATA : 223 130 195 95
c0 0c -> offset 12
00 01 -> TYPE: A type(1)
00 01 -> CLASS: IN(1)
00 00 00 b0 -> TTL : 176
00 04 -> RDLENGTH : 4
df 82 c3 c8 -> RDATA : 223 130 195 200
*
c0 0c -> offset 12
00 01 -> TYPE: A type(1)
00 01 -> CLASS: IN(1)
00 00 00 b0 -> TTL : 176
00 04 -> RDLENGTH : 4
df 82 c8 68 -> RDATA : 223 130 200 104
response from google.com
# Header section
00 01 -> 트랜잭션 ID (1)
80 80 -> |QR| Opcode |AA|TC|RD|RA|Z|RCODE|
00 01 -> QDCOUNT
00 01 -> ANCOUNT
00 00 -> NSCOUNT
00 00 -> ARCOUNT
# Question Section
// QNAME (6google3com0)
06 67 6f 6f 67 6c 65 -> google
03 63 6f 6d -> com
00
// QTYPE
00 01
// QCLASS
00 01
# Resource record
c0 0c -> offset
00 01 -> TYPE
00 01 -> CLASS
00 00 00 3b -> TTL
00 04 -> RDLENGTH
8e fa cf 6e -> RDATA : 142 250 207 110
=============================
HEADER SECTION
=============================
Transaction ID: 1
Flags: 1000000010000000
QDCOUNT: 1
ANCOUNT: 1
NSCOUNT: 4
ARCOUNT: 8
=============================
QUESTION SECTION
=============================
QName: google.com.
QType: A
QClass: 1
=============================
ANSWER SECTION
=============================
Name: google.com.
Type: A
Class: 1
TTL: 39
RDLength: 4
address: 172.217.26.238.
=============================
NAME SERVER SECTION
=============================
Name: google.com.
Type: NS
Class: 1
TTL: 47579
RDLength: 6
name server: ns1.google.com.
=============================
NAME SERVER SECTION
=============================
Name: google.com.
Type: NS
Class: 1
TTL: 47579
RDLength: 6
name server: ns4.google.com.
=============================
NAME SERVER SECTION
=============================
Name: google.com.
Type: NS
Class: 1
TTL: 47579
RDLength: 6
name server: ns3.google.com.
=============================
NAME SERVER SECTION
=============================
Name: google.com.
Type: NS
Class: 1
TTL: 47579
RDLength: 6
name server: ns2.google.com.
=============================
ADDITIONAL SECTION
=============================
Name: ns1.google.com.
Type: A
Class: 1
TTL: 45033
RDLength: 4
address: 216.239.32.10.
=============================
ADDITIONAL SECTION
=============================
Name: ns1.google.com.
Type: A
Class: 1
TTL: 45033
RDLength: 4
address: 216.239.32.10.
=============================
ADDITIONAL SECTION
=============================
Name: ns1.google.com.
Type: A
Class: 1
TTL: 45033
RDLength: 4
address: 216.239.32.10.
=============================
ADDITIONAL SECTION
=============================
Name: ns1.google.com.
Type: A
Class: 1
TTL: 45033
RDLength: 4
address: 216.239.32.10.
=============================
ADDITIONAL SECTION
=============================
Name: ns1.google.com.
Type: A
Class: 1
TTL: 45033
RDLength: 4
address: 216.239.32.10.
=============================
ADDITIONAL SECTION
=============================
Name: ns1.google.com.
Type: A
Class: 1
TTL: 45033
RDLength: 4
address: 216.239.32.10.
=============================
ADDITIONAL SECTION
=============================
Name: ns1.google.com.
Type: A
Class: 1
TTL: 45033
RDLength: 4
address: 216.239.32.10.
=============================
ADDITIONAL SECTION
=============================
Name: ns1.google.com.
Type: A
Class: 1
TTL: 45033
RDLength: 4
address: 216.239.32.10.
=============================
HEADER SECTION
=============================
Transaction ID: 1
Flags: 1000000010000000
QDCOUNT: 1
ANCOUNT: 3
NSCOUNT: 2
ARCOUNT: 5
=============================
QUESTION SECTION
=============================
QName: naver.com.
QType: MX
QClass: 1
=============================
ANSWER SECTION
=============================
Name: naver.com.
Type: MX
Class: 1
TTL: 196
RDLength: 8
preference: 10
exchange: mx3.naver.com.
=============================
ANSWER SECTION
=============================
Name: naver.com.
Type: MX
Class: 1
TTL: 196
RDLength: 8
preference: 10
exchange: mx1.naver.com.
=============================
ANSWER SECTION
=============================
Name: naver.com.
Type: MX
Class: 1
TTL: 196
RDLength: 8
preference: 10
exchange: mx2.naver.com.
=============================
NAME SERVER SECTION
=============================
Name: naver.com.
Type: NS
Class: 1
TTL: 31728
RDLength: 6
name server: ns1.naver.com.
=============================
NAME SERVER SECTION
=============================
Name: naver.com.
Type: NS
Class: 1
TTL: 31728
RDLength: 6
name server: ns2.naver.com.
=============================
ADDITIONAL SECTION
=============================
Name: mx1.naver.com.
Type: A
Class: 1
TTL: 197
RDLength: 4
address: 125.209.238.100.
=============================
ADDITIONAL SECTION
=============================
Name: mx1.naver.com.
Type: A
Class: 1
TTL: 197
RDLength: 4
address: 125.209.238.100.
=============================
ADDITIONAL SECTION
=============================
Name: mx1.naver.com.
Type: A
Class: 1
TTL: 197
RDLength: 4
address: 125.209.238.100.
=============================
ADDITIONAL SECTION
=============================
Name: mx1.naver.com.
Type: A
Class: 1
TTL: 197
RDLength: 4
address: 125.209.238.100.
=============================
ADDITIONAL SECTION
=============================
Name: mx1.naver.com.
Type: A
Class: 1
TTL: 197
RDLength: 4
address: 125.209.238.100.
import java.util.Map;
public class ResponseParser {
private enum QuestionSectionCount {
QDCOUNT, ANCOUNT, NSCOUNT, ARCOUNT
}
public void printPretty(byte[] bytes) {
Map<QuestionSectionCount, Integer> questionSectionCounts = printHeaderSection(bytes);
int qdCount = questionSectionCounts.get(QuestionSectionCount.QDCOUNT);
int anCount = questionSectionCounts.get(QuestionSectionCount.ANCOUNT);
int nsCount = questionSectionCounts.get(QuestionSectionCount.NSCOUNT);
int arCount = questionSectionCounts.get(QuestionSectionCount.ARCOUNT);
int questionSectionIdx = 12;
while (qdCount-- > 0) {
questionSectionIdx = printQuestionSection(bytes, questionSectionIdx);
}
int answerSectionIdx = questionSectionIdx;
while (anCount-- > 0) {
answerSectionIdx = printAnswerSection(bytes, answerSectionIdx);
}
int nameServerSectionIdx = answerSectionIdx;
while (nsCount-- > 0) {
nameServerSectionIdx = printNameServerSection(bytes, nameServerSectionIdx);
}
int additionalSectionIdx = nameServerSectionIdx;
while (arCount-- > 0) {
printAdditionalSection(bytes, additionalSectionIdx);
}
}
private Map<QuestionSectionCount, Integer> printHeaderSection(byte[] bytes) {
System.out.println("\n=============================");
System.out.println("HEADER SECTION");
System.out.println("=============================");
System.out.println("Transaction ID: " + Integer.valueOf(ParserUtils.bytesToHex(bytes[0], bytes[1]), 16));
System.out.println("Flags: " + Integer.toBinaryString(Integer.valueOf(ParserUtils.bytesToHex(bytes[2], bytes[3]), 16)));
int qdCount = Integer.valueOf(ParserUtils.bytesToHex(bytes[4], bytes[5]), 16);
int anCount = Integer.valueOf(ParserUtils.bytesToHex(bytes[6], bytes[7]), 16);
int nsCount = Integer.valueOf(ParserUtils.bytesToHex(bytes[8], bytes[9]), 16);
int arCount = Integer.valueOf(ParserUtils.bytesToHex(bytes[10], bytes[11]), 16);
System.out.println("QDCOUNT: " + qdCount);
System.out.println("ANCOUNT: " + anCount);
System.out.println("NSCOUNT: " + nsCount);
System.out.println("ARCOUNT: " + arCount);
return Map.of(
QuestionSectionCount.QDCOUNT, qdCount,
QuestionSectionCount.ANCOUNT, anCount,
QuestionSectionCount.NSCOUNT, nsCount,
QuestionSectionCount.ARCOUNT, arCount
);
}
private int printQuestionSection(byte[] bytes, int startIdx) {
System.out.println("=============================");
System.out.println("QUESTION SECTION");
System.out.println("=============================");
System.out.println("QName: " + ParserUtils.parseName(bytes, startIdx));
int domainLength = ParserUtils.parseNameLength(bytes, startIdx);
int qTypeEndIdx = startIdx + domainLength + 1;
System.out.println("QType: " + QType.parseType(bytes, qTypeEndIdx));
System.out.println("QClass: " + Integer.parseInt(ParserUtils.bytesToHex(bytes[qTypeEndIdx + 2], bytes[qTypeEndIdx + 3]), 16));
return qTypeEndIdx + 4;
}
private int printAnswerSection(byte[] bytes, int startIdx) {
System.out.println("=============================");
System.out.println("ANSWER SECTION");
System.out.println("=============================");
return printCommonAndReturnEndIdx(bytes, startIdx);
}
private int printNameServerSection(byte[] bytes, int startIdx) {
System.out.println("=============================");
System.out.println("NAME SERVER SECTION");
System.out.println("=============================");
return printCommonAndReturnEndIdx(bytes, startIdx);
}
private void printAdditionalSection(byte[] bytes, int startIdx) {
System.out.println("=============================");
System.out.println("ADDITIONAL SECTION");
System.out.println("=============================");
printCommonAndReturnEndIdx(bytes, startIdx);
}
private int printCommonAndReturnEndIdx(byte[] bytes, int startIdx) {
startIdx = printNameAndReturnEndIdx(bytes, startIdx);
System.out.println("Type: " + QType.parseType(bytes, startIdx));
System.out.println("Class: " + Integer.parseInt(ParserUtils.bytesToHex(bytes[startIdx + 2], bytes[startIdx + 3]), 16));
System.out.println("TTL: " + Integer.parseInt(ParserUtils.bytesToHex(bytes[startIdx + 4], bytes[startIdx + 5], bytes[startIdx + 6], bytes[startIdx + 7]), 16));
int rDataLength = bytes[startIdx + 8] * 256 + bytes[startIdx + 9];
System.out.println("RDLength: " + Integer.parseInt(ParserUtils.bytesToHex(bytes[startIdx + 8], bytes[startIdx + 9]), 16));
QType.of(bytes, startIdx).printRData(bytes, startIdx + 10, rDataLength);
return startIdx + 10 + rDataLength;
}
private int printNameAndReturnEndIdx(byte[] bytes, int startIdx) {
System.out.println("Name: " + ParserUtils.parseName(bytes, startIdx));
if (!ParserUtils.checkMessageCompression(bytes[startIdx])) {
startIdx += ParserUtils.parseNameLength(bytes, startIdx) + 1;
} else {
startIdx += 2;
}
return startIdx;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment