named_setup.in 11.4 KB
Newer Older
1
#!/usr/bin/perl -wT
Leigh B. Stoller's avatar
Leigh B. Stoller committed
2
3
4

#
# EMULAB-COPYRIGHT
5
# Copyright (c) 2000-2003 University of Utah and the Flux Group.
Leigh B. Stoller's avatar
Leigh B. Stoller committed
6
7
8
# All rights reserved.
#

9
10
11
12
13
14
use English;
use Fcntl ':flock';

#
# Suck out virtual names and create CNAME map entries.
#
15
16
# This script always does the right thing, so it does not matter who calls it. 
#
17
18
19
20
21
22
23
# usage: named_setup
#

#
# Configure variables
#
my $TB		= "@prefix@";
24
my $TBOPS       = "@TBOPSEMAIL@";
25
my $USERS	= "@USERNODE@";
26
my $DISABLED    = "@DISABLE_NAMED_SETUP@";
27
my $OURDOMAIN   = "@OURDOMAIN@";
28

29
30
31
32
33
34
35
36
37
38
my $mapdir			= "/etc/namedb";
my $mapfile			= "$mapdir/${OURDOMAIN}.db";
my $mapfiletail			= "$mapfile.tail";
my $mapfile_internal		= "$mapdir/${OURDOMAIN}.internal.db";
my $mapfile_internal_head	= "$mapfile_internal.head";
my $vnodesfile			= "$mapdir/vnodes.${OURDOMAIN}.db";
my $vnodesback 			= "$mapdir/vnodes.${OURDOMAIN}.db.backup";
my $reversedir			= "$mapdir/reverse";
my $lockfile			= "/var/tmp/testbed_named_lockfile";
my $dbg	= 0;
39
40
my @row;

41
42
use strict;

43
44
45
46
47
# If we're disabled, just quietly exit
if ($DISABLED) {
    exit 0;
}

48
49
# We don't want to run this script unless its the real version.
if ($EUID != 0) {
50
51
    die("*** $0:\n".
	"    Must be root! Maybe its a development version?\n");
52
53
}
# XXX Hacky!
54
if (0 && $TB ne "/usr/testbed") {
55
56
    die("*** $0:\n".
	"    Wrong version. Maybe its a development version?\n");
57
58
}

59
60
61
62
# un-taint path
$ENV{'PATH'} = '/bin:/usr/bin:/usr/sbin:/usr/local/bin';
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};

63
64
# Turn off line buffering on output
$| = 1;
65

66
# Testbed Support libraries
67
68
69
use lib "@prefix@/lib";
use libtestbed;
use libdb;
70
71
72
73
74
75

#
# We need to serialize this script to avoid a trashed map file. Use
# a dummy file in /var/tmp, opened for writing and flock'ed. 
#
open(LOCK, ">>$lockfile") || fatal("Couldn't open $lockfile\n");
76
my $count = 0;
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
if (flock(LOCK, LOCK_EX|LOCK_NB) == 0) {
    #
    # If we don't get it the first time, we wait for:
    # 1) The lock to become free, in which case we do our thing
    # 2) The time on the lock to change, in which case we wait for that process
    #    to finish
    #
    my $oldlocktime = (stat(LOCK))[9];
    my $gotlock = 0;
    while (1) {
	print "Another named map update in progress, waiting for it to finish\n";
	if (flock(LOCK, LOCK_EX|LOCK_NB) != 0) {
	    # OK, got the lock, we can do what we're supposed to
	    $gotlock = 1;
	    last;
	}
93
	my $locktime = (stat(LOCK))[9];
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
	if ($locktime != $oldlocktime) {
	    $oldlocktime = $locktime;
	    last;
	}
	if ($count++ > 20)  {
	    fatal("Could not get the lock after a long time!\n");
	}
	sleep(1);
    }

    $count = 0;
    #
    # If we didn't get the lock, wait for the processes that did to finish
    #
    if (!$gotlock) {
109
110
111
112
113
114
115
116
	while (1) {
	    if ((stat(LOCK))[9] != $oldlocktime) {
		exit(0);
	    }
	    if (flock(LOCK, LOCK_EX|LOCK_NB) != 0) {
		close(LOCK);
		exit(0);
	    }
117
	    if ($count++ > 20)  {
118
		fatal("Process with the lock didn't finish after a long time!\n");
119
120
	    }
	    sleep(1); 
121
122
	}

123
124
125
    }
}

126
127
128
129
130
131
132

#
# Perl-style touch(1)
#
my $now = time;
utime $now, $now, $lockfile;

133
134
135
136
137
138
139
140
141
142
143
#
# We stick the new map entries into the tail file. First zero it out.
#
open(MAP, ">$mapfiletail") || fatal("Couldn't open $mapfiletail\n");
print MAP "\n";
print MAP ";\n";
print MAP "; DO NOT EDIT below this point. Auto generated map entries!\n";
print MAP ";\n";
print MAP "\n";

#
144
145
146
# First create the IA records for those nodes that have a named_root
# defined. We glean the IP for the node from the interfaces table.
#
147
my $db_result =
148
149
150
151
    DBQueryFatal("select nt.type,nt.isremotenode,n.node_id,i.IP ".
		 "  from nodes as n ".
		 "left join node_types as nt on n.type=nt.type ".
		 "left join interfaces as i on n.node_id=i.node_id and ".
152
		 "     nt.control_iface=i.iface ".
153
154
		 "where nt.isvirtnode=0 and nt.issubnode=0 and ".
		 "      n.role='testnode' and i.IP is not null ".
155
156
		 "order by nt.type,n.node_id");

157
my %reverse;
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
if ($db_result->numrows > 0) {
    #
    # Create an IN record for each node. 
    #
    my $oldtype = "";
    
    while (@row = $db_result->fetchrow_array) {
	my $type    = $row[0];
	my $isremote= $row[1];
	my $node_id = $row[2];
	my $IP      = $row[3];

	if ($oldtype ne $type) {
	    print MAP ";\n";
	    print MAP "; $type nodes.\n";
	    print MAP ";\n";
	    
	    $oldtype = $type;
	}

178
179
180
	print MAP "$node_id\tIN\tA\t$IP\n";
	print MAP "\tIN\tMX 10\t$USERS.\n";

181
	#
182
183
184
	# Put it into a map so we can generate the reverse zone file later
	#
	$IP =~ /(\d+\.\d+\.\d+)\.(\d+)/;
185
186
187
188
189
190
191
	if ($1 && $2) {
	    my $subnet = $1;
	    my $host = $2;
	    push @{$reverse{$subnet}}, [$host, $node_id];
	} else {
	    warn "Poorly formed IP address $IP\n";
	}
192
193
194
    }
}
print MAP "\n";
195
print MAP "\$TTL\t1\n\n";
196
197
198
199

#
# Figure out the set of experiment nodes we are working on. These are the 
# nodes that have a vname in the reserved table. 
200
#
201
$db_result =
202
    DBQueryFatal("select r.node_id,pid,eid,vname,n.phys_nodeid,n.jailip ".
203
		 " from reserved as r ".
204
205
206
		 "left join nodes as n on n.node_id=r.node_id ".
		 "left join node_types as nt on nt.type=n.type ".
		 "where nt.issubnode=0");
207
208
209
210
211

if ($db_result->numrows > 0) {
    #
    # Create a CNAME for each reserved node.
    #
212
213
214
215
216
217
    while (my %row = $db_result->fetchhash) {
	my $node_id = $row{"node_id"};
	my $pid     = $row{"pid"};
	my $eid     = $row{"eid"};
	my $physid  = $row{"phys_nodeid"};
	my $jailip  = $row{"jailip"};
218
	my $vname   = $node_id;
219
	my $cname;
220

221
222
	if (defined($row{"vname"})) {
	    $vname = $row{"vname"};
223
	}
224
225

        #
226
227
228
229
        # VIRTNODE HACK: Map cname to underlying physnode, but first
	# spit out a vname for the virtnode name (no point in polluting
	# the map with a zillion cnames for nodes that do not actually
	# exist until they are allocated).
230
        #
231
232
233
234
235
236
237
238
239
	if (defined($physid) && $physid ne $node_id) {
	    if (defined($jailip)) {
		#
		# If the vnode has its own jailip, then skip this completely
		# since both the name and the cname will be entered into
		# the "private" vnodes map below. 
		# 
		next;
	    }
240
241
242
243
	    $cname   = sprintf("%-40s", "$node_id");
	    printf MAP "$cname IN\tCNAME\t$physid\n";
	    
	    $node_id = $physid;
244
	}
245
	    
246
	$cname = sprintf("%-40s", "$vname.$eid.$pid");
247
248
249
250
251
252
253
254
	printf MAP "$cname IN\tCNAME\t$node_id\n";
    }
}

print MAP "\n";
close(MAP);

#
255
# Make this map!
256
#
257
make_zonefile($mapfile);
258

259
260
261
262
263
264
265
266
#
# If they have an 'internal' zone file (ie. with some internal IPs for boss and
# ops), make that too
#
if (-e $mapfile_internal_head) {
    make_zonefile($mapfile_internal,$mapfile_internal_head,$mapfiletail);
}

267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
#
# Now we tack on the virtual node, but that has to go onto the end
# of a *copy* of the file we just created.
#
if (-e $vnodesfile) {
    system("mv -f $vnodesfile $vnodesback") == 0 or
	fatal("Could not back up $vnodesfile to $vnodesback\n");
}

system("cp $mapfile $vnodesfile") == 0 or
    fatal("Could not copy $mapfile to $vnodesfile\n");

#
# Open up the file and append the jail ips.
# 
open(MAP, ">>$vnodesfile") || fatal("Couldn't open $vnodesfile\n");
print MAP
    ";\n".
    "; Jail IPs (allocated nodes only).\n" .
    ";\n";
    
$db_result =
    DBQueryFatal("select r.*,n.jailip from reserved as r ".
		 "left join nodes as n on n.node_id=r.node_id ".
		 "left join node_types as nt on nt.type=n.type ".
292
		 "where nt.isvirtnode=1 and nt.isremotenode=0 and ".
293
		 "      nt.issubnode=0 and n.jailip is not null");
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317

if ($db_result->numrows > 0) {
    #
    # Create a CNAME for each reserved node.
    #
    while (my %row = $db_result->fetchhash) {
	my $node_id = $row{"node_id"};
	my $pid     = $row{"pid"};
	my $eid     = $row{"eid"};
	my $IP      = $row{"jailip"};
	my $vname   = $node_id;

	if (defined($row{"vname"})) {
	    $vname = $row{"vname"};
	}

	# Spit an A record for the node.
	print MAP "$node_id\tIN\tA\t$IP\n";

	# Then a CNAME.
	my $cname = sprintf("%-40s", "$vname.$eid.$pid");
	printf MAP "$cname IN\tCNAME\t$node_id\n";
    }
}
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332

#
# Other unroutable addresses.
# 
print MAP
    ";\n".
    "; Other unroutable IPs (allocated subnodes only).\n" .
    ";\n";
    
$db_result =
    DBQueryFatal("select r.node_id,r.pid,r.eid,r.vname,i.IP ".
		 "  from reserved as r ".
		 "left join nodes as n on r.node_id=n.node_id ".
		 "left join node_types as nt on n.type=nt.type ".
		 "left join interfaces as i on n.node_id=i.node_id and ".
333
		 "     nt.control_iface=i.iface ".
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
		 "where nt.issubnode=1 and ".
		 "      n.role='testnode' and i.IP is not null ".
		 "order by nt.type,n.node_id");

if ($db_result->numrows > 0) {
    #
    # Create a CNAME for each reserved node.
    #
    while (my %row = $db_result->fetchhash) {
	my $node_id = $row{"node_id"};
	my $pid     = $row{"pid"};
	my $eid     = $row{"eid"};
	my $IP      = $row{"IP"};
	my $vname   = $node_id;

	if (defined($row{"vname"})) {
	    $vname = $row{"vname"};
	}

	# Spit an A record for the node.
	print MAP "$node_id\tIN\tA\t$IP\n";

	# Then a CNAME.
	my $cname = sprintf("%-40s", "$vname.$eid.$pid");
	printf MAP "$cname IN\tCNAME\t$node_id\n";
    }
}
361
362
close(MAP);

363
364
365
366
367
368
369
370
371
372
373
374
375
#
# Look for reverse zone files that we may need to make
#
opendir(DIR,$reversedir) or fatal("Unable to open directory $reversedir\n");
while (my $dirent = readdir(DIR)) {
    if ($dirent !~ /((\d+\.\d+\.\d+)\.db)\.head/) {
	next;
    }
    my $subnet = $2;
    my $basename = $1;

    my $filename = "$reversedir/$basename.tail";
    open MAP, ">$filename" || fatal("Couldn't open $filename: $!\n");
376
377
378
379
380
    if ($reverse{$subnet}) {
	foreach my $aref (sort {$$a[0] <=> $$b[0]} @{$reverse{$subnet}}) {
	    my ($host, $name) = @$aref;
	    printf MAP "$host\tIN\tPTR\t$name.$OURDOMAIN.\n";
	}
381
382
383
384
385
386
387
388
    }
    close MAP;

    make_zonefile("$reversedir/$basename");
    
}
closedir DIR;

389
#
390
391
392
393
394
395
396
397
398
399
400
# This is better than HUPing the nameserver directly. Notet that we look
# for a local port of named first.
#
if (-x "/usr/local/sbin/rndc") {
    system("/usr/local/sbin/rndc reload > /dev/null") == 0 or
	fatal("/usr/local/sbin/rndc reload failed!\n");
}
else {
    system("named.reload > /dev/null") == 0 or
	fatal("named.reload failed!\n");
}
401
402
403
404

exit(0);

sub fatal {
405
    my $msg = $_[0];
406

407
    SENDMAIL($TBOPS, "Named Setup Failed", $msg);
408
409
    die($msg);
}
410
411
412
413

#
# Make a zone file from the 
#
414
415
sub make_zonefile($;$$) {
    my ($mapfile,$mapfilehead,$mapfiletail) = @_;
416
417

    my $mapfileback = "$mapfile.backup";
418
419
420
421
422
423
    if (!defined $mapfilehead) {
	$mapfilehead = "$mapfile.head";
    }
    if (!defined $mapfiletail) {
	$mapfiletail = "$mapfile.tail";
    }
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470

    #
    # Concat the head and tail files to create the new map.
    #
    if (-e $mapfile) {
	system("mv -f $mapfile $mapfileback") == 0 or
	fatal("Could not back up $mapfile to $mapfileback\n");
    }

    #
    # Generate a warning so that no one tries to edit the file by hand
    #
    open(MAP, ">$mapfile") || fatal("Couldn't open $mapfile\n");
    print MAP
    ";\n".
    "; ******************************************************************\n".
    "; DO NOT EDIT THIS FILE. IT IS A CREATION, A FIGMENT, A CONTRIVANCE!\n".
    ";\n".
    "; Edit the \"head\" file, then run ${TB}bin/named_setup.\n".
    "; ******************************************************************\n".
    ";\n";

    #
    # Now copy in the head part of the map, looking for the serial
    # number so it can be bumped up.
    #
    open(MAPHEAD, "<$mapfilehead") || fatal("Couldn't open $mapfilehead\n");
    while (<MAPHEAD>) {
	if ( /;\s*Serial\s+/i ) {
	    my $serial = `date +%s`;
	    chop $serial;

	    print MAP "\t\t\t$serial\t; Serial Number -- DO NOT EDIT\n";
	}
	else {
	    print MAP "$_";
	}
    }
    close(MAPHEAD);
    close(MAP);

    #
    # Now the tail of the map.
    # 
    system("cat $mapfiletail >> $mapfile") == 0 or
    fatal("Failed to concat $mapfiletail to $mapfile\n");
}