]>
Commit | Line | Data |
---|---|---|
33074dfb SH |
1 | package PVE::Network::SDN::Dhcp::Dnsmasq; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
5 | ||
6 | use base qw(PVE::Network::SDN::Dhcp::Plugin); | |
7 | ||
8 | use Net::IP qw(:PROC); | |
9 | use PVE::Tools qw(file_set_contents run_command lock_file); | |
10 | ||
11 | use File::Copy; | |
e6211bae | 12 | use Net::DBus; |
33074dfb SH |
13 | |
14 | my $DNSMASQ_CONFIG_ROOT = '/etc/dnsmasq.d'; | |
15 | my $DNSMASQ_DEFAULT_ROOT = '/etc/default'; | |
16 | my $DNSMASQ_LEASE_ROOT = '/var/lib/misc'; | |
17 | ||
18 | sub type { | |
19 | return 'dnsmasq'; | |
20 | } | |
21 | ||
33074dfb | 22 | sub add_ip_mapping { |
beef73d6 | 23 | my ($class, $dhcpid, $macdb, $mac, $ip4, $ip6) = @_; |
33074dfb SH |
24 | |
25 | my $ethers_file = "$DNSMASQ_CONFIG_ROOT/$dhcpid/ethers"; | |
26 | my $ethers_tmp_file = "$ethers_file.tmp"; | |
27 | ||
a26a43bf | 28 | my $reload = undef; |
beef73d6 | 29 | |
33074dfb SH |
30 | my $appendFn = sub { |
31 | open(my $in, '<', $ethers_file) or die "Could not open file '$ethers_file' $!\n"; | |
32 | open(my $out, '>', $ethers_tmp_file) or die "Could not open file '$ethers_tmp_file' $!\n"; | |
33 | ||
a26a43bf AD |
34 | my $match = undef; |
35 | ||
36 | while (my $line = <$in>) { | |
beef73d6 | 37 | chomp($line); |
a26a43bf AD |
38 | my $parsed_ip4 = undef; |
39 | my $parsed_ip6 = undef; | |
40 | my ($parsed_mac, $parsed_ip1, $parsed_ip2) = split(/,/, $line); | |
41 | ||
42 | if ($parsed_ip2) { | |
43 | $parsed_ip4 = $parsed_ip1; | |
44 | $parsed_ip6 = $parsed_ip2; | |
45 | } elsif (Net::IP::ip_is_ipv4($parsed_ip1)) { | |
46 | $parsed_ip4 = $parsed_ip1; | |
47 | } else { | |
48 | $parsed_ip6 = $parsed_ip1; | |
49 | } | |
50 | $parsed_ip6 = $1 if $parsed_ip6 && $parsed_ip6 =~ m/\[(\S+)\]/; | |
51 | ||
52 | #delete changed | |
53 | if (!defined($macdb->{macs}->{$parsed_mac}) || | |
54 | ($parsed_ip4 && $macdb->{macs}->{$parsed_mac}->{'ip4'} && $macdb->{macs}->{$parsed_mac}->{'ip4'} ne $parsed_ip4) || | |
55 | ($parsed_ip6 && $macdb->{macs}->{$parsed_mac}->{'ip6'} && $macdb->{macs}->{$parsed_mac}->{'ip6'} ne $parsed_ip6)) { | |
56 | $reload = 1; | |
57 | next; | |
beef73d6 AD |
58 | } |
59 | ||
a26a43bf AD |
60 | if ($parsed_mac eq $mac) { |
61 | $match = 1 if $ip4 && $parsed_ip4 && $ip4; | |
62 | $match = 1 if $ip6 && $parsed_ip6 && $ip6; | |
beef73d6 | 63 | } |
33074dfb | 64 | |
a26a43bf | 65 | print $out "$line\n"; |
beef73d6 AD |
66 | } |
67 | ||
a26a43bf AD |
68 | if(!$match) { |
69 | my $reservation = $mac; | |
70 | $reservation .= ",$ip4" if $ip4; | |
71 | $reservation .= ",[$ip6]" if $ip6; | |
72 | print $out "$reservation\n"; | |
73 | $reload = 1; | |
beef73d6 | 74 | } |
a26a43bf | 75 | |
33074dfb SH |
76 | close $in; |
77 | close $out; | |
78 | move $ethers_tmp_file, $ethers_file; | |
79 | chmod 0644, $ethers_file; | |
80 | }; | |
81 | ||
82 | PVE::Tools::lock_file($ethers_file, 10, $appendFn); | |
83 | ||
84 | if ($@) { | |
beef73d6 | 85 | warn "Unable to add $mac to the dnsmasq configuration: $@\n"; |
33074dfb SH |
86 | return; |
87 | } | |
88 | ||
89 | my $service_name = "dnsmasq\@$dhcpid"; | |
a26a43bf | 90 | PVE::Tools::run_command(['systemctl', 'reload', $service_name]) if $reload; |
e6211bae AD |
91 | |
92 | #update lease as ip could still be associated to an old removed mac | |
93 | my $bus = Net::DBus->system(); | |
94 | my $dnsmasq = $bus->get_service("uk.org.thekelleys.dnsmasq.$dhcpid"); | |
95 | my $manager = $dnsmasq->get_object("/uk/org/thekelleys/dnsmasq","uk.org.thekelleys.dnsmasq.$dhcpid"); | |
96 | ||
97 | my @hostname = unpack("C*", "*"); | |
98 | $manager->AddDhcpLease($ip4, $mac, \@hostname, undef, 0, 0, 0) if $ip4; | |
a26a43bf | 99 | # $manager->AddDhcpLease($ip6, $mac, \@hostname, undef, 0, 0, 0) if $ip6; |
e6211bae | 100 | |
33074dfb SH |
101 | } |
102 | ||
103 | sub configure_subnet { | |
104 | my ($class, $dhcpid, $subnet_config) = @_; | |
105 | ||
106 | die "No gateway defined for subnet $subnet_config->{id}" | |
107 | if !$subnet_config->{gateway}; | |
108 | ||
109 | my $tag = $subnet_config->{id}; | |
110 | ||
111 | my @dnsmasq_config = ( | |
112 | "listen-address=$subnet_config->{gateway}", | |
113 | ); | |
114 | ||
115 | my $option_string; | |
116 | if (ip_is_ipv6($subnet_config->{network})) { | |
117 | $option_string = 'option6'; | |
118 | push @dnsmasq_config, "enable-ra"; | |
119 | } else { | |
120 | $option_string = 'option'; | |
121 | push @dnsmasq_config, "dhcp-option=tag:$tag,$option_string:router,$subnet_config->{gateway}"; | |
122 | } | |
123 | ||
124 | push @dnsmasq_config, "dhcp-option=tag:$tag,$option_string:dns-server,$subnet_config->{'dhcp-dns-server'}" | |
125 | if $subnet_config->{'dhcp-dns-server'}; | |
126 | ||
127 | PVE::Tools::file_set_contents( | |
128 | "$DNSMASQ_CONFIG_ROOT/$dhcpid/10-$subnet_config->{id}.conf", | |
129 | join("\n", @dnsmasq_config) . "\n" | |
130 | ); | |
131 | } | |
132 | ||
133 | sub configure_range { | |
134 | my ($class, $dhcpid, $subnet_config, $range_config) = @_; | |
135 | ||
3f764f82 | 136 | my $subnet_file = "$DNSMASQ_CONFIG_ROOT/$dhcpid/10-$subnet_config->{id}.conf"; |
33074dfb SH |
137 | my $tag = $subnet_config->{id}; |
138 | ||
3f764f82 AD |
139 | my ($zone, $network, $mask) = split(/-/, $tag); |
140 | ||
141 | if (Net::IP::ip_is_ipv4($network)) { | |
142 | $mask = (2 ** $mask - 1) << (32 - $mask); | |
143 | $mask = join( '.', unpack( "C4", pack( "N", $mask ) ) ); | |
144 | } | |
145 | ||
146 | open(my $fh, '>>', $subnet_file) or die "Could not open file '$subnet_file' $!\n"; | |
147 | print $fh "dhcp-range=set:$tag,$network,static,$mask,infinite\n"; | |
33074dfb SH |
148 | close $fh; |
149 | } | |
150 | ||
151 | sub before_configure { | |
152 | my ($class, $dhcpid) = @_; | |
153 | ||
39062bc5 AD |
154 | my $dbus_config = <<DBUSCFG; |
155 | <!DOCTYPE busconfig PUBLIC | |
156 | "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" | |
157 | "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> | |
158 | <busconfig> | |
159 | <policy user="root"> | |
160 | <allow own="uk.org.thekelleys.dnsmasq.$dhcpid"/> | |
161 | <allow send_destination="uk.org.thekelleys.dnsmasq.$dhcpid"/> | |
162 | </policy> | |
163 | <policy user="dnsmasq"> | |
164 | <allow own="uk.org.thekelleys.dnsmasq.$dhcpid"/> | |
165 | <allow send_destination="uk.org.thekelleys.dnsmasq.$dhcpid"/> | |
166 | </policy> | |
167 | <policy context="default"> | |
168 | <deny own="uk.org.thekelleys.dnsmasq.$dhcpid"/> | |
169 | <deny send_destination="uk.org.thekelleys.dnsmasq.$dhcpid"/> | |
170 | </policy> | |
171 | </busconfig> | |
172 | DBUSCFG | |
173 | ||
174 | PVE::Tools::file_set_contents( | |
175 | "/etc/dbus-1/system.d/dnsmasq.$dhcpid.conf", | |
176 | $dbus_config | |
177 | ); | |
178 | ||
33074dfb SH |
179 | my $config_directory = "$DNSMASQ_CONFIG_ROOT/$dhcpid"; |
180 | ||
181 | mkdir($config_directory, 755) if !-d $config_directory; | |
182 | ||
183 | my $default_config = <<CFG; | |
184 | CONFIG_DIR='$config_directory,\*.conf' | |
e6211bae | 185 | DNSMASQ_OPTS="--conf-file=/dev/null --enable-dbus=uk.org.thekelleys.dnsmasq.$dhcpid" |
33074dfb SH |
186 | CFG |
187 | ||
188 | PVE::Tools::file_set_contents( | |
189 | "$DNSMASQ_DEFAULT_ROOT/dnsmasq.$dhcpid", | |
190 | $default_config | |
191 | ); | |
192 | ||
193 | my $default_dnsmasq_config = <<CFG; | |
194 | except-interface=lo | |
195 | bind-dynamic | |
196 | no-resolv | |
197 | no-hosts | |
198 | dhcp-leasefile=$DNSMASQ_LEASE_ROOT/dnsmasq.$dhcpid.leases | |
199 | dhcp-hostsfile=$config_directory/ethers | |
200 | dhcp-ignore=tag:!known | |
201 | ||
202 | # Send an empty WPAD option. This may be REQUIRED to get windows 7 to behave. | |
203 | dhcp-option=252,"\\n" | |
204 | ||
205 | # Send microsoft-specific option to tell windows to release the DHCP lease | |
206 | # when it shuts down. Note the "i" flag, to tell dnsmasq to send the | |
207 | # value as a four-byte integer - that's what microsoft wants. | |
208 | dhcp-option=vendor:MSFT,2,1i | |
209 | ||
210 | # If a DHCP client claims that its name is "wpad", ignore that. | |
211 | # This fixes a security hole. see CERT Vulnerability VU#598349 | |
212 | dhcp-name-match=set:wpad-ignore,wpad | |
213 | dhcp-ignore-names=tag:wpad-ignore | |
214 | CFG | |
215 | ||
216 | PVE::Tools::file_set_contents( | |
217 | "$config_directory/00-default.conf", | |
218 | $default_dnsmasq_config | |
219 | ); | |
220 | ||
221 | unlink glob "$config_directory/10-*.conf"; | |
222 | } | |
223 | ||
224 | sub after_configure { | |
225 | my ($class, $dhcpid) = @_; | |
226 | ||
227 | my $service_name = "dnsmasq\@$dhcpid"; | |
228 | ||
39062bc5 | 229 | PVE::Tools::run_command(['systemctl', 'reload', 'dbus']); |
33074dfb SH |
230 | PVE::Tools::run_command(['systemctl', 'enable', $service_name]); |
231 | PVE::Tools::run_command(['systemctl', 'restart', $service_name]); | |
232 | } | |
233 | ||
234 | sub before_regenerate { | |
235 | my ($class) = @_; | |
236 | ||
237 | PVE::Tools::run_command(['systemctl', 'stop', "dnsmasq@*"]); | |
238 | PVE::Tools::run_command(['systemctl', 'disable', 'dnsmasq@']); | |
239 | } | |
240 | ||
241 | sub after_regenerate { | |
242 | my ($class) = @_; | |
243 | # noop | |
244 | } | |
245 | ||
246 | 1; |