powerdns_pipe 1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
|