github.com/kelleygo/clashcore@v1.0.2/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/kelleygo/clashcore/common/atomic" 21 "github.com/kelleygo/clashcore/common/buf" 22 "github.com/kelleygo/clashcore/common/pool" 23 tlsC "github.com/kelleygo/clashcore/component/tls" 24 25 "golang.org/x/net/http2" 26 ) 27 28 var ( 29 ErrInvalidLength = errors.New("invalid length") 30 ErrSmallBuffer = errors.New("buffer too small") 31 ) 32 33 var defaultHeader = http.Header{ 34 "content-type": []string{"application/grpc"}, 35 "user-agent": []string{"grpc-go/1.36.0"}, 36 } 37 38 type DialFn = func(network, addr string) (net.Conn, error) 39 40 type Conn struct { 41 response *http.Response 42 request *http.Request 43 transport *TransportWrap 44 writer *io.PipeWriter 45 once sync.Once 46 close atomic.Bool 47 err error 48 remain int 49 br *bufio.Reader 50 // deadlines 51 deadline *time.Timer 52 } 53 54 type Config struct { 55 ServiceName string 56 Host string 57 ClientFingerprint string 58 } 59 60 func (g *Conn) initRequest() { 61 response, err := g.transport.RoundTrip(g.request) 62 if err != nil { 63 g.err = err 64 g.writer.Close() 65 return 66 } 67 68 if !g.close.Load() { 69 g.response = response 70 g.br = bufio.NewReader(response.Body) 71 } else { 72 response.Body.Close() 73 } 74 } 75 76 func (g *Conn) Read(b []byte) (n int, err error) { 77 g.once.Do(g.initRequest) 78 if g.err != nil { 79 return 0, g.err 80 } 81 82 if g.remain > 0 { 83 size := g.remain 84 if len(b) < size { 85 size = len(b) 86 } 87 88 n, err = io.ReadFull(g.br, b[:size]) 89 g.remain -= n 90 return 91 } else if g.response == nil { 92 return 0, net.ErrClosed 93 } 94 95 // 0x00 grpclength(uint32) 0x0A uleb128 payload 96 _, err = g.br.Discard(6) 97 if err != nil { 98 return 0, err 99 } 100 101 protobufPayloadLen, err := binary.ReadUvarint(g.br) 102 if err != nil { 103 return 0, ErrInvalidLength 104 } 105 106 size := int(protobufPayloadLen) 107 if len(b) < size { 108 size = len(b) 109 } 110 111 n, err = io.ReadFull(g.br, b[:size]) 112 if err != nil { 113 return 114 } 115 116 remain := int(protobufPayloadLen) - n 117 if remain > 0 { 118 g.remain = remain 119 } 120 121 return n, nil 122 } 123 124 func (g *Conn) Write(b []byte) (n int, err error) { 125 protobufHeader := [binary.MaxVarintLen64 + 1]byte{0x0A} 126 varuintSize := binary.PutUvarint(protobufHeader[1:], uint64(len(b))) 127 var grpcHeader [5]byte 128 grpcPayloadLen := uint32(varuintSize + 1 + len(b)) 129 binary.BigEndian.PutUint32(grpcHeader[1:5], grpcPayloadLen) 130 131 buf := pool.GetBuffer() 132 defer pool.PutBuffer(buf) 133 buf.Write(grpcHeader[:]) 134 buf.Write(protobufHeader[:varuintSize+1]) 135 buf.Write(b) 136 137 _, err = g.writer.Write(buf.Bytes()) 138 if err == io.ErrClosedPipe && g.err != nil { 139 err = g.err 140 } 141 142 return len(b), err 143 } 144 145 func (g *Conn) WriteBuffer(buffer *buf.Buffer) error { 146 defer buffer.Release() 147 dataLen := buffer.Len() 148 varLen := UVarintLen(uint64(dataLen)) 149 header := buffer.ExtendHeader(6 + varLen) 150 _ = header[6] // bounds check hint to compiler 151 header[0] = 0x00 152 binary.BigEndian.PutUint32(header[1:5], uint32(1+varLen+dataLen)) 153 header[5] = 0x0A 154 binary.PutUvarint(header[6:], uint64(dataLen)) 155 _, err := g.writer.Write(buffer.Bytes()) 156 157 if err == io.ErrClosedPipe && g.err != nil { 158 err = g.err 159 } 160 161 return err 162 } 163 164 func (g *Conn) FrontHeadroom() int { 165 return 6 + binary.MaxVarintLen64 166 } 167 168 func (g *Conn) Close() error { 169 g.close.Store(true) 170 if r := g.response; r != nil { 171 r.Body.Close() 172 } 173 174 return g.writer.Close() 175 } 176 177 func (g *Conn) LocalAddr() net.Addr { return g.transport.LocalAddr() } 178 func (g *Conn) RemoteAddr() net.Addr { return g.transport.RemoteAddr() } 179 func (g *Conn) SetReadDeadline(t time.Time) error { return g.SetDeadline(t) } 180 func (g *Conn) SetWriteDeadline(t time.Time) error { return g.SetDeadline(t) } 181 182 func (g *Conn) SetDeadline(t time.Time) error { 183 d := time.Until(t) 184 if g.deadline != nil { 185 g.deadline.Reset(d) 186 return nil 187 } 188 g.deadline = time.AfterFunc(d, func() { 189 g.Close() 190 }) 191 return nil 192 } 193 194 func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config, Fingerprint string, realityConfig *tlsC.RealityConfig) *TransportWrap { 195 wrap := TransportWrap{} 196 197 dialFunc := func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) { 198 pconn, err := dialFn(network, addr) 199 if err != nil { 200 return nil, err 201 } 202 wrap.remoteAddr = pconn.RemoteAddr() 203 204 if tlsConfig == nil { 205 return pconn, nil 206 } 207 208 if len(Fingerprint) != 0 { 209 if realityConfig == nil { 210 if fingerprint, exists := tlsC.GetFingerprint(Fingerprint); exists { 211 utlsConn := tlsC.UClient(pconn, cfg, fingerprint) 212 if err := utlsConn.HandshakeContext(ctx); err != nil { 213 pconn.Close() 214 return nil, err 215 } 216 state := utlsConn.ConnectionState() 217 if p := state.NegotiatedProtocol; p != http2.NextProtoTLS { 218 utlsConn.Close() 219 return nil, fmt.Errorf("http2: unexpected ALPN protocol %s, want %s", p, http2.NextProtoTLS) 220 } 221 return utlsConn, nil 222 } 223 } else { 224 realityConn, err := tlsC.GetRealityConn(ctx, pconn, Fingerprint, cfg, realityConfig) 225 if err != nil { 226 pconn.Close() 227 return nil, err 228 } 229 //state := realityConn.(*utls.UConn).ConnectionState() 230 //if p := state.NegotiatedProtocol; p != http2.NextProtoTLS { 231 // realityConn.Close() 232 // return nil, fmt.Errorf("http2: unexpected ALPN protocol %s, want %s", p, http2.NextProtoTLS) 233 //} 234 return realityConn, nil 235 } 236 } 237 if realityConfig != nil { 238 return nil, errors.New("REALITY is based on uTLS, please set a client-fingerprint") 239 } 240 241 conn := tls.Client(pconn, cfg) 242 if err := conn.HandshakeContext(ctx); err != nil { 243 pconn.Close() 244 return nil, err 245 } 246 state := conn.ConnectionState() 247 if p := state.NegotiatedProtocol; p != http2.NextProtoTLS { 248 conn.Close() 249 return nil, fmt.Errorf("http2: unexpected ALPN protocol %s, want %s", p, http2.NextProtoTLS) 250 } 251 return conn, nil 252 } 253 254 wrap.Transport = &http2.Transport{ 255 DialTLSContext: dialFunc, 256 TLSClientConfig: tlsConfig, 257 AllowHTTP: false, 258 DisableCompression: true, 259 PingTimeout: 0, 260 } 261 262 return &wrap 263 } 264 265 func StreamGunWithTransport(transport *TransportWrap, cfg *Config) (net.Conn, error) { 266 serviceName := "GunService" 267 if cfg.ServiceName != "" { 268 serviceName = cfg.ServiceName 269 } 270 271 reader, writer := io.Pipe() 272 request := &http.Request{ 273 Method: http.MethodPost, 274 Body: reader, 275 URL: &url.URL{ 276 Scheme: "https", 277 Host: cfg.Host, 278 Path: fmt.Sprintf("/%s/Tun", serviceName), 279 // for unescape path 280 Opaque: fmt.Sprintf("//%s/%s/Tun", cfg.Host, serviceName), 281 }, 282 Proto: "HTTP/2", 283 ProtoMajor: 2, 284 ProtoMinor: 0, 285 Header: defaultHeader, 286 } 287 288 conn := &Conn{ 289 request: request, 290 transport: transport, 291 writer: writer, 292 close: atomic.NewBool(false), 293 } 294 295 go conn.once.Do(conn.initRequest) 296 return conn, nil 297 } 298 299 func StreamGunWithConn(conn net.Conn, tlsConfig *tls.Config, cfg *Config, realityConfig *tlsC.RealityConfig) (net.Conn, error) { 300 dialFn := func(network, addr string) (net.Conn, error) { 301 return conn, nil 302 } 303 304 transport := NewHTTP2Client(dialFn, tlsConfig, cfg.ClientFingerprint, realityConfig) 305 return StreamGunWithTransport(transport, cfg) 306 }