mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-10-02 22:06:39 +00:00
Compare commits
44
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e088cc4a25 | ||
|
|
41bc47b1ec | ||
|
|
8043924af5 | ||
|
|
3a826b7ece | ||
|
|
ccac265911 | ||
|
|
c423317305 | ||
|
|
286a702bfa | ||
|
|
9d4dd2c32f | ||
|
|
8939657016 | ||
|
|
f1f0d5970a | ||
|
|
3004e1e399 | ||
|
|
2ecfbcf1b6 | ||
|
|
9f8f5c2591 | ||
|
|
bb79f554dd | ||
|
|
be9a229d68 | ||
|
|
7416fd225e | ||
|
|
e79f3a4361 | ||
|
|
9421ac0b13 | ||
|
|
7a9c592221 | ||
|
|
266ae17654 | ||
|
|
1cc7349529 | ||
|
|
61c39a2834 | ||
|
|
1d13700481 | ||
|
|
ca3cd5fb88 | ||
|
|
97ad6cef43 | ||
|
|
181193581b | ||
|
|
5906445c68 | ||
|
|
a54c54a559 | ||
|
|
64f783f2b0 | ||
|
|
12ecf47fcc | ||
|
|
4636ca2675 | ||
|
|
5afd664c8b | ||
|
|
1d4250e6f0 | ||
|
|
e06c536ce6 | ||
|
|
aeb689284c | ||
|
|
9ad099774a | ||
|
|
8fca7745fb | ||
|
|
57253b736d | ||
|
|
c48c475256 | ||
|
|
2691a1aa0e | ||
|
|
84d04d0eab | ||
|
|
27b8b29ef8 | ||
|
|
2fdfa727b4 | ||
|
|
c2116bb869 |
@@ -18,6 +18,7 @@ import (
|
|||||||
"github.com/xtls/xray-core/features/routing"
|
"github.com/xtls/xray-core/features/routing"
|
||||||
"github.com/xtls/xray-core/features/stats"
|
"github.com/xtls/xray-core/features/stats"
|
||||||
"github.com/xtls/xray-core/transport/internet"
|
"github.com/xtls/xray-core/transport/internet"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/browser_dialer"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Server is an instance of Xray. At any time, there must be at most one Server instance running.
|
// Server is an instance of Xray. At any time, there must be at most one Server instance running.
|
||||||
@@ -262,6 +263,9 @@ func (s *Instance) Close() error {
|
|||||||
s.running = false
|
s.running = false
|
||||||
|
|
||||||
var errs []interface{}
|
var errs []interface{}
|
||||||
|
if err := browser_dialer.StopCollectedDialerProxyURLs(); err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
for _, f := range s.features {
|
for _, f := range s.features {
|
||||||
if err := f.Close(); err != nil {
|
if err := f.Close(); err != nil {
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
@@ -385,9 +389,16 @@ func (s *Instance) Start() error {
|
|||||||
s.statusLock.Lock()
|
s.statusLock.Lock()
|
||||||
defer s.statusLock.Unlock()
|
defer s.statusLock.Unlock()
|
||||||
|
|
||||||
|
if err := browser_dialer.StartCollectedDialerProxyURLs(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
s.running = true
|
s.running = true
|
||||||
for _, f := range s.features {
|
for _, f := range s.features {
|
||||||
if err := f.Start(); err != nil {
|
if err := f.Start(); err != nil {
|
||||||
|
s.running = false
|
||||||
|
if stopErr := browser_dialer.StopCollectedDialerProxyURLs(); stopErr != nil {
|
||||||
|
return errors.New("browser dialer cleanup after startup failure also failed: ", stopErr).Base(err)
|
||||||
|
}
|
||||||
return err
|
return 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"
|
||||||
@@ -1970,6 +1971,35 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
|
|||||||
}
|
}
|
||||||
config.ProtocolName = protocol
|
config.ProtocolName = protocol
|
||||||
}
|
}
|
||||||
|
if c.SocketSettings != nil && c.SocketSettings.DialerProxy != "" {
|
||||||
|
if browser_dialer.IsBrowserDialerProxy(c.SocketSettings.DialerProxy) {
|
||||||
|
if config.ProtocolName != "websocket" && config.ProtocolName != "splithttp" {
|
||||||
|
return nil, errors.New("dialerProxy ", c.SocketSettings.DialerProxy, " only supports websocket or splithttp")
|
||||||
|
}
|
||||||
|
if strings.EqualFold(c.Security, "reality") {
|
||||||
|
return nil, errors.New("dialerProxy ", c.SocketSettings.DialerProxy, " does not support REALITY")
|
||||||
|
}
|
||||||
|
if config.ProtocolName == "splithttp" {
|
||||||
|
splitHTTPSettings := c.SplitHTTPSettings
|
||||||
|
if c.XHTTPSettings != nil {
|
||||||
|
splitHTTPSettings = c.XHTTPSettings
|
||||||
|
}
|
||||||
|
if splitHTTPSettings != nil {
|
||||||
|
splitHTTPSettingsCopy := *splitHTTPSettings
|
||||||
|
hs, err := splitHTTPSettingsCopy.Build()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("failed to build XHTTP config for browser dialer validation").Base(err)
|
||||||
|
}
|
||||||
|
if splitHTTPConfig, ok := hs.(*splithttp.Config); ok && splitHTTPConfig.Mode != "auto" && splitHTTPConfig.Mode != "packet-up" {
|
||||||
|
return nil, errors.New("dialerProxy ", c.SocketSettings.DialerProxy, " only supports XHTTP modes \"auto\" or \"packet-up\", got: \"", splitHTTPConfig.Mode, "\"")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := browser_dialer.RegisterDialerProxyURL(c.SocketSettings.DialerProxy); err != nil {
|
||||||
|
return nil, errors.New("failed to collect browser dialer URL").Base(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch strings.ToLower(c.Security) {
|
switch strings.ToLower(c.Security) {
|
||||||
case "", "none":
|
case "", "none":
|
||||||
|
|||||||
+7
-1
@@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/xtls/xray-core/common/serial"
|
"github.com/xtls/xray-core/common/serial"
|
||||||
core "github.com/xtls/xray-core/core"
|
core "github.com/xtls/xray-core/core"
|
||||||
"github.com/xtls/xray-core/transport/internet"
|
"github.com/xtls/xray-core/transport/internet"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/browser_dialer"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -437,7 +438,6 @@ func (c *Config) Override(o *Config, fn string) {
|
|||||||
if o.Geodata != nil {
|
if o.Geodata != nil {
|
||||||
c.Geodata = o.Geodata
|
c.Geodata = o.Geodata
|
||||||
}
|
}
|
||||||
|
|
||||||
// update the Inbound in slice if the only one in override config has same tag
|
// update the Inbound in slice if the only one in override config has same tag
|
||||||
if len(o.InboundConfigs) > 0 {
|
if len(o.InboundConfigs) > 0 {
|
||||||
for i := range o.InboundConfigs {
|
for i := range o.InboundConfigs {
|
||||||
@@ -604,6 +604,9 @@ func (c *Config) Build() (*core.Config, error) {
|
|||||||
if len(c.Transport) > 0 {
|
if len(c.Transport) > 0 {
|
||||||
return nil, errors.PrintRemovedFeatureError("Global transport config", "streamSettings in inbounds and outbounds")
|
return nil, errors.PrintRemovedFeatureError("Global transport config", "streamSettings in inbounds and outbounds")
|
||||||
}
|
}
|
||||||
|
if err := browser_dialer.BeginCollectingDialerProxyURLs(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
for _, rawInboundConfig := range inbounds {
|
for _, rawInboundConfig := range inbounds {
|
||||||
ic, err := rawInboundConfig.Build()
|
ic, err := rawInboundConfig.Build()
|
||||||
@@ -626,6 +629,9 @@ func (c *Config) Build() (*core.Config, error) {
|
|||||||
}
|
}
|
||||||
config.Outbound = append(config.Outbound, oc)
|
config.Outbound = append(config.Outbound, oc)
|
||||||
}
|
}
|
||||||
|
if err := browser_dialer.ConfigureCollectedDialerProxyURLs(); err != nil {
|
||||||
|
return nil, errors.New("failed to configure browser dialer").Base(err)
|
||||||
|
}
|
||||||
|
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,101 +1,33 @@
|
|||||||
package browser_dialer
|
package browser_dialer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"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/uuid"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed dialer.html
|
//go:embed dialer.html
|
||||||
var webpage []byte
|
var webpage []byte
|
||||||
|
|
||||||
type task struct {
|
type task struct {
|
||||||
Method string `json:"method"`
|
Method string `json:"method"`
|
||||||
URL string `json:"url"`
|
URL string `json:"url"`
|
||||||
Extra any `json:"extra,omitempty"`
|
Extra any `json:"extra,omitempty"`
|
||||||
StreamResponse bool `json:"streamResponse"`
|
StreamResponse bool `json:"streamResponse"`
|
||||||
}
|
|
||||||
|
|
||||||
var conns chan *websocket.Conn
|
|
||||||
var server *http.Server
|
|
||||||
var mu sync.Mutex
|
|
||||||
|
|
||||||
var upgrader = &websocket.Upgrader{
|
|
||||||
ReadBufferSize: 0,
|
|
||||||
WriteBufferSize: 0,
|
|
||||||
HandshakeTimeout: time.Second * 4,
|
|
||||||
CheckOrigin: func(r *http.Request) bool {
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Used by external projects when using xray as a go module
|
|
||||||
func Reload() {
|
|
||||||
addr := platform.NewEnvFlag(platform.BrowserDialerAddress).GetValue(func() string { return "" })
|
|
||||||
mu.Lock()
|
|
||||||
defer mu.Unlock()
|
|
||||||
|
|
||||||
if server != nil {
|
|
||||||
server.Close()
|
|
||||||
}
|
|
||||||
if HasBrowserDialer() {
|
|
||||||
for len(conns) > 0 {
|
|
||||||
select {
|
|
||||||
case c := <-conns:
|
|
||||||
c.Close()
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
conns = nil
|
|
||||||
}
|
|
||||||
if addr != "" {
|
|
||||||
token := uuid.New()
|
|
||||||
csrfToken := token.String()
|
|
||||||
webpage := bytes.ReplaceAll(webpage, []byte("csrfToken"), []byte(csrfToken))
|
|
||||||
conns = make(chan *websocket.Conn, 256)
|
|
||||||
server = &http.Server{
|
|
||||||
Addr: addr,
|
|
||||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.URL.Path == "/websocket" {
|
|
||||||
if r.URL.Query().Get("token") == csrfToken {
|
|
||||||
if conn, err := upgrader.Upgrade(w, r, nil); err == nil {
|
|
||||||
conns <- conn
|
|
||||||
} else {
|
|
||||||
errors.LogError(context.Background(), "Browser dialer http upgrade unexpected error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
w.Header().Set("Access-Control-Allow-Origin", "*");
|
|
||||||
w.Write(webpage)
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
go server.ListenAndServe()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func HasBrowserDialer() bool {
|
|
||||||
return conns != nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type webSocketExtra struct {
|
type webSocketExtra struct {
|
||||||
Protocol string `json:"protocol,omitempty"`
|
Protocol string `json:"protocol,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func DialWS(uri string, ed []byte) (*websocket.Conn, error) {
|
func DialWSWithAddress(addr string, uri string, ed []byte) (*websocket.Conn, error) {
|
||||||
task := task{
|
task := task{
|
||||||
Method: "WS",
|
Method: "WS",
|
||||||
URL: uri,
|
URL: uri,
|
||||||
StreamResponse: true,
|
StreamResponse: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,7 +37,7 @@ func DialWS(uri string, ed []byte) (*websocket.Conn, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return dialTask(task)
|
return dialTaskWithAddress(addr, task)
|
||||||
}
|
}
|
||||||
|
|
||||||
type httpExtra struct {
|
type httpExtra struct {
|
||||||
@@ -142,30 +74,26 @@ func httpExtraFromHeadersAndCookies(headers http.Header, cookies []*http.Cookie)
|
|||||||
return &extra
|
return &extra
|
||||||
}
|
}
|
||||||
|
|
||||||
func DialGet(uri string, headers http.Header, cookies []*http.Cookie) (*websocket.Conn, error) {
|
func DialGetWithAddress(addr string, uri string, headers http.Header, cookies []*http.Cookie) (*websocket.Conn, error) {
|
||||||
task := task{
|
task := task{
|
||||||
Method: "GET",
|
Method: "GET",
|
||||||
URL: uri,
|
URL: uri,
|
||||||
Extra: httpExtraFromHeadersAndCookies(headers, cookies),
|
Extra: httpExtraFromHeadersAndCookies(headers, cookies),
|
||||||
StreamResponse: true,
|
StreamResponse: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
return dialTask(task)
|
return dialTaskWithAddress(addr, task)
|
||||||
}
|
}
|
||||||
|
|
||||||
func DialPacket(method string, uri string, headers http.Header, cookies []*http.Cookie, payload []byte) error {
|
func DialPacketWithAddress(addr string, method string, uri string, headers http.Header, cookies []*http.Cookie, payload []byte) error {
|
||||||
return dialWithBody(method, uri, headers, cookies, payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
func dialWithBody(method string, uri string, headers http.Header, cookies []*http.Cookie, payload []byte) error {
|
|
||||||
task := task{
|
task := task{
|
||||||
Method: method,
|
Method: method,
|
||||||
URL: uri,
|
URL: uri,
|
||||||
Extra: httpExtraFromHeadersAndCookies(headers, cookies),
|
Extra: httpExtraFromHeadersAndCookies(headers, cookies),
|
||||||
StreamResponse: false,
|
StreamResponse: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, err := dialTask(task)
|
conn, err := dialTaskWithAddress(addr, task)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -184,12 +112,21 @@ func dialWithBody(method string, uri string, headers http.Header, cookies []*htt
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func dialTask(task task) (*websocket.Conn, error) {
|
func dialTaskWithAddress(addr string, task task) (*websocket.Conn, error) {
|
||||||
data, err := json.Marshal(task)
|
data, err := json.Marshal(task)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if addr == "" {
|
||||||
|
return nil, errors.New("browser dialer is not configured; set sockopt.dialerProxy to browser://host:port/uuid")
|
||||||
|
}
|
||||||
|
dialer, err := getDialerByAddress(addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
conns := dialer.conns
|
||||||
|
|
||||||
var conn *websocket.Conn
|
var conn *websocket.Conn
|
||||||
for {
|
for {
|
||||||
conn = <-conns
|
conn = <-conns
|
||||||
@@ -218,8 +155,3 @@ func CheckOK(conn *websocket.Conn) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
|
||||||
Reload()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -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?token=csrfToken";
|
let url = "ws://" + window.location.host + "/dialerPath";
|
||||||
let clientIdleCount = 0;
|
let clientIdleCount = 0;
|
||||||
let upstreamGetCount = 0;
|
let upstreamGetCount = 0;
|
||||||
let upstreamWsCount = 0;
|
let upstreamWsCount = 0;
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
}
|
}
|
||||||
clientIdleCount += 1;
|
clientIdleCount += 1;
|
||||||
console.log("Prepare", url);
|
console.log("Prepare", url);
|
||||||
let ws = new WebSocket(url);
|
let ws = new WebSocket(url, "browser-dialer");
|
||||||
// arraybuffer is significantly faster in chrome than default
|
// arraybuffer is significantly faster in chrome than default
|
||||||
// blob, tested with chrome 123
|
// blob, tested with chrome 123
|
||||||
ws.binaryType = "arraybuffer";
|
ws.binaryType = "arraybuffer";
|
||||||
|
|||||||
@@ -0,0 +1,318 @@
|
|||||||
|
package browser_dialer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
stderrors "errors"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
pathlib "path"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/common/platform"
|
||||||
|
"github.com/xtls/xray-core/common/serial"
|
||||||
|
"github.com/xtls/xray-core/common/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
var dialersByAddress = map[string]*dialerInstance{}
|
||||||
|
var serversByListenAddr = map[string]*dialerServer{}
|
||||||
|
var initialized bool
|
||||||
|
var pendingURLs map[string]struct{}
|
||||||
|
|
||||||
|
const browserDialerSubprotocol = "browser-dialer"
|
||||||
|
|
||||||
|
var upgrader = &websocket.Upgrader{
|
||||||
|
ReadBufferSize: 0,
|
||||||
|
WriteBufferSize: 0,
|
||||||
|
HandshakeTimeout: time.Second * 4,
|
||||||
|
CheckOrigin: func(r *http.Request) bool {
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckLegacyEnv() error {
|
||||||
|
envAddress := platform.NewEnvFlag(platform.BrowserDialerAddress).GetValue(func() string { return "" })
|
||||||
|
if envAddress == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.PrintRemovedFeatureError("env "+platform.BrowserDialerAddress, "sockopt.dialerProxy with browser://host:port/uuid")
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsBrowserDialerProxy(raw string) bool {
|
||||||
|
_, _, ok := parseBrowserDialerAddress(raw)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func BeginCollectingDialerProxyURLs() error {
|
||||||
|
if initialized {
|
||||||
|
return errors.New("browser dialer does not support dynamic add/remove; restart is required after changing configuration")
|
||||||
|
}
|
||||||
|
if err := CheckLegacyEnv(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pendingURLs = map[string]struct{}{}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterDialerProxyURL(raw string) error {
|
||||||
|
if !IsBrowserDialerProxy(raw) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if pendingURLs == nil {
|
||||||
|
return errors.New("browser dialer url collection is not initialized")
|
||||||
|
}
|
||||||
|
pendingURLs[raw] = struct{}{}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConfigureCollectedDialerProxyURLs() error {
|
||||||
|
if initialized {
|
||||||
|
return errors.New("browser dialer does not support dynamic add/remove; restart is required after changing configuration")
|
||||||
|
}
|
||||||
|
if err := CheckLegacyEnv(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
listenAddrByPort := make(map[string]string, len(pendingURLs))
|
||||||
|
for browserDialerURL := range pendingURLs {
|
||||||
|
listenAddr, _, ok := parseBrowserDialerAddress(browserDialerURL)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("invalid browser dialer url: ", browserDialerURL)
|
||||||
|
}
|
||||||
|
_, port, err := net.SplitHostPort(listenAddr)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("invalid browser dialer listen address: ", listenAddr)
|
||||||
|
}
|
||||||
|
if existingAddr, found := listenAddrByPort[port]; found && existingAddr != listenAddr {
|
||||||
|
return errors.New("browser dialer cannot use the same port with a different listen address: ", existingAddr, " and ", listenAddr)
|
||||||
|
}
|
||||||
|
listenAddrByPort[port] = listenAddr
|
||||||
|
}
|
||||||
|
for existingAddr := range serversByListenAddr {
|
||||||
|
_, existingPort, splitErr := net.SplitHostPort(existingAddr)
|
||||||
|
if splitErr != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if newAddr, found := listenAddrByPort[existingPort]; found && newAddr != existingAddr {
|
||||||
|
return errors.New("browser dialer cannot use the same port with a different listen address: ", existingAddr, " and ", newAddr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for browserDialerURL := range pendingURLs {
|
||||||
|
if _, err := ensureDialerWithAddress(browserDialerURL); err != nil {
|
||||||
|
return errors.New("failed to initialize browser dialer listener for url ", browserDialerURL).Base(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartCollectedDialerProxyURLs starts listeners for browser dialer URLs
|
||||||
|
// prepared by ConfigureCollectedDialerProxyURLs.
|
||||||
|
// Subsequent calls after successful initialization return nil.
|
||||||
|
func StartCollectedDialerProxyURLs() error {
|
||||||
|
if initialized {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for listenAddr, server := range serversByListenAddr {
|
||||||
|
if err := server.start(); err != nil {
|
||||||
|
return errors.New("failed to start browser dialer listener on ", listenAddr).Base(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
initialized = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func StopCollectedDialerProxyURLs() error {
|
||||||
|
var stopErrs []string
|
||||||
|
for listenAddr, server := range serversByListenAddr {
|
||||||
|
if err := server.stop(); err != nil {
|
||||||
|
stopErrs = append(stopErrs, serial.Concat("failed to stop browser dialer listener on ", listenAddr, ": ", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dialersByAddress = map[string]*dialerInstance{}
|
||||||
|
serversByListenAddr = map[string]*dialerServer{}
|
||||||
|
pendingURLs = nil
|
||||||
|
initialized = false
|
||||||
|
if len(stopErrs) > 0 {
|
||||||
|
return errors.New(strings.Join(stopErrs, "; "))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type dialerInstance struct {
|
||||||
|
conns chan *websocket.Conn
|
||||||
|
page []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type dialerServer struct {
|
||||||
|
server *http.Server
|
||||||
|
pageRoutes map[string]*dialerInstance
|
||||||
|
started bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseBrowserDialerAddress(addr string) (string, string, bool) {
|
||||||
|
if addr == "" {
|
||||||
|
return "", "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedAddr, err := url.Parse(addr)
|
||||||
|
if err != nil || !strings.EqualFold(parsedAddr.Scheme, "browser") || parsedAddr.Host == "" || parsedAddr.Path == "" || parsedAddr.RawQuery != "" || parsedAddr.Fragment != "" {
|
||||||
|
return "", "", false
|
||||||
|
}
|
||||||
|
listenAddr := parsedAddr.Host
|
||||||
|
if _, _, err := net.SplitHostPort(listenAddr); err != nil {
|
||||||
|
return "", "", false
|
||||||
|
}
|
||||||
|
path := strings.TrimSuffix(parsedAddr.Path, "/")
|
||||||
|
if !strings.HasPrefix(path, "/") {
|
||||||
|
path = "/" + path
|
||||||
|
}
|
||||||
|
cleanPath := pathlib.Clean(path)
|
||||||
|
if cleanPath == "." || cleanPath == "/" || cleanPath != path {
|
||||||
|
return "", "", false
|
||||||
|
}
|
||||||
|
if strings.Count(cleanPath, "/") != 1 {
|
||||||
|
return "", "", false
|
||||||
|
}
|
||||||
|
id := strings.TrimPrefix(cleanPath, "/")
|
||||||
|
id = strings.ToLower(id)
|
||||||
|
parsedUUID, err := uuid.ParseString(id)
|
||||||
|
if err != nil || parsedUUID.String() != id {
|
||||||
|
return "", "", false
|
||||||
|
}
|
||||||
|
return listenAddr, "/" + id, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDialerServer(listenAddr string) (*dialerServer, error) {
|
||||||
|
dialer := &dialerServer{
|
||||||
|
pageRoutes: make(map[string]*dialerInstance),
|
||||||
|
}
|
||||||
|
dialer.server = &http.Server{
|
||||||
|
Addr: listenAddr,
|
||||||
|
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
pageDialer := dialer.pageRoutes[r.URL.Path]
|
||||||
|
|
||||||
|
if pageDialer != nil && websocket.IsWebSocketUpgrade(r) {
|
||||||
|
ok := false
|
||||||
|
for _, protocol := range websocket.Subprotocols(r) {
|
||||||
|
if protocol == browserDialerSubprotocol {
|
||||||
|
ok = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
closeConnection(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if conn, err := upgrader.Upgrade(w, r, http.Header{"Sec-WebSocket-Protocol": []string{browserDialerSubprotocol}}); err == nil {
|
||||||
|
pageDialer.conns <- conn
|
||||||
|
} else {
|
||||||
|
errors.LogError(context.Background(), "Browser dialer http upgrade unexpected error: ", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if pageDialer != nil {
|
||||||
|
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, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
||||||
|
if err := d.server.Serve(listener); err != nil && !stderrors.Is(err, http.ErrServerClosed) {
|
||||||
|
errors.LogError(context.Background(), "Browser dialer http server unexpected error on ", d.server.Addr, ": ", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dialerServer) stop() error {
|
||||||
|
if !d.started {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
d.started = false
|
||||||
|
return d.server.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func closeConnection(w http.ResponseWriter) {
|
||||||
|
hijacker, ok := w.(http.Hijacker)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
conn, _, err := hijacker.Hijack()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDialerByAddress(addr string) (*dialerInstance, error) {
|
||||||
|
listenAddr, path, ok := parseBrowserDialerAddress(addr)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("invalid browser dialer url: ", addr)
|
||||||
|
}
|
||||||
|
key := listenAddr + path
|
||||||
|
if dialer, found := dialersByAddress[key]; found {
|
||||||
|
return dialer, nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("browser dialer is not configured for url: ", addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ensureDialerWithAddress(addr string) (*dialerInstance, error) {
|
||||||
|
listenAddr, path, ok := parseBrowserDialerAddress(addr)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("invalid browser dialer url: ", addr)
|
||||||
|
}
|
||||||
|
_, port, err := net.SplitHostPort(listenAddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("invalid browser dialer listen address: ", listenAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
key := listenAddr + path
|
||||||
|
if dialer, found := dialersByAddress[key]; found {
|
||||||
|
return dialer, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
server, found := serversByListenAddr[listenAddr]
|
||||||
|
if !found {
|
||||||
|
for existingAddr := range serversByListenAddr {
|
||||||
|
_, existingPort, splitErr := net.SplitHostPort(existingAddr)
|
||||||
|
if splitErr == nil && existingPort == port {
|
||||||
|
return nil, errors.New("browser dialer cannot use the same port with a different listen address: ", existingAddr, " and ", listenAddr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newServer, serverErr := newDialerServer(listenAddr)
|
||||||
|
if serverErr != nil {
|
||||||
|
return nil, serverErr
|
||||||
|
}
|
||||||
|
server = newServer
|
||||||
|
serversByListenAddr[listenAddr] = server
|
||||||
|
}
|
||||||
|
|
||||||
|
dialer := &dialerInstance{
|
||||||
|
conns: make(chan *websocket.Conn, 256),
|
||||||
|
page: bytes.ReplaceAll(webpage, []byte("dialerPath"), []byte(strings.TrimPrefix(path, "/"))),
|
||||||
|
}
|
||||||
|
dialersByAddress[key] = dialer
|
||||||
|
server.pageRoutes[path] = dialer
|
||||||
|
return dialer, nil
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/xtls/xray-core/features/dns"
|
"github.com/xtls/xray-core/features/dns"
|
||||||
"github.com/xtls/xray-core/features/outbound"
|
"github.com/xtls/xray-core/features/outbound"
|
||||||
"github.com/xtls/xray-core/transport"
|
"github.com/xtls/xray-core/transport"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/browser_dialer"
|
||||||
"github.com/xtls/xray-core/transport/internet/stat"
|
"github.com/xtls/xray-core/transport/internet/stat"
|
||||||
"github.com/xtls/xray-core/transport/pipe"
|
"github.com/xtls/xray-core/transport/pipe"
|
||||||
)
|
)
|
||||||
@@ -269,6 +270,9 @@ func DialSystem(ctx context.Context, dest net.Destination, sockopt *SocketConfig
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(sockopt.DialerProxy) > 0 {
|
if len(sockopt.DialerProxy) > 0 {
|
||||||
|
if browser_dialer.IsBrowserDialerProxy(sockopt.DialerProxy) {
|
||||||
|
return nil, errors.New("dialerProxy ", sockopt.DialerProxy, " only supports WebSocket or splithttp").AtError()
|
||||||
|
}
|
||||||
if obm == nil {
|
if obm == nil {
|
||||||
return nil, errors.New("there is no outbound manager for dialerProxy").AtError()
|
return nil, errors.New("there is no outbound manager for dialerProxy").AtError()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
// BrowserDialerClient implements splithttp.DialerClient in terms of browser dialer
|
// BrowserDialerClient implements splithttp.DialerClient in terms of browser dialer
|
||||||
type BrowserDialerClient struct {
|
type BrowserDialerClient struct {
|
||||||
transportConfig *Config
|
transportConfig *Config
|
||||||
|
browserDialer string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *BrowserDialerClient) IsClosed() bool {
|
func (c *BrowserDialerClient) IsClosed() bool {
|
||||||
@@ -33,7 +34,7 @@ func (c *BrowserDialerClient) OpenStream(ctx context.Context, url string, sessio
|
|||||||
|
|
||||||
c.transportConfig.FillStreamRequest(request, sessionId, "")
|
c.transportConfig.FillStreamRequest(request, sessionId, "")
|
||||||
|
|
||||||
conn, err := browser_dialer.DialGet(request.URL.String(), request.Header, request.Cookies())
|
conn, err := browser_dialer.DialGetWithAddress(c.browserDialer, request.URL.String(), request.Header, request.Cookies())
|
||||||
dummyAddr := &net.IPAddr{}
|
dummyAddr := &net.IPAddr{}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, dummyAddr, dummyAddr, err
|
return nil, dummyAddr, dummyAddr, err
|
||||||
@@ -62,7 +63,7 @@ func (c *BrowserDialerClient) PostPacket(ctx context.Context, url string, sessio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = browser_dialer.DialPacket(method, request.URL.String(), request.Header, request.Cookies(), bytes)
|
err = browser_dialer.DialPacketWithAddress(c.browserDialer, method, request.URL.String(), request.Header, request.Cookies(), bytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,20 @@ type dialerConf struct {
|
|||||||
*internet.MemoryStreamConfig
|
*internet.MemoryStreamConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type errorDialerClient struct {
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *errorDialerClient) IsClosed() bool { return true }
|
||||||
|
|
||||||
|
func (c *errorDialerClient) OpenStream(context.Context, string, string, io.Reader, bool) (io.ReadCloser, net.Addr, net.Addr, error) {
|
||||||
|
return nil, nil, nil, c.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *errorDialerClient) PostPacket(context.Context, string, string, string, buf.MultiBuffer) error {
|
||||||
|
return c.err
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
globalDialerMap map[dialerConf]*XmuxManager
|
globalDialerMap map[dialerConf]*XmuxManager
|
||||||
globalDialerAccess sync.Mutex
|
globalDialerAccess sync.Mutex
|
||||||
@@ -47,9 +61,24 @@ var (
|
|||||||
|
|
||||||
func getHTTPClient(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (DialerClient, *XmuxClient) {
|
func getHTTPClient(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (DialerClient, *XmuxClient) {
|
||||||
realityConfig := reality.ConfigFromStreamSettings(streamSettings)
|
realityConfig := reality.ConfigFromStreamSettings(streamSettings)
|
||||||
|
browserDialer := ""
|
||||||
|
if streamSettings.SocketSettings != nil {
|
||||||
|
if browser_dialer.IsBrowserDialerProxy(streamSettings.SocketSettings.DialerProxy) {
|
||||||
|
browserDialer = streamSettings.SocketSettings.DialerProxy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if browser_dialer.HasBrowserDialer() && realityConfig == nil {
|
if browserDialer != "" && realityConfig == nil {
|
||||||
return &BrowserDialerClient{transportConfig: streamSettings.ProtocolSettings.(*Config)}, nil
|
transportConfig := streamSettings.ProtocolSettings.(*Config)
|
||||||
|
if transportConfig.Mode != "auto" && transportConfig.Mode != "packet-up" {
|
||||||
|
return &errorDialerClient{
|
||||||
|
err: errors.New("browserDialer with splithttp only supports modes \"auto\" or \"packet-up\", got: \"", transportConfig.Mode, "\""),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return &BrowserDialerClient{
|
||||||
|
transportConfig: transportConfig,
|
||||||
|
browserDialer: browserDialer,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
globalDialerAccess.Lock()
|
globalDialerAccess.Lock()
|
||||||
|
|||||||
@@ -117,8 +117,14 @@ func dialWebSocket(ctx context.Context, dest net.Destination, streamSettings *in
|
|||||||
}
|
}
|
||||||
uri := protocol + "://" + host + wsSettings.GetNormalizedPath()
|
uri := protocol + "://" + host + wsSettings.GetNormalizedPath()
|
||||||
|
|
||||||
if browser_dialer.HasBrowserDialer() {
|
browserDialer := ""
|
||||||
conn, err := browser_dialer.DialWS(uri, ed)
|
if streamSettings.SocketSettings != nil {
|
||||||
|
if browser_dialer.IsBrowserDialerProxy(streamSettings.SocketSettings.DialerProxy) {
|
||||||
|
browserDialer = streamSettings.SocketSettings.DialerProxy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if browserDialer != "" {
|
||||||
|
conn, err := browser_dialer.DialWSWithAddress(browserDialer, uri, ed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user