85f1b22340
Ports in TCP and UDP should be parsed as an unsigned integer,
otherwise ports > 32767 get truncated to 32767. As this is
the case with Metadata.UDPAddr(), this fundamentally breaks
UDP connections where demand for high port numbers is high.
This commit fixes all known cases where ParseInt is used for ports,
and has been verified to fix Discord voice connections on port
50001~50004.
Fixes: d40e5e4fe6
Co-authored-by: Hamster Tian <haotia@gmail.com>
137 lines
3.7 KiB
Go
137 lines
3.7 KiB
Go
package outbound
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"strconv"
|
|
|
|
"github.com/Dreamacro/clash/common/structure"
|
|
"github.com/Dreamacro/clash/component/dialer"
|
|
C "github.com/Dreamacro/clash/constant"
|
|
obfs "github.com/Dreamacro/clash/transport/simple-obfs"
|
|
"github.com/Dreamacro/clash/transport/snell"
|
|
)
|
|
|
|
type Snell struct {
|
|
*Base
|
|
psk []byte
|
|
pool *snell.Pool
|
|
obfsOption *simpleObfsOption
|
|
version int
|
|
}
|
|
|
|
type SnellOption struct {
|
|
BasicOption
|
|
Name string `proxy:"name"`
|
|
Server string `proxy:"server"`
|
|
Port int `proxy:"port"`
|
|
Psk string `proxy:"psk"`
|
|
Version int `proxy:"version,omitempty"`
|
|
ObfsOpts map[string]interface{} `proxy:"obfs-opts,omitempty"`
|
|
}
|
|
|
|
type streamOption struct {
|
|
psk []byte
|
|
version int
|
|
addr string
|
|
obfsOption *simpleObfsOption
|
|
}
|
|
|
|
func streamConn(c net.Conn, option streamOption) *snell.Snell {
|
|
switch option.obfsOption.Mode {
|
|
case "tls":
|
|
c = obfs.NewTLSObfs(c, option.obfsOption.Host)
|
|
case "http":
|
|
_, port, _ := net.SplitHostPort(option.addr)
|
|
c = obfs.NewHTTPObfs(c, option.obfsOption.Host, port)
|
|
}
|
|
return snell.StreamConn(c, option.psk, option.version)
|
|
}
|
|
|
|
// StreamConn implements C.ProxyAdapter
|
|
func (s *Snell) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|
c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
|
|
port, _ := strconv.ParseUint(metadata.DstPort, 10, 16)
|
|
err := snell.WriteHeader(c, metadata.String(), uint(port), s.version)
|
|
return c, err
|
|
}
|
|
|
|
// DialContext implements C.ProxyAdapter
|
|
func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
|
if s.version == snell.Version2 && len(opts) == 0 {
|
|
c, err := s.pool.Get()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
port, _ := strconv.ParseUint(metadata.DstPort, 10, 16)
|
|
if err = snell.WriteHeader(c, metadata.String(), uint(port), s.version); err != nil {
|
|
c.Close()
|
|
return nil, err
|
|
}
|
|
return NewConn(c, s), err
|
|
}
|
|
|
|
c, err := dialer.DialContext(ctx, "tcp", s.addr, s.Base.DialOptions(opts...)...)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%s connect error: %w", s.addr, err)
|
|
}
|
|
tcpKeepAlive(c)
|
|
|
|
defer safeConnClose(c, err)
|
|
|
|
c, err = s.StreamConn(c, metadata)
|
|
return NewConn(c, s), err
|
|
}
|
|
|
|
func NewSnell(option SnellOption) (*Snell, error) {
|
|
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
|
psk := []byte(option.Psk)
|
|
|
|
decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true})
|
|
obfsOption := &simpleObfsOption{Host: "bing.com"}
|
|
if err := decoder.Decode(option.ObfsOpts, obfsOption); err != nil {
|
|
return nil, fmt.Errorf("snell %s initialize obfs error: %w", addr, err)
|
|
}
|
|
|
|
switch obfsOption.Mode {
|
|
case "tls", "http", "":
|
|
break
|
|
default:
|
|
return nil, fmt.Errorf("snell %s obfs mode error: %s", addr, obfsOption.Mode)
|
|
}
|
|
|
|
// backward compatible
|
|
if option.Version == 0 {
|
|
option.Version = snell.DefaultSnellVersion
|
|
}
|
|
if option.Version != snell.Version1 && option.Version != snell.Version2 {
|
|
return nil, fmt.Errorf("snell version error: %d", option.Version)
|
|
}
|
|
|
|
s := &Snell{
|
|
Base: &Base{
|
|
name: option.Name,
|
|
addr: addr,
|
|
tp: C.Snell,
|
|
iface: option.Interface,
|
|
},
|
|
psk: psk,
|
|
obfsOption: obfsOption,
|
|
version: option.Version,
|
|
}
|
|
|
|
if option.Version == snell.Version2 {
|
|
s.pool = snell.NewPool(func(ctx context.Context) (*snell.Snell, error) {
|
|
c, err := dialer.DialContext(ctx, "tcp", addr, s.Base.DialOptions()...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
tcpKeepAlive(c)
|
|
return streamConn(c, streamOption{psk, option.Version, addr, obfsOption}), nil
|
|
})
|
|
}
|
|
return s, nil
|
|
}
|