github.com/igoogolx/clash@v1.19.8/adapter/outbound/shadowsocksr.go (about)

     1  package outbound
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net"
     7  	"strconv"
     8  
     9  	"github.com/igoogolx/clash/component/dialer"
    10  	C "github.com/igoogolx/clash/constant"
    11  	"github.com/igoogolx/clash/transport/shadowsocks/core"
    12  	"github.com/igoogolx/clash/transport/shadowsocks/shadowaead"
    13  	"github.com/igoogolx/clash/transport/shadowsocks/shadowstream"
    14  	"github.com/igoogolx/clash/transport/ssr/obfs"
    15  	"github.com/igoogolx/clash/transport/ssr/protocol"
    16  )
    17  
    18  type ShadowSocksR struct {
    19  	*Base
    20  	cipher   core.Cipher
    21  	obfs     obfs.Obfs
    22  	protocol protocol.Protocol
    23  }
    24  
    25  type ShadowSocksROption struct {
    26  	BasicOption
    27  	Name          string `proxy:"name"`
    28  	Server        string `proxy:"server"`
    29  	Port          int    `proxy:"port"`
    30  	Password      string `proxy:"password"`
    31  	Cipher        string `proxy:"cipher"`
    32  	Obfs          string `proxy:"obfs"`
    33  	ObfsParam     string `proxy:"obfs-param,omitempty"`
    34  	Protocol      string `proxy:"protocol"`
    35  	ProtocolParam string `proxy:"protocol-param,omitempty"`
    36  	UDP           bool   `proxy:"udp,omitempty"`
    37  }
    38  
    39  // StreamConn implements C.ProxyAdapter
    40  func (ssr *ShadowSocksR) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
    41  	c = ssr.obfs.StreamConn(c)
    42  	c = ssr.cipher.StreamConn(c)
    43  	var (
    44  		iv  []byte
    45  		err error
    46  	)
    47  	switch conn := c.(type) {
    48  	case *shadowstream.Conn:
    49  		iv, err = conn.ObtainWriteIV()
    50  		if err != nil {
    51  			return nil, err
    52  		}
    53  	case *shadowaead.Conn:
    54  		return nil, fmt.Errorf("invalid connection type")
    55  	}
    56  	c = ssr.protocol.StreamConn(c, iv)
    57  	_, err = c.Write(serializesSocksAddr(metadata))
    58  	return c, err
    59  }
    60  
    61  // DialContext implements C.ProxyAdapter
    62  func (ssr *ShadowSocksR) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
    63  	c, err := dialer.DialContext(ctx, "tcp", ssr.addr, ssr.Base.DialOptions(opts...)...)
    64  	if err != nil {
    65  		return nil, fmt.Errorf("%s connect error: %w", ssr.addr, err)
    66  	}
    67  	tcpKeepAlive(c)
    68  
    69  	defer func(c net.Conn) {
    70  		safeConnClose(c, err)
    71  	}(c)
    72  
    73  	c, err = ssr.StreamConn(c, metadata)
    74  	return NewConn(c, ssr), err
    75  }
    76  
    77  // ListenPacketContext implements C.ProxyAdapter
    78  func (ssr *ShadowSocksR) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
    79  	pc, err := dialer.ListenPacket(ctx, "udp", "", ssr.Base.DialOptions(opts...)...)
    80  	if err != nil {
    81  		return nil, err
    82  	}
    83  
    84  	addr, err := resolveUDPAddr("udp", ssr.addr)
    85  	if err != nil {
    86  		pc.Close()
    87  		return nil, err
    88  	}
    89  
    90  	pc = ssr.cipher.PacketConn(pc)
    91  	pc = ssr.protocol.PacketConn(pc)
    92  	return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ssr), nil
    93  }
    94  
    95  func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
    96  	// SSR protocol compatibility
    97  	// https://github.com/igoogolx/clash/pull/2056
    98  	if option.Cipher == "none" {
    99  		option.Cipher = "dummy"
   100  	}
   101  
   102  	addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
   103  	cipher := option.Cipher
   104  	password := option.Password
   105  	coreCiph, err := core.PickCipher(cipher, nil, password)
   106  	if err != nil {
   107  		return nil, fmt.Errorf("ssr %s initialize error: %w", addr, err)
   108  	}
   109  	var (
   110  		ivSize int
   111  		key    []byte
   112  	)
   113  
   114  	if option.Cipher == "dummy" {
   115  		ivSize = 0
   116  		key = core.Kdf(option.Password, 16)
   117  	} else {
   118  		ciph, ok := coreCiph.(*core.StreamCipher)
   119  		if !ok {
   120  			return nil, fmt.Errorf("%s is not none or a supported stream cipher in ssr", cipher)
   121  		}
   122  		ivSize = ciph.IVSize()
   123  		key = ciph.Key
   124  	}
   125  
   126  	obfs, obfsOverhead, err := obfs.PickObfs(option.Obfs, &obfs.Base{
   127  		Host:   option.Server,
   128  		Port:   option.Port,
   129  		Key:    key,
   130  		IVSize: ivSize,
   131  		Param:  option.ObfsParam,
   132  	})
   133  	if err != nil {
   134  		return nil, fmt.Errorf("ssr %s initialize obfs error: %w", addr, err)
   135  	}
   136  
   137  	protocol, err := protocol.PickProtocol(option.Protocol, &protocol.Base{
   138  		Key:      key,
   139  		Overhead: obfsOverhead,
   140  		Param:    option.ProtocolParam,
   141  	})
   142  	if err != nil {
   143  		return nil, fmt.Errorf("ssr %s initialize protocol error: %w", addr, err)
   144  	}
   145  
   146  	return &ShadowSocksR{
   147  		Base: &Base{
   148  			name:  option.Name,
   149  			addr:  addr,
   150  			tp:    C.ShadowsocksR,
   151  			udp:   option.UDP,
   152  			iface: option.Interface,
   153  			rmark: option.RoutingMark,
   154  		},
   155  		cipher:   coreCiph,
   156  		obfs:     obfs,
   157  		protocol: protocol,
   158  	}, nil
   159  }