]> git.proxmox.com Git - pve-container.git/blob - src/PVE/LXC/Setup/Debian.pm
50398892e512f695c79df4245c303caac618fe2b
[pve-container.git] / src / PVE / LXC / Setup / Debian.pm
1 package PVE::LXC::Setup::Debian;
2
3 use strict;
4 use warnings;
5
6 use PVE::Tools qw($IPV6RE);
7 use PVE::LXC;
8 use PVE::Network;
9 use File::Path;
10
11 use PVE::LXC::Setup::Base;
12
13 use base qw(PVE::LXC::Setup::Base);
14
15 sub new {
16 my ($class, $conf, $rootdir) = @_;
17
18 my $version = PVE::Tools::file_read_firstline("$rootdir/etc/debian_version");
19
20 die "unable to read version info\n" if !defined($version);
21
22 # translate testing version names
23 $version = 9.1 if $version eq 'stretch/sid';
24 $version = 10 if $version eq 'buster/sid';
25
26 die "unable to parse version info '$version'\n"
27 if $version !~ m/^(\d+(\.\d+)?)(\.\d+)?/;
28
29 $version = $1;
30
31 die "unsupported debian version '$version'\n"
32 if !($version >= 4 && $version <= 10);
33
34 my $self = { conf => $conf, rootdir => $rootdir, version => $version };
35
36 $conf->{ostype} = "debian";
37
38 return bless $self, $class;
39 }
40
41 # Debian doesn't support the /dev/lxc/ subdirectory.
42 sub devttydir {
43 return '';
44 }
45
46 sub setup_init {
47 my ($self, $conf) = @_;
48
49 my $systemd = $self->ct_readlink('/sbin/init');
50 if (defined($systemd) && $systemd =~ m@/systemd$@) {
51 $self->setup_container_getty_service($conf);
52 }
53
54 my $filename = "/etc/inittab";
55 return if !$self->ct_file_exists($filename);
56
57 my $ttycount = PVE::LXC::Config->get_tty_count($conf);
58 my $inittab = $self->ct_file_get_contents($filename);
59
60 my @lines = grep {
61 # remove getty lines
62 !/^\s*\d+:\d*:[^:]*:.*getty/ &&
63 # remove power lines
64 !/^\s*p[fno0]:/
65 } split(/\n/, $inittab);
66
67 $inittab = join("\n", @lines) . "\n";
68
69 $inittab .= "p0::powerfail:/sbin/init 0\n";
70
71 my $version = $self->{version};
72 for (my $id = 1; $id <= $ttycount; $id++) {
73 next if $id == 7; # reserved for X11
74 my $levels = ($id == 1) ? '2345' : '23';
75 if ($version < 7) {
76 $inittab .= "$id:$levels:respawn:/sbin/getty -L 38400 tty$id\n";
77 } else {
78 $inittab .= "$id:$levels:respawn:/sbin/getty --noclear 38400 tty$id\n";
79 }
80 }
81
82 $self->ct_file_set_contents($filename, $inittab);
83 }
84
85 sub remove_gateway_scripts {
86 my ($attr) = @_;
87 my $length = scalar(@$attr);
88
89 my $found_section = 0;
90 my $keep = 1;
91 @$attr = grep {
92 if ($_ eq '# --- BEGIN PVE ---') {
93 $found_section = 1;
94 $keep = 0;
95 0; # remove this line
96 } elsif ($_ eq '# --- END PVE ---') {
97 $found_section = 1;
98 $keep = 1;
99 0; # remove this line
100 } else {
101 $keep;
102 }
103 } @$attr;
104
105 return if $found_section;
106 # XXX: To deal with existing setups we perform two types of removal for
107 # now. Newly started containers have their routing sections marked with
108 # begin/end comments. For older containers we perform a strict matching on
109 # the routing rules we added. We can probably remove this part at some point
110 # when it is unlikely that old debian setups are still around.
111
112 for (my $i = 0; $i < $length-3; ++$i) {
113 next if $attr->[$i+0] !~ m@^\s*post-up\s+ip\s+route\s+add\s+(\S+)\s+dev\s+(\S+)$@;
114 my ($ip, $dev) = ($1, $2);
115 if ($attr->[$i+1] =~ m@^\s*post-up\s+ip\s+route\s+add\s+default\s+via\s+(\S+)\s+dev\s+(\S+)$@ &&
116 ($ip eq $1 && $dev eq $2) &&
117 $attr->[$i+2] =~ m@^\s*pre-down\s+ip\s+route\s+del\s+default\s+via\s+(\S+)\s+dev\s+(\S+)$@ &&
118 ($ip eq $1 && $dev eq $2) &&
119 $attr->[$i+3] =~ m@^\s*pre-down\s+ip\s+route\s+del\s+(\S+)\s+dev\s+(\S+)$@ &&
120 ($ip eq $1 && $dev eq $2))
121 {
122 splice @$attr, $i, 4;
123 $length -= 4;
124 --$i;
125 }
126 }
127 }
128
129 sub make_gateway_scripts {
130 my ($ifname, $gw) = @_;
131 return <<"SCRIPTS";
132 # --- BEGIN PVE ---
133 \tpost-up ip route add $gw dev $ifname
134 \tpost-up ip route add default via $gw dev $ifname
135 \tpre-down ip route del default via $gw dev $ifname
136 \tpre-down ip route del $gw dev $ifname
137 # --- END PVE ---
138 SCRIPTS
139 }
140
141 sub setup_network {
142 my ($self, $conf) = @_;
143
144 my $networks = {};
145 foreach my $k (keys %$conf) {
146 next if $k !~ m/^net(\d+)$/;
147 my $ind = $1;
148 my $d = PVE::LXC::Config->parse_lxc_network($conf->{$k});
149 if ($d->{name}) {
150 my $net = {};
151 my $cidr;
152 if (defined($d->{ip})) {
153 if ($d->{ip} =~ /^(?:dhcp|manual)$/) {
154 $net->{address} = $d->{ip};
155 } else {
156 my $ipinfo = PVE::LXC::parse_ipv4_cidr($d->{ip});
157 $net->{address} = $ipinfo->{address};
158 $net->{netmask} = $ipinfo->{netmask};
159 $cidr = $d->{ip};
160 }
161 }
162 if (defined($d->{'gw'})) {
163 $net->{gateway} = $d->{'gw'};
164 if (defined($cidr) && !PVE::Network::is_ip_in_cidr($d->{gw}, $cidr, 4)) {
165 # gateway is not reachable, need an extra route
166 $net->{needsroute} = 1;
167 }
168 }
169 $cidr = undef;
170 if (defined($d->{ip6})) {
171 if ($d->{ip6} =~ /^(?:auto|dhcp|manual)$/) {
172 $net->{address6} = $d->{ip6};
173 } elsif ($d->{ip6} !~ /^($IPV6RE)\/(\d+)$/) {
174 die "unable to parse ipv6 address/prefix\n";
175 } else {
176 $net->{address6} = $1;
177 $net->{netmask6} = $2;
178 $cidr = $d->{ip6};
179 }
180 }
181 if (defined($d->{'gw6'})) {
182 $net->{gateway6} = $d->{'gw6'};
183 if (defined($cidr) && !PVE::Network::is_ip_in_cidr($d->{gw6}, $cidr, 6) &&
184 !PVE::Network::is_ip_in_cidr($d->{gw6}, 'fe80::/10', 6)) {
185 # gateway is not reachable, need an extra route
186 $net->{needsroute6} = 1;
187 }
188 }
189 $networks->{$d->{name}} = $net if keys %$net;
190 }
191 }
192
193 return if !scalar(keys %$networks);
194
195 my $filename = "/etc/network/interfaces";
196 my $interfaces = "";
197
198 my $section;
199
200 my $done_auto = {};
201 my $done_v4_hash = {};
202 my $done_v6_hash = {};
203
204 my $print_section = sub {
205 return if !$section;
206
207 my $ifname = $section->{ifname};
208 my $net = $networks->{$ifname};
209
210 if (!$done_auto->{$ifname}) {
211 $interfaces .= "auto $ifname\n";
212 $done_auto->{$ifname} = 1;
213 }
214
215 if ($section->{type} eq 'ipv4') {
216 $done_v4_hash->{$ifname} = 1;
217
218 if (!defined($net->{address})) {
219 # no address => no iface line
220 } elsif ($net->{address} =~ /^(dhcp|manual)$/) {
221 $interfaces .= "iface $ifname inet $1\n\n";
222 } else {
223 $interfaces .= "iface $ifname inet static\n";
224 $interfaces .= "\taddress $net->{address}\n" if defined($net->{address});
225 $interfaces .= "\tnetmask $net->{netmask}\n" if defined($net->{netmask});
226
227 remove_gateway_scripts($section->{attr});
228 if (defined(my $gw = $net->{gateway})) {
229 if ($net->{needsroute}) {
230 $interfaces .= make_gateway_scripts($ifname, $gw);
231 } else {
232 $interfaces .= "\tgateway $gw\n";
233 }
234 }
235 foreach my $attr (@{$section->{attr}}) {
236 $interfaces .= "\t$attr\n";
237 }
238 $interfaces .= "\n";
239 }
240 } elsif ($section->{type} eq 'ipv6') {
241 $done_v6_hash->{$ifname} = 1;
242
243 if (!defined($net->{address6})) {
244 # no address => no iface line
245 } elsif ($net->{address6} =~ /^(auto|dhcp|manual)$/) {
246 $interfaces .= "iface $ifname inet6 $1\n\n";
247 } else {
248 $interfaces .= "iface $ifname inet6 static\n";
249 $interfaces .= "\taddress $net->{address6}\n" if defined($net->{address6});
250 $interfaces .= "\tnetmask $net->{netmask6}\n" if defined($net->{netmask6});
251 remove_gateway_scripts($section->{attr});
252 if (defined(my $gw = $net->{gateway6})) {
253 if ($net->{needsroute6}) {
254 $interfaces .= make_gateway_scripts($ifname, $gw);
255 } else {
256 $interfaces .= "\tgateway $net->{gateway6}\n" if defined($net->{gateway6});
257 }
258 }
259 foreach my $attr (@{$section->{attr}}) {
260 $interfaces .= "\t$attr\n";
261 }
262 $interfaces .= "\n";
263 }
264 } else {
265 die "unknown section type '$section->{type}'";
266 }
267
268 $section = undef;
269 };
270
271 if (my $fh = $self->ct_open_file_read($filename)) {
272 while (defined (my $line = <$fh>)) {
273 chomp $line;
274 if ($line =~ m/^# --- (?:BEGIN|END) PVE ---/) {
275 # Include markers in the attribute section so
276 # remove_gateway_scripts() can find them.
277 push @{$section->{attr}}, $line if $section;
278 next;
279 }
280 if ($line =~ m/^#/) {
281 $interfaces .= "$line\n";
282 next;
283 }
284 if ($line =~ m/^\s*$/) {
285 if ($section) {
286 &$print_section();
287 } else {
288 $interfaces .= "$line\n";
289 }
290 next;
291 }
292 if ($line =~ m/^\s*iface\s+(\S+)\s+inet\s+(\S+)\s*$/) {
293 my $ifname = $1;
294 &$print_section(); # print previous section
295 if (!$networks->{$ifname}) {
296 $interfaces .= "$line\n";
297 next;
298 }
299 $section = { type => 'ipv4', ifname => $ifname, attr => []};
300 next;
301 }
302 if ($line =~ m/^\s*iface\s+(\S+)\s+inet6\s+(\S+)\s*$/) {
303 my $ifname = $1;
304 &$print_section(); # print previous section
305 if (!$networks->{$ifname}) {
306 $interfaces .= "$line\n";
307 next;
308 }
309 $section = { type => 'ipv6', ifname => $ifname, attr => []};
310 next;
311 }
312 # Handle 'auto'
313 if ($line =~ m/^\s*auto\s*(.*)$/) {
314 foreach my $iface (split(/\s+/, $1)) {
315 $done_auto->{$iface} = 1;
316 }
317 &$print_section();
318 $interfaces .= "$line\n";
319 next;
320 }
321 # Handle other section delimiters:
322 if ($line =~ m/^\s*(?:mapping\s
323 |allow-
324 |source\s
325 |source-directory\s
326 )/x) {
327 &$print_section();
328 $interfaces .= "$line\n";
329 next;
330 }
331 if ($section && $line =~ m/^\s*((\S+)\s(.*))$/) {
332 my ($adata, $aname) = ($1, $2);
333 if ($aname eq 'address' || $aname eq 'netmask' ||
334 $aname eq 'gateway' || $aname eq 'broadcast') {
335 # skip
336 } else {
337 push @{$section->{attr}}, $adata;
338 }
339 next;
340 }
341
342 $interfaces .= "$line\n";
343 }
344 &$print_section();
345 }
346
347 my $need_separator = length($interfaces) && ($interfaces !~ /\n\n$/);
348 foreach my $ifname (sort keys %$networks) {
349 my $net = $networks->{$ifname};
350
351 if (!$done_v4_hash->{$ifname} && defined($net->{address})) {
352 if ($need_separator) { $interfaces .= "\n"; $need_separator = 0; };
353 $section = { type => 'ipv4', ifname => $ifname, attr => []};
354 &$print_section();
355 }
356 if (!$done_v6_hash->{$ifname} && defined($net->{address6})) {
357 if ($need_separator) { $interfaces .= "\n"; $need_separator = 0; };
358 $section = { type => 'ipv6', ifname => $ifname, attr => []};
359 &$print_section();
360 }
361 }
362
363 # older templates (< Debian 8) do not configure the loopback interface
364 # if not explicitly told to do so
365 if (!$done_auto->{lo}) {
366 $interfaces = "auto lo\niface lo inet loopback\n" .
367 "iface lo inet6 loopback\n\n" .
368 $interfaces;
369 }
370
371 $self->ct_file_set_contents($filename, $interfaces);
372 }
373
374 1;