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
== "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
% nif
.source
.toStringWithPort());
106 syslogFmt(boost::format("External notification received for domain '%s' from %s") % nif
.domain
% 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(!pdns_iequals(nif
.domain
,mdp
.d_qname
)) {
154 syslogFmt(boost::format("Response from inner nameserver for different domain '%s' than original notification '%s'") % mdp
.d_qname
% nif
.domain
);
157 memcpy(&dh
, buffer
, sizeof(dh
));
160 if(sendto(nif
.origSocket
, buffer
, len
, 0, (sockaddr
*) &nif
.source
, nif
.source
.getSocklen()) < 0) {
161 syslogFmt(boost::format("Unable to send notification response to external nameserver %s - %s") % nif
.source
.toStringWithPort() % stringerror());
164 syslogFmt(boost::format("Sent notification response to external nameserver %s for domain '%s'") % nif
.source
.toStringWithPort() % nif
.domain
);
166 g_nifs
.erase(mdp
.d_header
.id
);
169 catch(std::exception
&e
)
171 syslogFmt(boost::format("Error parsing packet from internal nameserver: %s") % e
.what());
174 void expireOldNotifications()
176 time_t limit
= time(0) - 10;
177 for(nifs_t::iterator iter
= g_nifs
.begin(); iter
!= g_nifs
.end(); ) {
178 if(iter
->second
.resentTime
< limit
) {
179 syslogFmt(boost::format("Notification for domain '%s' was sent to inner nameserver, but no response within 10 seconds") % iter
->second
.domain
);
180 g_nifs
.erase(iter
++);
187 void daemonize(int null_fd
);
189 int main(int argc
, char** argv
)
193 openlog("nproxy", LOG_NDELAY
| LOG_PID
, LOG_DAEMON
);
195 po::options_description
desc("Allowed options");
197 ("help,h", "produce help message")
198 ("powerdns-address", po::value
<string
>(), "IP address of PowerDNS server")
199 ("chroot", po::value
<string
>(), "chroot to this directory for additional security")
200 ("setuid", po::value
<int>(), "setuid to this numerical user id")
201 ("setgid", po::value
<int>(), "setgid to this numerical user id")
202 ("origin-address", po::value
<string
>()->default_value("::"), "Source address for notifications to PowerDNS")
203 ("listen-address", po::value
<vector
<string
> >(), "IP addresses to listen on")
204 ("listen-port", po::value
<int>()->default_value(53), "Source port to listen on")
205 ("daemon,d", po::value
<bool>()->default_value(true), "operate in the background")
206 ("verbose,v", "be verbose");
208 po::store(po::command_line_parser(argc
, argv
).options(desc
).run(), g_vm
);
211 if (g_vm
.count("help")) {
212 cerr
<< desc
<< "\n";
216 if(!g_vm
.count("powerdns-address")) {
217 cerr
<<"Mandatory setting 'powerdns-address' unset:\n"<<desc
<<endl
;
221 if(!g_vm
.count("verbose")) {
225 vector
<string
> addresses
;
226 if(g_vm
.count("listen-address"))
227 addresses
=g_vm
["listen-address"].as
<vector
<string
> >();
229 addresses
.push_back("::");
231 // create sockets to listen on
233 syslogFmt(boost::format("Starting up"));
234 for(vector
<string
>::const_iterator address
= addresses
.begin(); address
!= addresses
.end(); ++address
) {
235 ComboAddress
local(*address
, g_vm
["listen-port"].as
<int>());
236 int sock
= socket(local
.sin4
.sin_family
, SOCK_DGRAM
, 0);
238 throw runtime_error("Creating socket for incoming packets: "+stringerror());
240 if(::bind(sock
,(sockaddr
*) &local
, local
.getSocklen()) < 0)
241 throw runtime_error("Binding socket for incoming packets to '"+ local
.toStringWithPort()+"': "+stringerror());
243 g_fdm
.addReadFD(sock
, handleOutsideUDPPacket
); // add to fdmultiplexer for each socket
244 syslogFmt(boost::format("Listening for external notifications on address %s") % local
.toStringWithPort());
247 // create socket that talks to inner PowerDNS
248 ComboAddress
originAddress(g_vm
["origin-address"].as
<string
>(), 0);
249 g_pdnssocket
=socket(originAddress
.sin4
.sin_family
, SOCK_DGRAM
, 0);
251 throw runtime_error("Creating socket for packets to PowerDNS: "+stringerror());
254 if(::bind(g_pdnssocket
,(sockaddr
*) &originAddress
, originAddress
.getSocklen()) < 0)
255 throw runtime_error("Binding local address of inward socket to '"+ originAddress
.toStringWithPort()+"': "+stringerror());
258 ComboAddress
pdns(g_vm
["powerdns-address"].as
<string
>(), 53);
259 if(connect(g_pdnssocket
, (struct sockaddr
*) &pdns
, pdns
.getSocklen()) < 0)
260 throw runtime_error("Failed to connect PowerDNS socket to address "+pdns
.toStringWithPort()+": "+stringerror());
262 syslogFmt(boost::format("Sending notifications from %s to internal address %s") % originAddress
.toString() % pdns
.toStringWithPort());
264 g_fdm
.addReadFD(g_pdnssocket
, handleInsideUDPPacket
);
266 int null_fd
=open("/dev/null",O_RDWR
); /* open stdin */
268 throw runtime_error("Unable to open /dev/null: "+stringerror());
270 if(g_vm
.count("chroot")) {
271 if(chroot(g_vm
["chroot"].as
<string
>().c_str()) < 0 || chdir("/") < 0)
272 throw runtime_error("while chrooting to "+g_vm
["chroot"].as
<string
>());
273 syslogFmt(boost::format("Changed root to directory '%s'") % g_vm
["chroot"].as
<string
>());
276 if(g_vm
.count("setgid")) {
277 if(setgid(g_vm
["setgid"].as
<int>()) < 0)
278 throw runtime_error("while changing gid to "+boost::lexical_cast
<std::string
>(g_vm
["setgid"].as
<int>()));
279 syslogFmt(boost::format("Changed gid to %d") % g_vm
["setgid"].as
<int>());
280 if(setgroups(0, NULL
) < 0)
281 throw runtime_error("while dropping supplementary groups");
284 if(g_vm
.count("setuid")) {
285 if(setuid(g_vm
["setuid"].as
<int>()) < 0)
286 throw runtime_error("while changing uid to "+boost::lexical_cast
<std::string
>(g_vm
["setuid"].as
<int>()));
287 syslogFmt(boost::format("Changed uid to %d") % g_vm
["setuid"].as
<int>());
290 if(g_vm
["daemon"].as
<bool>()) {
291 syslogFmt(boost::format("Daemonizing"));
295 syslogFmt(boost::format("Program operational"));
301 gettimeofday(&now
, 0);
303 // check for notifications that have been outstanding for more than 10 seconds
304 expireOldNotifications();
307 catch(boost::program_options::error
& e
)
309 syslogFmt(boost::format("Error parsing command line options: %s") % e
.what());
311 catch(std::exception
& e
)
313 syslogFmt(boost::format("Fatal: %s") % e
.what());
315 catch(PDNSException
& e
)
317 syslogFmt(boost::format("Fatal: %s") % e
.reason
);
320 void daemonize(int null_fd
)
327 dup2(null_fd
,0); /* stdin */
328 dup2(null_fd
,1); /* stderr */
329 dup2(null_fd
,2); /* stderr */