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 }