]>
Commit | Line | Data |
---|---|---|
6bb38cd6 RG |
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 | */ | |
f037144c RG |
22 | #include <fcntl.h> |
23 | #include <sys/stat.h> | |
24 | #include <sys/types.h> | |
25 | ||
f4b1f1fd | 26 | #include "config.h" |
6bb38cd6 RG |
27 | #include "dnsdist.hh" |
28 | #include "dnsdist-lua.hh" | |
29 | #include "dnsdist-protobuf.hh" | |
30 | ||
82a91ddf | 31 | #include "dnstap.hh" |
6bb38cd6 | 32 | #include "dolog.hh" |
82a91ddf | 33 | #include "fstrm_logger.hh" |
6bb38cd6 RG |
34 | #include "remote_logger.hh" |
35 | ||
f4b1f1fd | 36 | #ifdef HAVE_LIBCRYPTO |
5d5d8c2e | 37 | #include "ipcipher.hh" |
f4b1f1fd | 38 | #endif /* HAVE_LIBCRYPTO */ |
6bb38cd6 RG |
39 | |
40 | void setupLuaBindings(bool client) | |
41 | { | |
42 | g_lua.writeFunction("infolog", [](const string& arg) { | |
43 | infolog("%s", arg); | |
44 | }); | |
45 | g_lua.writeFunction("errlog", [](const string& arg) { | |
46 | errlog("%s", arg); | |
47 | }); | |
48 | g_lua.writeFunction("warnlog", [](const string& arg) { | |
49 | warnlog("%s", arg); | |
50 | }); | |
51 | g_lua.writeFunction("show", [](const string& arg) { | |
52 | g_outputBuffer+=arg; | |
53 | g_outputBuffer+="\n"; | |
54 | }); | |
55 | ||
f5aff975 CHB |
56 | /* Exceptions */ |
57 | g_lua.registerFunction<string(std::exception_ptr::*)()>("__tostring", [](const std::exception_ptr& eptr) { | |
58 | try { | |
59 | if (eptr) { | |
60 | std::rethrow_exception(eptr); | |
61 | } | |
62 | } catch(const std::exception& e) { | |
63 | return string(e.what()); | |
64 | } catch(const PDNSException& e) { | |
65 | return e.reason; | |
66 | } catch(...) { | |
67 | return string("Unknown exception"); | |
68 | } | |
69 | return string("No exception"); | |
70 | }); | |
6bb38cd6 | 71 | /* ServerPolicy */ |
a1b1a29d | 72 | g_lua.writeFunction("newServerPolicy", [](string name, policyfunc_t policy) { return ServerPolicy{name, policy, true};}); |
6bb38cd6 RG |
73 | g_lua.registerMember("name", &ServerPolicy::name); |
74 | g_lua.registerMember("policy", &ServerPolicy::policy); | |
a1b1a29d | 75 | g_lua.registerMember("isLua", &ServerPolicy::isLua); |
a4fd2d2f | 76 | g_lua.registerFunction("toString", &ServerPolicy::toString); |
6bb38cd6 | 77 | |
a1b1a29d RG |
78 | g_lua.writeVariable("firstAvailable", ServerPolicy{"firstAvailable", firstAvailable, false}); |
79 | g_lua.writeVariable("roundrobin", ServerPolicy{"roundrobin", roundrobin, false}); | |
80 | g_lua.writeVariable("wrandom", ServerPolicy{"wrandom", wrandom, false}); | |
81 | g_lua.writeVariable("whashed", ServerPolicy{"whashed", whashed, false}); | |
1720247e | 82 | g_lua.writeVariable("chashed", ServerPolicy{"chashed", chashed, false}); |
a1b1a29d | 83 | g_lua.writeVariable("leastOutstanding", ServerPolicy{"leastOutstanding", leastOutstanding, false}); |
6bb38cd6 RG |
84 | |
85 | /* ServerPool */ | |
86 | g_lua.registerFunction<void(std::shared_ptr<ServerPool>::*)(std::shared_ptr<DNSDistPacketCache>)>("setCache", [](std::shared_ptr<ServerPool> pool, std::shared_ptr<DNSDistPacketCache> cache) { | |
87 | if (pool) { | |
88 | pool->packetCache = cache; | |
89 | } | |
90 | }); | |
91 | g_lua.registerFunction("getCache", &ServerPool::getCache); | |
92 | g_lua.registerFunction<void(std::shared_ptr<ServerPool>::*)()>("unsetCache", [](std::shared_ptr<ServerPool> pool) { | |
93 | if (pool) { | |
94 | pool->packetCache = nullptr; | |
95 | } | |
96 | }); | |
7e687744 RG |
97 | g_lua.registerFunction("getECS", &ServerPool::getECS); |
98 | g_lua.registerFunction("setECS", &ServerPool::setECS); | |
6bb38cd6 RG |
99 | |
100 | /* DownstreamState */ | |
101 | g_lua.registerFunction<void(DownstreamState::*)(int)>("setQPS", [](DownstreamState& s, int lim) { s.qps = lim ? QPSLimiter(lim, lim) : QPSLimiter(); }); | |
102 | g_lua.registerFunction<void(std::shared_ptr<DownstreamState>::*)(string)>("addPool", [](std::shared_ptr<DownstreamState> s, string pool) { | |
103 | auto localPools = g_pools.getCopy(); | |
104 | addServerToPool(localPools, pool, s); | |
105 | g_pools.setState(localPools); | |
106 | s->pools.insert(pool); | |
107 | }); | |
108 | g_lua.registerFunction<void(std::shared_ptr<DownstreamState>::*)(string)>("rmPool", [](std::shared_ptr<DownstreamState> s, string pool) { | |
109 | auto localPools = g_pools.getCopy(); | |
110 | removeServerFromPool(localPools, pool, s); | |
111 | g_pools.setState(localPools); | |
112 | s->pools.erase(pool); | |
113 | }); | |
2831298d | 114 | g_lua.registerFunction<uint64_t(DownstreamState::*)()>("getOutstanding", [](const DownstreamState& s) { return s.outstanding.load(); }); |
6bb38cd6 RG |
115 | g_lua.registerFunction("isUp", &DownstreamState::isUp); |
116 | g_lua.registerFunction("setDown", &DownstreamState::setDown); | |
117 | g_lua.registerFunction("setUp", &DownstreamState::setUp); | |
118 | g_lua.registerFunction<void(DownstreamState::*)(boost::optional<bool> newStatus)>("setAuto", [](DownstreamState& s, boost::optional<bool> newStatus) { | |
119 | if (newStatus) { | |
120 | s.upStatus = *newStatus; | |
121 | } | |
122 | s.setAuto(); | |
123 | }); | |
124 | g_lua.registerFunction("getName", &DownstreamState::getName); | |
125 | g_lua.registerFunction("getNameWithAddr", &DownstreamState::getNameWithAddr); | |
126 | g_lua.registerMember("upStatus", &DownstreamState::upStatus); | |
5a2bbe8c CHB |
127 | g_lua.registerMember<int (DownstreamState::*)>("weight", |
128 | [](const DownstreamState& s) -> int {return s.weight;}, | |
129 | [](DownstreamState& s, int newWeight) {s.setWeight(newWeight);} | |
130 | ); | |
6bb38cd6 RG |
131 | g_lua.registerMember("order", &DownstreamState::order); |
132 | g_lua.registerMember("name", &DownstreamState::name); | |
133 | ||
134 | /* dnsheader */ | |
135 | g_lua.registerFunction<void(dnsheader::*)(bool)>("setRD", [](dnsheader& dh, bool v) { | |
136 | dh.rd=v; | |
137 | }); | |
138 | ||
139 | g_lua.registerFunction<bool(dnsheader::*)()>("getRD", [](dnsheader& dh) { | |
140 | return (bool)dh.rd; | |
141 | }); | |
142 | ||
143 | g_lua.registerFunction<void(dnsheader::*)(bool)>("setCD", [](dnsheader& dh, bool v) { | |
144 | dh.cd=v; | |
145 | }); | |
146 | ||
147 | g_lua.registerFunction<bool(dnsheader::*)()>("getCD", [](dnsheader& dh) { | |
148 | return (bool)dh.cd; | |
149 | }); | |
150 | ||
151 | g_lua.registerFunction<void(dnsheader::*)(bool)>("setTC", [](dnsheader& dh, bool v) { | |
152 | dh.tc=v; | |
153 | if(v) dh.ra = dh.rd; // you'll always need this, otherwise TC=1 gets ignored | |
154 | }); | |
155 | ||
156 | g_lua.registerFunction<void(dnsheader::*)(bool)>("setQR", [](dnsheader& dh, bool v) { | |
157 | dh.qr=v; | |
158 | }); | |
159 | ||
160 | /* ComboAddress */ | |
161 | g_lua.writeFunction("newCA", [](const std::string& name) { return ComboAddress(name); }); | |
162 | g_lua.registerFunction<string(ComboAddress::*)()>("tostring", [](const ComboAddress& ca) { return ca.toString(); }); | |
163 | g_lua.registerFunction<string(ComboAddress::*)()>("tostringWithPort", [](const ComboAddress& ca) { return ca.toStringWithPort(); }); | |
164 | g_lua.registerFunction<string(ComboAddress::*)()>("toString", [](const ComboAddress& ca) { return ca.toString(); }); | |
165 | g_lua.registerFunction<string(ComboAddress::*)()>("toStringWithPort", [](const ComboAddress& ca) { return ca.toStringWithPort(); }); | |
166 | g_lua.registerFunction<uint16_t(ComboAddress::*)()>("getPort", [](const ComboAddress& ca) { return ntohs(ca.sin4.sin_port); } ); | |
167 | g_lua.registerFunction<void(ComboAddress::*)(unsigned int)>("truncate", [](ComboAddress& ca, unsigned int bits) { ca.truncate(bits); }); | |
168 | g_lua.registerFunction<bool(ComboAddress::*)()>("isIPv4", [](const ComboAddress& ca) { return ca.sin4.sin_family == AF_INET; }); | |
169 | g_lua.registerFunction<bool(ComboAddress::*)()>("isIPv6", [](const ComboAddress& ca) { return ca.sin4.sin_family == AF_INET6; }); | |
170 | g_lua.registerFunction<bool(ComboAddress::*)()>("isMappedIPv4", [](const ComboAddress& ca) { return ca.isMappedIPv4(); }); | |
171 | g_lua.registerFunction<ComboAddress(ComboAddress::*)()>("mapToIPv4", [](const ComboAddress& ca) { return ca.mapToIPv4(); }); | |
172 | g_lua.registerFunction<bool(nmts_t::*)(const ComboAddress&)>("match", [](nmts_t& s, const ComboAddress& ca) { return s.match(ca); }); | |
173 | ||
f4b1f1fd | 174 | #ifdef HAVE_LIBCRYPTO |
7d280342 | 175 | g_lua.registerFunction<ComboAddress(ComboAddress::*)(const std::string& key)>("ipencrypt", [](const ComboAddress& ca, const std::string& key) { |
176 | return encryptCA(ca, key); | |
177 | }); | |
178 | g_lua.registerFunction<ComboAddress(ComboAddress::*)(const std::string& key)>("ipdecrypt", [](const ComboAddress& ca, const std::string& key) { | |
179 | return decryptCA(ca, key); | |
180 | }); | |
01223eb9 | 181 | |
182 | g_lua.writeFunction("makeIPCipherKey", [](const std::string& password) { | |
183 | return makeIPCipherKey(password); | |
184 | }); | |
f4b1f1fd | 185 | #endif /* HAVE_LIBCRYPTO */ |
488bcb39 | 186 | |
6bb38cd6 RG |
187 | /* DNSName */ |
188 | g_lua.registerFunction("isPartOf", &DNSName::isPartOf); | |
189 | g_lua.registerFunction<bool(DNSName::*)()>("chopOff", [](DNSName&dn ) { return dn.chopOff(); }); | |
190 | g_lua.registerFunction<unsigned int(DNSName::*)()>("countLabels", [](const DNSName& name) { return name.countLabels(); }); | |
191 | g_lua.registerFunction<size_t(DNSName::*)()>("wirelength", [](const DNSName& name) { return name.wirelength(); }); | |
192 | g_lua.registerFunction<string(DNSName::*)()>("tostring", [](const DNSName&dn ) { return dn.toString(); }); | |
193 | g_lua.registerFunction<string(DNSName::*)()>("toString", [](const DNSName&dn ) { return dn.toString(); }); | |
194 | g_lua.writeFunction("newDNSName", [](const std::string& name) { return DNSName(name); }); | |
195 | g_lua.writeFunction("newSuffixMatchNode", []() { return SuffixMatchNode(); }); | |
9f618bcc AD |
196 | g_lua.writeFunction("newDNSNameSet", []() { return DNSNameSet(); }); |
197 | ||
198 | /* DNSNameSet */ | |
199 | g_lua.registerFunction<string(DNSNameSet::*)()>("toString", [](const DNSNameSet&dns ) { return dns.toString(); }); | |
200 | g_lua.registerFunction<void(DNSNameSet::*)(DNSName&)>("add", [](DNSNameSet& dns, DNSName& dn) { dns.insert(dn); }); | |
cc0e7aa9 | 201 | g_lua.registerFunction<bool(DNSNameSet::*)(DNSName&)>("check", [](DNSNameSet& dns, DNSName& dn) { return dns.find(dn) != dns.end(); }); |
9f618bcc AD |
202 | g_lua.registerFunction("delete",(size_t (DNSNameSet::*)(const DNSName&)) &DNSNameSet::erase); |
203 | g_lua.registerFunction("size",(size_t (DNSNameSet::*)() const) &DNSNameSet::size); | |
204 | g_lua.registerFunction("clear",(void (DNSNameSet::*)()) &DNSNameSet::clear); | |
205 | g_lua.registerFunction("empty",(bool (DNSNameSet::*)()) &DNSNameSet::empty); | |
6bb38cd6 RG |
206 | |
207 | /* SuffixMatchNode */ | |
208 | g_lua.registerFunction("add",(void (SuffixMatchNode::*)(const DNSName&)) &SuffixMatchNode::add); | |
209 | g_lua.registerFunction("check",(bool (SuffixMatchNode::*)(const DNSName&) const) &SuffixMatchNode::check); | |
210 | ||
211 | /* NetmaskGroup */ | |
212 | g_lua.writeFunction("newNMG", []() { return NetmaskGroup(); }); | |
213 | g_lua.registerFunction<void(NetmaskGroup::*)(const std::string&mask)>("addMask", [](NetmaskGroup&nmg, const std::string& mask) | |
214 | { | |
215 | nmg.addMask(mask); | |
216 | }); | |
217 | g_lua.registerFunction<void(NetmaskGroup::*)(const std::map<ComboAddress,int>& map)>("addMasks", [](NetmaskGroup&nmg, const std::map<ComboAddress,int>& map) | |
218 | { | |
219 | for (const auto& entry : map) { | |
220 | nmg.addMask(Netmask(entry.first)); | |
221 | } | |
222 | }); | |
223 | ||
224 | g_lua.registerFunction("match", (bool (NetmaskGroup::*)(const ComboAddress&) const)&NetmaskGroup::match); | |
225 | g_lua.registerFunction("size", &NetmaskGroup::size); | |
226 | g_lua.registerFunction("clear", &NetmaskGroup::clear); | |
a4fd2d2f | 227 | g_lua.registerFunction<string(NetmaskGroup::*)()>("toString", [](const NetmaskGroup& nmg ) { return "NetmaskGroup " + nmg.toString(); }); |
6bb38cd6 RG |
228 | |
229 | /* QPSLimiter */ | |
230 | g_lua.writeFunction("newQPSLimiter", [](int rate, int burst) { return QPSLimiter(rate, burst); }); | |
231 | g_lua.registerFunction("check", &QPSLimiter::check); | |
232 | ||
233 | /* ClientState */ | |
234 | g_lua.registerFunction<std::string(ClientState::*)()>("toString", [](const ClientState& fe) { | |
235 | setLuaNoSideEffect(); | |
236 | return fe.local.toStringWithPort(); | |
237 | }); | |
238 | g_lua.registerMember("muted", &ClientState::muted); | |
239 | #ifdef HAVE_EBPF | |
240 | g_lua.registerFunction<void(ClientState::*)(std::shared_ptr<BPFFilter>)>("attachFilter", [](ClientState& frontend, std::shared_ptr<BPFFilter> bpf) { | |
241 | if (bpf) { | |
242 | frontend.attachFilter(bpf); | |
243 | } | |
244 | }); | |
245 | g_lua.registerFunction<void(ClientState::*)()>("detachFilter", [](ClientState& frontend) { | |
246 | frontend.detachFilter(); | |
247 | }); | |
248 | #endif /* HAVE_EBPF */ | |
249 | ||
250 | /* PacketCache */ | |
c1b81381 RG |
251 | g_lua.writeFunction("newPacketCache", [](size_t maxEntries, boost::optional<uint32_t> maxTTL, boost::optional<uint32_t> minTTL, boost::optional<uint32_t> tempFailTTL, boost::optional<uint32_t> staleTTL, boost::optional<bool> dontAge, boost::optional<size_t> numberOfShards, boost::optional<bool> deferrableInsertLock, boost::optional<uint32_t> maxNegativeTTL, boost::optional<bool> ecsParsing, boost::optional<std::unordered_map<std::string, boost::variant<bool, size_t>>> vars) { |
252 | ||
253 | bool keepStaleData = false; | |
254 | ||
255 | if (vars) { | |
256 | ||
257 | if (vars->count("deferrableInsertLock")) { | |
258 | deferrableInsertLock = boost::get<bool>((*vars)["deferrableInsertLock"]); | |
259 | } | |
260 | ||
261 | if (vars->count("dontAge")) { | |
262 | dontAge = boost::get<bool>((*vars)["dontAge"]); | |
263 | } | |
264 | ||
265 | if (vars->count("keepStaleData")) { | |
266 | keepStaleData = boost::get<bool>((*vars)["keepStaleData"]); | |
267 | } | |
268 | ||
269 | if (vars->count("maxEntries")) { | |
270 | maxEntries = boost::get<size_t>((*vars)["maxEntries"]); | |
271 | } | |
272 | ||
273 | if (vars->count("maxNegativeTTL")) { | |
274 | maxNegativeTTL = boost::get<size_t>((*vars)["maxNegativeTTL"]); | |
275 | } | |
276 | ||
277 | if (vars->count("maxTTL")) { | |
278 | maxTTL = boost::get<size_t>((*vars)["maxTTL"]); | |
279 | } | |
280 | ||
281 | if (vars->count("minTTL")) { | |
282 | minTTL = boost::get<size_t>((*vars)["minTTL"]); | |
283 | } | |
284 | ||
285 | if (vars->count("numberOfShards")) { | |
286 | numberOfShards = boost::get<size_t>((*vars)["numberOfShards"]); | |
287 | } | |
288 | ||
289 | if (vars->count("parseECS")) { | |
290 | ecsParsing = boost::get<bool>((*vars)["parseECS"]); | |
291 | } | |
292 | ||
293 | if (vars->count("staleTTL")) { | |
294 | staleTTL = boost::get<size_t>((*vars)["staleTTL"]); | |
295 | } | |
296 | ||
297 | if (vars->count("temporaryFailureTTL")) { | |
298 | tempFailTTL = boost::get<size_t>((*vars)["temporaryFailureTTL"]); | |
299 | } | |
300 | ||
301 | } | |
302 | ||
303 | auto res = std::make_shared<DNSDistPacketCache>(maxEntries, maxTTL ? *maxTTL : 86400, minTTL ? *minTTL : 0, tempFailTTL ? *tempFailTTL : 60, maxNegativeTTL ? *maxNegativeTTL : 3600, staleTTL ? *staleTTL : 60, dontAge ? *dontAge : false, numberOfShards ? *numberOfShards : 1, deferrableInsertLock ? *deferrableInsertLock : true, ecsParsing ? *ecsParsing : false); | |
304 | ||
305 | res->setKeepStaleData(keepStaleData); | |
306 | ||
307 | return res; | |
6bb38cd6 RG |
308 | }); |
309 | g_lua.registerFunction("toString", &DNSDistPacketCache::toString); | |
310 | g_lua.registerFunction("isFull", &DNSDistPacketCache::isFull); | |
311 | g_lua.registerFunction("purgeExpired", &DNSDistPacketCache::purgeExpired); | |
312 | g_lua.registerFunction("expunge", &DNSDistPacketCache::expunge); | |
313 | g_lua.registerFunction<void(std::shared_ptr<DNSDistPacketCache>::*)(const DNSName& dname, boost::optional<uint16_t> qtype, boost::optional<bool> suffixMatch)>("expungeByName", []( | |
314 | std::shared_ptr<DNSDistPacketCache> cache, | |
315 | const DNSName& dname, | |
316 | boost::optional<uint16_t> qtype, | |
317 | boost::optional<bool> suffixMatch) { | |
318 | if (cache) { | |
eace2c24 | 319 | cache->expungeByName(dname, qtype ? *qtype : QType(QType::ANY).getCode(), suffixMatch ? *suffixMatch : false); |
6bb38cd6 RG |
320 | } |
321 | }); | |
322 | g_lua.registerFunction<void(std::shared_ptr<DNSDistPacketCache>::*)()>("printStats", [](const std::shared_ptr<DNSDistPacketCache> cache) { | |
323 | if (cache) { | |
324 | g_outputBuffer="Entries: " + std::to_string(cache->getEntriesCount()) + "/" + std::to_string(cache->getMaxEntries()) + "\n"; | |
325 | g_outputBuffer+="Hits: " + std::to_string(cache->getHits()) + "\n"; | |
326 | g_outputBuffer+="Misses: " + std::to_string(cache->getMisses()) + "\n"; | |
327 | g_outputBuffer+="Deferred inserts: " + std::to_string(cache->getDeferredInserts()) + "\n"; | |
328 | g_outputBuffer+="Deferred lookups: " + std::to_string(cache->getDeferredLookups()) + "\n"; | |
329 | g_outputBuffer+="Lookup Collisions: " + std::to_string(cache->getLookupCollisions()) + "\n"; | |
330 | g_outputBuffer+="Insert Collisions: " + std::to_string(cache->getInsertCollisions()) + "\n"; | |
331 | g_outputBuffer+="TTL Too Shorts: " + std::to_string(cache->getTTLTooShorts()) + "\n"; | |
332 | } | |
333 | }); | |
c1b81381 RG |
334 | g_lua.registerFunction<std::unordered_map<std::string, uint64_t>(std::shared_ptr<DNSDistPacketCache>::*)()>("getStats", [](const std::shared_ptr<DNSDistPacketCache> cache) { |
335 | std::unordered_map<std::string, uint64_t> stats; | |
336 | if (cache) { | |
337 | stats["entries"] = cache->getEntriesCount(); | |
338 | stats["maxEntries"] = cache->getMaxEntries(); | |
339 | stats["hits"] = cache->getHits(); | |
340 | stats["misses"] = cache->getMisses(); | |
341 | stats["deferredInserts"] = cache->getDeferredInserts(); | |
342 | stats["deferredLookups"] = cache->getDeferredLookups(); | |
343 | stats["lookupCollisions"] = cache->getLookupCollisions(); | |
344 | stats["insertCollisions"] = cache->getInsertCollisions(); | |
345 | stats["ttlTooShorts"] = cache->getTTLTooShorts(); | |
346 | } | |
347 | return stats; | |
348 | }); | |
f037144c RG |
349 | g_lua.registerFunction<void(std::shared_ptr<DNSDistPacketCache>::*)(const std::string& fname)>("dump", [](const std::shared_ptr<DNSDistPacketCache> cache, const std::string& fname) { |
350 | if (cache) { | |
351 | ||
352 | int fd = open(fname.c_str(), O_CREAT | O_EXCL | O_WRONLY, 0660); | |
353 | if (fd < 0) { | |
354 | g_outputBuffer = "Error opening dump file for writing: " + string(strerror(errno)) + "\n"; | |
355 | return; | |
356 | } | |
357 | ||
358 | uint64_t records = 0; | |
359 | try { | |
360 | records = cache->dump(fd); | |
361 | } | |
362 | catch (const std::exception& e) { | |
363 | close(fd); | |
364 | throw; | |
365 | } | |
366 | ||
367 | close(fd); | |
368 | ||
369 | g_outputBuffer += "Dumped " + std::to_string(records) + " records\n"; | |
370 | } | |
371 | }); | |
6bb38cd6 RG |
372 | |
373 | /* ProtobufMessage */ | |
374 | g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(std::string)>("setTag", [](DNSDistProtoBufMessage& message, const std::string& strValue) { | |
375 | message.addTag(strValue); | |
376 | }); | |
377 | g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(vector<pair<int, string>>)>("setTagArray", [](DNSDistProtoBufMessage& message, const vector<pair<int, string>>&tags) { | |
378 | for (const auto& tag : tags) { | |
379 | message.addTag(tag.second); | |
380 | } | |
381 | }); | |
382 | g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(boost::optional <time_t> sec, boost::optional <uint32_t> uSec)>("setProtobufResponseType", | |
383 | [](DNSDistProtoBufMessage& message, boost::optional <time_t> sec, boost::optional <uint32_t> uSec) { | |
384 | message.setType(DNSProtoBufMessage::Response); | |
385 | message.setQueryTime(sec?*sec:0, uSec?*uSec:0); | |
386 | }); | |
387 | g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(const std::string& strQueryName, uint16_t uType, uint16_t uClass, uint32_t uTTL, const std::string& strBlob)>("addResponseRR", [](DNSDistProtoBufMessage& message, | |
388 | const std::string& strQueryName, uint16_t uType, uint16_t uClass, uint32_t uTTL, const std::string& strBlob) { | |
389 | message.addRR(DNSName(strQueryName), uType, uClass, uTTL, strBlob); | |
390 | }); | |
391 | g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(const Netmask&)>("setEDNSSubnet", [](DNSDistProtoBufMessage& message, const Netmask& subnet) { message.setEDNSSubnet(subnet); }); | |
392 | g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(const DNSName&, uint16_t, uint16_t)>("setQuestion", [](DNSDistProtoBufMessage& message, const DNSName& qname, uint16_t qtype, uint16_t qclass) { message.setQuestion(qname, qtype, qclass); }); | |
393 | g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(size_t)>("setBytes", [](DNSDistProtoBufMessage& message, size_t bytes) { message.setBytes(bytes); }); | |
394 | g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(time_t, uint32_t)>("setTime", [](DNSDistProtoBufMessage& message, time_t sec, uint32_t usec) { message.setTime(sec, usec); }); | |
395 | g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(time_t, uint32_t)>("setQueryTime", [](DNSDistProtoBufMessage& message, time_t sec, uint32_t usec) { message.setQueryTime(sec, usec); }); | |
396 | g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(uint8_t)>("setResponseCode", [](DNSDistProtoBufMessage& message, uint8_t rcode) { message.setResponseCode(rcode); }); | |
397 | g_lua.registerFunction<std::string(DNSDistProtoBufMessage::*)()>("toDebugString", [](const DNSDistProtoBufMessage& message) { return message.toDebugString(); }); | |
398 | g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(const ComboAddress&)>("setRequestor", [](DNSDistProtoBufMessage& message, const ComboAddress& addr) { | |
399 | message.setRequestor(addr); | |
400 | }); | |
401 | g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(const std::string&)>("setRequestorFromString", [](DNSDistProtoBufMessage& message, const std::string& str) { | |
402 | message.setRequestor(str); | |
403 | }); | |
404 | g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(const ComboAddress&)>("setResponder", [](DNSDistProtoBufMessage& message, const ComboAddress& addr) { | |
405 | message.setResponder(addr); | |
406 | }); | |
407 | g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(const std::string&)>("setResponderFromString", [](DNSDistProtoBufMessage& message, const std::string& str) { | |
408 | message.setResponder(str); | |
409 | }); | |
312a09a6 RG |
410 | g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(const std::string&)>("setServerIdentity", [](DNSDistProtoBufMessage& message, const std::string& str) { |
411 | message.setServerIdentity(str); | |
412 | }); | |
6bb38cd6 | 413 | |
82a91ddf CH |
414 | g_lua.registerFunction<std::string(DnstapMessage::*)()>("toDebugString", [](const DnstapMessage& message) { return message.toDebugString(); }); |
415 | g_lua.registerFunction<void(DnstapMessage::*)(const std::string&)>("setExtra", [](DnstapMessage& message, const std::string& str) { | |
416 | message.setExtra(str); | |
417 | }); | |
418 | ||
6bb38cd6 RG |
419 | /* RemoteLogger */ |
420 | g_lua.writeFunction("newRemoteLogger", [client](const std::string& remote, boost::optional<uint16_t> timeout, boost::optional<uint64_t> maxQueuedEntries, boost::optional<uint8_t> reconnectWaitTime) { | |
da71b63b | 421 | return std::shared_ptr<RemoteLoggerInterface>(new RemoteLogger(ComboAddress(remote), timeout ? *timeout : 2, maxQueuedEntries ? (*maxQueuedEntries*100) : 10000, reconnectWaitTime ? *reconnectWaitTime : 1, client)); |
82a91ddf CH |
422 | }); |
423 | ||
424 | g_lua.writeFunction("newFrameStreamUnixLogger", [client](const std::string& address) { | |
82a91ddf | 425 | #ifdef HAVE_FSTRM |
6b44773a | 426 | return std::shared_ptr<RemoteLoggerInterface>(new FrameStreamLogger(AF_UNIX, address, !client)); |
82a91ddf CH |
427 | #else |
428 | throw std::runtime_error("fstrm support is required to build an AF_UNIX FrameStreamLogger"); | |
429 | #endif /* HAVE_FSTRM */ | |
430 | }); | |
431 | ||
432 | g_lua.writeFunction("newFrameStreamTcpLogger", [client](const std::string& address) { | |
82a91ddf | 433 | #if defined(HAVE_FSTRM) && defined(HAVE_FSTRM_TCP_WRITER_INIT) |
6b44773a | 434 | return std::shared_ptr<RemoteLoggerInterface>(new FrameStreamLogger(AF_INET, address, !client)); |
82a91ddf CH |
435 | #else |
436 | throw std::runtime_error("fstrm with TCP support is required to build an AF_INET FrameStreamLogger"); | |
437 | #endif /* HAVE_FSTRM */ | |
438 | }); | |
6bb38cd6 | 439 | |
a4fd2d2f CH |
440 | g_lua.registerFunction("toString", &RemoteLoggerInterface::toString); |
441 | ||
6bb38cd6 | 442 | #ifdef HAVE_DNSCRYPT |
43234e76 RG |
443 | /* DNSCryptContext bindings */ |
444 | g_lua.registerFunction<std::string(DNSCryptContext::*)()>("getProviderName", [](const DNSCryptContext& ctx) { return ctx.getProviderName().toStringNoDot(); }); | |
43234e76 RG |
445 | g_lua.registerFunction("markActive", &DNSCryptContext::markActive); |
446 | g_lua.registerFunction("markInactive", &DNSCryptContext::markInactive); | |
447 | g_lua.registerFunction("removeInactiveCertificate", &DNSCryptContext::removeInactiveCertificate); | |
448 | g_lua.registerFunction<void(std::shared_ptr<DNSCryptContext>::*)(const std::string& certFile, const std::string& keyFile, boost::optional<bool> active)>("loadNewCertificate", [](std::shared_ptr<DNSCryptContext> ctx, const std::string& certFile, const std::string& keyFile, boost::optional<bool> active) { | |
449 | ||
450 | if (ctx == nullptr) { | |
451 | throw std::runtime_error("DNSCryptContext::loadNewCertificate() called on a nil value"); | |
452 | } | |
453 | ||
454 | ctx->loadNewCertificate(certFile, keyFile, active ? *active : true); | |
455 | }); | |
456 | g_lua.registerFunction<void(std::shared_ptr<DNSCryptContext>::*)(const DNSCryptCert& newCert, const DNSCryptPrivateKey& newKey, boost::optional<bool> active)>("addNewCertificate", [](std::shared_ptr<DNSCryptContext> ctx, const DNSCryptCert& newCert, const DNSCryptPrivateKey& newKey, boost::optional<bool> active) { | |
457 | ||
458 | if (ctx == nullptr) { | |
459 | throw std::runtime_error("DNSCryptContext::addNewCertificate() called on a nil value"); | |
460 | } | |
461 | ||
462 | ctx->addNewCertificate(newCert, newKey, active ? *active : true); | |
463 | }); | |
464 | g_lua.registerFunction<std::map<int, std::shared_ptr<DNSCryptCertificatePair>>(std::shared_ptr<DNSCryptContext>::*)()>("getCertificatePairs", [](std::shared_ptr<DNSCryptContext> ctx) { | |
465 | std::map<int, std::shared_ptr<DNSCryptCertificatePair>> result; | |
466 | ||
467 | if (ctx != nullptr) { | |
468 | size_t idx = 1; | |
469 | for (auto pair : ctx->getCertificates()) { | |
470 | result[idx++] = pair; | |
6bb38cd6 RG |
471 | } |
472 | } | |
43234e76 RG |
473 | |
474 | return result; | |
475 | }); | |
c2baf928 | 476 | |
43234e76 RG |
477 | g_lua.registerFunction<std::shared_ptr<DNSCryptCertificatePair>(std::shared_ptr<DNSCryptContext>::*)(size_t idx)>("getCertificatePair", [](std::shared_ptr<DNSCryptContext> ctx, size_t idx) { |
478 | ||
479 | if (ctx == nullptr) { | |
480 | throw std::runtime_error("DNSCryptContext::getCertificatePair() called on a nil value"); | |
481 | } | |
482 | ||
483 | std::shared_ptr<DNSCryptCertificatePair> result = nullptr; | |
55710b0f RG |
484 | auto pairs = ctx->getCertificates(); |
485 | if (idx < pairs.size()) { | |
486 | result = pairs.at(idx); | |
43234e76 RG |
487 | } |
488 | ||
489 | return result; | |
490 | }); | |
491 | ||
c2baf928 | 492 | g_lua.registerFunction<const DNSCryptCert(std::shared_ptr<DNSCryptContext>::*)(size_t idx)>("getCertificate", [](std::shared_ptr<DNSCryptContext> ctx, size_t idx) { |
55710b0f RG |
493 | |
494 | if (ctx == nullptr) { | |
c2baf928 | 495 | throw std::runtime_error("DNSCryptContext::getCertificate() called on a nil value"); |
55710b0f RG |
496 | } |
497 | ||
498 | auto pairs = ctx->getCertificates(); | |
c2baf928 RG |
499 | if (idx < pairs.size()) { |
500 | return pairs.at(idx)->cert; | |
55710b0f RG |
501 | } |
502 | ||
c2baf928 | 503 | throw std::runtime_error("This DNSCrypt context has no certificate at index " + std::to_string(idx)); |
55710b0f RG |
504 | }); |
505 | ||
43234e76 RG |
506 | g_lua.registerFunction<std::string(std::shared_ptr<DNSCryptContext>::*)()>("printCertificates", [](const std::shared_ptr<DNSCryptContext> ctx) { |
507 | ostringstream ret; | |
508 | ||
509 | if (ctx != nullptr) { | |
510 | size_t idx = 1; | |
511 | boost::format fmt("%1$-3d %|5t|%2$-8d %|10t|%3$-2d %|20t|%4$-21.21s %|41t|%5$-21.21s"); | |
512 | ret << (fmt % "#" % "Serial" % "Version" % "From" % "To" ) << endl; | |
513 | ||
514 | for (auto pair : ctx->getCertificates()) { | |
515 | const auto cert = pair->cert; | |
516 | const DNSCryptExchangeVersion version = DNSCryptContext::getExchangeVersion(cert); | |
517 | ||
518 | ret << (fmt % idx % cert.getSerial() % (version == DNSCryptExchangeVersion::VERSION1 ? 1 : 2) % DNSCryptContext::certificateDateToStr(cert.getTSStart()) % DNSCryptContext::certificateDateToStr(cert.getTSEnd())) << endl; | |
519 | } | |
520 | } | |
521 | ||
522 | return ret.str(); | |
523 | }); | |
524 | ||
525 | g_lua.registerFunction<void(DNSCryptContext::*)(const std::string& providerPrivateKeyFile, uint32_t serial, time_t begin, time_t end, boost::optional<DNSCryptExchangeVersion> version)>("generateAndLoadInMemoryCertificate", [](DNSCryptContext& ctx, const std::string& providerPrivateKeyFile, uint32_t serial, time_t begin, time_t end, boost::optional<DNSCryptExchangeVersion> version) { | |
526 | DNSCryptPrivateKey privateKey; | |
527 | DNSCryptCert cert; | |
528 | ||
529 | try { | |
530 | if (generateDNSCryptCertificate(providerPrivateKeyFile, serial, begin, end, version ? *version : DNSCryptExchangeVersion::VERSION1, cert, privateKey)) { | |
531 | ctx.addNewCertificate(cert, privateKey); | |
532 | } | |
533 | } | |
534 | catch(const std::exception& e) { | |
535 | errlog(e.what()); | |
536 | g_outputBuffer="Error: "+string(e.what())+"\n"; | |
537 | } | |
538 | }); | |
539 | ||
540 | /* DNSCryptCertificatePair */ | |
541 | g_lua.registerFunction<const DNSCryptCert(std::shared_ptr<DNSCryptCertificatePair>::*)()>("getCertificate", [](const std::shared_ptr<DNSCryptCertificatePair> pair) { | |
542 | if (pair == nullptr) { | |
543 | throw std::runtime_error("DNSCryptCertificatePair::getCertificate() called on a nil value"); | |
544 | } | |
545 | return pair->cert; | |
546 | }); | |
547 | g_lua.registerFunction<bool(std::shared_ptr<DNSCryptCertificatePair>::*)()>("isActive", [](const std::shared_ptr<DNSCryptCertificatePair> pair) { | |
548 | if (pair == nullptr) { | |
549 | throw std::runtime_error("DNSCryptCertificatePair::isActive() called on a nil value"); | |
6bb38cd6 | 550 | } |
43234e76 | 551 | return pair->active; |
6bb38cd6 RG |
552 | }); |
553 | ||
43234e76 RG |
554 | /* DNSCryptCert */ |
555 | g_lua.registerFunction<std::string(DNSCryptCert::*)()>("getMagic", [](const DNSCryptCert& cert) { return std::string(reinterpret_cast<const char*>(cert.magic), sizeof(cert.magic)); }); | |
556 | g_lua.registerFunction<std::string(DNSCryptCert::*)()>("getEsVersion", [](const DNSCryptCert& cert) { return std::string(reinterpret_cast<const char*>(cert.esVersion), sizeof(cert.esVersion)); }); | |
557 | g_lua.registerFunction<std::string(DNSCryptCert::*)()>("getProtocolMinorVersion", [](const DNSCryptCert& cert) { return std::string(reinterpret_cast<const char*>(cert.protocolMinorVersion), sizeof(cert.protocolMinorVersion)); }); | |
558 | g_lua.registerFunction<std::string(DNSCryptCert::*)()>("getSignature", [](const DNSCryptCert& cert) { return std::string(reinterpret_cast<const char*>(cert.signature), sizeof(cert.signature)); }); | |
559 | g_lua.registerFunction<std::string(DNSCryptCert::*)()>("getResolverPublicKey", [](const DNSCryptCert& cert) { return std::string(reinterpret_cast<const char*>(cert.signedData.resolverPK), sizeof(cert.signedData.resolverPK)); }); | |
560 | g_lua.registerFunction<std::string(DNSCryptCert::*)()>("getClientMagic", [](const DNSCryptCert& cert) { return std::string(reinterpret_cast<const char*>(cert.signedData.clientMagic), sizeof(cert.signedData.clientMagic)); }); | |
561 | g_lua.registerFunction<uint32_t(DNSCryptCert::*)()>("getSerial", [](const DNSCryptCert& cert) { return cert.getSerial(); }); | |
562 | g_lua.registerFunction<uint32_t(DNSCryptCert::*)()>("getTSStart", [](const DNSCryptCert& cert) { return ntohl(cert.getTSStart()); }); | |
563 | g_lua.registerFunction<uint32_t(DNSCryptCert::*)()>("getTSEnd", [](const DNSCryptCert& cert) { return ntohl(cert.getTSEnd()); }); | |
6bb38cd6 RG |
564 | #endif |
565 | ||
566 | /* BPF Filter */ | |
567 | #ifdef HAVE_EBPF | |
568 | g_lua.writeFunction("newBPFFilter", [client](uint32_t maxV4, uint32_t maxV6, uint32_t maxQNames) { | |
569 | if (client) { | |
570 | return std::shared_ptr<BPFFilter>(nullptr); | |
571 | } | |
572 | return std::make_shared<BPFFilter>(maxV4, maxV6, maxQNames); | |
573 | }); | |
574 | ||
575 | g_lua.registerFunction<void(std::shared_ptr<BPFFilter>::*)(const ComboAddress& ca)>("block", [](std::shared_ptr<BPFFilter> bpf, const ComboAddress& ca) { | |
576 | if (bpf) { | |
577 | return bpf->block(ca); | |
578 | } | |
579 | }); | |
580 | ||
581 | g_lua.registerFunction<void(std::shared_ptr<BPFFilter>::*)(const DNSName& qname, boost::optional<uint16_t> qtype)>("blockQName", [](std::shared_ptr<BPFFilter> bpf, const DNSName& qname, boost::optional<uint16_t> qtype) { | |
582 | if (bpf) { | |
583 | return bpf->block(qname, qtype ? *qtype : 255); | |
584 | } | |
585 | }); | |
586 | ||
587 | g_lua.registerFunction<void(std::shared_ptr<BPFFilter>::*)(const ComboAddress& ca)>("unblock", [](std::shared_ptr<BPFFilter> bpf, const ComboAddress& ca) { | |
588 | if (bpf) { | |
589 | return bpf->unblock(ca); | |
590 | } | |
591 | }); | |
592 | ||
593 | g_lua.registerFunction<void(std::shared_ptr<BPFFilter>::*)(const DNSName& qname, boost::optional<uint16_t> qtype)>("unblockQName", [](std::shared_ptr<BPFFilter> bpf, const DNSName& qname, boost::optional<uint16_t> qtype) { | |
594 | if (bpf) { | |
595 | return bpf->unblock(qname, qtype ? *qtype : 255); | |
596 | } | |
597 | }); | |
598 | ||
599 | g_lua.registerFunction<std::string(std::shared_ptr<BPFFilter>::*)()>("getStats", [](const std::shared_ptr<BPFFilter> bpf) { | |
600 | setLuaNoSideEffect(); | |
601 | std::string res; | |
602 | if (bpf) { | |
603 | std::vector<std::pair<ComboAddress, uint64_t> > stats = bpf->getAddrStats(); | |
604 | for (const auto& value : stats) { | |
605 | if (value.first.sin4.sin_family == AF_INET) { | |
606 | res += value.first.toString() + ": " + std::to_string(value.second) + "\n"; | |
607 | } | |
608 | else if (value.first.sin4.sin_family == AF_INET6) { | |
609 | res += "[" + value.first.toString() + "]: " + std::to_string(value.second) + "\n"; | |
610 | } | |
611 | } | |
612 | std::vector<std::tuple<DNSName, uint16_t, uint64_t> > qstats = bpf->getQNameStats(); | |
613 | for (const auto& value : qstats) { | |
614 | res += std::get<0>(value).toString() + " " + std::to_string(std::get<1>(value)) + ": " + std::to_string(std::get<2>(value)) + "\n"; | |
615 | } | |
616 | } | |
617 | return res; | |
618 | }); | |
619 | ||
620 | g_lua.registerFunction<void(std::shared_ptr<BPFFilter>::*)()>("attachToAllBinds", [](std::shared_ptr<BPFFilter> bpf) { | |
621 | std::string res; | |
622 | if (bpf) { | |
623 | for (const auto& frontend : g_frontends) { | |
624 | frontend->attachFilter(bpf); | |
625 | } | |
626 | } | |
627 | }); | |
628 | ||
629 | g_lua.writeFunction("newDynBPFFilter", [client](std::shared_ptr<BPFFilter> bpf) { | |
630 | if (client) { | |
631 | return std::shared_ptr<DynBPFFilter>(nullptr); | |
632 | } | |
633 | return std::make_shared<DynBPFFilter>(bpf); | |
634 | }); | |
635 | ||
636 | g_lua.registerFunction<void(std::shared_ptr<DynBPFFilter>::*)(const ComboAddress& addr, boost::optional<int> seconds)>("block", [](std::shared_ptr<DynBPFFilter> dbpf, const ComboAddress& addr, boost::optional<int> seconds) { | |
637 | if (dbpf) { | |
638 | struct timespec until; | |
639 | clock_gettime(CLOCK_MONOTONIC, &until); | |
640 | until.tv_sec += seconds ? *seconds : 10; | |
641 | dbpf->block(addr, until); | |
642 | } | |
643 | }); | |
644 | ||
645 | g_lua.registerFunction<void(std::shared_ptr<DynBPFFilter>::*)()>("purgeExpired", [](std::shared_ptr<DynBPFFilter> dbpf) { | |
646 | if (dbpf) { | |
647 | struct timespec now; | |
648 | clock_gettime(CLOCK_MONOTONIC, &now); | |
649 | dbpf->purgeExpired(now); | |
650 | } | |
651 | }); | |
ee38369c RS |
652 | |
653 | g_lua.registerFunction<void(std::shared_ptr<DynBPFFilter>::*)(boost::variant<std::string, std::vector<std::pair<int, std::string>>>)>("excludeRange", [](std::shared_ptr<DynBPFFilter> dbpf, boost::variant<std::string, std::vector<std::pair<int, std::string>>> ranges) { | |
654 | if (ranges.type() == typeid(std::vector<std::pair<int, std::string>>)) { | |
655 | for (const auto& range : *boost::get<std::vector<std::pair<int, std::string>>>(&ranges)) { | |
656 | dbpf->excludeRange(Netmask(range.second)); | |
657 | } | |
658 | } | |
659 | else { | |
660 | dbpf->excludeRange(Netmask(*boost::get<std::string>(&ranges))); | |
661 | } | |
662 | }); | |
663 | ||
664 | g_lua.registerFunction<void(std::shared_ptr<DynBPFFilter>::*)(boost::variant<std::string, std::vector<std::pair<int, std::string>>>)>("includeRange", [](std::shared_ptr<DynBPFFilter> dbpf, boost::variant<std::string, std::vector<std::pair<int, std::string>>> ranges) { | |
665 | if (ranges.type() == typeid(std::vector<std::pair<int, std::string>>)) { | |
666 | for (const auto& range : *boost::get<std::vector<std::pair<int, std::string>>>(&ranges)) { | |
667 | dbpf->includeRange(Netmask(range.second)); | |
668 | } | |
669 | } | |
670 | else { | |
671 | dbpf->includeRange(Netmask(*boost::get<std::string>(&ranges))); | |
672 | } | |
673 | }); | |
6bb38cd6 | 674 | #endif /* HAVE_EBPF */ |
cbf4e13a RG |
675 | |
676 | /* EDNSOptionView */ | |
677 | g_lua.registerFunction<size_t(EDNSOptionView::*)()>("count", [](const EDNSOptionView& option) { | |
678 | return option.values.size(); | |
679 | }); | |
0d51414d PD |
680 | g_lua.registerFunction<std::vector<string>(EDNSOptionView::*)()>("getValues", [] (const EDNSOptionView& option) { |
681 | std::vector<string> values; | |
cbf4e13a | 682 | for (const auto& value : option.values) { |
0d51414d | 683 | values.push_back(std::string(value.content, value.size)); |
cbf4e13a RG |
684 | } |
685 | return values; | |
686 | }); | |
6bb38cd6 | 687 | } |