1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty.resolver.dns;
17
18 import io.netty.buffer.ByteBuf;
19 import io.netty.handler.codec.dns.DefaultDnsQuestion;
20 import io.netty.handler.codec.dns.DnsOptCookieRecord;
21 import io.netty.handler.codec.dns.DnsQuestion;
22 import io.netty.handler.codec.dns.DnsRawRecord;
23 import io.netty.handler.codec.dns.DnsRecord;
24 import io.netty.handler.codec.dns.DnsRecordType;
25 import io.netty.handler.codec.dns.DnsResponse;
26 import io.netty.handler.codec.dns.DnsSection;
27
28 import java.net.InetSocketAddress;
29 import java.security.SecureRandom;
30 import java.util.Set;
31 import java.util.concurrent.ConcurrentHashMap;
32
33
34
35
36
37
38
39
40
41
42
43
44 final class DnsQueryProtection {
45
46
47
48
49
50 private static final int MAX_COOKIE_SERVERS = 1024;
51
52 private static final String ACE_PREFIX = "xn--";
53
54
55
56
57 enum CookieStatus {
58
59
60
61 ABSENT,
62
63
64
65 VALID,
66
67
68
69 INVALID
70 }
71
72 private final SecureRandom random = new SecureRandom();
73 private final boolean caseRandomization;
74
75 private final Set<InetSocketAddress> cookieServers;
76
77 DnsQueryProtection(boolean caseRandomization, boolean cookies) {
78 this.caseRandomization = caseRandomization;
79 this.cookieServers = cookies ? ConcurrentHashMap.<InetSocketAddress>newKeySet() : null;
80 }
81
82 boolean isCaseRandomizationEnabled() {
83 return caseRandomization;
84 }
85
86 boolean isCookiesEnabled() {
87 return cookieServers != null;
88 }
89
90
91
92
93 DnsQuestion randomizeCase(DnsQuestion question) {
94 final String name = question.name();
95 final int length = name.length();
96 final char[] chars = name.toCharArray();
97 final byte[] bits = new byte[(length + 7) >>> 3];
98 random.nextBytes(bits);
99
100 int labelStart = 0;
101 while (labelStart < length) {
102 int labelEnd = name.indexOf('.', labelStart);
103 if (labelEnd == -1) {
104 labelEnd = length;
105 }
106
107 if (!name.regionMatches(true, labelStart, ACE_PREFIX, 0, ACE_PREFIX.length())) {
108 for (int i = labelStart; i < labelEnd; i++) {
109 char c = chars[i];
110 if (((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) && (bits[i >>> 3] >>> (i & 7) & 1) != 0) {
111
112 chars[i] = (char) (c ^ 0x20);
113 }
114 }
115 }
116 labelStart = labelEnd + 1;
117 }
118 return new DefaultDnsQuestion(new String(chars), question.type(), question.dnsClass());
119 }
120
121 byte[] newClientCookie() {
122 byte[] cookie = new byte[DnsOptCookieRecord.CLIENT_COOKIE_LENGTH];
123 random.nextBytes(cookie);
124 return cookie;
125 }
126
127
128
129
130
131 boolean isCookieExpected(InetSocketAddress nameServerAddr) {
132 return cookieServers != null && cookieServers.contains(nameServerAddr);
133 }
134
135 void cookieReceived(InetSocketAddress nameServerAddr) {
136 if (cookieServers != null && cookieServers.size() < MAX_COOKIE_SERVERS) {
137 cookieServers.add(nameServerAddr);
138 }
139 }
140
141
142
143
144 static CookieStatus checkCookie(DnsResponse response, byte[] clientCookie) {
145 final int count = response.count(DnsSection.ADDITIONAL);
146 for (int i = 0; i < count; i++) {
147 DnsRecord record = response.recordAt(DnsSection.ADDITIONAL, i);
148 if (record.type() == DnsRecordType.OPT) {
149
150 if (!(record instanceof DnsRawRecord)) {
151 return CookieStatus.INVALID;
152 }
153 return checkCookie(((DnsRawRecord) record).content(), clientCookie);
154 }
155 }
156 return CookieStatus.ABSENT;
157 }
158
159 private static CookieStatus checkCookie(ByteBuf rdata, byte[] clientCookie) {
160
161 int index = rdata.readerIndex();
162 final int end = rdata.writerIndex();
163 CookieStatus status = CookieStatus.ABSENT;
164 while (index < end) {
165 if (end - index < 4) {
166 return CookieStatus.INVALID;
167 }
168 final int code = rdata.getUnsignedShort(index);
169 final int length = rdata.getUnsignedShort(index + 2);
170 index += 4;
171 if (end - index < length) {
172 return CookieStatus.INVALID;
173 }
174 if (code == DnsOptCookieRecord.OPTION_CODE) {
175 if (status != CookieStatus.ABSENT) {
176
177 return CookieStatus.INVALID;
178 }
179
180 if (length != clientCookie.length && (length < clientCookie.length + 8 ||
181 length > clientCookie.length + 32)) {
182 return CookieStatus.INVALID;
183 }
184 status = CookieStatus.VALID;
185
186 int diff = 0;
187 for (int i = 0; i < clientCookie.length; i++) {
188 diff |= rdata.getByte(index + i) ^ clientCookie[i];
189 }
190 if (diff != 0) {
191 return CookieStatus.INVALID;
192 }
193 }
194 index += length;
195 }
196 return status;
197 }
198 }