liblocsetup.pm 38.4 KB
Newer Older
1
#!/usr/bin/perl -wT
Leigh B. Stoller's avatar
Leigh B. Stoller committed
2
#
3
# Copyright (c) 2000-2017 University of Utah and the Flux Group.
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 
# {{{EMULAB-LICENSE
# 
# This file is part of the Emulab network testbed software.
# 
# This file is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
# 
# This file is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public
# License for more details.
# 
# You should have received a copy of the GNU Affero General Public License
# along with this file.  If not, see <http://www.gnu.org/licenses/>.
# 
# }}}
Leigh B. Stoller's avatar
Leigh B. Stoller committed
23
24
#

25
26
27
28
29
30
31
#
# FreeBSD specific routines and constants for the client bootime setup stuff.
#
package liblocsetup;
use Exporter;
@ISA = "Exporter";
@EXPORT =
Leigh B Stoller's avatar
Leigh B Stoller committed
32
33
    qw ( $CP $EGREP $NFSMOUNT $UMOUNT $TMPASSWD $SFSSD $SFSCD $RPMCMD
	 $HOSTSFILE $LOOPBACKMOUNT $CHMOD
34
	 os_account_cleanup os_ifconfig_line os_etchosts_line
35
	 os_setup os_groupadd os_useradd os_userdel os_usermod os_mkdir
36
	 os_ifconfig_veth os_viface_name os_modpasswd
37
38
	 os_routing_enable_forward os_routing_enable_gated
	 os_routing_add_manual os_routing_del_manual os_homedirdel
39
	 os_groupdel os_getnfsmounts os_islocaldir os_mountextrafs
40
	 os_fwconfig_line os_fwrouteconfig_line os_config_gre os_nfsmount
41
	 os_find_freedisk os_get_ctrlnet_ip
42
	 os_getarpinfo os_createarpentry os_removearpentry
43
	 os_getstaticarp os_setstaticarp os_ismounted os_unmount os_mount
44
45
       );

46
47
sub VERSION()	{ return 1.0; }

48
49
50
# Must come after package declaration!
use English;

51
52
53
54
# Load up the paths. Its conditionalized to be compatabile with older images.
# Note this file has probably already been loaded by the caller.
BEGIN
{
55

56
57
58
59
60
61
62
63
64
65
66
    if (-e "/etc/emulab/paths.pm") {
	require "/etc/emulab/paths.pm";
	import emulabpaths;
    }
    else {
	my $ETCDIR  = "/etc/testbed";
	my $BINDIR  = "/etc/testbed";
	my $VARDIR  = "/etc/testbed";
	my $BOOTDIR = "/etc/testbed";
    }
}
67
68
# Convenience.
sub REMOTE()	{ return libsetup::REMOTE(); }
69
sub REMOTEDED()	{ return libsetup::REMOTEDED(); }
70
sub MFS()	{ return libsetup::MFS(); }
71
sub JAILED()	{ return libsetup::JAILED(); }
Mike Hibler's avatar
Mike Hibler committed
72
sub INXENVM()   { return libsetup::INXENVM(); }
73
74
75

#
# Various programs and things specific to FreeBSD and that we want to export.
Ryan Jackson's avatar
Ryan Jackson committed
76
#
77
$CP		= "/bin/cp";
Mike Hibler's avatar
Mike Hibler committed
78
$DF		= "/bin/df";
79
$EGREP		= "/usr/bin/egrep -s -q";
80
81
$NFSMOUNT	= "/sbin/mount -o -b ";
$LOOPBACKMOUNT	= "/sbin/mount -t null ";
82
$MOUNT		= "/sbin/mount";
83
$UMOUNT		= "/sbin/umount";
84
$TMPASSWD	= "$ETCDIR/master.passwd";
Austin Clements's avatar
Austin Clements committed
85
86
$SFSSD		= "/usr/local/sbin/sfssd";
$SFSCD		= "/usr/local/sbin/sfscd";
Mike Hibler's avatar
Mike Hibler committed
87
$RPMCMD		= "/usr/local/bin/rpm";
88
$HOSTSFILE	= "/etc/hosts";
89
$WGET		= "/usr/local/bin/wget";
Leigh B Stoller's avatar
Leigh B Stoller committed
90
$CHMOD		= "/bin/chmod";
91
$ARP		= "/usr/sbin/arp";
92
93
94
95

#
# These are not exported
#
96
my $TMGROUP	= "$ETCDIR/group";
97
98
99
100
my $USERADD     = "/usr/sbin/pw useradd";
my $USERDEL     = "/usr/sbin/pw userdel";
my $USERMOD     = "/usr/sbin/pw usermod";
my $GROUPADD	= "/usr/sbin/pw groupadd";
101
my $GROUPDEL	= "/usr/sbin/pw groupdel";
102
103
my $CHPASS	= "/usr/bin/chpass -p";
my $MKDB	= "/usr/sbin/pwd_mkdb -p";
104
105
my $IFCONFIGBIN = "/sbin/ifconfig";
my $IFCONFIG    = "$IFCONFIGBIN %s inet %s netmask %s %s %s";
106
my $IFALIAS     = "$IFCONFIGBIN %s alias %s netmask %s";
107
my $IFC_1000MBS = "media 1000baseTX";
108
109
my $IFC_100MBS  = "media 100baseTX";
my $IFC_10MBS   = "media 10baseT/UTP";
110
my $IFC_AUTO	= "";
111
my $IFC_FDUPLEX = "mediaopt full-duplex";
112
my $IFC_HDUPLEX = "mediaopt half-duplex";
113
my $IFC_ADUPLEX = "";
114
my $MKDIR	= "/bin/mkdir";
115
116
my $GATED	= "/usr/local/sbin/gated";
my $ROUTE	= "/sbin/route";
117
118
my $SHELLS	= "/etc/shells";
my $DEFSHELL	= "/bin/tcsh";
119
120
my $ISCSI	= "/sbin/iscontrol";
my $ISCSICNF	= "/etc/iscsi.conf";
121
my $SMARTCTL	= "/usr/local/sbin/smartctl";
122
123

#
124
# OS dependent part of account cleanup. On a remote node, this will
Ryan Jackson's avatar
Ryan Jackson committed
125
126
# only be called from inside a JAIL, or from the prepare script.
#
127
sub os_account_cleanup($)
128
{
129
130
131
132
133
134
    # XXX this stuff should be lifted up into rc.accounts, sigh
    my ($updatemasterpasswdfiles) = @_;
    if (!defined($updatemasterpasswdfiles)) {
	$updatemasterpasswdfiles = 0;
    }

135
136
137
    printf STDOUT "Resetting passwd and group files\n";
    if (system("$CP -f $TMGROUP /etc/group") != 0) {
	print STDERR "Could not copy default group file into place: $!\n";
138
	return -1;
139
    }
Ryan Jackson's avatar
Ryan Jackson committed
140

141
142
    if (system("$CP -f $TMPASSWD /etc/master.passwd_testbed") != 0) {
	print STDERR "Could not copy default passwd file into place: $!\n";
143
	return -1;
144
    }
Ryan Jackson's avatar
Ryan Jackson committed
145

146
147
    if (system("$MKDB /etc/master.passwd_testbed") != 0) {
	print STDERR "Failure running $MKDB on default password file: $!\n";
148
	return -1;
149
150
151
152
153
154
155
156
    }
    return 0;
}

#
# Generate and return an ifconfig line that is approriate for putting
# into a shell script (invoked at bootup).
#
157
sub os_ifconfig_line($$$$$$$$;$$%)
158
{
159
    my ($iface, $inet, $mask, $speed, $duplex, $aliases, $iface_type, $lan,
160
	$settings, $rtabid, $cookie) = @_;
161
162
    my $media    = "";
    my $mediaopt = "";
163
    my ($uplines, $downlines);
164
165
166
167

    #
    # Need to check units on the speed. Just in case.
    #
Mike Hibler's avatar
Mike Hibler committed
168
169
170
171
172
173
174
175
176
177
178
179
    if (!INXENVM()) {
	if ($speed =~ /(\d*)([A-Za-z]*)/) {
	    if ($2 eq "Mbps") {
		$speed = $1;
	    }
	    elsif ($2 eq "Kbps") {
		$speed = $1 / 1000;
	    }
	    else {
		warn("*** Bad speed units $2 in ifconfig, default to 100Mbps\n");
		$speed = 100;
	    }
Mike Hibler's avatar
Mike Hibler committed
180
181
	    if ($speed == 10000) {
		# 10G is 10G, take it or leave it
182
		$media = $IFC_AUTO;
Mike Hibler's avatar
Mike Hibler committed
183
184
	    }
	    elsif ($speed == 1000) {
Mike Hibler's avatar
Mike Hibler committed
185
186
187
188
189
190
191
192
		$media = $IFC_1000MBS;
	    }
	    elsif ($speed == 100) {
		$media = $IFC_100MBS;
	    }
	    elsif ($speed == 10) {
		$media = $IFC_10MBS;
	    }
193
194
195
	    elsif ($speed == 0) {
		$media = $IFC_AUTO;
	    }
Mike Hibler's avatar
Mike Hibler committed
196
	    else {
197
198
199
		warn("*** Bad Speed $speed in ifconfig, default to autoconfig\n");
		$speed = 0;
		$media = $IFC_AUTO;
Mike Hibler's avatar
Mike Hibler committed
200
	    }
201
	}
Mike Hibler's avatar
Mike Hibler committed
202

203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
	if ($media eq $IFC_AUTO) {
	    $mediaopt = $IFC_ADUPLEX;
	} else {
	    if ($duplex eq "full") {
		if ($speed == 10000) {
		    # 10G is always full duplex, no need to set
		    $mediaopt = $IFC_ADUPLEX;
		} else {
		    $mediaopt = $IFC_FDUPLEX;
		}
	    }
	    elsif ($duplex eq "half") {
		$mediaopt = $IFC_HDUPLEX;
	    }
	    else {
		warn("*** Bad duplex $duplex in ifconfig, default to full\n");
		$duplex = "full";
Mike Hibler's avatar
Mike Hibler committed
220
221
		$mediaopt = $IFC_FDUPLEX;
	    }
222
223
224
	}
    }

225
    $uplines = "";
Ryan Jackson's avatar
Ryan Jackson committed
226

227
228
    if ($inet eq "") {
	$uplines .= "$IFCONFIGBIN $iface up $media $mediaopt";
229
    }
230
231
232
233
234
235
236
237
    else {
	#
	# Must set route table id before assigning IP address so that interface
	# route winds up in the correct table.
	#
	if (defined($rtabid)) {
	    $uplines .= "$IFCONFIGBIN $iface rtabid $rtabid\n    ";
	}
238

239
	# Config the interface.
240
241
	$uplines  .= sprintf($IFCONFIG, $iface, $inet, $mask,
			     $media, $mediaopt);
242
243
244
	# An interface underlying virtual interfaces does not go down.
	$downlines = "$IFCONFIGBIN $iface down"
	    if (defined($lan) && $lan ne "");
245
    }
246
247
248
249
    return ($uplines, $downlines);
}

#
250
# Specialized function for configing virtual ethernet devices:
251
#
252
253
254
255
256
#	'veth'	locally hacked veth devices
#	'vlan'	802.1q tagged vlan devices
#	'alias'	IP aliases on physical interfaces
#
sub os_ifconfig_veth($$$$$;$$$$$)
257
{
258
259
    my ($iface, $inet, $mask, $id, $vmac,
	$rtabid, $encap, $vtag, $itype, $cookie) = @_;
260
261
262
263
264
265
266
267
268
269
270
    my ($uplines, $downlines);

    #
    # Do not try this on the MFS!
    #
    return ""
	if (MFS());

    require Socket;
    import Socket;

271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
    if ($itype !~ /^(alias|vlan|veth)$/) {
	warn("Unknown virtual interface type $itype\n");
	return "";
    }

    #
    # IP aliases
    #
    if ($itype eq "alias") {
	# Must do this first to avoid lo0 routes.
	if (!exists($cookie->{"alias"})) {
	    $uplines =
	      "sysctl net.link.ether.inet.useloopback=0 >/dev/null 2>&1\n    ";
	    $cookie->{"alias"} = 1;
	}
Ryan Jackson's avatar
Ryan Jackson committed
286

287
288
289
290
	$uplines   .= sprintf($IFALIAS, $iface, $inet, $mask);
	$downlines .= "$IFCONFIGBIN $iface -alias $inet";

	return ($uplines, $downlines);
291
    }
292

293
294
295
296
297
298
299
300
    #
    # VLANs
    #
    if ($itype eq "vlan") {
	if (!defined($vtag)) {
	    warn("No vtag in veth config\n");
	    return "";
	}
301
302
303
304
	if ($vmac =~ /^(\w{2})(\w{2})(\w{2})(\w{2})(\w{2})(\w{2})$/) {
	    $vmac = "$1:$2:$3:$4:$5:$6";
	}
	$uplines = "$IFCONFIGBIN vlan${id} create link $vmac " .
305
306
		   "vlan $vtag vlandev $iface\n    ";

307
308
309
310
	# XXX we have to explicitly put the physical interface into
	# promiscuous mode when the virtual device has a different MAC
	$uplines .= "$IFCONFIGBIN $iface promisc\n    ";

311
	$encap = 1;
312
    }
313
314
315
316

    #
    # VETHs
    #
317
    else {
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
	if (!defined($vtag)) {
	    # Need to derive a vlan tag. Just use the middle two octets.
	    my $vtag = (unpack("I", inet_aton($inet)) >> 8) & 0xffff;
	}

	if ($vmac =~ /^(\w{2})(\w{2})(\w{2})(\w{2})(\w{2})(\w{2})$/) {
	    $vmac = "$1:$2:$3:$4:$5:$6";
	}
	else {
	    warn("Bad vmac in veth config: $vmac\n");
	    return "";
	}

	$uplines = "$IFCONFIGBIN veth${id} create\n    " .
		   "$IFCONFIGBIN veth${id} vethaddr $vmac/$vtag" .
		       (defined($iface) ? " vethdev $iface\n    " : "\n    ");
334
335
    }

336
337
338
339
    #
    # Must set route table id before assigning IP address so that interface
    # route winds up in the correct table.
    #
340
    if (defined($rtabid)) {
341
	$uplines .= "$IFCONFIGBIN ${itype}${id} rtabid $rtabid\n    ";
342
    }
343

344
    $uplines .= "$IFCONFIGBIN ${itype}${id} inet $inet netmask $mask";
345
346
347
348
349
350
351
352

    #
    # link1 on the veth device implies no encapsulation
    #
    if (!$encap) {
	$uplines .= " link1";
    }

353
354
    $downlines = "$IFCONFIGBIN ${itype}${id} destroy";

355
    return ($uplines, $downlines);
356
357
}

358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
#
# Compute the name of a virtual interface device based on the
# information in ifconfig hash (as returned by getifconfig).
#
sub os_viface_name($)
{
    my ($ifconfig) = @_;
    my $piface = $ifconfig->{"IFACE"};

    #
    # Physical interfaces use their own name
    #
    if (!$ifconfig->{"ISVIRT"}) {
	return $piface;
    }

    #
    # Otherwise we have a virtual interface: alias, veth, vlan.
    #
    # alias: There is no alias device, just use the phys device
    # veth:  veth<ID>
    # vlan:  vlan<ID>
    #
    my $itype = $ifconfig->{"ITYPE"};
    if ($itype eq "alias") {
	return $piface;
    } elsif ($itype =~ /^(vlan|veth)$/) {
	return $itype . $ifconfig->{"ID"};
    }

    warn("FreeBSD does not support virtual interface type '$itype'\n");
    return undef;
}

392
393
394
395
#
# Generate and return an string that is approriate for putting
# into /etc/hosts.
#
396
sub os_etchosts_line($$$)
397
{
398
    my ($name, $ip, $aliases) = @_;
Ryan Jackson's avatar
Ryan Jackson committed
399

400
    return sprintf("%s\t%s %s", $ip, $name, $aliases);
401
402
403
404
}

#
# Add a new group
Ryan Jackson's avatar
Ryan Jackson committed
405
#
406
407
408
409
410
411
412
sub os_groupadd($$)
{
    my($group, $gid) = @_;

    return system("$GROUPADD $group -g $gid");
}

413
414
#
# Delete an old group
Ryan Jackson's avatar
Ryan Jackson committed
415
#
416
417
418
419
420
421
422
sub os_groupdel($)
{
    my($group) = @_;

    return system("$GROUPDEL $group");
}

423
424
#
# Remove a user account.
Ryan Jackson's avatar
Ryan Jackson committed
425
#
426
427
428
429
430
431
432
433
434
sub os_userdel($)
{
    my($login) = @_;

    return system("$USERDEL $login");
}

#
# Modify user group membership.
Ryan Jackson's avatar
Ryan Jackson committed
435
#
436
sub os_usermod($$$$$$)
437
{
438
    my($login, $gid, $glist, $pswd, $root, $shell) = @_;
439
440
441
442
443
444
445

    if ($root) {
	$glist = join(',', split(/,/, $glist), "wheel");
    }
    if ($glist ne "") {
	$glist = "-G $glist";
    }
446
447
    # Map the shell into a full path.
    $shell = MapShell($shell);
448

449
    if (system("$CHPASS '$pswd' $login") != 0) {
450
451
452
	warn "*** WARNING: $CHPASS $login error.\n";
	return -1;
    }
453
    return system("$USERMOD $login -s $shell -g $gid $glist");
454
455
456
457
}

#
# Add a user.
Ryan Jackson's avatar
Ryan Jackson committed
458
#
459
sub os_useradd($$$$$$$$$)
460
{
461
    my($login, $uid, $gid, $pswd, $glist, $homedir, $gcos, $root, $shell) = @_;
462
    my $args = "";
463
464
465
466
467

    if ($root) {
	$glist = join(',', split(/,/, $glist), "wheel");
    }
    if ($glist ne "") {
468
469
470
471
472
	$args .= "-G $glist ";
    }
    # If remote, let it decide where to put the homedir.
    if (!REMOTE()) {
	$args .= "-d $homedir ";
473
474
475
476

	# Locally, if directory exists and is populated, skip -m
	# cause FreeBSD copies files in anyway!
	$args .= "-m "
477
	    if (! -d $homedir || ! -e "$homedir/.profile");
478
    }
479
480
481
482
483
484
485
    else {
	# populate on remote nodes. At some point will tar files over.
	$args .= "-m ";
    }

    # Map the shell into a full path.
    $shell = MapShell($shell);
486

487
    if (system("$USERADD $login -u $uid -g $gid $args ".
488
	       "-s $shell -c \"$gcos\"") != 0) {
489
490
491
	warn "*** WARNING: $USERADD $login error.\n";
	return -1;
    }
492
    chown($uid, $gid, $homedir);
493

494
    if (system("$CHPASS '$pswd' $login") != 0) {
495
496
497
498
499
500
	warn "*** WARNING: $CHPASS $login error.\n";
	return -1;
    }
    return 0;
}

501
502
#
# Modify user password
Ryan Jackson's avatar
Ryan Jackson committed
503
#
504
505
506
507
508
509
510
511
512
sub os_modpasswd($$)
{
    my($login, $pswd) = @_;

    if (system("$CHPASS '$pswd' $login") != 0) {
	warn "*** WARNING: $CHPASS $login error.\n";
	return -1;
    }
    if ($login eq "root" &&
513
	!system("grep -q toor /etc/passwd") &&
514
515
516
517
518
519
520
	system("$CHPASS '$pswd' toor") != 0) {
	warn "*** WARNING: $CHPASS $login error.\n";
	return -1;
    }
    return 0;
}

521
522
523
524
525
526
527
528
#
# Remove a homedir. Might someday archive and ship back.
#
sub os_homedirdel($$)
{
    return 0;
}

529
530
531
532
533
534
535
536
537
538
539
540
541
#
# Create a directory including all intermediate directories.
#
sub os_mkdir($$)
{
    my ($dir, $mode) = @_;

    if (system("$MKDIR -p -m $mode $dir")) {
	return 0;
    }
    return 1;
}

542
#
Ryan Jackson's avatar
Ryan Jackson committed
543
544
# OS Dependent configuration.
#
545
546
sub os_setup()
{
547
    # This should never happen!
548
    if ((REMOTE() && !REMOTEDED()) || MFS()) {
549
550
	print "Ignoring os setup on remote/MFS node!\n";
	return 0;
551
    }
552
553
}

554
555
556
557
558
559
#
# OS dependent, routing-related commands
#
sub os_routing_enable_forward()
{
    my $cmd;
560
    my $fname = libsetup::ISDELAYNODEPATH();
561

562
    if (REMOTE() && !REMOTEDED()) {
563
564
	$cmd = "echo 'IP forwarding not turned on!'";
    }
565
566
567
    elsif (JAILED()) {
	$cmd = "# IP forwarding is enabled outside the jail";
    } else {
Ryan Jackson's avatar
Ryan Jackson committed
568
	# No Fast Forwarding when operating with linkdelays.
569
	$cmd  = "sysctl net.inet.ip.forwarding=1\n" .
570
	        "    if [ ! -e $fname ]; then\n" .
571
	        "        sysctl net.inet.ip.fastforwarding=1\n" .
572
		"    fi\n";
573
    }
574
575
576
    return $cmd;
}

577
sub os_routing_enable_gated($)
578
{
579
    my ($conffile) = @_;
580
581
    my $cmd;

582
    if (REMOTE() && !REMOTEDED()) {
583
584
585
	$cmd = "echo 'GATED IS NOT ALLOWED!'";
    }
    else {
586
	$cmd = "$GATED -f $conffile";
587
    }
588
589
590
    return $cmd;
}

591
sub os_routing_add_manual($$$$$;$)
592
{
593
    my ($routetype, $destip, $destmask, $gate, $cost, $rtabid) = @_;
594
    my $cmd;
595
    my $rtabopt = (defined($rtabid) ? "-rtabid $rtabid" : "");
596
597

    if ($routetype eq "host") {
598
	$cmd = "$ROUTE add $rtabopt -host $destip $gate";
599
    } elsif ($routetype eq "net") {
600
	$cmd = "$ROUTE add $rtabopt -net $destip $gate $destmask";
601
602
    } elsif ($routetype eq "default") {
	$cmd = "$ROUTE add $rtabopt default $gate";
603
604
    } else {
	warn "*** WARNING: bad routing entry type: $routetype\n";
605
	$cmd = "false";
606
607
608
609
610
    }

    return $cmd;
}

611
sub os_routing_del_manual($$$$$;$)
612
{
613
    my ($routetype, $destip, $destmask, $gate, $cost, $rtabid) = @_;
614
    my $cmd;
615
    my $rtabopt = (defined($rtabid) ? "-rtabid $rtabid" : "");
616
617

    if ($routetype eq "host") {
618
	$cmd = "$ROUTE delete $rtabopt -host $destip";
619
    } elsif ($routetype eq "net") {
620
	$cmd = "$ROUTE delete $rtabopt -net $destip $gate $destmask";
621
622
    } elsif ($routetype eq "default") {
	$cmd = "$ROUTE delete $rtabopt default";
623
624
    } else {
	warn "*** WARNING: bad routing entry type: $routetype\n";
625
	$cmd = "false";
626
627
628
629
630
    }

    return $cmd;
}

631
632
633
634
635
636
637
638
639
640
# Map a shell name to a full path using /etc/shells
sub MapShell($)
{
   my ($shell) = @_;

   if ($shell eq "") {
       return $DEFSHELL;
   }

   my $fullpath = `grep '/${shell}\$' $SHELLS`;
Ryan Jackson's avatar
Ryan Jackson committed
641

642
643
644
645
646
647
648
649
650
651
652
653
654
655
   if ($?) {
       return $DEFSHELL;
   }

   # Sanity Check
   if ($fullpath =~ /^([-\w\/]*)$/) {
       $fullpath = $1;
   }
   else {
       $fullpath = $DEFSHELL;
   }
   return $fullpath;
}

Mike Hibler's avatar
Mike Hibler committed
656
657
658
659
# Return non-zero if given directory is on a "local" filesystem
sub os_islocaldir($)
{
    my ($dir) = @_;
Ryan Jackson's avatar
Ryan Jackson committed
660
    my $rv = 0;
Mike Hibler's avatar
Mike Hibler committed
661

662
663
664
665
666
667
668
669
670
671
672
673
674
675
    #
    # XXX 'df -l' doesn't do the right thing on older FreeBSD
    # so we compare what df returns to the mount table.  If the current
    # hack doesn't work, we will now err on the safe side and say the
    # homedir is remote, since our caller only gets destructive if the
    # homedir is local.
    #
    my $dfoutput = `$DF -l $dir | grep -v -i filesystem 2>/dev/null`;
    my $mdev = (split('\s+', $dfoutput))[0];
    if ($mdev) {
	my $mountout = `$MOUNT | grep $mdev`;
	if ($mountout && $mountout ne "" && $mountout !~ /\(nfs/) {
	    $rv = 1;
	}
Mike Hibler's avatar
Mike Hibler committed
676
677
678
679
    }
    return $rv;
}

680
#
Ryan Jackson's avatar
Ryan Jackson committed
681
682
# Find out what NFS mounts exist already!
#
683
684
685
686
687
688
sub os_getnfsmounts($)
{
    my ($rptr) = @_;
    my %mounted = ();

    #
Ryan Jackson's avatar
Ryan Jackson committed
689
    # Grab the output of the mount command and parse.
690
    #
691
    if (! open(MOUNT, "$MOUNT|")) {
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
	print "os_getnfsmounts: Cannot run mount command\n";
	return -1;
    }
    while (<MOUNT>) {
	if ($_ =~ /^([-\w\.\/:\(\)]+) on ([-\w\.\/]+) \((.*)\)$/) {
	    # Search for nfs string in the option list.
	    foreach my $opt (split(',', $3)) {
		if ($opt eq "nfs") {
		    $mounted{$1} = $2;
		}
	    }
	}
    }
    close(MOUNT);
    %$rptr = %mounted;
    return 0;
}

710
711
712
713
714
sub os_fwconfig_line($@)
{
    my ($fwinfo, @fwrules) = @_;
    my ($upline, $downline);

715
716
717
718
719
    if ($fwinfo->{TYPE} ne "ipfw" && $fwinfo->{TYPE} ne "ipfw2-vlan") {
	warn "*** WARNING: unsupported firewall type '", $fwinfo->{TYPE}, "'\n";
	return ("false", "false");
    }

720
721
722
    # XXX debugging
    my $logaccept = defined($fwinfo->{LOGACCEPT}) ? $fwinfo->{LOGACCEPT} : 0;
    my $logreject = defined($fwinfo->{LOGREJECT}) ? $fwinfo->{LOGREJECT} : 0;
723
    my $dotcpdump = defined($fwinfo->{LOGTCPDUMP}) ? $fwinfo->{LOGTCPDUMP} : 0;
724

Mike Hibler's avatar
Mike Hibler committed
725
726
727
728
729
730
731
732
733
734
735
736
737
738
    #
    # Convert MAC info to a useable form and filter out the firewall itself
    #
    my $href = $fwinfo->{MACS};
    while (my ($node,$mac) = each(%$href)) {
	if ($mac eq $fwinfo->{OUT_IF}) {
	    delete($$href{$node});
	} elsif ($mac =~ /^(\w{2})(\w{2})(\w{2})(\w{2})(\w{2})(\w{2})$/) {
	    $$href{$node} = "$1:$2:$3:$4:$5:$6";
	} else {
	    warn "*** WARNING: Bad MAC returned for $node in fwinfo: $mac\n";
	    return ("false", "false");
	}
    }
739
740
741
742
743
744
745
746
747
748
749
    $href = $fwinfo->{SRVMACS};
    while (my ($node,$mac) = each(%$href)) {
	if ($mac eq $fwinfo->{OUT_IF}) {
	    delete($$href{$node});
	} elsif ($mac =~ /^(\w{2})(\w{2})(\w{2})(\w{2})(\w{2})(\w{2})$/) {
	    $$href{$node} = "$1:$2:$3:$4:$5:$6";
	} else {
	    warn "*** WARNING: Bad MAC returned for $node in fwinfo: $mac\n";
	    return ("false", "false");
	}
    }
Mike Hibler's avatar
Mike Hibler committed
750

751
752
753
754
755
756
757
758
759
760
    #
    # VLAN enforced layer2 firewall with FreeBSD/IPFW2
    #
    if ($fwinfo->{TYPE} eq "ipfw2-vlan") {
	if (!defined($fwinfo->{IN_VLAN})) {
	    warn "*** WARNING: no VLAN for ipfw2-vlan firewall, NOT SETUP!\n";
	    return ("false", "false");
	}

	my $vlandev = "vlan0";
761
	my $vip;
762
763
764
765
766
767
768
769
770
771
	my $vlanno  = $fwinfo->{IN_VLAN};
	my $pdev    = `$BINDIR/findif $fwinfo->{IN_IF}`;
	chomp($pdev);

	$upline  = "ifconfig $vlandev create link vlan $vlanno vlandev $pdev\n";
	$upline .= "    if [ -z \"`sysctl net.link.ether.bridge 2>/dev/null`\" ]; then\n";
	$upline .= "        kldload bridge.ko >/dev/null 2>&1\n";
	$upline .= "    fi\n";
	$upline .= "    sysctl net.link.ether.bridge_vlan=0\n";
	$upline .= "    sysctl net.link.ether.bridge_ipfw=1\n";
Mike Hibler's avatar
Mike Hibler committed
772
	$upline .= "    sysctl net.link.ether.ipfw=0\n";
773
774
775
776
	$upline .= "    sysctl net.link.ether.bridge_cfg=$vlandev,$pdev\n";
	$upline .= "    if [ -z \"`sysctl net.inet.ip.fw.enable 2>/dev/null`\" ]; then\n";
	$upline .= "        kldload ipfw.ko >/dev/null 2>&1\n";
	$upline .= "    fi\n";
777

Mike Hibler's avatar
Mike Hibler committed
778
	#
779
780
781
782
783
784
785
786
787
788
789
	# Setup proxy ARP entries.
	#
	# Since we want to control which entries are proxied on inside/outside
	# we use our multiple routing table support and put the inside IF
	# into a different routing domain.  Then we can enter entries into
	# one routing table or the other.
	#
	# XXX this blows: in order to enter an ARP entry on the vlan0
	# interface, it has to be configured with an address in the same
	# subnet.  So we give vlan0 our same address (the beauty of separate
	# routing tables).  This *shouldn't* confuse anything on the firewall.
Mike Hibler's avatar
Mike Hibler committed
790
791
	#
	if (defined($fwinfo->{MACS})) {
792
	    my $myip = `cat $BOOTDIR/myip`;
793
	    chomp($myip);
794
	    my $mymask = `cat $BOOTDIR/mynetmask`;
795
	    chomp($mymask);
Mike Hibler's avatar
Mike Hibler committed
796

797
798
	    $upline .=
		"    ifconfig $vlandev inet $myip netmask $mymask rtabid 2\n";
Mike Hibler's avatar
Mike Hibler committed
799

800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
	    #
	    # XXX this blows II: for vnodes, we need to proxy arp the vnode
	    # network GW, so we have to give both devices a vnode addr
	    # alias as well! To give us a unique addr, we make sure that the
	    # vnode net is at least /16 and take the last two octets of the
	    # real address. This will work for Utah.
	    #
	    if (defined($fwinfo->{VARS}->{EMULAB_VGWIP}) &&
		defined($fwinfo->{VARS}->{EMULAB_VCNET})) {
		my $vgwip = $fwinfo->{VARS}->{EMULAB_VGWIP};
		my $bits = 0;
		if ($fwinfo->{VARS}->{EMULAB_VCNET} =~ /\/(\d+)$/) {
		    $bits = 32 - $1;
		}
		if ($bits >= 16) {
		    if ($vgwip =~ /^(\d+\.\d+)\.\d+\.\d+$/ && ($vip = $1) &&
			$myip =~ /^\d+\.\d+\.(\d+\.\d+)$/) {
			$vip .= ".$1";
			my $vmask =
			    sprintf "0x%08x", (~0 << $bits) & 0xFFFFFFFF;
			$upline .=
			    "    ifconfig $pdev alias $vip netmask $vmask\n";
			$upline .=
			    "    ifconfig $vlandev alias $vip netmask $vmask rtabid 2\n";
		    }
		}
	    }

828
829
830
831
832
833
834
835
	    # publish servers (including GW) on inside and for us on outside
	    if (defined($fwinfo->{SRVMACS})) {
		my $href = $fwinfo->{SRVMACS};
		while (my ($ip,$mac) = each %$href) {
		    $upline .= "    arp -r 2 -s $ip $mac pub only\n";
		    $upline .= "    arp -s $ip $mac\n";
		}
	    }
Mike Hibler's avatar
Mike Hibler committed
836

837
	    # provide node MACs to outside, and unpublished on inside for us
Mike Hibler's avatar
Mike Hibler committed
838
839
	    my $href = $fwinfo->{MACS};
	    while (my ($node,$mac) = each %$href) {
840
841
		$upline .= "    arp -s $node $mac pub only\n";
		$upline .= "    arp -r 2 -s $node $mac\n";
Mike Hibler's avatar
Mike Hibler committed
842
843
	    }
	}
844
	foreach my $rule (sort { $a->{RULENO} <=> $b->{RULENO}} @fwrules) {
845
846
847
848
849
850
851
852
853
854
	    my $rulestr = $rule->{RULE};
	    if ($logaccept && $rulestr =~ /^(allow|accept|pass|permit)\s.*/) {
		my $action = $1;
		$rulestr =~ s/$action/$action log/;
	    } elsif ($logreject && $rulestr =~ /^(deny|drop)\s.*/) {
		my $action = $1;
		$rulestr =~ s/$action/$action log/;
	    }

	    $upline .= "    ipfw add $rule->{RULENO} $rulestr || {\n";
855
	    $upline .= "        echo 'WARNING: could not load ipfw rule:'\n";
856
857
	    $upline .= "        echo '  $rulestr'\n";
	    $upline .= "        ipfw -q flush\n";
858
859
860
	    $upline .= "        exit 1\n";
	    $upline .= "    }\n";
	}
861
862
863
864
865
866
867
868

	if ($dotcpdump) {
	    $upline .= "    tcpdump -i $vlandev ".
		"-w $LOGDIR/in.tcpdump >/dev/null 2>&1 &\n";
	    $upline .= "    tcpdump -i $pdev ".
		"-w $LOGDIR/out.tcpdump not vlan >/dev/null 2>&1 &\n";
	}

869
870
871
	if ($logaccept || $logreject) {
	    $upline .= "    sysctl net.inet.ip.fw.verbose=1\n";
	}
872
873
874
875
	$upline .= "    sysctl net.inet.ip.fw.enable=1 || {\n";
	$upline .= "        echo 'WARNING: could not enable firewall'\n";
	$upline .= "        exit 1\n";
	$upline .= "    }\n";
876
877
	$upline .= "    sysctl net.link.ether.bridge=1";

878
879
880
881
882
883
884
885
886
887
888
889
890
	#
	# XXX maybe we should be more careful to ensure that the bridge
	# is really down before turning off the firewall.  OTOH, if
	# someone has really hacked the firewall to the extent that they
	# can prevent us from shutting down the bridge, then they should
	# be quite capable of taking down the firewall on their own.
	#
	$downline  = "sysctl net.link.ether.bridge=0 || {\n";
	$downline .= "        echo 'WARNING: could not disable bridge'\n";
	$downline .= "        echo '         firewall left enabled'\n";
	$downline .= "        exit 1\n";
	$downline .= "    }\n";
	$downline .= "    sysctl net.inet.ip.fw.enable=0\n";
891
892
893
	if ($dotcpdump) {
	    $downline .= "    killall tcpdump >/dev/null 2>&1\n";
	}
894
895
896
	if ($logaccept || $logreject) {
	    $downline .= "    sysctl net.inet.ip.fw.verbose=0\n";
	}
897
898
899
900
	$downline .= "    ipfw -q flush\n";
	$downline .= "    sysctl net.link.ether.bridge_cfg=\"\"\n";
	$downline .= "    sysctl net.link.ether.bridge_ipfw=0\n";
	$downline .= "    sysctl net.link.ether.bridge_vlan=1\n";
Mike Hibler's avatar
Mike Hibler committed
901
902
903
	if (defined($fwinfo->{MACS})) {
	    my $href = $fwinfo->{MACS};
	    while (my ($node,$mac) = each %$href) {
904
905
		$downline .= "    arp -d $node pub\n";
		$downline .= "    arp -r 2 -d $node\n";
Mike Hibler's avatar
Mike Hibler committed
906
907
	    }
	}
908
909
910
911
912
913
914
915
916
917
918
	if (defined($fwinfo->{SRVMACS})) {
	    my $href = $fwinfo->{SRVMACS};
	    while (my ($ip,$mac) = each %$href) {
		$downline .= "    arp -d $ip\n";
		$downline .= "    arp -r 2 -d $ip pub\n";
	    }
	}
	$downline .= "    ifconfig $vlandev destroy\n";
	if ($vip) {
	    $downline .= "    ifconfig $pdev -alias $vip";
	}
919
920
921
922
923
924
925
926
927
928

	return ($upline, $downline);
    }

    #
    # Voluntary IP firewall with FreeBSD/IPFW
    #
    $upline  = "if [ -z \"`sysctl net.inet.ip.fw.enable 2>/dev/null`\" ]; then\n";
    $upline .= "        kldload ipfw.ko >/dev/null 2>&1\n";
    $upline .= "    fi\n";
929

930
    foreach my $rule (sort { $a->{RULENO} <=> $b->{RULENO}} @fwrules) {
931
932
933
934
935
936
937
938
939
940
	my $rulestr = $rule->{RULE};
	if ($logaccept && $rulestr =~ /^(allow|accept|pass|permit)\s.*/) {
	    my $action = $1;
	    $rulestr =~ s/$action/$action log/;
	} elsif ($logreject && $rulestr =~ /^(deny|drop)\s.*/) {
	    my $action = $1;
	    $rulestr =~ s/$action/$action log/;
	}

	$upline .= "    ipfw add $rule->{RULENO} $rulestr || {\n";
941
	$upline .= "        echo 'WARNING: could not load ipfw rule:'\n";
942
943
	$upline .= "        echo '  $rulestr'\n";
	$upline .= "        ipfw -q flush\n";
944
945
946
	$upline .= "        exit 1\n";
	$upline .= "    }\n";
    }
947
948
949
950
    $upline .= "    sysctl net.inet.ip.fw.enable=1 || {\n";
    $upline .= "        echo 'WARNING: could not enable firewall'\n";
    $upline .= "        exit 1\n";
    $upline .= "    }\n";
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
    $upline .= "    sysctl net.inet.ip.redirect=0\n";
    $upline .= "    sysctl net.inet.ip.forwarding=1";

    $downline  = "sysctl net.inet.ip.forwarding=0\n";
    $downline .= "    sysctl net.inet.ip.redirect=1\n";
    $downline .= "    ipfw -q flush\n";
    $downline .= "    kldunload ipfw.ko >/dev/null 2>&1";

    return ($upline, $downline);
}

sub os_fwrouteconfig_line($$$)
{
    my ($orouter, $fwrouter, $routestr) = @_;
    my ($upline, $downline);

    #
    # XXX assume the original default route should be used to reach servers.
    #
    # For setting up the firewall, this means we create explicit routes for
    # each host via the original default route.
    #
    # For tearing down the firewall, we just remove the explicit routes
    # and let them fall back on the now re-established original default route.
    #
    $upline  = "for vir in $routestr; do\n";
    $upline .= "        $ROUTE -q delete \$vir >/dev/null 2>&1\n";
    $upline .= "        $ROUTE -q add \$vir $orouter || {\n";
    $upline .= "            echo \"Could not establish route for \$vir\"\n";
    $upline .= "            exit 1\n";
    $upline .= "        }\n";
    $upline .= "    done";

    $downline  = "for vir in $routestr; do\n";
    $downline .= "        $ROUTE -q delete \$vir >/dev/null 2>&1\n";
    $downline .= "    done";

    return ($upline, $downline);
}

991
sub os_config_gre($$$$$$$;$)
992
{
993
994
995
996
997
998
999
1000
    my ($name, $unit, $inetip, $peerip, $mask, $srchost, $dsthost, $tag) = @_;

    if (GENVNODE() && GENVNODETYPE() eq "openvz") {
	$dev = "gre$unit";

	if (system("$IFCONFIGBIN $dev $inetip netmask $mask mtu 1472 up")) {
	    warn("Could not start tunnel $dev!\n");
	    return -1;