]>
Commit | Line | Data |
---|---|---|
2231d226 WB |
1 | package Proxmox::Install::RunEnv; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
5 | ||
6 | use Carp; | |
7 | use JSON qw(from_json to_json); | |
2231d226 | 8 | |
0d27321b | 9 | use Proxmox::Log; |
88adf315 | 10 | use Proxmox::Sys::File qw(file_read_firstline); |
ceec3def | 11 | use Proxmox::Sys::Block; |
fe06d7e9 | 12 | use Proxmox::Sys::Net; |
2231d226 | 13 | |
e1b74696 TL |
14 | use Proxmox::Install::ISOEnv; |
15 | ||
2231d226 WB |
16 | my sub fromjs : prototype($) { |
17 | return from_json($_[0], { utf8 => 1 }); | |
18 | } | |
19 | ||
b91f9cad TL |
20 | my $mem_total = undef; |
21 | sub query_total_memory : prototype() { | |
22 | return $mem_total if defined($mem_total); | |
23 | ||
24 | open (my $MEMINFO, '<', '/proc/meminfo'); | |
25 | ||
26 | my $res = 512; # default to 512 if something goes wrong | |
27 | while (my $line = <$MEMINFO>) { | |
28 | if ($line =~ m/^MemTotal:\s+(\d+)\s*kB/i) { | |
29 | $res = int ($1 / 1024); | |
30 | } | |
31 | } | |
32 | close($MEMINFO); | |
33 | ||
34 | $mem_total = $res; | |
35 | return $mem_total; | |
36 | } | |
37 | ||
4d8aec8a CH |
38 | my $cpu_hvm_support = undef; |
39 | sub query_cpu_hvm_support : prototype() { | |
40 | return $cpu_hvm_support if defined($cpu_hvm_support); | |
41 | ||
42 | open (my $CPUINFO, '<', '/proc/cpuinfo'); | |
43 | ||
44 | my $res = 0; | |
45 | while (my $line = <$CPUINFO>) { | |
46 | if ($line =~ /^flags\s*:.*(vmx|svm)/m) { | |
47 | $res = 1; | |
48 | last; | |
49 | } | |
50 | } | |
51 | close($CPUINFO); | |
52 | ||
53 | $cpu_hvm_support = $res; | |
54 | return $cpu_hvm_support; | |
55 | } | |
56 | ||
2231d226 WB |
57 | # Returns a hash. |
58 | # | |
59 | # { | |
60 | # <ifname> => { | |
61 | # mac => <mac address>, | |
62 | # index => <index>, | |
63 | # name => <ifname>, | |
64 | # addresses => [ | |
65 | # family => <inet|inet6>, | |
66 | # address => <mac address>, | |
67 | # prefix => <length>, | |
68 | # ], | |
69 | # }, | |
70 | # } | |
71 | my sub query_netdevs : prototype() { | |
72 | my $ifs = {}; | |
73 | my $default; | |
74 | ||
88adf315 | 75 | # FIXME: not the same as the battle proven way we used in the installer for years? |
2231d226 WB |
76 | my $interfaces = fromjs(qx/ip --json address show/); |
77 | ||
78 | for my $if (@$interfaces) { | |
79 | my ($index, $name, $state, $mac, $addresses) = | |
80 | $if->@{qw(ifindex ifname operstate address addr_info)}; | |
81 | ||
5ea718f6 | 82 | next if !$name || $name eq 'lo'; # could also check flags for LOOPBACK.. |
2231d226 WB |
83 | |
84 | my @valid_addrs; | |
5ea718f6 TL |
85 | if (uc($state) eq 'UP') { |
86 | for my $addr (@$addresses) { | |
87 | next if $addr->{scope} eq 'link'; | |
88 | ||
89 | my ($family, $addr, $prefix) = $addr->@{qw(family local prefixlen)}; | |
90 | ||
91 | push @valid_addrs, { | |
92 | family => $family, | |
93 | address => $addr, | |
94 | prefix => $prefix, | |
95 | }; | |
96 | } | |
2231d226 WB |
97 | } |
98 | ||
5ea718f6 TL |
99 | $ifs->{$name} = { |
100 | index => $index, | |
101 | name => $name, | |
102 | mac => $mac, | |
5c450165 | 103 | state => uc($state), |
5ea718f6 TL |
104 | }; |
105 | $ifs->{$name}->{addresses} = \@valid_addrs if @valid_addrs; | |
2231d226 WB |
106 | } |
107 | ||
108 | return $ifs; | |
109 | } | |
110 | ||
111 | # Returns a hash. | |
112 | # | |
113 | # { | |
114 | # gateway4 => { | |
115 | # dst => "default", | |
116 | # gateway => <ipv4>, | |
117 | # dev => <ifname>, | |
118 | # }, | |
119 | # gateway6 => { | |
120 | # dst => "default", | |
121 | # gateway => <ipv6>, | |
122 | # dev => <ifname>, | |
123 | # }, | |
124 | # } | |
125 | my sub query_routes : prototype() { | |
126 | my ($gateway4, $gateway6); | |
127 | ||
a467ec47 | 128 | log_info("query routes"); |
2231d226 WB |
129 | my $route4 = fromjs(qx/ip -4 --json route show/); |
130 | for my $route (@$route4) { | |
131 | if ($route->{dst} eq 'default') { | |
132 | $gateway4 = { | |
133 | dev => $route->{dev}, | |
134 | gateway => $route->{gateway}, | |
135 | }; | |
136 | last; | |
137 | } | |
138 | } | |
139 | ||
140 | my $route6 = fromjs(qx/ip -6 --json route show/); | |
141 | for my $route (@$route6) { | |
142 | if ($route->{dst} eq 'default') { | |
143 | $gateway6 = { | |
144 | dev => $route->{dev}, | |
145 | gateway => $route->{gateway}, | |
146 | }; | |
147 | last; | |
148 | } | |
149 | } | |
150 | ||
151 | my $routes; | |
152 | $routes->{gateway4} = $gateway4 if $gateway4; | |
153 | $routes->{gateway6} = $gateway6 if $gateway6; | |
154 | ||
155 | return $routes; | |
156 | } | |
157 | ||
158 | # If `/etc/resolv.conf` fails to open this returns nothing. | |
159 | # Otherwise it returns a hash: | |
160 | # { | |
161 | # dns => <first dns entry>, | |
162 | # | |
163 | my sub query_dns : prototype() { | |
a467ec47 | 164 | log_info("query DNS from resolv.conf (managed by DHCP client)"); |
2231d226 WB |
165 | open my $fh , '<', '/etc/resolv.conf' or return; |
166 | ||
167 | my @dns; | |
168 | my $domain; | |
169 | while (defined(my $line = <$fh>)) { | |
170 | if ($line =~ /^nameserver\s+(\S+)/) { | |
171 | push @dns, $1; | |
172 | } elsif (!defined($domain) && $line =~ /^domain\s+(\S+)/) { | |
173 | $domain = $1; | |
174 | } | |
175 | } | |
176 | ||
177 | my $output = { | |
178 | domain => $domain, | |
179 | @dns ? (dns => \@dns) : (), | |
180 | }; | |
181 | }; | |
182 | ||
183 | # Uses `traceroute` and `geoiplookup`/`geoiplookup6` to figure out the current country. | |
184 | # Has a 10s timeout and uses the stops at the first entry found in the geoip database. | |
185 | my sub detect_country_tracing_to : prototype($$) { | |
186 | my ($ipver, $destination) = @_; | |
187 | ||
a467ec47 TL |
188 | print STDERR "trying to detect country...\n"; |
189 | open(my $TRACEROUTE_FH, '-|', 'traceroute', "-$ipver", '-N', '1', '-q', '1', '-n', $destination) | |
2231d226 WB |
190 | or return undef; |
191 | ||
192 | my $geoip_bin = ($ipver == 6) ? 'geoiplookup6' : 'geoiplookup'; | |
193 | ||
194 | my $country; | |
195 | ||
196 | my $previous_alarm = alarm (10); | |
197 | eval { | |
198 | local $SIG{ALRM} = sub { die "timed out!\n" }; | |
199 | my $line; | |
200 | while (defined ($line = <$TRACEROUTE_FH>)) { | |
201 | log_debug("DC TRACEROUTE: $line"); | |
202 | if ($line =~ m/^\s*\d+\s+(\S+)\s/) { | |
203 | my $geoip = qx/$geoip_bin $1/; | |
204 | log_debug("DC GEOIP: $geoip"); | |
205 | if ($geoip =~ m/GeoIP Country Edition:\s*([A-Z]+),/) { | |
206 | $country = lc ($1); | |
207 | log_info("DC FOUND: $country\n"); | |
208 | last; | |
209 | } | |
210 | } | |
211 | } | |
212 | }; | |
213 | my $err = $@; | |
214 | alarm ($previous_alarm); | |
215 | ||
216 | close($TRACEROUTE_FH); | |
217 | ||
218 | if ($err) { | |
219 | die "unable to detect country - $err\n"; | |
220 | } elsif ($country) { | |
a467ec47 | 221 | print STDERR "detected country: " . uc($country) . "\n"; |
2231d226 WB |
222 | } |
223 | ||
224 | return $country; | |
225 | } | |
226 | ||
227 | # Returns the entire environment as a hash. | |
228 | # { | |
229 | # country => <short country>, | |
4d8aec8a CH |
230 | # ipconf = <see Proxmox::Sys::Net::get_ip_config()>, |
231 | # kernel_cmdline = <contents of /proc/cmdline>, | |
232 | # total_memory = <memory size in MiB>, | |
233 | # hvm_supported = <1 if the CPU supports hardware-accelerated virtualization>, | |
234 | # boot_type = <either 'efi' or 'bios'>, | |
ceec3def | 235 | # disks => <see Proxmox::Sys::Block::hd_list()>, |
2231d226 WB |
236 | # network => { |
237 | # interfaces => <see query_netdevs()>, | |
238 | # routes => <see query_routes()>, | |
1c5e103a | 239 | # dns => <see query_dns()>, |
2231d226 | 240 | # }, |
2231d226 WB |
241 | # } |
242 | sub query_installation_environment : prototype() { | |
e1b74696 TL |
243 | # check first if somebody already cached this for us and re-use that |
244 | my $run_env_file = Proxmox::Install::ISOEnv::get('run-env-cache-file'); | |
245 | if (-f "$run_env_file") { | |
246 | log_info("re-using cached runtime env from $run_env_file"); | |
247 | my $cached_env = eval { | |
248 | my $run_env_raw = Proxmox::Sys::File::file_read_all($run_env_file); | |
249 | return fromjs($run_env_raw); # returns from eval | |
250 | }; | |
251 | log_error("failed to parse cached runtime env - $@") if $@; | |
252 | return $cached_env if defined($cached_env) && scalar keys $cached_env->%*; | |
253 | log_warn("cached runtime env seems empty, query everything (again)"); | |
254 | } | |
255 | # else re-query everything | |
2231d226 WB |
256 | my $output = {}; |
257 | ||
258 | my $routes = query_routes(); | |
259 | ||
a467ec47 | 260 | log_info("query block devices"); |
ceec3def | 261 | $output->{disks} = Proxmox::Sys::Block::get_cached_disks(); |
2231d226 WB |
262 | $output->{network} = { |
263 | interfaces => query_netdevs(), | |
264 | routes => $routes, | |
1c5e103a | 265 | dns => query_dns(), |
2231d226 | 266 | }; |
fe06d7e9 TL |
267 | # FIXME: move whatever makes sense over to Proxmox::Sys::Net:: and keep that as single source, |
268 | # it can then use some different structure just fine (after adapting the GTK GUI to that) but | |
269 | # **never** to (slightly different!) things for the same stuff... | |
270 | $output->{ipconf} = Proxmox::Sys::Net::get_ip_config(); | |
2231d226 | 271 | |
88adf315 | 272 | $output->{kernel_cmdline} = file_read_firstline("/proc/cmdline"); |
b91f9cad | 273 | $output->{total_memory} = query_total_memory(); |
4d8aec8a | 274 | $output->{hvm_supported} = query_cpu_hvm_support(); |
71583761 | 275 | $output->{boot_type} = -d '/sys/firmware/efi' ? 'efi' : 'bios'; |
b91f9cad | 276 | |
2231d226 WB |
277 | my $err; |
278 | my $country; | |
279 | if ($routes->{gateway4}) { | |
a467ec47 | 280 | log_info("trace country via IPv4"); |
2231d226 WB |
281 | $country = eval { detect_country_tracing_to(4 => '8.8.8.8') }; |
282 | $err = $@ if !$country; | |
283 | } | |
284 | ||
285 | if (!$country && $routes->{gateway6}) { | |
a467ec47 | 286 | log_info("trace country via IPv6"); |
2231d226 WB |
287 | $country = eval { detect_country_tracing_to(6 => '2001:4860:4860::8888') }; |
288 | $err = $@ if !$country; | |
289 | } | |
290 | ||
291 | if (defined($country)) { | |
292 | $output->{country} = $country; | |
293 | } else { | |
294 | warn ($err // "unable to detect country\n"); | |
295 | } | |
296 | ||
297 | return $output; | |
298 | } | |
299 | ||
ea3b7cae TL |
300 | my $_env = undef; |
301 | sub get { | |
302 | my ($k) = @_; | |
303 | $_env = query_installation_environment() if !defined($_env); | |
304 | return defined($k) ? $_env->{$k} : $_env; | |
305 | } | |
306 | ||
2231d226 | 307 | 1; |