line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Net::DNS::Dynamic::Proxyserver; |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
our $VERSION = '1.2'; |
4
|
|
|
|
|
|
|
|
5
|
3
|
|
|
3
|
|
43886
|
use strict; |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
75
|
|
6
|
3
|
|
|
3
|
|
15
|
use warnings; |
|
3
|
|
|
|
|
3
|
|
|
3
|
|
|
|
|
87
|
|
7
|
|
|
|
|
|
|
|
8
|
3
|
|
|
3
|
|
2300
|
use Perl6::Junction qw( all any none one ); |
|
3
|
|
|
|
|
26467
|
|
|
3
|
|
|
|
|
230
|
|
9
|
|
|
|
|
|
|
|
10
|
3
|
|
|
3
|
|
3165
|
use Net::DNS; |
|
3
|
|
|
|
|
330533
|
|
|
3
|
|
|
|
|
324
|
|
11
|
3
|
|
|
3
|
|
2918
|
use Net::DNS::Nameserver; |
|
3
|
|
|
|
|
20009
|
|
|
3
|
|
|
|
|
98
|
|
12
|
|
|
|
|
|
|
|
13
|
3
|
|
|
3
|
|
2897
|
use POSIX qw( strftime ); |
|
3
|
|
|
|
|
25189
|
|
|
3
|
|
|
|
|
21
|
|
14
|
3
|
|
|
3
|
|
4051
|
use Carp; |
|
3
|
|
|
|
|
17
|
|
|
3
|
|
|
|
|
146
|
|
15
|
|
|
|
|
|
|
|
16
|
3
|
|
|
3
|
|
7672
|
use Moose; |
|
3
|
|
|
|
|
1861837
|
|
|
3
|
|
|
|
|
37
|
|
17
|
3
|
|
|
3
|
|
25635
|
use Moose::Util::TypeConstraints; |
|
3
|
|
|
|
|
9
|
|
|
3
|
|
|
|
|
35
|
|
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
=head1 NAME |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
Net::DNS::Dynamic::Proxyserver - A dynamic DNS proxy-server |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
=head1 DESCRIPTION |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
This proxy-server is able to resolve incoming DNS queries by asking the /etc/hosts file |
26
|
|
|
|
|
|
|
and/or a SQL database. You could run it as a simple proxy-server which just loops |
27
|
|
|
|
|
|
|
through the DNS question/answer to other nameservers. However, it could also be |
28
|
|
|
|
|
|
|
used to build your own dynamic DNS service, share one /etc/hosts file with all PC's in |
29
|
|
|
|
|
|
|
your local network or to deliver different answers for hosts at different locations. |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
For example, if you have a home or office server behind NAT with port-forwarding and |
32
|
|
|
|
|
|
|
want to connect to this server always with the same hostname based on your notebooks |
33
|
|
|
|
|
|
|
location, you could run this dns-proxy to answer to your servers hostname with it's |
34
|
|
|
|
|
|
|
internal RFC1918 IP when you are at home or in the office. When you're at a different |
35
|
|
|
|
|
|
|
location and use an external nameserver, the hostname of your server will be resolved |
36
|
|
|
|
|
|
|
with the "external" IP address (of your router). |
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
If you like to build your own dynamic DNS service, you need to write your dynamic IP |
39
|
|
|
|
|
|
|
addresses into a SQL databases and let your DNS proxy-server answer queries from it. |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
=head1 SYNOPSIS |
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
my $proxy = Net::DNS::Dynamic::Proxyserver->new( |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
debug => 1, |
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
host => '*', |
48
|
|
|
|
|
|
|
port => 53, |
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
uid => 65534, |
51
|
|
|
|
|
|
|
gid => 65534, |
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
nameservers => [ '127.0.0.1', '192.168.1.110' ], |
54
|
|
|
|
|
|
|
nameservers_port => 53, |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
ask_etc_hosts => { ttl => 3600 }, |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
ask_sql => { |
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
ttl => 60, |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
dsn => 'DBI:mysql:database=my_database;host=localhost;port=3306', |
63
|
|
|
|
|
|
|
user => 'my_user', |
64
|
|
|
|
|
|
|
pass => 'my_password', |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
statement => "SELECT ip FROM hosts WHERE hostname='{qname}' AND type='{qtype}'" |
67
|
|
|
|
|
|
|
}, |
68
|
|
|
|
|
|
|
); |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
$proxy->run(); |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
=head1 WORKFLOW |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
At startup, the file /etc/resolv.conf will be read and parsed. All defined nameservers will |
75
|
|
|
|
|
|
|
be used to proxy through queries that can not be answered locally. If you define the 'ask_etc_hosts' |
76
|
|
|
|
|
|
|
argument, then also the file /etc/hosts will be read at startup and will be used as the first |
77
|
|
|
|
|
|
|
resource to answer DNS questions. If you make changes to /etc/hosts, you can send a kernel |
78
|
|
|
|
|
|
|
signal HUP to your script, which will trigger a re-read of this file at run-time. The hosts-file |
79
|
|
|
|
|
|
|
will only answer queries for type 'A' (name to IP) and 'PTR' (IP to name). |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
If you specify the 'ask_sql' argument, the SQL database will be asked in second order, right |
82
|
|
|
|
|
|
|
after a look into the hosts file. The SQL statement will be parsed for every query with the |
83
|
|
|
|
|
|
|
given query name and type. Your statement should return the IP address as the first column |
84
|
|
|
|
|
|
|
in the result-set. Right now, only "forward lookups" are supported (PTR records can not |
85
|
|
|
|
|
|
|
be resolved yet because we'd need a second, different SQL statement for that). |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
Then, if the query could not be answered from the hosts-file and/or the database, the question |
88
|
|
|
|
|
|
|
will be handed over to the nameserves from your /etc/resolv.conf and the answer will be looped |
89
|
|
|
|
|
|
|
trough to the caller. |
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
=head1 Arguments to new() |
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
The following options may be passed over when creating a new object: |
94
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
=head2 debug Int |
96
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
When the debug option is set to 1 or higher (1-3), this module will print out some |
98
|
|
|
|
|
|
|
helpful debug informations to STDOUT. If you like, redirect the output to a |
99
|
|
|
|
|
|
|
log-file, like so |
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
./my-dns-proxy.pl >>/var/log/my_dns_proxy.log |
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
A debug value of 1 prints out some basic action logging. A value of 2 and |
104
|
|
|
|
|
|
|
higher turns on nameserver verbosity, a value of 3 and higher turns on resolver |
105
|
|
|
|
|
|
|
debug output. |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
=head2 host String |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
You can specify the IP address to bind to with this option. If not defined, the |
110
|
|
|
|
|
|
|
server binds to all interfaces. |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
Examples: |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
my $proxy = Net::DNS::Dynamic::Proxyserver->new( host => '127.0.0.1' ); |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
my $proxy = Net::DNS::Dynamic::Proxyserver->new( host => '192.168.1.1' ); |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
my $proxy = Net::DNS::Dynamic::Proxyserver->new( host => '*' ); |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
=head2 port Int |
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
The tcp & udp port to run the DNS server under. Default is port 53, which means |
123
|
|
|
|
|
|
|
that you need to start your script as user root (all ports below 1000 need root |
124
|
|
|
|
|
|
|
rights). |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
my $proxy = Net::DNS::Dynamic::Proxyserver->new( port => 5353 ); |
127
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
=head2 uid Int |
129
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
The user id to switch to, after the socket has been created. Could be set to |
131
|
|
|
|
|
|
|
the uid of 'nobody' (65534 on some systems). |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
my $proxy = Net::DNS::Dynamic::Proxyserver->new( uid => 65534 ); |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
=head2 gid Int |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
The group id to switch to, after the socket has been created. Could be set to |
138
|
|
|
|
|
|
|
the gid of 'nogroup' (65534 on some systems). |
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
my $proxy = Net::DNS::Dynamic::Proxyserver->new( gid => 65534 ); |
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
=head2 nameservers ArrayRef |
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
This argument allows to defined one or more nameservers to forward any DNS question |
145
|
|
|
|
|
|
|
which can not be locally answered. Must be an Arrayref of IP addresses. |
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
If you do not specify nameservers this way, the file /etc/resolv.conf will be read |
148
|
|
|
|
|
|
|
instead and any nameserver defined there will be used. |
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
my $proxy = Net::DNS::Dynamic::Proxyserver->new( nameservers => [ '127.0.0.1', '192.168.1.110' ] ); |
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
=head2 nameservers_port Int |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
Specify the port of the remote nameservers. By default, this is set to 53 (the standard port), |
155
|
|
|
|
|
|
|
but you can ovewrite it if you run a nameserver on a different port. This port will be used |
156
|
|
|
|
|
|
|
for every nameserver - due to a limitation of Net::DNS::Resolver which cant deal with ports |
157
|
|
|
|
|
|
|
for each individual nameserver. |
158
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
my $proxy = Net::DNS::Dynamic::Proxyserver->new( nameservers_port => 5353 ); |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
=head2 ask_etc_hosts HashRef |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
If you'd like to anwer DNS queries from entries in your /etc/hosts file, then |
164
|
|
|
|
|
|
|
define this argument like so: |
165
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
my $proxy = Net::DNS::Dynamic::Proxyserver->new( ask_etc_hosts => { ttl => 3600 } ); |
167
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
The only argument that can be passed to 'ask_etc_hosts' is the TTL (time to life) for |
169
|
|
|
|
|
|
|
the response. |
170
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
If 'ask_etc_hosts' is not defined, no queries to /etc/hosts will be made. |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
If you make changes to your /etc/hosts file, you can send your script a |
174
|
|
|
|
|
|
|
signal HUP and it will re-read the file on the fly. |
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
=head2 ask_sql HashRef |
177
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
If you'd like to answer DNS queries from entries in your SQL database, then define |
179
|
|
|
|
|
|
|
this argument like so: |
180
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
my $proxy = Net::DNS::Dynamic::Proxyserver->new( ask_sql => { |
182
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
ttl => 60, |
184
|
|
|
|
|
|
|
dsn => 'DBI:mysql:database=db_name;host=localhost;port=3306', |
185
|
|
|
|
|
|
|
user => 'my_user', |
186
|
|
|
|
|
|
|
pass => 'my_password', |
187
|
|
|
|
|
|
|
statement => "SELECT ip FROM hosts WHERE hostname='{qname}' AND type='{qtype}'" |
188
|
|
|
|
|
|
|
} ); |
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
The 'ttl' specifies the TTL (time to life) for the DNS response. Setting this to a |
191
|
|
|
|
|
|
|
low value will tell the client to ask you again after the TTL time has passed by; |
192
|
|
|
|
|
|
|
which also means some higher load for your dns-proxy-server. |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
The 'dsn' is the 'data source name' for the DBI module. This information is used |
195
|
|
|
|
|
|
|
to connect to your SQL database. You can use every flavour of SQL database that |
196
|
|
|
|
|
|
|
is supported by DBI and a DBD::* module, like MySQL, PostgreSQL, SQLite, Oracle, etc... |
197
|
|
|
|
|
|
|
Please have a look at the manual page of DBI and DBD::* to see how a dsn looks like |
198
|
|
|
|
|
|
|
and which options it could contain. |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
The 'user' and 'pass' is the username and password for the connection to the database. If |
201
|
|
|
|
|
|
|
you use SQLite, just leave the values empty (user => '', pass => ''). Also make sure, the |
202
|
|
|
|
|
|
|
SQLite database file can be accessed (read/write) with the defined uid/gid! |
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
The 'statement' is a SELECT statement, which must return the IP address for the |
205
|
|
|
|
|
|
|
given query name (qname) and query type (qtype, like 'A' or 'MX'). The placeholders |
206
|
|
|
|
|
|
|
{qname} and {qtype} will be replaced by the actual query name and type. Your statement |
207
|
|
|
|
|
|
|
must return the IP address as the first column in the result. |
208
|
|
|
|
|
|
|
|
209
|
|
|
|
|
|
|
If 'ask_sql' is not defined, no queries to a database will be made. |
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
=cut |
212
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
subtype 'Net.DNS.Dynamic.Proxyserver.ValidSQLArguments' |
214
|
|
|
|
|
|
|
=> as 'HashRef' |
215
|
|
|
|
|
|
|
=> where { $_->{dsn} && $_->{user} && $_->{pass} && $_->{statement} } |
216
|
|
|
|
|
|
|
=> message { "Mandatory elements missing in argument 'ask_sql': dsn, user, pass, statement" }; |
217
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
has debug => ( is => 'ro', isa => 'Int', required => 0, default => 0 ); |
219
|
|
|
|
|
|
|
has host => ( is => 'ro', isa => 'Str', required => 0, default => '*' ); |
220
|
|
|
|
|
|
|
has port => ( is => 'ro', isa => 'Int', required => 0, default => 53 ); |
221
|
|
|
|
|
|
|
has uid => ( is => 'ro', isa => 'Int', required => 0 ); |
222
|
|
|
|
|
|
|
has gid => ( is => 'ro', isa => 'Int', required => 0 ); |
223
|
|
|
|
|
|
|
has ask_etc_hosts => ( is => 'ro', isa => 'HashRef', required => 0 ); |
224
|
|
|
|
|
|
|
has ask_sql => ( is => 'ro', isa => 'Net.DNS.Dynamic.Proxyserver.ValidSQLArguments', required => 0 ); |
225
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
has addrs => ( is => 'rw', isa => 'HashRef', init_arg => undef ); |
227
|
|
|
|
|
|
|
has forwarders => ( is => 'rw', isa => 'ArrayRef', required => 0, init_arg => 'nameservers' ); |
228
|
|
|
|
|
|
|
has forwarders_port => ( is => 'ro', isa => 'Int', required => 0, init_arg => 'nameservers_port' ); |
229
|
|
|
|
|
|
|
has dbh => ( is => 'rw', isa => 'Object', init_arg => undef ); |
230
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
has nameserver => ( is => 'rw', isa => 'Net::DNS::Nameserver', init_arg => undef ); |
232
|
|
|
|
|
|
|
has resolver => ( is => 'rw', isa => 'Net::DNS::Resolver', init_arg => undef ); |
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
sub BUILD { |
235
|
2
|
|
|
2
|
0
|
22
|
my ( $self ) = shift; |
236
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
# initialize signal handlers |
238
|
|
|
|
|
|
|
# |
239
|
2
|
|
|
0
|
|
80
|
$SIG{KILL} = sub { $self->signal_handler(@_) }; |
|
0
|
|
|
|
|
0
|
|
240
|
2
|
|
|
1
|
|
34
|
$SIG{QUIT} = sub { $self->signal_handler(@_) }; |
|
1
|
|
|
|
|
7292
|
|
241
|
2
|
|
|
0
|
|
28
|
$SIG{TERM} = sub { $self->signal_handler(@_) }; |
|
0
|
|
|
|
|
0
|
|
242
|
2
|
|
|
0
|
|
28
|
$SIG{INT} = sub { $self->signal_handler(@_) }; |
|
0
|
|
|
|
|
0
|
|
243
|
2
|
|
|
0
|
|
26
|
$SIG{HUP} = sub { $self->read_config() }; |
|
0
|
|
|
|
|
0
|
|
244
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
# slurp in /etc/hosts and /etc/resolv.conf |
246
|
|
|
|
|
|
|
# |
247
|
2
|
|
|
|
|
14
|
$self->read_config(); |
248
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
# initialize nameserver object |
250
|
|
|
|
|
|
|
# |
251
|
|
|
|
|
|
|
my $ns = Net::DNS::Nameserver->new( |
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
LocalAddr => $self->host, |
254
|
|
|
|
|
|
|
LocalPort => $self->port, |
255
|
1
|
|
|
1
|
|
19376
|
ReplyHandler => sub { $self->reply_handler(@_); }, |
256
|
2
|
50
|
|
|
|
74
|
Verbose => ($self->debug > 1 ? 1 : 0) |
257
|
|
|
|
|
|
|
); |
258
|
|
|
|
|
|
|
|
259
|
2
|
|
|
|
|
3412
|
$self->nameserver( $ns ); |
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
# initialize resolver object |
262
|
|
|
|
|
|
|
# |
263
|
2
|
|
|
|
|
90
|
my $res = Net::DNS::Resolver->new( |
264
|
|
|
|
|
|
|
|
265
|
2
|
50
|
50
|
|
|
4
|
nameservers => [ @{$self->forwarders} ], |
266
|
|
|
|
|
|
|
port => $self->forwarders_port || 53, |
267
|
|
|
|
|
|
|
recurse => 1, |
268
|
|
|
|
|
|
|
debug => ($self->debug > 2 ? 1 : 0), |
269
|
|
|
|
|
|
|
); |
270
|
|
|
|
|
|
|
|
271
|
2
|
|
|
|
|
448
|
$self->resolver( $res ); |
272
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
# change the effective user id and group id |
274
|
|
|
|
|
|
|
# |
275
|
2
|
50
|
|
|
|
74
|
$> = $self->uid if $self->uid; |
276
|
2
|
50
|
|
|
|
62
|
$) = $self->gid if $self->gid; |
277
|
|
|
|
|
|
|
} |
278
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
sub run { |
280
|
1
|
|
|
1
|
0
|
2420
|
my ( $self ) = shift; |
281
|
|
|
|
|
|
|
|
282
|
1
|
|
|
|
|
419
|
$self->log("listening for DNS queries on address " . $self->host . " and port " . $self->port, 1); |
283
|
|
|
|
|
|
|
|
284
|
1
|
50
|
|
|
|
49
|
$self->log("Try a DNS query to your server: dig @" . ($self->host eq '*' ? '127.0.0.1' : $self->host ) . " -p " . $self->port . " -q hostname.domain.com"); |
285
|
|
|
|
|
|
|
|
286
|
1
|
|
|
|
|
91
|
$self->nameserver->main_loop; |
287
|
|
|
|
|
|
|
} |
288
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
sub reply_handler { |
290
|
1
|
|
|
1
|
0
|
13
|
my ($self, $qname, $qclass, $qtype, $peerhost,$query,$conn) = @_; |
291
|
|
|
|
|
|
|
|
292
|
1
|
|
|
|
|
21
|
my ($rcode, @ans, @auth, @add); |
293
|
|
|
|
|
|
|
|
294
|
1
|
|
|
|
|
281
|
$self->log("received query from $peerhost: qtype '$qtype', qname '$qname'"); |
295
|
|
|
|
|
|
|
|
296
|
|
|
|
|
|
|
# see if we can answer the question from /etc/hosts |
297
|
|
|
|
|
|
|
# |
298
|
1
|
0
|
0
|
|
|
73
|
if ($self->ask_etc_hosts && ($qtype eq 'A' || $qtype eq 'PTR')) { |
|
|
|
33
|
|
|
|
|
299
|
|
|
|
|
|
|
|
300
|
0
|
0
|
|
|
|
0
|
if (my $ip = $self->query_etc_hosts( $qname, $qtype )) { |
301
|
|
|
|
|
|
|
|
302
|
0
|
|
|
|
|
0
|
$self->log("[/etc/hosts] resolved $qname to $ip NOERROR"); |
303
|
|
|
|
|
|
|
|
304
|
0
|
0
|
|
|
|
0
|
my ($ttl, $rdata) = (($self->ask_etc_hosts->{ttl} ? $self->ask_etc_hosts->{ttl} : 3600), $ip ); |
305
|
|
|
|
|
|
|
|
306
|
0
|
|
|
|
|
0
|
push @ans, Net::DNS::RR->new("$qname $ttl $qclass $qtype $rdata"); |
307
|
|
|
|
|
|
|
|
308
|
0
|
|
|
|
|
0
|
$rcode = "NOERROR"; |
309
|
|
|
|
|
|
|
|
310
|
0
|
|
|
|
|
0
|
return ($rcode, \@ans, \@auth, \@add, { aa => 1, ra => 1 }); |
311
|
|
|
|
|
|
|
} |
312
|
|
|
|
|
|
|
} |
313
|
|
|
|
|
|
|
|
314
|
|
|
|
|
|
|
# see if we can answer the question from the SQL database |
315
|
|
|
|
|
|
|
# |
316
|
1
|
50
|
|
|
|
53
|
if ($self->ask_sql) { |
317
|
|
|
|
|
|
|
|
318
|
0
|
0
|
|
|
|
0
|
if (my $ip = $self->query_sql( $qname, $qtype )) { |
319
|
|
|
|
|
|
|
|
320
|
0
|
|
|
|
|
0
|
$self->log("[SQL] resolved $qname to $ip NOERROR"); |
321
|
|
|
|
|
|
|
|
322
|
0
|
0
|
|
|
|
0
|
my ($ttl, $rdata) = (($self->ask_sql->{ttl} ? $self->ask_sql->{ttl} : 3600), $ip ); |
323
|
|
|
|
|
|
|
|
324
|
0
|
|
|
|
|
0
|
push @ans, Net::DNS::RR->new("$qname $ttl $qclass $qtype $rdata"); |
325
|
|
|
|
|
|
|
|
326
|
0
|
|
|
|
|
0
|
$rcode = "NOERROR"; |
327
|
|
|
|
|
|
|
|
328
|
0
|
|
|
|
|
0
|
return ($rcode, \@ans, \@auth, \@add, { aa => 1, ra => 1 }); |
329
|
|
|
|
|
|
|
} |
330
|
|
|
|
|
|
|
} |
331
|
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
# forward to remote nameserver and loop through the result |
333
|
|
|
|
|
|
|
# |
334
|
1
|
|
|
|
|
73
|
my $answer = $self->resolver->send($qname, $qtype, $qclass); |
335
|
|
|
|
|
|
|
|
336
|
1
|
50
|
|
|
|
42188
|
if ($answer) { |
337
|
|
|
|
|
|
|
|
338
|
1
|
|
|
|
|
7
|
$rcode = $answer->header->rcode; |
339
|
1
|
|
|
|
|
50
|
@ans = $answer->answer; |
340
|
1
|
|
|
|
|
19
|
@auth = $answer->authority; |
341
|
1
|
|
|
|
|
11
|
@add = $answer->additional; |
342
|
|
|
|
|
|
|
|
343
|
1
|
|
|
|
|
21
|
$self->log("[proxy] response from remote resolver: $qname $rcode"); |
344
|
|
|
|
|
|
|
|
345
|
1
|
|
|
|
|
40
|
return ($rcode, \@ans, \@auth, \@add); |
346
|
|
|
|
|
|
|
} |
347
|
|
|
|
|
|
|
else { |
348
|
|
|
|
|
|
|
|
349
|
0
|
|
|
|
|
0
|
$self->log("[proxy] can not resolve $qtype $qname - no answer from remote resolver. Sending NXDOMAIN response."); |
350
|
|
|
|
|
|
|
|
351
|
0
|
|
|
|
|
0
|
$rcode = "NXDOMAIN"; |
352
|
|
|
|
|
|
|
|
353
|
0
|
|
|
|
|
0
|
return ($rcode, \@ans, \@auth, \@add, { aa => 1, ra => 1 }); |
354
|
|
|
|
|
|
|
} |
355
|
|
|
|
|
|
|
} |
356
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
sub log { |
358
|
7
|
|
|
7
|
0
|
77
|
my ( $self, $msg, $force_flag ) = @_; |
359
|
|
|
|
|
|
|
|
360
|
7
|
100
|
66
|
|
|
427
|
print "[" . strftime('%Y-%m-%d %H:%M:%S', localtime(time)) . "] " . $msg . "\n" if $self->debug || $force_flag; |
361
|
|
|
|
|
|
|
} |
362
|
|
|
|
|
|
|
|
363
|
|
|
|
|
|
|
sub read_config { |
364
|
2
|
|
|
2
|
0
|
6
|
my ( $self ) = shift; |
365
|
|
|
|
|
|
|
|
366
|
2
|
|
|
|
|
10
|
$self->forwarders([ $self->parse_resolv_conf() ]); # /etc/resolv.conf |
367
|
2
|
|
|
|
|
10
|
$self->addrs({ $self->parse_etc_hosts() }); # /etc/hosts |
368
|
|
|
|
|
|
|
} |
369
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
sub signal_handler { |
371
|
1
|
|
|
1
|
0
|
6
|
my ( $self, $signal ) = @_; |
372
|
|
|
|
|
|
|
|
373
|
1
|
|
|
|
|
24
|
$self->log("shutting down because of signal $signal"); |
374
|
|
|
|
|
|
|
|
375
|
1
|
50
|
|
|
|
82
|
$self->dbh->disconnect() if $self->dbh; |
376
|
|
|
|
|
|
|
|
377
|
1
|
|
|
|
|
157
|
exit; |
378
|
|
|
|
|
|
|
} |
379
|
|
|
|
|
|
|
|
380
|
|
|
|
|
|
|
sub query_etc_hosts { |
381
|
0
|
|
|
0
|
0
|
0
|
my ( $self, $qname, $qtype ) = @_; |
382
|
|
|
|
|
|
|
|
383
|
0
|
0
|
|
|
|
0
|
return $self->search_ip_by_hostname( $qname ) if $qtype eq 'A'; |
384
|
0
|
0
|
|
|
|
0
|
return $self->search_hostname_by_ip( $qname ) if $qtype eq 'PTR'; |
385
|
|
|
|
|
|
|
} |
386
|
|
|
|
|
|
|
|
387
|
|
|
|
|
|
|
sub search_ip_by_hostname { |
388
|
0
|
|
|
0
|
0
|
0
|
my ( $self, $hostname ) = @_; |
389
|
|
|
|
|
|
|
|
390
|
0
|
|
|
|
|
0
|
foreach my $ip (keys %{$self->addrs}) { |
|
0
|
|
|
|
|
0
|
|
391
|
|
|
|
|
|
|
|
392
|
0
|
0
|
|
|
|
0
|
if ( any(@{$self->addrs->{$ip}}) eq $hostname ) { |
|
0
|
|
|
|
|
0
|
|
393
|
|
|
|
|
|
|
|
394
|
0
|
|
|
|
|
0
|
return $ip; |
395
|
|
|
|
|
|
|
} |
396
|
|
|
|
|
|
|
} |
397
|
|
|
|
|
|
|
|
398
|
0
|
|
|
|
|
0
|
return; |
399
|
|
|
|
|
|
|
} |
400
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
sub search_hostname_by_ip { |
402
|
0
|
|
|
0
|
0
|
0
|
my ( $self, $ip ) = @_; |
403
|
|
|
|
|
|
|
|
404
|
0
|
|
0
|
|
|
0
|
$ip = $self->get_in_addr_arpa( $ip ) || return; |
405
|
|
|
|
|
|
|
|
406
|
0
|
0
|
|
|
|
0
|
return $self->addrs->{$ip}->[0] if $self->addrs->{$ip}; |
407
|
|
|
|
|
|
|
|
408
|
0
|
|
|
|
|
0
|
return; |
409
|
|
|
|
|
|
|
} |
410
|
|
|
|
|
|
|
|
411
|
|
|
|
|
|
|
sub get_in_addr_arpa { |
412
|
0
|
|
|
0
|
0
|
0
|
my ( $self, $ptr ) = @_; |
413
|
|
|
|
|
|
|
|
414
|
|
|
|
|
|
|
# convert ipv4 -> 10.1.168.192.in-addr.arpa -> 192.168.1.10 |
415
|
|
|
|
|
|
|
# |
416
|
0
|
|
|
|
|
0
|
my ($reverse_ip) = ($ptr =~ m!^([\d\.]+)\.in-addr\.arpa$!); |
417
|
|
|
|
|
|
|
|
418
|
0
|
0
|
|
|
|
0
|
return unless $reverse_ip; |
419
|
|
|
|
|
|
|
|
420
|
0
|
|
|
|
|
0
|
my @octets = reverse split(/\./, $reverse_ip); |
421
|
|
|
|
|
|
|
|
422
|
0
|
|
|
|
|
0
|
return join('.', @octets); |
423
|
|
|
|
|
|
|
} |
424
|
|
|
|
|
|
|
|
425
|
|
|
|
|
|
|
sub parse_etc_hosts { |
426
|
2
|
|
|
2
|
0
|
8
|
my ( $self ) = shift; |
427
|
|
|
|
|
|
|
|
428
|
2
|
50
|
|
|
|
84
|
return unless $self->ask_etc_hosts; |
429
|
|
|
|
|
|
|
|
430
|
0
|
|
|
|
|
0
|
$self->log('reading /etc/hosts file'); |
431
|
|
|
|
|
|
|
|
432
|
0
|
|
|
|
|
0
|
my %addrs; |
433
|
|
|
|
|
|
|
my %names; |
434
|
|
|
|
|
|
|
|
435
|
0
|
0
|
|
|
|
0
|
open(HOSTS, "/etc/hosts") or croak "cant open /etc/hosts file: $!"; |
436
|
|
|
|
|
|
|
|
437
|
0
|
|
|
|
|
0
|
while (<HOSTS>) { |
438
|
|
|
|
|
|
|
|
439
|
0
|
0
|
|
|
|
0
|
next if /^\s*#/; # skip comments |
440
|
0
|
0
|
|
|
|
0
|
next if /^$/; # skip empty lines |
441
|
0
|
|
|
|
|
0
|
s/\s*#.*$//; # delete in-line comments and preceding whitespace |
442
|
|
|
|
|
|
|
|
443
|
0
|
|
|
|
|
0
|
my ($ip, @names) = split; |
444
|
|
|
|
|
|
|
|
445
|
0
|
0
|
|
|
|
0
|
next unless $ip =~ /^[\d\.]+$/; # skip ipv6 adresses |
446
|
|
|
|
|
|
|
|
447
|
0
|
|
|
|
|
0
|
push @{$addrs{$ip}}, @names; |
|
0
|
|
|
|
|
0
|
|
448
|
|
|
|
|
|
|
|
449
|
0
|
|
|
|
|
0
|
foreach (@names) { |
450
|
|
|
|
|
|
|
|
451
|
0
|
0
|
|
|
|
0
|
croak "The hostname $_ has been defined for more then one IP address!\n" if exists $names{$_}; |
452
|
|
|
|
|
|
|
|
453
|
0
|
|
|
|
|
0
|
$names{$_} = $ip; |
454
|
|
|
|
|
|
|
} |
455
|
|
|
|
|
|
|
} |
456
|
|
|
|
|
|
|
|
457
|
0
|
|
|
|
|
0
|
close(HOSTS); |
458
|
|
|
|
|
|
|
|
459
|
0
|
|
|
|
|
0
|
return %addrs; |
460
|
|
|
|
|
|
|
} |
461
|
|
|
|
|
|
|
|
462
|
|
|
|
|
|
|
sub parse_resolv_conf { |
463
|
2
|
|
|
2
|
0
|
4
|
my ( $self ) = shift; |
464
|
|
|
|
|
|
|
|
465
|
2
|
50
|
|
|
|
90
|
return @{$self->forwarders} if $self->forwarders; |
|
0
|
|
|
|
|
0
|
|
466
|
|
|
|
|
|
|
|
467
|
2
|
|
|
|
|
12
|
$self->log('reading /etc/resolv.conf file'); |
468
|
|
|
|
|
|
|
|
469
|
2
|
|
|
|
|
4
|
my @dns_servers; |
470
|
|
|
|
|
|
|
|
471
|
2
|
50
|
|
|
|
118
|
open (RESOLV, "/etc/resolv.conf") || croak "cant open /etc/resolv.conf file: $!"; |
472
|
|
|
|
|
|
|
|
473
|
2
|
|
|
|
|
68
|
while (<RESOLV>) { |
474
|
|
|
|
|
|
|
|
475
|
12
|
100
|
|
|
|
62
|
if (/^nameserver\s+([\d\.]+)/) { |
476
|
|
|
|
|
|
|
|
477
|
4
|
|
|
|
|
20
|
push @dns_servers, $1; |
478
|
|
|
|
|
|
|
} |
479
|
|
|
|
|
|
|
} |
480
|
|
|
|
|
|
|
|
481
|
2
|
|
|
|
|
18
|
close (RESOLV); |
482
|
|
|
|
|
|
|
|
483
|
2
|
50
|
|
|
|
10
|
croak "you have not specified a nameserver in /etc/resolv.conf!" unless @dns_servers; |
484
|
|
|
|
|
|
|
|
485
|
2
|
|
|
|
|
92
|
return @dns_servers; |
486
|
|
|
|
|
|
|
} |
487
|
|
|
|
|
|
|
|
488
|
|
|
|
|
|
|
sub query_sql { |
489
|
0
|
|
|
0
|
0
|
|
my ( $self, $qname, $qtype ) = @_; |
490
|
|
|
|
|
|
|
|
491
|
3
|
|
|
3
|
|
45692
|
use DBI; |
|
3
|
|
|
|
|
83151
|
|
|
3
|
|
|
|
|
1132
|
|
492
|
|
|
|
|
|
|
|
493
|
0
|
|
|
|
|
|
my $args = $self->ask_sql; |
494
|
|
|
|
|
|
|
|
495
|
|
|
|
|
|
|
# see if we have an open database handle already, which we can re-use |
496
|
|
|
|
|
|
|
# |
497
|
0
|
0
|
0
|
|
|
|
unless ($self->dbh && $self->dbh->ping()) { |
498
|
|
|
|
|
|
|
|
499
|
|
|
|
|
|
|
# connect |
500
|
|
|
|
|
|
|
# |
501
|
0
|
|
0
|
|
|
|
my $dbh = DBI->connect( $args->{dsn}, $args->{user}, $args->{pass} ) || croak "can not connect to database $args->{dsn} $!"; |
502
|
|
|
|
|
|
|
|
503
|
0
|
|
|
|
|
|
$self->dbh( $dbh ); |
504
|
|
|
|
|
|
|
} |
505
|
|
|
|
|
|
|
|
506
|
0
|
0
|
|
|
|
|
$qname = $self->get_in_addr_arpa( $qname ) if $qtype eq 'PTR'; |
507
|
|
|
|
|
|
|
|
508
|
|
|
|
|
|
|
# parse the statement variables |
509
|
|
|
|
|
|
|
# |
510
|
0
|
|
|
|
|
|
$qname =~ s!'!!g; |
511
|
0
|
|
|
|
|
|
$qtype =~ s!'!!g; |
512
|
|
|
|
|
|
|
|
513
|
0
|
|
|
|
|
|
my $statement = $args->{statement}; |
514
|
|
|
|
|
|
|
|
515
|
0
|
|
|
|
|
|
$statement =~ s!{qname}!$qname!g; |
516
|
0
|
|
|
|
|
|
$statement =~ s!{qtype}!$qtype!g; |
517
|
|
|
|
|
|
|
|
518
|
0
|
|
|
|
|
|
my $sth = $self->dbh->prepare( $statement ); |
519
|
|
|
|
|
|
|
|
520
|
0
|
|
|
|
|
|
$sth->execute(); |
521
|
|
|
|
|
|
|
|
522
|
|
|
|
|
|
|
# we expect exact one column to come back from the SQL statement - the IP address of the given hostname and query type |
523
|
|
|
|
|
|
|
# |
524
|
0
|
|
|
|
|
|
my $result = $sth->fetchrow_arrayref(); |
525
|
|
|
|
|
|
|
|
526
|
0
|
|
|
|
|
|
return $result->[0]; |
527
|
|
|
|
|
|
|
} |
528
|
|
|
|
|
|
|
|
529
|
|
|
|
|
|
|
=head1 AUTHOR |
530
|
|
|
|
|
|
|
|
531
|
|
|
|
|
|
|
Marc Sebastian Jakobs <maja@cpan.org> |
532
|
|
|
|
|
|
|
|
533
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE |
534
|
|
|
|
|
|
|
|
535
|
|
|
|
|
|
|
Copyright 2009 by Marc Sebastian Jakobs |
536
|
|
|
|
|
|
|
|
537
|
|
|
|
|
|
|
This library is free software, you can redistribute it and/or modify |
538
|
|
|
|
|
|
|
it under the same terms as Perl itself. |
539
|
|
|
|
|
|
|
|
540
|
|
|
|
|
|
|
=cut |
541
|
|
|
|
|
|
|
|
542
|
|
|
|
|
|
|
__PACKAGE__->meta->make_immutable; |
543
|
|
|
|
|
|
|
|
544
|
|
|
|
|
|
|
1; |
545
|
|
|
|
|
|
|
|