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 needLinux / WSLmacOS
LAN IPv4ip addr or ip -4 addr shownetworksetup -listallhardwareports, then ipconfig getifaddr <device>
Default gatewayip routenetstat -r
Listening portsss -tlnplsof -iTCP -sTCP:LISTEN -P -n
Loopback evidencethe inet 127.0.0.1/8 line from ip addrthe 127.0.0.1 line from cat /etc/hosts
Status codecurl -s -o /dev/null -w '%{http_code}\n' https://example.comsame

A Mac has no ip or ss. Do not paste a Linux command that failed.

Your tasks

  1. platform: — exactly macOS, Linux, or WSL.
  2. Print the LAN IPv4. Put the address in lan_ip: and the command in lan_ip_command:.
  3. Name the RFC 1918 block: 10.0.0.0/8, 172.16.0.0/12, or 192.168.0.0/16.
  4. Record the default gateway.
  5. Record the curl status code from the command above.
  6. Paste the raw LAN-address command output between --- lan_ip_command_output ---.
  7. Paste the raw listeners output between --- listening_ports ---.
  8. Paste one loopback line between --- loopback_line ---.
  9. 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

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
CriterionWhat earns itWeight
The address portrait is correct and internally consistent

lan_ip is a valid dotted quad, four octets each 0–255. It is not inside 127.0.0.0/8 and is not 0.0.0.0. It falls inside one of RFC 1918's three blocks — 10.0.0.0/8, 172.16.0.0/12, or 192.168.0.0/16 [src 47] — and rfc1918_block names that block, not a different one. Watch the middle block in particular: an address in 172.32.x.x is public and belongs in none of the three, so a submission claiming 172.16.0.0/12 for it is wrong even though the first octet matches.

gateway is a valid dotted quad, is not inside 127.0.0.0/8, and is a different address from lan_ip. loopback_line contains 127.0.0.1, and on Linux or WSL it carries the /8 prefix, showing the learner understood that the whole block is loopback rather than the single address [src 49, 50]. lan_ip and the address in loopback_line are different values.

curl_status is exactly three digits in the range 100–599 — the five status classes [src 84].

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: lan_ip is a loopback address, is outside all three private blocks with no explanation, or rfc1918_block contradicts lan_ip.

30
Every captured field is real output from the learner's own machine

lan_ip appears verbatim inside lan_ip_command_output — the address was read off a screen, not recalled. On Linux or WSL, lan_ip_command_output contains the token inet, which is what a real ip addr block prints [src 51]; a bare address with no surrounding output does not qualify on those platforms. On macOS a bare address does qualify, 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.

listening_ports is raw pasted output containing at least one address:port pair, with the port an integer in 1–65535 [src 36, 38] — or the explicit NO LISTENERS FOUND sentinel where the machine genuinely had none. Blank process columns are expected and must not be penalised: ss -p fills that column for the learner's own processes and leaves it blank for other users' [src 59]. Reformatted, re-typed, aligned, or truncated output loses credit here even when the values are right, because the point of the field is that it is evidence.

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: lan_ip does not appear anywhere in lan_ip_command_output, or a block is paraphrased rather than captured.

25
Commands are the ones the stated platform actually has

platform is one of macOS, Linux, WSL, and lan_ip_command is a command that platform has.

For macOS, that means ipconfig getifaddr <device> [src 64], ideally with networksetup -listallhardwareports shown as the step that found the device name [src 65]. It must not be ip addr or anything using ss: no ip(8) or ss man page exists in the current macOS man-page set [src 63], so those commands do not run there at all. A submission pairing platform: macOS with ip addr fails this criterion outright — the output could not have come from the machine described.

For Linux or WSL, that means ip addr or a narrower form such as ip -4 addr show [src 51]. ifconfig does not earn credit: Canonical's own statement is "We've already stopped installing ifconfig on desktops (it still gets installed on servers for now)" [src 62].

Award the same scrutiny to the other captured commands where the learner names them: ss -tlnp and ip route on Linux and WSL, lsof -iTCP -sTCP:LISTEN -P -n and netstat -r on macOS [src 58, 61, 66].

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 refused_vs_timeout, not on the fields around it.

Full credit needs all four of these:

  1. Both exit codes named and attached to the right failure7 for the connection nothing accepted, 28 for the host that never answered [src 132, 134].
  2. The timing stated as an observation, in the learner's own units — how long each command actually took on their machine, with the first described as immediate and the second as a wait that ran out. A restatement of the definitions ("refused is fast, timeout is slow") without any evidence of having watched it happen is a partial answer at best.
  3. The reason the timing differs, correctly explained — a refusal is an answer, and answers come back at the speed of the network; a host that is not there, or a firewall discarding packets, sends nothing at all, so curl has no choice but to wait until it gives up. A response that says the timings differ but treats it as arbitrary, or attributes the delay to "a slower server", misses the point.
  4. The wording trap avoided — the learner does not claim to have seen "Connection refused" in the plain output. That phrase appears only under -v [src 132]; the plain line says Could not connect to server. A submission that reports seeing "Connection refused" without -v is reporting something that did not happen on their screen, and loses this element even if everything else is right.

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
Total100
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 alone172.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())

                      

Erase saved progress?

This erases all quiz scores, reading progress, project checklists, and your name on the certificate. It cannot be undone, and it affects only this course in this browser.