github.com/chwjbn/xclash@v0.2.0/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/chwjbn/xclash/component/dialer" 12 C "github.com/chwjbn/xclash/constant" 13 "github.com/chwjbn/xclash/transport/gun" 14 "github.com/chwjbn/xclash/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 safeConnClose(c, err) 113 114 c, err = t.StreamConn(c, metadata) 115 if err != nil { 116 return nil, err 117 } 118 119 return NewConn(c, t), err 120 } 121 122 // ListenPacketContext implements C.ProxyAdapter 123 func (t *Trojan) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) { 124 var c net.Conn 125 126 // grpc transport 127 if t.transport != nil && len(opts) == 0 { 128 c, err = gun.StreamGunWithTransport(t.transport, t.gunConfig) 129 if err != nil { 130 return nil, fmt.Errorf("%s connect error: %w", t.addr, err) 131 } 132 defer safeConnClose(c, err) 133 } else { 134 c, err = dialer.DialContext(ctx, "tcp", t.addr, t.Base.DialOptions(opts...)...) 135 if err != nil { 136 return nil, fmt.Errorf("%s connect error: %w", t.addr, err) 137 } 138 defer safeConnClose(c, err) 139 tcpKeepAlive(c) 140 c, err = t.plainStream(c) 141 if err != nil { 142 return nil, fmt.Errorf("%s connect error: %w", t.addr, err) 143 } 144 } 145 146 err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata)) 147 if err != nil { 148 return nil, err 149 } 150 151 pc := t.instance.PacketConn(c) 152 return newPacketConn(pc, t), err 153 } 154 155 func NewTrojan(option TrojanOption) (*Trojan, error) { 156 addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) 157 158 tOption := &trojan.Option{ 159 Password: option.Password, 160 ALPN: option.ALPN, 161 ServerName: option.Server, 162 SkipCertVerify: option.SkipCertVerify, 163 } 164 165 if option.SNI != "" { 166 tOption.ServerName = option.SNI 167 } 168 169 t := &Trojan{ 170 Base: &Base{ 171 name: option.Name, 172 addr: addr, 173 tp: C.Trojan, 174 udp: option.UDP, 175 iface: option.Interface, 176 }, 177 instance: trojan.New(tOption), 178 option: &option, 179 } 180 181 if option.Network == "grpc" { 182 dialFn := func(network, addr string) (net.Conn, error) { 183 c, err := dialer.DialContext(context.Background(), "tcp", t.addr, t.Base.DialOptions()...) 184 if err != nil { 185 return nil, fmt.Errorf("%s connect error: %s", t.addr, err.Error()) 186 } 187 tcpKeepAlive(c) 188 return c, nil 189 } 190 191 tlsConfig := &tls.Config{ 192 NextProtos: option.ALPN, 193 MinVersion: tls.VersionTLS12, 194 InsecureSkipVerify: tOption.SkipCertVerify, 195 ServerName: tOption.ServerName, 196 } 197 198 t.transport = gun.NewHTTP2Client(dialFn, tlsConfig) 199 t.gunTLSConfig = tlsConfig 200 t.gunConfig = &gun.Config{ 201 ServiceName: option.GrpcOpts.GrpcServiceName, 202 Host: tOption.ServerName, 203 } 204 } 205 206 return t, nil 207 }