Cách đưa một ứng dụng qua VPN mà không cần cả máy
Không có môi trường máy tính nào không được quản lý cung cấp công tắc VPN theo từng tiến trình. Hãy chọn tầng chọn lưu lượng: một socket đã gắn, một quy tắc UID, một hồ sơ tường lửa hay một bước nhảy SOCKS.
Ứng dụng khách VPN kết nối, trình duyệt chạy được, và lệnh duy nhất bạn thực sự quan tâm vẫn đi ra qua kết nối tại nhà. Hoặc điều ngược lại xảy ra: đường hầm chiếm tuyến mặc định và máy chủ tệp cục bộ biến mất khỏi màn hình. Tôi gặp chuyện này với một tác nhân build cần chạm tới gương artefact nội bộ trong khi phần còn lại của máy vẫn ở LAN văn phòng, và ứng dụng khách chỉ đưa ra đúng hai lựa chọn, toàn bộ lưu lượng hoặc không gì cả. Yêu cầu ở cả hai chiều là như nhau, chương trình này chứ không phải cả máy, và hầu hết ứng dụng khách không có ô chọn cho nó, vì bảng tuyến chưa bao giờ biết một chương trình là gì.
Vì sao bảng tuyến không thể thấy chương trình của bạn
Khi một tiến trình gửi gói tin, ngăn xếp tra địa chỉ đích trong bảng tuyến. Cả tên tệp thực thi lẫn cổng đều không đi vào phép tra đó. Tên miền thất bại vì lý do tương tự: đường hầm chở lưu lượng tới một địa chỉ, và khi một tên được phân giải thành địa chỉ thì định tuyến là đòn bẩy duy nhất nhân hệ điều hành còn có. Đó là lý do "VPN chỉ cho trang này" cuối cùng nghĩa là một proxy, còn "VPN chỉ cho chương trình này" nghĩa là một trong một tập nhỏ cơ chế.
| Cơ chế | Chọn cái gì | Bao phủ | Khi đường hầm rớt |
|---|---|---|---|
| Gắn socket | địa chỉ nguồn | một ứng dụng khách bạn kiểm soát | các kết nối thất bại |
| Quy tắc UID (Linux) | người dùng sở hữu | mọi tiến trình của một tài khoản | các kết nối thất bại |
| Hồ sơ tường lửa (Windows) | hồ sơ bộ điều hợp | một chương trình | thất bại theo hướng đóng |
| Không gian tên mạng | tư cách thành viên không gian tên | mọi thứ khởi chạy bên trong | các kết nối thất bại |
| Bước nhảy SOCKS | thiết lập proxy của ứng dụng | một ứng dụng hoặc một hồ sơ trình duyệt | các kết nối thất bại |
Không cái nào là tuyến theo tiến trình. Mỗi cái chuyển quyết định sang một tầng có thể thấy chương trình, người dùng hoặc cấu hình của chính ứng dụng.
Một đích đến vẫn là tuyến, không phải quy tắc chương trình. Trên macOS, công thức thường dùng là bỏ chọn "Gửi toàn bộ lưu lượng qua VPN" và thêm một tuyến máy chủ: sudo route add -host internal-tuner.example -interface ppp0, rồi gỡ lại bằng sudo route delete -host internal-tuner.example -interface ppp0. Trước tiên hãy đọc tên giao diện từ ifconfig, vì ppp0 chỉ là ví dụ. Ký tự đại diện không hoạt động trong tuyến máy chủ, nên một dịch vụ nói với nhiều tên cần một mục cho mỗi tên, và quy tắc bao phủ mọi tiến trình trên máy, không chỉ cái bạn nghĩ tới.
Gắn socket vào địa chỉ của đường hầm
Khi bạn kiểm soát ứng dụng khách, gắn là bộ chọn rẻ nhất. Một socket gắn vào địa chỉ cục bộ của đường hầm sẽ đi ra qua giao diện đó miễn là ở đó có tuyến tới đích, và mọi thứ khác trên máy giữ đường đi bình thường. Điểm tinh tế là việc gắn chọn địa chỉ nguồn trong khi bảng tuyến vẫn chọn lối ra. Chỉ gắn thôi không đưa lưu lượng không có tuyến vào đường hầm, và đó là lỗi đứng sau hầu hết báo cáo "tôi gắn rồi mà nó vẫn đi sai đường".
import socket
s = socket.socket()
s.bind(("10.8.0.2", 0))
s.connect(("build.internal.example", 443))Các công cụ cấp cao hơn phơi bày cùng ý tưởng: curl --interface 10.8.0.2, CURLOPT_INTERFACE trong PHP và đối số source_address trong các ứng dụng khách HTTP của Python. Địa chỉ phải thuộc về đường hầm, và một lỗi gõ ở đây thất bại như lỗi kết nối thông thường, không phải như lời phàn nàn về định tuyến. Gắn vào tên thiết bị thay vì địa chỉ là đặc quyền của Linux và không chuyển sang Windows hay macOS, đó là lý do các script di động truyền địa chỉ.
Linux: khớp chủ sở hữu, không phải tên chương trình
Nhân hệ điều hành khớp được người dùng sở hữu của một socket, nên một tài khoản riêng trở thành bộ chọn. Đặt đường hầm vào bảng tuyến riêng và chỉ gửi lưu lượng của tài khoản đó tới đó:
ip rule add uidrange 1001-1001 lookup 100
ip route add default dev tun0 table 100Cách này chỉ chạy khi đường hầm chưa là tuyến mặc định. Khởi động ứng dụng khách với --route-nopull, hoặc lọc phần chuyển hướng bị đẩy xuống bằng --pull-filter ignore "redirect-gateway" trên OpenVPN 2.4 và mới hơn, rồi tự thêm mục vào bảng. Nếu không, cả hai tuyến mặc định cùng tồn tại và cái ưu tiên thấp hơn thắng. Phiên bản có chủ đích là trỏ tuyến mặc định vào cổng của đường hầm, hoặc làm điều đó từ script ngắt kết nối: nó giữ lưu lượng ngoài internet khi đường hầm rớt, nhưng đó là công tắc tiêu diệt, không phải bộ chọn, và nó tồn tại cho tới khi ai đó gỡ.
Phiên bản mạnh hơn là một không gian tên mạng. Tạo nó, chạy ứng dụng khách bên trong và khởi chạy ứng dụng bằng ip netns exec. Không gì ngoài không gian tên thấy giao diện đường hầm, nên ứng dụng không thể vô tình quay lại tuyến của máy chủ. Cái giá là thật: không gian tên cần một cặp veth để tới mạng vật lý và tệp phân giải riêng, vì không gian tên gắn kết không chia sẻ thiết lập phân giải của máy chủ.
Hai kiểu hỏng giải thích phần lớn rò rỉ. Việc phân giải tên vẫn diễn ra trong bộ phân giải của máy chủ, nên truy vấn đi ra ngoài đường hầm ngay cả khi kết nối TCP đi vào trong. Và IPv6 cần ip -6 rule riêng, vì một quy tắc v4 không bao nó.
Windows và macOS: giới hạn tường lửa hoặc trao một proxy
Windows cũng không có bộ chọn tuyến theo ứng dụng, nhưng quy tắc tường lửa có thể giới hạn theo hồ sơ, và bộ điều hợp VPN có thể được phân loại là Công cộng trong khi LAN tại nhà vẫn Riêng tư. Tạo quy tắc chặn chiều ra cho chương trình trên hồ sơ Riêng tư và Miền, để Công cộng mở, và ứng dụng chỉ tới được mạng qua đường hầm. Đặt loại mạng của kết nối trước, rồi xác nhận trong bảng giám sát của tường lửa rằng quy tắc đã khớp. Đó là chặn, không phải tuyến: nó thất bại theo hướng đóng khi đường hầm rớt, và không ngăn được ứng dụng phớt lờ tường lửa.
macOS theo cùng logic với công cụ khác. Một trình lắng nghe SOCKS tiếp cận qua SSH là lựa chọn ít xâm lấn nhất, vì ssh -D 1080 user@host tạo một cổng cục bộ và chỉ những ứng dụng được cấu hình dùng nó mới đi đường đó. Một quy tắc pf có thể chặn một chương trình theo người dùng hoặc nhóm, nhưng vẫn không chọn được đường hầm riêng cho chương trình đó. Tunnelblick hoàn toàn không lọc theo tên miền, vì tuyến làm việc với địa chỉ; người dùng OpenConnect tìm tới vpn-slice khi máy chủ đẩy xuống một danh sách tiền tố quá rộng.
Hai lối vòng bao phủ phần công cụ nền tảng bỏ sót. Tiện ích mở rộng trình duyệt đổi proxy theo tên miền là câu trả lời thực tế cho "chỉ trang này", vì chúng tác động ở tầng ứng dụng nơi tên vẫn tồn tại; chúng chỉ bao một trình duyệt và không gì khác. Cách thô là chạy ứng dụng khách của nhà cung cấp trong một máy ảo để khách sở hữu thay đổi tuyến trong khi máy chủ giữ đường đi của nó, với cái giá là hai ngăn xếp mạng phải bảo trì.
Một bước nhảy SOCKS cho công cụ không có tùy chọn gắn
Hầu hết ứng dụng có thể được trỏ tới proxy HTTP hoặc SOCKS dù chúng không biết gì về giao diện. Một ứng dụng khách SSH cung cấp một cái mà không cần thay đổi phía máy chủ:
ssh -N -D 127.0.0.1:9999 [email protected]Bất cứ thứ gì được cấu hình dùng cổng đó sẽ ra ở pháo đài trong khi phần còn lại của màn hình giữ kết nối bình thường. Một phiên bản 3proxy cục bộ gắn vào địa chỉ đường hầm làm điều tương tự cho một cổng mà không cần SSH, và stunnel bao một cổng duy nhất được bọc TLS. Giới hạn là thực tế: proxy chỉ chở các giao thức biết dùng nó, UDP cần liên kết UDP của SOCKS5 mà thiết lập "SOCKS5" của nhiều ứng dụng khách không bật, và DNS phải do proxy phân giải (socks5h trong curl) nếu không tên sẽ rò rỉ tới bộ phân giải cục bộ.
Giữ trình lắng nghe trên loopback trừ khi máy khác cần nó.
Kiểm chứng đường đi bằng bắt gói, không bằng thông báo trạng thái
Cửa sổ trạng thái của ứng dụng mô tả thiết lập, không mô tả gói tin. Bắt lưu lượng trên cả hai giao diện và so sánh:
tcpdump -i any -n port 443
ss -tnp | grep 443Nếu kết nối xuất hiện trên giao diện vật lý mà không có mục khớp trên đường hầm, bộ chọn không có hiệu lực, bất kể ứng dụng khách báo gì. Kiểm tra IPv6 và bộ phân giải riêng, và tìm một đường thứ hai như tiến trình trợ giúp hoặc proxy hệ thống thừa hưởng.
Rồi chọn ranh giới khớp với yêu cầu: một proxy đủ cho một trang, một quy tắc UID đủ cho một tài khoản, một hồ sơ tường lửa đủ khi kết nối phải thất bại theo hướng đóng, và không gian tên là lựa chọn duy nhất giấu đường hầm khỏi mọi thứ bên ngoài nó.