mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-07-03 02:08:45 +00:00
feat: validate and initialize browser dialer at config build
Agent-Logs-Url: https://github.com/XTLS/Xray-core/sessions/d0035ff5-3633-402f-890e-e68c267a65c1 Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
9ad099774a
commit
aeb689284c
@@ -0,0 +1,63 @@
|
|||||||
|
package conf_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
. "github.com/xtls/xray-core/infra/conf"
|
||||||
|
)
|
||||||
|
|
||||||
|
const testBrowserDialerPath = "/123e4567-e89b-12d3-a456-426614174000"
|
||||||
|
|
||||||
|
func TestStreamConfigBuildRejectsBrowserDialerUnsupportedProtocol(t *testing.T) {
|
||||||
|
network := TransportProtocol("tcp")
|
||||||
|
config := &StreamConfig{
|
||||||
|
Network: &network,
|
||||||
|
SocketSettings: &SocketConfig{
|
||||||
|
BrowserDialer: "127.0.0.1:18080" + testBrowserDialerPath,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := config.Build()
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "sockopt.browserDialer only supports WS or XHTTP") {
|
||||||
|
t.Fatalf("expected unsupported protocol error, got: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStreamConfigBuildRejectsBrowserDialerWithREALITY(t *testing.T) {
|
||||||
|
network := TransportProtocol("splithttp")
|
||||||
|
config := &StreamConfig{
|
||||||
|
Network: &network,
|
||||||
|
Security: "reality",
|
||||||
|
SocketSettings: &SocketConfig{
|
||||||
|
BrowserDialer: "127.0.0.1:18081" + testBrowserDialerPath,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := config.Build()
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "sockopt.browserDialer does not support REALITY") {
|
||||||
|
t.Fatalf("expected REALITY rejection, got: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStreamConfigBuildFailsOnBrowserDialerAddressConflict(t *testing.T) {
|
||||||
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to prepare occupied listener: %v", err)
|
||||||
|
}
|
||||||
|
defer listener.Close()
|
||||||
|
|
||||||
|
network := TransportProtocol("websocket")
|
||||||
|
config := &StreamConfig{
|
||||||
|
Network: &network,
|
||||||
|
SocketSettings: &SocketConfig{
|
||||||
|
BrowserDialer: listener.Addr().String() + testBrowserDialerPath,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = config.Build()
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "Failed to start Browser Dialer listener") {
|
||||||
|
t.Fatalf("expected address conflict error, got: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@ import (
|
|||||||
"github.com/xtls/xray-core/common/platform/filesystem"
|
"github.com/xtls/xray-core/common/platform/filesystem"
|
||||||
"github.com/xtls/xray-core/common/serial"
|
"github.com/xtls/xray-core/common/serial"
|
||||||
"github.com/xtls/xray-core/transport/internet"
|
"github.com/xtls/xray-core/transport/internet"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/browser_dialer"
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask/fragment"
|
"github.com/xtls/xray-core/transport/internet/finalmask/fragment"
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask/header/custom"
|
"github.com/xtls/xray-core/transport/internet/finalmask/header/custom"
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask/header/dns"
|
"github.com/xtls/xray-core/transport/internet/finalmask/header/dns"
|
||||||
@@ -1972,6 +1973,14 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
|
|||||||
}
|
}
|
||||||
config.ProtocolName = protocol
|
config.ProtocolName = protocol
|
||||||
}
|
}
|
||||||
|
if c.SocketSettings != nil && c.SocketSettings.BrowserDialer != "" {
|
||||||
|
if config.ProtocolName != "websocket" && config.ProtocolName != "splithttp" {
|
||||||
|
return nil, errors.New("sockopt.browserDialer only supports WS or XHTTP")
|
||||||
|
}
|
||||||
|
if strings.EqualFold(c.Security, "reality") {
|
||||||
|
return nil, errors.New("sockopt.browserDialer does not support REALITY")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch strings.ToLower(c.Security) {
|
switch strings.ToLower(c.Security) {
|
||||||
case "", "none":
|
case "", "none":
|
||||||
@@ -2088,6 +2097,9 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("Failed to build sockopt.").Base(err)
|
return nil, errors.New("Failed to build sockopt.").Base(err)
|
||||||
}
|
}
|
||||||
|
if err := browser_dialer.EnsureDialerWithAddress(ss.BrowserDialer); err != nil {
|
||||||
|
return nil, errors.New("Failed to start Browser Dialer listener.").Base(err)
|
||||||
|
}
|
||||||
config.SocketSettings = ss
|
config.SocketSettings = ss
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
pathlib "path"
|
pathlib "path"
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -19,6 +18,7 @@ import (
|
|||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/platform"
|
"github.com/xtls/xray-core/common/platform"
|
||||||
|
"github.com/xtls/xray-core/common/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed dialer.html
|
//go:embed dialer.html
|
||||||
@@ -37,8 +37,6 @@ var mu sync.RWMutex
|
|||||||
|
|
||||||
const browserDialerSubprotocol = "browser-dialer"
|
const browserDialerSubprotocol = "browser-dialer"
|
||||||
|
|
||||||
var uuidPathPattern = regexp.MustCompile(`^/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$`)
|
|
||||||
|
|
||||||
var upgrader = &websocket.Upgrader{
|
var upgrader = &websocket.Upgrader{
|
||||||
ReadBufferSize: 0,
|
ReadBufferSize: 0,
|
||||||
WriteBufferSize: 0,
|
WriteBufferSize: 0,
|
||||||
@@ -66,6 +64,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
|
||||||
}
|
}
|
||||||
|
|
||||||
type browserDialerAddress struct {
|
type browserDialerAddress struct {
|
||||||
@@ -99,7 +98,15 @@ func parseBrowserDialerAddress(addr string) (*browserDialerAddress, bool) {
|
|||||||
if cleanPath == "." || cleanPath == "/" || cleanPath != path {
|
if cleanPath == "." || cleanPath == "/" || cleanPath != path {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
if !uuidPathPattern.MatchString(cleanPath) {
|
if strings.Count(cleanPath, "/") != 1 {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
id := strings.TrimPrefix(cleanPath, "/")
|
||||||
|
if len(id) != 36 {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
parsedUUID, err := uuid.ParseString(id)
|
||||||
|
if err != nil || !strings.EqualFold(parsedUUID.String(), id) {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,15 +183,20 @@ func closeConnection(w http.ResponseWriter) {
|
|||||||
conn.Close()
|
conn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func startDialerServer(dialer *dialerServer) {
|
func startDialerServer(dialer *dialerServer) error {
|
||||||
if dialer == nil || dialer.server == nil {
|
if dialer == nil || dialer.server == nil {
|
||||||
return
|
return nil
|
||||||
|
}
|
||||||
|
listener, err := net.Listen("tcp", dialer.server.Addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
if err := dialer.server.ListenAndServe(); err != nil && !stderrors.Is(err, http.ErrServerClosed) {
|
if err := dialer.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 ", dialer.server.Addr, ": ", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func closeDialerInstance(d *dialerInstance) {
|
func closeDialerInstance(d *dialerInstance) {
|
||||||
@@ -201,14 +213,15 @@ func closeDialerInstance(d *dialerInstance) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDialerByAddress(addr string) *dialerInstance {
|
func getDialerByAddress(addr string) (*dialerInstance, error) {
|
||||||
parsed, ok := parseBrowserDialerAddress(addr)
|
parsed, ok := parseBrowserDialerAddress(addr)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil, errors.New("invalid sockopt.browserDialer: ", addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
key := parsed.listenAddr + parsed.path
|
key := parsed.listenAddr + parsed.path
|
||||||
startServer := false
|
var server *dialerServer
|
||||||
|
var dialer *dialerInstance
|
||||||
|
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
if sockoptDialers == nil {
|
if sockoptDialers == nil {
|
||||||
@@ -219,26 +232,47 @@ func getDialerByAddress(addr string) *dialerInstance {
|
|||||||
}
|
}
|
||||||
if dialer, found := sockoptDialers[key]; found {
|
if dialer, found := sockoptDialers[key]; found {
|
||||||
mu.Unlock()
|
mu.Unlock()
|
||||||
return dialer
|
return dialer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
server, found := dialerServers[parsed.listenAddr]
|
found := false
|
||||||
|
server, found = dialerServers[parsed.listenAddr]
|
||||||
if !found {
|
if !found {
|
||||||
server = newDialerServer(parsed.listenAddr)
|
server = newDialerServer(parsed.listenAddr)
|
||||||
dialerServers[parsed.listenAddr] = server
|
dialerServers[parsed.listenAddr] = server
|
||||||
startServer = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dialer := newDialerInstance(parsed.path)
|
dialer = newDialerInstance(parsed.path)
|
||||||
sockoptDialers[key] = dialer
|
sockoptDialers[key] = dialer
|
||||||
server.pageRoutes[dialer.pagePath] = dialer
|
server.pageRoutes[dialer.pagePath] = dialer
|
||||||
|
startServer := !server.started
|
||||||
|
server.started = true
|
||||||
mu.Unlock()
|
mu.Unlock()
|
||||||
|
|
||||||
if startServer {
|
if startServer {
|
||||||
startDialerServer(server)
|
if err := startDialerServer(server); err != nil {
|
||||||
|
mu.Lock()
|
||||||
|
delete(sockoptDialers, key)
|
||||||
|
delete(server.pageRoutes, dialer.pagePath)
|
||||||
|
if len(server.pageRoutes) == 0 {
|
||||||
|
delete(dialerServers, parsed.listenAddr)
|
||||||
|
server.started = false
|
||||||
|
}
|
||||||
|
mu.Unlock()
|
||||||
|
closeDialerInstance(dialer)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return dialer
|
return dialer, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func EnsureDialerWithAddress(addr string) error {
|
||||||
|
if addr == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
_, err := getDialerByAddress(addr)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func DialWS(uri string, ed []byte) (*websocket.Conn, error) {
|
func DialWS(uri string, ed []byte) (*websocket.Conn, error) {
|
||||||
@@ -396,8 +430,8 @@ func connsByAddress(addr string) chan *websocket.Conn {
|
|||||||
if addr == "" {
|
if addr == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
dialer := getDialerByAddress(addr)
|
dialer, err := getDialerByAddress(addr)
|
||||||
if dialer == nil {
|
if err != nil || dialer == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return dialer.conns
|
return dialer.conns
|
||||||
|
|||||||
Reference in New Issue
Block a user