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