source: fedkit/gateway_lib.pm @ a65a65a

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

Move get_ip into the library so others can use it. Minor other fixes to support the use of Net::hostent.

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