]>
git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/nproxy.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 "dnsparser.hh"
28 #include <boost/program_options.hpp>
30 #include <boost/format.hpp>
31 #include <boost/utility.hpp>
32 #include <boost/multi_index_container.hpp>
33 #include <boost/multi_index/ordered_index.hpp>
34 #include <boost/multi_index/key_extractors.hpp>
35 #include <boost/algorithm/string.hpp>
36 #include <sys/types.h>
41 #include "dnsrecords.hh"
45 #include "namespaces.hh"
46 using namespace :: boost :: multi_index
;
47 #include "namespaces.hh"
49 namespace po
= boost :: program_options
;
50 po :: variables_map g_vm
;
58 struct NotificationInFlight
63 uint16_t origID
, resentID
;
67 typedef map
< uint16_t , NotificationInFlight
> nifs_t
;
70 void syslogFmt ( const boost :: format
& fmt
)
72 cerr
<< "nproxy: " << fmt
<< endl
;
73 syslog ( LOG_WARNING
, "%s" , str ( fmt
). c_str ());
76 void handleOutsideUDPPacket ( int fd
, boost :: any
&)
80 struct NotificationInFlight nif
;
81 /* make sure we report enough room for IPv6 */
82 nif
. source
. sin4
. sin_family
= AF_INET6
;
85 socklen_t socklen
= nif
. source
. getSocklen ();
87 int res
= recvfrom ( fd
, buffer
, sizeof ( buffer
), 0 , ( struct sockaddr
*)& nif
. source
, & socklen
);
92 throw runtime_error ( "reading packet from remote: " + stringerror ());
94 MOADNSParser
mdp ( true , string ( buffer
, res
));
95 nif
. domain
= mdp
. d_qname
;
96 nif
. origID
= mdp
. d_header
. id
;
99 if ( mdp
. d_header
. opcode
== Opcode :: Query
&& ! mdp
. d_header
. qr
&& mdp
. d_answers
. empty () && mdp
. d_qname
. toString () == "pdns.nproxy." &&
100 ( mdp
. d_qtype
== QType :: TXT
|| mdp
. d_qtype
== QType :: A
)) {
101 vector
< uint8_t > packet
;
102 DNSPacketWriter
pw ( packet
, mdp
. d_qname
, mdp
. d_qtype
);
103 pw
. getHeader ()-> id
= mdp
. d_header
. id
;
104 pw
. getHeader ()-> rd
= mdp
. d_header
. rd
;
105 pw
. getHeader ()-> qr
= 1 ;
107 pw
. startRecord ( mdp
. d_qname
, mdp
. d_qtype
);
108 if ( mdp
. d_qtype
== QType :: TXT
) {
109 TXTRecordContent
trc ( " \" OK \" " );
112 else if ( mdp
. d_qtype
== QType :: A
) {
113 ARecordContent
arc ( "1.2.3.4" );
118 if ( sendto ( fd
, & packet
[ 0 ], packet
. size (), 0 , ( struct sockaddr
*)& nif
. source
, socklen
) < 0 ) {
119 syslogFmt ( boost :: format ( "Unable to send health check response to external nameserver %s - %s" ) % nif
. source
. toStringWithPort () % stringerror ());
124 if ( mdp
. d_header
. opcode
!= Opcode :: Notify
|| mdp
. d_qtype
!= QType :: SOA
) {
125 syslogFmt ( boost :: format ( "Received non-notification packet for domain '%s' from external nameserver %s" ) % nif
. domain
. toString () % nif
. source
. toStringWithPort ());
128 syslogFmt ( boost :: format ( "External notification received for domain '%s' from %s" ) % nif
. domain
. toString () % nif
. source
. toStringWithPort ());
129 vector
< uint8_t > outpacket
;
130 DNSPacketWriter
pw ( outpacket
, mdp
. d_qname
, mdp
. d_qtype
, 1 , Opcode :: Notify
);
132 static uint16_t s_idpool
;
133 pw
. getHeader ()-> id
= nif
. resentID
= s_idpool
++;
135 if ( send ( g_pdnssocket
, & outpacket
[ 0 ], outpacket
. size (), 0 ) < 0 ) {
136 throw runtime_error ( "Unable to send notify to PowerDNS: " + stringerror ());
138 nif
. resentTime
= time ( 0 );
139 g_nifs
[ nif
. resentID
] = nif
;
142 catch ( std :: exception
& e
)
144 syslogFmt ( boost :: format ( "Error parsing packet from external nameserver: %s" ) % e
. what ());
148 void handleInsideUDPPacket ( int fd
, boost :: any
&)
152 struct NotificationInFlight nif
;
153 /* make sure we report enough room for IPv6 */
154 nif
. source
. sin4
. sin_family
= AF_INET6
;
156 socklen_t socklen
= nif
. source
. getSocklen ();
158 int len
= recvfrom ( fd
, buffer
, sizeof ( buffer
), 0 , ( struct sockaddr
*)& nif
. source
, & socklen
);
163 throw runtime_error ( "reading packet from remote: " + stringerror ());
165 string
packet ( buffer
, len
);
166 MOADNSParser
mdp ( false , packet
);
168 // cerr<<"Inside notification response for: "<<mdp.d_qname<<endl;
170 if (! g_nifs
. count ( mdp
. d_header
. id
)) {
171 syslogFmt ( boost :: format ( "Response from inner PowerDNS with unknown ID %1%" ) % ( uint16_t ) mdp
. d_header
. id
);
175 nif
= g_nifs
[ mdp
. d_header
. id
];
177 if ( nif
. domain
!= mdp
. d_qname
) {
178 syslogFmt ( boost :: format ( "Response from inner nameserver for different domain '%s' than original notification '%s'" ) % mdp
. d_qname
. toString () % nif
. domain
. toString ());
180 if ( sendto ( nif
. origSocket
, buffer
, len
, 0 , ( sockaddr
*) & nif
. source
, nif
. source
. getSocklen ()) < 0 ) {
181 syslogFmt ( boost :: format ( "Unable to send notification response to external nameserver %s - %s" ) % nif
. source
. toStringWithPort () % stringerror ());
184 syslogFmt ( boost :: format ( "Sent notification response to external nameserver %s for domain '%s'" ) % nif
. source
. toStringWithPort () % nif
. domain
. toString ());
186 g_nifs
. erase ( mdp
. d_header
. id
);
189 catch ( std :: exception
& e
)
191 syslogFmt ( boost :: format ( "Error parsing packet from internal nameserver: %s" ) % e
. what ());
194 void expireOldNotifications ()
196 time_t limit
= time ( 0 ) - 10 ;
197 for ( nifs_t :: iterator iter
= g_nifs
. begin (); iter
!= g_nifs
. end (); ) {
198 if ( iter
-> second
. resentTime
< limit
) {
199 syslogFmt ( boost :: format ( "Notification for domain '%s' was sent to inner nameserver, but no response within 10 seconds" ) % iter
-> second
. domain
. toString ());
200 g_nifs
. erase ( iter
++);
207 void daemonize ( int null_fd
);
209 void usage ( po :: options_description
& desc
) {
210 cerr
<< "nproxy" << endl
;
214 int main ( int argc
, char ** argv
)
218 openlog ( "nproxy" , LOG_NDELAY
| LOG_PID
, LOG_DAEMON
);
220 g_fdm
= FDMultiplexer :: getMultiplexerSilent ();
222 throw std :: runtime_error ( "Could not enable a multiplexer" );
225 po :: options_description
desc ( "Allowed options" );
227 ( "help,h" , "produce help message" )
228 ( "version" , "print the version" )
229 ( "powerdns-address" , po :: value
< string
>(), "IP address of PowerDNS server" )
230 ( "chroot" , po :: value
< string
>(), "chroot to this directory for additional security" )
231 ( "setuid" , po :: value
< int >(), "setuid to this numerical user id" )
232 ( "setgid" , po :: value
< int >(), "setgid to this numerical user id" )
233 ( "origin-address" , po :: value
< string
>()-> default_value ( "::" ), "Source address for notifications to PowerDNS" )
234 ( "listen-address" , po :: value
< vector
< string
> >(), "IP addresses to listen on" )
235 ( "listen-port" , po :: value
< int >()-> default_value ( 53 ), "Source port to listen on" )
236 ( "daemon,d" , po :: value
< bool >()-> default_value ( true ), "operate in the background" )
237 ( "verbose,v" , "be verbose" );
239 po :: store ( po :: command_line_parser ( argc
, argv
). options ( desc
). run (), g_vm
);
242 if ( g_vm
. count ( "help" )) {
247 if ( g_vm
. count ( "version" )) {
248 cerr
<< "nproxy " << VERSION
<< endl
;
252 if (! g_vm
. count ( "powerdns-address" )) {
253 cerr
<< "Mandatory setting 'powerdns-address' unset: \n " << endl
;
258 if (! g_vm
. count ( "verbose" )) {
262 vector
< string
> addresses
;
263 if ( g_vm
. count ( "listen-address" ))
264 addresses
= g_vm
[ "listen-address" ]. as
< vector
< string
> >();
266 addresses
. push_back ( "::" );
268 // create sockets to listen on
270 syslogFmt ( boost :: format ( "Starting up" ));
271 for ( vector
< string
>:: const_iterator address
= addresses
. begin (); address
!= addresses
. end (); ++ address
) {
272 ComboAddress
local (* address
, g_vm
[ "listen-port" ]. as
< int >());
273 int sock
= socket ( local
. sin4
. sin_family
, SOCK_DGRAM
, 0 );
275 throw runtime_error ( "Creating socket for incoming packets: " + stringerror ());
277 if (:: bind ( sock
,( sockaddr
*) & local
, local
. getSocklen ()) < 0 )
278 throw runtime_error ( "Binding socket for incoming packets to '" + local
. toStringWithPort ()+ "': " + stringerror ());
280 g_fdm
-> addReadFD ( sock
, handleOutsideUDPPacket
); // add to fdmultiplexer for each socket
281 syslogFmt ( boost :: format ( "Listening for external notifications on address %s" ) % local
. toStringWithPort ());
284 // create socket that talks to inner PowerDNS
285 ComboAddress
originAddress ( g_vm
[ "origin-address" ]. as
< string
>(), 0 );
286 g_pdnssocket
= socket ( originAddress
. sin4
. sin_family
, SOCK_DGRAM
, 0 );
288 throw runtime_error ( "Creating socket for packets to PowerDNS: " + stringerror ());
291 if (:: bind ( g_pdnssocket
,( sockaddr
*) & originAddress
, originAddress
. getSocklen ()) < 0 )
292 throw runtime_error ( "Binding local address of inward socket to '" + originAddress
. toStringWithPort ()+ "': " + stringerror ());
295 ComboAddress
pdns ( g_vm
[ "powerdns-address" ]. as
< string
>(), 53 );
296 if ( connect ( g_pdnssocket
, ( struct sockaddr
*) & pdns
, pdns
. getSocklen ()) < 0 )
297 throw runtime_error ( "Failed to connect PowerDNS socket to address " + pdns
. toStringWithPort ()+ ": " + stringerror ());
299 syslogFmt ( boost :: format ( "Sending notifications from %s to internal address %s" ) % originAddress
. toString () % pdns
. toStringWithPort ());
301 g_fdm
-> addReadFD ( g_pdnssocket
, handleInsideUDPPacket
);
303 int null_fd
= open ( "/dev/null" , O_RDWR
); /* open stdin */
305 throw runtime_error ( "Unable to open /dev/null: " + stringerror ());
307 if ( g_vm
. count ( "chroot" )) {
308 if ( chroot ( g_vm
[ "chroot" ]. as
< string
>(). c_str ()) < 0 || chdir ( "/" ) < 0 )
309 throw runtime_error ( "while chrooting to " + g_vm
[ "chroot" ]. as
< string
>());
310 syslogFmt ( boost :: format ( "Changed root to directory '%s'" ) % g_vm
[ "chroot" ]. as
< string
>());
313 if ( g_vm
. count ( "setgid" )) {
314 if ( setgid ( g_vm
[ "setgid" ]. as
< int >()) < 0 )
315 throw runtime_error ( "while changing gid to " + std :: to_string ( g_vm
[ "setgid" ]. as
< int >()));
316 syslogFmt ( boost :: format ( "Changed gid to %d" ) % g_vm
[ "setgid" ]. as
< int >());
317 if ( setgroups ( 0 , NULL
) < 0 )
318 throw runtime_error ( "while dropping supplementary groups" );
321 if ( g_vm
. count ( "setuid" )) {
322 if ( setuid ( g_vm
[ "setuid" ]. as
< int >()) < 0 )
323 throw runtime_error ( "while changing uid to " + std :: to_string ( g_vm
[ "setuid" ]. as
< int >()));
324 syslogFmt ( boost :: format ( "Changed uid to %d" ) % g_vm
[ "setuid" ]. as
< int >());
327 if ( g_vm
[ "daemon" ]. as
< bool >()) {
328 syslogFmt ( boost :: format ( "Daemonizing" ));
332 syslogFmt ( boost :: format ( "Program operational" ));
338 gettimeofday (& now
, 0 );
340 // check for notifications that have been outstanding for more than 10 seconds
341 expireOldNotifications ();
344 catch ( boost :: program_options :: error
& e
)
346 syslogFmt ( boost :: format ( "Error parsing command line options: %s" ) % e
. what ());
348 catch ( std :: exception
& e
)
350 syslogFmt ( boost :: format ( "Fatal: %s" ) % e
. what ());
352 catch ( PDNSException
& e
)
354 syslogFmt ( boost :: format ( "Fatal: %s" ) % e
. reason
);
357 void daemonize ( int null_fd
)
364 dup2 ( null_fd
, 0 ); /* stdin */
365 dup2 ( null_fd
, 1 ); /* stderr */
366 dup2 ( null_fd
, 2 ); /* stderr */