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
;
83 socklen_t socklen
=sizeof(nif
.source
);
85 int res
=recvfrom(fd
, buffer
, sizeof(buffer
), 0, (struct sockaddr
*)&nif
.source
, &socklen
);
90 throw runtime_error("reading packet from remote: "+stringerror());
92 string
packet(buffer
, res
);
93 MOADNSParser
mdp(true, packet
);
94 nif
.domain
= mdp
.d_qname
;
95 nif
.origID
= mdp
.d_header
.id
;
98 if(mdp
.d_header
.opcode
== Opcode::Query
&& !mdp
.d_header
.qr
&& mdp
.d_answers
.empty() && mdp
.d_qname
.toString() == "pdns.nproxy." &&
99 (mdp
.d_qtype
== QType::TXT
|| mdp
.d_qtype
==QType::A
)) {
100 vector
<uint8_t> packet
;
101 DNSPacketWriter
pw(packet
, mdp
.d_qname
, mdp
.d_qtype
);
102 pw
.getHeader()->id
= mdp
.d_header
.id
;
103 pw
.getHeader()->rd
= mdp
.d_header
.rd
;
104 pw
.getHeader()->qr
= 1;
106 pw
.startRecord(mdp
.d_qname
, mdp
.d_qtype
);
107 if(mdp
.d_qtype
== QType::TXT
) {
108 TXTRecordContent
trc("\"OK\"");
111 else if(mdp
.d_qtype
== QType::A
) {
112 ARecordContent
arc("1.2.3.4");
117 if(sendto(fd
, &packet
[0], packet
.size(), 0, (struct sockaddr
*)&nif
.source
, socklen
) < 0) {
118 syslogFmt(boost::format("Unable to send health check response to external nameserver %s - %s") % nif
.source
.toStringWithPort() % stringerror());
123 if(mdp
.d_header
.opcode
!= Opcode::Notify
|| mdp
.d_qtype
!= QType::SOA
) {
124 syslogFmt(boost::format("Received non-notification packet for domain '%s' from external nameserver %s") % nif
.domain
.toString() % nif
.source
.toStringWithPort());
127 syslogFmt(boost::format("External notification received for domain '%s' from %s") % nif
.domain
.toString() % nif
.source
.toStringWithPort());
128 vector
<uint8_t> outpacket
;
129 DNSPacketWriter
pw(outpacket
, mdp
.d_qname
, mdp
.d_qtype
, 1, Opcode::Notify
);
131 static uint16_t s_idpool
;
132 pw
.getHeader()->id
= nif
.resentID
= s_idpool
++;
134 if(send(g_pdnssocket
, &outpacket
[0], outpacket
.size(), 0) < 0) {
135 throw runtime_error("Unable to send notify to PowerDNS: "+stringerror());
137 nif
.resentTime
=time(0);
138 g_nifs
[nif
.resentID
] = nif
;
141 catch(std::exception
&e
)
143 syslogFmt(boost::format("Error parsing packet from external nameserver: %s") % e
.what());
147 void handleInsideUDPPacket(int fd
, boost::any
&)
151 struct NotificationInFlight nif
;
153 socklen_t socklen
=sizeof(nif
.source
);
155 int len
=recvfrom(fd
, buffer
, sizeof(buffer
), 0, (struct sockaddr
*)&nif
.source
, &socklen
);
160 throw runtime_error("reading packet from remote: "+stringerror());
162 string
packet(buffer
, len
);
163 MOADNSParser
mdp(false, packet
);
165 // cerr<<"Inside notification response for: "<<mdp.d_qname<<endl;
167 if(!g_nifs
.count(mdp
.d_header
.id
)) {
168 syslogFmt(boost::format("Response from inner PowerDNS with unknown ID %1%") % (uint16_t)mdp
.d_header
.id
);
172 nif
=g_nifs
[mdp
.d_header
.id
];
174 if(nif
.domain
!= mdp
.d_qname
) {
175 syslogFmt(boost::format("Response from inner nameserver for different domain '%s' than original notification '%s'") % mdp
.d_qname
.toString() % nif
.domain
.toString());
177 if(sendto(nif
.origSocket
, buffer
, len
, 0, (sockaddr
*) &nif
.source
, nif
.source
.getSocklen()) < 0) {
178 syslogFmt(boost::format("Unable to send notification response to external nameserver %s - %s") % nif
.source
.toStringWithPort() % stringerror());
181 syslogFmt(boost::format("Sent notification response to external nameserver %s for domain '%s'") % nif
.source
.toStringWithPort() % nif
.domain
.toString());
183 g_nifs
.erase(mdp
.d_header
.id
);
186 catch(std::exception
&e
)
188 syslogFmt(boost::format("Error parsing packet from internal nameserver: %s") % e
.what());
191 void expireOldNotifications()
193 time_t limit
= time(0) - 10;
194 for(nifs_t::iterator iter
= g_nifs
.begin(); iter
!= g_nifs
.end(); ) {
195 if(iter
->second
.resentTime
< limit
) {
196 syslogFmt(boost::format("Notification for domain '%s' was sent to inner nameserver, but no response within 10 seconds") % iter
->second
.domain
.toString());
197 g_nifs
.erase(iter
++);
204 void daemonize(int null_fd
);
206 void usage(po::options_description
&desc
) {
207 cerr
<<"nproxy"<<endl
;
211 int main(int argc
, char** argv
)
215 openlog("nproxy", LOG_NDELAY
| LOG_PID
, LOG_DAEMON
);
217 po::options_description
desc("Allowed options");
219 ("help,h", "produce help message")
220 ("version", "print the version")
221 ("powerdns-address", po::value
<string
>(), "IP address of PowerDNS server")
222 ("chroot", po::value
<string
>(), "chroot to this directory for additional security")
223 ("setuid", po::value
<int>(), "setuid to this numerical user id")
224 ("setgid", po::value
<int>(), "setgid to this numerical user id")
225 ("origin-address", po::value
<string
>()->default_value("::"), "Source address for notifications to PowerDNS")
226 ("listen-address", po::value
<vector
<string
> >(), "IP addresses to listen on")
227 ("listen-port", po::value
<int>()->default_value(53), "Source port to listen on")
228 ("daemon,d", po::value
<bool>()->default_value(true), "operate in the background")
229 ("verbose,v", "be verbose");
231 po::store(po::command_line_parser(argc
, argv
).options(desc
).run(), g_vm
);
234 if (g_vm
.count("help")) {
239 if (g_vm
.count("version")) {
240 cerr
<< "nproxy " << VERSION
<< endl
;
244 if(!g_vm
.count("powerdns-address")) {
245 cerr
<<"Mandatory setting 'powerdns-address' unset:\n"<<endl
;
250 if(!g_vm
.count("verbose")) {
254 vector
<string
> addresses
;
255 if(g_vm
.count("listen-address"))
256 addresses
=g_vm
["listen-address"].as
<vector
<string
> >();
258 addresses
.push_back("::");
260 // create sockets to listen on
262 syslogFmt(boost::format("Starting up"));
263 for(vector
<string
>::const_iterator address
= addresses
.begin(); address
!= addresses
.end(); ++address
) {
264 ComboAddress
local(*address
, g_vm
["listen-port"].as
<int>());
265 int sock
= socket(local
.sin4
.sin_family
, SOCK_DGRAM
, 0);
267 throw runtime_error("Creating socket for incoming packets: "+stringerror());
269 if(::bind(sock
,(sockaddr
*) &local
, local
.getSocklen()) < 0)
270 throw runtime_error("Binding socket for incoming packets to '"+ local
.toStringWithPort()+"': "+stringerror());
272 g_fdm
.addReadFD(sock
, handleOutsideUDPPacket
); // add to fdmultiplexer for each socket
273 syslogFmt(boost::format("Listening for external notifications on address %s") % local
.toStringWithPort());
276 // create socket that talks to inner PowerDNS
277 ComboAddress
originAddress(g_vm
["origin-address"].as
<string
>(), 0);
278 g_pdnssocket
=socket(originAddress
.sin4
.sin_family
, SOCK_DGRAM
, 0);
280 throw runtime_error("Creating socket for packets to PowerDNS: "+stringerror());
283 if(::bind(g_pdnssocket
,(sockaddr
*) &originAddress
, originAddress
.getSocklen()) < 0)
284 throw runtime_error("Binding local address of inward socket to '"+ originAddress
.toStringWithPort()+"': "+stringerror());
287 ComboAddress
pdns(g_vm
["powerdns-address"].as
<string
>(), 53);
288 if(connect(g_pdnssocket
, (struct sockaddr
*) &pdns
, pdns
.getSocklen()) < 0)
289 throw runtime_error("Failed to connect PowerDNS socket to address "+pdns
.toStringWithPort()+": "+stringerror());
291 syslogFmt(boost::format("Sending notifications from %s to internal address %s") % originAddress
.toString() % pdns
.toStringWithPort());
293 g_fdm
.addReadFD(g_pdnssocket
, handleInsideUDPPacket
);
295 int null_fd
=open("/dev/null",O_RDWR
); /* open stdin */
297 throw runtime_error("Unable to open /dev/null: "+stringerror());
299 if(g_vm
.count("chroot")) {
300 if(chroot(g_vm
["chroot"].as
<string
>().c_str()) < 0 || chdir("/") < 0)
301 throw runtime_error("while chrooting to "+g_vm
["chroot"].as
<string
>());
302 syslogFmt(boost::format("Changed root to directory '%s'") % g_vm
["chroot"].as
<string
>());
305 if(g_vm
.count("setgid")) {
306 if(setgid(g_vm
["setgid"].as
<int>()) < 0)
307 throw runtime_error("while changing gid to "+std::to_string(g_vm
["setgid"].as
<int>()));
308 syslogFmt(boost::format("Changed gid to %d") % g_vm
["setgid"].as
<int>());
309 if(setgroups(0, NULL
) < 0)
310 throw runtime_error("while dropping supplementary groups");
313 if(g_vm
.count("setuid")) {
314 if(setuid(g_vm
["setuid"].as
<int>()) < 0)
315 throw runtime_error("while changing uid to "+std::to_string(g_vm
["setuid"].as
<int>()));
316 syslogFmt(boost::format("Changed uid to %d") % g_vm
["setuid"].as
<int>());
319 if(g_vm
["daemon"].as
<bool>()) {
320 syslogFmt(boost::format("Daemonizing"));
324 syslogFmt(boost::format("Program operational"));
330 gettimeofday(&now
, 0);
332 // check for notifications that have been outstanding for more than 10 seconds
333 expireOldNotifications();
336 catch(boost::program_options::error
& e
)
338 syslogFmt(boost::format("Error parsing command line options: %s") % e
.what());
340 catch(std::exception
& e
)
342 syslogFmt(boost::format("Fatal: %s") % e
.what());
344 catch(PDNSException
& e
)
346 syslogFmt(boost::format("Fatal: %s") % e
.reason
);
349 void daemonize(int null_fd
)
356 dup2(null_fd
,0); /* stdin */
357 dup2(null_fd
,1); /* stderr */
358 dup2(null_fd
,2); /* stderr */