commit 89bd8eed08d163724b2ef04107bd977ebcf5180e Author: <> Date: Tue Mar 7 16:26:08 2023 +0000 Deployed 2c108da with MkDocs version: 1.4.2 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 @@ + + + + + + + + + + + + + + + + + + + + blocky + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ +

404 - Not found

+ +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/additional_information/index.html b/additional_information/index.html new file mode 100644 index 00000000..85dec716 --- /dev/null +++ b/additional_information/index.html @@ -0,0 +1,640 @@ + + + + + + + + + + + + + + + + + + + + + + Additional information - blocky + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Additional information

+ +

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

+
+

Debug / Profiling

+

If http listener is enabled, pprof endpoint (/debug/pprof) is enabled +automatically.

+

List sources

+

Some links/ideas for lists:

+

Blacklists

+ +
+

Warning

+

Use only blacklists from the sources you trust!

+
+

Whitelists

+ +

List of public DNS servers

+
+

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.

+
+ + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/assets/images/favicon.png b/assets/images/favicon.png new file mode 100644 index 00000000..1cf13b9f Binary files /dev/null and b/assets/images/favicon.png differ diff --git a/assets/javascripts/bundle.b78d2936.min.js b/assets/javascripts/bundle.b78d2936.min.js new file mode 100644 index 00000000..520d0e51 --- /dev/null +++ b/assets/javascripts/bundle.b78d2936.min.js @@ -0,0 +1,29 @@ +"use strict";(()=>{var Ri=Object.create;var gr=Object.defineProperty;var ki=Object.getOwnPropertyDescriptor;var Hi=Object.getOwnPropertyNames,kt=Object.getOwnPropertySymbols,Pi=Object.getPrototypeOf,yr=Object.prototype.hasOwnProperty,on=Object.prototype.propertyIsEnumerable;var nn=(e,t,r)=>t in e?gr(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,P=(e,t)=>{for(var r in t||(t={}))yr.call(t,r)&&nn(e,r,t[r]);if(kt)for(var r of kt(t))on.call(t,r)&&nn(e,r,t[r]);return e};var an=(e,t)=>{var r={};for(var n in e)yr.call(e,n)&&t.indexOf(n)<0&&(r[n]=e[n]);if(e!=null&&kt)for(var n of kt(e))t.indexOf(n)<0&&on.call(e,n)&&(r[n]=e[n]);return r};var Ht=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var $i=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of Hi(t))!yr.call(e,o)&&o!==r&&gr(e,o,{get:()=>t[o],enumerable:!(n=ki(t,o))||n.enumerable});return e};var yt=(e,t,r)=>(r=e!=null?Ri(Pi(e)):{},$i(t||!e||!e.__esModule?gr(r,"default",{value:e,enumerable:!0}):r,e));var cn=Ht((xr,sn)=>{(function(e,t){typeof xr=="object"&&typeof sn!="undefined"?t():typeof define=="function"&&define.amd?define(t):t()})(xr,function(){"use strict";function e(r){var n=!0,o=!1,i=null,s={text:!0,search:!0,url:!0,tel:!0,email:!0,password:!0,number:!0,date:!0,month:!0,week:!0,time:!0,datetime:!0,"datetime-local":!0};function a(T){return!!(T&&T!==document&&T.nodeName!=="HTML"&&T.nodeName!=="BODY"&&"classList"in T&&"contains"in T.classList)}function c(T){var Qe=T.type,De=T.tagName;return!!(De==="INPUT"&&s[Qe]&&!T.readOnly||De==="TEXTAREA"&&!T.readOnly||T.isContentEditable)}function f(T){T.classList.contains("focus-visible")||(T.classList.add("focus-visible"),T.setAttribute("data-focus-visible-added",""))}function u(T){T.hasAttribute("data-focus-visible-added")&&(T.classList.remove("focus-visible"),T.removeAttribute("data-focus-visible-added"))}function p(T){T.metaKey||T.altKey||T.ctrlKey||(a(r.activeElement)&&f(r.activeElement),n=!0)}function m(T){n=!1}function d(T){a(T.target)&&(n||c(T.target))&&f(T.target)}function h(T){a(T.target)&&(T.target.classList.contains("focus-visible")||T.target.hasAttribute("data-focus-visible-added"))&&(o=!0,window.clearTimeout(i),i=window.setTimeout(function(){o=!1},100),u(T.target))}function v(T){document.visibilityState==="hidden"&&(o&&(n=!0),G())}function G(){document.addEventListener("mousemove",N),document.addEventListener("mousedown",N),document.addEventListener("mouseup",N),document.addEventListener("pointermove",N),document.addEventListener("pointerdown",N),document.addEventListener("pointerup",N),document.addEventListener("touchmove",N),document.addEventListener("touchstart",N),document.addEventListener("touchend",N)}function oe(){document.removeEventListener("mousemove",N),document.removeEventListener("mousedown",N),document.removeEventListener("mouseup",N),document.removeEventListener("pointermove",N),document.removeEventListener("pointerdown",N),document.removeEventListener("pointerup",N),document.removeEventListener("touchmove",N),document.removeEventListener("touchstart",N),document.removeEventListener("touchend",N)}function N(T){T.target.nodeName&&T.target.nodeName.toLowerCase()==="html"||(n=!1,oe())}document.addEventListener("keydown",p,!0),document.addEventListener("mousedown",m,!0),document.addEventListener("pointerdown",m,!0),document.addEventListener("touchstart",m,!0),document.addEventListener("visibilitychange",v,!0),G(),r.addEventListener("focus",d,!0),r.addEventListener("blur",h,!0),r.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&r.host?r.host.setAttribute("data-js-focus-visible",""):r.nodeType===Node.DOCUMENT_NODE&&(document.documentElement.classList.add("js-focus-visible"),document.documentElement.setAttribute("data-js-focus-visible",""))}if(typeof window!="undefined"&&typeof document!="undefined"){window.applyFocusVisiblePolyfill=e;var t;try{t=new CustomEvent("focus-visible-polyfill-ready")}catch(r){t=document.createEvent("CustomEvent"),t.initCustomEvent("focus-visible-polyfill-ready",!1,!1,{})}window.dispatchEvent(t)}typeof document!="undefined"&&e(document)})});var fn=Ht(Er=>{(function(e){var t=function(){try{return!!Symbol.iterator}catch(f){return!1}},r=t(),n=function(f){var u={next:function(){var p=f.shift();return{done:p===void 0,value:p}}};return r&&(u[Symbol.iterator]=function(){return u}),u},o=function(f){return encodeURIComponent(f).replace(/%20/g,"+")},i=function(f){return decodeURIComponent(String(f).replace(/\+/g," "))},s=function(){var f=function(p){Object.defineProperty(this,"_entries",{writable:!0,value:{}});var m=typeof p;if(m!=="undefined")if(m==="string")p!==""&&this._fromString(p);else if(p instanceof f){var d=this;p.forEach(function(oe,N){d.append(N,oe)})}else if(p!==null&&m==="object")if(Object.prototype.toString.call(p)==="[object Array]")for(var h=0;hd[0]?1:0}),f._entries&&(f._entries={});for(var p=0;p1?i(d[1]):"")}})})(typeof global!="undefined"?global:typeof window!="undefined"?window:typeof self!="undefined"?self:Er);(function(e){var t=function(){try{var o=new e.URL("b","http://a");return o.pathname="c d",o.href==="http://a/c%20d"&&o.searchParams}catch(i){return!1}},r=function(){var o=e.URL,i=function(c,f){typeof c!="string"&&(c=String(c)),f&&typeof f!="string"&&(f=String(f));var u=document,p;if(f&&(e.location===void 0||f!==e.location.href)){f=f.toLowerCase(),u=document.implementation.createHTMLDocument(""),p=u.createElement("base"),p.href=f,u.head.appendChild(p);try{if(p.href.indexOf(f)!==0)throw new Error(p.href)}catch(T){throw new Error("URL unable to set base "+f+" due to "+T)}}var m=u.createElement("a");m.href=c,p&&(u.body.appendChild(m),m.href=m.href);var d=u.createElement("input");if(d.type="url",d.value=c,m.protocol===":"||!/:/.test(m.href)||!d.checkValidity()&&!f)throw new TypeError("Invalid URL");Object.defineProperty(this,"_anchorElement",{value:m});var h=new e.URLSearchParams(this.search),v=!0,G=!0,oe=this;["append","delete","set"].forEach(function(T){var Qe=h[T];h[T]=function(){Qe.apply(h,arguments),v&&(G=!1,oe.search=h.toString(),G=!0)}}),Object.defineProperty(this,"searchParams",{value:h,enumerable:!0});var N=void 0;Object.defineProperty(this,"_updateSearchParams",{enumerable:!1,configurable:!1,writable:!1,value:function(){this.search!==N&&(N=this.search,G&&(v=!1,this.searchParams._fromString(this.search),v=!0))}})},s=i.prototype,a=function(c){Object.defineProperty(s,c,{get:function(){return this._anchorElement[c]},set:function(f){this._anchorElement[c]=f},enumerable:!0})};["hash","host","hostname","port","protocol"].forEach(function(c){a(c)}),Object.defineProperty(s,"search",{get:function(){return this._anchorElement.search},set:function(c){this._anchorElement.search=c,this._updateSearchParams()},enumerable:!0}),Object.defineProperties(s,{toString:{get:function(){var c=this;return function(){return c.href}}},href:{get:function(){return this._anchorElement.href.replace(/\?$/,"")},set:function(c){this._anchorElement.href=c,this._updateSearchParams()},enumerable:!0},pathname:{get:function(){return this._anchorElement.pathname.replace(/(^\/?)/,"/")},set:function(c){this._anchorElement.pathname=c},enumerable:!0},origin:{get:function(){var c={"http:":80,"https:":443,"ftp:":21}[this._anchorElement.protocol],f=this._anchorElement.port!=c&&this._anchorElement.port!=="";return this._anchorElement.protocol+"//"+this._anchorElement.hostname+(f?":"+this._anchorElement.port:"")},enumerable:!0},password:{get:function(){return""},set:function(c){},enumerable:!0},username:{get:function(){return""},set:function(c){},enumerable:!0}}),i.createObjectURL=function(c){return o.createObjectURL.apply(o,arguments)},i.revokeObjectURL=function(c){return o.revokeObjectURL.apply(o,arguments)},e.URL=i};if(t()||r(),e.location!==void 0&&!("origin"in e.location)){var n=function(){return e.location.protocol+"//"+e.location.hostname+(e.location.port?":"+e.location.port:"")};try{Object.defineProperty(e.location,"origin",{get:n,enumerable:!0})}catch(o){setInterval(function(){e.location.origin=n()},100)}}})(typeof global!="undefined"?global:typeof window!="undefined"?window:typeof self!="undefined"?self:Er)});var Kr=Ht((Mt,qr)=>{/*! + * clipboard.js v2.0.11 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */(function(t,r){typeof Mt=="object"&&typeof qr=="object"?qr.exports=r():typeof define=="function"&&define.amd?define([],r):typeof Mt=="object"?Mt.ClipboardJS=r():t.ClipboardJS=r()})(Mt,function(){return function(){var e={686:function(n,o,i){"use strict";i.d(o,{default:function(){return Ci}});var s=i(279),a=i.n(s),c=i(370),f=i.n(c),u=i(817),p=i.n(u);function m(j){try{return document.execCommand(j)}catch(O){return!1}}var d=function(O){var E=p()(O);return m("cut"),E},h=d;function v(j){var O=document.documentElement.getAttribute("dir")==="rtl",E=document.createElement("textarea");E.style.fontSize="12pt",E.style.border="0",E.style.padding="0",E.style.margin="0",E.style.position="absolute",E.style[O?"right":"left"]="-9999px";var H=window.pageYOffset||document.documentElement.scrollTop;return E.style.top="".concat(H,"px"),E.setAttribute("readonly",""),E.value=j,E}var G=function(O,E){var H=v(O);E.container.appendChild(H);var I=p()(H);return m("copy"),H.remove(),I},oe=function(O){var E=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body},H="";return typeof O=="string"?H=G(O,E):O instanceof HTMLInputElement&&!["text","search","url","tel","password"].includes(O==null?void 0:O.type)?H=G(O.value,E):(H=p()(O),m("copy")),H},N=oe;function T(j){return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?T=function(E){return typeof E}:T=function(E){return E&&typeof Symbol=="function"&&E.constructor===Symbol&&E!==Symbol.prototype?"symbol":typeof E},T(j)}var Qe=function(){var O=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},E=O.action,H=E===void 0?"copy":E,I=O.container,q=O.target,Me=O.text;if(H!=="copy"&&H!=="cut")throw new Error('Invalid "action" value, use either "copy" or "cut"');if(q!==void 0)if(q&&T(q)==="object"&&q.nodeType===1){if(H==="copy"&&q.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if(H==="cut"&&(q.hasAttribute("readonly")||q.hasAttribute("disabled")))throw new Error(`Invalid "target" attribute. You can't cut text from elements with "readonly" or "disabled" attributes`)}else throw new Error('Invalid "target" value, use a valid Element');if(Me)return N(Me,{container:I});if(q)return H==="cut"?h(q):N(q,{container:I})},De=Qe;function $e(j){return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?$e=function(E){return typeof E}:$e=function(E){return E&&typeof Symbol=="function"&&E.constructor===Symbol&&E!==Symbol.prototype?"symbol":typeof E},$e(j)}function wi(j,O){if(!(j instanceof O))throw new TypeError("Cannot call a class as a function")}function rn(j,O){for(var E=0;E0&&arguments[0]!==void 0?arguments[0]:{};this.action=typeof I.action=="function"?I.action:this.defaultAction,this.target=typeof I.target=="function"?I.target:this.defaultTarget,this.text=typeof I.text=="function"?I.text:this.defaultText,this.container=$e(I.container)==="object"?I.container:document.body}},{key:"listenClick",value:function(I){var q=this;this.listener=f()(I,"click",function(Me){return q.onClick(Me)})}},{key:"onClick",value:function(I){var q=I.delegateTarget||I.currentTarget,Me=this.action(q)||"copy",Rt=De({action:Me,container:this.container,target:this.target(q),text:this.text(q)});this.emit(Rt?"success":"error",{action:Me,text:Rt,trigger:q,clearSelection:function(){q&&q.focus(),window.getSelection().removeAllRanges()}})}},{key:"defaultAction",value:function(I){return vr("action",I)}},{key:"defaultTarget",value:function(I){var q=vr("target",I);if(q)return document.querySelector(q)}},{key:"defaultText",value:function(I){return vr("text",I)}},{key:"destroy",value:function(){this.listener.destroy()}}],[{key:"copy",value:function(I){var q=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body};return N(I,q)}},{key:"cut",value:function(I){return h(I)}},{key:"isSupported",value:function(){var I=arguments.length>0&&arguments[0]!==void 0?arguments[0]:["copy","cut"],q=typeof I=="string"?[I]:I,Me=!!document.queryCommandSupported;return q.forEach(function(Rt){Me=Me&&!!document.queryCommandSupported(Rt)}),Me}}]),E}(a()),Ci=Ai},828:function(n){var o=9;if(typeof Element!="undefined"&&!Element.prototype.matches){var i=Element.prototype;i.matches=i.matchesSelector||i.mozMatchesSelector||i.msMatchesSelector||i.oMatchesSelector||i.webkitMatchesSelector}function s(a,c){for(;a&&a.nodeType!==o;){if(typeof a.matches=="function"&&a.matches(c))return a;a=a.parentNode}}n.exports=s},438:function(n,o,i){var s=i(828);function a(u,p,m,d,h){var v=f.apply(this,arguments);return u.addEventListener(m,v,h),{destroy:function(){u.removeEventListener(m,v,h)}}}function c(u,p,m,d,h){return typeof u.addEventListener=="function"?a.apply(null,arguments):typeof m=="function"?a.bind(null,document).apply(null,arguments):(typeof u=="string"&&(u=document.querySelectorAll(u)),Array.prototype.map.call(u,function(v){return a(v,p,m,d,h)}))}function f(u,p,m,d){return function(h){h.delegateTarget=s(h.target,p),h.delegateTarget&&d.call(u,h)}}n.exports=c},879:function(n,o){o.node=function(i){return i!==void 0&&i instanceof HTMLElement&&i.nodeType===1},o.nodeList=function(i){var s=Object.prototype.toString.call(i);return i!==void 0&&(s==="[object NodeList]"||s==="[object HTMLCollection]")&&"length"in i&&(i.length===0||o.node(i[0]))},o.string=function(i){return typeof i=="string"||i instanceof String},o.fn=function(i){var s=Object.prototype.toString.call(i);return s==="[object Function]"}},370:function(n,o,i){var s=i(879),a=i(438);function c(m,d,h){if(!m&&!d&&!h)throw new Error("Missing required arguments");if(!s.string(d))throw new TypeError("Second argument must be a String");if(!s.fn(h))throw new TypeError("Third argument must be a Function");if(s.node(m))return f(m,d,h);if(s.nodeList(m))return u(m,d,h);if(s.string(m))return p(m,d,h);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function f(m,d,h){return m.addEventListener(d,h),{destroy:function(){m.removeEventListener(d,h)}}}function u(m,d,h){return Array.prototype.forEach.call(m,function(v){v.addEventListener(d,h)}),{destroy:function(){Array.prototype.forEach.call(m,function(v){v.removeEventListener(d,h)})}}}function p(m,d,h){return a(document.body,m,d,h)}n.exports=c},817:function(n){function o(i){var s;if(i.nodeName==="SELECT")i.focus(),s=i.value;else if(i.nodeName==="INPUT"||i.nodeName==="TEXTAREA"){var a=i.hasAttribute("readonly");a||i.setAttribute("readonly",""),i.select(),i.setSelectionRange(0,i.value.length),a||i.removeAttribute("readonly"),s=i.value}else{i.hasAttribute("contenteditable")&&i.focus();var c=window.getSelection(),f=document.createRange();f.selectNodeContents(i),c.removeAllRanges(),c.addRange(f),s=c.toString()}return s}n.exports=o},279:function(n){function o(){}o.prototype={on:function(i,s,a){var c=this.e||(this.e={});return(c[i]||(c[i]=[])).push({fn:s,ctx:a}),this},once:function(i,s,a){var c=this;function f(){c.off(i,f),s.apply(a,arguments)}return f._=s,this.on(i,f,a)},emit:function(i){var s=[].slice.call(arguments,1),a=((this.e||(this.e={}))[i]||[]).slice(),c=0,f=a.length;for(c;c{"use strict";/*! + * escape-html + * Copyright(c) 2012-2013 TJ Holowaychuk + * Copyright(c) 2015 Andreas Lubbe + * Copyright(c) 2015 Tiancheng "Timothy" Gu + * MIT Licensed + */var ns=/["'&<>]/;Go.exports=os;function os(e){var t=""+e,r=ns.exec(t);if(!r)return t;var n,o="",i=0,s=0;for(i=r.index;i0&&i[i.length-1])&&(f[0]===6||f[0]===2)){r=0;continue}if(f[0]===3&&(!i||f[1]>i[0]&&f[1]=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function W(e,t){var r=typeof Symbol=="function"&&e[Symbol.iterator];if(!r)return e;var n=r.call(e),o,i=[],s;try{for(;(t===void 0||t-- >0)&&!(o=n.next()).done;)i.push(o.value)}catch(a){s={error:a}}finally{try{o&&!o.done&&(r=n.return)&&r.call(n)}finally{if(s)throw s.error}}return i}function D(e,t,r){if(r||arguments.length===2)for(var n=0,o=t.length,i;n1||a(m,d)})})}function a(m,d){try{c(n[m](d))}catch(h){p(i[0][3],h)}}function c(m){m.value instanceof et?Promise.resolve(m.value.v).then(f,u):p(i[0][2],m)}function f(m){a("next",m)}function u(m){a("throw",m)}function p(m,d){m(d),i.shift(),i.length&&a(i[0][0],i[0][1])}}function ln(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t=e[Symbol.asyncIterator],r;return t?t.call(e):(e=typeof Ee=="function"?Ee(e):e[Symbol.iterator](),r={},n("next"),n("throw"),n("return"),r[Symbol.asyncIterator]=function(){return this},r);function n(i){r[i]=e[i]&&function(s){return new Promise(function(a,c){s=e[i](s),o(a,c,s.done,s.value)})}}function o(i,s,a,c){Promise.resolve(c).then(function(f){i({value:f,done:a})},s)}}function A(e){return typeof e=="function"}function at(e){var t=function(n){Error.call(n),n.stack=new Error().stack},r=e(t);return r.prototype=Object.create(Error.prototype),r.prototype.constructor=r,r}var $t=at(function(e){return function(r){e(this),this.message=r?r.length+` errors occurred during unsubscription: +`+r.map(function(n,o){return o+1+") "+n.toString()}).join(` + `):"",this.name="UnsubscriptionError",this.errors=r}});function Ve(e,t){if(e){var r=e.indexOf(t);0<=r&&e.splice(r,1)}}var Ie=function(){function e(t){this.initialTeardown=t,this.closed=!1,this._parentage=null,this._finalizers=null}return e.prototype.unsubscribe=function(){var t,r,n,o,i;if(!this.closed){this.closed=!0;var s=this._parentage;if(s)if(this._parentage=null,Array.isArray(s))try{for(var a=Ee(s),c=a.next();!c.done;c=a.next()){var f=c.value;f.remove(this)}}catch(v){t={error:v}}finally{try{c&&!c.done&&(r=a.return)&&r.call(a)}finally{if(t)throw t.error}}else s.remove(this);var u=this.initialTeardown;if(A(u))try{u()}catch(v){i=v instanceof $t?v.errors:[v]}var p=this._finalizers;if(p){this._finalizers=null;try{for(var m=Ee(p),d=m.next();!d.done;d=m.next()){var h=d.value;try{mn(h)}catch(v){i=i!=null?i:[],v instanceof $t?i=D(D([],W(i)),W(v.errors)):i.push(v)}}}catch(v){n={error:v}}finally{try{d&&!d.done&&(o=m.return)&&o.call(m)}finally{if(n)throw n.error}}}if(i)throw new $t(i)}},e.prototype.add=function(t){var r;if(t&&t!==this)if(this.closed)mn(t);else{if(t instanceof e){if(t.closed||t._hasParent(this))return;t._addParent(this)}(this._finalizers=(r=this._finalizers)!==null&&r!==void 0?r:[]).push(t)}},e.prototype._hasParent=function(t){var r=this._parentage;return r===t||Array.isArray(r)&&r.includes(t)},e.prototype._addParent=function(t){var r=this._parentage;this._parentage=Array.isArray(r)?(r.push(t),r):r?[r,t]:t},e.prototype._removeParent=function(t){var r=this._parentage;r===t?this._parentage=null:Array.isArray(r)&&Ve(r,t)},e.prototype.remove=function(t){var r=this._finalizers;r&&Ve(r,t),t instanceof e&&t._removeParent(this)},e.EMPTY=function(){var t=new e;return t.closed=!0,t}(),e}();var Sr=Ie.EMPTY;function It(e){return e instanceof Ie||e&&"closed"in e&&A(e.remove)&&A(e.add)&&A(e.unsubscribe)}function mn(e){A(e)?e():e.unsubscribe()}var Le={onUnhandledError:null,onStoppedNotification:null,Promise:void 0,useDeprecatedSynchronousErrorHandling:!1,useDeprecatedNextContext:!1};var st={setTimeout:function(e,t){for(var r=[],n=2;n0},enumerable:!1,configurable:!0}),t.prototype._trySubscribe=function(r){return this._throwIfClosed(),e.prototype._trySubscribe.call(this,r)},t.prototype._subscribe=function(r){return this._throwIfClosed(),this._checkFinalizedStatuses(r),this._innerSubscribe(r)},t.prototype._innerSubscribe=function(r){var n=this,o=this,i=o.hasError,s=o.isStopped,a=o.observers;return i||s?Sr:(this.currentObservers=null,a.push(r),new Ie(function(){n.currentObservers=null,Ve(a,r)}))},t.prototype._checkFinalizedStatuses=function(r){var n=this,o=n.hasError,i=n.thrownError,s=n.isStopped;o?r.error(i):s&&r.complete()},t.prototype.asObservable=function(){var r=new F;return r.source=this,r},t.create=function(r,n){return new En(r,n)},t}(F);var En=function(e){ie(t,e);function t(r,n){var o=e.call(this)||this;return o.destination=r,o.source=n,o}return t.prototype.next=function(r){var n,o;(o=(n=this.destination)===null||n===void 0?void 0:n.next)===null||o===void 0||o.call(n,r)},t.prototype.error=function(r){var n,o;(o=(n=this.destination)===null||n===void 0?void 0:n.error)===null||o===void 0||o.call(n,r)},t.prototype.complete=function(){var r,n;(n=(r=this.destination)===null||r===void 0?void 0:r.complete)===null||n===void 0||n.call(r)},t.prototype._subscribe=function(r){var n,o;return(o=(n=this.source)===null||n===void 0?void 0:n.subscribe(r))!==null&&o!==void 0?o:Sr},t}(x);var Et={now:function(){return(Et.delegate||Date).now()},delegate:void 0};var wt=function(e){ie(t,e);function t(r,n,o){r===void 0&&(r=1/0),n===void 0&&(n=1/0),o===void 0&&(o=Et);var i=e.call(this)||this;return i._bufferSize=r,i._windowTime=n,i._timestampProvider=o,i._buffer=[],i._infiniteTimeWindow=!0,i._infiniteTimeWindow=n===1/0,i._bufferSize=Math.max(1,r),i._windowTime=Math.max(1,n),i}return t.prototype.next=function(r){var n=this,o=n.isStopped,i=n._buffer,s=n._infiniteTimeWindow,a=n._timestampProvider,c=n._windowTime;o||(i.push(r),!s&&i.push(a.now()+c)),this._trimBuffer(),e.prototype.next.call(this,r)},t.prototype._subscribe=function(r){this._throwIfClosed(),this._trimBuffer();for(var n=this._innerSubscribe(r),o=this,i=o._infiniteTimeWindow,s=o._buffer,a=s.slice(),c=0;c0?e.prototype.requestAsyncId.call(this,r,n,o):(r.actions.push(this),r._scheduled||(r._scheduled=ut.requestAnimationFrame(function(){return r.flush(void 0)})))},t.prototype.recycleAsyncId=function(r,n,o){var i;if(o===void 0&&(o=0),o!=null?o>0:this.delay>0)return e.prototype.recycleAsyncId.call(this,r,n,o);var s=r.actions;n!=null&&((i=s[s.length-1])===null||i===void 0?void 0:i.id)!==n&&(ut.cancelAnimationFrame(n),r._scheduled=void 0)},t}(Ut);var Tn=function(e){ie(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.flush=function(r){this._active=!0;var n=this._scheduled;this._scheduled=void 0;var o=this.actions,i;r=r||o.shift();do if(i=r.execute(r.state,r.delay))break;while((r=o[0])&&r.id===n&&o.shift());if(this._active=!1,i){for(;(r=o[0])&&r.id===n&&o.shift();)r.unsubscribe();throw i}},t}(Wt);var Te=new Tn(Sn);var _=new F(function(e){return e.complete()});function Dt(e){return e&&A(e.schedule)}function Cr(e){return e[e.length-1]}function Ye(e){return A(Cr(e))?e.pop():void 0}function Oe(e){return Dt(Cr(e))?e.pop():void 0}function Vt(e,t){return typeof Cr(e)=="number"?e.pop():t}var pt=function(e){return e&&typeof e.length=="number"&&typeof e!="function"};function zt(e){return A(e==null?void 0:e.then)}function Nt(e){return A(e[ft])}function qt(e){return Symbol.asyncIterator&&A(e==null?void 0:e[Symbol.asyncIterator])}function Kt(e){return new TypeError("You provided "+(e!==null&&typeof e=="object"?"an invalid object":"'"+e+"'")+" where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.")}function Ni(){return typeof Symbol!="function"||!Symbol.iterator?"@@iterator":Symbol.iterator}var Qt=Ni();function Yt(e){return A(e==null?void 0:e[Qt])}function Gt(e){return pn(this,arguments,function(){var r,n,o,i;return Pt(this,function(s){switch(s.label){case 0:r=e.getReader(),s.label=1;case 1:s.trys.push([1,,9,10]),s.label=2;case 2:return[4,et(r.read())];case 3:return n=s.sent(),o=n.value,i=n.done,i?[4,et(void 0)]:[3,5];case 4:return[2,s.sent()];case 5:return[4,et(o)];case 6:return[4,s.sent()];case 7:return s.sent(),[3,2];case 8:return[3,10];case 9:return r.releaseLock(),[7];case 10:return[2]}})})}function Bt(e){return A(e==null?void 0:e.getReader)}function U(e){if(e instanceof F)return e;if(e!=null){if(Nt(e))return qi(e);if(pt(e))return Ki(e);if(zt(e))return Qi(e);if(qt(e))return On(e);if(Yt(e))return Yi(e);if(Bt(e))return Gi(e)}throw Kt(e)}function qi(e){return new F(function(t){var r=e[ft]();if(A(r.subscribe))return r.subscribe(t);throw new TypeError("Provided object does not correctly implement Symbol.observable")})}function Ki(e){return new F(function(t){for(var r=0;r=2;return function(n){return n.pipe(e?L(function(o,i){return e(o,i,n)}):de,ge(1),r?He(t):Vn(function(){return new Xt}))}}function zn(){for(var e=[],t=0;t=2,!0))}function pe(e){e===void 0&&(e={});var t=e.connector,r=t===void 0?function(){return new x}:t,n=e.resetOnError,o=n===void 0?!0:n,i=e.resetOnComplete,s=i===void 0?!0:i,a=e.resetOnRefCountZero,c=a===void 0?!0:a;return function(f){var u,p,m,d=0,h=!1,v=!1,G=function(){p==null||p.unsubscribe(),p=void 0},oe=function(){G(),u=m=void 0,h=v=!1},N=function(){var T=u;oe(),T==null||T.unsubscribe()};return y(function(T,Qe){d++,!v&&!h&&G();var De=m=m!=null?m:r();Qe.add(function(){d--,d===0&&!v&&!h&&(p=$r(N,c))}),De.subscribe(Qe),!u&&d>0&&(u=new rt({next:function($e){return De.next($e)},error:function($e){v=!0,G(),p=$r(oe,o,$e),De.error($e)},complete:function(){h=!0,G(),p=$r(oe,s),De.complete()}}),U(T).subscribe(u))})(f)}}function $r(e,t){for(var r=[],n=2;ne.next(document)),e}function K(e,t=document){return Array.from(t.querySelectorAll(e))}function z(e,t=document){let r=ce(e,t);if(typeof r=="undefined")throw new ReferenceError(`Missing element: expected "${e}" to be present`);return r}function ce(e,t=document){return t.querySelector(e)||void 0}function _e(){return document.activeElement instanceof HTMLElement&&document.activeElement||void 0}function er(e){return C(b(document.body,"focusin"),b(document.body,"focusout")).pipe(ke(1),l(()=>{let t=_e();return typeof t!="undefined"?e.contains(t):!1}),V(e===_e()),B())}function Xe(e){return{x:e.offsetLeft,y:e.offsetTop}}function Qn(e){return C(b(window,"load"),b(window,"resize")).pipe(Ce(0,Te),l(()=>Xe(e)),V(Xe(e)))}function tr(e){return{x:e.scrollLeft,y:e.scrollTop}}function dt(e){return C(b(e,"scroll"),b(window,"resize")).pipe(Ce(0,Te),l(()=>tr(e)),V(tr(e)))}var Gn=function(){if(typeof Map!="undefined")return Map;function e(t,r){var n=-1;return t.some(function(o,i){return o[0]===r?(n=i,!0):!1}),n}return function(){function t(){this.__entries__=[]}return Object.defineProperty(t.prototype,"size",{get:function(){return this.__entries__.length},enumerable:!0,configurable:!0}),t.prototype.get=function(r){var n=e(this.__entries__,r),o=this.__entries__[n];return o&&o[1]},t.prototype.set=function(r,n){var o=e(this.__entries__,r);~o?this.__entries__[o][1]=n:this.__entries__.push([r,n])},t.prototype.delete=function(r){var n=this.__entries__,o=e(n,r);~o&&n.splice(o,1)},t.prototype.has=function(r){return!!~e(this.__entries__,r)},t.prototype.clear=function(){this.__entries__.splice(0)},t.prototype.forEach=function(r,n){n===void 0&&(n=null);for(var o=0,i=this.__entries__;o0},e.prototype.connect_=function(){!Dr||this.connected_||(document.addEventListener("transitionend",this.onTransitionEnd_),window.addEventListener("resize",this.refresh),ga?(this.mutationsObserver_=new MutationObserver(this.refresh),this.mutationsObserver_.observe(document,{attributes:!0,childList:!0,characterData:!0,subtree:!0})):(document.addEventListener("DOMSubtreeModified",this.refresh),this.mutationEventsAdded_=!0),this.connected_=!0)},e.prototype.disconnect_=function(){!Dr||!this.connected_||(document.removeEventListener("transitionend",this.onTransitionEnd_),window.removeEventListener("resize",this.refresh),this.mutationsObserver_&&this.mutationsObserver_.disconnect(),this.mutationEventsAdded_&&document.removeEventListener("DOMSubtreeModified",this.refresh),this.mutationsObserver_=null,this.mutationEventsAdded_=!1,this.connected_=!1)},e.prototype.onTransitionEnd_=function(t){var r=t.propertyName,n=r===void 0?"":r,o=va.some(function(i){return!!~n.indexOf(i)});o&&this.refresh()},e.getInstance=function(){return this.instance_||(this.instance_=new e),this.instance_},e.instance_=null,e}(),Bn=function(e,t){for(var r=0,n=Object.keys(t);r0},e}(),Xn=typeof WeakMap!="undefined"?new WeakMap:new Gn,Zn=function(){function e(t){if(!(this instanceof e))throw new TypeError("Cannot call a class as a function.");if(!arguments.length)throw new TypeError("1 argument required, but only 0 present.");var r=ya.getInstance(),n=new Aa(t,r,this);Xn.set(this,n)}return e}();["observe","unobserve","disconnect"].forEach(function(e){Zn.prototype[e]=function(){var t;return(t=Xn.get(this))[e].apply(t,arguments)}});var Ca=function(){return typeof rr.ResizeObserver!="undefined"?rr.ResizeObserver:Zn}(),eo=Ca;var to=new x,Ra=$(()=>k(new eo(e=>{for(let t of e)to.next(t)}))).pipe(g(e=>C(ze,k(e)).pipe(R(()=>e.disconnect()))),J(1));function he(e){return{width:e.offsetWidth,height:e.offsetHeight}}function ye(e){return Ra.pipe(S(t=>t.observe(e)),g(t=>to.pipe(L(({target:r})=>r===e),R(()=>t.unobserve(e)),l(()=>he(e)))),V(he(e)))}function bt(e){return{width:e.scrollWidth,height:e.scrollHeight}}function ir(e){let t=e.parentElement;for(;t&&(e.scrollWidth<=t.scrollWidth&&e.scrollHeight<=t.scrollHeight);)t=(e=t).parentElement;return t?e:void 0}var ro=new x,ka=$(()=>k(new IntersectionObserver(e=>{for(let t of e)ro.next(t)},{threshold:0}))).pipe(g(e=>C(ze,k(e)).pipe(R(()=>e.disconnect()))),J(1));function ar(e){return ka.pipe(S(t=>t.observe(e)),g(t=>ro.pipe(L(({target:r})=>r===e),R(()=>t.unobserve(e)),l(({isIntersecting:r})=>r))))}function no(e,t=16){return dt(e).pipe(l(({y:r})=>{let n=he(e),o=bt(e);return r>=o.height-n.height-t}),B())}var sr={drawer:z("[data-md-toggle=drawer]"),search:z("[data-md-toggle=search]")};function oo(e){return sr[e].checked}function Ke(e,t){sr[e].checked!==t&&sr[e].click()}function Ue(e){let t=sr[e];return b(t,"change").pipe(l(()=>t.checked),V(t.checked))}function Ha(e,t){switch(e.constructor){case HTMLInputElement:return e.type==="radio"?/^Arrow/.test(t):!0;case HTMLSelectElement:case HTMLTextAreaElement:return!0;default:return e.isContentEditable}}function Pa(){return C(b(window,"compositionstart").pipe(l(()=>!0)),b(window,"compositionend").pipe(l(()=>!1))).pipe(V(!1))}function io(){let e=b(window,"keydown").pipe(L(t=>!(t.metaKey||t.ctrlKey)),l(t=>({mode:oo("search")?"search":"global",type:t.key,claim(){t.preventDefault(),t.stopPropagation()}})),L(({mode:t,type:r})=>{if(t==="global"){let n=_e();if(typeof n!="undefined")return!Ha(n,r)}return!0}),pe());return Pa().pipe(g(t=>t?_:e))}function le(){return new URL(location.href)}function ot(e){location.href=e.href}function ao(){return new x}function so(e,t){if(typeof t=="string"||typeof t=="number")e.innerHTML+=t.toString();else if(t instanceof Node)e.appendChild(t);else if(Array.isArray(t))for(let r of t)so(e,r)}function M(e,t,...r){let n=document.createElement(e);if(t)for(let o of Object.keys(t))typeof t[o]!="undefined"&&(typeof t[o]!="boolean"?n.setAttribute(o,t[o]):n.setAttribute(o,""));for(let o of r)so(n,o);return n}function cr(e){if(e>999){let t=+((e-950)%1e3>99);return`${((e+1e-6)/1e3).toFixed(t)}k`}else return e.toString()}function co(){return location.hash.substring(1)}function Vr(e){let t=M("a",{href:e});t.addEventListener("click",r=>r.stopPropagation()),t.click()}function $a(){return b(window,"hashchange").pipe(l(co),V(co()),L(e=>e.length>0),J(1))}function fo(){return $a().pipe(l(e=>ce(`[id="${e}"]`)),L(e=>typeof e!="undefined"))}function zr(e){let t=matchMedia(e);return Zt(r=>t.addListener(()=>r(t.matches))).pipe(V(t.matches))}function uo(){let e=matchMedia("print");return C(b(window,"beforeprint").pipe(l(()=>!0)),b(window,"afterprint").pipe(l(()=>!1))).pipe(V(e.matches))}function Nr(e,t){return e.pipe(g(r=>r?t():_))}function fr(e,t={credentials:"same-origin"}){return ue(fetch(`${e}`,t)).pipe(fe(()=>_),g(r=>r.status!==200?Tt(()=>new Error(r.statusText)):k(r)))}function We(e,t){return fr(e,t).pipe(g(r=>r.json()),J(1))}function po(e,t){let r=new DOMParser;return fr(e,t).pipe(g(n=>n.text()),l(n=>r.parseFromString(n,"text/xml")),J(1))}function ur(e){let t=M("script",{src:e});return $(()=>(document.head.appendChild(t),C(b(t,"load"),b(t,"error").pipe(g(()=>Tt(()=>new ReferenceError(`Invalid script: ${e}`))))).pipe(l(()=>{}),R(()=>document.head.removeChild(t)),ge(1))))}function lo(){return{x:Math.max(0,scrollX),y:Math.max(0,scrollY)}}function mo(){return C(b(window,"scroll",{passive:!0}),b(window,"resize",{passive:!0})).pipe(l(lo),V(lo()))}function ho(){return{width:innerWidth,height:innerHeight}}function bo(){return b(window,"resize",{passive:!0}).pipe(l(ho),V(ho()))}function vo(){return Q([mo(),bo()]).pipe(l(([e,t])=>({offset:e,size:t})),J(1))}function pr(e,{viewport$:t,header$:r}){let n=t.pipe(Z("size")),o=Q([n,r]).pipe(l(()=>Xe(e)));return Q([r,t,o]).pipe(l(([{height:i},{offset:s,size:a},{x:c,y:f}])=>({offset:{x:s.x-c,y:s.y-f+i},size:a})))}(()=>{function e(n,o){parent.postMessage(n,o||"*")}function t(...n){return n.reduce((o,i)=>o.then(()=>new Promise(s=>{let a=document.createElement("script");a.src=i,a.onload=s,document.body.appendChild(a)})),Promise.resolve())}var r=class extends EventTarget{constructor(n){super(),this.url=n,this.m=i=>{i.source===this.w&&(this.dispatchEvent(new MessageEvent("message",{data:i.data})),this.onmessage&&this.onmessage(i))},this.e=(i,s,a,c,f)=>{if(s===`${this.url}`){let u=new ErrorEvent("error",{message:i,filename:s,lineno:a,colno:c,error:f});this.dispatchEvent(u),this.onerror&&this.onerror(u)}};let o=document.createElement("iframe");o.hidden=!0,document.body.appendChild(this.iframe=o),this.w.document.open(),this.w.document.write(`", + "mode": "html" + }, + "pluginVersion": "8.3.3", + "title": "Blocking status", + "transparent": true, + "type": "text" + }, + { + "description": "Blocky [version](https://github.com/0xERR0R/blocky) number", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 3 + }, + "id": 55, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/^version$/", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "8.3.3", + "repeatDirection": "v", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": false, + "expr": "blocky_build_info ", + "format": "table", + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Version", + "transformations": [ + { + "id": "labelsToFields", + "options": {} + }, + { + "id": "merge", + "options": {} + } + ], + "transparent": true, + "type": "stat" + }, + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "description": "Average query response time for all query types", + "fieldConfig": { + "defaults": { + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ms" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 6, + "y": 3 + }, + "id": 24, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "8.3.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": true, + "expr": "sum(increase(blocky_request_duration_ms_sum[$__range])) / sum(increase(blocky_request_duration_ms_count[$__range]))", + "format": "table", + "instant": false, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Avg response time", + "transparent": true, + "type": "stat" + }, + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "description": "Number of blacklist entries", + "fieldConfig": { + "defaults": { + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 12, + "y": 5 + }, + "id": 30, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "8.3.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": true, + "expr": "sum(blocky_blacklist_cache) / sum(up{job=\"blocky\"})", + "format": "table", + "instant": false, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Blacklist entries total", + "transparent": true, + "type": "stat" + }, + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "decimals": 2, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 18, + "y": 5 + }, + "id": 28, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "8.3.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": true, + "expr": "sum(go_memstats_sys_bytes{job=\"blocky\"})/sum(up{job=\"blocky\"})", + "format": "table", + "instant": false, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Memory allocated", + "transparent": true, + "type": "stat" + }, + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "description": "Percentage of blocked queries", + "fieldConfig": { + "defaults": { + "decimals": 2, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "max": 1, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 6 + }, + "id": 34, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "8.3.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": true, + "expr": "sum(increase(blocky_response_total{response_type=\"BLOCKED\"}[$__range])) / sum(increase(blocky_query_total[$__range])) ", + "format": "table", + "instant": false, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Queries blocked", + "transparent": true, + "type": "stat" + }, + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "description": "Number of all queries. Shows the last value", + "fieldConfig": { + "defaults": { + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 6, + "y": 6 + }, + "hideTimeOverride": true, + "id": 4, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "8.3.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": true, + "expr": "ceil(sum(increase(blocky_query_total[$__range]))) ", + "format": "table", + "instant": false, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Query Count Total", + "transparent": true, + "type": "stat" + }, + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "description": "Number of entries in the cache. Shows the last value", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 12, + "y": 8 + }, + "id": 45, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "8.3.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": true, + "expr": "sum(blocky_cache_entry_count)/ sum(up{job=\"blocky\"})", + "format": "table", + "instant": false, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Cache entries count", + "transparent": true, + "type": "stat" + }, + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "description": "Cache Hit/Miss ratio. 100 % means, all queries could be answered from the cache, 0% - all queries must be resolved via external DNS", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "max": 1, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 18, + "y": 8 + }, + "id": 47, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "8.3.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": true, + "expr": "sum(increase(blocky_cache_hit_count[$__range])) / (sum(increase(blocky_cache_hit_count[$__range])) + sum(increase(blocky_cache_miss_count[$__range])))", + "format": "table", + "instant": false, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Cache Hit/Miss ratio", + "transparent": true, + "type": "stat" + }, + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "description": "Number of occured errors", + "fieldConfig": { + "defaults": { + "decimals": 0, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "#299c46", + "value": null + }, + { + "color": "rgba(237, 129, 40, 0.89)", + "value": 1 + }, + { + "color": "#d44a3a" + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 9 + }, + "id": 36, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "8.3.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": true, + "expr": "sum(increase(blocky_error_total[$__range]))", + "format": "table", + "instant": false, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Error count", + "transparent": true, + "type": "stat" + }, + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "description": "Amount of performed DNS queries to prefetch cached queries", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 6, + "y": 9 + }, + "id": 53, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "8.3.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": true, + "expr": "ceil(sum(increase(blocky_prefetch_count[$__range])))", + "format": "table", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Prefetch count", + "transparent": true, + "type": "stat" + }, + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "description": "Amount of prefetch queries per minute", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 12, + "y": 11 + }, + "id": 51, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "8.3.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": true, + "expr": "sum(rate(blocky_prefetch_count[5m])) * 60", + "format": "table", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Prefetch rate per min", + "transparent": true, + "type": "stat" + }, + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "description": "How many of cached entries were prefetched automatically", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "max": 1, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 18, + "y": 11 + }, + "id": 58, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "8.3.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": true, + "expr": "sum(increase(blocky_prefetch_hit_count[$__range])) / (sum(increase(blocky_cache_hit_count[$__range])))", + "format": "table", + "instant": false, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Prefetch Hit ratio", + "transparent": true, + "type": "stat" + }, + { + "description": "Time since last list refresh", + "fieldConfig": { + "defaults": { + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 12 + }, + "id": 57, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "8.3.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": false, + "expr": "sum(time() -blocky_last_list_group_refresh)/ sum(up{job=\"blocky\"})", + "format": "table", + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Last list refresh", + "transparent": true, + "type": "stat" + }, + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "description": "Amount of unique domains in the prefetched cache", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 6, + "y": 12 + }, + "id": 49, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "8.3.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": true, + "expr": "sum(blocky_prefetch_domain_name_cache_count)/ sum(up{job=\"blocky\"})", + "format": "table", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Prefetch domain count", + "transparent": true, + "type": "stat" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "avg requests / min", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "links": [], + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 15 + }, + "id": 10, + "options": { + "legend": { + "calcs": [], + "displayMode": "hidden", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "pluginVersion": "8.3.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": true, + "expr": "sum(rate(blocky_query_total[5m])) * 60", + "format": "time_series", + "instant": false, + "interval": "", + "legendFormat": " ", + "refId": "A" + } + ], + "title": "Request rate", + "transparent": true, + "type": "timeseries" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "avg requests / min", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 100, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "links": [], + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 22 + }, + "id": 52, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "pluginVersion": "8.3.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": true, + "expr": "sum by (client) (rate(blocky_query_total[5m])) * 60", + "format": "time_series", + "instant": false, + "interval": "", + "legendFormat": " {{client}}", + "refId": "A" + } + ], + "title": "Request rate per client", + "transparent": true, + "type": "timeseries" + }, + { + "cards": {}, + "color": { + "cardColor": "#FADE2A", + "colorScale": "sqrt", + "colorScheme": "interpolateYlOrBr", + "exponent": 0.5, + "mode": "opacity" + }, + "dataFormat": "tsbuckets", + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 29 + }, + "heatmap": {}, + "hideZeroBuckets": false, + "highlightCards": true, + "id": 22, + "legend": { + "show": true + }, + "reverseYBuckets": false, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": true, + "expr": "sum(increase(blocky_request_duration_ms_bucket{response_type=\"RESOLVED\"}[$__range])) by (le)", + "format": "heatmap", + "instant": false, + "interval": "", + "legendFormat": "{{le}}", + "refId": "A" + } + ], + "title": "request duration (upstream)", + "tooltip": { + "show": true, + "showHistogram": false + }, + "transparent": true, + "type": "heatmap", + "xAxis": { + "show": true + }, + "yAxis": { + "format": "ms", + "logBase": 1, + "show": true + }, + "yBucketBound": "auto" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "decimals": 0, + "mappings": [], + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 38 + }, + "id": 2, + "links": [], + "maxDataPoints": 3, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "values": [ + "value", + "percent" + ] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "single" + } + }, + "pluginVersion": "6.6.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": false, + "expr": " sort_desc(sum by (type) (ceil(increase(blocky_query_total[$__range]))))", + "format": "time_series", + "instant": true, + "interval": "", + "legendFormat": "{{ type }}", + "refId": "A" + } + ], + "title": "Query by type", + "transparent": true, + "type": "piechart" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "decimals": 0, + "mappings": [], + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 38 + }, + "id": 8, + "links": [], + "maxDataPoints": 3, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "values": [ + "value", + "percent" + ] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "single" + } + }, + "pluginVersion": "6.6.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": false, + "expr": "sort_desc(sum by (client) (ceil(increase(blocky_query_total[$__range]))))", + "format": "time_series", + "instant": true, + "interval": "", + "legendFormat": "{{ client }}", + "refId": "A" + } + ], + "title": "Query per Client", + "transparent": true, + "type": "piechart" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "decimals": 0, + "mappings": [], + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 46 + }, + "id": 32, + "links": [], + "maxDataPoints": 3, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "values": [ + "value" + ] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "single" + } + }, + "pluginVersion": "6.6.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": false, + "expr": "topk(1, blocky_blacklist_cache) by (group)", + "format": "time_series", + "instant": true, + "interval": "", + "legendFormat": "{{ group }}", + "refId": "A" + } + ], + "title": "Blacklist by group", + "transparent": true, + "type": "piechart" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "decimals": 0, + "mappings": [], + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 46 + }, + "id": 14, + "links": [], + "maxDataPoints": 3, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "values": [ + "value", + "percent" + ] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": false, + "expr": " sort_desc(sum by (reason) (ceil(increase(blocky_response_total[$__range]))))", + "format": "time_series", + "instant": true, + "interval": "", + "legendFormat": "{{reason}}", + "refId": "A" + } + ], + "title": "Response Reasons", + "transparent": true, + "type": "piechart" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "decimals": 0, + "mappings": [], + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 54 + }, + "id": 38, + "interval": "", + "links": [], + "maxDataPoints": 3, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "values": [ + "value", + "percent" + ] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": false, + "expr": " sort_desc(sum by (response_type) (ceil(increase(blocky_response_total[$__range]))))", + "format": "time_series", + "instant": true, + "interval": "", + "legendFormat": "{{response_type}}", + "refId": "A" + } + ], + "title": "Response Type", + "transparent": true, + "type": "piechart" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "decimals": 0, + "mappings": [], + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 54 + }, + "id": 12, + "links": [], + "maxDataPoints": 3, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "values": [ + "value", + "percent" + ] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": false, + "expr": " sort_desc(sum by (response_code) (ceil(increase(blocky_response_total[$__range]))))", + "format": "time_series", + "instant": true, + "interval": "", + "legendFormat": "{{response_code}}", + "refId": "A" + } + ], + "title": "Response status", + "transparent": true, + "type": "piechart" + } + ], + "refresh": false, + "schemaVersion": 34, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "hide": 2, + "label": "blocky API URL", + "name": "blocky_url", + "query": "${VAR_BLOCKY_URL}", + "skipUrlSync": false, + "type": "constant", + "current": { + "value": "${VAR_BLOCKY_URL}", + "text": "${VAR_BLOCKY_URL}", + "selected": false + }, + "options": [ + { + "value": "${VAR_BLOCKY_URL}", + "text": "${VAR_BLOCKY_URL}", + "selected": false + } + ] + } + ] + }, + "time": { + "from": "now-3h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "blocky", + "uid": "JvOqE4gRk", + "version": 1, + "weekStart": "" +} \ No newline at end of file diff --git a/blocky-query-grafana-postgres.json b/blocky-query-grafana-postgres.json new file mode 100644 index 00000000..4dad81c9 --- /dev/null +++ b/blocky-query-grafana-postgres.json @@ -0,0 +1,882 @@ +{ + "__inputs": [ + { + "name": "DS_POSTGRES", + "label": "Postgres", + "description": "", + "type": "datasource", + "pluginId": "postgres", + "pluginName": "Postgres" + } + ], + "__requires": [ + { + "type": "panel", + "id": "barchart", + "name": "Bar chart", + "version": "" + }, + { + "type": "panel", + "id": "bargauge", + "name": "Bar gauge", + "version": "" + }, + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "8.1.2" + }, + { + "type": "datasource", + "id": "postgres", + "name": "Postgres", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "piechart", + "name": "Pie chart", + "version": "" + }, + { + "type": "panel", + "id": "table", + "name": "Table", + "version": "" + }, + { + "type": "panel", + "id": "timeseries", + "name": "Time series", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": null, + "iteration": 1631130053746, + "links": [], + "panels": [ + { + "cacheTimeout": null, + "datasource": "${DS_POSTGRES}", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "displayName": "${__field.labels.response_type}", + "mappings": [], + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 14, + "interval": null, + "links": [], + "options": { + "displayLabels": [], + "legend": { + "displayMode": "table", + "placement": "right", + "values": [ + "value" + ] + }, + "pieType": "pie", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "single" + } + }, + "pluginVersion": "8.1.2", + "repeatDirection": "v", + "targets": [ + { + "format": "time_series", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT t.response_type, max(t.request_Ts) as time, count(*) as cnt from log_entries t \n WHERE $__timeFilter(t.request_Ts) and \n t.response_type in ($response_type) and \n t.client_name in ($client_name) and \n (length('$question') = 0 or POSITION(lower('$question') IN t.question_name) > 0)\n group by t.response_type\n order by time", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Query count by response type", + "transformations": [], + "type": "piechart" + }, + { + "datasource": "${DS_POSTGRES}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [] + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 6, + "y": 0 + }, + "id": 16, + "options": { + "displayLabels": [], + "legend": { + "displayMode": "table", + "placement": "right", + "values": [ + "value" + ] + }, + "pieType": "pie", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "format": "time_series", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT max(t.request_ts) AS time,\n case when t.reason like 'BLOCKED%' then SPLIT_PART(SPLIT_PART(t.reason,'(',-1), ')',1) else '' end AS metric,\n count(t.reason) AS cnt\nFROM log_entries t\nWHERE t.response_type ='BLOCKED'\n AND $__timeFilter(t.request_Ts)\n AND t.client_name in ($client_name)\n AND (length('$question') = 0 or POSITION(lower('$question') IN t.question_name) > 0)\nGROUP BY 2\nORDER BY time", + "refId": "A", + "select": [ + [ + { + "params": [ + "duration_ms" + ], + "type": "column" + } + ] + ], + "table": "log_entries", + "timeColumn": "request_ts", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Blocked by Blacklist", + "type": "piechart" + }, + { + "cacheTimeout": null, + "datasource": "${DS_POSTGRES}", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 13, + "interval": null, + "links": [], + "options": { + "displayMode": "gradient", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": true + }, + "showUnfilled": true, + "text": {} + }, + "pluginVersion": "8.1.2", + "repeatDirection": "v", + "targets": [ + { + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT max(t.request_Ts) as time, t.client_name as metric, count(*) as cnt from log_entries t \n WHERE $__timeFilter(t.request_Ts) and \n t.response_type in ($response_type) and \n t.client_name in ($client_name) and \n (length('$question') = 0 or POSITION(lower('$question') IN t.question_name) > 0)\n group by t.client_name\n order by 3 desc", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Query count by client", + "transformations": [], + "type": "bargauge" + }, + { + "datasource": "${DS_POSTGRES}", + "description": "Top 20 effective top level domain plus one more label", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 67, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 2 + }, + "displayName": "count", + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 8 + }, + "id": 11, + "options": { + "barWidth": 0.26, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "hidden", + "placement": "bottom" + }, + "orientation": "horizontal", + "showValue": "never", + "stacking": "none", + "text": { + "valueSize": 10 + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "format": "table", + "group": [], + "hide": false, + "metricColumn": "question_name", + "rawQuery": true, + "rawSql": "SELECT t.effective_tldp as metric, count(*) as value from log_entries t \nWHERE $__timeFilter(t.request_Ts) \n and t.response_type in ($response_type) \n and t.client_name in ($client_name) \n and (length('$question') = 0 or POSITION(lower('$question') IN t.question_name) > 0) \n group by t.effective_tldp order by count(*) desc limit 20", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "table": "log_entries", + "timeColumn": "request_ts", + "where": [] + } + ], + "title": "Top 20 effective TLD+1", + "type": "barchart" + }, + { + "datasource": "${DS_POSTGRES}", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 67, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 2 + }, + "displayName": "count", + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 8 + }, + "id": 8, + "options": { + "barWidth": 0.26, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "hidden", + "placement": "bottom" + }, + "orientation": "horizontal", + "showValue": "never", + "stacking": "none", + "text": { + "valueSize": 10 + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "format": "table", + "group": [], + "hide": false, + "metricColumn": "question_name", + "rawQuery": true, + "rawSql": "SELECT t.question_name as metric, count(*) as value from log_entries t \n WHERE $__timeFilter(t.request_Ts) and \n t.response_type in ($response_type) and \n t.client_name in ($client_name) and \n (length('$question') = 0 or POSITION(lower('$question') IN t.question_name) > 0) \n group by t.question_name order by count(*) desc limit 20", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "table": "log_entries", + "timeColumn": "request_ts", + "where": [] + } + ], + "title": "Top 20 queried domains", + "type": "barchart" + }, + { + "datasource": "${DS_POSTGRES}", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "queries count", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 35, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 12, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": 3600000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "displayName": "${__field.labels.client_name}", + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 16 + }, + "id": 12, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "right" + }, + "tooltip": { + "mode": "single" + } + }, + "pluginVersion": "8.1.2", + "targets": [ + { + "format": "time_series", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT\n $__timeGroupAlias(t.request_Ts, '30m'),\n t.client_name,\n count(*) as c\nFROM log_entries t\nWHERE\n $__timeFilter(t.request_Ts) and \n t.response_type in ($response_type) and \n t.client_name in ($client_name) and \n (length('$question') = 0 or POSITION(lower('$question') IN t.question_name) > 0)\nGROUP BY 1,2\nORDER BY 1", + "refId": "A", + "select": [ + [ + { + "params": [ + "duration_ms" + ], + "type": "column" + } + ] + ], + "table": "log_entries", + "timeColumn": "request_ts", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Queries number per client (30m)", + "type": "timeseries" + }, + { + "datasource": "${DS_POSTGRES}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": -1, + "drawStyle": "bars", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "stepBefore", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "line" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "dtdurationms" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 23 + }, + "id": 10, + "options": { + "legend": { + "calcs": [], + "displayMode": "hidden", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "format": "time_series", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT\n EXTRACT(EPOCH from t.request_Ts) as time,\n t.duration_ms\nFROM log_entries t\nWHERE\n $__timeFilter(t.request_Ts) and \n t.response_type in ($response_type) and \n t.client_name in ($client_name) and \n (length('$question') = 0 or POSITION(lower('$question') IN t.question_name) > 0)\nORDER BY request_ts", + "refId": "A", + "select": [ + [ + { + "params": [ + "duration_ms" + ], + "type": "column" + } + ] + ], + "table": "log_entries", + "timeColumn": "request_ts", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Query duration", + "type": "timeseries" + }, + { + "datasource": "${DS_POSTGRES}", + "description": "Last 100 queries, newest on top", + "fieldConfig": { + "defaults": { + "custom": { + "align": null, + "displayMode": "auto", + "filterable": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "time" + }, + "properties": [ + { + "id": "unit", + "value": "dateTimeAsIsoNoDateIfToday" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 31 + }, + "id": 4, + "options": { + "showHeader": true + }, + "pluginVersion": "8.1.2", + "targets": [ + { + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT EXTRACT(EPOCH from t.request_Ts) as \"time\", \n t.client_ip as \"client IP\", \n t.client_name as \"client name\", \n t.duration_ms as \"duration in ms\", \n t.response_type as \"response type\", \n t.question_type as \"question type\", \n t.question_name as \"question name\", \n t.effective_tldp as \"effective TLD+1\", \n t.answer as \"answer\" from log_entries t \n WHERE $__timeFilter(t.request_Ts) and \n t.response_type in ($response_type) and \n t.client_name in ($client_name) and \n (length('$question') = 0 or POSITION(lower('$question') IN t.question_name) > 0) \n order by t.request_Ts desc limit 100", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Last queries", + "type": "table" + } + ], + "refresh": "", + "schemaVersion": 30, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "allValue": "", + "current": {}, + "datasource": "${DS_POSTGRES}", + "definition": "select distinct client_name from log_entries", + "description": null, + "error": null, + "hide": 0, + "includeAll": true, + "label": "Client name", + "multi": true, + "name": "client_name", + "options": [], + "query": "select distinct client_name from log_entries", + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 1, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allValue": null, + "current": {}, + "datasource": "${DS_POSTGRES}", + "definition": "select distinct response_type from log_entries", + "description": null, + "error": null, + "hide": 0, + "includeAll": true, + "label": "Response type", + "multi": true, + "name": "response_type", + "options": [], + "query": "select distinct response_type from log_entries", + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 1, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "current": { + "selected": false, + "text": "", + "value": "" + }, + "description": null, + "error": null, + "hide": 0, + "label": "Domain (contains)", + "name": "question", + "options": [ + { + "selected": true, + "text": "", + "value": "" + } + ], + "query": "", + "skipUrlSync": false, + "type": "textbox" + } + ] + }, + "time": { + "from": "now-24h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Blocky query", + "uid": "AVmWSVWgz", + "version": 3 +} \ No newline at end of file diff --git a/blocky-query-grafana.json b/blocky-query-grafana.json new file mode 100644 index 00000000..336af8cc --- /dev/null +++ b/blocky-query-grafana.json @@ -0,0 +1,882 @@ +{ + "__inputs": [ + { + "name": "DS_MYSQL", + "label": "MySQL", + "description": "", + "type": "datasource", + "pluginId": "mysql", + "pluginName": "MySQL" + } + ], + "__requires": [ + { + "type": "panel", + "id": "barchart", + "name": "Bar chart", + "version": "" + }, + { + "type": "panel", + "id": "bargauge", + "name": "Bar gauge", + "version": "" + }, + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "8.1.2" + }, + { + "type": "datasource", + "id": "mysql", + "name": "MySQL", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "piechart", + "name": "Pie chart", + "version": "" + }, + { + "type": "panel", + "id": "table", + "name": "Table", + "version": "" + }, + { + "type": "panel", + "id": "timeseries", + "name": "Time series", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": null, + "iteration": 1631130053746, + "links": [], + "panels": [ + { + "cacheTimeout": null, + "datasource": "${DS_MYSQL}", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "displayName": "${__field.labels.response_type}", + "mappings": [], + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 14, + "interval": null, + "links": [], + "options": { + "displayLabels": [], + "legend": { + "displayMode": "table", + "placement": "right", + "values": [ + "value" + ] + }, + "pieType": "pie", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "single" + } + }, + "pluginVersion": "8.1.2", + "repeatDirection": "v", + "targets": [ + { + "format": "time_series", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT t.response_type, t.request_Ts as time, count(*) as cnt from log_entries t \n WHERE $__timeFilter(t.request_Ts) and \n t.response_type in ($response_type) and \n t.client_name in ($client_name) and \n (length('$question') = 0 or INSTR(t.question_name, lower('$question')) > 0)\n group by t.response_type\n order by t.request_Ts", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Query count by response type", + "transformations": [], + "type": "piechart" + }, + { + "datasource": "${DS_MYSQL}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [] + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 6, + "y": 0 + }, + "id": 16, + "options": { + "displayLabels": [], + "legend": { + "displayMode": "table", + "placement": "right", + "values": [ + "value" + ] + }, + "pieType": "pie", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "format": "time_series", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT t.request_ts AS time,\n case when t.reason like 'BLOCKED%' then SUBSTRING_INDEX(SUBSTRING_INDEX(t.reason,'(',-1), ')',1) else '' end AS metric,\n count(t.reason) AS cnt\nFROM log_entries t\nWHERE t.response_type ='BLOCKED'\n AND $__timeFilter(t.request_Ts)\n AND t.client_name in ($client_name)\n AND (length('$question') = 0 or INSTR(t.question_name, lower('$question')) > 0)\nGROUP BY 2\nORDER BY time", + "refId": "A", + "select": [ + [ + { + "params": [ + "duration_ms" + ], + "type": "column" + } + ] + ], + "table": "log_entries", + "timeColumn": "request_ts", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Blocked by Blacklist", + "type": "piechart" + }, + { + "cacheTimeout": null, + "datasource": "${DS_MYSQL}", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 13, + "interval": null, + "links": [], + "options": { + "displayMode": "gradient", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": true + }, + "showUnfilled": true, + "text": {} + }, + "pluginVersion": "8.1.2", + "repeatDirection": "v", + "targets": [ + { + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT t.request_Ts as time, t.client_name as metric, count(*) as cnt from log_entries t \n WHERE $__timeFilter(t.request_Ts) and \n t.response_type in ($response_type) and \n t.client_name in ($client_name) and \n (length('$question') = 0 or INSTR(t.question_name, lower('$question')) > 0)\n group by t.client_name\n order by 3 desc", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Query count by client", + "transformations": [], + "type": "bargauge" + }, + { + "datasource": "${DS_MYSQL}", + "description": "Top 20 effective top level domain plus one more label", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 67, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 2 + }, + "displayName": "count", + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 8 + }, + "id": 11, + "options": { + "barWidth": 0.26, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "hidden", + "placement": "bottom" + }, + "orientation": "horizontal", + "showValue": "never", + "stacking": "none", + "text": { + "valueSize": 10 + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "format": "table", + "group": [], + "hide": false, + "metricColumn": "question_name", + "rawQuery": true, + "rawSql": "SELECT t.effective_tldp as metric, count(*) as value from log_entries t \nWHERE $__timeFilter(t.request_Ts) \n and t.response_type in ($response_type) \n and t.client_name in ($client_name) \n and (length('$question') = 0 or INSTR(t.question_name, lower('$question')) > 0) \n group by t.effective_tldp order by count(*) desc limit 20", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "table": "log_entries", + "timeColumn": "request_ts", + "where": [] + } + ], + "title": "Top 20 effective TLD+1", + "type": "barchart" + }, + { + "datasource": "${DS_MYSQL}", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 67, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 2 + }, + "displayName": "count", + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 8 + }, + "id": 8, + "options": { + "barWidth": 0.26, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "hidden", + "placement": "bottom" + }, + "orientation": "horizontal", + "showValue": "never", + "stacking": "none", + "text": { + "valueSize": 10 + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "format": "table", + "group": [], + "hide": false, + "metricColumn": "question_name", + "rawQuery": true, + "rawSql": "SELECT t.question_name as metric, count(*) as value from log_entries t \n WHERE $__timeFilter(t.request_Ts) and \n t.response_type in ($response_type) and \n t.client_name in ($client_name) and \n (length('$question') = 0 or INSTR(t.question_name, lower('$question')) > 0) \n group by t.question_name order by count(*) desc limit 20", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "table": "log_entries", + "timeColumn": "request_ts", + "where": [] + } + ], + "title": "Top 20 queried domains", + "type": "barchart" + }, + { + "datasource": "${DS_MYSQL}", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "queries count", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 35, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 12, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": 3600000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "displayName": "${__field.labels.client_name}", + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 16 + }, + "id": 12, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "right" + }, + "tooltip": { + "mode": "single" + } + }, + "pluginVersion": "8.1.2", + "targets": [ + { + "format": "time_series", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT\n $__timeGroupAlias(t.request_Ts, '30m'),\n t.client_name,\n count(*) as c\nFROM log_entries t\nWHERE\n $__timeFilter(t.request_Ts) and \n t.response_type in ($response_type) and \n t.client_name in ($client_name) and \n (length('$question') = 0 or INSTR(t.question_name, lower('$question')) > 0)\nGROUP BY 1,2\nORDER BY 1", + "refId": "A", + "select": [ + [ + { + "params": [ + "duration_ms" + ], + "type": "column" + } + ] + ], + "table": "log_entries", + "timeColumn": "request_ts", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Queries number per client (30m)", + "type": "timeseries" + }, + { + "datasource": "${DS_MYSQL}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": -1, + "drawStyle": "bars", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "stepBefore", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "line" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "dtdurationms" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 23 + }, + "id": 10, + "options": { + "legend": { + "calcs": [], + "displayMode": "hidden", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "format": "time_series", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT\n UNIX_TIMESTAMP(t.request_Ts) as time,\n t.duration_ms\nFROM log_entries t\nWHERE\n $__timeFilter(t.request_Ts) and \n t.response_type in ($response_type) and \n t.client_name in ($client_name) and \n (length('$question') = 0 or INSTR(t.question_name, lower('$question')) > 0)\nORDER BY request_ts", + "refId": "A", + "select": [ + [ + { + "params": [ + "duration_ms" + ], + "type": "column" + } + ] + ], + "table": "log_entries", + "timeColumn": "request_ts", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Query duration", + "type": "timeseries" + }, + { + "datasource": "${DS_MYSQL}", + "description": "Last 100 queries, newest on top", + "fieldConfig": { + "defaults": { + "custom": { + "align": null, + "displayMode": "auto", + "filterable": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "time" + }, + "properties": [ + { + "id": "unit", + "value": "dateTimeAsIsoNoDateIfToday" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 31 + }, + "id": 4, + "options": { + "showHeader": true + }, + "pluginVersion": "8.1.2", + "targets": [ + { + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT UNIX_TIMESTAMP(t.request_Ts) as \"time\", \n t.client_ip as \"client IP\", \n t.client_name as \"client name\", \n t.duration_ms as \"duration in ms\", \n t.response_type as \"response type\", \n t.question_type as \"question type\", \n t.question_name as \"question name\", \n t.effective_tldp as \"effective TLD+1\", \n t.answer as \"answer\" from log_entries t \n WHERE $__timeFilter(t.request_Ts) and \n t.response_type in ($response_type) and \n t.client_name in ($client_name) and \n (length('$question') = 0 or INSTR(t.question_name, lower('$question')) > 0) \n order by t.request_Ts desc limit 100", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Last queries", + "type": "table" + } + ], + "refresh": "", + "schemaVersion": 30, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "allValue": "", + "current": {}, + "datasource": "${DS_MYSQL}", + "definition": "select distinct client_name from log_entries", + "description": null, + "error": null, + "hide": 0, + "includeAll": true, + "label": "Client name", + "multi": true, + "name": "client_name", + "options": [], + "query": "select distinct client_name from log_entries", + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 1, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allValue": null, + "current": {}, + "datasource": "${DS_MYSQL}", + "definition": "select distinct response_type from log_entries", + "description": null, + "error": null, + "hide": 0, + "includeAll": true, + "label": "Response type", + "multi": true, + "name": "response_type", + "options": [], + "query": "select distinct response_type from log_entries", + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 1, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "current": { + "selected": false, + "text": "", + "value": "" + }, + "description": null, + "error": null, + "hide": 0, + "label": "Domain (contains)", + "name": "question", + "options": [ + { + "selected": true, + "text": "", + "value": "" + } + ], + "query": "", + "skipUrlSync": false, + "type": "textbox" + } + ] + }, + "time": { + "from": "now-24h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Blocky query", + "uid": "AVmWSVWgz", + "version": 3 +} \ No newline at end of file diff --git a/blocky.svg b/blocky.svg new file mode 100644 index 00000000..7bb10e12 --- /dev/null +++ b/blocky.svg @@ -0,0 +1,768 @@ + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config.yml b/config.yml new file mode 100644 index 00000000..738efd12 --- /dev/null +++ b/config.yml @@ -0,0 +1,265 @@ +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 \ No newline at end of file diff --git a/configuration/index.html b/configuration/index.html new file mode 100644 index 00000000..235c9baf --- /dev/null +++ b/configuration/index.html @@ -0,0 +1,2372 @@ + + + + + + + + + + + + + + + + + + + + + + + + Configuration - blocky + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Configuration

+

This chapter describes all configuration options in config.yaml. You can download a reference file with all +configuration properties as JSON.

+
+reference configuration file +
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
+
+
+

Basic configuration

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeMandatoryDefault valueDescription
certFilepathnoPath to cert and key file for SSL encryption (DoH and DoT); if empty, self-signed certificate is generated
keyFilepathnoPath to cert and key file for SSL encryption (DoH and DoT); if empty, self-signed certificate is generated
dohUserAgentstringnoHTTP User Agent for DoH upstreams
minTlsServeVersionstringno1.2Minimum TLS version that the DoT and DoH server use to serve those encrypted DNS requests
startVerifyUpstreamboolnofalseIf true, blocky will fail to start unless at least one upstream server per group is reachable.
connectIPVersionenum (dual, v4, v6)nodualIP version to use for outgoing connections (dual, v4, v6)
+
+

Example

+
minTlsServeVersion: 1.1
+connectIPVersion: v4
+
+
+

Ports configuration

+

All logging port are optional.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDefault valueDescription
ports.dns[IP]:port[,[IP]:port]*53Port(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
+
+
+

Logging configuration

+

All logging options are optional.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDefault valueDescription
log.levelenum (debug, info, warn, error)infoLog level
log.formatenum (text, json)textLog format (text or json).
log.timestampbooltrueLog time stamps (true or false).
log.privacyboolfalseObfuscate 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
+
+
+

Upstream configuration

+

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):

+
    +
  • tcp+udp (UDP and TCP, dependent on query type)
  • +
  • https (aka DoH)
  • +
  • tcp-tls (aka DoT)
  • +
+
+

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].

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeMandatoryDefault value
netenum (tcp+udp, tcp-tls or https)notcp+udp
hostIP or hostnameyes
portint (1 - 65535)no53 for udp/tcp, 853 for tcp-tls and 443 for https
commonNamestringnothe 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.

+

Upstream lookup timeout

+

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
+
+
+

Bootstrap DNS configuration

+

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.

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeMandatoryDefault valueDescription
upstreamUpstream (see above)no
ipsList of IPsyes, if upstream is DoT/DoHOnly 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
+
+
+

Filtering

+

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.

+

FQDN only

+

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
+
+
+

Custom DNS

+

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.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeMandatoryDefault value
customTTLduration (no unit is minutes)no1h
rewritestring: string (domain: domain)no
mappingstring: string (hostname: address list)no
filterUnmappedTypesbooleannotrue
+
+

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.

+

Conditional DNS resolution

+

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.

+

Client name lookup

+

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.

+

Resolving client name from URL/Host

+

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

+

Resolving client name from IP address

+

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.

+

Single name order

+

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.

+

Custom client name mapping

+

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.

+
+

Blocking and whitelisting

+

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.

+

Definition black and 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

+
+

Regex support

+

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
  • +
+

Client groups

+

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

+
+

Block type

+

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):

+ + + + + + + + + + + + + + + + + + + + + + + + + +
blockTypeExampleDescription
zeroIPzeroIPThis is the default block type. Server returns 0.0.0.0 (or :: for IPv6) as result for A and AAAA queries
nxDomainnxDomainreturn NXDOMAIN as return code
custom IPs192.100.100.15, 2001:0db8:85a3:08d3:1319:8a2e:0370:7344comma 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
+
+
+

Block TTL

+

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
+
+
+

List refresh period

+

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.

+

Download

+

You can configure the list download attempts according to your internet connection:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeMandatoryDefault valueDescription
downloadTimeoutduration formatno60sDownload attempt timeout
downloadAttemptsintno3How many download attempts should be performed
downloadCooldownduration formatno1sTime between the download attempts
+
+

Example

+
blocking:
+  downloadTimeout: 4m
+  downloadAttempts: 5
+  downloadCooldown: 10s
+
+
+

Start strategy

+

You can configure the blocking behavior during application start of blocky.
+If no starategy is selected blocking will be used.

+ + + + + + + + + + + + + + + + + + + + + +
startStrategyDescription
blockingall blocking lists will be loaded before DNS resolution starts
failOnErrorlike blocking but blocky will shut down if any download fails
fastDNS resolution starts immediately without blocking which will be enabled after list load is completed
+
+

Example

+
blocking:
+  startStrategy: failOnError
+
+
+

Concurrency

+

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
+
+
+

Caching

+

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.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeMandatoryDefault valueDescription
caching.minTimeduration formatno0 (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.maxTimeduration formatno0 (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.maxItemsCountintno0 (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.prefetchingboolnofalseif 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.prefetchExpiresduration formatno2hPrefetch track time window
caching.prefetchThresholdintno5Name queries threshold for prefetch
caching.prefetchMaxItemsCountintno0 (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.cacheTimeNegativeduration formatno30mTime 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
+
+
+

Redis

+

Blocky can synchronize its cache and blocking state between multiple instances through redis. +Synchronization is disabled if no address is configured.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeMandatoryDefault valueDescription
redis.addressstringnoServer address and port or master name if sentinel is used
redis.usernamestringnoUsername if necessary
redis.passwordstringnoPassword if necessary
redis.databaseintno0Database
redis.requiredboolnofalseConnection is required for blocky to start
redis.connectionAttemptsintno3Max connection attempts
redis.connectionCooldownduration formatno1sTime between the connection attempts
redis.sentinelUsernamestringnoSentinel username if necessary
redis.sentinelPasswordstringnoSentinel password if necessary
redis.sentinelAddressesstring[]noSentinel 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
+
+
+

Prometheus

+

Blocky can expose various metrics for prometheus. To use the prometheus feature, the HTTP listener must be enabled ( +see Basic Configuration).

+ + + + + + + + + + + + + + + + + + + + + + + +
ParameterMandatoryDefault valueDescription
prometheus.enablenofalseIf true, enables prometheus metrics
prometheus.pathno/metricsURL path to the metrics endpoint
+
+

Example

+
prometheus:
+  enable: true
+  path: /metrics
+
+
+

Query logging

+

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.

+
+

Query log types

+

You can select one of following query log types:

+
    +
  • mysql - log each query in the external MySQL/MariaDB database
  • +
  • postgresql - log each query in the external PostgreSQL database
  • +
  • csv - log into CSV file (one per day)
  • +
  • csv-client - log into CSV file (one per day and per client)
  • +
  • console - log into console output
  • +
  • none - do not log any queries
  • +
+

Query log fields

+

You 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 request
  • +
  • clientName - resolved client name(s) from the origins request
  • +
  • responseReason - reason for the response (e.g. from which upstream resolver), response type and code
  • +
  • responseAnswer - returned DNS answer
  • +
  • question - DNS question from the request
  • +
  • duration - request processing time in milliseconds
  • +
+
+

Hint

+

If not defined, blocky will log all available information

+
+

Configuration parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeMandatoryDefault valueDescription
queryLog.typeenum (mysql, postgresql, csv, csv-client, console, none (see above))noType of logging target. Console if empty
queryLog.targetstringnodirectory for writing the logs (for csv) or database url (for mysql or postgresql)
queryLog.logRetentionDaysintno0if > 0, deletes log files/database entries which are older than ... days
queryLog.creationAttemptsintno3Max attempts to create specific query log writer
queryLog.CreationCooldownduration formatno2Time between the creation attempts
queryLog.fieldslist enum (clientIP, clientName, responseReason, responseAnswer, question, duration)noallwhich 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
+
+
+

Hosts file

+

You can enable resolving of entries, located in local hosts file.

+

Configuration parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeMandatoryDefault valueDescription
hostsFile.filePathstringnoPath to hosts file (e.g. /etc/hosts on Linux)
hostsFile.hostsTTLduration (no units is minutes)no1hTTL
hostsFile.refreshPeriodduration formatno1hTime between hosts file refresh
hostsFile.filterLoopbackboolnofalseFilter loopback addresses (127.0.0.0/8 and ::1)
+
+

Example

+
hostsFile:
+  filePath: /etc/hosts
+  hostsTTL: 60m
+  refreshPeriod: 30m
+
+
+

Deliver EDE codes as EDNS0 option

+

DNS responses can be extended with EDE codes according to RFC8914.

+

Configuration parameters:

+ + + + + + + + + + + + + + + + + + + +
ParameterTypeMandatoryDefault valueDescription
ede.enableboolnofalseIf true, DNS responses are deliverd with EDE codes
+
+

Example

+
ede:
+  enable: true
+
+
+

SSL certificate configuration (DoH / TLS listener)

+

See Wiki - Configuration of HTTPS +for detailed information, how to create and configure SSL certificates.

+

DoH url: https://host:port/dns-query

+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/fb_dns_config.png b/fb_dns_config.png new file mode 100644 index 00000000..7e203714 Binary files /dev/null and b/fb_dns_config.png differ diff --git a/grafana-dashboard.png b/grafana-dashboard.png new file mode 100644 index 00000000..6e3e1702 Binary files /dev/null and b/grafana-dashboard.png differ diff --git a/grafana-query-dashboard.png b/grafana-query-dashboard.png new file mode 100644 index 00000000..d72bc04a Binary files /dev/null and b/grafana-query-dashboard.png differ diff --git a/includes/abbreviations/index.html b/includes/abbreviations/index.html new file mode 100644 index 00000000..3afe4d12 --- /dev/null +++ b/includes/abbreviations/index.html @@ -0,0 +1,412 @@ + + + + + + + + + + + + + + + + + + + + Abbreviations - blocky + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Abbreviations

+ + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 00000000..a989fde6 --- /dev/null +++ b/index.html @@ -0,0 +1,573 @@ + + + + + + + + + + + + + + + + + + + + + + blocky + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Blocky

+
+ +
+ +

Blocky is a DNS proxy and ad-blocker for the local network written in Go with following features:

+

Features

+
    +
  • +

    Blocking - ⛔ Blocking of DNS queries with external lists (Ad-block, malware) and whitelisting

    +
      +
    • Definition of black and white lists per client group (Kids, Smart home devices, etc.)
    • +
    • Periodical reload of external black and white lists
    • +
    • Regex support
    • +
    • Blocking of request domain, response CNAME (deep CNAME inspection) and response IP addresses (against IP lists)
    • +
    +
  • +
  • +

    Advanced DNS configuration - 🤓 not just an ad-blocker

    +
      +
    • Custom DNS resolution for certain domain names
    • +
    • Conditional forwarding to external DNS server
    • +
    • Upstream resolvers can be defined per client group
    • +
    +
  • +
  • +

    Performance - 🚀 Improves speed and performance in your network

    +
      +
    • Customizable caching of DNS answers for queries -> improves DNS resolution speed and reduces amount of external DNS + queries
    • +
    • Prefetching and caching of often used queries
    • +
    • Using multiple external resolver simultaneously
    • +
    • Low memory footprint
    • +
    +
  • +
  • +

    Various Protocols - 💻 Supports modern DNS protocols

    +
      +
    • DNS over UDP and TCP
    • +
    • DNS over HTTPS (aka DoH)
    • +
    • DNS over TLS (aka DoT)
    • +
    +
  • +
  • +

    Security and Privacy - 🕶 Secure communication

    +
      +
    • Supports modern DNS extensions: DNSSEC, eDNS, ...
    • +
    • Free configurable blocking lists - no hidden filtering etc.
    • +
    • Provides DoH Endpoint
    • +
    • Uses random upstream resolvers from the configuration - increases your privacy through the distribution of your DNS + traffic over multiple provider
    • +
    • Open source development
    • +
    • Blocky does NOT collect any user data, telemetry, statistics etc.
    • +
    +
  • +
  • +

    Integration - 📔 various integration

    +
      +
    • Prometheus metrics
    • +
    • Prepared Grafana dashboards (Prometheus and database)
    • +
    • Logging of DNS queries per day / per client in CSV format or MySQL/MariaDB/PostgreSQL database - easy to analyze
    • +
    • Various REST API endpoints
    • +
    • CLI tool
    • +
    +
  • +
  • +

    Simple configuration - 👶 single configuration file in YAML format

    +
      +
    • Simple to maintain
    • +
    • Simple to backup
    • +
    +
  • +
  • +

    Simple installation/configuration - ☁ blocky was designed for simple installation

    +
      +
    • Stateless (no database, no temporary files)
    • +
    • Docker image with Multi-arch support
    • +
    • Single binary
    • +
    • Supports x86-64 and ARM architectures -> runs fine on Raspberry PI
    • +
    • Community supported Helm chart for k8s deployment
    • +
    +
  • +
+

Contribution

+

Issues, feature suggestions and pull requests are welcome! Blocky lives on GitHub.

+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/installation/index.html b/installation/index.html new file mode 100644 index 00000000..a428af5f --- /dev/null +++ b/installation/index.html @@ -0,0 +1,873 @@ + + + + + + + + + + + + + + + + + + + + + + + + Installation - blocky + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Installation

+

You can choose one of the following installation options:

+
    +
  • Run as standalone binary
  • +
  • Run as docker container
  • +
  • Kubernetes with helm chart
  • +
+

Prepare your configuration

+

Blocky supports single or multiple YAML files as configuration. Create new config.yaml with your configuration ( +see Configuration for more details and all configuration options).

+

Simple configuration file, which enables only basic features:

+
upstream:
+  default:
+    - 46.182.19.48
+    - 80.241.218.68
+    - tcp-tls:fdns1.dismail.de:853
+    - https://dns.digitale-gesellschaft.ch/dns-query
+blocking:
+  blackLists:
+    ads:
+      - https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
+  clientGroupsBlock:
+    default:
+      - ads
+port: 53
+httpPort: 4000
+
+

Run as standalone binary

+

Download the binary file from GitHub for your architecture and +run ./blocky --config config.yml.

+
+

Warning

+

Please be aware, if you want to use port 53 or 953 on Linux you should add CAP_NET_BIND_SERVICE capability +to the binary or run with root privileges (running as root is not recommended).

+
+

Run with docker

+

Alternative registry

+

Blocky docker images are deployed to DockerHub (spx01/blocky) and GitHub Container Registry (ghcr.io/0xerr0r/blocky) +.

+

Parameters

+

You can define the location of the config file in the container with environment variable "BLOCKY_CONFIG_FILE". +Default value is "/app/config.yml".

+

Docker from command line

+

Execute following command from the command line:

+
docker run --name blocky -v /path/to/config.yml:/app/config.yml -p 4000:4000 -p 53:53/udp spx01/blocky
+
+

Run with docker-compose

+

Create following docker-compose.yml file

+
version: "2.1"
+services:
+  blocky:
+    image: spx01/blocky
+    container_name: blocky
+    restart: unless-stopped
+    # Optional the instance hostname for logging purpose
+    hostname: blocky-hostname
+    ports:
+      - "53:53/tcp"
+      - "53:53/udp"
+      - "4000:4000/tcp"
+    environment:
+      - TZ=Europe/Berlin # Optional to synchronize the log timestamp with host
+    volumes:
+      # Optional to synchronize the log timestamp with host
+      - /etc/localtime:/etc/localtime:ro
+      # config file
+      - ./config.yml:/app/config.yml
+
+

and start docker container with

+
docker-compose up -d
+
+

Advanced setup

+

Following example shows, how to run blocky in a docker container and store query logs on a SAMBA share. Local black and +whitelists directories are mounted as volume. You can create own black or whitelists in these directories and define the +path like '/app/whitelists/whitelist.txt' in the config file.

+
+

Example

+
+
version: "2.1"
+services:
+  blocky:
+    image: spx01/blocky
+    container_name: blocky
+    restart: unless-stopped
+    ports:
+      - "53:53/tcp"
+      - "53:53/udp"
+      - "4000:4000/tcp" # Prometheus stats (if enabled).
+    environment:
+      - TZ=Europe/Berlin
+    volumes:
+      # config file
+      - ./config.yml:/app/config.yml
+      # write query logs in this volume
+      - queryLogs:/logs
+      # put your custom white and blacklists in these directories
+      - ./blacklists:/app/blacklists/
+      - ./whitelists:/app/whitelists/
+
+volumes:
+  queryLogs:
+    driver: local
+    driver_opts:
+      type: cifs
+      o: username=USER,password=PASSWORD,rw
+      device: //NAS_HOSTNAME/blocky  
+
+

Multiple configuration files

+

For complex setups, splitting the configuration between multiple YAML files might be desired. In this case, folder containing YAML files is passed on startup, Blocky will join all the files.

+

./blocky --config ./config/

+
+

Warning

+

Blocky simply joins the multiple YAML files. If a directive (e.g. upstream) is repeated in multiple files, the configuration will not load and start will fail.

+
+

Other installation types

+
+

Warning

+

These projects are maintained by other people.

+
+

Web UI

+

Blocky Frontend provides a Web UI to control blocky. See linked project for installation instructions.

+

Run with helm chart on Kubernetes

+

See this repo, +the documentation +and the configuration instructions for details about running blocky via helm in kubernetes.

+

Run as an App for TrueNAS SCALE

+

You can find the App in the TrueCharts App Catalog +or read the documentation +and configuration instructions for details about running blocky as a native TrueNAS SCALE App.

+

AUR package for Arch Linux

+

See https://aur.archlinux.org/packages/blocky/

+

Package for Alpine Linux

+

See https://pkgs.alpinelinux.org/package/edge/testing/x86/blocky

+

Installation script for CentOS/Fedora

+

See https://github.com/m0zgen/blocky-installer

+

Package for FreeBSD

+

See https://www.freebsd.org/cgi/ports.cgi?query=blocky&stype=all

+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/interfaces/index.html b/interfaces/index.html new file mode 100644 index 00000000..e0671d25 --- /dev/null +++ b/interfaces/index.html @@ -0,0 +1,512 @@ + + + + + + + + + + + + + + + + + + + + + + + + Interfaces - blocky + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Interfaces

+

REST API

+

If http listener is enabled, blocky provides REST API. You can browse the API documentation (Swagger) documentation +under https://0xERR0R.github.io/blocky/swagger.html.

+

CLI

+

Blocky provides a CLI interface to control. This interface uses internally the REST API.

+

To run the CLI, please ensure, that blocky DNS server is running, then execute blocky help for help or

+
    +
  • ./blocky blocking enable to enable blocking
  • +
  • ./blocky blocking disable to disable blocking
  • +
  • ./blocky blocking disable --duration [duration] to disable blocking for a certain amount of time (30s, 5m, 10m30s, + ...)
  • +
  • ./blocky blocking disable --groups ads,othergroup to disable blocking only for special groups
  • +
  • ./blocky blocking status to print current status of blocking
  • +
  • ./blocky query <domain> execute DNS query (A) (simple replacement for dig, useful for debug purposes)
  • +
  • ./blocky query <domain> --type <queryType> execute DNS query with passed query type (A, AAAA, MX, ...)
  • +
  • ./blocky lists refresh reloads all white and blacklists
  • +
+
+

Tip

+

To run this inside docker run docker exec blocky ./blocky blocking status

+
+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/network_configuration/index.html b/network_configuration/index.html new file mode 100644 index 00000000..abc67451 --- /dev/null +++ b/network_configuration/index.html @@ -0,0 +1,546 @@ + + + + + + + + + + + + + + + + + + + + + + + + Network configuration - blocky + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Network configuration

+

In order, to benefit from all the advantages of blocky like ad-blocking, privacy and speed, it is necessary to use +blocky as DNS server for your devices. You can configure DNS server on each device manually or use DHCP in your network +router and push the right settings to your device. With this approach, you will configure blocky only once in your +router and each device in your network will automatically use blocky as DNS server.

+

Transparent configuration with DHCP

+

Let us assume, blocky is installed on a Raspberry PI with fix IP address 192.168.178.2. Each device which connects to +the router will obtain an IP address and receive the network configuration. The IP address of the Raspberry PI should be +pushed to the device as DNS server.

+
┌──────────────┐         ┌─────────────────┐
+│              │         │ Raspberry PI    │
+│  Router      │         │   blocky        │        
+│              │         │ 192.168.178.2   │            
+└─▲─────┬──────┘         └────▲────────────┘        
+  │1    │                     │  3                  
+  │     │                     │                         
+  │     │                     │ 
+  │     │                     │                     
+  │     │                     │
+  │     │                     │
+  │     │                     │
+  │     │       ┌─────────────┴──────┐
+  │     │   2   │                    │
+  │     └───────►  Network device    │
+  │             │    Android         │
+  └─────────────┤                    │
+                └────────────────────┘
+
+

1 - Network device asks the DHCP server (on Router) for the network configuration

+

2 - Router assigns a free IP address to the device and says "Use 192.168.178.2" as DNS server

+

3 - Clients makes DNS queries and is happy to use blocky 😄

+
+

Warning

+

It is necessary to assign the server which runs blocky (e.g. Raspberry PI) a fix IP address.

+
+

Example configuration with FritzBox

+

To configure the DNS server in the FritzBox, please open in the FritzBox web interface:

+
    +
  • in navigation menu on the left side: Home Network -> Network
  • +
  • Network Settings tab on the top
  • +
  • "IPv4 Configuration" Button at the bottom op the page
  • +
  • Enter the IP address of blocky under "Local DNS server", see screenshot
  • +
+

FritzBox DNS configuration

+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/prometheus_grafana/index.html b/prometheus_grafana/index.html new file mode 100644 index 00000000..a441e53f --- /dev/null +++ b/prometheus_grafana/index.html @@ -0,0 +1,652 @@ + + + + + + + + + + + + + + + + + + + + + + + + Prometheus / Grafana - blocky + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Integration in Grafana

+

Prometheus

+

Prometheus export

+

Blocky can optionally export metrics for Prometheus.

+

Following metrics will be exported:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
nameDescription
blocky_blacklist_cache / blocky_whitelist_cacheNumber of entries in blacklist/whitelist cache, partitioned by group
blocky_error_totalCounter for internal errors
blocky_query_totalNumber of total queries, partitioned by client and DNS request type (A, AAAA, PTR, etc)
blocky_request_duration_ms_bucketRequest duration histogram, partitioned by response type (Blocked, cached, etc)
blocky_response_totalNumber of responses, partitioned by response type (Blocked, cached, etc), DNS response code, and reason
blocky_blocking_enabled1 if blocking is enabled, 0 otherwise
blocky_cache_entry_countNumber of entries in cache
blocky_cache_hit_count / blocky_cache_miss_countCache hit/miss counters
blocky_prefetch_countAmount of prefetched DNS responses
blocky_prefetch_domain_name_cache_countAmount of domain names being prefetched
blocky_failed_download_countNumber of failed list downloads
+

Grafana dashboard

+

Example Grafana dashboard +definition as JSON +or at grafana.com +grafana-dashboard.

+

This dashboard shows all relevant statistics and allows enabling and disabling the blocking status.

+

Grafana configuration

+

Please install grafana-piechart-panel and +set disable-sanitize-html +in config or as env to use control buttons to enable/disable the blocking status.

+

Grafana and Prometheus example project

+

This repo contains example docker-compose.yml with +blocky, prometheus (with configured scraper for blocky) and grafana with prometheus datasource.

+

MySQL / MariaDB

+

If database query logging is activated (see Query logging), you can use following +Grafana Dashboard as JSON +or at grafana.com

+

grafana-dashboard.

+

Please define the MySQL source in Grafana, which points to the database with blocky's log entries.

+

Postgres

+

The JSON for a Grafana dashboard equivalent to the MySQL/MariaDB version is located here

+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/search/search_index.json b/search/search_index.json new file mode 100644 index 00000000..fb8701a0 --- /dev/null +++ b/search/search_index.json @@ -0,0 +1 @@ +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Blocky","text":"

Blocky is a DNS proxy and ad-blocker for the local network written in Go with following features:

"},{"location":"#features","title":"Features","text":"
  • Blocking - Blocking of DNS queries with external lists (Ad-block, malware) and whitelisting

    • Definition of black and white lists per client group (Kids, Smart home devices, etc.)
    • Periodical reload of external black and white lists
    • Regex support
    • Blocking of request domain, response CNAME (deep CNAME inspection) and response IP addresses (against IP lists)
  • Advanced DNS configuration - not just an ad-blocker

    • Custom DNS resolution for certain domain names
    • Conditional forwarding to external DNS server
    • Upstream resolvers can be defined per client group
  • Performance - Improves speed and performance in your network

    • Customizable caching of DNS answers for queries -> improves DNS resolution speed and reduces amount of external DNS queries
    • Prefetching and caching of often used queries
    • Using multiple external resolver simultaneously
    • Low memory footprint
  • Various Protocols - Supports modern DNS protocols

    • DNS over UDP and TCP
    • DNS over HTTPS (aka DoH)
    • DNS over TLS (aka DoT)
  • Security and Privacy - Secure communication

    • Supports modern DNS extensions: DNSSEC, eDNS, ...
    • Free configurable blocking lists - no hidden filtering etc.
    • Provides DoH Endpoint
    • Uses random upstream resolvers from the configuration - increases your privacy through the distribution of your DNS traffic over multiple provider
    • Open source development
    • Blocky does NOT collect any user data, telemetry, statistics etc.
  • Integration - various integration

    • Prometheus metrics
    • Prepared Grafana dashboards (Prometheus and database)
    • Logging of DNS queries per day / per client in CSV format or MySQL/MariaDB/PostgreSQL database - easy to analyze
    • Various REST API endpoints
    • CLI tool
  • Simple configuration - single configuration file in YAML format

    • Simple to maintain
    • Simple to backup
  • Simple installation/configuration - blocky was designed for simple installation

    • Stateless (no database, no temporary files)
    • Docker image with Multi-arch support
    • Single binary
    • Supports x86-64 and ARM architectures -> runs fine on Raspberry PI
    • Community supported Helm chart for k8s deployment
"},{"location":"#contribution","title":"Contribution","text":"

Issues, feature suggestions and pull requests are welcome! Blocky lives on GitHub.

"},{"location":"additional_information/","title":"Additional information","text":""},{"location":"additional_information/#print-current-configuration","title":"Print current configuration","text":"

To print runtime configuration / statistics, you can send SIGUSR1 signal to running process.

Summary

Example output:

INFO server: current configuration:\nINFO server: -> resolver: 'ClientNamesResolver'\nINFO server:      singleNameOrder = \"[2 1]\"\nINFO server:      externalResolver = \"upstream 'tcp+udp:192.168.178.1:53'\"\nINFO server:      cache item count = 7\nINFO server: -> resolver: 'QueryLoggingResolver'\nINFO server:      logDir= \"/logs\"\nINFO server:      perClient = false\nINFO server:      logRetentionDays= 7\nINFO server: -> resolver: 'MetricsResolver'\nINFO server:      metrics:\nINFO server:        Enable = true\nINFO server:        Path   = /metrics\nINFO server: -> resolver: 'ConditionalUpstreamResolver'\nINFO server:      fritz.box = \"parallel upstreams 'upstream 'tcp+udp:192.168.178.1:53''\"\nINFO server: -> resolver: 'CustomDNSResolver'\nINFO server: runtime information:\n...\nINFO server: MEM Alloc =                 9 MB\nINFO server: MEM HeapAlloc =             9 MB\nINFO server: MEM Sys =                  88 MB\nINFO server: MEM NumGC =              1533\nINFO server: RUN NumCPU =                4\nINFO server: RUN NumGoroutine =         18\n

Hint

To send a signal to a process you can use kill -s USR1 <PID> or docker kill -s SIGUSR1 blocky for docker setup

"},{"location":"additional_information/#debug-profiling","title":"Debug / Profiling","text":"

If http listener is enabled, pprof endpoint (/debug/pprof) is enabled automatically.

"},{"location":"additional_information/#list-sources","title":"List sources","text":"

Some links/ideas for lists:

"},{"location":"additional_information/#blacklists","title":"Blacklists","text":"
  • https://github.com/StevenBlack/hosts
  • https://github.com/nickspaargaren/no-google
  • https://energized.pro/
  • https://github.com/Perflyst/PiHoleBlocklist
  • https://github.com/kboghdady/youTube_ads_4_pi-hole
  • https://github.com/chadmayfield/my-pihole-blocklists

Warning

Use only blacklists from the sources you trust!

"},{"location":"additional_information/#whitelists","title":"Whitelists","text":"
  • https://github.com/anudeepND/whitelist
"},{"location":"additional_information/#list-of-public-dns-servers","title":"List of public DNS servers","text":"

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.

  • AdGuard
  • CloudFlare
  • Comodo
  • DigitalCourage
  • DigitaleGesellschaft
  • Dismail
  • dnsforge
  • Google
  • OpenDNS
  • Quad9
  • UncensoredDNS
"},{"location":"configuration/","title":"Configuration","text":"

This chapter describes all configuration options in config.yaml. You can download a reference file with all configuration properties as JSON.

reference configuration file
upstream:\n# these external DNS resolvers will be used. Blocky picks 2 random resolvers from the list for each query\n# 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))\n# this configuration is mandatory, please define at least one external DNS resolver\ndefault:\n# example for tcp+udp IPv4 server (https://digitalcourage.de/)\n- 5.9.164.112\n# Cloudflare\n- 1.1.1.1\n# example for DNS-over-TLS server (DoT)\n- tcp-tls:fdns1.dismail.de:853\n# example for DNS-over-HTTPS (DoH)\n- https://dns.digitale-gesellschaft.ch/dns-query\n# optional: use client name (with wildcard support: * - sequence of any characters, [0-9] - range)\n# or single ip address / client subnet as CIDR notation\nlaptop*:\n- 123.123.123.123\n\n# optional: timeout to query the upstream resolver. Default: 2s\nupstreamTimeout: 2s\n\n# optional: If true, blocky will fail to start unless at least one upstream server per group is reachable. Default: false\nstartVerifyUpstream: true\n\n# optional: Determines how blocky will create outgoing connections. This impacts both upstreams, and lists.\n# accepted: dual, v4, v6\n# default: dual\nconnectIPVersion: dual\n\n# optional: custom IP address(es) for domain name (with all sub-domains). Multiple addresses must be separated by a comma\n# example: query \"printer.lan\" or \"my.printer.lan\" will return 192.168.178.3\ncustomDNS:\ncustomTTL: 1h\n# optional: if true (default), return empty result for unmapped query types (for example TXT, MX or AAAA if only IPv4 address is defined).\n# if false, queries with unmapped types will be forwarded to the upstream resolver\nfilterUnmappedTypes: true\n# optional: replace domain in the query with other domain before resolver lookup in the mapping\nrewrite:\nexample.com: printer.lan\nmapping:\nprinter.lan: 192.168.178.3,2001:0db8:85a3:08d3:1319:8a2e:0370:7344\n\n# 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\n# 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\nconditional:\n# 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\n# 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.\n# Usage: One usecase when having split DNS for internal and external (internet facing) users, but not all subdomains are listed in the internal domain.\nfallbackUpstream: false\n# optional: replace domain in the query with other domain before resolver lookup in the mapping\nrewrite:\nexample.com: fritz.box\nmapping:\nfritz.box: 192.168.178.1\nlan.net: 192.168.178.1,192.168.178.2\n\n# optional: use black and white lists to block queries (for example ads, trackers, adult pages etc.)\nblocking:\n# definition of blacklist groups. Can be external link (http/https) or local file\nblackLists:\nads:\n- https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt\n- https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts\n- http://sysctl.org/cameleon/hosts\n- https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt\n- |\n# inline definition with YAML literal block scalar style\n# hosts format\nsomeadsdomain.com\nspecial:\n- https://raw.githubusercontent.com/StevenBlack/hosts/master/alternates/fakenews/hosts\n# 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\nwhiteLists:\nads:\n- whitelist.txt\n- |\n# inline definition with YAML literal block scalar style\n# hosts format\nwhitelistdomain.com\n# this is a regex\n/^banners?[_.-]/\n# definition: which groups should be applied for which client\nclientGroupsBlock:\n# default will be used, if no special definition for a client name exists\ndefault:\n- ads\n- special\n# use client name (with wildcard support: * - sequence of any characters, [0-9] - range)\n# or single ip address / client subnet as CIDR notation\nlaptop*:\n- ads\n192.168.178.1/24:\n- special\n# which response will be sent, if query is blocked:\n# zeroIp: 0.0.0.0 will be returned (default)\n# nxDomain: return NXDOMAIN as return code\n# 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.\nblockType: zeroIp\n# optional: TTL for answers to blocked domains\n# default: 6h\nblockTTL: 1m\n# optional: automatically list refresh period (in duration format). Default: 4h.\n# Negative value -> deactivate automatically refresh.\n# 0 value -> use default\nrefreshPeriod: 4h\n# optional: timeout for list download (each url). Default: 60s. Use large values for big lists or slow internet connections\ndownloadTimeout: 4m\n# optional: Download attempt timeout. Default: 60s\ndownloadAttempts: 5\n# optional: Time between the download attempts. Default: 1s\ndownloadCooldown: 10s\n# optional: if failOnError, application startup will fail if at least one list can't be downloaded / opened. Default: blocking\nstartStrategy: failOnError\n\n# optional: configuration for caching of DNS responses\ncaching:\n# duration how long a response must be cached (min value).\n# If <=0, use response's TTL, if >0 use this value, if TTL is smaller\n# Default: 0\nminTime: 5m\n# duration how long a response must be cached (max value).\n# If <0, do not cache responses\n# If 0, use TTL\n# If > 0, use this value, if TTL is greater\n# Default: 0\nmaxTime: 30m\n# Max number of cache entries (responses) to be kept in cache (soft limit). Useful on systems with limited amount of RAM.\n# Default (0): unlimited\nmaxItemsCount: 0\n# if true, will preload DNS results for often used queries (default: names queried more than 5 times in a 2-hour time window)\n# this improves the response time for often used queries, but significantly increases external traffic\n# default: false\nprefetching: true\n# prefetch track time window (in duration format)\n# default: 120\nprefetchExpires: 2h\n# name queries threshold for prefetch\n# default: 5\nprefetchThreshold: 5\n# Max number of domains to be kept in cache for prefetching (soft limit). Useful on systems with limited amount of RAM.\n# Default (0): unlimited\nprefetchMaxItemsCount: 0\n# Time how long negative results (NXDOMAIN response or empty result) are cached. A value of -1 will disable caching for negative results.\n# Default: 30m\ncacheTimeNegative: 30m\n\n# optional: configuration of client name resolution\nclientLookup:\n# optional: this DNS resolver will be used to perform reverse DNS lookup (typically local router)\nupstream: 192.168.178.1\n# optional: some routers return multiple names for client (host name and user defined name). Define which single name should be used.\n# Example: take second name if present, if not take first name\nsingleNameOrder:\n- 2\n- 1\n# optional: custom mapping of client name to IP addresses. Useful if reverse DNS does not work properly or just to have custom client names.\nclients:\nlaptop:\n- 192.168.178.29\n# optional: configuration for prometheus metrics endpoint\nprometheus:\n# enabled if true\nenable: true\n# url path, optional (default '/metrics')\npath: /metrics\n\n# optional: write query information (question, answer, client, duration etc.) to daily csv file\nqueryLog:\n# optional one of: mysql, postgresql, csv, csv-client. If empty, log to console\ntype: mysql\n# directory (should be mounted as volume in docker) for csv, db connection string for mysql/postgresql\ntarget: db_user:db_password@tcp(db_host_or_ip:3306)/db_name?charset=utf8mb4&parseTime=True&loc=Local\n#postgresql target: postgres://user:password@db_host_or_ip:5432/db_name\n# if > 0, deletes log files which are older than ... days\nlogRetentionDays: 7\n# optional: Max attempts to create specific query log writer, default: 3\ncreationAttempts: 1\n# optional: Time between the creation attempts, default: 2s\ncreationCooldown: 2s\n# 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\nfields:\n- clientIP\n- duration\n\n# optional: Blocky can synchronize its cache and blocking state between multiple instances through redis.\nredis:\n# Server address and port or master name if sentinel is used\naddress: redismaster\n# Username if necessary\nusername: usrname\n# Password if necessary\npassword: passwd\n# Database, default: 0\ndatabase: 2\n# Connection is required for blocky to start. Default: false\nrequired: true\n# Max connection attempts, default: 3\nconnectionAttempts: 10\n# Time between the connection attempts, default: 1s\nconnectionCooldown: 3s\n# Sentinal username if necessary\nsentinelUsername: usrname\n# Sentinal password if necessary\nsentinelPassword: passwd\n# List with address and port of sentinel hosts(sentinel is activated if at least one sentinel address is configured)\nsentinelAddresses:\n- redis-sentinel1:26379\n- redis-sentinel2:26379\n- redis-sentinel3:26379\n\n# optional: Mininal TLS version that the DoH and DoT server will use\nminTlsServeVersion: 1.3\n# if https port > 0: path to cert and key file for SSL encryption. if not set, self-signed certificate will be generated\n#certFile: server.crt\n#keyFile: server.key\n# 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.\nbootstrapDns:\n- tcp+udp:1.1.1.1\n- https://1.1.1.1/dns-query\n- upstream: https://dns.digitale-gesellschaft.ch/dns-query\nips:\n- 185.95.218.42\n\n# optional: drop all queries with following query types. Default: empty\nfiltering:\nqueryTypes:\n- AAAA\n\n# optional: if path defined, use this file for query resolution (A, AAAA and rDNS). Default: empty\nhostsFile:\n# optional: Path to hosts file (e.g. /etc/hosts on Linux)\nfilePath: /etc/hosts\n# optional: TTL, default: 1h\nhostsTTL: 60m\n# optional: Time between hosts file refresh, default: 1h\nrefreshPeriod: 30m\n# optional: Whether loopback hosts addresses (127.0.0.0/8 and ::1) should be filtered or not, default: false\nfilterLoopback: true\n\n# optional: ports configuration\nports:\n# 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\"\ndns: 53\n# optional: Port(s) and bind ip address(es) for DoT (DNS-over-TLS) listener. Example: 853, 127.0.0.1:853\ntls: 853\n# 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\nhttps: 443\n# 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\nhttp: 4000\n\n# optional: logging configuration\nlog:\n# optional: Log level (one from debug, info, warn, error). Default: info\nlevel: info\n# optional: Log format (text or json). Default: text\nformat: text\n# optional: log timestamps. Default: true\ntimestamp: true\n# optional: obfuscate log output (replace all alphanumeric characters with *) for user sensitive data like request domains or responses to increase privacy. Default: false\nprivacy: false\n\n# optional: add EDE error codes to dns response\nede:\n# enabled if true, Default: false\nenable: true\n
"},{"location":"configuration/#basic-configuration","title":"Basic configuration","text":"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\nconnectIPVersion: v4\n
"},{"location":"configuration/#ports-configuration","title":"Ports configuration","text":"

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:\ndns: 53\nhttp: 4000\nhttps: 443\n
"},{"location":"configuration/#logging-configuration","title":"Logging configuration","text":"

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:\nlevel: debug\nformat: json\ntimestamp: false\nprivacy: true\n
"},{"location":"configuration/#upstream-configuration","title":"Upstream configuration","text":"

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):

  • tcp+udp (UDP and TCP, dependent on query type)
  • https (aka DoH)
  • tcp-tls (aka DoT)

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:\ndefault:\n- 5.9.164.112\n- 1.1.1.1\n- tcp-tls:fdns1.dismail.de:853\n- https://dns.digitale-gesellschaft.ch/dns-query\nlaptop*:\n- 123.123.123.123\n10.43.8.67/28:\n- 1.1.1.1\n- 9.9.9.9\n

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.

"},{"location":"configuration/#upstream-lookup-timeout","title":"Upstream lookup timeout","text":"

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:\ndefault:\n- 46.182.19.48\n- 80.241.218.68\nupstreamTimeout: 5s\n
"},{"location":"configuration/#bootstrap-dns-configuration","title":"Bootstrap DNS configuration","text":"

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:\n- upstream: tcp-tls:dns.example.com\nips:\n- 123.123.123.123\n- upstream: https://234.234.234.234/dns-query\n
"},{"location":"configuration/#filtering","title":"Filtering","text":"

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:\nqueryTypes:\n- AAAA\n

This configuration will drop all 'AAAA' (IPv6) queries.

"},{"location":"configuration/#fqdn-only","title":"FQDN only","text":"

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\n
"},{"location":"configuration/#custom-dns","title":"Custom DNS","text":"

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:\ncustomTTL: 1h\nfilterUnmappedTypes: true\nrewrite:\nhome: lan\nreplace-me.com: with-this.com\nmapping:\nprinter.lan: 192.168.178.3\notherdevice.lan: 192.168.178.15,2001:0db8:85a3:08d3:1319:8a2e:0370:7344\n

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.

"},{"location":"configuration/#conditional-dns-resolution","title":"Conditional DNS resolution","text":"

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:\nfallbackUpstream: false\nrewrite:\nexample.com: fritz.box\nreplace-me.com: with-this.com\nmapping:\nfritz.box: 192.168.178.1\nlan.net: 192.170.1.2,192.170.1.3\n# for reverse DNS lookups of local devices\n178.168.192.in-addr.arpa: 192.168.178.1\n# for all unqualified hostnames\n.: 168.168.0.1\n

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.

"},{"location":"configuration/#client-name-lookup","title":"Client name lookup","text":"

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.

"},{"location":"configuration/#resolving-client-name-from-urlhost","title":"Resolving client name from URL/Host","text":"

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

"},{"location":"configuration/#resolving-client-name-from-ip-address","title":"Resolving client name from IP address","text":"

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.

"},{"location":"configuration/#single-name-order","title":"Single name order","text":"

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.

"},{"location":"configuration/#custom-client-name-mapping","title":"Custom client name mapping","text":"

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:\nupstream: 192.168.178.1\nsingleNameOrder:\n- 2\n- 1\nclients:\nlaptop:\n- 192.168.178.29\n

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.

"},{"location":"configuration/#blocking-and-whitelisting","title":"Blocking and whitelisting","text":"

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.

"},{"location":"configuration/#definition-black-and-whitelists","title":"Definition black and whitelists","text":"

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:\nblackLists:\nads:\n- https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt\n- https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts\n- |\n# inline definition with YAML literal block scalar style\nsomeadsdomain.com\nanotheradsdomain.com\n# this is a regex\n/^banners?[_.-]/\nspecial:\n- https://raw.githubusercontent.com/StevenBlack/hosts/master/alternates/fakenews/hosts\nwhiteLists:\nads:\n- whitelist.txt\n- |\n# inline definition with YAML literal block scalar style\nwhitelistdomain.com\n

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

"},{"location":"configuration/#regex-support","title":"Regex support","text":"

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
"},{"location":"configuration/#client-groups","title":"Client groups","text":"

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:\nclientGroupsBlock:\n# default will be used, if no special definition for a client name exists\ndefault:\n- ads\n- special\nlaptop*:\n- ads\n192.168.178.1/24:\n- special\nkid-laptop:\n- ads\n- adult\n

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

"},{"location":"configuration/#block-type","title":"Block type","text":"

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:\nblockType: nxDomain\n
"},{"location":"configuration/#block-ttl","title":"Block TTL","text":"

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:\nblockType: 192.100.100.15, 2001:0db8:85a3:08d3:1319:8a2e:0370:7344\nblockTTL: 10s\n
"},{"location":"configuration/#list-refresh-period","title":"List refresh period","text":"

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:\nrefreshPeriod: 60m\n

Refresh every hour.

"},{"location":"configuration/#download","title":"Download","text":"

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:\ndownloadTimeout: 4m\ndownloadAttempts: 5\ndownloadCooldown: 10s\n
"},{"location":"configuration/#start-strategy","title":"Start strategy","text":"

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:\nstartStrategy: failOnError\n
"},{"location":"configuration/#concurrency","title":"Concurrency","text":"

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:\nprocessingConcurrency: 10\n
"},{"location":"configuration/#caching","title":"Caching","text":"

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:\nminTime: 5m\nmaxTime: 30m\nprefetching: true\n
"},{"location":"configuration/#redis","title":"Redis","text":"

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:\naddress: redismaster\nusername: usrname\npassword: passwd\ndatabase: 2\nrequired: true\nconnectionAttempts: 10\nconnectionCooldown: 3s\nsentinelUsername: sentUsrname\nsentinelPassword: sentPasswd\nsentinelAddresses:\n- redis-sentinel1:26379\n- redis-sentinel2:26379\n- redis-sentinel3:26379\n
"},{"location":"configuration/#prometheus","title":"Prometheus","text":"

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:\nenable: true\npath: /metrics\n
"},{"location":"configuration/#query-logging","title":"Query logging","text":"

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.

"},{"location":"configuration/#query-log-types","title":"Query log types","text":"

You can select one of following query log types:

  • mysql - log each query in the external MySQL/MariaDB database
  • postgresql - log each query in the external PostgreSQL database
  • csv - log into CSV file (one per day)
  • csv-client - log into CSV file (one per day and per client)
  • console - log into console output
  • none - do not log any queries
"},{"location":"configuration/#query-log-fields","title":"Query log fields","text":"

You 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 request
  • clientName - resolved client name(s) from the origins request
  • responseReason - reason for the response (e.g. from which upstream resolver), response type and code
  • responseAnswer - returned DNS answer
  • question - DNS question from the request
  • duration - request processing time in milliseconds

Hint

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:\ntype: csv\ntarget: /logs\nlogRetentionDays: 7\nfields:\n- clientIP\n- duration\n

example for Database

Example

queryLog:\ntype: mysql\ntarget: db_user:db_password@tcp(db_host_or_ip:3306)/db_user?charset=utf8mb4&parseTime=True&loc=Local\nlogRetentionDays: 7\n
"},{"location":"configuration/#hosts-file","title":"Hosts file","text":"

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:\nfilePath: /etc/hosts\nhostsTTL: 60m\nrefreshPeriod: 30m\n
"},{"location":"configuration/#deliver-ede-codes-as-edns0-option","title":"Deliver EDE codes as EDNS0 option","text":"

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:\nenable: true\n
"},{"location":"configuration/#ssl-certificate-configuration-doh-tls-listener","title":"SSL certificate configuration (DoH / TLS listener)","text":"

See Wiki - Configuration of HTTPS for detailed information, how to create and configure SSL certificates.

DoH url: https://host:port/dns-query

"},{"location":"installation/","title":"Installation","text":"

You can choose one of the following installation options:

  • Run as standalone binary
  • Run as docker container
  • Kubernetes with helm chart
"},{"location":"installation/#prepare-your-configuration","title":"Prepare your configuration","text":"

Blocky supports single or multiple YAML files as configuration. Create new config.yaml with your configuration ( see Configuration for more details and all configuration options).

Simple configuration file, which enables only basic features:

upstream:\ndefault:\n- 46.182.19.48\n- 80.241.218.68\n- tcp-tls:fdns1.dismail.de:853\n- https://dns.digitale-gesellschaft.ch/dns-query\nblocking:\nblackLists:\nads:\n- https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts\nclientGroupsBlock:\ndefault:\n- ads\nport: 53\nhttpPort: 4000\n
"},{"location":"installation/#run-as-standalone-binary","title":"Run as standalone binary","text":"

Download the binary file from GitHub for your architecture and run ./blocky --config config.yml.

Warning

Please be aware, if you want to use port 53 or 953 on Linux you should add CAP_NET_BIND_SERVICE capability to the binary or run with root privileges (running as root is not recommended).

"},{"location":"installation/#run-with-docker","title":"Run with docker","text":""},{"location":"installation/#alternative-registry","title":"Alternative registry","text":"

Blocky docker images are deployed to DockerHub (spx01/blocky) and GitHub Container Registry (ghcr.io/0xerr0r/blocky) .

"},{"location":"installation/#parameters","title":"Parameters","text":"

You can define the location of the config file in the container with environment variable \"BLOCKY_CONFIG_FILE\". Default value is \"/app/config.yml\".

"},{"location":"installation/#docker-from-command-line","title":"Docker from command line","text":"

Execute following command from the command line:

docker run --name blocky -v /path/to/config.yml:/app/config.yml -p 4000:4000 -p 53:53/udp spx01/blocky\n
"},{"location":"installation/#run-with-docker-compose","title":"Run with docker-compose","text":"

Create following docker-compose.yml file

version: \"2.1\"\nservices:\nblocky:\nimage: spx01/blocky\ncontainer_name: blocky\nrestart: unless-stopped\n# Optional the instance hostname for logging purpose\nhostname: blocky-hostname\nports:\n- \"53:53/tcp\"\n- \"53:53/udp\"\n- \"4000:4000/tcp\"\nenvironment:\n- TZ=Europe/Berlin # Optional to synchronize the log timestamp with host\nvolumes:\n# Optional to synchronize the log timestamp with host\n- /etc/localtime:/etc/localtime:ro\n# config file\n- ./config.yml:/app/config.yml\n

and start docker container with

docker-compose up -d\n
"},{"location":"installation/#advanced-setup","title":"Advanced setup","text":"

Following example shows, how to run blocky in a docker container and store query logs on a SAMBA share. Local black and whitelists directories are mounted as volume. You can create own black or whitelists in these directories and define the path like '/app/whitelists/whitelist.txt' in the config file.

Example

version: \"2.1\"\nservices:\nblocky:\nimage: spx01/blocky\ncontainer_name: blocky\nrestart: unless-stopped\nports:\n- \"53:53/tcp\"\n- \"53:53/udp\"\n- \"4000:4000/tcp\" # Prometheus stats (if enabled).\nenvironment:\n- TZ=Europe/Berlin\nvolumes:\n# config file\n- ./config.yml:/app/config.yml\n# write query logs in this volume\n- queryLogs:/logs\n# put your custom white and blacklists in these directories\n- ./blacklists:/app/blacklists/\n- ./whitelists:/app/whitelists/\n\nvolumes:\nqueryLogs:\ndriver: local\ndriver_opts:\ntype: cifs\no: username=USER,password=PASSWORD,rw\ndevice: //NAS_HOSTNAME/blocky  
"},{"location":"installation/#multiple-configuration-files","title":"Multiple configuration files","text":"

For complex setups, splitting the configuration between multiple YAML files might be desired. In this case, folder containing YAML files is passed on startup, Blocky will join all the files.

./blocky --config ./config/

Warning

Blocky simply joins the multiple YAML files. If a directive (e.g. upstream) is repeated in multiple files, the configuration will not load and start will fail.

"},{"location":"installation/#other-installation-types","title":"Other installation types","text":"

Warning

These projects are maintained by other people.

"},{"location":"installation/#web-ui","title":"Web UI","text":"

Blocky Frontend provides a Web UI to control blocky. See linked project for installation instructions.

"},{"location":"installation/#run-with-helm-chart-on-kubernetes","title":"Run with helm chart on Kubernetes","text":"

See this repo, the documentation and the configuration instructions for details about running blocky via helm in kubernetes.

"},{"location":"installation/#run-as-an-app-for-truenas-scale","title":"Run as an App for TrueNAS SCALE","text":"

You can find the App in the TrueCharts App Catalog or read the documentation and configuration instructions for details about running blocky as a native TrueNAS SCALE App.

"},{"location":"installation/#aur-package-for-arch-linux","title":"AUR package for Arch Linux","text":"

See https://aur.archlinux.org/packages/blocky/

"},{"location":"installation/#package-for-alpine-linux","title":"Package for Alpine Linux","text":"

See https://pkgs.alpinelinux.org/package/edge/testing/x86/blocky

"},{"location":"installation/#installation-script-for-centosfedora","title":"Installation script for CentOS/Fedora","text":"

See https://github.com/m0zgen/blocky-installer

"},{"location":"installation/#package-for-freebsd","title":"Package for FreeBSD","text":"

See https://www.freebsd.org/cgi/ports.cgi?query=blocky&stype=all

"},{"location":"interfaces/","title":"Interfaces","text":""},{"location":"interfaces/#rest-api","title":"REST API","text":"

If http listener is enabled, blocky provides REST API. You can browse the API documentation (Swagger) documentation under https://0xERR0R.github.io/blocky/swagger.html.

"},{"location":"interfaces/#cli","title":"CLI","text":"

Blocky provides a CLI interface to control. This interface uses internally the REST API.

To run the CLI, please ensure, that blocky DNS server is running, then execute blocky help for help or

  • ./blocky blocking enable to enable blocking
  • ./blocky blocking disable to disable blocking
  • ./blocky blocking disable --duration [duration] to disable blocking for a certain amount of time (30s, 5m, 10m30s, ...)
  • ./blocky blocking disable --groups ads,othergroup to disable blocking only for special groups
  • ./blocky blocking status to print current status of blocking
  • ./blocky query <domain> execute DNS query (A) (simple replacement for dig, useful for debug purposes)
  • ./blocky query <domain> --type <queryType> execute DNS query with passed query type (A, AAAA, MX, ...)
  • ./blocky lists refresh reloads all white and blacklists

Tip

To run this inside docker run docker exec blocky ./blocky blocking status

"},{"location":"network_configuration/","title":"Network configuration","text":"

In order, to benefit from all the advantages of blocky like ad-blocking, privacy and speed, it is necessary to use blocky as DNS server for your devices. You can configure DNS server on each device manually or use DHCP in your network router and push the right settings to your device. With this approach, you will configure blocky only once in your router and each device in your network will automatically use blocky as DNS server.

"},{"location":"network_configuration/#transparent-configuration-with-dhcp","title":"Transparent configuration with DHCP","text":"

Let us assume, blocky is installed on a Raspberry PI with fix IP address 192.168.178.2. Each device which connects to the router will obtain an IP address and receive the network configuration. The IP address of the Raspberry PI should be pushed to the device as DNS server.

\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510         \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502              \u2502         \u2502 Raspberry PI    \u2502\n\u2502  Router      \u2502         \u2502   blocky        \u2502        \n\u2502              \u2502         \u2502 192.168.178.2   \u2502            \n\u2514\u2500\u25b2\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2518         \u2514\u2500\u2500\u2500\u2500\u25b2\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518        \n  \u25021    \u2502                     \u2502  3                  \n  \u2502     \u2502                     \u2502                         \n  \u2502     \u2502                     \u2502 \n  \u2502     \u2502                     \u2502                     \n  \u2502     \u2502                     \u2502\n  \u2502     \u2502                     \u2502\n  \u2502     \u2502                     \u2502\n  \u2502     \u2502       \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n  \u2502     \u2502   2   \u2502                    \u2502\n  \u2502     \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba  Network device    \u2502\n  \u2502             \u2502    Android         \u2502\n  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524                    \u2502\n                \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n

1 - Network device asks the DHCP server (on Router) for the network configuration

2 - Router assigns a free IP address to the device and says \"Use 192.168.178.2\" as DNS server

3 - Clients makes DNS queries and is happy to use blocky

Warning

It is necessary to assign the server which runs blocky (e.g. Raspberry PI) a fix IP address.

"},{"location":"network_configuration/#example-configuration-with-fritzbox","title":"Example configuration with FritzBox","text":"

To configure the DNS server in the FritzBox, please open in the FritzBox web interface:

  • in navigation menu on the left side: Home Network -> Network
  • Network Settings tab on the top
  • \"IPv4 Configuration\" Button at the bottom op the page
  • Enter the IP address of blocky under \"Local DNS server\", see screenshot

"},{"location":"prometheus_grafana/","title":"Integration in Grafana","text":""},{"location":"prometheus_grafana/#prometheus","title":"Prometheus","text":""},{"location":"prometheus_grafana/#prometheus-export","title":"Prometheus export","text":"

Blocky can optionally export metrics for Prometheus.

Following metrics will be exported:

name Description blocky_blacklist_cache / blocky_whitelist_cache Number of entries in blacklist/whitelist cache, partitioned by group blocky_error_total Counter for internal errors blocky_query_total Number of total queries, partitioned by client and DNS request type (A, AAAA, PTR, etc) blocky_request_duration_ms_bucket Request duration histogram, partitioned by response type (Blocked, cached, etc) blocky_response_total Number of responses, partitioned by response type (Blocked, cached, etc), DNS response code, and reason blocky_blocking_enabled 1 if blocking is enabled, 0 otherwise blocky_cache_entry_count Number of entries in cache blocky_cache_hit_count / blocky_cache_miss_count Cache hit/miss counters blocky_prefetch_count Amount of prefetched DNS responses blocky_prefetch_domain_name_cache_count Amount of domain names being prefetched blocky_failed_download_count Number of failed list downloads"},{"location":"prometheus_grafana/#grafana-dashboard","title":"Grafana dashboard","text":"

Example Grafana dashboard definition as JSON or at grafana.com .

This dashboard shows all relevant statistics and allows enabling and disabling the blocking status.

"},{"location":"prometheus_grafana/#grafana-configuration","title":"Grafana configuration","text":"

Please install grafana-piechart-panel and set disable-sanitize-html in config or as env to use control buttons to enable/disable the blocking status.

"},{"location":"prometheus_grafana/#grafana-and-prometheus-example-project","title":"Grafana and Prometheus example project","text":"

This repo contains example docker-compose.yml with blocky, prometheus (with configured scraper for blocky) and grafana with prometheus datasource.

"},{"location":"prometheus_grafana/#mysql-mariadb","title":"MySQL / MariaDB","text":"

If database query logging is activated (see Query logging), you can use following Grafana Dashboard as JSON or at grafana.com

.

Please define the MySQL source in Grafana, which points to the database with blocky's log entries.

"},{"location":"prometheus_grafana/#postgres","title":"Postgres","text":"

The JSON for a Grafana dashboard equivalent to the MySQL/MariaDB version is located here

"}]} \ No newline at end of file diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 00000000..4f05c916 --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,43 @@ + + + + None + 2023-03-07 + daily + + + None + 2023-03-07 + daily + + + None + 2023-03-07 + daily + + + None + 2023-03-07 + daily + + + None + 2023-03-07 + daily + + + None + 2023-03-07 + daily + + + None + 2023-03-07 + daily + + + None + 2023-03-07 + daily + + \ No newline at end of file diff --git a/sitemap.xml.gz b/sitemap.xml.gz new file mode 100644 index 00000000..3d088641 Binary files /dev/null and b/sitemap.xml.gz differ diff --git a/swagger.html b/swagger.html new file mode 100644 index 00000000..268bcc8c --- /dev/null +++ b/swagger.html @@ -0,0 +1,8639 @@ + + + + + + blocky API + + +
+

blocky API

+

+ Base URL: /api/, + Version: +

+

+

blocky API

+

+ + +
+ Schemes: + +
+ + +

Summary

+

Tag: blocking

+ +

+

+ + + + + + + + + + + + + + + + + + + + + +
OperationDescription
GET /blocking/disable

Disable blocking

+
GET /blocking/enable

Enable blocking

+
GET /blocking/status

Blocking status

+
+

Tag: lists

+ +

+

+ + + + + + + + + + + + + +
OperationDescription
POST /lists/refresh

List refresh

+
+

Tag: query

+ +

+

+ + + + + + + + + + + + + +
OperationDescription
POST /query

Performs DNS query

+
+ + +

Paths

+ + + +
+
+
Disable blocking
+

GET /blocking/disable

+ Tags: + blocking +
+
+
+

disable the blocking status

+ +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ duration +

duration of blocking (Example: 300s, 5m, 1h, 5m30s)

+
query + string (duration) + + + +
+ groups +

groups to disable (comma separated). If empty, disable all groups

+
query + string (string) + + + +
+
+ +
+ +
+
+ 200 OK + +
+
+
+
+

Blocking is disabled

+ +
+
+
+ +
+
+ +
+
+
+ 400 Bad Request + +
+
+
+
+

Unknown group

+ +
+
+
+ +
+
+ +
+
+
+
+
+
+ + +
+
+
Enable blocking
+

GET /blocking/enable

+ Tags: + blocking +
+
+
+

enable the blocking status

+ +
+ + +
+ +
+
+ 200 OK + +
+
+
+
+

Blocking is enabled

+ +
+
+
+ +
+
+ +
+
+
+
+
+
+ + +
+
+
Blocking status
+

GET /blocking/status

+ Tags: + blocking +
+
+
+

get current blocking status

+ +
+ + +
+

application/json +

+ +
+
+ 200 OK + +
+
+
+
+

Returns current blocking status

+ +
+
+
+ +
+ +
+ +
+
+
+
+
+
+ + +
+
+
List refresh
+

POST /lists/refresh

+ Tags: + lists +
+
+
+

Refresh all lists

+ +
+ + +
+ +
+
+ 200 OK + +
+
+
+
+

Lists were reloaded

+ +
+
+
+ +
+
+ +
+
+
+
+
+
+ + +
+
+
Performs DNS query
+

POST /query

+ Tags: + query +
+
+
+

Performs DNS query

+ +
+ + +
+ +

application/json +

+
+
+

+

query data

+

+
+
+ +
+
+
+ +
+

application/json +

+ +
+
+ 200 OK + +
+
+
+
+

query was executed

+ +
+
+
+ +
+
+ +
+
+ +
+
+
+ 400 Bad Request + +
+
+
+
+

Wrong request format

+ +
+
+
+ +
+
+ +
+
+
+
+
+
+ + +

Schema definitions

+ + +
+
+

api.BlockingStatus: + + object + + + +

+
+
+ +
+
+
+ autoEnableInSec: + + integer + + +
+
+

If blocking is temporary disabled: amount of seconds until blocking will be enabled

+ +
+ +
+
+
+ disabledGroups: + + string[] + + +
+
+

Disabled group names

+ +
+ +
+ + string + + +
+ +
+
+
+
+
+ enabled: + + boolean + + +
+
+

True if blocking is enabled

+ +
+ +
+
+
+
+
+
+
+
+

api.QueryRequest: + + object + + + +

+
+
+ +
+
+
+ query: + + string + + +
+
+

query for DNS request

+ +
+ +
+
+
+ type: + + string + + +
+
+

request type (A, AAAA, ...)

+ +
+ +
+
+
+
+
+
+
+
+

api.QueryResult: + + object + + + +

+
+
+ +
+
+
+ reason: + + string + + +
+
+

blocky reason for resolution

+ +
+ +
+
+
+ response: + + string + + +
+
+

actual DNS response

+ +
+ +
+
+
+ responseType: + + string + + +
+
+

response type (CACHED, BLOCKED, ...)

+ +
+ +
+
+
+ returnCode: + + string + + +
+
+

DNS return code (NOERROR, NXDOMAIN, ...)

+ +
+ +
+
+
+
+
+
+
+ + +