]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/nproxy.cc
96d8eeb3fea99edb965e39ec99e875049a780854
[thirdparty/pdns.git] / pdns / nproxy.cc
1 #ifdef HAVE_CONFIG_H
2 #include "config.h"
3 #endif
4 #include <bitset>
5 #include "dnsparser.hh"
6 #include "iputils.hh"
7 #undef L
8 #include <boost/program_options.hpp>
9
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>
17 #include <sys/stat.h>
18 #include <fcntl.h>
19 #include <grp.h>
20 #include "dnsrecords.hh"
21 #include "mplexer.hh"
22 #include "statbag.hh"
23
24 #include "namespaces.hh"
25 using namespace ::boost::multi_index;
26 #include "namespaces.hh"
27
28 namespace po = boost::program_options;
29 po::variables_map g_vm;
30
31 StatBag S;
32
33 SelectFDMultiplexer g_fdm;
34 int g_pdnssocket;
35 bool g_verbose;
36
37 struct NotificationInFlight
38 {
39 ComboAddress source;
40 time_t resentTime;
41 DNSName domain;
42 uint16_t origID, resentID;
43 int origSocket;
44 };
45
46 typedef map<uint16_t, NotificationInFlight> nifs_t;
47 nifs_t g_nifs;
48
49 void syslogFmt(const boost::format& fmt)
50 {
51 cerr<<"nproxy: "<<fmt<<endl;
52 syslog(LOG_WARNING, "%s", str(fmt).c_str());
53 }
54
55 void handleOutsideUDPPacket(int fd, boost::any&)
56 try
57 {
58 char buffer[1500];
59 struct NotificationInFlight nif;
60 nif.origSocket = fd;
61
62 socklen_t socklen=sizeof(nif.source);
63
64 int res=recvfrom(fd, buffer, sizeof(buffer), 0, (struct sockaddr*)&nif.source, &socklen);
65 if(!res)
66 return;
67
68 if(res < 0)
69 throw runtime_error("reading packet from remote: "+stringerror());
70
71 string packet(buffer, res);
72 MOADNSParser mdp(packet);
73 nif.domain = mdp.d_qname;
74 nif.origID = mdp.d_header.id;
75
76
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;
84
85 pw.startRecord(mdp.d_qname, mdp.d_qtype);
86 if(mdp.d_qtype == QType::TXT) {
87 TXTRecordContent trc("\"OK\"");
88 trc.toPacket(pw);
89 }
90 else if(mdp.d_qtype == QType::A) {
91 ARecordContent arc("1.2.3.4");
92 arc.toPacket(pw);
93 }
94 pw.commit();
95
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());
98 }
99 return;
100 }
101
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());
104 return;
105 }
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);
109
110 static uint16_t s_idpool;
111 pw.getHeader()->id = nif.resentID = s_idpool++;
112
113 if(send(g_pdnssocket, &outpacket[0], outpacket.size(), 0) < 0) {
114 throw runtime_error("Unable to send notify to PowerDNS: "+stringerror());
115 }
116 nif.resentTime=time(0);
117 g_nifs[nif.resentID] = nif;
118
119 }
120 catch(std::exception &e)
121 {
122 syslogFmt(boost::format("Error parsing packet from external nameserver: %s") % e.what());
123 }
124
125
126 void handleInsideUDPPacket(int fd, boost::any&)
127 try
128 {
129 char buffer[1500];
130 struct NotificationInFlight nif;
131
132 socklen_t socklen=sizeof(nif.source);
133
134 int len=recvfrom(fd, buffer, sizeof(buffer), 0, (struct sockaddr*)&nif.source, &socklen);
135 if(!len)
136 return;
137
138 if(len < 0)
139 throw runtime_error("reading packet from remote: "+stringerror());
140
141 string packet(buffer, len);
142 MOADNSParser mdp(packet);
143
144 // cerr<<"Inside notification response for: "<<mdp.d_qname<<endl;
145
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);
148 return;
149 }
150
151 nif=g_nifs[mdp.d_header.id];
152
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());
155 } else {
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());
158 }
159 else
160 syslogFmt(boost::format("Sent notification response to external nameserver %s for domain '%s'") % nif.source.toStringWithPort() % nif.domain.toString());
161 }
162 g_nifs.erase(mdp.d_header.id);
163
164 }
165 catch(std::exception &e)
166 {
167 syslogFmt(boost::format("Error parsing packet from internal nameserver: %s") % e.what());
168 }
169
170 void expireOldNotifications()
171 {
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++);
177 }
178 else
179 ++iter;
180 }
181 }
182
183 void daemonize(int null_fd);
184
185 void usage(po::options_description &desc) {
186 cerr<<"nproxy"<<endl;
187 cerr<<desc<<endl;
188 }
189
190 int main(int argc, char** argv)
191 try
192 {
193 reportAllTypes();
194 openlog("nproxy", LOG_NDELAY | LOG_PID, LOG_DAEMON);
195
196 po::options_description desc("Allowed options");
197 desc.add_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");
209
210 po::store(po::command_line_parser(argc, argv).options(desc).run(), g_vm);
211 po::notify(g_vm);
212
213 if (g_vm.count("help")) {
214 usage(desc);
215 return EXIT_SUCCESS;
216 }
217
218 if (g_vm.count("version")) {
219 cerr << "nproxy " << VERSION << endl;
220 return EXIT_SUCCESS;
221 }
222
223 if(!g_vm.count("powerdns-address")) {
224 cerr<<"Mandatory setting 'powerdns-address' unset:\n"<<endl;
225 usage(desc);
226 return EXIT_FAILURE;
227 }
228
229 if(!g_vm.count("verbose")) {
230 g_verbose=true;
231 }
232
233 vector<string> addresses;
234 if(g_vm.count("listen-address"))
235 addresses=g_vm["listen-address"].as<vector<string> >();
236 else
237 addresses.push_back("::");
238
239 // create sockets to listen on
240
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);
245 if(sock < 0)
246 throw runtime_error("Creating socket for incoming packets: "+stringerror());
247
248 if(::bind(sock,(sockaddr*) &local, local.getSocklen()) < 0)
249 throw runtime_error("Binding socket for incoming packets to '"+ local.toStringWithPort()+"': "+stringerror());
250
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());
253 }
254
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);
258 if(g_pdnssocket < 0)
259 throw runtime_error("Creating socket for packets to PowerDNS: "+stringerror());
260
261
262 if(::bind(g_pdnssocket,(sockaddr*) &originAddress, originAddress.getSocklen()) < 0)
263 throw runtime_error("Binding local address of inward socket to '"+ originAddress.toStringWithPort()+"': "+stringerror());
264
265
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());
269
270 syslogFmt(boost::format("Sending notifications from %s to internal address %s") % originAddress.toString() % pdns.toStringWithPort());
271
272 g_fdm.addReadFD(g_pdnssocket, handleInsideUDPPacket);
273
274 int null_fd=open("/dev/null",O_RDWR); /* open stdin */
275 if(null_fd < 0)
276 throw runtime_error("Unable to open /dev/null: "+stringerror());
277
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>());
282 }
283
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");
290 }
291
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>());
296 }
297
298 if(g_vm["daemon"].as<bool>()) {
299 syslogFmt(boost::format("Daemonizing"));
300 daemonize(null_fd);
301 }
302 close(null_fd);
303 syslogFmt(boost::format("Program operational"));
304
305
306 // start loop
307 struct timeval now;
308 for(;;) {
309 gettimeofday(&now, 0);
310 g_fdm.run(&now);
311 // check for notifications that have been outstanding for more than 10 seconds
312 expireOldNotifications();
313 }
314 }
315 catch(boost::program_options::error& e)
316 {
317 syslogFmt(boost::format("Error parsing command line options: %s") % e.what());
318 }
319 catch(std::exception& e)
320 {
321 syslogFmt(boost::format("Fatal: %s") % e.what());
322 }
323 catch(PDNSException& e)
324 {
325 syslogFmt(boost::format("Fatal: %s") % e.reason);
326 }
327
328 void daemonize(int null_fd)
329 {
330 if(fork())
331 exit(0); // bye bye
332
333 setsid();
334
335 dup2(null_fd,0); /* stdin */
336 dup2(null_fd,1); /* stderr */
337 dup2(null_fd,2); /* stderr */
338 }