powerdns_pipe 1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +149 -0
- data/lib/powerdns_pipe.rb +127 -0
- metadata +71 -0
data/README.rdoc
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
= Ruby PowerDNS Pipe Backend Abstraction
|
2
|
+
|
3
|
+
powerdns_pipe is a Ruby library for developing
|
4
|
+
{PowerDNS}[http://www.powerdns.com] pipe backend resolvers.
|
5
|
+
|
6
|
+
More information on the PowerDNS pipe backend {can be found
|
7
|
+
here}[http://doc.powerdns.com/backends-detail.html].
|
8
|
+
|
9
|
+
== Installation
|
10
|
+
|
11
|
+
sudo gem install powerdns_pipe
|
12
|
+
|
13
|
+
== Documentation
|
14
|
+
|
15
|
+
PowerDNS::Pipe works by handling all the communication with PowerDNS
|
16
|
+
for you. It handles the <tt>HELO</tt> and <tt>PING</tt> and stuff,
|
17
|
+
but calls a block when a query or axfr request is made. The block has
|
18
|
+
access to a <tt>question</tt> object which has information about the
|
19
|
+
query from PowerDNS, and the <tt>answer</tt> method, which provides
|
20
|
+
responses to PowerDNS.
|
21
|
+
|
22
|
+
<tt>PowerDNS::Pipe.new</tt> takes a hash of options, all are optional
|
23
|
+
and the defaults are usually all you need:
|
24
|
+
|
25
|
+
[:input] The IO object to read requests from PowerDNS. Defaults to
|
26
|
+
STDIN
|
27
|
+
|
28
|
+
[:output] The IO object to write responses to PowerDNS. Defaults to
|
29
|
+
STDOUT
|
30
|
+
|
31
|
+
[:err] The IO object to write debugging information. Defaults to
|
32
|
+
STDERR, but is rarely used.
|
33
|
+
|
34
|
+
[:version_range] A Range instance representing the Pipe protocol
|
35
|
+
versions this backend supports. Defaults to 1..2
|
36
|
+
|
37
|
+
[:banner] A string used in response to HELO requests from PowerDNS, is
|
38
|
+
logged to the PowerDNS logs. Defaults to "Ruby PowerDNS::Pipe"
|
39
|
+
|
40
|
+
=== question object
|
41
|
+
|
42
|
+
The <tt>question</tt> object provides information about the query from
|
43
|
+
the server:
|
44
|
+
|
45
|
+
[<tt>name</tt>] The record being requested, e.g: <tt>www.example.com</tt>
|
46
|
+
|
47
|
+
[<tt>qtype</tt>] The request type, such as <tt>A</tt> or
|
48
|
+
<tt>MX</tt>. PowerDNS often uses <tt>ANY</tt>, to
|
49
|
+
which you should return all valid records for the
|
50
|
+
name and PowerDNS worries about returning the right
|
51
|
+
one to the client. You <em>must</em> support this
|
52
|
+
type.
|
53
|
+
|
54
|
+
[<tt>qclass</tt>] The request class, this is always <tt>IN</tt>.
|
55
|
+
|
56
|
+
[<tt>remote_ip_address</tt>] The IP address of the host making the
|
57
|
+
dns request. You could use this to
|
58
|
+
return different records for different
|
59
|
+
geographic regions.
|
60
|
+
|
61
|
+
[<tt>local_ip_address</tt>] The server IP address the request came
|
62
|
+
into. Useful if your PowerDNS server is
|
63
|
+
listening on multiple IPs and you want to
|
64
|
+
consider that in your answers.
|
65
|
+
|
66
|
+
[<tt>id</tt>] The id of the last answer to this question provided to
|
67
|
+
PowerDNS by this backend for this. This might be
|
68
|
+
useful to you to speed up subsequent lookups. -1 by
|
69
|
+
default and can be ignored.
|
70
|
+
|
71
|
+
[<tt>query?</tt>] Returns true if this is a normal Q query.
|
72
|
+
|
73
|
+
[<tt>axfr?</tt>] Returns true if this is an axfr query.
|
74
|
+
|
75
|
+
=== answer method
|
76
|
+
|
77
|
+
The <tt>answer</tt> method is used to return records to PowerDNS. It
|
78
|
+
can be called multiple times to return multiple records. Any
|
79
|
+
exceptions are caught for you so garbage is not returned to PowerDNS.
|
80
|
+
If you have nothing to return, just don't call answer at all.
|
81
|
+
|
82
|
+
It takes the following options:
|
83
|
+
|
84
|
+
[<tt>:name</tt>] The record name, e.g: <tt>www.example.com</tt>. Can
|
85
|
+
usually just be set to <tt>question.name</tt>
|
86
|
+
|
87
|
+
[<tt>:ttl</tt>] Time to Live in seconds. Defaults to 3600
|
88
|
+
|
89
|
+
[<tt>:content</tt>] The content of the response, so an IP address
|
90
|
+
string for <tt>A</tt> answers, or arbitrary text
|
91
|
+
for <tt>TXT</tt> answers. For records with a
|
92
|
+
priority (like MX records) put the priority first
|
93
|
+
and then a space and then the content, e.g: <tt>10
|
94
|
+
mail.example.com</tt>
|
95
|
+
|
96
|
+
[<tt>:id</tt>] An integer id for this answer. PowerDNS will remember
|
97
|
+
this and pass it back for subsequent requests for the
|
98
|
+
same record. You might use this to pass around a
|
99
|
+
primary key or something to speed up subsequent
|
100
|
+
lookups. Defaults to -1 and can be ignored.
|
101
|
+
|
102
|
+
[<tt>:class</tt>] The class of this answer, defaults to <tt>IN</tt>
|
103
|
+
and shouldn't be changed.
|
104
|
+
|
105
|
+
== Basic example
|
106
|
+
|
107
|
+
Return an <tt>A</tt> record of <tt>1.2.3.4</tt> for all queries:
|
108
|
+
|
109
|
+
require 'powerdns_pipe'
|
110
|
+
PowerDNS::Pipe.new.run! do
|
111
|
+
answer :name => question.name, :type => 'A', :ttl => 60, :content => '1.2.3.4'
|
112
|
+
end
|
113
|
+
|
114
|
+
== Advanced example
|
115
|
+
|
116
|
+
Return the HTTP Server header as a TXT record for the host requested.
|
117
|
+
|
118
|
+
Example usage:
|
119
|
+
|
120
|
+
$ host -t txt www.ubuntu.com.example.com
|
121
|
+
ubuntu.com.example.com descriptive text "Apache/2.2.8 (Ubuntu) mod_python/3.3.1"
|
122
|
+
|
123
|
+
Code:
|
124
|
+
|
125
|
+
require 'powerdns_pipe'
|
126
|
+
require 'net/http'
|
127
|
+
re = Regexp.new("^(.+)\.example\.com$")
|
128
|
+
pipe = PowerDNS::Pipe.new :banner => 'HTTP Server Header TXT Pipe'
|
129
|
+
pipe.run! do
|
130
|
+
if m = re.match(question.name)
|
131
|
+
domain = m[1]
|
132
|
+
case question.qtype
|
133
|
+
when "TXT", "ANY"
|
134
|
+
res = Net::HTTP.get_response(URI.parse("http://" + domain))
|
135
|
+
answer :name => question.name , :type => 'TXT', :ttl => 3600,
|
136
|
+
:content => res['Server']
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
= More Info
|
142
|
+
|
143
|
+
Author:: John Leach (mailto:john@johnleach.co.uk)
|
144
|
+
Copyright:: Copyright (c) 2010 John Leach
|
145
|
+
License:: MIT
|
146
|
+
Github:: http://github.com/johnl/powerdns_pipe/tree/master
|
147
|
+
|
148
|
+
See also the {ruby-pdns library}[http://code.google.com/p/ruby-pdns/]
|
149
|
+
which does things differently, with some limitations.
|
@@ -0,0 +1,127 @@
|
|
1
|
+
module PowerDNS
|
2
|
+
|
3
|
+
# DnsPipe is an abstraction of the Powerdns pipe backend protocol.
|
4
|
+
# http://doc.powerdns.com/backends-detail.html
|
5
|
+
#
|
6
|
+
# It's dead simple to use, see the README for examples
|
7
|
+
#
|
8
|
+
# Written by John Leach <john@johnleach.co.uk>
|
9
|
+
#
|
10
|
+
class Pipe
|
11
|
+
attr_reader :input, :output, :err, :version_range, :banner
|
12
|
+
|
13
|
+
# Answer object is just used to wrap the block that handles answering
|
14
|
+
# queries
|
15
|
+
class Answer
|
16
|
+
attr_reader :question
|
17
|
+
|
18
|
+
def initialize(pipe, question)
|
19
|
+
@pipe = pipe
|
20
|
+
@question = question
|
21
|
+
end
|
22
|
+
|
23
|
+
def answer(*args)
|
24
|
+
@pipe.answer *args
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
# Question wraps any type of question line from the server
|
30
|
+
class Question < Struct.new(:tag, :name, :qclass, :qtype, :id, :remote_ip_address, :local_ip_address)
|
31
|
+
|
32
|
+
def to_s
|
33
|
+
[tag, name, qclass, qtype, id, remote_ip_address, local_ip_address].join("\t")
|
34
|
+
end
|
35
|
+
|
36
|
+
def query?
|
37
|
+
tag == "Q"
|
38
|
+
end
|
39
|
+
|
40
|
+
def axfr?
|
41
|
+
tag == "AXFR"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def initialize(options = {})
|
46
|
+
options = {
|
47
|
+
:input => STDIN,
|
48
|
+
:output => STDOUT,
|
49
|
+
:err => STDERR,
|
50
|
+
:version_range => 1..2,
|
51
|
+
:banner => "Ruby PowerDNS::Pipe"
|
52
|
+
}.merge options
|
53
|
+
|
54
|
+
@input = options[:input]
|
55
|
+
@output = options[:output]
|
56
|
+
@err = options[:err]
|
57
|
+
@version_range = options[:version_range]
|
58
|
+
@banner = options[:banner]
|
59
|
+
end
|
60
|
+
|
61
|
+
def run!(&query_processor)
|
62
|
+
while (line = input.readline) do
|
63
|
+
process_line line, query_processor
|
64
|
+
end
|
65
|
+
rescue EOFError
|
66
|
+
err.write "EOF, terminating loop\n"
|
67
|
+
end
|
68
|
+
|
69
|
+
def answer(options = {})
|
70
|
+
options = {
|
71
|
+
:ttl => 3600,
|
72
|
+
:id => -1,
|
73
|
+
:class => 'IN'
|
74
|
+
}.merge options
|
75
|
+
|
76
|
+
respond "DATA", options[:name], options[:class], options[:type], options[:ttl], options[:content]
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def process_line(line, query_processor)
|
82
|
+
q = Question.new *line.chomp.split("\t")
|
83
|
+
qtypes = {
|
84
|
+
"HELO" => :process_helo,
|
85
|
+
"Q" => :process_query,
|
86
|
+
"AXFR" => :process_query,
|
87
|
+
"PING" => :process_ping
|
88
|
+
}
|
89
|
+
|
90
|
+
self.send(qtypes.fetch(q.tag, :process_unknown), q, query_processor)
|
91
|
+
end
|
92
|
+
|
93
|
+
def process_helo(q, query_processor)
|
94
|
+
if version_range === (q.name.to_i rescue -1)
|
95
|
+
respond "OK", banner
|
96
|
+
else
|
97
|
+
respond "FAIL", banner
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def process_query(q, query_processor)
|
102
|
+
answer = Answer.new(self, q)
|
103
|
+
begin
|
104
|
+
answer.instance_eval &query_processor
|
105
|
+
respond "END"
|
106
|
+
rescue StandardError => e
|
107
|
+
respond "LOG", "Error: #{e.class}: #{e.message}"
|
108
|
+
respond "FAIL"
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
def process_unknown(q, query_processor)
|
114
|
+
respond "LOG", "Unknown Question received: #{q}"
|
115
|
+
respond "FAIL"
|
116
|
+
end
|
117
|
+
|
118
|
+
def process_ping(q, query_processor)
|
119
|
+
respond "END"
|
120
|
+
end
|
121
|
+
|
122
|
+
def respond(*args)
|
123
|
+
output.write(args.join("\t") + "\n")
|
124
|
+
output.flush
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
metadata
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: powerdns_pipe
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 15
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
version: "1.0"
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- John Leach
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-08-16 00:00:00 +01:00
|
18
|
+
default_executable:
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: A library to allow easy development of powerdns pipe backend resolvers in Ruby
|
22
|
+
email: john@johnleach.co.uk
|
23
|
+
executables: []
|
24
|
+
|
25
|
+
extensions: []
|
26
|
+
|
27
|
+
extra_rdoc_files:
|
28
|
+
- README.rdoc
|
29
|
+
files:
|
30
|
+
- lib/powerdns_pipe.rb
|
31
|
+
- README.rdoc
|
32
|
+
has_rdoc: true
|
33
|
+
homepage: http://github.com/johnl/powerdns_pipe/tree/master
|
34
|
+
licenses: []
|
35
|
+
|
36
|
+
post_install_message:
|
37
|
+
rdoc_options:
|
38
|
+
- --title
|
39
|
+
- PowerDNS Pipe
|
40
|
+
- --main
|
41
|
+
- README.rdoc
|
42
|
+
- --line-numbers
|
43
|
+
require_paths:
|
44
|
+
- lib
|
45
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
46
|
+
none: false
|
47
|
+
requirements:
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
hash: 3
|
51
|
+
segments:
|
52
|
+
- 0
|
53
|
+
version: "0"
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
hash: 3
|
60
|
+
segments:
|
61
|
+
- 0
|
62
|
+
version: "0"
|
63
|
+
requirements: []
|
64
|
+
|
65
|
+
rubyforge_project:
|
66
|
+
rubygems_version: 1.3.7
|
67
|
+
signing_key:
|
68
|
+
specification_version: 3
|
69
|
+
summary: A Ruby abstraction of the PowerDNS pipe backend protocol
|
70
|
+
test_files: []
|
71
|
+
|