Merge from remote branch

This commit is contained in:
yaling888 2022-01-18 09:51:20 +08:00
commit daae846db3
9 changed files with 250 additions and 9 deletions

View file

@ -27,6 +27,7 @@ type SnellOption struct {
Server string `proxy:"server"` Server string `proxy:"server"`
Port int `proxy:"port"` Port int `proxy:"port"`
Psk string `proxy:"psk"` Psk string `proxy:"psk"`
UDP bool `proxy:"udp,omitempty"`
Version int `proxy:"version,omitempty"` Version int `proxy:"version,omitempty"`
ObfsOpts map[string]interface{} `proxy:"obfs-opts,omitempty"` ObfsOpts map[string]interface{} `proxy:"obfs-opts,omitempty"`
} }
@ -85,6 +86,24 @@ func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
return NewConn(c, s), err return NewConn(c, s), err
} }
// ListenPacketContext implements C.ProxyAdapter
func (s *Snell) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
c, err := dialer.DialContext(ctx, "tcp", s.addr, s.Base.DialOptions(opts...)...)
if err != nil {
return nil, err
}
tcpKeepAlive(c)
c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
err = snell.WriteUDPHeader(c, s.version)
if err != nil {
return nil, err
}
pc := snell.PacketConn(c)
return newPacketConn(pc, s), nil
}
func NewSnell(option SnellOption) (*Snell, error) { func NewSnell(option SnellOption) (*Snell, error) {
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
psk := []byte(option.Psk) psk := []byte(option.Psk)
@ -106,7 +125,13 @@ func NewSnell(option SnellOption) (*Snell, error) {
if option.Version == 0 { if option.Version == 0 {
option.Version = snell.DefaultSnellVersion option.Version = snell.DefaultSnellVersion
} }
if option.Version != snell.Version1 && option.Version != snell.Version2 { switch option.Version {
case snell.Version1, snell.Version2:
if option.UDP {
return nil, fmt.Errorf("snell version %d not support UDP", option.Version)
}
case snell.Version3:
default:
return nil, fmt.Errorf("snell version error: %d", option.Version) return nil, fmt.Errorf("snell version error: %d", option.Version)
} }
@ -115,6 +140,7 @@ func NewSnell(option SnellOption) (*Snell, error) {
name: option.Name, name: option.Name,
addr: addr, addr: addr,
tp: C.Snell, tp: C.Snell,
udp: option.UDP,
iface: option.Interface, iface: option.Interface,
}, },
psk: psk, psk: psk,

View file

@ -33,6 +33,7 @@ const (
const ( const (
DefaultTCPTimeout = 5 * time.Second DefaultTCPTimeout = 5 * time.Second
DefaultUDPTimeout = DefaultTCPTimeout DefaultUDPTimeout = DefaultTCPTimeout
DefaultTLSTimeout = DefaultTCPTimeout
) )
type Connection interface { type Connection interface {

View file

@ -60,6 +60,10 @@ func ReCreateServer(addr string, resolver *Resolver, mapper *ResolverEnhancer) {
address = "" address = ""
} }
if addr == "" {
return
}
var err error var err error
defer func() { defer func() {
if err != nil { if err != nil {

View file

@ -32,7 +32,7 @@ const (
ImageVmess = "v2fly/v2fly-core:latest" ImageVmess = "v2fly/v2fly-core:latest"
ImageTrojan = "trojangfw/trojan:latest" ImageTrojan = "trojangfw/trojan:latest"
ImageTrojanGo = "p4gefau1t/trojan-go:latest" ImageTrojanGo = "p4gefau1t/trojan-go:latest"
ImageSnell = "icpz/snell-server:latest" ImageSnell = "ghcr.io/icpz/snell-server:latest"
ImageXray = "teddysun/xray:latest" ImageXray = "teddysun/xray:latest"
) )

View file

@ -120,6 +120,42 @@ func TestClash_Snell(t *testing.T) {
testSuit(t, proxy) testSuit(t, proxy)
} }
func TestClash_Snellv3(t *testing.T) {
cfg := &container.Config{
Image: ImageSnell,
ExposedPorts: defaultExposedPorts,
Cmd: []string{"-c", "/config.conf"},
}
hostCfg := &container.HostConfig{
PortBindings: defaultPortBindings,
Binds: []string{fmt.Sprintf("%s:/config.conf", C.Path.Resolve("snell.conf"))},
}
id, err := startContainer(cfg, hostCfg, "snell")
if err != nil {
assert.FailNow(t, err.Error())
}
t.Cleanup(func() {
cleanContainer(id)
})
proxy, err := outbound.NewSnell(outbound.SnellOption{
Name: "snell",
Server: localIP.String(),
Port: 10002,
Psk: "password",
UDP: true,
Version: 3,
})
if err != nil {
assert.FailNow(t, err.Error())
}
time.Sleep(waitTime)
testSuit(t, proxy)
}
func Benchmark_Snell(b *testing.B) { func Benchmark_Snell(b *testing.B) {
cfg := &container.Config{ cfg := &container.Config{
Image: ImageSnell, Image: ImageSnell,

View file

@ -5,6 +5,7 @@ package gun
import ( import (
"bufio" "bufio"
"context"
"crypto/tls" "crypto/tls"
"encoding/binary" "encoding/binary"
"errors" "errors"
@ -17,6 +18,7 @@ import (
"time" "time"
"github.com/Dreamacro/clash/common/pool" "github.com/Dreamacro/clash/common/pool"
C "github.com/Dreamacro/clash/constant"
"go.uber.org/atomic" "go.uber.org/atomic"
"golang.org/x/net/http2" "golang.org/x/net/http2"
@ -173,7 +175,11 @@ func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config) *http2.Transport {
} }
cn := tls.Client(pconn, cfg) cn := tls.Client(pconn, cfg)
if err := cn.Handshake(); err != nil {
// fix tls handshake not timeout
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
defer cancel()
if err := cn.HandshakeContext(ctx); err != nil {
pconn.Close() pconn.Close()
return nil, err return nil, err
} }

View file

@ -6,8 +6,10 @@ import (
"fmt" "fmt"
"io" "io"
"net" "net"
"sync"
"github.com/Dreamacro/clash/common/pool" "github.com/Dreamacro/clash/common/pool"
"github.com/Dreamacro/clash/transport/socks5"
"github.com/Dreamacro/go-shadowsocks2/shadowaead" "github.com/Dreamacro/go-shadowsocks2/shadowaead"
) )
@ -15,13 +17,19 @@ import (
const ( const (
Version1 = 1 Version1 = 1
Version2 = 2 Version2 = 2
Version3 = 3
DefaultSnellVersion = Version1 DefaultSnellVersion = Version1
// max packet length
maxLength = 0x3FFF
) )
const ( const (
CommandPing byte = 0 CommandPing byte = 0
CommandConnect byte = 1 CommandConnect byte = 1
CommandConnectV2 byte = 5 CommandConnectV2 byte = 5
CommandUDP byte = 6
CommondUDPForward byte = 1
CommandTunnel byte = 0 CommandTunnel byte = 0
CommandPong byte = 1 CommandPong byte = 1
@ -100,6 +108,16 @@ func WriteHeader(conn net.Conn, host string, port uint, version int) error {
return nil return nil
} }
func WriteUDPHeader(conn net.Conn, version int) error {
if version < Version3 {
return errors.New("unsupport UDP version")
}
// version, command, clientID length
_, err := conn.Write([]byte{Version, CommandUDP, 0x00})
return err
}
// HalfClose works only on version2 // HalfClose works only on version2
func HalfClose(conn net.Conn) error { func HalfClose(conn net.Conn) error {
if _, err := conn.Write(endSignal); err != nil { if _, err := conn.Write(endSignal); err != nil {
@ -114,10 +132,147 @@ func HalfClose(conn net.Conn) error {
func StreamConn(conn net.Conn, psk []byte, version int) *Snell { func StreamConn(conn net.Conn, psk []byte, version int) *Snell {
var cipher shadowaead.Cipher var cipher shadowaead.Cipher
if version == Version2 { if version != Version1 {
cipher = NewAES128GCM(psk) cipher = NewAES128GCM(psk)
} else { } else {
cipher = NewChacha20Poly1305(psk) cipher = NewChacha20Poly1305(psk)
} }
return &Snell{Conn: shadowaead.NewConn(conn, cipher)} return &Snell{Conn: shadowaead.NewConn(conn, cipher)}
} }
func PacketConn(conn net.Conn) net.PacketConn {
return &packetConn{
Conn: conn,
}
}
func writePacket(w io.Writer, socks5Addr, payload []byte) (int, error) {
buf := pool.GetBuffer()
defer pool.PutBuffer(buf)
// compose snell UDP address format (refer: icpz/snell-server-reversed)
// a brand new wheel to replace socks5 address format, well done Yachen
buf.WriteByte(CommondUDPForward)
switch socks5Addr[0] {
case socks5.AtypDomainName:
hostLen := socks5Addr[1]
buf.Write(socks5Addr[1 : 1+1+hostLen+2])
case socks5.AtypIPv4:
buf.Write([]byte{0x00, 0x04})
buf.Write(socks5Addr[1 : 1+net.IPv4len+2])
case socks5.AtypIPv6:
buf.Write([]byte{0x00, 0x06})
buf.Write(socks5Addr[1 : 1+net.IPv6len+2])
}
buf.Write(payload)
_, err := w.Write(buf.Bytes())
if err != nil {
return 0, err
}
return len(payload), nil
}
func WritePacket(w io.Writer, socks5Addr, payload []byte) (int, error) {
if len(payload) <= maxLength {
return writePacket(w, socks5Addr, payload)
}
offset := 0
total := len(payload)
for {
cursor := offset + maxLength
if cursor > total {
cursor = total
}
n, err := writePacket(w, socks5Addr, payload[offset:cursor])
if err != nil {
return offset + n, err
}
offset = cursor
if offset == total {
break
}
}
return total, nil
}
func ReadPacket(r io.Reader, payload []byte) (net.Addr, int, error) {
buf := pool.Get(pool.UDPBufferSize)
defer pool.Put(buf)
n, err := r.Read(buf)
headLen := 1
if err != nil {
return nil, 0, err
}
if n < headLen {
return nil, 0, errors.New("insufficient UDP length")
}
// parse snell UDP response address format
switch buf[0] {
case 0x04:
headLen += net.IPv4len + 2
if n < headLen {
err = errors.New("insufficient UDP length")
break
}
buf[0] = socks5.AtypIPv4
case 0x06:
headLen += net.IPv6len + 2
if n < headLen {
err = errors.New("insufficient UDP length")
break
}
buf[0] = socks5.AtypIPv6
default:
err = errors.New("ip version invalid")
}
if err != nil {
return nil, 0, err
}
addr := socks5.SplitAddr(buf[0:])
if addr == nil {
return nil, 0, errors.New("remote address invalid")
}
uAddr := addr.UDPAddr()
length := len(payload)
if n-headLen < length {
length = n - headLen
}
copy(payload[:], buf[headLen:headLen+length])
return uAddr, length, nil
}
type packetConn struct {
net.Conn
rMux sync.Mutex
wMux sync.Mutex
}
func (pc *packetConn) WriteTo(b []byte, addr net.Addr) (int, error) {
pc.wMux.Lock()
defer pc.wMux.Unlock()
return WritePacket(pc, socks5.ParseAddr(addr.String()), b)
}
func (pc *packetConn) ReadFrom(b []byte) (int, net.Addr, error) {
pc.rMux.Lock()
defer pc.rMux.Unlock()
addr, n, err := ReadPacket(pc.Conn, b)
if err != nil {
return 0, nil, err
}
return n, addr, nil
}

View file

@ -1,6 +1,7 @@
package trojan package trojan
import ( import (
"context"
"crypto/sha256" "crypto/sha256"
"crypto/tls" "crypto/tls"
"encoding/binary" "encoding/binary"
@ -12,6 +13,7 @@ import (
"sync" "sync"
"github.com/Dreamacro/clash/common/pool" "github.com/Dreamacro/clash/common/pool"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/socks5" "github.com/Dreamacro/clash/transport/socks5"
"github.com/Dreamacro/clash/transport/vmess" "github.com/Dreamacro/clash/transport/vmess"
) )
@ -68,7 +70,11 @@ 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 {
// fix tls handshake not timeout
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
defer cancel()
if err := tlsConn.HandshakeContext(ctx); err != nil {
return nil, err return nil, err
} }

View file

@ -1,8 +1,11 @@
package vmess package vmess
import ( import (
"context"
"crypto/tls" "crypto/tls"
"net" "net"
C "github.com/Dreamacro/clash/constant"
) )
type TLSConfig struct { type TLSConfig struct {
@ -19,6 +22,10 @@ func StreamTLSConn(conn net.Conn, cfg *TLSConfig) (net.Conn, error) {
} }
tlsConn := tls.Client(conn, tlsConfig) tlsConn := tls.Client(conn, tlsConfig)
err := tlsConn.Handshake()
// fix tls handshake not timeout
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
defer cancel()
err := tlsConn.HandshakeContext(ctx)
return tlsConn, err return tlsConn, err
} }