mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-09-15 22:10:26 +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/stats"
|
||||
"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.
|
||||
@@ -262,6 +263,9 @@ func (s *Instance) Close() error {
|
||||
s.running = false
|
||||
|
||||
var errs []interface{}
|
||||
if err := browser_dialer.StopCollectedDialerProxyURLs(); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
for _, f := range s.features {
|
||||
if err := f.Close(); err != nil {
|
||||
errs = append(errs, err)
|
||||
@@ -385,9 +389,16 @@ func (s *Instance) Start() error {
|
||||
s.statusLock.Lock()
|
||||
defer s.statusLock.Unlock()
|
||||
|
||||
if err := browser_dialer.StartCollectedDialerProxyURLs(); err != nil {
|
||||
return err
|
||||
}
|
||||
s.running = true
|
||||
for _, f := range s.features {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"github.com/xtls/xray-core/common/platform/filesystem"
|
||||
"github.com/xtls/xray-core/common/serial"
|
||||
"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/header/custom"
|
||||
"github.com/xtls/xray-core/transport/internet/finalmask/header/dns"
|
||||
@@ -1970,6 +1971,35 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
|
||||
}
|
||||
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) {
|
||||
case "", "none":
|
||||
|
||||
+7
-1
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/xtls/xray-core/common/serial"
|
||||
core "github.com/xtls/xray-core/core"
|
||||
"github.com/xtls/xray-core/transport/internet"
|
||||
"github.com/xtls/xray-core/transport/internet/browser_dialer"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -437,7 +438,6 @@ func (c *Config) Override(o *Config, fn string) {
|
||||
if o.Geodata != nil {
|
||||
c.Geodata = o.Geodata
|
||||
}
|
||||
|
||||
// update the Inbound in slice if the only one in override config has same tag
|
||||
if len(o.InboundConfigs) > 0 {
|
||||
for i := range o.InboundConfigs {
|
||||
@@ -604,6 +604,9 @@ func (c *Config) Build() (*core.Config, error) {
|
||||
if len(c.Transport) > 0 {
|
||||
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 {
|
||||
ic, err := rawInboundConfig.Build()
|
||||
@@ -626,6 +629,9 @@ func (c *Config) Build() (*core.Config, error) {
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1,101 +1,33 @@
|
||||
package browser_dialer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
_ "embed"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"sync"
|
||||
"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/uuid"
|
||||
)
|
||||
|
||||
//go:embed dialer.html
|
||||
var webpage []byte
|
||||
|
||||
type task struct {
|
||||
Method string `json:"method"`
|
||||
URL string `json:"url"`
|
||||
Extra any `json:"extra,omitempty"`
|
||||
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
|
||||
Method string `json:"method"`
|
||||
URL string `json:"url"`
|
||||
Extra any `json:"extra,omitempty"`
|
||||
StreamResponse bool `json:"streamResponse"`
|
||||
}
|
||||
|
||||
type webSocketExtra struct {
|
||||
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{
|
||||
Method: "WS",
|
||||
URL: uri,
|
||||
Method: "WS",
|
||||
URL: uri,
|
||||
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 {
|
||||
@@ -142,30 +74,26 @@ func httpExtraFromHeadersAndCookies(headers http.Header, cookies []*http.Cookie)
|
||||
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{
|
||||
Method: "GET",
|
||||
URL: uri,
|
||||
Extra: httpExtraFromHeadersAndCookies(headers, cookies),
|
||||
Method: "GET",
|
||||
URL: uri,
|
||||
Extra: httpExtraFromHeadersAndCookies(headers, cookies),
|
||||
StreamResponse: true,
|
||||
}
|
||||
|
||||
return dialTask(task)
|
||||
return dialTaskWithAddress(addr, task)
|
||||
}
|
||||
|
||||
func DialPacket(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 {
|
||||
func DialPacketWithAddress(addr string, method string, uri string, headers http.Header, cookies []*http.Cookie, payload []byte) error {
|
||||
task := task{
|
||||
Method: method,
|
||||
URL: uri,
|
||||
Extra: httpExtraFromHeadersAndCookies(headers, cookies),
|
||||
Method: method,
|
||||
URL: uri,
|
||||
Extra: httpExtraFromHeadersAndCookies(headers, cookies),
|
||||
StreamResponse: false,
|
||||
}
|
||||
|
||||
conn, err := dialTask(task)
|
||||
conn, err := dialTaskWithAddress(addr, task)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -184,12 +112,21 @@ func dialWithBody(method string, uri string, headers http.Header, cookies []*htt
|
||||
return nil
|
||||
}
|
||||
|
||||
func dialTask(task task) (*websocket.Conn, error) {
|
||||
func dialTaskWithAddress(addr string, task task) (*websocket.Conn, error) {
|
||||
data, err := json.Marshal(task)
|
||||
if err != nil {
|
||||
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
|
||||
for {
|
||||
conn = <-conns
|
||||
@@ -218,8 +155,3 @@ func CheckOK(conn *websocket.Conn) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
Reload()
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
// Enable a much more aggressive JIT for performance gains
|
||||
|
||||
// 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 upstreamGetCount = 0;
|
||||
let upstreamWsCount = 0;
|
||||
@@ -67,7 +67,7 @@
|
||||
}
|
||||
clientIdleCount += 1;
|
||||
console.log("Prepare", url);
|
||||
let ws = new WebSocket(url);
|
||||
let ws = new WebSocket(url, "browser-dialer");
|
||||
// arraybuffer is significantly faster in chrome than default
|
||||
// blob, tested with chrome 123
|
||||
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/outbound"
|
||||
"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/pipe"
|
||||
)
|
||||
@@ -269,6 +270,9 @@ func DialSystem(ctx context.Context, dest net.Destination, sockopt *SocketConfig
|
||||
}
|
||||
|
||||
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 {
|
||||
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
|
||||
type BrowserDialerClient struct {
|
||||
transportConfig *Config
|
||||
browserDialer string
|
||||
}
|
||||
|
||||
func (c *BrowserDialerClient) IsClosed() bool {
|
||||
@@ -33,7 +34,7 @@ func (c *BrowserDialerClient) OpenStream(ctx context.Context, url string, sessio
|
||||
|
||||
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{}
|
||||
if err != nil {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -40,6 +40,20 @@ type dialerConf struct {
|
||||
*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 (
|
||||
globalDialerMap map[dialerConf]*XmuxManager
|
||||
globalDialerAccess sync.Mutex
|
||||
@@ -47,9 +61,24 @@ var (
|
||||
|
||||
func getHTTPClient(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (DialerClient, *XmuxClient) {
|
||||
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 {
|
||||
return &BrowserDialerClient{transportConfig: streamSettings.ProtocolSettings.(*Config)}, nil
|
||||
if browserDialer != "" && realityConfig == 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()
|
||||
|
||||
@@ -117,8 +117,14 @@ func dialWebSocket(ctx context.Context, dest net.Destination, streamSettings *in
|
||||
}
|
||||
uri := protocol + "://" + host + wsSettings.GetNormalizedPath()
|
||||
|
||||
if browser_dialer.HasBrowserDialer() {
|
||||
conn, err := browser_dialer.DialWS(uri, ed)
|
||||
browserDialer := ""
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user