4 use Debconf::Client::ConfModule ':all';
7 use Digest::SHA1 'sha1_hex';
11 # XXX should support configuring SWITCH_NETMASK and SWITCH_GATEWAY
12 # when the mode is in-band.
14 my $debconf_owner = 'openvswitch-switch';
16 my $default = '/etc/default/openvswitch-switch';
17 my $template = '/usr/share/openvswitch/switch/default.template';
18 my $etc = '/etc/openvswitch';
19 my $rundir = '/var/run/openvswitch';
20 my $privkey_file = "$etc/of0-privkey.pem";
21 my $req_file = "$etc/of0-req.pem";
22 my $cert_file = "$etc/of0-cert.pem";
23 my $cacert_file = "$etc/cacert.pem";
24 my $ovs_discover_pidfile = "$rundir/ovs-discover.pid";
26 my $ua = LWP::UserAgent->new;
30 system("/etc/init.d/openvswitch-switch stop 1>&2");
35 title('Open vSwitch Switch Setup');
37 my (%netdevs) = find_netdevs();
38 db_subst('netdevs', 'choices',
39 join(', ', map($netdevs{$_}, sort(keys(%netdevs)))));
40 db_set('netdevs', join(', ', grep(!/IP/, values(%netdevs))));
44 %oldconfig = load_config($default);
48 db_set('netdevs', join(', ', map($netdevs{$_},
49 grep(exists $netdevs{$_}, split))))
53 $_ eq 'in-band' || $_ eq 'out-of-band' ? $_ : 'discovery')
55 SWITCH_IP => sub { db_set('switch-ip', $_) },
56 CONTROLLER => sub { db_set('controller-vconn', $_) },
57 PRIVKEY => sub { $privkey_file = $_ },
58 CERT => sub { $cert_file = $_ },
59 CACERT => sub { $cacert_file = $_ },
62 for my $key (keys(%map)) {
63 local $_ = $oldconfig{$key};
64 &{$map{$key}}() if defined && !/^\s*$/;
66 } elsif (-e $template) {
67 %oldconfig = load_config($template);
70 my $cacert_preverified = -e $cacert_file;
71 my ($req, $req_fingerprint);
77 # User backed up from first dialog box.
81 # Prompt for ports to include in switch.
86 # Validate the chosen ports.
87 my (@netdevs) = split(', ', db_get('netdevs'));
89 # No ports chosen. Disable switch.
90 db_input('no-netdevs');
91 return 'prev' if db_go();
93 } elsif (my (@conf_netdevs) = grep(/IP/, @netdevs)) {
94 # Point out that some ports have configured IP addresses.
95 db_subst('configured-netdevs', 'configured-netdevs',
96 join(', ', @conf_netdevs));
97 db_input('configured-netdevs');
105 # Discovery or in-band or out-of-band controller?
110 return 'skip' if db_get('mode') ne 'discovery';
112 # Notify user that we are going to do discovery.
113 db_input('discover');
114 return 'prev' if db_go();
115 print STDERR "Please wait up to 30 seconds for discovery...\n";
117 # Make sure that there's no running discovery process.
122 open(DISCOVER, '-|', 'ovs-discover --timeout=30 --pidfile '
123 . join(' ', netdev_names()));
126 if (my ($name, $value) = /^([^=]+)=(.*)$/) {
127 if ($value =~ /^"(.*)"$/) {
129 $value =~ s/\\([0-7][0-7][0-7])/chr($1)/ge;
131 $value =~ s/^(0x[[:xdigit:]]+)$/hex($1)/e;
132 $value = '' if $value eq 'empty';
133 next if $value eq 'null'; # Shouldn't happen.
135 $options{$name} = $value;
141 my $vconn = $options{'ovs-controller-vconn'};
142 my $pki_uri = $options{'ovs-pki-uri'};
145 && is_valid_vconn($vconn)
146 && (!is_ssl_vconn($vconn) || defined($pki_uri)));
150 db_input('discovery-failure');
155 return 'skip' if db_get('mode') ne 'discovery';
157 my $vconn = $options{'ovs-controller-vconn'};
158 my $pki_uri = $options{'ovs-pki-uri'};
159 db_subst('discovery-success', 'controller-vconn', $vconn);
160 db_subst('discovery-success',
161 'pki-uri', is_ssl_vconn($vconn) ? $pki_uri : "no PKI in use");
162 db_input('discovery-success');
163 return 'prev' if db_go();
164 db_set('controller-vconn', $vconn);
165 db_set('pki-uri', $pki_uri);
169 return 'skip' if db_get('mode') ne 'in-band';
171 db_input('switch-ip');
172 return 'prev' if db_go();
174 my $ip = db_get('switch-ip');
175 return 'next' if $ip =~ /^dhcp|\d+\.\d+.\d+.\d+$/i;
177 db_input('switch-ip-error');
182 return 'skip' if db_get('mode') eq 'discovery';
184 my $old_vconn = db_get('controller-vconn');
185 db_input('controller-vconn');
186 return 'prev' if db_go();
188 my $vconn = db_get('controller-vconn');
189 if (is_valid_vconn($vconn)) {
190 if ($old_vconn ne $vconn || db_get('pki-uri') eq '') {
191 db_set('pki-uri', pki_host_to_uri($2));
196 db_input('controller-vconn-error');
201 return 'skip' if !ssl_enabled();
203 if (! -e $privkey_file) {
204 my $old_umask = umask(077);
205 run_cmd("ovs-pki req $etc/of0 >&2 2>/dev/null");
206 chmod(0644, $req_file) or die "$req_file: chmod: $!\n";
210 if (! -e $cert_file) {
211 open(REQ, '<', $req_file) or die "$req_file: open: $!\n";
212 $req = join('', <REQ>);
214 $req_fingerprint = sha1_hex($req);
219 return 'skip' if !ssl_enabled();
220 return 'skip' if -e $cacert_file && -e $cert_file;
223 return 'prev' if db_go();
227 return 'skip' if !ssl_enabled();
228 return 'skip' if -e $cacert_file;
230 my $pki_uri = db_get('pki-uri');
231 if ($pki_uri !~ /:/) {
232 $pki_uri = pki_host_to_uri($pki_uri);
234 # Trim trailing slashes.
237 db_set('pki-uri', $pki_uri);
239 my $url = "$pki_uri/controllerca/cacert.pem";
240 my $response = $ua->get($url, ':content_file' => $cacert_file);
241 if ($response->is_success) {
245 db_subst('fetch-cacert-failed', 'url', $url);
246 db_subst('fetch-cacert-failed', 'error', $response->status_line);
247 db_subst('fetch-cacert-failed', 'pki-uri', $pki_uri);
248 db_input('fetch-cacert-failed');
253 return 'skip' if !ssl_enabled();
254 return 'skip' if -e $cert_file;
257 db_set('send-cert-req', 'true');
258 db_input('send-cert-req');
259 return 'prev' if db_go();
260 return 'next' if db_get('send-cert-req') eq 'false';
262 my $pki_uri = db_get('pki-uri');
263 my ($pki_base_uri) = $pki_uri =~ m%^([^/]+://[^/]+)/%;
264 my $url = "$pki_base_uri/cgi-bin/ovs-pki-cgi";
265 my $response = $ua->post($url, {'type' => 'switch',
267 return 'next' if $response->is_success;
269 db_subst('send-cert-req-failed', 'url', $url);
270 db_subst('send-cert-req-failed', 'error',
271 $response->status_line);
272 db_subst('send-cert-req-failed', 'pki-uri', $pki_uri);
273 db_input('send-cert-req-failed');
278 return 'skip' if !ssl_enabled();
279 return 'skip' if $cacert_preverified;
281 my ($cacert_fingerprint) = x509_fingerprint($cacert_file);
282 db_subst('verify-controller-ca', 'fingerprint', $cacert_fingerprint);
283 db_input('verify-controller-ca');
284 return 'prev' if db_go();
285 return 'next' if db_get('verify-controller-ca') eq 'true';
286 unlink($cacert_file);
290 return 'skip' if !ssl_enabled();
291 return 'skip' if -e $cert_file;
294 db_set('fetch-switch-cert', 'true');
295 db_input('fetch-switch-cert');
296 return 'prev' if db_go();
297 exit(1) if db_get('fetch-switch-cert') eq 'false';
299 my $pki_uri = db_get('pki-uri');
300 my $url = "$pki_uri/switchca/certs/$req_fingerprint-cert.pem";
301 my $response = $ua->get($url, ':content_file' => $cert_file);
302 if ($response->is_success) {
306 db_subst('fetch-switch-cert-failed', 'url', $url);
307 db_subst('fetch-switch-cert-failed', 'error',
308 $response->status_line);
309 db_subst('fetch-switch-cert-failed', 'pki-uri', $pki_uri);
310 db_input('fetch-switch-cert-failed');
315 db_input('complete');
327 my $ret = &{$states[$state]}();
328 $ret = db_go() ? 'prev' : 'next' if !defined $ret;
329 if ($ret eq 'next') {
331 } elsif ($ret eq 'prev') {
333 } elsif ($ret eq 'skip') {
335 } elsif ($ret eq 'done') {
338 die "unknown ret $ret";
340 $state += $direction;
343 my %config = %oldconfig;
344 $config{NETDEVS} = join(' ', netdev_names());
345 $config{MODE} = db_get('mode');
346 if (db_get('mode') eq 'in-band') {
347 $config{SWITCH_IP} = db_get('switch-ip');
349 if (db_get('mode') ne 'discovery') {
350 $config{CONTROLLER} = db_get('controller-vconn');
352 $config{PRIVKEY} = $privkey_file;
353 $config{CERT} = $cert_file;
354 $config{CACERT} = $cacert_file;
355 save_config($default, %config);
357 dup2(2, 1); # Get stdout back.
359 system("/etc/init.d/openvswitch-switch start");
362 return is_ssl_vconn(db_get('controller-vconn'));
366 my ($question, $key, $value) = @_;
367 $question = "$debconf_owner/$question";
368 my ($ret, $seen) = subst($question, $key, $value);
369 if ($ret && $ret != 30) {
370 die "Error substituting $value for $key in debconf question "
371 . "$question: $seen";
376 my ($question, $value) = @_;
377 $question = "$debconf_owner/$question";
378 my ($ret, $seen) = set($question, $value);
379 if ($ret && $ret != 30) {
380 die "Error setting debconf question $question to $value: $seen";
386 $question = "$debconf_owner/$question";
387 my ($ret, $seen) = get($question);
389 die "Error getting debconf question $question answer: $seen";
395 my ($question, $flag, $value) = @_;
396 $question = "$debconf_owner/$question";
397 my ($ret, $seen) = fset($question, $flag, $value);
398 if ($ret && $ret != 30) {
399 die "Error setting debconf question $question flag $flag to $value: "
405 my ($question, $flag) = @_;
406 $question = "$debconf_owner/$question";
407 my ($ret, $seen) = fget($question, $flag);
409 die "Error getting debconf question $question flag $flag: $seen";
416 db_fset($question, "seen", "false");
418 $question = "$debconf_owner/$question";
419 my ($ret, $seen) = input('high', $question);
420 if ($ret && $ret != 30) {
421 die "Error requesting debconf question $question: $seen";
427 my ($ret, $seen) = go();
428 if (!defined($ret)) {
429 exit(1); # Cancel button was pushed.
431 if ($ret && $ret != 30) {
432 die "Error asking debconf questions: $seen";
439 return if system($cmd) == 0;
442 die "$cmd: failed to execute: $!\n";
444 die sprintf("$cmd: child died with signal %d, %s coredump\n",
445 ($? & 127), ($? & 128) ? 'with' : 'without');
447 die sprintf("$cmd: child exited with value %d\n", $? >> 8);
451 sub x509_fingerprint {
453 my $cmd = "openssl x509 -noout -in $file -fingerprint";
454 open(OPENSSL, '-|', $cmd) or die "$cmd: failed to execute: $!\n";
455 my $line = <OPENSSL>;
457 my ($fingerprint) = $line =~ /SHA1 Fingerprint=(.*)/;
458 return $line if !defined $fingerprint;
459 $fingerprint =~ s/://g;
464 my ($netdev, %netdevs);
465 open(IFCONFIG, "/sbin/ifconfig -a|") or die "ifconfig failed: $!";
467 if (my ($nd) = /^([^\s]+)/) {
469 $netdevs{$netdev} = "$netdev";
470 if (my ($hwaddr) = /HWaddr (\S+)/) {
471 $netdevs{$netdev} .= " (MAC: $hwaddr)";
473 } elsif (my ($ip4) = /^\s*inet addr:(\S+)/) {
474 $netdevs{$netdev} .= " (IP: $ip4)";
475 } elsif (my ($ip6) = /^\s*inet6 addr:(\S+)/) {
476 $netdevs{$netdev} .= " (IPv6: $ip6)";
479 foreach my $nd (keys(%netdevs)) {
480 delete $netdevs{$nd} if $nd eq 'lo' || $nd =~ /^wmaster/;
489 # Get the list of the variables that the shell sets automatically.
490 my (%auto_vars) = read_vars("set -a && env");
492 # Get the variables from $default.
493 my (%config) = read_vars("set -a && . '$default' && env");
496 delete @config{keys %auto_vars};
504 if (!open(VARS, '-|', $cmd)) {
505 print STDERR "$cmd: failed to execute: $!\n";
510 my ($var, $value) = /^([^=]+)=(.*)$/ or next;
511 $config{$var} = $value;
521 } elsif (m&^[-a-zA-Z0-9:./%^_+,]*$&) {
530 my ($var, $value) = @_;
531 return $var . '=' . shell_escape($value);
535 my ($file, %config) = @_;
537 if (open(FILE, '<', $file)) {
543 # Replace all existing variable assignments.
544 for (my ($i) = 0; $i <= $#lines; $i++) {
545 local $_ = $lines[$i];
546 my ($var, $value) = /^\s*([^=#]+)=(.*)$/ or next;
547 if (exists($config{$var})) {
548 $lines[$i] = shell_assign($var, $config{$var});
549 delete $config{$var};
551 $lines[$i] = "#$lines[$i]";
555 # Find a place to put any remaining variable assignments.
557 for my $var (keys(%config)) {
558 my $assign = shell_assign($var, $config{$var});
560 # Replace the last commented-out variable assignment to $var, if any.
561 for (my ($i) = $#lines; $i >= 0; $i--) {
562 local $_ = $lines[$i];
563 if (/^\s*#\s*$var=/) {
564 $lines[$i] = $assign;
569 # Find a place to add the var: after the final commented line
570 # just after a line that contains "$var:".
571 for (my ($i) = 0; $i <= $#lines; $i++) {
572 if ($lines[$i] =~ /^\s*#\s*$var:/) {
573 for (my ($j) = $i + 1; $j <= $#lines; $j++) {
574 if ($lines[$j] !~ /^\s*#/) {
575 splice(@lines, $j, 0, $assign);
583 push(@lines, $assign);
586 open(NEWFILE, '>', "$file.tmp") or die "$file.tmp: create: $!\n";
587 print NEWFILE join('', map("$_\n", @lines));
589 rename("$file.tmp", $file) or die "$file.tmp: rename to $file: $!\n";
592 sub pki_host_to_uri {
594 return "http://$pki_host/openvswitch/pki";
597 sub kill_ovs_discover {
598 # Delegate this to a subprocess because there is no portable way
599 # to invoke fcntl(F_GETLK) from Perl.
600 system("ovs-kill --force $ovs_discover_pidfile");
604 return map(/^(\S+)/, split(', ', db_get('netdevs')));
609 return scalar($vconn =~ /^(tcp|ssl):([^:]+)(:.*)?/);
614 return scalar($vconn =~ /^ssl:/);