]>
Commit | Line | Data |
---|---|---|
a2670f5e 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 | */ | |
22 | #ifdef HAVE_CONFIG_H | |
23 | #include "config.h" | |
24 | #endif | |
a2670f5e | 25 | #include <boost/program_options.hpp> |
352df81e | 26 | #include <arpa/inet.h> |
6f8cb809 PL |
27 | #include <sys/types.h> |
28 | #include <grp.h> | |
29 | #include <pwd.h> | |
38e6a9ee | 30 | #include <sys/stat.h> |
772cdea3 | 31 | #include <mutex> |
33494729 | 32 | #include <thread> |
37499e32 | 33 | #include <dirent.h> |
a2969595 PL |
34 | #include <queue> |
35 | #include <condition_variable> | |
a2670f5e | 36 | #include "ixfr.hh" |
4db8fd44 | 37 | #include "ixfrutils.hh" |
38e6a9ee PL |
38 | #include "resolver.hh" |
39 | #include "dns_random.hh" | |
aff1f1fd PL |
40 | #include "sstuff.hh" |
41 | #include "mplexer.hh" | |
0eb03ead | 42 | #include "misc.hh" |
16f495e0 | 43 | #include "iputils.hh" |
1361013e | 44 | #include "logger.hh" |
f2d45260 | 45 | #include <yaml-cpp/yaml.h> |
772cdea3 | 46 | |
aeaf4c15 PL |
47 | /* BEGIN Needed because of deeper dependencies */ |
48 | #include "arguments.hh" | |
49 | #include "statbag.hh" | |
a2670f5e PL |
50 | StatBag S; |
51 | ||
52 | ArgvMap &arg() | |
53 | { | |
54 | static ArgvMap theArg; | |
55 | return theArg; | |
56 | } | |
aeaf4c15 PL |
57 | /* END Needed because of deeper dependencies */ |
58 | ||
f2d45260 PL |
59 | // Allows reading/writing ComboAddresses and DNSNames in YAML-cpp |
60 | namespace YAML { | |
61 | template<> | |
62 | struct convert<ComboAddress> { | |
63 | static Node encode(const ComboAddress& rhs) { | |
64 | return Node(rhs.toStringWithPort()); | |
65 | } | |
66 | static bool decode(const Node& node, ComboAddress& rhs) { | |
67 | if (!node.IsScalar()) { | |
68 | return false; | |
69 | } | |
70 | try { | |
4f2b4c54 | 71 | rhs = ComboAddress(node.as<string>(), 53); |
f2d45260 PL |
72 | return true; |
73 | } catch(const runtime_error &e) { | |
74 | return false; | |
75 | } catch (const PDNSException &e) { | |
76 | return false; | |
77 | } | |
78 | } | |
79 | }; | |
80 | ||
81 | template<> | |
82 | struct convert<DNSName> { | |
83 | static Node encode(const DNSName& rhs) { | |
84 | return Node(rhs.toStringRootDot()); | |
85 | } | |
86 | static bool decode(const Node& node, DNSName& rhs) { | |
87 | if (!node.IsScalar()) { | |
88 | return false; | |
89 | } | |
90 | try { | |
91 | rhs = DNSName(node.as<string>()); | |
92 | return true; | |
93 | } catch(const runtime_error &e) { | |
94 | return false; | |
95 | } catch (const PDNSException &e) { | |
96 | return false; | |
97 | } | |
98 | } | |
99 | }; | |
100 | } // namespace YAML | |
101 | ||
023d807e RG |
102 | struct ixfrdiff_t { |
103 | shared_ptr<SOARecordContent> oldSOA; | |
104 | shared_ptr<SOARecordContent> newSOA; | |
105 | vector<DNSRecord> removals; | |
106 | vector<DNSRecord> additions; | |
107 | }; | |
108 | ||
109 | struct ixfrinfo_t { | |
110 | shared_ptr<SOARecordContent> soa; // The SOA of the latestAXFR | |
111 | records_t latestAXFR; // The most recent AXFR | |
112 | vector<std::shared_ptr<ixfrdiff_t>> ixfrDiffs; | |
113 | }; | |
114 | ||
f2d45260 PL |
115 | // Why a struct? This way we can add more options to a domain in the future |
116 | struct ixfrdistdomain_t { | |
117 | set<ComboAddress> masters; // A set so we can do multiple master addresses in the future | |
118 | }; | |
aff1f1fd | 119 | |
f2d45260 | 120 | // This contains the configuration for each domain |
971e5911 | 121 | static map<DNSName, ixfrdistdomain_t> g_domainConfigs; |
aff1f1fd | 122 | |
e2cddb03 | 123 | // Map domains and their data |
971e5911 RG |
124 | static std::map<DNSName, std::shared_ptr<ixfrinfo_t>> g_soas; |
125 | static std::mutex g_soas_mutex; | |
772cdea3 | 126 | |
a2969595 | 127 | // Condition variable for TCP handling |
971e5911 RG |
128 | static std::condition_variable g_tcpHandlerCV; |
129 | static std::queue<pair<int, ComboAddress>> g_tcpRequestFDs; | |
130 | static std::mutex g_tcpRequestFDsMutex; | |
a2969595 | 131 | |
a2670f5e | 132 | namespace po = boost::program_options; |
772cdea3 | 133 | |
971e5911 | 134 | static bool g_exiting = false; |
bf676f2f | 135 | |
971e5911 RG |
136 | static NetmaskGroup g_acl; |
137 | static bool g_compress = false; | |
6329cf4d | 138 | |
ba779b12 | 139 | static void handleSignal(int signum) { |
1361013e | 140 | g_log<<Logger::Notice<<"Got "<<strsignal(signum)<<" signal"; |
37499e32 | 141 | if (g_exiting) { |
1361013e | 142 | g_log<<Logger::Notice<<", this is the second time we were asked to stop, forcefully exiting"<<endl; |
37499e32 PL |
143 | exit(EXIT_FAILURE); |
144 | } | |
b23d9f7a | 145 | g_log<<Logger::Notice<<", stopping, this may take a few second due to in-progress transfers and cleanup. Send this signal again to forcefully stop"<<endl; |
bf676f2f PL |
146 | g_exiting = true; |
147 | } | |
148 | ||
ba779b12 | 149 | static void usage(po::options_description &desc) { |
f2d45260 | 150 | cerr << "Usage: ixfrdist [OPTION]..."<<endl; |
a2670f5e PL |
151 | cerr << desc << "\n"; |
152 | } | |
153 | ||
37499e32 | 154 | // The compiler does not like using rfc1982LessThan in std::sort directly |
ba779b12 | 155 | static bool sortSOA(uint32_t i, uint32_t j) { |
37499e32 PL |
156 | return rfc1982LessThan(i, j); |
157 | } | |
158 | ||
ba779b12 | 159 | static void cleanUpDomain(const DNSName& domain, const uint16_t& keep, const string& workdir) { |
592f52eb | 160 | string dir = workdir + "/" + domain.toString(); |
37499e32 PL |
161 | DIR *dp; |
162 | dp = opendir(dir.c_str()); | |
163 | if (dp == nullptr) { | |
164 | return; | |
165 | } | |
166 | vector<uint32_t> zoneVersions; | |
167 | struct dirent *d; | |
168 | while ((d = readdir(dp)) != nullptr) { | |
169 | if(!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) { | |
170 | continue; | |
171 | } | |
172 | zoneVersions.push_back(std::stoi(d->d_name)); | |
173 | } | |
174 | closedir(dp); | |
592f52eb PL |
175 | g_log<<Logger::Info<<"Found "<<zoneVersions.size()<<" versions of "<<domain<<", asked to keep "<<keep<<", "; |
176 | if (zoneVersions.size() <= keep) { | |
1361013e | 177 | g_log<<Logger::Info<<"not cleaning up"<<endl; |
37499e32 PL |
178 | return; |
179 | } | |
592f52eb | 180 | g_log<<Logger::Info<<"cleaning up the oldest "<<zoneVersions.size() - keep<<endl; |
37499e32 PL |
181 | |
182 | // Sort the versions | |
183 | std::sort(zoneVersions.begin(), zoneVersions.end(), sortSOA); | |
184 | ||
185 | // And delete all the old ones | |
186 | { | |
187 | // Lock to ensure no one reads this. | |
188 | std::lock_guard<std::mutex> guard(g_soas_mutex); | |
592f52eb | 189 | for (auto iter = zoneVersions.cbegin(); iter != zoneVersions.cend() - keep; ++iter) { |
37499e32 | 190 | string fname = dir + "/" + std::to_string(*iter); |
1361013e | 191 | g_log<<Logger::Debug<<"Removing "<<fname<<endl; |
37499e32 PL |
192 | unlink(fname.c_str()); |
193 | } | |
194 | } | |
195 | } | |
196 | ||
ddd3fe8d | 197 | static shared_ptr<SOARecordContent> getSOAFromRecords(const records_t& records) { |
e2cddb03 PL |
198 | for (const auto& dnsrecord : records) { |
199 | if (dnsrecord.d_type == QType::SOA) { | |
200 | auto soa = getRR<SOARecordContent>(dnsrecord); | |
201 | if (soa == nullptr) { | |
202 | throw PDNSException("Unable to determine SOARecordContent from old records"); | |
203 | } | |
204 | return soa; | |
205 | } | |
206 | } | |
207 | throw PDNSException("No SOA in supplied records"); | |
208 | } | |
209 | ||
023d807e RG |
210 | static void makeIXFRDiff(const records_t& from, const records_t& to, std::shared_ptr<ixfrdiff_t>& diff, const shared_ptr<SOARecordContent>& fromSOA = nullptr, const shared_ptr<SOARecordContent>& toSOA = nullptr) { |
211 | set_difference(from.cbegin(), from.cend(), to.cbegin(), to.cend(), back_inserter(diff->removals), from.value_comp()); | |
212 | set_difference(to.cbegin(), to.cend(), from.cbegin(), from.cend(), back_inserter(diff->additions), from.value_comp()); | |
213 | diff->oldSOA = fromSOA; | |
ddd3fe8d | 214 | if (fromSOA == nullptr) { |
023d807e | 215 | diff->oldSOA = getSOAFromRecords(from); |
ddd3fe8d | 216 | } |
023d807e | 217 | diff->newSOA = toSOA; |
ddd3fe8d | 218 | if (toSOA == nullptr) { |
023d807e | 219 | diff->newSOA = getSOAFromRecords(to); |
ddd3fe8d | 220 | } |
e2cddb03 PL |
221 | } |
222 | ||
023d807e RG |
223 | /* you can _never_ alter the content of the resulting shared pointer */ |
224 | static std::shared_ptr<ixfrinfo_t> getCurrentZoneInfo(const DNSName& domain) | |
225 | { | |
226 | std::lock_guard<std::mutex> guard(g_soas_mutex); | |
227 | return g_soas[domain]; | |
228 | } | |
229 | ||
230 | static void updateCurrentZoneInfo(const DNSName& domain, std::shared_ptr<ixfrinfo_t>& newInfo) | |
231 | { | |
232 | std::lock_guard<std::mutex> guard(g_soas_mutex); | |
233 | g_soas[domain] = newInfo; | |
234 | } | |
235 | ||
592f52eb | 236 | void updateThread(const string& workdir, const uint16_t& keep, const uint16_t& axfrTimeout) { |
38e6a9ee PL |
237 | std::map<DNSName, time_t> lastCheck; |
238 | ||
239 | // Initialize the serials we have | |
f2d45260 PL |
240 | for (const auto &domainConfig : g_domainConfigs) { |
241 | DNSName domain = domainConfig.first; | |
38e6a9ee | 242 | lastCheck[domain] = 0; |
592f52eb | 243 | string dir = workdir + "/" + domain.toString(); |
38e6a9ee | 244 | try { |
1361013e | 245 | g_log<<Logger::Info<<"Trying to initially load domain "<<domain<<" from disk"<<endl; |
772cdea3 PL |
246 | auto serial = getSerialsFromDir(dir); |
247 | shared_ptr<SOARecordContent> soa; | |
248 | { | |
592f52eb | 249 | string fname = workdir + "/" + domain.toString() + "/" + std::to_string(serial); |
e2cddb03 PL |
250 | loadSOAFromDisk(domain, fname, soa); |
251 | records_t records; | |
772cdea3 | 252 | if (soa != nullptr) { |
e2cddb03 | 253 | loadZoneFromDisk(records, fname, domain); |
772cdea3 | 254 | } |
023d807e RG |
255 | auto zoneInfo = std::make_shared<ixfrinfo_t>(); |
256 | zoneInfo->latestAXFR = std::move(records); | |
257 | zoneInfo->soa = soa; | |
258 | updateCurrentZoneInfo(domain, zoneInfo); | |
772cdea3 | 259 | } |
37499e32 | 260 | if (soa != nullptr) { |
1361013e | 261 | g_log<<Logger::Notice<<"Loaded zone "<<domain<<" with serial "<<soa->d_st.serial<<endl; |
37499e32 | 262 | // Initial cleanup |
592f52eb | 263 | cleanUpDomain(domain, keep, workdir); |
37499e32 | 264 | } |
38e6a9ee PL |
265 | } catch (runtime_error &e) { |
266 | // Most likely, the directory does not exist. | |
1361013e | 267 | g_log<<Logger::Info<<e.what()<<", attempting to create"<<endl; |
38e6a9ee | 268 | // Attempt to create it, if _that_ fails, there is no hope |
33494729 | 269 | if (mkdir(dir.c_str(), 0777) == -1 && errno != EEXIST) { |
1361013e | 270 | g_log<<Logger::Error<<"Could not create '"<<dir<<"': "<<strerror(errno)<<endl; |
38e6a9ee PL |
271 | exit(EXIT_FAILURE); |
272 | } | |
273 | } | |
274 | } | |
275 | ||
1361013e | 276 | g_log<<Logger::Notice<<"Update Thread started"<<endl; |
772cdea3 | 277 | |
38e6a9ee | 278 | while (true) { |
bf676f2f | 279 | if (g_exiting) { |
1361013e | 280 | g_log<<Logger::Notice<<"UpdateThread stopped"<<endl; |
bf676f2f PL |
281 | break; |
282 | } | |
38e6a9ee | 283 | time_t now = time(nullptr); |
f2d45260 | 284 | for (const auto &domainConfig : g_domainConfigs) { |
c9198339 RG |
285 | |
286 | if (g_exiting) { | |
287 | break; | |
288 | } | |
289 | ||
f2d45260 | 290 | DNSName domain = domainConfig.first; |
26ecdd79 | 291 | shared_ptr<SOARecordContent> current_soa; |
023d807e RG |
292 | const auto& zoneInfo = getCurrentZoneInfo(domain); |
293 | if (zoneInfo != nullptr) { | |
294 | current_soa = zoneInfo->soa; | |
26ecdd79 | 295 | } |
4d2bb666 RG |
296 | |
297 | auto& zoneLastCheck = lastCheck[domain]; | |
298 | if ((current_soa != nullptr && now - zoneLastCheck < current_soa->d_st.refresh) || // Only check if we have waited `refresh` seconds | |
299 | (current_soa == nullptr && now - zoneLastCheck < 30)) { // Or if we could not get an update at all still, every 30 seconds | |
772cdea3 | 300 | continue; |
38e6a9ee | 301 | } |
f2d45260 PL |
302 | |
303 | // TODO Keep track of 'down' masters | |
4d2bb666 RG |
304 | set<ComboAddress>::const_iterator it(domainConfig.second.masters.begin()); |
305 | std::advance(it, random() % domainConfig.second.masters.size()); | |
f2d45260 PL |
306 | ComboAddress master = *it; |
307 | ||
592f52eb | 308 | string dir = workdir + "/" + domain.toString(); |
f2d45260 | 309 | g_log<<Logger::Info<<"Attempting to retrieve SOA Serial update for '"<<domain<<"' from '"<<master.toStringWithPort()<<"'"<<endl; |
772cdea3 PL |
310 | shared_ptr<SOARecordContent> sr; |
311 | try { | |
4d2bb666 | 312 | zoneLastCheck = now; |
f2d45260 | 313 | auto newSerial = getSerialFromMaster(master, domain, sr); // TODO TSIG |
26ecdd79 | 314 | if(current_soa != nullptr) { |
f2d45260 | 315 | g_log<<Logger::Info<<"Got SOA Serial for "<<domain<<" from "<<master.toStringWithPort()<<": "<< newSerial<<", had Serial: "<<current_soa->d_st.serial; |
26ecdd79 | 316 | if (newSerial == current_soa->d_st.serial) { |
1361013e | 317 | g_log<<Logger::Info<<", not updating."<<endl; |
23b644c6 | 318 | continue; |
38e6a9ee | 319 | } |
1361013e | 320 | g_log<<Logger::Info<<", will update."<<endl; |
38e6a9ee | 321 | } |
772cdea3 | 322 | } catch (runtime_error &e) { |
2dfb13fe | 323 | g_log<<Logger::Warning<<"Unable to get SOA serial update for '"<<domain<<"' from master "<<master.toStringWithPort()<<": "<<e.what()<<endl; |
772cdea3 | 324 | continue; |
38e6a9ee PL |
325 | } |
326 | // Now get the full zone! | |
1361013e | 327 | g_log<<Logger::Info<<"Attempting to receive full zonedata for '"<<domain<<"'"<<endl; |
f2d45260 | 328 | ComboAddress local = master.isIPv4() ? ComboAddress("0.0.0.0") : ComboAddress("::"); |
38e6a9ee | 329 | TSIGTriplet tt; |
26ecdd79 PL |
330 | |
331 | // The *new* SOA | |
772cdea3 | 332 | shared_ptr<SOARecordContent> soa; |
451605af | 333 | records_t records; |
38e6a9ee | 334 | try { |
f2d45260 | 335 | AXFRRetriever axfr(master, domain, tt, &local); |
38e6a9ee PL |
336 | unsigned int nrecords=0; |
337 | Resolver::res_t nop; | |
338 | vector<DNSRecord> chunk; | |
281ecb93 | 339 | time_t t_start = time(nullptr); |
f2457bfb | 340 | time_t axfr_now = time(nullptr); |
592f52eb | 341 | while(axfr.getChunk(nop, &chunk, (axfr_now - t_start + axfrTimeout))) { |
38e6a9ee PL |
342 | for(auto& dr : chunk) { |
343 | if(dr.d_type == QType::TSIG) | |
344 | continue; | |
345 | dr.d_name.makeUsRelative(domain); | |
346 | records.insert(dr); | |
347 | nrecords++; | |
772cdea3 PL |
348 | if (dr.d_type == QType::SOA) { |
349 | soa = getRR<SOARecordContent>(dr); | |
350 | } | |
38e6a9ee | 351 | } |
f2457bfb | 352 | axfr_now = time(nullptr); |
592f52eb | 353 | if (axfr_now - t_start > axfrTimeout) { |
f2457bfb PL |
354 | throw PDNSException("Total AXFR time exceeded!"); |
355 | } | |
38e6a9ee | 356 | } |
23b644c6 | 357 | if (soa == nullptr) { |
1361013e | 358 | g_log<<Logger::Warning<<"No SOA was found in the AXFR of "<<domain<<endl; |
23b644c6 PL |
359 | continue; |
360 | } | |
1361013e | 361 | g_log<<Logger::Notice<<"Retrieved all zone data for "<<domain<<". Received "<<nrecords<<" records."<<endl; |
451605af RG |
362 | } catch (PDNSException &e) { |
363 | g_log<<Logger::Warning<<"Could not retrieve AXFR for '"<<domain<<"': "<<e.reason<<endl; | |
97a2d904 | 364 | continue; |
451605af RG |
365 | } catch (runtime_error &e) { |
366 | g_log<<Logger::Warning<<"Could not retrieve AXFR for zone '"<<domain<<"': "<<e.what()<<endl; | |
97a2d904 | 367 | continue; |
451605af RG |
368 | } |
369 | ||
370 | try { | |
371 | ||
38e6a9ee | 372 | writeZoneToDisk(records, domain, dir); |
1361013e | 373 | g_log<<Logger::Notice<<"Wrote zonedata for "<<domain<<" with serial "<<soa->d_st.serial<<" to "<<dir<<endl; |
451605af | 374 | |
023d807e RG |
375 | const auto oldZoneInfo = getCurrentZoneInfo(domain); |
376 | auto zoneInfo = std::make_shared<ixfrinfo_t>(); | |
451605af | 377 | |
023d807e RG |
378 | if (oldZoneInfo && !oldZoneInfo->latestAXFR.empty()) { |
379 | auto diff = std::make_shared<ixfrdiff_t>(); | |
380 | zoneInfo->ixfrDiffs = oldZoneInfo->ixfrDiffs; | |
381 | g_log<<Logger::Debug<<"Calculating diff for "<<domain<<endl; | |
382 | makeIXFRDiff(oldZoneInfo->latestAXFR, records, diff, oldZoneInfo->soa, soa); | |
383 | g_log<<Logger::Debug<<"Calculated diff for "<<domain<<", we had "<<diff->removals.size()<<" removals and "<<diff->additions.size()<<" additions"<<endl; | |
384 | zoneInfo->ixfrDiffs.push_back(std::move(diff)); | |
385 | } | |
451605af | 386 | |
023d807e RG |
387 | // Clean up the diffs |
388 | while (zoneInfo->ixfrDiffs.size() > keep) { | |
389 | zoneInfo->ixfrDiffs.erase(zoneInfo->ixfrDiffs.begin()); | |
0d19f1ca | 390 | } |
023d807e RG |
391 | |
392 | g_log<<Logger::Debug<<"Zone "<<domain<<" previously contained "<<(oldZoneInfo ? oldZoneInfo->latestAXFR.size() : 0)<<" entries, "<<records.size()<<" now"<<endl; | |
393 | zoneInfo->latestAXFR = std::move(records); | |
394 | zoneInfo->soa = soa; | |
395 | updateCurrentZoneInfo(domain, zoneInfo); | |
281ecb93 | 396 | } catch (PDNSException &e) { |
451605af | 397 | g_log<<Logger::Warning<<"Could not save zone '"<<domain<<"' to disk: "<<e.reason<<endl; |
38e6a9ee | 398 | } catch (runtime_error &e) { |
1361013e | 399 | g_log<<Logger::Warning<<"Could not save zone '"<<domain<<"' to disk: "<<e.what()<<endl; |
38e6a9ee | 400 | } |
451605af | 401 | |
37499e32 | 402 | // Now clean up the directory |
592f52eb | 403 | cleanUpDomain(domain, keep, workdir); |
38e6a9ee | 404 | } /* for (const auto &domain : domains) */ |
bf676f2f | 405 | sleep(1); |
38e6a9ee PL |
406 | } /* while (true) */ |
407 | } /* updateThread */ | |
4db8fd44 | 408 | |
ba779b12 | 409 | static bool checkQuery(const MOADNSParser& mdp, const ComboAddress& saddr, const bool udp = true, const string& logPrefix="") { |
772cdea3 PL |
410 | vector<string> info_msg; |
411 | ||
1361013e | 412 | g_log<<Logger::Debug<<logPrefix<<"Had "<<mdp.d_qname<<"|"<<QType(mdp.d_qtype).getName()<<" query from "<<saddr.toStringWithPort()<<endl; |
772cdea3 PL |
413 | |
414 | if (udp && mdp.d_qtype != QType::SOA && mdp.d_qtype != QType::IXFR) { | |
23b644c6 | 415 | info_msg.push_back("QType is unsupported (" + QType(mdp.d_qtype).getName() + " is not in {SOA,IXFR}"); |
772cdea3 PL |
416 | } |
417 | ||
418 | if (!udp && mdp.d_qtype != QType::SOA && mdp.d_qtype != QType::IXFR && mdp.d_qtype != QType::AXFR) { | |
23b644c6 | 419 | info_msg.push_back("QType is unsupported (" + QType(mdp.d_qtype).getName() + " is not in {SOA,IXFR,AXFR}"); |
772cdea3 PL |
420 | } |
421 | ||
d8e734f4 | 422 | { |
f2d45260 | 423 | if (g_domainConfigs.find(mdp.d_qname) == g_domainConfigs.end()) { |
d8e734f4 PL |
424 | info_msg.push_back("Domain name '" + mdp.d_qname.toLogString() + "' is not configured for distribution"); |
425 | } | |
772cdea3 | 426 | |
023d807e RG |
427 | const auto zoneInfo = getCurrentZoneInfo(mdp.d_qname); |
428 | if (zoneInfo == nullptr) { | |
d8e734f4 PL |
429 | info_msg.push_back("Domain has not been transferred yet"); |
430 | } | |
772cdea3 PL |
431 | } |
432 | ||
433 | if (!info_msg.empty()) { | |
1361013e PL |
434 | g_log<<Logger::Warning<<logPrefix<<"Ignoring "<<mdp.d_qname<<"|"<<QType(mdp.d_qtype).getName()<<" query from "<<saddr.toStringWithPort(); |
435 | g_log<<Logger::Warning<<": "; | |
436 | bool first = true; | |
437 | for (const auto& s : info_msg) { | |
438 | if (!first) { | |
439 | g_log<<Logger::Warning<<", "; | |
440 | first = false; | |
772cdea3 | 441 | } |
1361013e | 442 | g_log<<Logger::Warning<<s; |
772cdea3 | 443 | } |
1361013e | 444 | g_log<<Logger::Warning<<endl; |
772cdea3 PL |
445 | return false; |
446 | } | |
447 | ||
448 | return true; | |
449 | } | |
450 | ||
0c16458b PL |
451 | /* |
452 | * Returns a vector<uint8_t> that represents the full response to a SOA | |
453 | * query. QNAME is read from mdp. | |
0c16458b | 454 | */ |
ba779b12 | 455 | static bool makeSOAPacket(const MOADNSParser& mdp, vector<uint8_t>& packet) { |
023d807e RG |
456 | |
457 | auto zoneInfo = getCurrentZoneInfo(mdp.d_qname); | |
458 | if (zoneInfo == nullptr) { | |
459 | return false; | |
460 | } | |
461 | ||
772cdea3 PL |
462 | DNSPacketWriter pw(packet, mdp.d_qname, mdp.d_qtype); |
463 | pw.getHeader()->id = mdp.d_header.id; | |
464 | pw.getHeader()->rd = mdp.d_header.rd; | |
465 | pw.getHeader()->qr = 1; | |
466 | ||
467 | pw.startRecord(mdp.d_qname, QType::SOA); | |
023d807e | 468 | zoneInfo->soa->toPacket(pw); |
772cdea3 PL |
469 | pw.commit(); |
470 | ||
0eb03ead | 471 | return true; |
772cdea3 PL |
472 | } |
473 | ||
ba779b12 | 474 | static vector<uint8_t> getSOAPacket(const MOADNSParser& mdp, const shared_ptr<SOARecordContent>& soa) { |
da704871 | 475 | vector<uint8_t> packet; |
0c16458b PL |
476 | DNSPacketWriter pw(packet, mdp.d_qname, mdp.d_qtype); |
477 | pw.getHeader()->id = mdp.d_header.id; | |
478 | pw.getHeader()->rd = mdp.d_header.rd; | |
479 | pw.getHeader()->qr = 1; | |
480 | ||
da704871 PL |
481 | // Add the first SOA |
482 | pw.startRecord(mdp.d_qname, QType::SOA); | |
483 | soa->toPacket(pw); | |
484 | pw.commit(); | |
485 | return packet; | |
486 | } | |
487 | ||
ba779b12 RG |
488 | static bool sendPacketOverTCP(int fd, const std::vector<uint8_t>& packet) |
489 | { | |
490 | char sendBuf[2]; | |
491 | sendBuf[0]=packet.size()/256; | |
492 | sendBuf[1]=packet.size()%256; | |
493 | ||
494 | ssize_t send = writen2(fd, sendBuf, 2); | |
495 | send += writen2(fd, &packet[0], packet.size()); | |
496 | return true; | |
497 | } | |
498 | ||
971e5911 | 499 | static bool addRecordToWriter(DNSPacketWriter& pw, const DNSName& zoneName, const DNSRecord& record, bool compress) |
ba779b12 | 500 | { |
971e5911 | 501 | pw.startRecord(record.d_name + zoneName, record.d_type, record.d_ttl, QClass::IN, DNSResourceRecord::ANSWER, compress); |
ba779b12 RG |
502 | record.d_content->toPacket(pw); |
503 | if (pw.size() > 65535) { | |
504 | pw.rollback(); | |
505 | return false; | |
506 | } | |
507 | return true; | |
508 | } | |
509 | ||
510 | template <typename T> static bool sendRecordsOverTCP(int fd, const MOADNSParser& mdp, const T& records) | |
511 | { | |
512 | vector<uint8_t> packet; | |
513 | ||
514 | for (auto it = records.cbegin(); it != records.cend();) { | |
515 | bool recordsAdded = false; | |
516 | packet.clear(); | |
517 | DNSPacketWriter pw(packet, mdp.d_qname, mdp.d_qtype); | |
518 | pw.getHeader()->id = mdp.d_header.id; | |
519 | pw.getHeader()->rd = mdp.d_header.rd; | |
520 | pw.getHeader()->qr = 1; | |
521 | ||
522 | while (it != records.cend()) { | |
523 | if (it->d_type == QType::SOA) { | |
524 | it++; | |
525 | continue; | |
526 | } | |
527 | ||
971e5911 | 528 | if (addRecordToWriter(pw, mdp.d_qname, *it, g_compress)) { |
ba779b12 RG |
529 | recordsAdded = true; |
530 | it++; | |
531 | } | |
532 | else { | |
533 | if (recordsAdded) { | |
534 | pw.commit(); | |
535 | sendPacketOverTCP(fd, packet); | |
536 | } | |
537 | if (it == records.cbegin()) { | |
538 | /* something is wrong */ | |
539 | return false; | |
540 | } | |
541 | ||
542 | break; | |
543 | } | |
544 | } | |
545 | ||
546 | if (it == records.cend() && recordsAdded) { | |
547 | pw.commit(); | |
548 | sendPacketOverTCP(fd, packet); | |
549 | } | |
550 | } | |
551 | ||
552 | return true; | |
553 | } | |
554 | ||
555 | ||
556 | static bool handleAXFR(int fd, const MOADNSParser& mdp) { | |
023d807e RG |
557 | /* we get a shared pointer of the zone info that we can't modify, ever. |
558 | A newer one may arise in the meantime, but this one will stay valid | |
559 | until we release it. | |
560 | */ | |
561 | auto zoneInfo = getCurrentZoneInfo(mdp.d_qname); | |
562 | if (zoneInfo == nullptr) { | |
563 | return false; | |
0c16458b PL |
564 | } |
565 | ||
023d807e RG |
566 | shared_ptr<SOARecordContent> soa = zoneInfo->soa; |
567 | const records_t& records = zoneInfo->latestAXFR; | |
7750f6c6 | 568 | |
da704871 | 569 | // Initial SOA |
7750f6c6 | 570 | const auto soaPacket = getSOAPacket(mdp, soa); |
ba779b12 RG |
571 | if (!sendPacketOverTCP(fd, soaPacket)) { |
572 | return false; | |
573 | } | |
0c16458b | 574 | |
ba779b12 RG |
575 | if (!sendRecordsOverTCP(fd, mdp, records)) { |
576 | return false; | |
0c16458b | 577 | } |
0c16458b | 578 | |
da704871 | 579 | // Final SOA |
ba779b12 RG |
580 | if (!sendPacketOverTCP(fd, soaPacket)) { |
581 | return false; | |
582 | } | |
0c16458b PL |
583 | |
584 | return true; | |
585 | } | |
772cdea3 | 586 | |
0eb03ead PL |
587 | /* Produces an IXFR if one can be made according to the rules in RFC 1995 and |
588 | * creates a SOA or AXFR packet when required by the RFC. | |
ac9e0a99 | 589 | */ |
ba779b12 | 590 | static bool handleIXFR(int fd, const ComboAddress& destination, const MOADNSParser& mdp, const shared_ptr<SOARecordContent>& clientSOA) { |
023d807e RG |
591 | vector<std::shared_ptr<ixfrdiff_t>> toSend; |
592 | ||
593 | /* we get a shared pointer of the zone info that we can't modify, ever. | |
594 | A newer one may arise in the meantime, but this one will stay valid | |
595 | until we release it. | |
596 | */ | |
597 | auto zoneInfo = getCurrentZoneInfo(mdp.d_qname); | |
598 | if (zoneInfo == nullptr) { | |
599 | return false; | |
26ecdd79 | 600 | } |
ac9e0a99 | 601 | |
023d807e RG |
602 | uint32_t ourLatestSerial = zoneInfo->soa->d_st.serial; |
603 | ||
604 | if (rfc1982LessThan(ourLatestSerial, clientSOA->d_st.serial) || ourLatestSerial == clientSOA->d_st.serial) { | |
0eb03ead PL |
605 | /* RFC 1995 Section 2 |
606 | * If an IXFR query with the same or newer version number than that of | |
607 | * the server is received, it is replied to with a single SOA record of | |
608 | * the server's current version, just as in AXFR. | |
609 | */ | |
da704871 PL |
610 | vector<uint8_t> packet; |
611 | bool ret = makeSOAPacket(mdp, packet); | |
612 | if (ret) { | |
ba779b12 | 613 | sendPacketOverTCP(fd, packet); |
da704871 PL |
614 | } |
615 | return ret; | |
0eb03ead PL |
616 | } |
617 | ||
023d807e RG |
618 | // as we use push_back in the updater, we know the vector is sorted as oldest first |
619 | bool shouldAdd = false; | |
620 | // Get all relevant IXFR differences | |
621 | for (const auto& diff : zoneInfo->ixfrDiffs) { | |
622 | if (shouldAdd) { | |
623 | toSend.push_back(diff); | |
624 | continue; | |
625 | } | |
626 | if (diff->oldSOA->d_st.serial == clientSOA->d_st.serial) { | |
627 | toSend.push_back(diff); | |
628 | // Add all consecutive diffs | |
629 | shouldAdd = true; | |
37499e32 | 630 | } |
37499e32 | 631 | } |
ac9e0a99 | 632 | |
e2cddb03 | 633 | if (toSend.empty()) { |
1361013e | 634 | g_log<<Logger::Warning<<"No IXFR available from serial "<<clientSOA->d_st.serial<<" for zone "<<mdp.d_qname<<", attempting to send AXFR"<<endl; |
ba779b12 | 635 | return handleAXFR(fd, mdp); |
ac9e0a99 PL |
636 | } |
637 | ||
ba779b12 | 638 | std::vector<std::vector<uint8_t>> packets; |
e2cddb03 PL |
639 | for (const auto& diff : toSend) { |
640 | /* An IXFR packet's ANSWER section looks as follows: | |
641 | * SOA new_serial | |
642 | * SOA old_serial | |
643 | * ... removed records ... | |
644 | * SOA new_serial | |
645 | * ... added records ... | |
646 | * SOA new_serial | |
647 | */ | |
023d807e | 648 | |
ba779b12 RG |
649 | const auto newSOAPacket = getSOAPacket(mdp, diff->newSOA); |
650 | const auto oldSOAPacket = getSOAPacket(mdp, diff->oldSOA); | |
651 | ||
652 | if (!sendPacketOverTCP(fd, newSOAPacket)) { | |
653 | return false; | |
654 | } | |
655 | ||
656 | if (!sendPacketOverTCP(fd, oldSOAPacket)) { | |
657 | return false; | |
658 | } | |
659 | ||
660 | if (!sendRecordsOverTCP(fd, mdp, diff->removals)) { | |
661 | return false; | |
662 | } | |
663 | ||
664 | if (!sendPacketOverTCP(fd, newSOAPacket)) { | |
665 | return false; | |
666 | } | |
667 | ||
668 | if (!sendRecordsOverTCP(fd, mdp, diff->additions)) { | |
669 | return false; | |
670 | } | |
671 | ||
672 | if (!sendPacketOverTCP(fd, newSOAPacket)) { | |
673 | return false; | |
674 | } | |
e2cddb03 | 675 | } |
ac9e0a99 PL |
676 | |
677 | return true; | |
678 | } | |
679 | ||
ba779b12 | 680 | static bool allowedByACL(const ComboAddress& addr) { |
6329cf4d PL |
681 | return g_acl.match(addr); |
682 | } | |
683 | ||
ba779b12 | 684 | static void handleUDPRequest(int fd, boost::any&) { |
aff1f1fd PL |
685 | // TODO make the buffer-size configurable |
686 | char buf[4096]; | |
772cdea3 | 687 | ComboAddress saddr; |
8be49a38 | 688 | socklen_t fromlen = sizeof(saddr); |
772cdea3 | 689 | int res = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr*) &saddr, &fromlen); |
aff1f1fd PL |
690 | |
691 | if (res == 0) { | |
1361013e | 692 | g_log<<Logger::Warning<<"Got an empty message from "<<saddr.toStringWithPort()<<endl; |
aff1f1fd PL |
693 | return; |
694 | } | |
695 | ||
aff1f1fd | 696 | if(res < 0) { |
8b9554c9 | 697 | auto savedErrno = errno; |
1361013e | 698 | g_log<<Logger::Warning<<"Could not read message from "<<saddr.toStringWithPort()<<": "<<strerror(savedErrno)<<endl; |
772cdea3 PL |
699 | return; |
700 | } | |
701 | ||
fefb237c PL |
702 | if (saddr == ComboAddress("0.0.0.0", 0)) { |
703 | g_log<<Logger::Warning<<"Could not determine source of message"<<endl; | |
6329cf4d PL |
704 | return; |
705 | } | |
706 | ||
fefb237c PL |
707 | if (!allowedByACL(saddr)) { |
708 | g_log<<Logger::Warning<<"UDP query from "<<saddr.toString()<<" is not allowed, dropping"<<endl; | |
aff1f1fd PL |
709 | return; |
710 | } | |
711 | ||
712 | MOADNSParser mdp(true, string(buf, res)); | |
772cdea3 PL |
713 | if (!checkQuery(mdp, saddr)) { |
714 | return; | |
715 | } | |
aff1f1fd | 716 | |
0eb03ead PL |
717 | /* RFC 1995 Section 2 |
718 | * Transport of a query may be by either UDP or TCP. If an IXFR query | |
719 | * is via UDP, the IXFR server may attempt to reply using UDP if the | |
720 | * entire response can be contained in a single DNS packet. If the UDP | |
721 | * reply does not fit, the query is responded to with a single SOA | |
722 | * record of the server's current version to inform the client that a | |
723 | * TCP query should be initiated. | |
724 | * | |
725 | * Let's not complicate this with IXFR over UDP (and looking if we need to truncate etc). | |
726 | * Just send the current SOA and let the client try over TCP | |
727 | */ | |
728 | vector<uint8_t> packet; | |
729 | makeSOAPacket(mdp, packet); | |
772cdea3 | 730 | if(sendto(fd, &packet[0], packet.size(), 0, (struct sockaddr*) &saddr, fromlen) < 0) { |
8b9554c9 | 731 | auto savedErrno = errno; |
1361013e | 732 | g_log<<Logger::Warning<<"Could not send reply for "<<mdp.d_qname<<"|"<<QType(mdp.d_qtype).getName()<<" to "<<saddr.toStringWithPort()<<": "<<strerror(savedErrno)<<endl; |
aff1f1fd | 733 | } |
772cdea3 PL |
734 | return; |
735 | } | |
aff1f1fd | 736 | |
ba779b12 | 737 | static void handleTCPRequest(int fd, boost::any&) { |
772cdea3 | 738 | ComboAddress saddr; |
4f80c4e0 | 739 | int cfd = 0; |
772cdea3 | 740 | |
4f80c4e0 PL |
741 | try { |
742 | cfd = SAccept(fd, saddr); | |
743 | setBlocking(cfd); | |
744 | } catch(runtime_error &e) { | |
1361013e | 745 | g_log<<Logger::Error<<e.what()<<endl; |
772cdea3 | 746 | return; |
aff1f1fd PL |
747 | } |
748 | ||
fefb237c PL |
749 | if (saddr == ComboAddress("0.0.0.0", 0)) { |
750 | g_log<<Logger::Warning<<"Could not determine source of message"<<endl; | |
7d6ccb44 | 751 | close(cfd); |
6329cf4d PL |
752 | return; |
753 | } | |
754 | ||
fefb237c PL |
755 | if (!allowedByACL(saddr)) { |
756 | g_log<<Logger::Warning<<"TCP query from "<<saddr.toString()<<" is not allowed, dropping"<<endl; | |
757 | close(cfd); | |
772cdea3 | 758 | return; |
aff1f1fd PL |
759 | } |
760 | ||
a2969595 PL |
761 | { |
762 | std::lock_guard<std::mutex> lg(g_tcpRequestFDsMutex); | |
763 | g_tcpRequestFDs.push({cfd, saddr}); | |
772cdea3 | 764 | } |
a2969595 PL |
765 | g_tcpHandlerCV.notify_one(); |
766 | } | |
772cdea3 | 767 | |
a2969595 PL |
768 | /* Thread to handle TCP traffic |
769 | */ | |
ba779b12 | 770 | static void tcpWorker(int tid) { |
a2969595 PL |
771 | string prefix = "TCP Worker " + std::to_string(tid) + ": "; |
772 | ||
773 | while(true) { | |
1361013e | 774 | g_log<<Logger::Debug<<prefix<<"ready for a new request!"<<endl; |
a2969595 PL |
775 | std::unique_lock<std::mutex> lk(g_tcpRequestFDsMutex); |
776 | g_tcpHandlerCV.wait(lk, []{return g_tcpRequestFDs.size() || g_exiting ;}); | |
777 | if (g_exiting) { | |
1361013e | 778 | g_log<<Logger::Debug<<prefix<<"Stopping thread"<<endl; |
a2969595 PL |
779 | break; |
780 | } | |
1361013e | 781 | g_log<<Logger::Debug<<prefix<<"Going to handle a query"<<endl; |
a2969595 PL |
782 | auto request = g_tcpRequestFDs.front(); |
783 | g_tcpRequestFDs.pop(); | |
784 | lk.unlock(); | |
aff1f1fd | 785 | |
a2969595 PL |
786 | int cfd = request.first; |
787 | ComboAddress saddr = request.second; | |
788 | ||
789 | char buf[4096]; | |
790 | ssize_t res; | |
791 | try { | |
792 | uint16_t toRead; | |
793 | readn2(cfd, &toRead, sizeof(toRead)); | |
794 | toRead = std::min(ntohs(toRead), static_cast<uint16_t>(sizeof(buf))); | |
795 | res = readn2WithTimeout(cfd, &buf, toRead, 2); | |
1361013e | 796 | g_log<<Logger::Debug<<prefix<<"Had message of "<<std::to_string(toRead)<<" bytes from "<<saddr.toStringWithPort()<<endl; |
a2969595 | 797 | } catch (runtime_error &e) { |
1361013e | 798 | g_log<<Logger::Warning<<prefix<<"Could not read message from "<<saddr.toStringWithPort()<<": "<<e.what()<<endl; |
772cdea3 | 799 | close(cfd); |
a2969595 | 800 | continue; |
772cdea3 PL |
801 | } |
802 | ||
a2969595 PL |
803 | try { |
804 | MOADNSParser mdp(true, string(buf, res)); | |
805 | ||
1361013e | 806 | if (!checkQuery(mdp, saddr, false, prefix)) { |
0eb03ead | 807 | close(cfd); |
a2969595 | 808 | continue; |
0eb03ead | 809 | } |
772cdea3 | 810 | |
a2969595 | 811 | if (mdp.d_qtype == QType::SOA) { |
7d6ccb44 | 812 | vector<uint8_t> packet; |
a2969595 PL |
813 | bool ret = makeSOAPacket(mdp, packet); |
814 | if (!ret) { | |
815 | close(cfd); | |
816 | continue; | |
817 | } | |
ba779b12 | 818 | sendPacketOverTCP(cfd, packet); |
0c16458b | 819 | } |
ba779b12 RG |
820 | else if (mdp.d_qtype == QType::AXFR) { |
821 | if (!handleAXFR(cfd, mdp)) { | |
a2969595 PL |
822 | close(cfd); |
823 | continue; | |
824 | } | |
825 | } | |
ba779b12 | 826 | else if (mdp.d_qtype == QType::IXFR) { |
a2969595 PL |
827 | /* RFC 1995 section 3: |
828 | * The IXFR query packet format is the same as that of a normal DNS | |
829 | * query, but with the query type being IXFR and the authority section | |
830 | * containing the SOA record of client's version of the zone. | |
831 | */ | |
832 | shared_ptr<SOARecordContent> clientSOA; | |
833 | for (auto &answer : mdp.d_answers) { | |
834 | // from dnsparser.hh: | |
835 | // typedef vector<pair<DNSRecord, uint16_t > > answers_t; | |
836 | if (answer.first.d_type == QType::SOA && answer.first.d_place == DNSResourceRecord::AUTHORITY) { | |
837 | clientSOA = getRR<SOARecordContent>(answer.first); | |
838 | if (clientSOA != nullptr) { | |
839 | break; | |
840 | } | |
ac9e0a99 | 841 | } |
a2969595 PL |
842 | } /* for (auto const &answer : mdp.d_answers) */ |
843 | ||
844 | if (clientSOA == nullptr) { | |
1361013e | 845 | g_log<<Logger::Warning<<prefix<<"IXFR request packet did not contain a SOA record in the AUTHORITY section"<<endl; |
a2969595 PL |
846 | close(cfd); |
847 | continue; | |
ac9e0a99 | 848 | } |
ac9e0a99 | 849 | |
ba779b12 | 850 | if (!handleIXFR(cfd, saddr, mdp, clientSOA)) { |
a2969595 PL |
851 | close(cfd); |
852 | continue; | |
853 | } | |
854 | } /* if (mdp.d_qtype == QType::IXFR) */ | |
ac9e0a99 | 855 | |
a2969595 PL |
856 | shutdown(cfd, 2); |
857 | } catch (MOADNSException &e) { | |
1361013e | 858 | g_log<<Logger::Warning<<prefix<<"Could not parse DNS packet from "<<saddr.toStringWithPort()<<": "<<e.what()<<endl; |
a2969595 | 859 | } catch (runtime_error &e) { |
1361013e | 860 | g_log<<Logger::Warning<<prefix<<"Could not write reply to "<<saddr.toStringWithPort()<<": "<<e.what()<<endl; |
a2969595 PL |
861 | } |
862 | // bye! | |
863 | close(cfd); | |
772cdea3 | 864 | |
a2969595 PL |
865 | if (g_exiting) { |
866 | break; | |
da704871 | 867 | } |
772cdea3 | 868 | } |
772cdea3 | 869 | } |
aff1f1fd | 870 | |
f2d45260 PL |
871 | /* Parses the configuration file in configpath into config, adding defaults for |
872 | * missing parameters (if applicable), returning true if the config file was | |
873 | * good, false otherwise. Will log all issues with the config | |
874 | */ | |
ba779b12 | 875 | static bool parseAndCheckConfig(const string& configpath, YAML::Node& config) { |
592f52eb | 876 | g_log<<Logger::Info<<"Loading configuration file from "<<configpath<<endl; |
f2d45260 PL |
877 | try { |
878 | config = YAML::LoadFile(configpath); | |
879 | } catch (const runtime_error &e) { | |
592f52eb | 880 | g_log<<Logger::Error<<"Unable to load configuration file '"<<configpath<<"': "<<e.what()<<endl; |
f2d45260 PL |
881 | return false; |
882 | } | |
883 | ||
884 | bool retval = true; | |
885 | ||
886 | if (config["keep"]) { | |
887 | try { | |
888 | config["keep"].as<uint16_t>(); | |
889 | } catch (const runtime_error &e) { | |
890 | g_log<<Logger::Error<<"Unable to read 'keep' value: "<<e.what()<<endl; | |
891 | retval = false; | |
892 | } | |
893 | } else { | |
592f52eb | 894 | config["keep"] = 20; |
f2d45260 PL |
895 | } |
896 | ||
897 | if (config["axfr-timeout"]) { | |
898 | try { | |
899 | config["axfr-timeout"].as<uint16_t>(); | |
900 | } catch (const runtime_error &e) { | |
901 | g_log<<Logger::Error<<"Unable to read 'axfr-timeout' value: "<<e.what()<<endl; | |
902 | } | |
903 | } else { | |
e6aaf558 | 904 | config["axfr-timeout"] = 20; |
f2d45260 PL |
905 | } |
906 | ||
907 | if (config["tcp-in-threads"]) { | |
908 | try { | |
909 | config["tcp-in-threads"].as<uint16_t>(); | |
910 | } catch (const runtime_error &e) { | |
4529cc91 | 911 | g_log<<Logger::Error<<"Unable to read 'tcp-in-threads' value: "<<e.what()<<endl; |
f2d45260 PL |
912 | } |
913 | } else { | |
914 | config["tcp-in-threads"] = 10; | |
915 | } | |
916 | ||
917 | if (config["listen"]) { | |
918 | try { | |
919 | config["listen"].as<vector<ComboAddress>>(); | |
920 | } catch (const runtime_error &e) { | |
921 | g_log<<Logger::Error<<"Unable to read 'listen' value: "<<e.what()<<endl; | |
922 | retval = false; | |
923 | } | |
924 | } else { | |
925 | config["listen"].push_back("127.0.0.1:53"); | |
926 | config["listen"].push_back("[::1]:53"); | |
927 | } | |
928 | ||
929 | if (config["acl"]) { | |
930 | try { | |
931 | config["acl"].as<vector<string>>(); | |
932 | } catch (const runtime_error &e) { | |
933 | g_log<<Logger::Error<<"Unable to read 'acl' value: "<<e.what()<<endl; | |
934 | retval = false; | |
935 | } | |
936 | } else { | |
937 | config["acl"].push_back("127.0.0.0/8"); | |
938 | config["acl"].push_back("::1/128"); | |
939 | } | |
940 | ||
941 | if (config["work-dir"]) { | |
942 | try { | |
943 | config["work-dir"].as<string>(); | |
944 | } catch(const runtime_error &e) { | |
945 | g_log<<Logger::Error<<"Unable to read 'work-dir' value: "<<e.what()<<endl; | |
946 | retval = false; | |
947 | } | |
948 | } else { | |
949 | char tmp[512]; | |
950 | config["work-dir"] = getcwd(tmp, sizeof(tmp)) ? string(tmp) : "";; | |
951 | } | |
952 | ||
953 | if (config["uid"]) { | |
954 | try { | |
955 | config["uid"].as<string>(); | |
956 | } catch(const runtime_error &e) { | |
957 | g_log<<Logger::Error<<"Unable to read 'uid' value: "<<e.what()<<endl; | |
958 | retval = false; | |
959 | } | |
960 | } | |
961 | ||
962 | if (config["gid"]) { | |
963 | try { | |
964 | config["gid"].as<string>(); | |
965 | } catch(const runtime_error &e) { | |
966 | g_log<<Logger::Error<<"Unable to read 'gid' value: "<<e.what()<<endl; | |
967 | retval = false; | |
968 | } | |
969 | } | |
970 | ||
971 | if (config["domains"]) { | |
972 | if (config["domains"].size() == 0) { | |
973 | g_log<<Logger::Error<<"No domains configured"<<endl; | |
974 | retval = false; | |
975 | } | |
976 | for (auto const &domain : config["domains"]) { | |
977 | try { | |
978 | if (!domain["domain"]) { | |
979 | g_log<<Logger::Error<<"An entry in 'domains' is missing a 'domain' key!"<<endl; | |
980 | retval = false; | |
981 | continue; | |
982 | } | |
983 | domain["domain"].as<DNSName>(); | |
984 | } catch (const runtime_error &e) { | |
985 | g_log<<Logger::Error<<"Unable to read domain '"<<domain["domain"].as<string>()<<"': "<<e.what()<<endl; | |
986 | } | |
987 | try { | |
988 | if (!domain["master"]) { | |
989 | g_log<<Logger::Error<<"Domain '"<<domain["domain"].as<string>()<<"' has no master configured!"<<endl; | |
990 | retval = false; | |
991 | continue; | |
992 | } | |
993 | domain["master"].as<ComboAddress>(); | |
994 | } catch (const runtime_error &e) { | |
995 | g_log<<Logger::Error<<"Unable to read domain '"<<domain["domain"].as<string>()<<"' master address: "<<e.what()<<endl; | |
996 | retval = false; | |
997 | } | |
998 | } | |
999 | } else { | |
1000 | g_log<<Logger::Error<<"No domains configured"<<endl; | |
1001 | retval = false; | |
1002 | } | |
1003 | ||
971e5911 RG |
1004 | if (config["compress"]) { |
1005 | try { | |
1006 | config["compress"].as<bool>(); | |
1007 | } | |
1008 | catch (const runtime_error &e) { | |
1009 | g_log<<Logger::Error<<"Unable to read 'compress' value: "<<e.what()<<endl; | |
1010 | retval = false; | |
1011 | } | |
1012 | } | |
1013 | else { | |
1014 | config["compress"] = false; | |
1015 | } | |
1016 | ||
f2d45260 PL |
1017 | return retval; |
1018 | } | |
1019 | ||
a2670f5e | 1020 | int main(int argc, char** argv) { |
1361013e PL |
1021 | g_log.setLoglevel(Logger::Notice); |
1022 | g_log.toConsole(Logger::Notice); | |
1023 | g_log.setPrefixed(true); | |
1024 | g_log.disableSyslog(true); | |
1025 | g_log.setTimestamps(false); | |
592f52eb | 1026 | po::variables_map g_vm; |
347671ac PL |
1027 | try { |
1028 | po::options_description desc("IXFR distribution tool"); | |
1029 | desc.add_options() | |
1030 | ("help", "produce help message") | |
1031 | ("version", "Display the version of ixfrdist") | |
1032 | ("verbose", "Be verbose") | |
1033 | ("debug", "Be even more verbose") | |
f2d45260 | 1034 | ("config", po::value<string>()->default_value(SYSCONFDIR + string("/ixfrdist.yml")), "Configuration file to use") |
347671ac | 1035 | ; |
347671ac | 1036 | |
f2d45260 | 1037 | po::store(po::command_line_parser(argc, argv).options(desc).run(), g_vm); |
347671ac PL |
1038 | po::notify(g_vm); |
1039 | ||
1040 | if (g_vm.count("help") > 0) { | |
1041 | usage(desc); | |
1042 | return EXIT_SUCCESS; | |
1043 | } | |
a2670f5e | 1044 | |
347671ac PL |
1045 | if (g_vm.count("version") > 0) { |
1046 | cout<<"ixfrdist "<<VERSION<<endl; | |
1047 | return EXIT_SUCCESS; | |
1048 | } | |
347671ac | 1049 | } catch (po::error &e) { |
1361013e | 1050 | g_log<<Logger::Error<<e.what()<<". See `ixfrdist --help` for valid options"<<endl; |
347671ac | 1051 | return(EXIT_FAILURE); |
aff1f1fd PL |
1052 | } |
1053 | ||
a2670f5e PL |
1054 | bool had_error = false; |
1055 | ||
1361013e PL |
1056 | if (g_vm.count("verbose")) { |
1057 | g_log.setLoglevel(Logger::Info); | |
1058 | g_log.toConsole(Logger::Info); | |
f3c63e34 PL |
1059 | } |
1060 | ||
1061 | if (g_vm.count("debug") > 0) { | |
1361013e PL |
1062 | g_log.setLoglevel(Logger::Debug); |
1063 | g_log.toConsole(Logger::Debug); | |
f3c63e34 PL |
1064 | } |
1065 | ||
08fc00e2 PL |
1066 | g_log<<Logger::Notice<<"IXFR distributor version "<<VERSION<<" starting up!"<<endl; |
1067 | ||
f2d45260 PL |
1068 | YAML::Node config; |
1069 | if (!parseAndCheckConfig(g_vm["config"].as<string>(), config)) { | |
1070 | // parseAndCheckConfig already logged whatever was wrong | |
1071 | return EXIT_FAILURE; | |
a2670f5e PL |
1072 | } |
1073 | ||
f2d45260 | 1074 | /* From hereon out, we known that all the values in config are valid. */ |
a2670f5e | 1075 | |
f2d45260 PL |
1076 | for (auto const &domain : config["domains"]) { |
1077 | set<ComboAddress> s; | |
1078 | s.insert(domain["master"].as<ComboAddress>()); | |
1079 | g_domainConfigs[domain["domain"].as<DNSName>()].masters = s; | |
a2670f5e | 1080 | } |
38e6a9ee | 1081 | |
f2d45260 | 1082 | for (const auto &addr : config["acl"].as<vector<string>>()) { |
6329cf4d PL |
1083 | try { |
1084 | g_acl.addMask(addr); | |
1085 | } catch (const NetmaskException &e) { | |
1361013e | 1086 | g_log<<Logger::Error<<e.reason<<endl; |
6329cf4d PL |
1087 | had_error = true; |
1088 | } | |
1089 | } | |
1361013e | 1090 | g_log<<Logger::Notice<<"ACL set to "<<g_acl.toString()<<"."<<endl; |
6329cf4d | 1091 | |
971e5911 RG |
1092 | if (config["compress"]) { |
1093 | g_compress = config["compress"].as<bool>(); | |
1094 | if (g_compress) { | |
1095 | g_log<<Logger::Notice<<"Record compression is enabled."<<endl; | |
1096 | } | |
1097 | } | |
1098 | ||
592f52eb PL |
1099 | FDMultiplexer* fdm = FDMultiplexer::getMultiplexerSilent(); |
1100 | if (fdm == nullptr) { | |
f2d45260 PL |
1101 | g_log<<Logger::Error<<"Could not enable a multiplexer for the listen sockets!"<<endl; |
1102 | return EXIT_FAILURE; | |
1103 | } | |
1104 | ||
bf676f2f | 1105 | set<int> allSockets; |
f2d45260 | 1106 | for (const auto& addr : config["listen"].as<vector<ComboAddress>>()) { |
4f80c4e0 PL |
1107 | for (const auto& stype : {SOCK_DGRAM, SOCK_STREAM}) { |
1108 | try { | |
1109 | int s = SSocket(addr.sin4.sin_family, stype, 0); | |
1110 | setNonBlocking(s); | |
1111 | setReuseAddr(s); | |
1112 | SBind(s, addr); | |
1113 | if (stype == SOCK_STREAM) { | |
1114 | SListen(s, 30); // TODO make this configurable | |
1115 | } | |
592f52eb | 1116 | fdm->addReadFD(s, stype == SOCK_DGRAM ? handleUDPRequest : handleTCPRequest); |
4f80c4e0 PL |
1117 | allSockets.insert(s); |
1118 | } catch(runtime_error &e) { | |
1361013e | 1119 | g_log<<Logger::Error<<e.what()<<endl; |
4f80c4e0 PL |
1120 | had_error = true; |
1121 | continue; | |
1122 | } | |
aff1f1fd | 1123 | } |
aff1f1fd PL |
1124 | } |
1125 | ||
6f8cb809 PL |
1126 | int newgid = 0; |
1127 | ||
f2d45260 PL |
1128 | if (config["gid"]) { |
1129 | string gid = config["gid"].as<string>(); | |
6f8cb809 PL |
1130 | if (!(newgid = atoi(gid.c_str()))) { |
1131 | struct group *gr = getgrnam(gid.c_str()); | |
1132 | if (gr == nullptr) { | |
1361013e | 1133 | g_log<<Logger::Error<<"Can not determine group-id for gid "<<gid<<endl; |
6f8cb809 PL |
1134 | had_error = true; |
1135 | } else { | |
1136 | newgid = gr->gr_gid; | |
1137 | } | |
1138 | } | |
1361013e | 1139 | g_log<<Logger::Notice<<"Dropping effective group-id to "<<newgid<<endl; |
6f8cb809 | 1140 | if (setgid(newgid) < 0) { |
1361013e | 1141 | g_log<<Logger::Error<<"Could not set group id to "<<newgid<<": "<<stringerror()<<endl; |
6f8cb809 PL |
1142 | had_error = true; |
1143 | } | |
1144 | } | |
1145 | ||
1146 | int newuid = 0; | |
1147 | ||
f2d45260 PL |
1148 | if (config["uid"]) { |
1149 | string uid = config["uid"].as<string>(); | |
6f8cb809 PL |
1150 | if (!(newuid = atoi(uid.c_str()))) { |
1151 | struct passwd *pw = getpwnam(uid.c_str()); | |
1152 | if (pw == nullptr) { | |
1361013e | 1153 | g_log<<Logger::Error<<"Can not determine user-id for uid "<<uid<<endl; |
6f8cb809 PL |
1154 | had_error = true; |
1155 | } else { | |
1156 | newuid = pw->pw_uid; | |
1157 | } | |
1158 | } | |
1159 | ||
1160 | struct passwd *pw = getpwuid(newuid); | |
1161 | if (pw == nullptr) { | |
1162 | if (setgroups(0, nullptr) < 0) { | |
1361013e | 1163 | g_log<<Logger::Error<<"Unable to drop supplementary gids: "<<stringerror()<<endl; |
6f8cb809 PL |
1164 | had_error = true; |
1165 | } | |
1166 | } else { | |
1167 | if (initgroups(pw->pw_name, newgid) < 0) { | |
1361013e | 1168 | g_log<<Logger::Error<<"Unable to set supplementary groups: "<<stringerror()<<endl; |
6f8cb809 PL |
1169 | had_error = true; |
1170 | } | |
1171 | } | |
1172 | ||
1361013e | 1173 | g_log<<Logger::Notice<<"Dropping effective user-id to "<<newuid<<endl; |
e88e004e | 1174 | if (setuid(newuid) < 0) { |
1361013e | 1175 | g_log<<Logger::Error<<"Could not set user id to "<<newuid<<": "<<stringerror()<<endl; |
6f8cb809 PL |
1176 | had_error = true; |
1177 | } | |
1178 | } | |
1179 | ||
a2670f5e PL |
1180 | if (had_error) { |
1181 | // We have already sent the errors to stderr, just die | |
1182 | return EXIT_FAILURE; | |
1183 | } | |
38e6a9ee PL |
1184 | |
1185 | // It all starts here | |
bf676f2f PL |
1186 | signal(SIGTERM, handleSignal); |
1187 | signal(SIGINT, handleSignal); | |
a2969595 | 1188 | signal(SIGPIPE, SIG_IGN); |
bf676f2f | 1189 | |
38e6a9ee PL |
1190 | // Init the things we need |
1191 | reportAllTypes(); | |
aff1f1fd | 1192 | |
413f38c6 | 1193 | dns_random_init(); |
38e6a9ee | 1194 | |
592f52eb PL |
1195 | std::thread ut(updateThread, |
1196 | config["work-dir"].as<string>(), | |
1197 | config["keep"].as<uint16_t>(), | |
1198 | config["axfr-timeout"].as<uint16_t>()); | |
f2d45260 | 1199 | |
75d837d5 | 1200 | vector<std::thread> tcpHandlers; |
f2d45260 PL |
1201 | tcpHandlers.reserve(config["tcp-in-threads"].as<uint16_t>()); |
1202 | for (size_t i = 0; i < tcpHandlers.capacity(); ++i) { | |
75d837d5 | 1203 | tcpHandlers.push_back(std::thread(tcpWorker, i)); |
a2969595 | 1204 | } |
aff1f1fd | 1205 | |
aff1f1fd PL |
1206 | struct timeval now; |
1207 | for(;;) { | |
1208 | gettimeofday(&now, 0); | |
592f52eb | 1209 | fdm->run(&now); |
bf676f2f | 1210 | if (g_exiting) { |
b23d9f7a | 1211 | g_log<<Logger::Debug<<"Closing listening sockets"<<endl; |
bf676f2f PL |
1212 | for (const int& fd : allSockets) { |
1213 | try { | |
1214 | closesocket(fd); | |
1215 | } catch(PDNSException &e) { | |
1361013e | 1216 | g_log<<Logger::Error<<e.reason<<endl; |
bf676f2f PL |
1217 | } |
1218 | } | |
1219 | break; | |
1220 | } | |
aff1f1fd | 1221 | } |
b23d9f7a | 1222 | g_log<<Logger::Debug<<"Waiting for al threads to stop"<<endl; |
a2969595 | 1223 | g_tcpHandlerCV.notify_all(); |
1361013e | 1224 | ut.join(); |
75d837d5 PL |
1225 | for (auto &t : tcpHandlers) { |
1226 | t.join(); | |
a2969595 | 1227 | } |
1361013e | 1228 | g_log<<Logger::Notice<<"IXFR distributor stopped"<<endl; |
bf676f2f | 1229 | return EXIT_SUCCESS; |
a2670f5e | 1230 | } |