mirror of
https://github.com/XTLS/Xray-core.git
synced 2024-11-23 23:43:02 +00:00
270 lines
7.5 KiB
Go
270 lines
7.5 KiB
Go
|
package reality
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"context"
|
||
|
"crypto/aes"
|
||
|
"crypto/cipher"
|
||
|
"crypto/ed25519"
|
||
|
"crypto/hmac"
|
||
|
"crypto/rand"
|
||
|
"crypto/sha256"
|
||
|
"crypto/sha512"
|
||
|
gotls "crypto/tls"
|
||
|
"crypto/x509"
|
||
|
"encoding/binary"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"math/big"
|
||
|
"net/http"
|
||
|
"reflect"
|
||
|
"regexp"
|
||
|
"strings"
|
||
|
"sync"
|
||
|
"time"
|
||
|
"unsafe"
|
||
|
|
||
|
utls "github.com/refraction-networking/utls"
|
||
|
"github.com/xtls/reality"
|
||
|
"github.com/xtls/xray-core/common/errors"
|
||
|
"github.com/xtls/xray-core/common/net"
|
||
|
"github.com/xtls/xray-core/core"
|
||
|
"github.com/xtls/xray-core/transport/internet/tls"
|
||
|
"golang.org/x/crypto/hkdf"
|
||
|
"golang.org/x/net/http2"
|
||
|
)
|
||
|
|
||
|
//go:generate go run github.com/xtls/xray-core/common/errors/errorgen
|
||
|
|
||
|
type Conn struct {
|
||
|
*reality.Conn
|
||
|
}
|
||
|
|
||
|
func (c *Conn) HandshakeAddress() net.Address {
|
||
|
if err := c.Handshake(); err != nil {
|
||
|
return nil
|
||
|
}
|
||
|
state := c.ConnectionState()
|
||
|
if state.ServerName == "" {
|
||
|
return nil
|
||
|
}
|
||
|
return net.ParseAddress(state.ServerName)
|
||
|
}
|
||
|
|
||
|
func Server(c net.Conn, config *reality.Config) (net.Conn, error) {
|
||
|
realityConn, err := reality.Server(c, config)
|
||
|
return &Conn{Conn: realityConn}, err
|
||
|
}
|
||
|
|
||
|
type UConn struct {
|
||
|
*utls.UConn
|
||
|
ServerName string
|
||
|
AuthKey []byte
|
||
|
Verified bool
|
||
|
}
|
||
|
|
||
|
func (c *UConn) HandshakeAddress() net.Address {
|
||
|
if err := c.Handshake(); err != nil {
|
||
|
return nil
|
||
|
}
|
||
|
state := c.ConnectionState()
|
||
|
if state.ServerName == "" {
|
||
|
return nil
|
||
|
}
|
||
|
return net.ParseAddress(state.ServerName)
|
||
|
}
|
||
|
|
||
|
func (c *UConn) VerifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
||
|
p, _ := reflect.TypeOf(c.Conn).Elem().FieldByName("peerCertificates")
|
||
|
certs := *(*([]*x509.Certificate))(unsafe.Pointer(uintptr(unsafe.Pointer(c.Conn)) + p.Offset))
|
||
|
if pub, ok := certs[0].PublicKey.(ed25519.PublicKey); ok {
|
||
|
h := hmac.New(sha512.New, c.AuthKey)
|
||
|
h.Write(pub)
|
||
|
if bytes.Equal(h.Sum(nil), certs[0].Signature) {
|
||
|
c.Verified = true
|
||
|
return nil
|
||
|
}
|
||
|
}
|
||
|
opts := x509.VerifyOptions{
|
||
|
DNSName: c.ServerName,
|
||
|
Intermediates: x509.NewCertPool(),
|
||
|
}
|
||
|
for _, cert := range certs[1:] {
|
||
|
opts.Intermediates.AddCert(cert)
|
||
|
}
|
||
|
if _, err := certs[0].Verify(opts); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func UClient(c net.Conn, config *Config, ctx context.Context, dest net.Destination) (net.Conn, error) {
|
||
|
localAddr := c.LocalAddr().String()
|
||
|
uConn := &UConn{}
|
||
|
utlsConfig := &utls.Config{
|
||
|
VerifyPeerCertificate: uConn.VerifyPeerCertificate,
|
||
|
ServerName: config.ServerName,
|
||
|
InsecureSkipVerify: true,
|
||
|
SessionTicketsDisabled: true,
|
||
|
}
|
||
|
if utlsConfig.ServerName == "" && dest.Address.Family().IsDomain() {
|
||
|
utlsConfig.ServerName = dest.Address.Domain()
|
||
|
}
|
||
|
uConn.ServerName = utlsConfig.ServerName
|
||
|
fingerprint := tls.GetFingerprint(config.Fingerprint)
|
||
|
if fingerprint == nil {
|
||
|
return nil, newError("REALITY: failed to get fingerprint").AtError()
|
||
|
}
|
||
|
uConn.UConn = utls.UClient(c, utlsConfig, *fingerprint)
|
||
|
{
|
||
|
uConn.BuildHandshakeState()
|
||
|
hello := uConn.HandshakeState.Hello
|
||
|
hello.SessionId = make([]byte, 32)
|
||
|
copy(hello.Raw[39:], hello.SessionId) // the location of session ID
|
||
|
binary.BigEndian.PutUint64(hello.SessionId, uint64(time.Now().Unix()))
|
||
|
hello.SessionId[0] = core.Version_x
|
||
|
hello.SessionId[1] = core.Version_y
|
||
|
hello.SessionId[2] = core.Version_z
|
||
|
copy(hello.SessionId[8:], config.ShortId)
|
||
|
if config.Show {
|
||
|
fmt.Printf("REALITY localAddr: %v\thello.sessionId[:16]: %v\n", localAddr, hello.SessionId[:16])
|
||
|
}
|
||
|
uConn.AuthKey = uConn.HandshakeState.State13.EcdheParams.SharedKey(config.PublicKey)
|
||
|
if uConn.AuthKey == nil {
|
||
|
return nil, errors.New("REALITY: SharedKey == nil")
|
||
|
}
|
||
|
if _, err := hkdf.New(sha256.New, uConn.AuthKey, hello.Random[:20], []byte("REALITY")).Read(uConn.AuthKey); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
block, _ := aes.NewCipher(uConn.AuthKey)
|
||
|
aead, _ := cipher.NewGCM(block)
|
||
|
aead.Seal(hello.SessionId[:0], hello.Random[20:], hello.SessionId[:16], hello.Raw)
|
||
|
copy(hello.Raw[39:], hello.SessionId)
|
||
|
if config.Show {
|
||
|
fmt.Printf("REALITY localAddr: %v\thello.sessionId: %v\n", localAddr, hello.SessionId)
|
||
|
fmt.Printf("REALITY localAddr: %v\tuConn.AuthKey: %v\n", localAddr, uConn.AuthKey)
|
||
|
}
|
||
|
}
|
||
|
if err := uConn.Handshake(); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
if config.Show {
|
||
|
fmt.Printf("REALITY localAddr: %v\tuConn.Verified: %v\n", localAddr, uConn.Verified)
|
||
|
}
|
||
|
if !uConn.Verified {
|
||
|
go func() {
|
||
|
client := &http.Client{
|
||
|
Transport: &http2.Transport{
|
||
|
DialTLSContext: func(ctx context.Context, network, addr string, cfg *gotls.Config) (net.Conn, error) {
|
||
|
fmt.Printf("REALITY localAddr: %v\tDialTLSContext\n", localAddr)
|
||
|
return uConn, nil
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
prefix := []byte("https://" + uConn.ServerName)
|
||
|
maps.Lock()
|
||
|
if maps.maps == nil {
|
||
|
maps.maps = make(map[string]map[string]bool)
|
||
|
}
|
||
|
paths := maps.maps[uConn.ServerName]
|
||
|
if paths == nil {
|
||
|
paths = make(map[string]bool)
|
||
|
paths[config.SpiderX] = true
|
||
|
maps.maps[uConn.ServerName] = paths
|
||
|
}
|
||
|
firstURL := string(prefix) + getPathLocked(paths)
|
||
|
maps.Unlock()
|
||
|
get := func(first bool) {
|
||
|
var (
|
||
|
req *http.Request
|
||
|
resp *http.Response
|
||
|
err error
|
||
|
body []byte
|
||
|
)
|
||
|
if first {
|
||
|
req, _ = http.NewRequest("GET", firstURL, nil)
|
||
|
} else {
|
||
|
maps.Lock()
|
||
|
req, _ = http.NewRequest("GET", string(prefix)+getPathLocked(paths), nil)
|
||
|
maps.Unlock()
|
||
|
}
|
||
|
req.Header.Set("User-Agent", fingerprint.Client) // TODO: User-Agent map
|
||
|
if first && config.Show {
|
||
|
fmt.Printf("REALITY localAddr: %v\treq.UserAgent(): %v\n", localAddr, req.UserAgent())
|
||
|
}
|
||
|
times := 1
|
||
|
if !first {
|
||
|
times = int(randBetween(config.SpiderY[4], config.SpiderY[5]))
|
||
|
}
|
||
|
for j := 0; j < times; j++ {
|
||
|
if !first && j == 0 {
|
||
|
req.Header.Set("Referer", firstURL)
|
||
|
}
|
||
|
req.AddCookie(&http.Cookie{Name: "padding", Value: strings.Repeat("0", int(randBetween(config.SpiderY[0], config.SpiderY[1])))})
|
||
|
if resp, err = client.Do(req); err != nil {
|
||
|
break
|
||
|
}
|
||
|
req.Header.Set("Referer", req.URL.String())
|
||
|
if body, err = io.ReadAll(resp.Body); err != nil {
|
||
|
break
|
||
|
}
|
||
|
maps.Lock()
|
||
|
for _, m := range href.FindAllSubmatch(body, -1) {
|
||
|
m[1] = bytes.TrimPrefix(m[1], prefix)
|
||
|
if !bytes.Contains(m[1], dot) {
|
||
|
paths[string(m[1])] = true
|
||
|
}
|
||
|
}
|
||
|
req.URL.Path = getPathLocked(paths)
|
||
|
if config.Show {
|
||
|
fmt.Printf("REALITY localAddr: %v\treq.Referer(): %v\n", localAddr, req.Referer())
|
||
|
fmt.Printf("REALITY localAddr: %v\tlen(body): %v\n", localAddr, len(body))
|
||
|
fmt.Printf("REALITY localAddr: %v\tlen(paths): %v\n", localAddr, len(paths))
|
||
|
}
|
||
|
maps.Unlock()
|
||
|
if !first {
|
||
|
time.Sleep(time.Duration(randBetween(config.SpiderY[6], config.SpiderY[7])) * time.Millisecond) // interval
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
get(true)
|
||
|
concurrency := int(randBetween(config.SpiderY[2], config.SpiderY[3]))
|
||
|
for i := 0; i < concurrency; i++ {
|
||
|
go get(false)
|
||
|
}
|
||
|
// Do not close the connection
|
||
|
}()
|
||
|
time.Sleep(time.Duration(randBetween(config.SpiderY[8], config.SpiderY[9])) * time.Millisecond) // return
|
||
|
return nil, errors.New("REALITY: processed invalid connection")
|
||
|
}
|
||
|
return uConn, nil
|
||
|
}
|
||
|
|
||
|
var href = regexp.MustCompile(`href="([/h].*?)"`)
|
||
|
var dot = []byte(".")
|
||
|
|
||
|
var maps struct {
|
||
|
sync.Mutex
|
||
|
maps map[string]map[string]bool
|
||
|
}
|
||
|
|
||
|
func getPathLocked(paths map[string]bool) string {
|
||
|
stopAt := int(randBetween(0, int64(len(paths)-1)))
|
||
|
i := 0
|
||
|
for s := range paths {
|
||
|
if i == stopAt {
|
||
|
return s
|
||
|
}
|
||
|
i++
|
||
|
}
|
||
|
return "/"
|
||
|
}
|
||
|
|
||
|
func randBetween(left int64, right int64) int64 {
|
||
|
if left == right {
|
||
|
return left
|
||
|
}
|
||
|
bigInt, _ := rand.Int(rand.Reader, big.NewInt(right-left))
|
||
|
return left + bigInt.Int64()
|
||
|
}
|