From 0e2ee368654dfe7e70157dacea0b37e39f94b38f Mon Sep 17 00:00:00 2001 From: KujouRinka Date: Sat, 30 Mar 2024 12:00:17 +0800 Subject: [PATCH] refactor: merge openvpn_tcp and openvpn_udp to openvpn --- analyzer/internal/openvpn.go | 44 ---- analyzer/tcp/openvpn.go | 234 --------------------- analyzer/udp/openvpn.go | 381 +++++++++++++++++++++++++---------- cmd/root.go | 1 - 4 files changed, 278 insertions(+), 382 deletions(-) delete mode 100644 analyzer/internal/openvpn.go delete mode 100644 analyzer/tcp/openvpn.go diff --git a/analyzer/internal/openvpn.go b/analyzer/internal/openvpn.go deleted file mode 100644 index ab1a473..0000000 --- a/analyzer/internal/openvpn.go +++ /dev/null @@ -1,44 +0,0 @@ -package internal - -// Ref paper: -// https://www.usenix.org/system/files/sec22fall_xue-diwen.pdf - -// OpenVPN Opcodes definitions from: -// https://github.com/OpenVPN/openvpn/blob/master/src/openvpn/ssl_pkt.h -const ( - OpenVpnControlHardResetClientV1 = 1 - OpenVpnControlHardResetServerV1 = 2 - OpenVpnControlSoftResetV1 = 3 - OpenVpnControlV1 = 4 - OpenVpnAckV1 = 5 - OpenVpnDataV1 = 6 - OpenVpnControlHardResetClientV2 = 7 - OpenVpnControlHardResetServerV2 = 8 - OpenVpnDataV2 = 9 - OpenVpnControlHardResetClientV3 = 10 - OpenVpnControlWkcV1 = 11 -) - -const ( - OpenVpnMinPktLen = 6 - OpenVpnTcpPktDefaultLimit = 256 - OpenVpnUdpPktDefaultLimit = 256 -) - -func OpenVpnCheckForValidOpcode(opcode byte) bool { - switch opcode { - case OpenVpnControlHardResetClientV1, - OpenVpnControlHardResetServerV1, - OpenVpnControlSoftResetV1, - OpenVpnControlV1, - OpenVpnAckV1, - OpenVpnDataV1, - OpenVpnControlHardResetClientV2, - OpenVpnControlHardResetServerV2, - OpenVpnDataV2, - OpenVpnControlHardResetClientV3, - OpenVpnControlWkcV1: - return true - } - return false -} diff --git a/analyzer/tcp/openvpn.go b/analyzer/tcp/openvpn.go deleted file mode 100644 index ff28ef3..0000000 --- a/analyzer/tcp/openvpn.go +++ /dev/null @@ -1,234 +0,0 @@ -package tcp - -import ( - "github.com/apernet/OpenGFW/analyzer" - "github.com/apernet/OpenGFW/analyzer/internal" - "github.com/apernet/OpenGFW/analyzer/utils" -) - -var _ analyzer.TCPAnalyzer = (*OpenVpnAnalyzer)(nil) -var _ analyzer.TCPStream = (*openVpnStream)(nil) - -type OpenVpnAnalyzer struct{} - -func (a *OpenVpnAnalyzer) Name() string { - return "openvpn_tcp" -} - -func (a *OpenVpnAnalyzer) Limit() int { - return 0 -} - -func (a *OpenVpnAnalyzer) NewTCP(info analyzer.TCPInfo, logger analyzer.Logger) analyzer.TCPStream { - return newOpenVpnTcpStream(logger) -} - -type openVpnStream struct { - logger analyzer.Logger - - reqBuf *utils.ByteBuffer - reqUpdated bool - reqLSM *utils.LinearStateMachine - reqDone bool - - respBuf *utils.ByteBuffer - respUpdated bool - respLSM *utils.LinearStateMachine - respDone bool - - rxPktCnt int - txPktCnt int - pktLimit int - - lastOpcode byte -} - -type openVpnTcpPkt struct { - pktLen uint16 - opcode byte // 5 bits - _keyId byte // 3 bits, not used - - // We don't care about the rest of the packet - // payload []byte -} - -func newOpenVpnTcpStream(logger analyzer.Logger) *openVpnStream { - s := &openVpnStream{ - logger: logger, - reqBuf: &utils.ByteBuffer{}, - respBuf: &utils.ByteBuffer{}, - pktLimit: internal.OpenVpnTcpPktDefaultLimit, - } - s.reqLSM = utils.NewLinearStateMachine( - s.parseCtlHardResetClient, - s.parseReq, - ) - s.respLSM = utils.NewLinearStateMachine( - s.parseCtlHardResetServer, - s.parseResp, - ) - return s -} - -func (o *openVpnStream) 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 { - o.respBuf.Append(data) - o.respUpdated = false - cancelled, o.respDone = o.respLSM.Run() - if o.respUpdated { - update = &analyzer.PropUpdate{ - Type: analyzer.PropUpdateMerge, - M: analyzer.PropMap{"rx_pkt_cnt": o.rxPktCnt}, - } - o.respUpdated = false - } - } else { - o.reqBuf.Append(data) - o.reqUpdated = false - cancelled, o.reqDone = o.reqLSM.Run() - if o.reqUpdated { - update = &analyzer.PropUpdate{ - Type: analyzer.PropUpdateMerge, - M: analyzer.PropMap{"tx_pkt_cnt": o.txPktCnt}, - } - o.reqUpdated = false - } - } - - return update, cancelled || (o.reqDone && o.respDone) || o.rxPktCnt+o.txPktCnt > o.pktLimit -} - -func (o *openVpnStream) Close(limited bool) *analyzer.PropUpdate { - o.reqBuf.Reset() - o.respBuf.Reset() - return nil -} - -func (o *openVpnStream) parseCtlHardResetClient() utils.LSMAction { - pkt, action := o.parsePkt(false) - if action != utils.LSMActionNext { - return action - } - - if pkt.opcode != internal.OpenVpnControlHardResetClientV1 && - pkt.opcode != internal.OpenVpnControlHardResetClientV2 && - pkt.opcode != internal.OpenVpnControlHardResetClientV3 { - return utils.LSMActionCancel - } - o.lastOpcode = pkt.opcode - - return utils.LSMActionNext -} - -func (o *openVpnStream) parseCtlHardResetServer() utils.LSMAction { - if o.lastOpcode != internal.OpenVpnControlHardResetClientV1 && - o.lastOpcode != internal.OpenVpnControlHardResetClientV2 && - o.lastOpcode != internal.OpenVpnControlHardResetClientV3 { - return utils.LSMActionCancel - } - - pkt, action := o.parsePkt(true) - if action != utils.LSMActionNext { - return action - } - - if pkt.opcode != internal.OpenVpnControlHardResetServerV1 && - pkt.opcode != internal.OpenVpnControlHardResetServerV2 { - return utils.LSMActionCancel - } - o.lastOpcode = pkt.opcode - - return utils.LSMActionNext -} - -func (o *openVpnStream) parseReq() utils.LSMAction { - pkt, action := o.parsePkt(false) - if action != utils.LSMActionNext { - return action - } - - if pkt.opcode != internal.OpenVpnControlSoftResetV1 && - pkt.opcode != internal.OpenVpnControlV1 && - pkt.opcode != internal.OpenVpnAckV1 && - pkt.opcode != internal.OpenVpnDataV1 && - pkt.opcode != internal.OpenVpnDataV2 && - pkt.opcode != internal.OpenVpnControlWkcV1 { - return utils.LSMActionCancel - } - - o.txPktCnt += 1 - o.reqUpdated = true - - return utils.LSMActionPause -} - -func (o *openVpnStream) parseResp() utils.LSMAction { - pkt, action := o.parsePkt(true) - if action != utils.LSMActionNext { - return action - } - - if pkt.opcode != internal.OpenVpnControlSoftResetV1 && - pkt.opcode != internal.OpenVpnControlV1 && - pkt.opcode != internal.OpenVpnAckV1 && - pkt.opcode != internal.OpenVpnDataV1 && - pkt.opcode != internal.OpenVpnDataV2 && - pkt.opcode != internal.OpenVpnControlWkcV1 { - return utils.LSMActionCancel - } - - o.rxPktCnt += 1 - o.respUpdated = true - - return utils.LSMActionPause -} - -// Parse OpenVpn packet header but not consume buffer. -func (o *openVpnStream) parsePkt(rev bool) (p *openVpnTcpPkt, action utils.LSMAction) { - var buffer *utils.ByteBuffer - if rev { - buffer = o.respBuf - } else { - buffer = o.reqBuf - } - - // Parse packet length - pktLen, ok := buffer.GetUint16(false, false) - if !ok { - return nil, utils.LSMActionPause - } - - if pktLen < internal.OpenVpnMinPktLen { - return nil, utils.LSMActionCancel - } - - pktOp, ok := buffer.Get(3, false) - if !ok { - return nil, utils.LSMActionPause - } - if !internal.OpenVpnCheckForValidOpcode(pktOp[2] >> 3) { - return nil, utils.LSMActionCancel - } - - pkt, ok := buffer.Get(int(pktLen)+2, true) - if !ok { - return nil, utils.LSMActionPause - } - pkt = pkt[2:] - - // Parse packet header - p = &openVpnTcpPkt{} - p.pktLen = pktLen - p.opcode = pkt[0] >> 3 - p._keyId = pkt[0] & 0x07 - - return p, utils.LSMActionNext -} diff --git a/analyzer/udp/openvpn.go b/analyzer/udp/openvpn.go index ae0cb90..1d64ed3 100644 --- a/analyzer/udp/openvpn.go +++ b/analyzer/udp/openvpn.go @@ -2,17 +2,44 @@ package udp import ( "github.com/apernet/OpenGFW/analyzer" - "github.com/apernet/OpenGFW/analyzer/internal" "github.com/apernet/OpenGFW/analyzer/utils" ) var _ analyzer.UDPAnalyzer = (*OpenVpnAnalyzer)(nil) -var _ analyzer.UDPStream = (*openVpnStream)(nil) +var _ analyzer.TCPAnalyzer = (*OpenVpnAnalyzer)(nil) + +var _ analyzer.UDPStream = (*openVpnUdpStream)(nil) +var _ analyzer.TCPStream = (*openVpnTcpStream)(nil) + +// Ref paper: +// https://www.usenix.org/system/files/sec22fall_xue-diwen.pdf + +// OpenVPN Opcodes definitions from: +// https://github.com/OpenVPN/openvpn/blob/master/src/openvpn/ssl_pkt.h +const ( + OpenVpnControlHardResetClientV1 = 1 + OpenVpnControlHardResetServerV1 = 2 + OpenVpnControlSoftResetV1 = 3 + OpenVpnControlV1 = 4 + OpenVpnAckV1 = 5 + OpenVpnDataV1 = 6 + OpenVpnControlHardResetClientV2 = 7 + OpenVpnControlHardResetServerV2 = 8 + OpenVpnDataV2 = 9 + OpenVpnControlHardResetClientV3 = 10 + OpenVpnControlWkcV1 = 11 +) + +const ( + OpenVpnMinPktLen = 6 + OpenVpnTcpPktDefaultLimit = 256 + OpenVpnUdpPktDefaultLimit = 256 +) type OpenVpnAnalyzer struct{} func (a *OpenVpnAnalyzer) Name() string { - return "openvpn_udp" + return "openvpn" } func (a *OpenVpnAnalyzer) Limit() int { @@ -20,15 +47,24 @@ func (a *OpenVpnAnalyzer) Limit() int { } func (a *OpenVpnAnalyzer) NewUDP(info analyzer.UDPInfo, logger analyzer.Logger) analyzer.UDPStream { - return newOpenVPNUdpStream(logger) + return newOpenVpnUdpStream(logger) +} + +func (a *OpenVpnAnalyzer) NewTCP(info analyzer.TCPInfo, logger analyzer.Logger) analyzer.TCPStream { + return newOpenVpnTcpStream(logger) +} + +type openVpnPkt struct { + pktLen uint16 // 16 bits, TCP proto only + opcode byte // 5 bits + _keyId byte // 3 bits, not used + + // We don't care about the rest of the packet + // payload []byte } type openVpnStream struct { logger analyzer.Logger - // We don't introduce `invalidCount` here to decrease the false positive rate - // invalidCount int - - curPkt []byte reqUpdated bool reqLSM *utils.LinearStateMachine @@ -42,22 +78,107 @@ type openVpnStream struct { txPktCnt int pktLimit int + reqPktParse func() (*openVpnPkt, utils.LSMAction) + respPktParse func() (*openVpnPkt, utils.LSMAction) + lastOpcode byte } -type openVpnUdpPkt struct { - opcode byte // 5 bits - _keyId byte // 3 bits, not used +func (o *openVpnStream) parseCtlHardResetClient() utils.LSMAction { + pkt, action := o.reqPktParse() + if action != utils.LSMActionNext { + return action + } - // We don't care about the rest of the packet - // payload []byte + if pkt.opcode != OpenVpnControlHardResetClientV1 && + pkt.opcode != OpenVpnControlHardResetClientV2 && + pkt.opcode != OpenVpnControlHardResetClientV3 { + return utils.LSMActionCancel + } + o.lastOpcode = pkt.opcode + + return utils.LSMActionNext } -func newOpenVPNUdpStream(logger analyzer.Logger) *openVpnStream { - s := &openVpnStream{ - logger: logger, - pktLimit: internal.OpenVpnUdpPktDefaultLimit, +func (o *openVpnStream) parseCtlHardResetServer() utils.LSMAction { + if o.lastOpcode != OpenVpnControlHardResetClientV1 && + o.lastOpcode != OpenVpnControlHardResetClientV2 && + o.lastOpcode != OpenVpnControlHardResetClientV3 { + return utils.LSMActionCancel } + + pkt, action := o.respPktParse() + if action != utils.LSMActionNext { + return action + } + + if pkt.opcode != OpenVpnControlHardResetServerV1 && + pkt.opcode != OpenVpnControlHardResetServerV2 { + return utils.LSMActionCancel + } + o.lastOpcode = pkt.opcode + + return utils.LSMActionNext +} + +func (o *openVpnStream) parseReq() utils.LSMAction { + pkt, action := o.reqPktParse() + if action != utils.LSMActionNext { + return action + } + + if pkt.opcode != OpenVpnControlSoftResetV1 && + pkt.opcode != OpenVpnControlV1 && + pkt.opcode != OpenVpnAckV1 && + pkt.opcode != OpenVpnDataV1 && + pkt.opcode != OpenVpnDataV2 && + pkt.opcode != OpenVpnControlWkcV1 { + return utils.LSMActionCancel + } + + o.txPktCnt += 1 + o.reqUpdated = true + + return utils.LSMActionPause +} + +func (o *openVpnStream) parseResp() utils.LSMAction { + pkt, action := o.respPktParse() + if action != utils.LSMActionNext { + return action + } + + if pkt.opcode != OpenVpnControlSoftResetV1 && + pkt.opcode != OpenVpnControlV1 && + pkt.opcode != OpenVpnAckV1 && + pkt.opcode != OpenVpnDataV1 && + pkt.opcode != OpenVpnDataV2 && + pkt.opcode != OpenVpnControlWkcV1 { + return utils.LSMActionCancel + } + + o.rxPktCnt += 1 + o.respUpdated = true + + return utils.LSMActionPause +} + +type openVpnUdpStream struct { + openVpnStream + curPkt []byte + // We don't introduce `invalidCount` here to decrease the false positive rate + // invalidCount int +} + +func newOpenVpnUdpStream(logger analyzer.Logger) *openVpnUdpStream { + s := &openVpnUdpStream{ + openVpnStream: openVpnStream{ + logger: logger, + pktLimit: OpenVpnUdpPktDefaultLimit, + }, + } + s.respPktParse = s.parsePkt + s.reqPktParse = s.parsePkt s.reqLSM = utils.NewLinearStateMachine( s.parseCtlHardResetClient, s.parseReq, @@ -69,7 +190,7 @@ func newOpenVPNUdpStream(logger analyzer.Logger) *openVpnStream { return s } -func (o *openVpnStream) Feed(rev bool, data []byte) (u *analyzer.PropUpdate, d bool) { +func (o *openVpnUdpStream) Feed(rev bool, data []byte) (u *analyzer.PropUpdate, d bool) { if len(data) == 0 { return nil, false } @@ -101,105 +222,159 @@ func (o *openVpnStream) Feed(rev bool, data []byte) (u *analyzer.PropUpdate, d b return update, cancelled || (o.reqDone && o.respDone) || o.rxPktCnt+o.txPktCnt > o.pktLimit } -func (o *openVpnStream) Close(limited bool) *analyzer.PropUpdate { +func (o *openVpnUdpStream) Close(limited bool) *analyzer.PropUpdate { return nil } -func (o *openVpnStream) parseCtlHardResetClient() utils.LSMAction { - pkt, action := o.parsePkt() - if action != utils.LSMActionNext { - return action - } - - if pkt.opcode != internal.OpenVpnControlHardResetClientV1 && - pkt.opcode != internal.OpenVpnControlHardResetClientV2 && - pkt.opcode != internal.OpenVpnControlHardResetClientV3 { - return utils.LSMActionCancel - } - o.lastOpcode = pkt.opcode - - return utils.LSMActionNext -} - -func (o *openVpnStream) parseCtlHardResetServer() utils.LSMAction { - - if o.lastOpcode != internal.OpenVpnControlHardResetClientV1 && - o.lastOpcode != internal.OpenVpnControlHardResetClientV2 && - o.lastOpcode != internal.OpenVpnControlHardResetClientV3 { - return utils.LSMActionCancel - } - - pkt, action := o.parsePkt() - if action != utils.LSMActionNext { - return action - } - - if pkt.opcode != internal.OpenVpnControlHardResetServerV1 && - pkt.opcode != internal.OpenVpnControlHardResetServerV2 { - return utils.LSMActionCancel - } - o.lastOpcode = pkt.opcode - - return utils.LSMActionNext -} - -func (o *openVpnStream) parseReq() utils.LSMAction { - pkt, action := o.parsePkt() - if action != utils.LSMActionNext { - return action - } - - if pkt.opcode != internal.OpenVpnControlSoftResetV1 && - pkt.opcode != internal.OpenVpnControlV1 && - pkt.opcode != internal.OpenVpnAckV1 && - pkt.opcode != internal.OpenVpnDataV1 && - pkt.opcode != internal.OpenVpnDataV2 && - pkt.opcode != internal.OpenVpnControlWkcV1 { - return utils.LSMActionCancel - } - - o.txPktCnt += 1 - o.reqUpdated = true - - return utils.LSMActionPause -} - -func (o *openVpnStream) parseResp() utils.LSMAction { - pkt, action := o.parsePkt() - if action != utils.LSMActionNext { - return action - } - - if pkt.opcode != internal.OpenVpnControlSoftResetV1 && - pkt.opcode != internal.OpenVpnControlV1 && - pkt.opcode != internal.OpenVpnAckV1 && - pkt.opcode != internal.OpenVpnDataV1 && - pkt.opcode != internal.OpenVpnDataV2 && - pkt.opcode != internal.OpenVpnControlWkcV1 { - return utils.LSMActionCancel - } - - o.rxPktCnt += 1 - o.respUpdated = true - - return utils.LSMActionPause -} - -// Parse OpenVpn packet header but not consume buffer. -func (o *openVpnStream) parsePkt() (p *openVpnUdpPkt, action utils.LSMAction) { +// Parse OpenVpn UDP packet. +func (o *openVpnUdpStream) parsePkt() (p *openVpnPkt, action utils.LSMAction) { if o.curPkt == nil { return nil, utils.LSMActionPause } - if !internal.OpenVpnCheckForValidOpcode(o.curPkt[0] >> 3) { + if !OpenVpnCheckForValidOpcode(o.curPkt[0] >> 3) { return nil, utils.LSMActionCancel } // Parse packet header - p = &openVpnUdpPkt{} + p = &openVpnPkt{} p.opcode = o.curPkt[0] >> 3 p._keyId = o.curPkt[0] & 0x07 o.curPkt = nil return p, utils.LSMActionNext } + +type openVpnTcpStream struct { + openVpnStream + reqBuf *utils.ByteBuffer + respBuf *utils.ByteBuffer +} + +func newOpenVpnTcpStream(logger analyzer.Logger) *openVpnTcpStream { + s := &openVpnTcpStream{ + openVpnStream: openVpnStream{ + logger: logger, + pktLimit: OpenVpnTcpPktDefaultLimit, + }, + reqBuf: &utils.ByteBuffer{}, + respBuf: &utils.ByteBuffer{}, + } + s.respPktParse = func() (*openVpnPkt, utils.LSMAction) { + return s.parsePkt(true) + } + s.reqPktParse = func() (*openVpnPkt, utils.LSMAction) { + return s.parsePkt(false) + } + s.reqLSM = utils.NewLinearStateMachine( + s.parseCtlHardResetClient, + s.parseReq, + ) + s.respLSM = utils.NewLinearStateMachine( + s.parseCtlHardResetServer, + s.parseResp, + ) + return s +} + +func (o *openVpnTcpStream) 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 { + o.respBuf.Append(data) + o.respUpdated = false + cancelled, o.respDone = o.respLSM.Run() + if o.respUpdated { + update = &analyzer.PropUpdate{ + Type: analyzer.PropUpdateMerge, + M: analyzer.PropMap{"rx_pkt_cnt": o.rxPktCnt}, + } + o.respUpdated = false + } + } else { + o.reqBuf.Append(data) + o.reqUpdated = false + cancelled, o.reqDone = o.reqLSM.Run() + if o.reqUpdated { + update = &analyzer.PropUpdate{ + Type: analyzer.PropUpdateMerge, + M: analyzer.PropMap{"tx_pkt_cnt": o.txPktCnt}, + } + o.reqUpdated = false + } + } + + return update, cancelled || (o.reqDone && o.respDone) || o.rxPktCnt+o.txPktCnt > o.pktLimit +} + +func (o *openVpnTcpStream) Close(limited bool) *analyzer.PropUpdate { + o.reqBuf.Reset() + o.respBuf.Reset() + return nil +} + +// Parse OpenVpn TCP packet. +func (o *openVpnTcpStream) parsePkt(rev bool) (p *openVpnPkt, action utils.LSMAction) { + var buffer *utils.ByteBuffer + if rev { + buffer = o.respBuf + } else { + buffer = o.reqBuf + } + + // Parse packet length + pktLen, ok := buffer.GetUint16(false, false) + if !ok { + return nil, utils.LSMActionPause + } + + if pktLen < OpenVpnMinPktLen { + return nil, utils.LSMActionCancel + } + + pktOp, ok := buffer.Get(3, false) + if !ok { + return nil, utils.LSMActionPause + } + if !OpenVpnCheckForValidOpcode(pktOp[2] >> 3) { + return nil, utils.LSMActionCancel + } + + pkt, ok := buffer.Get(int(pktLen)+2, true) + if !ok { + return nil, utils.LSMActionPause + } + pkt = pkt[2:] + + // Parse packet header + p = &openVpnPkt{} + p.pktLen = pktLen + p.opcode = pkt[0] >> 3 + p._keyId = pkt[0] & 0x07 + + return p, utils.LSMActionNext +} + +func OpenVpnCheckForValidOpcode(opcode byte) bool { + switch opcode { + case OpenVpnControlHardResetClientV1, + OpenVpnControlHardResetServerV1, + OpenVpnControlSoftResetV1, + OpenVpnControlV1, + OpenVpnAckV1, + OpenVpnDataV1, + OpenVpnControlHardResetClientV2, + OpenVpnControlHardResetServerV2, + OpenVpnDataV2, + OpenVpnControlHardResetClientV3, + OpenVpnControlWkcV1: + return true + } + return false +} diff --git a/cmd/root.go b/cmd/root.go index dfb5f4c..7ba319c 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -87,7 +87,6 @@ var logFormatMap = map[string]zapcore.EncoderConfig{ var analyzers = []analyzer.Analyzer{ &tcp.FETAnalyzer{}, &tcp.HTTPAnalyzer{}, - &tcp.OpenVpnAnalyzer{}, &tcp.SocksAnalyzer{}, &tcp.SSHAnalyzer{}, &tcp.TLSAnalyzer{},