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