4 use Debconf::Client::ConfModule ':all';
7 use Digest::SHA1 'sha1_hex';
11 my $debconf_owner = 'openflow-switch';
13 my $default = '/etc/default/openflow-switch';
14 my $etc = '/etc/openflow-switch';
15 my $rundir = '/var/run';
16 my $privkey_file = "$etc/of0-privkey.pem";
17 my $req_file = "$etc/of0-req.pem";
18 my $cert_file = "$etc/of0-cert.pem";
19 my $cacert_file = "$etc/cacert.pem";
20 my $ofp_discover_pidfile = "$rundir/ofp-discover.pid";
22 my $ua = LWP::UserAgent->new;
26 system("/etc/init.d/openflow-switch stop 1>&2");
31 title('OpenFlow Switch Setup');
33 my (%netdevs) = find_netdevs();
34 db_subst('netdevs', 'choices',
35 join(', ', map($netdevs{$_}, sort(keys(%netdevs)))));
36 db_set('netdevs', join(', ', grep(!/IP/, values(%netdevs))));
39 my (%config) = load_config($default);
43 db_set('netdevs', join(', ', map($netdevs{$_},
44 grep(exists $netdevs{$_}, split))))
48 $_ eq 'in-band' || $_ eq 'out-of-band' ? $_ : 'discovery')
50 SWITCH_IP => sub { db_set('switch-ip', $_) },
51 CONTROLLER => sub { db_set('controller-vconn', $_) },
52 PRIVKEY => sub { $privkey_file = $_ },
53 CERT => sub { $cert_file = $_ },
54 CACERT => sub { $cacert_file = $_ },
57 for my $key (keys(%map)) {
58 local $_ = $config{$key};
59 &{$map{$key}}() if defined && !/^\s*$/;
63 my $cacert_preverified = -e $cacert_file;
64 my ($req, $req_fingerprint);
70 # User backed up from first dialog box.
74 # Prompt for ports to include in switch.
79 # Validate the chosen ports.
80 my (@netdevs) = split(', ', db_get('netdevs'));
82 # No ports chosen. Disable switch.
83 db_input('no-netdevs');
84 return 'prev' if db_go();
86 } elsif (my (@conf_netdevs) = grep(/IP/, @netdevs)) {
87 # Point out that some ports have configured IP addresses.
88 db_subst('configured-netdevs', 'configured-netdevs',
89 join(', ', @conf_netdevs));
90 db_input('configured-netdevs');
98 # Discovery or in-band or out-of-band controller?
103 return 'skip' if db_get('mode') ne 'discovery';
105 # Notify user that we are going to do discovery.
106 db_input('discover');
107 return 'prev' if db_go();
108 print STDERR "Please wait up to 30 seconds for discovery...\n";
110 # Make sure that there's no running discovery process.
115 open(DISCOVER, '-|', 'ofp-discover --timeout=30 --pidfile '
116 . join(' ', netdev_names()));
119 if (my ($name, $value) = /^([^=]+)=(.*)$/) {
120 if ($value =~ /^"(.*)"$/) {
122 $value =~ s/\\([0-7][0-7][0-7])/chr($1)/ge;
124 $value =~ s/^(0x[[:xdigit:]]+)$/hex($1)/e;
126 $options{$name} = $value;
132 my $vconn = $options{'ofp-controller-vconn'};
133 my $pki_uri = $options{'ofp-pki-uri'};
136 && is_valid_vconn($vconn)
137 && (!is_ssl_vconn($vconn) || defined($pki_uri)));
141 db_input('discovery-failure');
146 return 'skip' if db_get('mode') ne 'discovery';
148 my $vconn = $options{'ofp-controller-vconn'};
149 my $pki_uri = $options{'ofp-pki-uri'};
150 db_subst('discovery-success', 'controller-vconn', $vconn);
151 db_subst('discovery-success',
152 'pki-uri', is_ssl_vconn($vconn) ? $pki_uri : "no PKI in use");
153 db_input('discovery-success');
154 return 'prev' if db_go();
155 db_set('controller-vconn', $vconn);
156 db_set('pki-uri', $pki_uri);
160 return 'skip' if db_get('mode') ne 'in-band';
162 db_input('switch-ip');
163 return 'prev' if db_go();
165 my $ip = db_get('switch-ip');
166 return 'next' if $ip =~ /^dhcp|\d+\.\d+.\d+.\d+$/i;
168 db_input('switch-ip-error');
173 return 'skip' if db_get('mode') eq 'discovery';
175 my $old_vconn = db_get('controller-vconn');
176 db_input('controller-vconn');
177 return 'prev' if db_go();
179 my $vconn = db_get('controller-vconn');
180 if (is_valid_vconn($vconn)) {
181 if ($old_vconn ne $vconn || db_get('pki-uri') eq '') {
182 db_set('pki-uri', pki_host_to_uri($2));
187 db_input('controller-vconn-error');
192 return 'skip' if !ssl_enabled();
194 if (! -e $privkey_file) {
195 my $old_umask = umask(077);
196 run_cmd("ofp-pki req $etc/of0 >&2 2>/dev/null");
197 chmod(0644, $req_file) or die "$req_file: chmod: $!\n";
201 if (! -e $cert_file) {
202 open(REQ, '<', $req_file) or die "$req_file: open: $!\n";
203 $req = join('', <REQ>);
205 $req_fingerprint = sha1_hex($req);
210 return 'skip' if !ssl_enabled();
211 return 'skip' if -e $cacert_file && -e $cert_file;
214 return 'prev' if db_go();
218 return 'skip' if !ssl_enabled();
219 return 'skip' if -e $cacert_file;
221 my $pki_uri = db_get('pki-uri');
222 if ($pki_uri !~ /:/) {
223 $pki_uri = pki_host_to_uri($pki_uri);
225 # Trim trailing slashes.
228 db_set('pki-uri', $pki_uri);
230 my $url = "$pki_uri/controllerca/cacert.pem";
231 my $response = $ua->get($url, ':content_file' => $cacert_file);
232 if ($response->is_success) {
236 db_subst('fetch-cacert-failed', 'url', $url);
237 db_subst('fetch-cacert-failed', 'error', $response->status_line);
238 db_subst('fetch-cacert-failed', 'pki-uri', $pki_uri);
239 db_input('fetch-cacert-failed');
244 return 'skip' if !ssl_enabled();
245 return 'skip' if -e $cert_file;
248 db_set('send-cert-req', 'yes');
249 db_input('send-cert-req');
250 return 'prev' if db_go();
251 return 'next' if db_get('send-cert-req') eq 'no';
253 my $pki_uri = db_get('pki-uri');
254 my ($pki_base_uri) = $pki_uri =~ m%^([^/]+://[^/]+)/%;
255 my $url = "$pki_base_uri/cgi-bin/ofp-pki-cgi";
256 my $response = $ua->post($url, {'type' => 'switch',
258 return 'next' if $response->is_success;
260 db_subst('send-cert-req-failed', 'url', $url);
261 db_subst('send-cert-req-failed', 'error',
262 $response->status_line);
263 db_subst('send-cert-req-failed', 'pki-uri', $pki_uri);
264 db_input('send-cert-req-failed');
269 return 'skip' if !ssl_enabled();
270 return 'skip' if $cacert_preverified;
272 my ($cacert_fingerprint) = x509_fingerprint($cacert_file);
273 db_subst('verify-controller-ca', 'fingerprint', $cacert_fingerprint);
274 db_input('verify-controller-ca');
275 return 'prev' if db_go();
276 return 'next' if db_get('verify-controller-ca') eq 'yes';
277 unlink($cacert_file);
281 return 'skip' if !ssl_enabled();
282 return 'skip' if -e $cert_file;
285 db_set('fetch-switch-cert', 'yes');
286 db_input('fetch-switch-cert');
287 return 'prev' if db_go();
288 exit(1) if db_get('fetch-switch-cert') eq 'no';
290 my $pki_uri = db_get('pki-uri');
291 my $url = "$pki_uri/switchca/certs/$req_fingerprint-cert.pem";
292 my $response = $ua->get($url, ':content_file' => $cert_file);
293 if ($response->is_success) {
297 db_subst('fetch-switch-cert-failed', 'url', $url);
298 db_subst('fetch-switch-cert-failed', 'error',
299 $response->status_line);
300 db_subst('fetch-switch-cert-failed', 'pki-uri', $pki_uri);
301 db_input('fetch-switch-cert-failed');
306 db_input('complete');
318 my $ret = &{$states[$state]}();
319 $ret = db_go() ? 'prev' : 'next' if !defined $ret;
320 if ($ret eq 'next') {
322 } elsif ($ret eq 'prev') {
324 } elsif ($ret eq 'skip') {
326 } elsif ($ret eq 'done') {
329 die "unknown ret $ret";
331 $state += $direction;
335 $config{NETDEVS} = join(' ', netdev_names());
336 $config{MODE} = db_get('mode');
337 if (db_get('mode') eq 'in-band') {
338 $config{SWITCH_IP} = db_get('switch-ip');
340 if (db_get('mode') ne 'discovery') {
341 $config{CONTROLLER} = db_get('controller-vconn');
343 $config{PRIVKEY} = $privkey_file;
344 $config{CERT} = $cert_file;
345 $config{CACERT} = $cacert_file;
346 save_config($default, %config);
348 dup2(2, 1); # Get stdout back.
350 system("/etc/init.d/openflow-switch start");
353 return is_ssl_vconn(db_get('controller-vconn'));
357 my ($question, $key, $value) = @_;
358 $question = "$debconf_owner/$question";
359 my ($ret, $seen) = subst($question, $key, $value);
360 if ($ret && $ret != 30) {
361 die "Error substituting $value for $key in debconf question "
362 . "$question: $seen";
367 my ($question, $value) = @_;
368 $question = "$debconf_owner/$question";
369 my ($ret, $seen) = set($question, $value);
370 if ($ret && $ret != 30) {
371 die "Error setting debconf question $question to $value: $seen";
377 $question = "$debconf_owner/$question";
378 my ($ret, $seen) = get($question);
380 die "Error getting debconf question $question answer: $seen";
386 my ($question, $flag, $value) = @_;
387 $question = "$debconf_owner/$question";
388 my ($ret, $seen) = fset($question, $flag, $value);
389 if ($ret && $ret != 30) {
390 die "Error setting debconf question $question flag $flag to $value: "
396 my ($question, $flag) = @_;
397 $question = "$debconf_owner/$question";
398 my ($ret, $seen) = fget($question, $flag);
400 die "Error getting debconf question $question flag $flag: $seen";
407 db_fset($question, "seen", "false");
409 $question = "$debconf_owner/$question";
410 my ($ret, $seen) = input('high', $question);
411 if ($ret && $ret != 30) {
412 die "Error requesting debconf question $question: $seen";
418 my ($ret, $seen) = go();
419 if (!defined($ret)) {
420 exit(1); # Cancel button was pushed.
422 if ($ret && $ret != 30) {
423 die "Error asking debconf questions: $seen";
430 return if system($cmd) == 0;
433 die "$cmd: failed to execute: $!\n";
435 die sprintf("$cmd: child died with signal %d, %s coredump\n",
436 ($? & 127), ($? & 128) ? 'with' : 'without');
438 die sprintf("$cmd: child exited with value %d\n", $? >> 8);
442 sub x509_fingerprint {
444 my $cmd = "openssl x509 -noout -in $file -fingerprint";
445 open(OPENSSL, '-|', $cmd) or die "$cmd: failed to execute: $!\n";
446 my $line = <OPENSSL>;
448 my ($fingerprint) = $line =~ /SHA1 Fingerprint=(.*)/;
449 return $line if !defined $fingerprint;
450 $fingerprint =~ s/://g;
455 my ($netdev, %netdevs);
456 open(IFCONFIG, "/sbin/ifconfig -a|") or die "ifconfig failed: $!";
458 if (my ($nd) = /^([^\s]+)/) {
460 $netdevs{$netdev} = "$netdev";
461 if (my ($hwaddr) = /HWaddr (\S+)/) {
462 $netdevs{$netdev} .= " (MAC: $hwaddr)";
464 } elsif (my ($ip4) = /^\s*inet addr:(\S+)/) {
465 $netdevs{$netdev} .= " (IP: $ip4)";
466 } elsif (my ($ip6) = /^\s*inet6 addr:(\S+)/) {
467 $netdevs{$netdev} .= " (IPv6: $ip6)";
470 foreach my $nd (keys(%netdevs)) {
471 delete $netdevs{$nd} if $nd eq 'lo' || $nd =~ /^wmaster/;
479 my ($cmd) = "set -a && . $file && env";
480 if (!open(VARS, '-|', $cmd)) {
481 print STDERR "$cmd: failed to execute: $!\n";
486 my ($var, $value) = /^([^=]+)=(.*)$/ or next;
487 $config{$var} = $value;
497 } elsif (m&^[-a-zA-Z0-9:./%^_+,]*$&) {
506 my ($var, $value) = @_;
507 return $var . '=' . shell_escape($value);
511 my ($file, %config) = @_;
513 if (open(FILE, '<', $file)) {
519 # Replace all existing variable assignments.
520 for (my ($i) = 0; $i <= $#lines; $i++) {
521 local $_ = $lines[$i];
522 my ($var, $value) = /^\s*([^=#]+)=(.*)$/ or next;
523 if (exists($config{$var})) {
524 $lines[$i] = shell_assign($var, $config{$var});
525 delete $config{$var};
527 $lines[$i] = "#$lines[$i]";
531 # Find a place to put any remaining variable assignments.
533 for my $var (keys(%config)) {
534 my $assign = shell_assign($var, $config{$var});
536 # Replace the last commented-out variable assignment to $var, if any.
537 for (my ($i) = $#lines; $i >= 0; $i--) {
538 local $_ = $lines[$i];
539 if (/^\s*#\s*$var=/) {
540 $lines[$i] = $assign;
545 # Find a place to add the var: after the final commented line
546 # just after a line that contains "$var:".
547 for (my ($i) = 0; $i <= $#lines; $i++) {
548 if ($lines[$i] =~ /^\s*#\s*$var:/) {
549 for (my ($j) = $i + 1; $j <= $#lines; $j++) {
550 if ($lines[$j] !~ /^\s*#/) {
551 splice(@lines, $j, 0, $assign);
559 push(@lines, $assign);
562 open(NEWFILE, '>', "$file.tmp") or die "$file.tmp: create: $!\n";
563 print NEWFILE join('', map("$_\n", @lines));
565 rename("$file.tmp", $file) or die "$file.tmp: rename to $file: $!\n";
568 sub pki_host_to_uri {
570 return "http://$pki_host/openflow/pki";
573 sub kill_ofp_discover {
574 # Delegate this to a subprocess because there is no portable way
575 # to invoke fcntl(F_GETLK) from Perl.
576 system("ofp-kill --force $ofp_discover_pidfile");
580 return map(/^(\S+)/, split(', ', db_get('netdevs')));
585 return scalar($vconn =~ /^(tcp|ssl):([^:]+)(:.*)?/);
590 return scalar($vconn =~ /^ssl:/);