]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/nproxy.cc
Merge pull request #9099 from PowerDNS/omoerbeek-patch-1
[thirdparty/pdns.git] / pdns / nproxy.cc
1 /*
2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
4 *
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.
8 *
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.
12 *
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.
17 *
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.
21 */
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25 #include <bitset>
26 #include "dnsparser.hh"
27 #include "iputils.hh"
28 #include <boost/program_options.hpp>
29
30 #include <boost/format.hpp>
31 #include <boost/utility.hpp>
32 #include <boost/multi_index_container.hpp>
33 #include <boost/multi_index/ordered_index.hpp>
34 #include <boost/multi_index/key_extractors.hpp>
35 #include <boost/algorithm/string.hpp>
36 #include <sys/types.h>
37 #include <sys/stat.h>
38 #include <fcntl.h>
39 #include <grp.h>
40 #include <unistd.h>
41 #include "dnsrecords.hh"
42 #include "mplexer.hh"
43 #include "statbag.hh"
44
45 #include "namespaces.hh"
46 using namespace ::boost::multi_index;
47 #include "namespaces.hh"
48
49 namespace po = boost::program_options;
50 po::variables_map g_vm;
51
52 StatBag S;
53
54 FDMultiplexer* g_fdm;
55 int g_pdnssocket;
56 bool g_verbose;
57
58 struct NotificationInFlight
59 {
60 ComboAddress source;
61 time_t resentTime;
62 DNSName domain;
63 uint16_t origID, resentID;
64 int origSocket;
65 };
66
67 typedef map<uint16_t, NotificationInFlight> nifs_t;
68 nifs_t g_nifs;
69
70 static void syslogFmt(const boost::format& fmt)
71 {
72 cerr<<"nproxy: "<<fmt<<endl;
73 syslog(LOG_WARNING, "%s", str(fmt).c_str());
74 }
75
76 static void handleOutsideUDPPacket(int fd, boost::any&)
77 try
78 {
79 char buffer[1500];
80 struct NotificationInFlight nif;
81 /* make sure we report enough room for IPv6 */
82 nif.source.sin4.sin_family = AF_INET6;
83 nif.origSocket = fd;
84
85 socklen_t socklen=nif.source.getSocklen();
86
87 int res=recvfrom(fd, buffer, sizeof(buffer), 0, (struct sockaddr*)&nif.source, &socklen);
88 if(!res)
89 return;
90
91 if(res < 0)
92 throw runtime_error("reading packet from remote: "+stringerror());
93
94 MOADNSParser mdp(true, string(buffer,res));
95 nif.domain = mdp.d_qname;
96 nif.origID = mdp.d_header.id;
97
98
99 if(mdp.d_header.opcode == Opcode::Query && !mdp.d_header.qr && mdp.d_answers.empty() && mdp.d_qname.toString() == "pdns.nproxy." &&
100 (mdp.d_qtype == QType::TXT || mdp.d_qtype ==QType::A)) {
101 vector<uint8_t> packet;
102 DNSPacketWriter pw(packet, mdp.d_qname, mdp.d_qtype);
103 pw.getHeader()->id = mdp.d_header.id;
104 pw.getHeader()->rd = mdp.d_header.rd;
105 pw.getHeader()->qr = 1;
106
107 pw.startRecord(mdp.d_qname, mdp.d_qtype);
108 if(mdp.d_qtype == QType::TXT) {
109 TXTRecordContent trc("\"OK\"");
110 trc.toPacket(pw);
111 }
112 else if(mdp.d_qtype == QType::A) {
113 ARecordContent arc("1.2.3.4");
114 arc.toPacket(pw);
115 }
116 pw.commit();
117
118 if(sendto(fd, &packet[0], packet.size(), 0, (struct sockaddr*)&nif.source, socklen) < 0) {
119 syslogFmt(boost::format("Unable to send health check response to external nameserver %s - %s") % nif.source.toStringWithPort() % stringerror());
120 }
121 return;
122 }
123
124 if(mdp.d_header.opcode != Opcode::Notify || mdp.d_qtype != QType::SOA) {
125 syslogFmt(boost::format("Received non-notification packet for domain '%s' from external nameserver %s") % nif.domain.toString() % nif.source.toStringWithPort());
126 return;
127 }
128 syslogFmt(boost::format("External notification received for domain '%s' from %s") % nif.domain.toString() % nif.source.toStringWithPort());
129 vector<uint8_t> outpacket;
130 DNSPacketWriter pw(outpacket, mdp.d_qname, mdp.d_qtype, 1, Opcode::Notify);
131
132 static uint16_t s_idpool;
133 pw.getHeader()->id = nif.resentID = s_idpool++;
134
135 if(send(g_pdnssocket, &outpacket[0], outpacket.size(), 0) < 0) {
136 throw runtime_error("Unable to send notify to PowerDNS: "+stringerror());
137 }
138 nif.resentTime=time(0);
139 g_nifs[nif.resentID] = nif;
140
141 }
142 catch(std::exception &e)
143 {
144 syslogFmt(boost::format("Error parsing packet from external nameserver: %s") % e.what());
145 }
146
147
148 static void handleInsideUDPPacket(int fd, boost::any&)
149 try
150 {
151 char buffer[1500];
152 struct NotificationInFlight nif;
153 /* make sure we report enough room for IPv6 */
154 nif.source.sin4.sin_family = AF_INET6;
155
156 socklen_t socklen=nif.source.getSocklen();
157
158 int len=recvfrom(fd, buffer, sizeof(buffer), 0, (struct sockaddr*)&nif.source, &socklen);
159 if(!len)
160 return;
161
162 if(len < 0)
163 throw runtime_error("reading packet from remote: "+stringerror());
164
165 string packet(buffer, len);
166 MOADNSParser mdp(false, packet);
167
168 // cerr<<"Inside notification response for: "<<mdp.d_qname<<endl;
169
170 if(!g_nifs.count(mdp.d_header.id)) {
171 syslogFmt(boost::format("Response from inner PowerDNS with unknown ID %1%") % (uint16_t)mdp.d_header.id);
172 return;
173 }
174
175 nif=g_nifs[mdp.d_header.id];
176
177 if(nif.domain != mdp.d_qname) {
178 syslogFmt(boost::format("Response from inner nameserver for different domain '%s' than original notification '%s'") % mdp.d_qname.toString() % nif.domain.toString());
179 } else {
180 if(sendto(nif.origSocket, buffer, len, 0, (sockaddr*) &nif.source, nif.source.getSocklen()) < 0) {
181 syslogFmt(boost::format("Unable to send notification response to external nameserver %s - %s") % nif.source.toStringWithPort() % stringerror());
182 }
183 else
184 syslogFmt(boost::format("Sent notification response to external nameserver %s for domain '%s'") % nif.source.toStringWithPort() % nif.domain.toString());
185 }
186 g_nifs.erase(mdp.d_header.id);
187
188 }
189 catch(std::exception &e)
190 {
191 syslogFmt(boost::format("Error parsing packet from internal nameserver: %s") % e.what());
192 }
193
194 static void expireOldNotifications()
195 {
196 time_t limit = time(0) - 10;
197 for(nifs_t::iterator iter = g_nifs.begin(); iter != g_nifs.end(); ) {
198 if(iter->second.resentTime < limit) {
199 syslogFmt(boost::format("Notification for domain '%s' was sent to inner nameserver, but no response within 10 seconds") % iter->second.domain.toString());
200 g_nifs.erase(iter++);
201 }
202 else
203 ++iter;
204 }
205 }
206
207 static void daemonize(int null_fd)
208 {
209 if(fork())
210 exit(0); // bye bye
211
212 setsid();
213
214 dup2(null_fd,0); /* stdin */
215 dup2(null_fd,1); /* stderr */
216 dup2(null_fd,2); /* stderr */
217 }
218
219 static void usage(po::options_description &desc) {
220 cerr<<"nproxy"<<endl;
221 cerr<<desc<<endl;
222 }
223
224 int main(int argc, char** argv)
225 try
226 {
227 reportAllTypes();
228 openlog("nproxy", LOG_NDELAY | LOG_PID, LOG_DAEMON);
229
230 g_fdm = FDMultiplexer::getMultiplexerSilent();
231 if(!g_fdm) {
232 throw std::runtime_error("Could not enable a multiplexer");
233 }
234
235 po::options_description desc("Allowed options");
236 desc.add_options()
237 ("help,h", "produce help message")
238 ("version", "print the version")
239 ("powerdns-address", po::value<string>(), "IP address of PowerDNS server")
240 ("chroot", po::value<string>(), "chroot to this directory for additional security")
241 ("setuid", po::value<int>(), "setuid to this numerical user id")
242 ("setgid", po::value<int>(), "setgid to this numerical user id")
243 ("origin-address", po::value<string>()->default_value("::"), "Source address for notifications to PowerDNS")
244 ("listen-address", po::value<vector<string> >(), "IP addresses to listen on")
245 ("listen-port", po::value<int>()->default_value(53), "Source port to listen on")
246 ("daemon,d", po::value<bool>()->default_value(true), "operate in the background")
247 ("verbose,v", "be verbose");
248
249 po::store(po::command_line_parser(argc, argv).options(desc).run(), g_vm);
250 po::notify(g_vm);
251
252 if (g_vm.count("help")) {
253 usage(desc);
254 return EXIT_SUCCESS;
255 }
256
257 if (g_vm.count("version")) {
258 cerr << "nproxy " << VERSION << endl;
259 return EXIT_SUCCESS;
260 }
261
262 if(!g_vm.count("powerdns-address")) {
263 cerr<<"Mandatory setting 'powerdns-address' unset:\n"<<endl;
264 usage(desc);
265 return EXIT_FAILURE;
266 }
267
268 if(!g_vm.count("verbose")) {
269 g_verbose=true;
270 }
271
272 vector<string> addresses;
273 if(g_vm.count("listen-address"))
274 addresses=g_vm["listen-address"].as<vector<string> >();
275 else
276 addresses.push_back("::");
277
278 // create sockets to listen on
279
280 syslogFmt(boost::format("Starting up"));
281 for(vector<string>::const_iterator address = addresses.begin(); address != addresses.end(); ++address) {
282 ComboAddress local(*address, g_vm["listen-port"].as<int>());
283 int sock = socket(local.sin4.sin_family, SOCK_DGRAM, 0);
284 if(sock < 0)
285 throw runtime_error("Creating socket for incoming packets: "+stringerror());
286
287 if(::bind(sock,(sockaddr*) &local, local.getSocklen()) < 0)
288 throw runtime_error("Binding socket for incoming packets to '"+ local.toStringWithPort()+"': "+stringerror());
289
290 g_fdm->addReadFD(sock, handleOutsideUDPPacket); // add to fdmultiplexer for each socket
291 syslogFmt(boost::format("Listening for external notifications on address %s") % local.toStringWithPort());
292 }
293
294 // create socket that talks to inner PowerDNS
295 ComboAddress originAddress(g_vm["origin-address"].as<string>(), 0);
296 g_pdnssocket=socket(originAddress.sin4.sin_family, SOCK_DGRAM, 0);
297 if(g_pdnssocket < 0)
298 throw runtime_error("Creating socket for packets to PowerDNS: "+stringerror());
299
300
301 if(::bind(g_pdnssocket,(sockaddr*) &originAddress, originAddress.getSocklen()) < 0)
302 throw runtime_error("Binding local address of inward socket to '"+ originAddress.toStringWithPort()+"': "+stringerror());
303
304
305 ComboAddress pdns(g_vm["powerdns-address"].as<string>(), 53);
306 if(connect(g_pdnssocket, (struct sockaddr*) &pdns, pdns.getSocklen()) < 0)
307 throw runtime_error("Failed to connect PowerDNS socket to address "+pdns.toStringWithPort()+": "+stringerror());
308
309 syslogFmt(boost::format("Sending notifications from %s to internal address %s") % originAddress.toString() % pdns.toStringWithPort());
310
311 g_fdm->addReadFD(g_pdnssocket, handleInsideUDPPacket);
312
313 int null_fd=open("/dev/null",O_RDWR); /* open stdin */
314 if(null_fd < 0)
315 throw runtime_error("Unable to open /dev/null: "+stringerror());
316
317 if(g_vm.count("chroot")) {
318 if(chroot(g_vm["chroot"].as<string>().c_str()) < 0 || chdir("/") < 0)
319 throw runtime_error("while chrooting to "+g_vm["chroot"].as<string>());
320 syslogFmt(boost::format("Changed root to directory '%s'") % g_vm["chroot"].as<string>());
321 }
322
323 if(g_vm.count("setgid")) {
324 if(setgid(g_vm["setgid"].as<int>()) < 0)
325 throw runtime_error("while changing gid to "+std::to_string(g_vm["setgid"].as<int>()));
326 syslogFmt(boost::format("Changed gid to %d") % g_vm["setgid"].as<int>());
327 if(setgroups(0, NULL) < 0)
328 throw runtime_error("while dropping supplementary groups");
329 }
330
331 if(g_vm.count("setuid")) {
332 if(setuid(g_vm["setuid"].as<int>()) < 0)
333 throw runtime_error("while changing uid to "+std::to_string(g_vm["setuid"].as<int>()));
334 syslogFmt(boost::format("Changed uid to %d") % g_vm["setuid"].as<int>());
335 }
336
337 if(g_vm["daemon"].as<bool>()) {
338 syslogFmt(boost::format("Daemonizing"));
339 daemonize(null_fd);
340 }
341 close(null_fd);
342 syslogFmt(boost::format("Program operational"));
343
344
345 // start loop
346 struct timeval now;
347 for(;;) {
348 gettimeofday(&now, 0);
349 g_fdm->run(&now);
350 // check for notifications that have been outstanding for more than 10 seconds
351 expireOldNotifications();
352 }
353 }
354 catch(boost::program_options::error& e)
355 {
356 syslogFmt(boost::format("Error parsing command line options: %s") % e.what());
357 }
358 catch(std::exception& e)
359 {
360 syslogFmt(boost::format("Fatal: %s") % e.what());
361 }
362 catch(PDNSException& e)
363 {
364 syslogFmt(boost::format("Fatal: %s") % e.reason);
365 }