]>
Commit | Line | Data |
---|---|---|
12c86877 | 1 | /* |
12471842 PL |
2 | * This file is part of PowerDNS or dnsdist. |
3 | * Copyright -- PowerDNS.COM B.V. and its contributors | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify | |
6 | * it under the terms of version 2 of the GNU General Public License as | |
7 | * published by the Free Software Foundation. | |
8 | * | |
9 | * In addition, for the avoidance of any doubt, permission is granted to | |
10 | * link this program with OpenSSL and to (re)distribute the binaries | |
11 | * produced as the result of such linking. | |
12 | * | |
13 | * This program is distributed in the hope that it will be useful, | |
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 | * GNU General Public License for more details. | |
17 | * | |
18 | * You should have received a copy of the GNU General Public License | |
19 | * along with this program; if not, write to the Free Software | |
20 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
21 | */ | |
870a0fe4 AT |
22 | #ifdef HAVE_CONFIG_H |
23 | #include "config.h" | |
24 | #endif | |
0ddde5fb RG |
25 | |
26 | #include <sys/types.h> | |
27 | #include <thread> | |
28 | ||
379ab445 | 29 | #include "packetcache.hh" |
b636533b | 30 | #include "utility.hh" |
12c86877 | 31 | #include "dnsproxy.hh" |
5c409fa2 | 32 | #include "pdnsexception.hh" |
12c86877 BH |
33 | #include "dns.hh" |
34 | #include "logger.hh" | |
35 | #include "statbag.hh" | |
d2116c15 | 36 | #include "dns_random.hh" |
c4e084f2 | 37 | #include "stubresolver.hh" |
c4e084f2 | 38 | #include "arguments.hh" |
519f5484 | 39 | #include "threadname.hh" |
12c86877 BH |
40 | |
41 | extern StatBag S; | |
12c86877 BH |
42 | |
43 | DNSProxy::DNSProxy(const string &remote) | |
44 | { | |
12c86877 BH |
45 | d_resanswers=S.getPointer("recursing-answers"); |
46 | d_resquestions=S.getPointer("recursing-questions"); | |
47 | d_udpanswers=S.getPointer("udp-answers"); | |
2b78726c PL |
48 | |
49 | vector<string> addresses; | |
50 | stringtok(addresses, remote, " ,\t"); | |
40c9a111 RG |
51 | d_remote = ComboAddress(addresses[0], 53); |
52 | ||
53 | if((d_sock=socket(d_remote.sin4.sin_family, SOCK_DGRAM,0))<0) { | |
a702a96c | 54 | throw PDNSException(string("socket: ")+stringerror()); |
40c9a111 RG |
55 | } |
56 | ||
c0cc6559 | 57 | ComboAddress local; |
40c9a111 | 58 | if(d_remote.sin4.sin_family==AF_INET) { |
c0cc6559 | 59 | local = ComboAddress("0.0.0.0"); |
40c9a111 RG |
60 | } |
61 | else { | |
c0cc6559 | 62 | local = ComboAddress("::"); |
40c9a111 | 63 | } |
c0cc6559 | 64 | |
40c9a111 | 65 | unsigned int n=0; |
12c86877 | 66 | for(;n<10;n++) { |
d2116c15 | 67 | local.sin4.sin_port = htons(10000+dns_random(50000)); |
12c86877 | 68 | |
c0cc6559 | 69 | if(::bind(d_sock, (struct sockaddr *)&local, local.getSocklen()) >= 0) |
12c86877 BH |
70 | break; |
71 | } | |
72 | if(n==10) { | |
3897b9e1 | 73 | closesocket(d_sock); |
12c86877 | 74 | d_sock=-1; |
a702a96c | 75 | throw PDNSException(string("binding dnsproxy socket: ")+stringerror()); |
12c86877 BH |
76 | } |
77 | ||
40c9a111 RG |
78 | if(connect(d_sock, (sockaddr *)&d_remote, d_remote.getSocklen())<0) { |
79 | throw PDNSException("Unable to UDP connect to remote nameserver "+d_remote.toStringWithPort()+": "+stringerror()); | |
80 | } | |
12c86877 | 81 | |
a410b176 | 82 | d_xor=dns_random_uint16(); |
e6a9dde5 | 83 | g_log<<Logger::Error<<"DNS Proxy launched, local port "<<ntohs(local.sin4.sin_port)<<", remote "<<d_remote.toStringWithPort()<<endl; |
12c86877 BH |
84 | } |
85 | ||
86 | void DNSProxy::go() | |
87 | { | |
969e4459 | 88 | std::thread t([this](){mainloop();}); |
0ddde5fb | 89 | t.detach(); |
12c86877 BH |
90 | } |
91 | ||
713010a2 | 92 | //! look up qname target with r->qtype, plonk it in the answer section of 'r' with name aname |
c2826d2e | 93 | bool DNSProxy::completePacket(std::unique_ptr<DNSPacket>& r, const DNSName& target,const DNSName& aname, const uint8_t scopeMask) |
d59b894d | 94 | { |
c4e084f2 | 95 | if(r->d_tcp) { |
c4e084f2 PD |
96 | vector<DNSZoneRecord> ips; |
97 | int ret1 = 0, ret2 = 0; | |
98 | ||
99 | if(r->qtype == QType::A || r->qtype == QType::ANY) | |
100 | ret1 = stubDoResolve(target, QType::A, ips); | |
101 | if(r->qtype == QType::AAAA || r->qtype == QType::ANY) | |
102 | ret2 = stubDoResolve(target, QType::AAAA, ips); | |
103 | ||
104 | if(ret1 != RCode::NoError || ret2 != RCode::NoError) { | |
a3928acd | 105 | g_log<<Logger::Error<<"Error resolving for "<<aname<<" ALIAS "<<target<<" over UDP, original query came in over TCP"; |
b9c5ccc5 PL |
106 | if (ret1 != RCode::NoError) { |
107 | g_log<<Logger::Error<<", A-record query returned "<<RCode::to_s(ret1); | |
108 | } | |
109 | if (ret2 != RCode::NoError) { | |
110 | g_log<<Logger::Error<<", AAAA-record query returned "<<RCode::to_s(ret2); | |
111 | } | |
112 | g_log<<Logger::Error<<", returning SERVFAIL"<<endl; | |
113 | r->clearRecords(); | |
114 | r->setRcode(RCode::ServFail); | |
115 | } else { | |
116 | for (auto &ip : ips) | |
117 | { | |
118 | ip.dr.d_name = aname; | |
9bbcf03a | 119 | r->addRecord(std::move(ip)); |
b9c5ccc5 | 120 | } |
c4e084f2 PD |
121 | } |
122 | ||
123 | uint16_t len=htons(r->getString().length()); | |
124 | string buffer((const char*)&len, 2); | |
125 | buffer.append(r->getString()); | |
50111728 | 126 | writen2WithTimeout(r->getSocket(), buffer.c_str(), buffer.length(), timeval{::arg().asNum("tcp-idle-timeout"),0}); |
c4e084f2 PD |
127 | |
128 | return true; | |
129 | } | |
130 | ||
d59b894d | 131 | uint16_t id; |
c2826d2e | 132 | uint16_t qtype = r->qtype.getCode(); |
d59b894d | 133 | { |
0ddde5fb | 134 | std::lock_guard<std::mutex> l(d_lock); |
d59b894d | 135 | id=getID_locked(); |
136 | ||
137 | ConntrackEntry ce; | |
138 | ce.id = r->d.id; | |
139 | ce.remote = r->d_remote; | |
140 | ce.outsock = r->getSocket(); | |
4646277d | 141 | ce.created = time( nullptr ); |
d59b894d | 142 | ce.qtype = r->qtype.getCode(); |
561434a6 | 143 | ce.qname = target; |
d59b894d | 144 | ce.anyLocal = r->d_anyLocal; |
c2826d2e | 145 | ce.complete = std::move(r); |
d59b894d | 146 | ce.aname=aname; |
0abea1ca | 147 | ce.anameScopeMask = scopeMask; |
c2826d2e | 148 | d_conntrack[id]=std::move(ce); |
d59b894d | 149 | } |
150 | ||
151 | vector<uint8_t> packet; | |
c2826d2e | 152 | DNSPacketWriter pw(packet, target, qtype); |
d59b894d | 153 | pw.getHeader()->rd=true; |
154 | pw.getHeader()->id=id ^ d_xor; | |
155 | ||
156 | if(send(d_sock,&packet[0], packet.size() , 0)<0) { // zoom | |
e6a9dde5 | 157 | g_log<<Logger::Error<<"Unable to send a packet to our recursing backend: "<<stringerror()<<endl; |
d59b894d | 158 | } |
159 | ||
160 | return true; | |
161 | ||
162 | } | |
163 | ||
164 | ||
12c86877 BH |
165 | /** This finds us an unused or stale ID. Does not actually clean the contents */ |
166 | int DNSProxy::getID_locked() | |
167 | { | |
168 | map_t::iterator i; | |
169 | for(int n=0;;++n) { | |
170 | i=d_conntrack.find(n); | |
171 | if(i==d_conntrack.end()) { | |
172 | return n; | |
173 | } | |
4646277d | 174 | else if(i->second.created<time(nullptr)-60) { |
d59b894d | 175 | if(i->second.created) { |
e6a9dde5 | 176 | g_log<<Logger::Warning<<"Recursive query for remote "<< |
85db02c5 | 177 | i->second.remote.toStringWithPort()<<" with internal id "<<n<< |
4957a608 | 178 | " was not answered by backend within timeout, reusing id"<<endl; |
c2826d2e | 179 | i->second.complete.reset(); |
bb6e54fe | 180 | S.inc("recursion-unanswered"); |
d59b894d | 181 | } |
12c86877 BH |
182 | return n; |
183 | } | |
184 | } | |
185 | } | |
186 | ||
e52fb6a4 | 187 | void DNSProxy::mainloop() |
12c86877 | 188 | { |
519f5484 | 189 | setThreadName("pdns/dnsproxy"); |
12c86877 BH |
190 | try { |
191 | char buffer[1500]; | |
a683e8bd | 192 | ssize_t len; |
12c86877 | 193 | |
65d8e171 KN |
194 | struct msghdr msgh; |
195 | struct iovec iov; | |
7bec330a | 196 | cmsgbuf_aligned cbuf; |
40c9a111 | 197 | ComboAddress fromaddr; |
65d8e171 | 198 | |
12c86877 | 199 | for(;;) { |
40c9a111 RG |
200 | socklen_t fromaddrSize = sizeof(fromaddr); |
201 | len=recvfrom(d_sock, buffer, sizeof(buffer),0, (struct sockaddr*) &fromaddr, &fromaddrSize); // answer from our backend | |
a683e8bd | 202 | if(len<(ssize_t)sizeof(dnsheader)) { |
4957a608 | 203 | if(len<0) |
e6a9dde5 | 204 | g_log<<Logger::Error<<"Error receiving packet from recursor backend: "<<stringerror()<<endl; |
4957a608 | 205 | else if(len==0) |
e6a9dde5 | 206 | g_log<<Logger::Error<<"Error receiving packet from recursor backend, EOF"<<endl; |
4957a608 | 207 | else |
e6a9dde5 | 208 | g_log<<Logger::Error<<"Short packet from recursor backend, "<<len<<" bytes"<<endl; |
4957a608 BH |
209 | |
210 | continue; | |
12c86877 | 211 | } |
40c9a111 | 212 | if (fromaddr != d_remote) { |
e6a9dde5 | 213 | g_log<<Logger::Error<<"Got answer from unexpected host "<<fromaddr.toStringWithPort()<<" instead of our recursor backend "<<d_remote.toStringWithPort()<<endl; |
40c9a111 RG |
214 | continue; |
215 | } | |
12c86877 BH |
216 | (*d_resanswers)++; |
217 | (*d_udpanswers)++; | |
88c1bc50 | 218 | dnsheader d; |
caa6eefa | 219 | memcpy(&d,buffer,sizeof(d)); |
12c86877 | 220 | { |
0ddde5fb | 221 | std::lock_guard<std::mutex> l(d_lock); |
bf549564 | 222 | #if BYTE_ORDER == BIG_ENDIAN |
4957a608 BH |
223 | // this is needed because spoof ID down below does not respect the native byteorder |
224 | d.id = ( 256 * (uint16_t)buffer[1] ) + (uint16_t)buffer[0]; | |
a899e13a | 225 | #endif |
4957a608 BH |
226 | map_t::iterator i=d_conntrack.find(d.id^d_xor); |
227 | if(i==d_conntrack.end()) { | |
e6a9dde5 | 228 | g_log<<Logger::Error<<"Discarding untracked packet from recursor backend with id "<<(d.id^d_xor)<< |
abc1d928 | 229 | ". Conntrack table size="<<d_conntrack.size()<<endl; |
4957a608 BH |
230 | continue; |
231 | } | |
232 | else if(i->second.created==0) { | |
e6a9dde5 | 233 | g_log<<Logger::Error<<"Received packet from recursor backend with id "<<(d.id^d_xor)<<" which is a duplicate"<<endl; |
4957a608 BH |
234 | continue; |
235 | } | |
d59b894d | 236 | |
4957a608 BH |
237 | d.id=i->second.id; |
238 | memcpy(buffer,&d,sizeof(d)); // commit spoofed id | |
239 | ||
27c0050c | 240 | DNSPacket p(false),q(false); |
a683e8bd RG |
241 | p.parse(buffer,(size_t)len); |
242 | q.parse(buffer,(size_t)len); | |
7de6b0d5 BH |
243 | |
244 | if(p.qtype.getCode() != i->second.qtype || p.qdomain != i->second.qname) { | |
e6a9dde5 | 245 | g_log<<Logger::Error<<"Discarding packet from recursor backend with id "<<(d.id^d_xor)<< |
c348fec2 | 246 | ", qname or qtype mismatch ("<<p.qtype.getCode()<<" v " <<i->second.qtype<<", "<<p.qdomain<<" v "<<i->second.qname<<")"<<endl; |
7de6b0d5 BH |
247 | continue; |
248 | } | |
f5310162 | 249 | |
65d8e171 KN |
250 | /* Set up iov and msgh structures. */ |
251 | memset(&msgh, 0, sizeof(struct msghdr)); | |
d808896e | 252 | string reply; // needs to be alive at time of sendmsg! |
a3928acd PL |
253 | MOADNSParser mdp(false, p.getString()); |
254 | // cerr<<"Got completion, "<<mdp.d_answers.size()<<" answers, rcode: "<<mdp.d_header.rcode<<endl; | |
255 | if (mdp.d_header.rcode == RCode::NoError) { | |
f80ebc05 | 256 | for(const auto & answer : mdp.d_answers) { |
a3928acd | 257 | // cerr<<"comp: "<<(int)j->first.d_place-1<<" "<<j->first.d_label<<" " << DNSRecordContent::NumberToType(j->first.d_type)<<" "<<j->first.d_content->getZoneRepresentation()<<endl; |
f80ebc05 | 258 | if(answer.first.d_place == DNSResourceRecord::ANSWER || (answer.first.d_place == DNSResourceRecord::AUTHORITY && answer.first.d_type == QType::SOA)) { |
d808896e | 259 | |
f80ebc05 | 260 | if(answer.first.d_type == i->second.qtype || (i->second.qtype == QType::ANY && (answer.first.d_type == QType::A || answer.first.d_type == QType::AAAA))) { |
a3928acd PL |
261 | DNSZoneRecord dzr; |
262 | dzr.dr.d_name=i->second.aname; | |
f80ebc05 O |
263 | dzr.dr.d_type = answer.first.d_type; |
264 | dzr.dr.d_ttl=answer.first.d_ttl; | |
265 | dzr.dr.d_place= answer.first.d_place; | |
266 | dzr.dr.d_content=answer.first.d_content; | |
9bbcf03a | 267 | i->second.complete->addRecord(std::move(dzr)); |
d808896e PL |
268 | } |
269 | } | |
270 | } | |
a3928acd PL |
271 | i->second.complete->setRcode(mdp.d_header.rcode); |
272 | } else { | |
d5fcd583 | 273 | g_log<<Logger::Error<<"Error resolving for "<<i->second.aname<<" ALIAS "<<i->second.qname<<" over UDP, "<<QType(i->second.qtype).toString()<<"-record query returned "<<RCode::to_s(mdp.d_header.rcode)<<", returning SERVFAIL"<<endl; |
a3928acd PL |
274 | i->second.complete->clearRecords(); |
275 | i->second.complete->setRcode(RCode::ServFail); | |
d808896e | 276 | } |
a3928acd PL |
277 | reply=i->second.complete->getString(); |
278 | iov.iov_base = (void*)reply.c_str(); | |
279 | iov.iov_len = reply.length(); | |
c2826d2e | 280 | i->second.complete.reset(); |
65d8e171 KN |
281 | msgh.msg_iov = &iov; |
282 | msgh.msg_iovlen = 1; | |
283 | msgh.msg_name = (struct sockaddr*)&i->second.remote; | |
284 | msgh.msg_namelen = i->second.remote.getSocklen(); | |
4646277d | 285 | msgh.msg_control=nullptr; |
65d8e171 KN |
286 | |
287 | if(i->second.anyLocal) { | |
7bec330a | 288 | addCMsgSrcAddr(&msgh, &cbuf, i->second.anyLocal.get_ptr(), 0); |
f5310162 | 289 | } |
a702a96c OM |
290 | if(sendmsg(i->second.outsock, &msgh, 0) < 0) { |
291 | int err = errno; | |
292 | g_log<<Logger::Warning<<"dnsproxy.cc: Error sending reply with sendmsg (socket="<<i->second.outsock<<"): "<<stringerror(err)<<endl; | |
293 | } | |
4957a608 | 294 | i->second.created=0; |
12c86877 BH |
295 | } |
296 | } | |
297 | } | |
3f81d239 | 298 | catch(PDNSException &ae) { |
e6a9dde5 | 299 | g_log<<Logger::Error<<"Fatal error in DNS proxy: "<<ae.reason<<endl; |
12c86877 | 300 | } |
adc10f99 | 301 | catch(std::exception &e) { |
e6a9dde5 | 302 | g_log<<Logger::Error<<"Communicator thread died because of STL error: "<<e.what()<<endl; |
12c86877 BH |
303 | } |
304 | catch( ... ) | |
305 | { | |
e6a9dde5 | 306 | g_log << Logger::Error << "Caught unknown exception." << endl; |
12c86877 | 307 | } |
e6a9dde5 | 308 | g_log<<Logger::Error<<"Exiting because DNS proxy failed"<<endl; |
5bd2ea7b | 309 | _exit(1); |
12c86877 | 310 | } |
732d9faa AT |
311 | |
312 | DNSProxy::~DNSProxy() { | |
a7b68ae7 RG |
313 | if (d_sock>-1) { |
314 | try { | |
315 | closesocket(d_sock); | |
316 | } | |
317 | catch(const PDNSException& e) { | |
318 | } | |
319 | } | |
320 | ||
732d9faa AT |
321 | d_sock=-1; |
322 | } |