mirror of
https://github.com/apernet/OpenGFW.git
synced 2024-11-11 04:49:22 +08:00
commit
73d78489b5
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
|
||||||
|
}
|
||||||
|
}
|
@ -1,362 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
@ -44,6 +44,10 @@ func (lsm *LinearStateMachine) Run() (cancelled bool, done bool) {
|
|||||||
return false, true
|
return false, true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (lsm *LinearStateMachine) AppendSteps(steps ...func() LSMAction) {
|
||||||
|
lsm.Steps = append(lsm.Steps, steps...)
|
||||||
|
}
|
||||||
|
|
||||||
func (lsm *LinearStateMachine) Reset() {
|
func (lsm *LinearStateMachine) Reset() {
|
||||||
lsm.index = 0
|
lsm.index = 0
|
||||||
lsm.cancelled = false
|
lsm.cancelled = false
|
||||||
|
@ -87,10 +87,10 @@ var logFormatMap = map[string]zapcore.EncoderConfig{
|
|||||||
var analyzers = []analyzer.Analyzer{
|
var analyzers = []analyzer.Analyzer{
|
||||||
&tcp.FETAnalyzer{},
|
&tcp.FETAnalyzer{},
|
||||||
&tcp.HTTPAnalyzer{},
|
&tcp.HTTPAnalyzer{},
|
||||||
|
&tcp.SocksAnalyzer{},
|
||||||
&tcp.SSHAnalyzer{},
|
&tcp.SSHAnalyzer{},
|
||||||
&tcp.TLSAnalyzer{},
|
&tcp.TLSAnalyzer{},
|
||||||
&tcp.TrojanAnalyzer{},
|
&tcp.TrojanAnalyzer{},
|
||||||
&tcp.Socks5Analyzer{},
|
|
||||||
&udp.DNSAnalyzer{},
|
&udp.DNSAnalyzer{},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -269,13 +269,42 @@ Example for blocking Trojan connections:
|
|||||||
expr: trojan != nil && trojan.yes
|
expr: trojan != nil && trojan.yes
|
||||||
```
|
```
|
||||||
|
|
||||||
## SOCKS5
|
## 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:
|
SOCKS5 without auth:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
"socks5": {
|
"socks": {
|
||||||
|
"version": 5,
|
||||||
"req": {
|
"req": {
|
||||||
"cmd": 1, // 0x01: connect, 0x02: bind, 0x03: udp
|
"cmd": 1, // 0x01: connect, 0x02: bind, 0x03: udp
|
||||||
"addr_type": 3, // 0x01: ipv4, 0x03: domain, 0x04: ipv6
|
"addr_type": 3, // 0x01: ipv4, 0x03: domain, 0x04: ipv6
|
||||||
@ -302,7 +331,8 @@ SOCKS5 with auth:
|
|||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
"socks5": {
|
"socks": {
|
||||||
|
"version": 5,
|
||||||
"req": {
|
"req": {
|
||||||
"cmd": 1, // 0x01: connect, 0x02: bind, 0x03: udp
|
"cmd": 1, // 0x01: connect, 0x02: bind, 0x03: udp
|
||||||
"addr_type": 3, // 0x01: ipv4, 0x03: domain, 0x04: ipv6
|
"addr_type": 3, // 0x01: ipv4, 0x03: domain, 0x04: ipv6
|
||||||
@ -331,11 +361,11 @@ SOCKS5 with auth:
|
|||||||
Example for blocking connections to `google.com:80` and user `foobar`:
|
Example for blocking connections to `google.com:80` and user `foobar`:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- name: Block SOCKS5 google.com:80
|
- name: Block SOCKS google.com:80
|
||||||
action: block
|
action: block
|
||||||
expr: string(socks5?.req?.addr) endsWith "google.com" && socks5?.req?.port == 80
|
expr: string(socks?.req?.addr) endsWith "google.com" && socks?.req?.port == 80
|
||||||
|
|
||||||
- name: Block SOCKS5 user foobar
|
- name: Block SOCKS user foobar
|
||||||
action: block
|
action: block
|
||||||
expr: socks5?.req?.auth?.method == 2 && socks5?.req?.auth?.username == "foobar"
|
expr: socks?.req?.auth?.method == 2 && socks?.req?.auth?.username == "foobar"
|
||||||
```
|
```
|
||||||
|
Loading…
Reference in New Issue
Block a user