#!/usr/bin/perl -w use strict; print "Content-type: text/html\n\n"; print "\\ \\ \Quiz\ 3\<\/TITLE\>\ \<\/HEAD\>\ \\ \\<\/SCRIPT\>\ \Connected\:\ An\ Internet\ Encyclopedia\<\/B\>\ \\ \Quiz\ 3\<\/EM\>\\ \\\ \Up\:\<\/B\>\ \Connected\:\ An\ Internet\ Encyclopedia\<\/A\>\\ \Up\:\<\/B\>\ \Programmed\ Instruction\ Course\<\/A\>\\ \Up\:\<\/B\>\ \Subnetting\ and\ CIDR\<\/A\>\\ \<\/CENTER\>\ \Prev\:\<\/B\>\ \Subnet\ Masks\ \(cont\)\<\/A\>\\ \Next\:\<\/B\>\ \Quiz\ 3\ \(cont\)\<\/A\>\\ \\\ \Quiz\ 3\<\/H3\>\\ "; print "\"; print "Quiz\ 3"; print "\<\/TITLE\>"; print "\ \ "; my %FORM; my $buffer; if (exists $ENV{"CONTENT_LENGTH"}) { read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'}); } elsif (exists $ENV{"QUERY_STRING"}) { $buffer = $ENV{"QUERY_STRING"}; } # Split the name-value pairs my @pairs = split(/&/, $buffer); foreach my $pair (@pairs) { my ($name, $value) = split(/=/, $pair); # Un-Webify plus signs and %-encoding $value =~ tr/+/ /; $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg; $FORM{$name} = $value; } # Play a little game here so we can tag each quiz with a unique ID value, # to aid in recreating bugs. my $quizseed; if (exists $FORM{"seed"}) { $quizseed = $FORM{"seed"}; } else { srand; $quizseed = int rand 10000000000; } srand $quizseed; my $quizType = "difficult"; my $qnum; my @ordinal = ("first", "second", "third", "fourth"); sub randomPrefixLength { my ($type) = @_; my $allow0 = 1; my $allow32 = 1; my $result; if ($type eq "easy") { $result = 8 * (int rand (3 + $allow0 + $allow32)); $result += 8 if (not $allow0); } elsif ($type eq "medium") { $result = 8 * (int rand 4); $result += 4 + int rand 3; } else { $result = 2 + int rand 30 while ($result % 8 == 0); } return $result; } sub mask { my ($prefixLen) = @_; return 0 if ($prefixLen == 0); return (0xffffffff << (32-$prefixLen)) & 0xffffffff; } sub keyMask { my ($prefixLen) = @_; $prefixLen -= 8 while ($prefixLen > 8); return (255 << (8-$prefixLen)) & 255; } sub keyIndex { my ($prefixLen) = @_; return 0 if ($prefixLen == 0); return int (($prefixLen-1)/8); } sub keyByte { my ($prefixLen, $address) = @_; return ($address >> (8*(3-&keyIndex($prefixLen)))) & 255; } sub classA { my ($address) = @_; return ($address & 0x80000000) == 0; } sub classB { my ($address) = @_; return ($address & 0xc0000000) == 0x80000000; } sub classC { my ($address) = @_; return (($address & 0xe0000000) == 0xc0000000); } sub networkField { my ($address) = @_; if (&classA($address)) { return ($address & 0xff000000); } elsif (&classB($address)) { return ($address & 0xffff0000); } elsif (&classC($address)) { return ($address & 0xffffff00); } else { return $address; } } sub binary { my ($value) = @_; my $result = ""; for (my $i=0; $i < 8; $i ++) { $result = (($value & 1) ? "1" : "0") . $result; $value >>= 1; } return $result; } sub randomAddress { return ((1+(int rand 239)) << 24) | (int rand 1<<24); } sub randomPrefix { my ($type) = @_; my $prefixLen = &randomPrefixLength($type); my $prefixAddr = &randomAddress; $prefixAddr &= &mask($prefixLen); # If this is a "medium" difficult quiz, then make sure the # "key byte" - the one being manipulated in binary - doesn't # exceed 64. We mask it, strip it off, whittle it down, and put it back if ($type eq "medium") { my $keyMask = 255 << (8 * (3 - &keyIndex($prefixLen))); my $keyByte = $prefixAddr & $keyMask; $prefixAddr &= ~ $keyMask; $prefixAddr |= $keyByte & (63 << (8 * (3 - &keyIndex($prefixLen)))); } return ($prefixLen, $prefixAddr); } sub formatPrefixNoLen { my ($prefixLen, $prefixAddr) = @_; my $result = ""; return "0" if ($prefixLen == 0); my $limit = 3 - int(($prefixLen-1)/8); for (my $i=3; $i >= $limit; $i--) { $result .= ($prefixAddr >> (8*$i)) & 255; $result .= "." unless ($i == $limit); } return $result; } sub formatPrefix { my ($prefixLen, $prefixAddr) = @_; return &formatPrefixNoLen($prefixLen, $prefixAddr) . "/$prefixLen"; } sub formatAddress { my ($addr) = @_; my $result = ""; for (my $i=3; $i>=0; $i--) { $result .= ($addr >> (8*$i)) & 255; $result .= "." if ($i); } return $result; } sub randomMatchingAddress { my ($prefixLen, $prefixAddr) = @_; my $mask = &mask($prefixLen); return $prefixAddr | (&randomAddress & (~ $mask)); } sub prefixPermutation { my ($prefixLen) = @_; my @changes = (1, 2, 3, 4, 5, 6); my $change = 0; while ($change == 0) { $change = $changes[int rand @changes]; $change <<= (32 - $prefixLen); $change &= 0xffffffff; } return $change; } sub randomNonMatchingAddress { my ($prefixLen, $prefixAddr) = @_; my $result = &randomMatchingAddress($prefixLen, $prefixAddr); return $result ^ &prefixPermutation($prefixLen); } # Permute a prefix so it no longer matches what it used to sub permutePrefix { my ($prefixLen, $prefixAddr) = @_; if (rand() < 0.5 and $prefixLen < 29) { if ($quizType eq "easy" and $prefixLen <= 24) { $prefixLen += 8; $prefixAddr |= int(rand 256) << (32 - $prefixLen); } else { $prefixLen += 1 + int rand 2; $prefixAddr ^= &prefixPermutation($prefixLen); } } elsif (rand() < 0.5 and $prefixLen > 3) { if ($quizType eq "easy" and $prefixLen >= 8) { $prefixLen -= 8; } else { $prefixLen -= 1 + int rand 2; $prefixAddr &= &mask($prefixLen); } $prefixAddr ^= &prefixPermutation($prefixLen); } else { $prefixAddr ^= &prefixPermutation($prefixLen); } return ($prefixLen, $prefixAddr); } sub longestCommonPrefix { my @addrs = @_; my $prefixAddr; my $prefixLen; my $mask; for ($prefixLen=1; $prefixLen<=33; $prefixLen++) { last if ($prefixLen == 33); $mask = &mask($prefixLen); $prefixAddr = $addrs[0] & $mask; last if ((grep { ($prefixAddr & $mask) == ($_ & $mask) } @addrs) != (scalar @addrs)); } $prefixLen --; return ($prefixLen, $addrs[0] & &mask($prefixLen)); } sub scramble { my @array = @_; for (my $i=0; $i < scalar @array; $i++) { my $j = int rand scalar @array; my $item = $array[$i]; $array[$i] = $array[$j]; $array[$j] = $item; } return @array; } sub match { my ($addr, @prefix) = @_; return ((&mask($prefix[0]) & $addr) == (&mask($prefix[0]) & $prefix[1])); } sub contains { my ($len1, $addr1, $len2, $addr2) = @_; return 0 if ($len1 > $len2); return ((&mask($len1) & $addr1) == (&mask($len1) & $addr2)); } sub explain { my ($addr, @prefix) = @_; my $prefix1 = 8 * int($prefix[0]/8); if ($prefix[0] == 32) { print "'32 is the length of an IP address in bits',\n"; print "'so ", &formatPrefix(@prefix), " is an exact match for "; print &formatAddress($prefix[1]), "'"; } elsif ($prefix[0] == 0) { print "'0/0 matches any address'"; } elsif ($prefix[0]%8 == 0) { print "'The prefix length of $prefix[0] is a multiple of eight',\n"; print "'so the first ", $prefix[0], "/8=", $prefix[0]/8; print $prefix[0]==8 ? " byte" : " bytes"; print " must match ", &formatPrefixNoLen(@prefix), " exactly.'"; } elsif ((&mask($prefix1) & $addr) != (&mask($prefix1) & $prefix[1])) { print "'The prefix length of $prefix[0] is greater than $prefix1',\n"; print "'so the first ", int($prefix[0]/8); print $prefix[0]==8 ? " byte" : " bytes"; print " must match ", &formatPrefixNoLen($prefix1, $prefix[1]); print " exactly.'"; } else { my $binarycorrect = &binary(&keyByte(@prefix)); print "'", &keyByte(@prefix), " in binary is ", $binarycorrect; print " so the ", $ordinal[$prefix1/8], " byte\\'s',\n'first "; print "$prefix[0]-$prefix1=" if ($prefix1 != 0); print $prefix[0]-$prefix1; print (($prefix[0]-$prefix1)==1 ? " bit must be ":" bits must be "); print substr($binarycorrect, 0, $prefix[0]-$prefix1), "',\n'"; print &keyByte($prefix[0], $addr), " in binary is "; print &binary(&keyByte($prefix[0], $addr)), "'"; } } sub problem1 { # Pick a random prefix; anything other than 0/0 will work my @prefix = &randomPrefix($quizType); @prefix = &randomPrefix($quizType) while ($prefix[1] == 0); # Pick a correct answer - a matching address my $correct = &randomMatchingAddress(@prefix); # Pick three non-matching address; take care to make them all different my @wrongs; wrongs: while (@wrongs < 3) { my $addr = &randomNonMatchingAddress(@prefix); for my $others (@wrongs) { next wrongs if ($others == $addr); } push @wrongs, $addr; } my @order = &scramble(0,1,2,3); print "Which one of the following addresses matches ", &formatPrefix(@prefix), "?\n"; print "

\n"; foreach my $i (@order) { my $addr; if ($i == 0) { $addr = $correct; } else { $addr = $wrongs[$i-1]; } print "
", &formatAddress($addr), "\n\n"; } print "
\n"; print "\n"; print "

\n\n"; } sub problem2 { my @correct = &randomPrefix($quizType); my $addr = &randomMatchingAddress(@correct); # Pick three non-matching prefixes; take care to make them all different my @wrongs; wrongs: while (@wrongs < 3) { my @wrong = &permutePrefix(@correct); next wrongs if (&match($addr, @wrong)); for my $others (@wrongs) { next wrongs if ($$others[0] == $wrong[0] and $$others[1] == $wrong[1]); } push @wrongs, \@wrong; } my @order = &scramble(0,1,2,3); print "Which one of the following prefixes matches ", &formatAddress($addr), "?\n"; print "

\n"; foreach my $i (@order) { my @answer; if ($i == 0) { @answer = @correct; } else { @answer = @{$wrongs[$i-1]}; } print "
", &formatPrefix(@answer), "\n\n"; } print "
\n"; print "\n"; print "

\n\n"; } sub problem3 { my @prefix1; my $addr1; my @prefix2; my $addr2; my @correct; do { @prefix1 = &randomPrefix($quizType); $addr1 = &randomMatchingAddress(@prefix1); do { @prefix2 = &permutePrefix(@prefix1); $addr2 = &randomMatchingAddress(@prefix2); } while ($addr2 == $addr1); @correct = &longestCommonPrefix($addr1, $addr2); } while ($quizType ne "easy" and $correct[0] % 8 == 0); # Pick three non-matching prefixes; take care to make them all different my @wrongs; wrongs: while (@wrongs < 3) { my @wrong = &permutePrefix(@correct); next wrongs if (&match($addr1, @wrong) and &match($addr2, @wrong)); next wrongs if ($quizType ne "easy" and ($addr1 == $wrong[1] or $addr2 == $wrong[1])); for my $others (@wrongs) { next wrongs if ($$others[0] == $wrong[0] and $$others[1] == $wrong[1]); } push @wrongs, \@wrong; } my @order = &scramble(0,1,2,3); print "Which one of the following prefixes matches both "; print &formatAddress($addr1), " and "; print &formatAddress($addr2), "?\n"; print "

\n"; foreach my $i (@order) { my @answer; if ($i == 0) { @answer = @correct; } else { @answer = @{$wrongs[$i-1]}; } print "
", &formatPrefix(@answer), "\n\n"; } print "
\n"; print "\n"; print "

\n\n"; } sub problem4 { # Pick a random prefix my @prefix; my @correct; do { @prefix = &randomPrefix($quizType); @correct = @prefix; if ($quizType eq "easy") { $correct[0] -= 8; } else { $correct[0] -= 1 + int rand 3; } } while ($correct[0] < 0 or ($quizType ne "easy" and $correct[0]==0)); $correct[1] &= &mask($correct[0]); # Pick three non-matching prefixes; take care to make them all different my @wrongs; wrongs: while (@wrongs < 3) { my @wrong = &permutePrefix(@correct); next wrongs if (&contains(@wrong, @prefix)); for my $others (@wrongs) { next wrongs if ($$others[0] == $wrong[0] and $$others[1] == $wrong[1]); } push @wrongs, \@wrong; } print "Which one of the following address prefixes contains ", &formatPrefix(@prefix), "?\n"; print "

\n"; my @order = &scramble(0,1,2,3); foreach my $i (@order) { my @answer; if ($i == 0) { @answer = @correct; } else { @answer = @{$wrongs[$i-1]}; } print "
$prefix[0]) { print "'A $answer[0]-bit prefix can not contain a $prefix[0]-bit prefix',''"; } else { &explain($prefix[1], @answer); } print ")\">", &formatPrefix(@answer), "\n"; } print "
\n"; print "\n"; print "

\n\n"; } sub problem5 { my @prefix = &randomPrefix("difficult"); my @prefixlens = ($prefix[0]); wrongs: while (@prefixlens < 4) { my $prefixlen = int rand 33; foreach my $others (@prefixlens) { next wrongs if ($others == $prefixlen); } push @prefixlens, $prefixlen; } print "Which subnet mask corresponds to a prefix length of /", $prefix[0], "?\n"; print "

\n"; @prefixlens = &scramble(@prefixlens); foreach my $i (@prefixlens) { print "
", &formatAddress(&mask($i)), "\n"; } print "
\n"; print "\n"; print "

\n\n"; } sub problem6 { my $addr = &randomAddress(); my $a1 = ($addr>>24) & 255; my $a2 = ($addr>>16) & 255; my $a3 = ($addr>>8) & 255; my $a4 = $addr & 255; print "Enter an address prefix that matches ", &formatAddress($addr), ":\n"; print "

\n"; print "\n"; print "\n"; print "

\n"; } sub problem7 { my @prefix = &randomPrefix($quizType); my $a1 = ($prefix[1]>>24) & 255; my $a2 = ($prefix[1]>>16) & 255; my $a3 = ($prefix[1]>>8) & 255; my $a4 = $prefix[1] & 255; print "Enter an address that matches ", &formatPrefix(@prefix), ":\n"; print "

\n"; print "\n"; print "\n"; print "

\n"; } sub problem8 { my @prefix1 = &randomPrefix($quizType); my @prefix2 = &permutePrefix(@prefix1); my $addr1 = &randomMatchingAddress(@prefix1); my $addr2 = &randomMatchingAddress(@prefix2); my @prefix = &longestCommonPrefix($addr1, $addr2); my $a1 = ($prefix[1]>>24) & 255; my $a2 = ($prefix[1]>>16) & 255; my $a3 = ($prefix[1]>>8) & 255; my $a4 = $prefix[1] & 255; print "Enter the longest prefix that matches both ", &formatAddress($addr1), " and ", &formatAddress($addr2), ":\n"; print "

\n"; print "\n"; print "\n"; print "

\n"; } sub problem9 { my @answers = (- int rand 33); wrongs: while (@answers < 4) { my $masklen = 2 + int rand 31; my $answer = &mask($masklen); my $mistakebit; do { $mistakebit = 1 + int rand 31; } while ($mistakebit >= $masklen); $answer ^= 1 << (32 - $mistakebit); foreach my $others (@answers) { next wrongs if ($others == $answer); } push @answers, $answer; } print "Which of the following is a valid subnet mask?\n"; print "

\n"; @answers = &scramble(@answers); foreach my $i (@answers) { print "
"; if ($i <= 0) { print &formatAddress(&mask(- $i)); } else { print &formatAddress($i); } print "\n"; } print "
\n"; print "\n"; print "

\n\n"; } sub problem10 { my @prefix1; getprefix1: { @prefix1 = &randomPrefix('difficult'); redo getprefix1 if ((&classA($prefix1[1]) and $prefix1[0] <= 9) or (&classB($prefix1[1]) and $prefix1[0] <= 17) or (&classC($prefix1[1]) and $prefix1[0] <= 25) or (not &classA($prefix1[1]) and not &classB($prefix1[1]) and not &classC($prefix1[1])) or ($prefix1[0] > 30)); } my @wrong; wrong: { @wrong = &permutePrefix(@prefix1); redo wrong if ((&networkField($prefix1[1]) != &networkField($wrong[1])) or $prefix1[0] == $wrong[0]); } my @corrects; corrects: while (@corrects < 3) { my @correct = &permutePrefix(@prefix1); next corrects if ((&networkField($prefix1[1]) == &networkField($correct[1])) and $prefix1[0] != $correct[0]); for my $others (@corrects) { next corrects if ($$others[0] == $correct[0] and $$others[1] == $correct[1]); } push @corrects, \@correct; } my @order = &scramble(0,1,2,3); print "", &formatPrefix(@prefix1), " has been assigned to a\n"; print "subnet in a network using the RIP routing protocol. Which of\n"; print "the following prefixes can not be assigned to another\n"; print "subnet?\n"; print "

\n"; foreach my $i (@order) { my $prefix; if ($i == 0) { $prefix = \@wrong; } else { $prefix = $corrects[$i-1]; } print "
", &formatPrefix(@$prefix), "\n\n"; } print "
\n"; print "\n"; print "

\n\n"; } print "Quiz ID code: classful-$quizseed

\n\n"; $qnum=1; &problem9; $qnum=2; &problem5; $qnum=3; &problem10; print "
\n"; print "\ "; print "\\\ \\Next\:\<\/B\>\ \Quiz\ 3\ \(cont\)\<\/A\>\<\/CENTER\>\\ \Connected\:\ An\ Internet\ Encyclopedia\<\/B\>\ \\ \Quiz\ 3\<\/EM\>\ \<\/BODY\>\ \<\/HTML\>\ ";