]> git.proxmox.com Git - pve-network.git/blame - src/PVE/Network/SDN/Dhcp/Dnsmasq.pm
dhcp : dnsmasq: generate dbus policy
[pve-network.git] / src / PVE / Network / SDN / Dhcp / Dnsmasq.pm
CommitLineData
33074dfb
SH
1package PVE::Network::SDN::Dhcp::Dnsmasq;
2
3use strict;
4use warnings;
5
6use base qw(PVE::Network::SDN::Dhcp::Plugin);
7
8use Net::IP qw(:PROC);
9use PVE::Tools qw(file_set_contents run_command lock_file);
10
11use File::Copy;
e6211bae 12use Net::DBus;
33074dfb
SH
13
14my $DNSMASQ_CONFIG_ROOT = '/etc/dnsmasq.d';
15my $DNSMASQ_DEFAULT_ROOT = '/etc/default';
16my $DNSMASQ_LEASE_ROOT = '/var/lib/misc';
17
18sub type {
19 return 'dnsmasq';
20}
21
33074dfb 22sub 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
103sub 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
133sub 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
151sub 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>
172DBUSCFG
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;
184CONFIG_DIR='$config_directory,\*.conf'
e6211bae 185DNSMASQ_OPTS="--conf-file=/dev/null --enable-dbus=uk.org.thekelleys.dnsmasq.$dhcpid"
33074dfb
SH
186CFG
187
188 PVE::Tools::file_set_contents(
189 "$DNSMASQ_DEFAULT_ROOT/dnsmasq.$dhcpid",
190 $default_config
191 );
192
193 my $default_dnsmasq_config = <<CFG;
194except-interface=lo
195bind-dynamic
196no-resolv
197no-hosts
198dhcp-leasefile=$DNSMASQ_LEASE_ROOT/dnsmasq.$dhcpid.leases
199dhcp-hostsfile=$config_directory/ethers
200dhcp-ignore=tag:!known
201
202# Send an empty WPAD option. This may be REQUIRED to get windows 7 to behave.
203dhcp-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.
208dhcp-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
212dhcp-name-match=set:wpad-ignore,wpad
213dhcp-ignore-names=tag:wpad-ignore
214CFG
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
224sub 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
234sub before_regenerate {
235 my ($class) = @_;
236
237 PVE::Tools::run_command(['systemctl', 'stop', "dnsmasq@*"]);
238 PVE::Tools::run_command(['systemctl', 'disable', 'dnsmasq@']);
239}
240
241sub after_regenerate {
242 my ($class) = @_;
243 # noop
244}
245
2461;