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:
Rayhaan JaufeerallyandClaude Fable 5.1 committed 2026-10-01 20:40:55 +02:00
1 parent 2910a2a800
commit b1b72fed39
3 files changed
+89 -7

No files matched your search

+63
View File
@@ -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.
+26 -7
View File
@@ -23,7 +23,17 @@
% ---- things to change per class ------------------------------------------- % ---- things to change per class -------------------------------------------
\newcommand{\laburl}{https://bgplab.rayhaan.net/} \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 ------------------------------------------------------ % ---- exercise machinery ------------------------------------------------------
\lstset{basicstyle=\ttfamily\small,columns=fullflexible,keepspaces=true, \lstset{basicstyle=\ttfamily\small,columns=fullflexible,keepspaces=true,
@@ -54,6 +64,7 @@
\maketitle \maketitle
\medskip\noindent \medskip\noindent
\ifx\studentnum\empty
\parbox[t]{0.44\linewidth}{Name:~\dotfill}\hfill \parbox[t]{0.44\linewidth}{Name:~\dotfill}\hfill
\parbox[t]{0.27\linewidth}{Prefix: \myprefix} \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 hexadecimal}.\par\noindent
Student 10 has \texttt{2a0d:d741:a::/48} and student 26 has Student 10 has \texttt{2a0d:d741:a::/48} and student 26 has
\texttt{2a0d:d741:1a::/48}. \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?} \section{Who Announces the Address You Are Using?}
@@ -76,8 +95,8 @@ announces that prefix.
The RIPE database records who holds address space The RIPE database records who holds address space
(\texttt{inet6num}, or \texttt{inetnum} for IPv4) and who may announce it (\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: (\texttt{route6}, \texttt{route}). Look up your IPv6 address, then your lab prefix:
\begin{lstlisting} \begin{lstlisting}[escapeinside={(*}{*)}]
whois <your IPv6 address> whois 2a0d:d741:<n>::/48 whois <your IPv6 address> whois 2a0d:d741:(*\nhex*)::/48
\end{lstlisting} \end{lstlisting}
\fillintwo{Your IPv6 address: held by (\texttt{netname})}{\texttt{origin} AS:} \fillintwo{Your IPv6 address: held by (\texttt{netname})}{\texttt{origin} AS:}
\fillintwo{Your lab prefix: \texttt{origin} AS}{is it announced right now?} \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:} \fillintwo{AS number(s) in \texttt{AS\_PATH}:}{attribute carrying your prefix:}
\task{See it from outside.} \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:} \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? \noindent The lab daemon's AS4200000001 is missing from that path. Where did it go, and why?
\answer[2] \answer[2]
@@ -124,7 +143,7 @@ Wait a moment, then look your prefix up on \texttt{bgp.tools}.
\task{Reach it.} \task{Reach it.}
The router answers for every address in the lab space, so your prefix is The router answers for every address in the lab space, so your prefix is
reachable exactly when your announcement got out:\quad reachable exactly when your announcement got out:\quad
\texttt{ping -6 2a0d:d741:<n>::1} \texttt{ping -6 2a0d:d741:\nhex::1}
\reach \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 \texttt{210036:1:42476}; announcing a prefix you own replaces it. Before you
look: who do you expect to still see it? look: who do you expect to still see it?
\answer[2] \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 \reach
\task{To SWITCH only.} \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 announce again. Our router now tells only the route servers, and they tell
only SWITCH. only SWITCH.
\reach \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] \answer[2]
\task{Use of large communities.} \task{Use of large communities.}