Paho MQTT qua proxy: proxy_set, MQTT_PROXY và WebSockets
Paho MQTT qua proxy là cách để client MQTT viết bằng thư viện paho-mqtt của Python kết nối tới broker thông qua một proxy HTTP hoặc SOCKS5, thay vì mở kết nối TCP thẳng. ProxyVN dựng một broker MQTT, một proxy có nhật ký và chạy 14 ca publish và subscribe thật để xem thư viện xử lý proxy tới đâu, chỗ nào hay hỏng.
Paho MQTT qua proxy: lớp nào quyết định hành vi
MQTT không phải HTTP
MQTT là giao thức nhắn tin dạng publish và subscribe, chạy trên một kết nối TCP sống lâu, cổng mặc định 1883, hoặc 8883 khi có TLS. Client gửi gói CONNECT của MQTT, broker trả CONNACK, rồi hai bên giữ kết nối để đẩy tin qua lại. Khác với request HTTP, không có URL để proxy đọc, không có tiêu đề Host.
Vì vậy proxy HTTP chỉ dùng được cho MQTT qua lệnh CONNECT. Client xin proxy mở một đường hầm TCP tới broker, sau đó proxy chỉ chuyển byte, không hiểu nội dung MQTT bên trong. Proxy SOCKS5 làm việc tương tự ở tầng thấp hơn. Bài proxy HTTP và SOCKS5 giải thích khác biệt giữa hai loại.
Thư viện paho-mqtt làm gì với proxy
Thư viện không tự viết mã proxy. Theo tài liệu module client của paho-mqtt, hàm proxy_set dùng thư viện PySocks để mở kết nối, hỗ trợ ba loại socks.HTTP, socks.SOCKS4 và socks.SOCKS5. Không cài PySocks thì proxy_set báo ValueError ngay.
Đọc mã nguồn paho-mqtt 2.1.0 cho thấy hàm _get_proxy chọn proxy theo ba bước. Đầu tiên là cấu hình truyền vào proxy_set. Nếu không có, thư viện xem biến môi trường tên mqtt_proxy. Cuối cùng là proxy mặc định đặt cho PySocks bằng socks.set_default_proxy. Không có bước nào đọc HTTP_PROXY hay HTTPS_PROXY.
Ranh giới với bài proxy cho IoT
Bài proxy cho IoT nói về gateway, giao thức và kiến trúc ở mức tổng quan. Bài này chỉ đi vào một thư viện cụ thể, với lệnh, cấu hình và kết quả chạy thật. Thiết bị nhúng dùng thư viện C hay firmware riêng không thuộc phạm vi bài.
Kiểm chứng Paho MQTT qua proxy với proxy_set và transport
Thiết kế lab
Lab chạy trên Windows 10 với Python 3.12.10, paho-mqtt 2.1.0, PySocks 1.7.1. Broker là amqtt 0.12.1 chạy cục bộ, mở cổng TCP 8973 và cổng WebSockets 8974, cho phép kết nối ẩn danh. Proxy tự dựng có hai cổng: HTTP hỗ trợ CONNECT và SOCKS5 có tài khoản theo RFC 1929, cùng tài khoản user1 với mật khẩu p@ss#2026. Proxy ghi mỗi kết nối thành một dòng JSON gồm loại proxy, đích, tài khoản gửi lên và kết quả xác thực.
Proxy cũng được dạy phân giải mọi tên miền đuôi .test về 127.0.0.1. Máy lab không biết tên mqtt.test, chỉ proxy biết. Nhờ vậy lab phân biệt được tên miền do máy client phân giải hay do proxy phân giải.
Lý do chọn amqtt làm broker: đây là broker viết bằng Python, cài bằng pip, không cần tải chương trình chạy riêng. Broker có cả cổng TCP và WebSockets nên một lab kiểm được hai transport. Broker kiểm thử công khai cũng dùng được, nhưng khi đó nhật ký phía broker nằm ngoài tầm tay và kết quả phụ thuộc mạng Internet lúc chạy.
Thứ được đo ở mỗi ca gồm ba phần: client có báo lỗi ở connect không, có nhận lại tin vừa publish không, và proxy ghi gì. Chỉ nhìn phần thứ hai thì không đủ, vì ca 9 cho thấy client nhận tin bình thường trong khi không hề đi qua proxy. Phải có nhật ký proxy mới biết đường đi thật.
Mỗi ca tạo một client, subscribe chủ đề lab/proxy/t với QoS 1, publish một tin lên chính chủ đề đó, đợi tối đa 5 giây để nhận lại tin, rồi ngắt kết nối. Nhận được tin nghĩa là cả chiều đi và chiều về đều chạy qua đường đã chọn.
Mã kiểm tra
import socks, threading
import paho.mqtt.client as mqtt
from paho.mqtt.enums import CallbackAPIVersion
def run(host, port, proxy=None, transport="tcp"):
got, ev = [], threading.Event()
c = mqtt.Client(CallbackAPIVersion.VERSION2, client_id="lab-1", transport=transport)
if proxy:
c.proxy_set(**proxy)
c.on_connect = lambda cl, u, f, rc, p: cl.subscribe("lab/proxy/t", qos=1)
c.on_subscribe = lambda cl, u, mid, rcs, p: cl.publish("lab/proxy/t", "hello", qos=1)
c.on_message = lambda cl, u, m: (got.append(m.payload.decode()), ev.set())
c.connect(host, port, keepalive=30)
c.loop_start(); ev.wait(5); c.disconnect(); c.loop_stop()
return got
HTTP = dict(proxy_type=socks.HTTP, proxy_addr="127.0.0.1", proxy_port=8971,
proxy_username="user1", proxy_password="p@ss#2026")
print(run("127.0.0.1", 8973, HTTP))
Tài liệu yêu cầu truyền callback_api_version khi tạo Client và khuyên dùng VERSION2. Chữ ký callback trong đoạn trên theo VERSION2: on_connect nhận thêm reason_code và properties.
Kết quả 14 ca
Ca |
Cấu hình |
Kết quả |
Nhật ký proxy |
|---|---|---|---|
1 |
Đi thẳng TCP |
Nhận tin |
Không có dòng |
2 |
HTTP CONNECT có tài khoản |
Nhận tin |
CONNECT 127.0.0.1:8973, user1 |
3 |
SOCKS5 có tài khoản |
Nhận tin |
SOCKS5 CONNECT, xác thực đạt |
4 |
SOCKS5, host mqtt.test |
Nhận tin |
Đích là tên mqtt.test |
5 |
SOCKS5, host mqtt.test, rdns False |
getaddrinfo failed |
Không có dòng |
6 |
HTTP sai mật khẩu |
407 Proxy Authentication Required |
CONNECT bị từ chối |
7 |
SOCKS5 sai mật khẩu |
SOCKS5 authentication failed |
Xác thực sai |
8 |
Proxy chết |
ProxyConnectionError |
Không có dòng |
9 |
Chỉ đặt HTTP_PROXY tới cổng chết |
Nhận tin, đi thẳng |
Không có dòng |
10 |
MQTT_PROXY http có tài khoản trong URL |
407 |
CONNECT không có tài khoản |
11 |
MQTT_PROXY socks |
Phương thức xác thực bị từ chối |
Client không gửi tài khoản |
12 |
MQTT_PROXY tới cổng chết, NO_PROXY=127.0.0.1 |
Nhận tin, đi thẳng |
Không có dòng |
13 |
WebSockets qua HTTP CONNECT |
Nhận tin |
CONNECT 127.0.0.1:8974, user1 |
14 |
WebSockets, host mqtt.test qua SOCKS5 |
Nhận tin |
SOCKS5 tới mqtt.test:8974 |
Các phần dưới đi vào từng nhóm ca.
Paho MQTT qua proxy HTTP CONNECT

Đường hầm CONNECT cho MQTT
Luồng thành công
Ca 2 truyền proxy_type=socks.HTTP cùng tài khoản. PySocks gửi CONNECT 127.0.0.1:8973 kèm tiêu đề Proxy-Authorization. Proxy trả 200, mở đường hầm, sau đó gói MQTT CONNECT đi bên trong. Nhật ký proxy chỉ có một dòng CONNECT, không có nội dung tin nhắn, vì proxy không đọc MQTT.
Tài liệu của proxy_set ghi proxy_username dành cho SOCKS5 hoặc SOCKS4, proxy_password dành cho SOCKS5. Thực tế PySocks 1.7.1 vẫn dùng hai tham số này cho proxy HTTP, như ca 2 cho thấy. Vì tài liệu không nói rõ, nên giữ phép kiểm này mỗi khi nâng cấp thư viện.
Một bài học về chữ basic viết thường
Lần chạy đầu, ca 2 trả 407 dù mật khẩu đúng. Đọc mã PySocks thấy thư viện gửi tiêu đề Proxy-Authorization: basic, chữ basic viết thường. Proxy lab ban đầu chỉ chấp nhận đúng chữ Basic viết hoa, nên từ chối. Chuẩn HTTP coi tên cơ chế xác thực không phân biệt hoa thường, nên lỗi nằm ở proxy lab. Sửa proxy lab xong, ca 2 chạy đúng.
Bài học thực tế: nếu Paho MQTT qua proxy HTTP báo 407 trong khi curl với cùng tài khoản chạy được, hãy nghĩ tới khả năng proxy so khớp quá chặt. Thử cổng SOCKS5 của cùng nhà cung cấp là cách đối chứng nhanh. Bài sửa lỗi 407 Proxy Authentication Required liệt kê các nguyên nhân khác.
Cổng đích có thể bị proxy chặn
Nhiều proxy HTTP chỉ cho CONNECT tới cổng 443, vì mục đích chính là chuyển tiếp HTTPS. Broker MQTT chạy cổng 1883 hay 8883 khi đó sẽ bị từ chối bằng mã 403 hoặc ngắt kết nối. Lab không gặp vì proxy tự dựng cho mọi cổng. Với proxy mua ngoài, hỏi nhà cung cấp danh sách cổng CONNECT được mở trước khi tích hợp.
Độ trễ đo được trong lab
Thời gian từ lúc kết nối tới lúc nhận lại tin nằm trong khoảng 3 đến 19 mili giây cho mọi ca thành công, có và không có proxy. Mọi thứ chạy trên cùng một máy nên con số này chỉ cho biết proxy lab không gây chậm đáng kể. Với proxy thật ở xa, độ trễ mỗi tin sẽ cộng thêm thời gian khứ hồi từ client tới proxy và từ proxy tới broker.
Vì đường hầm được mở một lần lúc kết nối, chi phí CONNECT chỉ trả một lần. Các tin publish sau đó không phải xin proxy lại. Đây là điểm khác với gọi API HTTP qua proxy, nơi mỗi request mới có thể phải mở kết nối mới.
Paho MQTT qua proxy SOCKS5
Tài khoản và mật khẩu

MQTT_PROXY bỏ mất tài khoản
Ca 3 dùng SOCKS5 với cùng tài khoản. Nhật ký ghi bước xác thực đạt rồi lệnh CONNECT tới 127.0.0.1:8973. Ca 7 đổi mật khẩu sai, PySocks báo SOCKS5 authentication failed trước khi chạm tới broker. Lỗi này hiện ngay ở connect, không phải ở callback on_connect.
proxy_rdns và tên miền của broker
proxy_rdns mặc định True, nghĩa là client gửi tên miền nguyên văn cho proxy, để proxy tự phân giải. Ca 4 kết nối tới mqtt.test, cái tên chỉ proxy biết. Nhật ký SOCKS5 ghi đích là mqtt.test, kết nối thành công. Ca 5 đặt proxy_rdns=False, client tự phân giải tên trước, máy lab không biết mqtt.test nên báo getaddrinfo failed, không có kết nối nào tới proxy.
Trong môi trường thật, proxy_rdns=True quan trọng khi broker chỉ có tên trong mạng nội bộ phía proxy, hoặc khi không muốn DNS của máy client lộ tên broker. Bài DNS leak khi dùng proxy nói thêm về rò rỉ DNS.
Với proxy HTTP, tên miền luôn được gửi trong lệnh CONNECT, nên rdns ít tác động hơn. Lab không chạy ca HTTP với rdns False.
SOCKS4
PySocks hỗ trợ SOCKS4 với userid, không có mật khẩu. Lab không dựng proxy SOCKS4. Gói proxy hiện nay hầu hết là HTTP và SOCKS5, nên SOCKS4 chỉ nên dùng khi hệ thống cũ bắt buộc.
Paho MQTT qua proxy bằng biến môi trường MQTT_PROXY
Thư viện không đọc HTTP_PROXY
Ca 9 đặt HTTP_PROXY trỏ tới một cổng không có proxy rồi chạy client không gọi proxy_set. Client nhận tin bình thường, nhật ký proxy trống. Nghĩa là paho-mqtt bỏ qua HTTP_PROXY. Nhiều người đặt biến này cho cả hệ thống rồi tưởng client MQTT cũng đi proxy, trong khi thực tế client đang đi thẳng.
Đọc MQTT_PROXY và giới hạn
Mã nguồn gọi urllib.request.getproxies, rồi tìm khóa mqtt. Khóa này tương ứng biến môi trường mqtt_proxy hoặc MQTT_PROXY. Thư viện chỉ nhận hai dạng URL: http:// được hiểu là proxy HTTP, socks:// được hiểu là SOCKS5. Các dạng khác bị bỏ qua.
Điểm cần chú ý: thư viện chỉ lấy tên máy và cổng từ URL, không lấy tài khoản. Ca 10 đặt MQTT_PROXY=http://user1:p%40ss%[email protected]:8971, proxy nhận CONNECT không có Proxy-Authorization và trả 407. Ca 11 dùng socks://, client chỉ đề nghị phương thức không xác thực, proxy từ chối. MQTT_PROXY chỉ dùng được với proxy xác thực bằng IP.
NO_PROXY vẫn có tác dụng
Trước khi đọc MQTT_PROXY, thư viện gọi urllib.request.proxy_bypass với host của broker. Ca 12 đặt MQTT_PROXY tới cổng chết và NO_PROXY=127.0.0.1, client đi thẳng và nhận tin. Đây là cách giữ broker nội bộ đi thẳng trong khi broker ngoài đi proxy. Cú pháp danh sách loại trừ có trong bài No_proxy là gì.
NO_PROXY chỉ áp cho MQTT_PROXY. Khi đã gọi proxy_set, thư viện dùng proxy đó cho mọi host, không xét NO_PROXY.
Proxy mặc định của PySocks
Bước cuối của _get_proxy đọc socks.get_default_proxy. Một số mã cũ gọi socks.set_default_proxy rồi thay socket.socket của Python để mọi kết nối đi proxy. Cách đó ảnh hưởng cả chương trình, khó kiểm soát. Với code mới, gọi proxy_set trên từng client rõ ràng hơn.
Paho MQTT qua proxy với WebSockets

WebSockets qua proxy cổng 443
Khi nào dùng WebSockets
Một số broker mở thêm MQTT qua WebSockets, thường ở cổng 443 hoặc 8443 với TLS, đường dẫn /mqtt. Đây là lựa chọn hữu ích khi proxy hay tường lửa chỉ cho ra cổng 443. Client tạo bằng transport="websockets", đường dẫn và tiêu đề đặt qua ws_set_options.
Proxy áp cho cả WebSockets
Ca 13 tạo client WebSockets tới cổng 8974, gọi proxy_set với proxy HTTP. Nhật ký có CONNECT 127.0.0.1:8974 kèm user1, client nhận tin. Ca 14 dùng SOCKS5 và tên mqtt.test, nhật ký ghi đích mqtt.test:8974. Cả hai cho thấy proxy_set tác động lên kết nối TCP bên dưới, nên áp được cho cả tcp lẫn websockets.
Điều này khác với cách trình duyệt làm. Trong paho-mqtt, kết nối WebSockets không gửi request HTTP thường qua proxy, mà vẫn mở đường hầm CONNECT trước. Proxy chỉ thấy tên máy và cổng, không thấy đường dẫn /mqtt.
Ví dụ WebSockets có TLS
c = mqtt.Client(CallbackAPIVersion.VERSION2, client_id="sensor-07", transport="websockets")
c.ws_set_options(path="/mqtt")
c.tls_set()
c.proxy_set(proxy_type=socks.HTTP, proxy_addr="203.0.113.10", proxy_port=8080,
proxy_username=os.environ["PROXY_USER"], proxy_password=os.environ["PROXY_PASS"])
c.connect("broker.vi-du.vn", 443, keepalive=60)
Lab không chạy ca TLS. Với TLS, việc bắt tay chứng chỉ diễn ra bên trong đường hầm, giữa client và broker, proxy HTTP thường không can thiệp.
Paho MQTT qua proxy với TLS cổng 8883
Thứ tự các lớp
Khi bật tls_set, client đi qua ba lớp theo thứ tự. Đầu tiên PySocks mở kết nối tới proxy và xin đường hầm. Tiếp theo client bắt tay TLS với broker bên trong đường hầm. Cuối cùng gói MQTT CONNECT đi trong kênh TLS. Proxy chỉ thấy bước đầu, phần còn lại là byte đã mã hóa.
Vì vậy lỗi chứng chỉ khi dùng TLS gần như luôn đến từ broker hoặc từ cấu hình CA phía client, không phải từ proxy. Proxy HTTP thông thường không chèn chứng chỉ vào giữa đường hầm CONNECT. Chỉ proxy công ty có chức năng kiểm tra TLS mới làm vậy, và khi đó cần đưa CA của công ty vào tham số ca_certs của tls_set.
Tên máy trong chứng chỉ
Client kiểm tên trong chứng chỉ theo host truyền vào connect. Nếu dùng proxy_rdns để kết nối tới một tên chỉ phía proxy biết, chứng chỉ của broker vẫn phải chứa đúng tên đó. Kết nối bằng địa chỉ IP thường làm việc kiểm tên thất bại vì chứng chỉ chỉ ghi tên miền. Hàm tls_insecure_set(True) tắt kiểm tên, chỉ nên dùng lúc chẩn đoán trong lab, không dùng cho thiết bị chạy thật.
Bài này không chạy ca TLS. Khi kiểm, chạy cùng ca với và không có proxy. Nếu không proxy chạy được mà có proxy lỗi chứng chỉ, proxy đang chặn TLS. Nếu cả hai cùng lỗi, vấn đề nằm ở chứng chỉ của broker. Bài ERR_PROXY_CERTIFICATE_INVALID có cách nhận biết proxy chặn TLS.
Paho MQTT qua proxy trong ứng dụng chạy lâu
Lần kết nối đầu tiên
Mặc định loop_forever ném lỗi nếu lần kết nối đầu thất bại. Với proxy, lần đầu hay thất bại vì lý do tạm thời như proxy đang khởi động lại. Tham số retry_first_connection=True cho phép thư viện tự thử lại. Đổi lại, cấu hình proxy sai cũng sẽ bị thử lại mãi mà không báo, nên chỉ bật khi đã kiểm cấu hình bằng bộ ca ở cuối bài.
Khoảng chờ khi nối lại
Hàm reconnect_delay_set(min_delay, max_delay) đặt khoảng chờ giữa các lần nối lại, tăng dần từ min tới max. Mặc định là 1 đến 120 giây. Khi nhiều thiết bị cùng đi qua một proxy và proxy gặp sự cố, nếu tất cả nối lại cùng một nhịp, proxy sẽ nhận một đợt kết nối dồn dập khi vừa hồi phục. Giữ max_delay đủ lớn để các thiết bị dàn đều thời điểm nối lại.
Ghi log lỗi proxy
Lỗi proxy ở lần nối lại không đi qua on_connect, vì kết nối chưa tới broker. Gọi enable_logger để thư viện ghi lỗi vào logging của Python:
import logging
logging.basicConfig(level=logging.INFO, filename="mqtt.log")
client.enable_logger()
client.reconnect_delay_set(min_delay=2, max_delay=300)
client.loop_forever(retry_first_connection=True)
Khi đọc log, dòng có chữ ProxyError, 407 hay SOCKS5 cho biết lỗi ở tầng proxy. Dòng có reason code của MQTT cho biết đã qua proxy và tới được broker.
Tin nhắn khi proxy đứt giữa chừng
Khi proxy ngắt đường hầm, broker thấy client mất kết nối. Tin QoS 0 đang gửi dở sẽ mất. Tin QoS 1 và QoS 2 được client giữ lại và gửi lại sau khi nối lại, với điều kiện phiên được giữ. Trong MQTT 3.1.1, tạo client với clean_session=False để broker nhớ các đăng ký và tin chờ của client đó. Trong MQTT 5.0, tham số tương ứng là clean_start cùng thời hạn phiên.
Thiết bị đi qua proxy hay bị ngắt nên dùng QoS 1 cho dữ liệu quan trọng, và giữ client ID cố định để broker nhận ra phiên cũ. Client ID sinh ngẫu nhiên mỗi lần chạy sẽ làm broker coi mỗi lần nối lại là một client mới, tin chờ của phiên trước bị bỏ.
Paho MQTT qua proxy hay proxy ở tầng hệ thống
Đặt proxy cho cả máy
Một cách khác là để chương trình không biết gì về proxy, rồi dùng công cụ ép mọi kết nối của máy hay của tiến trình đi qua proxy, như Proxifier trên Windows hoặc tun2socks. Cách này tiện khi không sửa được mã nguồn, ví dụ phần mềm đóng gói sẵn. Bài Proxifier hướng dẫn quy tắc theo tiến trình.
Vì sao nên dùng proxy_set khi sửa được mã
Đặt proxy trong mã cho phép từng client có proxy riêng, kiểm soát được lỗi ở connect và ghi log rõ ràng. Proxy ở tầng hệ thống làm mọi kết nối của tiến trình đi chung một đường, kể cả kết nối tới API nội bộ không cần proxy, và khi proxy lỗi thì chương trình chỉ thấy lỗi mạng chung chung. Với client MQTT do anh em tự viết, proxy_set là lựa chọn dễ bảo trì hơn.
Đọc lỗi khi Paho MQTT qua proxy
Lỗi ở bước connect
Lỗi proxy hiện dưới dạng ngoại lệ của PySocks ném ra từ connect, không phải mã trả về trong on_connect:
Thông báo |
Ý nghĩa |
Hướng xử lý |
|---|---|---|
GeneralProxyError: 407 Proxy Authentication Required |
Proxy HTTP không nhận tài khoản |
Kiểm tài khoản, xem proxy có chấp nhận chữ basic viết thường |
GeneralProxyError: SOCKS5 authentication failed |
Sai tài khoản SOCKS5 |
Kiểm lại user, mật khẩu |
All offered SOCKS5 authentication methods were rejected |
Client không gửi tài khoản |
Gọi proxy_set có tài khoản thay vì MQTT_PROXY |
ProxyConnectionError: Error connecting to HTTP proxy |
Proxy chết, sai cổng |
Thử cổng bằng curl hoặc Test-NetConnection |
getaddrinfo failed |
Client tự phân giải tên thất bại |
Bật proxy_rdns hoặc kiểm DNS |
403 hoặc ngắt khi CONNECT |
Proxy chặn cổng đích |
Hỏi danh sách cổng, cân nhắc WebSockets 443 |
Lỗi sau khi đã kết nối
Khi đường hầm đã mở, các lỗi còn lại là lỗi MQTT. Mã trả về trong on_connect như 4 sai username hoặc password, 5 không được phép, là do broker trả, không liên quan proxy. Tài liệu khuyên so sánh reason_code theo tên thay vì số, vì số khác nhau giữa MQTT 3.1.1 và 5.0.
Ngắt kết nối định kỳ
Proxy thương mại thường có thời gian chờ cho kết nối không có dữ liệu. MQTT giữ kết nối bằng gói PINGREQ theo keepalive. Nếu keepalive dài hơn thời gian chờ của proxy, kết nối sẽ bị ngắt định kỳ, client tự nối lại nhưng sẽ có khoảng trống. Đặt keepalive ngắn hơn thời gian chờ của proxy, ví dụ 30 đến 60 giây.
Paho MQTT qua proxy trong code thật
Lấy cấu hình từ một chuỗi URL
Nhiều nơi lưu proxy dạng URL. Hàm nhỏ dưới đổi URL thành tham số proxy_set, giữ được tài khoản mà MQTT_PROXY bỏ mất:
import os, socks
from urllib.parse import urlsplit, unquote
def proxy_args(url):
u = urlsplit(url)
kind = {"http": socks.HTTP, "socks5": socks.SOCKS5, "socks5h": socks.SOCKS5}[u.scheme]
args = dict(proxy_type=kind, proxy_addr=u.hostname, proxy_port=u.port,
proxy_rdns=(u.scheme != "socks5"))
if u.username:
args.update(proxy_username=unquote(u.username), proxy_password=unquote(u.password or ""))
return args
client.proxy_set(**proxy_args(os.environ["MQTT_PROXY_URL"]))
Biến tên MQTT_PROXY_URL được chọn khác MQTT_PROXY để thư viện không tự đọc nhầm. Mật khẩu trong URL phải mã hóa phần trăm. Bài định dạng proxy có cách chuyển từ dạng ip:port:user:pass.
Client ID khi nhiều máy dùng chung proxy
Broker phân biệt client bằng client ID, không bằng IP. Hai thiết bị đi qua cùng một proxy vẫn phải có client ID khác nhau. Trùng client ID, broker sẽ ngắt client cũ khi client mới kết nối, hai bên đá nhau liên tục. Lỗi này trông giống proxy chập chờn, nhưng nguyên nhân nằm ở broker.
Một proxy cho nhiều client
Mỗi client MQTT giữ một kết nối TCP lâu dài qua proxy. Một trăm thiết bị nghĩa là một trăm đường hầm mở cùng lúc. Kiểm giới hạn số kết nối đồng thời của gói proxy trước khi đưa nhiều thiết bị vào cùng một tài khoản.
Kiểm proxy trước khi chạy client
Trước khi nghi ngờ thư viện, kiểm proxy có mở đường hầm tới cổng broker không. Với proxy HTTP, curl có thể xin CONNECT tới một cổng bất kỳ:
curl -v -p -x http://user1:p%40ss%232026@203.0.113.10:8080 telnet://broker.vi-du.vn:1883
Dòng HTTP/1.1 200 Connection established trong kết quả nghĩa là proxy cho đi tới cổng đó. Mã 403 nghĩa là proxy chặn cổng. Mã 407 là tài khoản. Với SOCKS5, đoạn Python ngắn dùng PySocks là đủ:
import socks
s = socks.socksocket()
s.set_proxy(socks.SOCKS5, "203.0.113.10", 1080, rdns=True, username="user1", password="p@ss#2026")
s.settimeout(10); s.connect(("broker.vi-du.vn", 1883)); print("Mở được đường tới broker"); s.close()
Hai phép thử này tách lỗi proxy khỏi lỗi MQTT. Qua được bước này mà client vẫn lỗi, nguyên nhân nằm ở cấu hình client hoặc broker.
Kiểm thử Paho MQTT qua proxy trước khi chạy thật
Bộ ca tối thiểu
- Đi thẳng tới broker, để có mốc so sánh.
- Proxy đúng, nhận lại tin vừa publish.
- Sai mật khẩu proxy, thấy lỗi ở connect, không phải ở on_connect.
- Proxy chết, thấy ProxyConnectionError.
- Kiểm nhật ký hoặc bảng thống kê của nhà cung cấp có lưu lượng.
- Để client chạy lâu hơn thời gian chờ của proxy, xem có tự nối lại không.
Broker kiểm thử
Dùng broker riêng hoặc broker kiểm thử được phép. Broker công khai dùng chung cho mọi người, mọi tin trên đó ai cũng đọc được. Không gửi dữ liệu thật lên broker công khai, và không dùng proxy để vượt giới hạn mà broker đặt ra cho mỗi IP.
Gỡ cấu hình sau khi thử
Xóa biến MQTT_PROXY nếu đã đặt trong lúc thử, vì thư viện tự đọc biến này ở mọi lần chạy sau. Một biến quên xóa có thể làm thiết bị đi qua proxy thử nghiệm đã hết hạn.
Chọn proxy cho Paho MQTT qua proxy
HTTP hay SOCKS5
SOCKS5 có tài khoản là lựa chọn ít rắc rối nhất: không lệ thuộc danh sách cổng CONNECT, không gặp chuyện chữ basic viết thường. Proxy HTTP dùng tốt khi nhà cung cấp mở CONNECT tới cổng broker. Hai loại đều cần PySocks.
IP tĩnh cho broker có danh sách cho phép
Broker công nghiệp hay broker của đối tác thường chỉ nhận kết nối từ danh sách IP. Proxy IP tĩnh giúp mọi thiết bị đi ra cùng một địa chỉ đã đăng ký. Proxy xoay đổi IP giữa chừng sẽ làm broker từ chối hoặc làm đứt phiên MQTT.
ProxyVN có proxy dân cư tĩnh, datacenter và proxy xoay, hỗ trợ HTTP và SOCKS5, cấp tài khoản user và mật khẩu. Khi broker cần một IP cố định, anh em có thể mua proxy Việt Nam tại trang chủ ProxyVN rồi chạy bộ ca kiểm ở trên trước khi đưa vào thiết bị.
Proxy không thay quyền với broker
Proxy chỉ đổi đường đi của kết nối. Tài khoản MQTT, chứng chỉ client và quyền publish theo chủ đề vẫn do broker quyết định.
Câu hỏi thường gặp về Paho MQTT qua proxy
Paho có dùng proxy của Windows không?
Không qua proxy_set. Hàm getproxies trên Windows có thể đọc cài đặt proxy trong registry khi không có biến môi trường, nhưng thư viện chỉ lấy khóa mqtt, mà cài đặt proxy Windows thường không có khóa này. Cách chắc ăn là gọi proxy_set.
Có dùng proxy cho MQTT over UDP không?
MQTT chuẩn chạy trên TCP. MQTT-SN dùng UDP, không thuộc paho-mqtt Python, và proxy HTTP không chuyển UDP.
Vì sao proxy_set gọi sau connect không có tác dụng?
Proxy được áp khi tạo socket ở connect. Gọi proxy_set sau khi đã kết nối chỉ có tác dụng ở lần nối lại tiếp theo. Luôn gọi trước connect.
Proxy có đọc được nội dung tin MQTT không?
Proxy HTTP và SOCKS5 chỉ chuyển byte trong đường hầm. Với MQTT không TLS, byte đó là nội dung rõ, nên về kỹ thuật bên vận hành proxy có thể ghi lại. Bật TLS để chỉ client và broker đọc được nội dung, nhất là khi đi qua proxy dùng chung.
Phiên bản cũ 1.x có khác không?
Lab chỉ chạy bản 2.1.0. Bản 2.0 đổi cách khai báo callback, buộc truyền callback_api_version. Code cũ chuyển sang 2.x cần sửa chữ ký callback trước khi kiểm proxy.
Checklist Paho MQTT qua proxy

Checklist Paho MQTT qua proxy
Trước khi chạy
- Cài PySocks cùng paho-mqtt.
- Gọi proxy_set có tài khoản trước connect, không dựa vào HTTP_PROXY.
- Chỉ dùng MQTT_PROXY với proxy xác thực bằng IP.
- Giữ proxy_rdns True khi broker có tên chỉ phía proxy biết.
- Hỏi nhà cung cấp cổng CONNECT được mở, hoặc dùng WebSockets 443.
- keepalive ngắn hơn thời gian chờ của proxy.
- Client ID riêng cho từng thiết bị.
Giới hạn của bài
Lab chạy trên máy cục bộ với broker amqtt và proxy tự dựng. Chưa thử TLS, broker đám mây, proxy thương mại và thiết bị nhúng. Thư viện C hay JavaScript của Eclipse Paho có API proxy khác, cần kiểm riêng.
Paho MQTT qua proxy và những điều cần nhớ
Paho MQTT qua proxy chạy bằng proxy_set với PySocks, áp cho cả tcp và websockets, không đọc HTTP_PROXY và bỏ tài khoản khi đọc MQTT_PROXY. Kiểm bằng nhật ký proxy và một vòng publish rồi nhận lại tin trước khi tin vào cấu hình. Cần proxy SOCKS5 ổn định cho Paho MQTT qua proxy, liên hệ Proxy.vn - Nhà cung cấp dịch vụ proxy chất lượng hàng đầu Việt Nam.