smart_proxy_dns_dnsmasq 0.4
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.
- checksums.yaml +7 -0
- data/LICENSE +675 -0
- data/README.md +50 -0
- data/bundler.d/dns_dnsmasq.rb +1 -0
- data/config/dns_dnsmasq.yml +9 -0
- data/lib/smart_proxy_dns_dnsmasq.rb +3 -0
- data/lib/smart_proxy_dns_dnsmasq/backend/default.rb +168 -0
- data/lib/smart_proxy_dns_dnsmasq/backend/openwrt.rb +141 -0
- data/lib/smart_proxy_dns_dnsmasq/dns_dnsmasq_configuration.rb +37 -0
- data/lib/smart_proxy_dns_dnsmasq/dns_dnsmasq_main.rb +42 -0
- data/lib/smart_proxy_dns_dnsmasq/dns_dnsmasq_plugin.rb +25 -0
- data/lib/smart_proxy_dns_dnsmasq/dns_dnsmasq_version.rb +7 -0
- data/test/dns_dnsmasq_default_settings_test.rb +11 -0
- data/test/dns_dnsmasq_production_wiring_test.rb +39 -0
- data/test/dns_dnsmasq_record_default_test.rb +114 -0
- data/test/dns_dnsmasq_record_openwrt_test.rb +79 -0
- data/test/test_helper.rb +9 -0
- data/test/testdata.openwrt +43 -0
- metadata +109 -0
data/README.md
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# Dnsmasq Smart Proxy plugin
|
2
|
+
|
3
|
+
|
4
|
+
This plugin adds a new DNS provider for managing records in dnsmasq.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
See [How_to_Install_a_Smart-Proxy_Plugin](http://projects.theforeman.org/projects/foreman/wiki/How_to_Install_a_Smart-Proxy_Plugin)
|
9
|
+
for how to install Smart Proxy plugins
|
10
|
+
|
11
|
+
This plugin is compatible with Smart Proxy 1.15 or higher.
|
12
|
+
|
13
|
+
## Configuration
|
14
|
+
|
15
|
+
To enable this DNS provider, edit `/etc/foreman-proxy/settings.d/dns.yml` and set:
|
16
|
+
|
17
|
+
:use_provider: dns_dnsmasq
|
18
|
+
|
19
|
+
Configuration options for this plugin are in `/etc/foreman-proxy/settings.d/dns_dnsmasq.yml` and include:
|
20
|
+
|
21
|
+
* `backend` (*optional*): The backend to use, currently implemented ones are; `openwrt`, and `default`
|
22
|
+
* `config_path`: The path to the configuration file.
|
23
|
+
* `reload_cmd`: The command to use for reloading the dnsmasq configuration.
|
24
|
+
* `dns_ttl`: The TTL values for the DNS data. (*currently unused*)
|
25
|
+
|
26
|
+
For best results, `config_path` should point to a file in a dnsmasq `conf-dir` which only the smart-proxy accesses.
|
27
|
+
|
28
|
+
**NB**: The `openwrt` backend uses the UCI configuration files, which for the moment don't support IPv6 entries.
|
29
|
+
|
30
|
+
## Contributing
|
31
|
+
|
32
|
+
Fork and send a Pull Request. Thanks!
|
33
|
+
|
34
|
+
## Copyright
|
35
|
+
|
36
|
+
Copyright (c) 2017 Alexander Olofsson
|
37
|
+
|
38
|
+
This program is free software: you can redistribute it and/or modify
|
39
|
+
it under the terms of the GNU General Public License as published by
|
40
|
+
the Free Software Foundation, either version 3 of the License, or
|
41
|
+
(at your option) any later version.
|
42
|
+
|
43
|
+
This program is distributed in the hope that it will be useful,
|
44
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
45
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
46
|
+
GNU General Public License for more details.
|
47
|
+
|
48
|
+
You should have received a copy of the GNU General Public License
|
49
|
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
50
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
gem 'smart_proxy_dns_dnsmasq'
|
@@ -0,0 +1,168 @@
|
|
1
|
+
module Proxy::Dns::Dnsmasq
|
2
|
+
class Default < ::Proxy::Dns::Dnsmasq::Record
|
3
|
+
attr_reader :config_file, :reload_cmd, :dirty
|
4
|
+
|
5
|
+
def initialize(config, reload_cmd, dns_ttl)
|
6
|
+
@config_file = config
|
7
|
+
@reload_cmd = reload_cmd
|
8
|
+
@dirty = false
|
9
|
+
|
10
|
+
super(dns_ttl)
|
11
|
+
end
|
12
|
+
|
13
|
+
def update!
|
14
|
+
return unless @dirty
|
15
|
+
@dirty = false
|
16
|
+
|
17
|
+
File.write(@config_file, configuration.join("\n") + "\n")
|
18
|
+
system(@reload_cmd)
|
19
|
+
end
|
20
|
+
|
21
|
+
def add_entry(type, fqdn, ip)
|
22
|
+
case type
|
23
|
+
when 'A', 'AAAA'
|
24
|
+
e = AddressEntry.new
|
25
|
+
e.ip = ip
|
26
|
+
e.fqdn = [fqdn]
|
27
|
+
when 'PTR'
|
28
|
+
e = PTREntry.new
|
29
|
+
e.ip = IPAddr.new(ip).reverse
|
30
|
+
e.fqdn = fqdn
|
31
|
+
end
|
32
|
+
|
33
|
+
configuration << e
|
34
|
+
@dirty = true
|
35
|
+
end
|
36
|
+
|
37
|
+
def remove_entry(type, fqdn = nil, ip = nil)
|
38
|
+
return true unless case type
|
39
|
+
when 'A', 'AAAA'
|
40
|
+
e = configuration.find { |entry| entry.is_a?(AddressEntry) && entry.fqdn.include?(fqdn) }
|
41
|
+
when 'PTR'
|
42
|
+
e = configuration.find { |entry| entry.is_a?(PTREntry) && entry.fqdn == fqdn }
|
43
|
+
end
|
44
|
+
|
45
|
+
configuration.delete e
|
46
|
+
@dirty = true
|
47
|
+
end
|
48
|
+
|
49
|
+
def add_cname(name, canonical)
|
50
|
+
# dnsmasq will silently ignore broken CNAME records, even though they stay in config
|
51
|
+
# So avoid flooding the configuration if broken CNAME entries are added
|
52
|
+
return true if configuration.find { |entry| entry.is_a?(CNAMEEntry) && entry.name == name }
|
53
|
+
|
54
|
+
c = CNAMEEntry.new
|
55
|
+
c.name = name
|
56
|
+
c.target = canonical
|
57
|
+
configuration << c
|
58
|
+
@dirty = true
|
59
|
+
end
|
60
|
+
|
61
|
+
def remove_cname(name)
|
62
|
+
c = configuration.find { |entry| entry.is_a?(CNAMEEntry) && entry.name == name }
|
63
|
+
return true unless c
|
64
|
+
|
65
|
+
configuration.delete c
|
66
|
+
@dirty = true
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def load!
|
72
|
+
@configuration = []
|
73
|
+
File.open(@config_file).each_line do |line|
|
74
|
+
line = line.strip
|
75
|
+
next if line.empty? || line.start_with?('#') || !line.include?('=')
|
76
|
+
|
77
|
+
option, value = line.split('=')
|
78
|
+
|
79
|
+
case option
|
80
|
+
when 'address'
|
81
|
+
data = value.split('/')
|
82
|
+
data.shift
|
83
|
+
|
84
|
+
entry = AddressEntry.new
|
85
|
+
entry.ip = data.pop
|
86
|
+
entry.fqdn = data
|
87
|
+
when 'cname'
|
88
|
+
data = value.split(',')
|
89
|
+
|
90
|
+
entry = CNAMEEntry.new
|
91
|
+
entry.name = data.shift
|
92
|
+
entry.target = data.shift
|
93
|
+
entry.ttl = data.shift
|
94
|
+
when 'ptr-record'
|
95
|
+
data = value.split(',')
|
96
|
+
|
97
|
+
entry = PTREntry.new
|
98
|
+
entry.ip = data[0]
|
99
|
+
entry.fqdn = data[1]
|
100
|
+
# TODO: Handle these properly
|
101
|
+
# when 'host-record'
|
102
|
+
# data = value.split(',')
|
103
|
+
|
104
|
+
# entry = HostEntry.new
|
105
|
+
# until data.empty?
|
106
|
+
# v = data.pop
|
107
|
+
# if !entry.ttl && /\A\d+\z/ === v
|
108
|
+
# entry.ttl = v
|
109
|
+
# end
|
110
|
+
|
111
|
+
# begin
|
112
|
+
# ip = IPAddr.new(v)
|
113
|
+
# entry.ip << v
|
114
|
+
# rescue IPAddr::InvalidAddressError
|
115
|
+
# entry.fqdn << v
|
116
|
+
# end
|
117
|
+
# end
|
118
|
+
end
|
119
|
+
|
120
|
+
@configuration << entry if entry
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def configuration
|
125
|
+
load! unless @configuration
|
126
|
+
@configuration
|
127
|
+
end
|
128
|
+
|
129
|
+
class AddressEntry
|
130
|
+
attr_accessor :fqdn, :ip
|
131
|
+
def initialize
|
132
|
+
@fqdn = []
|
133
|
+
end
|
134
|
+
|
135
|
+
def to_s
|
136
|
+
"address=/#{fqdn.join '/'}/#{ip}"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
class CNAMEEntry
|
141
|
+
attr_accessor :name, :target, :ttl
|
142
|
+
|
143
|
+
def to_s
|
144
|
+
"cname=#{name},#{target}#{ttl && ',' + ttl}"
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
class PTREntry
|
149
|
+
attr_accessor :fqdn, :ip
|
150
|
+
|
151
|
+
def to_s
|
152
|
+
"ptr-record=#{ip},#{fqdn}"
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
class HostEntry
|
157
|
+
attr_accessor :ttl, :ip, :fqdn
|
158
|
+
def initialize
|
159
|
+
@fqdn = []
|
160
|
+
@ip = []
|
161
|
+
end
|
162
|
+
|
163
|
+
def to_s
|
164
|
+
"host-record=#{fqdn.join ','},#{ip.join ','}#{ttl && ',' + ttl}"
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
require 'ipaddr'
|
2
|
+
|
3
|
+
module Proxy::Dns::Dnsmasq
|
4
|
+
class Openwrt < ::Proxy::Dns::Dnsmasq::Record
|
5
|
+
attr_reader :config_file, :reload_cmd, :dirty
|
6
|
+
|
7
|
+
def initialize(config, reload_cmd, dns_ttl)
|
8
|
+
@config_file = config
|
9
|
+
@reload_cmd = reload_cmd
|
10
|
+
@dirty = false
|
11
|
+
|
12
|
+
super(dns_ttl)
|
13
|
+
end
|
14
|
+
|
15
|
+
def update!
|
16
|
+
return unless @dirty
|
17
|
+
@dirty = false
|
18
|
+
|
19
|
+
File.write(@config_file, "\n" + configuration.join("\n") + "\n")
|
20
|
+
system(@reload_cmd)
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_entry(type, fqdn, ip)
|
24
|
+
raise Proxy::Dns::Error, "OpenWRT UCI can't manage IPv6 entries" if type == 'AAAA' || type == 'PTR' && IPAddr.new(ip).ipv6?
|
25
|
+
found = find_type(:domain, :name, fqdn)
|
26
|
+
return true if found && found.options[:ip] == ip
|
27
|
+
|
28
|
+
h = found
|
29
|
+
h = DSL::Config.new :domain unless h
|
30
|
+
h.options[:name] = fqdn
|
31
|
+
h.options[:ip] = ip
|
32
|
+
|
33
|
+
configuration << h unless found
|
34
|
+
@dirty = true
|
35
|
+
end
|
36
|
+
|
37
|
+
def remove_entry(type, fqdn = nil, ip = nil)
|
38
|
+
raise Proxy::Dns::Error, "OpenWRT UCI can't manage IPv6 entries" if type == 'AAAA' || type == 'PTR' && IPAddr.new(ip).ipv6?
|
39
|
+
return true unless h = find_type(:domain, fqdn && :name || :ip, fqdn || ip)
|
40
|
+
|
41
|
+
configuration.delete h
|
42
|
+
@dirty = true
|
43
|
+
end
|
44
|
+
|
45
|
+
def add_cname(name, canonical)
|
46
|
+
found = find_type(:cname, :name, name)
|
47
|
+
return true if found && found.options[:target] == canonical
|
48
|
+
|
49
|
+
c = found
|
50
|
+
c = DSL::Config.new :cname unless c
|
51
|
+
c.options[:cname] = name
|
52
|
+
c.options[:target] = canonical
|
53
|
+
|
54
|
+
configuration << c unless found
|
55
|
+
@dirty = true
|
56
|
+
end
|
57
|
+
|
58
|
+
def remove_cname(name)
|
59
|
+
return true unless c = find_type(:cname, :name, name)
|
60
|
+
|
61
|
+
configuration.delete c
|
62
|
+
@dirty = true
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def find_type(filter_type, search_type, value)
|
68
|
+
configuration.find do |config|
|
69
|
+
next unless config.type == filter_type
|
70
|
+
|
71
|
+
config.options.find do |name, opt|
|
72
|
+
next unless name == search_type
|
73
|
+
|
74
|
+
opt == value
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def load!
|
80
|
+
@configuration = []
|
81
|
+
dsl = DSL.new(@configuration)
|
82
|
+
dsl.instance_eval open(@config_file).read, @config_file
|
83
|
+
end
|
84
|
+
|
85
|
+
def configuration
|
86
|
+
load! unless @configuration
|
87
|
+
@configuration
|
88
|
+
end
|
89
|
+
|
90
|
+
class DSL
|
91
|
+
class Config
|
92
|
+
attr_reader :type, :name, :options
|
93
|
+
|
94
|
+
def initialize(type, name = nil)
|
95
|
+
@type = type.to_sym
|
96
|
+
@name = name
|
97
|
+
@options = {}
|
98
|
+
end
|
99
|
+
|
100
|
+
def to_s
|
101
|
+
"config #{type} #{name}\n" + options.map do |name, value|
|
102
|
+
if value.is_a? Array
|
103
|
+
value.map do|val|
|
104
|
+
" list #{name} '#{val}'"
|
105
|
+
end.join "\n"
|
106
|
+
else
|
107
|
+
" option #{name} '#{value}'"
|
108
|
+
end
|
109
|
+
end.join("\n") + "\n"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def initialize(config)
|
114
|
+
@configs = config
|
115
|
+
end
|
116
|
+
|
117
|
+
def method_missing(m, *args)
|
118
|
+
[m, args].flatten
|
119
|
+
end
|
120
|
+
|
121
|
+
def config(args)
|
122
|
+
type, name = args
|
123
|
+
@current_config = Config.new type, name
|
124
|
+
@configs << @current_config
|
125
|
+
end
|
126
|
+
|
127
|
+
def option(args)
|
128
|
+
name, value = args
|
129
|
+
|
130
|
+
@current_config.options[name] = value
|
131
|
+
end
|
132
|
+
|
133
|
+
def list(args)
|
134
|
+
name, value = args
|
135
|
+
|
136
|
+
@current_config.options[name] = [] unless @current_config.options[name]
|
137
|
+
@current_config.options[name] << value
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module ::Proxy::Dns::Dnsmasq
|
2
|
+
class PluginConfiguration
|
3
|
+
def load_classes
|
4
|
+
require 'dns_common/dns_common'
|
5
|
+
require 'smart_proxy_dns_dnsmasq/dns_dnsmasq_main'
|
6
|
+
end
|
7
|
+
|
8
|
+
BACKENDS = [ 'openwrt', 'default' ].freeze
|
9
|
+
def load_dependency_injection_wirings(container_instance, settings)
|
10
|
+
backend = settings[:backend] || 'default'
|
11
|
+
|
12
|
+
unless BACKENDS.include? backend
|
13
|
+
raise ::Proxy::Error::ConfigurationError, 'In'
|
14
|
+
end
|
15
|
+
|
16
|
+
begin
|
17
|
+
require "smart_proxy_dns_dnsmasq/backend/#{backend}"
|
18
|
+
rescue LoadError, e
|
19
|
+
raise ::Proxy::Error::ConfigurationError, "Failed to load backend #{backend}: #{e}"
|
20
|
+
end
|
21
|
+
|
22
|
+
klass = case backend
|
23
|
+
when 'openwrt'
|
24
|
+
::Proxy::Dns::Dnsmasq::Openwrt
|
25
|
+
when 'default'
|
26
|
+
::Proxy::Dns::Dnsmasq::Default
|
27
|
+
end
|
28
|
+
|
29
|
+
container_instance.dependency :dns_provider, (lambda do
|
30
|
+
klass.new(
|
31
|
+
settings[:config_path],
|
32
|
+
settings[:reload_cmd],
|
33
|
+
settings[:dns_ttl])
|
34
|
+
end)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'dns_common/dns_common'
|
2
|
+
require 'ipaddr'
|
3
|
+
|
4
|
+
module Proxy::Dns::Dnsmasq
|
5
|
+
class Record < ::Proxy::Dns::Record
|
6
|
+
include Proxy::Log
|
7
|
+
|
8
|
+
def initialize(dns_ttl)
|
9
|
+
super('localhost', dns_ttl)
|
10
|
+
end
|
11
|
+
|
12
|
+
def do_create(name, value, type)
|
13
|
+
case type
|
14
|
+
when 'A', 'AAAA'
|
15
|
+
add_entry(type, name, value)
|
16
|
+
when 'PTR'
|
17
|
+
add_entry(type, value, ptr_to_ip(name))
|
18
|
+
when 'CNAME'
|
19
|
+
add_cname(name, value)
|
20
|
+
else
|
21
|
+
raise Proxy::Dns::Error, "Can't create entries of type #{type}"
|
22
|
+
end
|
23
|
+
|
24
|
+
update!
|
25
|
+
end
|
26
|
+
|
27
|
+
def do_remove(name, type)
|
28
|
+
case type
|
29
|
+
when 'A', 'AAAA'
|
30
|
+
remove_entry(type, name)
|
31
|
+
when 'PTR'
|
32
|
+
remove_entry(type, nil, ptr_to_ip(name))
|
33
|
+
when 'CNAME'
|
34
|
+
remove_cname(name)
|
35
|
+
else
|
36
|
+
raise Proxy::Dns::Error, "Can't remove entries of type #{type}"
|
37
|
+
end
|
38
|
+
|
39
|
+
update!
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|