latte 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +3 -1
- data/latte.gemspec +1 -0
- data/lib/latte/query.rb +32 -105
- data/lib/latte/response.rb +15 -177
- data/lib/latte/server.rb +10 -5
- data/lib/latte/version.rb +1 -1
- data/lib/latte.rb +1 -0
- metadata +19 -8
data/Gemfile.lock
CHANGED
@@ -3,6 +3,7 @@ PATH
|
|
3
3
|
specs:
|
4
4
|
latte (0.0.1)
|
5
5
|
bindata
|
6
|
+
net-dns
|
6
7
|
null_logger
|
7
8
|
pethau (>= 0.0.2)
|
8
9
|
|
@@ -10,8 +11,9 @@ GEM
|
|
10
11
|
remote: http://rubygems.org/
|
11
12
|
specs:
|
12
13
|
bindata (1.4.3)
|
14
|
+
net-dns (0.6.1)
|
13
15
|
null_logger (0.0.1)
|
14
|
-
pethau (0.0.
|
16
|
+
pethau (0.0.3)
|
15
17
|
|
16
18
|
PLATFORMS
|
17
19
|
ruby
|
data/latte.gemspec
CHANGED
data/lib/latte/query.rb
CHANGED
@@ -1,129 +1,56 @@
|
|
1
1
|
module Latte
|
2
2
|
class Query
|
3
|
-
|
4
|
-
|
5
|
-
# NS 2 an authoritative name server
|
6
|
-
# MD 3 a mail destination (Obsolete - use MX)
|
7
|
-
# MF 4 a mail forwarder (Obsolete - use MX)
|
8
|
-
# CNAME 5 the canonical name for an alias
|
9
|
-
# SOA 6 marks the start of a zone of authority
|
10
|
-
# MB 7 a mailbox domain name (EXPERIMENTAL)
|
11
|
-
# MG 8 a mail group member (EXPERIMENTAL)
|
12
|
-
# MR 9 a mail rename domain name (EXPERIMENTAL)
|
13
|
-
# NULL 10 a null RR (EXPERIMENTAL)
|
14
|
-
# WKS 11 a well known service description
|
15
|
-
# PTR 12 a domain name pointer
|
16
|
-
# HINFO 13 host information
|
17
|
-
# MINFO 14 mailbox or mail list information
|
18
|
-
# MX 15 mail exchange
|
19
|
-
# TXT 16 text strings
|
3
|
+
include Enumerable
|
4
|
+
initialize_with :raw_query, :logger
|
20
5
|
|
21
|
-
|
6
|
+
class Question
|
7
|
+
initialize_with :wrapped_question
|
22
8
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
# Question
|
27
|
-
# Answer
|
28
|
-
# Authority
|
29
|
-
# Additional
|
30
|
-
#
|
31
|
-
# DNS HEADER FORMAT
|
32
|
-
#
|
33
|
-
# OCTET 1,2 ID
|
34
|
-
# OCTET 3,4 QR(1 bit) + OPCODE(4 bit)+ AA(1 bit) + TC(1 bit) +
|
35
|
-
# RD(1 bit)+ RA(1 bit) + Z(3 bit) + RCODE(4 bit)
|
36
|
-
# OCTET 5,6 QDCOUNT
|
37
|
-
# OCTET 7,8 ANCOUNT
|
38
|
-
# OCTET 9,10 NSCOUNT
|
39
|
-
# OCTET 11,12 ARCOUNT
|
40
|
-
#
|
41
|
-
# QUESTION FORMAT
|
42
|
-
#
|
43
|
-
# OCTET 1,2,…n QNAME
|
44
|
-
# OCTET n+1,n+2 QTYPE
|
45
|
-
# OCTET n+3,n+4 QCLASS
|
46
|
-
#
|
47
|
-
# ANSWER, AUTHORITY, ADDITIONAL FORMAT
|
48
|
-
#
|
49
|
-
# OCTET 1,2,..n NAME
|
50
|
-
# OCTET n+1,n+2 TYPE
|
51
|
-
# OCTET n+3,n+4 CLASS
|
52
|
-
# OCTET n+5,n+6,n+7,n+8 TTL
|
53
|
-
# OCTET n+9,n+10 RDLENGTH
|
54
|
-
# OCTET n+11,n+12,….. RDATA
|
55
|
-
class QueryHeader < BinData::Record
|
56
|
-
endian :big
|
57
|
-
uint16 :id
|
58
|
-
bit1 :qr
|
59
|
-
bit4 :opcode
|
60
|
-
bit1 :aa
|
61
|
-
bit1 :tc
|
62
|
-
bit1 :rd
|
63
|
-
bit1 :ra
|
64
|
-
bit3 :z
|
65
|
-
bit4 :rcode
|
66
|
-
uint16 :qdcount
|
67
|
-
uint16 :ancount
|
68
|
-
uint16 :nscount
|
69
|
-
uint16 :arcount
|
70
|
-
end
|
9
|
+
def qname
|
10
|
+
wrapped_question.qName
|
11
|
+
end
|
71
12
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
uint16 :qclass
|
76
|
-
end
|
13
|
+
def qclass
|
14
|
+
wrapped_question.qClass
|
15
|
+
end
|
77
16
|
|
78
|
-
|
79
|
-
|
17
|
+
def qtype
|
18
|
+
wrapped_question.qType
|
19
|
+
end
|
80
20
|
end
|
81
|
-
private :parsed_header
|
82
21
|
|
83
|
-
def
|
84
|
-
|
22
|
+
def to_s
|
23
|
+
packet.to_s
|
85
24
|
end
|
86
|
-
private :build_header
|
87
25
|
|
88
|
-
def
|
89
|
-
|
26
|
+
def <=> other
|
27
|
+
to_s <=> other.to_s
|
90
28
|
end
|
91
|
-
private :parsed_record
|
92
29
|
|
93
|
-
def
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
else
|
98
|
-
raise "Unhandled QR value: #{parsed_header.qr.value}"
|
30
|
+
def each
|
31
|
+
questions.each do |q|
|
32
|
+
question = Question.new q
|
33
|
+
yield question
|
99
34
|
end
|
100
35
|
end
|
36
|
+
alias_method :each_question, :each
|
101
37
|
|
102
|
-
def
|
103
|
-
|
104
|
-
return result.value if result.respond_to? :value
|
105
|
-
result
|
106
|
-
end
|
107
|
-
|
108
|
-
def header
|
109
|
-
"ID=#{id} QR=#{qr} TC=#{tc} RD=#{rd} RA=#{ra} Z=#{z} " + \
|
110
|
-
"RCODE=#{rcode} QDCOUNT=#{qdcount} ANCOUNT=#{ancount} " + \
|
111
|
-
"NSCOUNT=#{nscount} ARCOUNT=#{arcount}"
|
38
|
+
def id
|
39
|
+
packet.header.id
|
112
40
|
end
|
113
41
|
|
114
|
-
def
|
115
|
-
|
116
|
-
value.gsub! /^[\x00-\x1f]/, ''
|
117
|
-
value.gsub! /[\x00-\x1f]/, '.'
|
118
|
-
value + '.'
|
42
|
+
def packet
|
43
|
+
@packet ||= build_packet
|
119
44
|
end
|
120
45
|
|
121
|
-
def
|
122
|
-
|
46
|
+
def build_packet
|
47
|
+
Net::DNS::Packet.parse raw_query
|
123
48
|
end
|
49
|
+
private :build_packet
|
124
50
|
|
125
|
-
def
|
126
|
-
|
51
|
+
def questions
|
52
|
+
packet.question
|
127
53
|
end
|
54
|
+
private :questions
|
128
55
|
end
|
129
56
|
end
|
data/lib/latte/response.rb
CHANGED
@@ -1,191 +1,29 @@
|
|
1
1
|
module Latte
|
2
2
|
class Response
|
3
|
-
initialize_with :query
|
3
|
+
initialize_with :query, :logger
|
4
4
|
|
5
|
-
|
6
|
-
|
5
|
+
def add_answer record
|
6
|
+
rr = parse_rr_string record
|
7
|
+
packet.answer << rr
|
7
8
|
end
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
bit1 :qr
|
12
|
-
bit4 :opcode
|
13
|
-
bit1 :aa
|
14
|
-
bit1 :tc
|
15
|
-
bit1 :rd
|
16
|
-
bit1 :ra
|
17
|
-
bit3 :z, :value => 0 # Reserved for future use
|
18
|
-
bit4 :rcode
|
19
|
-
uint16 :qdcount
|
20
|
-
uint16 :ancount
|
21
|
-
uint16 :nscount
|
22
|
-
uint16 :arcount
|
10
|
+
def data
|
11
|
+
packet.data
|
23
12
|
end
|
24
13
|
|
25
|
-
|
26
|
-
|
27
|
-
uint16 :qtype
|
28
|
-
uint16 :qclass
|
29
|
-
end
|
30
|
-
|
31
|
-
class Answer < BigEndianRecord
|
32
|
-
stringz :qname
|
33
|
-
uint16 :qtype
|
34
|
-
uint16 :qclass
|
35
|
-
uint32 :ttl
|
36
|
-
uint16 :rdlength, :value => lambda { rdata.length }
|
37
|
-
string :rdata
|
38
|
-
end
|
39
|
-
|
40
|
-
def header
|
41
|
-
ResponseHeader.new.tap { |h|
|
42
|
-
h.id = query.id
|
43
|
-
h.qr = 1 # I'm a response
|
44
|
-
h.opcode = 0 # I'm a standard query
|
45
|
-
h.aa = 0 # I'm not authoritative
|
46
|
-
h.tc = 0 # I wasn't truncated
|
47
|
-
h.rd = 0 # Please don't recursively query
|
48
|
-
h.ra = 0 # Recursion isn't welcome here
|
49
|
-
h.rcode = 0 # There are no errors here
|
50
|
-
h.qdcount = 1 # I'm answering one query
|
51
|
-
h.ancount = answers.size # The number of answer records I'm sending
|
52
|
-
h.nscount = 0 # There are 0 NS records in the authority part
|
53
|
-
h.arcount = 0 # How many additional records am I sending?
|
54
|
-
}
|
55
|
-
end
|
56
|
-
|
57
|
-
def question
|
58
|
-
Question.new.tap { |q|
|
59
|
-
q.qname = query.qname
|
60
|
-
q.qtype = query.qtype
|
61
|
-
q.qclass = query.qclass
|
62
|
-
}
|
63
|
-
end
|
64
|
-
|
65
|
-
def answers
|
66
|
-
@answers ||= [ ]
|
67
|
-
end
|
68
|
-
|
69
|
-
class RecordParser
|
70
|
-
initialize_with :record_string
|
71
|
-
private_attr_accessor :record
|
72
|
-
public :record
|
73
|
-
|
74
|
-
# QTYPE codes:
|
75
|
-
# A 1 a host address
|
76
|
-
# NS 2 an authoritative name server
|
77
|
-
# MD 3 a mail destination (Obsolete - use MX)
|
78
|
-
# MF 4 a mail forwarder (Obsolete - use MX)
|
79
|
-
# CNAME 5 the canonical name for an alias
|
80
|
-
# SOA 6 marks the start of a zone of authority
|
81
|
-
# MB 7 a mailbox domain name (EXPERIMENTAL)
|
82
|
-
# MG 8 a mail group member (EXPERIMENTAL)
|
83
|
-
# MR 9 a mail rename domain name (EXPERIMENTAL)
|
84
|
-
# NULL 10 a null RR (EXPERIMENTAL)
|
85
|
-
# WKS 11 a well known service description
|
86
|
-
# PTR 12 a domain name pointer
|
87
|
-
# HINFO 13 host information
|
88
|
-
# MINFO 14 mailbox or mail list information
|
89
|
-
# MX 15 mail exchange
|
90
|
-
# TXT 16 text strings
|
91
|
-
|
92
|
-
def qname
|
93
|
-
parts[0]
|
94
|
-
end
|
95
|
-
|
96
|
-
def qclass
|
97
|
-
parts[1]
|
98
|
-
end
|
99
|
-
|
100
|
-
def encoded_qclass
|
101
|
-
{
|
102
|
-
'IN' => 1,
|
103
|
-
'CH' => 3,
|
104
|
-
'HS' => 4
|
105
|
-
}[qclass]
|
106
|
-
end
|
107
|
-
|
108
|
-
def qtype
|
109
|
-
parts[2]
|
110
|
-
end
|
111
|
-
|
112
|
-
def encoded_qtype
|
113
|
-
{
|
114
|
-
'A' => 1,
|
115
|
-
'NS' => 2,
|
116
|
-
'CNAME' => 5,
|
117
|
-
'SOA' => 6,
|
118
|
-
'PTR' => 12,
|
119
|
-
'HINFO' => 13,
|
120
|
-
'MINFO' => 14,
|
121
|
-
'MX' => 15,
|
122
|
-
'TXT' => 16
|
123
|
-
}[qtype]
|
124
|
-
end
|
125
|
-
|
126
|
-
def ttl
|
127
|
-
parts[3].to_i
|
128
|
-
end
|
129
|
-
|
130
|
-
def rdata
|
131
|
-
parts[4]
|
132
|
-
end
|
133
|
-
|
134
|
-
def encode_name name
|
135
|
-
parts = name.split /\./
|
136
|
-
parts.map! { |p| BinData::Uint8.new(p.length).to_binary_s + p }
|
137
|
-
parts << BinData::Uint8.new(0).to_binary_s
|
138
|
-
parts.join ''
|
139
|
-
end
|
140
|
-
|
141
|
-
def encoded_qname
|
142
|
-
encode_name qname
|
143
|
-
end
|
144
|
-
|
145
|
-
def encoded_rdata
|
146
|
-
# FIXME: Extract this case statment into separate encoders
|
147
|
-
case qtype
|
148
|
-
when 'A', 'PTR'
|
149
|
-
parts = rdata.split /\./
|
150
|
-
parts.map! { |o| BinData::Uint8.new(o.to_i).to_binary_s }
|
151
|
-
parts.join ''
|
152
|
-
when 'NS', 'CNAME'
|
153
|
-
encode_name rdata
|
154
|
-
else
|
155
|
-
raise "I don't know how to encode QTYPE #{qtype.inspect}"
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
|
-
def parts
|
160
|
-
string = record_string.dup
|
161
|
-
string.strip!
|
162
|
-
parts = string.split /\s+/, 5
|
163
|
-
parts.map! { |p| p.strip }
|
164
|
-
parts
|
165
|
-
end
|
166
|
-
|
167
|
-
def execute
|
168
|
-
self.record = Answer.new.tap { |a|
|
169
|
-
a.qname = encoded_qname
|
170
|
-
a.qtype = encoded_qtype
|
171
|
-
a.qclass = encoded_qclass
|
172
|
-
a.ttl = ttl
|
173
|
-
a.rdata = encoded_rdata
|
174
|
-
}
|
175
|
-
end
|
14
|
+
def to_s
|
15
|
+
packet.to_s
|
176
16
|
end
|
177
17
|
|
178
|
-
def
|
179
|
-
|
180
|
-
parser.execute
|
181
|
-
record = parser.record
|
182
|
-
answers << record
|
18
|
+
def parse_rr_string string
|
19
|
+
Net::DNS::RR.new string
|
183
20
|
end
|
21
|
+
private :parse_rr_string
|
184
22
|
|
185
|
-
def
|
186
|
-
|
187
|
-
part.to_binary_s
|
188
|
-
}.join ''
|
23
|
+
def packet
|
24
|
+
query.packet
|
189
25
|
end
|
26
|
+
private :packet
|
190
27
|
end
|
191
28
|
end
|
29
|
+
|
data/lib/latte/server.rb
CHANGED
@@ -59,11 +59,16 @@ module Latte
|
|
59
59
|
begin
|
60
60
|
query = Query.new data
|
61
61
|
client_name = client.remote_address.ip_unpack.join ':'
|
62
|
-
logger.
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
62
|
+
logger.info "#{client_name} > #{address}: Received query:\n#{query}"
|
63
|
+
logger.debug "#{client_name} > #{address}: Packet: #{HexPresenter.new(data)}"
|
64
|
+
response = Response.new query, logger
|
65
|
+
query.each_question do |question|
|
66
|
+
resolver.call question, response
|
67
|
+
end
|
68
|
+
answer = response.data
|
69
|
+
logger.info "#{client_name} < #{address}: Sending repsonse:\n#{response}"
|
70
|
+
logger.debug "#{client_name} < #{address}: Packet: #{HexPresenter.new(answer)}"
|
71
|
+
client.reply answer
|
67
72
|
rescue => e
|
68
73
|
logger.error [ e.message, e.backtrace ].flatten.join("\n")
|
69
74
|
end
|
data/lib/latte/version.rb
CHANGED
data/lib/latte.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: latte
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2011-10-
|
12
|
+
date: 2011-10-30 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: pethau
|
16
|
-
requirement: &
|
16
|
+
requirement: &70193546165300 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,21 @@ dependencies:
|
|
21
21
|
version: 0.0.2
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70193546165300
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: net-dns
|
27
|
+
requirement: &70193546164880 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70193546164880
|
25
36
|
- !ruby/object:Gem::Dependency
|
26
37
|
name: null_logger
|
27
|
-
requirement: &
|
38
|
+
requirement: &70193546164420 !ruby/object:Gem::Requirement
|
28
39
|
none: false
|
29
40
|
requirements:
|
30
41
|
- - ! '>='
|
@@ -32,10 +43,10 @@ dependencies:
|
|
32
43
|
version: '0'
|
33
44
|
type: :runtime
|
34
45
|
prerelease: false
|
35
|
-
version_requirements: *
|
46
|
+
version_requirements: *70193546164420
|
36
47
|
- !ruby/object:Gem::Dependency
|
37
48
|
name: bindata
|
38
|
-
requirement: &
|
49
|
+
requirement: &70193546164000 !ruby/object:Gem::Requirement
|
39
50
|
none: false
|
40
51
|
requirements:
|
41
52
|
- - ! '>='
|
@@ -43,7 +54,7 @@ dependencies:
|
|
43
54
|
version: '0'
|
44
55
|
type: :runtime
|
45
56
|
prerelease: false
|
46
|
-
version_requirements: *
|
57
|
+
version_requirements: *70193546164000
|
47
58
|
description: Talks DNS and passes queries back to a query resolver build by you that
|
48
59
|
just talks Ruby
|
49
60
|
email:
|