176 lines
4.7 KiB
Go
176 lines
4.7 KiB
Go
package outbound
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"strconv"
|
|
|
|
"github.com/Dreamacro/clash/component/dialer"
|
|
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/ssr/obfs"
|
|
"github.com/Dreamacro/clash/transport/ssr/protocol"
|
|
)
|
|
|
|
type ShadowSocksR struct {
|
|
*Base
|
|
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"`
|
|
}
|
|
|
|
// StreamConn implements C.ProxyAdapter
|
|
func (ssr *ShadowSocksR) StreamConn(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) {
|
|
c, err := dialer.DialContext(ctx, "tcp", ssr.addr, ssr.Base.DialOptions(opts...)...)
|
|
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.StreamConn(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) {
|
|
addr, err := resolveUDPAddrWithPrefer(ctx, "udp", ssr.addr, ssr.prefer)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pc, err := dialer.ListenPacket(ctx, dialer.ParseNetwork("udp", addr.AddrPort().Addr()), "", ssr.Base.DialOptions(opts...)...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pc = ssr.cipher.PacketConn(pc)
|
|
pc = ssr.protocol.PacketConn(pc)
|
|
return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ssr), nil
|
|
}
|
|
|
|
// ListenPacketOnPacketConn implements C.ProxyAdapter
|
|
func (ssr *ShadowSocksR) ListenPacketOnPacketConn(ctx context.Context, c C.PacketConn, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
|
addr, err := resolveUDPAddrWithPrefer(ctx, "udp", ssr.addr, ssr.prefer)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pc := ssr.cipher.PacketConn(c)
|
|
pc = ssr.protocol.PacketConn(pc)
|
|
return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ssr), nil
|
|
}
|
|
|
|
// SupportLPPC implements C.ProxyAdapter
|
|
func (ssr *ShadowSocksR) SupportLPPC() bool {
|
|
return true
|
|
}
|
|
|
|
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,
|
|
iface: option.Interface,
|
|
rmark: option.RoutingMark,
|
|
prefer: C.NewDNSPrefer(option.IPVersion),
|
|
},
|
|
cipher: coreCiph,
|
|
obfs: obfs,
|
|
protocol: protocol,
|
|
}, nil
|
|
}
|