這是一個用 golang 寫的端口轉發程序,但她不僅僅支持轉發端口數據,她還支持轉發數據流。即它可以將一種流協議數據轉發爲另外一種流協議
例如你可以將 tcp 數據流轉爲 websocket 數據流,反之亦然。
index:
請使用 -conf 傳入設定檔案路徑運行程式
./streamf -conf your_configure_path
basic 是最基礎的轉發器,她就是網路上隨處可見 tcp 端口轉發程式
// 這是一個端口轉發例子
{
// 設置 basic 模式的轉發目標
dialer: [
{
// 在 listener 中使用這個 tag 來指定這個 dialer
tag: 'tcp',
// 連接超時時間
timeout: '200ms',
// 要連接的 URL, 可以使用可選參數 network 和 addr 來覆蓋 URL 中的連接地址
url: 'basic://example.com?addr=localhost:2000',
},
{
tag: 'tcp+tls',
timeout: '200ms',
// +tls 指定使用 tls 連接
url: 'basic+tls://example.com',
// 明確指定連接地址
network: 'tcp',
addr: 'localhost:2443',
// 設置爲 true 則不驗證伺服器證書
allowInsecure: true,
},
],
listener: [
// 這個 listener 接收 tcp 連接
{
network: 'tcp',
addr: ':4000',
dialer: {
// 將數據轉發給 tag 爲 'tcp+tls' 的 dialer
tag: 'tcp+tls',
// 在一端的連接端口後,等待多久再關閉另外一端
// (用於等待未傳輸完的數據,傳輸完成)
close: '1s',
},
},
// 這個 listener 接收 tls 連接
{
network: 'tcp',
addr: ':4443',
dialer: {
tag: 'tcp',
close: '1s',
},
// 啓用 tls
tls: {
certFile: 'test.crt',
keyFile: 'test.key',
},
},
],
}
curl https://127.0.0.1:4443/test/tls http://127.0.0.1:4000/test/tcp -k
http 模式能夠支持 http 入棧和出棧流:
- websocket 在 http1.1 中被支持,它支持雙向數據流
- http2.0 對普通請求也支持了流
{
dialer: [
{
tag: 'wss',
timeout: '200ms',
url: 'wss://example.com',
addr: 'localhost:2443',
allowInsecure: true,
},
{
tag: 'ws',
timeout: '200ms',
url: 'ws://example.com/http/ws',
addr: 'localhost:4000',
access: 'test access token',
},
{
tag: 'h2-post',
url: 'https://example.com/http2',
addr: 'localhost:4443',
allowInsecure: true,
},
{
tag: 'h2c-put',
url: 'http://example.com/http2',
addr: 'localhost:4000',
method: 'PUT',
access: 'test access token',
},
],
local router = [
{
// 接受 WebSocket 連接
method: 'WS',
// URL match pattern
pattern: '/http/ws',
dialer: {
tag: 'wss',
close: '1s',
},
// access 用於指定訪問 token,如果設置則只有 token 匹配的流量才會被轉發
access: 'test access token',
},
{
// 接受 POST 請求
method: 'POST',
pattern: '/http2',
dialer: {
tag: 'ws',
close: '1s',
},
},
{
// 接受 PUT 請求
method: 'PUT',
pattern: '/http2',
dialer: {
tag: 'h2-post',
close: '1s',
},
access: 'test access token',
},
{
// 接受 PATCH 請求
method: 'PATCH',
pattern: '/http2',
dialer: { tag: 'h2c-put' },
},
],
listener: [
{
network: 'tcp',
addr: ':4000',
// 指定使用 http 模式
mode: 'http',
// 爲 http 指定路由
router: router,
},
{
network: 'tcp',
addr: ':4443',
mode: 'http',
router: router,
tls: {
certFile: 'test.crt',
keyFile: 'test.key',
},
},
],
}
curl -X PATCH http://127.0.0.1:4000/http2 -d 'abc=123'
流入和流出流量可以是 http1.x,但是 http1.x 並不支持數據流,它可能會等到請求或響應流量傳輸完畢才傳輸到對端。通常不建議使用 http1.x
從 v0.0.3 開始 websocket 支持 fast 屬性,如果設置爲 true,它將只使用 websocket 建立連接,在連接建立後直接使用 tcp 傳輸數據
從 v0.0.4 開始 http/websocket dialer 支持 header屬性( map[string][]string ) 用於設置自定義的 http header
默認流入和流出流量都使用 tcp,但是你可以設置 network 爲 unix,這樣可以啓用 unix socket,它比經過網卡的 socket 更高效,但是它只在 linux 下被支持
{
dialer: [
{
tag: 'tcp',
timeout: '200ms',
url: 'basic://example.com?addr=localhost:2000',
},
{
tag: 'unix+tls',
timeout: '200ms',
url: 'basic+tls://example.com',
network: 'unix',
addr: '@streamf/unix.socket',
allowInsecure: true,
},
],
listener: [
{
network: 'unix',
addr: '@streamf/unix.socket',
dialer: {
tag: 'tcp',
close: '1s',
},
tls: {
certFile: 'test.crt',
keyFile: 'test.key',
},
},
{
network: 'tcp',
addr: ':4000',
dialer: {
tag: 'unix+tls',
close: '1s',
},
},
],
}
curl http://127.0.0.1:4000/
curl --abstract-unix-socket streamf/unix.socket https://example.com/ -k
pipe 只能在同一進程中被使用,它直接在內存中模擬一個 net.Conn 因此效率非常高。它用於在進程內轉換流協議,例如 cloudflare 不支持顯示傳輸 http2協議,此時先將 http2 轉換爲 webscoekt 以經過 cloudflare 網路傳輸,在伺服器上將 websocekt 轉爲 http2 給伺服器 http2 服務。
{
dialer: [
{
tag: 'tcp',
timeout: '200ms',
url: 'basic://example.com?addr=localhost:2000',
},
{
tag: 'pipe+tls',
timeout: '200ms',
url: 'basic+tls://example.com',
network: 'pipe',
addr: 'streamf/pipe.socket',
allowInsecure: true,
},
],
listener: [
{
network: 'pipe',
addr: 'streamf/pipe.socket',
dialer: {
tag: 'tcp',
close: '1s',
},
tls: {
certFile: 'test.crt',
keyFile: 'test.key',
},
},
{
network: 'tcp',
addr: ':4000',
dialer: {
tag: 'pipe+tls',
close: '1s',
},
},
],
}
curl http://127.0.0.1:4000/
有時我們需要將一個內網的服務映射到公網這時可以使用 portal-bridge 功能。
首先需要在有公網的伺服器上將一個 listener 的 mode 設置爲 'portal' 同時保證其 tag 唯一。之後可以在 dialer 中通過將 network 設置爲 'portal', 將 addr 設置爲 listener 的 tag 來創建連接。最後在內網伺服器上設置 bridge 數組反向連接 listener
// 這裏爲了測試方便將 portal/bridge 設置到了一起。通常真實環境 'portal' 位於公網伺服器,'bridge' 位於內網伺服器
local bridge = {
dialer: [
// 連接要發佈的服務
{
tag: 'tcp',
timeout: '200ms',
url: 'basic://example.com?addr=localhost:2000',
},
],
// 'bridge' 會連接 'portal' 網路
bridge: [
{
timeout: '200ms',
url: 'basic://',
network: 'pipe',
addr: 'streamf/pipe.socket',
// 提供 'bridge' 連接這個 dialer 到 'portal'
dialer: {
tag: 'tcp',
close: '1s',
},
},
],
};
// 這裏爲了測試方便將 portal/bridge 設置到了一起。通常真實環境 'portal' 位於公網伺服器,'bridge' 位於內網伺服器
local portal = {
dialer: [
// 這個 dialer 獲取到 tag 爲 'listener portal' 提供的連接
{
tag: 'portal',
timeout: '200ms',
url: 'basic://',
network: 'portal',
// connect portal tag
addr: 'listener portal',
},
],
listener: [
// 設置 mode 爲 'portal' 啓用 portal 網路
{
// 在 'portal' 模式下的監聽器必須保證 tag 唯一
tag: 'listener portal',
network: 'pipe',
addr: 'streamf/pipe.socket',
mode: 'portal',
portal: {
// 連接超時時間
// Default 500ms
timeout: '200ms',
// 空閒連接每個多久發送一次心跳
heart: '40s',
// 等待心跳響應的超時時間
heartTimeout: '1s',
},
},
// 這個 listener 使用 'listener portal' 的連接提供服務
{
network: 'tcp',
addr: ':4000',
dialer: {
tag: 'portal',
close: '1s',
},
},
],
};
{
dialer: bridge.dialer + portal.dialer,
listener: portal.listener,
bridge: bridge.bridge,
}
portal/bridge 也可以支持 http,並且 portal 模式的 listener 可以在 router 中可以混用 portal 和 普通的流量轉發
local bridge = {
dialer: [
{
tag: 'tcp',
timeout: '200ms',
url: 'basic://example.com?addr=localhost:2000',
},
],
bridge: [
// websocket connect portal
{
timeout: '200ms',
url: 'ws://example.com/http/ws',
network: 'pipe',
addr: 'streamf/pipe.socket',
access: 'test access token',
dialer: {
tag: 'tcp',
close: '1s',
},
},
// http2 post connect portal
{
timeout: '200ms',
url: 'http://example.com/http2',
method: 'POST',
network: 'pipe',
addr: 'streamf/pipe.socket',
access: 'test access token',
dialer: {
tag: 'tcp',
close: '1s',
},
},
],
};
local portal = {
dialer: [
// serve by portal ws
{
tag: 'portal-ws',
timeout: '200ms',
url: 'basic://',
network: 'portal',
addr: 'listener-portal-ws',
},
// serve by portal http2
{
tag: 'portal-http2',
timeout: '200ms',
url: 'basic://',
network: 'portal',
addr: 'listener-portal-http2',
},
// serve direct
{
tag: 'portal-direct',
timeout: '200ms',
url: 'http://example.com/http/direct',
network: 'pipe',
addr: 'streamf/pipe.socket',
},
],
listener: [
{
network: 'pipe',
addr: 'streamf/pipe.socket',
mode: 'http',
router: [
// websocket portal
{
method: 'WS',
pattern: '/http/ws',
access: 'test access token',
portal: {
tag: 'listener-portal-ws',
timeout: '200ms',
heart: '40s',
heartTimeout: '1s',
},
},
// http2 portal
{
method: 'POST',
pattern: '/http2',
access: 'test access token',
portal: {
tag: 'listener-portal-http2',
timeout: '200ms',
heart: '40s',
heartTimeout: '1s',
},
},
// direct router
{
pattern: '/http/direct',
dialer: {
tag: 'tcp',
close: '1s',
},
},
],
},
// portal-ws ingress
{
network: 'tcp',
addr: ':4000',
dialer: {
tag: 'portal-ws',
close: '1s',
},
},
// portal-http2 ingress
{
network: 'tcp',
addr: ':4001',
dialer: {
tag: 'portal-http2',
close: '1s',
},
},
// portal-direct ingress
{
network: 'tcp',
addr: ':4002',
dialer: {
tag: 'portal-direct',
close: '1s',
},
},
],
};
{
dialer: bridge.dialer + portal.dialer,
listener: portal.listener,
bridge: bridge.bridge,
}
curl http://127.0.0.1:4000 http://127.0.0.1:4001 http://127.0.0.1:4002
從 v0.0.4 開始 basic 的 listener/dialer 支持 udp,這個功能用於實現 udp over tcp。這在某些時候是有用的,例如防火牆阻擋了 udp,或者 udp 被限制了速度,這時可以使用此功能來用 tcp 傳輸 udp 數據,但是注意這會降低原本 udp 程序的傳輸效率
// 這裏演示了如何實現 udp over tcp
// 這是伺服器上的配置,從tcp解析出udp,傳輸到目的服務
local server = {
dialer: [
{
tag: 'google-dns',
timeout: '200ms',
url: 'basic://8.8.8.8:53',
network: 'udp',
udp: {
frame: 16,
timeout: '60s',
size: 1500,
},
},
],
listener: [
{
network: 'pipe',
addr: 'streamf/pipe.socket',
dialer: {
tag: 'google-dns',
close: '1s',
},
},
],
};
// 這是一個反向代理。它接收udp,將其打包,並使用tcp傳輸到伺服器。
local proxy = {
dialer: [
{
tag: 'udp-over-tcp',
timeout: '200ms',
url: 'basic://',
network: 'pipe',
addr: 'streamf/pipe.socket',
},
],
listener: [
{
network: 'udp',
addr: ':4000',
dialer: {
tag: 'udp-over-tcp',
close: '1s',
},
udp: {
frame: 16,
timeout: '60s',
size: 1500,
},
},
],
};
{
dialer: server.dialer + proxy.dialer,
listener: server.listener + proxy.listener,
logger: {
source: true,
},
}
從 v0.0.5 開始支持 udp 數組用於指定一組 udp 端口映射
{
udp:[
{
// udp listen host:port
listen:":1053",
// remote target addr
to:"8.8.8.8:53",
// udp max frame length, default 1024*2
size:1500,
// udp timeout, default 3m
timeout:"3m",
},
],
}
logger 用於設定日誌
{
logger: {
// 日誌等級 'debug' 'info' 'warn' 'error'
level: 'info',
// 是否顯示代碼檔案
source: false,
},
}
pool 爲連接設置讀寫緩存
{
pool: {
// 讀寫緩存大小
size: 1024 * 32,
// 最多緩存多少個空閒的內存塊
cache: 128,
},
}
你可以在 http 的 listener 中註冊 'API' 路由,她提供了一些 http 頁面用於查詢服務器運行狀態
{
listener: [
{
network: 'tcp',
addr: ':4000',
mode: 'http',
router: [
{
method: 'API',
pattern: '/api',
auth: [
{
username: 'dev',
password: '123',
},
],
},
],
},
],
}
fs 用於將一個操作系統目錄以靜態 http 的形式發佈到 http listener 的路由中,這不是這個程式的本職工作但這個需求很常見並且用 golang 實現毫不費力,所以也一起集成了此功能
local auth = [
{
username: 'dev',
password: '123',
},
];
{
listener: [
{
network: 'tcp',
addr: ':4000',
mode: 'http',
router: [
{
method: 'FS',
pattern: '/fs',
fs: '/tmp',
auth: auth,
},
{
method: 'API',
pattern: '/',
auth: auth,
},
],
},
],
}
docker run \
-v Your_Configure_Path:/data/streamf.jsonnet:ro \
-d king011/streamf:v0.0.1