From 0ecd6ba6ff0e80f8f23f509c122d0247da0f5584 Mon Sep 17 00:00:00 2001 From: Elvira <239804565+HNS2112@users.noreply.github.com> Date: Mon, 3 Aug 2026 22:31:07 +0300 Subject: [PATCH 1/6] docs: add PR template; ignore runtime data dir Signed-off-by: Elvira <239804565+HNS2112@users.noreply.github.com> --- .github/pull_request_template.md | 13 +++++++++++++ .gitignore | 2 ++ 2 files changed, 15 insertions(+) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..f336a9fbd --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,13 @@ +## Goal + + +## Changes +- + +## Testing + + +## Checklist +- [ ] Title is a clear sentence (≤ 70 chars) +- [ ] Commits are signed (`git log --show-signature`) +- [ ] `submissions/labN.md` updated diff --git a/.gitignore b/.gitignore index 1c0a1e94b..966b15200 100644 --- a/.gitignore +++ b/.gitignore @@ -61,3 +61,5 @@ Thumbs.db # *.sbom.cdx.json, zap-*.html/json, trivy-*.txt (Lab 9 scan evidence) # flake.nix, flake.lock (Lab 11) # wasm/main.go, spin.toml, go.sum (Lab 12) +data/ +app/data/ From 8768460afe0b3273bc441dd8b60ac000509128cc Mon Sep 17 00:00:00 2001 From: HNS <239804565+HNS2112@users.noreply.github.com> Date: Tue, 4 Aug 2026 19:44:42 +0300 Subject: [PATCH 2/6] docs: upstream moved while you worked Signed-off-by: HNS <239804565+HNS2112@users.noreply.github.com> From c606adff5329fb023b026c9a346435a80cddfc45 Mon Sep 17 00:00:00 2001 From: HNS <239804565+HNS2112@users.noreply.github.com> Date: Thu, 6 Aug 2026 18:54:41 +0300 Subject: [PATCH 3/6] evidence(lab4): packet capture and network diagnostics Signed-off-by: HNS <239804565+HNS2112@users.noreply.github.com> --- evidence/lab4/01-pcap-read.txt | 45 +++++++++++++++ evidence/lab4/02-diagnostics.txt | 93 +++++++++++++++++++++++++++++++ evidence/lab4/03-path.txt | 26 +++++++++ evidence/lab4/04-refused.txt | 13 +++++ evidence/lab4/05-latency.txt | 33 +++++++++++ evidence/lab4/quicknotes.pcap | Bin 0 -> 1242 bytes 6 files changed, 210 insertions(+) create mode 100644 evidence/lab4/01-pcap-read.txt create mode 100644 evidence/lab4/02-diagnostics.txt create mode 100644 evidence/lab4/03-path.txt create mode 100644 evidence/lab4/04-refused.txt create mode 100644 evidence/lab4/05-latency.txt create mode 100644 evidence/lab4/quicknotes.pcap diff --git a/evidence/lab4/01-pcap-read.txt b/evidence/lab4/01-pcap-read.txt new file mode 100644 index 000000000..58a16cf5c --- /dev/null +++ b/evidence/lab4/01-pcap-read.txt @@ -0,0 +1,45 @@ +18:50:14.556151 IP 127.0.0.1.52742 > 127.0.0.1.8080: Flags [S], seq 852912346, win 65495, options [mss 65495,sackOK,TS val 2230267369 ecr 0,nop,wscale 10], length 0 +E..<..@.@.;.............2.h..........0......... +..-........ + +18:50:14.556164 IP 127.0.0.1.8080 > 127.0.0.1.52742: Flags [S.], seq 3354113025, ack 852912347, win 65483, options [mss 65495,sackOK,TS val 407459794 ecr 2230267369,nop,wscale 10], length 0 +E..<..@.@.<.................2.h......0......... +.IW...-.... + +18:50:14.556173 IP 127.0.0.1.52742 > 127.0.0.1.8080: Flags [.], ack 1, win 64, options [nop,nop,TS val 2230267369 ecr 407459794], length 0 +E..4..@.@.;.............2.h........@.(..... +..-..IW. +18:50:14.556203 IP 127.0.0.1.52742 > 127.0.0.1.8080: Flags [P.], seq 1:176, ack 1, win 64, options [nop,nop,TS val 2230267369 ecr 407459794], length 175: HTTP: POST /notes HTTP/1.1 +E.....@.@.;6............2.h........@....... +..-..IW.POST /notes HTTP/1.1 +Host: localhost:8080 +User-Agent: curl/8.5.0 +Accept: */* +Content-Type: application/json +Content-Length: 40 + +{"title":"lab4","body":"packet capture"} +18:50:14.556207 IP 127.0.0.1.8080 > 127.0.0.1.52742: Flags [.], ack 176, win 64, options [nop,nop,TS val 407459794 ecr 2230267369], length 0 +E..4.q@.@..P................2.i....@.(..... +.IW...-. +18:50:14.556662 IP 127.0.0.1.8080 > 127.0.0.1.52742: Flags [P.], seq 1:208, ack 176, win 64, options [nop,nop,TS val 407459794 ecr 2230267369], length 207: HTTP: HTTP/1.1 201 Created +E....r@.@...................2.i....@....... +.IW...-.HTTP/1.1 201 Created +Content-Type: application/json +Date: Thu, 06 Aug 2026 15:50:14 GMT +Content-Length: 94 + +{"id":6,"title":"lab4","body":"packet capture","created_at":"2026-08-06T15:50:14.556405793Z"} + +18:50:14.556696 IP 127.0.0.1.52742 > 127.0.0.1.8080: Flags [.], ack 208, win 64, options [nop,nop,TS val 2230267369 ecr 407459794], length 0 +E..4..@.@.;.............2.i........@.(..... +..-..IW. +18:50:14.556827 IP 127.0.0.1.52742 > 127.0.0.1.8080: Flags [F.], seq 176, ack 208, win 64, options [nop,nop,TS val 2230267369 ecr 407459794], length 0 +E..4..@.@.;.............2.i........@.(..... +..-..IW. +18:50:14.556994 IP 127.0.0.1.8080 > 127.0.0.1.52742: Flags [F.], seq 208, ack 177, win 64, options [nop,nop,TS val 407459794 ecr 2230267369], length 0 +E..4.s@.@..N................2.i....@.(..... +.IW...-. +18:50:14.557066 IP 127.0.0.1.52742 > 127.0.0.1.8080: Flags [.], ack 209, win 64, options [nop,nop,TS val 2230267370 ecr 407459794], length 0 +E..4..@.@.;.............2.i........@.(..... +..-..IW. diff --git a/evidence/lab4/02-diagnostics.txt b/evidence/lab4/02-diagnostics.txt new file mode 100644 index 000000000..bb8530cd3 --- /dev/null +++ b/evidence/lab4/02-diagnostics.txt @@ -0,0 +1,93 @@ +$ ss -tlnp | head -20 +State Recv-Q Send-Q Local Address:Port Peer Address:PortProcess +LISTEN 0 10 0.0.0.0:7070 0.0.0.0:* +LISTEN 0 50 0.0.0.0:139 0.0.0.0:* +LISTEN 0 4096 127.0.0.1:10808 0.0.0.0:* +LISTEN 0 50 0.0.0.0:445 0.0.0.0:* +LISTEN 0 5 0.0.0.0:902 0.0.0.0:* +LISTEN 0 32 192.168.100.1:53 0.0.0.0:* +LISTEN 0 4096 127.0.0.1:11434 0.0.0.0:* +LISTEN 0 4096 127.0.0.53%lo:53 0.0.0.0:* +LISTEN 0 4096 127.0.0.54:53 0.0.0.0:* +LISTEN 0 4096 127.0.0.1:631 0.0.0.0:* +LISTEN 0 4096 127.0.0.1:33357 0.0.0.0:* +LISTEN 0 32 192.168.122.1:53 0.0.0.0:* +LISTEN 0 10 [::]:7070 [::]:* +LISTEN 0 4096 [::1]:631 [::]:* +LISTEN 0 4096 *:8080 *:* users:(("quicknotes",pid=5201,fd=3)) +LISTEN 0 50 [::]:139 [::]:* +LISTEN 0 50 [::]:445 [::]:* +LISTEN 0 5 [::]:902 [::]:* + +$ ss -tn state established +Recv-Q Send-Q Local Address:Port Peer Address:Port Process +0 0 127.0.0.1:10808 127.0.0.1:49374 +0 0 192.168.0.123%enp5s0:40090 192.227.253.103:50000 +0 0 127.0.0.1:39186 127.0.0.1:10808 +0 0 192.168.0.123%enp5s0:45180 192.227.253.103:50000 +0 0 192.168.0.123%enp5s0:52388 192.227.253.103:50000 +0 0 192.168.0.123%enp5s0:52400 192.227.253.103:50000 +0 0 127.0.0.1:49374 127.0.0.1:10808 +0 0 127.0.0.1:10808 127.0.0.1:39186 +0 0 10.33.0.2:53448 207.188.6.17:443 + +$ ip -brief addr +lo UNKNOWN 127.0.0.1/8 +enp5s0 UP 192.168.0.123/24 +wlx9ca2f49134b7 UP 192.168.0.120/24 +virbr0 DOWN 192.168.122.1/24 +virbr1 DOWN 192.168.100.1/24 +docker0 DOWN 172.17.0.1/16 +tun2 UNKNOWN 10.33.0.2/24 + +$ ip route +default via 192.168.0.1 dev enp5s0 proto dhcp src 192.168.0.123 metric 100 +default via 192.168.0.1 dev wlx9ca2f49134b7 proto dhcp src 192.168.0.120 metric 600 +1.0.0.0/8 via 10.33.0.2 dev tun2 +1.0.0.1 via 10.33.0.2 dev tun2 +2.0.0.0/7 via 10.33.0.2 dev tun2 +4.0.0.0/6 via 10.33.0.2 dev tun2 +8.0.0.0/5 via 10.33.0.2 dev tun2 +10.33.0.0/24 dev tun2 proto kernel scope link src 10.33.0.2 +16.0.0.0/4 via 10.33.0.2 dev tun2 +32.0.0.0/3 via 10.33.0.2 dev tun2 +64.0.0.0/2 via 10.33.0.2 dev tun2 +127.0.0.0/8 dev lo proto kernel scope link src 127.0.0.1 metric 30 +128.0.0.0/1 via 10.33.0.2 dev tun2 +172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 linkdown +172.29.172.254 via 10.33.0.2 dev tun2 +192.168.0.0/24 dev enp5s0 proto kernel scope link src 192.168.0.123 metric 100 +192.168.0.0/24 dev wlx9ca2f49134b7 proto kernel scope link src 192.168.0.120 metric 600 +192.168.100.0/24 dev virbr1 proto kernel scope link src 192.168.100.1 linkdown +192.168.122.0/24 dev virbr0 proto kernel scope link src 192.168.122.1 linkdown + +$ dig +short localhost +127.0.0.1 + +$ dig localhost | head -20 + +; <<>> DiG 9.18.39-0ubuntu0.24.04.5-Ubuntu <<>> localhost +;; global options: +cmd +;; Got answer: +;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 18509 +;; flags: qr aa rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 + +;; OPT PSEUDOSECTION: +; EDNS: version: 0, flags:; udp: 65494 +;; QUESTION SECTION: +;localhost. IN A + +;; ANSWER SECTION: +localhost. 0 IN A 127.0.0.1 + +;; Query time: 0 msec +;; SERVER: 127.0.0.53#53(127.0.0.53) (UDP) +;; WHEN: Thu Aug 06 18:52:47 MSK 2026 +;; MSG SIZE rcvd: 54 + + +$ getent hosts localhost +127.0.0.1 localhost + +$ curl -s -o /dev/null -w "dns=%{time_namelookup} connect=%{time_connect} total=%{time_total}\n" http://localhost:8080/health +dns=0.000031 connect=0.000240 total=0.000772 diff --git a/evidence/lab4/03-path.txt b/evidence/lab4/03-path.txt new file mode 100644 index 000000000..534e06beb --- /dev/null +++ b/evidence/lab4/03-path.txt @@ -0,0 +1,26 @@ +$ mtr -rwc 5 localhost +Start: 2026-08-06T18:53:36+0300 +HOST: hns Loss% Snt Last Avg Best Wrst StDev + 1.|-- localhost 0.0% 5 0.1 0.1 0.1 0.1 0.0 + +$ ip link show lo +1: lo: mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000 + link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 + +$ cat /proc/sys/net/ipv4/tcp_congestion_control +cubic + +$ ss -tim state established dst 127.0.0.1 | head -20 +Recv-Q Send-Q Local Address:Port Peer Address:Port Process +0 0 127.0.0.1:10808 127.0.0.1:49374 + skmem:(r0,rb170241,t0,tb2626560,f0,w0,o0,bl0,d6) cubic wscale:10,10 rto:201 rtt:0.601/0.329 ato:40 mss:65483 pmtu:65535 rcvmss:20480 advmss:65483 cwnd:27 bytes_sent:1645335 bytes_acked:1645335 bytes_received:1146513 segs_out:585 segs_in:386 data_segs_out:493 data_segs_in:112 send 23.5Gbps lastsnd:31928 lastrcv:34024 lastack:31928 pacing_rate 47Gbps delivery_rate 74.8Gbps delivered:494 app_limited busy:988ms rcv_rtt:640.625 rcv_space:70448 rcv_ssthresh:161157 minrtt:0.007 snd_wnd:325632 +0 0 127.0.0.1:10808 127.0.0.1:35028 + skmem:(r0,rb131072,t0,tb2626560,f0,w0,o0,bl0,d1) cubic wscale:10,10 rto:206 rtt:5.112/10.083 ato:40 mss:32768 pmtu:65535 rcvmss:536 advmss:65483 cwnd:10 bytes_sent:12 bytes_acked:12 bytes_received:13 segs_out:4 segs_in:7 data_segs_out:2 data_segs_in:2 send 513Mbps lastsnd:32576 lastrcv:32576 lastack:32535 pacing_rate 1.03Gbps delivery_rate 3.45Gbps delivered:3 app_limited busy:41ms rcv_space:65483 rcv_ssthresh:65483 minrtt:0.076 snd_wnd:65536 +0 0 127.0.0.1:35028 127.0.0.1:10808 + skmem:(r0,rb131072,t0,tb2626560,f0,w0,o0,bl0,d0) cubic wscale:10,10 rto:201 rtt:0.133/0.047 ato:40 mss:32768 pmtu:65535 rcvmss:536 advmss:65483 cwnd:10 bytes_sent:13 bytes_acked:14 bytes_received:12 segs_out:7 segs_in:5 data_segs_out:2 data_segs_in:2 send 19.7Gbps lastsnd:32578 lastrcv:32578 lastack:2113 pacing_rate 39.2Gbps delivery_rate 2.57Gbps delivered:3 app_limited rcv_space:65495 rcv_ssthresh:65495 minrtt:0.102 snd_wnd:65536 +0 0 127.0.0.1:39186 127.0.0.1:10808 + skmem:(r0,rb131072,t0,tb2626560,f0,w0,o0,bl0,d0) cubic wscale:10,10 rto:203 rtt:2.618/5.187 ato:40 mss:34304 pmtu:65535 rcvmss:1391 advmss:65483 cwnd:10 bytes_sent:2311 bytes_acked:2312 bytes_received:1680 segs_out:29 segs_in:32 data_segs_out:8 data_segs_in:7 send 1.05Gbps lastsnd:520092 lastrcv:519745 lastack:6210 pacing_rate 2.1Gbps delivery_rate 19.6Gbps delivered:9 app_limited busy:42ms rcv_space:65495 rcv_ssthresh:76663 minrtt:0.014 snd_wnd:68608 +0 0 127.0.0.1:49374 127.0.0.1:10808 + skmem:(r0,rb410514,t0,tb2626560,f0,w0,o0,bl0,d0) cubic wscale:10,10 rto:201 rtt:0.256/0.421 ato:40 mss:65483 pmtu:65535 rcvmss:8568 advmss:65483 cwnd:10 bytes_sent:1146513 bytes_acked:1146514 bytes_received:1645335 segs_out:386 segs_in:586 data_segs_out:112 data_segs_in:493 send 20.5Gbps lastsnd:34029 lastrcv:31933 lastack:1603 pacing_rate 40.9Gbps delivery_rate 105Gbps delivered:113 app_limited busy:210ms rcv_rtt:16 rcv_space:134229 rcv_ssthresh:351181 minrtt:0.005 snd_wnd:161792 +0 0 127.0.0.1:10808 127.0.0.1:39186 + skmem:(r0,rb131072,t0,tb2626560,f0,w0,o0,bl0,d17) cubic wscale:10,10 rto:202 rtt:1.9/2.611 ato:40 mss:38400 pmtu:65535 rcvmss:1085 advmss:65483 cwnd:10 bytes_sent:1680 bytes_acked:1680 bytes_received:2311 segs_out:31 segs_in:29 data_segs_out:7 data_segs_in:8 send 1.62Gbps lastsnd:519749 lastrcv:520096 lastack:519749 pacing_rate 3.23Gbps delivery_rate 16.2Gbps delivered:8 app_limited busy:18ms rcv_rtt:4 rcv_space:65483 rcv_ssthresh:68534 minrtt:0.019 snd_wnd:76800 diff --git a/evidence/lab4/04-refused.txt b/evidence/lab4/04-refused.txt new file mode 100644 index 000000000..a2675e337 --- /dev/null +++ b/evidence/lab4/04-refused.txt @@ -0,0 +1,13 @@ +$ curl -sv http://localhost:9999/health +* Host localhost:9999 was resolved. +* IPv6: ::1 +* IPv4: 127.0.0.1 +* Trying [::1]:9999... +* Immediate connect fail for ::1: Невозможно назначить запрошенный адрес +* Trying 127.0.0.1:9999... +* connect to 127.0.0.1 port 9999 from 127.0.0.1 port 58714 failed: В соединении отказано +* Failed to connect to localhost port 9999 after 0 ms: Couldn't connect to server +* Closing connection + +$ ss -tln sport = :9999 +State Recv-Q Send-Q Local Address:Port Peer Address:PortProcess diff --git a/evidence/lab4/05-latency.txt b/evidence/lab4/05-latency.txt new file mode 100644 index 000000000..b0f190811 --- /dev/null +++ b/evidence/lab4/05-latency.txt @@ -0,0 +1,33 @@ +$ 20 последовательных запросов, тайминги +0.000349 0.000828 0.000901 +0.000365 0.000775 0.000841 +0.000207 0.000502 0.000542 +0.000255 0.000540 0.000604 +0.000139 0.000299 0.000324 +0.000157 0.000355 0.000385 +0.000129 0.000285 0.000309 +0.000124 0.000271 0.000295 +0.000119 0.000260 0.000282 +0.000128 0.000273 0.000298 +0.000107 0.000231 0.000250 +0.000119 0.000283 0.000304 +0.000110 0.000246 0.000266 +0.000128 0.000283 0.000307 +0.000098 0.000203 0.000221 +0.000110 0.000232 0.000252 +0.000107 0.000231 0.000254 +0.000111 0.000279 0.000299 +0.000091 0.000213 0.000233 +0.000086 0.000181 0.000196 + +$ ss -s +Total: 972 +TCP: 49 (estab 10, closed 21, orphaned 0, timewait 21) + +Transport Total IP IPv6 +RAW 3 1 2 +UDP 39 35 4 +TCP 28 22 6 +INET 70 58 12 +FRAG 0 0 0 + diff --git a/evidence/lab4/quicknotes.pcap b/evidence/lab4/quicknotes.pcap new file mode 100644 index 0000000000000000000000000000000000000000..2cee86ac824c701afe417f1c18cebe7176db77bd GIT binary patch literal 1242 zcma)*PiWIn9LHbUQc9rUJPkbTk&}%6Nox|jxI^m{r}(E}MDQY68rsZ|#P(GgI@iNa z8)AQogD}{T6|6dOBB%%PwA~AI_zO_9gXI(V&!z?w4R{)AY3C+2pj~DL3=b=k-?7$$ma>DQ#9Q|g zXaZ73J6P0GF50P6;)B*$BDHjXU@0Rq8*yID;kml06SFef_rH8O1=Ccl)tV_RD!A0F z!tZ$n=VVnD)trRST+l`kX5Nf6Fwd$8s*?IY4^x6M&~#lV;4ob!Ef&(EqOr9yvaCp= ze0=8EHDTGm9}mHxkQ9IPGxvoVG)S?v1}WYOT2Ai;OYxDFP~xsn+#5@r7=JHbh7vms rKNc4maTtp&h{bv Date: Thu, 6 Aug 2026 19:03:32 +0300 Subject: [PATCH 4/6] docs(lab4): networking trace and deploy postmortem Signed-off-by: HNS <239804565+HNS2112@users.noreply.github.com> --- evidence/lab4/06-broken-deploy.txt | 3 + evidence/lab4/07-triage.txt | 19 ++ evidence/lab4/08-fix.txt | 14 ++ submissions/lab4.md | 354 +++++++++++++++++++++++++++++ 4 files changed, 390 insertions(+) create mode 100644 evidence/lab4/06-broken-deploy.txt create mode 100644 evidence/lab4/07-triage.txt create mode 100644 evidence/lab4/08-fix.txt create mode 100644 submissions/lab4.md diff --git a/evidence/lab4/06-broken-deploy.txt b/evidence/lab4/06-broken-deploy.txt new file mode 100644 index 000000000..7d1c122a0 --- /dev/null +++ b/evidence/lab4/06-broken-deploy.txt @@ -0,0 +1,3 @@ +2026/08/06 18:55:51 quicknotes listening on :8080 (notes loaded: 6) +2026/08/06 18:55:51 listen: listen tcp :8080: bind: address already in use +exit status 1 diff --git a/evidence/lab4/07-triage.txt b/evidence/lab4/07-triage.txt new file mode 100644 index 000000000..4db993e7a --- /dev/null +++ b/evidence/lab4/07-triage.txt @@ -0,0 +1,19 @@ +=== 1. Симптом: клиент не может подключиться к новому инстансу === +$ curl -sv --max-time 3 http://localhost:8080/health +{"notes":6,"status":"ok"} + + +=== 2. Кто занимает порт 8080 === +$ ss -tlnp sport = :8080 +State Recv-Q Send-Q Local Address:Port Peer Address:PortProcess +LISTEN 0 4096 *:8080 *:* users:(("quicknotes",pid=5201,fd=3)) + +=== 3. Что это за процесс === +$ ps -p 5201 -o pid,ppid,etime,cmd + PID PPID ELAPSED CMD + 5201 5094 09:29 /tmp/go-build2908606575/b001/exe/quicknotes + +=== 4. Когда он стартовал === +$ ls -la /proc/5201/cwd +lrwxrwxrwx 1 hns hns 0 авг 6 18:56 /proc/5201/cwd -> /home/hns/dev/DevOps-Intro/app +lrwxrwxrwx 1 hns hns 0 авг 6 18:56 /proc/5201/cwd -> /home/hns/dev/DevOps-Intro/app diff --git a/evidence/lab4/08-fix.txt b/evidence/lab4/08-fix.txt new file mode 100644 index 000000000..e9320c80e --- /dev/null +++ b/evidence/lab4/08-fix.txt @@ -0,0 +1,14 @@ +=== Fix: остановить старый инстанс === +$ kill 5201 +$ ss -tlnp sport = :8080 (порт должен освободиться) +State Recv-Q Send-Q Local Address:Port Peer Address:PortProcess +(пусто = порт свободен) + +=== Recovery: новый инстанс поднят === +$ ss -tlnp sport = :8080 +State Recv-Q Send-Q Local Address:Port Peer Address:PortProcess +LISTEN 0 4096 *:8080 *:* users:(("quicknotes",pid=5820,fd=3)) + +$ curl -s http://localhost:8080/health +{"notes":6,"status":"ok"} + diff --git a/submissions/lab4.md b/submissions/lab4.md new file mode 100644 index 000000000..9ec0f92df --- /dev/null +++ b/submissions/lab4.md @@ -0,0 +1,354 @@ +# Lab 4 — OS & Networking: Trace a Request, Debug a Deploy + +**Author:** HNS ([@HNS2112](https://github.com/HNS2112)) +**Date:** 6 August 2026 +**Environment:** Ubuntu 24.04 (noble), Linux kernel with systemd-resolved, Go 1.26.5 + +Raw command output is committed under `evidence/lab4/`. + +--- + +## Task 1 — Trace One Request End to End + +### 1.1 Packet capture of a single POST + +`tcpdump -i lo -w quicknotes.pcap 'tcp port 8080'` running while a single +`POST /notes` was issued. The capture contains the entire life of one TCP +connection in ten packets: + +```console +18:50:14.556151 IP 127.0.0.1.52742 > 127.0.0.1.8080: Flags [S], seq 852912346, win 65495 +18:50:14.556164 IP 127.0.0.1.8080 > 127.0.0.1.52742: Flags [S.], seq 3354113025, ack 852912347 +18:50:14.556173 IP 127.0.0.1.52742 > 127.0.0.1.8080: Flags [.], ack 1 +18:50:14.556203 IP 127.0.0.1.52742 > 127.0.0.1.8080: Flags [P.], length 175: HTTP: POST /notes HTTP/1.1 +POST /notes HTTP/1.1 +Host: localhost:8080 +User-Agent: curl/8.5.0 +Content-Type: application/json +Content-Length: 40 +{"title":"lab4","body":"packet capture"} +18:50:14.556207 IP 127.0.0.1.8080 > 127.0.0.1.52742: Flags [.], ack 176 +18:50:14.556662 IP 127.0.0.1.8080 > 127.0.0.1.52742: Flags [P.], length 207: HTTP: HTTP/1.1 201 Created +HTTP/1.1 201 Created +Content-Type: application/json +Content-Length: 94 +{"id":6,"title":"lab4","body":"packet capture","created_at":"2026-08-06T15:50:14.556405793Z"} +18:50:14.556696 IP 127.0.0.1.52742 > 127.0.0.1.8080: Flags [.], ack 208 +18:50:14.556827 IP 127.0.0.1.52742 > 127.0.0.1.8080: Flags [F.], seq 176 +18:50:14.556994 IP 127.0.0.1.8080 > 127.0.0.1.52742: Flags [F.], seq 208 +18:50:14.557066 IP 127.0.0.1.52742 > 127.0.0.1.8080: Flags [.], ack 209 +``` + +Mapping the four required phases onto the packets: + +| Phase | Packets | Flags | +|---|---|---| +| TCP three-way handshake | 1–3 | `[S]`, `[S.]`, `[.]` | +| HTTP request | 4 | `[P.]`, 175 bytes | +| HTTP response | 6 | `[P.]`, 207 bytes, `201 Created` | +| Connection teardown | 8–10 | `[F.]`, `[F.]`, `[.]` | + +Timings worth noting: the handshake completed in 22 microseconds +(`.556151` → `.556173`) and the whole exchange in 915 microseconds. Nothing here +leaves the kernel — see §1.3 on why loopback numbers look like this. + +The request and response each fit in a single segment. That is not luck; §1.3 +explains why nothing needed fragmenting. + +### 1.2 Diagnostic commands + +```console +$ ss -tlnp sport = :8080 +State Recv-Q Send-Q Local Address:Port Peer Address:Port Process +LISTEN 0 4096 *:8080 *:* users:(("quicknotes",pid=5201,fd=3)) + +$ ip -brief addr +lo UNKNOWN 127.0.0.1/8 +enp5s0 UP 192.168.0.123/24 +wlx9ca2f49134b7 UP 192.168.0.120/24 +virbr0 DOWN 192.168.122.1/24 +virbr1 DOWN 192.168.100.1/24 +docker0 DOWN 172.17.0.1/16 +tun2 UNKNOWN 10.33.0.2/24 + +$ ip route | head -3 +default via 192.168.0.1 dev enp5s0 proto dhcp src 192.168.0.123 metric 100 +default via 192.168.0.1 dev wlx9ca2f49134b7 proto dhcp src 192.168.0.120 metric 600 +127.0.0.0/8 dev lo proto kernel scope link src 127.0.0.1 metric 30 + +$ curl -s -o /dev/null -w "dns=%{time_namelookup} connect=%{time_connect} total=%{time_total}\n" \ + http://localhost:8080/health +dns=0.000031 connect=0.000240 total=0.000772 +``` + +**The listener is bound to `*:8080`, not to loopback.** This machine has seven +interfaces including two with routable LAN addresses, so QuickNotes — which has +no authentication — is reachable from the local network, not just from this host. +The application reads its bind address from `ADDR`, so `ADDR=127.0.0.1:8080` +would restrict it. This is the same environment variable used to break the deploy +in Task 2, and worth remembering for the container work in Lab 6. + +### 1.3 Path and transport properties + +```console +$ mtr -rwc 5 localhost +HOST: hns Loss% Snt Last Avg Best Wrst StDev + 1.|-- localhost 0.0% 5 0.1 0.1 0.1 0.1 0.0 + +$ ip link show lo +1: lo: mtu 65536 qdisc noqueue state UNKNOWN + +$ cat /proc/sys/net/ipv4/tcp_congestion_control +cubic + +$ ss -tim state established dst 127.0.0.1 +... cubic wscale:10,10 rto:201 rtt:0.256/0.421 mss:65483 pmtu:65535 cwnd:10 + send 20.5Gbps pacing_rate 40.9Gbps delivery_rate 105Gbps minrtt:0.005 +``` + +Three things explain the microsecond timings in §1.1. + +**MTU on `lo` is 65536**, against the 1500 typical of Ethernet. The negotiated +MSS is 65483 bytes, so the 175-byte request and 207-byte response fit in one +segment each with room to spare. There is nothing to fragment and no +path-MTU discovery to do. + +**`qdisc noqueue`** — loopback has no queueing discipline at all. There is no +buffer to fill and therefore no queueing delay. + +**`delivery_rate 105Gbps`** is not a network speed. It is the rate at which the +kernel copies between socket buffers in RAM; the packet never reaches a NIC +driver. `minrtt:0.005` — five microseconds — is the same fact from the latency +side. + +The congestion control algorithm is `cubic`, and `cwnd:10` shows the connection +never left the initial window. On a link with no loss and no queueing, congestion +control has nothing to react to. + +### 1.4 Name resolution — and a correction + +```console +$ dig +short localhost +127.0.0.1 + +$ dig localhost | head -20 +;; flags: qr aa rd ra ad; QUERY: 1, ANSWER: 1 +localhost. 0 IN A 127.0.0.1 +;; Query time: 0 msec +;; SERVER: 127.0.0.53#53(127.0.0.53) (UDP) + +$ getent hosts localhost +127.0.0.1 localhost +``` + +The expectation going in was that `dig localhost` would return nothing, on the +grounds that `localhost` is resolved from `/etc/hosts` rather than by DNS. That +turned out to be wrong on this system, and the reason is visible in the output: +`SERVER: 127.0.0.53#53` is `systemd-resolved`, which intercepts DNS on a loopback +stub address and synthesises an answer for `localhost` itself. The `aa` flag +marks it authoritative and `Query time: 0 msec` shows nothing left the machine. + +So both paths work, but through different mechanisms: `getent` consults NSS +(`/etc/hosts` first, per `/etc/nsswitch.conf`), while `dig` speaks DNS to a local +stub resolver that answers from its own knowledge. `curl` uses the NSS path, +which is why `time_namelookup` was 31 microseconds. + +The practical lesson is the one behind "it's never DNS": `dig` and the +application may not be asking the same question of the same resolver. Reproducing +a name-resolution problem with `dig` alone can mislead. + +### 1.5 Connection refused vs. timeout + +```console +$ curl -sv --max-time 3 http://localhost:9999/health +* Host localhost:9999 was resolved. +* IPv6: ::1 +* IPv4: 127.0.0.1 +* Trying [::1]:9999... +* Immediate connect fail for ::1: Cannot assign requested address +* Trying 127.0.0.1:9999... +* connect to 127.0.0.1 port 9999 from 127.0.0.1 port 58714 failed: Connection refused +* Failed to connect to localhost port 9999 after 0 ms + +$ ss -tln sport = :9999 +State Recv-Q Send-Q Local Address:Port Peer Address:Port + (empty) +``` + +The failure is immediate — "after 0 ms" — because nothing is on the wire. The +kernel knows no socket is listening on 9999 and answers the SYN with a RST +itself. `ss` confirms the port is unbound. + +This is the diagnostic difference that matters in an incident. **Connection +refused means something answered and said no**: the host is up, the route works, +the process is down. **A timeout means nothing answered at all**: a firewall +dropping packets, a wrong route, a dead host, or a process too wedged to accept. +The two point at completely different parts of the stack, and confusing them +sends the investigation the wrong way. + +The IPv6 attempt is a secondary detail worth noting: curl tried `::1` first and +failed differently — "cannot assign requested address" rather than "refused" — +because IPv6 is not configured on loopback here. Happy Eyeballs then fell back to +IPv4 with no measurable delay. + +### 1.6 Latency under repeated requests + +Twenty sequential `GET /health` calls, `connect` / `starttransfer` / `total` +in seconds: + +``` +0.000349 0.000828 0.000901 <- first +0.000365 0.000775 0.000841 +0.000207 0.000502 0.000542 +... +0.000091 0.000213 0.000233 +0.000086 0.000181 0.000196 <- twentieth +``` + +The first request took 901 µs and the twentieth 196 µs — a 4.6× speedup with no +change to the code or the request. The warm-up is in the CPU caches, the +scheduler's picture of these two processes, and the Go runtime's own state. + +This is why a single measurement of anything is untrustworthy, and it is the same +effect that produced the 18-second spread in the CI timings in Lab 3. + +```console +$ ss -s +TCP: 49 (estab 10, closed 21, orphaned 0, timewait 21) +``` + +The 21 sockets in TIME_WAIT are the closed connections from this loop. TIME_WAIT +is the 2×MSL wait that lets late duplicate segments drain before the port pair +can be reused — the cost of the clean four-way teardown seen in §1.1. + +--- + +## Task 2 — Debug a Broken Deploy + +### 2.1 The break + +A second instance was started while the first was still running: + +```console +$ ADDR=:8080 go run . +2026/08/06 18:55:51 quicknotes listening on :8080 (notes loaded: 6) +2026/08/06 18:55:51 listen: listen tcp :8080: bind: address already in use +exit status 1 +``` + +Note the ordering in the log: the application announced it was listening +*before* the bind failed. The message is emitted from the setup path, not from a +successful `Listen` call — so a log-scraping alert keyed on "listening on" would +have recorded a successful start for a process that died one line later. + +### 2.2 Outside-in triage + +**Step 1 — reproduce the symptom from the client's side.** + +```console +$ curl -s http://localhost:8080/health +{"notes":6,"status":"ok"} +``` + +The service answers. This is the most important step in the whole exercise: from +outside, nothing is wrong. The old instance is serving happily. The failure is +not "the service is down" but "the deploy did not take" — a distinction invisible +to a health check. + +**Step 2 — who owns the port.** + +```console +$ ss -tlnp sport = :8080 +LISTEN 0 4096 *:8080 *:* users:(("quicknotes",pid=5201,fd=3)) +``` + +**Step 3 — identify the process.** + +```console +$ ps -p 5201 -o pid,ppid,etime,cmd + PID PPID ELAPSED CMD + 5201 5094 09:29 /tmp/go-build2908606575/b001/exe/quicknotes +``` + +`ELAPSED 09:29` settles it. The process holding the port has been running for +nine and a half minutes, so it is the *old* instance, not the one just launched. +`PPID 5094` is the `go run` that spawned it. + +**Step 4 — where it came from.** + +```console +$ ls -la /proc/5201/cwd +lrwxrwxrwx 1 hns hns 0 /proc/5201/cwd -> /home/hns/dev/DevOps-Intro/app +``` + +`/proc//cwd` gives the working directory of a running process, which matters +here because QuickNotes resolves `DATA_PATH` and `SEED_PATH` relative to it. + +### 2.3 The fix + +```console +$ kill 5201 +$ ss -tlnp sport = :8080 + (empty — port released) +``` + +The old instance logged `shutting down` rather than dying abruptly: it caught +SIGTERM and shut down gracefully. Restart: + +```console +$ ss -tlnp sport = :8080 +LISTEN 0 4096 *:8080 *:* users:(("quicknotes",pid=5820,fd=3)) + +$ curl -s http://localhost:8080/health +{"notes":6,"status":"ok"} +``` + +New PID, six notes intact — state survived because it lives in `data/notes.json` +on disk, not in the process. + +### 2.4 Postmortem + +**Summary.** A deploy of QuickNotes failed at 18:55:51 with +`bind: address already in use`. The previous instance kept serving traffic +throughout, so no user-visible outage occurred, but the new build was not running +for the six minutes until the conflict was noticed. Resolved at 18:57:51 by +stopping the old process and starting the new one; recovery took 53 seconds. + +**Timeline.** + +| Time | Event | +|---|---| +| 18:46:43 | instance A starts, 4 notes loaded | +| 18:55:51 | deploy attempted; instance B exits with bind error | +| 18:56:58 | instance A stopped (SIGTERM, graceful) | +| 18:57:51 | instance B starts, 6 notes loaded | + +**What went wrong.** The deploy procedure starts the new process before stopping +the old one. Both bind the same port on the same host, and the second bind +necessarily fails. No data was lost and no request was dropped. + +**Why it was hard to see.** Two things hid the failure. The health endpoint kept +returning 200, because the process answering it was alive — it was just the wrong +version. And the application logs `listening on :8080` before the bind succeeds, +so the last line before the crash reads like a successful start. + +**What would prevent it, without blaming anyone.** The procedure, not the +operator, is at fault: any sequence that overlaps two processes on one port will +fail this way every time. Three changes address it at different levels. Stopping +the old instance before starting the new one removes the conflict outright — this +is what a systemd unit does by default and what `docker compose up` does when it +recreates a container. Binding the new instance to a different port and switching +traffic only after it reports healthy avoids the downtime the simple fix +introduces. And a deploy check that compares the running build to the intended +one — a version string on `/health`, or the PID's start time — would have +surfaced the problem in seconds instead of six minutes. + +--- + +## Summary + +| Task | Status | +|------|--------| +| Task 1 — packet capture, diagnostics, DNS, refused vs. timeout | Complete | +| Task 2 — broken deploy, outside-in triage, postmortem | Complete | +| Bonus — TLS proxy and handshake capture | Not attempted | From 7784ed8a6483c0699f07546c675d50f03b932397 Mon Sep 17 00:00:00 2001 From: Elvira <239804565+HNS2112@users.noreply.github.com> Date: Tue, 11 Aug 2026 12:31:55 +0300 Subject: [PATCH 5/6] docs(lab4): add TLS termination bonus with handshake analysis Signed-off-by: Elvira <239804565+HNS2112@users.noreply.github.com> --- evidence/lab4/09-tls-handshake.txt | 45 +++++++++++++++++++++++++++++ evidence/lab4/tls-handshake.pcap | Bin 0 -> 4912 bytes tls/Caddyfile | 9 ++++++ 3 files changed, 54 insertions(+) create mode 100644 evidence/lab4/09-tls-handshake.txt create mode 100644 evidence/lab4/tls-handshake.pcap create mode 100644 tls/Caddyfile diff --git a/evidence/lab4/09-tls-handshake.txt b/evidence/lab4/09-tls-handshake.txt new file mode 100644 index 000000000..c6fd050b3 --- /dev/null +++ b/evidence/lab4/09-tls-handshake.txt @@ -0,0 +1,45 @@ +=== Packet summary === + 1 0.000000 ::1 → ::1 TCP 88 59391 → 8443 [SYN] Seq=0 Win=65535 Len=0 MSS=16324 WS=64 TSval=1233291516 TSecr=0 SACK_PERM + 2 0.000160 ::1 → ::1 TCP 88 8443 → 59391 [SYN, ACK] Seq=0 Ack=1 Win=65535 Len=0 MSS=16324 WS=64 TSval=3794062206 TSecr=1233291516 SACK_PERM + 3 0.000233 ::1 → ::1 TCP 76 59391 → 8443 [ACK] Seq=1 Ack=1 Win=407808 Len=0 TSval=1233291516 TSecr=3794062206 + 4 0.000278 ::1 → ::1 TCP 76 [TCP Window Update] 8443 → 59391 [ACK] Seq=1 Ack=1 Win=407808 Len=0 TSval=3794062206 TSecr=1233291516 + 5 0.000497 ::1 → ::1 TLSv1 392 Client Hello (SNI=localhost) + 6 0.000564 ::1 → ::1 TCP 76 8443 → 59391 [ACK] Seq=1 Ack=317 Win=407552 Len=0 TSval=3794062207 TSecr=1233291517 + 7 0.001097 ::1 → ::1 TLSv1.3 1366 Server Hello, Change Cipher Spec, Application Data, Application Data, Application Data, Application Data + 8 0.001163 ::1 → ::1 TCP 76 59391 → 8443 [ACK] Seq=317 Ack=1291 Win=406528 Len=0 TSval=1233291517 TSecr=3794062207 + 9 0.030593 ::1 → ::1 TLSv1.3 82 Change Cipher Spec + 10 0.030630 ::1 → ::1 TLSv1.3 134 Application Data + 11 0.030684 ::1 → ::1 TLSv1.3 181 Application Data + 12 0.030688 ::1 → ::1 TCP 76 8443 → 59391 [ACK] Seq=1291 Ack=323 Win=407552 Len=0 TSval=3794062237 TSecr=1233291547 + 13 0.030715 ::1 → ::1 TCP 76 8443 → 59391 [ACK] Seq=1291 Ack=381 Win=407552 Len=0 TSval=3794062237 TSecr=1233291547 + 14 0.030723 ::1 → ::1 TCP 76 8443 → 59391 [ACK] Seq=1291 Ack=486 Win=407488 Len=0 TSval=3794062237 TSecr=1233291547 + 15 0.031516 ::1 → ::1 TLSv1.3 281 Application Data + 16 0.031551 ::1 → ::1 TCP 76 59391 → 8443 [ACK] Seq=486 Ack=1496 Win=406336 Len=0 TSval=1233291548 TSecr=3794062238 + 17 0.031673 ::1 → ::1 TLSv1.3 100 Application Data + 18 0.031702 ::1 → ::1 TCP 76 8443 → 59391 [ACK] Seq=1496 Ack=510 Win=407488 Len=0 TSval=3794062238 TSecr=1233291548 + 19 0.031712 ::1 → ::1 TCP 76 59391 → 8443 [FIN, ACK] Seq=510 Ack=1496 Win=406336 Len=0 TSval=1233291548 TSecr=3794062238 + 20 0.031736 ::1 → ::1 TCP 76 8443 → 59391 [ACK] Seq=1496 Ack=511 Win=407488 Len=0 TSval=3794062238 TSecr=1233291548 + +=== ClientHello === +0x0303 localhost 0x0304,0x0303,0x0302,0x0301 0x1303,0x1302,0x1301,0xcca9,0xcca8,0xccaa,0xc030,0xc02c,0xc028,0xc024,0xc014,0xc00a,0x009f,0x006b,0x0039,0xff85,0x00c4,0x0088,0x0081,0x009d,0x003d,0x0035,0x00c0,0x0084,0xc02f,0xc02b,0xc027,0xc023,0xc013,0xc009,0x009e,0x0067,0x0033,0x00be,0x0045,0x009c,0x003c,0x002f,0x00ba,0x0041,0xc011,0xc007,0x0005,0x0004,0xc012,0xc008,0x0016,0x000a,0x00ff + +=== ServerHello: negotiated version and cipher === +0x0303 0x0304 0x1303 + +=== Is the payload encrypted? === +(content type 23 = application data — the HTTP request and response are ciphertext) + +=== Contrast with the plaintext capture from Task 1.1 === +--- HTTP on :8080 (no TLS) — request and body readable --- +18:50:14.556203 IP localhost.52742 > localhost.http-alt: Flags [P.], seq 1:176, ack 1, win 64, options [nop,nop,TS val 2230267369 ecr 407459794], length 175: HTTP: POST /notes HTTP/1.1 +..-..IW.POST /notes HTTP/1.1 +Content-Type: application/json +{"title":"lab4","body":"packet capture"} +Content-Type: application/json + +--- HTTPS on :8443 (TLS 1.3) — same class of traffic, nothing readable --- + 7 0.001097 ::1 → ::1 TLSv1.3 1366 Server Hello, Change Cipher Spec, Application Data, Application Data, Application Data, Application Data + 10 0.030630 ::1 → ::1 TLSv1.3 134 Application Data + 11 0.030684 ::1 → ::1 TLSv1.3 181 Application Data + 15 0.031516 ::1 → ::1 TLSv1.3 281 Application Data + 17 0.031673 ::1 → ::1 TLSv1.3 100 Application Data diff --git a/evidence/lab4/tls-handshake.pcap b/evidence/lab4/tls-handshake.pcap new file mode 100644 index 0000000000000000000000000000000000000000..f58118530d0c6e312829408800c035064621f47a GIT binary patch literal 4912 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 literal 0 HcmV?d00001 diff --git a/tls/Caddyfile b/tls/Caddyfile new file mode 100644 index 000000000..fc4aaa361 --- /dev/null +++ b/tls/Caddyfile @@ -0,0 +1,9 @@ +{ + auto_https disable_redirects + admin off +} + +localhost:8443 { + tls internal + reverse_proxy 127.0.0.1:8080 +} From f9ddaf7f8ac945035dde0f993e18028b7b40ccf2 Mon Sep 17 00:00:00 2001 From: Elvira <239804565+HNS2112@users.noreply.github.com> Date: Tue, 11 Aug 2026 12:33:17 +0300 Subject: [PATCH 6/6] docs(lab4): document the TLS bonus in the report Signed-off-by: Elvira <239804565+HNS2112@users.noreply.github.com> --- submissions/lab4.md | 146 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 145 insertions(+), 1 deletion(-) diff --git a/submissions/lab4.md b/submissions/lab4.md index 9ec0f92df..4863ff5c5 100644 --- a/submissions/lab4.md +++ b/submissions/lab4.md @@ -345,10 +345,154 @@ surfaced the problem in seconds instead of six minutes. --- +## Bonus Task — TLS Termination and Handshake Capture + +### B.1 Setup + +QuickNotes bound to `127.0.0.1:8080` with no TLS of its own; Caddy terminating +TLS on `:8443` and proxying to it. + +`tls/Caddyfile`: + +``` +{ + auto_https disable_redirects + admin off +} + +localhost:8443 { + tls internal + reverse_proxy 127.0.0.1:8080 +} +``` + +`tls internal` makes Caddy issue a certificate from its own local CA rather than +going to Let's Encrypt — the right choice for `localhost`, which no public CA +will ever certify. Caddy installs its root into the macOS keychain on first run: + +``` +WARN pki.ca.local installing root certificate (you might be prompted for password) +INFO tls.obtain certificate obtained successfully {"identifier": "localhost", "issuer": "local"} +INFO certificate installed properly in macOS keychain +``` + +It works end to end: + +```console +$ curl -sk https://localhost:8443/health +{"notes":4,"status":"ok"} +``` + +### B.2 The handshake, packet by packet + +Captured with `tshark -i lo0 -f "tcp port 8443"` around a single request. + +``` + 1 ::1 → ::1 TCP 59391 → 8443 [SYN] + 2 ::1 → ::1 TCP 8443 → 59391 [SYN, ACK] + 3 ::1 → ::1 TCP 59391 → 8443 [ACK] + 5 ::1 → ::1 TLSv1 Client Hello (SNI=localhost) + 7 ::1 → ::1 TLSv1.3 Server Hello, Change Cipher Spec, Application Data ×4 + 9 ::1 → ::1 TLSv1.3 Change Cipher Spec +10 ::1 → ::1 TLSv1.3 Application Data +11 ::1 → ::1 TLSv1.3 Application Data +15 ::1 → ::1 TLSv1.3 Application Data +17 ::1 → ::1 TLSv1.3 Application Data +19 ::1 → ::1 TCP 59391 → 8443 [FIN, ACK] +``` + +The TCP three-way handshake of §1.1 is still there in packets 1–3 — TLS runs on +top of it, it does not replace it. The whole exchange took 31 milliseconds +against the 915 microseconds of the plaintext version in §1.1, and essentially +all of that difference is the handshake. + +**ClientHello** (packet 5): + +``` +Version: TLS 1.2 (0x0303) ← legacy field +Server Name: localhost +supported_versions: 0x0304, 0x0303, 0x0302, 0x0301 +Cipher Suites (49 suites): + TLS_CHACHA20_POLY1305_SHA256 (0x1303) + TLS_AES_256_GCM_SHA384 (0x1302) + TLS_AES_128_GCM_SHA256 (0x1301) + ... 46 more, down to TLS_RSA_WITH_AES_128_CBC_SHA +``` + +**ServerHello** (packet 7): + +``` +Version: TLS 1.2 (0x0303) ← legacy field + [Expert Info: This legacy_version field MUST be ignored. + The supported_versions extension is present and MUST be used instead.] +Extension: supported_versions — Supported Version: TLS 1.3 (0x0304) +Cipher Suite: TLS_CHACHA20_POLY1305_SHA256 (0x1303) +``` + +**Two things in that output are worth stopping on.** + +The record layer claims TLS 1.2 on both sides while the connection is actually +TLS 1.3 — and Wireshark flags it as an expert note, not a bug. TLS 1.3 pins the +legacy version field at 1.2 and moves the real negotiation into the +`supported_versions` extension, specifically because middleboxes on the internet +were dropping handshakes that advertised a version they had never seen. The +protocol lies about its own version to get through hardware that predates it. + +And the client offered 49 cipher suites spanning TLS 1.0 through 1.3 — +including `TLS_RSA_WITH_AES_128_CBC_SHA` and two GOST suites — while the server +picked `TLS_CHACHA20_POLY1305_SHA256`, one of the three TLS 1.3 suites. The +client's list is a compatibility net; the server's single choice is the actual +security posture. Reading a capture and seeing weak suites offered says nothing +by itself — what matters is which one came back. + +### B.3 Plaintext versus ciphertext + +Same application, same endpoint, two captures: + +``` +--- HTTP on :8080 (Task 1.1) --- +POST /notes HTTP/1.1 +Content-Type: application/json +{"title":"lab4","body":"packet capture"} + +--- HTTPS on :8443 (this task) --- + 10 TLSv1.3 134 Application Data + 11 TLSv1.3 181 Application Data + 15 TLSv1.3 281 Application Data + 17 TLSv1.3 100 Application Data +``` + +In §1.1 the request line, the headers and the JSON body were all readable +straight out of the capture. Here the same traffic is four opaque records. + +What TLS does **not** hide is worth naming precisely, because it is visible in +the very capture above: the source and destination addresses, the port, the +timing and sizes of every record, and — in packet 5 — the SNI field carrying +`localhost` in cleartext. An observer learns who is talking to which host, when, +and roughly how much. They do not learn what was said. Encrypted Client Hello +exists to close the SNI gap and was not in play here. + +### B.4 What this does and does not buy + +The proxy pattern is worth stating plainly: **QuickNotes still speaks plain HTTP** +and knows nothing about TLS. Caddy decrypts at the edge and forwards cleartext +over loopback to `127.0.0.1:8080`, which is safe here only because that hop never +leaves the machine. In a real deployment the same arrangement across a network +segment would be a plaintext hop an attacker on that segment could read — the +thing service meshes and mTLS exist to close. + +That separation is also what makes it practical. The application does not manage +certificates, renewals, cipher configuration or protocol versions; Caddy does, +and its defaults are current. Lab 6's image would need none of it, and Lab 10's +Render deployment got TLS for free from the platform for exactly the same reason +— someone else terminates it. + +--- + ## Summary | Task | Status | |------|--------| | Task 1 — packet capture, diagnostics, DNS, refused vs. timeout | Complete | | Task 2 — broken deploy, outside-in triage, postmortem | Complete | -| Bonus — TLS proxy and handshake capture | Not attempted | +| Bonus — TLS proxy and handshake capture | Complete |