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 }