]>
git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/dnsproxy.cc
2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
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.
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.
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.
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.
26 #include <sys/types.h>
29 #include "packetcache.hh"
31 #include "dnsproxy.hh"
32 #include "pdnsexception.hh"
36 #include "dns_random.hh"
37 #include "stubresolver.hh"
38 #include "arguments.hh"
39 #include "threadname.hh"
40 #include "ednsoptions.hh"
41 #include "ednssubnet.hh"
45 DNSProxy :: DNSProxy ( const string
& remote
) :
46 d_xor ( dns_random_uint16 ())
48 d_resanswers
= S
. getPointer ( "recursing-answers" );
49 d_resquestions
= S
. getPointer ( "recursing-questions" );
50 d_udpanswers
= S
. getPointer ( "udp-answers" );
52 vector
< string
> addresses
;
53 stringtok ( addresses
, remote
, " , \t " );
54 d_remote
= ComboAddress ( addresses
[ 0 ], 53 );
56 if (( d_sock
= socket ( d_remote
. sin4
. sin_family
, SOCK_DGRAM
, 0 )) < 0 ) {
57 throw PDNSException ( string ( "socket: " ) + stringerror ());
61 if ( d_remote
. sin4
. sin_family
== AF_INET
) {
62 local
= ComboAddress ( "0.0.0.0" );
65 local
= ComboAddress ( "::" );
68 unsigned int attempts
= 0 ;
69 for (; attempts
< 10 ; attempts
++) {
70 local
. sin4
. sin_port
= htons ( 10000 + dns_random ( 50000 ));
72 if (:: bind ( d_sock
, ( struct sockaddr
*)& local
, local
. getSocklen ()) >= 0 ) { // NOLINT(cppcoreguidelines-pro-type-cstyle-cast)
79 throw PDNSException ( string ( "binding dnsproxy socket: " ) + stringerror ());
82 if ( connect ( d_sock
, ( sockaddr
*)& d_remote
, d_remote
. getSocklen ()) < 0 ) { // NOLINT(cppcoreguidelines-pro-type-cstyle-cast)
83 throw PDNSException ( "Unable to UDP connect to remote nameserver " + d_remote
. toStringWithPort () + ": " + stringerror ());
86 g_log
<< Logger :: Error
<< "DNS Proxy launched, local port " << ntohs ( local
. sin4
. sin_port
) << ", remote " << d_remote
. toStringWithPort () << endl
;
91 std :: thread
proxythread ([ this ]() { mainloop (); });
95 //! look up qname target with r->qtype, plonk it in the answer section of 'r' with name aname
96 bool DNSProxy :: completePacket ( std :: unique_ptr
< DNSPacket
>& reply
, const DNSName
& target
, const DNSName
& aname
, const uint8_t scopeMask
)
100 if ( reply
-> hasEDNSSubnet ()) {
101 DLOG ( g_log
<< "dnsproxy::completePacket: Parsed edns source: " << reply
-> d_eso
. source
. toString () << ", scope: " << reply
-> d_eso
. scope
. toString () << ", family = " << reply
-> d_eso
. scope
. getNetwork (). sin4
. sin_family
<< endl
);
102 ECSOptionStr
= makeEDNSSubnetOptsString ( reply
-> d_eso
);
103 DLOG ( g_log
<< "from dnsproxy::completePacket: Creating ECS option string " << makeHexDump ( ECSOptionStr
) << endl
);
107 vector
< DNSZoneRecord
> ips
;
110 // rip out edns info here, pass it to the stubDoResolve
111 if ( reply
-> qtype
== QType :: A
|| reply
-> qtype
== QType :: ANY
) {
112 ret1
= stubDoResolve ( target
, QType :: A
, ips
, reply
-> hasEDNSSubnet () ? & reply
-> d_eso
: nullptr );
114 if ( reply
-> qtype
== QType :: AAAA
|| reply
-> qtype
== QType :: ANY
) {
115 ret2
= stubDoResolve ( target
, QType :: AAAA
, ips
, reply
-> hasEDNSSubnet () ? & reply
-> d_eso
: nullptr );
118 if ( ret1
!= RCode :: NoError
|| ret2
!= RCode :: NoError
) {
119 g_log
<< Logger :: Error
<< "Error resolving for " << aname
<< " ALIAS " << target
<< " over UDP, original query came in over TCP" ;
120 if ( ret1
!= RCode :: NoError
) {
121 g_log
<< Logger :: Error
<< ", A-record query returned " << RCode :: to_s ( ret1
);
123 if ( ret2
!= RCode :: NoError
) {
124 g_log
<< Logger :: Error
<< ", AAAA-record query returned " << RCode :: to_s ( ret2
);
126 g_log
<< Logger :: Error
<< ", returning SERVFAIL" << endl
;
127 reply
-> clearRecords ();
128 reply
-> setRcode ( RCode :: ServFail
);
131 for ( auto & ip
: ips
) { // NOLINT(readability-identifier-length)
132 ip
. dr
. d_name
= aname
;
133 reply
-> addRecord ( std :: move ( ip
));
137 uint16_t len
= htons ( reply
-> getString (). length ());
138 string
buffer (( const char *)& len
, 2 );
139 buffer
. append ( reply
-> getString ());
140 writen2WithTimeout ( reply
-> getSocket (), buffer
. c_str (), buffer
. length (), timeval
{:: arg (). asNum ( "tcp-idle-timeout" ), 0 });
146 uint16_t qtype
= reply
-> qtype
. getCode ();
148 auto conntrack
= d_conntrack
. lock ();
149 id
= getID_locked (* conntrack
);
153 ce
. remote
= reply
-> d_remote
;
154 ce
. outsock
= reply
-> getSocket ();
155 ce
. created
= time ( nullptr );
156 ce
. qtype
= reply
-> qtype
. getCode ();
158 ce
. anyLocal
= reply
-> d_anyLocal
;
159 ce
. complete
= std :: move ( reply
);
161 ce
. anameScopeMask
= scopeMask
;
162 (* conntrack
)[ id
] = std :: move ( ce
);
165 vector
< uint8_t > packet
;
166 DNSPacketWriter
pw ( packet
, target
, qtype
);
167 pw
. getHeader ()-> rd
= true ;
168 pw
. getHeader ()-> id
= id
^ d_xor
;
169 // Add EDNS Subnet if the client sent one - issue #5469
170 if (! ECSOptionStr
. empty ()) {
171 DLOG ( g_log
<< "from dnsproxy::completePacket: adding ECS option string to packet options " << makeHexDump ( ECSOptionStr
) << endl
);
172 DNSPacketWriter :: optvect_t opts
;
173 opts
. emplace_back ( EDNSOptionCode :: ECS
, ECSOptionStr
);
174 pw
. addOpt ( 512 , 0 , 0 , opts
);
178 if ( send ( d_sock
, packet
. data (), packet
. size (), 0 ) < 0 ) { // zoom
179 g_log
<< Logger :: Error
<< "Unable to send a packet to our recursing backend: " << stringerror () << endl
;
185 /** This finds us an unused or stale ID. Does not actually clean the contents */
186 int DNSProxy :: getID_locked ( map_t
& conntrack
)
188 map_t :: iterator iter
;
189 for ( int n
= 0 ;; ++ n
) { // NOLINT(readability-identifier-length)
190 iter
= conntrack
. find ( n
);
191 if ( iter
== conntrack
. end ()) {
194 if ( iter
-> second
. created
< time ( nullptr ) - 60 ) {
195 if ( iter
-> second
. created
!= 0 ) {
196 g_log
<< Logger :: Warning
<< "Recursive query for remote " << iter
-> second
. remote
. toStringWithPort () << " with internal id " << n
<< " was not answered by backend within timeout, reusing id" << endl
;
197 iter
-> second
. complete
. reset ();
198 S
. inc ( "recursion-unanswered" );
205 void DNSProxy :: mainloop ()
207 setThreadName ( "pdns/dnsproxy" );
214 cmsgbuf_aligned cbuf
;
215 ComboAddress fromaddr
;
218 socklen_t fromaddrSize
= sizeof ( fromaddr
);
219 len
= recvfrom ( d_sock
, & buffer
[ 0 ], sizeof ( buffer
), 0 , ( struct sockaddr
*)& fromaddr
, & fromaddrSize
); // answer from our backend NOLINT(cppcoreguidelines-pro-type-cstyle-cast)
220 if ( len
< ( ssize_t
) sizeof ( dnsheader
)) {
222 g_log
<< Logger :: Error
<< "Error receiving packet from recursor backend: " << stringerror () << endl
;
225 g_log
<< Logger :: Error
<< "Error receiving packet from recursor backend, EOF" << endl
;
228 g_log
<< Logger :: Error
<< "Short packet from recursor backend, " << len
<< " bytes" << endl
;
233 if ( fromaddr
!= d_remote
) {
234 g_log
<< Logger :: Error
<< "Got answer from unexpected host " << fromaddr
. toStringWithPort () << " instead of our recursor backend " << d_remote
. toStringWithPort () << endl
;
240 memcpy (& dHead
, & buffer
[ 0 ], sizeof ( dHead
));
242 auto conntrack
= d_conntrack
. lock ();
243 #if BYTE_ORDER == BIG_ENDIAN
244 // this is needed because spoof ID down below does not respect the native byteorder
245 d
. id
= ( 256 * ( uint16_t ) buffer
[ 1 ]) + ( uint16_t ) buffer
[ 0 ];
247 auto iter
= conntrack
-> find ( dHead
. id
^ d_xor
);
248 if ( iter
== conntrack
-> end ()) {
249 g_log
<< Logger :: Error
<< "Discarding untracked packet from recursor backend with id " << ( dHead
. id
^ d_xor
) << ". Conntrack table size=" << conntrack
-> size () << endl
;
252 if ( iter
-> second
. created
== 0 ) {
253 g_log
<< Logger :: Error
<< "Received packet from recursor backend with id " << ( dHead
. id
^ d_xor
) << " which is a duplicate" << endl
;
257 dHead
. id
= iter
-> second
. id
;
258 memcpy (& buffer
[ 0 ], & dHead
, sizeof ( dHead
)); // commit spoofed id
260 DNSPacket
packet ( false );
261 packet
. parse (& buffer
[ 0 ], ( size_t ) len
);
263 if ( packet
. qtype
. getCode () != iter
-> second
. qtype
|| packet
. qdomain
!= iter
-> second
. qname
) {
264 g_log
<< Logger :: Error
<< "Discarding packet from recursor backend with id " << ( dHead
. id
^ d_xor
) << ", qname or qtype mismatch (" << packet
. qtype
. getCode () << " v " << iter
-> second
. qtype
<< ", " << packet
. qdomain
<< " v " << iter
-> second
. qname
<< ")" << endl
;
268 /* Set up iov and msgh structures. */
269 memset (& msgh
, 0 , sizeof ( struct msghdr
));
270 string reply
; // needs to be alive at time of sendmsg!
271 MOADNSParser
mdp ( false , packet
. getString ());
272 // update the EDNS options with info from the resolver - issue #5469
273 // note that this relies on the ECS string encoder to use the source network, and only take the prefix length from scope
274 iter
-> second
. complete
-> d_eso
. scope
= packet
. d_eso
. scope
;
275 DLOG ( g_log
<< "from dnsproxy::mainLoop: updated EDNS options from resolver EDNS source: " << iter
-> second
. complete
-> d_eso
. source
. toString () << " EDNS scope: " << iter
-> second
. complete
-> d_eso
. scope
. toString () << endl
);
277 if ( mdp
. d_header
. rcode
== RCode :: NoError
) {
278 for ( const auto & answer
: mdp
. d_answers
) {
279 if ( answer
. first
. d_place
== DNSResourceRecord :: ANSWER
|| ( answer
. first
. d_place
== DNSResourceRecord :: AUTHORITY
&& answer
. first
. d_type
== QType :: SOA
)) {
281 if ( answer
. first
. d_type
== iter
-> second
. qtype
|| ( iter
-> second
. qtype
== QType :: ANY
&& ( answer
. first
. d_type
== QType :: A
|| answer
. first
. d_type
== QType :: AAAA
))) {
283 dzr
. dr
. d_name
= iter
-> second
. aname
;
284 dzr
. dr
. d_type
= answer
. first
. d_type
;
285 dzr
. dr
. d_ttl
= answer
. first
. d_ttl
;
286 dzr
. dr
. d_place
= answer
. first
. d_place
;
287 dzr
. dr
. setContent ( answer
. first
. getContent ());
288 iter
-> second
. complete
-> addRecord ( std :: move ( dzr
));
293 iter
-> second
. complete
-> setRcode ( mdp
. d_header
. rcode
);
296 g_log
<< Logger :: Error
<< "Error resolving for " << iter
-> second
. aname
<< " ALIAS " << iter
-> second
. qname
<< " over UDP, " << QType ( iter
-> second
. qtype
). toString () << "-record query returned " << RCode :: to_s ( mdp
. d_header
. rcode
) << ", returning SERVFAIL" << endl
;
297 iter
-> second
. complete
-> clearRecords ();
298 iter
-> second
. complete
-> setRcode ( RCode :: ServFail
);
300 reply
= iter
-> second
. complete
-> getString ();
301 iov
. iov_base
= ( void *) reply
. c_str ();
302 iov
. iov_len
= reply
. length ();
303 iter
-> second
. complete
. reset ();
306 msgh
. msg_name
= ( struct sockaddr
*)& iter
-> second
. remote
; // NOLINT(cppcoreguidelines-pro-type-cstyle-cast)
307 msgh
. msg_namelen
= iter
-> second
. remote
. getSocklen ();
308 msgh
. msg_control
= nullptr ;
310 if ( iter
-> second
. anyLocal
) {
311 addCMsgSrcAddr (& msgh
, & cbuf
, iter
-> second
. anyLocal
. get_ptr (), 0 );
313 if ( sendmsg ( iter
-> second
. outsock
, & msgh
, 0 ) < 0 ) {
315 g_log
<< Logger :: Warning
<< "dnsproxy.cc: Error sending reply with sendmsg (socket=" << iter
-> second
. outsock
<< "): " << stringerror ( err
) << endl
;
317 iter
-> second
. created
= 0 ;
321 catch ( PDNSException
& ae
) {
322 g_log
<< Logger :: Error
<< "Fatal error in DNS proxy: " << ae
. reason
<< endl
;
324 catch ( std :: exception
& e
) {
325 g_log
<< Logger :: Error
<< "Communicator thread died because of STL error: " << e
. what () << endl
;
328 g_log
<< Logger :: Error
<< "Caught unknown exception." << endl
;
330 g_log
<< Logger :: Error
<< "Exiting because DNS proxy failed" << endl
;
334 DNSProxy ::~ DNSProxy ()
340 catch ( const PDNSException
& e
) {