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"
29 #include <boost/program_options.hpp>
31 #include <boost/format.hpp>
32 #include <boost/utility.hpp>
33 #include <boost/multi_index_container.hpp>
34 #include <boost/multi_index/ordered_index.hpp>
35 #include <boost/multi_index/key_extractors.hpp>
36 #include <boost/algorithm/string.hpp>
37 #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
;
54 SelectFDMultiplexer g_fdm
;
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 string
packet(buffer
, res
);
95 MOADNSParser
mdp(true, packet
);
96 nif
.domain
= mdp
.d_qname
;
97 nif
.origID
= mdp
.d_header
.id
;
100 if(mdp
.d_header
.opcode
== Opcode::Query
&& !mdp
.d_header
.qr
&& mdp
.d_answers
.empty() && mdp
.d_qname
.toString() == "pdns.nproxy." &&
101 (mdp
.d_qtype
== QType::TXT
|| mdp
.d_qtype
==QType::A
)) {
102 vector
<uint8_t> packet
;
103 DNSPacketWriter
pw(packet
, mdp
.d_qname
, mdp
.d_qtype
);
104 pw
.getHeader()->id
= mdp
.d_header
.id
;
105 pw
.getHeader()->rd
= mdp
.d_header
.rd
;
106 pw
.getHeader()->qr
= 1;
108 pw
.startRecord(mdp
.d_qname
, mdp
.d_qtype
);
109 if(mdp
.d_qtype
== QType::TXT
) {
110 TXTRecordContent
trc("\"OK\"");
113 else if(mdp
.d_qtype
== QType::A
) {
114 ARecordContent
arc("1.2.3.4");
119 if(sendto(fd
, &packet
[0], packet
.size(), 0, (struct sockaddr
*)&nif
.source
, socklen
) < 0) {
120 syslogFmt(boost::format("Unable to send health check response to external nameserver %s - %s") % nif
.source
.toStringWithPort() % stringerror());
125 if(mdp
.d_header
.opcode
!= Opcode::Notify
|| mdp
.d_qtype
!= QType::SOA
) {
126 syslogFmt(boost::format("Received non-notification packet for domain '%s' from external nameserver %s") % nif
.domain
.toString() % nif
.source
.toStringWithPort());
129 syslogFmt(boost::format("External notification received for domain '%s' from %s") % nif
.domain
.toString() % nif
.source
.toStringWithPort());
130 vector
<uint8_t> outpacket
;
131 DNSPacketWriter
pw(outpacket
, mdp
.d_qname
, mdp
.d_qtype
, 1, Opcode::Notify
);
133 static uint16_t s_idpool
;
134 pw
.getHeader()->id
= nif
.resentID
= s_idpool
++;
136 if(send(g_pdnssocket
, &outpacket
[0], outpacket
.size(), 0) < 0) {
137 throw runtime_error("Unable to send notify to PowerDNS: "+stringerror());
139 nif
.resentTime
=time(0);
140 g_nifs
[nif
.resentID
] = nif
;
143 catch(std::exception
&e
)
145 syslogFmt(boost::format("Error parsing packet from external nameserver: %s") % e
.what());
149 void handleInsideUDPPacket(int fd
, boost::any
&)
153 struct NotificationInFlight nif
;
154 /* make sure we report enough room for IPv6 */
155 nif
.source
.sin4
.sin_family
= AF_INET6
;
157 socklen_t socklen
=nif
.source
.getSocklen();
159 int len
=recvfrom(fd
, buffer
, sizeof(buffer
), 0, (struct sockaddr
*)&nif
.source
, &socklen
);
164 throw runtime_error("reading packet from remote: "+stringerror());
166 string
packet(buffer
, len
);
167 MOADNSParser
mdp(false, packet
);
169 // cerr<<"Inside notification response for: "<<mdp.d_qname<<endl;
171 if(!g_nifs
.count(mdp
.d_header
.id
)) {
172 syslogFmt(boost::format("Response from inner PowerDNS with unknown ID %1%") % (uint16_t)mdp
.d_header
.id
);
176 nif
=g_nifs
[mdp
.d_header
.id
];
178 if(nif
.domain
!= mdp
.d_qname
) {
179 syslogFmt(boost::format("Response from inner nameserver for different domain '%s' than original notification '%s'") % mdp
.d_qname
.toString() % nif
.domain
.toString());
181 if(sendto(nif
.origSocket
, buffer
, len
, 0, (sockaddr
*) &nif
.source
, nif
.source
.getSocklen()) < 0) {
182 syslogFmt(boost::format("Unable to send notification response to external nameserver %s - %s") % nif
.source
.toStringWithPort() % stringerror());
185 syslogFmt(boost::format("Sent notification response to external nameserver %s for domain '%s'") % nif
.source
.toStringWithPort() % nif
.domain
.toString());
187 g_nifs
.erase(mdp
.d_header
.id
);
190 catch(std::exception
&e
)
192 syslogFmt(boost::format("Error parsing packet from internal nameserver: %s") % e
.what());
195 void expireOldNotifications()
197 time_t limit
= time(0) - 10;
198 for(nifs_t::iterator iter
= g_nifs
.begin(); iter
!= g_nifs
.end(); ) {
199 if(iter
->second
.resentTime
< limit
) {
200 syslogFmt(boost::format("Notification for domain '%s' was sent to inner nameserver, but no response within 10 seconds") % iter
->second
.domain
.toString());
201 g_nifs
.erase(iter
++);
208 void daemonize(int null_fd
);
210 void usage(po::options_description
&desc
) {
211 cerr
<<"nproxy"<<endl
;
215 int main(int argc
, char** argv
)
219 openlog("nproxy", LOG_NDELAY
| LOG_PID
, LOG_DAEMON
);
221 po::options_description
desc("Allowed options");
223 ("help,h", "produce help message")
224 ("version", "print the version")
225 ("powerdns-address", po::value
<string
>(), "IP address of PowerDNS server")
226 ("chroot", po::value
<string
>(), "chroot to this directory for additional security")
227 ("setuid", po::value
<int>(), "setuid to this numerical user id")
228 ("setgid", po::value
<int>(), "setgid to this numerical user id")
229 ("origin-address", po::value
<string
>()->default_value("::"), "Source address for notifications to PowerDNS")
230 ("listen-address", po::value
<vector
<string
> >(), "IP addresses to listen on")
231 ("listen-port", po::value
<int>()->default_value(53), "Source port to listen on")
232 ("daemon,d", po::value
<bool>()->default_value(true), "operate in the background")
233 ("verbose,v", "be verbose");
235 po::store(po::command_line_parser(argc
, argv
).options(desc
).run(), g_vm
);
238 if (g_vm
.count("help")) {
243 if (g_vm
.count("version")) {
244 cerr
<< "nproxy " << VERSION
<< endl
;
248 if(!g_vm
.count("powerdns-address")) {
249 cerr
<<"Mandatory setting 'powerdns-address' unset:\n"<<endl
;
254 if(!g_vm
.count("verbose")) {
258 vector
<string
> addresses
;
259 if(g_vm
.count("listen-address"))
260 addresses
=g_vm
["listen-address"].as
<vector
<string
> >();
262 addresses
.push_back("::");
264 // create sockets to listen on
266 syslogFmt(boost::format("Starting up"));
267 for(vector
<string
>::const_iterator address
= addresses
.begin(); address
!= addresses
.end(); ++address
) {
268 ComboAddress
local(*address
, g_vm
["listen-port"].as
<int>());
269 int sock
= socket(local
.sin4
.sin_family
, SOCK_DGRAM
, 0);
271 throw runtime_error("Creating socket for incoming packets: "+stringerror());
273 if(::bind(sock
,(sockaddr
*) &local
, local
.getSocklen()) < 0)
274 throw runtime_error("Binding socket for incoming packets to '"+ local
.toStringWithPort()+"': "+stringerror());
276 g_fdm
.addReadFD(sock
, handleOutsideUDPPacket
); // add to fdmultiplexer for each socket
277 syslogFmt(boost::format("Listening for external notifications on address %s") % local
.toStringWithPort());
280 // create socket that talks to inner PowerDNS
281 ComboAddress
originAddress(g_vm
["origin-address"].as
<string
>(), 0);
282 g_pdnssocket
=socket(originAddress
.sin4
.sin_family
, SOCK_DGRAM
, 0);
284 throw runtime_error("Creating socket for packets to PowerDNS: "+stringerror());
287 if(::bind(g_pdnssocket
,(sockaddr
*) &originAddress
, originAddress
.getSocklen()) < 0)
288 throw runtime_error("Binding local address of inward socket to '"+ originAddress
.toStringWithPort()+"': "+stringerror());
291 ComboAddress
pdns(g_vm
["powerdns-address"].as
<string
>(), 53);
292 if(connect(g_pdnssocket
, (struct sockaddr
*) &pdns
, pdns
.getSocklen()) < 0)
293 throw runtime_error("Failed to connect PowerDNS socket to address "+pdns
.toStringWithPort()+": "+stringerror());
295 syslogFmt(boost::format("Sending notifications from %s to internal address %s") % originAddress
.toString() % pdns
.toStringWithPort());
297 g_fdm
.addReadFD(g_pdnssocket
, handleInsideUDPPacket
);
299 int null_fd
=open("/dev/null",O_RDWR
); /* open stdin */
301 throw runtime_error("Unable to open /dev/null: "+stringerror());
303 if(g_vm
.count("chroot")) {
304 if(chroot(g_vm
["chroot"].as
<string
>().c_str()) < 0 || chdir("/") < 0)
305 throw runtime_error("while chrooting to "+g_vm
["chroot"].as
<string
>());
306 syslogFmt(boost::format("Changed root to directory '%s'") % g_vm
["chroot"].as
<string
>());
309 if(g_vm
.count("setgid")) {
310 if(setgid(g_vm
["setgid"].as
<int>()) < 0)
311 throw runtime_error("while changing gid to "+std::to_string(g_vm
["setgid"].as
<int>()));
312 syslogFmt(boost::format("Changed gid to %d") % g_vm
["setgid"].as
<int>());
313 if(setgroups(0, NULL
) < 0)
314 throw runtime_error("while dropping supplementary groups");
317 if(g_vm
.count("setuid")) {
318 if(setuid(g_vm
["setuid"].as
<int>()) < 0)
319 throw runtime_error("while changing uid to "+std::to_string(g_vm
["setuid"].as
<int>()));
320 syslogFmt(boost::format("Changed uid to %d") % g_vm
["setuid"].as
<int>());
323 if(g_vm
["daemon"].as
<bool>()) {
324 syslogFmt(boost::format("Daemonizing"));
328 syslogFmt(boost::format("Program operational"));
334 gettimeofday(&now
, 0);
336 // check for notifications that have been outstanding for more than 10 seconds
337 expireOldNotifications();
340 catch(boost::program_options::error
& e
)
342 syslogFmt(boost::format("Error parsing command line options: %s") % e
.what());
344 catch(std::exception
& e
)
346 syslogFmt(boost::format("Fatal: %s") % e
.what());
348 catch(PDNSException
& e
)
350 syslogFmt(boost::format("Fatal: %s") % e
.reason
);
353 void daemonize(int null_fd
)
360 dup2(null_fd
,0); /* stdin */
361 dup2(null_fd
,1); /* stderr */
362 dup2(null_fd
,2); /* stderr */