feat: Support ShadowTLS v2 as Shadowsocks plugin (#330)

This commit is contained in:
3andero 2023-01-10 08:10:39 -08:00 committed by metacubex
parent 337be9124f
commit 51f9b34a7c
7 changed files with 206 additions and 20 deletions

View file

@ -36,14 +36,16 @@ jobs:
run: make -j$(($(nproc) + 1)) releases run: make -j$(($(nproc) + 1)) releases
- name: Delete current release assets - name: Delete current release assets
uses: andreaswilli/delete-release-assets-action@v2.0.0 uses: mknejp/delete-release-assets@v1
with: with:
github_token: ${{ secrets.GITHUB_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }}
tag: Prerelease-${{ github.ref_name }} tag_name: Prerelease-${{ github.ref_name }}
deleteOnlyFromDrafts: false assets: |
*.zip
*.gz
- name: Tag Repo - name: Tag Repo
uses: richardsimko/update-tag@v1 uses: richardsimko/update-tag@v1.0.6
with: with:
tag_name: Prerelease-${{ github.ref_name }} tag_name: Prerelease-${{ github.ref_name }}
env: env:
@ -53,7 +55,6 @@ jobs:
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v1
if: ${{ success() }} if: ${{ success() }}
with: with:
tag: ${{ github.ref_name }}
tag_name: Prerelease-${{ github.ref_name }} tag_name: Prerelease-${{ github.ref_name }}
files: bin/* files: bin/*
prerelease: true prerelease: true

View file

@ -34,6 +34,26 @@ Documentations are now moved to [GitHub Wiki](https://github.com/Dreamacro/clash
## Advanced usage for this branch ## Advanced usage for this branch
## Build
You should install [golang](https://go.dev) first.
Then get the source code of Clash.Meta:
```shell
git clone https://github.com/MetaCubeX/Clash.Meta.git
cd Clash.Meta && go mod download
```
If you can't visit github,you should set proxy first:
```shell
go env -w GOPROXY=https://goproxy.io,direct
```
So now you can build it:
```shell
go build
```
### DNS configuration ### DNS configuration
Support `geosite` with `fallback-filter`. Support `geosite` with `fallback-filter`.

View file

@ -10,11 +10,13 @@ import (
"github.com/Dreamacro/clash/common/structure" "github.com/Dreamacro/clash/common/structure"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/shadowtls"
obfs "github.com/Dreamacro/clash/transport/simple-obfs" obfs "github.com/Dreamacro/clash/transport/simple-obfs"
"github.com/Dreamacro/clash/transport/socks5" "github.com/Dreamacro/clash/transport/socks5"
"github.com/Dreamacro/clash/transport/trojan"
v2rayObfs "github.com/Dreamacro/clash/transport/v2ray-plugin" v2rayObfs "github.com/Dreamacro/clash/transport/v2ray-plugin"
"github.com/metacubex/sing-shadowsocks" shadowsocks "github.com/metacubex/sing-shadowsocks"
"github.com/metacubex/sing-shadowsocks/shadowimpl" "github.com/metacubex/sing-shadowsocks/shadowimpl"
"github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/bufio"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
@ -27,9 +29,11 @@ type ShadowSocks struct {
option *ShadowSocksOption option *ShadowSocksOption
// obfs // obfs
obfsMode string obfsMode string
obfsOption *simpleObfsOption obfsOption *simpleObfsOption
v2rayOption *v2rayObfs.Option v2rayOption *v2rayObfs.Option
shadowTLSOption *shadowTLSOption
tlsConnector *trojan.Trojan
} }
type ShadowSocksOption struct { type ShadowSocksOption struct {
@ -61,6 +65,11 @@ type v2rayObfsOption struct {
Mux bool `obfs:"mux,omitempty"` Mux bool `obfs:"mux,omitempty"`
} }
type shadowTLSOption struct {
Password string `obfs:"password"`
Host string `obfs:"host"`
}
// StreamConn implements C.ProxyAdapter // StreamConn implements C.ProxyAdapter
func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
switch ss.obfsMode { switch ss.obfsMode {
@ -75,6 +84,11 @@ func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, e
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
} }
case shadowtls.Mode:
if ss.tlsConnector == nil {
ss.tlsConnector = trojan.New(&trojan.Option{ServerName: ss.shadowTLSOption.Host, SkipCertVerify: false})
}
c = shadowtls.NewShadowTls(c, ss.shadowTLSOption.Password, ss.tlsConnector)
} }
if metadata.NetWork == C.UDP && ss.option.UDPOverTCP { if metadata.NetWork == C.UDP && ss.option.UDPOverTCP {
return ss.method.DialConn(c, M.ParseSocksaddr(uot.UOTMagicAddress+":443")) return ss.method.DialConn(c, M.ParseSocksaddr(uot.UOTMagicAddress+":443"))
@ -157,6 +171,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
var v2rayOption *v2rayObfs.Option var v2rayOption *v2rayObfs.Option
var obfsOption *simpleObfsOption var obfsOption *simpleObfsOption
var shadowTLSOpt *shadowTLSOption
obfsMode := "" obfsMode := ""
decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true}) decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true})
@ -192,6 +207,12 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
v2rayOption.TLS = true v2rayOption.TLS = true
v2rayOption.SkipCertVerify = opts.SkipCertVerify v2rayOption.SkipCertVerify = opts.SkipCertVerify
} }
} else if option.Plugin == shadowtls.Mode {
obfsMode = shadowtls.Mode
shadowTLSOpt = &shadowTLSOption{}
if err := decoder.Decode(option.PluginOpts, shadowTLSOpt); err != nil {
return nil, fmt.Errorf("ss %s initialize shadow-tls-plugin error: %w", addr, err)
}
} }
return &ShadowSocks{ return &ShadowSocks{
@ -206,10 +227,11 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
}, },
method: method, method: method,
option: &option, option: &option,
obfsMode: obfsMode, obfsMode: obfsMode,
v2rayOption: v2rayOption, v2rayOption: v2rayOption,
obfsOption: obfsOption, obfsOption: obfsOption,
shadowTLSOption: shadowTLSOpt,
}, nil }, nil
} }

View file

@ -28,7 +28,7 @@
inherit version; inherit version;
src = ./.; src = ./.;
vendorSha256 = "sha256-XVz2vts4on42lfxnov4jnUrHzSFF05+i1TVY3C7bgdw="; vendorSha256 = "sha256-8cbcE9gKJjU14DNTLPc6nneEPZg7Akt+FlSDlPRvG5k=";
# Do not build testing suit # Do not build testing suit
excludedPackages = [ "./test" ]; excludedPackages = [ "./test" ];

View 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,
}
}

View file

@ -109,7 +109,12 @@ func (to *TLSObfs) write(b []byte) (int, error) {
binary.Write(buf, binary.BigEndian, uint16(len(b))) binary.Write(buf, binary.BigEndian, uint16(len(b)))
buf.Write(b) buf.Write(b)
_, err := to.Conn.Write(buf.Bytes()) _, 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 // NewTLSObfs return a SimpleObfs

View file

@ -8,18 +8,17 @@ import (
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt" "fmt"
tlsC "github.com/Dreamacro/clash/component/tls"
"io" "io"
"net" "net"
"net/http" "net/http"
"sync" "sync"
"github.com/Dreamacro/clash/common/pool" "github.com/Dreamacro/clash/common/pool"
tlsC "github.com/Dreamacro/clash/component/tls"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/socks5" "github.com/Dreamacro/clash/transport/socks5"
"github.com/Dreamacro/clash/transport/vless" "github.com/Dreamacro/clash/transport/vless"
"github.com/Dreamacro/clash/transport/vmess" "github.com/Dreamacro/clash/transport/vmess"
xtls "github.com/xtls/go" xtls "github.com/xtls/go"
) )
@ -117,9 +116,6 @@ func (t *Trojan) StreamConn(conn net.Conn) (net.Conn, error) {
} }
tlsConn := tls.Client(conn, tlsConfig) tlsConn := tls.Client(conn, tlsConfig)
if err := tlsConn.Handshake(); err != nil {
return nil, err
}
// fix tls handshake not timeout // fix tls handshake not timeout
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout) ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
defer cancel() defer cancel()