source: fedkit/gateway_lib.pm @ 358e0b8

axis_examplecompt_changesinfo-opsversion-3.01version-3.02
Last change on this file since 358e0b8 was c3a3fe3, checked in by Ted Faber <faber@…>, 15 years ago

Coordinate parallel starts: wait for remote nodes to configure ssh for access

  • Property mode set to 100644
File size: 8.9 KB
RevLine 
[2edec46]1#!/usr/bin/perl
2
3package gateway_lib;
4
[2b35261]5# Package stuff to keep the caller's namespace clean, but allow additions if
6# they need it.
[2edec46]7require Exporter;
8@ISA=qw(Exporter);
9@EXPORT_OK=qw(set_sshd_params import_key wait_for_DNS deter_tunnelip
10    configure_outgoing_iface add_route bind_tap_to_iface iface_to_addr
[8d4e4fb]11    dest_to_iface addr_to_iface read_config emulab_config_filename);
12
[2edec46]13
14use strict;
15
16use IO::File;
17use IO::Pipe;
[1899afd]18use IO::Socket;
[2edec46]19
20use File::Temp;
21use File::Copy;
22
[2b35261]23# Standard locations of these commands (FreeBSD)
[2edec46]24my $IFCONFIG = "/sbin/ifconfig";
25my $ROUTE = "/sbin/route";
26my $TMCC = "/usr/local/etc/emulab/tmcc";
27my $FINDIF = "/usr/local/etc/emulab/findif";
[8d4e4fb]28my $TMCC = "/usr/local/etc/emulab/tmcc";
[2edec46]29
[2b35261]30# Takes an ssh config file and a reference to a hash of keys whose values must
31# be set a specific way.  Replaces existing entries with the set values.
[2edec46]32sub set_sshd_params {
33    my($keys, $file) = @_;
34    my $f;
35    my $t;
36
37    $file = "/etc/ssh/sshd_config" unless $file;
38    $f = new IO::File("$file") || die "Can't read $f: $!\n";
39    $t = new File::Temp() || die "Can't open tempfile: $!\n";
40
41    while (<$f>) {
42        foreach my $k (keys %{$keys}) {
43            s/^\s*#?\s*$k.*/$k $keys->{$k}/;
44        }
45        print $t $_;
46    }
47    $f->close();
48    $t->close();
49    copy("$file", "$file.hold");
50    copy($t->filename, $file);
51    # When $t goes out of scope, the tmpfile is deleted
52}
53
[2b35261]54# Append the given keyfile to the given authorised key file.
[2edec46]55sub import_key {
56    my($keyfile, $authkeys) = @_;
57
58    my $kf = new IO::File($keyfile) || die "Can't open $keyfile:$!\n";
59    my $ak = new IO::File(">>$authkeys") || die "Can't open $authkeys: $!\n";
60
61    while (<$kf>) {
62        print $ak $_;
63    }
64    $kf->close();
65    $ak->close();
66}
67
[2b35261]68# Keep trying to look up the given hostname until successful.  If timeout is
69# given, die after waiting that long.  If sleep is given, wait that many
70# seconds between attempts (defaults to 5). 
[2edec46]71sub wait_for_DNS {
[2b35261]72    my($name, $timeout, $sleep) = @_;
[2edec46]73    my $start = time();
[2b35261]74    $sleep = 5 unless $sleep;
[2edec46]75    my @rv;
76
77    while (!@rv) {
78        @rv = gethostbyname($name);
79        die "Timeout waiting for DNS to get $name\n" 
80            if ($timeout && time() - $start > $timeout);
[2b35261]81        sleep($sleep) unless @rv;
[2edec46]82    }
83}
84
[2b35261]85# Get the external access parameters (interface, address, netmask, mac address,
86# and next hop router) from tmcd in Emulabs that support the DETER tunnelip
87# extension.
[2edec46]88sub deter_tunnelip {
89    # To parse tmcc
90    my $tmcc = new IO::Pipe || die "Can't create tmcc pipe: $!\n";
91    my $interface;              # Interface with external address
92    my $ip;                     # IP address of external interface
93    my $mac;                    # MAC address
94    my $netmask;                # Netmask
95    my $router;                 # Router for the internet
96
97
[2b35261]98    # Parse out the info about tunnelips.  Format is usually one line of
99    # ATTR=VALUE.  Multiple lines are possible.
[2edec46]100    $tmcc->reader("$TMCC tunnelip");
101    while (<$tmcc>) {
102        chomp;
103        /TUNNELIP=([\d\.]*)/ && do { $ip = $1; };
104        /TUNNELMASK=([\d\.]*)/ && do { $netmask = $1; };
105        /TUNNELMAC=([[:xdigit:]]*)/ && do { $mac = $1; };
106        /TUNNELROUTER=([\d\.]*)/ && do { $router = $1; };
107    }
108    $tmcc->close();
109
110    die "No MAC information for tunnel.\n" unless $mac;
111
112    # Use the emulab findif command to get the right interface to configure
113    $interface = `$FINDIF $mac`;
114    chomp $interface;
115    die "Can't get interface for mac address $mac: $?" if $? || !$interface;
116
117    return ($interface, $ip, $netmask, $mac, $router);
118}
119
[2b35261]120
121# Configure the given interface with the given IP address and netmask.
[2edec46]122sub configure_outgoing_iface {
[2b35261]123    my ($interface, $ip, $netmask) = @_;
[2edec46]124
125    my @ifconfig = ($IFCONFIG, $interface, $ip);
126    push(@ifconfig, 'netmask', $netmask) if $netmask;
127
128    system(@ifconfig);
129    die join(" ", @ifconfig) . " failed: $!\n" if $?;
130}
131
[2b35261]132# Add a route to the destination through the router.  If wait is given, do not
133# attempt to add the route until DNS has the hostname in it.  If timeout is
134# given, only wait that many seconds for DNS to acquire it.
[2edec46]135sub add_route {
136    my($routedest, $router, $wait, $timeout) = @_;
137    my @cmd;
138    # Linux and FreeBSD use slightly different route syntax, so get the OS
139    my $os = `uname`;
140    chomp $os;
141
142    $timeout = 0 unless $timeout;
143    die "add_host_route needs a router and a destination\n"
144        unless $router && $routedest;
145
146    wait_for_DNS($routedest, $timeout) if $wait;
147
148    if ( $os =~ /^Linux/ ) { 
149        @cmd = ($ROUTE, 'add', $routedest, 'gw', $router);
150    }
151    elsif ( $os =~ /^FreeBSD/ ) {
152        @cmd = ($ROUTE, 'add', $routedest, $router);
153    }
154    else {
155        die "Unknown OS: $os\n";
156    }
157    system(@cmd);
158    warn join(" ", @cmd) . " failed: $?\n" if $?;
159    return $?;
160}
161
[2b35261]162# Connect the tap($tapno) interface to $iface at the link level.  Remove any IP
163# addresses assigned to interface to avoid confusing the routing system.  We're
164# very tolerant of errors as the bridge and other interfaces may already exist
165# when this is called.
[2edec46]166sub bind_tap_to_iface {
167    my($tapno, $iface) = @_;
168    my $bridge = "bridge$tapno";
169    my $tap = "tap$tapno";
170    my @addr = iface_to_addr($iface);
171
172    # Wait for the tap
173    system("$IFCONFIG $tap > /dev/null 2>/dev/null");
174    while ($?) {
175        system("$IFCONFIG $tap > /dev/null 2>/dev/null");
176    }
177
178    system("ifconfig $bridge create");
179    warn "Cannot create bridge: $?\n" if $?;
180    foreach my $a (@addr) {
181        system("ifconfig $iface delete $a");
182        warn "Cannot delete address $a: $?\n" if $?;
183    }
184    system("ifconfig $bridge addm $iface up");
185    warn "Cannot add intefrace $iface to bridge: $?\n" if $?;
186    system("ifconfig $bridge addm $tap");
187    warn "Cannot add intefrace $tap to bridge: $?\n" if $?;
188    return $?;
189}
190
[2b35261]191# Return the IP addresses accociated with this interface (as a list)
[2edec46]192sub iface_to_addr {
193    my($iface) = @_;
194    my $ipipe = new IO::Pipe() || die "Can't create pipe for ifconfig: $!\n";
195    my @addr;
196
197    $ipipe->reader("$IFCONFIG $iface");
198    while(<$ipipe>) {
199        /inet\s+([0-9\.]+)/ && push(@addr, $1);
200    }
201    $ipipe->close();
202    return @addr;
203}
204
[2b35261]205# Return the interface that packets to this host (IP or DNS) would be sent on.
[2edec46]206sub dest_to_iface {
207    my($dest) =@_;
208    my $rpipe = new IO::Pipe() || die "Can't create pipe for route: $!\n";
209
210    $rpipe->reader("$ROUTE get $dest");
211    while (<$rpipe>) {
212        /interface:\s*([[:alnum:]]+)/ && do {
213            my $iface = $1;
214            $rpipe->close();
215            return $iface;
216        };
217    }
218    $rpipe->close();
219
220    die "No route to $dest\n";
221}
222
[2b35261]223# Return the interface bound to this IP address.  If there are more than one,
224# the first one returned by ifconfig is the one returned.
[2edec46]225sub addr_to_iface {
226    my($addr) = @_;
227    my $ipipe = new IO::Pipe() || die "Can't create pipe for ifconfig: $!\n";
228    my $iface;
229
230    $ipipe->reader("$IFCONFIG");
231    while(<$ipipe>) {
232        /^([[:alnum:]]+):/ && do {
233            $iface = $1;
234            next;
235        };
236        /inet\s+([0-9\.]+)/ && do {
237            if ($1 eq $addr) {
238                $ipipe->close();
239                return $iface;
240            }
241        };
242    }
243    $ipipe->close();
244    die "Cannot match $addr to an interface\n";
245}
246
[8d4e4fb]247sub read_config {
248    my($file, $map) = @_;
249    my %param;
250    my %keywords;
251    my $f;
252
253    foreach my $k (keys %{$map}) {
254        if ( $k =~ /(\S+)=[si]/ ) {
255            $keywords{$1} = $map->{$k};
256            $param{$1}++;
257        }
258        else {
259            $keywords{$k} = $map->{$k};
260        }
261    }
262
263    $f = new IO::File($file) || die "Can't open $file: $!\n";
264    while (<$f>) {
265        /^\s*([^:]+):\s*(.*)/ && do {
266            my $keyw = $1;
267            my $val = $2;
268
269            $keyw =~ tr [A-Z] [a-z];
270
271            if ($keywords{$keyw} ) {
272                if ( $param{$keyw} ) { 
273                    if ( ref($keywords{$keyw}) eq 'SCALAR') {
274                        ${$keywords{$keyw}} = $val;
275                    }
276                    elsif( ref($keywords{$keyw}) eq 'ARRAY') {
277                        push(@{$keywords{$keyw}}, $val);
278                    }
279                    else {
280                        die "Unknown variable type for $keyw\n";
281                    }
282                }
283                else  { ${$keywords{$keyw}}++; }
284            }
285            next;
286        };
287    }
288    $f->close();
289}
290
291
292sub emulab_config_filename {
293    # Find the configuration file in the usual place, which depends on what
294    # experiment and project we're in.
295    my $pid;
296    my $eid;
297    my $filename;
298    my $tmcd = new IO::Pipe() || die "Can't create pipe: $!\n";
299
300    $tmcd->reader("$TMCC status");
301
302    while (<$tmcd>) {
303        chomp;
304        /ALLOCATED=([^\/]+)\/(\S+)/ && do {
305            $pid = $1;
306            $eid = $2;
307        };
308    }
309    $tmcd->close();
310    my $hn = `hostname`;
311    chomp $hn;
312    $hn =~ s/\..*//;
313    $filename = "/proj/$pid/exp/$eid/tmp/$hn.gw.conf"
314        if $pid and $eid;
315
316    return $filename;
317}
318
[1899afd]319sub wait_for_port {
320    my($addr, $port, $timeout, $sleep) = @_;
321    my $start = time();
322    $sleep = 5 unless $sleep;
323    die "Need both address and port\n" unless $addr && $port;
324
325    my $s;
326
327    while (!$s) {
328        if (!($s = new IO::Socket(Domain => &AF_INET, PeerAddr => $addr, 
329            PeerPort => $port))) {
330            if ($timeout and time() - $start > $timeout) {
331                return undef;
332            }
333            else { sleep($sleep); }
334        }
335    }
336    $s->close();
337    return 1;
338
339}
340
[c3a3fe3]341sub testcmd_repeat {
342    my($cmd, $timeout, $sleep) = @_;
343    my $start = time();
344    $sleep = 5 unless $sleep;
345
346    while (1) {
347        system("$cmd");
348        if ($?) {
349            if ($timeout and time() - $start > $timeout) {
350                return undef;
351            }
352            else { sleep($sleep); }
353        }
354        else { return 1; }
355    }
356}
[1899afd]357
[2edec46]3581;
Note: See TracBrowser for help on using the repository browser.