]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/nproxy.cc
Include config.h only in .cc files
[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 string 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 == "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 % nif.source.toStringWithPort());
104 return;
105 }
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);
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(!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);
155 } else {
156 struct dnsheader dh;
157 memcpy(&dh, buffer, sizeof(dh));
158 dh.id = nif.origID;
159
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());
162 }
163 else
164 syslogFmt(boost::format("Sent notification response to external nameserver %s for domain '%s'") % nif.source.toStringWithPort() % nif.domain);
165 }
166 g_nifs.erase(mdp.d_header.id);
167
168 }
169 catch(std::exception &e)
170 {
171 syslogFmt(boost::format("Error parsing packet from internal nameserver: %s") % e.what());
172 }
173
174 void expireOldNotifications()
175 {
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++);
181 }
182 else
183 ++iter;
184 }
185 }
186
187 void daemonize(int null_fd);
188
189 int main(int argc, char** argv)
190 try
191 {
192 reportAllTypes();
193 openlog("nproxy", LOG_NDELAY | LOG_PID, LOG_DAEMON);
194
195 po::options_description desc("Allowed options");
196 desc.add_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");
207
208 po::store(po::command_line_parser(argc, argv).options(desc).run(), g_vm);
209 po::notify(g_vm);
210
211 if (g_vm.count("help")) {
212 cerr << desc << "\n";
213 return EXIT_SUCCESS;
214 }
215
216 if(!g_vm.count("powerdns-address")) {
217 cerr<<"Mandatory setting 'powerdns-address' unset:\n"<<desc<<endl;
218 return EXIT_FAILURE;
219 }
220
221 if(!g_vm.count("verbose")) {
222 g_verbose=true;
223 }
224
225 vector<string> addresses;
226 if(g_vm.count("listen-address"))
227 addresses=g_vm["listen-address"].as<vector<string> >();
228 else
229 addresses.push_back("::");
230
231 // create sockets to listen on
232
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);
237 if(sock < 0)
238 throw runtime_error("Creating socket for incoming packets: "+stringerror());
239
240 if(::bind(sock,(sockaddr*) &local, local.getSocklen()) < 0)
241 throw runtime_error("Binding socket for incoming packets to '"+ local.toStringWithPort()+"': "+stringerror());
242
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());
245 }
246
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);
250 if(g_pdnssocket < 0)
251 throw runtime_error("Creating socket for packets to PowerDNS: "+stringerror());
252
253
254 if(::bind(g_pdnssocket,(sockaddr*) &originAddress, originAddress.getSocklen()) < 0)
255 throw runtime_error("Binding local address of inward socket to '"+ originAddress.toStringWithPort()+"': "+stringerror());
256
257
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());
261
262 syslogFmt(boost::format("Sending notifications from %s to internal address %s") % originAddress.toString() % pdns.toStringWithPort());
263
264 g_fdm.addReadFD(g_pdnssocket, handleInsideUDPPacket);
265
266 int null_fd=open("/dev/null",O_RDWR); /* open stdin */
267 if(null_fd < 0)
268 throw runtime_error("Unable to open /dev/null: "+stringerror());
269
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>());
274 }
275
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");
282 }
283
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>());
288 }
289
290 if(g_vm["daemon"].as<bool>()) {
291 syslogFmt(boost::format("Daemonizing"));
292 daemonize(null_fd);
293 }
294 close(null_fd);
295 syslogFmt(boost::format("Program operational"));
296
297
298 // start loop
299 struct timeval now;
300 for(;;) {
301 gettimeofday(&now, 0);
302 g_fdm.run(&now);
303 // check for notifications that have been outstanding for more than 10 seconds
304 expireOldNotifications();
305 }
306 }
307 catch(boost::program_options::error& e)
308 {
309 syslogFmt(boost::format("Error parsing command line options: %s") % e.what());
310 }
311 catch(std::exception& e)
312 {
313 syslogFmt(boost::format("Fatal: %s") % e.what());
314 }
315 catch(PDNSException& e)
316 {
317 syslogFmt(boost::format("Fatal: %s") % e.reason);
318 }
319
320 void daemonize(int null_fd)
321 {
322 if(fork())
323 exit(0); // bye bye
324
325 setsid();
326
327 dup2(null_fd,0); /* stdin */
328 dup2(null_fd,1); /* stderr */
329 dup2(null_fd,2); /* stderr */
330 }