實現了一個端口服務復用的透明代理,可以在同一個端口上運行多個協議。根據每次連接中客戶端發起的首個請求檢測協議,根據協議或各種條件選擇代理的上游。
需要打一個補丁。由@fcicq在這個討論中貢獻。這個補丁實現了BSD的socket recv()語義。目前官方也有這個feature的PR。
示例配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
stream { init_by_lua_block { local mul = require("resty.multiplexer") mul.load_protocols( "http", "ssh", "dns", "tls", "xmpp" ) mul.set_rules( {{"client-host", "10.0.0.1"}, "internal-host", 80}, {{"protocol", "http"}, {"client-host", "10.0.0.2"}, "internal-host", 8001}, {{"protocol", "http"}, "example.com", 80}, {{"protocol", "ssh"}, "github.com", 22}, {{"protocol", "dns"}, "1.1.1.1", 53}, {{"protocol", "tls"}, {"time", nil}, "twitter.com", 443}, {{"protocol", "tls"}, "www.google.com", 443}, {{"default", nil}, "127.0.0.1", 80} ) mul.matcher_config.time = { minute_match = {0, 30}, minute_not_match = {{31, 59}}, } } resolver 8.8.8.8; server { listen 80; content_by_lua_block { local mul = require("resty.multiplexer") local mp = mul:new() mp:run() } } } |
示例中服務監聽在80端口,並定義規則:
- 如果客戶端來自
10.0.0.1
,代理到 internal-host.com:80 - 如果請求協議是
HTTP
而且客戶端來自10.0.0.2
,代理到 internal-host:8001 - 如果請求協議是
SSH
,代理到 github.com:22 - 如果請求協議是
DNS
,代理到 1.1.1.1:53 - 如果請求協議是
SSL/TLS
而且現在的時間是 0 到 30分,代理到 twitter.com:443 - 如果請求協議是
SSL/TLS
而且現在的時間是 31 到 59分,代理到 www.google.com:443 - 以上均不滿足,代理到 127.0.0.1:80
說明
- 只能實現識別連接建立後客戶端先發送請求的協議,不兼容服務端先發送響應的協議(比如FTP,SMTP等)
- 如果實現了ngx.reqsock.peak(),則可以使用ngx_stream_proxy來轉發流量,這樣的話除了首個請求以外同一連接的後續請求將沒有額外的性能損失;目前只能在Lua層轉發。
This module consists of two parts: protocol identifiers and matchers.
Protocol
The protocol part analyzes the first request that is sent from client and try to match it using known protocol signatures.
Currently supported: dns
, http
, ssh
, tls
, xmpp
. Based on the bytes of signature, each protocol may have different possibilities to be falsely identified.
Protocol | Length of signature | False rate |
---|---|---|
dns | 9 1/4 | 5.29e-23 |
http | 4 | 2.33e-10 |
ssh | 4 | 2.33e-10 |
tls | 6 | 3.55e-15 |
xmpp | 6 in 8 1/4 | ? |
Add new protocol
Create a new protocol_name.lua
file under resty/multiplexer/protocol
in the format of:
1 2 3 4 5 6 |
return { required_bytes = ?, check = function(buf) -- check with the buf and return true if the protocol is identified end } |
required_bytes
is the length of bytes we need to read before identifying the protocol.
Matcher
client-host
Match if $remote_addr
equals to expected value.
protocol
Match if protocol equals to expected value.
time
Match if current time is in configured range in mul.matcher_config.time
. If no range is defined, the matcher will always return false.
For example, to match year 2018
, January
and March
and hour 6
to 24
except for hour 12
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
init_by_lua_block { local mul = require("resty.multiplexer") mul.load_protocols( "http", "ssh", "dns", "tls", "xmpp" ) mul.set_rules( {{"time", ""}, "twitter.com", 443} ) mul.matcher_config.time = { year_match = {2018}, year_not_match = {}, month_match = {{1}, {3}}, month_not_match = {}, day_match = {}, -- day of month day_not_match = {}, hour_match = {{6, 24}}, hour_not_match = {{12}}, minute_match = {}, minute_not_match = {}, second_match = {}, second_not_match = {}, } } |
default
Always matches.
Add new matcher
Create a new matcher_name.lua
file under resty/multiplexer/matchers
in the format of:
1 2 3 4 5 6 7 |
local _M = {} function _M.match(protocol, expected) -- return true if it's a match end return _M |
Where protocol
is the identified protocol in lowercase string, and expected
is the expected value for this matcher defined in set_rules
.