บทความ

เมื่อแอปพลิเคชันทำงานผ่านพร็อกซีแล้วมีอะไรบางอย่างผิดพลาด สิ่งแรกที่วิศวกรเปิดดูคือล็อกของไคลเอนต์ ในนั้นจะเขียนอะไรทำนอง connection reset by peer, read timeout หรือแค่ EOF ปัญหาคือบรรทัดเหล่านี้แทบไม่บอกอะไรเกี่ยวกับสาเหตุเลย ใครเป็นคนรีเซ็ตการเชื่อมต่อ: พร็อกซี เซิร์ฟเวอร์ปลายทาง หรือผู้ให้บริการเครือข่ายระหว่างคุณกับพร็อกซี? คำขอไปถึงพร็อกซีแล้วหรือยัง? TLS handshake เสร็จทันหรือเปล่า? ไลบรารี HTTP client ไม่รู้เรื่องนี้ ดังนั้นคุณก็ไม่รู้เช่นกัน

ในบทความนี้เราจะลงไปต่ำกว่านั้น ถึงระดับแพ็กเก็ต เราจะอธิบายวิธีเก็บดัมป์ tcpdump บนเครื่องไคลเอนต์ อะไรที่มองเห็นได้ในดัมป์นี้เมื่อทำงานผ่าน HTTP proxy และ SOCKS5 ทำไมในอุโมงค์ HTTPS คุณจะเห็นแค่ CONNECT กับบันทึก TLS ที่ถูกเข้ารหัส วิธีอ่านดัมป์ใน Wireshark และวิธีดูจากแฟล็ก RST, FIN, retransmission และ zero window ว่าใครเป็นฝ่ายตัดการเชื่อมต่อ นอกจากนี้เราจะพูดถึงการถอดรหัสทราฟฟิกของตัวเองผ่าน SSLKEYLOGFILE และวิธีเตรียมดัมป์ให้ถูกต้องเพื่อติดต่อฝ่ายสนับสนุนของบริการพร็อกซี เช่น Proxeon

ข้อควรระวังสำคัญ: นี่ไม่ใช่บทความเกี่ยวกับ mitmproxy ที่นั่นพูดถึงการดักจับและเปลี่ยน HTTPS ในระดับแอปพลิเคชันด้วยใบรับรองปลอม ที่นี่เราทำงานในระดับแพ็กเก็ต ไม่เปลี่ยนอะไรและไม่แกะทราฟฟิกของคนอื่น งานของเราคือการวินิจฉัยล้วนๆ: เข้าใจว่าโซ่ไคลเอนต์ - พร็อกซี - เซิร์ฟเวอร์ปลายทางขาดตรงไหน

บทนำ: เมื่อล็อกของไคลเอนต์ไม่พออีกต่อไป

ลองนึกถึงสถานการณ์ทั่วไป สคริปต์ Python วิ่งผ่านพร็อกซีมือถือ ประมวลผลคำขอสองพันรายการต่อชั่วโมง และประมาณสองเปอร์เซ็นต์จบลงด้วยข้อผิดพลาด Connection aborted, RemoteDisconnected นักพัฒนาเพิ่ม retry ข้อผิดพลาดก็ไม่หาย เขาเขียนไปหาฝ่ายสนับสนุนพร็อกซี ทางนั้นขอตัวอย่างคำขอและเวลา ฝ่ายสนับสนุนดูล็อกของตัวเองแล้วตอบว่า: ทางเราปกติดี การเชื่อมต่อไปยังเซิร์ฟเวอร์ปลายทางเกิดขึ้นได้ ใครถูก?

ถ้าไม่มีดัมป์ การโต้เถียงนี้ไม่มีวันจบ แต่ถ้ามีดัมป์ มันจบในห้านาที: เห็นได้ว่าพร็อกซีตอบ CONNECT ด้วยรหัส 200 ไคลเอนต์ส่ง ClientHello แล้วผ่านไป 180 มิลลิวินาที มี RST มาจากพร็อกซี นั่นหมายความว่าพร็อกซีตกลงกับเซิร์ฟเวอร์ปลายทางไม่ได้ หรือเซิร์ฟเวอร์ปิดการเชื่อมต่อเอง จากนั้นก็ดูไทม์มิ่งและเจาะลึกได้ สิ่งสำคัญคือการสนทนาหลุดออกจากพื้นที่ของการคาดเดาเข้าสู่พื้นที่ของข้อเท็จจริง

ล็อกของ HTTP client ทำงานในระดับแอปพลิเคชัน มันเห็นผลลัพธ์แต่ไม่เห็นกระบวนการ ดัมป์ทราฟฟิกแสดงกระบวนการ: ทุกแพ็กเก็ตพร้อมเวลาที่แม่นยำ ทิศทาง แฟล็ก และขนาด นั่นคือเหตุผลที่ในการดำเนินงานโครงสร้างพื้นฐานพร็อกซีอย่างจริงจัง ทักษะการเก็บและอ่านดัมป์ถือเป็นทักษะพื้นฐาน ไม่ใช่เรื่องแปลก

สิ่งที่คุณจะได้จากบทความนี้

  • ความเข้าใจว่าข้อมูลอะไรที่ผ่านอินเทอร์เฟซเครือข่ายของไคลเอนต์จริงๆ เมื่อใช้พร็อกซี และอะไรที่มองเห็นได้แบบเปิดเผย
  • คำสั่ง tcpdump สำเร็จรูปสำหรับบันทึกดัมป์พร้อมฟิลเตอร์ การหมุนไฟล์ และจำกัดขนาด
  • ชุดฟิลเตอร์แสดงผลของ Wireshark ที่คัดลอกไปใช้ได้เลย
  • วิธีการแยกแยะการตัดการเชื่อมต่อที่ฝั่งเรา ฝั่งพร็อกซี และฝั่งเซิร์ฟเวอร์ปลายทาง
  • วิธีถอดรหัสทราฟฟิก TLS ของตัวเองอย่างปลอดภัยเพื่อดีบัก
  • เช็คลิสต์การเตรียมดัมป์สำหรับติดต่อฝ่ายสนับสนุน

พื้นฐาน: อะไรที่มองเห็นได้ก่อนถึงพร็อกซี ภายในอุโมงค์ และหลังจากนั้น

เริ่มจากแผนภาพ เมื่อทำงานผ่านพร็อกซี เส้นทางมีสามช่วง และบนเครื่องไคลเอนต์คุณสังเกตเห็นได้จริงแค่ช่วงแรก

สามโซนที่สังเกตได้

  1. โซน A: ไคลเอนต์ - พร็อกซี นี่คือการเชื่อมต่อ TCP เดียวที่ผ่านอินเทอร์เฟซเครือข่ายของคุณ tcpdump บนเครื่องคุณเห็นมัน ในนี้เห็นได้: TCP handshake กับ IP ของพร็อกซี โปรโตคอลพร็อกซี (HTTP CONNECT หรือ SOCKS5) แล้วหลังจากนั้นก็เป็น HTTP แบบเปิดหรือบันทึก TLS ที่เข้ารหัส
  2. โซน B: ภายในอุโมงค์ หลังจากพร็อกซีตอบ 200 Connection established ไบต์ทั้งหมดระหว่างคุณกับเซิร์ฟเวอร์ปลายทางจะถูกส่งผ่านไปเลย ถ้าเซิร์ฟเวอร์ปลายทางใช้ HTTPS ไบต์เหล่านี้คือบันทึก TLS คุณเห็นโครงสร้าง (ประเภทบันทึก ความยาว ClientHello พร้อม SNI, ServerHello, alert) แต่ไม่เห็นเนื้อหา
  3. โซน C: พร็อกซี - เซิร์ฟเวอร์ปลายทาง นี่คือการเชื่อมต่อ TCP แยกต่างหากที่พร็อกซีสร้างขึ้นในนามของตัวเองจากที่อยู่ภายนอกของตัวเอง บนเครื่องไคลเอนต์ไม่มีอยู่เลย จะเห็นได้เฉพาะบนเซิร์ฟเวอร์พร็อกซีเอง ซึ่งเป็นโครงสร้างพื้นฐานของผู้ให้บริการ ทุกอย่างที่คุณรู้เกี่ยวกับโซน C คุณรู้มาโดยอ้อม: จากรหัสตอบบน CONNECT จากความหน่วง และจากวิธีที่พร็อกซีปิดอุโมงค์

นี่คือความคิดหลักของบทความทั้งหมด ดัมป์บนไคลเอนต์ไม่แสดงเซิร์ฟเวอร์ปลายทางโดยตรง แต่มันแสดงพฤติกรรมของพร็อกซี และพร็อกซีในฐานะตัวส่งต่อที่ดี จะแปลงพฤติกรรมของเซิร์ฟเวอร์ปลายทางให้อยู่ในรูปแบบที่ตีความได้ งานของเราคือเรียนรู้ที่จะอ่านคำแปลนี้

HTTP proxy และ HTTP แบบเปิด

กรณีที่โปร่งใสที่สุด ถ้าเว็บไซต์ปลายทางใช้ http ไม่เข้ารหัส ไคลเอนต์ส่งคำขอไปยังพร็อกซีพร้อม URI แบบเต็ม:

GET http://example.com/api/items HTTP/1.1
Host: example.com
Proxy-Authorization: Basic dXNlcjpwYXNz
User-Agent: my-client/1.0

ในดัมป์เห็นทุกอย่าง: เมธอด พาธ เฮดเดอร์ บอดี้ คำตอบของพร็อกซีพร้อมบอดี้จากเซิร์ฟเวอร์ สังเกตเฮดเดอร์ Proxy-Authorization นี่คือข้อมูลรับรองของคุณใน base64 มันอยู่ในดัมป์แบบเปิดเผย จำข้อเท็จจริงนี้ไว้ จะมีประโยชน์ตอนที่เราพูดถึงการส่งดัมป์ให้บุคคลที่สาม

HTTP proxy และ HTTPS ผ่าน CONNECT

ตรงนี้เริ่มน่าสนใจ สำหรับ HTTPS ไคลเอนต์ขอให้พร็อกซีสร้าง TCP tunnel ไปยังโฮสต์และพอร์ตก่อน:

CONNECT api.example.com:443 HTTP/1.1
Host: api.example.com:443
Proxy-Authorization: Basic dXNlcjpwYXNz
Proxy-Connection: Keep-Alive

พร็อกซีตอบ:

HTTP/1.1 200 Connection established

จากจุดนี้พร็อกซีเลิกแยกวิเคราะห์ HTTP มันแค่คัดลอกไบต์จากซ็อกเก็ตหนึ่งไปอีกซ็อกเก็ต ไคลเอนต์เริ่ม TLS handshake ตรงกับเซิร์ฟเวอร์ปลายทาง และทราฟฟิก HTTP ทั้งหมด (GET, POST, เฮดเดอร์, บอดี้) จะอยู่ภายใน TLS นั่นคือเหตุผลที่ในดัมป์อุโมงค์ HTTPS เห็นแค่ CONNECT: นี่คือสิ่งสุดท้ายที่ไคลเอนต์บอกพร็อกซีแบบเปิดเผย หลังจากนั้นเป็นบันทึก TLS ที่พร็อกซีอ่านไม่ได้ และคุณก็อ่านไม่ได้ในดัมป์เช่นกัน

สิ่งที่ยังมองเห็นได้ภายในอุโมงค์:

  • ClientHello: เวอร์ชัน TLS ชุด cipher, ส่วนขยาย และโดยปกติ SNI (ชื่อโฮสต์แบบเปิดเผย)
  • ServerHello: เวอร์ชันและ cipher ที่เลือก
  • ใน TLS 1.2 - ใบรับรองของเซิร์ฟเวอร์แบบเปิดเผย ใน TLS 1.3 ใบรับรองถูกเข้ารหัสแล้ว
  • TLS Alert: ประเภทของ alert เห็นได้ใน TLS 1.2 ใน TLS 1.3 alert หลัง handshake ถูกเข้ารหัส แต่การมีอยู่ของบันทึกประเภท Alert ที่ยาว 2 ไบต์ก็ยังถูกจับได้
  • ขนาดและไทม์มิ่งของบันทึก Application Data จากนี้ประเมินได้ว่ามีข้อมูลมาเท่าไหร่ก่อนการเชื่อมต่อจะขาด

SOCKS5

SOCKS5 ทำงานต่างออกไป แต่แนวคิดเดียวกัน ไคลเอนต์ส่งทักทายพร้อมวิธีรับรองความถูกต้อง (ไบต์ 05 02 00 02) พร็อกซีเลือกวิธี จากนั้นรับรองด้วยชื่อผู้ใช้และรหัสผ่าน (โปรโตคอลย่อยที่มีไบต์ 01, ความยาว, ชื่อ, ความยาว, รหัสผ่าน) จากนั้นคำสั่ง CONNECT พร้อมประเภทที่อยู่ 03 (ชื่อโดเมน) และพอร์ต พร็อกซีตอบ 05 00 เมื่อสำเร็จ หรือรหัสข้อผิดพลาด: 01 ข้อผิดพลาดทั่วไป, 03 เครือข่ายไม่พร้อมใช้งาน, 04 โฮสต์ไม่พร้อมใช้งาน, 05 การเชื่อมต่อถูกปฏิเสธ, 06 TTL หมดอายุ รหัสข้อผิดพลาดเหล่านี้คือคำแปลตรงๆ ของสิ่งที่พร็อกซีเห็นในโซน C Wireshark สามารถถอดรหัส SOCKS ได้ถ้าระบุพอร์ตผ่าน Decode As

สิ่งที่ไม่มีวันเห็น

จากเครื่องไคลเอนต์คุณจะไม่เห็น: IP ภายนอกของพร็อกซีที่ใช้ติดต่อเซิร์ฟเวอร์ปลายทาง (สำหรับพร็อกซีมือถือคือที่อยู่ของผู้ให้บริการ) การเชื่อมต่อ TCP ระหว่างพร็อกซีกับเซิร์ฟเวอร์ คำขอ DNS ของพร็อกซี ถ้าฝ่ายสนับสนุนของ Proxeon บอกว่าเห็นข้อผิดพลาดที่ฝั่งเซิร์ฟเวอร์ปลายทาง พวกเขากำลังดูโซน C ซึ่งคุณเข้าถึงไม่ได้ งานของคุณคือนำดัมป์โซน A มาเพื่อเทียบภาพทั้งสองตามเวลา

เจาะลึก: ทำไมพร็อกซีแสดงได้ไม่มากกว่านี้ และวิธีอ่านสัญญาณทางอ้อม

ตรงนี้ควรทำความเข้าใจว่าทำไมภาพจึงเป็นเช่นนี้ และมันบอกอะไรสำหรับการวินิจฉัย

อุโมงค์ CONNECT เป็นท่อไบต์

หลังจากตอบ 200 HTTP proxy ตามสเปกต้องส่งไบต์ทั้งสองทางโดยไม่ตีความจนกว่าฝ่ายใดฝ่ายหนึ่งจะปิด มันไม่รู้ว่าข้างในคือ TLS มันไม่รู้ว่าคุณส่งคำขอ HTTP อะไร มันเห็นแค่สองเหตุการณ์: กระแสไบต์และการปิดซ็อกเก็ต เมื่อเซิร์ฟเวอร์ปลายทางปิดการเชื่อมต่อกับพร็อกซี (ส่ง FIN หรือ RST) พร็อกซีต้องปิดการเชื่อมต่อกับคุณ วิธีที่มันทำขึ้นอยู่กับการใช้งาน: พร็อกซีบางตัวส่ง FIN เป็น FIN และ RST เป็น RST บางตัวแปลงทุกอย่างเป็น FIN บางตัวเมื่ออ่านจากซ็อกเก็ตปลายทางผิดพลาดก็ส่ง RST ให้ไคลเอนต์

ผลในทางปฏิบัติ: RST จาก IP ของพร็อกซีไม่ได้หมายความว่าพร็อกซีผิด มันหมายความว่าอุโมงค์ถูกปิดจากฝั่งพร็อกซี และสาเหตุอาจอยู่ที่ไหนก็ได้หลังพร็อกซี เพื่อเข้าใจสาเหตุ ดูบริบท: เกิดอะไรขึ้นก่อน RST ผ่านไปนานเท่าไหร่ คำตอบมาถึงหรือยัง

ความต่างระหว่าง SYN ไปยังพร็อกซีกับ SYN ไปยังเซิร์ฟเวอร์ปลายทาง

นี่คือแหล่งความสับสนที่พบบ่อยที่สุด ในดัมป์ผ่านพร็อกซีคุณจะไม่มีวันเห็น SYN ไปยังพอร์ต 443 ของเซิร์ฟเวอร์ปลายทาง SYN ทั้งหมดไปยัง IP และพอร์ตของพร็อกซี ถ้า SYN ไปยังพร็อกซีไม่ได้รับ SYN-ACK และถูกส่งซ้ำด้วยช่วง 1, 2, 4, 8 วินาที ปัญหาอยู่ระหว่างคุณกับพร็อกซี: เครือข่าย ไฟร์วอลล์ ที่อยู่หรือพอร์ตผิด พร็อกซีไม่ทำงาน เซิร์ฟเวอร์ปลายทางไม่เกี่ยวเลย ในแง่ของดัมป์คุณยังไม่ได้พยายามติดต่อมันด้วยซ้ำ

แต่ถ้า TCP กับพร็อกซีถูกสร้างขึ้นแล้ว ส่ง CONNECT แล้ว และคำตอบมาใน 20-30 วินาทีด้วยรหัส 504 Gateway Timeout หรือ 502 Bad Gateway นั่นคือพร็อกซีสร้างโซน C ไม่ได้ ไทม์มิ่งบอกเอง: แพ็กเก็ต CONNECT ของคุณออกไปทันที ไม่มีคำตอบนาน แปลว่าพร็อกซีรอ timeout ในการพยายามของมัน

TLS 1.3, ECH และสิ่งที่เปลี่ยนไปถึงปี 2026

ภูมิทัศน์ค่อยๆ ปิดลง TLS 1.3 ครองตลาด และในนั้นใบรับรองของเซิร์ฟเวอร์ถูกเข้ารหัส ดังนั้นการตรวจสอบว่าเซิร์ฟเวอร์คืนใบรับรองอะไร จากดัมป์โดยไม่มีคีย์จึงทำไม่ได้ ส่วนขยาย ECH (Encrypted Client Hello) ค่อยๆ ถูกนำมาใช้โดยเบราว์เซอร์และ CDN ขนาดใหญ่ และ SNI ก็ถูกเข้ารหัสเช่นกัน สำหรับการวินิจฉัยผ่านพร็อกซีเรื่องนี้สำคัญน้อยกว่า เพราะชื่อโฮสต์ยังเห็นได้ในบรรทัด CONNECT แต่ฟิลเตอร์ SNI ตามปกติจะเจอน้อยลง

อีกแนวโน้มคือ HTTP/3 บน QUIC HTTP proxy แบบคลาสสิกกับ CONNECT อุโมงค์เฉพาะ TCP ถ้าในดัมป์คุณเห็นทราฟฟิก UDP ไปยังพอร์ต 443 ตรงไปยังที่อยู่ของเซิร์ฟเวอร์ปลายทาง โดยข้ามพร็อกซี นั่นคือสัญญาณของการรั่ว: แอปพลิเคชันพยายามใช้ QUIC โดยตรง ไม่ผ่านพร็อกซี ไคลเอนต์ที่ตั้งค่าถูกต้องควรปิด QUIC เมื่อทำงานผ่านพร็อกซี หรือใช้การพร็อกซี UDP ผ่านกลไกแยก ฟิลเตอร์สำหรับตรวจสอบเร็ว: udp.port == 443 ถ้าดัมป์ผ่านพร็อกซีแสดงแพ็กเก็ตเหล่านี้ ต้องแก้การตั้งค่าไคลเอนต์

จุดเก็บข้อมูลควรอยู่ตรงไหน

โดยปกติคำตอบเดียว - บนเครื่องไคลเอนต์ แต่มีรายละเอียด ถ้าไคลเอนต์ทำงานใน Docker container tcpdump บนโฮสต์บนอินเทอร์เฟซ docker0 หรือบนอินเทอร์เฟซบริดจ์จะแสดงทราฟฟิกก่อน NAT แต่บนอินเทอร์เฟซภายนอกจะแสดงหลัง NAT โดยมีที่อยู่ต้นทางต่างออกไป ง่ายกว่าคือเข้าไปในเน็ตเวิร์กเนมสเปซของ container: nsenter -t PID -n tcpdump ... หรือรัน container ที่มี tcpdump ผ่าน --net=container:ชื่อ บนเครื่องเสมือนในคลาวด์ให้สังเกต offloading: แพ็กเก็ตในดัมป์อาจดูใหญ่กว่า MTU เพราะการ์ดเครือข่ายรวมเซกเมนต์ นี่เป็นเรื่องปกติและไม่กระทบการวิเคราะห์แฟล็ก

tcpdump ในทางปฏิบัติ: ฟิลเตอร์ การบันทึกลงไฟล์ การหมุนไฟล์

tcpdump มีอยู่เกือบทุกเครื่อง Linux และบน macOS บน Windows บทบาทเดียวกันทำโดย Wireshark พร้อมไดรเวอร์ Npcap หรือ dumpcap แบบคอนโซล มาดูคำสั่งที่ครอบคลุม 95 เปอร์เซ็นต์ของงานวินิจฉัยพร็อกซี

การเก็บทราฟฟิกพื้นฐานไปยังพร็อกซี

สมมติที่อยู่พร็อกซี 203.0.113.10 พอร์ต 8080 บันทึกทุกอย่างที่ไปมาระหว่างเรากับพร็อกซีลงไฟล์:

sudo tcpdump -i any -nn -s 0 -w proxy.pcap 'host 203.0.113.10 and port 8080'

อธิบายคีย์:

  • -i any - ฟังทุกอินเทอร์เฟซ สะดวกเมื่อไม่รู้ว่าทราฟฟิกออกทางไหน ถ้ารู้ ควรระบุเจาะจง: -i eth0 หรือ -i wlan0 บน macOS -i any ไม่รองรับ ให้ระบุ en0
  • -nn - ไม่ resolve IP เป็นชื่อและพอร์ตเป็นชื่อบริการ การ resolve ทำให้การเก็บช้าลงและเพิ่มคำขอ DNS ในเครือข่าย
  • -s 0 - เก็บแพ็กเก็ตทั้งหมด ในเวอร์ชันใหม่นี่เป็นค่าเริ่มต้น แต่การระบุชัดเจนไม่เสียหาย
  • -w proxy.pcap - เขียนลงไฟล์ในรูปแบบ pcap ไม่ใช่แสดงบนหน้าจอ เฉพาะวิธีนี้เท่านั้นที่ดัมป์จะเปิดใน Wireshark ได้
  • ฟิลเตอร์ในเครื่องหมายคำพูดเดี่ยว - นี่คือ BPF filter สำหรับเก็บ มันตัดสิ่งที่ไม่ต้องการออกตั้งแต่ในเคอร์เนล ไฟล์จึงมีแค่สิ่งที่ต้องการ

ฟิลเตอร์ตามโฮสต์และพอร์ต

พร็อกซีหลายตัวหรือพูลพอร์ต:

sudo tcpdump -i eth0 -nn -w proxy.pcap 'host 203.0.113.10 and portrange 10000-10100'

พร็อกซีตามชื่อโดเมน (tcpdump จะ resolve หนึ่งครั้งตอนเริ่ม ซึ่งไม่เหมาะกับที่อยู่ที่หมุนเวียนเสมอ):

sudo tcpdump -i eth0 -nn -w proxy.pcap 'host proxy.example.net and port 8080'

เก็บเฉพาะแพ็กเก็ตควบคุมโดยไม่มีข้อมูล เพื่อดู handshake และการตัดเมื่อทราฟฟิกมาก:

sudo tcpdump -i eth0 -nn -w flags.pcap 'host 203.0.113.10 and tcp[tcpflags] & (tcp-syn|tcp-fin|tcp-rst) != 0'

เฉพาะ RST ทั้งสองทาง:

sudo tcpdump -i eth0 -nn 'tcp[tcpflags] & tcp-rst != 0'

เก็บโดยยกเว้นเซสชัน SSH ของตัวเอง เพื่อไม่ให้ดัมป์รกเมื่อเก็บบนเซิร์ฟเวอร์ระยะไกล:

sudo tcpdump -i eth0 -nn -w proxy.pcap 'host 203.0.113.10 and not port 22'

วิธีไม่ให้เก็บเป็นกิกะไบต์: หมุนตามขนาดและเวลา

ถ้าข้อผิดพลาดหายากและเกิดชั่วโมงละครั้ง ดัมป์ต้องหมุนนานๆ ถ้าไม่หมุน ดิสก์จะเต็ม หมุนตามขนาด 100 MB ต่อไฟล์ วงแหวน 10 ไฟล์:

sudo tcpdump -i eth0 -nn -w proxy-%Y%m%d-%H%M%S.pcap -C 100 -W 10 'host 203.0.113.10 and port 8080'

ตรงนี้ -C กำหนดขนาดเป็นล้านไบต์ -W จำกัดจำนวนไฟล์: ไฟล์ที่สิบเอ็ดจะเขียนทับไฟล์แรก หมุนตามเวลา ไฟล์ใหม่ทุก 10 นาที เก็บ 24 ไฟล์ล่าสุด (4 ชั่วโมง):

sudo tcpdump -i eth0 -nn -w proxy-%Y%m%d-%H%M%S.pcap -G 600 -W 24 'host 203.0.113.10 and port 8080'

คีย์ -G ระบุช่วงเวลาเป็นวินาที และรูปแบบเวลาในชื่อไฟล์เป็นสิ่งจำเป็น ไม่งั้น tcpdump จะเขียนทับไฟล์เดียว คีย์ที่มีประโยชน์ -Z ผู้ใช้ ลดสิทธิ์หลังเปิดอินเทอร์เฟซ และ -U บังคับให้เขียนแพ็กเก็ตลงไฟล์ทันทีโดยไม่บัฟเฟอร์ ซึ่งสำคัญถ้าคุณจะอ่านไฟล์ขนานกันหรือกลัวสูญเสียส่วนท้ายเมื่อโปรแกรมหยุดกะทันหัน

ตัด payload

อีกวิธีลดปริมาณ - เก็บเฉพาะเฮดเดอร์ สำหรับวินิจฉัยการตัด เนื้อหาของบันทึก TLS ไม่จำเป็น แค่ 128 ไบต์แรกของแต่ละแพ็กเก็ตก็พอ:

sudo tcpdump -i eth0 -nn -s 128 -w headers.pcap 'host 203.0.113.10'

ระวัง: ด้วย -s 128 บรรทัด CONNECT และเฮดเดอร์อาจถูกตัด และ Wireshark จะทำเครื่องหมายแพ็กเก็ตเป็น truncated สำหรับการวิเคราะห์ handshake TLS เต็มรูปแบบนี่น้อยไป ClientHello มักใช้ 300-600 ไบต์หรือมากกว่า ทางสายกลางคือ -s 600

อ่านดัมป์ตรงในคอนโซล

Wireshark ไม่ได้อยู่ใกล้มือเสมอ แต่ต้องดูเร็วๆ อ่านไฟล์โดยแสดงแฟล็ก TCP และหมายเลขลำดับแบบสัมบูรณ์:

tcpdump -nn -r proxy.pcap -S -tttt 'tcp[tcpflags] & (tcp-rst|tcp-fin) != 0'

คีย์ -tttt พิมพ์วันที่และเวลาเต็ม -S แสดง sequence numbers แบบสัมบูรณ์ ซึ่งช่วยเทียบกับล็อก แสดงเนื้อหาแพ็กเก็ตใน ASCII เพื่อดูบรรทัด CONNECT และคำตอบพร็อกซี:

tcpdump -nn -r proxy.pcap -A 'port 8080' | grep -E 'CONNECT|HTTP/1'

tshark เป็น Wireshark แบบคอนโซล

ถ้าติดตั้ง Wireshark บนเซิร์ฟเวอร์ tshark ให้เข้าถึง dissector เดียวกันจากคอนโซล รายการ CONNECT ทั้งหมดพร้อมรหัสตอบ:

tshark -r proxy.pcap -Y 'http.request.method == "CONNECT" or http.response.code' -T fields -e frame.time -e tcp.stream -e http.request.uri -e http.response.code

รายการสตรีมที่จบด้วย RST พร้อมระบุว่าใครส่ง:

tshark -r proxy.pcap -Y 'tcp.flags.reset == 1' -T fields -e frame.time_relative -e tcp.stream -e ip.src -e ip.dst

Wireshark: ฟิลเตอร์แสดงผล, Follow TCP Stream, การอ่าน TLS โดยไม่ถอดรหัส

เปิด pcap ใน Wireshark แล้วเห็นเป็นพันบรรทัด เริ่มจากตรงไหน? เริ่มจากฟิลเตอร์แสดงผล ต่างจาก BPF filter สำหรับเก็บ มันทำงานบนไฟล์ที่บันทึกแล้วและเข้าใจโครงสร้างโปรโตคอล

ฟิลเตอร์พื้นฐานสำหรับทำงานผ่านพร็อกซี

ทุกอย่างที่เกี่ยวข้องกับพร็อกซี:

ip.addr == 203.0.113.10 && tcp.port == 8080

คำขอ CONNECT ทั้งหมด:

http.request.method == "CONNECT"

CONNECT ไปยังโฮสต์เฉพาะ:

http.request.method == "CONNECT" && http.host contains "api.example.com"

คำตอบพร็อกซีที่ไม่ใช่ 200 (ข้อผิดพลาดการรับรอง 407, ไม่พร้อมใช้งาน 502, timeout 504):

http.response.code >= 400 && tcp.port == 8080

เฉพาะคำตอบ 407 สัญญาณของข้อมูลรับรองผิดหรือโควต้าหมด:

http.response.code == 407

ฟิลเตอร์ TLS ภายในอุโมงค์

Wireshark รู้จักว่าหลัง CONNECT ที่ตอบ 200 จะเริ่ม TLS และใส่ TLS dissector อัตโนมัติ ถ้าไม่รู้จัก (เกิดเมื่อแพ็กเก็ตถูกตัด) ให้คลิกขวาที่แพ็กเก็ต เลือก Decode As และระบุ TLS สำหรับพอร์ตนั้น

ClientHello ทั้งหมด:

tls.handshake.type == 1

ClientHello ที่มี SNI เฉพาะ:

tls.handshake.extensions_server_name contains "example.com"

ServerHello (ถ้าไม่มีหลัง ClientHello - handshake ไม่ได้เริ่มจากฝั่งเซิร์ฟเวอร์):

tls.handshake.type == 2

TLS alert:

tls.alert_message

สตรีมที่มี ClientHello แต่ไม่มี ServerHello หาได้ยากด้วยฟิลเตอร์เดียว สะดวกกว่าผ่าน Statistics > Conversations เรียงตามจำนวนแพ็กเก็ตและดูสตรีมที่มี 5-7 แพ็กเก็ต

ฟิลเตอร์ตามปัญหาของ TCP

แพ็กเก็ตทั้งหมดที่มี RST:

tcp.flags.reset == 1

RST ที่พร็อกซีส่งมาทางเรา:

tcp.flags.reset == 1 && ip.src == 203.0.113.10

RST ที่เราส่ง:

tcp.flags.reset == 1 && ip.dst == 203.0.113.10

Retransmission:

tcp.analysis.retransmission

Zero window และผลที่ตามมา:

tcp.analysis.zero_window || tcp.analysis.window_full

ทุกความผิดปกติที่ Wireshark สังเกตเอง (retransmission, duplicate ACK, เซกเมนต์ที่หายไป, zero window):

tcp.analysis.flags && !tcp.analysis.window_update

SYN ที่ไม่มีคำตอบ นั่นคือ SYN ซ้ำ:

tcp.flags.syn == 1 && tcp.flags.ack == 0 && tcp.analysis.retransmission

ช่วงหยุดยาวในสตรีม มากกว่า 5 วินาทีระหว่างแพ็กเก็ต:

tcp.time_delta > 5

สำหรับฟิลเตอร์นี้ต้องเปิด Edit > Preferences > Protocols > TCP > Calculate conversation timestamps

Follow TCP Stream

เจอแพ็กเก็ตน่าสงสัย เช่น RST คลิกขวา > Follow > TCP Stream Wireshark จะแสดงบทสนทนาทั้งหมดของการเชื่อมต่อนี้เป็นข้อความ: ทราฟฟิกของเราสีหนึ่ง ของพร็อกซีอีกสี สำหรับ HTTPS ผ่าน CONNECT คุณจะเห็นบรรทัด CONNECT คำตอบ 200 Connection established แล้วก็ไบต์ TLS ที่อ่านไม่ออก นี่เป็นเรื่องปกติ สิ่งที่ต้องดูคือปริมาณ: กี่ไบต์ออกจากเรา (ClientHello และคำขอ) กี่ไบต์เข้ามา (ServerHello และคำตอบ) และมันจบตรงไหน

ด้านล่างหน้าต่าง Follow มีตัวนับ: เท่านี้ไบต์ไคลเอนต์ เท่านี้ไบต์เซิร์ฟเวอร์ ถ้าจากเซิร์ฟเวอร์มา 0 ไบต์หลังคำตอบ 200 เซิร์ฟเวอร์ปลายทางไม่ตอบ ClientHello เลย ถ้ามาประมาณ 100-4000 ไบต์แล้วตัด handshake เริ่มแล้วแต่ไม่จบ ถ้ามาเป็นสิบกิโลไบต์แล้วตัด ปัญหาอยู่กลางการส่งข้อมูล

นิสัยที่มีประโยชน์: กรองตามหมายเลขสตรีม หลัง Follow ในบรรทัดฟิลเตอร์จะปรากฏ tcp.stream eq 42 โดยอัตโนมัติ ปิดหน้าต่าง Follow แล้วรายการหลักจะเหลือเฉพาะสตรีมนี้พร้อมแฟล็กและไทม์มิ่ง นี่คือมุมมองที่สะดวกที่สุดสำหรับวิเคราะห์การตัด

อ่าน TLS handshake โดยไม่ถอดรหัส

ขยาย ClientHello ในทรีแพ็กเก็ต สิ่งที่ต้องดู:

  • Version และ supported_versions - ไคลเอนต์เสนอ TLS 1.3 ไหม ถ้าเซิร์ฟเวอร์ต้องการ 1.3 แต่ไคลเอนต์เสนอแค่ 1.2 เซิร์ฟเวอร์จะปิดการเชื่อมต่อด้วย alert protocol_version หรือแค่ FIN
  • server_name - SNI ตรงกับโฮสต์ใน CONNECT ไหม ความไม่ตรงกันเกิดจากการตั้งค่าไคลเอนต์ผิดและนำไปสู่ข้อผิดพลาดใบรับรอง
  • Cipher Suites - รายการ cipher สั้นเกินไปหรือล้าสมัย - สาเหตุของ alert handshake_failure
  • ALPN - ไคลเอนต์เสนอ h2 ไหม ถ้าเซิร์ฟเวอร์เลือก h2 แต่ไคลเอนต์ภายในคาดหวัง HTTP/1.1 แอปอาจล้มด้วยข้อผิดพลาดที่ไม่เข้าใจ

ใน ServerHello ดูเวอร์ชันและ cipher ที่เลือก ใน TLS 1.2 หลังจากนั้น Certificate อยู่ในรูปแบบเปิด ตรวจสอบชื่อและวันหมดอายุได้ ใน TLS 1.3 หลัง ServerHello เกือบทุกอย่างถูกเข้ารหัส และสิ่งที่อ่านได้ถัดไปคือประเภทบันทึก Application Data หมายความว่า handshake สำเร็จและข้อมูลเริ่มส่งแล้ว Alert ยาว 2 ไบต์ทันทีหลัง ServerHello หรือแทนที่มันหมายความว่ามีอะไรผิด: ใน TLS 1.3 คุณไม่เห็นรหัส alert โดยไม่มีคีย์ แต่การมีอยู่ก็ให้ข้อมูลแล้ว

ใน Statistics > Conversations > TCP สะดวกในการประเมินภาพรวม: กี่สตรีม กี่ไบต์แต่ละทาง ระยะเวลา สตรีมที่ใช้เวลา 0.05 วินาทีและ 6 แพ็กเก็ต - น่าจะเป็นการเชื่อมต่อที่ถูกรีเซ็ตทันทีหลัง handshake

การวินิจฉัยจากดัมป์: RST, FIN, retransmission, zero window

ทีนี้ส่วนสำคัญ วิธีดูจากดัมป์โซน A ว่าขาดตรงไหน มาดูแต่ละสัญญาณและการตีความในบริบทพร็อกซี

เมทริกซ์ทิศทาง

คำถามแรกเสมอคือ: ใครส่งแพ็กเก็ตปิด? ในดัมป์คือฟิลด์ ip.src มีเพียงสองตัวเลือก - ที่อยู่ของเราหรือที่อยู่ของพร็อกซี

  • RST หรือ FIN จากเรา - แอปพลิเคชันของเรา ระบบปฏิบัติการ หรืออะไรในโฮสต์ของเราปิดการเชื่อมต่อ พร็อกซีและเซิร์ฟเวอร์ปลายทางไม่เกี่ยว สาเหตุทั่วไป: timeout ใน HTTP client, โปรเซสสิ้นสุดกะทันหัน, ตัวอธิบายไฟล์หมด, ไฟร์วอลล์หรือแอนติไวรัสในเครื่องทำงาน
  • RST หรือ FIN จากพร็อกซี - อุโมงค์ถูกปิดจากฝั่งพร็อกซี สาเหตุอาจอยู่ในพร็อกซีเอง (ลิมิต, นโยบาย, timeout ขณะไม่ได้ใช้งาน) หรือถูกแปลงมาจากเซิร์ฟเวอร์ปลายทาง หรือจากเครือข่ายระหว่างพร็อกซีกับเซิร์ฟเวอร์ แยกแยะจากบริบทและไทม์มิ่ง
  • ไม่มี RST และ FIN มีแค่ retransmission - แพ็กเก็ตหายในเส้นทางระหว่างเรากับพร็อกซี ไม่มีฝ่ายใดปิดการเชื่อมต่อ มันตายเพราะการสูญเสีย

RST หลัง CONNECT โดยไม่มีคำตอบ 200

ภาพ: TCP สร้างแล้ว เราส่ง CONNECT พร็อกซีตอบ RST โดยไม่มี HTTP response พฤติกรรมแบบนี้มักหมายความว่าพร็อกซีปฏิเสธในระดับนโยบายหรือแยกวิเคราะห์คำขอไม่ได้ สาเหตุ: พอร์ตปลายทางไม่อนุญาต, ลิมิตการเชื่อมต่อพร้อมกัน, รูปแบบคำขอไม่ถูกต้อง ตรงนี้ปัญหาอยู่ในโซน A หรือในพร็อกซีเอง เซิร์ฟเวอร์ปลายทางไม่เกี่ยว

คำตอบ 502, 503 หรือ 504 ต่อ CONNECT

ภาพ: CONNECT ออกไป ผ่านไป N วินาที มี HTTP response รหัส 5xx มา ดู N ถ้าคำตอบมาในเวลาประมาณ RTT ไปยังพร็อกซี พร็อกซีปฏิเสธทันที - อาจชื่อ DNS resolve ไม่ได้ที่ฝั่งมัน หรือที่อยู่ไม่พร้อมใช้งานทันที (ICMP unreachable) ถ้า N ประมาณ 10-30 วินาที พร็อกซีรอ timeout ในการเชื่อมต่อกับเซิร์ฟเวอร์ปลายทาง: เซิร์ฟเวอร์ไม่ตอบ SYN ในทั้งสองกรณีคือโซน C และดัมป์ของคุณพิสูจน์ว่าคุณทำถูกแล้ว และพร็อกซีแจ้งตามตรงว่าไม่สามารถได้

FIN หรือ RST ทันทีหลัง ClientHello

ภาพ: CONNECT - 200 - ClientHello ของเรา - ผ่านไปหนึ่ง RTT ไปยังพร็อกซีบวกเล็กน้อย มี FIN หรือ RST มาจากพร็อกซี ตรงนี้มีผู้ต้องสงสัยสองราย: เซิร์ฟเวอร์ปลายทางปฏิเสธการเชื่อมต่อในขั้น TLS (ไม่ชอบ SNI, เวอร์ชัน, ไม่มีไคลเอนต์ сертификат) หรือระบบป้องกันของเซิร์ฟเวอร์รีเซ็ตการเชื่อมต่อตามลักษณะของ ClientHello สัญญาณสำคัญคือเวลา ถ้าระหว่าง ClientHello ของเรากับการตัดผ่านไปนานกว่า RTT ไปยังพร็อกซีอย่างเห็นได้ชัด แสดงว่าพร็อกซีส่ง ClientHello ต่อไปแล้วและได้รับการตอบสนองกลับมา นี่ไม่ใช่พร็อกซีตัดสินใจปิด แต่เป็นการแปลงการตอบสนองของเซิร์ฟเวอร์ปลายทาง

เทียบกับ RTT ไปยังพร็อกซีที่วัดจาก TCP handshake: ระหว่าง SYN กับ SYN-ACK ถ้า RTT ไปยังพร็อกซี 40 ms และการตัดหลัง ClientHello มาหลัง 200 ms ผลต่าง 160 ms คือประมาณ RTT พร็อกซี - เซิร์ฟเวอร์ไปกลับ ภาพสอดคล้องกับการปฏิเสธของเซิร์ฟเวอร์อย่างสมบูรณ์

การตัดหลัง ServerHello หรือหลังข้อมูลบางส่วน

Handshake เริ่มแล้ว เซิร์ฟเวอร์ตอบแล้ว ข้อมูลเริ่มส่ง แล้วกลางทาง FIN หรือ RST ถ้าเป็น FIN และจากพร็อกซี และก่อนหน้านั้นบันทึก Application Data สุดท้ายมีขนาดสมเหตุสมผล อาจเป็นเซิร์ฟเวอร์ปิดการเชื่อมต่อหลังตอบ (Connection: close ภายใน TLS) และไคลเอนต์ตีความผิด ถ้า RST กลางกระแสข้อมูลโดยไม่มีความเร็วลดลงก่อน นี่อาจเป็นการปิดบังคับที่เซิร์ฟเวอร์ หรือพร็อกซีตัดอุโมงค์ตามลิมิตทราฟฟิกหรืออายุเซสชัน สำหรับพร็อกซีมือถือที่หมุน IP ตามเวลา อย่างที่สองเป็นไปได้มาก: การตัดเกิดตรงเวลาที่ IP เปลี่ยนพอดี ตรวจสอบว่าเวลาตัดตรงกับช่วงหมุนในการตั้งค่าพร็อกซีของคุณไหม

Retransmission และทิศทาง

Wireshark ทำเครื่องหมายแพ็กเก็ตเป็น retransmission เมื่อเห็นการส่งซ้ำของ sequence numbers เดียวกัน ดูว่าใคร retransmit:

  • เรา retransmit - แพ็กเก็ตของเราไม่ถูกยืนยันโดยพร็อกซี อาจหายในเส้นทางไปพร็อกซี หรือการยืนยันหายในเส้นทางกลับ ในทั้งสองกรณีปัญหาอยู่ในเครือข่ายโซน A
  • พร็อกซี retransmit - แพ็กเก็ตของมันไม่ถูกยืนยันโดยเรา เราไม่ได้รับ หรือ ACK ของเราไปไม่ถึง ก็โซน A แต่เอียงไปทางทิศทางขาเข้า
  • Retransmission ของ SYN - กรณีแยก: พร็อกซีเข้าถึงไม่ได้ในระดับ TCP การเชื่อมต่อไม่เกิดขึ้นเลย

สำคัญ: การสูญเสียในโซน C คุณจะไม่เห็นเป็น retransmission เลย พร็อกซีจัดการกับเซิร์ฟเวอร์ปลายทางเอง ร่องรอยเดียวของการสูญเสียในโซน C คือช่วงหยุด: พร็อกซีส่งข้อมูลให้เราไม่สม่ำเสมอ มีช่วงขาด แม้ไม่มี retransmission ในดัมป์ ฟิลเตอร์ tcp.time_delta > 1 สำหรับแพ็กเก็ตจากพร็อกซีจะแสดงช่วงหยุดเช่นนี้

Zero Window

TCP window ขนาดศูนย์หมายความว่าผู้รับอ่านข้อมูลจากบัฟเฟอร์ซ็อกเก็ตไม่ทัน ถ้าเราประกาศ zero window แอปพลิเคชันของเราอ่านคำตอบไม่เร็วพอ: เธรดไม่ว่าง ล็อก การประมวลผลช้า พร็อกซีในกรณีนี้จะรอ แล้วส่ง Zero Window Probe และถ้าแอปพลิเคชันยังไม่เริ่มอ่าน ผ่านไปหลายสิบวินาทีอาจปิดการเชื่อมต่อ ไคลเอนต์จะเห็นการตัดและโทษพร็อกซี ทั้งที่สาเหตุอยู่ในไคลเอนต์

ถ้าพร็อกซีประกาศ zero window หมายความว่ามันส่งข้อมูลของเราต่อไม่ทัน - เซิร์ฟเวอร์ปลายทางรับช้า นี่คือสัญญาณทางอ้อมของปัญหาในโซน C พบเมื่อดาวน์โหลดไฟล์ใหญ่ผ่านพร็อกซีไปยังเซิร์ฟเวอร์ช้า

Duplicate ACK และ SACK

Duplicate ACK จากพร็อกซี - สัญญาณว่ามันได้รับแพ็กเก็ตที่ไม่เรียงลำดับ บางอย่างของเราหายไป Duplicate ACK มากมายและ retransmission ตามมา - ภาพคลาสสิกของการสูญเสียในเครือข่ายมือถือหรือ Wi-Fi ฝั่งเรา Duplicate ACK จากเรา - แพ็กเก็ตจากพร็อกซีหายไป การมีตัวเลือก SACK ใน handshake ช่วยให้ TCP กู้คืนได้มีประสิทธิภาพขึ้น แต่ข้อเท็จจริงของการสูญเสียก็ยังเห็นได้

ฮิวริสติกของ TTL

เทคนิคขั้นสูง ดูฟิลด์ IP TTL ในแพ็กเก็ตปกติจากพร็อกซีและในแพ็กเก็ต RST ถ้า TTL ใน RST ต่างกันหลายหน่วย RST ถูกสร้างโดยไม่ใช่พร็อกซีเอง แต่เป็นโหนดกลางในเส้นทาง: ไฟร์วอลล์ โหลดบาลานเซอร์ หรือระบบกรองของผู้ให้บริการ นี่ไม่ใช่หลักฐานสัมบูรณ์ แต่เป็นสัญญาณแรงว่าควรตรวจสอบเส้นทางระหว่างคุณกับพร็อกซี ไม่ใช่โทษพร็อกซีหรือเซิร์ฟเวอร์ปลายทาง

ตารางสรุปการตีความ

  • SYN ซ้ำ ไม่มี SYN-ACK: พร็อกซีเข้าถึงไม่ได้หรือเครือข่ายไปยังมัน โซน A
  • SYN - RST: พอร์ตพร็อกซีปิดหรือถูกกรอง โซน A
  • CONNECT - 407: การรับรองผิดหรือลิมิตบัญชี พร็อกซี
  • CONNECT - 502/504 เร็ว: พร็อกซีเริ่มเชื่อมต่อเซิร์ฟเวอร์ไม่ได้ โซน C น่าจะ DNS หรือเส้นทาง
  • CONNECT - 504 ผ่านไป 20-30 วินาที: เซิร์ฟเวอร์ไม่ตอบ SYN ของพร็อกซี โซน C
  • 200 - ClientHello - RST/FIN ผ่านไปนานกว่า RTT: เซิร์ฟเวอร์ปฏิเสธ TLS โซน C
  • 200 - ClientHello - เงียบ - ตัดผ่าน timeout ของไคลเอนต์: เซิร์ฟเวอร์รับ TCP แต่ไม่ตอบ TLS โซน C หรือฟิลเตอร์บล็อกหลังพร็อกซี
  • ข้อมูลไหล - RST จากพร็อกซีในเวลาคงที่: ลิมิตหรือการหมุนของพร็อกซี พร็อกซี
  • Retransmission และ duplicate ACK โดยไม่มี RST: การสูญเสียในเครือข่ายระหว่างเรากับพร็อกซี โซน A
  • Zero Window จากเรา: แอปพลิเคชันของเราอ่านไม่ทัน ไคลเอนต์
  • RST จากเราหลังความเงียบนาน: timeout ของเรา ไคลเอนต์

การถอดรหัสทราฟฟิกของตัวเองผ่าน SSLKEYLOGFILE

บางครั้งแฟล็กไม่พอ และต้องเห็นว่าเซิร์ฟเวอร์คืนอะไรภายใน TLS: รหัสตอบ เฮดเดอร์ บอดี้ข้อผิดพลาด สำหรับสิ่งนี้ไม่ต้องใช้ mitmproxy และใบรับรองปลอม แค่ไคลเอนต์ของคุณเองบันทึกคีย์เซสชันลงไฟล์ และ Wireshark ใช้มันถอดรหัส วิธีนี้ใช้ได้เฉพาะกับไคลเอนต์ของคุณและการเชื่อมต่อของคุณ: คีย์มีเฉพาะฝ่ายที่เข้าร่วม handshake การถอดรหัสทราฟฟิกของคนอื่นหรือทราฟฟิกที่พร็อกซีทำกับเซิร์ฟเวอร์ในโซน C ด้วยวิธีนี้ทำไม่ได้ และนั่นก็ถูกต้อง

วิธีเปิดในไคลเอนต์ต่างๆ

เบราว์เซอร์บน Chromium และ Firefox อ่านตัวแปรสภาพแวดล้อม SSLKEYLOGFILE และเขียนคีย์ในรูปแบบ NSS Key Log:

export SSLKEYLOGFILE=/home/user/tls-keys.log
google-chrome --proxy-server=http://203.0.113.10:8080

curl ที่คอมไพล์กับ OpenSSL ก็อ่านตัวแปรนี้เช่นกัน:

export SSLKEYLOGFILE=/home/user/tls-keys.log
curl -v -x http://user:pass@203.0.113.10:8080 https://api.example.com/health

Node.js พร้อมคีย์บรรทัดคำสั่ง:

node --tls-keylog=/home/user/tls-keys.log app.js

Python ไม่อ่านตัวแปรอัตโนมัติ แต่ตั้งแต่เวอร์ชัน 3.8 ใน SSLContext มีแอตทริบิวต์ keylog_filename สำหรับ requests ผ่านอแดปเตอร์:

import os, ssl, requests
from requests.adapters import HTTPAdapter

class KeyLogAdapter(HTTPAdapter):
def init_poolmanager(self, *args, **kwargs):
ctx = ssl.create_default_context()
ctx.keylog_filename = os.environ.get("SSLKEYLOGFILE")
kwargs["ssl_context"] = ctx
return super().init_poolmanager(*args, **kwargs)

s = requests.Session()
s.mount("https://", KeyLogAdapter())
s.proxies = {"https": "http://user:pass@203.0.113.10:8080"}
r = s.get("https://api.example.com/health", timeout=30)
print(r.status_code)

Go ผ่านฟิลด์ KeyLogWriter ใน tls.Config:

f, _ := os.OpenFile(os.Getenv("SSLKEYLOGFILE"), os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0600)
tlsCfg := &tls.Config{KeyLogWriter: f}
tr := &http.Transport{
Proxy: http.ProxyURL(proxyURL),
TLSClientConfig: tlsCfg,
}

เชื่อมคีย์ใน Wireshark

สองวิธี วิธีแรก: Edit > Preferences > Protocols > TLS > (Pre)-Master-Secret log filename ระบุพาธไฟล์คีย์ Wireshark จะถอดรหัสทุกสตรีมที่จับคู่ Client Random ได้ วิธีที่สองน่าเชื่อถือกว่าสำหรับการส่งไฟล์ให้เพื่อนร่วมงาน: ฝังคีย์ลงใน pcapng โดยตรง:

editcap --inject-secrets tls,/home/user/tls-keys.log proxy.pcap proxy-with-keys.pcapng

หลังจากนั้นใน Wireshark ภายในอุโมงค์ CONNECT จะปรากฏคำขอและคำตอบ HTTP/1.1 หรือ HTTP/2 ที่ถอดรหัสแล้ว ฟิลเตอร์สำหรับ HTTP/2 ที่ถอดรหัสแล้ว:

http2.header.name == ":status"

สำหรับ HTTP/1.1 ที่ถอดรหัสแล้วในอุโมงค์ ฟิลเตอร์ http.response.code และ http.request.uri ตามปกติใช้ได้

สิ่งนี้ให้อะไรสำหรับการวินิจฉัยผ่านพร็อกซี

การถอดรหัสขจัดความไม่แน่นอนสุดท้าย คุณเห็นว่าเซิร์ฟเวอร์ตอบ 429 พร้อมเฮดเดอร์ Retry-After แล้วปิดการเชื่อมต่อ หรือคืน 200 แต่บอดี้ขาดที่ 40 เปอร์เซ็นต์ หรือคำขอออกไปแต่ไม่มีคำตอบเลย ด้วยภาพเช่นนี้ การติดต่อฝ่ายสนับสนุนพร็อกซีจึงเป็นเรื่องเป็นราว: ปัญหาอยู่ที่เซิร์ฟเวอร์ชัดเจน หรืออยู่ที่อุโมงค์ชัดเจน

กฎความปลอดภัย

  • ไฟล์คีย์ช่วยถอดรหัสเซสชันที่บันทึกไว้ทั้งหมด รวมถึงคุกกี้และโทเค็น เก็บมันเหมือนรหัสผ่านและลบหลังวิเคราะห์
  • อย่าส่งไฟล์คีย์พร้อมดัมป์ไปยังฝ่ายสนับสนุนพร็อกซีหรือใครก็ตาม ฝ่ายสนับสนุนไม่ต้องการคีย์เพื่อวิเคราะห์การตัด แค่แฟล็กและไทม์มิ่งก็พอ
  • ถ้าจำเป็นต้องแสดงเนื้อหาที่ถอดรหัสแล้ว ให้ทำกับบัญชีทดสอบบนบริการปลายทางและข้อมูลรับรองพร็อกซีทดสอบ
  • อย่าทิ้ง SSLKEYLOGFILE เปิดไว้ในโปรดักชัน ตัวแปรสภาพแวดล้อมที่หลุดเข้าไปในคอนฟิกเซอร์วิสโดยบังเอิญจะเขียนคีย์ลงดิสก์เป็นปีๆ

ภาพที่พบบ่อย: timeout การเชื่อมต่อ การตัดกลางคำตอบ การสูญเสียในเครือข่ายมือถือ

รวบรวมสัญญาณที่อธิบายมาเป็นสถานการณ์ที่จำได้ แต่ละอันอธิบายอย่างที่เห็นในรายการแพ็กเก็ต Wireshark หลังฟิลเตอร์ tcp.stream eq N

ภาพที่ 1: timeout การเชื่อมต่อไปยังพร็อกซี

0.000 client -> proxy [SYN]
1.002 client -> proxy [SYN] (TCP Retransmission)
3.006 client -> proxy [SYN] (TCP Retransmission)
7.010 client -> proxy [SYN] (TCP Retransmission)
15.02 client -> proxy [SYN] (TCP Retransmission)

ไม่มีแพ็กเก็ตจากพร็อกซีเลย ช่วงแบบเอ็กซ์โพเนนเชียล 1, 2, 4, 8 วินาที - เป็น retransmission backoff มาตรฐานของเคอร์เนล Linux การวินิจฉัย: พร็อกซีเข้าถึงไม่ได้จากจุดของคุณ ตรวจที่อยู่ พอร์ต ไฟร์วอลล์ การกำหนดเส้นทาง และดูว่าที่อยู่พร็อกซีเปลี่ยนไปไหม ถ้าเป็นพร็อกซีมือถือ Proxeon ที่มีพอร์ตเฉพาะ ตรวจสอบว่าพอร์ตจากหน้าสมาชิกตรงกับที่อยู่ในคอนฟิกไคลเอนต์ไหม

ภาพที่ 2: timeout การเชื่อมต่อของพร็อกซีไปยังเซิร์ฟเวอร์ปลายทาง

0.000 client -> proxy [SYN]
0.041 proxy -> client [SYN, ACK]
0.041 client -> proxy [ACK]
0.042 client -> proxy CONNECT api.example.com:443 HTTP/1.1
0.083 proxy -> client [ACK]
30.10 proxy -> client HTTP/1.1 504 Gateway Timeout
30.10 proxy -> client [FIN, ACK]

RTT ไปยังพร็อกซี 41 ms พร็อกซียืนยันการรับ CONNECT แล้วเงียบ 30 วินาทีและคืน 504 การวินิจฉัย: เซิร์ฟเวอร์ปลายทางไม่ตอบพร็อกซีในการเชื่อมต่อ สาเหตุที่เป็นไปได้ - เซิร์ฟเวอร์ล่ม พอร์ตปิดสำหรับพูลที่อยู่ของพร็อกซี ปัญหาเครือข่ายบนเส้นทางพร็อกซี - เซิร์ฟเวอร์ ไคลเอนต์และเครือข่ายของคุณไม่เกี่ยว

ภาพที่ 3: เซิร์ฟเวอร์ปฏิเสธ TLS

0.000 client -> proxy CONNECT api.example.com:443 HTTP/1.1
0.045 proxy -> client HTTP/1.1 200 Connection established
0.046 client -> proxy TLSv1.3 Client Hello (SNI=api.example.com)
0.210 proxy -> client [FIN, ACK]
0.210 client -> proxy [FIN, ACK]

ตัดหลัง ClientHello 164 ms โดย RTT ไปยังพร็อกซี 45 ms ผลต่างประมาณ 120 ms - เวลาที่พร็อกซีส่ง ClientHello ต่อให้เซิร์ฟเวอร์และได้รับการปิด การวินิจฉัย: เซิร์ฟเวอร์รับ TCP แต่ปิดการเชื่อมต่อในขั้น TLS ตรวจพารามิเตอร์ handshake: เวอร์ชัน cipher SNI ALPN ถ้าไม่มี ServerHello และไม่มี alert เซิร์ฟเวอร์ปิดเงียบ ซึ่งระบบป้องกันมักทำเมื่อ ClientHello ไม่ปกติ

ภาพที่ 4: ตัดกลางคำตอบ

0.000 client -> proxy CONNECT cdn.example.com:443 HTTP/1.1
0.044 proxy -> client HTTP/1.1 200 Connection established
0.045 client -> proxy Client Hello
0.190 proxy -> client Server Hello, Change Cipher Spec, Application Data
0.195 client -> proxy Application Data (request)
0.350 proxy -> client Application Data (1460 bytes)
... ещё 340 пакетов данных
4.812 proxy -> client Application Data (1460 bytes)
4.813 proxy -> client [RST, ACK]

Handshake ผ่าน ประมาณ 500 KB ข้อมูลมาแล้ว และ RST โดยไม่ชะลอ ไม่มี retransmission ไม่มี zero window ดูเวลาสัมบูรณ์ ถ้าตรงกับช่วงหมุน IP บนพร็อกซีมือถือหรือการหมดอายุลิมิตอายุเซสชัน สาเหตุอยู่ที่พร็อกซี และวิธีแก้คือปรับช่วงหมุนให้ตรงกับระยะเวลาคำขอของคุณ หรือใช้โหมดไม่หมุนระหว่างดาวน์โหลดยาว ถ้าไม่ตรง สาเหตุน่าจะเป็นการตัดที่เซิร์ฟเวอร์หรือ CDN ตรงนี้การถอดรหัสผ่าน SSLKEYLOGFILE ช่วยได้: ถ้าข้างในเห็นเฮดเดอร์ Content-Length และบอดี้มาไม่ครบ เซิร์ฟเวอร์ตัดการส่ง

ภาพที่ 5: การสูญเสียในเครือข่ายมือถือฝั่งไคลเอนต์

0.000 client -> proxy Application Data (1460 bytes)
0.001 client -> proxy Application Data (1460 bytes)
0.002 client -> proxy Application Data (1460 bytes)
0.180 proxy -> client [ACK] (Dup ACK 1)
0.181 proxy -> client [ACK] (Dup ACK 2)
0.182 proxy -> client [ACK] (Dup ACK 3)
0.183 client -> proxy Application Data (TCP Fast Retransmission)
0.420 client -> proxy Application Data (TCP Retransmission)
0.900 client -> proxy Application Data (TCP Retransmission)

Duplicate ACK จากพร็อกซี ตามด้วย retransmission จากเราที่ช่วงห่างเพิ่มขึ้น ไม่มี RST และ FIN การวินิจฉัย: การสูญเสียในเส้นทางไคลเอนต์ - พร็อกซี ถ้าไคลเอนต์เชื่อมต่อผ่านเครือข่ายเซลลูลาร์หรือ Wi-Fi เอง นี่เป็นเรื่องคาดหมายในจังหวะที่เปลี่ยนสถานีฐาน วิธีแก้คือเพิ่ม timeout ของไคลเอนต์ เปิด TCP keepalive ไม่ถือการเชื่อมต่อว่างนาน เพราะ NAT ของผู้ให้บริการลบระเบียนของการเชื่อมต่อ idle โดยปกติใน 30-300 วินาที และแพ็กเก็ตถัดไปจะสูญหาย ฟิลเตอร์สำหรับประเมินขนาดการสูญเสีย:

tcp.analysis.retransmission && ip.dst == 203.0.113.10

นับเปอร์เซ็นต์ของแพ็กเก็ตเหล่านี้จากจำนวนทั้งหมดผ่าน Statistics > Capture File Properties หนึ่ง-สองเปอร์เซ็นต์ - พอรับได้สำหรับเครือข่ายมือถือ มากกว่าห้า - หาปัญหาในสภาพวิทยุหรืออุปกรณ์

ภาพที่ 6: timeout ของเราเอง

0.000 client -> proxy CONNECT api.example.com:443 HTTP/1.1
0.044 proxy -> client HTTP/1.1 200 Connection established
0.045 client -> proxy Client Hello
0.200 proxy -> client Server Hello ... Application Data
0.205 client -> proxy Application Data (request)
0.250 proxy -> client [ACK]
10.21 client -> proxy [FIN, ACK]
10.25 proxy -> client [FIN, ACK]

คำขอออกไป พร็อกซียืนยัน คำตอบไม่มา และพอครบ 10 วินาที FIN ถูกส่งจากเรา นี่คือ read timeout ของเรา ข้อผิดพลาดในล็อกไคลเอนต์จะดูเหมือนการตัด แต่จากดัมป์เห็นได้: เราปิดการเชื่อมต่อเอง เพราะเซิร์ฟเวอร์คิดนานกว่าที่เรายอมรอ วิธีแก้คือเพิ่ม timeout หรือหาสาเหตุที่เซิร์ฟเวอร์ช้า แต่พร็อกซีตรงนี้แค่รออย่างซื่อสัตย์พร้อมกับเรา

ข้อผิดพลาดที่พบบ่อยในการเก็บและอ่านดัมป์

มาดูสิ่งที่แม้แต่วิศวกรมีประสบการณ์ก็สะดุดบ่อย

  • เก็บดัมป์โดยไม่มีฟิลเตอร์เก็บบนเครื่องที่มีโหลดสูง ในหนึ่งนาทีก็ได้เป็นกิกะไบต์ แต่ 200 แพ็กเก็ตที่ต้องการหาในนั้นยาก ควรกรองตามโฮสต์พร็อกซีเสมอ
  • กรองตามที่อยู่ของเซิร์ฟเวอร์ปลายทาง ผ่านพร็อกซีแพ็กเก็ตแบบนั้นไม่มีบนเครื่องคุณ ฟิลเตอร์จะคืนผลว่าง และคนก็คิดว่าทราฟฟิกไม่ไหล จริงๆ ไหล แต่ไปยังพร็อกซี
  • สับสนระหว่างฟิลเตอร์เก็บและฟิลเตอร์แสดงผล ไวยากรณ์ BPF ใน tcpdump (host, port, tcp[tcpflags]) กับไวยากรณ์ Wireshark (ip.addr, tcp.port, tcp.flags.reset) ต่างกัน ฟิลเตอร์ Wireshark ใน tcpdump จะเกิดข้อผิดพลาดไวยากรณ์ และกลับกัน
  • ดู RST จากพร็อกซีแล้วโทษพร็อกซีทันที RST จากที่อยู่พร็อกซีคือการปิดอุโมงค์ ไม่ใช่การยอมรับความผิด ดูไทม์มิ่งและดูว่าก่อน RST มีอะไร
  • ไม่สนใจ RTT ถ้าไม่วัดความหน่วงไปยังพร็อกซี ก็แยกการปฏิเสธทันทีของพร็อกซีจากการแปลงการปฏิเสธของเซิร์ฟเวอร์ไม่ได้
  • เก็บดัมป์ด้วย -s 64 แล้วพยายามอ่าน CONNECT เฮดเดอร์จะถูกตัด สำหรับวินิจฉัยพร็อกซีต้องเก็บเต็มหรืออย่างน้อย -s 600
  • ไม่ซิงค์เวลา ถ้านาฬิกาบนเครื่องที่เก็บดัมป์ช้าไปหนึ่งนาที จะเทียบดัมป์กับล็อกของฝ่ายสนับสนุนไม่ได้ เปิด NTP และระบุ UTC ในการติดต่อ
  • ส่งดัมป์ที่มีข้อมูลรับรองพร็อกซี เฮดเดอร์ Proxy-Authorization ในคำขอ HTTP แบบเปิดไปยังพร็อกซีมีชื่อผู้ใช้และรหัสผ่าน เก็บดัมป์ด้วยข้อมูลทดสอบ หรือเปลี่ยนรหัสผ่านหลังส่ง
  • ส่งไฟล์คีย์พร้อมดัมป์ นี่เปิดเผยเนื้อหาทั้งหมดของเซสชัน ฝ่ายสนับสนุนไม่ต้องการคีย์เพื่อวิเคราะห์การตัด
  • ถือดัมป์ยาวตัวเดียวโดยไม่หมุน ดิสก์จะเต็มในจังหวะที่แย่ที่สุด และ tcpdump จะล้มพร้อมแพ็กเก็ตที่ต้องการ
  • ไม่ตรวจการรั่วของ QUIC UDP บน 443 ตรงๆ - สัญญาณว่าทราฟฟิกส่วนหนึ่งไปนอกพร็อกซี และการวินิจฉัยผ่าน TCP tunnel จะไม่แสดงอะไร
  • ลืมเรื่อง segmentation offload แพ็กเก็ตขนาด 60 KB ในดัมป์บนเครื่องเสมือนไม่ได้หมายความว่า MTU ผิด นี่คือเคอร์เนลส่งเซกเมนต์ที่ยังไม่ถูกตัดให้ tcpdump

เครื่องมือและแหล่งข้อมูล

ชุดขั้นต่ำสำหรับทำงานตามวิธีการที่อธิบาย

การเก็บ

  • tcpdump - มาตรฐานบน Linux และ macOS ติดตั้งเกือบทุกที่ ต้องการ root หรือ capability CAP_NET_RAW
  • dumpcap - ตัวเก็บแบบคอนโซลจากชุด Wireshark รองรับคีย์หมุนเดียวกัน (-b filesize, -b files) และรูปแบบ pcapng
  • Wireshark กับ Npcap - สำหรับ Windows เก็บจาก GUI ได้เลยด้วยฟิลเตอร์เก็บในไวยากรณ์ BPF เดียวกัน
  • tshark - การวิเคราะห์แบบคอนโซลพร้อม dissector ของ Wireshark สะดวกบนเซิร์ฟเวอร์ที่ไม่มีกราฟิกและสำหรับระบบอัตโนมัติ

การวิเคราะห์

  • Wireshark - เครื่องมือหลัก Follow TCP Stream, Expert Info, Statistics > Conversations, IO Graph สำหรับแสดงช่วงหยุดและการกระเพื่อมของ retransmission
  • editcap - ตัดและกรอง pcap ฝังคีย์ TLS ใน pcapng
  • mergecap - รวมไฟล์ที่หมุนไว้เป็นหนึ่งเดียวเพื่อวิเคราะห์
  • capinfos - สรุปไฟล์เร็วๆ: ระยะเวลา จำนวนแพ็กเก็ต ขนาด

ไคลเอนต์ที่รองรับการดีบัก

  • curl กับคีย์ -v และ --trace-time ทำซ้ำภาพดัมป์ในระดับแอปพลิเคชันและรองรับ SSLKEYLOGFILE
  • openssl s_client -proxy host:port ให้ทำ CONNECT และ TLS handshake ผ่านพร็อกซีด้วยมือและเห็นคำตอบของเซิร์ฟเวอร์โดยไม่ต้องมี HTTP client

วันบรรทัดที่มีประโยชน์

รวมไฟล์ที่หมุนไว้:

mergecap -w all.pcapng proxy-*.pcap

ตัดเฉพาะหนึ่งสตรีมจากดัมป์เพื่อส่งให้ฝ่ายสนับสนุน:

tshark -r all.pcapng -Y 'tcp.stream eq 42' -w stream42.pcapng

สถิติเร็วของสตรีมที่มี RST:

tshark -r all.pcapng -q -z conv,tcp | head -40

ตรวจ CONNECT ด้วยมือผ่าน openssl:

openssl s_client -proxy 203.0.113.10:8080 -connect api.example.com:443 -servername api.example.com -brief

เคสและผลลัพธ์

เคสที่ 1: การตัดสองเปอร์เซ็นต์ กลายเป็นว่าไคลเอนต์ผิด

บริการเก็บราคาทำงานผ่านพูลพร็อกซีมือถือ ประมาณ 40,000 คำขอต่อวัน ประมาณ 2.3 เปอร์เซ็นต์ล้มด้วย RemoteDisconnected ทีมแน่ใจว่าพร็อกซีผิด เก็บดัมป์แบบหมุน 100 MB ต่อวัน กรอง tcp.flags.reset == 1 และดู ip.src ใน 91 เปอร์เซ็นต์ของกรณี RST ถูกส่งโดยไคลเอนต์เอง การวิเคราะห์สตรีมแสดง: ก่อน RST มีความเงียบพอดี 5 วินาทีหลังส่งคำขอ แล้ว RST จากไคลเอนต์ ในโค้ดมี read timeout 5 วินาที แต่เซิร์ฟเวอร์ปลายทางในชั่วโมงเร่งด่วนตอบใน 6-8 วินาที การเพิ่ม timeout เป็น 15 วินาทีลดข้อผิดพลาดเหลือ 0.3 เปอร์เซ็นต์ ที่เหลือ 0.3 เปอร์เซ็นต์เป็น FIN จริงจากพร็อกซีใน 160-200 ms หลัง ClientHello: เซิร์ฟเวอร์ปฏิเสธการเชื่อมต่อจากส่วนหนึ่งของพูลที่อยู่เป็นระยะๆ ข้อมูลนี้ส่งให้ฝ่ายสนับสนุน ทางนั้นยืนยันภาพจากล็อกโซน C ของพวกเขา

เคสที่ 2: การตัดของการดาวน์โหลดใหญ่ทุก 10 นาทีพอดี

ไคลเอนต์ดาวน์โหลดไฟล์ขนาด 300-800 MB ผ่านพร็อกซีมือถือและบ่นเรื่องการตัดกลางทาง ดัมป์แสดง RST จากพร็อกซีในเวลาที่ห่างกันพอดี 600 วินาที แม่นยำถึงวินาที ไม่ว่าการดาวน์โหลดจะเริ่มเมื่อไหร่ สาเหตุคือการหมุน IP ตามตารางทุก 10 นาที: เมื่อที่อยู่เปลี่ยน พร็อกซีปิดอุโมงค์ที่ใช้งานอยู่ วิธีแก้คือเปลี่ยนพอร์ตเป็นการหมุนตามคำขอและเริ่มเปลี่ยนที่อยู่ระหว่างการดาวน์โหลด การตัดหายทั้งหมด ใช้เวลาสองชั่วโมงในการวินิจฉัยแทนการโต้ตอบเป็นสัปดาห์

เคสที่ 3: เซิร์ฟเวอร์ล่ม แต่ดูเหมือนพร็อกซีผิด

เช้าวันหนึ่งคำขอทั้งหมดไปยัง API หนึ่งเริ่มคืนข้อผิดพลาดการเชื่อมต่อ ล็อกไคลเอนต์: Connection aborted ปฏิกิริยาแรก - พร็อกซีล่ม ดัมป์หนึ่งนาทีแสดง: TCP กับพร็อกซีสร้างใน 38 ms CONNECT ออกไป แล้วผ่านไป 30 วินาทีมี 504 และ FIN มา ในขณะเดียวกันคำขอไปยังโฮสต์อื่นผ่านพร็อกซีเดียวกันผ่านใน 300 ms การวินิจฉัย: เซิร์ฟเวอร์ปลายทางไม่รับการเชื่อมต่อจากพร็อกซี ผ่านไป 40 นาที หน้าสถานะของบริการปลายทางยืนยันเหตุการณ์ที่ฝั่ง mereka ดัมป์ประหยัดเวลาเช้าและหลีกเลี่ยงตั๋วเท็จไปยังฝ่ายสนับสนุนพร็อกซี

เคสที่ 4: การสูญเสียใน Wi-Fi ดูเหมือนปัญหาพร็อกซี

นักพัฒนาทดสอบการผสานรวมจากแล็ปท็อปผ่านพร็อกซีมือถือ Proxeon และได้ timeout เป็นครั้งคราว ดัมป์แสดง retransmission จากไคลเอนต์ที่ความถี่ 6 เปอร์เซ็นต์และ duplicate ACK จากพร็อกซี โดยไม่มี RST จากพร็อกซีเลย การเชื่อมต่อแล็ปท็อปด้วยสายลด retransmission เหลือศูนย์ timeout หายไป ปัญหาอยู่ที่จุดเข้าถึงที่โหลดหนักในออฟฟิศ

เช็คลิสต์การเก็บดัมป์สำหรับติดต่อฝ่ายสนับสนุน

ถ้าคุณจะส่งดัมป์ไปยังฝ่ายสนับสนุนของบริการพร็อกซี นี่คือลำดับที่จะประหยัดเวลาทั้งสองฝ่าย

การเตรียมการ

  1. ซิงค์นาฬิกาบนเครื่องผ่าน NTP ตรวจด้วยคำสั่ง timedatectl หรือ date -u
  2. ถ้าเป็นไปได้ ใช้ข้อมูลรับรองพร็อกซีทดสอบแยกต่างหากระหว่างวินิจฉัย หรือวางแผนเปลี่ยนรหัสผ่านหลังส่งดัมป์
  3. บันทึกเวอร์ชันไคลเอนต์ ไลบรารี HTTP ระบบปฏิบัติการ วิธีเชื่อมต่ออินเทอร์เน็ต (สาย, Wi-Fi, เครือข่ายเซลลูลาร์)
  4. จดที่อยู่และพอร์ตพร็อกซี โฮสต์ปลายทาง พฤติกรรมที่คาดหวังและที่เกิดขึ้นจริง

การเก็บ

  1. เริ่ม tcpdump ด้วยฟิลเตอร์ตามโฮสต์และพอร์ตพร็อกซี พร้อมการหมุน ขนาดแพ็กเก็ตเต็ม:
sudo tcpdump -i eth0 -nn -s 0 -U -w proxy-%Y%m%d-%H%M%S.pcap -G 300 -W 48 'host 203.0.113.10 and port 8080'
  1. ทำให้ปัญหาเกิดขึ้นซ้ำ ถ้าเกิดน้อย ให้ทิ้งการเก็บไว้ตามเวลาที่ต้องการ 48 ไฟล์ ไฟล์ละ 5 นาทีครอบคลุม 4 ชั่วโมง
  2. พร้อมกับการเก็บ ทำคำขอตรวจสอบผ่าน curl ด้วย -v และ --trace-time เก็บเอาต์พุตไว้ จะให้การอ้างอิงเวลาที่แม่นยำกับแพ็กเก็ต
  3. บันทึกเวลาที่แน่นอน (UTC) ของทุกครั้งที่เกิดข้อผิดพลาดจากล็อกไคลเอนต์
  4. หยุด tcpdump ด้วย Ctrl+C ตรวจว่าไฟล์ไม่ว่าง: capinfos proxy-*.pcap

การประมวลผล

  1. รวมไฟล์: mergecap -w all.pcapng proxy-*.pcap
  2. หาสตรีมที่มีปัญหา ด้วยฟิลเตอร์ tcp.flags.reset == 1 หรือตามเวลาจากล็อก
  3. ตัดเฉพาะสตรีมที่ต้องการ: tshark -r all.pcapng -Y 'tcp.stream in {12 47 93}' -w report.pcapng ฝ่ายสนับสนุนไม่ต้องการชั่วโมงของทราฟฟิกปกติ
  4. ตรวจว่าดัมป์ที่ตัดมาไม่มีอย่างอื่นที่ไม่ต้องการ: การเชื่อมต่อของคนอื่น ทราฟฟิกของบริการอื่น
  5. อย่าแนบไฟล์คีย์ TLS

ข้อความติดต่อ

  • เวลาเกิดเหตุใน UTC แม่นยำถึงวินาที
  • ที่อยู่และพอร์ตพร็อกซี โฮสต์ปลายทาง
  • การตีความสั้นๆ: สิ่งที่คุณเห็นในดัมป์ (เช่น 200 บน CONNECT แล้ว FIN จากพร็อกซีใน 170 ms หลัง ClientHello, RTT ไปยังพร็อกซี 45 ms)
  • หมายเลขเฟรมหรือสตรีมในไฟล์ที่แนบ
  • เอาต์พุต curl -v พร้อมestamp เวลา
  • สิ่งที่ตรวจและตัดออกแล้ว: โฮสต์อื่นผ่านพร็อกซีเดียวกัน โฮสต์เดียวกันผ่านการเชื่อมต่ออื่น

การติดต่อแบบนี้ฝ่ายสนับสนุนจัดการได้ในครั้งเดียว แค่เทียบestamp เวลาของคุณกับล็อกโซน C และยืนยันหรือปฏิเสธสมมติฐาน

คำถามที่พบบ่อย

ทำไมในดัมป์ไม่มี IP ของเซิร์ฟเวอร์ปลายทาง ทั้งที่ฉันต่อกับมัน?

เพราะคุณไม่ได้ต่อกับมันโดยตรง แต่ผ่านพร็อกซี ไคลเอนต์ของคุณสร้างการเชื่อมต่อ TCP กับที่อยู่พร็อกซีและขอให้มันเชื่อมต่อไปยังโฮสต์ปลายทาง การเชื่อมต่อพร็อกซี - เซิร์ฟเวอร์มีอยู่เฉพาะที่ฝั่งพร็อกซี ในดัมป์ชื่อของโฮสต์ปลายทางเห็นได้ในบรรทัด CONNECT และใน SNI ภายใน ClientHello แต่แพ็กเก็ตไปยัง IP ของมันจากเครื่องคุณไม่มีและเป็นไปไม่ได้

ดูจากดัมป์รู้ได้ไหมว่าพร็อกซีใช้ IP ภายนอกอะไรติดต่อเซิร์ฟเวอร์?

ไม่ได้ ข้อมูลนี้เป็นของโซน C วิธีเดียวคือขอผ่านพร็อกซีบริการที่คืนที่อยู่ไคลเอนต์ หรือดูในหน้าสมาชิกของผู้ให้บริการว่าที่อยู่ใดทำงานในขณะนั้น

พร็อกซีส่ง RST แปลว่าปัญหาอยู่ที่พร็อกซีหรือเปล่า?

ไม่จำเป็น RST จากที่อยู่พร็อกซีหมายความว่าอุโมงค์ถูกปิดจากฝั่งพร็อกซี และสาเหตุอาจอยู่ที่เซิร์ฟเวอร์ปลายทางหรือเครือข่ายหลังพร็อกซี ดูไทม์มิ่ง: ถ้า RST มาหลังจากผ่านไปนานกว่า RTT ไปยังพร็อกซีอย่างเห็นได้ชัดหลังแพ็กเก็ตล่าสุดของคุณ น่าจะเป็นพร็อกซีแปลงการตอบสนองของเซิร์ฟเวอร์ ถ้า RST มาในเวลาคงที่หรือหลังช่วงที่แม่นยำ น่าจะเป็นนโยบายของพร็อกซีเอง เช่น การหมุนหรือลิมิต

วัด RTT ไปยังพร็อกซีจากดัมป์ได้อย่างไร?

ผลต่างเวลาระหว่าง SYN จากคุณกับ SYN-ACK จากพร็อกซีตอนเริ่มการเชื่อมต่อใดๆ ใน Wireshark เปิด Statistics > TCP Stream Graphs > Round Trip Time สำหรับสตรีม หรือใช้ฟิลด์ tcp.analysis.ack_rtt เป็นคอลัมน์

Wireshark ไม่แสดง TLS ภายในอุโมงค์ แสดงแค่ TCP data ทำไงดี?

คลิกขวาที่แพ็กเก็ตหลังคำตอบ 200 Connection established เลือก Decode As ในคอลัมน์ Current เลือก TLS สำหรับ TCP port ของพร็อกซี ตรวจด้วยว่าแพ็กเก็ตไม่ถูกตัด (-s 0 ตอนเก็บ) และ HTTP dissector ตั้งค่าไว้สำหรับพอร์ตพร็อกซีถ้าไม่ใช่มาตรฐาน: Edit > Preferences > Protocols > HTTP > TCP ports

วิธีนี้ต่างจาก mitmproxy อย่างไร?

mitmproxy ทำงานในระดับแอปพลิเคชัน: มันยุติ TLS ด้วยใบรับรองปลอม อ่านและแก้ไขคำขอ HTTP ได้ สำหรับสิ่งนี้ไคลเอนต์ต้องเชื่อถือ root certificate ของมัน tcpdump และ Wireshark ทำงานในระดับแพ็กเก็ตและไม่เปลี่ยนอะไร: คุณเห็นแพ็กเก็ตจริง แฟล็กและไทม์มิ่งจริง รวมถึงข้อผิดพลาด TCP ที่ mitmproxy จะซ่อนเพราะจัดการเอง สำหรับวินิจฉัยการตัด ระดับแพ็กเก็ตซื่อสัตย์กว่า สำหรับดูเนื้อหาคำขอ mitmproxy ง่ายกว่า แต่ถ้าต้องการ เนื้อหาทราฟฟิกของตัวเองก็เห็นใน Wireshark ผ่าน SSLKEYLOGFILE โดยไม่ต้องเปลี่ยนใบรับรอง

ถอดรหัสใน Wireshark ทราฟฟิกที่พร็อกซีทำกับเซิร์ฟเวอร์ได้ไหม?

ไม่ได้ คุณไม่มีทั้งแพ็กเก็ตของการเชื่อมต่อนั้นและคีย์ของมัน SSLKEYLOGFILE ให้คีย์เฉพาะเซสชันของไคลเอนต์คุณ และภายในอุโมงค์ CONNECT นั่นคือเซสชันของคุณกับเซิร์ฟเวอร์ จึงถอดรหัสได้ แต่ TLS แยกต่างหากระหว่างพร็อกซีกับเซิร์ฟเวอร์ในกรณี CONNECT ไม่มี: พร็อกซีแค่ส่งต่อไบต์ของคุณ

รู้ได้อย่างไรว่าไคลเอนต์รั่วออกนอกพร็อกซี?

เก็บดัมป์โดยไม่กรองตามพร็อกซี แต่กรองตามอินเทอร์เฟซทั้งหมด แล้วดูการเชื่อมต่อขาออกไปยังพอร์ต 80 และ 443 ไปยังที่อยู่ที่ไม่ใช่พร็อกซี รวมถึง UDP 443 (QUIC) และคำขอ DNS ไปยัง resolver ภายนอกที่มีชื่อโฮสต์ปลายทาง ฟิลเตอร์ Wireshark: (tcp.port == 443 || udp.port == 443) && !(ip.addr == 203.0.113.10) ถ้ามีตรงกัน - การตั้งค่ารั่ว

ดัมป์สำหรับฝ่ายสนับสนุนควรใหญ่แค่ไหน?

ยิ่งเล็กยิ่งดี สตรีมที่มีปัญหาหนึ่งสตรีมตัดมาใช้พื้นที่ 5 ถึง 500 KB ไฟล์ใหญ่กว่า 50 MB ฝ่ายสนับสนุนจะวิเคราะห์นานกว่า และครึ่งหนึ่งของปริมาณจะเป็นทราฟฟิกปกติ ตัดสตรีมที่ต้องการผ่าน tshark หรือ File > Export Specified Packets ใน Wireshark

ทำไงดีถ้า tcpdump บนเครื่องเสมือนแสดงแพ็กเก็ตใหญ่กว่า MTU?

นี่คือ generic segmentation offload: เคอร์เนลส่งเซกเมนต์ใหญ่ให้ tcpdump ก่อนที่การ์ดเครือข่ายจะตัดมัน ไม่กระทบการวิเคราะห์แฟล็ก ไทม์มิ่ง และการตัด ถ้ารำคาญ ปิด offload ระหว่างเก็บด้วยคำสั่ง ethtool -K eth0 gso off tso off gro off แต่จำไว้ว่าจะลดประสิทธิภาพเครือข่าย

บทสรุป

ดัมป์ทราฟฟิกเมื่อทำงานผ่านพร็อกซีไม่ใช่เรื่องการแอบดูเนื้อหาและไม่ใช่เวทมนตร์ มันคือการบันทึกอย่างซื่อสัตย์ว่าจริงๆ เกิดอะไรบนสาย: แม่นยำถึงมิลลิวินาทีและถึงทุกแฟล็ก ล็อกไคลเอนต์บอกว่าการเชื่อมต่อถูกตัด ดัมป์บอกว่าใครทำ เมื่อไหร่ และก่อนหน้านั้นมีอะไร

สรุปวิธีการ บนเครื่องไคลเอนต์คุณเห็นเฉพาะการเชื่อมต่อถึงพร็อกซี: TCP handshake, CONNECT หรือบทสนทนา SOCKS และบันทึก TLS ที่เข้ารหัสภายในอุโมงค์ เซิร์ฟเวอร์ปลายทางไม่เห็นโดยตรง แต่พฤติกรรมของมันถูกแปลงผ่านพร็อกซีผ่านรหัสตอบบน CONNECT ไทม์มิ่ง และวิธีปิดอุโมงค์ RST หรือ FIN จากที่อยู่ของคุณ - ปัญหาของคุณหรือ timeout ของคุณ Retransmission และ duplicate ACK โดยไม่มีการปิด - การสูญเสียในเส้นทางถึงพร็อกซี การปิดจากพร็อกซีผ่านเวลาที่มากกว่า RTT ไปยังมันหลายเท่า - การตอบสนองของเซิร์ฟเวอร์ที่ถูกแปลง การปิดในเวลาคงที่ - นโยบายพร็อกซี Zero Window จากคุณ - แอปพลิเคชันของคุณอ่านไม่ทัน

ขั้นตอนปฏิบัติสำหรับวันนี้: คั่นหน้าใส่บุ๊กมาร์กคำสั่ง tcpdump พร้อมการหมุนและฟิลเตอร์ Wireshark จากบทความนี้ ตรวจว่านาฬิกาบนเครื่องที่ไคลเอนต์ทำงานซิงค์กันไหม ตรวจว่าไคลเอนต์ของคุณไม่รั่ว QUIC ออกนอกพร็อกซี สร้างข้อมูลรับรองทดสอบสำหรับวินิจฉัยเพื่อไม่ให้เปิดเผยของจริงในดัมป์ และครั้งต่อไปที่ล็อกบอก connection reset by peer อย่าเดา เก็บดัมป์ เปิดสตรีม ดูทิศทางและเวลา ผ่านไปห้านาทีคุณจะรู้ว่าต้องเขียนถึงใคร: ตัวเอง ฝ่ายสนับสนุนพร็อกซี หรือเจ้าของเซิร์ฟเวอร์ปลายทาง