5 #include "dnsparser.hh"
8 #include <boost/program_options.hpp>
10 #include <boost/format.hpp>
11 #include <boost/utility.hpp>
12 #include <boost/multi_index_container.hpp>
13 #include <boost/multi_index/ordered_index.hpp>
14 #include <boost/multi_index/key_extractors.hpp>
15 #include <boost/algorithm/string.hpp>
16 #include <sys/types.h>
20 #include "dnsrecords.hh"
24 #include "namespaces.hh"
25 using namespace ::boost::multi_index
;
26 #include "namespaces.hh"
28 namespace po
= boost::program_options
;
29 po::variables_map g_vm
;
33 SelectFDMultiplexer g_fdm
;
37 struct NotificationInFlight
42 uint16_t origID
, resentID
;
46 typedef map
<uint16_t, NotificationInFlight
> nifs_t
;
49 void syslogFmt(const boost::format
& fmt
)
51 cerr
<<"nproxy: "<<fmt
<<endl
;
52 syslog(LOG_WARNING
, "%s", str(fmt
).c_str());
55 void handleOutsideUDPPacket(int fd
, boost::any
&)
59 struct NotificationInFlight nif
;
62 socklen_t socklen
=sizeof(nif
.source
);
64 int res
=recvfrom(fd
, buffer
, sizeof(buffer
), 0, (struct sockaddr
*)&nif
.source
, &socklen
);
69 throw runtime_error("reading packet from remote: "+stringerror());
71 string
packet(buffer
, res
);
72 MOADNSParser
mdp(packet
);
73 nif
.domain
= mdp
.d_qname
;
74 nif
.origID
= mdp
.d_header
.id
;
77 if(mdp
.d_header
.opcode
== Opcode::Query
&& !mdp
.d_header
.qr
&& mdp
.d_answers
.empty() && mdp
.d_qname
.toString() == "pdns.nproxy." &&
78 (mdp
.d_qtype
== QType::TXT
|| mdp
.d_qtype
==QType::A
)) {
79 vector
<uint8_t> packet
;
80 DNSPacketWriter
pw(packet
, mdp
.d_qname
, mdp
.d_qtype
);
81 pw
.getHeader()->id
= mdp
.d_header
.id
;
82 pw
.getHeader()->rd
= mdp
.d_header
.rd
;
83 pw
.getHeader()->qr
= 1;
85 pw
.startRecord(mdp
.d_qname
, mdp
.d_qtype
);
86 if(mdp
.d_qtype
== QType::TXT
) {
87 TXTRecordContent
trc("\"OK\"");
90 else if(mdp
.d_qtype
== QType::A
) {
91 ARecordContent
arc("1.2.3.4");
96 if(sendto(fd
, &packet
[0], packet
.size(), 0, (struct sockaddr
*)&nif
.source
, socklen
) < 0) {
97 syslogFmt(boost::format("Unable to send health check response to external nameserver %s - %s") % nif
.source
.toStringWithPort() % stringerror());
102 if(mdp
.d_header
.opcode
!= Opcode::Notify
|| mdp
.d_qtype
!= QType::SOA
) {
103 syslogFmt(boost::format("Received non-notification packet for domain '%s' from external nameserver %s") % nif
.domain
.toString() % nif
.source
.toStringWithPort());
106 syslogFmt(boost::format("External notification received for domain '%s' from %s") % nif
.domain
.toString() % nif
.source
.toStringWithPort());
107 vector
<uint8_t> outpacket
;
108 DNSPacketWriter
pw(outpacket
, mdp
.d_qname
, mdp
.d_qtype
, 1, Opcode::Notify
);
110 static uint16_t s_idpool
;
111 pw
.getHeader()->id
= nif
.resentID
= s_idpool
++;
113 if(send(g_pdnssocket
, &outpacket
[0], outpacket
.size(), 0) < 0) {
114 throw runtime_error("Unable to send notify to PowerDNS: "+stringerror());
116 nif
.resentTime
=time(0);
117 g_nifs
[nif
.resentID
] = nif
;
120 catch(std::exception
&e
)
122 syslogFmt(boost::format("Error parsing packet from external nameserver: %s") % e
.what());
126 void handleInsideUDPPacket(int fd
, boost::any
&)
130 struct NotificationInFlight nif
;
132 socklen_t socklen
=sizeof(nif
.source
);
134 int len
=recvfrom(fd
, buffer
, sizeof(buffer
), 0, (struct sockaddr
*)&nif
.source
, &socklen
);
139 throw runtime_error("reading packet from remote: "+stringerror());
141 string
packet(buffer
, len
);
142 MOADNSParser
mdp(packet
);
144 // cerr<<"Inside notification response for: "<<mdp.d_qname<<endl;
146 if(!g_nifs
.count(mdp
.d_header
.id
)) {
147 syslogFmt(boost::format("Response from inner PowerDNS with unknown ID %1%") % (uint16_t)mdp
.d_header
.id
);
151 nif
=g_nifs
[mdp
.d_header
.id
];
153 if(nif
.domain
!= mdp
.d_qname
) {
154 syslogFmt(boost::format("Response from inner nameserver for different domain '%s' than original notification '%s'") % mdp
.d_qname
.toString() % nif
.domain
.toString());
156 if(sendto(nif
.origSocket
, buffer
, len
, 0, (sockaddr
*) &nif
.source
, nif
.source
.getSocklen()) < 0) {
157 syslogFmt(boost::format("Unable to send notification response to external nameserver %s - %s") % nif
.source
.toStringWithPort() % stringerror());
160 syslogFmt(boost::format("Sent notification response to external nameserver %s for domain '%s'") % nif
.source
.toStringWithPort() % nif
.domain
.toString());
162 g_nifs
.erase(mdp
.d_header
.id
);
165 catch(std::exception
&e
)
167 syslogFmt(boost::format("Error parsing packet from internal nameserver: %s") % e
.what());
170 void expireOldNotifications()
172 time_t limit
= time(0) - 10;
173 for(nifs_t::iterator iter
= g_nifs
.begin(); iter
!= g_nifs
.end(); ) {
174 if(iter
->second
.resentTime
< limit
) {
175 syslogFmt(boost::format("Notification for domain '%s' was sent to inner nameserver, but no response within 10 seconds") % iter
->second
.domain
.toString());
176 g_nifs
.erase(iter
++);
183 void daemonize(int null_fd
);
185 void usage(po::options_description
&desc
) {
186 cerr
<<"nproxy"<<endl
;
190 int main(int argc
, char** argv
)
194 openlog("nproxy", LOG_NDELAY
| LOG_PID
, LOG_DAEMON
);
196 po::options_description
desc("Allowed options");
198 ("help,h", "produce help message")
199 ("version", "print the version")
200 ("powerdns-address", po::value
<string
>(), "IP address of PowerDNS server")
201 ("chroot", po::value
<string
>(), "chroot to this directory for additional security")
202 ("setuid", po::value
<int>(), "setuid to this numerical user id")
203 ("setgid", po::value
<int>(), "setgid to this numerical user id")
204 ("origin-address", po::value
<string
>()->default_value("::"), "Source address for notifications to PowerDNS")
205 ("listen-address", po::value
<vector
<string
> >(), "IP addresses to listen on")
206 ("listen-port", po::value
<int>()->default_value(53), "Source port to listen on")
207 ("daemon,d", po::value
<bool>()->default_value(true), "operate in the background")
208 ("verbose,v", "be verbose");
210 po::store(po::command_line_parser(argc
, argv
).options(desc
).run(), g_vm
);
213 if (g_vm
.count("help")) {
218 if (g_vm
.count("version")) {
219 cerr
<< "nproxy " << VERSION
<< endl
;
223 if(!g_vm
.count("powerdns-address")) {
224 cerr
<<"Mandatory setting 'powerdns-address' unset:\n"<<endl
;
229 if(!g_vm
.count("verbose")) {
233 vector
<string
> addresses
;
234 if(g_vm
.count("listen-address"))
235 addresses
=g_vm
["listen-address"].as
<vector
<string
> >();
237 addresses
.push_back("::");
239 // create sockets to listen on
241 syslogFmt(boost::format("Starting up"));
242 for(vector
<string
>::const_iterator address
= addresses
.begin(); address
!= addresses
.end(); ++address
) {
243 ComboAddress
local(*address
, g_vm
["listen-port"].as
<int>());
244 int sock
= socket(local
.sin4
.sin_family
, SOCK_DGRAM
, 0);
246 throw runtime_error("Creating socket for incoming packets: "+stringerror());
248 if(::bind(sock
,(sockaddr
*) &local
, local
.getSocklen()) < 0)
249 throw runtime_error("Binding socket for incoming packets to '"+ local
.toStringWithPort()+"': "+stringerror());
251 g_fdm
.addReadFD(sock
, handleOutsideUDPPacket
); // add to fdmultiplexer for each socket
252 syslogFmt(boost::format("Listening for external notifications on address %s") % local
.toStringWithPort());
255 // create socket that talks to inner PowerDNS
256 ComboAddress
originAddress(g_vm
["origin-address"].as
<string
>(), 0);
257 g_pdnssocket
=socket(originAddress
.sin4
.sin_family
, SOCK_DGRAM
, 0);
259 throw runtime_error("Creating socket for packets to PowerDNS: "+stringerror());
262 if(::bind(g_pdnssocket
,(sockaddr
*) &originAddress
, originAddress
.getSocklen()) < 0)
263 throw runtime_error("Binding local address of inward socket to '"+ originAddress
.toStringWithPort()+"': "+stringerror());
266 ComboAddress
pdns(g_vm
["powerdns-address"].as
<string
>(), 53);
267 if(connect(g_pdnssocket
, (struct sockaddr
*) &pdns
, pdns
.getSocklen()) < 0)
268 throw runtime_error("Failed to connect PowerDNS socket to address "+pdns
.toStringWithPort()+": "+stringerror());
270 syslogFmt(boost::format("Sending notifications from %s to internal address %s") % originAddress
.toString() % pdns
.toStringWithPort());
272 g_fdm
.addReadFD(g_pdnssocket
, handleInsideUDPPacket
);
274 int null_fd
=open("/dev/null",O_RDWR
); /* open stdin */
276 throw runtime_error("Unable to open /dev/null: "+stringerror());
278 if(g_vm
.count("chroot")) {
279 if(chroot(g_vm
["chroot"].as
<string
>().c_str()) < 0 || chdir("/") < 0)
280 throw runtime_error("while chrooting to "+g_vm
["chroot"].as
<string
>());
281 syslogFmt(boost::format("Changed root to directory '%s'") % g_vm
["chroot"].as
<string
>());
284 if(g_vm
.count("setgid")) {
285 if(setgid(g_vm
["setgid"].as
<int>()) < 0)
286 throw runtime_error("while changing gid to "+std::to_string(g_vm
["setgid"].as
<int>()));
287 syslogFmt(boost::format("Changed gid to %d") % g_vm
["setgid"].as
<int>());
288 if(setgroups(0, NULL
) < 0)
289 throw runtime_error("while dropping supplementary groups");
292 if(g_vm
.count("setuid")) {
293 if(setuid(g_vm
["setuid"].as
<int>()) < 0)
294 throw runtime_error("while changing uid to "+std::to_string(g_vm
["setuid"].as
<int>()));
295 syslogFmt(boost::format("Changed uid to %d") % g_vm
["setuid"].as
<int>());
298 if(g_vm
["daemon"].as
<bool>()) {
299 syslogFmt(boost::format("Daemonizing"));
303 syslogFmt(boost::format("Program operational"));
309 gettimeofday(&now
, 0);
311 // check for notifications that have been outstanding for more than 10 seconds
312 expireOldNotifications();
315 catch(boost::program_options::error
& e
)
317 syslogFmt(boost::format("Error parsing command line options: %s") % e
.what());
319 catch(std::exception
& e
)
321 syslogFmt(boost::format("Fatal: %s") % e
.what());
323 catch(PDNSException
& e
)
325 syslogFmt(boost::format("Fatal: %s") % e
.reason
);
328 void daemonize(int null_fd
)
335 dup2(null_fd
,0); /* stdin */
336 dup2(null_fd
,1); /* stderr */
337 dup2(null_fd
,2); /* stderr */