253 lines
6.6 KiB
Go
253 lines
6.6 KiB
Go
package outbound
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"strconv"
|
|
|
|
N "github.com/Dreamacro/clash/common/net"
|
|
"github.com/Dreamacro/clash/component/dialer"
|
|
"github.com/Dreamacro/clash/component/proxydialer"
|
|
C "github.com/Dreamacro/clash/constant"
|
|
"github.com/Dreamacro/clash/transport/shadowsocks/core"
|
|
"github.com/Dreamacro/clash/transport/shadowsocks/shadowaead"
|
|
"github.com/Dreamacro/clash/transport/shadowsocks/shadowstream"
|
|
"github.com/Dreamacro/clash/transport/socks5"
|
|
"github.com/Dreamacro/clash/transport/ssr/obfs"
|
|
"github.com/Dreamacro/clash/transport/ssr/protocol"
|
|
)
|
|
|
|
type ShadowSocksR struct {
|
|
*Base
|
|
option *ShadowSocksROption
|
|
cipher core.Cipher
|
|
obfs obfs.Obfs
|
|
protocol protocol.Protocol
|
|
}
|
|
|
|
type ShadowSocksROption struct {
|
|
BasicOption
|
|
Name string `proxy:"name"`
|
|
Server string `proxy:"server"`
|
|
Port int `proxy:"port"`
|
|
Password string `proxy:"password"`
|
|
Cipher string `proxy:"cipher"`
|
|
Obfs string `proxy:"obfs"`
|
|
ObfsParam string `proxy:"obfs-param,omitempty"`
|
|
Protocol string `proxy:"protocol"`
|
|
ProtocolParam string `proxy:"protocol-param,omitempty"`
|
|
UDP bool `proxy:"udp,omitempty"`
|
|
}
|
|
|
|
// StreamConnContext implements C.ProxyAdapter
|
|
func (ssr *ShadowSocksR) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|
c = ssr.obfs.StreamConn(c)
|
|
c = ssr.cipher.StreamConn(c)
|
|
var (
|
|
iv []byte
|
|
err error
|
|
)
|
|
switch conn := c.(type) {
|
|
case *shadowstream.Conn:
|
|
iv, err = conn.ObtainWriteIV()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
case *shadowaead.Conn:
|
|
return nil, fmt.Errorf("invalid connection type")
|
|
}
|
|
c = ssr.protocol.StreamConn(c, iv)
|
|
_, err = c.Write(serializesSocksAddr(metadata))
|
|
return c, err
|
|
}
|
|
|
|
// DialContext implements C.ProxyAdapter
|
|
func (ssr *ShadowSocksR) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
|
return ssr.DialContextWithDialer(ctx, dialer.NewDialer(ssr.Base.DialOptions(opts...)...), metadata)
|
|
}
|
|
|
|
// DialContextWithDialer implements C.ProxyAdapter
|
|
func (ssr *ShadowSocksR) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
|
|
if len(ssr.option.DialerProxy) > 0 {
|
|
dialer, err = proxydialer.NewByName(ssr.option.DialerProxy, dialer)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
c, err := dialer.DialContext(ctx, "tcp", ssr.addr)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%s connect error: %w", ssr.addr, err)
|
|
}
|
|
tcpKeepAlive(c)
|
|
|
|
defer func(c net.Conn) {
|
|
safeConnClose(c, err)
|
|
}(c)
|
|
|
|
c, err = ssr.StreamConnContext(ctx, c, metadata)
|
|
return NewConn(c, ssr), err
|
|
}
|
|
|
|
// ListenPacketContext implements C.ProxyAdapter
|
|
func (ssr *ShadowSocksR) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
|
return ssr.ListenPacketWithDialer(ctx, dialer.NewDialer(ssr.Base.DialOptions(opts...)...), metadata)
|
|
}
|
|
|
|
// ListenPacketWithDialer implements C.ProxyAdapter
|
|
func (ssr *ShadowSocksR) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
|
if len(ssr.option.DialerProxy) > 0 {
|
|
dialer, err = proxydialer.NewByName(ssr.option.DialerProxy, dialer)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
addr, err := resolveUDPAddrWithPrefer(ctx, "udp", ssr.addr, ssr.prefer)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pc, err := dialer.ListenPacket(ctx, "udp", "", addr.AddrPort())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
epc := ssr.cipher.PacketConn(N.NewEnhancePacketConn(pc))
|
|
epc = ssr.protocol.PacketConn(epc)
|
|
return newPacketConn(&ssrPacketConn{EnhancePacketConn: epc, rAddr: addr}, ssr), nil
|
|
}
|
|
|
|
// SupportWithDialer implements C.ProxyAdapter
|
|
func (ssr *ShadowSocksR) SupportWithDialer() C.NetWork {
|
|
return C.ALLNet
|
|
}
|
|
|
|
func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
|
|
// SSR protocol compatibility
|
|
// https://github.com/Dreamacro/clash/pull/2056
|
|
if option.Cipher == "none" {
|
|
option.Cipher = "dummy"
|
|
}
|
|
|
|
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
|
cipher := option.Cipher
|
|
password := option.Password
|
|
coreCiph, err := core.PickCipher(cipher, nil, password)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("ssr %s initialize error: %w", addr, err)
|
|
}
|
|
var (
|
|
ivSize int
|
|
key []byte
|
|
)
|
|
|
|
if option.Cipher == "dummy" {
|
|
ivSize = 0
|
|
key = core.Kdf(option.Password, 16)
|
|
} else {
|
|
ciph, ok := coreCiph.(*core.StreamCipher)
|
|
if !ok {
|
|
return nil, fmt.Errorf("%s is not none or a supported stream cipher in ssr", cipher)
|
|
}
|
|
ivSize = ciph.IVSize()
|
|
key = ciph.Key
|
|
}
|
|
|
|
obfs, obfsOverhead, err := obfs.PickObfs(option.Obfs, &obfs.Base{
|
|
Host: option.Server,
|
|
Port: option.Port,
|
|
Key: key,
|
|
IVSize: ivSize,
|
|
Param: option.ObfsParam,
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("ssr %s initialize obfs error: %w", addr, err)
|
|
}
|
|
|
|
protocol, err := protocol.PickProtocol(option.Protocol, &protocol.Base{
|
|
Key: key,
|
|
Overhead: obfsOverhead,
|
|
Param: option.ProtocolParam,
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("ssr %s initialize protocol error: %w", addr, err)
|
|
}
|
|
|
|
return &ShadowSocksR{
|
|
Base: &Base{
|
|
name: option.Name,
|
|
addr: addr,
|
|
tp: C.ShadowsocksR,
|
|
udp: option.UDP,
|
|
tfo: option.TFO,
|
|
mpTcp: option.MPTCP,
|
|
iface: option.Interface,
|
|
rmark: option.RoutingMark,
|
|
prefer: C.NewDNSPrefer(option.IPVersion),
|
|
},
|
|
option: &option,
|
|
cipher: coreCiph,
|
|
obfs: obfs,
|
|
protocol: protocol,
|
|
}, nil
|
|
}
|
|
|
|
type ssrPacketConn struct {
|
|
N.EnhancePacketConn
|
|
rAddr net.Addr
|
|
}
|
|
|
|
func (spc *ssrPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
|
|
packet, err := socks5.EncodeUDPPacket(socks5.ParseAddrToSocksAddr(addr), b)
|
|
if err != nil {
|
|
return
|
|
}
|
|
return spc.EnhancePacketConn.WriteTo(packet[3:], spc.rAddr)
|
|
}
|
|
|
|
func (spc *ssrPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
|
n, _, e := spc.EnhancePacketConn.ReadFrom(b)
|
|
if e != nil {
|
|
return 0, nil, e
|
|
}
|
|
|
|
addr := socks5.SplitAddr(b[:n])
|
|
if addr == nil {
|
|
return 0, nil, errors.New("parse addr error")
|
|
}
|
|
|
|
udpAddr := addr.UDPAddr()
|
|
if udpAddr == nil {
|
|
return 0, nil, errors.New("parse addr error")
|
|
}
|
|
|
|
copy(b, b[len(addr):])
|
|
return n - len(addr), udpAddr, e
|
|
}
|
|
|
|
func (spc *ssrPacketConn) WaitReadFrom() (data []byte, put func(), addr net.Addr, err error) {
|
|
data, put, _, err = spc.EnhancePacketConn.WaitReadFrom()
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
|
|
_addr := socks5.SplitAddr(data)
|
|
if _addr == nil {
|
|
if put != nil {
|
|
put()
|
|
}
|
|
return nil, nil, nil, errors.New("parse addr error")
|
|
}
|
|
|
|
addr = _addr.UDPAddr()
|
|
if addr == nil {
|
|
if put != nil {
|
|
put()
|
|
}
|
|
return nil, nil, nil, errors.New("parse addr error")
|
|
}
|
|
|
|
data = data[len(_addr):]
|
|
return
|
|
}
|