/src/pdns/pdns/dnsdistdist/dnsdist-dynblocks.hh
Line | Count | Source |
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 | | #pragma once |
23 | | |
24 | | #ifndef DISABLE_DYNBLOCKS |
25 | | #include <unordered_set> |
26 | | |
27 | | #include "dolog.hh" |
28 | | #include "dnsdist-rings.hh" |
29 | | #include "gettime.hh" |
30 | | #include "statnode.hh" |
31 | | |
32 | | extern "C" |
33 | | { |
34 | | #include "dnsdist-lua-inspection-ffi.h" |
35 | | } |
36 | | |
37 | | #include "ext/luawrapper/include/LuaContext.hpp" |
38 | | |
39 | | // dnsdist_ffi_stat_node_t is a lightuserdata |
40 | | template <> |
41 | | struct LuaContext::Pusher<dnsdist_ffi_stat_node_t*> |
42 | | { |
43 | | static const int minSize = 1; |
44 | | static const int maxSize = 1; |
45 | | |
46 | | static PushedObject push(lua_State* state, dnsdist_ffi_stat_node_t* ptr) noexcept |
47 | 0 | { |
48 | 0 | lua_pushlightuserdata(state, ptr); |
49 | 0 | return PushedObject{state, 1}; |
50 | 0 | } |
51 | | }; |
52 | | |
53 | | using dnsdist_ffi_stat_node_visitor_t = std::function<bool(dnsdist_ffi_stat_node_t*)>; |
54 | | |
55 | | struct SMTBlockParameters |
56 | | { |
57 | | std::optional<std::string> d_reason; |
58 | | std::optional<DNSAction::Action> d_action; |
59 | | }; |
60 | | |
61 | | struct dnsdist_ffi_stat_node_t |
62 | | { |
63 | | dnsdist_ffi_stat_node_t(const StatNode& node_, const StatNode::Stat& self_, const StatNode::Stat& children_, SMTBlockParameters& blockParameters) : |
64 | 0 | node(node_), self(self_), children(children_), d_blockParameters(blockParameters) |
65 | 0 | { |
66 | 0 | } |
67 | | |
68 | | mutable std::string fullname; |
69 | | const StatNode& node; |
70 | | const StatNode::Stat& self; |
71 | | const StatNode::Stat& children; |
72 | | SMTBlockParameters& d_blockParameters; |
73 | | }; |
74 | | |
75 | | struct DynBlock |
76 | | { |
77 | | DynBlock() |
78 | 12 | { |
79 | 12 | until.tv_sec = 0; |
80 | 12 | until.tv_nsec = 0; |
81 | 12 | } |
82 | | |
83 | | DynBlock(const std::string& reason_, const struct timespec& until_, const DNSName& domain_, DNSAction::Action action_) : |
84 | 0 | reason(reason_), domain(domain_), until(until_), action(action_) |
85 | 0 | { |
86 | 0 | } |
87 | | |
88 | | DynBlock(const DynBlock& rhs) : |
89 | 0 | reason(rhs.reason), domain(rhs.domain), until(rhs.until), tagSettings(rhs.tagSettings), action(rhs.action), warning(rhs.warning), bpf(rhs.bpf) |
90 | 0 | { |
91 | 0 | blocks.store(rhs.blocks); |
92 | 0 | } |
93 | | |
94 | | DynBlock(DynBlock&& rhs) : |
95 | 0 | reason(std::move(rhs.reason)), domain(std::move(rhs.domain)), until(rhs.until), tagSettings(std::move(rhs.tagSettings)), action(rhs.action), warning(rhs.warning), bpf(rhs.bpf) |
96 | 0 | { |
97 | 0 | blocks.store(rhs.blocks); |
98 | 0 | } |
99 | | |
100 | | DynBlock& operator=(const DynBlock& rhs) |
101 | 0 | { |
102 | 0 | reason = rhs.reason; |
103 | 0 | until = rhs.until; |
104 | 0 | domain = rhs.domain; |
105 | 0 | action = rhs.action; |
106 | 0 | blocks.store(rhs.blocks); |
107 | 0 | warning = rhs.warning; |
108 | 0 | bpf = rhs.bpf; |
109 | 0 | tagSettings = rhs.tagSettings; |
110 | 0 | return *this; |
111 | 0 | } |
112 | | |
113 | | DynBlock& operator=(DynBlock&& rhs) |
114 | 0 | { |
115 | 0 | reason = std::move(rhs.reason); |
116 | 0 | until = rhs.until; |
117 | 0 | domain = std::move(rhs.domain); |
118 | 0 | action = rhs.action; |
119 | 0 | blocks.store(rhs.blocks); |
120 | 0 | warning = rhs.warning; |
121 | 0 | bpf = rhs.bpf; |
122 | 0 | tagSettings = std::move(rhs.tagSettings); |
123 | 0 | return *this; |
124 | 0 | } |
125 | | |
126 | | struct TagSettings |
127 | | { |
128 | | std::string d_name; |
129 | | std::string d_value; |
130 | | }; |
131 | | |
132 | | string reason; |
133 | | DNSName domain; |
134 | | timespec until{}; |
135 | | std::shared_ptr<TagSettings> tagSettings{nullptr}; |
136 | | mutable std::atomic<uint32_t> blocks{0}; |
137 | | DNSAction::Action action{DNSAction::Action::None}; |
138 | | bool warning{false}; |
139 | | bool bpf{false}; |
140 | | }; |
141 | | |
142 | | using dnsdist_ffi_dynamic_block_inserted_hook = std::function<void(uint8_t type, const char* key, const char* reason, uint8_t action, uint64_t duration, bool warning)>; |
143 | | using ClientAddressDynamicRules = NetmaskTree<DynBlock, AddressAndPortRange>; |
144 | | using SuffixDynamicRules = SuffixMatchTree<DynBlock>; |
145 | | |
146 | | class DynBlockRulesGroup |
147 | | { |
148 | | public: |
149 | | struct DynBlockRule |
150 | | { |
151 | 0 | DynBlockRule() = default; |
152 | | DynBlockRule(const std::string& blockReason, uint32_t blockDuration, uint32_t rate, uint32_t warningRate, uint32_t seconds, DNSAction::Action action) : |
153 | 0 | d_blockReason(blockReason), d_blockDuration(blockDuration), d_rate(rate), d_warningRate(warningRate), d_seconds(seconds), d_action(action), d_enabled(true) |
154 | 0 | { |
155 | 0 | } |
156 | | |
157 | | bool matches(const struct timespec& when); |
158 | | bool rateExceeded(uint32_t count, const struct timespec& now) const; |
159 | | bool warningRateExceeded(uint32_t count, const struct timespec& now) const; |
160 | | |
161 | | bool isEnabled() const |
162 | 0 | { |
163 | 0 | return d_enabled; |
164 | 0 | } |
165 | | |
166 | | std::string toString() const; |
167 | | |
168 | | std::string d_blockReason; |
169 | | std::shared_ptr<DynBlock::TagSettings> d_tagSettings; |
170 | | struct timespec d_cutOff; |
171 | | struct timespec d_minTime; |
172 | | uint32_t d_blockDuration{0}; |
173 | | uint32_t d_rate{0}; |
174 | | uint32_t d_warningRate{0}; |
175 | | uint32_t d_seconds{0}; |
176 | | DNSAction::Action d_action{DNSAction::Action::None}; |
177 | | bool d_enabled{false}; |
178 | | }; |
179 | | |
180 | | struct DynBlockRatioRule : DynBlockRule |
181 | | { |
182 | 0 | DynBlockRatioRule() = default; |
183 | | DynBlockRatioRule(const std::string& blockReason, uint32_t blockDuration, double ratio, double warningRatio, uint32_t seconds, DNSAction::Action action, size_t minimumNumberOfResponses) : |
184 | 0 | DynBlockRule(blockReason, blockDuration, 0, 0, seconds, action), d_minimumNumberOfResponses(minimumNumberOfResponses), d_ratio(ratio), d_warningRatio(warningRatio) |
185 | 0 | { |
186 | 0 | } |
187 | | |
188 | | bool ratioExceeded(uint32_t total, uint32_t count) const; |
189 | | bool warningRatioExceeded(uint32_t total, uint32_t count) const; |
190 | | std::string toString() const; |
191 | | |
192 | | size_t d_minimumNumberOfResponses{0}; |
193 | | double d_ratio{0.0}; |
194 | | double d_warningRatio{0.0}; |
195 | | }; |
196 | | |
197 | | struct DynBlockAllowedRCodesRatioRule : DynBlockRatioRule |
198 | | { |
199 | 0 | DynBlockAllowedRCodesRatioRule() = default; |
200 | | DynBlockAllowedRCodesRatioRule(const std::unordered_set<uint8_t>& rcodes, const std::string& blockReason, uint32_t blockDuration, double ratio, double warningRatio, uint32_t seconds, DNSAction::Action action, size_t minimumNumberOfResponses) : |
201 | 0 | DynBlockRatioRule(blockReason, blockDuration, ratio, warningRatio, seconds, action, minimumNumberOfResponses), d_allowedRCodes(rcodes) |
202 | 0 | { |
203 | 0 | } |
204 | | bool isRCodeAllowed(uint8_t rcode) const; |
205 | | std::string toString() const; |
206 | | |
207 | | std::unordered_set<uint8_t> d_allowedRCodes{}; |
208 | | }; |
209 | | |
210 | | struct DynBlockCacheMissRatioRule : public DynBlockRatioRule |
211 | | { |
212 | 0 | DynBlockCacheMissRatioRule() = default; |
213 | | DynBlockCacheMissRatioRule(const std::string& blockReason, uint32_t blockDuration, double ratio, double warningRatio, uint32_t seconds, DNSAction::Action action, size_t minimumNumberOfResponses, double minimumGlobalCacheHitRatio) : |
214 | 0 | DynBlockRatioRule(blockReason, blockDuration, ratio, warningRatio, seconds, action, minimumNumberOfResponses), d_minimumGlobalCacheHitRatio(minimumGlobalCacheHitRatio) |
215 | 0 | { |
216 | 0 | } |
217 | | |
218 | | bool checkGlobalCacheHitRatio() const; |
219 | | bool ratioExceeded(uint32_t total, uint32_t count) const; |
220 | | bool warningRatioExceeded(uint32_t total, uint32_t count) const; |
221 | | std::string toString() const; |
222 | | |
223 | | double d_minimumGlobalCacheHitRatio{0.0}; |
224 | | }; |
225 | | |
226 | | private: |
227 | | struct Counts |
228 | | { |
229 | | std::map<uint8_t, uint64_t> d_rcodeCounts; |
230 | | std::map<uint16_t, uint64_t> d_qtypeCounts; |
231 | | uint64_t queries{0}; |
232 | | uint64_t responses{0}; |
233 | | uint64_t respBytes{0}; |
234 | | uint64_t cacheMisses{0}; |
235 | | uint64_t notAllowedRCodes{0}; |
236 | | }; |
237 | | using counts_t = std::unordered_map<AddressAndPortRange, Counts, AddressAndPortRange::hash>; |
238 | | |
239 | | public: |
240 | | DynBlockRulesGroup() |
241 | 0 | { |
242 | 0 | } |
243 | | |
244 | | void setQueryRate(DynBlockRule&& rule) |
245 | 0 | { |
246 | 0 | d_queryRateRule = std::move(rule); |
247 | 0 | } |
248 | | |
249 | | /* rate is in bytes per second */ |
250 | | void setResponseByteRate(DynBlockRule&& rule) |
251 | 0 | { |
252 | 0 | d_respRateRule = std::move(rule); |
253 | 0 | } |
254 | | |
255 | | void setRCodeRate(uint8_t rcode, DynBlockRule&& rule) |
256 | 0 | { |
257 | 0 | d_rcodeRules[rcode] = std::move(rule); |
258 | 0 | } |
259 | | |
260 | | void setRCodeRatio(uint8_t rcode, DynBlockRatioRule&& rule) |
261 | 0 | { |
262 | 0 | d_rcodeRatioRules[rcode] = std::move(rule); |
263 | 0 | } |
264 | | |
265 | | void setAllowedRCodesRatio(DynBlockAllowedRCodesRatioRule&& rule) |
266 | 0 | { |
267 | 0 | d_allowedRCodesRatioRule = std::move(rule); |
268 | 0 | } |
269 | | |
270 | | void setQTypeRate(uint16_t qtype, DynBlockRule&& rule) |
271 | 0 | { |
272 | 0 | d_qtypeRules[qtype] = std::move(rule); |
273 | 0 | } |
274 | | |
275 | | void setCacheMissRatio(DynBlockCacheMissRatioRule&& rule) |
276 | 0 | { |
277 | 0 | d_respCacheMissRatioRule = std::move(rule); |
278 | 0 | } |
279 | | |
280 | | using smtVisitor_t = std::function<std::tuple<bool, std::optional<std::string>, std::optional<int>>(const StatNode&, const StatNode::Stat&, const StatNode::Stat&)>; |
281 | | |
282 | | void setSuffixMatchRule(DynBlockRule&& rule, smtVisitor_t visitor) |
283 | 0 | { |
284 | 0 | d_suffixMatchRule = std::move(rule); |
285 | 0 | d_smtVisitor = std::move(visitor); |
286 | 0 | } |
287 | | |
288 | | void setSuffixMatchRuleFFI(DynBlockRule&& rule, dnsdist_ffi_stat_node_visitor_t visitor) |
289 | 0 | { |
290 | 0 | d_suffixMatchRule = std::move(rule); |
291 | 0 | d_smtVisitorFFI = std::move(visitor); |
292 | 0 | } |
293 | | |
294 | | void setNewBlockHook(const dnsdist_ffi_dynamic_block_inserted_hook& callback) |
295 | 0 | { |
296 | 0 | d_newBlockHook = callback; |
297 | 0 | } |
298 | | |
299 | | void setMasks(uint8_t v4, uint8_t v6, uint8_t port) |
300 | 0 | { |
301 | 0 | d_v4Mask = v4; |
302 | 0 | d_v6Mask = v6; |
303 | 0 | d_portMask = port; |
304 | 0 | } |
305 | | |
306 | | void apply() |
307 | 0 | { |
308 | 0 | timespec now{}; |
309 | 0 | gettime(&now); |
310 | |
|
311 | 0 | apply(now); |
312 | 0 | } |
313 | | |
314 | | void apply(const timespec& now); |
315 | | |
316 | | void excludeRange(const Netmask& range) |
317 | 0 | { |
318 | 0 | d_excludedSubnets.addMask(range); |
319 | 0 | } |
320 | | |
321 | | void excludeRange(const NetmaskGroup& group) |
322 | 0 | { |
323 | 0 | d_excludedSubnets.addMasks(group, true); |
324 | 0 | } |
325 | | |
326 | | void includeRange(const Netmask& range) |
327 | 0 | { |
328 | 0 | d_excludedSubnets.addMask(range, false); |
329 | 0 | } |
330 | | |
331 | | void includeRange(const NetmaskGroup& group) |
332 | 0 | { |
333 | 0 | d_excludedSubnets.addMasks(group, false); |
334 | 0 | } |
335 | | |
336 | | void removeRange(const Netmask& range) |
337 | 0 | { |
338 | 0 | d_excludedSubnets.deleteMask(range); |
339 | 0 | } |
340 | | |
341 | | void removeRange(const NetmaskGroup& group) |
342 | 0 | { |
343 | 0 | d_excludedSubnets.deleteMasks(group); |
344 | 0 | } |
345 | | |
346 | | void excludeDomain(const DNSName& domain) |
347 | 0 | { |
348 | 0 | d_excludedDomains.add(domain); |
349 | 0 | } |
350 | | |
351 | | std::string toString() const |
352 | 0 | { |
353 | 0 | std::stringstream result; |
354 | |
|
355 | 0 | result << "Query rate rule: " << d_queryRateRule.toString() << std::endl; |
356 | 0 | result << "Response rate rule: " << d_respRateRule.toString() << std::endl; |
357 | 0 | result << "SuffixMatch rule: " << d_suffixMatchRule.toString() << std::endl; |
358 | 0 | result << "Response cache-miss ratio rule: " << d_respCacheMissRatioRule.toString() << std::endl; |
359 | 0 | result << "RCode rules: " << std::endl; |
360 | 0 | for (const auto& rule : d_rcodeRules) { |
361 | 0 | result << "- " << RCode::to_s(rule.first) << ": " << rule.second.toString() << std::endl; |
362 | 0 | } |
363 | 0 | for (const auto& rule : d_rcodeRatioRules) { |
364 | 0 | result << "- " << RCode::to_s(rule.first) << ": " << rule.second.toString() << std::endl; |
365 | 0 | } |
366 | 0 | result << "Allowed rcodes ratio rule: " << d_allowedRCodesRatioRule.toString() << std::endl; |
367 | 0 | result << "QType rules: " << std::endl; |
368 | 0 | for (const auto& rule : d_qtypeRules) { |
369 | 0 | result << "- " << QType(rule.first).toString() << ": " << rule.second.toString() << std::endl; |
370 | 0 | } |
371 | 0 | result << "Excluded Subnets: " << d_excludedSubnets.toString() << std::endl; |
372 | 0 | result << "Excluded Domains: " << d_excludedDomains.toString() << std::endl; |
373 | |
|
374 | 0 | return result.str(); |
375 | 0 | } |
376 | | |
377 | | void setQuiet(bool quiet) |
378 | 0 | { |
379 | 0 | d_beQuiet = quiet; |
380 | 0 | } |
381 | | |
382 | | private: |
383 | | void applySMT(const struct timespec& now, StatNode& statNodeRoot); |
384 | | bool checkIfQueryTypeMatches(const Rings::Query& query); |
385 | | bool checkIfResponseCodeMatches(const Rings::Response& response); |
386 | | void addOrRefreshBlock(std::optional<ClientAddressDynamicRules>& blocks, const struct timespec& now, const AddressAndPortRange& requestor, const DynBlockRule& rule, bool& updated, bool warning); |
387 | | void addOrRefreshBlockSMT(SuffixDynamicRules& blocks, const struct timespec& now, const DNSName& name, const DynBlockRule& rule, bool& updated); |
388 | | |
389 | | void addBlock(std::optional<ClientAddressDynamicRules>& blocks, const struct timespec& now, const AddressAndPortRange& requestor, const DynBlockRule& rule, bool& updated) |
390 | 0 | { |
391 | 0 | addOrRefreshBlock(blocks, now, requestor, rule, updated, false); |
392 | 0 | } |
393 | | |
394 | | void handleWarning(std::optional<ClientAddressDynamicRules>& blocks, const struct timespec& now, const AddressAndPortRange& requestor, const DynBlockRule& rule, bool& updated) |
395 | 0 | { |
396 | 0 | addOrRefreshBlock(blocks, now, requestor, rule, updated, true); |
397 | 0 | } |
398 | | |
399 | | bool hasQueryRules() const |
400 | 0 | { |
401 | 0 | return d_queryRateRule.isEnabled() || !d_qtypeRules.empty(); |
402 | 0 | } |
403 | | |
404 | | bool hasResponseRules() const |
405 | 0 | { |
406 | 0 | return d_respRateRule.isEnabled() || !d_rcodeRules.empty() || !d_rcodeRatioRules.empty() || d_allowedRCodesRatioRule.isEnabled() || d_respCacheMissRatioRule.isEnabled(); |
407 | 0 | } |
408 | | |
409 | | bool hasSuffixMatchRules() const |
410 | 0 | { |
411 | 0 | return d_suffixMatchRule.isEnabled(); |
412 | 0 | } |
413 | | |
414 | | bool hasRules() const |
415 | 0 | { |
416 | 0 | return hasQueryRules() || hasResponseRules(); |
417 | 0 | } |
418 | | |
419 | | void processQueryRules(counts_t& counts, const struct timespec& now); |
420 | | void processResponseRules(counts_t& counts, StatNode& root, const struct timespec& now); |
421 | | |
422 | | std::map<uint8_t, DynBlockRule> d_rcodeRules; |
423 | | std::map<uint8_t, DynBlockRatioRule> d_rcodeRatioRules; |
424 | | std::map<uint16_t, DynBlockRule> d_qtypeRules; |
425 | | DynBlockRule d_queryRateRule; |
426 | | DynBlockRule d_respRateRule; |
427 | | DynBlockRule d_suffixMatchRule; |
428 | | DynBlockCacheMissRatioRule d_respCacheMissRatioRule; |
429 | | DynBlockAllowedRCodesRatioRule d_allowedRCodesRatioRule; |
430 | | NetmaskGroup d_excludedSubnets; |
431 | | SuffixMatchNode d_excludedDomains; |
432 | | smtVisitor_t d_smtVisitor; |
433 | | dnsdist_ffi_stat_node_visitor_t d_smtVisitorFFI; |
434 | | dnsdist_ffi_dynamic_block_inserted_hook d_newBlockHook; |
435 | | uint8_t d_v6Mask{128}; |
436 | | uint8_t d_v4Mask{32}; |
437 | | uint8_t d_portMask{0}; |
438 | | bool d_beQuiet{false}; |
439 | | }; |
440 | | |
441 | | class DynBlockMaintenance |
442 | | { |
443 | | public: |
444 | | static void run(); |
445 | | |
446 | | /* return the (cached) number of hits per second for the top offenders, averaged over 60s */ |
447 | | static std::map<std::string, std::list<std::pair<AddressAndPortRange, uint32_t>>> getHitsForTopNetmasks(); |
448 | | static std::map<std::string, std::list<std::pair<DNSName, uint32_t>>> getHitsForTopSuffixes(); |
449 | | |
450 | | /* get the top offenders based on the current value of the counters */ |
451 | | static std::map<std::string, std::list<std::pair<AddressAndPortRange, uint32_t>>> getTopNetmasks(size_t topN); |
452 | | static std::map<std::string, std::list<std::pair<DNSName, uint32_t>>> getTopSuffixes(size_t topN); |
453 | | static void purgeExpired(const struct timespec& now); |
454 | | |
455 | | private: |
456 | | static void collectMetrics(); |
457 | | static void generateMetrics(); |
458 | | |
459 | | struct MetricsSnapshot |
460 | | { |
461 | | std::map<std::string, std::list<std::pair<AddressAndPortRange, uint32_t>>> nmgData; |
462 | | std::map<std::string, std::list<std::pair<DNSName, uint32_t>>> smtData; |
463 | | }; |
464 | | |
465 | | struct Tops |
466 | | { |
467 | | std::map<std::string, std::list<std::pair<AddressAndPortRange, uint32_t>>> topNMGsByReason; |
468 | | std::map<std::string, std::list<std::pair<DNSName, uint32_t>>> topSMTsByReason; |
469 | | }; |
470 | | |
471 | | static LockGuarded<Tops> s_tops; |
472 | | /* s_metricsData should only be accessed by the dynamic blocks maintenance thread so it does not need a lock */ |
473 | | // need N+1 datapoints to be able to do the diff after a collection point has been reached |
474 | | static std::list<MetricsSnapshot> s_metricsData; |
475 | | static constexpr size_t s_topN{20}; |
476 | | }; |
477 | | |
478 | | namespace dnsdist::DynamicBlocks |
479 | | { |
480 | | bool addOrRefreshBlock(ClientAddressDynamicRules& blocks, const timespec& now, const AddressAndPortRange& requestor, DynBlock&& dblock, bool beQuiet); |
481 | | bool addOrRefreshBlockSMT(SuffixDynamicRules& blocks, const timespec& now, DynBlock&& dblock, bool beQuiet); |
482 | | |
483 | | const ClientAddressDynamicRules& getClientAddressDynamicRules(); |
484 | | const SuffixDynamicRules& getSuffixDynamicRules(); |
485 | | ClientAddressDynamicRules getClientAddressDynamicRulesCopy(); |
486 | | SuffixDynamicRules getSuffixDynamicRulesCopy(); |
487 | | void setClientAddressDynamicRules(ClientAddressDynamicRules&& rules); |
488 | | void setSuffixDynamicRules(SuffixDynamicRules&& rules); |
489 | | void clearClientAddressDynamicRules(); |
490 | | void clearSuffixDynamicRules(); |
491 | | |
492 | | void registerGroup(std::shared_ptr<DynBlockRulesGroup>& group); |
493 | | void runRegisteredGroups(LuaContext& luaCtx); |
494 | | } |
495 | | #endif /* DISABLE_DYNBLOCKS */ |