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