source: fedkit/gateway_lib.pm @ f8fa72b

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

More ProtoGENI accomodation

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