feat: require browserDialer path and multiplex by path

Agent-Logs-Url: https://github.com/XTLS/Xray-core/sessions/d3d25e7b-e62f-49e0-90be-0ca7f974e115

Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-04-26 15:20:37 +00:00
committed by GitHub
parent 2691a1aa0e
commit c48c475256
2 changed files with 130 additions and 28 deletions
+129 -27
View File
@@ -9,6 +9,9 @@ import (
stderrors "errors" stderrors "errors"
"net" "net"
"net/http" "net/http"
"net/url"
pathlib "path"
"strings"
"sync" "sync"
"time" "time"
@@ -29,7 +32,8 @@ type task struct {
} }
var sockoptDialers map[string]*dialerInstance var sockoptDialers map[string]*dialerInstance
var mu sync.Mutex var dialerServers map[string]*dialerServer
var mu sync.RWMutex
var upgrader = &websocket.Upgrader{ var upgrader = &websocket.Upgrader{
ReadBufferSize: 0, ReadBufferSize: 0,
@@ -41,11 +45,8 @@ var upgrader = &websocket.Upgrader{
} }
func HasBrowserDialerWithAddress(addr string) bool { func HasBrowserDialerWithAddress(addr string) bool {
if addr == "" { _, ok := parseBrowserDialerAddress(addr)
return false return ok
}
_, _, err := net.SplitHostPort(addr)
return err == nil
} }
type webSocketExtra struct { type webSocketExtra struct {
@@ -53,39 +54,121 @@ type webSocketExtra struct {
} }
type dialerInstance struct { type dialerInstance struct {
conns chan *websocket.Conn conns chan *websocket.Conn
server *http.Server pagePath string
wsPath string
page []byte
} }
func newDialerInstance(addr string) *dialerInstance { type dialerServer struct {
server *http.Server
pageRoutes map[string]*dialerInstance
wsRoutes map[string]*dialerInstance
}
type browserDialerAddress struct {
listenAddr string
path string
}
func parseBrowserDialerAddress(addr string) (*browserDialerAddress, bool) {
if addr == "" {
return nil, false
}
index := strings.Index(addr, "/")
if index <= 0 {
return nil, false
}
listenAddr := addr[:index]
path := strings.TrimSuffix(addr[index:], "/")
if path == "" {
return nil, false
}
if _, _, err := net.SplitHostPort(listenAddr); err != nil {
return nil, false
}
parsedPath, err := url.ParseRequestURI(path)
if err != nil || parsedPath.RawQuery != "" || parsedPath.Fragment != "" {
return nil, false
}
cleanPath := pathlib.Clean(path)
if cleanPath == "." || cleanPath == "/" || cleanPath != path {
return nil, false
}
return &browserDialerAddress{
listenAddr: listenAddr,
path: cleanPath,
}, true
}
func newDialerInstance(path string) *dialerInstance {
token := uuid.New() token := uuid.New()
csrfToken := token.String() csrfToken := token.String()
page := bytes.ReplaceAll(webpage, []byte("csrfToken"), []byte(csrfToken)) escapedCsrfToken := url.PathEscape(csrfToken)
wsPath := "/websocket/" + csrfToken wsPath := path + "/" + escapedCsrfToken
page := bytes.ReplaceAll(webpage, []byte("dialerPath"), []byte(strings.TrimPrefix(path, "/")))
page = bytes.ReplaceAll(page, []byte("csrfToken"), []byte(escapedCsrfToken))
dialer := &dialerInstance{ dialer := &dialerInstance{
conns: make(chan *websocket.Conn, 256), conns: make(chan *websocket.Conn, 256),
pagePath: path,
wsPath: wsPath,
page: page,
}
return dialer
}
func newDialerServer(listenAddr string) *dialerServer {
dialer := &dialerServer{
pageRoutes: make(map[string]*dialerInstance),
wsRoutes: make(map[string]*dialerInstance),
} }
dialer.server = &http.Server{ dialer.server = &http.Server{
Addr: addr, Addr: listenAddr,
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == wsPath { mu.RLock()
wsDialer := dialer.wsRoutes[r.URL.Path]
pageDialer := dialer.pageRoutes[r.URL.Path]
mu.RUnlock()
if wsDialer != nil {
if conn, err := upgrader.Upgrade(w, r, nil); err == nil { if conn, err := upgrader.Upgrade(w, r, nil); err == nil {
dialer.conns <- conn wsDialer.conns <- conn
} else { } else {
errors.LogError(context.Background(), "Browser dialer http upgrade unexpected error: ", err) errors.LogError(context.Background(), "Browser dialer http upgrade unexpected error: ", err)
} }
return return
} }
w.Header().Set("Access-Control-Allow-Origin", "*")
if _, err := w.Write(page); err != nil { if pageDialer != nil {
errors.LogError(context.Background(), "Browser dialer http page write unexpected error: ", err) w.Header().Set("Access-Control-Allow-Origin", "*")
if _, err := w.Write(pageDialer.page); err != nil {
errors.LogError(context.Background(), "Browser dialer http page write unexpected error: ", err)
}
return
} }
closeConnection(w)
}), }),
} }
return dialer return dialer
} }
func startDialerInstance(dialer *dialerInstance) { func closeConnection(w http.ResponseWriter) {
hijacker, ok := w.(http.Hijacker)
if !ok {
return
}
conn, _, err := hijacker.Hijack()
if err != nil {
return
}
conn.Close()
}
func startDialerServer(dialer *dialerServer) {
if dialer == nil || dialer.server == nil { if dialer == nil || dialer.server == nil {
return return
} }
@@ -100,9 +183,6 @@ func closeDialerInstance(d *dialerInstance) {
if d == nil { if d == nil {
return return
} }
if d.server != nil {
d.server.Close()
}
for { for {
select { select {
case c := <-d.conns: case c := <-d.conns:
@@ -114,21 +194,43 @@ func closeDialerInstance(d *dialerInstance) {
} }
func getDialerByAddress(addr string) *dialerInstance { func getDialerByAddress(addr string) *dialerInstance {
if addr == "" { parsed, ok := parseBrowserDialerAddress(addr)
if !ok {
return nil return nil
} }
key := parsed.listenAddr + parsed.path
startServer := false
mu.Lock() mu.Lock()
if sockoptDialers == nil { if sockoptDialers == nil {
sockoptDialers = make(map[string]*dialerInstance) sockoptDialers = make(map[string]*dialerInstance)
} }
if dialer, found := sockoptDialers[addr]; found { if dialerServers == nil {
dialerServers = make(map[string]*dialerServer)
}
if dialer, found := sockoptDialers[key]; found {
mu.Unlock() mu.Unlock()
return dialer return dialer
} }
dialer := newDialerInstance(addr)
sockoptDialers[addr] = dialer server, found := dialerServers[parsed.listenAddr]
if !found {
server = newDialerServer(parsed.listenAddr)
dialerServers[parsed.listenAddr] = server
startServer = true
}
dialer := newDialerInstance(parsed.path)
sockoptDialers[key] = dialer
server.pageRoutes[dialer.pagePath] = dialer
server.wsRoutes[dialer.wsPath] = dialer
mu.Unlock() mu.Unlock()
startDialerInstance(dialer)
if startServer {
startDialerServer(server)
}
return dialer return dialer
} }
@@ -10,7 +10,7 @@
// Enable a much more aggressive JIT for performance gains // Enable a much more aggressive JIT for performance gains
// Copyright (c) 2021 XRAY. Mozilla Public License 2.0. // Copyright (c) 2021 XRAY. Mozilla Public License 2.0.
let url = "ws://" + window.location.host + "/websocket/csrfToken"; let url = "ws://" + window.location.host + "/dialerPath/csrfToken";
let clientIdleCount = 0; let clientIdleCount = 0;
let upstreamGetCount = 0; let upstreamGetCount = 0;
let upstreamWsCount = 0; let upstreamWsCount = 0;