latte 0.0.1 → 0.0.2
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/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:
|