]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: Add an optional name to rules 9746/head
authorRemi Gacogne <remi.gacogne@powerdns.com>
Wed, 18 Nov 2020 10:10:39 +0000 (11:10 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Wed, 18 Nov 2020 10:10:39 +0000 (11:10 +0100)
pdns/dnsdist-console.cc
pdns/dnsdist-lua-actions.cc
pdns/dnsdist-lua-rules.cc
pdns/dnsdist-lua.hh
pdns/dnsdist-web.cc
pdns/dnsdist.hh
pdns/dnsdistdist/docs/rules-actions.rst

index e3373f54bd6a1275d876cfec21c492b29966f2e6..b1b589d5075abe67de4d68d4bad7ba739c2a1747 100644 (file)
@@ -343,7 +343,7 @@ void doConsole()
 const std::vector<ConsoleKeyword> g_consoleKeywords{
   /* keyword, function, parameters, description */
   { "addACL", true, "netmask", "add to the ACL set who can use this server" },
-  { "addAction", true, "DNS rule, DNS action [, {uuid=\"UUID\"}]", "add a rule" },
+  { "addAction", true, "DNS rule, DNS action [, {uuid=\"UUID\", name=\"name\"}]", "add a rule" },
   { "addBPFFilterDynBlocks", true, "addresses, dynbpf[[, seconds=10], msg]", "This is the eBPF equivalent of addDynBlocks(), blocking a set of addresses for (optionally) a number of seconds, using an eBPF dynamic filter" },
   { "addConsoleACL", true, "netmask", "add a netmask to the console ACL" },
   { "addDNSCryptBind", true, "\"127.0.0.1:8443\", \"provider name\", \"/path/to/resolver.cert\", \"/path/to/resolver.key\", {reusePort=false, tcpFastOpenQueueSize=0, interface=\"\", cpus={}}", "listen to incoming DNSCrypt queries on 127.0.0.1 port 8443, with a provider name of `provider name`, using a resolver certificate and associated key stored respectively in the `resolver.cert` and `resolver.key` files. The fifth optional parameter is a table of parameters" },
@@ -351,9 +351,9 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "addDynBlocks", true, "addresses, message[, seconds[, action]]", "block the set of addresses with message `msg`, for `seconds` seconds (10 by default), applying `action` (default to the one set with `setDynBlocksAction()`)" },
   { "addDynBlockSMT", true, "names, message[, seconds [, action]]", "block the set of names with message `msg`, for `seconds` seconds (10 by default), applying `action` (default to the one set with `setDynBlocksAction()`)" },
   { "addLocal", true, "addr [, {doTCP=true, reusePort=false, tcpFastOpenQueueSize=0, interface=\"\", cpus={}}]", "add `addr` to the list of addresses we listen on" },
-  { "addCacheHitResponseAction", true, "DNS rule, DNS response action [, {uuid=\"UUID\"}]", "add a cache hit response rule" },
-  { "addResponseAction", true, "DNS rule, DNS response action [, {uuid=\"UUID\"}]", "add a response rule" },
-  { "addSelfAnsweredResponseAction", true, "DNS rule, DNS response action [, {uuid=\"UUID\"}]", "add a self-answered response rule" },
+  { "addCacheHitResponseAction", true, "DNS rule, DNS response action [, {uuid=\"UUID\", name=\"name\"}}]", "add a cache hit response rule" },
+  { "addResponseAction", true, "DNS rule, DNS response action [, {uuid=\"UUID\", name=\"name\"}}]", "add a response rule" },
+  { "addSelfAnsweredResponseAction", true, "DNS rule, DNS response action [, {uuid=\"UUID\", name=\"name\"}}]", "add a self-answered response rule" },
   { "addTLSLocal", true, "addr, certFile(s), keyFile(s) [,params]", "listen to incoming DNS over TLS queries on the specified address using the specified certificate (or list of) and key (or list of). The last parameter is a table" },
   { "AllowAction", true, "", "let these packets go through" },
   { "AllowResponseAction", true, "", "let these packets go through" },
@@ -471,7 +471,7 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "newPacketCache", true, "maxEntries[, maxTTL=86400, minTTL=0, temporaryFailureTTL=60, staleTTL=60, dontAge=false, numberOfShards=1, deferrableInsertLock=true, options={}]", "return a new Packet Cache" },
   { "newQPSLimiter", true, "rate, burst", "configure a QPS limiter with that rate and that burst capacity" },
   { "newRemoteLogger", true, "address:port [, timeout=2, maxQueuedEntries=100, reconnectWaitTime=1]", "create a Remote Logger object, to use with `RemoteLogAction()` and `RemoteLogResponseAction()`" },
-  { "newRuleAction", true, "DNS rule, DNS action [, {uuid=\"UUID\"}]", "return a pair of DNS Rule and DNS Action, to be used with `setRules()`" },
+  { "newRuleAction", true, "DNS rule, DNS action [, {uuid=\"UUID\", name=\"name\"}]", "return a pair of DNS Rule and DNS Action, to be used with `setRules()`" },
   { "newServer", true, "{address=\"ip:port\", qps=1000, order=1, weight=10, pool=\"abuse\", retries=5, tcpConnectTimeout=5, tcpSendTimeout=30, tcpRecvTimeout=30, checkName=\"a.root-servers.net.\", checkType=\"A\", maxCheckFailures=1, mustResolve=false, useClientSubnet=true, source=\"address|interface name|address@interface\", sockets=1, reconnectOnUp=false}", "instantiate a server" },
   { "newServerPolicy", true, "name, function", "create a policy object from a Lua function" },
   { "newSuffixMatchNode", true, "", "returns a new SuffixMatchNode" },
@@ -503,10 +503,10 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "RemoteLogAction", true, "RemoteLogger [, alterFunction [, serverID]]", "send the content of this query to a remote logger via Protocol Buffer. `alterFunction` is a callback, receiving a DNSQuestion and a DNSDistProtoBufMessage, that can be used to modify the Protocol Buffer content, for example for anonymization purposes. `serverID` is the server identifier." },
   { "RemoteLogResponseAction", true, "RemoteLogger [,alterFunction [,includeCNAME [, serverID]]]", "send the content of this response to a remote logger via Protocol Buffer. `alterFunction` is the same callback than the one in `RemoteLogAction` and `includeCNAME` indicates whether CNAME records inside the response should be parsed and exported. The default is to only exports A and AAAA records. `serverID` is the server identifier." },
   { "rmACL", true, "netmask", "remove netmask from ACL" },
-  { "rmCacheHitResponseRule", true, "id", "remove cache hit response rule in position 'id', or whose uuid matches if 'id' is an UUID string" },
-  { "rmResponseRule", true, "id", "remove response rule in position 'id', or whose uuid matches if 'id' is an UUID string" },
-  { "rmRule", true, "id", "remove rule in position 'id', or whose uuid matches if 'id' is an UUID string" },
-  { "rmSelfAnsweredResponseRule", true, "id", "remove self-answered response rule in position 'id', or whose uuid matches if 'id' is an UUID string" },
+  { "rmCacheHitResponseRule", true, "id", "remove cache hit response rule in position 'id', or whose uuid matches if 'id' is an UUID string, or finally whose name matches if 'id' is a string but not a valid UUID" },
+  { "rmResponseRule", true, "id", "remove response rule in position 'id', or whose uuid matches if 'id' is an UUID string, or finally whose name matches if 'id' is a string but not a valid UUID" },
+  { "rmRule", true, "id", "remove rule in position 'id', or whose uuid matches if 'id' is an UUID string, or finally whose name matches if 'id' is a string but not a valid UUID" },
+  { "rmSelfAnsweredResponseRule", true, "id", "remove self-answered response rule in position 'id', or whose uuid matches if 'id' is an UUID string, or finally whose name matches if 'id' is a string but not a valid UUID" },
   { "rmServer", true, "id", "remove server with index 'id' or whose uuid matches if 'id' is an UUID string" },
   { "roundrobin", false, "", "Simple round robin over available servers" },
   { "sendCustomTrap", true, "str", "send a custom `SNMP` trap from Lua, containing the `str` string"},
index aef1511f4657462859f788cafd5c156b09ed2df1..9ea2eedefd1d3ab92946c446f1b86f6d99baa313 100644 (file)
@@ -1456,13 +1456,14 @@ template<typename T, typename ActionT>
 static void addAction(GlobalStateHolder<vector<T> > *someRulActions, const luadnsrule_t& var, const std::shared_ptr<ActionT>& action, boost::optional<luaruleparams_t>& params) {
   setLuaSideEffect();
 
+  std::string name;
   boost::uuids::uuid uuid;
   uint64_t creationOrder;
-  parseRuleParams(params, uuid, creationOrder);
+  parseRuleParams(params, uuid, name, creationOrder);
 
-  auto rule=makeRule(var);
-  someRulActions->modify([&rule, &action, &uuid, creationOrder](vector<T>& rulactions){
-      rulactions.push_back({std::move(rule), std::move(action), std::move(uuid), creationOrder});
+  auto rule = makeRule(var);
+  someRulActions->modify([&rule, &action, &uuid, creationOrder, &name](vector<T>& rulactions){
+    rulactions.push_back({std::move(rule), std::move(action), std::move(name), std::move(uuid), creationOrder});
     });
 }
 
@@ -1510,10 +1511,11 @@ void setupLuaActions(LuaContext& luaCtx)
   luaCtx.writeFunction("newRuleAction", [](luadnsrule_t dnsrule, std::shared_ptr<DNSAction> action, boost::optional<luaruleparams_t> params) {
       boost::uuids::uuid uuid;
       uint64_t creationOrder;
-      parseRuleParams(params, uuid, creationOrder);
+      std::string name;
+      parseRuleParams(params, uuid, name, creationOrder);
 
-      auto rule=makeRule(dnsrule);
-      DNSDistRuleAction ra({std::move(rule), action, uuid, creationOrder});
+      auto rule = makeRule(dnsrule);
+      DNSDistRuleAction ra({std::move(rule), action, std::move(name), uuid, creationOrder});
       return std::make_shared<DNSDistRuleAction>(ra);
     });
 
index a3124d758d0067f020862b9326e1b223c88492a1..e5af721c6d4b32ad70b2e6c726d2ab4071d5d31f 100644 (file)
@@ -67,7 +67,7 @@ static boost::uuids::uuid makeRuleID(std::string& id)
   return getUniqueID(id);
 }
 
-void parseRuleParams(boost::optional<luaruleparams_t> params, boost::uuids::uuid& uuid, uint64_t& creationOrder)
+void parseRuleParams(boost::optional<luaruleparams_t> params, boost::uuids::uuid& uuid, std::string& name, uint64_t& creationOrder)
 {
   static uint64_t s_creationOrder = 0;
 
@@ -77,6 +77,9 @@ void parseRuleParams(boost::optional<luaruleparams_t> params, boost::uuids::uuid
     if (params->count("uuid")) {
       uuidStr = boost::get<std::string>((*params)["uuid"]);
     }
+    if (params->count("name")) {
+      name = boost::get<std::string>((*params)["name"]);
+    }
   }
 
   uuid = makeRuleID(uuidStr);
@@ -103,20 +106,20 @@ static std::string rulesToString(const std::vector<T>& rules, boost::optional<ru
   }
 
   if (showUUIDs) {
-    boost::format fmt("%-3d %-38s %9d %9d %-56s %s\n");
-    result += (fmt % "#" % "UUID" % "Cr. Order" % "Matches" % "Rule" % "Action").str();
+    boost::format fmt("%-3d %-30s %-38s %9d %9d %-56s %s\n");
+    result += (fmt % "#" % "Name" % "UUID" % "Cr. Order" % "Matches" % "Rule" % "Action").str();
     for(const auto& lim : rules) {
-      string name = lim.d_rule->toString().substr(0, truncateRuleWidth);
-      result += (fmt % num % boost::uuids::to_string(lim.d_id) % lim.d_creationOrder % lim.d_rule->d_matches % name % lim.d_action->toString()).str();
+      string desc = lim.d_rule->toString().substr(0, truncateRuleWidth);
+      result += (fmt % num % lim.d_name % boost::uuids::to_string(lim.d_id) % lim.d_creationOrder % lim.d_rule->d_matches % desc % lim.d_action->toString()).str();
       ++num;
     }
   }
   else {
-    boost::format fmt("%-3d %9d %-56s %s\n");
-    result += (fmt % "#" % "Matches" % "Rule" % "Action").str();
+    boost::format fmt("%-3d %-30s %9d %-56s %s\n");
+    result += (fmt % "#" % "Name" % "Matches" % "Rule" % "Action").str();
     for(const auto& lim : rules) {
-      string name = lim.d_rule->toString().substr(0, truncateRuleWidth);
-      result += (fmt % num % lim.d_rule->d_matches % name % lim.d_action->toString()).str();
+      string desc = lim.d_rule->toString().substr(0, truncateRuleWidth);
+      result += (fmt % num % lim.d_name %  lim.d_rule->d_matches % desc % lim.d_action->toString()).str();
       ++num;
     }
   }
@@ -136,13 +139,25 @@ static void rmRule(GlobalStateHolder<vector<T> > *someRulActions, boost::variant
   setLuaSideEffect();
   auto rules = someRulActions->getCopy();
   if (auto str = boost::get<std::string>(&id)) {
-    const auto uuid = getUniqueID(*str);
-    if (rules.erase(std::remove_if(rules.begin(),
-                                    rules.end(),
-                                    [uuid](const T& a) { return a.d_id == uuid; }),
-                    rules.end()) == rules.end()) {
-      g_outputBuffer = "Error: no rule matched\n";
-      return;
+    try {
+      const auto uuid = getUniqueID(*str);
+      if (rules.erase(std::remove_if(rules.begin(),
+                                     rules.end(),
+                                     [uuid](const T& a) { return a.d_id == uuid; }),
+                      rules.end()) == rules.end()) {
+        g_outputBuffer = "Error: no rule matched\n";
+        return;
+      }
+    }
+    catch (const std::runtime_error& e) {
+      /* it was not an UUID, let's see if it was a name instead */
+      if (rules.erase(std::remove_if(rules.begin(),
+                                     rules.end(),
+                                     [&str](const T& a) { return a.d_name == *str; }),
+                      rules.end()) == rules.end()) {
+        g_outputBuffer = "Error: no rule matched\n";
+        return;
+      }
     }
   }
   else if (auto pos = boost::get<unsigned int>(&id)) {
@@ -299,8 +314,8 @@ void setupLuaRules(LuaContext& luaCtx)
           for (const auto& pair : newruleactions) {
             const auto& newruleaction = pair.second;
             if (newruleaction->d_action) {
-              auto rule=makeRule(newruleaction->d_rule);
-              gruleactions.push_back({std::move(rule), newruleaction->d_action, newruleaction->d_id, newruleaction->d_creationOrder});
+              auto rule = makeRule(newruleaction->d_rule);
+              gruleactions.push_back({std::move(rule), newruleaction->d_action, newruleaction->d_name, newruleaction->d_id, newruleaction->d_creationOrder});
             }
           }
         });
index 97f2e1d0de3fa48335243d01e660cb2dc6f63a52..25d5e10c8e195b6f9c0ff02fc18382f6aadd4195 100644 (file)
@@ -90,7 +90,7 @@ private:
 typedef boost::variant<string, vector<pair<int, string>>, std::shared_ptr<DNSRule>, DNSName, vector<pair<int, DNSName> > > luadnsrule_t;
 std::shared_ptr<DNSRule> makeRule(const luadnsrule_t& var);
 typedef std::unordered_map<std::string, boost::variant<std::string> > luaruleparams_t;
-void parseRuleParams(boost::optional<luaruleparams_t> params, boost::uuids::uuid& uuid, uint64_t& creationOrder);
+void parseRuleParams(boost::optional<luaruleparams_t> params, boost::uuids::uuid& uuid, std::string& name, uint64_t& creationOrder);
 
 typedef NetmaskTree<DynBlock> nmts_t;
 
index 2497e9d76c235ab9d41206a3bfb09e6560e1734c..081646026232513b845644be6ddd386c41aad8d5 100644 (file)
@@ -298,6 +298,7 @@ static json11::Json::array someResponseRulesToJson(GlobalStateHolder<vector<T>>*
       {"id", num++},
       {"creationOrder", (double)a.d_creationOrder},
       {"uuid", boost::uuids::to_string(a.d_id)},
+      {"name", a.d_name},
       {"matches", (double)a.d_rule->d_matches},
       {"rule", a.d_rule->toString()},
       {"action", a.d_action->toString()},
@@ -307,7 +308,15 @@ static json11::Json::array someResponseRulesToJson(GlobalStateHolder<vector<T>>*
   return responseRules;
 }
 
-using namespace json11;
+template<typename T>
+static void addRulesToPrometheusOutput(std::ostringstream& output, GlobalStateHolder<vector<T> >& rules)
+{
+  auto localRules = rules.getLocal();
+  for (const auto& entry : *localRules) {
+    std::string id = !entry.d_name.empty() ? entry.d_name : boost::uuids::to_string(entry.d_id);
+    output << "dnsdist_rule_hits{id=\"" << id << "\"} " << entry.d_rule->d_matches << "\n";
+  }
+}
 
 static void handlePrometheus(const YaHTTP::Request& req, YaHTTP::Response& resp)
 {
@@ -651,6 +660,13 @@ static void handlePrometheus(const YaHTTP::Request& req, YaHTTP::Response& resp)
     }
   }
 
+  output << "# HELP dnsdist_rule_hits " << "Number of hits of that rule" << "\n";
+  output << "# TYPE dnsdist_rule_hits " << "counter" << "\n";
+  addRulesToPrometheusOutput(output, g_rulactions);
+  addRulesToPrometheusOutput(output, g_resprulactions);
+  addRulesToPrometheusOutput(output, g_cachehitresprulactions);
+  addRulesToPrometheusOutput(output, g_selfansweredresprulactions);
+
   output << "# HELP dnsdist_info " << "Info from dnsdist, value is always 1" << "\n";
   output << "# TYPE dnsdist_info " << "gauge" << "\n";
   output << "dnsdist_info{version=\"" << VERSION << "\"} " << "1" << "\n";
@@ -659,6 +675,8 @@ static void handlePrometheus(const YaHTTP::Request& req, YaHTTP::Response& resp)
   resp.headers["Content-Type"] = "text/plain";
 }
 
+using namespace json11;
+
 static void handleJSONStats(const YaHTTP::Request& req, YaHTTP::Response& resp)
 {
   handleCORS(req, resp);
@@ -923,9 +941,11 @@ static void handleStats(const YaHTTP::Request& req, YaHTTP::Response& resp)
   }
 
   Json::array rules;
+  /* unfortunately DNSActions have getStats(),
+     and DNSResponseActions do not. */
   auto localRules = g_rulactions.getLocal();
-  num=0;
-  for(const auto& a : *localRules) {
+  num = 0;
+  for (const auto& a : *localRules) {
     Json::object rule{
       {"id", num++},
       {"creationOrder", (double)a.d_creationOrder},
index d9b447e6b7e47ff0d7d39089ffc6e98eb9cac682..2b358b18c423aa89e86444692f5f22c70dd9c74c 100644 (file)
@@ -1027,6 +1027,7 @@ struct DNSDistRuleAction
 {
   std::shared_ptr<DNSRule> d_rule;
   std::shared_ptr<DNSAction> d_action;
+  std::string d_name;
   boost::uuids::uuid d_id;
   uint64_t d_creationOrder;
 };
@@ -1035,6 +1036,7 @@ struct DNSDistResponseRuleAction
 {
   std::shared_ptr<DNSRule> d_rule;
   std::shared_ptr<DNSResponseAction> d_action;
+  std::string d_name;
   boost::uuids::uuid d_id;
   uint64_t d_creationOrder;
 };
index 0dfe2b63c0a780b65eb302e3a87f0e5a7ab6004c..05a6084e4459ae6c7fad71369b82733a0559706b 100644 (file)
@@ -285,6 +285,9 @@ For Rules related to the incoming query:
   .. versionchanged:: 1.3.0
     Added the optional parameter ``options``.
 
+  .. versionchanged:: 1.6.0
+    Added ``name`` to the ``options``.
+
   Add a Rule and Action to the existing rules.
 
   :param DNSrule rule: A DNSRule, e.g. an :func:`AllRule` or a compounded bunch of rules using e.g. :func:`AndRule`
@@ -294,6 +297,7 @@ For Rules related to the incoming query:
   Options:
 
   * ``uuid``: string - UUID to assign to the new rule. By default a random UUID is generated for each rule.
+  * ``name``: string - Name to assign to the new rule.
 
 .. function:: clearRules()
 
@@ -324,6 +328,9 @@ For Rules related to the incoming query:
   .. versionchanged:: 1.3.0
     Added the optional parameter ``options``.
 
+  .. versionchanged:: 1.6.0
+    Added ``name`` to the ``options``.
+
   Return a pair of DNS Rule and DNS Action, to be used with :func:`setRules`.
 
   :param Rule rule: A Rule (see `Matching Packets (Selectors)`_)
@@ -333,6 +340,7 @@ For Rules related to the incoming query:
   Options:
 
   * ``uuid``: string - UUID to assign to the new rule. By default a random UUID is generated for each rule.
+  * ``name``: string - Name to assign to the new rule.
 
 .. function:: setRules(rules)
 
@@ -366,9 +374,12 @@ For Rules related to the incoming query:
   .. versionchanged:: 1.3.0
     ``id`` can now be an UUID.
 
+  .. versionchanged:: 1.6.0
+    ``id`` can now be a string representing the name of the rule.
+
   Remove rule ``id``.
 
-  :param int id: The UUID of the rule to remove if ``id`` is an UUID, its position otherwise
+  :param int id: The position of the rule to remove if ``id`` is numerical, its UUID or name otherwise
 
 For Rules related to responses:
 
@@ -377,6 +388,9 @@ For Rules related to responses:
   .. versionchanged:: 1.3.0
     Added the optional parameter ``options``.
 
+  .. versionchanged:: 1.6.0
+    Added ``name`` to the ``options``.
+
   Add a Rule and Action for responses to the existing rules.
 
   :param DNSRule: A DNSRule, e.g. an :func:`AllRule` or a compounded bunch of rules using e.g. :func:`AndRule`
@@ -386,6 +400,7 @@ For Rules related to responses:
   Options:
 
   * ``uuid``: string - UUID to assign to the new rule. By default a random UUID is generated for each rule.
+  * ``name``: string - Name to assign to the new rule.
 
 .. function:: mvResponseRule(from, to)
 
@@ -406,9 +421,12 @@ For Rules related to responses:
   .. versionchanged:: 1.3.0
     ``id`` can now be an UUID.
 
+  .. versionchanged:: 1.6.0
+    ``id`` can now be a string representing the name of the rule.
+
   Remove response rule ``id``.
 
-  :param int id: The UUID of the rule to remove if ``id`` is an UUID, its position otherwise
+  :param int id: The position of the rule to remove if ``id`` is numerical, its UUID or name otherwise
 
 .. function:: showResponseRules([options])
 
@@ -440,6 +458,9 @@ Functions for manipulating Cache Hit Response Rules:
   .. versionchanged:: 1.3.0
     Added the optional parameter ``options``.
 
+  .. versionchanged:: 1.6.0
+    Added ``name`` to the ``options``.
+
   Add a Rule and ResponseAction for Cache Hits to the existing rules.
 
   :param DNSRule: A DNSRule, e.g. an :func:`AllRule` or a compounded bunch of rules using e.g. :func:`AndRule`
@@ -449,6 +470,7 @@ Functions for manipulating Cache Hit Response Rules:
   Options:
 
   * ``uuid``: string - UUID to assign to the new rule. By default a random UUID is generated for each rule.
+  * ``name``: string - Name to assign to the new rule.
 
 .. function:: mvCacheHitResponseRule(from, to)
 
@@ -473,7 +495,10 @@ Functions for manipulating Cache Hit Response Rules:
   .. versionchanged:: 1.3.0
     ``id`` can now be an UUID.
 
-  :param int id: The UUID of the rule to remove if ``id`` is an UUID, its position otherwise
+  .. versionchanged:: 1.6.0
+    ``id`` can now be a string representing the name of the rule.
+
+  :param int id: The position of the rule to remove if ``id`` is numerical, its UUID or name otherwise
 
 .. function:: showCacheHitResponseRules([options])
 
@@ -506,10 +531,19 @@ Functions for manipulating Self-Answered Response Rules:
 
   .. versionadded:: 1.3.0
 
+  .. versionchanged:: 1.6.0
+    Added ``name`` to the ``options``.
+
   Add a Rule and Action for Self-Answered queries to the existing rules.
 
   :param DNSRule: A DNSRule, e.g. an :func:`AllRule` or a compounded bunch of rules using e.g. :func:`AndRule`
   :param action: The action to take
+  :param table options: A table with key: value pairs with options.
+
+  Options:
+
+  * ``uuid``: string - UUID to assign to the new rule. By default a random UUID is generated for each rule.
+  * ``name``: string - Name to assign to the new rule.
 
 .. function:: mvSelfAnsweredResponseRule(from, to)
 
@@ -531,9 +565,12 @@ Functions for manipulating Self-Answered Response Rules:
 
   .. versionadded:: 1.3.0
 
+  .. versionchanged:: 1.6.0
+    ``id`` can now be a string representing the name of the rule.
+
   Remove self answered response rule ``id``.
 
-  :param int id: The UUID of the rule to remove if ``id`` is an UUID, its position otherwise
+  :param int id: The position of the rule to remove if ``id`` is numerical, its UUID or name otherwise
 
 .. function:: showSelfAnsweredResponseRules([options])