From: Petr Špaček Date: Mon, 6 Jan 2020 18:53:08 +0000 (+0100) Subject: doc: split and redistribute HTTP module pieces into server & monitoring chapters X-Git-Tag: v5.0.0~8^2~24 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c6ac76e1e4b76e8cf3a58ebbf6952332c04a8222;p=thirdparty%2Fknot-resolver.git doc: split and redistribute HTTP module pieces into server & monitoring chapters --- diff --git a/daemon/bindings/net.rst b/daemon/bindings/net.rst index 80ed9b5da..7ce1083bf 100644 --- a/daemon/bindings/net.rst +++ b/daemon/bindings/net.rst @@ -1,7 +1,7 @@ .. _network-configuration: -Network -======= +Server addresses +----------------- Modern Linux distributions use so-called *Systemd socket activation*, which effectively means that IP addresses and ports to listen on are configured @@ -212,18 +212,6 @@ Examples: Following commands are useful in special situations and can be usef with and without systemd socket activation: -.. envvar:: net.ipv6 = true|false - - :return: boolean (default: true) - - Enable/disable using IPv6 for contacting upstream nameservers. - -.. envvar:: net.ipv4 = true|false - - :return: boolean (default: true) - - Enable/disable using IPv4 for contacting upstream nameservers. - .. function:: net.list() :return: Table of bound interfaces. @@ -284,21 +272,6 @@ Following commands are useful in special situations and can be usef with and wit .. tip:: You can use ``net.`` as a shortcut for specific interface, e.g. ``net.eth0`` -.. function:: net.bufsize([udp_bufsize]) - - Get/set maximum EDNS payload size advertised in DNS packets. Default is 4096 bytes and the default will be lowered to value around 1220 bytes in future, once `DNS Flag Day 2020 `_ becomes effective. - - Minimal value allowed by standard :rfc:`6891` is 512 bytes, which is equal to DNS packet size without Extension Mechanisms for DNS. Value 1220 bytes is minimum size required in DNSSEC standard :rfc:`4035`. - - Example output: - - .. code-block:: lua - - > net.bufsize(4096) - nil - > net.bufsize() - 4096 - .. function:: net.tcp_pipeline([len]) Get/set per-client TCP pipeline limit, i.e. the number of outstanding queries that a single client connection can make in parallel. Default is 100. @@ -312,71 +285,50 @@ Following commands are useful in special situations and can be usef with and wit .. warning:: Please note that too large limit may have negative impact on performance and can lead to increased number of SERVFAIL answers. -.. function:: net.outgoing_v4([string address]) - - Get/set the IPv4 address used to perform queries. There is also ``net.outgoing_v6`` for IPv6. - The default is ``nil``, which lets the OS choose any address. +Client +------ +Following settings affect client part of the resolver, i.e. communication between the resolver itself and other DNS servers. +.. envvar:: net.ipv4 = true|false -.. _tls-server-config: - -TLS server -========== -DNS-over-TLS server (:rfc:`7858`) is enabled by default on loopback interface port 853. -Information how to configure listening on specific IP addresses is in previous sections -:ref:`network-configuration`. + :return: boolean (default: true) -By default a self-signed certificate is generated. For serious deployments -it is strongly recommended to configure your own TLS certificates signed -by a trusted CA. This is done using function :c:func:`net.tls()`. + Enable/disable using IPv4 for contacting upstream nameservers. -.. function:: net.tls([cert_path], [key_path]) +.. function:: net.outgoing_v4([string address]) - Get/set path to a server TLS certificate and private key for DNS/TLS. + Get/set the IPv4 address used to perform queries. + The default is ``nil``, which lets the OS choose any address. - Example output: +.. envvar:: net.ipv6 = true|false - .. code-block:: lua + :return: boolean (default: true) - > net.tls("/etc/knot-resolver/server-cert.pem", "/etc/knot-resolver/server-key.pem") - > net.tls() -- print configured paths - ("/etc/knot-resolver/server-cert.pem", "/etc/knot-resolver/server-key.pem") + Enable/disable using IPv6 for contacting upstream nameservers. -.. function:: net.tls_padding([true | false]) +.. function:: net.outgoing_v6([string address]) - Get/set EDNS(0) padding of answers to queries that arrive over TLS - transport. If set to `true` (the default), it will use a sensible - default padding scheme, as implemented by libknot if available at - compile time. If set to a numeric value >= 2 it will pad the - answers to nearest *padding* boundary, e.g. if set to `64`, the - answer will have size of a multiple of 64 (64, 128, 192, ...). If - set to `false` (or a number < 2), it will disable padding entirely. + Get/set the IPv6 address used to perform queries. + The default is ``nil``, which lets the OS choose any address. -.. function:: net.tls_sticket_secret([string with pre-shared secret]) +DNS protocol +------------ +Following settings change low-level details of DNS protocol implementation. +Default values should not be changed except for very special cases. - Set secret for TLS session resumption via tickets, by :rfc:`5077`. +.. function:: net.bufsize([udp_bufsize]) - The server-side key is rotated roughly once per hour. - By default or if called without secret, the key is random. - That is good for long-term forward secrecy, but multiple kresd instances - won't be able to resume each other's sessions. + Get/set maximum EDNS payload size advertised in DNS packets. Default is 4096 bytes and the default will be lowered to value around 1220 bytes in future, once `DNS Flag Day 2020 `_ becomes effective. - If you provide the same secret to multiple instances, they will be able to resume - each other's sessions *without* any further communication between them. - This synchronization works only among instances having the same endianess - and time_t structure and size (`sizeof(time_t)`). + Minimal value allowed by standard :rfc:`6891` is 512 bytes, which is equal to DNS packet size without Extension Mechanisms for DNS. Value 1220 bytes is minimum size required in DNSSEC standard :rfc:`4035`. - **For good security** the secret must have enough entropy to be hard to guess, - and it should still be occasionally rotated manually and securely forgotten, - to reduce the scope of privacy leak in case the - `secret leaks eventually `_. + Example output: - .. warning:: **Setting the secret is probably too risky with TLS <= 1.2**. - GnuTLS stable release supports TLS 1.3 since 3.6.3 (summer 2018). - Therefore setting the secrets should be considered experimental for now - and might not be available on your system. + .. code-block:: lua -.. function:: net.tls_sticket_secret_file([string with path to a file containing pre-shared secret]) + > net.bufsize(4096) + nil + > net.bufsize() + 4096 - The same as :func:`net.tls_sticket_secret`, - except the secret is read from a (binary) file. +.. include:: ../modules/workarounds/README.rst diff --git a/daemon/bindings/net_tlssrv.rst b/daemon/bindings/net_tlssrv.rst new file mode 100644 index 000000000..699cc943e --- /dev/null +++ b/daemon/bindings/net_tlssrv.rst @@ -0,0 +1,62 @@ +.. _tls-server-config: + +DNS-over-TLS server (DoT) +------------------------- +DNS-over-TLS server (:rfc:`7858`) is enabled by default on loopback interface port 853. +Information how to configure listening on specific IP addresses is in previous sections +:ref:`network-configuration`. + +By default a self-signed certificate is generated. For serious deployments +it is strongly recommended to configure your own TLS certificates signed +by a trusted CA. This is done using function :c:func:`net.tls()`. + +.. function:: net.tls([cert_path], [key_path]) + + Get/set path to a server TLS certificate and private key for DNS/TLS. + + Example output: + + .. code-block:: lua + + > net.tls("/etc/knot-resolver/server-cert.pem", "/etc/knot-resolver/server-key.pem") + > net.tls() -- print configured paths + ("/etc/knot-resolver/server-cert.pem", "/etc/knot-resolver/server-key.pem") + +.. function:: net.tls_padding([true | false]) + + Get/set EDNS(0) padding of answers to queries that arrive over TLS + transport. If set to `true` (the default), it will use a sensible + default padding scheme, as implemented by libknot if available at + compile time. If set to a numeric value >= 2 it will pad the + answers to nearest *padding* boundary, e.g. if set to `64`, the + answer will have size of a multiple of 64 (64, 128, 192, ...). If + set to `false` (or a number < 2), it will disable padding entirely. + +.. function:: net.tls_sticket_secret([string with pre-shared secret]) + + Set secret for TLS session resumption via tickets, by :rfc:`5077`. + + The server-side key is rotated roughly once per hour. + By default or if called without secret, the key is random. + That is good for long-term forward secrecy, but multiple kresd instances + won't be able to resume each other's sessions. + + If you provide the same secret to multiple instances, they will be able to resume + each other's sessions *without* any further communication between them. + This synchronization works only among instances having the same endianess + and time_t structure and size (`sizeof(time_t)`). + + **For good security** the secret must have enough entropy to be hard to guess, + and it should still be occasionally rotated manually and securely forgotten, + to reduce the scope of privacy leak in case the + `secret leaks eventually `_. + + .. warning:: **Setting the secret is probably too risky with TLS <= 1.2**. + GnuTLS stable release supports TLS 1.3 since 3.6.3 (summer 2018). + Therefore setting the secrets should be considered experimental for now + and might not be available on your system. + +.. function:: net.tls_sticket_secret_file([string with path to a file containing pre-shared secret]) + + The same as :func:`net.tls_sticket_secret`, + except the secret is read from a (binary) file. diff --git a/doc/config.rst b/doc/config.rst index ee2cbfe9a..d3daaf61a 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -66,9 +66,14 @@ One last thing before we dive into configuring features: Now you know what configuration file to modify, how to read examples and what modules are so you are ready for a real configuration work! .. include:: ../daemon/README.rst -.. include:: ../daemon/bindings/net.rst .. include:: ../daemon/lua/trust_anchors.rst +Networking and protocols +======================== +.. include:: ../daemon/bindings/net.rst +.. include:: ../daemon/bindings/net_tlssrv.rst +.. include:: ../modules/http/README.rst +.. include:: ../modules/http/README.doh.rst Logging, monitoring, diagnostics ================================ @@ -98,10 +103,17 @@ More fine-grained tools are available in following modules: :local: .. include:: ../modules/nsid/README.rst + +.. include:: ../modules/stats/README.rst +.. .. subchapter of stats module .. include:: ../modules/graphite/README.rst +.. .. subchapter of stats module +.. include:: ../modules/http/prometheus.rst + .. include:: ../modules/dnstap/README.rst .. include:: ../modules/watchdog/README.rst .. include:: ../modules/bogus_log/README.rst +.. include:: ../modules/http/trace.rst .. include:: ../modules/ta_sentinel/README.rst .. include:: ../modules/ta_signal_query/README.rst .. include:: ../modules/detect_time_skew/README.rst @@ -116,7 +128,6 @@ Features in this section allow to configure what clients can get access to what :local: .. include:: ../modules/hints/README.rst -.. include:: ../modules/stats/README.rst .. include:: ../modules/policy/README.rst .. include:: ../modules/view/README.rst .. include:: ../modules/daf/README.rst @@ -167,15 +178,8 @@ impact than cache settings and number of instances. .. include:: ../modules/rfc7706.rst .. include:: ../modules/prefill/README.rst .. include:: ../modules/serve_stale/README.rst -.. include:: ../modules/workarounds/README.rst .. include:: ../modules/edns_keepalive/README.rst - -TODO: Other -=========== -.. include:: ../modules/http/README.rst -.. include:: ../modules/http/README.doh.rst - Experimental features ===================== .. include:: ../modules/experimental_dot_auth/README.rst diff --git a/modules/graphite/README.rst b/modules/graphite/README.rst index 0ceef45a7..415bb9e44 100644 --- a/modules/graphite/README.rst +++ b/modules/graphite/README.rst @@ -1,14 +1,13 @@ .. _mod-graphite: Graphite/InfluxDB/Metronome ---------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^ The ``graphite`` sends statistics over the Graphite_ protocol to either Graphite_, Metronome_, InfluxDB_ or any compatible storage. This allows powerful visualization over metrics collected by Knot Resolver. .. tip:: The Graphite server is challenging to get up and running, InfluxDB_ combined with Grafana_ are much easier, and provide richer set of options and available front-ends. Metronome_ by PowerDNS alternatively provides a mini-graphite server for much simpler setups. -Example configuration -^^^^^^^^^^^^^^^^^^^^^ +Example configuration: Only the ``host`` parameter is mandatory. @@ -36,8 +35,7 @@ The module supports sending data to multiple servers at once. } } -Dependencies -^^^^^^^^^^^^ +Dependencies: * `lua cqueues `_ package. diff --git a/modules/http/README.rst b/modules/http/README.rst index 89f89ef7c..a4816ad32 100644 --- a/modules/http/README.rst +++ b/modules/http/README.rst @@ -63,8 +63,8 @@ Now you can reach the web services and APIs, done! .. _mod-http-tls: -Configuring TLS -^^^^^^^^^^^^^^^ +HTTPS (TLS for HTTP) +^^^^^^^^^^^^^^^^^^^^ By default, the web interface starts HTTPS/2 on specified port using an ephemeral TLS certificate that is valid for 90 days and is automatically renewed. It is of @@ -133,73 +133,6 @@ The HTTP module has several built-in services to use. "``/trace/:name/:type``", "Tracking", ":ref:`Trace resolution ` of a DNS query and return the verbose logs." "``/doh``", "DNS-over-HTTP", ":rfc:`8484` endpoint, see :ref:`mod-http-doh`." -Prometheus metrics endpoint -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The module exposes ``/metrics`` endpoint that serves internal metrics in Prometheus_ text format. -You can use it out of the box: - -.. code-block:: bash - - $ curl -k https://localhost:8453/metrics | tail - # TYPE latency histogram - latency_bucket{le=10} 2.000000 - latency_bucket{le=50} 2.000000 - latency_bucket{le=100} 2.000000 - latency_bucket{le=250} 2.000000 - latency_bucket{le=500} 2.000000 - latency_bucket{le=1000} 2.000000 - latency_bucket{le=1500} 2.000000 - latency_bucket{le=+Inf} 2.000000 - latency_count 2.000000 - latency_sum 11.000000 - -You can namespace the metrics in configuration, using `http.prometheus.namespace` attribute: - -.. code-block:: lua - - modules.load('http') - -- Set Prometheus namespace - http.prometheus.namespace = 'resolver_' - -You can also add custom metrics or rewrite existing metrics before they are returned to Prometheus client. - -.. code-block:: lua - - modules.load('http') - -- Add an arbitrary metric to Prometheus - http.prometheus.finalize = function (metrics) - table.insert(metrics, 'build_info{version="1.2.3"} 1') - end - -.. _mod-http-trace: - -Tracing requests -^^^^^^^^^^^^^^^^ - -With the ``/trace`` endpoint you can trace various aspects of the request execution. -The basic mode allows you to resolve a query and trace verbose logs (and messages received): - -.. code-block:: bash - - $ curl https://localhost:8453/trace/e.root-servers.net - [ 8138] [iter] 'e.root-servers.net.' type 'A' created outbound query, parent id 0 - [ 8138] [ rc ] => rank: 020, lowest 020, e.root-servers.net. A - [ 8138] [ rc ] => satisfied from cache - [ 8138] [iter] <= answer received: - ;; ->>HEADER<<- opcode: QUERY; status: NOERROR; id: 8138 - ;; Flags: qr aa QUERY: 1; ANSWER: 0; AUTHORITY: 0; ADDITIONAL: 0 - - ;; QUESTION SECTION - e.root-servers.net. A - - ;; ANSWER SECTION - e.root-servers.net. 3556353 A 192.203.230.10 - - [ 8138] [iter] <= rcode: NOERROR - [ 8138] [resl] finished: 4, queries: 1, mempool: 81952 B - - .. _mod-http-custom-endpoint: How to expose custom services over HTTP @@ -355,7 +288,7 @@ Dependencies $ luarocks install http -* `mmdblua `_ available in LuaRocks +* (*optional*) `mmdblua `_ available in LuaRocks .. code-block:: bash diff --git a/modules/http/prometheus.rst b/modules/http/prometheus.rst new file mode 100644 index 000000000..d98a0a6c4 --- /dev/null +++ b/modules/http/prometheus.rst @@ -0,0 +1,42 @@ +.. _mod-http-prometheus: + +Prometheus metrics endpoint +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The `HTTP module `_ exposes ``/metrics`` endpoint that serves metrics +from :ref:`mod-stats` in Prometheus_ text format. +You can use it as soon as HTTP module is configured: + +.. code-block:: bash + + $ curl -k https://localhost:8453/metrics | tail + # TYPE latency histogram + latency_bucket{le=10} 2.000000 + latency_bucket{le=50} 2.000000 + latency_bucket{le=100} 2.000000 + latency_bucket{le=250} 2.000000 + latency_bucket{le=500} 2.000000 + latency_bucket{le=1000} 2.000000 + latency_bucket{le=1500} 2.000000 + latency_bucket{le=+Inf} 2.000000 + latency_count 2.000000 + latency_sum 11.000000 + +You can namespace the metrics in configuration, using `http.prometheus.namespace` attribute: + +.. code-block:: lua + + modules.load('http') + -- Set Prometheus namespace + http.prometheus.namespace = 'resolver_' + +You can also add custom metrics or rewrite existing metrics before they are returned to Prometheus client. + +.. code-block:: lua + + modules.load('http') + -- Add an arbitrary metric to Prometheus + http.prometheus.finalize = function (metrics) + table.insert(metrics, 'build_info{version="1.2.3"} 1') + end + diff --git a/modules/http/trace.rst b/modules/http/trace.rst new file mode 100644 index 000000000..72328597e --- /dev/null +++ b/modules/http/trace.rst @@ -0,0 +1,29 @@ +.. _mod-http-trace: + +Tracing requests +---------------- + +The `HTTP module `_ provides ``/trace`` endpoint which allows to trace various +aspects of the request execution. The basic mode allows you to resolve a query +and trace verbose logs (and messages received): + +.. code-block:: bash + + $ curl https://localhost:8453/trace/e.root-servers.net + [ 8138] [iter] 'e.root-servers.net.' type 'A' created outbound query, parent id 0 + [ 8138] [ rc ] => rank: 020, lowest 020, e.root-servers.net. A + [ 8138] [ rc ] => satisfied from cache + [ 8138] [iter] <= answer received: + ;; ->>HEADER<<- opcode: QUERY; status: NOERROR; id: 8138 + ;; Flags: qr aa QUERY: 1; ANSWER: 0; AUTHORITY: 0; ADDITIONAL: 0 + + ;; QUESTION SECTION + e.root-servers.net. A + + ;; ANSWER SECTION + e.root-servers.net. 3556353 A 192.203.230.10 + + [ 8138] [iter] <= rcode: NOERROR + [ 8138] [resl] finished: 4, queries: 1, mempool: 81952 B + +See chapter about `HTTP module `_ for further instructions how to load HTTP module. diff --git a/modules/stats/README.rst b/modules/stats/README.rst index 0d19060ec..27f67e88e 100644 --- a/modules/stats/README.rst +++ b/modules/stats/README.rst @@ -3,90 +3,11 @@ Statistics collector -------------------- -This modules gathers various counters from the query resolution and server internals, -and offers them as a key-value storage. Any module may update the metrics or simply hook -in new ones. - -.. code-block:: none - - modules.load('stats') - - -- Enumerate metrics - > stats.list() - [answer.cached] => 486178 - [iterator.tcp] => 490 - [answer.noerror] => 507367 - [answer.total] => 618631 - [iterator.udp] => 102408 - [query.concurrent] => 149 - - -- Query metrics by prefix - > stats.list('iter') - [iterator.udp] => 105104 - [iterator.tcp] => 490 - - -- Set custom metrics from modules - > stats['filter.match'] = 5 - > stats['filter.match'] - 5 - - -- Fetch most common queries - > stats.frequent() - [1] => { - [type] => 2 - [count] => 4 - [name] => cz. - } - - -- Fetch most common queries (sorted by frequency) - > table.sort(stats.frequent(), function (a, b) return a.count > b.count end) - - -- Show recently contacted authoritative servers - > stats.upstreams() - [2a01:618:404::1] => { - [1] => 26 -- RTT - } - [128.241.220.33] => { - [1] => 31 - RTT - } - -Properties -^^^^^^^^^^ - -.. function:: stats.get(key) - - :param string key: i.e. ``"answer.total"`` - :return: ``number`` - -Return nominal value of given metric. - -.. function:: stats.set(key, val) - - :param string key: i.e. ``"answer.total"`` - :param number val: i.e. ``5`` - -Set nominal value of given metric. - -.. function:: stats.list([prefix]) - - :param string prefix: optional metric prefix, i.e. ``"answer"`` shows only metrics beginning with "answer" - -Outputs collected metrics as a JSON dictionary. - -.. function:: stats.upstreams() - -Outputs a list of recent upstreams and their RTT. It is sorted by time and stored in a ring buffer of -a fixed size. This means it's not aggregated and readable by multiple consumers, but also that -you may lose entries if you don't read quickly enough. The default ring size is 512 entries, and may be overriden on compile time by ``-DUPSTREAMS_COUNT=X``. - -.. function:: stats.frequent() - -Outputs list of most frequent iterative queries as a JSON array. The queries are sampled probabilistically, -and include subrequests. The list maximum size is 5000 entries, make diffs if you want to track it over time. - -.. function:: stats.clear_frequent() - -Clear the list of most frequent iterative queries. +Module ``stats`` gathers various counters from the query resolution +and server internals, and offers them as a key-value storage. +These metrics can be either exported to :ref:`mod-graphite`, +exposed as :ref:`mod-http-prometheus`, or processed using user-provided script +as described in chapter :ref:`async-events`. .. _mod-stats-list: @@ -98,7 +19,7 @@ Built-in counters keep track of number of queries and answers matching specific +-----------------------------------------------------------------+ | **Global request counters** | +------------------+----------------------------------------------+ -| request.total | total number of DNS requests from clients | +| request.total | total number of DNS requests | | | (including internal client requests) | +------------------+----------------------------------------------+ | request.internal | internal requests generated by Knot Resolver | @@ -186,3 +107,88 @@ Built-in counters keep track of number of queries and answers matching specific +-----------------+----------------------------------+ | query.dnssec | queries with DNSSEC DO=1 | +-----------------+----------------------------------+ + +Example: + +.. code-block:: none + + modules.load('stats') + + -- Enumerate metrics + > stats.list() + [answer.cached] => 486178 + [iterator.tcp] => 490 + [answer.noerror] => 507367 + [answer.total] => 618631 + [iterator.udp] => 102408 + [query.concurrent] => 149 + + -- Query metrics by prefix + > stats.list('iter') + [iterator.udp] => 105104 + [iterator.tcp] => 490 + + -- Fetch most common queries + > stats.frequent() + [1] => { + [type] => 2 + [count] => 4 + [name] => cz. + } + + -- Fetch most common queries (sorted by frequency) + > table.sort(stats.frequent(), function (a, b) return a.count > b.count end) + + -- Show recently contacted authoritative servers + > stats.upstreams() + [2a01:618:404::1] => { + [1] => 26 -- RTT + } + [128.241.220.33] => { + [1] => 31 - RTT + } + + -- Set custom metrics from modules + > stats['filter.match'] = 5 + > stats['filter.match'] + 5 + +Module reference +^^^^^^^^^^^^^^^^ + +.. function:: stats.get(key) + + :param string key: i.e. ``"answer.total"`` + :return: ``number`` + +Return nominal value of given metric. + +.. function:: stats.set(key, val) + + :param string key: i.e. ``"answer.total"`` + :param number val: i.e. ``5`` + +Set nominal value of given metric. + +.. function:: stats.list([prefix]) + + :param string prefix: optional metric prefix, i.e. ``"answer"`` shows only metrics beginning with "answer" + +Outputs collected metrics as a JSON dictionary. + +.. function:: stats.upstreams() + +Outputs a list of recent upstreams and their RTT. It is sorted by time and stored in a ring buffer of +a fixed size. This means it's not aggregated and readable by multiple consumers, but also that +you may lose entries if you don't read quickly enough. The default ring size is 512 entries, and may be overriden on compile time by ``-DUPSTREAMS_COUNT=X``. + +.. function:: stats.frequent() + +Outputs list of most frequent iterative queries as a JSON array. The queries are sampled probabilistically, +and include subrequests. The list maximum size is 5000 entries, make diffs if you want to track it over time. + +.. function:: stats.clear_frequent() + +Clear the list of most frequent iterative queries. + + diff --git a/modules/workarounds/README.rst b/modules/workarounds/README.rst index 5aa8970c3..69ef1edf8 100644 --- a/modules/workarounds/README.rst +++ b/modules/workarounds/README.rst @@ -1,14 +1,9 @@ .. _mod-workarounds: -Workarounds ------------ +Module `workarounds` resolver behavior on specific broken sub-domains. +Currently it mainly disables case randomization. -A simple module that alters resolver behavior on specific broken sub-domains. -Currently it mainly disables case randomization on them. - -Running -^^^^^^^ .. code-block:: lua - modules = { 'workarounds < iterate' } + modules.load('workarounds < iterate')