github.com/chwjbn/xclash@v0.2.0/adapter/outbound/vmess.go (about)

     1  package outbound
     2  
     3  import (
     4  	"context"
     5  	"crypto/tls"
     6  	"errors"
     7  	"fmt"
     8  	"net"
     9  	"net/http"
    10  	"strconv"
    11  	"strings"
    12  
    13  	"github.com/chwjbn/xclash/component/dialer"
    14  	"github.com/chwjbn/xclash/component/resolver"
    15  	C "github.com/chwjbn/xclash/constant"
    16  	"github.com/chwjbn/xclash/transport/gun"
    17  	"github.com/chwjbn/xclash/transport/vmess"
    18  
    19  	"golang.org/x/net/http2"
    20  )
    21  
    22  type Vmess struct {
    23  	*Base
    24  	client *vmess.Client
    25  	option *VmessOption
    26  
    27  	// for gun mux
    28  	gunTLSConfig *tls.Config
    29  	gunConfig    *gun.Config
    30  	transport    *http2.Transport
    31  }
    32  
    33  type VmessOption struct {
    34  	BasicOption
    35  	Name           string       `proxy:"name"`
    36  	Server         string       `proxy:"server"`
    37  	Port           int          `proxy:"port"`
    38  	UUID           string       `proxy:"uuid"`
    39  	AlterID        int          `proxy:"alterId"`
    40  	Cipher         string       `proxy:"cipher"`
    41  	UDP            bool         `proxy:"udp,omitempty"`
    42  	Network        string       `proxy:"network,omitempty"`
    43  	TLS            bool         `proxy:"tls,omitempty"`
    44  	SkipCertVerify bool         `proxy:"skip-cert-verify,omitempty"`
    45  	ServerName     string       `proxy:"servername,omitempty"`
    46  	HTTPOpts       HTTPOptions  `proxy:"http-opts,omitempty"`
    47  	HTTP2Opts      HTTP2Options `proxy:"h2-opts,omitempty"`
    48  	GrpcOpts       GrpcOptions  `proxy:"grpc-opts,omitempty"`
    49  	WSOpts         WSOptions    `proxy:"ws-opts,omitempty"`
    50  }
    51  
    52  type HTTPOptions struct {
    53  	Method  string              `proxy:"method,omitempty"`
    54  	Path    []string            `proxy:"path,omitempty"`
    55  	Headers map[string][]string `proxy:"headers,omitempty"`
    56  }
    57  
    58  type HTTP2Options struct {
    59  	Host []string `proxy:"host,omitempty"`
    60  	Path string   `proxy:"path,omitempty"`
    61  }
    62  
    63  type GrpcOptions struct {
    64  	GrpcServiceName string `proxy:"grpc-service-name,omitempty"`
    65  }
    66  
    67  type WSOptions struct {
    68  	Path                string            `proxy:"path,omitempty"`
    69  	Headers             map[string]string `proxy:"headers,omitempty"`
    70  	MaxEarlyData        int               `proxy:"max-early-data,omitempty"`
    71  	EarlyDataHeaderName string            `proxy:"early-data-header-name,omitempty"`
    72  }
    73  
    74  // StreamConn implements C.ProxyAdapter
    75  func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
    76  	var err error
    77  	switch v.option.Network {
    78  	case "ws":
    79  		host, port, _ := net.SplitHostPort(v.addr)
    80  		wsOpts := &vmess.WebsocketConfig{
    81  			Host:                host,
    82  			Port:                port,
    83  			Path:                v.option.WSOpts.Path,
    84  			MaxEarlyData:        v.option.WSOpts.MaxEarlyData,
    85  			EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName,
    86  		}
    87  
    88  		if len(v.option.WSOpts.Headers) != 0 {
    89  			header := http.Header{}
    90  			for key, value := range v.option.WSOpts.Headers {
    91  				header.Add(key, value)
    92  			}
    93  			wsOpts.Headers = header
    94  		}
    95  
    96  		if v.option.TLS {
    97  			wsOpts.TLS = true
    98  			wsOpts.TLSConfig = &tls.Config{
    99  				ServerName:         host,
   100  				InsecureSkipVerify: v.option.SkipCertVerify,
   101  				NextProtos:         []string{"http/1.1"},
   102  			}
   103  			if v.option.ServerName != "" {
   104  				wsOpts.TLSConfig.ServerName = v.option.ServerName
   105  			} else if host := wsOpts.Headers.Get("Host"); host != "" {
   106  				wsOpts.TLSConfig.ServerName = host
   107  			}
   108  		}
   109  		c, err = vmess.StreamWebsocketConn(c, wsOpts)
   110  	case "http":
   111  		// readability first, so just copy default TLS logic
   112  		if v.option.TLS {
   113  			host, _, _ := net.SplitHostPort(v.addr)
   114  			tlsOpts := &vmess.TLSConfig{
   115  				Host:           host,
   116  				SkipCertVerify: v.option.SkipCertVerify,
   117  			}
   118  
   119  			if v.option.ServerName != "" {
   120  				tlsOpts.Host = v.option.ServerName
   121  			}
   122  
   123  			c, err = vmess.StreamTLSConn(c, tlsOpts)
   124  			if err != nil {
   125  				return nil, err
   126  			}
   127  		}
   128  
   129  		host, _, _ := net.SplitHostPort(v.addr)
   130  		httpOpts := &vmess.HTTPConfig{
   131  			Host:    host,
   132  			Method:  v.option.HTTPOpts.Method,
   133  			Path:    v.option.HTTPOpts.Path,
   134  			Headers: v.option.HTTPOpts.Headers,
   135  		}
   136  
   137  		c = vmess.StreamHTTPConn(c, httpOpts)
   138  	case "h2":
   139  		host, _, _ := net.SplitHostPort(v.addr)
   140  		tlsOpts := vmess.TLSConfig{
   141  			Host:           host,
   142  			SkipCertVerify: v.option.SkipCertVerify,
   143  			NextProtos:     []string{"h2"},
   144  		}
   145  
   146  		if v.option.ServerName != "" {
   147  			tlsOpts.Host = v.option.ServerName
   148  		}
   149  
   150  		c, err = vmess.StreamTLSConn(c, &tlsOpts)
   151  		if err != nil {
   152  			return nil, err
   153  		}
   154  
   155  		h2Opts := &vmess.H2Config{
   156  			Hosts: v.option.HTTP2Opts.Host,
   157  			Path:  v.option.HTTP2Opts.Path,
   158  		}
   159  
   160  		c, err = vmess.StreamH2Conn(c, h2Opts)
   161  	case "grpc":
   162  		c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig)
   163  	default:
   164  		// handle TLS
   165  		if v.option.TLS {
   166  			host, _, _ := net.SplitHostPort(v.addr)
   167  			tlsOpts := &vmess.TLSConfig{
   168  				Host:           host,
   169  				SkipCertVerify: v.option.SkipCertVerify,
   170  			}
   171  
   172  			if v.option.ServerName != "" {
   173  				tlsOpts.Host = v.option.ServerName
   174  			}
   175  
   176  			c, err = vmess.StreamTLSConn(c, tlsOpts)
   177  		}
   178  	}
   179  
   180  	if err != nil {
   181  		return nil, err
   182  	}
   183  
   184  	return v.client.StreamConn(c, parseVmessAddr(metadata))
   185  }
   186  
   187  // DialContext implements C.ProxyAdapter
   188  func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
   189  	// gun transport
   190  	if v.transport != nil && len(opts) == 0 {
   191  		c, err := gun.StreamGunWithTransport(v.transport, v.gunConfig)
   192  		if err != nil {
   193  			return nil, err
   194  		}
   195  		defer safeConnClose(c, err)
   196  
   197  		c, err = v.client.StreamConn(c, parseVmessAddr(metadata))
   198  		if err != nil {
   199  			return nil, err
   200  		}
   201  
   202  		return NewConn(c, v), nil
   203  	}
   204  
   205  	c, err := dialer.DialContext(ctx, "tcp", v.addr, v.Base.DialOptions(opts...)...)
   206  	if err != nil {
   207  		return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
   208  	}
   209  	tcpKeepAlive(c)
   210  	defer safeConnClose(c, err)
   211  
   212  	c, err = v.StreamConn(c, metadata)
   213  	return NewConn(c, v), err
   214  }
   215  
   216  // ListenPacketContext implements C.ProxyAdapter
   217  func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
   218  	// vmess use stream-oriented udp with a special address, so we needs a net.UDPAddr
   219  	if !metadata.Resolved() {
   220  		ip, err := resolver.ResolveIP(metadata.Host)
   221  		if err != nil {
   222  			return nil, errors.New("can't resolve ip")
   223  		}
   224  		metadata.DstIP = ip
   225  	}
   226  
   227  	var c net.Conn
   228  	// gun transport
   229  	if v.transport != nil && len(opts) == 0 {
   230  		c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig)
   231  		if err != nil {
   232  			return nil, err
   233  		}
   234  		defer safeConnClose(c, err)
   235  
   236  		c, err = v.client.StreamConn(c, parseVmessAddr(metadata))
   237  	} else {
   238  		c, err = dialer.DialContext(ctx, "tcp", v.addr, v.Base.DialOptions(opts...)...)
   239  		if err != nil {
   240  			return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
   241  		}
   242  		tcpKeepAlive(c)
   243  		defer safeConnClose(c, err)
   244  
   245  		c, err = v.StreamConn(c, metadata)
   246  	}
   247  
   248  	if err != nil {
   249  		return nil, fmt.Errorf("new vmess client error: %v", err)
   250  	}
   251  
   252  	return newPacketConn(&vmessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil
   253  }
   254  
   255  func NewVmess(option VmessOption) (*Vmess, error) {
   256  	security := strings.ToLower(option.Cipher)
   257  	client, err := vmess.NewClient(vmess.Config{
   258  		UUID:     option.UUID,
   259  		AlterID:  uint16(option.AlterID),
   260  		Security: security,
   261  		HostName: option.Server,
   262  		Port:     strconv.Itoa(option.Port),
   263  		IsAead:   option.AlterID == 0,
   264  	})
   265  	if err != nil {
   266  		return nil, err
   267  	}
   268  
   269  	switch option.Network {
   270  	case "h2", "grpc":
   271  		if !option.TLS {
   272  			return nil, fmt.Errorf("TLS must be true with h2/grpc network")
   273  		}
   274  	}
   275  
   276  	v := &Vmess{
   277  		Base: &Base{
   278  			name:  option.Name,
   279  			addr:  net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
   280  			tp:    C.Vmess,
   281  			udp:   option.UDP,
   282  			iface: option.Interface,
   283  		},
   284  		client: client,
   285  		option: &option,
   286  	}
   287  
   288  	switch option.Network {
   289  	case "h2":
   290  		if len(option.HTTP2Opts.Host) == 0 {
   291  			option.HTTP2Opts.Host = append(option.HTTP2Opts.Host, "www.example.com")
   292  		}
   293  	case "grpc":
   294  		dialFn := func(network, addr string) (net.Conn, error) {
   295  			c, err := dialer.DialContext(context.Background(), "tcp", v.addr, v.Base.DialOptions()...)
   296  			if err != nil {
   297  				return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
   298  			}
   299  			tcpKeepAlive(c)
   300  			return c, nil
   301  		}
   302  
   303  		gunConfig := &gun.Config{
   304  			ServiceName: v.option.GrpcOpts.GrpcServiceName,
   305  			Host:        v.option.ServerName,
   306  		}
   307  		tlsConfig := &tls.Config{
   308  			InsecureSkipVerify: v.option.SkipCertVerify,
   309  			ServerName:         v.option.ServerName,
   310  		}
   311  
   312  		if v.option.ServerName == "" {
   313  			host, _, _ := net.SplitHostPort(v.addr)
   314  			tlsConfig.ServerName = host
   315  			gunConfig.Host = host
   316  		}
   317  
   318  		v.gunTLSConfig = tlsConfig
   319  		v.gunConfig = gunConfig
   320  		v.transport = gun.NewHTTP2Client(dialFn, tlsConfig)
   321  	}
   322  
   323  	return v, nil
   324  }
   325  
   326  func parseVmessAddr(metadata *C.Metadata) *vmess.DstAddr {
   327  	var addrType byte
   328  	var addr []byte
   329  	switch metadata.AddrType {
   330  	case C.AtypIPv4:
   331  		addrType = byte(vmess.AtypIPv4)
   332  		addr = make([]byte, net.IPv4len)
   333  		copy(addr[:], metadata.DstIP.To4())
   334  	case C.AtypIPv6:
   335  		addrType = byte(vmess.AtypIPv6)
   336  		addr = make([]byte, net.IPv6len)
   337  		copy(addr[:], metadata.DstIP.To16())
   338  	case C.AtypDomainName:
   339  		addrType = byte(vmess.AtypDomainName)
   340  		addr = make([]byte, len(metadata.Host)+1)
   341  		addr[0] = byte(len(metadata.Host))
   342  		copy(addr[1:], []byte(metadata.Host))
   343  	}
   344  
   345  	port, _ := strconv.ParseUint(metadata.DstPort, 10, 16)
   346  	return &vmess.DstAddr{
   347  		UDP:      metadata.NetWork == C.UDP,
   348  		AddrType: addrType,
   349  		Addr:     addr,
   350  		Port:     uint(port),
   351  	}
   352  }
   353  
   354  type vmessPacketConn struct {
   355  	net.Conn
   356  	rAddr net.Addr
   357  }
   358  
   359  func (uc *vmessPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
   360  	return uc.Conn.Write(b)
   361  }
   362  
   363  func (uc *vmessPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
   364  	n, err := uc.Conn.Read(b)
   365  	return n, uc.rAddr, err
   366  }