Network Self-Portrait
Goal
Map your machine on the network: its LAN IPv4 and which private block it sits in, the default gateway, what is listening, and proof from your screen that loopback is not the LAN address. ---
Type: interactive-form
Unit: 3 — A second address
Goal
Map your machine on the network: its LAN IPv4 and which private block it sits in, the default gateway, what is listening, and proof from your screen that loopback is not the LAN address.
How this works
Run every command on your machine. Fill starter/submission.txt. Single-line fields are name: value. Block fields sit between --- name --- markers. Paste output; do not tidy it.
Pick your platform and stay in that column:
| What you need | Linux / WSL | macOS |
|---|---|---|
| LAN IPv4 | ip addr or ip -4 addr show | networksetup -listallhardwareports, then ipconfig getifaddr <device> |
| Default gateway | ip route | netstat -r |
| Listening ports | ss -tlnp | lsof -iTCP -sTCP:LISTEN -P -n |
| Loopback evidence | the inet 127.0.0.1/8 line from ip addr | the 127.0.0.1 line from cat /etc/hosts |
| Status code | curl -s -o /dev/null -w '%{http_code}\n' https://example.com | same |
A Mac has no ip or ss. Do not paste a Linux command that failed.
Your tasks
platform:— exactlymacOS,Linux, orWSL.- Print the LAN IPv4. Put the address in
lan_ip:and the command inlan_ip_command:. - Name the RFC 1918 block:
10.0.0.0/8,172.16.0.0/12, or192.168.0.0/16. - Record the default gateway.
- Record the
curlstatus code from the command above. - Paste the raw LAN-address command output between
--- lan_ip_command_output ---. - Paste the raw listeners output between
--- listening_ports ---. - Paste one loopback line between
--- loopback_line ---. - Run a refused connection (
curl http://127.0.0.1:9999) and a timed-out one (curl --max-time 4 http://192.0.2.1/). Paste both, and notice which one returns instantly.
What the scaffolding is for
Several fields are checked against each other. lan_ip must appear in the command output you pasted. 127.0.0.1 is not a LAN address.
Expected output
lan_ip is four numbers with dots, in a private block, and not 127.0.0.1. curl_status is 200.
Rules
- Use the command for your platform.
- Do not invent a LAN address.
- Do not edit
tests/.
See rubric.md for how this is scored.
Steps
Your files
Download these onto your machine and fill them in. The brief says which, and what “done” looks like.
submission.txt Download
# Network Self-Portrait — submission
#
# Fill in every field below. Lines beginning with # are comments and are ignored.
#
# TWO KINDS OF FIELD:
# name: value <- single line. One value. No quotes, no backticks, no fences.
# --- name --- <- block. Paste RAW command output on the lines that follow,
# up to the next --- marker --- or the end of the file.
#
# DO NOT rename a field. DO NOT delete or edit a --- marker --- line.
# DO NOT retype, tidy, align, or truncate anything you paste into a block.
# ---------------------------------------------------------------------------
# TODO A — your platform. Exactly one of: macOS Linux WSL
# ---------------------------------------------------------------------------
platform:
# ---------------------------------------------------------------------------
# TODO B — your machine's LAN IPv4 address, as four dot-separated numbers.
# Linux / WSL : ip addr -> the inet line that is NOT on interface lo
# macOS : networksetup -listallhardwareports (find the Wi-Fi device)
# ipconfig getifaddr <that device>
# It will start with 10. or 172. or 192.168. It must NOT start with 127.
# ---------------------------------------------------------------------------
lan_ip:
# ---------------------------------------------------------------------------
# TODO C — the exact command you ran for TODO B. Copy it, do not describe it.
# ---------------------------------------------------------------------------
lan_ip_command:
# ---------------------------------------------------------------------------
# TODO D — which RFC 1918 private block lan_ip falls in.
# Exactly one of: 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16
# Careful: the 172 block ends at 172.31.255.255.
# ---------------------------------------------------------------------------
rfc1918_block:
# ---------------------------------------------------------------------------
# TODO E — your default gateway address.
# Linux / WSL : ip route -> the default route entry
# macOS : netstat -r -> the default route entry
# ---------------------------------------------------------------------------
gateway:
# ---------------------------------------------------------------------------
# TODO F — the number printed by:
# curl -s -o /dev/null -w '%{http_code}\n' https://example.com
# Digits only.
# ---------------------------------------------------------------------------
curl_status:
# ---------------------------------------------------------------------------
# TODO G — paste the RAW output of the command you named in lan_ip_command.
# On Linux / WSL this is several blocks of text and contains the word "inet".
# On macOS, ipconfig getifaddr prints the address and nothing else — that is fine.
# ---------------------------------------------------------------------------
--- lan_ip_command_output ---
# ---------------------------------------------------------------------------
# TODO H — paste the RAW output of your listening-ports command, header included.
# Linux / WSL : ss -tlnp
# macOS : lsof -iTCP -sTCP:LISTEN -P -n
# Blank process columns are normal and expected — do not remove those rows.
# If there are genuinely no rows at all, make the first line below exactly:
# NO LISTENERS FOUND
# and paste whatever the command did print underneath it.
# ---------------------------------------------------------------------------
--- listening_ports ---
# ---------------------------------------------------------------------------
# TODO I — one line proving 127.0.0.1 exists on your machine.
# Linux / WSL : the loopback inet line from `ip addr` (it carries 127.0.0.1/8)
# macOS : `cat /etc/hosts`, then the line that begins 127.0.0.1
# This must be a DIFFERENT address from lan_ip. That is the whole point.
# ---------------------------------------------------------------------------
--- loopback_line ---
# ---------------------------------------------------------------------------
# TODO J — your own observation. Run both of these and watch the clock:
# curl http://127.0.0.1:9999
# curl --max-time 4 http://192.0.2.1/
# (If port 9999 is in use on your machine, pick another unused high port and
# say which one you used.)
#
# Write, in your own words:
# - which exit code came from which command (7 and 28)
# - how long each one took ON YOUR MACHINE
# - why one is instant and the other is not
#
# Do not write that you saw "Connection refused" unless you ran it with -v.
# The plain output does not say that.
# ---------------------------------------------------------------------------
--- refused_vs_timeout ---
On your machine, run the tests in this project’s tests/ folder. Each one
prints PASS or a FAIL: line that names what is wrong.
How it is graded
| Criterion | What earns it | Weight |
|---|---|---|
| The address portrait is correct and internally consistent |
Full credit: every one of the above holds.
Partial: the address is real and correctly classified but one supporting field (gateway,
curl_status) is malformed or missing.
No credit: | 30 |
| Every captured field is real output from the learner's own machine |
Full credit: every captured block is plainly raw command output and the cross-checks hold.
Partial: output is real but tidied, truncated, or one block is a summary rather than a paste.
No credit: | 25 |
| Commands are the ones the stated platform actually has |
For For Award the same scrutiny to the other captured commands where the learner names them: Full credit: every command named belongs to the stated platform, and the macOS two-step for finding the interface is present where the platform is macOS. Partial: the LAN IP command is right for the platform but another command is borrowed from the wrong column. No credit: the LAN IP command does not exist on the stated platform. | 20 |
| Craft — the refused-vs-timeout observation shows the timing tell was understood, not restated | This is the criterion that separates a filled-in form from an understood one, and it is graded on the
learner's own words in Full credit needs all four of these:
The strongest answers go one step further and say what the tell is for: that an instant failure sends you back to check what is actually bound and which port you typed, while a slow failure sends you looking at the address and at whatever sits between the two machines. That is the diagnostic Unit 6 runs on, and a learner who has already articulated it will not need it explained again. Full credit (23–25): all four elements, with the timing given as something observed. Strong (18–22): all four elements, but the timing reads as recalled from the reading rather than measured. Partial (10–17): both codes named and the timing contrast present, but the explanation of why is missing or wrong. No credit (0–9): the definitions are restated with no observation, the codes are swapped, or the plain output is claimed to have said "Connection refused". | 25 |
| Total | 100 |
Test cases and grader source
| Test | Checks | Expected | Weight |
|---|---|---|---|
| private_lan_address | Checks that lan_ip parses as four dot-separated octets each 0–255, is not inside 127.0.0.0/8, falls inside one of RFC 1918's three private blocks, and that rfc1918_block names the block it actually falls in. Catches the shortcut of classifying the address by its first octet alone — 172.32.4.4 starts with 172 but the block stops at 172.31.255.255 [src 47], so it is a public address and belongs in none of the three. | PASS | 25 |
| adversarial_loopback_shortcut | Catches a shortcut: Catches the learner who submits 127.0.0.1 as their LAN IP — the single most likely shortcut, because it is the address they have seen most often and the one every tutorial puts in front of them. Rejects any address in 127.0.0.0/8, which is loopback in its entirety [src 49, 50], and rejects 0.0.0.0, which means "every address this machine has" rather than an address this machine has. Then closes the back door: it requires loopback_line to contain 127.0.0.1 and requires lan_ip not to be one of the addresses appearing in loopback_line, so the learner cannot satisfy both fields by copying one capture into both. | PASS | 20 |
| adversarial_invented_address | Catches a shortcut: Catches the made-up answer — a plausible-looking 192.168.x.x typed from memory or lifted from a tutorial — by requiring lan_ip to appear verbatim inside lan_ip_command_output. If the address is not in the pasted output, it was not read off this machine. Also catches the thinner version of the same shortcut on Linux and WSL, where a learner pastes a bare address instead of real ip addr output: a genuine capture labels every IPv4 address with inet [src 51]. macOS is exempt from that second check, because ipconfig getifaddr prints the address and nothing else [src 64]. | PASS | 20 |
| adversarial_platform_command | Catches a shortcut: Catches the macOS learner who submits ip addr as their lan_ip_command. That command does not exist on macOS — no ip(8) or ss man page is in the current macOS man-page set, because both are Linux iproute2 tools [src 63] — so the submitted output cannot have come from the machine described. macOS must use ipconfig getifaddr <device> [src 64]. The mirror case is caught too: a Linux or WSL learner submitting the macOS command, or submitting ifconfig, which is no longer installed by default on Linux desktops [src 62]. | PASS | 15 |
| gateway_and_listeners | Checks that gateway parses as a dotted quad, is not inside 127.0.0.0/8 — nothing can be routed through a non-forwardable address [src 50] — and is not the same address as lan_ip. Then checks that listening_ports is real output containing at least one address:port pair whose port is an integer in 1–65535, the usable range that follows from ports being a 16-bit namespace [src 36] with port 0 Reserved [src 38]. The exact sentinel NO LISTENERS FOUND on the first line is accepted for a machine that genuinely has nothing listening. | PASS | 10 |
| loopback_status_and_timing | Checks the three remaining captured fields. loopback_line contains 127.0.0.1, and on Linux or WSL also carries the /8 prefix, since the whole block is loopback and the real captured line reads inet 127.0.0.1/8 scope host lo [src 51]. curl_status is exactly three digits inside 100–599, the five status classes [src 84]. refused_vs_timeout names both exit code 7 and exit code 28 [src 132, 134] and contains language describing both an immediate failure and a wait — catching the learner who names the two codes but never actually watched the clock, which is the observation the whole step exists to produce. Depth beyond that is graded by rubric criterion 4, not here. | PASS | 10 |
tests/test_1_private_lan_address.py
#!/usr/bin/env python3
"""Test 1 (weight 25) -- CORRECTNESS.
lan_ip is a well-formed dotted quad inside one of RFC 1918's three private
blocks, and rfc1918_block names the block it is actually in.
RFC 1918 blocks, verbatim [src 47]:
10.0.0.0 - 10.255.255.255 (10/8 prefix)
172.16.0.0 - 172.31.255.255 (172.16/12 prefix)
192.168.0.0 - 192.168.255.255 (192.168/16 prefix)
Prints PASS or a definite FAIL string.
"""
import re
import sys
BLOCKS = ("10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16")
def load(path):
fields, block, buf = {}, None, []
with open(path, encoding="utf-8") as fh:
text = fh.read()
for line in text.splitlines():
marker = re.match(r"^-{3}\s*([a-z0-9_]+)\s*-{3}\s*$", line)
if marker:
if block is not None:
fields[block] = "\n".join(buf).strip()
block, buf = marker.group(1), []
continue
if block is not None:
if not line.lstrip().startswith("#"):
buf.append(line)
continue
pair = re.match(r"^([a-z0-9_]+):\s*(.*)$", line)
if pair:
fields[pair.group(1)] = pair.group(2).strip()
if block is not None:
fields[block] = "\n".join(buf).strip()
return fields
def octets(value):
m = re.fullmatch(r"\s*(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\s*", value or "")
if not m:
return None
parts = [int(g) for g in m.groups()]
return parts if all(0 <= p <= 255 for p in parts) else None
def block_of(o):
if o[0] == 10:
return "10.0.0.0/8"
if o[0] == 172 and 16 <= o[1] <= 31:
return "172.16.0.0/12"
if o[0] == 192 and o[1] == 168:
return "192.168.0.0/16"
return None
def main():
path = sys.argv[1] if len(sys.argv) > 1 else "submission.txt"
f = load(path)
raw_ip = f.get("lan_ip", "")
if not raw_ip:
print("FAIL: lan_ip is empty -- no LAN IPv4 address was submitted")
return 1
o = octets(raw_ip)
if o is None:
print(
"FAIL: lan_ip=%r is not four dot-separated octets each 0-255 "
"(IPv4 is four octets, 32 bits)" % raw_ip
)
return 1
if o[0] == 127:
print(
"FAIL: lan_ip=%s is inside 127.0.0.0/8 (loopback) -- that is this machine "
"talking to itself, not its address on the LAN" % raw_ip
)
return 1
actual = block_of(o)
if actual is None:
print(
"FAIL: lan_ip=%s is not inside any RFC 1918 private block "
"(10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) -- note the 172 block "
"ends at 172.31.255.255" % raw_ip
)
return 1
claimed = (f.get("rfc1918_block", "") or "").strip().rstrip(".")
if claimed not in BLOCKS:
print(
"FAIL: rfc1918_block=%r is not one of 10.0.0.0/8, 172.16.0.0/12, "
"192.168.0.0/16" % f.get("rfc1918_block", "")
)
return 1
if claimed != actual:
print(
"FAIL: rfc1918_block=%s but lan_ip=%s is actually inside %s"
% (claimed, raw_ip, actual)
)
return 1
print("PASS")
return 0
if __name__ == "__main__":
sys.exit(main())
tests/test_2_adversarial_loopback_shortcut.py
#!/usr/bin/env python3
"""Test 2 (weight 20) -- ADVERSARIAL.
Catches the single most likely shortcut: submitting 127.0.0.1 as the LAN IP,
because it is the address the learner has seen most often. Also catches
0.0.0.0 (the bind-any placeholder, not an address the machine has) and the
subtler version where lan_ip is simply a copy of whatever is in loopback_line.
127.0.0.0/8 is loopback in its entirety: RFC 1122 form "(g) { 127, <any> }",
"Internal host loopback address. Addresses of this form MUST NOT appear
outside a host." [src 49]; RFC 6890 marks the block "Forwardable | False" and
"Global | False" [src 50].
Prints PASS or a definite FAIL string.
"""
import re
import sys
def load(path):
fields, block, buf = {}, None, []
with open(path, encoding="utf-8") as fh:
text = fh.read()
for line in text.splitlines():
marker = re.match(r"^-{3}\s*([a-z0-9_]+)\s*-{3}\s*$", line)
if marker:
if block is not None:
fields[block] = "\n".join(buf).strip()
block, buf = marker.group(1), []
continue
if block is not None:
if not line.lstrip().startswith("#"):
buf.append(line)
continue
pair = re.match(r"^([a-z0-9_]+):\s*(.*)$", line)
if pair:
fields[pair.group(1)] = pair.group(2).strip()
if block is not None:
fields[block] = "\n".join(buf).strip()
return fields
def octets(value):
m = re.fullmatch(r"\s*(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\s*", value or "")
if not m:
return None
parts = [int(g) for g in m.groups()]
return parts if all(0 <= p <= 255 for p in parts) else None
def addresses_in(text):
found = []
for m in re.finditer(r"(?<![\d.])(\d{1,3}(?:\.\d{1,3}){3})(?![\d])", text or ""):
if octets(m.group(1)) is not None:
found.append(m.group(1))
return found
def main():
path = sys.argv[1] if len(sys.argv) > 1 else "submission.txt"
f = load(path)
raw_ip = (f.get("lan_ip", "") or "").strip()
o = octets(raw_ip)
if o is None:
print("FAIL: lan_ip=%r is not a parseable IPv4 address" % raw_ip)
return 1
if o[0] == 127:
print(
"FAIL: lan_ip=%s is a loopback address (127.0.0.0/8). Every machine has one "
"and on every machine it means a different computer -- its own. It is not "
"this machine's address on the LAN, and RFC 6890 marks the block "
"Forwardable=False, Global=False" % raw_ip
)
return 1
if raw_ip == "0.0.0.0":
print(
"FAIL: lan_ip=0.0.0.0 is the bind-any placeholder meaning 'every address "
"this machine has', not an address this machine has"
)
return 1
loop = f.get("loopback_line", "") or ""
if "127.0.0.1" not in loop:
print(
"FAIL: loopback_line does not contain 127.0.0.1 -- the portrait needs the "
"loopback address captured from your own machine to contrast with lan_ip"
)
return 1
if raw_ip in addresses_in(loop):
print(
"FAIL: lan_ip=%s also appears in loopback_line. Loopback and the LAN "
"address are two different addresses doing two different jobs; they cannot "
"be the same value" % raw_ip
)
return 1
print("PASS")
return 0
if __name__ == "__main__":
sys.exit(main())
tests/test_3_adversarial_invented_address.py
#!/usr/bin/env python3
"""Test 3 (weight 20) -- ADVERSARIAL.
Catches the made-up answer: a plausible-looking 192.168.x.x (or 10.x, or 172.x)
typed from memory or from a tutorial, where the pasted lan_ip_command_output
does not actually contain that address.
Also catches the thinner version of the same shortcut on Linux and WSL: pasting
a bare address instead of real `ip addr` output. A genuine capture contains the
token `inet`, as in the real captured loopback line `inet 127.0.0.1/8 scope host
lo` [src 51]. macOS is exempt from that check, because `ipconfig getifaddr`
"Prints to standard output the IP address for the first network service
associated with the given interface" [src 64] and prints nothing else.
Prints PASS or a definite FAIL string.
"""
import re
import sys
def load(path):
fields, block, buf = {}, None, []
with open(path, encoding="utf-8") as fh:
text = fh.read()
for line in text.splitlines():
marker = re.match(r"^-{3}\s*([a-z0-9_]+)\s*-{3}\s*$", line)
if marker:
if block is not None:
fields[block] = "\n".join(buf).strip()
block, buf = marker.group(1), []
continue
if block is not None:
if not line.lstrip().startswith("#"):
buf.append(line)
continue
pair = re.match(r"^([a-z0-9_]+):\s*(.*)$", line)
if pair:
fields[pair.group(1)] = pair.group(2).strip()
if block is not None:
fields[block] = "\n".join(buf).strip()
return fields
def octets(value):
m = re.fullmatch(r"\s*(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\s*", value or "")
if not m:
return None
parts = [int(g) for g in m.groups()]
return parts if all(0 <= p <= 255 for p in parts) else None
def main():
path = sys.argv[1] if len(sys.argv) > 1 else "submission.txt"
f = load(path)
raw_ip = (f.get("lan_ip", "") or "").strip()
if octets(raw_ip) is None:
print("FAIL: lan_ip=%r is not a parseable IPv4 address" % raw_ip)
return 1
out = f.get("lan_ip_command_output", "") or ""
if not out.strip():
print(
"FAIL: lan_ip_command_output is empty -- paste the raw output of the "
"command you named in lan_ip_command"
)
return 1
if not re.search(r"(?<![\d.])" + re.escape(raw_ip) + r"(?![\d])", out):
print(
"FAIL: lan_ip=%s does not appear anywhere in lan_ip_command_output. "
"The address was not read off this machine" % raw_ip
)
return 1
platform = (f.get("platform", "") or "").strip().lower()
if platform in ("linux", "wsl"):
if not re.search(r"(?<![A-Za-z])inet(?![A-Za-z])", out):
print(
"FAIL: platform=%s but lan_ip_command_output contains no 'inet' token. "
"Real `ip addr` output labels every IPv4 address with `inet` -- paste "
"the whole output, not just the address"
% (f.get("platform", "") or "").strip()
)
return 1
print("PASS")
return 0
if __name__ == "__main__":
sys.exit(main())
tests/test_4_adversarial_platform_command.py
#!/usr/bin/env python3
"""Test 4 (weight 15) -- ADVERSARIAL.
Catches the cross-platform copy-paste: a macOS learner submitting `ip addr`.
No `ip(8)` or `ss` man page exists in the current macOS man-page set -- both are
Linux iproute2 tools [src 63] -- so that command cannot have produced output on
the machine described. The macOS command is `ipconfig getifaddr <interface>`
[src 64], found via `networksetup -listallhardwareports` [src 65].
The mirror case is caught too: a Linux or WSL learner submitting the macOS
command, or `ifconfig`, which is no longer installed by default [src 62].
Prints PASS or a definite FAIL string.
"""
import re
import sys
PLATFORMS = {"macos": "macOS", "linux": "Linux", "wsl": "WSL"}
def load(path):
fields, block, buf = {}, None, []
with open(path, encoding="utf-8") as fh:
text = fh.read()
for line in text.splitlines():
marker = re.match(r"^-{3}\s*([a-z0-9_]+)\s*-{3}\s*$", line)
if marker:
if block is not None:
fields[block] = "\n".join(buf).strip()
block, buf = marker.group(1), []
continue
if block is not None:
if not line.lstrip().startswith("#"):
buf.append(line)
continue
pair = re.match(r"^([a-z0-9_]+):\s*(.*)$", line)
if pair:
fields[pair.group(1)] = pair.group(2).strip()
if block is not None:
fields[block] = "\n".join(buf).strip()
return fields
def main():
path = sys.argv[1] if len(sys.argv) > 1 else "submission.txt"
f = load(path)
raw_platform = (f.get("platform", "") or "").strip()
key = raw_platform.lower()
if key not in PLATFORMS:
print(
"FAIL: platform=%r is not one of macOS, Linux, WSL" % raw_platform
)
return 1
cmd = (f.get("lan_ip_command", "") or "").strip()
if not cmd:
print("FAIL: lan_ip_command is empty -- state the exact command you ran")
return 1
low = cmd.lower()
uses_ip_tool = re.search(r"(?<![A-Za-z-])ip\s+(addr|a\b|-4|-o\b|link)", low) is not None
uses_ss = re.search(r"(?<![A-Za-z-])ss(?![A-Za-z])", low) is not None
uses_ipconfig_getifaddr = "ipconfig getifaddr" in low
uses_ifconfig = "ifconfig" in low
if key == "macos":
if uses_ip_tool:
print(
"FAIL: platform=macOS but lan_ip_command=%r uses the `ip` command. "
"macOS has no `ip` -- no ip(8) man page exists in the current macOS "
"man-page set. Use `ipconfig getifaddr <device>`, after finding the "
"device with `networksetup -listallhardwareports`" % cmd
)
return 1
if uses_ss:
print(
"FAIL: platform=macOS but lan_ip_command=%r uses `ss`. macOS has no "
"`ss` either -- and `ss` lists listening sockets, not addresses" % cmd
)
return 1
if not uses_ipconfig_getifaddr:
print(
"FAIL: platform=macOS but lan_ip_command=%r does not use "
"`ipconfig getifaddr`, which is the macOS command that prints a "
"LAN IPv4 address" % cmd
)
return 1
else:
if uses_ipconfig_getifaddr:
print(
"FAIL: platform=%s but lan_ip_command=%r uses `ipconfig getifaddr`, "
"which is the macOS command" % (PLATFORMS[key], cmd)
)
return 1
if not uses_ip_tool:
if uses_ifconfig:
print(
"FAIL: platform=%s but lan_ip_command=%r uses `ifconfig`, which is "
"no longer installed by default on Linux desktops. Use `ip addr`"
% (PLATFORMS[key], cmd)
)
return 1
print(
"FAIL: platform=%s but lan_ip_command=%r is not an `ip` command such "
"as `ip addr` or `ip -4 addr show`" % (PLATFORMS[key], cmd)
)
return 1
print("PASS")
return 0
if __name__ == "__main__":
sys.exit(main())
tests/test_5_gateway_and_listeners.py
#!/usr/bin/env python3
"""Test 5 (weight 10) -- STRUCTURAL.
The gateway is a real address, distinct from the machine's own, and the
listening-ports capture actually contains an address:port pair with a port in
the usable range.
Ports are a 16-bit namespace [src 36, 37] so the numbers run 0-65535; port 0 is
Reserved [src 38], leaving 1-65535 as the range a program can take. A real
captured `ss -tlnp` line looks like [src 58]:
LISTEN 0 5 0.0.0.0:8000 0.0.0.0:* users:(("python3",pid=8,fd=4))
Prints PASS or a definite FAIL string.
"""
import re
import sys
def load(path):
fields, block, buf = {}, None, []
with open(path, encoding="utf-8") as fh:
text = fh.read()
for line in text.splitlines():
marker = re.match(r"^-{3}\s*([a-z0-9_]+)\s*-{3}\s*$", line)
if marker:
if block is not None:
fields[block] = "\n".join(buf).strip()
block, buf = marker.group(1), []
continue
if block is not None:
if not line.lstrip().startswith("#"):
buf.append(line)
continue
pair = re.match(r"^([a-z0-9_]+):\s*(.*)$", line)
if pair:
fields[pair.group(1)] = pair.group(2).strip()
if block is not None:
fields[block] = "\n".join(buf).strip()
return fields
def octets(value):
m = re.fullmatch(r"\s*(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\s*", value or "")
if not m:
return None
parts = [int(g) for g in m.groups()]
return parts if all(0 <= p <= 255 for p in parts) else None
# an address:port pair as printed by ss (1.2.3.4:8000, 0.0.0.0:8000, [::]:8000)
# or by lsof (*:8000, 127.0.0.1:631)
SOCKET = re.compile(
r"(?:\d{1,3}(?:\.\d{1,3}){3}|\*|\[[0-9A-Fa-f:%.]*\]):(\d{1,5})(?![\d])"
)
def main():
path = sys.argv[1] if len(sys.argv) > 1 else "submission.txt"
f = load(path)
gw = (f.get("gateway", "") or "").strip()
g = octets(gw)
if g is None:
print(
"FAIL: gateway=%r is not four dot-separated octets each 0-255" % gw
)
return 1
if g[0] == 127:
print(
"FAIL: gateway=%s is inside 127.0.0.0/8 (loopback). A loopback address is "
"not forwardable, so nothing can be routed through it" % gw
)
return 1
lan = (f.get("lan_ip", "") or "").strip()
if octets(lan) is not None and gw == lan:
print(
"FAIL: gateway=%s is the same address as lan_ip. The gateway is the machine "
"your packets are handed to on the way off your network, not this machine"
% gw
)
return 1
listeners = f.get("listening_ports", "") or ""
lines = [ln for ln in listeners.splitlines() if ln.strip()]
if not lines:
print(
"FAIL: listening_ports is empty -- paste the raw output of `ss -tlnp` "
"(Linux/WSL) or `lsof -iTCP -sTCP:LISTEN -P -n` (macOS)"
)
return 1
if lines[0].strip() == "NO LISTENERS FOUND":
print("PASS")
return 0
ports = [int(m.group(1)) for m in SOCKET.finditer(listeners)]
usable = [p for p in ports if 1 <= p <= 65535]
if not usable:
print(
"FAIL: listening_ports contains no address:port pair with a port in 1-65535 "
"-- paste the raw output, header row and all, or use the exact sentinel "
"line NO LISTENERS FOUND if there were genuinely no rows"
)
return 1
print("PASS")
return 0
if __name__ == "__main__":
sys.exit(main())
tests/test_6_loopback_status_and_timing.py
#!/usr/bin/env python3
"""Test 6 (weight 10) -- EVIDENCE.
Three captured pieces of evidence are present and well-formed:
* loopback_line carries 127.0.0.1, and on Linux/WSL the /8 prefix too -- the
whole 127.0.0.0/8 block is loopback, not just the one address [src 49, 50],
and the real captured interface line reads `inet 127.0.0.1/8 scope host lo`
[src 51].
* curl_status is three digits in 100-599, the five status classes [src 84].
The idiom is `curl -s -o /dev/null -w '%{http_code}\\n' https://example.com`
[src 131].
* refused_vs_timeout names both exit codes -- 7 for the connection nothing
accepted, 28 for the host that never answered [src 132, 134] -- and states
the timing contrast rather than restating the definitions.
Prints PASS or a definite FAIL string.
"""
import re
import sys
FAST = (
"instant", "instantly", "immediate", "immediately", "at once", "straight away",
"right away", "0 ms", "0ms", "no wait", "no delay", "zero ms",
)
SLOW = (
"timeout", "timed out", "time out", "slow", "slowly", "hung", "hang", "waited",
"waiting", "delay", "seconds", "gave up", "gives up", "4002", "max-time",
)
def load(path):
fields, block, buf = {}, None, []
with open(path, encoding="utf-8") as fh:
text = fh.read()
for line in text.splitlines():
marker = re.match(r"^-{3}\s*([a-z0-9_]+)\s*-{3}\s*$", line)
if marker:
if block is not None:
fields[block] = "\n".join(buf).strip()
block, buf = marker.group(1), []
continue
if block is not None:
if not line.lstrip().startswith("#"):
buf.append(line)
continue
pair = re.match(r"^([a-z0-9_]+):\s*(.*)$", line)
if pair:
fields[pair.group(1)] = pair.group(2).strip()
if block is not None:
fields[block] = "\n".join(buf).strip()
return fields
def main():
path = sys.argv[1] if len(sys.argv) > 1 else "submission.txt"
f = load(path)
platform = (f.get("platform", "") or "").strip().lower()
loop = f.get("loopback_line", "") or ""
if "127.0.0.1" not in loop:
print(
"FAIL: loopback_line does not contain 127.0.0.1 -- on Linux/WSL take the "
"loopback inet line from `ip addr`; on macOS take the 127.0.0.1 line from "
"`cat /etc/hosts`"
)
return 1
if platform in ("linux", "wsl") and "/8" not in loop:
print(
"FAIL: platform=%s but loopback_line has no /8 prefix. The real interface "
"line reads `inet 127.0.0.1/8 scope host lo` -- the whole 127.0.0.0/8 block "
"is loopback, not just the single address"
% (f.get("platform", "") or "").strip()
)
return 1
status = (f.get("curl_status", "") or "").strip()
if not re.fullmatch(r"\d{3}", status):
print(
"FAIL: curl_status=%r is not exactly three digits -- submit only the number "
"printed by curl -s -o /dev/null -w '%%{http_code}\\n' https://example.com"
% status
)
return 1
if not 100 <= int(status) <= 599:
print(
"FAIL: curl_status=%s is outside 100-599, the five HTTP status classes. "
"A 000 means curl never got a response at all" % status
)
return 1
note = f.get("refused_vs_timeout", "") or ""
low = note.lower()
if len(low.strip()) < 80:
print(
"FAIL: refused_vs_timeout is too short to be an observation -- name both "
"exit codes and say how long each command took on your machine"
)
return 1
if not re.search(r"(?<![\d.])7(?![\d])", note):
print(
"FAIL: refused_vs_timeout does not name exit code 7, the code curl returns "
"when the host is there and nothing is listening on that port"
)
return 1
if not re.search(r"(?<![\d.])28(?![\d])", note):
print(
"FAIL: refused_vs_timeout does not name exit code 28, the code curl returns "
"when the operation times out with no reply at all"
)
return 1
if not any(w in low for w in FAST):
print(
"FAIL: refused_vs_timeout does not describe the fast failure -- say how "
"quickly the refused connection came back on your machine"
)
return 1
if not any(w in low for w in SLOW):
print(
"FAIL: refused_vs_timeout does not describe the slow failure -- say how "
"long the unreachable host took before curl gave up"
)
return 1
print("PASS")
return 0
if __name__ == "__main__":
sys.exit(main())