github.com/igoogolx/clash@v1.19.8/transport/gun/gun.go (about)

     1  // Modified from: https://github.com/Qv2ray/gun-lite
     2  // License: MIT
     3  
     4  package gun
     5  
     6  import (
     7  	"bufio"
     8  	"context"
     9  	"crypto/tls"
    10  	"encoding/binary"
    11  	"errors"
    12  	"fmt"
    13  	"io"
    14  	"net"
    15  	"net/http"
    16  	"net/url"
    17  	"sync"
    18  	"time"
    19  
    20  	"github.com/igoogolx/clash/common/pool"
    21  
    22  	"go.uber.org/atomic"
    23  	"golang.org/x/net/http2"
    24  )
    25  
    26  var (
    27  	ErrInvalidLength = errors.New("invalid length")
    28  	ErrSmallBuffer   = errors.New("buffer too small")
    29  )
    30  
    31  var defaultHeader = http.Header{
    32  	"content-type": []string{"application/grpc"},
    33  	"user-agent":   []string{"grpc-go/1.36.0"},
    34  }
    35  
    36  type DialFn = func(network, addr string) (net.Conn, error)
    37  
    38  type Conn struct {
    39  	response  *http.Response
    40  	request   *http.Request
    41  	transport *http2.Transport
    42  	writer    *io.PipeWriter
    43  	once      sync.Once
    44  	close     *atomic.Bool
    45  	err       error
    46  	remain    int
    47  	br        *bufio.Reader
    48  
    49  	// deadlines
    50  	deadline *time.Timer
    51  }
    52  
    53  type Config struct {
    54  	ServiceName string
    55  	Host        string
    56  }
    57  
    58  func (g *Conn) initRequest() {
    59  	response, err := g.transport.RoundTrip(g.request)
    60  	if err != nil {
    61  		g.err = err
    62  		g.writer.Close()
    63  		return
    64  	}
    65  
    66  	if !g.close.Load() {
    67  		g.response = response
    68  		g.br = bufio.NewReader(response.Body)
    69  	} else {
    70  		response.Body.Close()
    71  	}
    72  }
    73  
    74  func (g *Conn) Read(b []byte) (n int, err error) {
    75  	g.once.Do(g.initRequest)
    76  	if g.err != nil {
    77  		return 0, g.err
    78  	}
    79  
    80  	if g.remain > 0 {
    81  		size := g.remain
    82  		if len(b) < size {
    83  			size = len(b)
    84  		}
    85  
    86  		n, err = io.ReadFull(g.br, b[:size])
    87  		g.remain -= n
    88  		return
    89  	} else if g.response == nil {
    90  		return 0, net.ErrClosed
    91  	}
    92  
    93  	// 0x00 grpclength(uint32) 0x0A uleb128 payload
    94  	_, err = g.br.Discard(6)
    95  	if err != nil {
    96  		return 0, err
    97  	}
    98  
    99  	protobufPayloadLen, err := binary.ReadUvarint(g.br)
   100  	if err != nil {
   101  		return 0, ErrInvalidLength
   102  	}
   103  
   104  	size := int(protobufPayloadLen)
   105  	if len(b) < size {
   106  		size = len(b)
   107  	}
   108  
   109  	n, err = io.ReadFull(g.br, b[:size])
   110  	if err != nil {
   111  		return
   112  	}
   113  
   114  	remain := int(protobufPayloadLen) - n
   115  	if remain > 0 {
   116  		g.remain = remain
   117  	}
   118  
   119  	return n, nil
   120  }
   121  
   122  func (g *Conn) Write(b []byte) (n int, err error) {
   123  	protobufHeader := [binary.MaxVarintLen64 + 1]byte{0x0A}
   124  	varuintSize := binary.PutUvarint(protobufHeader[1:], uint64(len(b)))
   125  	grpcHeader := make([]byte, 5)
   126  	grpcPayloadLen := uint32(varuintSize + 1 + len(b))
   127  	binary.BigEndian.PutUint32(grpcHeader[1:5], grpcPayloadLen)
   128  
   129  	buf := pool.GetBytesBuffer()
   130  	defer pool.PutBytesBuffer(buf)
   131  	buf.PutSlice(grpcHeader)
   132  	buf.PutSlice(protobufHeader[:varuintSize+1])
   133  	buf.PutSlice(b)
   134  
   135  	_, err = g.writer.Write(buf.Bytes())
   136  	if err == io.ErrClosedPipe && g.err != nil {
   137  		err = g.err
   138  	}
   139  
   140  	return len(b), err
   141  }
   142  
   143  func (g *Conn) Close() error {
   144  	g.close.Store(true)
   145  	if r := g.response; r != nil {
   146  		r.Body.Close()
   147  	}
   148  
   149  	return g.writer.Close()
   150  }
   151  
   152  func (g *Conn) LocalAddr() net.Addr                { return &net.TCPAddr{IP: net.IPv4zero, Port: 0} }
   153  func (g *Conn) RemoteAddr() net.Addr               { return &net.TCPAddr{IP: net.IPv4zero, Port: 0} }
   154  func (g *Conn) SetReadDeadline(t time.Time) error  { return g.SetDeadline(t) }
   155  func (g *Conn) SetWriteDeadline(t time.Time) error { return g.SetDeadline(t) }
   156  
   157  func (g *Conn) SetDeadline(t time.Time) error {
   158  	d := time.Until(t)
   159  	if g.deadline != nil {
   160  		g.deadline.Reset(d)
   161  		return nil
   162  	}
   163  	g.deadline = time.AfterFunc(d, func() {
   164  		g.Close()
   165  	})
   166  	return nil
   167  }
   168  
   169  func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config) *http2.Transport {
   170  	dialFunc := func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) {
   171  		pconn, err := dialFn(network, addr)
   172  		if err != nil {
   173  			return nil, err
   174  		}
   175  
   176  		cn := tls.Client(pconn, cfg)
   177  		if err := cn.HandshakeContext(ctx); err != nil {
   178  			pconn.Close()
   179  			return nil, err
   180  		}
   181  		state := cn.ConnectionState()
   182  		if p := state.NegotiatedProtocol; p != http2.NextProtoTLS {
   183  			cn.Close()
   184  			return nil, fmt.Errorf("http2: unexpected ALPN protocol %s, want %s", p, http2.NextProtoTLS)
   185  		}
   186  		return cn, nil
   187  	}
   188  
   189  	return &http2.Transport{
   190  		DialTLSContext:     dialFunc,
   191  		TLSClientConfig:    tlsConfig,
   192  		AllowHTTP:          false,
   193  		DisableCompression: true,
   194  		PingTimeout:        0,
   195  	}
   196  }
   197  
   198  func StreamGunWithTransport(transport *http2.Transport, cfg *Config) (net.Conn, error) {
   199  	serviceName := "GunService"
   200  	if cfg.ServiceName != "" {
   201  		serviceName = cfg.ServiceName
   202  	}
   203  
   204  	reader, writer := io.Pipe()
   205  	request := &http.Request{
   206  		Method: http.MethodPost,
   207  		Body:   reader,
   208  		URL: &url.URL{
   209  			Scheme: "https",
   210  			Host:   cfg.Host,
   211  			Path:   fmt.Sprintf("/%s/Tun", serviceName),
   212  			// for unescape path
   213  			Opaque: fmt.Sprintf("//%s/%s/Tun", cfg.Host, serviceName),
   214  		},
   215  		Proto:      "HTTP/2",
   216  		ProtoMajor: 2,
   217  		ProtoMinor: 0,
   218  		Header:     defaultHeader,
   219  	}
   220  
   221  	conn := &Conn{
   222  		request:   request,
   223  		transport: transport,
   224  		writer:    writer,
   225  		close:     atomic.NewBool(false),
   226  	}
   227  
   228  	go conn.once.Do(conn.initRequest)
   229  	return conn, nil
   230  }
   231  
   232  func StreamGunWithConn(conn net.Conn, tlsConfig *tls.Config, cfg *Config) (net.Conn, error) {
   233  	dialFn := func(network, addr string) (net.Conn, error) {
   234  		return conn, nil
   235  	}
   236  
   237  	transport := NewHTTP2Client(dialFn, tlsConfig)
   238  	return StreamGunWithTransport(transport, cfg)
   239  }