source: fedkit/fed-tun.pl @ 27b6aea

axis_examplecompt_changesinfo-opsversion-1.30version-2.00version-3.01version-3.02
Last change on this file since 27b6aea was 27b6aea, checked in by Ted Faber <faber@…>, 16 years ago

seer private pubsub support

  • Property mode set to 100755
File size: 12.3 KB
Line 
1#!/usr/bin/perl -w
2
3# Kevin Lahey, lahey@isi.edu
4# July 11, 2007
5
6# Set up ssh tunnel infrastructure for federation:
7#
8# * Parse the configuration file provided.
9#
10# * Figure out whether we're the initiator or the reciever;  if we're
11#   the receiver, we just need to set up ssh keys and exit.
12#
13# * Pick out the experimental interface, remove the IP address
14#
15# * Create a layer 2 ssh tunnel, set up bridging, and hang loose.
16#
17
18use strict;
19use Getopt::Std;
20use POSIX qw(strftime);
21use Sys::Hostname;
22use IO::File;
23
24my $IFCONFIG = "/sbin/ifconfig";
25my $TMCC = "/usr/local/etc/emulab/tmcc";
26my $SSH = "/usr/local/bin/ssh";
27my $NC = "/usr/bin/nc";
28my $SSH_PORT = 22;
29my $ROUTE_GET = "/sbin/route get";  # XXX:  works on FreeBSD, but should
30                                    #       should be 'ip route get' for Linux
31
32# Ports that are forwarded between testbeds
33my $TMCD_PORT = 7777;
34my $SMBFS_PORT = 139;
35my $PUBSUB_PORT = 16505;
36my $SEER_PORT = 16606;
37
38
39my $remote_pubsub_port = $PUBSUB_PORT - 1;  # There will be a local
40                                            # pubsubd running, so we
41                                            # dodge the port on the
42                                            # remote tunnel node.
43sub setup_bridging;
44sub setup_tunnel_cfg;
45sub parse_config;
46
47# Option use is as follows:
48#    -f         filename containing config file
49#    -d         turn on debugging output
50#    -r         remotely invoked
51#               (if remotely invoked the last two args are the tun interface
52#               number and the remote address of the tunnel)
53
54my $usage = "Usage: fed-tun.pl [-r] [-d] [-f config-filename] [count addr]\n";
55
56my %opts;       # Note that this is used for both getops and the options file
57my @expected_opts = qw(active tunnelcfg bossname fsname type
58                       peer pubkeys privkeys);
59my $filename;
60my $remote;
61my $debug = 1;
62my $count;
63my $addr;
64my $active;
65my $type;
66my $tunnelcfg;
67my @ssh_port_fwds;              # Queue of ssh portforwarders to start.  The
68                                # -L or -R is in here.
69my $remote_script_dir;          # location of the other sides fed-tun.pl
70my $event_repeater;             # The pathname of the event repeater
71my $remote_config_file;         # Config file for the other side
72
73if ($#ARGV != 0 && !getopts('df:r', \%opts)) {
74    die "$usage";
75}
76
77if (defined($opts{'d'})) {
78    $debug = 1;
79}
80
81if (defined($opts{'f'})) {
82    $filename = $opts{'f'};
83}
84
85if (defined($opts{'r'})) {
86    $remote = 1;
87    die "$usage" if ($#ARGV != 1);
88   
89    $count = pop @ARGV;
90    $addr = pop @ARGV;
91}
92
93die "$usage" if (!defined($remote) && !defined($filename));
94
95if (defined($filename)) {
96    &parse_config("$filename", \%opts) || 
97        die "Cannot read config file $filename: $!\n";
98
99    foreach my $opt (@expected_opts) {
100        warn "Missing $opt option\n" if (!defined($opts{$opt}));
101    }
102
103    $active = 1 if ($opts{'active'} =~ /true/i);
104    $tunnelcfg = 1 if ($opts{'tunnelcfg'} =~ /true/i);
105    $type = $opts{'type'};
106    $type =~ tr/A-Z/a-z/;
107    $remote_script_dir = $opts{'remotescriptdir'} || ".";
108    $event_repeater = $opts{'eventrepeater'};
109    $remote_config_file = $opts{'remoteconfigfile'};
110    $remote_config_file = "-f $remote_config_file" if $remote_config_file;
111
112    if (defined($opts{'fsname'})) {
113        push(@ssh_port_fwds,"-R :$SMBFS_PORT:$opts{'fsname'}:$SMBFS_PORT");
114    }
115
116    if (defined($opts{'bossname'})) {
117        push(@ssh_port_fwds, "-R :$TMCD_PORT:$opts{'bossname'}:$TMCD_PORT");
118    }
119
120    if (defined($opts{'eventservername'})) {
121        push(@ssh_port_fwds,"-R ". 
122            ":$remote_pubsub_port:$opts{'eventservername'}:$PUBSUB_PORT");
123    }
124    if (defined($opts{'remoteeventservername'})) {
125        push(@ssh_port_fwds,"-L :$remote_pubsub_port:" . 
126            "$opts{'remoteeventservername'}:$PUBSUB_PORT");
127    }
128
129    # Forward connections to seer from remote TBs to control in this TB
130    if (defined($opts{'seercontrol'})) {
131        push(@ssh_port_fwds,"-R :$SEER_PORT:$opts{seercontrol}:$SEER_PORT");
132    }
133
134    @ssh_port_fwds = () if ($opts{'type'} eq 'experiment');
135
136    print "ssh_port_fwds = ", join("\n",@ssh_port_fwds), "\n" if ($debug);
137}
138
139# Need these to make the Ethernet tap and bridge to work...
140
141system("kldload /boot/kernel/if_bridge.ko");
142system("kldload /boot/kernel/if_tap.ko");
143
144if ($tunnelcfg && !$remote) {
145    # Most Emulab-like testbeds use globally-routable addresses on the
146    # control net;  at DETER, we isolate the control net from the Internet.
147    # On DETER-like testbeds, we need to create special tunneling nodes
148    # with external access.  Set up the external addresses as necessary.
149    &setup_tunnel_cfg(%opts);
150}
151
152if (!$remote) {
153    system("umask 077 && cp $opts{'privkeys'} /root/.ssh/id_rsa");
154    system("umask 077 && cp $opts{'pubkeys'} /root/.ssh/id_rsa.pub");
155    system("umask 077 && cat $opts{'pubkeys'} >> /root/.ssh/authorized_keys");
156}
157
158if ($active) {
159    # If we're the initiator, open up a separate tunnel to the remote
160    # host for each of the different experiment net interfaces on this
161    # machine.  Execute this startup script on the far end, but with
162    # the -r option to indicate that it's getting invoked remotely and
163    # we should just handle the remote-end tasks.
164
165    # Set up synchronization, so that the various user machines won't try to
166    # contact boss before the tunnels are set up.  (Thanks jjh!)
167
168    do {
169        system("$NC -z $opts{'peer'} $SSH_PORT");
170    } until (!$?);
171
172    # XXX:  Do we need to clear out previously created bridge interfaces?
173
174    my $count = 0;
175    my @SSHCMD;
176
177    # If we are just setting up a control net connection, just fire up
178    # ssh with the null command, and hang loose.
179
180    if ($type eq "control") {
181        foreach my $fwd (@ssh_port_fwds) {
182            system("$SSH -N $fwd -Nno \"StrictHostKeyChecking no\" ". 
183                "$opts{'peer'} &"); #or die "Failed to run ssh";
184        }
185
186        exit;
187    }
188
189    open(IFFILE, "/var/emulab/boot/ifmap") || die "couldn't open ifmap\n";
190    while (<IFFILE>) {
191        my @a = split(' ');
192        my $iface = $a[0];
193        my $addr = $a[1];
194        my $bridge = "bridge" . $count;
195        my $tun = "tap" . $count;
196        my $cmd;
197
198        print "Found $iface, $addr, to bridge on $bridge\n" if ($debug);
199
200        # Note that we're going to fire off an ssh which will never return;
201        # we need the connection to stay up to keep the tunnel up.
202        # In order to check for problems, we open it this way and read
203        # the expected single line of output when the tunnel is connected.
204        # To make debugging easier and to degrade more gracefully, I've split
205        # these out into multiple processes.
206       
207        foreach my $fwd (@ssh_port_fwds) {
208            $cmd = "$SSH -N $fwd -o \"StrictHostKeyChecking no\" ". 
209                "$opts{'peer'} &";
210
211            print "$cmd\n" if $debug;
212            system("$cmd"); # or die "Failed to run ssh";
213        }
214        $cmd =  "$SSH -w $count:$count -o \"StrictHostKeyChecking no\" " . 
215            "$opts{'peer'}  \"$remote_script_dir/fed-tun.pl " . 
216            "$remote_config_file -r $addr $count\" & |";
217
218        print "$cmd\n" if $debug;
219
220        open($SSHCMD[$count], $cmd) 
221           or die "Failed to run ssh";
222
223        my $check = <$SSHCMD[$count]>;  # Make sure something ran...
224        print "Got line [$check] from remote side\n" if ($debug);
225
226        &setup_bridging($tun, $bridge, $iface, $addr);
227        $count++;
228        @ssh_port_fwds = ();  # only do this on the first connection
229    }
230    close(IFFILE);
231
232    # Start a local event repeater (unless we're missing parameters
233    die "Missing event repeater params (No config file ?)\n"
234        unless $event_repeater && $opts{'remoteexperiment'} && 
235        $opts{'localexperiment'};
236
237    print "Starting event repeater\n" if $debug;
238   
239    print("$event_repeater -M -P $remote_pubsub_port -S localhost " . 
240        "-E $opts{'remoteexperiment'} -e $opts{'localexperiment'}\n") 
241        if $debug;
242    # Connect to the forwarded pubsub port on this host to listen to the
243    # other experiment's events, and to the local experiment to forward
244    # events.
245    system("$event_repeater -M -P $remote_pubsub_port -S localhost " . 
246        "-E $opts{'remoteexperiment'} -e $opts{'localexperiment'}");
247    warn "Event repeater returned $?\n" if $?;
248} elsif ($remote) {
249    # We're on the remote system;  figure out which interface to
250    # tweak, based on the IP address passed in.
251
252    my $iter = 0;
253    my $iface;
254
255    open(RTFILE, "$ROUTE_GET $addr |") || die "couldn't do $ROUTE_GET\n";
256    while (<RTFILE>) {
257        if (/interface: (\w+)/) {
258            $iface = $1;
259        }
260    }
261    close(RTFILE);
262
263    die "Couldn't find interface to use." if (!defined($iface));
264    my $bridge = "bridge" . $count;
265    my $tun = "tap" . $count;
266
267    &setup_bridging($tun, $bridge, $iface, $addr);
268    print "Remote connection all set up!\n";  # Trigger other end with output
269
270    # If this is the first remote invocation on a control gateway, start the
271    # event repeater.
272
273    if ( $count == 0 && $type ne "experiment" ) {
274        my $remote_pubsub_port = $PUBSUB_PORT - 1;  # There will be a local
275                                                    # pubsubd running, so we
276                                                    # dodge the port on the
277                                                    # remote tunnel node.
278        # Make sure we have the relevant parameters
279        die "Missing event repeater params (No config file ?)\n"
280            unless $event_repeater && $opts{'remoteexperiment'} && 
281            $opts{'localexperiment'};
282
283        print "Starting event repeater\n" if $debug;
284       
285        # Connect to the forwarded pubsub port on this host to listen to the
286        # other experiment's events, and to the local experiment to forward
287        # events.
288        system("$event_repeater -P $remote_pubsub_port -S localhost " . 
289            "-E $opts{'remoteexperiment'} -e $opts{'localexperiment'}");
290        warn "Event repeater returned $?\n" if $?;
291    }
292} else {
293    print "inactive end of a connection, finishing" if ($debug);
294}
295
296print "all done!\n" if ($debug);
297exit;
298
299
300# Set up the bridging for the new stuff...
301
302sub setup_bridging($; $; $; $) {
303    my ($tun, $bridge, $iface, $addr) = @_;
304
305    print "Waiting to see if new iface $tun is up\n" if ($debug);
306
307    do {
308        sleep 1;
309        system("$IFCONFIG $tun");
310    } until (!$?);
311
312    print "setting up $bridge with $iface and nuking $addr\n" if ($debug);
313
314    system("ifconfig $bridge create");
315    system("ifconfig $iface delete $addr");
316    system("ifconfig $bridge addm $iface up");
317    system("ifconfig $bridge addm $tun");
318}
319
320# Set up tunnel info for DETER-like testbeds.
321
322sub setup_tunnel_cfg {
323    my (%opts) = @_;
324    my $tunnel_iface = "em0";   # XXX
325    my $tunnel_ip;
326    my $tunnel_mask;
327    my $tunnel_mac;
328    my $tunnel_router;
329
330    print "Opening $TMCC tunnelip\n" if ($debug);
331
332    open(TMCD, "$TMCC tunnelip |") || die "tmcc failed\n";
333    print "Opened $TMCC tunnelip\n" if ($debug);
334    while (<TMCD>) {
335        print "got one line from tmcc\n" if ($debug);
336        print if ($debug);
337        if (/^TUNNELIP=([0-9.]*) TUNNELMASK=([0-9.]*) TUNNELMAC=(\w*) TUNNELROUTER=([0-9.]*)$/) {
338            $tunnel_ip = $1;
339            $tunnel_mask = $2;
340            $tunnel_mac = $3;
341            $tunnel_router = $4;
342        }
343    }
344    close(TMCD);
345
346    die "Unable to determine tunnel node configuration information"
347        if (!defined($tunnel_router));
348
349    print "tunnel options:  ip=$tunnel_ip mask=$tunnel_mask mac=$tunnel_mac router=$tunnel_router\n" if ($debug);
350
351    # Sadly, we ignore the tunnel mac for now -- we should eventually
352    # use it to determine which interface to use, just like the
353    # Emulab startup scripts.
354
355    system("ifconfig $tunnel_iface $tunnel_ip" .
356           ($tunnel_mask ? " netmask $tunnel_mask" : "") . " up");
357    warn "configuration of tunnel interface failed" if ($?);
358
359    # Sometimes the insertion of DNS names lags a bit.  Retry this
360    # configuration a few times to let DNS catch up.  Might want to really
361    # check the DNS name before we try this...
362    my $config_succeeded = 0;
363    my $tries = 0;
364    my $max_retries = 30;
365
366    do {
367        system("route add $opts{'peer'} $tunnel_router");
368        if ( $? ) {
369            warn "configuration routes via tunnel interface failed";
370            $tries++;
371            sleep(10);
372        }
373        else { $config_succeeded = 1; }
374    } until ( $config_succeeded || $tries > $max_retries );
375
376    print "setup_tunnel_cfg done\n" if ($debug);
377}
378
379# Trick config-file parsing code from Ted Faber:
380
381# Parse the config file.  The format is a colon-separated parameter name
382# followed by the value of that parameter to the end of the line.  This parses
383# that format and puts the parameters into the referenced hash.  Parameter
384# names are mapped to lower case, parameter values are unchanged.  Returns 0 on
385# failure (e.g. file open) and 1 on success.
386sub parse_config {
387    my($file, $href) = @_;
388    my($fh) = new IO::File($file);
389       
390    unless ($fh) {
391        warn "Can't open $file: $!\n";
392        return 0;
393    }
394
395    while (<$fh>) {
396        next if /^\s*#/ || /^\s*$/;     # Skip comments & blanks
397        chomp;
398        /^([^:]+):\s*(.*)/ && do {
399            my($key) = $1; 
400
401            $key =~ tr/A-Z/a-z/;
402            $href->{$key} = $2;
403            next;
404        };
405        warn "Unparasble line in $file: $_\n";
406    }
407    $fh->close();   # It will close when it goes out of scope, but...
408    return 1;
409}
Note: See TracBrowser for help on using the repository browser.