Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e23f8e06a2 | ||
|
|
3367cccf8c | ||
|
|
e6e9656ec6 | ||
|
|
73d78489b5 | ||
|
|
63510eda5e | ||
|
|
a2475d3722 | ||
|
|
bd724f43c0 | ||
|
|
ff27ee512a | ||
|
|
1ae0455fd5 | ||
|
|
96716561e0 | ||
|
|
ddfb2ce2af | ||
|
|
90542be7f2 | ||
|
|
fe2ff6aa69 | ||
|
|
bd92e716ce | ||
|
|
e0712f1d51 | ||
|
|
4581d0babe | ||
|
|
b0106c9941 | ||
|
|
eeb234552c | ||
|
|
cbbca0353e | ||
|
|
f004d17522 | ||
|
|
d2d4fa723a | ||
|
|
d7d3437d3c | ||
|
|
ce9f0145da | ||
|
|
7441d24aea | ||
|
|
99403d3150 | ||
|
|
1041d5fde1 | ||
|
|
00d88d7fbf |
113
README.ja.md
Normal file
113
README.ja.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# 
|
||||
|
||||
[![License][1]][2]
|
||||
|
||||
[1]: https://img.shields.io/badge/License-MPL_2.0-brightgreen.svg
|
||||
[2]: LICENSE
|
||||
|
||||
OpenGFW は、Linux 上の [GFW](https://en.wikipedia.org/wiki/Great_Firewall) の柔軟で使いやすいオープンソース実装であり、多くの点で本物より強力です。これは家庭用ルーターでできるサイバー主権です。
|
||||
|
||||
> [!CAUTION]
|
||||
> このプロジェクトはまだ開発の初期段階です。使用は自己責任でお願いします。
|
||||
|
||||
> [!NOTE]
|
||||
> 私たちはこのプロジェクト、特により多くのプロトコル用のアナライザーの実装を手伝ってくれるコントリビューターを探しています!!!
|
||||
|
||||
## 特徴
|
||||
|
||||
- フル IP/TCP 再アセンブル、各種プロトコルアナライザー
|
||||
- HTTP、TLS、DNS、SSH、SOCKS4/5、その他多数
|
||||
- Shadowsocks の"完全に暗号化されたトラフィック"の検出、
|
||||
など。 (https://gfw.report/publications/usenixsecurity23/data/paper/paper.pdf)
|
||||
- トロイの木馬キラー (https://github.com/XTLS/Trojan-killer) に基づくトロイの木馬 (プロキシプロトコル) 検出
|
||||
- [WIP] 機械学習に基づくトラフィック分類
|
||||
- IPv4 と IPv6 をフルサポート
|
||||
- フローベースのマルチコア負荷分散
|
||||
- 接続オフロード
|
||||
- [expr](https://github.com/expr-lang/expr) に基づく強力なルールエンジン
|
||||
- 柔軟なアナライザ&モディファイアフレームワーク
|
||||
- 拡張可能な IO 実装(今のところ NFQueue のみ)
|
||||
- [WIP] ウェブ UI
|
||||
|
||||
## ユースケース
|
||||
|
||||
- 広告ブロック
|
||||
- ペアレンタルコントロール
|
||||
- マルウェア対策
|
||||
- VPN/プロキシサービスの不正利用防止
|
||||
- トラフィック分析(ログのみモード)
|
||||
|
||||
## 使用方法
|
||||
|
||||
### ビルド
|
||||
|
||||
```shell
|
||||
go build
|
||||
```
|
||||
|
||||
### 実行
|
||||
|
||||
```shell
|
||||
export OPENGFW_LOG_LEVEL=debug
|
||||
./OpenGFW -c config.yaml rules.yaml
|
||||
```
|
||||
|
||||
### 設定例
|
||||
|
||||
```yaml
|
||||
io:
|
||||
queueSize: 1024
|
||||
local: true # FORWARD チェーンで OpenGFW を実行したい場合は false に設定する
|
||||
|
||||
workers:
|
||||
count: 4
|
||||
queueSize: 16
|
||||
tcpMaxBufferedPagesTotal: 4096
|
||||
tcpMaxBufferedPagesPerConn: 64
|
||||
udpMaxStreams: 4096
|
||||
```
|
||||
|
||||
### ルール例
|
||||
|
||||
サポートされているすべてのプロトコルと、それぞれのプロトコルがどのようなフィールドを持っているかについてのドキュメントはまだ準備できておりません。
|
||||
一旦は、"analyzer "ディレクトリの下にあるコードを直接チェックする必要があります。
|
||||
|
||||
式言語の構文については、[Expr 言語定義](https://expr-lang.org/docs/language-definition)を参照してください。
|
||||
|
||||
```yaml
|
||||
- name: block v2ex http
|
||||
action: block
|
||||
expr: string(http?.req?.headers?.host) endsWith "v2ex.com"
|
||||
|
||||
- name: block v2ex https
|
||||
action: block
|
||||
expr: string(tls?.req?.sni) endsWith "v2ex.com"
|
||||
|
||||
- name: block shadowsocks
|
||||
action: block
|
||||
expr: fet != nil && fet.yes
|
||||
|
||||
- name: block trojan
|
||||
action: block
|
||||
expr: trojan != nil && trojan.yes
|
||||
|
||||
- name: v2ex dns poisoning
|
||||
action: modify
|
||||
modifier:
|
||||
name: dns
|
||||
args:
|
||||
a: "0.0.0.0"
|
||||
aaaa: "::"
|
||||
expr: dns != nil && dns.qr && any(dns.questions, {.name endsWith "v2ex.com"})
|
||||
|
||||
- name: block google socks
|
||||
action: block
|
||||
expr: string(socks?.req?.addr) endsWith "google.com" && socks?.req?.port == 80
|
||||
```
|
||||
|
||||
#### サポートされるアクション
|
||||
|
||||
- `allow`: 接続を許可し、それ以上の処理は行わない。
|
||||
- `block`: 接続をブロックし。
|
||||
- `drop`: UDP の場合、ルールのトリガーとなったパケットをドロップし、同じフローに含まれる以降のパケットの処理を継続する。TCP の場合は、`block` と同じ。
|
||||
- `modify`: UDP の場合、与えられた修飾子を使って、ルールをトリガしたパケットを修正し、同じフロー内の今後のパケットを処理し続ける。TCP の場合は、`allow` と同じ。
|
||||
27
README.md
27
README.md
@@ -3,10 +3,10 @@
|
||||
[![License][1]][2]
|
||||
|
||||
[1]: https://img.shields.io/badge/License-MPL_2.0-brightgreen.svg
|
||||
|
||||
[2]: LICENSE
|
||||
|
||||
**[中文文档](README.zh.md)**
|
||||
**[日本語ドキュメント](README.ja.md)**
|
||||
|
||||
OpenGFW is a flexible, easy-to-use, open source implementation of [GFW](https://en.wikipedia.org/wiki/Great_Firewall) on
|
||||
Linux that's in many ways more powerful than the real thing. It's cyber sovereignty you can have on a home router.
|
||||
@@ -20,10 +20,12 @@ Linux that's in many ways more powerful than the real thing. It's cyber sovereig
|
||||
## Features
|
||||
|
||||
- Full IP/TCP reassembly, various protocol analyzers
|
||||
- HTTP, TLS, DNS, SSH, and many more to come
|
||||
- "Fully encrypted traffic" detection for Shadowsocks,
|
||||
etc. (https://gfw.report/publications/usenixsecurity23/data/paper/paper.pdf)
|
||||
- [WIP] Machine learning based traffic classification
|
||||
- HTTP, TLS, DNS, SSH, SOCKS4/5, and many more to come
|
||||
- "Fully encrypted traffic" detection for Shadowsocks,
|
||||
etc. (https://gfw.report/publications/usenixsecurity23/data/paper/paper.pdf)
|
||||
- Trojan (proxy protocol) detection based on Trojan-killer (https://github.com/XTLS/Trojan-killer)
|
||||
- [WIP] Machine learning based traffic classification
|
||||
- Full IPv4 and IPv6 support
|
||||
- Flow-based multicore load balancing
|
||||
- Connection offloading
|
||||
- Powerful rule engine based on [expr](https://github.com/expr-lang/expr)
|
||||
@@ -71,8 +73,7 @@ workers:
|
||||
|
||||
### Example rules
|
||||
|
||||
Documentation on all supported protocols and what field each one has is not yet ready. For now, you have to check the
|
||||
code under "analyzer" directory directly.
|
||||
[Analyzer properties](docs/Analyzers.md)
|
||||
|
||||
For syntax of the expression language, please refer
|
||||
to [Expr Language Definition](https://expr-lang.org/docs/language-definition).
|
||||
@@ -90,6 +91,10 @@ to [Expr Language Definition](https://expr-lang.org/docs/language-definition).
|
||||
action: block
|
||||
expr: fet != nil && fet.yes
|
||||
|
||||
- name: block trojan
|
||||
action: block
|
||||
expr: trojan != nil && trojan.yes
|
||||
|
||||
- name: v2ex dns poisoning
|
||||
action: modify
|
||||
modifier:
|
||||
@@ -98,13 +103,17 @@ to [Expr Language Definition](https://expr-lang.org/docs/language-definition).
|
||||
a: "0.0.0.0"
|
||||
aaaa: "::"
|
||||
expr: dns != nil && dns.qr && any(dns.questions, {.name endsWith "v2ex.com"})
|
||||
|
||||
- name: block google socks
|
||||
action: block
|
||||
expr: string(socks?.req?.addr) endsWith "google.com" && socks?.req?.port == 80
|
||||
```
|
||||
|
||||
#### Supported actions
|
||||
|
||||
- `allow`: Allow the connection, no further processing.
|
||||
- `block`: Block the connection, no further processing. Send a TCP RST if it's a TCP connection.
|
||||
- `block`: Block the connection, no further processing.
|
||||
- `drop`: For UDP, drop the packet that triggered the rule, continue processing future packets in the same flow. For
|
||||
TCP, same as `block`.
|
||||
- `modify`: For UDP, modify the packet that triggered the rule using the given modifier, continue processing future
|
||||
packets in the same flow. For TCP, same as `allow`.
|
||||
packets in the same flow. For TCP, same as `allow`.
|
||||
|
||||
23
README.zh.md
23
README.zh.md
@@ -3,7 +3,6 @@
|
||||
[![License][1]][2]
|
||||
|
||||
[1]: https://img.shields.io/badge/License-MPL_2.0-brightgreen.svg
|
||||
|
||||
[2]: LICENSE
|
||||
|
||||
OpenGFW 是一个 Linux 上灵活、易用、开源的 [GFW](https://zh.wikipedia.org/wiki/%E9%98%B2%E7%81%AB%E9%95%BF%E5%9F%8E)
|
||||
@@ -18,9 +17,11 @@ OpenGFW 是一个 Linux 上灵活、易用、开源的 [GFW](https://zh.wikipedi
|
||||
## 功能
|
||||
|
||||
- 完整的 IP/TCP 重组,各种协议解析器
|
||||
- HTTP, TLS, DNS, SSH, 更多协议正在开发中
|
||||
- Shadowsocks 等 "全加密流量" 检测 (https://gfw.report/publications/usenixsecurity23/data/paper/paper.pdf)
|
||||
- [开发中] 基于机器学习的流量分类
|
||||
- HTTP, TLS, DNS, SSH, SOCKS4/5, 更多协议正在开发中
|
||||
- Shadowsocks 等 "全加密流量" 检测 (https://gfw.report/publications/usenixsecurity23/data/paper/paper.pdf)
|
||||
- 基于 Trojan-killer 的 Trojan 检测 (https://github.com/XTLS/Trojan-killer)
|
||||
- [开发中] 基于机器学习的流量分类
|
||||
- 同等支持 IPv4 和 IPv6
|
||||
- 基于流的多核负载均衡
|
||||
- 连接 offloading
|
||||
- 基于 [expr](https://github.com/expr-lang/expr) 的强大规则引擎
|
||||
@@ -68,7 +69,7 @@ workers:
|
||||
|
||||
### 样例规则
|
||||
|
||||
关于规则具体支持哪些协议,以及每个协议包含哪些字段的文档还没有写。目前请直接参考 "analyzer" 目录下的代码。
|
||||
[解析器属性](docs/Analyzers.md)
|
||||
|
||||
规则的语法请参考 [Expr Language Definition](https://expr-lang.org/docs/language-definition)。
|
||||
|
||||
@@ -85,6 +86,10 @@ workers:
|
||||
action: block
|
||||
expr: fet != nil && fet.yes
|
||||
|
||||
- name: block trojan
|
||||
action: block
|
||||
expr: trojan != nil && trojan.yes
|
||||
|
||||
- name: v2ex dns poisoning
|
||||
action: modify
|
||||
modifier:
|
||||
@@ -93,11 +98,15 @@ workers:
|
||||
a: "0.0.0.0"
|
||||
aaaa: "::"
|
||||
expr: dns != nil && dns.qr && any(dns.questions, {.name endsWith "v2ex.com"})
|
||||
|
||||
- name: block google socks
|
||||
action: block
|
||||
expr: string(socks?.req?.addr) endsWith "google.com" && socks?.req?.port == 80
|
||||
```
|
||||
|
||||
#### 支持的 action
|
||||
|
||||
- `allow`: 放行连接,不再处理后续的包。
|
||||
- `block`: 阻断连接,不再处理后续的包。如果是 TCP 连接,会发送 RST 包。
|
||||
- `block`: 阻断连接,不再处理后续的包。
|
||||
- `drop`: 对于 UDP,丢弃触发规则的包,但继续处理同一流中的后续包。对于 TCP,效果同 `block`。
|
||||
- `modify`: 对于 UDP,用指定的修改器修改触发规则的包,然后继续处理同一流中的后续包。对于 TCP,效果同 `allow`。
|
||||
- `modify`: 对于 UDP,用指定的修改器修改触发规则的包,然后继续处理同一流中的后续包。对于 TCP,效果同 `allow`。
|
||||
|
||||
508
analyzer/tcp/socks.go
Normal file
508
analyzer/tcp/socks.go
Normal file
@@ -0,0 +1,508 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/apernet/OpenGFW/analyzer"
|
||||
"github.com/apernet/OpenGFW/analyzer/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
SocksInvalid = iota
|
||||
Socks4
|
||||
Socks4A
|
||||
Socks5
|
||||
|
||||
Socks4Version = 0x04
|
||||
Socks5Version = 0x05
|
||||
|
||||
Socks4ReplyVN = 0x00
|
||||
|
||||
Socks4CmdTCPConnect = 0x01
|
||||
Socks4CmdTCPBind = 0x02
|
||||
|
||||
Socks4ReqGranted = 0x5A
|
||||
Socks4ReqRejectOrFailed = 0x5B
|
||||
Socks4ReqRejectIdentd = 0x5C
|
||||
Socks4ReqRejectUser = 0x5D
|
||||
|
||||
Socks5CmdTCPConnect = 0x01
|
||||
Socks5CmdTCPBind = 0x02
|
||||
Socks5CmdUDPAssociate = 0x03
|
||||
|
||||
Socks5AuthNotRequired = 0x00
|
||||
Socks5AuthPassword = 0x02
|
||||
Socks5AuthNoMatchingMethod = 0xFF
|
||||
|
||||
Socks5AuthSuccess = 0x00
|
||||
Socks5AuthFailure = 0x01
|
||||
|
||||
Socks5AddrTypeIPv4 = 0x01
|
||||
Socks5AddrTypeDomain = 0x03
|
||||
Socks5AddrTypeIPv6 = 0x04
|
||||
)
|
||||
|
||||
var _ analyzer.Analyzer = (*SocksAnalyzer)(nil)
|
||||
|
||||
type SocksAnalyzer struct{}
|
||||
|
||||
func (a *SocksAnalyzer) Name() string {
|
||||
return "socks"
|
||||
}
|
||||
|
||||
func (a *SocksAnalyzer) Limit() int {
|
||||
// Socks4 length limit cannot be predicted
|
||||
return 0
|
||||
}
|
||||
|
||||
func (a *SocksAnalyzer) NewTCP(info analyzer.TCPInfo, logger analyzer.Logger) analyzer.TCPStream {
|
||||
return newSocksStream(logger)
|
||||
}
|
||||
|
||||
type socksStream struct {
|
||||
logger analyzer.Logger
|
||||
|
||||
reqBuf *utils.ByteBuffer
|
||||
reqMap analyzer.PropMap
|
||||
reqUpdated bool
|
||||
reqLSM *utils.LinearStateMachine
|
||||
reqDone bool
|
||||
|
||||
respBuf *utils.ByteBuffer
|
||||
respMap analyzer.PropMap
|
||||
respUpdated bool
|
||||
respLSM *utils.LinearStateMachine
|
||||
respDone bool
|
||||
|
||||
version int
|
||||
|
||||
authReqMethod int
|
||||
authUsername string
|
||||
authPassword string
|
||||
|
||||
authRespMethod int
|
||||
}
|
||||
|
||||
func newSocksStream(logger analyzer.Logger) *socksStream {
|
||||
s := &socksStream{logger: logger, reqBuf: &utils.ByteBuffer{}, respBuf: &utils.ByteBuffer{}}
|
||||
s.reqLSM = utils.NewLinearStateMachine(
|
||||
s.parseSocksReqVersion,
|
||||
)
|
||||
s.respLSM = utils.NewLinearStateMachine(
|
||||
s.parseSocksRespVersion,
|
||||
)
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *socksStream) Feed(rev, start, end bool, skip int, data []byte) (u *analyzer.PropUpdate, d bool) {
|
||||
if skip != 0 {
|
||||
return nil, true
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
var update *analyzer.PropUpdate
|
||||
var cancelled bool
|
||||
if rev {
|
||||
s.respBuf.Append(data)
|
||||
s.respUpdated = false
|
||||
cancelled, s.respDone = s.respLSM.Run()
|
||||
if s.respUpdated {
|
||||
update = &analyzer.PropUpdate{
|
||||
Type: analyzer.PropUpdateMerge,
|
||||
M: analyzer.PropMap{"resp": s.respMap},
|
||||
}
|
||||
s.respUpdated = false
|
||||
}
|
||||
} else {
|
||||
s.reqBuf.Append(data)
|
||||
s.reqUpdated = false
|
||||
cancelled, s.reqDone = s.reqLSM.Run()
|
||||
if s.reqUpdated {
|
||||
update = &analyzer.PropUpdate{
|
||||
Type: analyzer.PropUpdateMerge,
|
||||
M: analyzer.PropMap{
|
||||
"version": s.socksVersion(),
|
||||
"req": s.reqMap,
|
||||
},
|
||||
}
|
||||
s.reqUpdated = false
|
||||
}
|
||||
}
|
||||
|
||||
return update, cancelled || (s.reqDone && s.respDone)
|
||||
}
|
||||
|
||||
func (s *socksStream) Close(limited bool) *analyzer.PropUpdate {
|
||||
s.reqBuf.Reset()
|
||||
s.respBuf.Reset()
|
||||
s.reqMap = nil
|
||||
s.respMap = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *socksStream) parseSocksReqVersion() utils.LSMAction {
|
||||
socksVer, ok := s.reqBuf.GetByte(true)
|
||||
if !ok {
|
||||
return utils.LSMActionPause
|
||||
}
|
||||
if socksVer != Socks4Version && socksVer != Socks5Version {
|
||||
return utils.LSMActionCancel
|
||||
}
|
||||
s.reqMap = make(analyzer.PropMap)
|
||||
s.reqUpdated = true
|
||||
if socksVer == Socks4Version {
|
||||
s.version = Socks4
|
||||
s.reqLSM.AppendSteps(
|
||||
s.parseSocks4ReqIpAndPort,
|
||||
s.parseSocks4ReqUserId,
|
||||
s.parseSocks4ReqHostname,
|
||||
)
|
||||
} else {
|
||||
s.version = Socks5
|
||||
s.reqLSM.AppendSteps(
|
||||
s.parseSocks5ReqMethod,
|
||||
s.parseSocks5ReqAuth,
|
||||
s.parseSocks5ReqConnInfo,
|
||||
)
|
||||
}
|
||||
return utils.LSMActionNext
|
||||
}
|
||||
|
||||
func (s *socksStream) parseSocksRespVersion() utils.LSMAction {
|
||||
socksVer, ok := s.respBuf.GetByte(true)
|
||||
if !ok {
|
||||
return utils.LSMActionPause
|
||||
}
|
||||
if (s.version == Socks4 || s.version == Socks4A) && socksVer != Socks4ReplyVN ||
|
||||
s.version == Socks5 && socksVer != Socks5Version || s.version == SocksInvalid {
|
||||
return utils.LSMActionCancel
|
||||
}
|
||||
if socksVer == Socks4ReplyVN {
|
||||
s.respLSM.AppendSteps(
|
||||
s.parseSocks4RespPacket,
|
||||
)
|
||||
} else {
|
||||
s.respLSM.AppendSteps(
|
||||
s.parseSocks5RespMethod,
|
||||
s.parseSocks5RespAuth,
|
||||
s.parseSocks5RespConnInfo,
|
||||
)
|
||||
}
|
||||
return utils.LSMActionNext
|
||||
}
|
||||
|
||||
func (s *socksStream) parseSocks5ReqMethod() utils.LSMAction {
|
||||
nMethods, ok := s.reqBuf.GetByte(false)
|
||||
if !ok {
|
||||
return utils.LSMActionPause
|
||||
}
|
||||
methods, ok := s.reqBuf.Get(int(nMethods)+1, true)
|
||||
if !ok {
|
||||
return utils.LSMActionPause
|
||||
}
|
||||
|
||||
// For convenience, we only take the first method we can process
|
||||
s.authReqMethod = Socks5AuthNoMatchingMethod
|
||||
for _, method := range methods[1:] {
|
||||
switch method {
|
||||
case Socks5AuthNotRequired:
|
||||
s.authReqMethod = Socks5AuthNotRequired
|
||||
break
|
||||
case Socks5AuthPassword:
|
||||
s.authReqMethod = Socks5AuthPassword
|
||||
break
|
||||
default:
|
||||
// TODO: more auth method to support
|
||||
}
|
||||
}
|
||||
return utils.LSMActionNext
|
||||
}
|
||||
|
||||
func (s *socksStream) parseSocks5ReqAuth() utils.LSMAction {
|
||||
switch s.authReqMethod {
|
||||
case Socks5AuthNotRequired:
|
||||
s.reqMap["auth"] = analyzer.PropMap{"method": s.authReqMethod}
|
||||
case Socks5AuthPassword:
|
||||
meta, ok := s.reqBuf.Get(2, false)
|
||||
if !ok {
|
||||
return utils.LSMActionPause
|
||||
}
|
||||
if meta[0] != 0x01 {
|
||||
return utils.LSMActionCancel
|
||||
}
|
||||
usernameLen := int(meta[1])
|
||||
meta, ok = s.reqBuf.Get(usernameLen+3, false)
|
||||
if !ok {
|
||||
return utils.LSMActionPause
|
||||
}
|
||||
passwordLen := int(meta[usernameLen+2])
|
||||
meta, ok = s.reqBuf.Get(usernameLen+passwordLen+3, true)
|
||||
if !ok {
|
||||
return utils.LSMActionPause
|
||||
}
|
||||
s.authUsername = string(meta[2 : usernameLen+2])
|
||||
s.authPassword = string(meta[usernameLen+3:])
|
||||
s.reqMap["auth"] = analyzer.PropMap{
|
||||
"method": s.authReqMethod,
|
||||
"username": s.authUsername,
|
||||
"password": s.authPassword,
|
||||
}
|
||||
default:
|
||||
return utils.LSMActionCancel
|
||||
}
|
||||
s.reqUpdated = true
|
||||
return utils.LSMActionNext
|
||||
}
|
||||
|
||||
func (s *socksStream) parseSocks5ReqConnInfo() utils.LSMAction {
|
||||
/* preInfo struct
|
||||
+----+-----+-------+------+-------------+
|
||||
|VER | CMD | RSV | ATYP | DST.ADDR(1) |
|
||||
+----+-----+-------+------+-------------+
|
||||
*/
|
||||
preInfo, ok := s.reqBuf.Get(5, false)
|
||||
if !ok {
|
||||
return utils.LSMActionPause
|
||||
}
|
||||
|
||||
// verify socks version
|
||||
if preInfo[0] != Socks5Version {
|
||||
return utils.LSMActionCancel
|
||||
}
|
||||
|
||||
var pktLen int
|
||||
switch int(preInfo[3]) {
|
||||
case Socks5AddrTypeIPv4:
|
||||
pktLen = 10
|
||||
case Socks5AddrTypeDomain:
|
||||
domainLen := int(preInfo[4])
|
||||
pktLen = 7 + domainLen
|
||||
case Socks5AddrTypeIPv6:
|
||||
pktLen = 22
|
||||
default:
|
||||
return utils.LSMActionCancel
|
||||
}
|
||||
|
||||
pkt, ok := s.reqBuf.Get(pktLen, true)
|
||||
if !ok {
|
||||
return utils.LSMActionPause
|
||||
}
|
||||
|
||||
// parse cmd
|
||||
cmd := int(pkt[1])
|
||||
if cmd != Socks5CmdTCPConnect && cmd != Socks5CmdTCPBind && cmd != Socks5CmdUDPAssociate {
|
||||
return utils.LSMActionCancel
|
||||
}
|
||||
s.reqMap["cmd"] = cmd
|
||||
|
||||
// parse addr type
|
||||
addrType := int(pkt[3])
|
||||
var addr string
|
||||
switch addrType {
|
||||
case Socks5AddrTypeIPv4:
|
||||
addr = net.IPv4(pkt[4], pkt[5], pkt[6], pkt[7]).String()
|
||||
case Socks5AddrTypeDomain:
|
||||
addr = string(pkt[5 : 5+pkt[4]])
|
||||
case Socks5AddrTypeIPv6:
|
||||
addr = net.IP(pkt[4 : 4+net.IPv6len]).String()
|
||||
default:
|
||||
return utils.LSMActionCancel
|
||||
}
|
||||
s.reqMap["addr_type"] = addrType
|
||||
s.reqMap["addr"] = addr
|
||||
|
||||
// parse port
|
||||
port := int(pkt[pktLen-2])<<8 | int(pkt[pktLen-1])
|
||||
s.reqMap["port"] = port
|
||||
s.reqUpdated = true
|
||||
return utils.LSMActionNext
|
||||
}
|
||||
|
||||
func (s *socksStream) parseSocks5RespMethod() utils.LSMAction {
|
||||
method, ok := s.respBuf.Get(1, true)
|
||||
if !ok {
|
||||
return utils.LSMActionPause
|
||||
}
|
||||
s.authRespMethod = int(method[0])
|
||||
s.respMap = make(analyzer.PropMap)
|
||||
return utils.LSMActionNext
|
||||
}
|
||||
|
||||
func (s *socksStream) parseSocks5RespAuth() utils.LSMAction {
|
||||
switch s.authRespMethod {
|
||||
case Socks5AuthNotRequired:
|
||||
s.respMap["auth"] = analyzer.PropMap{"method": s.authRespMethod}
|
||||
case Socks5AuthPassword:
|
||||
authResp, ok := s.respBuf.Get(2, true)
|
||||
if !ok {
|
||||
return utils.LSMActionPause
|
||||
}
|
||||
if authResp[0] != 0x01 {
|
||||
return utils.LSMActionCancel
|
||||
}
|
||||
authStatus := int(authResp[1])
|
||||
s.respMap["auth"] = analyzer.PropMap{
|
||||
"method": s.authRespMethod,
|
||||
"status": authStatus,
|
||||
}
|
||||
default:
|
||||
return utils.LSMActionCancel
|
||||
}
|
||||
s.respUpdated = true
|
||||
return utils.LSMActionNext
|
||||
}
|
||||
|
||||
func (s *socksStream) parseSocks5RespConnInfo() utils.LSMAction {
|
||||
/* preInfo struct
|
||||
+----+-----+-------+------+-------------+
|
||||
|VER | REP | RSV | ATYP | BND.ADDR(1) |
|
||||
+----+-----+-------+------+-------------+
|
||||
*/
|
||||
preInfo, ok := s.respBuf.Get(5, false)
|
||||
if !ok {
|
||||
return utils.LSMActionPause
|
||||
}
|
||||
|
||||
// verify socks version
|
||||
if preInfo[0] != Socks5Version {
|
||||
return utils.LSMActionCancel
|
||||
}
|
||||
|
||||
var pktLen int
|
||||
switch int(preInfo[3]) {
|
||||
case Socks5AddrTypeIPv4:
|
||||
pktLen = 10
|
||||
case Socks5AddrTypeDomain:
|
||||
domainLen := int(preInfo[4])
|
||||
pktLen = 7 + domainLen
|
||||
case Socks5AddrTypeIPv6:
|
||||
pktLen = 22
|
||||
default:
|
||||
return utils.LSMActionCancel
|
||||
}
|
||||
|
||||
pkt, ok := s.respBuf.Get(pktLen, true)
|
||||
if !ok {
|
||||
return utils.LSMActionPause
|
||||
}
|
||||
|
||||
// parse rep
|
||||
rep := int(pkt[1])
|
||||
s.respMap["rep"] = rep
|
||||
|
||||
// parse addr type
|
||||
addrType := int(pkt[3])
|
||||
var addr string
|
||||
switch addrType {
|
||||
case Socks5AddrTypeIPv4:
|
||||
addr = net.IPv4(pkt[4], pkt[5], pkt[6], pkt[7]).String()
|
||||
case Socks5AddrTypeDomain:
|
||||
addr = string(pkt[5 : 5+pkt[4]])
|
||||
case Socks5AddrTypeIPv6:
|
||||
addr = net.IP(pkt[4 : 4+net.IPv6len]).String()
|
||||
default:
|
||||
return utils.LSMActionCancel
|
||||
}
|
||||
s.respMap["addr_type"] = addrType
|
||||
s.respMap["addr"] = addr
|
||||
|
||||
// parse port
|
||||
port := int(pkt[pktLen-2])<<8 | int(pkt[pktLen-1])
|
||||
s.respMap["port"] = port
|
||||
s.respUpdated = true
|
||||
return utils.LSMActionNext
|
||||
}
|
||||
|
||||
func (s *socksStream) parseSocks4ReqIpAndPort() utils.LSMAction {
|
||||
/* Following field will be parsed in this state:
|
||||
+-----+----------+--------+
|
||||
| CMD | DST.PORT | DST.IP |
|
||||
+-----+----------+--------+
|
||||
*/
|
||||
pkt, ok := s.reqBuf.Get(7, true)
|
||||
if !ok {
|
||||
return utils.LSMActionPause
|
||||
}
|
||||
if pkt[0] != Socks4CmdTCPConnect && pkt[0] != Socks4CmdTCPBind {
|
||||
return utils.LSMActionCancel
|
||||
}
|
||||
|
||||
dstPort := uint16(pkt[1])<<8 | uint16(pkt[2])
|
||||
dstIp := net.IPv4(pkt[3], pkt[4], pkt[5], pkt[6]).String()
|
||||
|
||||
// Socks4a extension
|
||||
if pkt[3] == 0 && pkt[4] == 0 && pkt[5] == 0 {
|
||||
s.version = Socks4A
|
||||
}
|
||||
|
||||
s.reqMap["cmd"] = pkt[0]
|
||||
s.reqMap["addr"] = dstIp
|
||||
s.reqMap["addr_type"] = Socks5AddrTypeIPv4
|
||||
s.reqMap["port"] = dstPort
|
||||
s.reqUpdated = true
|
||||
return utils.LSMActionNext
|
||||
}
|
||||
|
||||
func (s *socksStream) parseSocks4ReqUserId() utils.LSMAction {
|
||||
userIdSlice, ok := s.reqBuf.GetUntil([]byte("\x00"), true, true)
|
||||
if !ok {
|
||||
return utils.LSMActionPause
|
||||
}
|
||||
userId := string(userIdSlice[:len(userIdSlice)-1])
|
||||
s.reqMap["auth"] = analyzer.PropMap{
|
||||
"user_id": userId,
|
||||
}
|
||||
s.reqUpdated = true
|
||||
return utils.LSMActionNext
|
||||
}
|
||||
|
||||
func (s *socksStream) parseSocks4ReqHostname() utils.LSMAction {
|
||||
// Only Socks4a support hostname
|
||||
if s.version != Socks4A {
|
||||
return utils.LSMActionNext
|
||||
}
|
||||
hostnameSlice, ok := s.reqBuf.GetUntil([]byte("\x00"), true, true)
|
||||
if !ok {
|
||||
return utils.LSMActionPause
|
||||
}
|
||||
hostname := string(hostnameSlice[:len(hostnameSlice)-1])
|
||||
s.reqMap["addr"] = hostname
|
||||
s.reqMap["addr_type"] = Socks5AddrTypeDomain
|
||||
s.reqUpdated = true
|
||||
return utils.LSMActionNext
|
||||
}
|
||||
|
||||
func (s *socksStream) parseSocks4RespPacket() utils.LSMAction {
|
||||
pkt, ok := s.respBuf.Get(7, true)
|
||||
if !ok {
|
||||
return utils.LSMActionPause
|
||||
}
|
||||
if pkt[0] != Socks4ReqGranted &&
|
||||
pkt[0] != Socks4ReqRejectOrFailed &&
|
||||
pkt[0] != Socks4ReqRejectIdentd &&
|
||||
pkt[0] != Socks4ReqRejectUser {
|
||||
return utils.LSMActionCancel
|
||||
}
|
||||
dstPort := uint16(pkt[1])<<8 | uint16(pkt[2])
|
||||
dstIp := net.IPv4(pkt[3], pkt[4], pkt[5], pkt[6]).String()
|
||||
s.respMap = analyzer.PropMap{
|
||||
"rep": pkt[0],
|
||||
"addr": dstIp,
|
||||
"addr_type": Socks5AddrTypeIPv4,
|
||||
"port": dstPort,
|
||||
}
|
||||
s.respUpdated = true
|
||||
return utils.LSMActionNext
|
||||
}
|
||||
|
||||
func (s *socksStream) socksVersion() int {
|
||||
switch s.version {
|
||||
case Socks4, Socks4A:
|
||||
return Socks4Version
|
||||
case Socks5:
|
||||
return Socks5Version
|
||||
default:
|
||||
return SocksInvalid
|
||||
}
|
||||
}
|
||||
91
analyzer/tcp/trojan.go
Normal file
91
analyzer/tcp/trojan.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/apernet/OpenGFW/analyzer"
|
||||
)
|
||||
|
||||
var _ analyzer.TCPAnalyzer = (*TrojanAnalyzer)(nil)
|
||||
|
||||
// CCS stands for "Change Cipher Spec"
|
||||
var trojanCCS = []byte{20, 3, 3, 0, 1, 1}
|
||||
|
||||
const (
|
||||
trojanUpLB = 650
|
||||
trojanUpUB = 1000
|
||||
trojanDownLB1 = 170
|
||||
trojanDownUB1 = 180
|
||||
trojanDownLB2 = 3000
|
||||
trojanDownUB2 = 7500
|
||||
)
|
||||
|
||||
// TrojanAnalyzer uses a very simple packet length based check to determine
|
||||
// if a TLS connection is actually the Trojan proxy protocol.
|
||||
// The algorithm is from the following project, with small modifications:
|
||||
// https://github.com/XTLS/Trojan-killer
|
||||
// Warning: Experimental only. This method is known to have significant false positives and false negatives.
|
||||
type TrojanAnalyzer struct{}
|
||||
|
||||
func (a *TrojanAnalyzer) Name() string {
|
||||
return "trojan"
|
||||
}
|
||||
|
||||
func (a *TrojanAnalyzer) Limit() int {
|
||||
return 16384
|
||||
}
|
||||
|
||||
func (a *TrojanAnalyzer) NewTCP(info analyzer.TCPInfo, logger analyzer.Logger) analyzer.TCPStream {
|
||||
return newTrojanStream(logger)
|
||||
}
|
||||
|
||||
type trojanStream struct {
|
||||
logger analyzer.Logger
|
||||
active bool
|
||||
upCount int
|
||||
downCount int
|
||||
}
|
||||
|
||||
func newTrojanStream(logger analyzer.Logger) *trojanStream {
|
||||
return &trojanStream{logger: logger}
|
||||
}
|
||||
|
||||
func (s *trojanStream) Feed(rev, start, end bool, skip int, data []byte) (u *analyzer.PropUpdate, done bool) {
|
||||
if skip != 0 {
|
||||
return nil, true
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
if !rev && !s.active && len(data) >= 6 && bytes.Equal(data[:6], trojanCCS) {
|
||||
// Client CCS encountered, start counting
|
||||
s.active = true
|
||||
}
|
||||
if s.active {
|
||||
if rev {
|
||||
// Down direction
|
||||
s.downCount += len(data)
|
||||
} else {
|
||||
// Up direction
|
||||
if s.upCount >= trojanUpLB && s.upCount <= trojanUpUB &&
|
||||
((s.downCount >= trojanDownLB1 && s.downCount <= trojanDownUB1) ||
|
||||
(s.downCount >= trojanDownLB2 && s.downCount <= trojanDownUB2)) {
|
||||
return &analyzer.PropUpdate{
|
||||
Type: analyzer.PropUpdateReplace,
|
||||
M: analyzer.PropMap{
|
||||
"up": s.upCount,
|
||||
"down": s.downCount,
|
||||
"yes": true,
|
||||
},
|
||||
}, true
|
||||
}
|
||||
s.upCount += len(data)
|
||||
}
|
||||
}
|
||||
// Give up when either direction is over the limit
|
||||
return nil, s.upCount > trojanUpUB || s.downCount > trojanDownUB2
|
||||
}
|
||||
|
||||
func (s *trojanStream) Close(limited bool) *analyzer.PropUpdate {
|
||||
return nil
|
||||
}
|
||||
@@ -44,6 +44,10 @@ func (lsm *LinearStateMachine) Run() (cancelled bool, done bool) {
|
||||
return false, true
|
||||
}
|
||||
|
||||
func (lsm *LinearStateMachine) AppendSteps(steps ...func() LSMAction) {
|
||||
lsm.Steps = append(lsm.Steps, steps...)
|
||||
}
|
||||
|
||||
func (lsm *LinearStateMachine) Reset() {
|
||||
lsm.index = 0
|
||||
lsm.cancelled = false
|
||||
|
||||
@@ -87,8 +87,10 @@ var logFormatMap = map[string]zapcore.EncoderConfig{
|
||||
var analyzers = []analyzer.Analyzer{
|
||||
&tcp.FETAnalyzer{},
|
||||
&tcp.HTTPAnalyzer{},
|
||||
&tcp.SocksAnalyzer{},
|
||||
&tcp.SSHAnalyzer{},
|
||||
&tcp.TLSAnalyzer{},
|
||||
&tcp.TrojanAnalyzer{},
|
||||
&udp.DNSAnalyzer{},
|
||||
}
|
||||
|
||||
|
||||
371
docs/Analyzers.md
Normal file
371
docs/Analyzers.md
Normal file
@@ -0,0 +1,371 @@
|
||||
# Analyzers
|
||||
|
||||
Analyzers are one of the main components of OpenGFW. Their job is to analyze a connection, see if it's a protocol they
|
||||
support, and if so, extract information from that connection and provide properties for the rule engine to match against
|
||||
user-provided rules. OpenGFW will automatically analyze which analyzers are referenced in the given rules and enable
|
||||
only those that are needed.
|
||||
|
||||
This document lists the properties provided by each analyzer that can be used by rules.
|
||||
|
||||
## DNS (TCP & UDP)
|
||||
|
||||
For queries:
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
"aa": false,
|
||||
"id": 41953,
|
||||
"opcode": 0,
|
||||
"qr": false,
|
||||
"questions": [
|
||||
{
|
||||
"class": 1,
|
||||
"name": "www.google.com",
|
||||
"type": 1
|
||||
}
|
||||
],
|
||||
"ra": false,
|
||||
"rcode": 0,
|
||||
"rd": true,
|
||||
"tc": false,
|
||||
"z": 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For responses:
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
"aa": false,
|
||||
"answers": [
|
||||
{
|
||||
"a": "142.251.32.36",
|
||||
"class": 1,
|
||||
"name": "www.google.com",
|
||||
"ttl": 255,
|
||||
"type": 1
|
||||
}
|
||||
],
|
||||
"id": 41953,
|
||||
"opcode": 0,
|
||||
"qr": true,
|
||||
"questions": [
|
||||
{
|
||||
"class": 1,
|
||||
"name": "www.google.com",
|
||||
"type": 1
|
||||
}
|
||||
],
|
||||
"ra": true,
|
||||
"rcode": 0,
|
||||
"rd": true,
|
||||
"tc": false,
|
||||
"z": 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Example for blocking DNS queries for `www.google.com`:
|
||||
|
||||
```yaml
|
||||
- name: Block Google DNS
|
||||
action: drop
|
||||
expr: dns != nil && !dns.qr && any(dns.questions, {.name == "www.google.com"})
|
||||
```
|
||||
|
||||
## FET (Fully Encrypted Traffic)
|
||||
|
||||
Check https://www.usenix.org/system/files/usenixsecurity23-wu-mingshi.pdf for more information.
|
||||
|
||||
```json
|
||||
{
|
||||
"fet": {
|
||||
"ex1": 3.7560976,
|
||||
"ex2": true,
|
||||
"ex3": 0.9512195,
|
||||
"ex4": 39,
|
||||
"ex5": false,
|
||||
"yes": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Example for blocking fully encrypted traffic:
|
||||
|
||||
```yaml
|
||||
- name: Block suspicious proxy traffic
|
||||
action: block
|
||||
expr: fet != nil && fet.yes
|
||||
```
|
||||
|
||||
## HTTP
|
||||
|
||||
```json
|
||||
{
|
||||
"http": {
|
||||
"req": {
|
||||
"headers": {
|
||||
"accept": "*/*",
|
||||
"host": "ipinfo.io",
|
||||
"user-agent": "curl/7.81.0"
|
||||
},
|
||||
"method": "GET",
|
||||
"path": "/",
|
||||
"version": "HTTP/1.1"
|
||||
},
|
||||
"resp": {
|
||||
"headers": {
|
||||
"access-control-allow-origin": "*",
|
||||
"content-length": "333",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"date": "Wed, 24 Jan 2024 05:41:44 GMT",
|
||||
"referrer-policy": "strict-origin-when-cross-origin",
|
||||
"server": "nginx/1.24.0",
|
||||
"strict-transport-security": "max-age=2592000; includeSubDomains",
|
||||
"via": "1.1 google",
|
||||
"x-content-type-options": "nosniff",
|
||||
"x-envoy-upstream-service-time": "2",
|
||||
"x-frame-options": "SAMEORIGIN",
|
||||
"x-xss-protection": "1; mode=block"
|
||||
},
|
||||
"status": 200,
|
||||
"version": "HTTP/1.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Example for blocking HTTP requests to `ipinfo.io`:
|
||||
|
||||
```yaml
|
||||
- name: Block ipinfo.io HTTP
|
||||
action: block
|
||||
expr: http != nil && http.req != nil && http.req.headers != nil && http.req.headers.host == "ipinfo.io"
|
||||
```
|
||||
|
||||
## SSH
|
||||
|
||||
```json
|
||||
{
|
||||
"ssh": {
|
||||
"server": {
|
||||
"comments": "Ubuntu-3ubuntu0.6",
|
||||
"protocol": "2.0",
|
||||
"software": "OpenSSH_8.9p1"
|
||||
},
|
||||
"client": {
|
||||
"comments": "IMHACKER",
|
||||
"protocol": "2.0",
|
||||
"software": "OpenSSH_8.9p1"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Example for blocking all SSH connections:
|
||||
|
||||
```yaml
|
||||
- name: Block SSH
|
||||
action: block
|
||||
expr: ssh != nil
|
||||
```
|
||||
|
||||
## TLS
|
||||
|
||||
```json
|
||||
{
|
||||
"tls": {
|
||||
"req": {
|
||||
"alpn": [
|
||||
"h2",
|
||||
"http/1.1"
|
||||
],
|
||||
"ciphers": [
|
||||
4866,
|
||||
4867,
|
||||
4865,
|
||||
49196,
|
||||
49200,
|
||||
159,
|
||||
52393,
|
||||
52392,
|
||||
52394,
|
||||
49195,
|
||||
49199,
|
||||
158,
|
||||
49188,
|
||||
49192,
|
||||
107,
|
||||
49187,
|
||||
49191,
|
||||
103,
|
||||
49162,
|
||||
49172,
|
||||
57,
|
||||
49161,
|
||||
49171,
|
||||
51,
|
||||
157,
|
||||
156,
|
||||
61,
|
||||
60,
|
||||
53,
|
||||
47,
|
||||
255
|
||||
],
|
||||
"compression": "AA==",
|
||||
"random": "UqfPi+EmtMgusILrKcELvVWwpOdPSM/My09nPXl84dg=",
|
||||
"session": "jCTrpAzHpwrfuYdYx4FEjZwbcQxCuZ52HGIoOcbw1vA=",
|
||||
"sni": "ipinfo.io",
|
||||
"supported_versions": [
|
||||
772,
|
||||
771
|
||||
],
|
||||
"version": 771,
|
||||
"ech": true
|
||||
},
|
||||
"resp": {
|
||||
"cipher": 4866,
|
||||
"compression": 0,
|
||||
"random": "R/Cy1m9pktuBMZQIHahD8Y83UWPRf8j8luwNQep9yJI=",
|
||||
"session": "jCTrpAzHpwrfuYdYx4FEjZwbcQxCuZ52HGIoOcbw1vA=",
|
||||
"supported_versions": 772,
|
||||
"version": 771
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Example for blocking TLS connections to `ipinfo.io`:
|
||||
|
||||
```yaml
|
||||
- name: Block ipinfo.io TLS
|
||||
action: block
|
||||
expr: tls != nil && tls.req != nil && tls.req.sni == "ipinfo.io"
|
||||
```
|
||||
|
||||
## Trojan (proxy protocol)
|
||||
|
||||
Check https://github.com/XTLS/Trojan-killer for more information.
|
||||
|
||||
```json
|
||||
{
|
||||
"trojan": {
|
||||
"down": 4712,
|
||||
"up": 671,
|
||||
"yes": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Example for blocking Trojan connections:
|
||||
|
||||
```yaml
|
||||
- name: Block Trojan
|
||||
action: block
|
||||
expr: trojan != nil && trojan.yes
|
||||
```
|
||||
|
||||
## SOCKS
|
||||
|
||||
SOCKS4:
|
||||
|
||||
```json5
|
||||
{
|
||||
"socks": {
|
||||
"version": 4,
|
||||
"req": {
|
||||
"cmd": 1,
|
||||
"addr_type": 1, // same as socks5
|
||||
"addr": "1.1.1.1",
|
||||
// for socks4a
|
||||
// "addr_type": 3,
|
||||
// "addr": "google.com",
|
||||
"port": 443,
|
||||
"auth": {
|
||||
"user_id": "user"
|
||||
}
|
||||
},
|
||||
"resp": {
|
||||
"rep": 90, // 0x5A(90) granted
|
||||
"addr_type": 1,
|
||||
"addr": "1.1.1.1",
|
||||
"port": 443
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
SOCKS5 without auth:
|
||||
|
||||
```json5
|
||||
{
|
||||
"socks": {
|
||||
"version": 5,
|
||||
"req": {
|
||||
"cmd": 1, // 0x01: connect, 0x02: bind, 0x03: udp
|
||||
"addr_type": 3, // 0x01: ipv4, 0x03: domain, 0x04: ipv6
|
||||
"addr": "google.com",
|
||||
"port": 80,
|
||||
"auth": {
|
||||
"method": 0 // 0x00: no auth, 0x02: username/password
|
||||
}
|
||||
},
|
||||
"resp": {
|
||||
"rep": 0, // 0x00: success
|
||||
"addr_type": 1, // 0x01: ipv4, 0x03: domain, 0x04: ipv6
|
||||
"addr": "198.18.1.31",
|
||||
"port": 80,
|
||||
"auth": {
|
||||
"method": 0 // 0x00: no auth, 0x02: username/password
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
SOCKS5 with auth:
|
||||
|
||||
```json5
|
||||
{
|
||||
"socks": {
|
||||
"version": 5,
|
||||
"req": {
|
||||
"cmd": 1, // 0x01: connect, 0x02: bind, 0x03: udp
|
||||
"addr_type": 3, // 0x01: ipv4, 0x03: domain, 0x04: ipv6
|
||||
"addr": "google.com",
|
||||
"port": 80,
|
||||
"auth": {
|
||||
"method": 2, // 0x00: no auth, 0x02: username/password
|
||||
"username": "user",
|
||||
"password": "pass"
|
||||
}
|
||||
},
|
||||
"resp": {
|
||||
"rep": 0, // 0x00: success
|
||||
"addr_type": 1, // 0x01: ipv4, 0x03: domain, 0x04: ipv6
|
||||
"addr": "198.18.1.31",
|
||||
"port": 80,
|
||||
"auth": {
|
||||
"method": 2, // 0x00: no auth, 0x02: username/password
|
||||
"status": 0 // 0x00: success, 0x01: failure
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Example for blocking connections to `google.com:80` and user `foobar`:
|
||||
|
||||
```yaml
|
||||
- name: Block SOCKS google.com:80
|
||||
action: block
|
||||
expr: string(socks?.req?.addr) endsWith "google.com" && socks?.req?.port == 80
|
||||
|
||||
- name: Block SOCKS user foobar
|
||||
action: block
|
||||
expr: socks?.req?.auth?.method == 2 && socks?.req?.auth?.username == "foobar"
|
||||
```
|
||||
BIN
docs/logo.png
BIN
docs/logo.png
Binary file not shown.
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 43 KiB |
@@ -133,8 +133,9 @@ func (s *tcpStream) ReassembledSG(sg reassembly.ScatterGather, ac reassembly.Ass
|
||||
// Important: reverse order so we can remove entries
|
||||
entry := s.activeEntries[i]
|
||||
update, closeUpdate, done := s.feedEntry(entry, rev, start, end, skip, data)
|
||||
updated = updated || processPropUpdate(s.info.Props, entry.Name, update)
|
||||
updated = updated || processPropUpdate(s.info.Props, entry.Name, closeUpdate)
|
||||
up1 := processPropUpdate(s.info.Props, entry.Name, update)
|
||||
up2 := processPropUpdate(s.info.Props, entry.Name, closeUpdate)
|
||||
updated = updated || up1 || up2
|
||||
if done {
|
||||
s.activeEntries = append(s.activeEntries[:i], s.activeEntries[i+1:]...)
|
||||
s.doneEntries = append(s.doneEntries, entry)
|
||||
@@ -174,7 +175,8 @@ func (s *tcpStream) closeActiveEntries() {
|
||||
updated := false
|
||||
for _, entry := range s.activeEntries {
|
||||
update := entry.Stream.Close(false)
|
||||
updated = updated || processPropUpdate(s.info.Props, entry.Name, update)
|
||||
up := processPropUpdate(s.info.Props, entry.Name, update)
|
||||
updated = updated || up
|
||||
}
|
||||
if updated {
|
||||
s.logger.TCPStreamPropUpdate(s.info, true)
|
||||
|
||||
@@ -187,8 +187,9 @@ func (s *udpStream) Feed(udp *layers.UDP, rev bool, uc *udpContext) {
|
||||
// Important: reverse order so we can remove entries
|
||||
entry := s.activeEntries[i]
|
||||
update, closeUpdate, done := s.feedEntry(entry, rev, udp.Payload)
|
||||
updated = updated || processPropUpdate(s.info.Props, entry.Name, update)
|
||||
updated = updated || processPropUpdate(s.info.Props, entry.Name, closeUpdate)
|
||||
up1 := processPropUpdate(s.info.Props, entry.Name, update)
|
||||
up2 := processPropUpdate(s.info.Props, entry.Name, closeUpdate)
|
||||
updated = updated || up1 || up2
|
||||
if done {
|
||||
s.activeEntries = append(s.activeEntries[:i], s.activeEntries[i+1:]...)
|
||||
s.doneEntries = append(s.doneEntries, entry)
|
||||
@@ -244,7 +245,8 @@ func (s *udpStream) closeActiveEntries() {
|
||||
updated := false
|
||||
for _, entry := range s.activeEntries {
|
||||
update := entry.Stream.Close(false)
|
||||
updated = updated || processPropUpdate(s.info.Props, entry.Name, update)
|
||||
up := processPropUpdate(s.info.Props, entry.Name, update)
|
||||
updated = updated || up
|
||||
}
|
||||
if updated {
|
||||
s.logger.UDPStreamPropUpdate(s.info, true)
|
||||
|
||||
@@ -30,7 +30,7 @@ func processPropUpdate(cpm analyzer.CombinedPropMap, name string, update *analyz
|
||||
case analyzer.PropUpdateMerge:
|
||||
m := cpm[name]
|
||||
if m == nil {
|
||||
m = make(analyzer.PropMap)
|
||||
m = make(analyzer.PropMap, len(update.M))
|
||||
cpm[name] = m
|
||||
}
|
||||
for k, v := range update.M {
|
||||
|
||||
@@ -22,19 +22,16 @@ const (
|
||||
|
||||
var iptRulesForward = []iptRule{
|
||||
{"filter", "FORWARD", []string{"-m", "connmark", "--mark", strconv.Itoa(nfqueueConnMarkAccept), "-j", "ACCEPT"}},
|
||||
{"filter", "FORWARD", []string{"-p", "tcp", "-m", "connmark", "--mark", strconv.Itoa(nfqueueConnMarkDrop), "-j", "REJECT", "--reject-with", "tcp-reset"}},
|
||||
{"filter", "FORWARD", []string{"-m", "connmark", "--mark", strconv.Itoa(nfqueueConnMarkDrop), "-j", "DROP"}},
|
||||
{"filter", "FORWARD", []string{"-j", "NFQUEUE", "--queue-num", strconv.Itoa(nfqueueNum), "--queue-bypass"}},
|
||||
}
|
||||
|
||||
var iptRulesLocal = []iptRule{
|
||||
{"filter", "INPUT", []string{"-m", "connmark", "--mark", strconv.Itoa(nfqueueConnMarkAccept), "-j", "ACCEPT"}},
|
||||
{"filter", "INPUT", []string{"-p", "tcp", "-m", "connmark", "--mark", strconv.Itoa(nfqueueConnMarkDrop), "-j", "REJECT", "--reject-with", "tcp-reset"}},
|
||||
{"filter", "INPUT", []string{"-m", "connmark", "--mark", strconv.Itoa(nfqueueConnMarkDrop), "-j", "DROP"}},
|
||||
{"filter", "INPUT", []string{"-j", "NFQUEUE", "--queue-num", strconv.Itoa(nfqueueNum), "--queue-bypass"}},
|
||||
|
||||
{"filter", "OUTPUT", []string{"-m", "connmark", "--mark", strconv.Itoa(nfqueueConnMarkAccept), "-j", "ACCEPT"}},
|
||||
{"filter", "OUTPUT", []string{"-p", "tcp", "-m", "connmark", "--mark", strconv.Itoa(nfqueueConnMarkDrop), "-j", "REJECT", "--reject-with", "tcp-reset"}},
|
||||
{"filter", "OUTPUT", []string{"-m", "connmark", "--mark", strconv.Itoa(nfqueueConnMarkDrop), "-j", "DROP"}},
|
||||
{"filter", "OUTPUT", []string{"-j", "NFQUEUE", "--queue-num", strconv.Itoa(nfqueueNum), "--queue-bypass"}},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user