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;
65 if (! -e $privkey_file) {
66 my $old_umask = umask(077);
67 run_cmd("ofp-pki req $etc/of0 >&2 2>/dev/null");
68 chmod(0644, $req_file) or die "$req_file: chmod: $!\n";
72 my ($req, $req_fingerprint);
73 if (! -e $cert_file) {
74 open(REQ, '<', $req_file) or die "$req_file: open: $!\n";
75 $req = join('', <REQ>);
77 $req_fingerprint = sha1_hex($req);
84 # User backed up from first dialog box.
88 # Prompt for ports to include in switch.
93 # Validate the chosen ports.
94 my (@netdevs) = split(', ', db_get('netdevs'));
96 # No ports chosen. Disable switch.
97 db_input('no-netdevs');
98 return 'prev' if db_go();
100 } elsif (my (@conf_netdevs) = grep(/IP/, @netdevs)) {
101 # Point out that some ports have configured IP addresses.
102 db_subst('configured-netdevs', 'configured-netdevs',
103 join(', ', @conf_netdevs));
104 db_input('configured-netdevs');
112 # Discovery or in-band or out-of-band controller?
117 return 'skip' if db_get('mode') ne 'discovery';
119 # Notify user that we are going to do discovery.
120 db_input('discover');
121 return 'prev' if db_go();
122 print STDERR "Please wait up to 30 seconds for discovery...\n";
124 # Make sure that there's no running discovery process.
129 open(DISCOVER, '-|', 'ofp-discover --timeout=30 --pidfile '
130 . join(' ', netdev_names()));
133 if (my ($name, $value) = /^([^=]+)=(.*)$/) {
134 if ($value =~ /^"(.*)"$/) {
136 $value =~ s/\\([0-7][0-7][0-7])/chr($1)/ge;
138 $value =~ s/^(0x[[:xdigit:]]+)$/hex($1)/e;
140 $options{$name} = $value;
146 my $vconn = $options{'ofp-controller-vconn'};
147 my $pki_uri = $options{'ofp-pki-uri'};
150 && is_valid_vconn($vconn)
151 && (!is_ssl_vconn($vconn) || defined($pki_uri)));
155 db_input('discovery-failure');
160 return 'skip' if db_get('mode') ne 'discovery';
162 my $vconn = $options{'ofp-controller-vconn'};
163 my $pki_uri = $options{'ofp-pki-uri'};
164 db_subst('discovery-success', 'controller-vconn', $vconn);
165 db_subst('discovery-success',
166 'pki-uri', is_ssl_vconn($vconn) ? $pki_uri : "no PKI in use");
167 db_input('discovery-success');
168 return 'prev' if db_go();
169 db_set('controller-vconn', $vconn);
170 db_set('pki-uri', $pki_uri);
174 return 'skip' if db_get('mode') ne 'in-band';
176 db_input('switch-ip');
177 return 'prev' if db_go();
179 my $ip = db_get('switch-ip');
180 return 'next' if $ip =~ /^dhcp|\d+\.\d+.\d+.\d+$/i;
182 db_input('switch-ip-error');
187 return 'skip' if db_get('mode') eq 'discovery';
189 my $old_vconn = db_get('controller-vconn');
190 db_input('controller-vconn');
191 return 'prev' if db_go();
193 my $vconn = db_get('controller-vconn');
194 if (is_valid_vconn($vconn)) {
195 if ($old_vconn ne $vconn || db_get('pki-uri') eq '') {
196 db_set('pki-uri', pki_host_to_uri($2));
201 db_input('controller-vconn-error');
206 return 'skip' if !ssl_enabled();
207 return 'skip' if -e $cacert_file && -e $cert_file;
210 return 'prev' if db_go();
214 return 'skip' if !ssl_enabled();
215 return 'skip' if -e $cacert_file;
217 my $pki_uri = db_get('pki-uri');
218 if ($pki_uri !~ /:/) {
219 $pki_uri = pki_host_to_uri($pki_uri);
221 # Trim trailing slashes.
224 db_set('pki-uri', $pki_uri);
226 my $url = "$pki_uri/controllerca/cacert.pem";
227 my $response = $ua->get($url, ':content_file' => $cacert_file);
228 if ($response->is_success) {
232 db_subst('fetch-cacert-failed', 'url', $url);
233 db_subst('fetch-cacert-failed', 'error', $response->status_line);
234 db_subst('fetch-cacert-failed', 'pki-uri', $pki_uri);
235 db_input('fetch-cacert-failed');
240 return 'skip' if !ssl_enabled();
241 return 'skip' if -e $cert_file;
244 db_set('send-cert-req', 'yes');
245 db_input('send-cert-req');
246 return 'prev' if db_go();
247 return 'next' if db_get('send-cert-req') eq 'no';
249 my $pki_uri = db_get('pki-uri');
250 my ($pki_base_uri) = $pki_uri =~ m%^([^/]+://[^/]+)/%;
251 my $url = "$pki_base_uri/cgi-bin/ofp-pki-cgi";
252 my $response = $ua->post($url, {'type' => 'switch',
254 return 'next' if $response->is_success;
256 db_subst('send-cert-req-failed', 'url', $url);
257 db_subst('send-cert-req-failed', 'error',
258 $response->status_line);
259 db_subst('send-cert-req-failed', 'pki-uri', $pki_uri);
260 db_input('send-cert-req-failed');
265 return 'skip' if !ssl_enabled();
266 return 'skip' if $cacert_preverified;
268 my ($cacert_fingerprint) = x509_fingerprint($cacert_file);
269 db_subst('verify-controller-ca', 'fingerprint', $cacert_fingerprint);
270 db_input('verify-controller-ca');
271 return 'prev' if db_go();
272 return 'next' if db_get('verify-controller-ca') eq 'yes';
273 unlink($cacert_file);
277 return 'skip' if !ssl_enabled();
278 return 'skip' if -e $cert_file;
281 db_set('fetch-switch-cert', 'yes');
282 db_input('fetch-switch-cert');
283 return 'prev' if db_go();
284 exit(1) if db_get('fetch-switch-cert') eq 'no';
286 my $pki_uri = db_get('pki-uri');
287 my $url = "$pki_uri/switchca/certs/$req_fingerprint-cert.pem";
288 my $response = $ua->get($url, ':content_file' => $cert_file);
289 if ($response->is_success) {
293 db_subst('fetch-switch-cert-failed', 'url', $url);
294 db_subst('fetch-switch-cert-failed', 'error',
295 $response->status_line);
296 db_subst('fetch-switch-cert-failed', 'pki-uri', $pki_uri);
297 db_input('fetch-switch-cert-failed');
302 db_input('complete');
314 my $ret = &{$states[$state]}();
315 $ret = db_go() ? 'prev' : 'next' if !defined $ret;
316 if ($ret eq 'next') {
318 } elsif ($ret eq 'prev') {
320 } elsif ($ret eq 'skip') {
322 } elsif ($ret eq 'done') {
325 die "unknown ret $ret";
327 $state += $direction;
331 $config{NETDEVS} = join(' ', netdev_names());
332 $config{MODE} = db_get('mode');
333 if (db_get('mode') eq 'in-band') {
334 $config{SWITCH_IP} = db_get('switch-ip');
336 if (db_get('mode') ne 'discovery') {
337 $config{CONTROLLER} = db_get('controller-vconn');
339 $config{PRIVKEY} = $privkey_file;
340 $config{CERT} = $cert_file;
341 $config{CACERT} = $cacert_file;
342 save_config($default, %config);
344 dup2(2, 1); # Get stdout back.
346 system("/etc/init.d/openflow-switch start");
349 return is_ssl_vconn(db_get('controller-vconn'));
353 my ($question, $key, $value) = @_;
354 $question = "$debconf_owner/$question";
355 my ($ret, $seen) = subst($question, $key, $value);
356 if ($ret && $ret != 30) {
357 die "Error substituting $value for $key in debconf question "
358 . "$question: $seen";
363 my ($question, $value) = @_;
364 $question = "$debconf_owner/$question";
365 my ($ret, $seen) = set($question, $value);
366 if ($ret && $ret != 30) {
367 die "Error setting debconf question $question to $value: $seen";
373 $question = "$debconf_owner/$question";
374 my ($ret, $seen) = get($question);
376 die "Error getting debconf question $question answer: $seen";
382 my ($question, $flag, $value) = @_;
383 $question = "$debconf_owner/$question";
384 my ($ret, $seen) = fset($question, $flag, $value);
385 if ($ret && $ret != 30) {
386 die "Error setting debconf question $question flag $flag to $value: "
392 my ($question, $flag) = @_;
393 $question = "$debconf_owner/$question";
394 my ($ret, $seen) = fget($question, $flag);
396 die "Error getting debconf question $question flag $flag: $seen";
403 db_fset($question, "seen", "false");
405 $question = "$debconf_owner/$question";
406 my ($ret, $seen) = input('high', $question);
407 if ($ret && $ret != 30) {
408 die "Error requesting debconf question $question: $seen";
414 my ($ret, $seen) = go();
415 if (!defined($ret)) {
416 exit(1); # Cancel button was pushed.
418 if ($ret && $ret != 30) {
419 die "Error asking debconf questions: $seen";
426 return if system($cmd) == 0;
429 die "$cmd: failed to execute: $!\n";
431 die sprintf("$cmd: child died with signal %d, %s coredump\n",
432 ($? & 127), ($? & 128) ? 'with' : 'without');
434 die sprintf("$cmd: child exited with value %d\n", $? >> 8);
438 sub x509_fingerprint {
440 my $cmd = "openssl x509 -noout -in $file -fingerprint";
441 open(OPENSSL, '-|', $cmd) or die "$cmd: failed to execute: $!\n";
442 my $line = <OPENSSL>;
444 my ($fingerprint) = $line =~ /SHA1 Fingerprint=(.*)/;
445 return $line if !defined $fingerprint;
446 $fingerprint =~ s/://g;
451 my ($netdev, %netdevs);
452 open(IFCONFIG, "/sbin/ifconfig -a|") or die "ifconfig failed: $!";
454 if (my ($nd) = /^([^\s]+)/) {
456 $netdevs{$netdev} = "$netdev";
457 if (my ($hwaddr) = /HWaddr (\S+)/) {
458 $netdevs{$netdev} .= " (MAC: $hwaddr)";
460 } elsif (my ($ip4) = /^\s*inet addr:(\S+)/) {
461 $netdevs{$netdev} .= " (IP: $ip4)";
462 } elsif (my ($ip6) = /^\s*inet6 addr:(\S+)/) {
463 $netdevs{$netdev} .= " (IPv6: $ip6)";
466 foreach my $nd (keys(%netdevs)) {
467 delete $netdevs{$nd} if $nd eq 'lo' || $nd =~ /^wmaster/;
475 my ($cmd) = "set -a && . $file && env";
476 if (!open(VARS, '-|', $cmd)) {
477 print STDERR "$cmd: failed to execute: $!\n";
482 my ($var, $value) = /^([^=]+)=(.*)$/ or next;
483 $config{$var} = $value;
493 } elsif (m&^[-a-zA-Z0-9:./%^_+,]*$&) {
502 my ($var, $value) = @_;
503 return $var . '=' . shell_escape($value);
507 my ($file, %config) = @_;
509 if (open(FILE, '<', $file)) {
515 # Replace all existing variable assignments.
516 for (my ($i) = 0; $i <= $#lines; $i++) {
517 local $_ = $lines[$i];
518 my ($var, $value) = /^\s*([^=#]+)=(.*)$/ or next;
519 if (exists($config{$var})) {
520 $lines[$i] = shell_assign($var, $config{$var});
521 delete $config{$var};
523 $lines[$i] = "#$lines[$i]";
527 # Find a place to put any remaining variable assignments.
529 for my $var (keys(%config)) {
530 my $assign = shell_assign($var, $config{$var});
532 # Replace the last commented-out variable assignment to $var, if any.
533 for (my ($i) = $#lines; $i >= 0; $i--) {
534 local $_ = $lines[$i];
535 if (/^\s*#\s*$var=/) {
536 $lines[$i] = $assign;
541 # Find a place to add the var: after the final commented line
542 # just after a line that contains "$var:".
543 for (my ($i) = 0; $i <= $#lines; $i++) {
544 if ($lines[$i] =~ /^\s*#\s*$var:/) {
545 for (my ($j) = $i + 1; $j <= $#lines; $j++) {
546 if ($lines[$j] !~ /^\s*#/) {
547 splice(@lines, $j, 0, $assign);
555 push(@lines, $assign);
558 open(NEWFILE, '>', "$file.tmp") or die "$file.tmp: create: $!\n";
559 print NEWFILE join('', map("$_\n", @lines));
561 rename("$file.tmp", $file) or die "$file.tmp: rename to $file: $!\n";
564 sub pki_host_to_uri {
566 return "http://$pki_host/openflow/pki";
569 sub kill_ofp_discover {
570 # Delegate this to a subprocess because there is no portable way
571 # to invoke fcntl(F_GETLK) from Perl.
572 system("ofp-kill --force $ofp_discover_pidfile");
576 return map(/^(\S+)/, split(', ', db_get('netdevs')));
581 return scalar($vconn =~ /^(tcp|ssl):([^:]+)(:.*)?/);
586 return scalar($vconn =~ /^ssl:/);