Viết Một Cái Proxy, Mục Đích Thì Bạn Hiểu Rồi - nói dối e blog

Viết Một Cái Proxy, Mục Đích Thì Bạn Hiểu Rồi

Dùng Linode đã hơn một năm, ngoài việc dựng blog, mình cũng cài mấy cái tool nhỏ lên đó. Việc sở hữu một server riêng trên internet đối với lập trình viên là khá cần thiết. Tất nhiên VPS này tuyệt đối không thể chọn trong nước, lý do bạn hiểu mà. Một lợi ích kèm theo là có thể dùng ssh -D, tin rằng nhiều bạn đã quen dùng cách này.

Tự dưng nảy ra ý định viết một tool nhỏ làm được chức năng giống ssh -D, nhưng không dùng SSL. Tất nhiên cũng không thể để dữ liệu trần (plaintext), vì lý do众所周知 (bạn biết đấy), truyền dữ liệu trần dễ dẫn đến tình trạng mất kết nối kỳ lạ hoặc timeout kéo dài, lỗi này chắc chắn không phải do code của bạn. Mạng thì lúc nào cũng có độ tin cậy 100% đâu.

Mất hai ngày để hiện thực hóa ý tưởng. Ngày đầu tiên tưởng đã xong, nhưng cách triển khai phức tạp quá, bug đầy ra. Cả đêm thức trắng cũng chưa fix xong. Sau khi ngủ bù một giấc, trong mơ bỗng ngộ ra cần đơn giản hóa thiết kế. Ngày hôm sau rewrite lại từ đầu, cuối cùng cũng chạy được. Code dùng Go viết, kiểu “quick & dirty”, khá là đồ chơi. Dù vậy mình vẫn để phần code ở cuối bài, bạn nào hứng thú có thể lấy về cải tiến. Hiện tại bản demo này đã đủ dùng nên có lẽ sau này dù có nâng cấp mình cũng lười update tiếp.

Ý tưởng ban đầu là xây dựng hệ thống gồm 2 phần: chương trình chạy local sẽ mở một cổng tương thích SOCKS5 trên localhost. Tất nhiên chỉ có phần này chưa đủ, cần phải có một chương trình nữa đặt bên ngoài “bức tường” để làm proxy server thực sự. Hai chương trình này giữ một kết nối TCP duy nhất, chương trình local sẽ chuyển tiếp toàn bộ yêu cầu proxy đến server bên ngoài.

Đầu tiên mình đọc RFC1928 của SOCKS5, nghĩ rằng viết một SOCKS server cũng không có gì ghê gớm. Việc cần làm chỉ là mã hóa các yêu cầu SOCKS5 thành giao thức riêng, thông qua kết nối TCP duy nhất gửi ra ngoài tường, rồi để server bên đó xử lý proxy. Nhưng thực tế chứng minh, muốn implement chuẩn SOCKS5 trong một ngày là việc không đơn giản. Ngay cả khi dùng Go, với tay mới như mình cũng khó hoàn thành. P/s: Đêm thức trắng fix bug hiệu suất cực thấp, sau 30 tuổi không nên làm liều như vậy nữa.

Sau giấc ngủ hồi sinh, mình điều chỉnh lại thiết kế hệ thống thành 3 phần như sau: Chương trình local (n:1) —-(bức tường truyền thuyết)—- Chương trình bên ngoài tường (1:n) —- Server SOCKS5

Cụ thể hơn, client local có thể mở nhiều kết nối TCP yêu cầu dịch vụ SOCKS5. Chương trình của mình sẽ gom chúng lại thành một kết nối duy nhất, thông qua đường hầm TCP chuyển ra ngoài tường. Ở giữa có thể thêm chức năng mã hóa đơn giản hoặc nén dữ liệu.

Trên VPS bên ngoài tường, mình cài một chương trình riêng để nhận luồng dữ liệu tổng hợp này, sau đó tách thành các yêu cầu độc lập rồi kết nối đến server SOCKS5 local trên VPS.

Vì là dùng cá nhân nên hai chương trình ở hai đầu tường chỉ duy trì một kết nối. Sau khi kết nối được thiết lập, service bên ngoài tường sẽ không còn lắng nghe kết nối mới nữa, đảm bảo tính bảo mật 1:1. Về mặt kỹ thuật, mở rộng thành nhiều kết nối với Go cũng không khó, nhưng mình lười nên chưa làm.

Chương trình riêng này không có xác thực người dùng. Lý do là vì mình chỉ cung cấp dịch vụ 1:1, nên dù không xác thực cũng không sợ bị abuse. Mình chỉ cần thủ công bật service khi cần dùng. Tất nhiên thêm cơ chế xác thực cũng dễ thôi, nhưng lại… lười.

Còn server SOCKS5 thực sự thì không cần tự viết. Mình dùng ssocks - một tool nhẹ và đơn giản, đúng như tên gọi, chỉ là một SOCKS5 server thuần túy. Tuy nhiên phiên bản hiện tại (0.0.10) có một điểm bất tiện: địa chỉ bind mặc định là 0.0.0.0 và không thể cấu hình (cứng-coded trong code). Trong README có mục TODO tác giả dự định thêm tính năng này. Trong trường hợp của mình, mình chỉ muốn SOCKS server này phục vụ local, nên cần chỉnh sửa nhỏ trong code.

Chỉ cần sửa dòng sau trong file src/libsocks/net-util.c:

1
   addrS->sin_addr.s_addr = htonl(INADDR_ANY); /* All Local addresses */

Thành:

1
   addrS->sin_addr.s_addr = htonl(INADDR_LOOPBACK); /* Localhost only */

Code đầy đủ của mình xem ở đây. Nếu phát hiện bug xin cứ góp ý, nhưng hỏi gì thì mình không trả lời đâu nhé :) Code viết hơi rối, chắc sau này mình cũng ngại đọc lại.

P/s: Gần đây mua bộ board game mới “Seven Wonders” (Bảy Kỳ Quan), cực kỳ đã瘾, recommend cho các bạn mê game bàn.

0%