mirror of
https://github.com/nadoo/glider.git
synced 2026-09-29 16:55:50 +08:00
Compare commits
No commits in common. "e7b12e36a96e2c598245db07af550d171e087245" and "533571f1ecf0bd77436d5e54634561ca0f42a1b1" have entirely different histories.
e7b12e36a9
...
533571f1ec
21
README.md
21
README.md
@ -6,7 +6,7 @@
|
|||||||
[](https://github.com/nadoo/glider/actions)
|
[](https://github.com/nadoo/glider/actions)
|
||||||
[](https://hub.docker.com/r/nadoo/glider)
|
[](https://hub.docker.com/r/nadoo/glider)
|
||||||
|
|
||||||
glider is a forward proxy with multiple protocols support, and also a dns/dhcp server with ipset management features.
|
glider is a forward proxy with multiple protocols support, and also a dns/dhcp server with ipset management features(like dnsmasq).
|
||||||
|
|
||||||
we can set up local listeners as proxy servers, and forward requests to internet via forwarders.
|
we can set up local listeners as proxy servers, and forward requests to internet via forwarders.
|
||||||
|
|
||||||
@ -52,12 +52,13 @@ we can set up local listeners as proxy servers, and forward requests to internet
|
|||||||
|HTTP |√| |√| |client & server
|
|HTTP |√| |√| |client & server
|
||||||
|SOCKS5 |√|√|√|√|client & server
|
|SOCKS5 |√|√|√|√|client & server
|
||||||
|SS |√|√|√|√|client & server
|
|SS |√|√|√|√|client & server
|
||||||
|Trojan |√| |√|√|client & server
|
|Trojan |√|√|√|√|client & server
|
||||||
|Trojanc |√| |√|√|trojan cleartext(without tls)
|
|Trojanc |√|√|√|√|trojan cleartext(without tls)
|
||||||
|AnyTLS |√| |√|√|client & server
|
|AnyTLS |√| |√| |client & server
|
||||||
|AnyTLSc |√| |√|√|anytls cleartext(without tls)
|
|AnyTLSc |√| |√| |anytls cleartext(without tls)
|
||||||
|VLESS |√| |√|√|client & server
|
|VLESS |√|√|√|√|client & server
|
||||||
|VMess | | |√|√|client only
|
|VMess | | |√|√|client only
|
||||||
|
|SSR | | |√| |client only
|
||||||
|SSH | | |√| |client only
|
|SSH | | |√| |client only
|
||||||
|SOCKS4 | | |√| |client only
|
|SOCKS4 | | |√| |client only
|
||||||
|SOCKS4A | | |√| |client only
|
|SOCKS4A | | |√| |client only
|
||||||
@ -119,7 +120,7 @@ OPTION:
|
|||||||
-checkdisabledonly
|
-checkdisabledonly
|
||||||
check disabled fowarders only
|
check disabled fowarders only
|
||||||
-checkinterval int
|
-checkinterval int
|
||||||
fowarder check interval(seconds) (default 30)ß
|
fowarder check interval(seconds) (default 30)
|
||||||
-checklatencysamples int
|
-checklatencysamples int
|
||||||
use the average latency of the latest N checks (default 10)
|
use the average latency of the latest N checks (default 10)
|
||||||
-checktimeout int
|
-checktimeout int
|
||||||
@ -199,7 +200,7 @@ URL:
|
|||||||
|
|
||||||
SCHEME:
|
SCHEME:
|
||||||
listen : anytls anytlsc http kcp mixed pxyproto redir redir6 smux sni socks5 ss tcp tls tproxy trojan trojanc udp unix vless vsock ws wss
|
listen : anytls anytlsc http kcp mixed pxyproto redir redir6 smux sni socks5 ss tcp tls tproxy trojan trojanc udp unix vless vsock ws wss
|
||||||
forward: anytls anytlsc direct http kcp reject simple-obfs smux socks4 socks4a socks5 ss ssh tcp tls trojan trojanc udp unix vless vmess vsock ws wss
|
forward: anytls anytlsc direct http kcp reject simple-obfs smux socks4 socks4a socks5 ss ssh ssr tcp tls trojan trojanc udp unix vless vmess vsock ws wss
|
||||||
|
|
||||||
Note: use 'glider -scheme all' or 'glider -scheme SCHEME' to see help info for the scheme.
|
Note: use 'glider -scheme all' or 'glider -scheme SCHEME' to see help info for the scheme.
|
||||||
|
|
||||||
@ -303,6 +304,10 @@ SSH scheme:
|
|||||||
ssh://user[:pass]@host:port[?key=keypath&timeout=SECONDS]
|
ssh://user[:pass]@host:port[?key=keypath&timeout=SECONDS]
|
||||||
timeout: timeout of ssh handshake and channel operation, default: 5
|
timeout: timeout of ssh handshake and channel operation, default: 5
|
||||||
|
|
||||||
|
--
|
||||||
|
SSR scheme:
|
||||||
|
ssr://method:pass@host:port?protocol=xxx&protocol_param=yyy&obfs=zzz&obfs_param=xyz
|
||||||
|
|
||||||
--
|
--
|
||||||
TLS client scheme:
|
TLS client scheme:
|
||||||
tls://host:port[?serverName=SERVERNAME][&skipVerify=true][&cert=PATH][&alpn=proto1][&alpn=proto2]
|
tls://host:port[?serverName=SERVERNAME][&skipVerify=true][&cert=PATH][&alpn=proto1][&alpn=proto2]
|
||||||
|
|||||||
@ -115,6 +115,9 @@ listen=127.0.0.1:8443
|
|||||||
# SS proxy as forwarder
|
# SS proxy as forwarder
|
||||||
# forward=ss://method:pass@1.1.1.1:8443
|
# forward=ss://method:pass@1.1.1.1:8443
|
||||||
|
|
||||||
|
# SSR proxy as forwarder
|
||||||
|
# forward=ssr://method:pass@1.1.1.1:8443?protocol=auth_aes128_md5&protocol_param=xxx&obfs=tls1.2_ticket_auth&obfs_param=yyy
|
||||||
|
|
||||||
# ssh forwarder
|
# ssh forwarder
|
||||||
# forward=ssh://user[:pass]@host:port[?key=keypath&timeout=SECONDS]
|
# forward=ssh://user[:pass]@host:port[?key=keypath&timeout=SECONDS]
|
||||||
# forward=ssh://root:pass@host:port
|
# forward=ssh://root:pass@host:port
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import (
|
|||||||
_ "github.com/nadoo/glider/proxy/socks5"
|
_ "github.com/nadoo/glider/proxy/socks5"
|
||||||
_ "github.com/nadoo/glider/proxy/ss"
|
_ "github.com/nadoo/glider/proxy/ss"
|
||||||
_ "github.com/nadoo/glider/proxy/ssh"
|
_ "github.com/nadoo/glider/proxy/ssh"
|
||||||
|
_ "github.com/nadoo/glider/proxy/ssr"
|
||||||
_ "github.com/nadoo/glider/proxy/tcp"
|
_ "github.com/nadoo/glider/proxy/tcp"
|
||||||
_ "github.com/nadoo/glider/proxy/tls"
|
_ "github.com/nadoo/glider/proxy/tls"
|
||||||
_ "github.com/nadoo/glider/proxy/trojan"
|
_ "github.com/nadoo/glider/proxy/trojan"
|
||||||
|
|||||||
4
go.mod
4
go.mod
@ -4,6 +4,9 @@ go 1.26
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da
|
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da
|
||||||
|
github.com/dgryski/go-camellia v0.0.0-20191119043421-69a8a13fb23d
|
||||||
|
github.com/dgryski/go-idea v0.0.0-20170306091226-d2fb45a411fb
|
||||||
|
github.com/dgryski/go-rc2 v0.0.0-20150621095337-8a9021637152
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20260603135910-a415979eb11e
|
github.com/insomniacslk/dhcp v0.0.0-20260603135910-a415979eb11e
|
||||||
github.com/nadoo/conflag v0.3.1
|
github.com/nadoo/conflag v0.3.1
|
||||||
github.com/nadoo/ipset v0.5.0
|
github.com/nadoo/ipset v0.5.0
|
||||||
@ -13,6 +16,7 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||||
github.com/klauspost/reedsolomon v1.14.1 // indirect
|
github.com/klauspost/reedsolomon v1.14.1 // indirect
|
||||||
github.com/pierrec/lz4/v4 v4.1.27 // indirect
|
github.com/pierrec/lz4/v4 v4.1.27 // indirect
|
||||||
|
|||||||
8
go.sum
8
go.sum
@ -7,6 +7,14 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
|
|||||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dgryski/go-camellia v0.0.0-20191119043421-69a8a13fb23d h1:CPqTNIigGweVPT4CYb+OO2E6XyRKFOmvTHwWRLgCAlE=
|
||||||
|
github.com/dgryski/go-camellia v0.0.0-20191119043421-69a8a13fb23d/go.mod h1:QX5ZVULjAfZJux/W62Y91HvCh9hyW6enAwcrrv/sLj0=
|
||||||
|
github.com/dgryski/go-idea v0.0.0-20170306091226-d2fb45a411fb h1:zXpN5126w/mhECTkqazBkrOJIMatbPP71aSIDR5UuW4=
|
||||||
|
github.com/dgryski/go-idea v0.0.0-20170306091226-d2fb45a411fb/go.mod h1:F7WkpqJj9t98ePxB/WJGQTIDeOVPuSJ3qdn6JUjg170=
|
||||||
|
github.com/dgryski/go-rc2 v0.0.0-20150621095337-8a9021637152 h1:ED31mPIxDJnrLt9W9dH5xgd/6KjzEACKHBVGQ33czc0=
|
||||||
|
github.com/dgryski/go-rc2 v0.0.0-20150621095337-8a9021637152/go.mod h1:I9fhc/EvSg88cDxmfQ47v35Ssz9rlFunL/KY0A1JAYI=
|
||||||
|
github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521 h1:fBHFH+Y/GPGFGo7LIrErQc3p2MeAhoIQNgaxPWYsSxk=
|
||||||
|
github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:ucvhdsUCE3TH0LoLRb6ShHiJl8e39dGlx6A4g/ujlow=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
|
|||||||
@ -64,42 +64,7 @@ func (s *AnyTLS) Dial(network, addr string) (net.Conn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *AnyTLS) DialUDP(network, addr string) (net.PacketConn, error) {
|
func (s *AnyTLS) DialUDP(network, addr string) (net.PacketConn, error) {
|
||||||
if network != "udp" && network != "udp4" && network != "udp6" {
|
return nil, proxy.ErrNotSupported
|
||||||
return nil, proxy.ErrNotSupported
|
|
||||||
}
|
|
||||||
target := socks.ParseAddr(addr)
|
|
||||||
if target == nil {
|
|
||||||
return nil, fmt.Errorf("[anytls] invalid target address: %s", addr)
|
|
||||||
}
|
|
||||||
raw := socks.ParseAddr(net.JoinHostPort(uotV2MagicHost, "0"))
|
|
||||||
if raw == nil {
|
|
||||||
return nil, fmt.Errorf("[anytls] invalid udp-over-tcp target address")
|
|
||||||
}
|
|
||||||
ss, err := s.newClientSession()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
st, err := ss.openStream()
|
|
||||||
if err != nil {
|
|
||||||
_ = ss.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if _, err := st.Write(raw); err != nil {
|
|
||||||
_ = st.Close()
|
|
||||||
_ = ss.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := writeUOTV2Request(st, target); err != nil {
|
|
||||||
_ = st.Close()
|
|
||||||
_ = ss.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := ss.waitSYNACK(st.id, s.synackTimeout); err != nil {
|
|
||||||
_ = st.Close()
|
|
||||||
_ = ss.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &clientPacketConn{PacketConn: newUOTPacketConn(st, target), session: ss}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AnyTLS) newClientSession() (*session, error) {
|
func (s *AnyTLS) newClientSession() (*session, error) {
|
||||||
@ -140,14 +105,3 @@ func (c *clientConn) Close() error {
|
|||||||
_ = c.session.Close()
|
_ = c.session.Close()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
type clientPacketConn struct {
|
|
||||||
net.PacketConn
|
|
||||||
session *session
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *clientPacketConn) Close() error {
|
|
||||||
err := c.PacketConn.Close()
|
|
||||||
_ = c.session.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,95 +0,0 @@
|
|||||||
package anytls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/nadoo/glider/pkg/pool"
|
|
||||||
"github.com/nadoo/glider/pkg/socks"
|
|
||||||
)
|
|
||||||
|
|
||||||
const uotV2MagicHost = "sp.v2.udp-over-tcp.arpa"
|
|
||||||
|
|
||||||
// uotPacketConn carries UDP packets over an AnyTLS stream using sing-box
|
|
||||||
// udp-over-tcp v2 connect format.
|
|
||||||
type uotPacketConn struct {
|
|
||||||
net.Conn
|
|
||||||
target socks.Addr
|
|
||||||
}
|
|
||||||
|
|
||||||
func newUOTPacketConn(c net.Conn, target socks.Addr) *uotPacketConn {
|
|
||||||
return &uotPacketConn{Conn: c, target: target}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pc *uotPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
|
||||||
if len(b) < 2 {
|
|
||||||
return 0, pc.target, errors.New("buf size is not enough")
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := io.ReadFull(pc.Conn, b[:2]); err != nil {
|
|
||||||
return 0, pc.target, err
|
|
||||||
}
|
|
||||||
length := int(binary.BigEndian.Uint16(b[:2]))
|
|
||||||
if len(b) < length {
|
|
||||||
return 0, pc.target, errors.New("buf size is not enough")
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err := io.ReadFull(pc.Conn, b[:length])
|
|
||||||
return n, pc.target, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pc *uotPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
|
||||||
buf := pool.GetBytesBuffer()
|
|
||||||
defer pool.PutBytesBuffer(buf)
|
|
||||||
|
|
||||||
var head [2]byte
|
|
||||||
binary.BigEndian.PutUint16(head[:], uint16(len(b)))
|
|
||||||
buf.Write(head[:])
|
|
||||||
buf.Write(b)
|
|
||||||
|
|
||||||
n, err := pc.Write(buf.Bytes())
|
|
||||||
if n > 2 {
|
|
||||||
return n - 2, err
|
|
||||||
}
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pc *uotPacketConn) SetDeadline(t time.Time) error {
|
|
||||||
return pc.Conn.SetDeadline(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pc *uotPacketConn) SetReadDeadline(t time.Time) error {
|
|
||||||
return pc.Conn.SetReadDeadline(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pc *uotPacketConn) SetWriteDeadline(t time.Time) error {
|
|
||||||
return pc.Conn.SetWriteDeadline(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeUOTV2Request(w io.Writer, target socks.Addr) error {
|
|
||||||
if target == nil {
|
|
||||||
return errors.New("invalid target address")
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := pool.GetBytesBuffer()
|
|
||||||
defer pool.PutBytesBuffer(buf)
|
|
||||||
|
|
||||||
buf.WriteByte(1) // connect stream format
|
|
||||||
buf.Write(target)
|
|
||||||
_, err := w.Write(buf.Bytes())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func readUOTV2Request(r io.Reader) (socks.Addr, error) {
|
|
||||||
var connect [1]byte
|
|
||||||
if _, err := io.ReadFull(r, connect[:]); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if connect[0] != 1 {
|
|
||||||
return nil, errors.New("udp-over-tcp v2 non-connect format is not supported")
|
|
||||||
}
|
|
||||||
return socks.ReadAddr(r)
|
|
||||||
}
|
|
||||||
@ -8,7 +8,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/nadoo/glider/pkg/log"
|
"github.com/nadoo/glider/pkg/log"
|
||||||
"github.com/nadoo/glider/pkg/socks"
|
"github.com/nadoo/glider/pkg/socks"
|
||||||
@ -128,11 +127,6 @@ func (s *AnyTLS) serveStream(ss *session, st *stream) {
|
|||||||
log.F("[anytls] read target error: %v", err)
|
log.F("[anytls] read target error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
host, _, err := net.SplitHostPort(target.String())
|
|
||||||
if err == nil && host == uotV2MagicHost {
|
|
||||||
s.serveUoT(ss, st)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
rc, dialer, err := s.proxy.Dial("tcp", target.String())
|
rc, dialer, err := s.proxy.Dial("tcp", target.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -151,32 +145,3 @@ func (s *AnyTLS) serveStream(ss *session, st *stream) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AnyTLS) serveUoT(ss *session, st *stream) {
|
|
||||||
target, err := readUOTV2Request(st)
|
|
||||||
if err != nil {
|
|
||||||
_ = ss.writeFrame(frame{command: cmdSYNACK, streamID: st.id, data: []byte(err.Error())})
|
|
||||||
log.F("[anytls] read udp-over-tcp request error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
dstPC, dialer, err := s.proxy.DialUDP("udp", target.String())
|
|
||||||
if err != nil {
|
|
||||||
_ = ss.writeFrame(frame{command: cmdSYNACK, streamID: st.id, data: []byte(err.Error())})
|
|
||||||
log.F("[anytls] %s <-UoT-> %s via %s, error in dial: %v", st.RemoteAddr(), target, dialer.Addr(), err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer dstPC.Close()
|
|
||||||
|
|
||||||
_ = ss.writeFrame(frame{command: cmdSYNACK, streamID: st.id})
|
|
||||||
pc := newUOTPacketConn(st, target)
|
|
||||||
log.F("[anytls] %s <-UoT-> %s via %s", st.RemoteAddr(), target, dialer.Addr())
|
|
||||||
|
|
||||||
go proxy.CopyUDP(dstPC, nil, pc, 2*time.Minute, 5*time.Second)
|
|
||||||
if err := proxy.CopyUDP(pc, nil, dstPC, 2*time.Minute, 5*time.Second); err != nil {
|
|
||||||
log.F("[anytls] %s <-UoT-> %s via %s, relay error: %v", st.RemoteAddr(), target, dialer.Addr(), err)
|
|
||||||
if d, ok := dialer.(proxy.Dialer); ok && !strings.Contains(err.Error(), s.addr) {
|
|
||||||
s.proxy.Record(d, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
342
proxy/ssr/internal/cipher/cipher.go
Normal file
342
proxy/ssr/internal/cipher/cipher.go
Normal file
@ -0,0 +1,342 @@
|
|||||||
|
package cipher
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/des"
|
||||||
|
"crypto/md5"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rc4"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/aead/chacha20"
|
||||||
|
"github.com/dgryski/go-camellia"
|
||||||
|
"github.com/dgryski/go-idea"
|
||||||
|
"github.com/dgryski/go-rc2"
|
||||||
|
"golang.org/x/crypto/blowfish"
|
||||||
|
"golang.org/x/crypto/cast5"
|
||||||
|
"golang.org/x/crypto/salsa20/salsa"
|
||||||
|
|
||||||
|
"github.com/nadoo/glider/pkg/pool"
|
||||||
|
"github.com/nadoo/glider/proxy/ssr/internal/tools"
|
||||||
|
)
|
||||||
|
|
||||||
|
var errEmptyPassword = errors.New("empty key")
|
||||||
|
|
||||||
|
type DecOrEnc int
|
||||||
|
|
||||||
|
const (
|
||||||
|
Decrypt DecOrEnc = iota
|
||||||
|
Encrypt
|
||||||
|
)
|
||||||
|
|
||||||
|
func newCTRStream(block cipher.Block, err error, key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cipher.NewCTR(block, iv), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAESCTRStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
||||||
|
block, err := aes.NewCipher(key)
|
||||||
|
return newCTRStream(block, err, key, iv, doe)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newOFBStream(block cipher.Block, err error, key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cipher.NewCTR(block, iv), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAESOFBStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
||||||
|
block, err := aes.NewCipher(key)
|
||||||
|
return newOFBStream(block, err, key, iv, doe)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCFBStream(block cipher.Block, err error, key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if doe == Encrypt {
|
||||||
|
return cipher.NewCFBEncrypter(block, iv), nil
|
||||||
|
} else {
|
||||||
|
return cipher.NewCFBDecrypter(block, iv), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAESCFBStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
||||||
|
block, err := aes.NewCipher(key)
|
||||||
|
return newCFBStream(block, err, key, iv, doe)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDESStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
||||||
|
block, err := des.NewCipher(key)
|
||||||
|
return newCFBStream(block, err, key, iv, doe)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBlowFishStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
||||||
|
block, err := blowfish.NewCipher(key)
|
||||||
|
return newCFBStream(block, err, key, iv, doe)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCast5Stream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
||||||
|
block, err := cast5.NewCipher(key)
|
||||||
|
return newCFBStream(block, err, key, iv, doe)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRC4MD5Stream(key, iv []byte, _ DecOrEnc) (cipher.Stream, error) {
|
||||||
|
h := md5.New()
|
||||||
|
h.Write(key)
|
||||||
|
h.Write(iv)
|
||||||
|
rc4key := h.Sum(nil)
|
||||||
|
|
||||||
|
return rc4.NewCipher(rc4key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newChaCha20Stream(key, iv []byte, _ DecOrEnc) (cipher.Stream, error) {
|
||||||
|
return chacha20.NewCipher(iv, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newChacha20IETFStream(key, iv []byte, _ DecOrEnc) (cipher.Stream, error) {
|
||||||
|
return chacha20.NewCipher(iv, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
type salsaStreamCipher struct {
|
||||||
|
nonce [8]byte
|
||||||
|
key [32]byte
|
||||||
|
counter int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *salsaStreamCipher) XORKeyStream(dst, src []byte) {
|
||||||
|
var buf []byte
|
||||||
|
padLen := c.counter % 64
|
||||||
|
dataSize := len(src) + padLen
|
||||||
|
if cap(dst) >= dataSize {
|
||||||
|
buf = dst[:dataSize]
|
||||||
|
// nadoo: comment out codes here to use pool buffer
|
||||||
|
// modify start -->
|
||||||
|
// } else if leakybuf.GlobalLeakyBufSize >= dataSize {
|
||||||
|
// buf = leakybuf.GlobalLeakyBuf.Get()
|
||||||
|
// defer leakybuf.GlobalLeakyBuf.Put(buf)
|
||||||
|
// buf = buf[:dataSize]
|
||||||
|
// } else {
|
||||||
|
// buf = make([]byte, dataSize)
|
||||||
|
// }
|
||||||
|
} else {
|
||||||
|
buf = pool.GetBuffer(dataSize)
|
||||||
|
defer pool.PutBuffer(buf)
|
||||||
|
}
|
||||||
|
// --> modify end
|
||||||
|
|
||||||
|
var subNonce [16]byte
|
||||||
|
copy(subNonce[:], c.nonce[:])
|
||||||
|
binary.LittleEndian.PutUint64(subNonce[len(c.nonce):], uint64(c.counter/64))
|
||||||
|
|
||||||
|
// It's difficult to avoid data copy here. src or dst maybe slice from
|
||||||
|
// Conn.Read/Write, which can't have padding.
|
||||||
|
copy(buf[padLen:], src[:])
|
||||||
|
salsa.XORKeyStream(buf, buf, &subNonce, &c.key)
|
||||||
|
copy(dst, buf[padLen:])
|
||||||
|
|
||||||
|
c.counter += len(src)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSalsa20Stream(key, iv []byte, _ DecOrEnc) (cipher.Stream, error) {
|
||||||
|
var c salsaStreamCipher
|
||||||
|
copy(c.nonce[:], iv[:8])
|
||||||
|
copy(c.key[:], key[:32])
|
||||||
|
return &c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCamelliaStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
||||||
|
block, err := camellia.New(key)
|
||||||
|
return newCFBStream(block, err, key, iv, doe)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newIdeaStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
||||||
|
block, err := idea.NewCipher(key)
|
||||||
|
return newCFBStream(block, err, key, iv, doe)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRC2Stream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
||||||
|
block, err := rc2.New(key, 16)
|
||||||
|
return newCFBStream(block, err, key, iv, doe)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRC4Stream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
||||||
|
return rc4.NewCipher(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSeedStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
||||||
|
// TODO: SEED block cipher implementation is required
|
||||||
|
block, err := rc2.New(key, 16)
|
||||||
|
return newCFBStream(block, err, key, iv, doe)
|
||||||
|
}
|
||||||
|
|
||||||
|
type NoneStream struct {
|
||||||
|
cipher.Stream
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*NoneStream) XORKeyStream(dst, src []byte) {
|
||||||
|
copy(dst, src)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newNoneStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
||||||
|
return new(NoneStream), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type cipherInfo struct {
|
||||||
|
keyLen int
|
||||||
|
ivLen int
|
||||||
|
newStream func(key, iv []byte, doe DecOrEnc) (cipher.Stream, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
var streamCipherMethod = map[string]*cipherInfo{
|
||||||
|
"aes-128-cfb": {16, 16, newAESCFBStream},
|
||||||
|
"aes-192-cfb": {24, 16, newAESCFBStream},
|
||||||
|
"aes-256-cfb": {32, 16, newAESCFBStream},
|
||||||
|
"aes-128-ctr": {16, 16, newAESCTRStream},
|
||||||
|
"aes-192-ctr": {24, 16, newAESCTRStream},
|
||||||
|
"aes-256-ctr": {32, 16, newAESCTRStream},
|
||||||
|
"aes-128-ofb": {16, 16, newAESOFBStream},
|
||||||
|
"aes-192-ofb": {24, 16, newAESOFBStream},
|
||||||
|
"aes-256-ofb": {32, 16, newAESOFBStream},
|
||||||
|
"des-cfb": {8, 8, newDESStream},
|
||||||
|
"bf-cfb": {16, 8, newBlowFishStream},
|
||||||
|
"cast5-cfb": {16, 8, newCast5Stream},
|
||||||
|
"rc4-md5": {16, 16, newRC4MD5Stream},
|
||||||
|
"rc4-md5-6": {16, 6, newRC4MD5Stream},
|
||||||
|
"chacha20": {32, 8, newChaCha20Stream},
|
||||||
|
"chacha20-ietf": {32, 12, newChacha20IETFStream},
|
||||||
|
"salsa20": {32, 8, newSalsa20Stream},
|
||||||
|
"camellia-128-cfb": {16, 16, newCamelliaStream},
|
||||||
|
"camellia-192-cfb": {24, 16, newCamelliaStream},
|
||||||
|
"camellia-256-cfb": {32, 16, newCamelliaStream},
|
||||||
|
"idea-cfb": {16, 8, newIdeaStream},
|
||||||
|
"rc2-cfb": {16, 8, newRC2Stream},
|
||||||
|
"seed-cfb": {16, 8, newSeedStream},
|
||||||
|
"rc4": {16, 0, newRC4Stream},
|
||||||
|
"none": {16, 0, newNoneStream},
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckCipherMethod(method string) error {
|
||||||
|
if method == "" {
|
||||||
|
method = "rc4-md5"
|
||||||
|
}
|
||||||
|
_, ok := streamCipherMethod[method]
|
||||||
|
if !ok {
|
||||||
|
return errors.New("Unsupported encryption method: " + method)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type StreamCipher struct {
|
||||||
|
enc cipher.Stream
|
||||||
|
dec cipher.Stream
|
||||||
|
key []byte
|
||||||
|
info *cipherInfo
|
||||||
|
iv []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStreamCipher creates a cipher that can be used in Dial() etc.
|
||||||
|
// Use cipher.Copy() to create a new cipher with the same method and password
|
||||||
|
// to avoid the cost of repeated cipher initialization.
|
||||||
|
func NewStreamCipher(method, password string) (c *StreamCipher, err error) {
|
||||||
|
if password == "" {
|
||||||
|
return nil, errEmptyPassword
|
||||||
|
}
|
||||||
|
if method == "" {
|
||||||
|
method = "rc4-md5"
|
||||||
|
}
|
||||||
|
mi, ok := streamCipherMethod[method]
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("Unsupported encryption method: " + method)
|
||||||
|
}
|
||||||
|
|
||||||
|
key := tools.EVPBytesToKey(password, mi.keyLen)
|
||||||
|
|
||||||
|
c = &StreamCipher{key: key, info: mi}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *StreamCipher) EncryptInited() bool {
|
||||||
|
return c.enc != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *StreamCipher) DecryptInited() bool {
|
||||||
|
return c.dec != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initializes the block cipher with CFB mode, returns IV.
|
||||||
|
func (c *StreamCipher) InitEncrypt() (iv []byte, err error) {
|
||||||
|
if c.iv == nil {
|
||||||
|
iv = make([]byte, c.info.ivLen)
|
||||||
|
rand.Read(iv)
|
||||||
|
c.iv = iv
|
||||||
|
} else {
|
||||||
|
iv = c.iv
|
||||||
|
}
|
||||||
|
c.enc, err = c.info.newStream(c.key, iv, Encrypt)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *StreamCipher) InitDecrypt(iv []byte) (err error) {
|
||||||
|
c.dec, err = c.info.newStream(c.key, iv, Decrypt)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *StreamCipher) Encrypt(dst, src []byte) {
|
||||||
|
c.enc.XORKeyStream(dst, src)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *StreamCipher) Decrypt(dst, src []byte) {
|
||||||
|
c.dec.XORKeyStream(dst, src)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy creates a new cipher at it's initial state.
|
||||||
|
func (c *StreamCipher) Copy() *StreamCipher {
|
||||||
|
// This optimization maybe not necessary. But without this function, we
|
||||||
|
// need to maintain a table cache for newTableCipher and use lock to
|
||||||
|
// protect concurrent access to that cache.
|
||||||
|
|
||||||
|
// AES and DES ciphers does not return specific types, so it's difficult
|
||||||
|
// to create copy. But their initialization time is less than 4000ns on my
|
||||||
|
// 2.26 GHz Intel Core 2 Duo processor. So no need to worry.
|
||||||
|
|
||||||
|
// Currently, blow-fish and cast5 initialization cost is an order of
|
||||||
|
// magnitude slower than other ciphers. (I'm not sure whether this is
|
||||||
|
// because the current implementation is not highly optimized, or this is
|
||||||
|
// the nature of the algorithm.)
|
||||||
|
|
||||||
|
nc := *c
|
||||||
|
nc.enc = nil
|
||||||
|
nc.dec = nil
|
||||||
|
return &nc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *StreamCipher) Key() []byte {
|
||||||
|
return c.key
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *StreamCipher) IV() []byte {
|
||||||
|
return c.iv
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *StreamCipher) SetIV(iv []byte) {
|
||||||
|
c.iv = iv
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *StreamCipher) SetKey(key []byte) {
|
||||||
|
c.key = key
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *StreamCipher) InfoIVLen() int {
|
||||||
|
return c.info.ivLen
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *StreamCipher) InfoKeyLen() int {
|
||||||
|
return c.info.keyLen
|
||||||
|
}
|
||||||
228
proxy/ssr/internal/client.go
Normal file
228
proxy/ssr/internal/client.go
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
// source code from https://github.com/v2rayA/shadowsocksR
|
||||||
|
// Just copy here to use glider's builtin buffer pool.
|
||||||
|
// As this protocol hasn't been maintained since 2017, it doesn't deserve our research to rewrite it.
|
||||||
|
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/nadoo/glider/pkg/pool"
|
||||||
|
"github.com/nadoo/glider/proxy"
|
||||||
|
"github.com/nadoo/glider/proxy/ssr/internal/cipher"
|
||||||
|
"github.com/nadoo/glider/proxy/ssr/internal/obfs"
|
||||||
|
"github.com/nadoo/glider/proxy/ssr/internal/protocol"
|
||||||
|
)
|
||||||
|
|
||||||
|
var bufSize = proxy.TCPBufSize
|
||||||
|
|
||||||
|
// SSTCPConn the struct that override the net.Conn methods
|
||||||
|
type SSTCPConn struct {
|
||||||
|
net.Conn
|
||||||
|
*cipher.StreamCipher
|
||||||
|
IObfs obfs.IObfs
|
||||||
|
IProtocol protocol.IProtocol
|
||||||
|
readBuf []byte
|
||||||
|
underPostdecryptBuf *bytes.Buffer
|
||||||
|
readIndex uint64
|
||||||
|
decryptedBuf *bytes.Buffer
|
||||||
|
writeBuf []byte
|
||||||
|
lastReadError error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSSTCPConn(c net.Conn, cipher *cipher.StreamCipher) *SSTCPConn {
|
||||||
|
return &SSTCPConn{
|
||||||
|
Conn: c,
|
||||||
|
StreamCipher: cipher,
|
||||||
|
readBuf: pool.GetBuffer(bufSize),
|
||||||
|
decryptedBuf: pool.GetBytesBuffer(),
|
||||||
|
underPostdecryptBuf: pool.GetBytesBuffer(),
|
||||||
|
writeBuf: pool.GetBuffer(bufSize),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *SSTCPConn) Close() error {
|
||||||
|
pool.PutBuffer(c.readBuf)
|
||||||
|
pool.PutBytesBuffer(c.decryptedBuf)
|
||||||
|
pool.PutBytesBuffer(c.underPostdecryptBuf)
|
||||||
|
pool.PutBuffer(c.writeBuf)
|
||||||
|
return c.Conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *SSTCPConn) GetIv() (iv []byte) {
|
||||||
|
iv = make([]byte, len(c.IV()))
|
||||||
|
copy(iv, c.IV())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *SSTCPConn) GetKey() (key []byte) {
|
||||||
|
key = make([]byte, len(c.Key()))
|
||||||
|
copy(key, c.Key())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *SSTCPConn) initEncryptor(b []byte) (iv []byte, err error) {
|
||||||
|
if !c.EncryptInited() {
|
||||||
|
iv, err = c.InitEncrypt()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
overhead := c.IObfs.GetOverhead() + c.IProtocol.GetOverhead()
|
||||||
|
// should initialize obfs/protocol now, because iv is ready now
|
||||||
|
obfsServerInfo := c.IObfs.GetServerInfo()
|
||||||
|
obfsServerInfo.SetHeadLen(b, 30)
|
||||||
|
obfsServerInfo.IV, obfsServerInfo.IVLen = c.IV(), c.InfoIVLen()
|
||||||
|
obfsServerInfo.Key, obfsServerInfo.KeyLen = c.Key(), c.InfoKeyLen()
|
||||||
|
obfsServerInfo.Overhead = overhead
|
||||||
|
c.IObfs.SetServerInfo(obfsServerInfo)
|
||||||
|
|
||||||
|
protocolServerInfo := c.IProtocol.GetServerInfo()
|
||||||
|
protocolServerInfo.SetHeadLen(b, 30)
|
||||||
|
protocolServerInfo.IV, protocolServerInfo.IVLen = c.IV(), c.InfoIVLen()
|
||||||
|
protocolServerInfo.Key, protocolServerInfo.KeyLen = c.Key(), c.InfoKeyLen()
|
||||||
|
protocolServerInfo.Overhead = overhead
|
||||||
|
c.IProtocol.SetServerInfo(protocolServerInfo)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *SSTCPConn) Read(b []byte) (n int, err error) {
|
||||||
|
for {
|
||||||
|
n, err = c.doRead(b)
|
||||||
|
if b == nil || n != 0 || err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *SSTCPConn) doRead(b []byte) (n int, err error) {
|
||||||
|
if c.decryptedBuf.Len() > 0 {
|
||||||
|
return c.decryptedBuf.Read(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err = c.Conn.Read(c.readBuf)
|
||||||
|
if n == 0 || err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
decodedData, needSendBack, err := c.IObfs.Decode(c.readBuf[:n])
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
//do send back
|
||||||
|
if needSendBack {
|
||||||
|
c.Write(nil)
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
decodedDataLen := len(decodedData)
|
||||||
|
if decodedDataLen == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !c.DecryptInited() {
|
||||||
|
if len(decodedData) < c.InfoIVLen() {
|
||||||
|
return 0, errors.New(fmt.Sprintf("invalid ivLen:%v, actual length:%v", c.InfoIVLen(), len(decodedData)))
|
||||||
|
}
|
||||||
|
|
||||||
|
iv := decodedData[0:c.InfoIVLen()]
|
||||||
|
if err = c.InitDecrypt(iv); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.IV()) == 0 {
|
||||||
|
c.SetIV(iv)
|
||||||
|
}
|
||||||
|
|
||||||
|
decodedDataLen -= c.InfoIVLen()
|
||||||
|
if decodedDataLen <= 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
decodedData = decodedData[c.InfoIVLen():]
|
||||||
|
}
|
||||||
|
|
||||||
|
buf1 := pool.GetBuffer(decodedDataLen)
|
||||||
|
defer pool.PutBuffer(buf1)
|
||||||
|
|
||||||
|
c.Decrypt(buf1, decodedData)
|
||||||
|
c.underPostdecryptBuf.Write(buf1)
|
||||||
|
buf := c.underPostdecryptBuf.Bytes()
|
||||||
|
|
||||||
|
postDecryptedData, length, err := c.IProtocol.PostDecrypt(buf)
|
||||||
|
if err != nil {
|
||||||
|
c.underPostdecryptBuf.Reset()
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if length == 0 {
|
||||||
|
// not enough to postDecrypt
|
||||||
|
return 0, nil
|
||||||
|
} else {
|
||||||
|
c.underPostdecryptBuf.Next(length)
|
||||||
|
}
|
||||||
|
|
||||||
|
postDecryptedLength := len(postDecryptedData)
|
||||||
|
blength := len(b)
|
||||||
|
|
||||||
|
if blength >= postDecryptedLength {
|
||||||
|
copy(b, postDecryptedData)
|
||||||
|
return postDecryptedLength, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(b, postDecryptedData[:blength])
|
||||||
|
c.decryptedBuf.Write(postDecryptedData[blength:])
|
||||||
|
|
||||||
|
return blength, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *SSTCPConn) preWrite(b []byte) (outData []byte, err error) {
|
||||||
|
if b == nil {
|
||||||
|
b = make([]byte, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
var iv []byte
|
||||||
|
if iv, err = c.initEncryptor(b); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var preEncryptedData []byte
|
||||||
|
preEncryptedData, err = c.IProtocol.PreEncrypt(b)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
preEncryptedDataLen := len(preEncryptedData)
|
||||||
|
//! \attention here the expected output buffer length MUST be accurate, it is preEncryptedDataLen now!
|
||||||
|
|
||||||
|
cipherData := c.writeBuf
|
||||||
|
dataSize := preEncryptedDataLen + len(iv)
|
||||||
|
if dataSize > len(cipherData) {
|
||||||
|
cipherData = make([]byte, dataSize)
|
||||||
|
} else {
|
||||||
|
cipherData = cipherData[:dataSize]
|
||||||
|
}
|
||||||
|
|
||||||
|
if iv != nil {
|
||||||
|
// Put initialization vector in buffer before be encoded
|
||||||
|
copy(cipherData, iv)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Encrypt(cipherData[len(iv):], preEncryptedData)
|
||||||
|
return c.IObfs.Encode(cipherData)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *SSTCPConn) Write(b []byte) (n int, err error) {
|
||||||
|
outData, err := c.preWrite(b)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
n, err = c.Conn.Write(outData)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return len(b), nil
|
||||||
|
}
|
||||||
36
proxy/ssr/internal/obfs/base.go
Normal file
36
proxy/ssr/internal/obfs/base.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package obfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/nadoo/glider/proxy/ssr/internal/ssr"
|
||||||
|
)
|
||||||
|
|
||||||
|
type creator func() IObfs
|
||||||
|
|
||||||
|
var (
|
||||||
|
creatorMap = make(map[string]creator)
|
||||||
|
)
|
||||||
|
|
||||||
|
type IObfs interface {
|
||||||
|
SetServerInfo(s *ssr.ServerInfo)
|
||||||
|
GetServerInfo() (s *ssr.ServerInfo)
|
||||||
|
Encode(data []byte) (encodedData []byte, err error)
|
||||||
|
Decode(data []byte) (decodedData []byte, needSendBack bool, err error)
|
||||||
|
SetData(data any)
|
||||||
|
GetData() any
|
||||||
|
GetOverhead() int
|
||||||
|
}
|
||||||
|
|
||||||
|
func register(name string, c creator) {
|
||||||
|
creatorMap[name] = c
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewObfs create an obfs object by name and return as an IObfs interface
|
||||||
|
func NewObfs(name string) IObfs {
|
||||||
|
c, ok := creatorMap[strings.ToLower(name)]
|
||||||
|
if ok {
|
||||||
|
return c()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
20
proxy/ssr/internal/obfs/http_post.go
Normal file
20
proxy/ssr/internal/obfs/http_post.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package obfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
register("http_post", newHttpPost)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newHttpPost create a http_post object
|
||||||
|
func newHttpPost() IObfs {
|
||||||
|
// newHttpSimple create a http_simple object
|
||||||
|
|
||||||
|
t := &httpSimplePost{
|
||||||
|
userAgentIndex: rand.IntN(len(requestUserAgent)),
|
||||||
|
methodGet: false,
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
185
proxy/ssr/internal/obfs/http_simple.go
Normal file
185
proxy/ssr/internal/obfs/http_simple.go
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
package obfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"math/rand/v2"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/nadoo/glider/proxy/ssr/internal/ssr"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
requestPath = []string{
|
||||||
|
"", "",
|
||||||
|
"login.php?redir=", "",
|
||||||
|
"register.php?code=", "",
|
||||||
|
"?keyword=", "",
|
||||||
|
"search?src=typd&q=", "&lang=en",
|
||||||
|
"s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&ch=&bar=&wd=", "&rn=",
|
||||||
|
"post.php?id=", "&goto=view.php",
|
||||||
|
}
|
||||||
|
requestUserAgent = []string{
|
||||||
|
"Mozilla/5.0 (Windows NT 6.3; WOW64; rv:40.0) Gecko/20100101 Firefox/40.0",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.3; WOW64; rv:40.0) Gecko/20100101 Firefox/44.0",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.11 (KHTML, like Gecko) Ubuntu/11.10 Chromium/27.0.1453.93 Chrome/27.0.1453.93 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:35.0) Gecko/20100101 Firefox/35.0",
|
||||||
|
"Mozilla/5.0 (compatible; WOW64; MSIE 10.0; Windows NT 6.2)",
|
||||||
|
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27",
|
||||||
|
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.3; Trident/7.0; .NET4.0E; .NET4.0C)",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko",
|
||||||
|
"Mozilla/5.0 (Linux; Android 4.4; Nexus 5 Build/BuildID) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (iPad; CPU OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3",
|
||||||
|
"Mozilla/5.0 (iPhone; CPU iPhone OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// HttpSimple http_simple obfs encapsulate
|
||||||
|
type httpSimplePost struct {
|
||||||
|
ssr.ServerInfo
|
||||||
|
rawTransSent bool
|
||||||
|
rawTransReceived bool
|
||||||
|
userAgentIndex int
|
||||||
|
methodGet bool // true for get, false for post
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
register("http_simple", newHttpSimple)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newHttpSimple create a http_simple object
|
||||||
|
func newHttpSimple() IObfs {
|
||||||
|
|
||||||
|
t := &httpSimplePost{
|
||||||
|
rawTransSent: false,
|
||||||
|
rawTransReceived: false,
|
||||||
|
userAgentIndex: rand.IntN(len(requestUserAgent)),
|
||||||
|
methodGet: true,
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *httpSimplePost) SetServerInfo(s *ssr.ServerInfo) {
|
||||||
|
t.ServerInfo = *s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *httpSimplePost) GetServerInfo() (s *ssr.ServerInfo) {
|
||||||
|
return &t.ServerInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *httpSimplePost) SetData(data any) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *httpSimplePost) GetData() any {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *httpSimplePost) boundary() (ret string) {
|
||||||
|
|
||||||
|
set := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||||||
|
for range 32 {
|
||||||
|
ret = fmt.Sprintf("%s%c", ret, set[rand.IntN(len(set))])
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *httpSimplePost) data2URLEncode(data []byte) (ret string) {
|
||||||
|
for i := range data {
|
||||||
|
ret = fmt.Sprintf("%s%%%s", ret, hex.EncodeToString([]byte{data[i]}))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *httpSimplePost) Encode(data []byte) (encodedData []byte, err error) {
|
||||||
|
if t.rawTransSent {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
dataLength := len(data)
|
||||||
|
var headData []byte
|
||||||
|
if headSize := t.IVLen + t.HeadLen; dataLength-headSize > 64 {
|
||||||
|
headData = make([]byte, headSize+rand.IntN(64))
|
||||||
|
} else {
|
||||||
|
headData = make([]byte, dataLength)
|
||||||
|
}
|
||||||
|
copy(headData, data[0:len(headData)])
|
||||||
|
requestPathIndex := rand.IntN(len(requestPath)/2) * 2
|
||||||
|
host := t.Host
|
||||||
|
var customHead string
|
||||||
|
|
||||||
|
if len(t.Param) > 0 {
|
||||||
|
customHeads := strings.Split(t.Param, "#")
|
||||||
|
if len(customHeads) > 2 {
|
||||||
|
customHeads = customHeads[0:2]
|
||||||
|
}
|
||||||
|
param := t.Param
|
||||||
|
if len(customHeads) > 1 {
|
||||||
|
customHead = customHeads[1]
|
||||||
|
param = customHeads[0]
|
||||||
|
}
|
||||||
|
hosts := strings.Split(param, ",")
|
||||||
|
if len(hosts) > 0 {
|
||||||
|
host = strings.TrimSpace(hosts[rand.IntN(len(hosts))])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
method := "GET /"
|
||||||
|
if !t.methodGet {
|
||||||
|
method = "POST /"
|
||||||
|
}
|
||||||
|
httpBuf := fmt.Sprintf("%s%s%s%s HTTP/1.1\r\nHost: %s:%d\r\n",
|
||||||
|
method,
|
||||||
|
requestPath[requestPathIndex],
|
||||||
|
t.data2URLEncode(headData),
|
||||||
|
requestPath[requestPathIndex+1],
|
||||||
|
host,
|
||||||
|
t.Port)
|
||||||
|
if len(customHead) > 0 {
|
||||||
|
httpBuf = httpBuf + strings.Replace(customHead, "\\n", "\r\n", -1) + "\r\n\r\n"
|
||||||
|
} else {
|
||||||
|
var contentType string
|
||||||
|
if !t.methodGet {
|
||||||
|
contentType = "Content-Type: multipart/form-data; boundary=" + t.boundary() + "\r\n"
|
||||||
|
}
|
||||||
|
httpBuf = httpBuf +
|
||||||
|
"User-Agent: " + requestUserAgent[t.userAgentIndex] + "\r\n" +
|
||||||
|
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" +
|
||||||
|
"Accept-Language: en-US,en;q=0.8\r\n" +
|
||||||
|
"Accept-Encoding: gzip, deflate\r\n" +
|
||||||
|
contentType +
|
||||||
|
"DNT: 1\r\n" +
|
||||||
|
"Connection: keep-alive\r\n" +
|
||||||
|
"\r\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(headData) < dataLength {
|
||||||
|
encodedData = make([]byte, len(httpBuf)+(dataLength-len(headData)))
|
||||||
|
copy(encodedData, []byte(httpBuf))
|
||||||
|
copy(encodedData[len(httpBuf):], data[len(headData):])
|
||||||
|
} else {
|
||||||
|
encodedData = []byte(httpBuf)
|
||||||
|
}
|
||||||
|
t.rawTransSent = true
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *httpSimplePost) Decode(data []byte) (decodedData []byte, needSendBack bool, err error) {
|
||||||
|
if t.rawTransReceived {
|
||||||
|
return data, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pos := bytes.Index(data, []byte("\r\n\r\n"))
|
||||||
|
if pos > 0 {
|
||||||
|
decodedData = make([]byte, len(data)-pos-4)
|
||||||
|
copy(decodedData, data[pos+4:])
|
||||||
|
t.rawTransReceived = true
|
||||||
|
}
|
||||||
|
return decodedData, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *httpSimplePost) GetOverhead() int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
46
proxy/ssr/internal/obfs/plain.go
Normal file
46
proxy/ssr/internal/obfs/plain.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package obfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/nadoo/glider/proxy/ssr/internal/ssr"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
register("plain", newPlainObfs)
|
||||||
|
}
|
||||||
|
|
||||||
|
type plain struct {
|
||||||
|
ssr.ServerInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPlainObfs() IObfs {
|
||||||
|
p := &plain{}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *plain) SetServerInfo(s *ssr.ServerInfo) {
|
||||||
|
p.ServerInfo = *s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *plain) GetServerInfo() (s *ssr.ServerInfo) {
|
||||||
|
return &p.ServerInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *plain) Encode(data []byte) (encodedData []byte, err error) {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *plain) Decode(data []byte) (decodedData []byte, needSendBack bool, err error) {
|
||||||
|
return data, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *plain) SetData(data any) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *plain) GetData() any {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *plain) GetOverhead() int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
84
proxy/ssr/internal/obfs/random_head.go
Normal file
84
proxy/ssr/internal/obfs/random_head.go
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
package obfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
crand "crypto/rand"
|
||||||
|
"math/rand/v2"
|
||||||
|
|
||||||
|
"github.com/nadoo/glider/proxy/ssr/internal/ssr"
|
||||||
|
)
|
||||||
|
|
||||||
|
type randomHead struct {
|
||||||
|
ssr.ServerInfo
|
||||||
|
rawTransSent bool
|
||||||
|
rawTransReceived bool
|
||||||
|
hasSentHeader bool
|
||||||
|
dataBuffer []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
register("random_head", newRandomHead)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRandomHead() IObfs {
|
||||||
|
p := &randomHead{}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *randomHead) SetServerInfo(s *ssr.ServerInfo) {
|
||||||
|
r.ServerInfo = *s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *randomHead) GetServerInfo() (s *ssr.ServerInfo) {
|
||||||
|
return &r.ServerInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *randomHead) SetData(data any) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *randomHead) GetData() any {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *randomHead) Encode(data []byte) (encodedData []byte, err error) {
|
||||||
|
if r.rawTransSent {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
dataLength := len(data)
|
||||||
|
if r.hasSentHeader {
|
||||||
|
if dataLength > 0 {
|
||||||
|
d := make([]byte, len(r.dataBuffer)+dataLength)
|
||||||
|
copy(d, r.dataBuffer)
|
||||||
|
copy(d[len(r.dataBuffer):], data)
|
||||||
|
r.dataBuffer = d
|
||||||
|
} else {
|
||||||
|
encodedData = r.dataBuffer
|
||||||
|
r.dataBuffer = nil
|
||||||
|
r.rawTransSent = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
size := rand.IntN(96) + 8
|
||||||
|
encodedData = make([]byte, size)
|
||||||
|
crand.Read(encodedData)
|
||||||
|
ssr.SetCRC32(encodedData, size)
|
||||||
|
|
||||||
|
d := make([]byte, dataLength)
|
||||||
|
copy(d, data)
|
||||||
|
r.dataBuffer = d
|
||||||
|
}
|
||||||
|
r.hasSentHeader = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *randomHead) Decode(data []byte) (decodedData []byte, needSendBack bool, err error) {
|
||||||
|
if r.rawTransReceived {
|
||||||
|
return data, false, nil
|
||||||
|
}
|
||||||
|
r.rawTransReceived = true
|
||||||
|
return data, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *randomHead) GetOverhead() int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
313
proxy/ssr/internal/obfs/tls12_ticket_auth.go
Normal file
313
proxy/ssr/internal/obfs/tls12_ticket_auth.go
Normal file
@ -0,0 +1,313 @@
|
|||||||
|
package obfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/hmac"
|
||||||
|
crand "crypto/rand"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"math/rand/v2"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/nadoo/glider/proxy/ssr/internal/ssr"
|
||||||
|
"github.com/nadoo/glider/proxy/ssr/internal/tools"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
register("tls1.2_ticket_auth", newTLS12TicketAuth)
|
||||||
|
register("tls1.2_ticket_fastauth", newTLS12TicketFastAuth)
|
||||||
|
}
|
||||||
|
|
||||||
|
type tlsAuthData struct {
|
||||||
|
localClientID [32]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// tls12TicketAuth tls1.2_ticket_auth obfs encapsulate
|
||||||
|
type tls12TicketAuth struct {
|
||||||
|
ssr.ServerInfo
|
||||||
|
data *tlsAuthData
|
||||||
|
handshakeStatus int
|
||||||
|
sendSaver []byte
|
||||||
|
recvBuffer bytes.Buffer
|
||||||
|
fastAuth bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// newTLS12TicketAuth create a tlv1.2_ticket_auth object
|
||||||
|
func newTLS12TicketAuth() IObfs {
|
||||||
|
return &tls12TicketAuth{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// newTLS12TicketFastAuth create a tlv1.2_ticket_fastauth object
|
||||||
|
func newTLS12TicketFastAuth() IObfs {
|
||||||
|
return &tls12TicketAuth{
|
||||||
|
fastAuth: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tls12TicketAuth) SetServerInfo(s *ssr.ServerInfo) {
|
||||||
|
t.ServerInfo = *s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tls12TicketAuth) GetServerInfo() (s *ssr.ServerInfo) {
|
||||||
|
return &t.ServerInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tls12TicketAuth) SetData(data any) {
|
||||||
|
if auth, ok := data.(*tlsAuthData); ok {
|
||||||
|
t.data = auth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tls12TicketAuth) GetData() any {
|
||||||
|
if t.data == nil {
|
||||||
|
t.data = &tlsAuthData{}
|
||||||
|
b := make([]byte, 32)
|
||||||
|
|
||||||
|
crand.Read(b)
|
||||||
|
copy(t.data.localClientID[:], b)
|
||||||
|
}
|
||||||
|
return t.data
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tls12TicketAuth) getHost() string {
|
||||||
|
host := t.Host
|
||||||
|
if len(t.Param) > 0 {
|
||||||
|
hosts := strings.Split(t.Param, ",")
|
||||||
|
if len(hosts) > 0 {
|
||||||
|
|
||||||
|
host = hosts[rand.IntN(len(hosts))]
|
||||||
|
host = strings.TrimSpace(host)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(host) > 0 && host[len(host)-1] >= byte('0') && host[len(host)-1] <= byte('9') && len(t.Param) == 0 {
|
||||||
|
host = ""
|
||||||
|
}
|
||||||
|
return host
|
||||||
|
}
|
||||||
|
|
||||||
|
func packData(prefixData []byte, suffixData []byte) (outData []byte) {
|
||||||
|
d := []byte{0x17, 0x3, 0x3, 0, 0}
|
||||||
|
binary.BigEndian.PutUint16(d[3:5], uint16(len(suffixData)&0xFFFF))
|
||||||
|
outData = append(prefixData, d...)
|
||||||
|
outData = append(outData, suffixData...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tls12TicketAuth) Encode(data []byte) (encodedData []byte, err error) {
|
||||||
|
encodedData = make([]byte, 0)
|
||||||
|
switch t.handshakeStatus {
|
||||||
|
case 8:
|
||||||
|
if len(data) < 1024 {
|
||||||
|
d := []byte{0x17, 0x3, 0x3, 0, 0}
|
||||||
|
binary.BigEndian.PutUint16(d[3:5], uint16(len(data)&0xFFFF))
|
||||||
|
encodedData = append(d, data...)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
start := 0
|
||||||
|
var l int
|
||||||
|
for len(data)-start > 2048 {
|
||||||
|
l = rand.IntN(4096) + 100
|
||||||
|
if l > len(data)-start {
|
||||||
|
l = len(data) - start
|
||||||
|
}
|
||||||
|
encodedData = packData(encodedData, data[start:start+l])
|
||||||
|
start += l
|
||||||
|
}
|
||||||
|
if len(data)-start > 0 {
|
||||||
|
l = len(data) - start
|
||||||
|
encodedData = packData(encodedData, data[start:start+l])
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case 1:
|
||||||
|
if len(data) > 0 {
|
||||||
|
if len(data) < 1024 {
|
||||||
|
t.sendSaver = packData(t.sendSaver, data)
|
||||||
|
} else {
|
||||||
|
start := 0
|
||||||
|
var l int
|
||||||
|
for len(data)-start > 2048 {
|
||||||
|
l = rand.IntN(4096) + 100
|
||||||
|
if l > len(data)-start {
|
||||||
|
l = len(data) - start
|
||||||
|
}
|
||||||
|
encodedData = packData(encodedData, data[start:start+l])
|
||||||
|
start += l
|
||||||
|
}
|
||||||
|
if len(data)-start > 0 {
|
||||||
|
l = len(data) - start
|
||||||
|
encodedData = packData(encodedData, data[start:start+l])
|
||||||
|
}
|
||||||
|
t.sendSaver = append(t.sendSaver, encodedData...)
|
||||||
|
encodedData = encodedData[:0]
|
||||||
|
}
|
||||||
|
return []byte{}, nil
|
||||||
|
}
|
||||||
|
hmacData := make([]byte, 43)
|
||||||
|
handshakeFinish := []byte("\x14\x03\x03\x00\x01\x01\x16\x03\x03\x00\x20")
|
||||||
|
copy(hmacData, handshakeFinish)
|
||||||
|
crand.Read(hmacData[11:33])
|
||||||
|
h := t.hmacSHA1(hmacData[:33])
|
||||||
|
copy(hmacData[33:], h)
|
||||||
|
encodedData = append(hmacData, t.sendSaver...)
|
||||||
|
t.sendSaver = t.sendSaver[:0]
|
||||||
|
t.handshakeStatus = 8
|
||||||
|
case 0:
|
||||||
|
tlsData0 := []byte("\x00\x1c\xc0\x2b\xc0\x2f\xcc\xa9\xcc\xa8\xcc\x14\xcc\x13\xc0\x0a\xc0\x14\xc0\x09\xc0\x13\x00\x9c\x00\x35\x00\x2f\x00\x0a\x01\x00")
|
||||||
|
tlsData1 := []byte("\xff\x01\x00\x01\x00")
|
||||||
|
tlsData2 := []byte("\x00\x17\x00\x00\x00\x23\x00\xd0")
|
||||||
|
tlsData3 := []byte("\x00\x0d\x00\x16\x00\x14\x06\x01\x06\x03\x05\x01\x05\x03\x04\x01\x04\x03\x03\x01\x03\x03\x02\x01\x02\x03\x00\x05\x00\x05\x01\x00\x00\x00\x00\x00\x12\x00\x00\x75\x50\x00\x00\x00\x0b\x00\x02\x01\x00\x00\x0a\x00\x06\x00\x04\x00\x17\x00\x18\x00\x15\x00\x66\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
|
||||||
|
|
||||||
|
var tlsData [2048]byte
|
||||||
|
tlsDataLen := 0
|
||||||
|
copy(tlsData[0:], tlsData1)
|
||||||
|
tlsDataLen += len(tlsData1)
|
||||||
|
sni := t.sni(t.getHost())
|
||||||
|
copy(tlsData[tlsDataLen:], sni)
|
||||||
|
tlsDataLen += len(sni)
|
||||||
|
copy(tlsData[tlsDataLen:], tlsData2)
|
||||||
|
tlsDataLen += len(tlsData2)
|
||||||
|
ticketLen := rand.IntN(164)*2 + 64
|
||||||
|
tlsData[tlsDataLen-1] = uint8(ticketLen & 0xff)
|
||||||
|
tlsData[tlsDataLen-2] = uint8(ticketLen >> 8)
|
||||||
|
//ticketLen := 208
|
||||||
|
crand.Read(tlsData[tlsDataLen : tlsDataLen+ticketLen])
|
||||||
|
tlsDataLen += ticketLen
|
||||||
|
copy(tlsData[tlsDataLen:], tlsData3)
|
||||||
|
tlsDataLen += len(tlsData3)
|
||||||
|
|
||||||
|
length := 11 + 32 + 1 + 32 + len(tlsData0) + 2 + tlsDataLen
|
||||||
|
encodedData = make([]byte, length)
|
||||||
|
pdata := length - tlsDataLen
|
||||||
|
l := tlsDataLen
|
||||||
|
copy(encodedData[pdata:], tlsData[:tlsDataLen])
|
||||||
|
encodedData[pdata-1] = uint8(tlsDataLen)
|
||||||
|
encodedData[pdata-2] = uint8(tlsDataLen >> 8)
|
||||||
|
pdata -= 2
|
||||||
|
l += 2
|
||||||
|
copy(encodedData[pdata-len(tlsData0):], tlsData0)
|
||||||
|
pdata -= len(tlsData0)
|
||||||
|
l += len(tlsData0)
|
||||||
|
copy(encodedData[pdata-32:], t.data.localClientID[:])
|
||||||
|
pdata -= 32
|
||||||
|
l += 32
|
||||||
|
encodedData[pdata-1] = 0x20
|
||||||
|
pdata -= 1
|
||||||
|
l += 1
|
||||||
|
copy(encodedData[pdata-32:], t.packAuthData())
|
||||||
|
pdata -= 32
|
||||||
|
l += 32
|
||||||
|
encodedData[pdata-1] = 0x3
|
||||||
|
encodedData[pdata-2] = 0x3 // tls version
|
||||||
|
pdata -= 2
|
||||||
|
l += 2
|
||||||
|
encodedData[pdata-1] = uint8(l)
|
||||||
|
encodedData[pdata-2] = uint8(l >> 8)
|
||||||
|
encodedData[pdata-3] = 0
|
||||||
|
encodedData[pdata-4] = 1
|
||||||
|
pdata -= 4
|
||||||
|
l += 4
|
||||||
|
encodedData[pdata-1] = uint8(l)
|
||||||
|
encodedData[pdata-2] = uint8(l >> 8)
|
||||||
|
pdata -= 2
|
||||||
|
// l += 2
|
||||||
|
encodedData[pdata-1] = 0x1
|
||||||
|
encodedData[pdata-2] = 0x3 // tls version
|
||||||
|
pdata -= 2
|
||||||
|
// l += 2
|
||||||
|
encodedData[pdata-1] = 0x16 // tls handshake
|
||||||
|
// pdata -= 1
|
||||||
|
// l += 1
|
||||||
|
|
||||||
|
t.sendSaver = packData(t.sendSaver, data)
|
||||||
|
t.handshakeStatus = 1
|
||||||
|
default:
|
||||||
|
//log.Println(fmt.Errorf("unexpected handshake status: %d", t.handshakeStatus))
|
||||||
|
return nil, fmt.Errorf("unexpected handshake status: %d", t.handshakeStatus)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tls12TicketAuth) Decode(data []byte) (decodedData []byte, needSendBack bool, err error) {
|
||||||
|
if t.handshakeStatus == -1 {
|
||||||
|
return data, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.handshakeStatus == 8 {
|
||||||
|
t.recvBuffer.Write(data)
|
||||||
|
for t.recvBuffer.Len() > 5 {
|
||||||
|
var h [5]byte
|
||||||
|
_, _ = t.recvBuffer.Read(h[:])
|
||||||
|
if !bytes.Equal(h[0:3], []byte{0x17, 0x3, 0x3}) {
|
||||||
|
log.Println("incorrect magic number", h[0:3], ", 0x170303 is expected")
|
||||||
|
return nil, false, ssr.ErrTLS12TicketAuthIncorrectMagicNumber
|
||||||
|
}
|
||||||
|
size := int(binary.BigEndian.Uint16(h[3:5]))
|
||||||
|
if t.recvBuffer.Len() < size {
|
||||||
|
unread := t.recvBuffer.Bytes()
|
||||||
|
t.recvBuffer.Reset()
|
||||||
|
t.recvBuffer.Write(h[:])
|
||||||
|
t.recvBuffer.Write(unread)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
d := make([]byte, size)
|
||||||
|
_, _ = t.recvBuffer.Read(d)
|
||||||
|
decodedData = append(decodedData, d...)
|
||||||
|
}
|
||||||
|
return decodedData, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(data) < 11+32+1+32 {
|
||||||
|
return nil, false, ssr.ErrTLS12TicketAuthTooShortData
|
||||||
|
}
|
||||||
|
|
||||||
|
hash := t.hmacSHA1(data[11 : 11+22])
|
||||||
|
|
||||||
|
if !hmac.Equal(data[33:33+ssr.ObfsHMACSHA1Len], hash) {
|
||||||
|
return nil, false, ssr.ErrTLS12TicketAuthHMACError
|
||||||
|
}
|
||||||
|
return nil, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tls12TicketAuth) packAuthData() (outData []byte) {
|
||||||
|
outSize := 32
|
||||||
|
outData = make([]byte, outSize)
|
||||||
|
|
||||||
|
now := time.Now().Unix()
|
||||||
|
binary.BigEndian.PutUint32(outData[0:4], uint32(now))
|
||||||
|
|
||||||
|
crand.Read(outData[4 : 4+18])
|
||||||
|
|
||||||
|
hash := t.hmacSHA1(outData[:outSize-ssr.ObfsHMACSHA1Len])
|
||||||
|
copy(outData[outSize-ssr.ObfsHMACSHA1Len:], hash)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tls12TicketAuth) hmacSHA1(data []byte) []byte {
|
||||||
|
key := make([]byte, t.KeyLen+32)
|
||||||
|
copy(key, t.Key)
|
||||||
|
copy(key[t.KeyLen:], t.data.localClientID[:])
|
||||||
|
|
||||||
|
sha1Data := tools.HmacSHA1(key, data)
|
||||||
|
return sha1Data[:ssr.ObfsHMACSHA1Len]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tls12TicketAuth) sni(u string) []byte {
|
||||||
|
bURL := []byte(u)
|
||||||
|
length := len(bURL)
|
||||||
|
ret := make([]byte, length+9)
|
||||||
|
copy(ret[9:9+length], bURL)
|
||||||
|
binary.BigEndian.PutUint16(ret[7:], uint16(length&0xFFFF))
|
||||||
|
length += 3
|
||||||
|
binary.BigEndian.PutUint16(ret[4:], uint16(length&0xFFFF))
|
||||||
|
length += 2
|
||||||
|
binary.BigEndian.PutUint16(ret[2:], uint16(length&0xFFFF))
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tls12TicketAuth) GetOverhead() int {
|
||||||
|
return 5
|
||||||
|
}
|
||||||
279
proxy/ssr/internal/protocol/auth_aes128_md5.go
Normal file
279
proxy/ssr/internal/protocol/auth_aes128_md5.go
Normal file
@ -0,0 +1,279 @@
|
|||||||
|
package protocol
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
crand "crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/binary"
|
||||||
|
"math/rand/v2"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/nadoo/glider/proxy/ssr/internal/ssr"
|
||||||
|
"github.com/nadoo/glider/proxy/ssr/internal/tools"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
register("auth_aes128_md5", NewAuthAES128MD5)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAuthAES128MD5() IProtocol {
|
||||||
|
a := &authAES128{
|
||||||
|
salt: "auth_aes128_md5",
|
||||||
|
hmac: tools.HmacMD5,
|
||||||
|
hashDigest: tools.MD5Sum,
|
||||||
|
packID: 1,
|
||||||
|
recvInfo: recvInfo{
|
||||||
|
recvID: 1,
|
||||||
|
buffer: bytes.NewBuffer(nil),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
type recvInfo struct {
|
||||||
|
recvID uint32
|
||||||
|
buffer *bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
type authAES128 struct {
|
||||||
|
ssr.ServerInfo
|
||||||
|
recvInfo
|
||||||
|
data *AuthData
|
||||||
|
hasSentHeader bool
|
||||||
|
packID uint32
|
||||||
|
userKey []byte
|
||||||
|
salt string
|
||||||
|
hmac hmacMethod
|
||||||
|
hashDigest hashDigestMethod
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *authAES128) SetServerInfo(s *ssr.ServerInfo) {
|
||||||
|
a.ServerInfo = *s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *authAES128) GetServerInfo() (s *ssr.ServerInfo) {
|
||||||
|
return &a.ServerInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *authAES128) SetData(data any) {
|
||||||
|
if auth, ok := data.(*AuthData); ok {
|
||||||
|
a.data = auth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *authAES128) GetData() any {
|
||||||
|
if a.data == nil {
|
||||||
|
a.data = &AuthData{}
|
||||||
|
}
|
||||||
|
return a.data
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *authAES128) packData(data []byte) (outData []byte) {
|
||||||
|
dataLength := len(data)
|
||||||
|
randLength := 1
|
||||||
|
if dataLength <= 1200 {
|
||||||
|
if a.packID > 4 {
|
||||||
|
randLength += rand.IntN(32)
|
||||||
|
} else {
|
||||||
|
if dataLength > 900 {
|
||||||
|
randLength += rand.IntN(128)
|
||||||
|
} else {
|
||||||
|
randLength += rand.IntN(512)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
outLength := randLength + dataLength + 8
|
||||||
|
outData = make([]byte, outLength)
|
||||||
|
// 0~1, out length
|
||||||
|
binary.LittleEndian.PutUint16(outData[0:], uint16(outLength&0xFFFF))
|
||||||
|
// 2~3, hmac
|
||||||
|
key := make([]byte, len(a.userKey)+4)
|
||||||
|
copy(key, a.userKey)
|
||||||
|
binary.LittleEndian.PutUint32(key[len(key)-4:], a.packID)
|
||||||
|
h := a.hmac(key, outData[0:2])
|
||||||
|
copy(outData[2:4], h[:2])
|
||||||
|
// 4~rand length+4, rand number
|
||||||
|
crand.Read(outData[4 : 4+randLength])
|
||||||
|
// 4, rand length
|
||||||
|
if randLength < 128 {
|
||||||
|
outData[4] = byte(randLength & 0xFF)
|
||||||
|
} else {
|
||||||
|
// 4, magic number 0xFF
|
||||||
|
outData[4] = 0xFF
|
||||||
|
// 5~6, rand length
|
||||||
|
binary.LittleEndian.PutUint16(outData[5:], uint16(randLength&0xFFFF))
|
||||||
|
}
|
||||||
|
// rand length+4~out length-4, data
|
||||||
|
if dataLength > 0 {
|
||||||
|
copy(outData[randLength+4:], data)
|
||||||
|
}
|
||||||
|
a.packID++
|
||||||
|
h = a.hmac(key, outData[:outLength-4])
|
||||||
|
copy(outData[outLength-4:], h[:4])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *authAES128) packAuthData(data []byte) (outData []byte) {
|
||||||
|
dataLength := len(data)
|
||||||
|
var randLength int
|
||||||
|
if dataLength > 400 {
|
||||||
|
randLength = rand.IntN(512)
|
||||||
|
} else {
|
||||||
|
randLength = rand.IntN(1024)
|
||||||
|
}
|
||||||
|
|
||||||
|
dataOffset := randLength + 16 + 4 + 4 + 7
|
||||||
|
outLength := dataOffset + dataLength + 4
|
||||||
|
outData = make([]byte, outLength)
|
||||||
|
encrypt := make([]byte, 24)
|
||||||
|
key := make([]byte, a.IVLen+a.KeyLen)
|
||||||
|
copy(key, a.IV)
|
||||||
|
copy(key[a.IVLen:], a.Key)
|
||||||
|
|
||||||
|
crand.Read(outData[dataOffset-randLength:])
|
||||||
|
a.data.mutex.Lock()
|
||||||
|
a.data.connectionID++
|
||||||
|
if a.data.connectionID > 0xFF000000 {
|
||||||
|
a.data.clientID = nil
|
||||||
|
}
|
||||||
|
if len(a.data.clientID) == 0 {
|
||||||
|
a.data.clientID = make([]byte, 8)
|
||||||
|
crand.Read(a.data.clientID)
|
||||||
|
b := make([]byte, 4)
|
||||||
|
crand.Read(b)
|
||||||
|
a.data.connectionID = binary.LittleEndian.Uint32(b) & 0xFFFFFF
|
||||||
|
}
|
||||||
|
copy(encrypt[4:], a.data.clientID)
|
||||||
|
binary.LittleEndian.PutUint32(encrypt[8:], a.data.connectionID)
|
||||||
|
a.data.mutex.Unlock()
|
||||||
|
|
||||||
|
now := time.Now().Unix()
|
||||||
|
binary.LittleEndian.PutUint32(encrypt[0:4], uint32(now))
|
||||||
|
|
||||||
|
binary.LittleEndian.PutUint16(encrypt[12:], uint16(outLength&0xFFFF))
|
||||||
|
binary.LittleEndian.PutUint16(encrypt[14:], uint16(randLength&0xFFFF))
|
||||||
|
|
||||||
|
params := strings.Split(a.Param, ":")
|
||||||
|
uid := make([]byte, 4)
|
||||||
|
if len(params) >= 2 {
|
||||||
|
if userID, err := strconv.ParseUint(params[0], 10, 32); err != nil {
|
||||||
|
crand.Read(uid)
|
||||||
|
} else {
|
||||||
|
binary.LittleEndian.PutUint32(uid, uint32(userID))
|
||||||
|
a.userKey = a.hashDigest([]byte(params[1]))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
crand.Read(uid)
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.userKey == nil {
|
||||||
|
a.userKey = make([]byte, a.KeyLen)
|
||||||
|
copy(a.userKey, a.Key)
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptKey := make([]byte, len(a.userKey))
|
||||||
|
copy(encryptKey, a.userKey)
|
||||||
|
|
||||||
|
aesCipherKey := tools.EVPBytesToKey(base64.StdEncoding.EncodeToString(encryptKey)+a.salt, 16)
|
||||||
|
block, err := aes.NewCipher(aesCipherKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptData := make([]byte, 16)
|
||||||
|
iv := make([]byte, aes.BlockSize)
|
||||||
|
cbc := cipher.NewCBCEncrypter(block, iv)
|
||||||
|
cbc.CryptBlocks(encryptData, encrypt[0:16])
|
||||||
|
copy(encrypt[4:4+16], encryptData)
|
||||||
|
copy(encrypt[0:4], uid)
|
||||||
|
|
||||||
|
h := a.hmac(key, encrypt[0:20])
|
||||||
|
copy(encrypt[20:], h[:4])
|
||||||
|
|
||||||
|
crand.Read(outData[0:1])
|
||||||
|
h = a.hmac(key, outData[0:1])
|
||||||
|
copy(outData[1:], h[0:7-1])
|
||||||
|
|
||||||
|
copy(outData[7:], encrypt)
|
||||||
|
copy(outData[dataOffset:], data)
|
||||||
|
|
||||||
|
h = a.hmac(a.userKey, outData[0:outLength-4])
|
||||||
|
copy(outData[outLength-4:], h[:4])
|
||||||
|
|
||||||
|
//log.Println("clientID:", a.data.clientID, "connectionID:", a.data.connectionID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *authAES128) PreEncrypt(plainData []byte) (outData []byte, err error) {
|
||||||
|
dataLength := len(plainData)
|
||||||
|
offset := 0
|
||||||
|
if dataLength > 0 && !a.hasSentHeader {
|
||||||
|
authLength := dataLength
|
||||||
|
if authLength > 1200 {
|
||||||
|
authLength = 1200
|
||||||
|
}
|
||||||
|
packData := a.packAuthData(plainData[:authLength])
|
||||||
|
a.hasSentHeader = true
|
||||||
|
outData = append(outData, packData...)
|
||||||
|
dataLength -= authLength
|
||||||
|
offset += authLength
|
||||||
|
}
|
||||||
|
const blockSize = 4096
|
||||||
|
for dataLength > blockSize {
|
||||||
|
packData := a.packData(plainData[offset : offset+blockSize])
|
||||||
|
outData = append(outData, packData...)
|
||||||
|
dataLength -= blockSize
|
||||||
|
offset += blockSize
|
||||||
|
}
|
||||||
|
if dataLength > 0 {
|
||||||
|
packData := a.packData(plainData[offset:])
|
||||||
|
outData = append(outData, packData...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *authAES128) PostDecrypt(plainData []byte) ([]byte, int, error) {
|
||||||
|
a.buffer.Reset()
|
||||||
|
plainLength := len(plainData)
|
||||||
|
readlenth := 0
|
||||||
|
key := make([]byte, len(a.userKey)+4)
|
||||||
|
copy(key, a.userKey)
|
||||||
|
for plainLength > 4 {
|
||||||
|
binary.LittleEndian.PutUint32(key[len(key)-4:], a.recvID)
|
||||||
|
|
||||||
|
h := a.hmac(key, plainData[0:2])
|
||||||
|
if h[0] != plainData[2] || h[1] != plainData[3] {
|
||||||
|
return nil, 0, ssr.ErrAuthAES128IncorrectHMAC
|
||||||
|
}
|
||||||
|
length := int(binary.LittleEndian.Uint16(plainData[0:2]))
|
||||||
|
if length >= 8192 || length < 8 {
|
||||||
|
return nil, 0, ssr.ErrAuthAES128DataLengthError
|
||||||
|
}
|
||||||
|
if length > plainLength {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
a.recvID++
|
||||||
|
pos := int(plainData[4])
|
||||||
|
if pos < 255 {
|
||||||
|
pos += 4
|
||||||
|
} else {
|
||||||
|
pos = int(binary.LittleEndian.Uint16(plainData[5:7])) + 4
|
||||||
|
}
|
||||||
|
|
||||||
|
a.buffer.Write(plainData[pos : length-4])
|
||||||
|
plainData = plainData[length:]
|
||||||
|
plainLength -= length
|
||||||
|
readlenth += length
|
||||||
|
}
|
||||||
|
return a.buffer.Bytes(), readlenth, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *authAES128) GetOverhead() int {
|
||||||
|
return 9
|
||||||
|
}
|
||||||
25
proxy/ssr/internal/protocol/auth_aes128_sha1.go
Normal file
25
proxy/ssr/internal/protocol/auth_aes128_sha1.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package protocol
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
|
||||||
|
"github.com/nadoo/glider/proxy/ssr/internal/tools"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
register("auth_aes128_sha1", NewAuthAES128SHA1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAuthAES128SHA1() IProtocol {
|
||||||
|
a := &authAES128{
|
||||||
|
salt: "auth_aes128_sha1",
|
||||||
|
hmac: tools.HmacSHA1,
|
||||||
|
hashDigest: tools.SHA1Sum,
|
||||||
|
packID: 1,
|
||||||
|
recvInfo: recvInfo{
|
||||||
|
recvID: 1,
|
||||||
|
buffer: bytes.NewBuffer(nil),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return a
|
||||||
|
}
|
||||||
320
proxy/ssr/internal/protocol/auth_chain_a.go
Normal file
320
proxy/ssr/internal/protocol/auth_chain_a.go
Normal file
@ -0,0 +1,320 @@
|
|||||||
|
// https://github.com/shadowsocksr-backup/shadowsocks-rss/blob/master/doc/auth_chain_a.md
|
||||||
|
|
||||||
|
package protocol
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/aes"
|
||||||
|
stdCipher "crypto/cipher"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/binary"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/nadoo/glider/proxy/ssr/internal/cipher"
|
||||||
|
"github.com/nadoo/glider/proxy/ssr/internal/ssr"
|
||||||
|
"github.com/nadoo/glider/proxy/ssr/internal/tools"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
register("auth_chain_a", NewAuthChainA)
|
||||||
|
}
|
||||||
|
|
||||||
|
type authChainA struct {
|
||||||
|
ssr.ServerInfo
|
||||||
|
randomClient tools.Shift128plusContext
|
||||||
|
randomServer tools.Shift128plusContext
|
||||||
|
recvInfo
|
||||||
|
cipher *cipher.StreamCipher
|
||||||
|
hasSentHeader bool
|
||||||
|
lastClientHash []byte
|
||||||
|
lastServerHash []byte
|
||||||
|
userKey []byte
|
||||||
|
uid [4]byte
|
||||||
|
salt string
|
||||||
|
data *AuthData
|
||||||
|
hmac hmacMethod
|
||||||
|
hashDigest hashDigestMethod
|
||||||
|
rnd rndMethod
|
||||||
|
dataSizeList []int
|
||||||
|
dataSizeList2 []int
|
||||||
|
chunkID uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAuthChainA() IProtocol {
|
||||||
|
a := &authChainA{
|
||||||
|
salt: "auth_chain_a",
|
||||||
|
hmac: tools.HmacMD5,
|
||||||
|
hashDigest: tools.SHA1Sum,
|
||||||
|
rnd: authChainAGetRandLen,
|
||||||
|
recvInfo: recvInfo{
|
||||||
|
recvID: 1,
|
||||||
|
buffer: new(bytes.Buffer),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *authChainA) SetServerInfo(s *ssr.ServerInfo) {
|
||||||
|
a.ServerInfo = *s
|
||||||
|
if a.salt == "auth_chain_b" {
|
||||||
|
a.authChainBInitDataSize()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *authChainA) GetServerInfo() (s *ssr.ServerInfo) {
|
||||||
|
return &a.ServerInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *authChainA) SetData(data any) {
|
||||||
|
if auth, ok := data.(*AuthData); ok {
|
||||||
|
a.data = auth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *authChainA) GetData() any {
|
||||||
|
if a.data == nil {
|
||||||
|
a.data = &AuthData{}
|
||||||
|
}
|
||||||
|
return a.data
|
||||||
|
}
|
||||||
|
|
||||||
|
func authChainAGetRandLen(dataLength int, random *tools.Shift128plusContext, lastHash []byte, dataSizeList, dataSizeList2 []int, overhead int) int {
|
||||||
|
if dataLength > 1440 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
random.InitFromBinDatalen(lastHash[:16], dataLength)
|
||||||
|
if dataLength > 1300 {
|
||||||
|
return int(random.Next() % 31)
|
||||||
|
}
|
||||||
|
if dataLength > 900 {
|
||||||
|
return int(random.Next() % 127)
|
||||||
|
}
|
||||||
|
if dataLength > 400 {
|
||||||
|
return int(random.Next() % 521)
|
||||||
|
}
|
||||||
|
return int(random.Next() % 1021)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRandStartPos(random *tools.Shift128plusContext, randLength int) int {
|
||||||
|
if randLength > 0 {
|
||||||
|
return int(int64(random.Next()%8589934609) % int64(randLength))
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *authChainA) getClientRandLen(dataLength int, overhead int) int {
|
||||||
|
return a.rnd(dataLength, &a.randomClient, a.lastClientHash, a.dataSizeList, a.dataSizeList2, overhead)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *authChainA) getServerRandLen(dataLength int, overhead int) int {
|
||||||
|
return a.rnd(dataLength, &a.randomServer, a.lastServerHash, a.dataSizeList, a.dataSizeList2, overhead)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *authChainA) packedDataLen(data []byte) (chunkLength, randLength int) {
|
||||||
|
dataLength := len(data)
|
||||||
|
randLength = a.getClientRandLen(dataLength, a.Overhead)
|
||||||
|
chunkLength = randLength + dataLength + 2 + 2
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *authChainA) packData(outData []byte, data []byte, randLength int) {
|
||||||
|
dataLength := len(data)
|
||||||
|
outLength := randLength + dataLength + 2
|
||||||
|
outData[0] = byte(dataLength) ^ a.lastClientHash[14]
|
||||||
|
outData[1] = byte(dataLength>>8) ^ a.lastClientHash[15]
|
||||||
|
|
||||||
|
{
|
||||||
|
if dataLength > 0 {
|
||||||
|
randPart1Length := getRandStartPos(&a.randomClient, randLength)
|
||||||
|
rand.Read(outData[2 : 2+randPart1Length])
|
||||||
|
a.cipher.Encrypt(outData[2+randPart1Length:], data)
|
||||||
|
rand.Read(outData[2+randPart1Length+dataLength : outLength])
|
||||||
|
} else {
|
||||||
|
rand.Read(outData[2 : 2+randLength])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
userKeyLen := uint8(len(a.userKey))
|
||||||
|
key := make([]byte, userKeyLen+4)
|
||||||
|
copy(key, a.userKey)
|
||||||
|
a.chunkID++
|
||||||
|
binary.LittleEndian.PutUint32(key[userKeyLen:], a.chunkID)
|
||||||
|
a.lastClientHash = a.hmac(key, outData[:outLength])
|
||||||
|
copy(outData[outLength:], a.lastClientHash[:2])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const authheadLength = 4 + 8 + 4 + 16 + 4
|
||||||
|
|
||||||
|
func (a *authChainA) packAuthData(data []byte) (outData []byte) {
|
||||||
|
outData = make([]byte, authheadLength, authheadLength+1500)
|
||||||
|
a.data.connectionID++
|
||||||
|
if a.data.connectionID > 0xFF000000 {
|
||||||
|
rand.Read(a.data.clientID)
|
||||||
|
b := make([]byte, 4)
|
||||||
|
rand.Read(b)
|
||||||
|
a.data.connectionID = binary.LittleEndian.Uint32(b) & 0xFFFFFF
|
||||||
|
}
|
||||||
|
var key = make([]byte, a.IVLen+a.KeyLen)
|
||||||
|
copy(key, a.IV)
|
||||||
|
copy(key[a.IVLen:], a.Key)
|
||||||
|
|
||||||
|
encrypt := make([]byte, 20)
|
||||||
|
t := time.Now().Unix()
|
||||||
|
binary.LittleEndian.PutUint32(encrypt[:4], uint32(t))
|
||||||
|
copy(encrypt[4:8], a.data.clientID)
|
||||||
|
binary.LittleEndian.PutUint32(encrypt[8:], a.data.connectionID)
|
||||||
|
binary.LittleEndian.PutUint16(encrypt[12:], uint16(a.Overhead))
|
||||||
|
//binary.LittleEndian.PutUint16(encrypt[14:], 0)
|
||||||
|
|
||||||
|
// first 12 bytes
|
||||||
|
{
|
||||||
|
rand.Read(outData[:4])
|
||||||
|
a.lastClientHash = a.hmac(key, outData[:4])
|
||||||
|
copy(outData[4:], a.lastClientHash[:8])
|
||||||
|
}
|
||||||
|
var base64UserKey string
|
||||||
|
// uid & 16 bytes auth data
|
||||||
|
{
|
||||||
|
uid := make([]byte, 4)
|
||||||
|
if a.userKey == nil {
|
||||||
|
params := strings.Split(a.ServerInfo.Param, ":")
|
||||||
|
if len(params) >= 2 {
|
||||||
|
if userID, err := strconv.ParseUint(params[0], 10, 32); err == nil {
|
||||||
|
binary.LittleEndian.PutUint32(a.uid[:], uint32(userID))
|
||||||
|
a.userKey = a.hashDigest([]byte(params[1]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if a.userKey == nil {
|
||||||
|
rand.Read(a.uid[:])
|
||||||
|
a.userKey = make([]byte, a.KeyLen)
|
||||||
|
copy(a.userKey, a.Key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := range 4 {
|
||||||
|
uid[i] = a.uid[i] ^ a.lastClientHash[8+i]
|
||||||
|
}
|
||||||
|
base64UserKey = base64.StdEncoding.EncodeToString(a.userKey)
|
||||||
|
aesCipherKey := tools.EVPBytesToKey(base64UserKey+a.salt, 16)
|
||||||
|
block, err := aes.NewCipher(aesCipherKey)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
encryptData := make([]byte, 16)
|
||||||
|
iv := make([]byte, aes.BlockSize)
|
||||||
|
cbc := stdCipher.NewCBCEncrypter(block, iv)
|
||||||
|
cbc.CryptBlocks(encryptData, encrypt[:16])
|
||||||
|
copy(encrypt[:4], uid[:])
|
||||||
|
copy(encrypt[4:4+16], encryptData)
|
||||||
|
}
|
||||||
|
// final HMAC
|
||||||
|
{
|
||||||
|
a.lastServerHash = a.hmac(a.userKey, encrypt[0:20])
|
||||||
|
|
||||||
|
copy(outData[12:], encrypt)
|
||||||
|
copy(outData[12+20:], a.lastServerHash[:4])
|
||||||
|
}
|
||||||
|
|
||||||
|
// init cipher
|
||||||
|
password := make([]byte, len(base64UserKey)+base64.StdEncoding.EncodedLen(16))
|
||||||
|
copy(password, base64UserKey)
|
||||||
|
base64.StdEncoding.Encode(password[len(base64UserKey):], a.lastClientHash[:16])
|
||||||
|
a.cipher, _ = cipher.NewStreamCipher("rc4", string(password))
|
||||||
|
_, _ = a.cipher.InitEncrypt()
|
||||||
|
_ = a.cipher.InitDecrypt(nil)
|
||||||
|
|
||||||
|
// data
|
||||||
|
chunkLength, randLength := a.packedDataLen(data)
|
||||||
|
if chunkLength <= 1500 {
|
||||||
|
outData = outData[:authheadLength+chunkLength]
|
||||||
|
} else {
|
||||||
|
newOutData := make([]byte, authheadLength+chunkLength)
|
||||||
|
copy(newOutData, outData[:authheadLength])
|
||||||
|
outData = newOutData
|
||||||
|
}
|
||||||
|
a.packData(outData[authheadLength:], data, randLength)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *authChainA) PreEncrypt(plainData []byte) (outData []byte, err error) {
|
||||||
|
a.buffer.Reset()
|
||||||
|
dataLength := len(plainData)
|
||||||
|
length := dataLength
|
||||||
|
offset := 0
|
||||||
|
if length > 0 && !a.hasSentHeader {
|
||||||
|
headSize := 1200
|
||||||
|
if headSize > dataLength {
|
||||||
|
headSize = dataLength
|
||||||
|
}
|
||||||
|
a.buffer.Write(a.packAuthData(plainData[:headSize]))
|
||||||
|
offset += headSize
|
||||||
|
dataLength -= headSize
|
||||||
|
a.hasSentHeader = true
|
||||||
|
}
|
||||||
|
var unitSize = a.TcpMss - a.Overhead
|
||||||
|
for dataLength > unitSize {
|
||||||
|
dataLen, randLength := a.packedDataLen(plainData[offset : offset+unitSize])
|
||||||
|
b := make([]byte, dataLen)
|
||||||
|
a.packData(b, plainData[offset:offset+unitSize], randLength)
|
||||||
|
a.buffer.Write(b)
|
||||||
|
dataLength -= unitSize
|
||||||
|
offset += unitSize
|
||||||
|
}
|
||||||
|
if dataLength > 0 {
|
||||||
|
dataLen, randLength := a.packedDataLen(plainData[offset:])
|
||||||
|
b := make([]byte, dataLen)
|
||||||
|
a.packData(b, plainData[offset:], randLength)
|
||||||
|
a.buffer.Write(b)
|
||||||
|
}
|
||||||
|
return a.buffer.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *authChainA) PostDecrypt(plainData []byte) (outData []byte, n int, err error) {
|
||||||
|
a.buffer.Reset()
|
||||||
|
key := make([]byte, len(a.userKey)+4)
|
||||||
|
readlenth := 0
|
||||||
|
copy(key, a.userKey)
|
||||||
|
for len(plainData) > 4 {
|
||||||
|
binary.LittleEndian.PutUint32(key[len(a.userKey):], a.recvID)
|
||||||
|
dataLen := (int)((uint(plainData[1]^a.lastServerHash[15]) << 8) + uint(plainData[0]^a.lastServerHash[14]))
|
||||||
|
randLen := a.getServerRandLen(dataLen, a.Overhead)
|
||||||
|
length := randLen + dataLen
|
||||||
|
if length >= 4096 {
|
||||||
|
return nil, 0, ssr.ErrAuthChainDataLengthError
|
||||||
|
}
|
||||||
|
length += 4
|
||||||
|
if length > len(plainData) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
hash := a.hmac(key, plainData[:length-2])
|
||||||
|
if !bytes.Equal(hash[:2], plainData[length-2:length]) {
|
||||||
|
return nil, 0, ssr.ErrAuthChainIncorrectHMAC
|
||||||
|
}
|
||||||
|
var dataPos int
|
||||||
|
if dataLen > 0 && randLen > 0 {
|
||||||
|
dataPos = 2 + getRandStartPos(&a.randomServer, randLen)
|
||||||
|
} else {
|
||||||
|
dataPos = 2
|
||||||
|
}
|
||||||
|
b := make([]byte, dataLen)
|
||||||
|
a.cipher.Decrypt(b, plainData[dataPos:dataPos+dataLen])
|
||||||
|
a.buffer.Write(b)
|
||||||
|
if a.recvID == 1 {
|
||||||
|
a.TcpMss = int(binary.LittleEndian.Uint16(a.buffer.Next(2)))
|
||||||
|
}
|
||||||
|
a.lastServerHash = hash
|
||||||
|
a.recvID++
|
||||||
|
plainData = plainData[length:]
|
||||||
|
readlenth += length
|
||||||
|
|
||||||
|
}
|
||||||
|
return a.buffer.Bytes(), readlenth, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *authChainA) GetOverhead() int {
|
||||||
|
return 4
|
||||||
|
}
|
||||||
81
proxy/ssr/internal/protocol/auth_chain_b.go
Normal file
81
proxy/ssr/internal/protocol/auth_chain_b.go
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
package protocol
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/nadoo/glider/proxy/ssr/internal/tools"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
register("auth_chain_b", NewAuthChainB)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAuthChainB() IProtocol {
|
||||||
|
a := &authChainA{
|
||||||
|
salt: "auth_chain_b",
|
||||||
|
hmac: tools.HmacMD5,
|
||||||
|
hashDigest: tools.SHA1Sum,
|
||||||
|
rnd: authChainBGetRandLen,
|
||||||
|
recvInfo: recvInfo{
|
||||||
|
recvID: 1,
|
||||||
|
buffer: new(bytes.Buffer),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *authChainA) authChainBInitDataSize() {
|
||||||
|
if len(a.Key) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// libev version
|
||||||
|
random := &a.randomServer
|
||||||
|
random.InitFromBin(a.Key)
|
||||||
|
length := random.Next()%8 + 4
|
||||||
|
a.dataSizeList = make([]int, length)
|
||||||
|
for i := range int(length) {
|
||||||
|
a.dataSizeList[i] = int(random.Next() % 2340 % 2040 % 1440)
|
||||||
|
}
|
||||||
|
sort.Ints(a.dataSizeList)
|
||||||
|
|
||||||
|
length = random.Next()%16 + 8
|
||||||
|
a.dataSizeList2 = make([]int, length)
|
||||||
|
for i := range int(length) {
|
||||||
|
a.dataSizeList2[i] = int(random.Next() % 2340 % 2040 % 1440)
|
||||||
|
}
|
||||||
|
sort.Ints(a.dataSizeList2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func authChainBGetRandLen(dataLength int, random *tools.Shift128plusContext, lastHash []byte, dataSizeList, dataSizeList2 []int, overhead int) int {
|
||||||
|
if dataLength > 1440 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
random.InitFromBinDatalen(lastHash[:16], dataLength)
|
||||||
|
// libev version, upper_bound
|
||||||
|
pos := sort.Search(len(dataSizeList), func(i int) bool { return dataSizeList[i] > dataLength+overhead })
|
||||||
|
finalPos := uint64(pos) + random.Next()%uint64(len(dataSizeList))
|
||||||
|
if finalPos < uint64(len(dataSizeList)) {
|
||||||
|
return dataSizeList[finalPos] - dataLength - overhead
|
||||||
|
}
|
||||||
|
// libev version, upper_bound
|
||||||
|
pos = sort.Search(len(dataSizeList2), func(i int) bool { return dataSizeList2[i] > dataLength+overhead })
|
||||||
|
finalPos = uint64(pos) + random.Next()%uint64(len(dataSizeList2))
|
||||||
|
if finalPos < uint64(len(dataSizeList2)) {
|
||||||
|
return dataSizeList2[finalPos] - dataLength - overhead
|
||||||
|
}
|
||||||
|
if finalPos < uint64(pos+len(dataSizeList2)-1) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if dataLength > 1300 {
|
||||||
|
return int(random.Next() % 31)
|
||||||
|
}
|
||||||
|
if dataLength > 900 {
|
||||||
|
return int(random.Next() % 127)
|
||||||
|
}
|
||||||
|
if dataLength > 400 {
|
||||||
|
return int(random.Next() % 521)
|
||||||
|
}
|
||||||
|
return int(random.Next() % 1021)
|
||||||
|
}
|
||||||
227
proxy/ssr/internal/protocol/auth_sha1_v4.go
Normal file
227
proxy/ssr/internal/protocol/auth_sha1_v4.go
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
package protocol
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
crand "crypto/rand"
|
||||||
|
"encoding/binary"
|
||||||
|
"math/rand/v2"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/nadoo/glider/proxy/ssr/internal/ssr"
|
||||||
|
"github.com/nadoo/glider/proxy/ssr/internal/tools"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
register("auth_sha1_v4", NewAuthSHA1v4)
|
||||||
|
}
|
||||||
|
|
||||||
|
type authSHA1v4 struct {
|
||||||
|
ssr.ServerInfo
|
||||||
|
data *AuthData
|
||||||
|
hasSentHeader bool
|
||||||
|
buffer bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAuthSHA1v4() IProtocol {
|
||||||
|
a := &authSHA1v4{}
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *authSHA1v4) SetServerInfo(s *ssr.ServerInfo) {
|
||||||
|
a.ServerInfo = *s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *authSHA1v4) GetServerInfo() (s *ssr.ServerInfo) {
|
||||||
|
return &a.ServerInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *authSHA1v4) SetData(data any) {
|
||||||
|
if auth, ok := data.(*AuthData); ok {
|
||||||
|
a.data = auth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *authSHA1v4) GetData() any {
|
||||||
|
if a.data == nil {
|
||||||
|
a.data = &AuthData{}
|
||||||
|
}
|
||||||
|
return a.data
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *authSHA1v4) packData(data []byte) (outData []byte) {
|
||||||
|
dataLength := len(data)
|
||||||
|
randLength := 1
|
||||||
|
|
||||||
|
if dataLength <= 1300 {
|
||||||
|
if dataLength > 400 {
|
||||||
|
randLength += rand.IntN(128)
|
||||||
|
} else {
|
||||||
|
randLength += rand.IntN(1024)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
outLength := randLength + dataLength + 8
|
||||||
|
outData = make([]byte, outLength)
|
||||||
|
// 0~1, out length
|
||||||
|
binary.BigEndian.PutUint16(outData[0:2], uint16(outLength&0xFFFF))
|
||||||
|
// 2~3, crc of out length
|
||||||
|
crc32 := ssr.CalcCRC32(outData, 2, 0xFFFFFFFF)
|
||||||
|
binary.LittleEndian.PutUint16(outData[2:4], uint16(crc32&0xFFFF))
|
||||||
|
// 4, rand length
|
||||||
|
if randLength < 128 {
|
||||||
|
outData[4] = uint8(randLength & 0xFF)
|
||||||
|
} else {
|
||||||
|
outData[4] = uint8(0xFF)
|
||||||
|
binary.BigEndian.PutUint16(outData[5:7], uint16(randLength&0xFFFF))
|
||||||
|
}
|
||||||
|
// rand length+4~out length-4, data
|
||||||
|
if dataLength > 0 {
|
||||||
|
copy(outData[randLength+4:], data)
|
||||||
|
}
|
||||||
|
// out length-4~end, adler32 of full data
|
||||||
|
adler := ssr.CalcAdler32(outData[:outLength-4])
|
||||||
|
binary.LittleEndian.PutUint32(outData[outLength-4:], adler)
|
||||||
|
|
||||||
|
return outData
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *authSHA1v4) packAuthData(data []byte) (outData []byte) {
|
||||||
|
|
||||||
|
dataLength := len(data)
|
||||||
|
randLength := 1
|
||||||
|
if dataLength <= 1300 {
|
||||||
|
if dataLength > 400 {
|
||||||
|
randLength += rand.IntN(128)
|
||||||
|
} else {
|
||||||
|
randLength += rand.IntN(1024)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dataOffset := randLength + 4 + 2
|
||||||
|
outLength := dataOffset + dataLength + 12 + ssr.ObfsHMACSHA1Len
|
||||||
|
outData = make([]byte, outLength)
|
||||||
|
a.data.connectionID++
|
||||||
|
if a.data.connectionID > 0xFF000000 {
|
||||||
|
a.data.clientID = nil
|
||||||
|
}
|
||||||
|
if len(a.data.clientID) == 0 {
|
||||||
|
a.data.clientID = make([]byte, 8)
|
||||||
|
crand.Read(a.data.clientID)
|
||||||
|
b := make([]byte, 4)
|
||||||
|
crand.Read(b)
|
||||||
|
a.data.connectionID = binary.LittleEndian.Uint32(b) & 0xFFFFFF
|
||||||
|
}
|
||||||
|
// 0-1, out length
|
||||||
|
binary.BigEndian.PutUint16(outData[0:2], uint16(outLength&0xFFFF))
|
||||||
|
|
||||||
|
// 2~6, crc of out length+salt+key
|
||||||
|
salt := []byte("auth_sha1_v4")
|
||||||
|
crcData := make([]byte, len(salt)+a.KeyLen+2)
|
||||||
|
copy(crcData[0:2], outData[0:2])
|
||||||
|
copy(crcData[2:], salt)
|
||||||
|
copy(crcData[2+len(salt):], a.Key)
|
||||||
|
crc32 := ssr.CalcCRC32(crcData, len(crcData), 0xFFFFFFFF)
|
||||||
|
// 2~6, crc of out length+salt+key
|
||||||
|
binary.LittleEndian.PutUint32(outData[2:], crc32)
|
||||||
|
// 6~rand length+6, rand numbers
|
||||||
|
crand.Read(outData[dataOffset-randLength : dataOffset])
|
||||||
|
// 6, rand length
|
||||||
|
if randLength < 128 {
|
||||||
|
outData[6] = byte(randLength & 0xFF)
|
||||||
|
} else {
|
||||||
|
// 6, magic number 0xFF
|
||||||
|
outData[6] = 0xFF
|
||||||
|
// 7-8, rand length
|
||||||
|
binary.BigEndian.PutUint16(outData[7:9], uint16(randLength&0xFFFF))
|
||||||
|
}
|
||||||
|
// rand length+6~rand length+10, time stamp
|
||||||
|
now := time.Now().Unix()
|
||||||
|
binary.LittleEndian.PutUint32(outData[dataOffset:dataOffset+4], uint32(now))
|
||||||
|
// rand length+10~rand length+14, client ID
|
||||||
|
copy(outData[dataOffset+4:dataOffset+4+4], a.data.clientID[0:4])
|
||||||
|
// rand length+14~rand length+18, connection ID
|
||||||
|
binary.LittleEndian.PutUint32(outData[dataOffset+8:dataOffset+8+4], a.data.connectionID)
|
||||||
|
// rand length+18~rand length+18+data length, data
|
||||||
|
copy(outData[dataOffset+12:], data)
|
||||||
|
|
||||||
|
key := make([]byte, a.IVLen+a.KeyLen)
|
||||||
|
copy(key, a.IV)
|
||||||
|
copy(key[a.IVLen:], a.Key)
|
||||||
|
|
||||||
|
h := tools.HmacSHA1(key, outData[:outLength-ssr.ObfsHMACSHA1Len])
|
||||||
|
// out length-10~out length/rand length+18+data length~end, hmac
|
||||||
|
copy(outData[outLength-ssr.ObfsHMACSHA1Len:], h[0:ssr.ObfsHMACSHA1Len])
|
||||||
|
return outData
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *authSHA1v4) PreEncrypt(plainData []byte) (outData []byte, err error) {
|
||||||
|
a.buffer.Reset()
|
||||||
|
dataLength := len(plainData)
|
||||||
|
offset := 0
|
||||||
|
if !a.hasSentHeader && dataLength > 0 {
|
||||||
|
headSize := ssr.GetHeadSize(plainData, 30)
|
||||||
|
if headSize > dataLength {
|
||||||
|
headSize = dataLength
|
||||||
|
}
|
||||||
|
a.buffer.Write(a.packAuthData(plainData[:headSize]))
|
||||||
|
offset += headSize
|
||||||
|
dataLength -= headSize
|
||||||
|
a.hasSentHeader = true
|
||||||
|
}
|
||||||
|
const blockSize = 4096
|
||||||
|
for dataLength > blockSize {
|
||||||
|
a.buffer.Write(a.packData(plainData[offset : offset+blockSize]))
|
||||||
|
offset += blockSize
|
||||||
|
dataLength -= blockSize
|
||||||
|
}
|
||||||
|
if dataLength > 0 {
|
||||||
|
a.buffer.Write(a.packData(plainData[offset:]))
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.buffer.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *authSHA1v4) PostDecrypt(plainData []byte) (outData []byte, n int, err error) {
|
||||||
|
a.buffer.Reset()
|
||||||
|
dataLength := len(plainData)
|
||||||
|
plainLength := dataLength
|
||||||
|
for dataLength > 4 {
|
||||||
|
crc32 := ssr.CalcCRC32(plainData, 2, 0xFFFFFFFF)
|
||||||
|
if binary.LittleEndian.Uint16(plainData[2:4]) != uint16(crc32&0xFFFF) {
|
||||||
|
//common.Error("auth_sha1_v4 post decrypt data crc32 error")
|
||||||
|
return nil, 0, ssr.ErrAuthSHA1v4CRC32Error
|
||||||
|
}
|
||||||
|
length := int(binary.BigEndian.Uint16(plainData[0:2]))
|
||||||
|
if length >= 8192 || length < 8 {
|
||||||
|
//common.Error("auth_sha1_v4 post decrypt data length error")
|
||||||
|
dataLength = 0
|
||||||
|
plainData = nil
|
||||||
|
return nil, 0, ssr.ErrAuthSHA1v4DataLengthError
|
||||||
|
}
|
||||||
|
if length > dataLength {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if ssr.CheckAdler32(plainData, length) {
|
||||||
|
pos := int(plainData[4])
|
||||||
|
if pos != 0xFF {
|
||||||
|
pos += 4
|
||||||
|
} else {
|
||||||
|
pos = int(binary.BigEndian.Uint16(plainData[5:5+2])) + 4
|
||||||
|
}
|
||||||
|
outLength := length - pos - 4
|
||||||
|
a.buffer.Write(plainData[pos : pos+outLength])
|
||||||
|
dataLength -= length
|
||||||
|
plainData = plainData[length:]
|
||||||
|
} else {
|
||||||
|
//common.Error("auth_sha1_v4 post decrypt incorrect checksum")
|
||||||
|
dataLength = 0
|
||||||
|
plainData = nil
|
||||||
|
return nil, 0, ssr.ErrAuthSHA1v4IncorrectChecksum
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return a.buffer.Bytes(), plainLength - dataLength, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *authSHA1v4) GetOverhead() int {
|
||||||
|
return 7
|
||||||
|
}
|
||||||
47
proxy/ssr/internal/protocol/base.go
Normal file
47
proxy/ssr/internal/protocol/base.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package protocol
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/nadoo/glider/proxy/ssr/internal/ssr"
|
||||||
|
"github.com/nadoo/glider/proxy/ssr/internal/tools"
|
||||||
|
)
|
||||||
|
|
||||||
|
type creator func() IProtocol
|
||||||
|
|
||||||
|
var (
|
||||||
|
creatorMap = make(map[string]creator)
|
||||||
|
)
|
||||||
|
|
||||||
|
type hmacMethod func(key []byte, data []byte) []byte
|
||||||
|
type hashDigestMethod func(data []byte) []byte
|
||||||
|
type rndMethod func(dataLength int, random *tools.Shift128plusContext, lastHash []byte, dataSizeList, dataSizeList2 []int, overhead int) int
|
||||||
|
|
||||||
|
type IProtocol interface {
|
||||||
|
SetServerInfo(s *ssr.ServerInfo)
|
||||||
|
GetServerInfo() *ssr.ServerInfo
|
||||||
|
PreEncrypt(data []byte) ([]byte, error)
|
||||||
|
PostDecrypt(data []byte) ([]byte, int, error)
|
||||||
|
SetData(data any)
|
||||||
|
GetData() any
|
||||||
|
GetOverhead() int
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthData struct {
|
||||||
|
clientID []byte
|
||||||
|
connectionID uint32
|
||||||
|
mutex sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func register(name string, c creator) {
|
||||||
|
creatorMap[name] = c
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProtocol(name string) IProtocol {
|
||||||
|
c, ok := creatorMap[strings.ToLower(name)]
|
||||||
|
if ok {
|
||||||
|
return c()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
46
proxy/ssr/internal/protocol/origin.go
Normal file
46
proxy/ssr/internal/protocol/origin.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package protocol
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/nadoo/glider/proxy/ssr/internal/ssr"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
register("origin", NewOrigin)
|
||||||
|
}
|
||||||
|
|
||||||
|
type origin struct {
|
||||||
|
ssr.ServerInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOrigin() IProtocol {
|
||||||
|
a := &origin{}
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *origin) SetServerInfo(s *ssr.ServerInfo) {
|
||||||
|
o.ServerInfo = *s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *origin) GetServerInfo() (s *ssr.ServerInfo) {
|
||||||
|
return &o.ServerInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *origin) PreEncrypt(data []byte) (encryptedData []byte, err error) {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *origin) PostDecrypt(data []byte) ([]byte, int, error) {
|
||||||
|
return data, len(data), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *origin) SetData(data any) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *origin) GetData() any {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *origin) GetOverhead() int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
105
proxy/ssr/internal/protocol/verify_sha1.go
Normal file
105
proxy/ssr/internal/protocol/verify_sha1.go
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
package protocol
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
|
||||||
|
"github.com/nadoo/glider/proxy/ssr/internal/ssr"
|
||||||
|
"github.com/nadoo/glider/proxy/ssr/internal/tools"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
register("verify_sha1", NewVerifySHA1)
|
||||||
|
register("ota", NewVerifySHA1)
|
||||||
|
}
|
||||||
|
|
||||||
|
type verifySHA1 struct {
|
||||||
|
ssr.ServerInfo
|
||||||
|
hasSentHeader bool
|
||||||
|
buffer bytes.Buffer
|
||||||
|
chunkId uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
oneTimeAuthMask byte = 0x10
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewVerifySHA1() IProtocol {
|
||||||
|
a := &verifySHA1{}
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *verifySHA1) otaConnectAuth(data []byte) []byte {
|
||||||
|
return append(data, tools.HmacSHA1(append(v.IV, v.Key...), data)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *verifySHA1) otaReqChunkAuth(chunkId uint32, data []byte) []byte {
|
||||||
|
nb := make([]byte, 2)
|
||||||
|
binary.BigEndian.PutUint16(nb, uint16(len(data)))
|
||||||
|
chunkIdBytes := make([]byte, 4)
|
||||||
|
binary.BigEndian.PutUint32(chunkIdBytes, chunkId)
|
||||||
|
header := append(nb, tools.HmacSHA1(append(v.IV, chunkIdBytes...), data)...)
|
||||||
|
return append(header, data...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *verifySHA1) otaVerifyAuth(iv []byte, chunkId uint32, data []byte, expectedHmacSha1 []byte) bool {
|
||||||
|
chunkIdBytes := make([]byte, 4)
|
||||||
|
binary.BigEndian.PutUint32(chunkIdBytes, chunkId)
|
||||||
|
actualHmacSha1 := tools.HmacSHA1(append(iv, chunkIdBytes...), data)
|
||||||
|
return bytes.Equal(expectedHmacSha1, actualHmacSha1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *verifySHA1) getAndIncreaseChunkId() (chunkId uint32) {
|
||||||
|
chunkId = v.chunkId
|
||||||
|
v.chunkId += 1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *verifySHA1) SetServerInfo(s *ssr.ServerInfo) {
|
||||||
|
v.ServerInfo = *s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *verifySHA1) GetServerInfo() (s *ssr.ServerInfo) {
|
||||||
|
return &v.ServerInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *verifySHA1) SetData(data any) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *verifySHA1) GetData() any {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *verifySHA1) PreEncrypt(data []byte) (encryptedData []byte, err error) {
|
||||||
|
v.buffer.Reset()
|
||||||
|
dataLength := len(data)
|
||||||
|
offset := 0
|
||||||
|
if !v.hasSentHeader {
|
||||||
|
data[0] |= oneTimeAuthMask
|
||||||
|
v.buffer.Write(v.otaConnectAuth(data[:v.HeadLen]))
|
||||||
|
v.hasSentHeader = true
|
||||||
|
dataLength -= v.HeadLen
|
||||||
|
offset += v.HeadLen
|
||||||
|
}
|
||||||
|
const blockSize = 4096
|
||||||
|
for dataLength > blockSize {
|
||||||
|
chunkId := v.getAndIncreaseChunkId()
|
||||||
|
v.buffer.Write(v.otaReqChunkAuth(chunkId, data[offset:offset+blockSize]))
|
||||||
|
dataLength -= blockSize
|
||||||
|
offset += blockSize
|
||||||
|
}
|
||||||
|
if dataLength > 0 {
|
||||||
|
chunkId := v.getAndIncreaseChunkId()
|
||||||
|
v.buffer.Write(v.otaReqChunkAuth(chunkId, data[offset:]))
|
||||||
|
}
|
||||||
|
return v.buffer.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *verifySHA1) PostDecrypt(data []byte) ([]byte, int, error) {
|
||||||
|
return data, len(data), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *verifySHA1) GetOverhead() int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
31
proxy/ssr/internal/ssr/adler32.go
Normal file
31
proxy/ssr/internal/ssr/adler32.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package ssr
|
||||||
|
|
||||||
|
import "encoding/binary"
|
||||||
|
|
||||||
|
func calcShortAdler32(input []byte, a, b uint32) (uint32, uint32) {
|
||||||
|
for _, i := range input {
|
||||||
|
a += uint32(i)
|
||||||
|
b += a
|
||||||
|
}
|
||||||
|
a %= 65521
|
||||||
|
b %= 65521
|
||||||
|
return a, b
|
||||||
|
}
|
||||||
|
|
||||||
|
func CalcAdler32(input []byte) uint32 {
|
||||||
|
var a uint32 = 1
|
||||||
|
var b uint32 = 0
|
||||||
|
const nMax = 5552
|
||||||
|
for length := len(input); length > nMax; length -= nMax {
|
||||||
|
a, b = calcShortAdler32(input[:nMax], a, b)
|
||||||
|
input = input[nMax:]
|
||||||
|
}
|
||||||
|
a, b = calcShortAdler32(input, a, b)
|
||||||
|
return (b << 16) + a
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckAdler32(input []byte, l int) bool {
|
||||||
|
adler32 := CalcAdler32(input[:l-4])
|
||||||
|
checksum := binary.LittleEndian.Uint32(input[l-4:])
|
||||||
|
return adler32 == checksum
|
||||||
|
}
|
||||||
52
proxy/ssr/internal/ssr/crc32.go
Normal file
52
proxy/ssr/internal/ssr/crc32.go
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package ssr
|
||||||
|
|
||||||
|
import "encoding/binary"
|
||||||
|
|
||||||
|
var (
|
||||||
|
crc32Table = make([]uint32, 256)
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
createCRC32Table()
|
||||||
|
}
|
||||||
|
|
||||||
|
func createCRC32Table() {
|
||||||
|
for i := range 256 {
|
||||||
|
crc := uint32(i)
|
||||||
|
for j := 8; j > 0; j-- {
|
||||||
|
if crc&1 == 1 {
|
||||||
|
crc = (crc >> 1) ^ 0xEDB88320
|
||||||
|
} else {
|
||||||
|
crc >>= 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
crc32Table[i] = crc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CalcCRC32(input []byte, length int, value uint32) uint32 {
|
||||||
|
value = 0xFFFFFFFF
|
||||||
|
return DoCalcCRC32(input, 0, length, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DoCalcCRC32(input []byte, index int, length int, value uint32) uint32 {
|
||||||
|
buffer := input
|
||||||
|
for i := index; i < length; i++ {
|
||||||
|
value = (value >> 8) ^ crc32Table[byte(value&0xFF)^buffer[i]]
|
||||||
|
}
|
||||||
|
return value ^ 0xFFFFFFFF
|
||||||
|
}
|
||||||
|
|
||||||
|
func DoSetCRC32(buffer []byte, index int, length int) {
|
||||||
|
crc := CalcCRC32(buffer[:length-4], length-4, 0xFFFFFFFF)
|
||||||
|
binary.LittleEndian.PutUint32(buffer[length-4:], crc^0xFFFFFFFF)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetCRC32(buffer []byte, length int) {
|
||||||
|
DoSetCRC32(buffer, 0, length)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckCRC32(buffer []byte, length int) bool {
|
||||||
|
crc := CalcCRC32(buffer, length, 0xFFFFFFFF)
|
||||||
|
return crc == 0xFFFFFFFF
|
||||||
|
}
|
||||||
59
proxy/ssr/internal/ssr/obfs.go
Normal file
59
proxy/ssr/internal/ssr/obfs.go
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
package ssr
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
const ObfsHMACSHA1Len = 10
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrAuthSHA1v4CRC32Error = errors.New("auth_sha1_v4 post decrypt data crc32 error")
|
||||||
|
ErrAuthSHA1v4DataLengthError = errors.New("auth_sha1_v4 post decrypt data length error")
|
||||||
|
ErrAuthSHA1v4IncorrectChecksum = errors.New("auth_sha1_v4 post decrypt incorrect checksum")
|
||||||
|
ErrAuthAES128IncorrectHMAC = errors.New("auth_aes128_* post decrypt incorrect hmac")
|
||||||
|
ErrAuthAES128DataLengthError = errors.New("auth_aes128_* post decrypt length mismatch")
|
||||||
|
ErrAuthChainDataLengthError = errors.New("auth_chain_* post decrypt length mismatch")
|
||||||
|
ErrAuthChainIncorrectHMAC = errors.New("auth_chain_* post decrypt incorrect hmac")
|
||||||
|
ErrAuthAES128IncorrectChecksum = errors.New("auth_aes128_* post decrypt incorrect checksum")
|
||||||
|
ErrAuthAES128PosOutOfRange = errors.New("auth_aes128_* post decrypt pos out of range")
|
||||||
|
ErrTLS12TicketAuthTooShortData = errors.New("tls1.2_ticket_auth too short data")
|
||||||
|
ErrTLS12TicketAuthHMACError = errors.New("tls1.2_ticket_auth hmac verifying failed")
|
||||||
|
ErrTLS12TicketAuthIncorrectMagicNumber = errors.New("tls1.2_ticket_auth incorrect magic number")
|
||||||
|
)
|
||||||
|
|
||||||
|
type ServerInfo struct {
|
||||||
|
Host string
|
||||||
|
Port uint16
|
||||||
|
Param string
|
||||||
|
IV []byte
|
||||||
|
IVLen int
|
||||||
|
RecvIV []byte
|
||||||
|
RecvIVLen int
|
||||||
|
Key []byte
|
||||||
|
KeyLen int
|
||||||
|
HeadLen int
|
||||||
|
TcpMss int
|
||||||
|
Overhead int
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetHeadSize(data []byte, defaultValue int) int {
|
||||||
|
if data == nil || len(data) < 2 {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
headType := data[0] & 0x07
|
||||||
|
switch headType {
|
||||||
|
case 1:
|
||||||
|
// IPv4 1+4+2
|
||||||
|
return 7
|
||||||
|
case 4:
|
||||||
|
// IPv6 1+16+2
|
||||||
|
return 19
|
||||||
|
case 3:
|
||||||
|
// domain name, variant length
|
||||||
|
return 4 + int(data[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServerInfo) SetHeadLen(data []byte, defaultValue int) {
|
||||||
|
s.HeadLen = GetHeadSize(data, defaultValue)
|
||||||
|
}
|
||||||
51
proxy/ssr/internal/tools/encrypt.go
Normal file
51
proxy/ssr/internal/tools/encrypt.go
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
package tools
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/md5"
|
||||||
|
"crypto/sha1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func HmacMD5(key []byte, data []byte) []byte {
|
||||||
|
hmacMD5 := hmac.New(md5.New, key)
|
||||||
|
hmacMD5.Write(data)
|
||||||
|
return hmacMD5.Sum(nil)[:16]
|
||||||
|
}
|
||||||
|
|
||||||
|
func HmacSHA1(key []byte, data []byte) []byte {
|
||||||
|
hmacSHA1 := hmac.New(sha1.New, key)
|
||||||
|
hmacSHA1.Write(data)
|
||||||
|
return hmacSHA1.Sum(nil)[:20]
|
||||||
|
}
|
||||||
|
|
||||||
|
func MD5Sum(d []byte) []byte {
|
||||||
|
h := md5.New()
|
||||||
|
h.Write(d)
|
||||||
|
return h.Sum(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SHA1Sum(d []byte) []byte {
|
||||||
|
h := sha1.New()
|
||||||
|
h.Write(d)
|
||||||
|
return h.Sum(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func EVPBytesToKey(password string, keyLen int) (key []byte) {
|
||||||
|
const md5Len = 16
|
||||||
|
|
||||||
|
cnt := (keyLen-1)/md5Len + 1
|
||||||
|
m := make([]byte, cnt*md5Len)
|
||||||
|
copy(m, MD5Sum([]byte(password)))
|
||||||
|
|
||||||
|
// Repeatedly call md5 until bytes generated is enough.
|
||||||
|
// Each call to md5 uses data: prev md5 sum + password.
|
||||||
|
d := make([]byte, md5Len+len(password))
|
||||||
|
start := 0
|
||||||
|
for i := 1; i < cnt; i++ {
|
||||||
|
start += md5Len
|
||||||
|
copy(d, m[start-md5Len:start])
|
||||||
|
copy(d[md5Len:], password)
|
||||||
|
copy(m[start:], MD5Sum(d))
|
||||||
|
}
|
||||||
|
return m[:keyLen]
|
||||||
|
}
|
||||||
53
proxy/ssr/internal/tools/obfsutil.go
Normal file
53
proxy/ssr/internal/tools/obfsutil.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package tools
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func IsLittleEndian() bool {
|
||||||
|
const N int = int(unsafe.Sizeof(0))
|
||||||
|
x := 0x1234
|
||||||
|
p := unsafe.Pointer(&x)
|
||||||
|
p2 := (*[N]byte)(p)
|
||||||
|
if p2[0] == 0 {
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Shift128plusContext struct {
|
||||||
|
v [2]uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *Shift128plusContext) InitFromBin(bin []byte) {
|
||||||
|
var fillBin [16]byte
|
||||||
|
copy(fillBin[:], bin)
|
||||||
|
|
||||||
|
ctx.v[0] = binary.LittleEndian.Uint64(fillBin[:8])
|
||||||
|
ctx.v[1] = binary.LittleEndian.Uint64(fillBin[8:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *Shift128plusContext) InitFromBinDatalen(bin []byte, datalen int) {
|
||||||
|
var fillBin [16]byte
|
||||||
|
copy(fillBin[:], bin)
|
||||||
|
binary.LittleEndian.PutUint16(fillBin[:2], uint16(datalen))
|
||||||
|
|
||||||
|
ctx.v[0] = binary.LittleEndian.Uint64(fillBin[:8])
|
||||||
|
ctx.v[1] = binary.LittleEndian.Uint64(fillBin[8:])
|
||||||
|
|
||||||
|
for range 4 {
|
||||||
|
ctx.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *Shift128plusContext) Next() uint64 {
|
||||||
|
x := ctx.v[0]
|
||||||
|
y := ctx.v[1]
|
||||||
|
ctx.v[0] = y
|
||||||
|
x ^= x << 23
|
||||||
|
x ^= y ^ (x >> 17) ^ (y >> 26)
|
||||||
|
ctx.v[1] = x
|
||||||
|
return x + y
|
||||||
|
}
|
||||||
164
proxy/ssr/ssr.go
Normal file
164
proxy/ssr/ssr.go
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
package ssr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/nadoo/glider/pkg/log"
|
||||||
|
"github.com/nadoo/glider/pkg/socks"
|
||||||
|
"github.com/nadoo/glider/proxy"
|
||||||
|
|
||||||
|
"github.com/nadoo/glider/proxy/ssr/internal"
|
||||||
|
"github.com/nadoo/glider/proxy/ssr/internal/cipher"
|
||||||
|
"github.com/nadoo/glider/proxy/ssr/internal/obfs"
|
||||||
|
"github.com/nadoo/glider/proxy/ssr/internal/protocol"
|
||||||
|
ssrinfo "github.com/nadoo/glider/proxy/ssr/internal/ssr"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
proxy.RegisterDialer("ssr", NewSSRDialer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SSR struct.
|
||||||
|
type SSR struct {
|
||||||
|
dialer proxy.Dialer
|
||||||
|
addr string
|
||||||
|
|
||||||
|
EncryptMethod string
|
||||||
|
EncryptPassword string
|
||||||
|
Obfs string
|
||||||
|
ObfsParam string
|
||||||
|
ObfsData any
|
||||||
|
Protocol string
|
||||||
|
ProtocolParam string
|
||||||
|
ProtocolData any
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSSR returns a shadowsocksr proxy, ssr://method:pass@host:port/query
|
||||||
|
func NewSSR(s string, d proxy.Dialer) (*SSR, error) {
|
||||||
|
u, err := url.Parse(s)
|
||||||
|
if err != nil {
|
||||||
|
log.F("[ssr] parse err: %s", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := u.Host
|
||||||
|
method := u.User.Username()
|
||||||
|
pass, _ := u.User.Password()
|
||||||
|
|
||||||
|
p := &SSR{
|
||||||
|
dialer: d,
|
||||||
|
addr: addr,
|
||||||
|
EncryptMethod: method,
|
||||||
|
EncryptPassword: pass,
|
||||||
|
}
|
||||||
|
|
||||||
|
query := u.Query()
|
||||||
|
p.Protocol = query.Get("protocol")
|
||||||
|
p.ProtocolParam = query.Get("protocol_param")
|
||||||
|
p.Obfs = query.Get("obfs")
|
||||||
|
p.ObfsParam = query.Get("obfs_param")
|
||||||
|
|
||||||
|
p.ProtocolData = new(protocol.AuthData)
|
||||||
|
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSSRDialer returns a ssr proxy dialer.
|
||||||
|
func NewSSRDialer(s string, d proxy.Dialer) (proxy.Dialer, error) {
|
||||||
|
return NewSSR(s, d)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Addr returns forwarder's address
|
||||||
|
func (s *SSR) Addr() string {
|
||||||
|
if s.addr == "" {
|
||||||
|
return s.dialer.Addr()
|
||||||
|
}
|
||||||
|
return s.addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dial connects to the address addr on the network net via the proxy.
|
||||||
|
func (s *SSR) Dial(network, addr string) (net.Conn, error) {
|
||||||
|
target := socks.ParseAddr(addr)
|
||||||
|
if target == nil {
|
||||||
|
return nil, errors.New("[ssr] unable to parse address: " + addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
cipher, err := cipher.NewStreamCipher(s.EncryptMethod, s.EncryptPassword)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := s.dialer.Dial("tcp", s.addr)
|
||||||
|
if err != nil {
|
||||||
|
log.F("[ssr] dial to %s error: %s", s.addr, err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ssrconn := internal.NewSSTCPConn(c, cipher)
|
||||||
|
if ssrconn.Conn == nil || ssrconn.RemoteAddr() == nil {
|
||||||
|
return nil, errors.New("[ssr] nil connection")
|
||||||
|
}
|
||||||
|
|
||||||
|
// should initialize obfs/protocol now
|
||||||
|
rs := strings.Split(ssrconn.RemoteAddr().String(), ":")
|
||||||
|
port, _ := strconv.Atoi(rs[1])
|
||||||
|
|
||||||
|
ssrconn.IObfs = obfs.NewObfs(s.Obfs)
|
||||||
|
if ssrconn.IObfs == nil {
|
||||||
|
return nil, errors.New("[ssr] unsupported obfs type: " + s.Obfs)
|
||||||
|
}
|
||||||
|
|
||||||
|
obfsServerInfo := &ssrinfo.ServerInfo{
|
||||||
|
Host: rs[0],
|
||||||
|
Port: uint16(port),
|
||||||
|
TcpMss: 1460,
|
||||||
|
Param: s.ObfsParam,
|
||||||
|
}
|
||||||
|
ssrconn.IObfs.SetServerInfo(obfsServerInfo)
|
||||||
|
|
||||||
|
ssrconn.IProtocol = protocol.NewProtocol(s.Protocol)
|
||||||
|
if ssrconn.IProtocol == nil {
|
||||||
|
return nil, errors.New("[ssr] unsupported protocol type: " + s.Protocol)
|
||||||
|
}
|
||||||
|
|
||||||
|
protocolServerInfo := &ssrinfo.ServerInfo{
|
||||||
|
Host: rs[0],
|
||||||
|
Port: uint16(port),
|
||||||
|
TcpMss: 1460,
|
||||||
|
Param: s.ProtocolParam,
|
||||||
|
}
|
||||||
|
ssrconn.IProtocol.SetServerInfo(protocolServerInfo)
|
||||||
|
|
||||||
|
if s.ObfsData == nil {
|
||||||
|
s.ObfsData = ssrconn.IObfs.GetData()
|
||||||
|
}
|
||||||
|
ssrconn.IObfs.SetData(s.ObfsData)
|
||||||
|
|
||||||
|
if s.ProtocolData == nil {
|
||||||
|
s.ProtocolData = ssrconn.IProtocol.GetData()
|
||||||
|
}
|
||||||
|
ssrconn.IProtocol.SetData(s.ProtocolData)
|
||||||
|
|
||||||
|
if _, err := ssrconn.Write(target); err != nil {
|
||||||
|
ssrconn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ssrconn, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialUDP connects to the given address via the proxy.
|
||||||
|
func (s *SSR) DialUDP(network, addr string) (net.PacketConn, error) {
|
||||||
|
return nil, proxy.ErrNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
proxy.AddUsage("ssr", `
|
||||||
|
SSR scheme:
|
||||||
|
ssr://method:pass@host:port?protocol=xxx&protocol_param=yyy&obfs=zzz&obfs_param=xyz
|
||||||
|
`)
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user