mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-07-03 10:18:42 +00:00
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:
committed by
GitHub
parent
2691a1aa0e
commit
c48c475256
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user