wamupd 1.1.1

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.
@@ -0,0 +1,230 @@
1
+ # Copyright (C) 2009-2010 James Brown <roguelazer@roguelazer.com>.
2
+ #
3
+ # This file is part of wamupd.
4
+ #
5
+ # wamupd is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # wamupd is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with wamupd. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ require "wamupd/action"
19
+ require "wamupd/avahi_service"
20
+ require "wamupd/dns_update"
21
+ require "wamupd/lease_update"
22
+ require "wamupd/main_settings"
23
+ require "wamupd/signals"
24
+
25
+ require "algorithms"
26
+ require "dnsruby"
27
+ require "thread"
28
+
29
+ # Wamupd is a module that is used to namespace all of the wamupd code.
30
+ module Wamupd
31
+ # Duplicate services were registered with the controller.
32
+ # Should almost certainly be treated as non-fatal
33
+ class DuplicateServiceError < StandardError
34
+ end
35
+
36
+ # Coordinate between a set of Avahi Services and DNS records
37
+ #
38
+ # == Signals
39
+ #
40
+ # [:added]
41
+ # Raised when a new service is added to the controller and
42
+ # successfully registered. has two parameters: the AvahiService
43
+ # added and the queue ID of the DNS request (or <tt>true</tt> if the
44
+ # request was synchronous)
45
+ #
46
+ # [:deleted]
47
+ # Raised when a service is deleted from the controller. Contains
48
+ # the deleted service as its parameter
49
+ #
50
+ # [:renewed]
51
+ # Raised when a service's lease is renewed. Contains the renewed
52
+ # service as its parameter
53
+ #
54
+ # [:quit]
55
+ # Raised when the controller is quitting
56
+ class DNSAvahiController
57
+ include Wamupd::Signals
58
+
59
+ # A queue to put actions into.
60
+ attr_reader :queue
61
+
62
+ # Initialize the controller.
63
+ def initialize()
64
+ @sa = MainSettings.instance
65
+ # Services stored as a hash from
66
+ @services = {}
67
+ @resolver = @sa.resolver
68
+ @added = []
69
+ @queue = Queue.new
70
+ # Make a min priority queue for leases
71
+ @lease_queue = Containers::PriorityQueue.new { |x,y| (x<=>y) == -1 }
72
+ end
73
+
74
+ # Add an array of services to the controller
75
+ def add_services(services)
76
+ services.each { |s| add_service(s) }
77
+ end
78
+
79
+ # Add a single service record to the controller
80
+ def add_service(service)
81
+ if service.kind_of?(AvahiService)
82
+ if (not @services.has_key?(service.identifier))
83
+ @services[service.identifier] = service
84
+ else
85
+ raise DuplicateServiceError.new("Got a duplicate")
86
+ end
87
+ elsif (service.kind_of?(AvahiServiceFile))
88
+ service.each { |service_entry|
89
+ add_service(service_entry)
90
+ }
91
+ else
92
+ raise ArgumentError.new("Not an AvahiService")
93
+ end
94
+ end
95
+
96
+ # Delete a signle service record from the service
97
+ def delete_service(service)
98
+ if service.kind_of?(AvahiService)
99
+ @services.delete(service.identifier)
100
+ else
101
+ raise ArgumentError.new("Not an AvahiService")
102
+ end
103
+ end
104
+
105
+ # Return the number of elements in the controller
106
+ def size
107
+ return @services.size
108
+ end
109
+
110
+ # Keys
111
+ def keys
112
+ return @services.keys
113
+ end
114
+
115
+ # Publish all currently stored records
116
+ def publish_all
117
+ ids = []
118
+ @services.each { |key,service|
119
+ ids << publish(service)
120
+ }
121
+ return ids
122
+ end
123
+
124
+ # Unpublish all stored records
125
+ def unpublish_all
126
+ todo = []
127
+ @services.each { |key,service|
128
+ todo << { :target=>service.type_in_zone_with_name,
129
+ :type=>Dnsruby::Types.SRV,
130
+ :value=> "#{@sa.priority} #{@sa.weight} #{service.port} #{service.target}"}
131
+ todo << { :target=>service.type_in_zone,
132
+ :type=>Dnsruby::Types.PTR,
133
+ :value=>service.type_in_zone_with_name}
134
+ todo << { :target=>service.type_in_zone_with_name,
135
+ :type=>Dnsruby::Types.TXT}
136
+ }
137
+ DNSUpdate.unpublish_all(*todo)
138
+ end
139
+
140
+ # Publish a single service
141
+ #
142
+ # Returns: the DNS request ID
143
+ def publish(service, ttl=@sa.ttl, lease_time=@sa.lease_time)
144
+ to_update = []
145
+ to_update << {:target=>service.type_in_zone,
146
+ :type=>Dnsruby::Types.PTR, :ttl=>ttl,
147
+ :value=>service.type_in_zone_with_name}
148
+ to_update << {:target=>service.type_in_zone_with_name,
149
+ :type=>Dnsruby::Types.SRV, :ttl=>ttl,
150
+ :value=> "#{@sa.priority} #{@sa.weight} #{service.port} #{service.target}"}
151
+ # why doesn't Ruby have !==
152
+ unless (service.txt === false)
153
+ to_update << {:target => service.type_in_zone_with_name,
154
+ :type=>Dnsruby::Types.TXT, :ttl=>ttl,
155
+ :value=>service.txt}
156
+ end
157
+ update_time = Time.now() + lease_time
158
+ @lease_queue.push(Wamupd::LeaseUpdate.new(update_time, service), update_time)
159
+ return DNSUpdate.publish_all(to_update)
160
+ end
161
+
162
+ # Unpublish a single service
163
+ def unpublish(service, ttl=@sa.ttl)
164
+ todo = []
165
+ to_update << {:target=>service.type_in_zone,
166
+ :type=>Dnsruby::Types.PTR,
167
+ :value=>service.type_in_zone_with_name}
168
+ to_update << {:target=>service.type_in_zone_with_name,
169
+ :type=>DnsRuby::Types.SRV}
170
+ to_update << {:target=>service.type_in_zone_with_name,
171
+ :type=>Dnsruby::Types.TXT}
172
+ DNSUpdate.unpublish_all(todo)
173
+ end
174
+
175
+ # Process a single Wamupd::Action out of the queue
176
+ def process_action(action)
177
+ case action.action
178
+ when Wamupd::ActionType::ADD
179
+ begin
180
+ add_service(action.record)
181
+ id = publish(action.record)
182
+ signal(:added, action.record, id)
183
+ rescue DuplicateServiceError
184
+ # Do nothing
185
+ end
186
+ when Wamupd::ActionType::DELETE
187
+ delete_service(action.record)
188
+ unpublish_service(action.record)
189
+ signal(:deleted, action.record)
190
+ when Wamupd::ActionType::QUIT
191
+ # Flush the queue, then signal quit
192
+ until @queue.empty?
193
+ process_action(@queue.pop(false))
194
+ end
195
+ unpublish_all
196
+ signal(:quit)
197
+ end
198
+ end
199
+
200
+ # Exit out of the main loop
201
+ def exit
202
+ @queue << Wamupd::Action.new(Wamupd::ActionType::QUIT)
203
+ end
204
+
205
+ # Wait for data to go into the queue, and handle it when it does
206
+ def run
207
+ while true
208
+ process_action(@queue.pop)
209
+ end
210
+ end
211
+
212
+ # Takes care of updating leases. Run it in a separate thread from
213
+ # the main "run" function
214
+ def update_leases
215
+ while true
216
+ now = Time.now
217
+ while (not @lease_queue.empty?) and (@lease_queue.next.date < now)
218
+ item = @lease_queue.pop
219
+ if @services.has_key?(item.service.identifier)
220
+ signal(:renewed, item.service)
221
+ publish(item.service)
222
+ end
223
+ end
224
+ sleep(@sa.sleep_time)
225
+ end
226
+ end
227
+
228
+ private :process_action, :publish
229
+ end
230
+ end
@@ -0,0 +1,95 @@
1
+ # Copyright (C) 2009-2010 James Brown <roguelazer@roguelazer.com>.
2
+ #
3
+ # This file is part of wamupd.
4
+ #
5
+ # wamupd is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # wamupd is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with wamupd. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ require "wamupd/lease_update"
19
+ require "wamupd/main_settings"
20
+
21
+ require "algorithms"
22
+ require "dnsruby"
23
+ require "ipaddr"
24
+ require "socket"
25
+
26
+ # Wamupd is a module that is used to namespace all of the wamupd code.
27
+ module Wamupd
28
+ # Manage IP information in DNS
29
+ #
30
+ # == Signals
31
+ # [:added]
32
+ # A record was published. Parameters are the type (A/AAAA) and the
33
+ # address
34
+ # [:removed]
35
+ # A record was unpublished. Parameters are the type (A/AAAA) and the
36
+ # address
37
+ class DNSIpController
38
+ include Wamupd::Signals
39
+
40
+ # Constructor
41
+ def initialize()
42
+ @sa = MainSettings.instance
43
+ @sa.get_ip_addresses
44
+ @resolver = @sa.resolver
45
+ @lease_queue = Containers::PriorityQueue.new
46
+ end
47
+
48
+ # Publish A and AAAA records
49
+ def publish
50
+ if (@sa.ipv4)
51
+ DNSUpdate.publish(@sa.target, Dnsruby::Types.A, @sa.ttl, @sa.ipv4)
52
+ signal(:added, Dnsruby::Types.A, @sa.ipv4)
53
+ end
54
+ if (@sa.ipv6)
55
+ DNSUpdate.publish(@sa.target, Dnsruby::Types.AAAA, @sa.ttl, @sa.ipv6)
56
+ signal(:added, Dnsruby::Types.AAAA, @sa.ipv6)
57
+ end
58
+ update_time = Time.now() + @sa.lease_time
59
+ @lease_queue.push(Wamupd::LeaseUpdate.new(update_time, nil), update_time)
60
+ end
61
+
62
+ # Synonym for publish
63
+ def publish_all
64
+ publish
65
+ end
66
+
67
+ # Unpublish A and AAAA records
68
+ def unpublish
69
+ if (@sa.ipv4)
70
+ DNSUpdate.unpublish(@sa.target, Dnsruby::Types.A, @sa.ipv4)
71
+ signal(:removed, Dnsruby::Types.A, @sa.ipv4)
72
+ end
73
+ if (@sa.ipv6)
74
+ DNSUpdate.unpublish(@sa.target, Dnsruby::Types.AAAA, @sa.ipv6)
75
+ signal(:removed, Dnsruby::Types.A, @sa.ipv6)
76
+ end
77
+ end
78
+
79
+ # Synonym for unpublish
80
+ def unpublish_all
81
+ unpublish
82
+ end
83
+
84
+ # Update leases when required. Please run in a separate thread.
85
+ def update_leases
86
+ while true
87
+ now = Time.now
88
+ while (not @lease_queue.empty?) and (@lease_queue.next.date < now)
89
+ publish
90
+ end
91
+ sleep(@sa.sleep_time)
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,154 @@
1
+ #!/usr/bin/ruby
2
+ # Copyright (C) 2009-2010 James Brown <roguelazer@roguelazer.com>.
3
+ #
4
+ # This file is part of wamupd.
5
+ #
6
+ # wamupd is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # wamupd is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with wamupd. If not, see <http://www.gnu.org/licenses/>.
18
+
19
+ require "dnsruby"
20
+
21
+ # Wamupd is a module that is used to namespace all of the wamupd code.
22
+ module Wamupd
23
+ # Class to help with constructing DNS UPDATEs. Probably not useful except to
24
+ # me.
25
+ class DNSUpdate
26
+ @@queue = nil
27
+ @@outstanding = []
28
+
29
+ # Set the queue
30
+ def self.queue=(v)
31
+ @@queue=v
32
+ end
33
+
34
+ # How many requests are outstanding?
35
+ def self.outstanding
36
+ return @@outstanding
37
+ end
38
+
39
+ # Is this update going to be asynchronous?
40
+ def self.async?
41
+ return (not @@queue.nil?)
42
+ end
43
+
44
+ # Publish a batch of DNS records
45
+ #
46
+ # Arguments:
47
+ # An array of records to publish. Each record is either a Hash
48
+ # containing the keys :target, :type, :ttl, and :value, or an array of
49
+ # items which could be args to Dnsruby::Update::add
50
+ def self.publish_all(args)
51
+ sa = MainSettings.instance()
52
+ resolver = sa.resolver
53
+ update = Dnsruby::Update.new(sa.zone, "IN")
54
+ shortest_ttl=86400
55
+ args.each { |arg|
56
+ if (arg.kind_of?(Hash))
57
+ update.add(arg[:target], arg[:type], arg[:ttl], arg[:value])
58
+ if (arg[:ttl] < shortest_ttl)
59
+ shortest_ttl = arg[:ttl]
60
+ end
61
+ elsif (arg.kind_of?(Array))
62
+ if (arg[2] < shortest_ttl)
63
+ shortest_ttl = arg[2]
64
+ end
65
+ update.add(*arg)
66
+ else
67
+ raise ArgumentError.new("Could not parse arguments")
68
+ end
69
+ }
70
+ opt = Dnsruby::RR::OPT.new
71
+ lease_time = Dnsruby::RR::OPT::Option.new(2, [shortest_ttl].pack("N"))
72
+ opt.klass="IN"
73
+ opt.options=[lease_time]
74
+ opt.ttl = 0
75
+ update.add_additional(opt)
76
+ update.header.rd = false
77
+ queue_id = nil
78
+ begin
79
+ if (async?)
80
+ queue_id = resolver.send_async(update, @@queue)
81
+ @@outstanding << queue_id
82
+ else
83
+ resolver.send_message(update)
84
+ queue_id = true
85
+ end
86
+ rescue Dnsruby::TsigNotSignedResponseError => e
87
+ # Not really an error for UPDATE; we don't care if the reply is
88
+ # signed!
89
+ nil
90
+ rescue Exception => e
91
+ $stderr.puts "Registration failed: #{e.to_s}"
92
+ end
93
+ return queue_id
94
+ end
95
+
96
+ # Publish a single DNS record
97
+ #
98
+ # Arguments:
99
+ # Same as Dnsruby::Update::add
100
+ def self.publish(*args)
101
+ self.publish_all([args])
102
+ end
103
+
104
+ # Unpublish a batch of DNS records
105
+ #
106
+ # Arguments:
107
+ # An array of records to unpublish. Each record is either a Hash
108
+ # containing keys :target, :type, and :value, or an array of items which
109
+ # could be args to Dnsruby::Update::delete
110
+ def self.unpublish_all(*args)
111
+ sa = MainSettings.instance()
112
+ resolver = sa.resolver
113
+ update = Dnsruby::Update.new(sa.zone, "IN")
114
+ args.each { |arg|
115
+ if (arg.kind_of?(Hash))
116
+ if (arg.has_key?(:value))
117
+ update.delete(arg[:target], arg[:type], arg[:value])
118
+ else
119
+ update.delete(arg[:target], arg[:type])
120
+ end
121
+ elsif (arg.kind_of?(Array))
122
+ update.delete(*arg)
123
+ else
124
+ raise ArgumentError.new("Could not parse arguments")
125
+ end
126
+ }
127
+ begin
128
+ if (async?)
129
+ queue_id = resolver.send_async(update, @@queue)
130
+ @@outstanding << queue_id
131
+ else
132
+ resolver.send_message(update)
133
+ end
134
+ rescue Dnsruby::NXRRSet => e
135
+ $stderr.puts "Could not remove record because it doesn't exist!"
136
+ rescue Dnsruby::TsigNotSignedResponseError => e
137
+ # Not really an error for UPDATE; we don't care if the reply is
138
+ # signed!
139
+ nil
140
+ rescue Exception => e
141
+ $stderr.puts "Unregistration failed: #{e.to_s}"
142
+ end
143
+
144
+ end
145
+
146
+ # Unpublish a single DNS record
147
+ #
148
+ # Arguments:
149
+ # Same as Dnsruby::Update::delete
150
+ def self.unpublish(*args)
151
+ self.unpublish_all(args)
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,29 @@
1
+ # Copyright (C) 2010 James Brown <roguelazer@roguelazer.com>.
2
+ #
3
+ # This file is part of wamupd.
4
+ #
5
+ # wamupd is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # wamupd is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with wamupd. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ module Wamupd
19
+ # Struct for lease update records
20
+ class LeaseUpdate
21
+ attr_accessor :date
22
+ attr_accessor :service
23
+
24
+ def initialize(date, service)
25
+ @date = date
26
+ @service = service
27
+ end
28
+ end
29
+ end