From 89bd8eed08d163724b2ef04107bd977ebcf5180e Mon Sep 17 00:00:00 2001 From: <> Date: Tue, 7 Mar 2023 16:26:08 +0000 Subject: [PATCH] Deployed 2c108da with MkDocs version: 1.4.2 --- .nojekyll | 0 404.html | 399 + additional_information/index.html | 640 ++ assets/images/favicon.png | Bin 0 -> 1870 bytes assets/javascripts/bundle.b78d2936.min.js | 29 + assets/javascripts/bundle.b78d2936.min.js.map | 8 + assets/javascripts/lunr/min/lunr.ar.min.js | 1 + assets/javascripts/lunr/min/lunr.da.min.js | 18 + assets/javascripts/lunr/min/lunr.de.min.js | 18 + assets/javascripts/lunr/min/lunr.du.min.js | 18 + assets/javascripts/lunr/min/lunr.es.min.js | 18 + assets/javascripts/lunr/min/lunr.fi.min.js | 18 + assets/javascripts/lunr/min/lunr.fr.min.js | 18 + assets/javascripts/lunr/min/lunr.hi.min.js | 1 + assets/javascripts/lunr/min/lunr.hu.min.js | 18 + assets/javascripts/lunr/min/lunr.it.min.js | 18 + assets/javascripts/lunr/min/lunr.ja.min.js | 1 + assets/javascripts/lunr/min/lunr.jp.min.js | 1 + assets/javascripts/lunr/min/lunr.ko.min.js | 1 + assets/javascripts/lunr/min/lunr.multi.min.js | 1 + assets/javascripts/lunr/min/lunr.nl.min.js | 18 + assets/javascripts/lunr/min/lunr.no.min.js | 18 + assets/javascripts/lunr/min/lunr.pt.min.js | 18 + assets/javascripts/lunr/min/lunr.ro.min.js | 18 + assets/javascripts/lunr/min/lunr.ru.min.js | 18 + .../lunr/min/lunr.stemmer.support.min.js | 1 + assets/javascripts/lunr/min/lunr.sv.min.js | 18 + assets/javascripts/lunr/min/lunr.ta.min.js | 1 + assets/javascripts/lunr/min/lunr.th.min.js | 1 + assets/javascripts/lunr/min/lunr.tr.min.js | 18 + assets/javascripts/lunr/min/lunr.vi.min.js | 1 + assets/javascripts/lunr/min/lunr.zh.min.js | 1 + assets/javascripts/lunr/tinyseg.js | 206 + assets/javascripts/lunr/wordcut.js | 6708 +++++++++++++ .../workers/search.208ed371.min.js | 42 + .../workers/search.208ed371.min.js.map | 8 + assets/stylesheets/main.402914a4.min.css | 1 + assets/stylesheets/main.402914a4.min.css.map | 1 + assets/stylesheets/palette.a0c5b2b5.min.css | 1 + .../stylesheets/palette.a0c5b2b5.min.css.map | 1 + blocky-grafana.json | 1958 ++++ blocky-query-grafana-postgres.json | 882 ++ blocky-query-grafana.json | 882 ++ blocky.svg | 768 ++ config.yml | 265 + configuration/index.html | 2372 +++++ fb_dns_config.png | Bin 0 -> 40713 bytes grafana-dashboard.png | Bin 0 -> 233493 bytes grafana-query-dashboard.png | Bin 0 -> 193087 bytes includes/abbreviations/index.html | 412 + index.html | 573 ++ installation/index.html | 873 ++ interfaces/index.html | 512 + network_configuration/index.html | 546 ++ prometheus_grafana/index.html | 652 ++ search/search_index.json | 1 + sitemap.xml | 43 + sitemap.xml.gz | Bin 0 -> 203 bytes swagger.html | 8639 +++++++++++++++++ 59 files changed, 27703 insertions(+) create mode 100644 .nojekyll create mode 100644 404.html create mode 100644 additional_information/index.html create mode 100644 assets/images/favicon.png create mode 100644 assets/javascripts/bundle.b78d2936.min.js create mode 100644 assets/javascripts/bundle.b78d2936.min.js.map create mode 100644 assets/javascripts/lunr/min/lunr.ar.min.js create mode 100644 assets/javascripts/lunr/min/lunr.da.min.js create mode 100644 assets/javascripts/lunr/min/lunr.de.min.js create mode 100644 assets/javascripts/lunr/min/lunr.du.min.js create mode 100644 assets/javascripts/lunr/min/lunr.es.min.js create mode 100644 assets/javascripts/lunr/min/lunr.fi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.fr.min.js create mode 100644 assets/javascripts/lunr/min/lunr.hi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.hu.min.js create mode 100644 assets/javascripts/lunr/min/lunr.it.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ja.min.js create mode 100644 assets/javascripts/lunr/min/lunr.jp.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ko.min.js create mode 100644 assets/javascripts/lunr/min/lunr.multi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.nl.min.js create mode 100644 assets/javascripts/lunr/min/lunr.no.min.js create mode 100644 assets/javascripts/lunr/min/lunr.pt.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ro.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ru.min.js create mode 100644 assets/javascripts/lunr/min/lunr.stemmer.support.min.js create mode 100644 assets/javascripts/lunr/min/lunr.sv.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ta.min.js create mode 100644 assets/javascripts/lunr/min/lunr.th.min.js create mode 100644 assets/javascripts/lunr/min/lunr.tr.min.js create mode 100644 assets/javascripts/lunr/min/lunr.vi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.zh.min.js create mode 100644 assets/javascripts/lunr/tinyseg.js create mode 100644 assets/javascripts/lunr/wordcut.js create mode 100644 assets/javascripts/workers/search.208ed371.min.js create mode 100644 assets/javascripts/workers/search.208ed371.min.js.map create mode 100644 assets/stylesheets/main.402914a4.min.css create mode 100644 assets/stylesheets/main.402914a4.min.css.map create mode 100644 assets/stylesheets/palette.a0c5b2b5.min.css create mode 100644 assets/stylesheets/palette.a0c5b2b5.min.css.map create mode 100644 blocky-grafana.json create mode 100644 blocky-query-grafana-postgres.json create mode 100644 blocky-query-grafana.json create mode 100644 blocky.svg create mode 100644 config.yml create mode 100644 configuration/index.html create mode 100644 fb_dns_config.png create mode 100644 grafana-dashboard.png create mode 100644 grafana-query-dashboard.png create mode 100644 includes/abbreviations/index.html create mode 100644 index.html create mode 100644 installation/index.html create mode 100644 interfaces/index.html create mode 100644 network_configuration/index.html create mode 100644 prometheus_grafana/index.html create mode 100644 search/search_index.json create mode 100644 sitemap.xml create mode 100644 sitemap.xml.gz create mode 100644 swagger.html diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/404.html b/404.html new file mode 100644 index 00000000..55aada5a --- /dev/null +++ b/404.html @@ -0,0 +1,399 @@ + + + +
+ + + + + + + + + + + + + + + +To print runtime configuration / statistics, you can send SIGUSR1
signal to running process.
Summary
+Example output:
+INFO server: current configuration:
+INFO server: -> resolver: 'ClientNamesResolver'
+INFO server: singleNameOrder = "[2 1]"
+INFO server: externalResolver = "upstream 'tcp+udp:192.168.178.1:53'"
+INFO server: cache item count = 7
+INFO server: -> resolver: 'QueryLoggingResolver'
+INFO server: logDir= "/logs"
+INFO server: perClient = false
+INFO server: logRetentionDays= 7
+INFO server: -> resolver: 'MetricsResolver'
+INFO server: metrics:
+INFO server: Enable = true
+INFO server: Path = /metrics
+INFO server: -> resolver: 'ConditionalUpstreamResolver'
+INFO server: fritz.box = "parallel upstreams 'upstream 'tcp+udp:192.168.178.1:53''"
+INFO server: -> resolver: 'CustomDNSResolver'
+INFO server: runtime information:
+...
+INFO server: MEM Alloc = 9 MB
+INFO server: MEM HeapAlloc = 9 MB
+INFO server: MEM Sys = 88 MB
+INFO server: MEM NumGC = 1533
+INFO server: RUN NumCPU = 4
+INFO server: RUN NumGoroutine = 18
+
Hint
+To send a signal to a process you can use kill -s USR1 <PID>
or docker kill -s SIGUSR1 blocky
for docker setup
If http listener is enabled, pprof endpoint (/debug/pprof
) is enabled
+automatically.
Some links/ideas for lists:
+Warning
+Use only blacklists from the sources you trust!
+Warning
+DNS server provider has access to all your DNS queries (all visited domain names). Some DNS providers can use (tracking, analyzing, profiling etc.). It is recommended to use different DNS upstream servers in blocky to distribute your DNS queries over multiple providers.
+Please read the description before using the DNS server as upstream. Some of them provide already an ad-blocker, some +filters other content. If you use external DNS server with included ad-blocker, you can't choose which domains should be +blocked, and you can't use whitelisting.
+This is only a small excerpt of all free available DNS servers and should only be understood as an idee.
+Info
+I will NOT rate the DNS providers in the list. This list is sorted alphabetically.
+This chapter describes all configuration options in config.yaml
. You can download a reference file with all
+configuration properties as JSON.
upstream:
+ # these external DNS resolvers will be used. Blocky picks 2 random resolvers from the list for each query
+ # format for resolver: [net:]host:[port][/path]. net could be empty (default, shortcut for tcp+udp), tcp+udp, tcp, udp, tcp-tls or https (DoH). If port is empty, default port will be used (53 for udp and tcp, 853 for tcp-tls, 443 for https (Doh))
+ # this configuration is mandatory, please define at least one external DNS resolver
+ default:
+ # example for tcp+udp IPv4 server (https://digitalcourage.de/)
+ - 5.9.164.112
+ # Cloudflare
+ - 1.1.1.1
+ # example for DNS-over-TLS server (DoT)
+ - tcp-tls:fdns1.dismail.de:853
+ # example for DNS-over-HTTPS (DoH)
+ - https://dns.digitale-gesellschaft.ch/dns-query
+ # optional: use client name (with wildcard support: * - sequence of any characters, [0-9] - range)
+ # or single ip address / client subnet as CIDR notation
+ laptop*:
+ - 123.123.123.123
+
+# optional: timeout to query the upstream resolver. Default: 2s
+upstreamTimeout: 2s
+
+# optional: If true, blocky will fail to start unless at least one upstream server per group is reachable. Default: false
+startVerifyUpstream: true
+
+# optional: Determines how blocky will create outgoing connections. This impacts both upstreams, and lists.
+# accepted: dual, v4, v6
+# default: dual
+connectIPVersion: dual
+
+# optional: custom IP address(es) for domain name (with all sub-domains). Multiple addresses must be separated by a comma
+# example: query "printer.lan" or "my.printer.lan" will return 192.168.178.3
+customDNS:
+ customTTL: 1h
+ # optional: if true (default), return empty result for unmapped query types (for example TXT, MX or AAAA if only IPv4 address is defined).
+ # if false, queries with unmapped types will be forwarded to the upstream resolver
+ filterUnmappedTypes: true
+ # optional: replace domain in the query with other domain before resolver lookup in the mapping
+ rewrite:
+ example.com: printer.lan
+ mapping:
+ printer.lan: 192.168.178.3,2001:0db8:85a3:08d3:1319:8a2e:0370:7344
+
+# optional: definition, which DNS resolver(s) should be used for queries to the domain (with all sub-domains). Multiple resolvers must be separated by a comma
+# Example: Query client.fritz.box will ask DNS server 192.168.178.1. This is necessary for local network, to resolve clients by host name
+conditional:
+ # optional: if false (default), return empty result if after rewrite, the mapped resolver returned an empty answer. If true, the original query will be sent to the upstream resolver
+ # Example: The query "blog.example.com" will be rewritten to "blog.fritz.box" and also redirected to the resolver at 192.168.178.1. If not found and if `fallbackUpstream` was set to `true`, the original query "blog.example.com" will be sent upstream.
+ # Usage: One usecase when having split DNS for internal and external (internet facing) users, but not all subdomains are listed in the internal domain.
+ fallbackUpstream: false
+ # optional: replace domain in the query with other domain before resolver lookup in the mapping
+ rewrite:
+ example.com: fritz.box
+ mapping:
+ fritz.box: 192.168.178.1
+ lan.net: 192.168.178.1,192.168.178.2
+
+# optional: use black and white lists to block queries (for example ads, trackers, adult pages etc.)
+blocking:
+ # definition of blacklist groups. Can be external link (http/https) or local file
+ blackLists:
+ ads:
+ - https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt
+ - https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
+ - http://sysctl.org/cameleon/hosts
+ - https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt
+ - |
+ # inline definition with YAML literal block scalar style
+ # hosts format
+ someadsdomain.com
+ special:
+ - https://raw.githubusercontent.com/StevenBlack/hosts/master/alternates/fakenews/hosts
+ # definition of whitelist groups. Attention: if the same group has black and whitelists, whitelists will be used to disable particular blacklist entries. If a group has only whitelist entries -> this means only domains from this list are allowed, all other domains will be blocked
+ whiteLists:
+ ads:
+ - whitelist.txt
+ - |
+ # inline definition with YAML literal block scalar style
+ # hosts format
+ whitelistdomain.com
+ # this is a regex
+ /^banners?[_.-]/
+ # definition: which groups should be applied for which client
+ clientGroupsBlock:
+ # default will be used, if no special definition for a client name exists
+ default:
+ - ads
+ - special
+ # use client name (with wildcard support: * - sequence of any characters, [0-9] - range)
+ # or single ip address / client subnet as CIDR notation
+ laptop*:
+ - ads
+ 192.168.178.1/24:
+ - special
+ # which response will be sent, if query is blocked:
+ # zeroIp: 0.0.0.0 will be returned (default)
+ # nxDomain: return NXDOMAIN as return code
+ # comma separated list of destination IP addresses (for example: 192.100.100.15, 2001:0db8:85a3:08d3:1319:8a2e:0370:7344). Should contain ipv4 and ipv6 to cover all query types. Useful with running web server on this address to display the "blocked" page.
+ blockType: zeroIp
+ # optional: TTL for answers to blocked domains
+ # default: 6h
+ blockTTL: 1m
+ # optional: automatically list refresh period (in duration format). Default: 4h.
+ # Negative value -> deactivate automatically refresh.
+ # 0 value -> use default
+ refreshPeriod: 4h
+ # optional: timeout for list download (each url). Default: 60s. Use large values for big lists or slow internet connections
+ downloadTimeout: 4m
+ # optional: Download attempt timeout. Default: 60s
+ downloadAttempts: 5
+ # optional: Time between the download attempts. Default: 1s
+ downloadCooldown: 10s
+ # optional: if failOnError, application startup will fail if at least one list can't be downloaded / opened. Default: blocking
+ startStrategy: failOnError
+
+# optional: configuration for caching of DNS responses
+caching:
+ # duration how long a response must be cached (min value).
+ # If <=0, use response's TTL, if >0 use this value, if TTL is smaller
+ # Default: 0
+ minTime: 5m
+ # duration how long a response must be cached (max value).
+ # If <0, do not cache responses
+ # If 0, use TTL
+ # If > 0, use this value, if TTL is greater
+ # Default: 0
+ maxTime: 30m
+ # Max number of cache entries (responses) to be kept in cache (soft limit). Useful on systems with limited amount of RAM.
+ # Default (0): unlimited
+ maxItemsCount: 0
+ # if true, will preload DNS results for often used queries (default: names queried more than 5 times in a 2-hour time window)
+ # this improves the response time for often used queries, but significantly increases external traffic
+ # default: false
+ prefetching: true
+ # prefetch track time window (in duration format)
+ # default: 120
+ prefetchExpires: 2h
+ # name queries threshold for prefetch
+ # default: 5
+ prefetchThreshold: 5
+ # Max number of domains to be kept in cache for prefetching (soft limit). Useful on systems with limited amount of RAM.
+ # Default (0): unlimited
+ prefetchMaxItemsCount: 0
+ # Time how long negative results (NXDOMAIN response or empty result) are cached. A value of -1 will disable caching for negative results.
+ # Default: 30m
+ cacheTimeNegative: 30m
+
+# optional: configuration of client name resolution
+clientLookup:
+ # optional: this DNS resolver will be used to perform reverse DNS lookup (typically local router)
+ upstream: 192.168.178.1
+ # optional: some routers return multiple names for client (host name and user defined name). Define which single name should be used.
+ # Example: take second name if present, if not take first name
+ singleNameOrder:
+ - 2
+ - 1
+ # optional: custom mapping of client name to IP addresses. Useful if reverse DNS does not work properly or just to have custom client names.
+ clients:
+ laptop:
+ - 192.168.178.29
+# optional: configuration for prometheus metrics endpoint
+prometheus:
+ # enabled if true
+ enable: true
+ # url path, optional (default '/metrics')
+ path: /metrics
+
+# optional: write query information (question, answer, client, duration etc.) to daily csv file
+queryLog:
+ # optional one of: mysql, postgresql, csv, csv-client. If empty, log to console
+ type: mysql
+ # directory (should be mounted as volume in docker) for csv, db connection string for mysql/postgresql
+ target: db_user:db_password@tcp(db_host_or_ip:3306)/db_name?charset=utf8mb4&parseTime=True&loc=Local
+ #postgresql target: postgres://user:password@db_host_or_ip:5432/db_name
+ # if > 0, deletes log files which are older than ... days
+ logRetentionDays: 7
+ # optional: Max attempts to create specific query log writer, default: 3
+ creationAttempts: 1
+ # optional: Time between the creation attempts, default: 2s
+ creationCooldown: 2s
+ # optional: Which fields should be logged. You can choose one or more from: clientIP, clientName, responseReason, responseAnswer, question, duration. If not defined, it logs all fields
+ fields:
+ - clientIP
+ - duration
+
+# optional: Blocky can synchronize its cache and blocking state between multiple instances through redis.
+redis:
+ # Server address and port or master name if sentinel is used
+ address: redismaster
+ # Username if necessary
+ username: usrname
+ # Password if necessary
+ password: passwd
+ # Database, default: 0
+ database: 2
+ # Connection is required for blocky to start. Default: false
+ required: true
+ # Max connection attempts, default: 3
+ connectionAttempts: 10
+ # Time between the connection attempts, default: 1s
+ connectionCooldown: 3s
+ # Sentinal username if necessary
+ sentinelUsername: usrname
+ # Sentinal password if necessary
+ sentinelPassword: passwd
+ # List with address and port of sentinel hosts(sentinel is activated if at least one sentinel address is configured)
+ sentinelAddresses:
+ - redis-sentinel1:26379
+ - redis-sentinel2:26379
+ - redis-sentinel3:26379
+
+# optional: Mininal TLS version that the DoH and DoT server will use
+minTlsServeVersion: 1.3
+# if https port > 0: path to cert and key file for SSL encryption. if not set, self-signed certificate will be generated
+#certFile: server.crt
+#keyFile: server.key
+# optional: use these DNS servers to resolve blacklist urls and upstream DNS servers. It is useful if no system DNS resolver is configured, and/or to encrypt the bootstrap queries.
+bootstrapDns:
+ - tcp+udp:1.1.1.1
+ - https://1.1.1.1/dns-query
+ - upstream: https://dns.digitale-gesellschaft.ch/dns-query
+ ips:
+ - 185.95.218.42
+
+# optional: drop all queries with following query types. Default: empty
+filtering:
+ queryTypes:
+ - AAAA
+
+# optional: if path defined, use this file for query resolution (A, AAAA and rDNS). Default: empty
+hostsFile:
+ # optional: Path to hosts file (e.g. /etc/hosts on Linux)
+ filePath: /etc/hosts
+ # optional: TTL, default: 1h
+ hostsTTL: 60m
+ # optional: Time between hosts file refresh, default: 1h
+ refreshPeriod: 30m
+ # optional: Whether loopback hosts addresses (127.0.0.0/8 and ::1) should be filtered or not, default: false
+ filterLoopback: true
+
+# optional: ports configuration
+ports:
+ # optional: DNS listener port(s) and bind ip address(es), default 53 (UDP and TCP). Example: 53, :53, "127.0.0.1:5353,[::1]:5353"
+ dns: 53
+ # optional: Port(s) and bind ip address(es) for DoT (DNS-over-TLS) listener. Example: 853, 127.0.0.1:853
+ tls: 853
+ # optional: Port(s) and optional bind ip address(es) to serve HTTPS used for prometheus metrics, pprof, REST API, DoH... If you wish to specify a specific IP, you can do so such as 192.168.0.1:443. Example: 443, :443, 127.0.0.1:443,[::1]:443
+ https: 443
+ # optional: Port(s) and optional bind ip address(es) to serve HTTP used for prometheus metrics, pprof, REST API, DoH... If you wish to specify a specific IP, you can do so such as 192.168.0.1:4000. Example: 4000, :4000, 127.0.0.1:4000,[::1]:4000
+ http: 4000
+
+# optional: logging configuration
+log:
+ # optional: Log level (one from debug, info, warn, error). Default: info
+ level: info
+ # optional: Log format (text or json). Default: text
+ format: text
+ # optional: log timestamps. Default: true
+ timestamp: true
+ # optional: obfuscate log output (replace all alphanumeric characters with *) for user sensitive data like request domains or responses to increase privacy. Default: false
+ privacy: false
+
+# optional: add EDE error codes to dns response
+ede:
+ # enabled if true, Default: false
+ enable: true
+
Parameter | +Type | +Mandatory | +Default value | +Description | +
---|---|---|---|---|
certFile | +path | +no | ++ | Path to cert and key file for SSL encryption (DoH and DoT); if empty, self-signed certificate is generated | +
keyFile | +path | +no | ++ | Path to cert and key file for SSL encryption (DoH and DoT); if empty, self-signed certificate is generated | +
dohUserAgent | +string | +no | ++ | HTTP User Agent for DoH upstreams | +
minTlsServeVersion | +string | +no | +1.2 | +Minimum TLS version that the DoT and DoH server use to serve those encrypted DNS requests | +
startVerifyUpstream | +bool | +no | +false | +If true, blocky will fail to start unless at least one upstream server per group is reachable. | +
connectIPVersion | +enum (dual, v4, v6) | +no | +dual | +IP version to use for outgoing connections (dual, v4, v6) | +
Example
+minTlsServeVersion: 1.1
+connectIPVersion: v4
+
All logging port are optional.
+Parameter | +Type | +Default value | +Description | +
---|---|---|---|
ports.dns | +[IP]:port[,[IP]:port]* | +53 | +Port(s) and optional bind ip address(es) to serve DNS endpoint (TCP and UDP). If you wish to specify a specific IP, you can do so such as 192.168.0.1:53 . Example: 53 , :53 , 127.0.0.1:53,[::1]:53 |
+
ports.tls | +[IP]:port[,[IP]:port]* | ++ | Port(s) and optional bind ip address(es) to serve DoT DNS endpoint (DNS-over-TLS). If you wish to specify a specific IP, you can do so such as 192.168.0.1:853 . Example: 83 , :853 , 127.0.0.1:853,[::1]:853 |
+
ports.http | +[IP]:port[,[IP]:port]* | ++ | Port(s) and optional bind ip address(es) to serve HTTP used for prometheus metrics, pprof, REST API, DoH... If you wish to specify a specific IP, you can do so such as 192.168.0.1:4000 . Example: 4000 , :4000 , 127.0.0.1:4000,[::1]:4000 |
+
ports.https | +[IP]:port[,[IP]:port]* | ++ | Port(s) and optional bind ip address(es) to serve HTTPS used for prometheus metrics, pprof, REST API, DoH... If you wish to specify a specific IP, you can do so such as 192.168.0.1:443 . Example: 443 , :443 , 127.0.0.1:443,[::1]:443 |
+
Example
+ports:
+ dns: 53
+ http: 4000
+ https: 443
+
All logging options are optional.
+Parameter | +Type | +Default value | +Description | +
---|---|---|---|
log.level | +enum (debug, info, warn, error) | +info | +Log level | +
log.format | +enum (text, json) | +text | +Log format (text or json). | +
log.timestamp | +bool | +true | +Log time stamps (true or false). | +
log.privacy | +bool | +false | +Obfuscate log output (replace all alphanumeric characters with *) for user sensitive data like request domains or responses to increase privacy. | +
Example
+log:
+ level: debug
+ format: json
+ timestamp: false
+ privacy: true
+
To resolve a DNS query, blocky needs external public or private DNS resolvers. Blocky supports DNS resolvers with +following network protocols (net part of the resolver URL):
+Hint
+You can (and should!) configure multiple DNS resolvers. Blocky picks 2 random resolvers from the list for each query and +returns the answer from the fastest one. This improves your network speed and increases your privacy - your DNS traffic +will be distributed over multiple providers.
+Each resolver must be defined as a string in following format: [net:]host:[port][/path][#commonName]
.
Parameter | +Type | +Mandatory | +Default value | +
---|---|---|---|
net | +enum (tcp+udp, tcp-tls or https) | +no | +tcp+udp | +
host | +IP or hostname | +yes | ++ |
port | +int (1 - 65535) | +no | +53 for udp/tcp, 853 for tcp-tls and 443 for https | +
commonName | +string | +no | +the host value | +
The commonName parameter overrides the expected certificate common name value used for verification.
+Blocky needs at least the configuration of the default group. This group will be used as a fallback, if no client +specific resolver configuration is available.
+You can use the client name (see Client name lookup), client's IP address or a client subnet as +CIDR notation.
+Tip
+You can use *
as wildcard for the sequence of any character or [0-9]
as number range
Example
+upstream:
+ default:
+ - 5.9.164.112
+ - 1.1.1.1
+ - tcp-tls:fdns1.dismail.de:853
+ - https://dns.digitale-gesellschaft.ch/dns-query
+ laptop*:
+ - 123.123.123.123
+ 10.43.8.67/28:
+ - 1.1.1.1
+ - 9.9.9.9
+
Use 123.123.123.123
as single upstream DNS resolver for client laptop-home,
+1.1.1.1
and 9.9.9.9
for all clients in the sub-net 10.43.8.67/28
and 4 resolvers (default) for all others clients.
Note
+Blocky needs at least one upstream DNS server
+See List of public DNS servers if you need some ideas, which +public free DNS server you could use.
+Blocky will wait 2 seconds (default value) for the response from the external upstream DNS server. You can change this
+value by setting the upstreamTimeout
configuration parameter (in duration format).
Example
+upstream:
+ default:
+ - 46.182.19.48
+ - 80.241.218.68
+upstreamTimeout: 5s
+
These DNS servers are used to resolve upstream DoH and DoT servers that are specified as host names, and list domains. +It is useful if no system DNS resolver is configured, and/or to encrypt the bootstrap queries.
+Parameter | +Type | +Mandatory | +Default value | +Description | +
---|---|---|---|---|
upstream | +Upstream (see above) | +no | ++ | + |
ips | +List of IPs | +yes, if upstream is DoT/DoH | ++ | Only valid if upstream is DoH or DoT | +
When using an upstream specified by IP, and not by hostname, you can write only the upstream and skip ips
.
Note
+Works only on Linux/*nix OS due to golang limitations under Windows.
+Example
+ bootstrapDns:
+ - upstream: tcp-tls:dns.example.com
+ ips:
+ - 123.123.123.123
+ - upstream: https://234.234.234.234/dns-query
+
Under certain circumstances, it may be useful to filter some types of DNS queries. You can define one or more DNS query +types, all queries with these types will be dropped (empty answer will be returned).
+Example
+filtering:
+ queryTypes:
+ - AAAA
+
This configuration will drop all 'AAAA' (IPv6) queries.
+In domain environments, it may be useful to only response to FQDN requests. If this option is enabled blocky respond immediately +with NXDOMAIN if the request is not a valid FQDN. The request is therefore not further processed by other options like custom or conditional. +Please be aware that by enabling it your hostname resolution will break unless every hostname is part of a domain.
+Example
+fqdnOnly: true
+
You can define your own domain name to IP mappings. For example, you can use a user-friendly name for a network printer +or define a domain name for your local device on order to use the HTTPS certificate. Multiple IP addresses for one +domain must be separated by a comma.
+Parameter | +Type | +Mandatory | +Default value | +
---|---|---|---|
customTTL | +duration (no unit is minutes) | +no | +1h | +
rewrite | +string: string (domain: domain) | +no | ++ |
mapping | +string: string (hostname: address list) | +no | ++ |
filterUnmappedTypes | +boolean | +no | +true | +
Example
+customDNS:
+ customTTL: 1h
+ filterUnmappedTypes: true
+ rewrite:
+ home: lan
+ replace-me.com: with-this.com
+ mapping:
+ printer.lan: 192.168.178.3
+ otherdevice.lan: 192.168.178.15,2001:0db8:85a3:08d3:1319:8a2e:0370:7344
+
This configuration will also resolve any subdomain of the defined domain. For example a query "printer.lan" or " +my.printer.lan" will return 192.168.178.3 as IP address.
+With the optional parameter rewrite
you can replace domain part of the query with the defined part before the
+resolver lookup is performed.
+The query "printer.home" will be rewritten to "printer.lan" and return 192.168.178.3.
With parameter filterUnmappedTypes = true
(default), blocky will filter all queries with unmapped types, for example:
+AAAA for "printer.lan" or TXT for "otherdevice.lan".
+With filterUnmappedTypes = false
a query AAAA "printer.lan" will be forwarded to the upstream DNS server.
You can define, which DNS resolver(s) should be used for queries for the particular domain (with all subdomains). This +is for example useful, if you want to reach devices in your local network by the name. Since only your router know which +hostname belongs to which IP address, all DNS queries for the local network should be redirected to the router.
+The optional parameter rewrite
behaves the same as with custom DNS.
The optional parameter fallbackUpstream
, if false (default), return empty result if after rewrite, the mapped resolver returned an empty answer. If true, the original query will be sent to the upstream resolver.
Usage: One usecase when having split DNS for internal and external (internet facing) users, but not all subdomains are listed in the internal domain
+Example
+conditional:
+ fallbackUpstream: false
+ rewrite:
+ example.com: fritz.box
+ replace-me.com: with-this.com
+ mapping:
+ fritz.box: 192.168.178.1
+ lan.net: 192.170.1.2,192.170.1.3
+ # for reverse DNS lookups of local devices
+ 178.168.192.in-addr.arpa: 192.168.178.1
+ # for all unqualified hostnames
+ .: 168.168.0.1
+
Tip
+You can use .
as wildcard for all non full qualified domains (domains without dot)
In this example, a DNS query "client.fritz.box" will be redirected to the router's DNS server at 192.168.178.1 and client.lan.net to 192.170.1.2 and 192.170.1.3. +The query "client.example.com" will be rewritten to "client.fritz.box" and also redirected to the resolver at 192.168.178.1.
+If not found and if fallbackUpstream
was set to true
, the original query "blog.example.com" will be sent upstream.
All unqualified host names (e.g. "test") will be redirected to the DNS server at 168.168.0.1.
+One usecase for fallbackUpstream
is when having split DNS for internal and external (internet facing) users, but not all subdomains are listed in the internal domain.
Blocky can try to resolve a user-friendly client name from the IP address or server URL (DoT and DoH). This is useful +for defining of blocking groups, since IP address can change dynamically.
+If DoT or DoH is enabled, you can use a subdomain prefixed with id-
to provide a client name (wildcard ssl certificate
+recommended).
Example: domain example.com
DoT Host: id-bob.example.com
-> request's client name is bob
+DoH URL: https://id-bob.example.com/dns-query
-> request's client name is bob
For DoH you can also pass the client name as url parameter:
+DoH URL: https://blocky.example.com/dns-query/alice
-> request's client name is alice
Blocky uses rDNS to retrieve client's name. To use this feature, you can configure a DNS server for client lookup ( +typically your router). You can also define client names manually per IP address.
+Some routers return multiple names for the client (host name and user defined name). With
+parameter clientLookup.singleNameOrder
you can specify, which of retrieved names should be used.
You can also map a particular client name to one (or more) IP (ipv4/ipv6) addresses. Parameter clientLookup.clients
+contains a map of client name and multiple IP addresses.
Example
+clientLookup:
+ upstream: 192.168.178.1
+ singleNameOrder:
+ - 2
+ - 1
+ clients:
+ laptop:
+ - 192.168.178.29
+
Use 192.168.178.1
for rDNS lookup. Take second name if present, if not take first name. IP address 192.168.178.29
is mapped to laptop
as client name.
Blocky can download and use external lists with domains or IP addresses to block DNS query (e.g. advertisement, malware, +trackers, adult sites). You can group several list sources together and define the blocking behavior per client. +External blacklists must be either in the well-known Hosts format or just +a plain domain list (one domain per line). Blocky also supports regex as more powerful tool to define patterns to block.
+Blocky uses DNS sinkhole approach to block a DNS query. Domain name from +the request, IP address from the response, and the CNAME record will be checked against configured blacklists.
+To avoid over-blocking, you can define or use already existing whitelists.
+Each black or whitelist can be either a path to the local file, a URL to download or inline list definition of a domains +in hosts format (YAML literal block scalar style). All Urls must be grouped to a group name.
+Example
+blocking:
+ blackLists:
+ ads:
+ - https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt
+ - https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
+ - |
+ # inline definition with YAML literal block scalar style
+ someadsdomain.com
+ anotheradsdomain.com
+ # this is a regex
+ /^banners?[_.-]/
+ special:
+ - https://raw.githubusercontent.com/StevenBlack/hosts/master/alternates/fakenews/hosts
+ whiteLists:
+ ads:
+ - whitelist.txt
+ - |
+ # inline definition with YAML literal block scalar style
+ whitelistdomain.com
+
In this example you can see 2 groups: ads with 2 lists and special with one list. One local whitelist was defined for the ads group.
+Warning
+If the same group has black and whitelists, whitelists will be used to disable particular blacklist entries. +If a group has only whitelist entries -> this means only domains from this list are allowed, all other domains will +be blocked
+Note
+Please define also client group mapping, otherwise you black and whitelist definition will have no effect
+You can use regex to define patterns to block. A regex entry must start and end with the slash character (/). Some +Examples:
+/baddomain/
will block www.baddomain.com
, baddomain.com
, but also mybaddomain-sometext.com
/^baddomain/
will block baddomain.com
, but not www.baddomain.com
/^apple\.(de|com)$/
will only block apple.de
and apple.com
In this configuration section, you can define, which blocking group(s) should be used for which client in your network. +Example: All clients should use the ads group, which blocks advertisement and kids devices should use the adult +group, which blocky adult sites.
+Clients without a group assignment will use automatically the default group.
+You can use the client name (see Client name lookup), client's IP address, client's full-qualified domain name +or a client subnet as CIDR notation.
+If full-qualified domain name is used (for example "myclient.ddns.org"), blocky will try to resolve the IP address (A and AAAA records) of this domain. +If client's IP address matches with the result, the defined group will be used.
+Example
+blocking:
+ clientGroupsBlock:
+ # default will be used, if no special definition for a client name exists
+ default:
+ - ads
+ - special
+ laptop*:
+ - ads
+ 192.168.178.1/24:
+ - special
+ kid-laptop:
+ - ads
+ - adult
+
All queries from network clients, whose device name starts with laptop
, will be filtered against the ads group's lists. All devices from the subnet 192.168.178.1/24
against the special group and kid-laptop
against ads and adult. All other clients: ads and special.
Tip
+You can use *
as wildcard for the sequence of any character or [0-9]
as number range
You can configure, which response should be sent to the client, if a requested query is blocked (only for A and AAAA +queries, NXDOMAIN for other types):
+blockType | +Example | +Description | +
---|---|---|
zeroIP | +zeroIP | +This is the default block type. Server returns 0.0.0.0 (or :: for IPv6) as result for A and AAAA queries | +
nxDomain | +nxDomain | +return NXDOMAIN as return code | +
custom IPs | +192.100.100.15, 2001:0db8:85a3:08d3:1319:8a2e:0370:7344 | +comma separated list of destination IP addresses. Should contain ipv4 and ipv6 to cover all query types. Useful with running web server on this address to display the "blocked" page. | +
Example
+blocking:
+ blockType: nxDomain
+
TTL for answers to blocked domains can be set to customize the time (in duration format) clients ask for those
+domains again. Default Block TTL is 6hours. This setting only makes sense when blockType
is set to nxDomain
or
+zeroIP
, and will affect how much time it could take for a client to be able to see the real IP address for a domain
+after receiving the custom value.
Example
+blocking:
+ blockType: 192.100.100.15, 2001:0db8:85a3:08d3:1319:8a2e:0370:7344
+ blockTTL: 10s
+
To keep the list cache up-to-date, blocky will periodically download and reload all external lists. Default period is
+4 hours. You can configure this by setting the blocking.refreshPeriod
parameter to a value in duration format.
+Negative value will deactivate automatically refresh.
Example
+blocking:
+ refreshPeriod: 60m
+
Refresh every hour.
+You can configure the list download attempts according to your internet connection:
+Parameter | +Type | +Mandatory | +Default value | +Description | +
---|---|---|---|---|
downloadTimeout | +duration format | +no | +60s | +Download attempt timeout | +
downloadAttempts | +int | +no | +3 | +How many download attempts should be performed | +
downloadCooldown | +duration format | +no | +1s | +Time between the download attempts | +
Example
+blocking:
+ downloadTimeout: 4m
+ downloadAttempts: 5
+ downloadCooldown: 10s
+
You can configure the blocking behavior during application start of blocky.
+If no starategy is selected blocking will be used.
startStrategy | +Description | +
---|---|
blocking | +all blocking lists will be loaded before DNS resolution starts | +
failOnError | +like blocking but blocky will shut down if any download fails | +
fast | +DNS resolution starts immediately without blocking which will be enabled after list load is completed | +
Example
+blocking:
+ startStrategy: failOnError
+
Blocky downloads and processes links in a single group concurrently. With parameter processingConcurrency
you can adjust
+how many links can be processed in the same time. Higher value can reduce the overall list refresh time, but more parallel
+ download and processing jobs need more RAM. Please consider to reduce this value on systems with limited memory. Default value is 4.
Example
+blocking:
+ processingConcurrency: 10
+
Each DNS response has a TTL (Time-to-live) value. This value defines, how long is the record valid in seconds. The +values are maintained by domain owners, server administrators etc. Blocky caches the answers from all resolved queries +in own cache in order to avoid repeated requests. This reduces the DNS traffic and increases the network speed, since +blocky can serve the result immediately from the cache.
+With following parameters you can tune the caching behavior:
+Warning
+Wrong values can significantly increase external DNS traffic or memory consumption.
+Parameter | +Type | +Mandatory | +Default value | +Description | +
---|---|---|---|---|
caching.minTime | +duration format | +no | +0 (use TTL) | +How long a response must be cached (min value). If <=0, use response's TTL, if >0 use this value, if TTL is smaller | +
caching.maxTime | +duration format | +no | +0 (use TTL) | +How long a response must be cached (max value). If <0, do not cache responses. If 0, use TTL. If > 0, use this value, if TTL is greater | +
caching.maxItemsCount | +int | +no | +0 (unlimited) | +Max number of cache entries (responses) to be kept in cache (soft limit). Default (0): unlimited. Useful on systems with limited amount of RAM. | +
caching.prefetching | +bool | +no | +false | +if true, blocky will preload DNS results for often used queries (default: names queried more than 5 times in a 2 hour time window). Results in cache will be loaded again on their expire (TTL). This improves the response time for often used queries, but significantly increases external traffic. It is recommended to increase "minTime" to reduce the number of prefetch queries to external resolvers. | +
caching.prefetchExpires | +duration format | +no | +2h | +Prefetch track time window | +
caching.prefetchThreshold | +int | +no | +5 | +Name queries threshold for prefetch | +
caching.prefetchMaxItemsCount | +int | +no | +0 (unlimited) | +Max number of domains to be kept in cache for prefetching (soft limit). Default (0): unlimited. Useful on systems with limited amount of RAM. | +
caching.cacheTimeNegative | +duration format | +no | +30m | +Time how long negative results (NXDOMAIN response or empty result) are cached. A value of -1 will disable caching for negative results. | +
Example
+caching:
+ minTime: 5m
+ maxTime: 30m
+ prefetching: true
+
Blocky can synchronize its cache and blocking state between multiple instances through redis. +Synchronization is disabled if no address is configured.
+Parameter | +Type | +Mandatory | +Default value | +Description | +
---|---|---|---|---|
redis.address | +string | +no | ++ | Server address and port or master name if sentinel is used | +
redis.username | +string | +no | ++ | Username if necessary | +
redis.password | +string | +no | ++ | Password if necessary | +
redis.database | +int | +no | +0 | +Database | +
redis.required | +bool | +no | +false | +Connection is required for blocky to start | +
redis.connectionAttempts | +int | +no | +3 | +Max connection attempts | +
redis.connectionCooldown | +duration format | +no | +1s | +Time between the connection attempts | +
redis.sentinelUsername | +string | +no | ++ | Sentinel username if necessary | +
redis.sentinelPassword | +string | +no | ++ | Sentinel password if necessary | +
redis.sentinelAddresses | +string[] | +no | ++ | Sentinel host list (Sentinel is activated if addresses are defined) | +
Example
+redis:
+ address: redismaster
+ username: usrname
+ password: passwd
+ database: 2
+ required: true
+ connectionAttempts: 10
+ connectionCooldown: 3s
+ sentinelUsername: sentUsrname
+ sentinelPassword: sentPasswd
+ sentinelAddresses:
+ - redis-sentinel1:26379
+ - redis-sentinel2:26379
+ - redis-sentinel3:26379
+
Blocky can expose various metrics for prometheus. To use the prometheus feature, the HTTP listener must be enabled ( +see Basic Configuration).
+Parameter | +Mandatory | +Default value | +Description | +
---|---|---|---|
prometheus.enable | +no | +false | +If true, enables prometheus metrics | +
prometheus.path | +no | +/metrics | +URL path to the metrics endpoint | +
Example
+prometheus:
+ enable: true
+ path: /metrics
+
You can enable the logging of DNS queries (question, answer, client, duration etc.) to a daily CSV file (can be opened +in Excel or OpenOffice Calc) or MySQL/MariaDB database.
+Warning
+Query file/database contains sensitive information. Please ensure to inform users, if you log their queries.
+You can select one of following query log types:
+mysql
- log each query in the external MySQL/MariaDB databasepostgresql
- log each query in the external PostgreSQL databasecsv
- log into CSV file (one per day)csv-client
- log into CSV file (one per day and per client)console
- log into console outputnone
- do not log any queriesYou can choose which information from processed DNS request and response should be logged in the target system. You can define one or more of following fields:
+clientIP
- origin IP address from the requestclientName
- resolved client name(s) from the origins requestresponseReason
- reason for the response (e.g. from which upstream resolver), response type and coderesponseAnswer
- returned DNS answerquestion
- DNS question from the requestduration
- request processing time in millisecondsHint
+If not defined, blocky will log all available information
+Configuration parameters:
+Parameter | +Type | +Mandatory | +Default value | +Description | +
---|---|---|---|---|
queryLog.type | +enum (mysql, postgresql, csv, csv-client, console, none (see above)) | +no | ++ | Type of logging target. Console if empty | +
queryLog.target | +string | +no | ++ | directory for writing the logs (for csv) or database url (for mysql or postgresql) | +
queryLog.logRetentionDays | +int | +no | +0 | +if > 0, deletes log files/database entries which are older than ... days | +
queryLog.creationAttempts | +int | +no | +3 | +Max attempts to create specific query log writer | +
queryLog.CreationCooldown | +duration format | +no | +2 | +Time between the creation attempts | +
queryLog.fields | +list enum (clientIP, clientName, responseReason, responseAnswer, question, duration) | +no | +all | +which information should be logged | +
Hint
+Please ensure, that the log directory is writable or database exists. If you use docker, please ensure, that the directory is properly +mounted (e.g. volume)
+example for CSV format with limited logging information
+Example
+queryLog:
+ type: csv
+ target: /logs
+ logRetentionDays: 7
+ fields:
+ - clientIP
+ - duration
+
example for Database
+Example
+queryLog:
+ type: mysql
+ target: db_user:db_password@tcp(db_host_or_ip:3306)/db_user?charset=utf8mb4&parseTime=True&loc=Local
+ logRetentionDays: 7
+
You can enable resolving of entries, located in local hosts file.
+Configuration parameters:
+Parameter | +Type | +Mandatory | +Default value | +Description | +
---|---|---|---|---|
hostsFile.filePath | +string | +no | ++ | Path to hosts file (e.g. /etc/hosts on Linux) | +
hostsFile.hostsTTL | +duration (no units is minutes) | +no | +1h | +TTL | +
hostsFile.refreshPeriod | +duration format | +no | +1h | +Time between hosts file refresh | +
hostsFile.filterLoopback | +bool | +no | +false | +Filter loopback addresses (127.0.0.0/8 and ::1) | +
Example
+hostsFile:
+ filePath: /etc/hosts
+ hostsTTL: 60m
+ refreshPeriod: 30m
+
DNS responses can be extended with EDE codes according to RFC8914.
+Configuration parameters:
+Parameter | +Type | +Mandatory | +Default value | +Description | +
---|---|---|---|---|
ede.enable | +bool | +no | +false | +If true, DNS responses are deliverd with EDE codes | +
Example
+ede:
+ enable: true
+
See Wiki - Configuration of HTTPS +for detailed information, how to create and configure SSL certificates.
+DoH url: https://host:port/dns-query
AYDX2)7bPE#FUDDkk4R^WsKIhzf z_PyiYF)rg9{}}Rryx;ph?-OgyHRoJLMs(;BwBf*i0>cb*3yY=h6p!_h!sX>GKi?t7>LW&tCu?;5-#%_@O!9(OVAiZHX`aM_I_%O4 zu5rO85M5#`wc{s7efIx;?Ij1ZwDC@->yjZetX6t+exa0?pNa^qnu}kr;ZW5fwk{i4 z7Hj2><$rjKjYn5gf2-U@Maam=cr3>GozLiv4&!d)`2Y45yFhPkYBX|k#?Qq=pYPtO zD(x{ZSXqm8aO_x%9jP6wsj^@3=EcaU3r{P2Et>v<4>v`swfa5g?sTz7K5i6-Hno>) z0vx1-jEb@G@lXPo-4LR*0u3wV&P7E0;-2-5fmGNS;VsR1ifhWG3-&0HmTKc1>(%SaT3Bfb`Yf%yPyR6KBUyznwkm) zrza;5rKBcm-KxHP`8hfYpqCSXOFs84q;%AMVM>Qh=lWJwx_$bTsnuGYq{EAYC> zTiKXr vapgQO|kuULCKu c?lV2fj)q`Sm{)cT*A;#VRLrPxA`#T@5gXW? zS@ib3#p^sUc|Ej1(;cRGW4C7F=Ay3Pk84PeWX)+YNk01PW>PFa(s%L o5q z%lCG*Q;cx#xY!6moQ*#lEiKl#QoO4~<8cuP=5Zbf@YtG?$;M5V66Gc53cJeyRFXS@ z8L=}09>0xGgV%<;TXpJ-cwS-np+U*J;)W5^`aW2$z64J^Y?&nAetl;zw)i(_R{<2y zohzl~%}<5WYib}zFHK_P0GF^Pg+Q?1=AZuIjIf_SznCmr4(Dm60w?;3wzd*Ogb8c# zb&ASwp!y_|<%9p-IQF%`P}F@Q_PbAJ_oq+j-a>AF8pqlRkl(99U$U}(*!`&PH)0P^ zb;kA*8|$Fzk;saTEqq-U>riN7a!bg^$T-!1tbAjFkc`;Uz{GvM=VE2^89tf`MLyB; zGM;Z=2nQ{iNsXbG6bd~7o*it1w?Iukw*$~Y&m@Rz5#)V~Dcs0&3~5z$qM`K{FCwwA z_qT){6XTP6dqx5tf2mHK*a@S~_q8FM&i?)@HX%BBl$jMBZMWv^3M)M7Tly!97VB%V z<){iL8TkUPCmJVwC&wp)9saR6Qp3do4<9`WmuJY%{G>-r8S1}>nHpL)bQ(DTA?F>Q zz(+qd*vdo8RMwR70uYEB#XDs0mU)qldxNvu s{;O;hOU zVLv71ub&*74-XFpS32d`VZps5o!w&PA~%nZNN_l8ptHSt7ZKA)l%|Q8u71zTde7sC z{$!#Bo#lS;Ljd?J&7J6F@Qn)1zvYIc2%XOkCcr`qn`7+ftljyrF4UX$rSPXwzK5mO z1q4(zSFV4{WpOt7L+|%a?5{16e^5|+hD?{uNGPT7>Ueqnw{PZa$`Wr&%3ZdXAL$PS zSD$&4mtWMl@9~(D^sa-+pC8uOuX(j^*QW8KyUgr?T4*_4q5Wj%(~;iK){g{td>2U~ zsQ*qlqY~w^gb!sZ`Q4ukvQSSlh;O{ziIO9c(K wFW+qxGJ8bvkM>J$ zqwNX?I`7YhUPkAamL7l;%64>Rr2wQBl%eJB=O>`&3j3)KA@6=aXWfYpNxrIHYL1}M zB3C*pir3lmi+VW8+on6uPY(kbKuxIszzO_8svNc$1l+M(sN|AGT*4D#>MWYn(sAMa z=6{cQ4~kC)3zRlOpk8n-vxp8~d2B059hQO(VZ(gBq+;24jYl5A-y<|;$7tvm9R4tQ z4w3&J7t|k1v`tG7WROS^La=eB`}_1?0mJ|MvH$NO#ea`r{_j!Ag&`r@?xnnwbW5(s z7SQ@WQ1Lz#*9uvN00i-zo@N(v-S_zvOmV6GGg}EP9ha|h{IYue&v1e;S2U`;a3`9_ z9^y;1Qx>KCxz{rzJZjV#>kO4$i4Los$A8Vq8phjq3btP*Yf0Th5Krk2mFrij=+rJP zw~0#r8J=8Nd p_sg^1;r@>KA;2$f+qgBquo)6k|XR5`UWVx36{*WG?(_^0CK#E zVK6#-RjRezcxzO}^$YCA>sBmu{ZY%OY}fXA$$F*(jISakBD}5zAU*AzdN`KQRTnXx z_#Q&9>CE(WUG3iu&4+i}gD}iqvi1+H`i^PWhVa|(?)#_f9&DQK89K$1mYXWw##sXX z@)KSw689r(RP;Ke+C_e~oy;1UkPisa5UV-)ur=$m2Nm?bZX$s+&Y4r&N52ALD=N z7OwvYwoiBv_xO|LNG0G@u9NKsh-c(L$!|SI(BK#DJi_ITxwZL>*L*Uq|7F#ez`gEV zkF_=Oham;~YWHz-3)FZ{x5EiTQ$+OcFB`tQ?({3}r!L18M7o9Q@EY2u%uhKEFUf^^ z=Ib~u$-lO@-^R&4F$*^5z111}@8RR}+*Rav*Z4^4?+jpcUvn1Ftg>xLr$)El*#!c1 z{w`i}#V8(*|GNqv^U2q#+3G4 c4O
a -z^J zdiNK*<9d~}p?K5e)g(h0G1bv-Z$m0-`SG1_pj3PNuN}1to_dGjWH+yt7Ah;P8rd{u z1aY7e8e3SyMPk=^P?0zEZQ51k#E56d!lo#x^ Rh!-)tA|!0pHIK9{g%>3SUa($oO^zDK-C-7B<(*uEoR66SvQ+ zw$4wIaNDATJNADUpHrqBxwni3BZy3l$@%^P%DHP#z2KiCw{x{BqgG;DUO`5O= Z#ge4D>oX^y;vyv@E{Z>JCiI zOaPQryHYge<&bll`Ya{B;vyqLf25%^j%tzfO}lyepc4}rsbTN2Twg{#weP?6p +F zM+)8OH+FHcZ#-3bp851Hf@oi+f=QAFpIf8RDX bwsXcDo z*ikThITlP2?5vbjL4D`06Nk`1tIOWt8t*jtfBZ?RIxJjU4WS(KxUXaN%ISC}gAw`r zIMNMrR&3RJY9}!le+9|OH-_{*6uC*#Zww{!N7Xz3o`pPdv3@)jAFSSWErD{X&SAd| zzi=l|HF7T|FA+ !uw1(sUR^D87JK&eWT222_IBIv{ )xAyAVYFo$88{e+t2YlDzP+@t+*WUzMvzg7{d&BvOFQ13; zQuJI-SK%sKNlG^5@LI`W@mf*bh>VU(60mV!X*?DzvjlVKYTIKnQxeBDJbM~UDIE+Z zaNyzW>925QTVHPQB+1v6qG$WIQTnOKox&rD*ix&!-E|qyB3FxW!-BIoo-rphk&oZv z>s|rPyGr{NT@l;1`imd%z-IOABEok@Yn|Fsy}mNH_iJQ_$ ;i%C7|O^M<)>#Pi;;c$* AJS-DP-~-Rna% z=6sa%A6x)#k2ElQf}hV{Kg&LP9FMacYLD(i1Sd!ReGZ=HW_Ct)t8ZOxBS&b6n6)Wc zlA`1()rPd9qW#*B(wb7P13SBD)<5!!kH8BOu`rfHJO%70D_?XN=vP9K=QV#L9gBQb z{HJ=tQ^UzLEFOvi>y>o(6X(^*L5(EFrNxDpDlZ<%$*rvYxr)S+-ooLaFg7
)m|&&=?A0)kp<0Ur8K%;=jxidyyzs($tuKR^8P3yb4N24k<7zJ_bf z@BTnJS?7brjp&1%)0MS@p}vXA*A^zPjz!D2Jj*oS#pG%HGso{)AJr4{$QbOXv7D|+ zNr+MU^z=?MG-H6t0p=wvXPy@iXIX~czqIO^% ZW$E$?Q>)kAp -M^ z(fcHzCE2EIYm7W4jLUw%?a7b|+NUR5d0C&SFS|DcpCvIYERbYo8Sp7t*H<4YX7as% zq26%~saAS)uJ(+x;U|&JTRw$Sb9?K@@m_g25sz*w5q2HRy=0Sln05 bO$E*Gkw=E4Oi0Yj=ktpTW@ZNuxO#AfZlWLoO0HI) z^Do^v?T-v1YPeib5IFzMK0A;^iwkL1**EyT@m)Y4zuMRoP^@ZU6^){_o?z!6o*eG9 zbkUSKKL8*;3R3%A#V$OW;acMk45tW(N#Z-s$_`1q;7!w5l4*LWcbWyuGp1^J=oi%` z&3K5fyo$ z$lmK<{Kw=vUOKw&@)y*zdesxL!O{=XDFBV1gD>E~g)s&i-)&JKd>6f5I z_r6dsdUHWaZkAr|3)mQu8So23G|R2yKBjxWD;D&oMi3|dia8o_Nai1Eoh~&l$X-qg zo*J9Qk& w`4t388)#K;A-}G==(f0f_ltD04f-<*$DRyo zA*nL;ayg^;CFK6+=NPY%GS-oh(zn(nM)#X5f7%l9xm1Gz*qL`Df3c7X`(CBnVMAc_ zA;;&N4gQk;ztJj>@Kpv-FuPhzW&>sx;ri1RMk#@XGhjsFOXSP )~v6AGHIi=kBsWyv)79ejuzB0 zWrvsf(?V~VU8laSa878}Jy%go*a+0(qZPx`|L%Ky^1>p?!-B5srB7;gz%_E~vykry zO oB!MfFkV`f*S!Wb@)wPFLTE=UTR R^ppMENXf ze`xh!ec@g@QR{(+B{|u0cLKL&;MPur1OqMy#qv_IruIwT)kW5UHeC*j*0I}yC3Qtb zs|#z3g*Z2H{B0m@My8WUnj}Lh#@L;k5$US(p{2g3`(?f|FQoZd=_1B=m+9AU1$k!r z_yiRdK`!DYakOHyGPk_6_L7}SS0L>E)6B2wkR63===y`TGJV9)yCx_aX8}y6UcL L9W?PgVB#*UI5TF(@!z52jVpEC)$_H=Uj%e&8+ zA+(<(pm0U+e2@NE#Ep&l;&Z X5>o)`s?=jcH>^xR|nx2`e ze15PtrZBEM)dGNUmVtiJ+KA7g0F*6aT3twDltIt1^XX$<9*z( XZuOjTfLq~+FrNln_K8uWVpz%k?mufxPbex;fl2dmKJV; zkqyCgHW=5mFxDPo3d!1{BFHi=t3sMGSl}Uu2BlTIz6?NdE#r*$7|ZATv)ewC!_YSK z c>Ys1v8w>z<< vqY+p{$4sz4qa7D1rC z0mu4}T)|hbMi&hwl`{&|V0Htg@oY;8vr6X%=wn^kSpPmU<)bU5ZEdy``U2EU^UDD< z#JNpD5+k{izkNT1%4YNeJc5sgAC1zUGi(Kogi;`>JYGEzJf!U0e}gJPKr-}4>bXj1 z6<@+m#4G_C3;W3{TP)Ur;lUf*lLu^UuTLhfuJKqI6^{l7=We>OMKChoW$t|S-FBQ! zyZ;IUs@md9K#DCVD|ijYzFn15AtPH>id$!*VH45ssRuNHFmclUbOWqa{Jd(}MDAkv zK4^FT?(sVfia@uszlTzanv9uuTTL-kmw%%&H=V6<$#1>g4Pn=EybF=RCotr_LthKc zY*vl~?)lhtPy73PKLT hgFQr`C-r?)7lK>=Ckg^Ixef7T{uB~_YC#& z;5;}XzvX &Jp|Gvp$_z)tHQI5-gI3ET%*nejZFUwF;J*i)mvMtKK{# z66ebc41aB}n5U?1VH?&w@>%<_TICZ_RIenxy);QMSc9?%qxl?!inI(rR4Ywux~CAc zdmYky338G;*VJY1(B7AURL`xX^v@9$9)8wF7Xi9xVcEcBpY)#SO`@t_vE;<=zswqh zyz;I4S8RvxMjTS`>2J-G+06krTWb4?DCp9SSD2OZqjmzffY;5TXqHKThom#<7XB@6 zmxhmrKfg%vLKa1nI$j={f9drpXUHKu`p@3ZLq@-G(wKUX?H?T2!gs4R9yKM7aMGE! zESc+zHiMLJw&$V-vfJvGj`GtZwRl|38rK^fcvM48tWad!urd!gK=B>)4o|xh^$=Tx z_(WSD@od8+4qXw82&eP2nkcjRo2R|#3VoI#6AWyDGRsh6j4C_ysxXdkEL~0AnnxOH z3@le~Y9@*71U|xy$*lxSwXX|W(M_G6e 8`RUuwc#~*8P(+p- zUD$5XLZfl!+$AtXwAn9L5Lo2``y@r~xqbG~^hRWOa2OgrAqXDYqwY4KtriD{DM^b_ zQ(&5y+$Azt5WVNa_xqh=)!qEhjud-;`l8JGC*9ZXU7to<`CWP(49-XalOw%%QbbtQ zYtq0Wu_4vr<+I5W%cz2G25~4!Z0V1#VguVG*~X}JL4jO$+)FR>|G%*Qe?-CSWH64a zWFgVH$F!W6^)){yA|d8-`w>_0W4riy>TQPr!0P9-x38c0iZ`?pv#JMg_$Bd+y7vtf z;wXYh_Pa8L-&>qtkhwCdT|p2QwK4&wBhrTN|7(?9&we_Zs_FF7uVX=NY(%=w@#^1D zcI)?ga@anlon}CQMy2!QUQGU$sPogEPksGn((dA~4PFpQ&W9Rb(xz)y7nKwimlTq7 z{FwS_kp=K+U*CgH@|&*ak-2cdU7n*HL3I;IG=-BNl~T$#M=V4~?K;u2?Gnn33=Q=1 z>ipGsT#Cj&VB&{MwNl-oeb7GY#?U)<_h &stWN=cQ&lWf z)6-!QIVn{hx2KrIKi%Uu{e&c-o}4`heHcoCLh$%}eN)u$TMg|rUFUK!#+$s{+UI@1 z#j`F`QWinS*eH+}z}(h>%|ikCwg0A8)!PoY)kqLgvZ~KR2bch)z~J08|LUImiWQcV zbME)k-}t>_63wFpHrXNhszqSXY6bQjP;9wR64N1!PaJ}IN(GJGU?P9DGTxr5o^n`H z8MU0W-@-h@tb51)Utszb6WsKK9_?Sa$wD&(7vTemjQnRd3ypC-VWa(%8J1Hx=ok!s zM^R@p9DPCTQHZD4c`)R(1a1^5R#Od(f@6dV8`(u)gV1W $DZZmrct{ zGkljK;=(c7tCbUDxF$<2Ot&WaWamoVq;b>G@Susg`I(`MG2L|1vzxBA7`{d3Z*#O6 zFX^_ImW`qYx5RA;Pb>j}@7>2d3W;bPCV_7hGH~CRE8fTFJG`nBZ&&60x~!jB(865a zpduvbb@Zul6q-8Jej=*qI(rJXPzZ8cGmG_A1y1ql36*N0JzZIXwsJr)!92;$JheI} zc)%`w{oR^rY0^to}4uJ2$ z=@Qs9<4Gk;byYkJCKb-F%|D8Hrzp5-CP9d(RuJQZRlm8LhkZrXh!|@^4KKBE;=5w_ zg`1cE$hv@Q+n4^OiCUY>p(Gb56q)f_pdtW>Z_XMpmd6dv-4?1**$&l&s_d(=k(v-E z|INk41@*BJA|jH1FjW4mW0s_>3k)^ o?bx!B;~ZZn0;bTxWuAy!o!0jhvC~a zBQ^P*8-5#JhVv}D+v$it9uN6zAG@^Xq4VKfpCoiNhA!j2ucW^xgrKuKjvXpi(DMRA z*zQNanQ!)R5Fm`tJ G#{) 4c7dS19*_Ak9c3i-w%?AGD%WZ)m^i8UAy!9Sk z4R;rl b?3O+yq$><@il ze>C~(pF0;>FG-{7pDfDeL1;RcyV$bE3){-KR(|;;k)@NaabtwKC=2!+7*d eSnwQV2h0m*n&n6Lakx9I|vJW`mEd {HB$d}iiGDXIZjcqAXE&Olj zF&t9lvX2${iJiw>_O9`dS|XnR4GD*GTw0<4vAM1}pYwbGfVt} Os(^fJXtnBio3fg`m_2%CQFDQttT=zuju*VOs;rBC)P> ;oP1E*P^OGQ~cH)k^%^pD!A0h>d-T;CPrBBR5-Cf7Ki_Y+5_`8Mnj5af2Z|GeXP z1%VnwLfKXk1?TGS@iV9&G$vO{JwK$pM9q* N|eI$Rc7iyzKC_` z>V8A}m~?I+4ihZ9AWwIz=GCroCrs#{4eglrsddUpd-~Ch%CNlscJ ~+0}2nb^6j^NhWx>$6m6LO7h-*%6ns4yl~OdTbRPj|0iSHBXp@_F5%ehBKjA%a zBOk;GIY-)oY)1Swjqys}lg~Fnb#XjYPxp_el}hcPNCrSulR*iK-~D6j9KN4+nWaXR z{>)kCeB@}d7DPTL$^&OUA${Ov)YAj)Wohr+H4#X?1ioB-`PXslk)9diroVQm@|3Sl zlB1IYMcCcHe3O5ySXjhGlH~Sk)N;?LTPG;6*My5j!5uA{;0G!BTKA`vh>y<@1V$AQ zH%M39GY4PW$rKd-uefz&ILL>O7QIwh)iWWUyK|S{wvGBNv035LZH{Y_*`Z|*9ZE~~ zv9@ywe%y!t3B6OG9a>&nwSD f{M+& Q_9Da(kw&oeh($@+ltRv6Ig%gz9tp|6(h` >--C=iZWPAtQWMv$rO%&aT}AKw(2rZ*KDAXQQ6MZou@%4tc< z+*Lxlg#^cc?^DrWqPVpwgochV{~YzI98*4$rNnamYPdDIKojuLfxC0OSjOfm^pAk` z#jaUOX~X@NPnD5q%_&t$hr86AMU@=v$gd-^vRtC&X_7_}jEVs#OG6N}6vWEA>Hurh zO7Mr}w=PIi-i7+Isg<3`FXOXVl9;Rj@!x8qvdLD=W4tg!;rjmd>93Y72?&y_6+b!n zJVmMw3ZIwjE%)rW2Ps!40VtQ!r=rqCOP713_PAX)cC^lc3TBIxsK*!J6+m$fm>OG_ zc8$M+8dCLMI>QH?ok{fgEe0o5f9J!T?!8GulK&<5>NNA~4=Qh8eH&BbF}Bg@Est;6 zTS7se_Vx9+-~@p*WyOX>G&&NZc2seLU4g|(AV_2jDCicdm2#knPF+YZg G3%B zcxHD`7 R(xP5DIWI!sr_s4ihobY549 zn={_qf6XoS!AAThR_{ FiI@0pq)krH)Z{sT50Z3Ymb zclR()_lF)l;0g$q3yb~;qL+AKZiw+i{Sks23FKmbkL5;5JTw!zljQ*2@nPrfz3rW6 z!b!CPZl#hTDH_GDHxOiorf9NKQ-JU>7BkxZ#Ey}iDaVEXb~RLFDR6H%Q|$5Vj){`6 z$((FWjmoHFir0Vi-D@3DC6p`=i%%}p-FWCSUbv>h_U!5NH*f4)+wpuQ(#~WH|0eXe zEMG%i_(6IGw-D$SZsUriHfIl=;VBleW*Qm70aA^HiZqjcxYe)G)Y#4b=WJ`!PaH&{ zao9tflN~=T784u_RfwDJ$Gq=v1_<41vZBtWTZB-rscY?Jc}}H_YJA2$hx8O)+_18H zrxreSk?XC&n#Cq-aDVA3byC;OrAh^6f^#)fvuRf0?BMtP6M6rk%N$1aAe2K4?~^Wn z)I>9R@nAVC-~*cP?S#!m>pv3BBfUePwJn(>nwusbbYZ?K<7wy5~IXC-r)| z=FgRnW&n|f=k?{esYjDLAU1cunE0JIqkF@SHKi8+^UD{YuxYTxw9Wwrw`5&0R^xI7 zQI$757|xf)MCk|d2f*`3uNMBWp+;;BSwPeVhAwr7zc~atKToQ!*_fE9*yC_8;V?(F zFK{c}*zZx!?l*qB6Bx_OBT{IUqC)ZDT9P77MByxR{*m|L)3$low9Z}2*49=ZPF7z< zmU9Q!Mmt^}-A97~8t)PFfMQ8wo*1}hf#wZ356f)jNi8VQmhB&y{blZ9aL>9ZIFy(l zr#=0Awij~-+njc#b(iik-o=#~AlkIN>V$wxCmOH^Obk(-D$ot Q8UwESa8 zOX~LL;F27l8vy}w#uAN|+)xYZS%`sfvE@!lyKiJ>=Esk;hBWtGDy!gp{dBC{dylj> z|H^MAEh)-z^vl7_+(EO!qLyNz33d(QQXXuC`Yugc`Ztzwg@>tl?*B7cxH(^n@b6ll zU1A@UKs+HuIK@rTUs}XAxpm(*dAVm`WWIP<6i|6hjm9ZY)FBn(e-s`;(ERH8*-py1 z1DH2D9G}Fvo=h;a0$ucR5C%jxkX-^4fFLKvX1O0KXYad>eGI^COP8Jq3kwXg&LsO| zS?AgL4YnJ-a}*q`a?-D9Q%OxT`^#w|Y;qlKof7@6w&4#m+}K#>b{uC}qJv8ZcOtU2 ztDPf~BA{-i@BENqhaO4&7fO$k6&`$Y3(EZVFP|mxHCRr2N!rq3VIm0S`$tmZ&u`-x ziFwcw>SeGB{;3WQW(QMTC=}pNGb_9xit=fm2lW;pEvhV8r_Y!4UT0H@x~ae%ZVN1! zAK+?KMvapn8d!gOaMA^)O|42P6xi5J8enh_3a|XnM^DcR=k*>1e)L8`>TP~~0J`*T zY~dHr&p6oYemko2mGz)XtEn;9{gwnk%U*Gl^0Iz4`i=)Fs=^02&K*upva^in?_`_r z{Gnk r?B{$1NYk3Tz5!#aOY{5w zdP@KhVMdO8;DdrNcF%(^Tw=KR?^ukpO@HV#=c!`baf2>sksm*r!i0qb#LVOmmXK{` zw%@fA+V(?(OThwi-o@=gLbp cNzTDfH8`*G41kuI~=IhoN>By4@2WO zMTqRM$#foo4B&T0ntaxXJT5Pk2O85C2MrwMKLqDZSJzCKUGb&{5n*DBZd6p!(~shh zA0VDL>hB=!$rEz8BC1SubFX=9dt`HdR0=!@Hk|0znOJ@z6XG8j$dOg0^y2ArrjsLl zO*W1RU0~&sSbWO`n2h*W%H+Vn{6=xiuT&-bg1;1U$7=}0?z2_w+mH75!d0TBv@t33 z*bXU0Y8@B8=IrTjv92lk__}OOjYHonx#H}cJ$eZpP-XNzcwoq`s+*b(mKPg~v(363 zxK!A=N4uXK*t@7Ng~?VYZ#;=++np$8f781==y%Pzs@y7WUVx%>Tva_u$c>G9`TqNs z2UG(3khCD4K=1L~%KNRC2Jyarl^wac0Ce%WajCKRz)4rdelyWmj)-X9$oadQi4!5} zJBhUQiAox3rV+CiGAI6HV3^H{4)+nmNPFWRUA;=BUSdlW@pAyeL@Rw+ERewHeBGnJ zaU-yQi46^n0>WDH;miQT-m}r#3-Qh3MJ2mruE{Rn-%0BNNfHbWYeN=5Vd~Ekz;wa4 zFIYiP-6pHgBy1fTs$Z>6zPPM#TY`LZmPJJrRrpd^u=f)$(|?v *NezUBn2tV|zS#4`M#Q9Y2!}>$aq+L4(rqxmMroYg z<)v9yK}AxZez|s13A}+^&5E1Y`T5y%RW|C|g*`p}Mo{L7X4lf;?EAVvEfF5RvN|KM z&=Wofq*H42j}b8`5iy}!nWWRb2s$G>OQ;1}${3Iw^ca=8HNRtB38>9uyWno#5(88C zg}xS$m~|&s>5G`RS?6CBJUq(yTVmq79a2x8k51xsnbms1^R>?1E{?|q*h#1- !+VBEO;i1U}a*iWbP@d8Ah`gRO-0CoDf~OWB(zZp*URU30bs3&&DC~Jg?V(p! z0PUx%t7k`k_QUXc_~Pkq ZQ{*~O za4F)rUJKo96qSAD qFVeCTgdUuw!608{iHG{hFzSI-d&*UTmlDN zPQ0M53rk{f5C~sy_&~Pz?pJ>L+-EEvbs=yoX7rCr>S}K%=;)*`R?nBtQsxtOv4&>X zm6b3&srk4AgLelOhcA%4(17Xsi@BL{(8v*8T*nc0J+p0ovnRc`)MxB57h%gt!+&Q( z&S0U3=3M~X(k`#*%+?igJhqtkLE4bjK6^&!-!x-gu}u5a7IpH+I=c5Z-)*(Gg;I(k zj*2)ZDX&z~)$$c2= 6bJbd=Bqf1AaFMm~{I099~ zDC-#nxtAH_)x;*vkp$ r=Z8qxCV$L1E8i$ibcx7c;b|Kp+z%9vO1_W5b$ac+>Kdxy!=KfhU6OMM|~*!m8) zX~FstH}`BsMHgqt=qTLo%?TMB&`n6q{#o|2zYSv h`g`}7qf z&*&RR1}l<(M*+<=4LRySi;y%YnPjlN9UiSkhY{1UkYd@)KvCFySHDR0Rh+&S<-L2D zNw)g)iyzfITG3w@!U|e89b6o~ZD6oNAtR4@%nJrdWtgreI$zEmi~c^UY)`c#Y@aAx z2LF+3?&MJd)l9Q4{;^j@hXWjFu(5TWoR*z$|D4cJvFE8-7{1Nn7!-7yf LL# z8P6kmtU=LlTLI^-1BG^EUFgM_XdTp9z9aa(nyxy3g|v8LtmeFDcl^rn& ){Fd&0sQp`jg0aT7s4KUd7xTF#^!b3pt0^(J#v zkAqaxZnwznOt}s_|+wwl$@|GkPNEqdeJn`LR~qv6{v7?|Q~oHLIey;ZLT_4694urT%A zz%L}fUApiuwMo&v5g8fl=g_TBSnrz4hT79|OX#!n_*1%Qrw=pjk&3|I!Fze7bUUVA z`>P|r)TJJI^$>;3fG;yu?Oec?V7fh93??F08ay__6rRGK96^^1Yxo!Vc|5Jf^Put{ zuMvdz8Jo^(+cbB?irGmm#8vCxxbywkU+=qSRdl{;@9))iokBo^f*&^Bqm7gVDf1I} zRvq{%jj_b(mpkOkJYR)B(-M>wULIZ0nwo6#Syv3~THDGKofk0QV-FoT@lVGSNE*+^ zWeEPe3{pMYNz>*=Zc~bX*I=_g wV^?ldgPyv(k7)EGxcDbU=A~WdJ{%|t}rMl-i|{sAujIGJvK{z zYfd2`WDMW7vvaDf6qE61VGB7R!AApDV_ScApS`_L0OD!wa{9H@tfkU{gpe?!e+9TF zQB7OQ&7Wm1&zUS`8r+ZY(!U7XHnO}^j9XKTT?j!rtK6jW)F#D4Ygl(=XLk9VnEG0* zgO9n)E0A{RSu=|x9V~;#vaY>-%w{*lzUCR0%JZgEQDsL)^=4#<+jitTeEgY6DXgqH zzq0by&W=1PlF!t%uWM7q_I=RDT93>AM&B33M#Y(#Fx23T^Yjb4H&8;+#E)!Py+iDd z&m+k-7&{* lud)X%Upt{H-BuG}QH-@TzhJ-f zA~#?AQ}%TP04NYSy-@M{#h0G*Vx^*DVBOr*p69zeg)moWiz#5~lZ*ap>N&G(9ktdA zdszzDP5#BIEG6#(+G+4275?s1 ^wk;Z+H?7Oz&m;UBe_L6cj6cW$p=aHkf?8BdS zm=BZXu5or79FTejcxPk~#)hzqzIIJx2oqMEphISg(A~ZLZ@c^Yb}Qa&IbPPFxxY|M zCAQZ&dVyDr_fRD~`jKGuL+>HZkXW{Ka!1TDH*-_dSstFpjEos~JqRDW)kuhmZ6pb{ z4Ch@zfN{n14t)V6_h&C)#6`WdHMdvW^5fB-ND<+quk!6W3gP$zQ67Gn?6#?cw{PuO zHO%aQsYZ{%CwhV#>aQ?hNASR dZ2@on
G`vaYSQ(FJZM0K@gw$f!xqL^$Z#;v%gL{Zi+ z1|QoUsIjsZ#j6dknu3f`4fAXl>+ekxANfpKGVp&074kUQzNkUHzCTU=hWbdAY0BmF z*E!^Mj+j7nI>hn2oiKl2gsI-auWAhl(AACy&@ 99(+ E zDZgLZJ-!IV2YAornQQ~gg)FnQ %VDgctee{BUQDDJipkaU4n=BPQ3 zvo0yL_y)6$KRGY+!K4iU!yxK5(8v-J5I`eG=-c=(nCt*-F~F+xb52eu88-%1q2!yD zzE9AuRQ~sq)wMP7V}0@bIar{oJ$qJc+)JyZq=bzf?2k*82PSUtDRAwBKYx q_wdRCTS=xL12 za!(rAZNOuZc=n9*{{1)57!>Svz^tbEP1tnDWg77!$xTWu{^+pCPW_ dS$!Q*b0r{(9AJ(hn{h103!K-ai0l}H ~0Hs6^hWQR*G;H=!UdQ0;_%S^pr!UqkSid`M_%=`t?9eET*qxU$A?#1L;TKI4z- zAIiW`6BD(uc;9`3{<;f6;7$Bn;IL!NF|aOctO`RSXHy)e$C$4v@D8Wa+DWMO?k2bu z-!d_H)nP~_Vm-UlZ-jMi(tMj5GNxB7hM;yEZZEx3TLtU`7Pbq=z10?I;n_7MB3N$u zU^P258H>k0=prBd0&P$K5b#uitufeJ)Yc|<&i#y#RTU-J5ShJ%g$0XuUv#WRs&u;! z>;3%2`6>8i!4qjA2b)#f)q%SMq@<*OT`Lt5c);P7oq^#R_pewEI%Z~OTH16v(;0As zLJ%<0b({m=Kro`ws&NJ_D6olwgQfk8wk9fomtbIQjE;;H8FYYX8C*ZXqGtQ;r8}D8 zEAW~e2mdG@PEMOQZx93;M*7J!R8>`>BH#l9F1Rph{!X~77pFVh+oYoILSLuBdjbr% zY8 wGXw z%Akw;&yw_%rovx7Ajr-@7-1g~PLp*HjUaG9Cg-J=`xQfw#WN_7Qn|A1ze%NH1H!tz zUHo_Rt{=B-NEG7M?^`#&;kfn=kD86*70nkLe`v;OW+`oqZDzrEUAkpi***ALDuI zrTgGU6f)@!63fyuAesWu$j!}7A$pUEhGuGHq~ubkt?IiYkSh_-=FEAp_fC7Ka0L;B zttfy4q-sjE;ojb%lp@Z$3w_eY?f+C?jX}>hiL`A;^cc_xP}+-pHl{)M#z}u_!lhPj zISb_84zR<4jetVA<>9kl8WmH_qe2xf{VE@2YOpblin=u0ntB&s*@t+hpgBnq_@sIv z7#O4tB(h=DW#V6#b%>PliI`GR$cerRWgd;Fyvd>!9B3gjFZGtzRHH oJ;FmmaA0%r^ScI6vd(?KuL|g} z1N8RxHcQdM-&tA`WB5WhQc}Oc;bE|Wlp7}9T80X(VrwY)BSUjh8=GQV{QK7~+M354 z|0p;!L`jTNQR)hrWtB!PO#M#1H=HM$=j4qSbU7=R7q#%qbAuDxXq(uEH_t&F-GnnP z<>7_f4XZ-0+NtZ52~0OlSI(_RANosY^j2261cbWL_8J}@)CqfpyR7ki)S}U~ ASeMlVcYi4DYk}32^JG5({Ph+m6eZm|^f{B~1f1ug%{pmFehW-zn z;}%JKxD~uRyTX&g_^%t}v!a87Fp~0WW2=@8r|MB7EGK`JmlkIA8XrF1ig_V4LPGp4 zzja7&?V9=CG0h?I4oQtLZ$dmNxbM^Y5ZjQr#Uz!r>C%M1Z~7l)-FrCGeH#F9kDdyT zQz&!DoXJ@Up)Ip`=aoasuuu+(hNPMt%Q447sHT-fk{lxDG(FCw y99F2%(Q*cJo*^e*Dfwx?o`&P&CN|IK}%?nQJ0EUtP7q^ zNP~i3hcd9KT?mA?MT&DwW;yI1%wN_$LxonDRbHLKite_Eh4&8+85wy{FHOCwhT?3# zKjF-Ve-5; p<)DE?=(M(m66QBo82=Sm3 ?t5bGXm`cv55PpRK9q1?l_y2L` ne9E3`TpqOC^kY#4Y~jh# zQWtS4sUzV(h?UN=2*em5$NN&ceL$iDr*H=^Z&%V~xJsVT+E-4fT>{dOXlQC?MrSaP z3fO{vEp6?Ul@<6R!^6WchiMZWw_t`1n5fUrYnf%2L6y;5R|Z(D0N|$gkV-2rgO2X= zHpg5`Rz%fRt4;57G*v?2iM^=dowW_#tYi;O3zW&!PnsikQLd&e{Y67=uXWn=S`zlQ zzkeQHgm_<`S{mHbRloY3#ArtCNfv!j@{TS}EwEFr|6w6~WS@qnoynD{0uQ8<4@`K? z7dd4-=!L_!hy?A~<_2A&$n%> @YdGWF!>cVHBR!PEN(=;y>W)g Z9KLg9^5@n;4j*f!^155NeCGsB~F+}1WQiyR8=dC98 z$<4!lDuq6tm7&6&5() Ud!_49>8yu7OBNyE{gKmz9$G|UK|x4eY?IB# zQ(RWfX4Iu6#&eAli@&_1Xqw8qUEdvWyv5d(>Ksxg+D#b-mEWxvj1%n~TFq@}!8bL% zns+ZKD)Fyv`_yTQh8hYN|BJEZ@1}bV11=CtOQMts8=O`pWU%C&h*(jid}d R=%F4#Eg tlWf2G&Z(yxD+S|3AD~=h8r0(RV$45S)F(_!YD41^OqYcR$*hd|0sIu$J z|7JJc>zm0jWt~X%RfwzE44cqVH_Kih9{lKwW@w|NWz=&1Rb g}JCD?&`Y zNF b=#q+R-J}dB%5P|6X&L^ghSW$&FgPE 9O;#dw3uz#UMXEdp3I1c=Kq@ *Sg&?Uu``2IvRccypz?og_q XRJo`d;L^xs5=E#dD~0lV)R`ZX%{6mSmIadz~62Ka9ZS%c~S_ zZ%^DhxNZ=6nU$rXZw;3<6H`<6{A0MHz^(Z(&Nx}~F_dDS6iRDKG`T1er zI06+74d`KY2lzSA>aZFQ3=A|kH;3pGwzX}*b=B0AQJZb4ZOr+wy>yVW`EC5r{WKDV zLfPanA%_m96cDSv@AHz<(o`f7nn8?t2y3jYtb`K*Lv(d>OFM^%$LJf%josbZA-L-e zQ&}P7os7_Zqh)=;-!y)mQ~8;?m~&$gb;FhQ1t2Bo&%@Y?!E-mhmiq`O#liBX>)qCM zmbe^$W4zgnc28_eYb%@AC}^^}x*A+^XkO9N-Tk+?2P|g8If3xCcf~@d4KkT*IyU(z zaL%nmPiw_;OAj7UbxK|IFL;Bx33t+iPqm~pFaPDd0OpT| zREbxk0%JaPdUAL++ twJK!|lcRWw|b4Bdzwob1gk zZA^(>JRD4kP2DZcKtSBrYO*YyjJbb>d>jAJ13NP0#%3h_1J1O)4O($lJ*rEvt0GlM zeaE+hWDY`X{(Zl_{E79gbvt`F=Sk4)`E{5ep3Qsmb#MQtuJ8Ey@l$%W^}4agYpdjN z-V4J+Dvn>^l|x{A;NbPHj`L%Kta@AE`t6ys-jC_wb7&ee=?dS&!Iyx1dSv>Z#E*&X z;yyoi%a`CRGVl8Ry`MAr`uI)nA?B~2@2?*I1Uy9mF!o(kn&kDl*-tE zw(RwB=W`F#iJQ=u$H5o&=TpucQ4rDb^N&;H`s!`$+Z`r9)G>YN z?~s>erx#n`?Q;(d--Uqe@w> GPWTFIII@t)3r`fdsyc1MpZR^4GR0al_`jj$TAT zaaU#A#UC%1SVTP0D5GPpBw^@gZGBtM1RNq0v!s2IELZ7U&V0>Wdz9({__wHbHkQRn zfYG&C9VuP{KRjvoXM$^A9+xX`Fs7J*sF1Xao>zzOWv1^WWVbpayuiTKy{I4ik+fi; z1rw)qb)+P_zV~W+{(k)3r_VhYm?ctB Fl{F7{+3vc#?`0u@*2i9; z!h>D#L9h`BFStsu?93A@!o;yT5VB%~%CS)h!Ag6E@hoL3Mb-(ayIX(CLY~(&jAA@- zZAeim78m|bN+g*a>XhfD^02c>)3zroNz-knD4NN#FDqHwsuFW*TeYXCX ;Brbedc@O zy)6cp$@W}3HP5!}!;vstja0Q>vR(PJuqMm7?R0M2y6t#uula8FC{Oe)wA9aOeQPRJ zXWe5OW3Fp0kOmW&RG6JmpAL>=KL!FxkixnxGNqWIJoN!GtQ3LJK+SmtVH%BL24BB% zqkZ+2((aecss1J~rv2fT;ViZJEq;Vw@$S6!MQSb1+p0gzuUnq}27J~%;-CCs#5@J! z=A?fJqR|}OXm7YdXhe(^zyi_u1hqbPVS0&ov~eJ8S;cU5#bKXWQ*2$M&*+54>}uA^ zZG~T>TD7ovI)LtQzuan2#OCLf&k~&7!nE&%uyroP6u)@4`h?>%nI*4f31T(=)_xew zYGuyW)OZbH;P@=kVsFJz*Su-P%^WX7oe5^*nynIcR<>-blN2yS55r`9T2WjsgvvQ> zMpfE~Gw)KB>^!>1|2VZEw^`w;l!t@3N}JmTaStN?fcO#;9#?6}aCJPMp85oE?rd~A z+~jmE%~S;|$kCB_rtzs=6xX~AHg(SVNfV`;+V4f(x=#9=P|X(72Vyu0T<|n`=AE09 zwMkD-p4?hlgN>l+XU(fAkK(p^(l!6pH2zrkugZNVGG(8n35X_f_HEeJP3Z{3IdZg= zSwFkpPf4lAi707h#^(jgszC9@mK0ku)XAn<0k1^m@F*uic9sP<#6m}D>Zqz2^Rwzp z>0MtkD469-e4C;dEtx;|Px^L5tvJ7Bu$F`@LxyhBjUzTVaQZ`)@?S98y*BW3?P!); zoDvBgy#$a!tyr@=@#+@P^E@l-vu!K~xvXfZ)@o@aWZGA#0wj!CGumiWGOOj_z}euh zCtodiOTYzdSbk>?Q1eV0x@fIU8sPCT;+QUYtP_u?jLP5T;PpBk(elmr{EpGRL!AS( zlc-gPh~U()xCta#S*j6yszBRzSMcfE6*$RlHQR=^pIEK=trqq&GFRX{CKl!_E`D!x za!RcoUz%OryF@z8(MI4>=FmA0we>a|N9a@d?)J6$6nWfC%upU>8L8h}kzEK0nf-)W zG>2MH ii7-NZ!#lT6^yZRGM}f@cCXTy7{1t*hfs z76XOP`mK>FVcmiiogXn&rlL-Edr4th@aZa3H-B$d#!b5jvW<|c*X;U%UCj42$A;<&4h}ls oPjtY!9gZf zx>QpZcU^gj&_2=4u*I}YZ4G^-X*CFCbZsWIF3*f=o0*AI8LcHLv7W+oXoGZYNSX%N zkJmXB0vIXg-(W0=O}8cjM2b?GGpGu>$l;n~34+%TIaVuje3%)Gqu{dngth#X{RS?~ zi7i$R&S0hQ!sh+53kEhGWP4{*$i?ypm2&8X3a^_13 BNtSbCCSj@Ru% z&JIvZk*#iadHpK&MI?M7Cdu;lh8LY%6DLN+;2wGd(l(|dtqRG^w@|^}c_<8~Q4ToV z6G|f&wKc)%c9QeZ=v9C?3!d>^ `mSg@|7P2g^`1yH}Wt&z;zAW(wf z9{&i)%ly=}MA&q|GD2=Nh4wEbwK`AUwtH~ L3B(T1|W&FW<-odWMU|OIE-9Q%8$)BO>#!f~4^nYPDGC zY{ei)by^R28E63nferEeR^L91bvlZH>DERl*n07gbP?279tD6gpEzFdT=H?5KWXqq zQpzw0 I7UhO#cv+x_qu7?FnTU>{gSvndgsR-Y2it3B z>`fB##4JG&By|6AEIPPo(DC4?KZPALx8ZQsH~9w;(aws4IR@r~%HWAJ(-Oi8WLpJS z9WjM~oY pn^d%T(SqJXktex1d7P( zPw( ?}e9S{iW@a|6h#E+Z?4L}q*nv+|l+civ)Q(b_vPMHKv9~w0MN7< z1+4VGAajm_B=DQ*E1<5t!cl&N 9Fb#(Q #^5gz4%aMc@AXAwe3~pq1^&Cfgke& zMaW;}T&?m8KK>6@!fzARxS5ydmKgHAk!SG77UY`(azF#M;~D~uFawKKIzC Ze}G-}NmTvJQkj6er8gCTqlBRg^iIVU_VELe(r9!1 3Rp^uy87a&b5=bu?Ln|N&n~e$txl$6$8&aWWP1!Oj zViuT==#3(<`Lr0&^f0&qNXEbg%%CxX8Z=^P6)3sr(v**MF+4;WnmZF>A1Fm6P;8+h z@Gr1{V`0ja85%gLq(Wf^!4Cz>Xa8&z;3Txf+bM%i&d9O+jC;E l%2 &R$4FT;-^OHdLF1YeF)KwJHLXh8m%m zE7DMz#uX5icKO>vA!;odvVNE;aR|p|ati-HbvsTs?o0 c>L=ts@ z=6@b$p(ChoC)nz5j|-3!yJc#~O)66g6dw}Bh%Y%>yO!WVqZiSi;8*=UL~V34Jn)Sm z@X;I=T48637m2;ZM!n*j1Sx}_{RPsX?{Bk>awWhOs IDy6c6-qlvPM%Evn~_5;lP7%$lQVSGos&;IFJpn@u^p;@Hv(SI%V$ZfOSCDhfP z8g698{M`B8cnfNu01f$k6(u*2i&(__8J32Vhg{&XOo |kGjq6FptFBa*`R&VtbgK4gth|)FgfU%zb{jCmbH|I%yRICkbb(<5U$O^PSx= zXTx#ty*A1_vd1c{pq=mtCjaZ7*gv{PiHB8@&38a@`yL4Ez|$w U1i~~WIxd@q$saByy$%XtG(0ZvWUH0e@8xgElq~UVAyLD6H}|Fs z Lt3%l?fs%I zhLZ`38K9FLP=7OR`}C|V*-A&>-4T%;AfcK(m%(wi1B21U9PM94l+wnn15|)P9VFf6 zb_`8+!$pvHh+RfIhqCdVAZQor%-bv$5%xT!909r@xN+pv0$#H;5BMxV#-ZReF9sM- zoHfb#w%-KApc{gyqpMqoPB`ra-@_T)V^xN0BfKlHHF5E&lvR`Qv~O?~obgT>zvDYL z);6FK5Ym-Lc5bf>bZ^2R1wSoK<)y2_4Nzj-jBXB$Zt!u3N)_@`{gEkQw3Sd0&0^O= zjuaD76lYt)Qi-?sq*A!6f5wP MrAKQ__wNB>Ku+&7U7Zk70Q6&`8=mFy^nB=2dwD^%Lzh-;UderNWZ zbH5kt4QC8ai>Rupob%-rjhZ2(_p{f{zrJ~-Jp;JR$(9fq?j=_##TSUAG19W3tu;Zl z2*D^_9n|`_;lj#i-KSqUxnL%oGr#U84H41+z6bz6yy_{02v2;w^kSg_jADq+!dr6z z+2cyCSe|-G%wgxZL<=?<>Fu{{R{F(?2@|SK1i&U0N0zy1>Hn1Ibl;ehIRS(q3B*F= zgL&a6eo&(`aN4uU5s?^0WefvL6s5GbymC!HLYw6Rz15&k$8Ks~R7Ny#J>P`^Yd z4S-cPSSq)p*p-Wp7Z%cL!HQ3m?rVvfCTL-E(R!1k&^1~x(1M1Bt(6ae4@s8ysZLK1 z9FQE$l0_5EP@4lRiM1t~8nr|iz}5=fE;VPVN>!5(e Uw|I z@BSS}384UQQygLz#P76J{7~A-c_Bf8aE(h8pzBJ&1YJ92l>rz2u+l}&f2iLZd7Jw= zrYIedX-{si3xZ+uT!X-3M&9w;nHx$nK@le1Cb$7eO>k&D;zsr1F)Ja|j}RX7;UQXk zim`yJgq!=0(C;G_z%jh$8}HPgr~$QE=X1rG8BSqckBOa}`348maD2~V8wmEbr*5@r zC%QsAD~sROe1c48phA;RQssSU;1Cqa*pK=+rQL0;bw%|P7kz6VFYKoM-VJE7GJm2g zX~iVj+VwX$Iw~|*;Q&cLtzfTbOzFi0_9Hrv{#IqL8CNaveqKTaBR8SpuUsXM$S+hH znO&x)*LbfktsSzT5x>2m`l*{GwQfTGrMx9?>NAnh%!CAmc*QlDQ2e(2b qKYOkY2}+%T(-b%=?5B{9aT{p${Z@-zT^Rr zh<<|Ds+kpjk>t3T)q{J4rxii!DYqvpe2S7F7g -2~?281dZne(DC|o+DS0y zn)V}q*028Hu{Dqc>~}QjK)UTq?7T$Jj`;ETMrs^E5=R=M@S=3aQrlXMl3@sD|0%-` z;$=*N-L^`eV+qIE1jS(i#bV6?M5 #$fQpyuWva^ku