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 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.2)
16
+ pethau (0.0.3)
15
17
 
16
18
  PLATFORMS
17
19
  ruby
data/latte.gemspec CHANGED
@@ -19,6 +19,7 @@ Gem::Specification.new do |s|
19
19
  s.require_paths = ["lib"]
20
20
 
21
21
  s.add_runtime_dependency "pethau", ">= 0.0.2"
22
+ s.add_runtime_dependency "net-dns"
22
23
  s.add_runtime_dependency "null_logger"
23
24
  s.add_runtime_dependency "bindata"
24
25
  end
data/lib/latte/query.rb CHANGED
@@ -1,129 +1,56 @@
1
1
  module Latte
2
2
  class Query
3
- # QTYPE codes:
4
- # A 1 a host address
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
- initialize_with :raw_query
6
+ class Question
7
+ initialize_with :wrapped_question
22
8
 
23
- # DNS MESSAGE FORMAT
24
- #
25
- # Header
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
- class QueryRequest < QueryHeader
73
- stringz :qname
74
- uint16 :qtype
75
- uint16 :qclass
76
- end
13
+ def qclass
14
+ wrapped_question.qClass
15
+ end
77
16
 
78
- def parsed_header
79
- @parsed_header ||= build_header
17
+ def qtype
18
+ wrapped_question.qType
19
+ end
80
20
  end
81
- private :parsed_header
82
21
 
83
- def build_header
84
- QueryHeader.read raw_query
22
+ def to_s
23
+ packet.to_s
85
24
  end
86
- private :build_header
87
25
 
88
- def parsed_record
89
- @parsed_record ||= build_record
26
+ def <=> other
27
+ to_s <=> other.to_s
90
28
  end
91
- private :parsed_record
92
29
 
93
- def build_record
94
- case parsed_header.qr.value
95
- when 0
96
- QueryRequest.read raw_query
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 method_missing *args
103
- result = parsed_record.send *args
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 human_qname
115
- value = parsed_record.qname.value.dup
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 query
122
- "QNAME=#{human_qname} QTYPE=#{qtype} QCLASS=#{qclass}"
46
+ def build_packet
47
+ Net::DNS::Packet.parse raw_query
123
48
  end
49
+ private :build_packet
124
50
 
125
- def to_s
126
- [ header, query ].join " "
51
+ def questions
52
+ packet.question
127
53
  end
54
+ private :questions
128
55
  end
129
56
  end
@@ -1,191 +1,29 @@
1
1
  module Latte
2
2
  class Response
3
- initialize_with :query
3
+ initialize_with :query, :logger
4
4
 
5
- class BigEndianRecord < BinData::Record
6
- endian :big
5
+ def add_answer record
6
+ rr = parse_rr_string record
7
+ packet.answer << rr
7
8
  end
8
9
 
9
- class ResponseHeader < BigEndianRecord
10
- uint16 :id
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
- class Question < BigEndianRecord
26
- stringz :qname
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 add record
179
- parser = RecordParser.new record
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 to_s
186
- [ header, question, *answers ].map { |part|
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.debug "#{client_name} > #{address}: #{HexPresenter.new(data)}"
63
- response = Response.new query
64
- resolver.call query, response
65
- logger.debug "#{client_name} < #{address}: #{HexPresenter.new(response)}"
66
- client.reply response.to_s
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
@@ -1,3 +1,3 @@
1
1
  module Latte
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
data/lib/latte.rb CHANGED
@@ -2,6 +2,7 @@ require 'null_logger'
2
2
  require 'socket'
3
3
  require 'bindata'
4
4
  require 'pethau'
5
+ require 'net/dns/packet'
5
6
 
6
7
  class Object
7
8
  include Pethau::InitializeWith
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.1
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-26 00:00:00.000000000Z
12
+ date: 2011-10-30 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: pethau
16
- requirement: &70282543179020 !ruby/object:Gem::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: *70282543179020
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: &70282539604060 !ruby/object:Gem::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: *70282539604060
46
+ version_requirements: *70193546164420
36
47
  - !ruby/object:Gem::Dependency
37
48
  name: bindata
38
- requirement: &70282539603600 !ruby/object:Gem::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: *70282539603600
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: