lab: multi-line answer boxes, one sheet per student
The fill-in fields put the label on its own line with dotted lines to write on. handout.tex takes \studentnum and \studenthex; build-class.py compiles one copy per student with the prefix filled in, checks each is two pages and joins them for double-sided printing. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
2910a2a800
commit
b1b72fed39
3 files changed
+89
-7
No files matched your search
Executable
+63
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env python3
|
||||
"""One exercise sheet per student, with their prefix filled in, in one PDF.
|
||||
|
||||
python3 build-class.py 100 # students 1..100 -> class.pdf
|
||||
python3 build-class.py 100 -o out.pdf
|
||||
python3 build-class.py 5 12 -o few.pdf # students 5..12
|
||||
|
||||
Student n gets 2a0d:d741:<n in hex>::/48; the lab /40 allows 1..255. Each
|
||||
sheet is compiled separately and must come out to exactly two pages, so
|
||||
the result prints double-sided with one student per leaf. Needs pdflatex
|
||||
and pdfunite (poppler).
|
||||
"""
|
||||
import argparse
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
def page_count(pdf):
|
||||
out = subprocess.run(["pdfinfo", pdf], capture_output=True, text=True, check=True).stdout
|
||||
return int(next(line.split()[-1] for line in out.splitlines() if line.startswith("Pages:")))
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description=__doc__.split("\n")[0])
|
||||
ap.add_argument("first", type=int, help="first student number, or the count if it is the only number")
|
||||
ap.add_argument("last", type=int, nargs="?")
|
||||
ap.add_argument("-o", "--output", default=os.path.join(HERE, "class.pdf"))
|
||||
args = ap.parse_args()
|
||||
first, last = (1, args.first) if args.last is None else (args.first, args.last)
|
||||
if not 1 <= first <= last <= 255:
|
||||
sys.exit("student numbers must be within 1..255: the lab /40 has 255 usable /48s")
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
for name in os.listdir(HERE):
|
||||
if name.endswith((".tex", ".cls", ".sty", ".bst", ".bib")):
|
||||
shutil.copy(os.path.join(HERE, name), tmp)
|
||||
parts = []
|
||||
for n in range(first, last + 1):
|
||||
job = f"student-{n:03d}"
|
||||
tex = f"\\def\\studentnum{{{n}}}\\def\\studenthex{{{n:x}}}\\input{{handout.tex}}"
|
||||
for _ in range(2): # twice, for the figure and table references
|
||||
r = subprocess.run(["pdflatex", "-interaction=nonstopmode", "-halt-on-error", "-jobname", job, tex],
|
||||
cwd=tmp, capture_output=True, text=True)
|
||||
if r.returncode != 0:
|
||||
sys.exit(f"student {n}: pdflatex failed:\n{r.stdout[-2000:]}")
|
||||
pdf = os.path.join(tmp, job + ".pdf")
|
||||
pages = page_count(pdf)
|
||||
if pages != 2:
|
||||
sys.exit(f"student {n}: sheet is {pages} pages, not 2; double-sided printing would misalign")
|
||||
parts.append(pdf)
|
||||
print(f"\rstudent {n}: 2a0d:d741:{n:x}::/48", end="", flush=True)
|
||||
print()
|
||||
subprocess.run(["pdfunite", *parts, args.output], check=True)
|
||||
print(f"wrote {args.output}: students {first}..{last}, {page_count(args.output)} pages")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Binary file not shown.
@@ -23,7 +23,17 @@
|
||||
|
||||
% ---- things to change per class -------------------------------------------
|
||||
\newcommand{\laburl}{https://bgplab.rayhaan.net/}
|
||||
\newcommand{\myprefix}{\texttt{2a0d:d741:}\underline{\texttt{\,n\,}}\texttt{::/48}}
|
||||
% build-class.py compiles one copy per student with \studentnum and
|
||||
% \studenthex defined (e.g. 26 and 1a); without them this is the blank sheet.
|
||||
\providecommand{\studentnum}{}
|
||||
\providecommand{\studenthex}{}
|
||||
\ifx\studentnum\empty
|
||||
\newcommand{\myprefix}{\texttt{2a0d:d741:}\underline{\texttt{\,n\,}}\texttt{::/48}}
|
||||
\newcommand{\nhex}{<n>}
|
||||
\else
|
||||
\newcommand{\myprefix}{\texttt{2a0d:d741:\studenthex::/48}}
|
||||
\newcommand{\nhex}{\studenthex}
|
||||
\fi
|
||||
|
||||
% ---- exercise machinery ------------------------------------------------------
|
||||
\lstset{basicstyle=\ttfamily\small,columns=fullflexible,keepspaces=true,
|
||||
@@ -54,6 +64,7 @@
|
||||
\maketitle
|
||||
|
||||
\medskip\noindent
|
||||
\ifx\studentnum\empty
|
||||
\parbox[t]{0.44\linewidth}{Name:~\dotfill}\hfill
|
||||
\parbox[t]{0.27\linewidth}{Prefix: \myprefix}
|
||||
|
||||
@@ -62,6 +73,14 @@ Your prefix is \myprefix, where \texttt{n} is your number \emph{in
|
||||
hexadecimal}.\par\noindent
|
||||
Student 10 has \texttt{2a0d:d741:a::/48} and student 26 has
|
||||
\texttt{2a0d:d741:1a::/48}.
|
||||
\else
|
||||
\parbox[t]{0.44\linewidth}{Name:~\dotfill}\hfill
|
||||
\parbox[t]{0.5\linewidth}{Student \studentnum\quad Prefix: \myprefix}
|
||||
|
||||
\smallskip\noindent
|
||||
Your prefix is \myprefix: student number \studentnum, written in
|
||||
hexadecimal. Make sure to use this prefix in the exercise.
|
||||
\fi
|
||||
|
||||
% ==============================================================================
|
||||
\section{Who Announces the Address You Are Using?}
|
||||
@@ -76,8 +95,8 @@ announces that prefix.
|
||||
The RIPE database records who holds address space
|
||||
(\texttt{inet6num}, or \texttt{inetnum} for IPv4) and who may announce it
|
||||
(\texttt{route6}, \texttt{route}). Look up your IPv6 address, then your lab prefix:
|
||||
\begin{lstlisting}
|
||||
whois <your IPv6 address> whois 2a0d:d741:<n>::/48
|
||||
\begin{lstlisting}[escapeinside={(*}{*)}]
|
||||
whois <your IPv6 address> whois 2a0d:d741:(*\nhex*)::/48
|
||||
\end{lstlisting}
|
||||
\fillintwo{Your IPv6 address: held by (\texttt{netname})}{\texttt{origin} AS:}
|
||||
\fillintwo{Your lab prefix: \texttt{origin} AS}{is it announced right now?}
|
||||
@@ -116,7 +135,7 @@ Click \emph{bytes} next to your route and hover over the coloured segments.
|
||||
\fillintwo{AS number(s) in \texttt{AS\_PATH}:}{attribute carrying your prefix:}
|
||||
|
||||
\task{See it from outside.}
|
||||
Wait a moment, then look your prefix up on \texttt{bgp.tools}.
|
||||
Wait a moment, then look your prefix up on \texttt{https://bgp.tools/super-lg}.
|
||||
\fillintwo{Origin AS the world sees:}{an AS path you found:}
|
||||
\noindent The lab daemon's AS4200000001 is missing from that path. Where did it go, and why?
|
||||
\answer[2]
|
||||
@@ -124,7 +143,7 @@ Wait a moment, then look your prefix up on \texttt{bgp.tools}.
|
||||
\task{Reach it.}
|
||||
The router answers for every address in the lab space, so your prefix is
|
||||
reachable exactly when your announcement got out:\quad
|
||||
\texttt{ping -6 2a0d:d741:<n>::1}
|
||||
\texttt{ping -6 2a0d:d741:\nhex::1}
|
||||
\reach
|
||||
|
||||
% ==============================================================================
|
||||
@@ -154,7 +173,7 @@ Announce your prefix again with \texttt{210036:0:0} and
|
||||
\texttt{210036:1:42476}; announcing a prefix you own replaces it. Before you
|
||||
look: who do you expect to still see it?
|
||||
\answer[2]
|
||||
\noindent Wait a moment, then check on \texttt{bgp.tools} and with \texttt{ping}.
|
||||
\noindent Wait a moment, then check on \texttt{https://bgp.tools/super-lg} and with \texttt{ping}.
|
||||
\reach
|
||||
|
||||
\task{To SWITCH only.}
|
||||
@@ -162,7 +181,7 @@ Add \texttt{42476:0:0} and \texttt{42476:1:559} to the two you already have and
|
||||
announce again. Our router now tells only the route servers, and they tell
|
||||
only SWITCH.
|
||||
\reach
|
||||
\noindent Is your prefix still visible on \texttt{bgp.tools}? Explain what you see.
|
||||
\noindent Is your prefix still visible on \texttt{https://bgp.tools/super-lg}? Explain what you see.
|
||||
\answer[2]
|
||||
|
||||
\task{Use of large communities.}
|
||||
|
||||
Reference in new issue
Block a user