15 Commits

Author SHA1 Message Date
Toby
90542be7f2 docs: add SOCKS5 2024-01-26 14:03:22 -08:00
Toby
fe2ff6aa69 Merge pull request #34 from KujouRinka/master
Add Socks5 Analyzer
2024-01-26 13:59:26 -08:00
Toby
bd92e716ce docs: improve grammar 2024-01-26 13:57:15 -08:00
Toby
e0712f1d51 fix: import format 2024-01-26 13:54:34 -08:00
Toby
4581d0babe Merge branch 'master' of https://github.com/KujouRinka/OpenGFW into feat-socks5 2024-01-26 13:37:41 -08:00
KujouRinka
b0106c9941 docs: add socks5 doc 2024-01-26 14:45:26 +08:00
Toby
eeb234552c Merge pull request #33 from Fangliding/master
Add ECH in doc
2024-01-25 22:00:30 -08:00
风扇滑翔翼
cbbca0353e Add ECH in doc 2024-01-26 12:54:20 +08:00
KujouRinka
f004d17522 feat: set Limit for socks5 analyzer 2024-01-26 11:31:11 +08:00
KujouRinka
d2d4fa723a feat: socks5 analyzer 2024-01-26 11:24:36 +08:00
Toby
d7d3437d3c docs: analyzers 2024-01-24 20:01:53 -08:00
Toby
7441d24aea docs: trivial updates 2024-01-21 16:11:01 -08:00
Toby
99403d3150 docs: logo transparency 2024-01-21 15:33:18 -08:00
Toby
1041d5fde1 feat: Trojan analyzer based on github.com/XTLS/Trojan-killer 2024-01-21 14:48:54 -08:00
Toby
00d88d7fbf fix: incorrect prop update logic 2024-01-21 12:26:23 -08:00
10 changed files with 833 additions and 14 deletions

View File

@@ -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
- HTTP, TLS, DNS, SSH, SOCKS5, 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,6 +103,10 @@ 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.com:80 via SOCKS5
action: block
expr: string(socks5?.req?.addr) endsWith "google.com" && socks5?.req?.port == 80
```
#### Supported actions
@@ -107,4 +116,4 @@ to [Expr Language Definition](https://expr-lang.org/docs/language-definition).
- `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`.

View File

@@ -18,9 +18,11 @@ OpenGFW 是一个 Linux 上灵活、易用、开源的 [GFW](https://zh.wikipedi
## 功能
- 完整的 IP/TCP 重组,各种协议解析器
- HTTP, TLS, DNS, SSH, 更多协议正在开发中
- HTTP, TLS, DNS, SSH, SOCKS5, 更多协议正在开发中
- 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 +70,7 @@ workers:
### 样例规则
关于规则具体支持哪些协议,以及每个协议包含哪些字段的文档还没有写。目前请直接参考 "analyzer" 目录下的代码。
[解析器属性](docs/Analyzers.md)
规则的语法请参考 [Expr Language Definition](https://expr-lang.org/docs/language-definition)。
@@ -85,6 +87,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,6 +99,10 @@ workers:
a: "0.0.0.0"
aaaa: "::"
expr: dns != nil && dns.qr && any(dns.questions, {.name endsWith "v2ex.com"})
- name: block google.com:80 via SOCKS5
action: block
expr: string(socks5?.req?.addr) endsWith "google.com" && socks5?.req?.port == 80
```
#### 支持的 action
@@ -100,4 +110,4 @@ workers:
- `allow`: 放行连接,不再处理后续的包。
- `block`: 阻断连接,不再处理后续的包。如果是 TCP 连接,会发送 RST 包。
- `drop`: 对于 UDP丢弃触发规则的包但继续处理同一流中的后续包。对于 TCP效果同 `block`
- `modify`: 对于 UDP用指定的修改器修改触发规则的包然后继续处理同一流中的后续包。对于 TCP效果同 `allow`
- `modify`: 对于 UDP用指定的修改器修改触发规则的包然后继续处理同一流中的后续包。对于 TCP效果同 `allow`

362
analyzer/tcp/socks5.go Normal file
View File

@@ -0,0 +1,362 @@
package tcp
import (
"net"
"github.com/apernet/OpenGFW/analyzer"
"github.com/apernet/OpenGFW/analyzer/utils"
)
const (
Socks5Version = 0x05
CmdTCPConnect = 0x01
CmdTCPBind = 0x02
CmdUDPAssociate = 0x03
AuthNotRequired = 0x00
AuthPassword = 0x02
AuthNoMatchingMethod = 0xFF
AuthSuccess = 0x00
AuthFailure = 0x01
AddrTypeIPv4 = 0x01
AddrTypeDomain = 0x03
AddrTypeIPv6 = 0x04
)
var _ analyzer.Analyzer = (*Socks5Analyzer)(nil)
type Socks5Analyzer struct{}
func (a *Socks5Analyzer) Name() string {
return "socks5"
}
func (a *Socks5Analyzer) Limit() int {
// TODO: more precise calculate
return 1298
}
func (a *Socks5Analyzer) NewTCP(info analyzer.TCPInfo, logger analyzer.Logger) analyzer.TCPStream {
return newSocksStream(logger)
}
type socks5Stream 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
authReqMethod int
authUsername string
authPassword string
authRespMethod int
}
func newSocksStream(logger analyzer.Logger) *socks5Stream {
s := &socks5Stream{logger: logger, reqBuf: &utils.ByteBuffer{}, respBuf: &utils.ByteBuffer{}}
s.reqLSM = utils.NewLinearStateMachine(
s.parseSocks5ReqVersion,
s.parseSocks5ReqMethod,
s.parseSocks5ReqAuth,
s.parseSocks5ReqConnInfo,
)
s.respLSM = utils.NewLinearStateMachine(
s.parseSocks5RespVerAndMethod,
s.parseSocks5RespAuth,
s.parseSocks5RespConnInfo,
)
return s
}
func (s *socks5Stream) 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{"req": s.reqMap},
}
s.reqUpdated = false
}
}
return update, cancelled || (s.reqDone && s.respDone)
}
func (s *socks5Stream) Close(limited bool) *analyzer.PropUpdate {
s.reqBuf.Reset()
s.respBuf.Reset()
s.reqMap = nil
s.respMap = nil
return nil
}
func (s *socks5Stream) parseSocks5ReqVersion() utils.LSMAction {
socksVer, ok := s.reqBuf.GetByte(true)
if !ok {
return utils.LSMActionPause
}
if socksVer != Socks5Version {
return utils.LSMActionCancel
}
return utils.LSMActionNext
}
func (s *socks5Stream) 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 = AuthNoMatchingMethod
for _, method := range methods[1:] {
switch method {
case AuthNotRequired:
s.authReqMethod = AuthNotRequired
break
case AuthPassword:
s.authReqMethod = AuthPassword
break
default:
// TODO: more auth method to support
}
}
s.reqMap = make(analyzer.PropMap)
return utils.LSMActionNext
}
func (s *socks5Stream) parseSocks5ReqAuth() utils.LSMAction {
switch s.authReqMethod {
case AuthNotRequired:
s.reqMap["auth"] = analyzer.PropMap{"method": s.authReqMethod}
case AuthPassword:
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 *socks5Stream) 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] != 0x05 {
return utils.LSMActionCancel
}
var pktLen int
switch int(preInfo[3]) {
case AddrTypeIPv4:
pktLen = 10
case AddrTypeDomain:
domainLen := int(preInfo[4])
pktLen = 7 + domainLen
case AddrTypeIPv6:
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 != CmdTCPConnect && cmd != CmdTCPBind && cmd != CmdUDPAssociate {
return utils.LSMActionCancel
}
s.reqMap["cmd"] = cmd
// parse addr type
addrType := int(pkt[3])
var addr string
switch addrType {
case AddrTypeIPv4:
addr = net.IPv4(pkt[4], pkt[5], pkt[6], pkt[7]).String()
case AddrTypeDomain:
addr = string(pkt[5 : 5+pkt[4]])
case AddrTypeIPv6:
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 *socks5Stream) parseSocks5RespVerAndMethod() utils.LSMAction {
verAndMethod, ok := s.respBuf.Get(2, true)
if !ok {
return utils.LSMActionPause
}
if verAndMethod[0] != Socks5Version {
return utils.LSMActionCancel
}
s.authRespMethod = int(verAndMethod[1])
s.respMap = make(analyzer.PropMap)
return utils.LSMActionNext
}
func (s *socks5Stream) parseSocks5RespAuth() utils.LSMAction {
switch s.authRespMethod {
case AuthNotRequired:
s.respMap["auth"] = analyzer.PropMap{"method": s.authRespMethod}
case AuthPassword:
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 *socks5Stream) 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 AddrTypeIPv4:
pktLen = 10
case AddrTypeDomain:
domainLen := int(preInfo[4])
pktLen = 7 + domainLen
case AddrTypeIPv6:
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 AddrTypeIPv4:
addr = net.IPv4(pkt[4], pkt[5], pkt[6], pkt[7]).String()
case AddrTypeDomain:
addr = string(pkt[5 : 5+pkt[4]])
case AddrTypeIPv6:
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
}

91
analyzer/tcp/trojan.go Normal file
View 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
}

View File

@@ -89,6 +89,8 @@ var analyzers = []analyzer.Analyzer{
&tcp.HTTPAnalyzer{},
&tcp.SSHAnalyzer{},
&tcp.TLSAnalyzer{},
&tcp.TrojanAnalyzer{},
&tcp.Socks5Analyzer{},
&udp.DNSAnalyzer{},
}

341
docs/Analyzers.md Normal file
View File

@@ -0,0 +1,341 @@
# 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
```
## SOCKS5
SOCKS5 without auth:
```json5
{
"socks5": {
"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
{
"socks5": {
"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 SOCKS5 google.com:80
action: block
expr: string(socks5?.req?.addr) endsWith "google.com" && socks5?.req?.port == 80
- name: Block SOCKS5 user foobar
action: block
expr: socks5?.req?.auth?.method == 2 && socks5?.req?.auth?.username == "foobar"
```

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 43 KiB

View File

@@ -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)

View File

@@ -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)

View File

@@ -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 {