From 1ada56947aa42eada4e5496fd53c16e507727b09 Mon Sep 17 00:00:00 2001 From: Fabian Franz Date: Sat, 25 Mar 2017 17:01:36 +0100 Subject: [PATCH] unbound debugging (#1504) --- .../Unbound/Api/DiagnosticsController.php | 113 +++++++++++ .../scripts/unbound/unboundctlwrapper | 177 ++++++++++++++++++ .../conf/actions.d/actions_unbound.conf | 36 ++++ 3 files changed, 326 insertions(+) create mode 100644 src/opnsense/mvc/app/controllers/OPNsense/Unbound/Api/DiagnosticsController.php create mode 100755 src/opnsense/scripts/unbound/unboundctlwrapper create mode 100644 src/opnsense/service/conf/actions.d/actions_unbound.conf diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Unbound/Api/DiagnosticsController.php b/src/opnsense/mvc/app/controllers/OPNsense/Unbound/Api/DiagnosticsController.php new file mode 100644 index 000000000..870b4cadb --- /dev/null +++ b/src/opnsense/mvc/app/controllers/OPNsense/Unbound/Api/DiagnosticsController.php @@ -0,0 +1,113 @@ +configdRun('unbound stats')); + if ($result != "null") { + $ret['status'] = "ok"; + $ret['data'] = json_decode($result); + } + return $ret; + } + + /** + * return the entries of the cache + */ + public function dumpcacheAction() + { + $ret['status'] = "failed"; + $backend = new Backend(); + $result = json_decode(trim($backend->configdRun("unbound dumpcache")), true); + if ($result !== null) { + $ret['data'] = $result; + $ret['status'] = 'ok'; + } + return $ret; + } + public function dumpinfraAction() + { + $ret['status'] = "failed"; + $backend = new Backend(); + $result = json_decode(trim($backend->configdRun("unbound dumpinfra")), true); + if ($result !== null) { + $ret['data'] = $result; + $ret['status'] = 'ok'; + } + return $ret; + } + public function listlocaldataAction() + { + $ret['status'] = "failed"; + $backend = new Backend(); + $result = json_decode(trim($backend->configdRun("unbound listlocaldata")), true); + if ($result !== null) { + $ret['data'] = $result; + $ret['status'] = 'ok'; + } + return $ret; + } + public function listlocalzonesAction() + { + $ret['status'] = "failed"; + $backend = new Backend(); + $result = json_decode(trim($backend->configdRun("unbound listlocalzones")), true); + if ($result !== null) { + $ret['data'] = $result; + $ret['status'] = 'ok'; + } + return $ret; + } + public function listinsecureAction() + { + $ret['status'] = "failed"; + $backend = new Backend(); + $result = json_decode(trim($backend->configdRun("unbound listinsecure")), true); + if ($result !== null) { + $ret['data'] = $result; + $ret['status'] = 'ok'; + } + return $ret; + } +} diff --git a/src/opnsense/scripts/unbound/unboundctlwrapper b/src/opnsense/scripts/unbound/unboundctlwrapper new file mode 100755 index 000000000..deb4b2c8a --- /dev/null +++ b/src/opnsense/scripts/unbound/unboundctlwrapper @@ -0,0 +1,177 @@ +#!/usr/local/bin/ruby +=begin +Copyright (C) 2017 Fabian Franz + * +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, +OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE +=end + + +require 'json' +require 'optparse' + +supported_formats = %w{json} +options = {format: 'json'} + +OptionParser.new do |opts| + opts.banner = "Usage: #{__FILE__} command" + opts.on("-c", "--cache", "Dump cache") do |c| + options[:dump_cache] = c + end + opts.on("-i", "--infra", "Dump infrastructure cache") do |c| + options[:dump_infra] = c + end + opts.on("-f", "--format FORMAT") do |format| + if supported_formats.include? format + options[:format] = format + else + puts "the specified format is not valid" + exit + end + end + opts.on("-s", "--stats") do |stats| + options[:stats] = stats + end + opts.on("-l", "--list-local-zones", "List local Zones") do |llz| + options[:llz] = llz + end + opts.on("-I", "--list-insecure", "List Domain-Insecure Zones") do |i| + options[:insecure] = i + end + opts.on("-d", "--list-local-data", "List local data") do |i| + options[:lld] = i + end + opts.on("-h", "--help", "Prints this help") do + puts opts + exit + end +end.parse! + + +def dump_cache + raw = `unbound-control -c /var/unbound/remotecontrol.conf dump_cache`.split("\n") + raw = raw.select {|x| (x.include? "IN") && (x[0] != ";") && !(x.start_with? "msg ") } + raw.map do |line| + host,ttl, type, rrtype, value = line.scan(/^(\S+)\s+(?:([\d]*)\s+)?(IN)\s+(\S+)\s+(.*)$/).first + {host: host, ttl: ttl, type: type, rrtype: rrtype, value: value} + end +end + +def stats + raw = `unbound-control -c /var/unbound/remotecontrol.conf stats`.split("\n") + data = {} + raw.each do |line| + key,value = line.split("=") + key_parts = key.split(".") + if key_parts[0] == 'histogram' + data['histogram'] = [] unless data['histogram'] + data['histogram'] << {from: key_parts[1..2].map(&:to_i), + to: key_parts[4..5].map(&:to_i), + value: value.to_i} + else + key = key_parts.pop + origin = data + key_parts.each do |kp| + unless origin[kp] + origin[kp] = {} + end + origin = origin[kp] + end + origin[key] = value.to_i + end + end + data +end + +def dump_infra + raw = `unbound-control -c /var/unbound/remotecontrol.conf dump_infra`.split("\n") + raw.map do |line| + elements = line.split(/\s+/) + data = {} + data['ip'] = elements.shift + data['host'] = elements.shift + while elements.count > 2 + key = elements.shift + if key == 'lame' + data['lame'] = true + next + end + tmp = elements.shift + data[key] = tmp =~ /^\d+$/ ? tmp.to_i : tmp + end + data + end +end + +def insecure + `unbound-control -c /var/unbound/remotecontrol.conf list_insecure`.split("\n") +end + +def list_local_zones + raw = `unbound-control -c /var/unbound/remotecontrol.conf list_local_zones`.split("\n") + raw.map do |line| + z, t = line.split(/\s+/) + {zone: z, type: t} + end +end + +def list_local_data + raw = `unbound-control -c /var/unbound/remotecontrol.conf list_local_data`.split("\n") + result = [] + raw.map do |line| + line.strip! + next if line.length < 10 # not a valid entry + name, ttl, type, rrtype, value = line.split(/\s+/,5) + {name: name, ttl: ttl, type: type, rrtype: rrtype, value: value} + end.select {|x| x } +end + +output = nil +if options[:dump_cache] + output = dump_cache +end + +if options[:dump_infra] + output = dump_infra +end + +if options[:stats] + output = stats +end + +if options[:insecure] + output = insecure +end + +if options[:llz] + output = list_local_zones +end + +if options[:lld] + output = list_local_data +end + +puts case options[:format] + when 'json'; then + output.to_json +end diff --git a/src/opnsense/service/conf/actions.d/actions_unbound.conf b/src/opnsense/service/conf/actions.d/actions_unbound.conf new file mode 100644 index 000000000..f723fd828 --- /dev/null +++ b/src/opnsense/service/conf/actions.d/actions_unbound.conf @@ -0,0 +1,36 @@ +[dumpcache] +command:/usr/local/opnsense/scripts/unbound/unboundctlwrapper -c +parameters: +type:script_output +message:dumping name server cache + +[dumpinfra] +command:/usr/local/opnsense/scripts/unbound/unboundctlwrapper -i +parameters: +type:script_output +message:dumping infrastructure cache + +[stats] +command:/usr/local/opnsense/scripts/unbound/unboundctlwrapper -s +parameters: +type:script_output +message:loading stats + +[listinsecure] +command:/usr/local/opnsense/scripts/unbound/unboundctlwrapper -I +parameters: +type:script_output +message:list isecure local zones + +[listlocalzones] +command:/usr/local/opnsense/scripts/unbound/unboundctlwrapper -l +parameters: +type:script_output +message:list local zones + +[listlocaldata] +command:/usr/local/opnsense/scripts/unbound/unboundctlwrapper -d +parameters: +type:script_output +message:list local data +