คอนเทนเนอร์อุโมงค์ของคุณทำงาน docker compose exec vpn curl -s https://ifconfig.me ตอบกลับด้วยที่อยู่ของผู้ให้บริการ แต่เบราว์เซอร์บนเดสก์ท็อปของคุณยังออกผ่าน ISP ช่องว่างนี้เป็นสิ่งที่คาดไว้ คอนเทนเนอร์ที่รันไคลเอนต์ VPN ถือเนมสเปซเครือข่าย เส้นทาง และอินเทอร์เฟซอุโมงค์ ไม่มีอะไรที่พูด HTTP หรือ SOCKS การเผยแพร่พอร์ตบนนั้นไม่สามารถสร้างตัวรับฟังที่ไม่เคยมีได้

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

ปรับการจัดวางให้ตรงกับที่ที่ไคลเอนต์รัน

ถามหนึ่งคำถามก่อนแก้ Compose: เริ่มไคลเอนต์ภายในเนมสเปซเครือข่ายของคอนเทนเนอร์ VPN ได้หรือไม่ อะไรก็ตามที่รันเป็นคอนเทนเนอร์ Linux อยู่แล้วทำได้ เบราว์เซอร์ IDE หรือ CLI บนโต๊ะของคุณทำไม่ได้ เพราะการตั้งค่าพร็อกซีคาดหวังบริการโปรโตคอล ไม่ใช่เนมสเปซ

ClientArrangementWhat must already exist
Another Compose serviceShares the vpn namespace via network_mode: service:vpnA working tunnel and correct start order
Host browser, IDE or CLIProxy listener inside the namespace, published to host loopbackA configured SOCKS or HTTP proxy and its access rules
Router, VM or phoneThe container as a layer-3 gatewayForwarding, NAT and a return route

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

แบ่งปันเนมสเปซของอุโมงค์กับคอนเทนเนอร์ข้างเคียง

นี่คือตัวเลือกที่ถูกที่สุดเพราะไม่มีโปรโตคอลพร็อกซีเข้ามาเกี่ยวข้อง แอปเข้าร่วมเนมสเปซของบริการ vpn และสืบทอดอินเทอร์เฟซ เส้นทาง และพื้นที่พอร์ตของบริการนั้น:

services:
  vpn:
    image: your-vpn-client
    cap_add:
      - NET_ADMIN
    healthcheck:
      test: ["CMD-SHELL", "ip link show tun0 >/dev/null"]
      interval: 10s
      retries: 5
  app:
    image: your-app
    network_mode: service:vpn
    depends_on:
      vpn:
        condition: service_healthy

มีข้อจำกัดสามข้อที่ตามมาจากการที่เนมสเปซเป็นของบริการอื่น แอปไม่ได้คีย์ networks ของตัวเองเพราะไม่มีเนมสเปซให้ยึด การเผยแพร่พอร์ตเป็นของ vpn เพราะเนมสเปซอาศัยอยู่ที่นั่น และการสร้าง vpn ใหม่จะทำลายเนมสเปซที่แอปเข้าร่วม ดังนั้น docker compose up -d --force-recreate vpn จึงปล่อยให้คุณสร้าง app ใหม่ด้วย การรีสตาร์ตธรรมดาไม่เพียงพอเสมอไป เอกสารอ้างอิงบริการ Compose ครอบคลุมการจัดวางนี้และข้อจำกัดการเผยแพร่พอร์ต

อย่าสันนิษฐานว่าตัวแก้ชื่อมาด้วย /etc/resolv.conf เป็นไฟล์ต่อคอนเทนเนอร์ ไม่ใช่ส่วนหนึ่งของเนมสเปซเครือข่าย ดังนั้นแอปยังถามตัวแก้ชื่อของโฮสต์ได้ขณะที่แพ็กเก็ตออกผ่านอุโมงค์ อ่านจากภายในคอนเทนเนอร์และตั้ง dns: บนแอปเมื่อชื่อส่วนตัวต้องแก้ผ่านอุโมงค์

docker compose exec app ip route
docker compose exec app cat /etc/resolv.conf
docker compose exec app curl -s https://ifconfig.me

ถ้ารายการเส้นทางมีอินเทอร์เฟซอุโมงค์และที่อยู่เป็นของผู้ให้บริการ แอปอยู่ภายในอุโมงค์ ถ้าอินเทอร์เฟซหายไป app เริ่มก่อนที่ vpn จะมีอุโมงค์ ดังนั้นสร้าง app ใหม่เมื่อ vpn รายงานว่าสุขภาพดี

วางตัวรับฟังพร็อกซีจริงในเนมสเปซสำหรับไคลเอนต์โฮสต์

เบราว์เซอร์เดสก์ท็อปเข้าร่วมเนมสเปซไม่ได้ ดังนั้นอุโมงค์ต้องถูกเปิดเป็นบริการ รันพร็อกซี SOCKS หรือ HTTP จริงภายในเนมสเปซ vpn และเผยแพร่เฉพาะพอร์ตของมัน:

services:
  vpn:
    ports:
      - "127.0.0.1:1080:1080"
  proxy:
    image: your-proxy
    network_mode: service:vpn
    command: ["--socks", "0.0.0.0:1080"]

ที่อยู่ฝั่งโฮสต์คือครึ่งสำคัญของการจับคู่นั้น 127.0.0.1:1080 ทางซ้ายเก็บตัวรับฟังไว้นอก LAN ของคุณ "1080:1080" เปลือยเผยแพร่พร็อกซีเปิดให้ทุกอุปกรณ์ที่เข้าถึงเครื่องของคุณได้

ฝั่งคอนเทนเนอร์มีกับดักของตัวเอง พอร์ตที่เผยแพร่ถูกส่งไปยังที่อยู่ของคอนเทนเนอร์บนบริดจ์ ไม่ใช่ loopback ของมัน ดังนั้นพร็อกซีที่เริ่มด้วย --socks 127.0.0.1:1080 ภายในเนมสเปซจะรับการเชื่อมต่อจากข้างเคียงและปฏิเสธทุกอย่างที่มาทางพอร์ตที่เผยแพร่ ผูกมันกับ 0.0.0.0 และพึ่งการผูก loopback ฝั่งโฮสต์เพื่อแยกส่วน กฎเดียวกันใช้กับที่อยู่รับฟังในไฟล์กำหนดค่าพร็อกซี

จากนั้นตัดสินใจว่าจะแก้ชื่อที่ไหน ใน curl --socks5 แก้ชื่อโฮสต์ในเครื่อง และ --socks5-hostname ส่งชื่อไปยังพร็อกซี เบราว์เซอร์แสดงสวิตช์เดียวกันภายใต้ป้ายอื่น การแก้ในเครื่องรั่วการสอบถามแม้หน้าเว็บโหลดผ่านอุโมงค์

ถือว่าคอนเทนเนอร์เป็นเกตเวย์เฉพาะเมื่อมีเส้นทางกลับ

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

sysctl -w net.ipv4.ip_forward=1
iptables -t nat -A POSTROUTING -o tun0 -j MASQUERADE

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

ยังมีรัศมีผลกระทบกว้างที่สุด โฮสต์ที่ทำหน้าที่เกตเวย์ให้ทั้ง LAN ส่งไคลเอนต์ที่ตั้งค่าผิดทุกตัวผ่านบัญชี VPN ของคุณ และเมื่ออุโมงค์ล่มก็ทำให้ LAN มืดหรือถอยกลับไปใช้ ISP ขึ้นอยู่กับ kill switch

ตรวจสอบเส้นทางออก แล้วย้อนกลับตามลำดับ

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

docker compose exec vpn curl -s https://ifconfig.me
curl -s -x socks5h://127.0.0.1:1080 https://ifconfig.me
curl -s -x socks5h://127.0.0.1:1080 http://internal.example/health

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

การย้อนกลับคือสิ่งที่ตรงข้ามกับการตั้งค่า ลบตัวแปรสภาพแวดล้อมพร็อกซีออกจากโปรไฟล์เชลล์และจากยูนิต systemd ใด ๆ แล้วลดสแตกลงเพื่อให้เนมสเปซที่ใช้ร่วมกันหายไปพร้อมเจ้าของ ลบพอร์ตที่เผยแพร่ก่อนลบบริการพร็อกซี มิฉะนั้นโฮสต์จะเก็บตัวรับฟัง loopback ที่ไม่มีใครอ่าน

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

บทความที่เกี่ยวข้อง