feat: Support ShadowTLS v2 as Shadowsocks plugin (#330)
This commit is contained in:
142
transport/shadowtls/shadowtls.go
Normal file
142
transport/shadowtls/shadowtls.go
Normal file
@ -0,0 +1,142 @@
|
||||
package shadowtls
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
"github.com/Dreamacro/clash/transport/trojan"
|
||||
)
|
||||
|
||||
const (
|
||||
chunkSize = 1 << 13
|
||||
Mode string = "shadow-tls"
|
||||
hashLen int = 8
|
||||
tlsHeaderLen int = 5
|
||||
)
|
||||
|
||||
// TLSObfs is shadowsocks tls simple-obfs implementation
|
||||
type ShadowTls struct {
|
||||
net.Conn
|
||||
password []byte
|
||||
remain int
|
||||
firstRequest bool
|
||||
tlsConnector *trojan.Trojan
|
||||
}
|
||||
|
||||
type HashedConn struct {
|
||||
net.Conn
|
||||
hasher hash.Hash
|
||||
}
|
||||
|
||||
func newHashedStream(conn net.Conn, password []byte) HashedConn {
|
||||
return HashedConn{
|
||||
Conn: conn,
|
||||
hasher: hmac.New(sha1.New, password),
|
||||
}
|
||||
}
|
||||
|
||||
func (h HashedConn) Read(b []byte) (n int, err error) {
|
||||
n, err = h.Conn.Read(b)
|
||||
h.hasher.Write(b[:n])
|
||||
return
|
||||
}
|
||||
|
||||
func (to *ShadowTls) read(b []byte) (int, error) {
|
||||
buf := pool.Get(tlsHeaderLen)
|
||||
_, err := io.ReadFull(to.Conn, buf)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("shadowtls read failed %w", err)
|
||||
}
|
||||
if buf[0] != 0x17 || buf[1] != 0x3 || buf[2] != 0x3 {
|
||||
return 0, fmt.Errorf("invalid shadowtls header %v", buf)
|
||||
}
|
||||
length := int(binary.BigEndian.Uint16(buf[3:]))
|
||||
pool.Put(buf)
|
||||
|
||||
if length > len(b) {
|
||||
n, err := to.Conn.Read(b)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
to.remain = length - n
|
||||
return n, nil
|
||||
}
|
||||
|
||||
return io.ReadFull(to.Conn, b[:length])
|
||||
}
|
||||
|
||||
func (to *ShadowTls) Read(b []byte) (int, error) {
|
||||
if to.remain > 0 {
|
||||
length := to.remain
|
||||
if length > len(b) {
|
||||
length = len(b)
|
||||
}
|
||||
|
||||
n, err := io.ReadFull(to.Conn, b[:length])
|
||||
if err != nil {
|
||||
return n, fmt.Errorf("shadowtls Read failed with %w", err)
|
||||
}
|
||||
to.remain -= n
|
||||
return n, nil
|
||||
}
|
||||
|
||||
return to.read(b)
|
||||
}
|
||||
|
||||
func (to *ShadowTls) Write(b []byte) (int, error) {
|
||||
length := len(b)
|
||||
for i := 0; i < length; i += chunkSize {
|
||||
end := i + chunkSize
|
||||
if end > length {
|
||||
end = length
|
||||
}
|
||||
|
||||
n, err := to.write(b[i:end])
|
||||
if err != nil {
|
||||
return n, fmt.Errorf("shadowtls Write failed with %w, i=%d, end=%d, n=%d", err, i, end, n)
|
||||
}
|
||||
}
|
||||
return length, nil
|
||||
}
|
||||
|
||||
func (s *ShadowTls) write(b []byte) (int, error) {
|
||||
var hashVal []byte
|
||||
if s.firstRequest {
|
||||
hashedConn := newHashedStream(s.Conn, s.password)
|
||||
if _, err := s.tlsConnector.StreamConn(hashedConn); err != nil {
|
||||
return 0, fmt.Errorf("tls connect failed with %w", err)
|
||||
}
|
||||
hashVal = hashedConn.hasher.Sum(nil)[:hashLen]
|
||||
s.firstRequest = false
|
||||
}
|
||||
|
||||
buf := pool.GetBuffer()
|
||||
defer pool.PutBuffer(buf)
|
||||
buf.Write([]byte{0x17, 0x03, 0x03})
|
||||
binary.Write(buf, binary.BigEndian, uint16(len(b)+len(hashVal)))
|
||||
buf.Write(hashVal)
|
||||
buf.Write(b)
|
||||
_, err := s.Conn.Write(buf.Bytes())
|
||||
if err != nil {
|
||||
// return 0 because errors occur here make the
|
||||
// whole situation irrecoverable
|
||||
return 0, err
|
||||
}
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
// NewShadowTls return a ShadowTls
|
||||
func NewShadowTls(conn net.Conn, password string, tlsConnector *trojan.Trojan) net.Conn {
|
||||
return &ShadowTls{
|
||||
Conn: conn,
|
||||
password: []byte(password),
|
||||
firstRequest: true,
|
||||
tlsConnector: tlsConnector,
|
||||
}
|
||||
}
|
@ -109,7 +109,12 @@ func (to *TLSObfs) write(b []byte) (int, error) {
|
||||
binary.Write(buf, binary.BigEndian, uint16(len(b)))
|
||||
buf.Write(b)
|
||||
_, err := to.Conn.Write(buf.Bytes())
|
||||
return len(b), err
|
||||
if err != nil {
|
||||
// return 0 because errors occur here make the
|
||||
// whole situation irrecoverable
|
||||
return 0, err
|
||||
}
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
// NewTLSObfs return a SimpleObfs
|
||||
|
@ -8,18 +8,17 @@ import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
"github.com/Dreamacro/clash/transport/vless"
|
||||
"github.com/Dreamacro/clash/transport/vmess"
|
||||
|
||||
xtls "github.com/xtls/go"
|
||||
)
|
||||
|
||||
@ -117,9 +116,6 @@ func (t *Trojan) StreamConn(conn net.Conn) (net.Conn, error) {
|
||||
}
|
||||
|
||||
tlsConn := tls.Client(conn, tlsConfig)
|
||||
if err := tlsConn.Handshake(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// fix tls handshake not timeout
|
||||
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
|
||||
defer cancel()
|
||||
|
Reference in New Issue
Block a user