实现了一个端口服务复用的透明代理,可以在同一个端口上运行多个协议。根据每次连接中客户端发起的首个请求检测协议,根据协议或各种条件选择代理的上游。
需要打一个补丁。由@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
.