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  }