[SuProxy]Ngnix+Lua 實現SSH2,LDAP,ORACLE,SQLSERVER等TCP/IP協議分析,劫持,代理,會話及負載
阿新 • • 發佈:2020-10-27
# 目錄
[TOC]
# 前言
Nginx+Lua或Openresty已經是閘道器代理的流行架構,比如Openresty,KONG,API6等,現有大多數閘道器均在http協議上工作,雖然Nginx已經支援了TCP、IP stream,但由於對除http協議外,其他協議解析和分析模組的缺乏,使得其應用條件非常有限,除基本的代理功能外,其他一些在http下可以實現的功能,均無法在其他協議,比如在SSH2、TDS(SQL server)、TNS(ORACLE)、LDAP等協議上實現、劫持、修改、響應、負載及會話管理等能力。而對上述能力的支援,均需要建立在對各種協議的詳盡解析之上。SuProxy正是在這個條件下出現,目標是建立一個基於nginx+lua的四層的協議分析與代理庫,把nginx的穩定效能引入到除http外更廣泛的領域,並作為進一步審計,安全攻防,身份管理,協議分析的基礎元件。同時Suproxy也設計為一個能靈活的可擴充套件架構,第三方可以自行擴充套件協議解析,進一步豐富開源協議解析領域的公共知識。
SuProxy當前還在試驗階段,使用示例和文件將會陸續在部落格園,CSDN部落格,Git上釋出,歡迎大家測試,並提出意見與建議,
專案Git地址:
https://github.com/yizhu2000/suproxy
文件地址(英文):
https://github.com/yizhu2000/suproxy/blob/main/readme.md
有問題可以在此留言,或直接在git上提交
https://github.com/yizhu2000/suproxy/issues
也可以通過郵件[email protected]進行溝通
# 介紹
SuProxy是事件驅動的Lua TCP/IP代理庫,用於分析,攔截,負載平衡和會話管理。它提供以下API:
- 身份驗證攔截:在身份驗證期間讀取或更改憑據,或引入自定義身份驗證器。
- 命令輸入攔截:監視,過濾或更改命令輸入。
- 命令輸出攔截:監視,過濾或更改命令相應。
- 上下文收集:獲取網路、使用者、客戶端和伺服器資訊,例如IP、埠、版本等。
- 會話管理:將會話儲存在Redis中,提供會話列表,終止和搜尋會話的API。
- 協議解析器:解析和編碼協議資料包。
- 負載平衡:具有容錯能力的負載均衡。
當前,支援的協議包括SSH2,ORACLE TNS,SQLSERVER TDS,LDAP。
| SSH協議 | SQL伺服器 | 甲骨文 | LDAP | |
| --------------------------------------------------- | --------- | ------- | ------- | ------- |
| 獲取使用者名稱 | Y [^ 1] | Y [^ 2] | Y | Y [^ 6] |
| 獲取密碼 | Y [^ 1] | Y [^ 2] | N | Y [^ 6] |
| 變更使用者名稱 | Y | Y | Y [^ 4] | Y |
| 更改密碼 | Y | Y | N | Y |
| 第三方認證 | Y | Y | Y [^ 5] | Y |
| 獲取命令 | Y | Y | Y | Y [^ 7] |
| 得到回覆 | Y | Y | N | Y [^ 7] |
| 變更指令 | Y | Y [^ 3] | Y [^ 3] | N |
| 獲取網路上下文 (IP,埠等)。 | Y | Y | Y | Y |
| 獲取客戶端上下文 (客戶端/伺服器程式名稱 和版本等) | Y | Y | Y | N |
- [^ 1]:僅密碼認證
- [^ 2]:獲取SQL Server的使用者名稱和密碼會禁用SSL加密
- [^ 3]:更改SQL命令未經過全面測試,某些更改(例如,更改選擇命令到刪除命令)可能無法成功
- [^ 4]:不支援為oracle10更改使用者名稱
- [^ 5]:僅支援基於使用者名稱的身份驗證
- [^ 6]:不支援SSL
- [^ 7]:僅支援搜尋請求和回覆
SuProxy由純粹的Lua編寫,基於事件驅動模式,SuProxy庫的使用和擴充套件很簡單:啟動偵聽器通道並處理其事件。下面的示例顯示如何啟動SSH2偵聽器並處理SSH連線的身份驗證成功事件。
```lua
server {
listen 22;
content_by_lua_block {
local ssh=require("suproxy.ssh2"):new()
local channel=require("suproxy.channel"):new({{ip="192.168.1.135",port=22}},tds)
channel:run()
ssh.AuthSuccessEvent:addHandler(ssh,logAuth)
}
}
```
SuProxy提供基本的負載平衡功能。以下示例顯示瞭如何將多個upstream傳遞給通道。
```lua
package.loaded.my_SSHB=package.loaded.my_SSHB or
require ("suproxy.balancer.balancer"):new{
{ip="127.0.0.1",port=2222,id="local",gid="linuxServer"},
{ip="192.168.46.128",port=22,id="remote",gid="linuxServer"},
{ip="192.168.1.121",port=22,id="UBUNTU14",gid="testServer"}
}
local channel=require("suproxy.channel"):new(package.loaded.my_SSHB,ssh)
```
SuProxy可以在記憶體或Redis中收集和維護會話上下文,以下是SuProxy在ssh連線中收集的資訊
```json
{
"sid": "xxxxxxxxxxxx",
"uid": "xxxx",
"stype": "ssh2",
"uptime": 1600831353.066,
"ctime": 1600831353.066,
"ctx": {
"srvIP": "127.0.0.1",
"client": "SSH-2.0-PuTTY_Release_0.74",
"clientIP": "127.0.0.1",
"clientPort": "56127",
"username": "xxxx",
"srvPort": 2222,
"server": "SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.1"
}
}
```
# 安裝
## 下載並拷貝
從這裡https://github.com/yizhu2000/suproxy/releases 下載tar或zip包,解壓並複製到Openresty的lualib目錄。
依賴的庫包括
lua-resty-openssl> 0.6 https://github.com/fffonion/lua-resty-openssl
lua-resty-logger-socket(僅用於示例專案)https://github.com/cloudflare/lua-resty-logger-socket
## 使用LuaRocks安裝
在安裝了luarocks環境的機器上可以使用下列命令安裝本庫及其依賴項
luarocks install suproxy
## 執行測試
luajit.exe ./suproxy/test.lua
# 使用簡介
初始化Suproxy有4個步驟
1. 建立處理器(processor),傳入初始引數
2. 建立通道(channel),傳入upstream伺服器列表及上一步建立的processor
3. 處理處理器(processor)或通道(channel)的事件
4. 啟動通道(channel)
以下程式碼建立一個TNS通道並處理它的事件
```lua
--Create a TNS processor and passing server version to it
local tns=require("suproxy.tns"):new({oracleVersion=11})
--Create a channel with upstreams and TNS processor
local channel=require("suproxy.channel"):new({{ip="192.168.1.96",port=1521}},tns)
--Processing events
tns.AuthSuccessEvent:addHandler(tns,logAuth)
tns.CommandEnteredEvent:addHandler(tns,forbidden)
tns.CommandFinishedEvent:addHandler(tns,logCmd)
tns.BeforeAuthEvent:addHandler(tns,simpleUserPassOracle)
channel.OnConnectEvent:addHandler(channel,logConnect)
--start the channel
channel:run()
```
執行channel:run()之後,通道將在套接字上偵聽。然後,事件將在不同的場合觸發。使用者程式應處理這些事件以完成其工作。通道和處理器都可以觸發事件
## 處理器(processor)建立
處理器使用協議特定的解析器解析網路流。
```lua
--xxx can be ssh2,ldap,tns,tds for now
require("suproxy.xxx"):new(options)
```
使用上面的程式碼可以建立處理器(將xxx更改為處理器名稱)。目前,SSH,TDS,TNS和LDAP處理器已準備就緒。處理器是否可以具有自定義的初始選項。例如,TNS處理器可以接受兩個引數oracleVersion及swapPass,oracleVersion指定伺服器主版本,而swapPass則告訴處理器是否需要在登入時更改使用者密碼。有關詳細資訊,請參閱每個處理器的文件。
## 通道(channel)建立
通道維護客戶端和伺服器之間的連線,從套接字讀取資料並將手資料傳送到不同的協議處理器以進行進一步處理,通道還負責將資料傳送到上游伺服器。
```lua
require("suproxy.channel"):new({{ip="192.168.1.97",port=1521}},tns)
```
上面的程式碼建立了一個具有一個後端伺服器(upstream)和TNS協議處理器的通道。如果將多個上游傳遞到通道,則[預設balancer]將從這些上游中隨機選擇。請注意,只有在呼叫channel.run()之後,通道才會啟動。
**Channel.c2pSend**將資料放入客戶端代理套接字
**Channel.p2sSend**將資料傳送到代理伺服器套接字
**Channel.c2pRead**從客戶端代理套接字讀取資料
**Channel.p2cRead**從代理伺服器套接字讀取資料
如何使用它們,請參閱[閱讀和響應]
channel.new方法可以接受額外的選項來設定套接字超時
```lua
options.c2pConnTimeout -- client-proxy connect timeout default 10000
options.c2pSendTimeout -- client-proxy send timeout default 10000
options.c2pReadTimeout -- client-proxy read timeout default 3600000
options.p2sConnTimeout --proxy-server connect timeout default 10000
options.p2sSendTimeout --proxy-server send timeout default 10000
options.p2sReadTimeout --proxy-server read timeout 3600000
require("suproxy.channel"):new(upstream,processor,options)
```
## 負載均衡
SuProxy提供基本的負載均衡功能。多個上游可以傳送到通道。預設負載平衡器將從給定的上游伺服器中隨機選擇一個。如果一個上游發生故障,負載均衡將暫時將該上游暫停一會兒。建立負載均衡:呼叫suproxy.balancer.balancer.new 方法,傳入upstream list和suspendSpan(可選,預設為30秒,然後將負載均衡傳遞給通道的建構函式,如下所示:
```lua
--here use "package.loaded" to ensure balancer only init once across multiple request, cause balancer will maintain the state of those upstreams.
package.loaded.my_SSHB=package.loaded.my_SSHB or
require ("suproxy.balancer.balancer"):new({
{ip="127.0.0.1",port=2222,id="local",gid="linuxServer"},
{ip="192.168.46.128",port=22,id="remote",gid="linuxServer"},
{ip="192.168.1.121",port=22,id="UBUNTU14",gid="testServer"}
},10)
local channel=require("suproxy.channel"):new(package.loaded.my_SSHB,ssh)
```
每個上游必須包括IP,埠。ID和GID是可選欄位,ID代表此上游伺服器的識別符號,GID代表該伺服器所屬的組。事件處理器可以獲取這兩個引數。
通過實現**getBest**和**blame**方法,可以輕鬆編寫自己的平衡器。有關更多資訊,請參考balancer.balancer.lua。
## 會話資訊和會話管理
SuProxy維護會話上下文包括:伺服器IP,伺服器埠,客戶端IP,客戶端埠,連線時間,使用者名稱以及某些處理器特定的屬性,例如客戶端版本或連線字串。以下是SSH2處理器的會話上下文:
```json
{
"srvIP": "127.0.0.1",
"client": "SSH-2.0-PuTTY_Release_0.74",
"clientIP": "127.0.0.1",
"clientPort": "56127",
"username": "xxxx",
"srvPort": 2222,
"connTime":1600831353.066
"server": "SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.1"
}
```
這些資訊將被傳遞到處理器的事件處理程式。
預設情況下,會話上下文儲存在本地,因此不會在請求之間共享。提供了Redis會話管理器以支援Redis儲存。Redis會話管理器還提供了簡單的會話管理操作,例如獲取活動會話列表和終止會話。更改預設會話管理器的方法如下:
```lua
local sessionManager= require ("suproxy.session.sessionManager"):new{ip="127.0.0.1",port=6379,expire=-1,extend=false,timeout=2000}
local channel=require("suproxy.channel"):new(package.loaded.my_OracleB,tns,{sessionMan=sessionManager})
```
其中**IP**和**埠**是Redis伺服器的地址。**Expire**設定會話預設值3600的預設過期時間跨度(以秒為單位),-1表示永不過期。**extend**指示從客戶端傳送新資料包後是否延長會話租約,**timeout**指示Redis超時(以毫秒為單位),預設為5000。
LUA程式碼example.session.lua顯示瞭如何使用瀏覽器管理會話。將以下行新增到nginx config進行測試。
```nginx
server {
listen 80;
server_name localhost;
...
location /suproxy/manage{
content_by_lua_file lualib/suproxy/example/session.lua;
}
}
```
http://localhost/suproxy/manage/session/all 列出所有會話
http://localhost/suproxy/manage/session/clear 殺死所有會話
http://localhost/suproxy/manage/session/kill?sid=xxxx 通過sessionID終止會話
http://localhost/suproxy/manage/session/kill?uid=xxxx 通過uid終止會話
http://localhost/suproxy/manage/session/get?sid=xxxx 通過sessionID獲取會話
http://localhost/suproxy/manage/session/get?uid=xxxx 通過uid殺死會話
## Event Handling
通道和處理器都會觸發事件,為事件新增處理程式的方法如下
```lua
event:addHandler(context,handler)
```
context表示程式將在哪個物件上執行,處理程式可以訪問上下文物件中定義的引數(self物件),handler是處理事件的函式。
典型的處理程式如下所示
```lua
function handler(context,eventSource,[other event params])
-- handler logic here
end
```
處理程式至少會接收到兩個引數:context和eventSource。context是由addHandler方法定義的執行上下文,eventSource是觸發此事件的物件,大多數情況下是處理器本身。
### NoReturnEvent 和 ReturnedEvent
事件有兩種:**NoReturnEvent**和**ReturnedEvent**,**ReturnedEvents**的處理程式可以返回值,而NoReturn Event的處理程式則不能。NoReturnEvent可以有多個處理程式,但Returned Event可以只有一個。將處理程式新增到已經具有處理程式的Returned Event中將覆蓋舊的處理程式。在相同情況下向NoReturnEvent新增更多處理程式將形成一個處理程式鏈,該鏈中的每個處理程式將一個接一個地執行。
### addHandler 和 setHandler
對於NoReturnEvent,event:addHandler將新處理程式追加到處理程式鏈,setHandler方法清除該鏈並將處理程式追加到頭部。呼叫setHandler方法可確保該事件有且僅有一個處理程式。
### Channel Event
#### OnConnectEvent
此事件在剛建立連線時觸發。handler引數為連線相關資訊:
```lua
{
clientIP, --client ip address
clientPort, --client port if tcp is used
srvIP, --upstream server ip
srvPort=serverPort --upstream server port if tcp is used
}
```
這是處理通道的OnConnectEvent的示例,該事件將連線資訊寫入日誌檔案:
```lua
local function logConnect(context,source,connInfo)
local rs={
os.date("%Y.%m.%d %H:%M:%S", ngx.time()).."\t" ,
connInfo.clientIP..":"..connInfo.clientPort.."\t",
"connect to ",
connInfo.srvIP..":"..connInfo.srvPort.."\r\n"
}
print(table.concat(rs))
end
```
結果如下
```
2020.09.24 18:28:03 127.0.0.1:60486 connect to 127.0.0.1:2222
```
### Processor Events
不同的處理器可能具備不同的事件,但是所有處理器都實現以下的事件
- BeforeAuthEvent
- AuthSuccessEvent
- AuthFailEvent
- CommandEnteredEvent(對於ssh2Processor,這由commandCollector而非processor觸發)
- CommandFinishedEvent(對於ssh2Processor,這由commandCollector而非processor觸發)
- ContextUpdateEvent
每個處理器事件將不同的引數和一個會話上下文(session context)物件傳遞給其處理程式。會話上下文物件包含該會話中處理器收集的所有資訊。以下是SSH2.0處理器中的典型會話上下文:
```
{
"srvIP": "127.0.0.1",
"client": "SSH-2.0-PuTTY_Release_0.74",
"clientIP": "127.0.0.1",
"clientPort": "56127",
"username": "root"
"srvPort": 2222,
"server": "SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.1"
}
```
會話上下文資訊隨處理器型別和連線階段的不同而有所不同。某些處理器沒有“client”欄位或“server”欄位(如LDAP),有些處理器有附加資訊,例如連線字串(比如TNS)。另外,在連線的某個階段,使用者名稱尚未傳輸,因此在此時,username不會出現在上下文資訊中。但是,上下文至少包含srvIP,srvPort,clientIP,clientPort,connTime,並且username將在使用者身份驗證後立即新增到上下文中。
#### BeforeAuthEvent
此事件在使用者身份驗證之前觸發,這是交換使用者憑據的絕佳時機。傳遞給其處理程式的引數是**認證憑證**和**會話上下文**物件,憑證物件定義如下(對於TNS處理器,除非滿足某些版本條件,否則不提供密碼欄位)。
```lua
{
username,--string, username entered by user
password --string, password entered by user
}
```
此事件的事件處理程式可以返回新的憑據,如果返回了新的憑據,則新的憑據將轉發到上游,而不是舊的憑據。
下面是將原始憑證傳遞到遠端伺服器並通過OAUTH2.0從中獲取新憑證的示例(此例項中用到的OAUTH驗證模組ssoProcessors尚在實驗階段,使用者完全可以用成熟的OAUTH2.0客戶端替代)
```lua
local function oauth(context,source,cred,session)
--show how to get password with oauth protocal,using username as code, an app should be add and a password attributes should be add
local param={
ssoProtocal="OAUTH",
validate_code_url="http://xxxxxxxxxxxx/oauth2/token",
profile_url="http://xxxxxxxxxxxxx/oauth2/userinfo",
client_secret="xxxxxxxxxxx",
}
local authenticator=authFactory.getAuthenticator(param)
local result=authenticator:valiate({username=cred.username,password=cred.password})
local newCred={}
if result.status==ssoProcessors.CHECK_STATUS.SUCCESS then
--confirm oauth password attributes correctly configged
newCred.username=result.accountData.user
newCred.password=result.accountData.attributes.password
end
if not newCred then return nil,"can not get cred from remote server" end
return newCred
end
```
#### OnAuthEvent
該事件在認證通過時觸發。這是交換使用者憑證或引入自定義身份驗證的絕佳時機。傳遞給其處理程式的引數是**憑據**(使用者名稱和密碼)和**會話上下文**物件,憑據物件定義如下(對於TNS處理器,除特定版本外無密碼欄位)。
BeforeAuthEvent和OnAuthEvent之間的區別在於:BeforeAuthEvent在憑據的任何部分(如使用者名稱)傳輸到伺服器之前觸發,而OnAuthEvent在身份驗證真正發生並將密碼傳輸到伺服器時觸發。對於某些協議(例如LDAP和TDS),這兩個時機是相同的;對於處理器(例如SSH2或TNS),使用者名稱是在密碼之前傳輸的。如果需要為這些通道更改使用者名稱,則必須在BeforeAuthEvent處理程式中準備好新的使用者名稱。
此事件的事件處理程式可以返回驗證結果,錯誤訊息和新憑據。使用新憑據欄位在LDAP和TDS處理器中可以替代BeforeAuthEvent
本示例說明如何引入第三方認證。
```lua
local function authenticator(context,source,credential,session)
--OAUTH or other auth protocol should be used to swap real credential in real world
local result=credential.username=="test" and credential.password=="test"
if result then
return result
else
local message="login with "..credential.username.." failed"
return result,message
end
end
ssh.OnAuthEvent:addHandler(tns,authenticator)
```
#### AuthSuccessEvent
使用者身份驗證成功完成後觸發此事件,這是寫入日誌檔案的正確時機。傳遞給其處理程式的引數是**使用者名稱**和 **會話上下文物件**
下面是將登入操作寫入日誌檔案的示例。
```lua
local function logAuth(context,source,username,session)
local rs={
os.date("%Y.%m.%d %H:%M:%S", ngx.time()).."\t" ,
session.clientIP..":"..session.clientPort.."\t",
username.."\t",
"login with ",
(session and session.client) and session.client or "unknown client",
(session and session.clientVersion) and session.clientVersion or ""
}
print(table.concat(rs))
end
```
結果如下
```
2020.09.24 19:03:40 127.0.0.1:60844 root login with SSH-2.0-PuTTY_Release_0.74
```
#### AuthFailEvent
使用者身份驗證失敗時觸發此事件,這是寫入日誌檔案的正確時機。傳遞給其處理程式的引數是**failInfo**和**會話上下文物件**
failInfo定義如下
```lua
{
username, --string, failed username
message --string, fail message passed
}
```
下面是將登入失敗操作寫入日誌檔案的示例。
```lua
local function logAuthFail(context,source,failInfo,session)
local rs={
os.date("%Y.%m.%d %H:%M:%S", ngx.time()).."\t" ,
session.clientIP..":"..session.clientPort.."\t",
failInfo.username.."\t",
"login fail, fail message: ",
failInfo.message
}
print(table.concat(rs))
end
```
結果如下
```
2020.09.24 19:27:48 127.0.0.1:61020 zhuyi login fail, fail message: wrong password
```
#### CommandEnteredEvent
該事件將在命令傳送到伺服器之前觸發。如果是資料庫或LDAP協議,則命令是SQL命令或LDAP請求;如果是SSH連線,則命令是shell命令。這是執行命令檢查或更改某些命令的最佳時機。
傳遞給其處理程式的引數是**命令字串**和**會話上下文物件**。處理程式可能會返回**新命令**或**錯誤訊息**。如果返回新命令,則新命令將取代原始命令被髮送到伺服器。如果返回錯誤訊息,則該命令將不會執行,處理器將通知客戶端錯誤訊息。(通知客戶端的方式隨處理器而異,有些可能會提示訊息,有些可能沒有)以下是檢查命令並禁止命令中使用某些關鍵字的示例。
```lua
local function forbidden(context,source,command,session)
if command:match("forbidden")then
print("forbidden command triggered")
return nil,{message=command.." is a forbidden command"}
end
return command
end
```
以下是sql執行對sqlserver的影響
![](https://img2020.cnblogs.com/blog/2276/202010/2276-20201027153936028-1152863388.png)
以下是putty的響應
![](https://img2020.cnblogs.com/blog/2276/202010/2276-20201027154108388-138115565.png)
#### CommandFinishedEvent
從伺服器回覆命令時觸發此事件,如果是資料庫/ LDAP協議,則命令可能是SQL/ LDAP請求,如果是SSH連線,則命令可能是shell命令。這是編寫命令及其在檔案中回覆的最佳時機。
傳遞給其處理程式的引數是**命令字串**,**回覆字串**和**上下文物件**。某些處理器中可能沒有答覆字串(在當前版本中未收集TNS(ORACLE)答覆),以下是記錄命令和答覆的示例
```lua
local function logCmd(context,source,command,reply,session)
local rs={
os.date("%Y.%m.%d %H:%M:%S", ngx.time()).."\t" ,
session.clientIP..":"..session.clientPort.."\t",
username.."\t",
command.."\r\n"
}
if reply then
rs[#rs+1]="------------------reply--------------------\r\n"
rs[#rs+1]=reply
rs[#rs+1]="\r\n----------------reply end------------------\r\n")
end
print(table.concat(rs))
end
```
日誌檔案如下
```
2020.09.24 19:28:43 127.0.0.1:61020 root ls
------------------reply--------------------
libc6_2.31-0ubuntu8+lp1871129~1_amd64.deb
----------------reply end------------------
```
#### ContextUpdateEvent
處理器在會話中收集新資訊時觸發此事件。聽此事件,程式可以獲取上下文資訊並構造自己的會話狀態。以下示例顯示如何使用resty.redis庫將上下文記錄到Redis中
```lua
function contextHandler(self,source,ctx)
local red = require ("resty.redis"):new()
red:set_timeouts(10000, 10000, 10000)
local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
ngx.log(ngx.ERR,"failed to connect: ", err)
return
end
red:set(ctx.clientIP..ctx.clientPort,cjson.encode(ctx))
end
```
### Parser 及 Parser Events
每個處理器都有2個解析器c2pParser和s2pParser。c2pParser負責解析客戶端與代理間的通訊,s2pParser負責解析服務端和代理間的通訊。當這些解析器成功解析一個數據包時,將觸發一個解析器事件。使用者程式可以偵聽這些事件以進行攔截,更改傳送到伺服器的資料包或停止轉發。
傳遞給資料包事件處理程式的引數是解析器構造的資料包,其中包含協議特定的資訊。處理程式可以獲取或設定其欄位以獲取或更改傳送到伺服器的內容。例如,可以像這樣更改TNS連線中的版本。
```lua
function _M:ConnectHandler(src,packet)
packet:setTnsVersion(314)
packet:pack()
end
```
#### 攔截,更改和停止轉發
設定packet的allBytes屬性可以修改包的內容,這意味著計算位元組長度,重新生成資料包頭和將傳送到伺服器的位元組流。每次更改欄位時,都必須呼叫packet.pack()。位元組流可以通過packet.allBytes訪問。
如果我們不希望我們的資料包再轉發。(例如,處理程式已作出手動響應),只需將packet.allBytes欄位設定為“”即可。
```
p.allBytes=""
p:pack()
```
#### 讀取資料與響應
處理程式可以使用Channel提供的4種方法來讀取並響應客戶端或伺服器:
**Channel.c2pSend**將資料傳送到c2p套接字
**Channel.p2sSend**把資料P2S插座
**Channel.c2pRead**從c2p套接字讀取資料
**Channel.p2cRead**讀取P2S套接字資料
這四個方法包裝了lua stream socket。引數和返回值與socket.receive和send相同,xxxSendMehod採用一個引數:要傳送的位元組。接收方法的輸入引數是讀取的長度或模式。
以下是攔截LDAP SearchRequest和構造響應的示例。
```lua
local function ldap_SearchRequestHandler(context,src,p)
if context.command:match("pleasechangeme") then
local packets=require("suproxy.ldap.ldapPackets")
local response=packets.SearchResultEntry:new()
local done=packets.SearchResultDone:new()
response.objectName="cn=admin,dc=www,dc=test,dc=com"
response.messageId=p.messageId
response.attributes={
{attrType="objectClass",values={"posixGroup","top"}},
{attrType="cn",values={"group"}},
{attrType="memberUid",values={"haha","zhuyi","joeyzhu"}},
{attrType="gidNumber",values={"44789"}},
{attrType="description",values={"group"}}
}
done.resultCode=packets.ResultCode.success
done.messageId=p.messageId
response:pack() done:pack()
context.channel:c2pSend(response.allBytes..done.allBytes)
--stop forwarding
p.allBytes=""
end
end
local ldap=require("suproxy.ldap"):new()
ldap.c2pParser.events.SearchRequest:addHandler(ldap,ldap_SearchRequestHandler)
```
# Examples
example.gateway演示為TNS,TDS,SSH2,LDAP協議實現了一個簡單的閘道器。要測試此演示,請修改nginx配置,將以下部分新增到您的配置檔案中。確保commands.log檔案路徑有效。
```nginx
stream {
init_by_lua_file lualib/suproxy/init.lua;
lua_code_cache off;
#mock logserver if you do not have one
server {
listen 12080;
content_by_lua_block {
ngx.log(ngx.DEBUG,"logserver Triggerred")
local reqsock, err = ngx.req.socket(true)
reqsock:settimeout(100)
while(not err) do
local command,err=reqsock:receive()
if(err) then ngx.exit(0) end
local f = assert(io.open("/data/logs/commands.log", "a"))
if(command) then
f:write(command .. "\n")
f:close()
end
end
}
}
#listen on ports
server {
listen 389;
listen 1521;
listen 22;
listen 1433;
content_by_lua_file lualib/suproxy/example/gateway.lua;
}
}
#Session manager interfaces. if you want to view and manage your session
#over http, this should be set.
http {
include mime.types;
lua_code_cache off;
server {
listen 80;
server_name localhost;
default_type text/html;
location /suproxy/manage{
content_by_lua_file lualib/suproxy/example/session.lua;
}
}
```
關於會話管理的介面,請參閱[會話管理。](#會話資訊和會