package masque import ( "context" "errors" "fmt" "io" "net" "net/http" "net/netip" "strings" connectip "github.com/Diniboy1123/connect-ip-go" "github.com/sagernet/quic-go" "github.com/sagernet/quic-go/congestion" "github.com/sagernet/quic-go/http3" qtls "github.com/sagernet/sing-quic" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" aTLS "github.com/sagernet/sing/common/tls" "github.com/yosida95/uritemplate/v3" ) type ( DialContext func(ctx context.Context, network, address string) (net.Conn, error) ListenPacket func(network string, address string) (net.PacketConn, error) ) type IpConn interface { ReadPacket() (b []byte, err error) WritePacket(b []byte) (icmp []byte, err error) Close() error } type closerFunc func() error func (f closerFunc) Close() error { return f() } type quicIpConn struct { conn *connectip.Conn buf []byte } func newQuicIpConn(conn *connectip.Conn) *quicIpConn { return &quicIpConn{ conn: conn, buf: make([]byte, 0xFFFF), } } func (c *quicIpConn) ReadPacket() ([]byte, error) { n, err := c.conn.ReadPacket(c.buf, true) if err != nil { return nil, err } return c.buf[:n], nil } func (c *quicIpConn) WritePacket(b []byte) (icmp []byte, err error) { return c.conn.WritePacket(b) } func (c *quicIpConn) Close() error { return c.conn.Close() } func ConnectTunnel(ctx context.Context, dialer N.Dialer, tlsConfig aTLS.Config, quicConfig *quic.Config, connectUri string, endpoint net.Addr, useHTTP2 bool, congestionControl func(conn *quic.Conn) congestion.CongestionControl) (io.Closer, IpConn, *http.Response, error) { if useHTTP2 { h2Endpoint, ok := endpoint.(*net.TCPAddr) if !ok || h2Endpoint == nil { return nil, nil, nil, errors.New("missing HTTP/2 TCP endpoint") } return ConnectTunnelH2(ctx, dialer, tlsConfig, h2Endpoint, connectUri) } quicEndpoint, ok := endpoint.(*net.UDPAddr) if !ok || quicEndpoint == nil { return nil, nil, nil, errors.New("missing HTTP/3 UDP endpoint") } udpConn, err := dialer.ListenPacket(ctx, M.SocksaddrFromNetIP(quicEndpoint.AddrPort())) if err != nil { return nil, nil, nil, err } conn, err := qtls.Dial( ctx, udpConn, quicEndpoint, tlsConfig, quicConfig, ) if err != nil { _ = udpConn.Close() return nil, nil, nil, err } if congestionControl != nil { conn.SetCongestionControl(congestionControl(conn)) } tr := &http3.Transport{ EnableDatagrams: true, AdditionalSettings: map[uint64]uint64{ 0x276: 1, }, DisableCompression: true, } hconn := tr.NewClientConn(conn) template := uritemplate.MustNew(connectUri) additionalHeaders := http.Header{ "User-Agent": []string{""}, } ipConn, rsp, err := connectip.Dial(ctx, hconn, template, "cf-connect-ip", additionalHeaders, true) if err != nil { _ = tr.Close() _ = conn.CloseWithError(0, "connect-ip dial failed") _ = udpConn.Close() if strings.Contains(err.Error(), "tls: access denied") { return nil, nil, nil, errors.New("login failed! Please double-check if your tls key and cert is enrolled in the Cloudflare Access service") } return nil, nil, rsp, fmt.Errorf("failed to dial connect-ip: %w", err) } err = ipConn.AdvertiseRoute(ctx, []connectip.IPRoute{ { IPProtocol: 0, StartIP: netip.AddrFrom4([4]byte{}), EndIP: netip.AddrFrom4([4]byte{255, 255, 255, 255}), }, { IPProtocol: 0, StartIP: netip.AddrFrom16([16]byte{}), EndIP: netip.AddrFrom16([16]byte{ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, }), }, }) if err != nil { _ = ipConn.Close() _ = tr.Close() _ = udpConn.Close() return nil, nil, nil, err } closer := closerFunc(func() error { _ = tr.Close() _ = udpConn.Close() return nil }) return closer, newQuicIpConn(ipConn), rsp, nil }