github.com/igoogolx/clash@v1.19.8/adapter/outbound/trojan.go (about) 1 package outbound 2 3 import ( 4 "context" 5 "crypto/tls" 6 "fmt" 7 "net" 8 "net/http" 9 "strconv" 10 11 "github.com/igoogolx/clash/component/dialer" 12 C "github.com/igoogolx/clash/constant" 13 "github.com/igoogolx/clash/transport/gun" 14 "github.com/igoogolx/clash/transport/trojan" 15 16 "golang.org/x/net/http2" 17 ) 18 19 type Trojan struct { 20 *Base 21 instance *trojan.Trojan 22 option *TrojanOption 23 24 // for gun mux 25 gunTLSConfig *tls.Config 26 gunConfig *gun.Config 27 transport *http2.Transport 28 } 29 30 type TrojanOption struct { 31 BasicOption 32 Name string `proxy:"name"` 33 Server string `proxy:"server"` 34 Port int `proxy:"port"` 35 Password string `proxy:"password"` 36 ALPN []string `proxy:"alpn,omitempty"` 37 SNI string `proxy:"sni,omitempty"` 38 SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` 39 UDP bool `proxy:"udp,omitempty"` 40 Network string `proxy:"network,omitempty"` 41 GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"` 42 WSOpts WSOptions `proxy:"ws-opts,omitempty"` 43 } 44 45 func (t *Trojan) plainStream(c net.Conn) (net.Conn, error) { 46 if t.option.Network == "ws" { 47 host, port, _ := net.SplitHostPort(t.addr) 48 wsOpts := &trojan.WebsocketOption{ 49 Host: host, 50 Port: port, 51 Path: t.option.WSOpts.Path, 52 } 53 54 if t.option.SNI != "" { 55 wsOpts.Host = t.option.SNI 56 } 57 58 if len(t.option.WSOpts.Headers) != 0 { 59 header := http.Header{} 60 for key, value := range t.option.WSOpts.Headers { 61 header.Add(key, value) 62 } 63 wsOpts.Headers = header 64 } 65 66 return t.instance.StreamWebsocketConn(c, wsOpts) 67 } 68 69 return t.instance.StreamConn(c) 70 } 71 72 // StreamConn implements C.ProxyAdapter 73 func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { 74 var err error 75 if t.transport != nil { 76 c, err = gun.StreamGunWithConn(c, t.gunTLSConfig, t.gunConfig) 77 } else { 78 c, err = t.plainStream(c) 79 } 80 81 if err != nil { 82 return nil, fmt.Errorf("%s connect error: %w", t.addr, err) 83 } 84 85 err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata)) 86 return c, err 87 } 88 89 // DialContext implements C.ProxyAdapter 90 func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { 91 // gun transport 92 if t.transport != nil && len(opts) == 0 { 93 c, err := gun.StreamGunWithTransport(t.transport, t.gunConfig) 94 if err != nil { 95 return nil, err 96 } 97 98 if err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata)); err != nil { 99 c.Close() 100 return nil, err 101 } 102 103 return NewConn(c, t), nil 104 } 105 106 c, err := dialer.DialContext(ctx, "tcp", t.addr, t.Base.DialOptions(opts...)...) 107 if err != nil { 108 return nil, fmt.Errorf("%s connect error: %w", t.addr, err) 109 } 110 tcpKeepAlive(c) 111 112 defer func(c net.Conn) { 113 safeConnClose(c, err) 114 }(c) 115 116 c, err = t.StreamConn(c, metadata) 117 if err != nil { 118 return nil, err 119 } 120 121 return NewConn(c, t), err 122 } 123 124 // ListenPacketContext implements C.ProxyAdapter 125 func (t *Trojan) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) { 126 var c net.Conn 127 128 // grpc transport 129 if t.transport != nil && len(opts) == 0 { 130 c, err = gun.StreamGunWithTransport(t.transport, t.gunConfig) 131 if err != nil { 132 return nil, fmt.Errorf("%s connect error: %w", t.addr, err) 133 } 134 defer func(c net.Conn) { 135 safeConnClose(c, err) 136 }(c) 137 } else { 138 c, err = dialer.DialContext(ctx, "tcp", t.addr, t.Base.DialOptions(opts...)...) 139 if err != nil { 140 return nil, fmt.Errorf("%s connect error: %w", t.addr, err) 141 } 142 defer func(c net.Conn) { 143 safeConnClose(c, err) 144 }(c) 145 tcpKeepAlive(c) 146 c, err = t.plainStream(c) 147 if err != nil { 148 return nil, fmt.Errorf("%s connect error: %w", t.addr, err) 149 } 150 } 151 152 err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata)) 153 if err != nil { 154 return nil, err 155 } 156 157 pc := t.instance.PacketConn(c) 158 return newPacketConn(pc, t), err 159 } 160 161 func NewTrojan(option TrojanOption) (*Trojan, error) { 162 addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) 163 164 tOption := &trojan.Option{ 165 Password: option.Password, 166 ALPN: option.ALPN, 167 ServerName: option.Server, 168 SkipCertVerify: option.SkipCertVerify, 169 } 170 171 if option.SNI != "" { 172 tOption.ServerName = option.SNI 173 } 174 175 t := &Trojan{ 176 Base: &Base{ 177 name: option.Name, 178 addr: addr, 179 tp: C.Trojan, 180 udp: option.UDP, 181 iface: option.Interface, 182 rmark: option.RoutingMark, 183 }, 184 instance: trojan.New(tOption), 185 option: &option, 186 } 187 188 if option.Network == "grpc" { 189 dialFn := func(network, addr string) (net.Conn, error) { 190 c, err := dialer.DialContext(context.Background(), "tcp", t.addr, t.Base.DialOptions()...) 191 if err != nil { 192 return nil, fmt.Errorf("%s connect error: %s", t.addr, err.Error()) 193 } 194 tcpKeepAlive(c) 195 return c, nil 196 } 197 198 tlsConfig := &tls.Config{ 199 NextProtos: option.ALPN, 200 MinVersion: tls.VersionTLS12, 201 InsecureSkipVerify: tOption.SkipCertVerify, 202 ServerName: tOption.ServerName, 203 } 204 205 t.transport = gun.NewHTTP2Client(dialFn, tlsConfig) 206 t.gunTLSConfig = tlsConfig 207 t.gunConfig = &gun.Config{ 208 ServiceName: option.GrpcOpts.GrpcServiceName, 209 Host: tOption.ServerName, 210 } 211 } 212 213 return t, nil 214 }