From e008df45276c67ff311edbe4b29c4085e5c1e920 Mon Sep 17 00:00:00 2001 From: Denis Knauf <^_^@denkn.at> Date: Fri, 14 Dec 2018 14:58:11 +0100 Subject: [PATCH] first working implementation --- Gemfile | 6 +++++ Gemfile.lock | 21 ++++++++++++++++ config.ru | 28 +++++++++++++++++++++ config.yml | 62 +++++++++++++++++++++++++++++++++++++++++++++++ dnsbl_exporter.rb | 54 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 171 insertions(+) create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100644 config.ru create mode 100644 config.yml create mode 100644 dnsbl_exporter.rb diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..56d11a9 --- /dev/null +++ b/Gemfile @@ -0,0 +1,6 @@ +source 'https://rubygems.org' + +gem 'net-dns' +gem 'prometheus-client' +gem 'rack' +gem 'puma' diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..3a9c4ae --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,21 @@ +GEM + remote: https://rubygems.org/ + specs: + net-dns (0.9.0) + prometheus-client (0.8.0) + quantile (~> 0.2.1) + puma (3.12.0) + quantile (0.2.1) + rack (2.0.6) + +PLATFORMS + ruby + +DEPENDENCIES + net-dns + prometheus-client + puma + rack + +BUNDLED WITH + 1.13.6 diff --git a/config.ru b/config.ru new file mode 100644 index 0000000..13d7500 --- /dev/null +++ b/config.ru @@ -0,0 +1,28 @@ +require 'rack' +require './dnsbl_exporter' + + +run lambda {|env| + begin + req = Rack::Request.new env + case req.path + when '/metrics' + collector = DnsblCollector.new + target = + begin + target = IPAddr.new req.params['target'] + rescue IPAddr::AddressFamilyError + return [400, {"Content-Type" => "text/plain"}, ["Valid target-IP-Address expected.\n"]] + end + collector.collect target + [200, {"Content-Type" => "text/plain"}, [ + Prometheus::Client::Formats::Text.marshal( collector.prometheus) + ]] + else + [404, {"Content-Type" => "text/plain"}, ["Not found.\nYou want to try /metrics?\n"]] + end + rescue + STDERR.puts "#$! (#{$!.class})", $!.backtrace + [500, {"Content-Type" => "text/plain"}, ["Server-error.\n"]] + end +} diff --git a/config.yml b/config.yml new file mode 100644 index 0000000..6be99f2 --- /dev/null +++ b/config.yml @@ -0,0 +1,62 @@ +# vim: set et sw=2 ts=2 sts=2: +--- +resolver: + nameservers: 8.8.8.8 +blacklists: +- all.s5h.net +- b.barracudacentral.org +- bl.emailbasura.org +- bl.spamcannibal.org +- bl.spamcop.net +- blacklist.woody.ch +- bogons.cymru.com +- cbl.abuseat.org +- cdl.anti-spam.org.cn +- combined.abuse.ch +- db.wpbl.info +- dnsbl-1.uceprotect.net +- dnsbl-2.uceprotect.net +- dnsbl-3.uceprotect.net +- dnsbl.anticaptcha.net +- dnsbl.dronebl.org +- dnsbl.inps.de +- dnsbl.sorbs.net +- dnsbl.spfbl.net +- drone.abuse.ch +- duinv.aupads.org +- dul.dnsbl.sorbs.net +- dyna.spamrats.com +- dynip.rothen.com +- http.dnsbl.sorbs.net +- ips.backscatterer.org +- ix.dnsbl.manitu.net +- korea.services.net +- misc.dnsbl.sorbs.net +- noptr.spamrats.com +- orvedb.aupads.org +- pbl.spamhaus.org +- proxy.bl.gweep.ca +- psbl.surriel.com +- relays.bl.gweep.ca +- relays.nether.net +- sbl.spamhaus.org +- short.rbl.jp +- singular.ttk.pte.hu +- smtp.dnsbl.sorbs.net +- socks.dnsbl.sorbs.net +- spam.abuse.ch +- spam.dnsbl.anonmails.de +- spam.dnsbl.sorbs.net +- spam.spamrats.com +- spambot.bls.digibase.ca +- spamrbl.imp.ch +- spamsources.fabel.dk +- ubl.lashback.com +- ubl.unsubscore.com +- virus.rbl.jp +- web.dnsbl.sorbs.net +- wormrbl.imp.ch +- xbl.spamhaus.org +- z.mailspike.net +- zen.spamhaus.org +- zombie.dnsbl.sorbs.net diff --git a/dnsbl_exporter.rb b/dnsbl_exporter.rb new file mode 100644 index 0000000..fba6bf5 --- /dev/null +++ b/dnsbl_exporter.rb @@ -0,0 +1,54 @@ +require 'prometheus/client' +require 'prometheus/client/formats/text' +require 'net/dns' +require 'yaml' + +class DnsblCollector + attr_reader :lists, :prometheus, :configfile, :resolver, :metrics + + def initialize prometheus: nil, configfile: nil + @configfile = configfile || 'config.yml' + @prometheus = prometheus || Prometheus::Client.registry + %i[dnsbl_listed dnsbl_query_error].each {|m| @prometheus.unregister m } + @metrics = { + dnsbl_listed: @prometheus.gauge( :dnsbl_listed, 'IP listed currently on Blacklist'), + dnsbl_query_error: @prometheus.counter( :dnsbl_query_error, 'Errors while query for blacklist'), + } + load_config + end + + def load_config + config = YAML.load_file @configfile + @resolver = config['resolver'] + @lists = + config['blacklists'].map do |bl| + case bl + when String then {blacklist: bl} + when Hash then bl + else raise ConfigError, "Unexpected Element in blacklists: #{bl}" + end + end + end + + def collect ip + prefix = ip.send :_reverse + ip = ip.to_s + dns = Net::DNS::Resolver.new @resolver + todo = @lists.sort{|_|rand} + durs = {} + 10.times.map do + Thread.new do + while bl = @lists.pop + b = Time.now + begin + r = dns.search( "#{prefix}.#{bl[:blacklist]}", Net::DNS::A).answer.empty? + @metrics[:dnsbl_listed].set( {blacklist: bl[:blacklist], target: ip}, r ? 0 : 1) + rescue Net::DNS::Resolver::NoResponseError + @metrics[:dnsbl_query_error].increment( {blacklist: bl[:blacklist], target: ip}) + end + durs[bl[:blacklist]] = Time.now-b + end + end + end.each &:join + end +end