]> git.proxmox.com Git - pve-container.git/blob - src/PVE/LXC/Setup/Debian.pm
cleanup gateway hooks in debian network setup
[pve-container.git] / src / PVE / LXC / Setup / Debian.pm
1 package PVE::LXC::Setup::Debian;
2
3 use strict;
4 use warnings;
5 use Data::Dumper;
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 stretch/sid => 9.0 (used on debian testing repository)
23 $version = 9.0 if $version eq 'stretch/sid';
24
25 die "unable to parse version info '$version'\n"
26 if $version !~ m/^(\d+(\.\d+)?)(\.\d+)?/;
27
28 $version = $1;
29
30 die "unsupported debian version '$version'\n"
31 if !($version >= 4 && $version <= 9);
32
33 my $self = { conf => $conf, rootdir => $rootdir, version => $version };
34
35 $conf->{ostype} = "debian";
36
37 return bless $self, $class;
38 }
39
40 sub setup_init {
41 my ($self, $conf) = @_;
42
43 my $systemd = $self->ct_readlink('/sbin/init');
44 if (defined($systemd) && $systemd =~ m@/systemd$@) {
45 $self->setup_container_getty_service(1);
46 }
47
48 my $filename = "/etc/inittab";
49 return if !$self->ct_file_exists($filename);
50
51 my $ttycount = PVE::LXC::Config->get_tty_count($conf);
52 my $inittab = $self->ct_file_get_contents($filename);
53
54 my @lines = grep {
55 # remove getty lines
56 !/^\s*\d+:\d+:[^:]*:.*getty/ &&
57 # remove power lines
58 !/^\s*p[fno0]:/
59 } split(/\n/, $inittab);
60
61 $inittab = join("\n", @lines) . "\n";
62
63 $inittab .= "p0::powerfail:/sbin/init 0\n";
64
65 my $version = $self->{version};
66 for (my $id = 1; $id <= $ttycount; $id++) {
67 next if $id == 7; # reserved for X11
68 my $levels = ($id == 1) ? '2345' : '23';
69 if ($version < 7) {
70 $inittab .= "$id:$levels:respawn:/sbin/getty -L 38400 tty$id\n";
71 } else {
72 $inittab .= "$id:$levels:respawn:/sbin/getty --noclear 38400 tty$id\n";
73 }
74 }
75
76 $self->ct_file_set_contents($filename, $inittab);
77 }
78
79 sub remove_gateway_scripts {
80 my ($attr) = @_;
81 my $length = scalar(@$attr);
82 for (my $i = 0; $i < $length; ++$i) {
83 my $a = $attr->[$i];
84 if ($a =~ m@^\s*post-up\s+.*route.*add.*default.*(?:gw|via)\s+(\S+)@) {
85 my $gw = $1;
86 if ($i > 0 && $attr->[$i-1] =~ m@^\s*post-up\s+.*route.*add.*\Q$1\E@) {
87 --$i;
88 splice @$attr, $i, 2;
89 $length -= 2;
90 } else {
91 splice @$attr, $i, 1;
92 $length -= 1;
93 }
94 --$i;
95 next;
96 }
97 if ($a =~ m@^\s*pre-down\s+.*route.*del.*default.*(?:gw|via)\s+(\S+)@) {
98 my $gw = $1;
99 if ($attr->[$i+1] =~ m@^\s*pre-down\s+.*route.*del.*\Q$1\E@) {
100 splice @$attr, $i, 2;
101 $length -= 2;
102 } else {
103 splice @$attr, $i, 1;
104 $length -= 1;
105 }
106 --$i;
107 next;
108 }
109 }
110 }
111
112 sub make_gateway_scripts {
113 my ($ifname, $gw) = @_;
114 return <<"SCRIPTS";
115 \tpost-up ip route add $gw dev $ifname
116 \tpost-up ip route add default via $gw dev $ifname
117 \tpre-down ip route del default via $gw dev $ifname
118 \tpre-down ip route del $gw dev $ifname
119 SCRIPTS
120 }
121
122 sub setup_network {
123 my ($self, $conf) = @_;
124
125 my $networks = {};
126 foreach my $k (keys %$conf) {
127 next if $k !~ m/^net(\d+)$/;
128 my $ind = $1;
129 my $d = PVE::LXC::Config->parse_lxc_network($conf->{$k});
130 if ($d->{name}) {
131 my $net = {};
132 my $cidr;
133 if (defined($d->{ip})) {
134 if ($d->{ip} =~ /^(?:dhcp|manual)$/) {
135 $net->{address} = $d->{ip};
136 } else {
137 my $ipinfo = PVE::LXC::parse_ipv4_cidr($d->{ip});
138 $net->{address} = $ipinfo->{address};
139 $net->{netmask} = $ipinfo->{netmask};
140 $cidr = $d->{ip};
141 }
142 }
143 if (defined($d->{'gw'})) {
144 $net->{gateway} = $d->{'gw'};
145 if (defined($cidr) && !PVE::Network::is_ip_in_cidr($d->{gw}, $cidr, 4)) {
146 # gateway is not reachable, need an extra route
147 $net->{needsroute} = 1;
148 }
149 }
150 $cidr = undef;
151 if (defined($d->{ip6})) {
152 if ($d->{ip6} =~ /^(?:auto|dhcp|manual)$/) {
153 $net->{address6} = $d->{ip6};
154 } elsif ($d->{ip6} !~ /^($IPV6RE)\/(\d+)$/) {
155 die "unable to parse ipv6 address/prefix\n";
156 } else {
157 $net->{address6} = $1;
158 $net->{netmask6} = $2;
159 $cidr = $d->{ip6};
160 }
161 }
162 if (defined($d->{'gw6'})) {
163 $net->{gateway6} = $d->{'gw6'};
164 if (defined($cidr) && !PVE::Network::is_ip_in_cidr($d->{gw6}, $cidr, 6) &&
165 !PVE::Network::is_ip_in_cidr($d->{gw6}, 'fe80::/10', 6)) {
166 # gateway is not reachable, need an extra route
167 $net->{needsroute6} = 1;
168 }
169 }
170 $networks->{$d->{name}} = $net if keys %$net;
171 }
172 }
173
174 return if !scalar(keys %$networks);
175
176 my $filename = "/etc/network/interfaces";
177 my $interfaces = "";
178
179 my $section;
180
181 my $done_auto = {};
182 my $done_v4_hash = {};
183 my $done_v6_hash = {};
184
185 my $print_section = sub {
186 return if !$section;
187
188 my $ifname = $section->{ifname};
189 my $net = $networks->{$ifname};
190
191 if (!$done_auto->{$ifname}) {
192 $interfaces .= "auto $ifname\n";
193 $done_auto->{$ifname} = 1;
194 }
195
196 if ($section->{type} eq 'ipv4') {
197 $done_v4_hash->{$ifname} = 1;
198
199 if (defined($net->{address}) && $net->{address} =~ /^(dhcp|manual)$/) {
200 $interfaces .= "iface $ifname inet $1\n";
201 } else {
202 $interfaces .= "iface $ifname inet static\n";
203 $interfaces .= "\taddress $net->{address}\n" if defined($net->{address});
204 $interfaces .= "\tnetmask $net->{netmask}\n" if defined($net->{netmask});
205
206 remove_gateway_scripts($section->{attr});
207 if (defined(my $gw = $net->{gateway})) {
208 if ($net->{needsroute}) {
209 $interfaces .= make_gateway_scripts($ifname, $gw);
210 } else {
211 $interfaces .= "\tgateway $gw\n";
212 }
213 }
214 foreach my $attr (@{$section->{attr}}) {
215 $interfaces .= "\t$attr\n";
216 }
217 }
218
219 $interfaces .= "\n";
220
221 } elsif ($section->{type} eq 'ipv6') {
222 $done_v6_hash->{$ifname} = 1;
223
224 if (defined($net->{address6}) && $net->{address6} =~ /^(auto|dhcp|manual)$/) {
225 $interfaces .= "iface $ifname inet6 $1\n";
226 } else {
227 $interfaces .= "iface $ifname inet6 static\n";
228 $interfaces .= "\taddress $net->{address6}\n" if defined($net->{address6});
229 $interfaces .= "\tnetmask $net->{netmask6}\n" if defined($net->{netmask6});
230 remove_gateway_scripts($section->{attr});
231 if (defined(my $gw = $net->{gateway6})) {
232 if ($net->{needsroute6}) {
233 $interfaces .= make_gateway_scripts($ifname, $gw);
234 } else {
235 $interfaces .= "\tgateway $net->{gateway6}\n" if defined($net->{gateway6});
236 }
237 }
238 foreach my $attr (@{$section->{attr}}) {
239 $interfaces .= "\t$attr\n";
240 }
241 }
242
243 $interfaces .= "\n";
244 } else {
245 die "unknown section type '$section->{type}'";
246 }
247
248 $section = undef;
249 };
250
251 if (my $fh = $self->ct_open_file_read($filename)) {
252 while (defined (my $line = <$fh>)) {
253 chomp $line;
254 if ($line =~ m/^#/) {
255 $interfaces .= "$line\n";
256 next;
257 }
258 if ($line =~ m/^\s*$/) {
259 if ($section) {
260 &$print_section();
261 } else {
262 $interfaces .= "$line\n";
263 }
264 next;
265 }
266 if ($line =~ m/^\s*iface\s+(\S+)\s+inet\s+(\S+)\s*$/) {
267 my $ifname = $1;
268 &$print_section(); # print previous section
269 if (!$networks->{$ifname}) {
270 $interfaces .= "$line\n";
271 next;
272 }
273 $section = { type => 'ipv4', ifname => $ifname, attr => []};
274 next;
275 }
276 if ($line =~ m/^\s*iface\s+(\S+)\s+inet6\s+(\S+)\s*$/) {
277 my $ifname = $1;
278 &$print_section(); # print previous section
279 if (!$networks->{$ifname}) {
280 $interfaces .= "$line\n";
281 next;
282 }
283 $section = { type => 'ipv6', ifname => $ifname, attr => []};
284 next;
285 }
286 # Handle 'auto'
287 if ($line =~ m/^\s*auto\s*(.*)$/) {
288 foreach my $iface (split(/\s+/, $1)) {
289 $done_auto->{$iface} = 1;
290 }
291 &$print_section();
292 $interfaces .= "$line\n";
293 next;
294 }
295 # Handle other section delimiters:
296 if ($line =~ m/^\s*(?:mapping\s
297 |allow-
298 |source\s
299 |source-directory\s
300 )/x) {
301 &$print_section();
302 $interfaces .= "$line\n";
303 next;
304 }
305 if ($section && $line =~ m/^\s*((\S+)\s(.*))$/) {
306 my ($adata, $aname) = ($1, $2);
307 if ($aname eq 'address' || $aname eq 'netmask' ||
308 $aname eq 'gateway' || $aname eq 'broadcast') {
309 # skip
310 } else {
311 push @{$section->{attr}}, $adata;
312 }
313 next;
314 }
315
316 $interfaces .= "$line\n";
317 }
318 &$print_section();
319 }
320
321 my $need_separator = length($interfaces) && ($interfaces !~ /\n\n$/);
322 foreach my $ifname (sort keys %$networks) {
323 my $net = $networks->{$ifname};
324
325 if (!$done_v4_hash->{$ifname} && defined($net->{address})) {
326 if ($need_separator) { $interfaces .= "\n"; $need_separator = 0; };
327 $section = { type => 'ipv4', ifname => $ifname, attr => []};
328 &$print_section();
329 }
330 if (!$done_v6_hash->{$ifname} && defined($net->{address6})) {
331 if ($need_separator) { $interfaces .= "\n"; $need_separator = 0; };
332 $section = { type => 'ipv6', ifname => $ifname, attr => []};
333 &$print_section();
334 }
335 }
336
337 $self->ct_file_set_contents($filename, $interfaces);
338 }
339
340 1;