whiteboard
01 — Whiteboard model
Learning objectives
- Explain each flag in one sentence without notes.
- Choose the narrowest flag for four drill scenarios.
Intuition
Before typing SSH, know who listens and who dials. Wrong mental model → wrong flag under pressure.
Friend annotation — where you type vs who listens
Usual class topology: you already SSH to B. You type both -L and -R on A (your laptop). The flag does not mean "run -R on the target."
| You said | Precise meaning |
|---|---|
-L opens my 8080 for whatever is on their 80 | Yes. Listener on A:8080. Browser hits 127.0.0.1:8080 on A → B dials C:80 (or 127.0.0.1:80 on B). |
-R opens 8080 on the target back to my 8080 | Yes, with port names clear: listener on B (often 127.0.0.1:18080). Something on B hits that port → A serves your 127.0.0.1:8080. |
# both from YOUR laptop (A)
ssh -N -L 127.0.0.1:8080:127.0.0.1:80 user@B # pull their :80 to your :8080
ssh -N -R 127.0.0.1:18080:127.0.0.1:8080 user@B # push your :8080 onto B as :18080
Only if B opens SSH back to you (you are the SSH server) do the roles flip. That is a different topology — not the default demo.
Mermaid — -L local forward
sequenceDiagram participant You as A your laptop participant B as B bastion participant C as C service :80 Note over You: Listener on A :8080 You->>You: curl 127.0.0.1:8080 You->>B: SSH channel (-L) B->>C: B dials C:80 C-->>B: response B-->>You: back through SSH
Mermaid — -R remote forward
sequenceDiagram participant You as A your laptop participant B as B bastion participant Svc as A service :8080 Note over B: Listener on B :18080 Note over Svc: Your lab HTTP on A B->>B: curl 127.0.0.1:18080 B->>You: SSH channel (-R) You->>Svc: A dials 127.0.0.1:8080 Svc-->>You: response You-->>B: back through SSH
Mermaid — side-by-side mental model
flowchart LR
subgraph L["-L pull to me"]
A1["A listens :8080"] --> SSH1["SSH"] --> B1["B dials C:80"]
end
subgraph R["-R push to them"]
B2["B listens :18080"] --> SSH2["SSH"] --> A2["A dials my :8080"]
endFAQ / common mistakes (from class Q&A)
Use this after the friend annotation. Kill these myths before anyone types ssh.
| Myth / mix-up | Truth |
|---|---|
"-L opens a port on the bastion" | False. -L opens the listener on A (your laptop). B only dials the destination after you hit your local port. |
"-L 8080:127.0.0.1:80 means C:8080 shows up as my port 80" | False. Left side = your listen port (8080). Right side = destination host:port as B sees it (127.0.0.1:80 on B, or C_IP:80). |
"I used 127.0.0.1 but never wrote C's IP — so the tunnel is broken" | Not broken. Destination is resolved from B. 127.0.0.1 means B's loopback. If the service is on host C, put C's IP/hostname there instead. |
| "The service on C sees my laptop's IP" | No. C (or the service on B) sees the connection coming from the bastion (B). |
"-R means I must run SSH on the target" | Usually false. With A→B SSH, you still type -R on A. Listener appears on B. Only if B SSHes back to you do roles flip. |
"-D needs a target host:port like -L" | False. -D only sets a local SOCKS port. Each app picks destinations through B. The app must speak SOCKS (or use a SOCKS-aware wrapper). |
Port order cheat (say it aloud)
-L [bind:]LOCAL_PORT:DEST_HOST:DEST_PORT
^ you listen here ^ B dials this
Example friends often mistype mentally:
# YOUR listen :8080 → B dials its own :80
ssh -N -L 127.0.0.1:8080:127.0.0.1:80 user@B
# YOUR listen :8080 → B dials C:80 (put C's address)
ssh -N -L 127.0.0.1:8080:10.10.10.50:80 user@B
Bind defaults (class safety)
| Mode | Safer class bind | Why |
|---|---|---|
-L / -D | Prefix 127.0.0.1: | Listener stays on your laptop loopback |
-R | Prefix 127.0.0.1: on the remote side | Avoids a wide-open listen on B; do not enable GatewayPorts for class |
One-sentence each (checkpoint)
-L: I listen locally; B dials one destination; that destination sees B.-R: B listens; I serve one local service back through SSH.-D: I listen locally as SOCKS; B dials whatever the app asks (TCP).
Practitioner
Goal: Friends can pick -L / -R / -D for a new scenario without opening a terminal.
Time: ~15 minutes. No SSH yet.
Draw once
[A student] ----SSH----> [B bastion] ....?.... [C internal service]
Fill in for each flag: who listens? who dials C (or A's service)?
Drill questions (say answers out loud)
- "I need the internal web on C:80 in my browser." →
-L(or-Dif many ports unknown). - "A callback / webhook on B must hit a tiny HTTP server on my laptop." →
-R. - "I will run several tools against hosts only B can reach; ports unclear." →
-D. - "I already know exactly one
db.internal:5432." → Prefer-Lover-D(narrower).
Rules of thumb
| Prefer | When |
|---|---|
-L | One destination, known port |
-D | Tooling / browsing through B; many destinations |
-R | Listener must live on B |
| Later (out of scope here) | ligolo / chisel / ssh -J multi-hop once forwards click |
Checkpoint
Each friend: explain the three flags in one sentence each, no notes.
Cite: peachcyber SSH note [S-PC-01]; OpenSSH ssh(1) [S-OSS-01].
Deep dive
Compare jump host (-J) vs forwarding: shell on private host vs TCP to one service — save for later modules [S-PC-01 | medium | derived].
Check
Each friend: three one-sentence explanations + drill answers aloud.