refactor: simplify browser dialer static state and remove sockopt browserDialer parsing

Agent-Logs-Url: https://github.com/XTLS/Xray-core/sessions/4875f50c-9a90-4d34-afbe-2e629296faa0

Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-04-26 18:38:07 +00:00
committed by GitHub
parent 1cc7349529
commit 266ae17654
4 changed files with 62 additions and 54 deletions
-8
View File
@@ -1075,15 +1075,10 @@ type SocketConfig struct {
AddressPortStrategy string `json:"addressPortStrategy"` AddressPortStrategy string `json:"addressPortStrategy"`
HappyEyeballsSettings *HappyEyeballsConfig `json:"happyEyeballs"` HappyEyeballsSettings *HappyEyeballsConfig `json:"happyEyeballs"`
TrustedXForwardedFor []string `json:"trustedXForwardedFor"` TrustedXForwardedFor []string `json:"trustedXForwardedFor"`
BrowserDialer string `json:"browserDialer"`
} }
// Build implements Buildable. // Build implements Buildable.
func (c *SocketConfig) Build() (*internet.SocketConfig, error) { func (c *SocketConfig) Build() (*internet.SocketConfig, error) {
if c.BrowserDialer != "" {
return nil, errors.PrintRemovedFeatureError("sockopt.browserDialer", "root browserDialers + sockopt.dialerProxy")
}
tfo := int32(0) // don't invoke setsockopt() for TFO tfo := int32(0) // don't invoke setsockopt() for TFO
if c.TFO != nil { if c.TFO != nil {
switch v := c.TFO.(type) { switch v := c.TFO.(type) {
@@ -1976,9 +1971,6 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
} }
config.ProtocolName = protocol config.ProtocolName = protocol
} }
if c.SocketSettings != nil && c.SocketSettings.BrowserDialer != "" {
return nil, errors.PrintRemovedFeatureError("sockopt.browserDialer", "root browserDialers + sockopt.dialerProxy")
}
if c.SocketSettings != nil && c.SocketSettings.DialerProxy != "" { if c.SocketSettings != nil && c.SocketSettings.DialerProxy != "" {
if _, ok := browser_dialer.GetAddressByTag(c.SocketSettings.DialerProxy); ok { if _, ok := browser_dialer.GetAddressByTag(c.SocketSettings.DialerProxy); ok {
if config.ProtocolName != "websocket" && config.ProtocolName != "splithttp" { if config.ProtocolName != "websocket" && config.ProtocolName != "splithttp" {
+60 -44
View File
@@ -13,6 +13,7 @@ import (
pathlib "path" pathlib "path"
"strings" "strings"
"sync" "sync"
"sync/atomic"
"time" "time"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
@@ -31,10 +32,11 @@ type task struct {
StreamResponse bool `json:"streamResponse"` StreamResponse bool `json:"streamResponse"`
} }
var sockoptDialers map[string]*dialerInstance var dialersByAddress = map[string]*dialerInstance{}
var dialerServers map[string]*dialerServer var serversByListenAddr = map[string]*dialerServer{}
var dialerTags map[string]string var addressByTag atomic.Value
var mu sync.RWMutex var initMu sync.Mutex
var initialized bool
const browserDialerSubprotocol = "browser-dialer" const browserDialerSubprotocol = "browser-dialer"
@@ -47,18 +49,12 @@ var upgrader = &websocket.Upgrader{
}, },
} }
func HasBrowserDialerWithAddress(addr string) bool {
_, _, ok := parseBrowserDialerAddress(addr)
return ok
}
func GetAddressByTag(tag string) (string, bool) { func GetAddressByTag(tag string) (string, bool) {
if tag == "" { if tag == "" {
return "", false return "", false
} }
mu.RLock() tags, _ := addressByTag.Load().(map[string]string)
defer mu.RUnlock() addr, ok := tags[tag]
addr, ok := dialerTags[tag]
return addr, ok return addr, ok
} }
@@ -71,6 +67,13 @@ func CheckLegacyEnv() error {
} }
func ConfigureDialerTags(tags map[string]string) error { func ConfigureDialerTags(tags map[string]string) error {
initMu.Lock()
defer initMu.Unlock()
if initialized {
return errors.New("browserDialers does not support dynamic add/remove; restart is required after changing configuration")
}
if err := CheckLegacyEnv(); err != nil { if err := CheckLegacyEnv(); err != nil {
return err return err
} }
@@ -97,9 +100,7 @@ func ConfigureDialerTags(tags map[string]string) error {
listenAddrByPort[port] = listenAddr listenAddrByPort[port] = listenAddr
next[tag] = addr next[tag] = addr
} }
mu.RLock() for existingAddr := range serversByListenAddr {
defer mu.RUnlock()
for existingAddr := range dialerServers {
_, existingPort, splitErr := net.SplitHostPort(existingAddr) _, existingPort, splitErr := net.SplitHostPort(existingAddr)
if splitErr != nil { if splitErr != nil {
continue continue
@@ -113,10 +114,13 @@ func ConfigureDialerTags(tags map[string]string) error {
return errors.New("failed to initialize browserDialers listener for tag ", tag).Base(err) return errors.New("failed to initialize browserDialers listener for tag ", tag).Base(err)
} }
} }
for listenAddr, server := range serversByListenAddr {
mu.Lock() if err := server.start(); err != nil {
dialerTags = next return errors.New("failed to start browserDialers listener on ", listenAddr).Base(err)
mu.Unlock() }
}
addressByTag.Store(next)
initialized = true
return nil return nil
} }
@@ -132,6 +136,7 @@ type dialerInstance struct {
type dialerServer struct { type dialerServer struct {
server *http.Server server *http.Server
pageRoutes map[string]*dialerInstance pageRoutes map[string]*dialerInstance
started bool
} }
func parseBrowserDialerAddress(addr string) (string, string, bool) { func parseBrowserDialerAddress(addr string) (string, string, bool) {
@@ -178,9 +183,7 @@ func newDialerServer(listenAddr string) (*dialerServer, error) {
dialer.server = &http.Server{ dialer.server = &http.Server{
Addr: listenAddr, Addr: listenAddr,
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
mu.RLock()
pageDialer := dialer.pageRoutes[r.URL.Path] pageDialer := dialer.pageRoutes[r.URL.Path]
mu.RUnlock()
if pageDialer != nil && websocket.IsWebSocketUpgrade(r) { if pageDialer != nil && websocket.IsWebSocketUpgrade(r) {
ok := false ok := false
@@ -213,16 +216,24 @@ func newDialerServer(listenAddr string) (*dialerServer, error) {
closeConnection(w) closeConnection(w)
}), }),
} }
listener, err := net.Listen("tcp", listenAddr) return dialer, nil
if err != nil { }
return nil, err
func (d *dialerServer) start() error {
if d.started {
return nil
} }
listener, err := net.Listen("tcp", d.server.Addr)
if err != nil {
return err
}
d.started = true
go func() { go func() {
if err := dialer.server.Serve(listener); err != nil && !stderrors.Is(err, http.ErrServerClosed) { if err := d.server.Serve(listener); err != nil && !stderrors.Is(err, http.ErrServerClosed) {
errors.LogError(context.Background(), "Browser dialer http server unexpected error on ", dialer.server.Addr, ": ", err) errors.LogError(context.Background(), "Browser dialer http server unexpected error on ", d.server.Addr, ": ", err)
} }
}() }()
return dialer, nil return nil
} }
func closeConnection(w http.ResponseWriter) { func closeConnection(w http.ResponseWriter) {
@@ -238,6 +249,18 @@ func closeConnection(w http.ResponseWriter) {
} }
func getDialerByAddress(addr string) (*dialerInstance, error) { func getDialerByAddress(addr string) (*dialerInstance, error) {
listenAddr, path, ok := parseBrowserDialerAddress(addr)
if !ok {
return nil, errors.New("invalid browserDialers url: ", addr)
}
key := listenAddr + path
if dialer, found := dialersByAddress[key]; found {
return dialer, nil
}
return nil, errors.New("browser dialer is not configured for browserDialers url: ", addr)
}
func ensureDialerWithAddress(addr string) (*dialerInstance, error) {
listenAddr, path, ok := parseBrowserDialerAddress(addr) listenAddr, path, ok := parseBrowserDialerAddress(addr)
if !ok { if !ok {
return nil, errors.New("invalid browserDialers url: ", addr) return nil, errors.New("invalid browserDialers url: ", addr)
@@ -248,23 +271,13 @@ func getDialerByAddress(addr string) (*dialerInstance, error) {
} }
key := listenAddr + path key := listenAddr + path
if dialer, found := dialersByAddress[key]; found {
mu.Lock()
defer mu.Unlock()
if sockoptDialers == nil {
sockoptDialers = make(map[string]*dialerInstance)
}
if dialerServers == nil {
dialerServers = make(map[string]*dialerServer)
}
if dialer, found := sockoptDialers[key]; found {
return dialer, nil return dialer, nil
} }
server, found := dialerServers[listenAddr] server, found := serversByListenAddr[listenAddr]
if !found { if !found {
for existingAddr := range dialerServers { for existingAddr := range serversByListenAddr {
_, existingPort, splitErr := net.SplitHostPort(existingAddr) _, existingPort, splitErr := net.SplitHostPort(existingAddr)
if splitErr == nil && existingPort == port { if splitErr == nil && existingPort == port {
return nil, errors.New("browserDialers cannot use the same port with a different listen address: ", existingAddr, " and ", listenAddr) return nil, errors.New("browserDialers cannot use the same port with a different listen address: ", existingAddr, " and ", listenAddr)
@@ -275,14 +288,14 @@ func getDialerByAddress(addr string) (*dialerInstance, error) {
return nil, serverErr return nil, serverErr
} }
server = newServer server = newServer
dialerServers[listenAddr] = server serversByListenAddr[listenAddr] = server
} }
dialer := &dialerInstance{ dialer := &dialerInstance{
conns: make(chan *websocket.Conn, 256), conns: make(chan *websocket.Conn, 256),
page: bytes.ReplaceAll(webpage, []byte("dialerPath"), []byte(strings.TrimPrefix(path, "/"))), page: bytes.ReplaceAll(webpage, []byte("dialerPath"), []byte(strings.TrimPrefix(path, "/"))),
} }
sockoptDialers[key] = dialer dialersByAddress[key] = dialer
server.pageRoutes[path] = dialer server.pageRoutes[path] = dialer
return dialer, nil return dialer, nil
} }
@@ -291,7 +304,7 @@ func EnsureDialerWithAddress(addr string) error {
if addr == "" { if addr == "" {
return nil return nil
} }
_, err := getDialerByAddress(addr) _, err := ensureDialerWithAddress(addr)
return err return err
} }
@@ -393,7 +406,10 @@ func dialTaskWithAddress(addr string, task task) (*websocket.Conn, error) {
return nil, errors.New("browser dialer is not configured; set root browserDialers and use sockopt.dialerProxy tag") return nil, errors.New("browser dialer is not configured; set root browserDialers and use sockopt.dialerProxy tag")
} }
dialer, err := getDialerByAddress(addr) dialer, err := getDialerByAddress(addr)
if err != nil || dialer == nil { if err != nil {
return nil, err
}
if dialer == nil {
return nil, errors.New("browser dialer is not configured for browserDialers url: ", addr) return nil, errors.New("browser dialer is not configured for browserDialers url: ", addr)
} }
conns := dialer.conns conns := dialer.conns
+1 -1
View File
@@ -68,7 +68,7 @@ func getHTTPClient(ctx context.Context, dest net.Destination, streamSettings *in
} }
} }
if browser_dialer.HasBrowserDialerWithAddress(browserDialer) && realityConfig == nil { if browserDialer != "" && realityConfig == nil {
transportConfig := streamSettings.ProtocolSettings.(*Config) transportConfig := streamSettings.ProtocolSettings.(*Config)
if transportConfig.Mode != "auto" && transportConfig.Mode != "packet-up" { if transportConfig.Mode != "auto" && transportConfig.Mode != "packet-up" {
return &errorDialerClient{ return &errorDialerClient{
+1 -1
View File
@@ -123,7 +123,7 @@ func dialWebSocket(ctx context.Context, dest net.Destination, streamSettings *in
browserDialer = taggedDialer browserDialer = taggedDialer
} }
} }
if browser_dialer.HasBrowserDialerWithAddress(browserDialer) { if browserDialer != "" {
conn, err := browser_dialer.DialWSWithAddress(browserDialer, uri, ed) conn, err := browser_dialer.DialWSWithAddress(browserDialer, uri, ed)
if err != nil { if err != nil {
return nil, err return nil, err