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