mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-10-21 16:54:29 +00:00
Compare commits
15
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
695e68ef9e | ||
|
|
dfdbcf86cc | ||
|
|
2b828b7bc2 | ||
|
|
45cf2898ab | ||
|
|
18b85adb4e | ||
|
|
452b719504 | ||
|
|
345c76f9a8 | ||
|
|
f496437b84 | ||
|
|
b12bc504c8 | ||
|
|
dda2b10c9d | ||
|
|
241aa38ac0 | ||
|
|
7e7e820763 | ||
|
|
f9eb1597ad | ||
|
|
ac04c445bd | ||
|
|
e7e9254630 |
@@ -14,13 +14,13 @@ jobs:
|
|||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
||||||
steps:
|
steps:
|
||||||
- name: Restore Geodat Cache
|
- name: Restore Geodat Cache
|
||||||
uses: actions/cache/restore@v5
|
uses: actions/cache/restore@v6
|
||||||
with:
|
with:
|
||||||
path: resources
|
path: resources
|
||||||
key: xray-geodat-
|
key: xray-geodat-
|
||||||
|
|
||||||
- name: Restore Wintun Cache
|
- name: Restore Wintun Cache
|
||||||
uses: actions/cache/restore@v5
|
uses: actions/cache/restore@v6
|
||||||
with:
|
with:
|
||||||
path: resources
|
path: resources
|
||||||
key: xray-wintun-
|
key: xray-wintun-
|
||||||
@@ -119,13 +119,13 @@ jobs:
|
|||||||
# go build -o build_assets/wxray.exe -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-H windowsgui -X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main
|
# go build -o build_assets/wxray.exe -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-H windowsgui -X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main
|
||||||
|
|
||||||
- name: Restore Geodat Cache
|
- name: Restore Geodat Cache
|
||||||
uses: actions/cache/restore@v5
|
uses: actions/cache/restore@v6
|
||||||
with:
|
with:
|
||||||
path: resources
|
path: resources
|
||||||
key: xray-geodat-
|
key: xray-geodat-
|
||||||
|
|
||||||
- name: Restore Wintun Cache
|
- name: Restore Wintun Cache
|
||||||
uses: actions/cache/restore@v5
|
uses: actions/cache/restore@v6
|
||||||
with:
|
with:
|
||||||
path: resources
|
path: resources
|
||||||
key: xray-wintun-
|
key: xray-wintun-
|
||||||
|
|||||||
@@ -14,13 +14,13 @@ jobs:
|
|||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
||||||
steps:
|
steps:
|
||||||
- name: Restore Geodat Cache
|
- name: Restore Geodat Cache
|
||||||
uses: actions/cache/restore@v5
|
uses: actions/cache/restore@v6
|
||||||
with:
|
with:
|
||||||
path: resources
|
path: resources
|
||||||
key: xray-geodat-
|
key: xray-geodat-
|
||||||
|
|
||||||
- name: Restore Wintun Cache
|
- name: Restore Wintun Cache
|
||||||
uses: actions/cache/restore@v5
|
uses: actions/cache/restore@v6
|
||||||
with:
|
with:
|
||||||
path: resources
|
path: resources
|
||||||
key: xray-wintun-
|
key: xray-wintun-
|
||||||
@@ -225,14 +225,14 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Restore Geodat Cache
|
- name: Restore Geodat Cache
|
||||||
uses: actions/cache/restore@v5
|
uses: actions/cache/restore@v6
|
||||||
with:
|
with:
|
||||||
path: resources
|
path: resources
|
||||||
key: xray-geodat-
|
key: xray-geodat-
|
||||||
|
|
||||||
- name: Restore Wintun Cache
|
- name: Restore Wintun Cache
|
||||||
if: matrix.goos == 'windows'
|
if: matrix.goos == 'windows'
|
||||||
uses: actions/cache/restore@v5
|
uses: actions/cache/restore@v6
|
||||||
with:
|
with:
|
||||||
path: resources
|
path: resources
|
||||||
key: xray-wintun-
|
key: xray-wintun-
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Restore Geodat Cache
|
- name: Restore Geodat Cache
|
||||||
uses: actions/cache/restore@v5
|
uses: actions/cache/restore@v6
|
||||||
with:
|
with:
|
||||||
path: resources
|
path: resources
|
||||||
key: xray-geodat-
|
key: xray-geodat-
|
||||||
@@ -59,7 +59,7 @@ jobs:
|
|||||||
done
|
done
|
||||||
|
|
||||||
- name: Save Geodat Cache
|
- name: Save Geodat Cache
|
||||||
uses: actions/cache/save@v5
|
uses: actions/cache/save@v6
|
||||||
if: ${{ steps.update.outputs.unhit }}
|
if: ${{ steps.update.outputs.unhit }}
|
||||||
with:
|
with:
|
||||||
path: resources
|
path: resources
|
||||||
@@ -73,7 +73,7 @@ jobs:
|
|||||||
ASSETHASH: 07c256185d6ee3652e09fa55c0b673e2624b565e02c4b9091c79ca7d2f24ef51
|
ASSETHASH: 07c256185d6ee3652e09fa55c0b673e2624b565e02c4b9091c79ca7d2f24ef51
|
||||||
steps:
|
steps:
|
||||||
- name: Restore Wintun Cache
|
- name: Restore Wintun Cache
|
||||||
uses: actions/cache/restore@v5
|
uses: actions/cache/restore@v6
|
||||||
with:
|
with:
|
||||||
path: resources
|
path: resources
|
||||||
key: xray-wintun-
|
key: xray-wintun-
|
||||||
@@ -129,7 +129,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Save Wintun Cache
|
- name: Save Wintun Cache
|
||||||
uses: actions/cache/save@v5
|
uses: actions/cache/save@v6
|
||||||
if: ${{ steps.update.outputs.unhit }}
|
if: ${{ steps.update.outputs.unhit }}
|
||||||
with:
|
with:
|
||||||
path: resources
|
path: resources
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ jobs:
|
|||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
||||||
steps:
|
steps:
|
||||||
- name: Restore Geodat Cache
|
- name: Restore Geodat Cache
|
||||||
uses: actions/cache/restore@v5
|
uses: actions/cache/restore@v6
|
||||||
with:
|
with:
|
||||||
path: resources
|
path: resources
|
||||||
key: xray-geodat-
|
key: xray-geodat-
|
||||||
@@ -90,7 +90,7 @@ jobs:
|
|||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- name: Restore Geodat Cache
|
- name: Restore Geodat Cache
|
||||||
uses: actions/cache/restore@v5
|
uses: actions/cache/restore@v6
|
||||||
with:
|
with:
|
||||||
path: resources
|
path: resources
|
||||||
key: xray-geodat-
|
key: xray-geodat-
|
||||||
|
|||||||
@@ -73,6 +73,7 @@
|
|||||||
- [Xray_bash_onekey](https://github.com/hello-yunshu/Xray_bash_onekey), [XTool](https://github.com/LordPenguin666/XTool), [VPainLess](https://github.com/vpainless/vpainless)
|
- [Xray_bash_onekey](https://github.com/hello-yunshu/Xray_bash_onekey), [XTool](https://github.com/LordPenguin666/XTool), [VPainLess](https://github.com/vpainless/vpainless)
|
||||||
- [v2ray-agent](https://github.com/mack-a/v2ray-agent), [Xray_onekey](https://github.com/wulabing/Xray_onekey), [ProxySU](https://github.com/proxysu/ProxySU)
|
- [v2ray-agent](https://github.com/mack-a/v2ray-agent), [Xray_onekey](https://github.com/wulabing/Xray_onekey), [ProxySU](https://github.com/proxysu/ProxySU)
|
||||||
- Magisk
|
- Magisk
|
||||||
|
- [Magic_V2Ray](https://github.com/vincentng295/Magic_V2Ray)
|
||||||
- [Xray_For_Magisk](https://github.com/E7KMbb/Xray_For_Magisk)
|
- [Xray_For_Magisk](https://github.com/E7KMbb/Xray_For_Magisk)
|
||||||
- Homebrew
|
- Homebrew
|
||||||
- `brew install xray`
|
- `brew install xray`
|
||||||
|
|||||||
@@ -198,9 +198,14 @@ func parseResponse(payload []byte) (*IPRecord, error) {
|
|||||||
ipRecord := &IPRecord{
|
ipRecord := &IPRecord{
|
||||||
ReqID: h.ID,
|
ReqID: h.ID,
|
||||||
RCode: h.RCode,
|
RCode: h.RCode,
|
||||||
Expire: now.Add(time.Second * dns_feature.DefaultTTL),
|
|
||||||
RawHeader: &h,
|
RawHeader: &h,
|
||||||
}
|
}
|
||||||
|
defer func() {
|
||||||
|
// set to default TTL if no valid TTL is found
|
||||||
|
if ipRecord.Expire.IsZero() {
|
||||||
|
ipRecord.Expire = now.Add(time.Second * dns_feature.DefaultTTL)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
L:
|
L:
|
||||||
for {
|
for {
|
||||||
@@ -217,7 +222,7 @@ L:
|
|||||||
ttl = 1
|
ttl = 1
|
||||||
}
|
}
|
||||||
expire := now.Add(time.Duration(ttl) * time.Second)
|
expire := now.Add(time.Duration(ttl) * time.Second)
|
||||||
if ipRecord.Expire.After(expire) {
|
if ipRecord.Expire.IsZero() || ipRecord.Expire.After(expire) {
|
||||||
ipRecord.Expire = expire
|
ipRecord.Expire = expire
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+85
-43
@@ -2,6 +2,7 @@ package geodata
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
go_errors "errors"
|
go_errors "errors"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -9,6 +10,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
utls "github.com/refraction-networking/utls"
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/common/platform/filesystem"
|
"github.com/xtls/xray-core/common/platform/filesystem"
|
||||||
@@ -16,6 +18,7 @@ import (
|
|||||||
"github.com/xtls/xray-core/common/utils"
|
"github.com/xtls/xray-core/common/utils"
|
||||||
"github.com/xtls/xray-core/features/routing"
|
"github.com/xtls/xray-core/features/routing"
|
||||||
"github.com/xtls/xray-core/transport/internet/tagged"
|
"github.com/xtls/xray-core/transport/internet/tagged"
|
||||||
|
"golang.org/x/net/http2"
|
||||||
)
|
)
|
||||||
|
|
||||||
const idleTimeout = 30 * time.Second
|
const idleTimeout = 30 * time.Second
|
||||||
@@ -26,8 +29,9 @@ type stage struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type downloader struct {
|
type downloader struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
client *http.Client
|
httpClient *http.Client
|
||||||
|
httpsClient *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
type idleConn struct {
|
type idleConn struct {
|
||||||
@@ -53,52 +57,84 @@ func (c *idleConn) Write(b []byte) (int, error) {
|
|||||||
|
|
||||||
func newDownloader(ctx context.Context, dispatcher routing.Dispatcher, outbound string) *downloader {
|
func newDownloader(ctx context.Context, dispatcher routing.Dispatcher, outbound string) *downloader {
|
||||||
return &downloader{
|
return &downloader{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
client: newClient(ctx, dispatcher, outbound),
|
httpClient: newClient(ctx, dispatcher, outbound, false),
|
||||||
|
httpsClient: newClient(ctx, dispatcher, outbound, true),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newClient(baseCtx context.Context, dispatcher routing.Dispatcher, outbound string) *http.Client {
|
func newClient(baseCtx context.Context, dispatcher routing.Dispatcher, outbound string, isHTTPS bool) *http.Client {
|
||||||
return &http.Client{
|
dial := func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||||
Transport: &http.Transport{
|
var conn net.Conn
|
||||||
Proxy: nil,
|
err := task.Run(ctx, func() error {
|
||||||
DisableKeepAlives: true,
|
if tagged.Dialer == nil {
|
||||||
DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
|
return errors.New("tagged dialer is not initialized")
|
||||||
var conn net.Conn
|
|
||||||
err := task.Run(ctx, func() error {
|
|
||||||
if tagged.Dialer == nil {
|
|
||||||
return errors.New("tagged dialer is not initialized")
|
|
||||||
}
|
|
||||||
dest, err := net.ParseDestination(network + ":" + address)
|
|
||||||
if err != nil {
|
|
||||||
return errors.New("cannot understand address").Base(err)
|
|
||||||
}
|
|
||||||
c, err := tagged.Dialer(baseCtx, dispatcher, dest, outbound)
|
|
||||||
if err != nil {
|
|
||||||
return errors.New("cannot dial remote address ", dest).Base(err)
|
|
||||||
}
|
|
||||||
conn = c
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New("cannot finish connection").Base(err)
|
|
||||||
}
|
|
||||||
return &idleConn{
|
|
||||||
Conn: conn,
|
|
||||||
}, nil
|
|
||||||
},
|
|
||||||
TLSHandshakeTimeout: idleTimeout,
|
|
||||||
ResponseHeaderTimeout: idleTimeout,
|
|
||||||
},
|
|
||||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
|
||||||
if req.URL.Scheme != "https" {
|
|
||||||
return errors.New("redirected to non-https URL: ", req.URL.String())
|
|
||||||
}
|
}
|
||||||
if len(via) >= 10 {
|
dest, err := net.ParseDestination(network + ":" + address)
|
||||||
return errors.New("stopped after 10 redirects")
|
if err != nil {
|
||||||
|
return errors.New("cannot understand address").Base(err)
|
||||||
}
|
}
|
||||||
|
c, err := tagged.Dialer(baseCtx, dispatcher, dest, outbound)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("cannot dial remote address ", dest).Base(err)
|
||||||
|
}
|
||||||
|
conn = c
|
||||||
return nil
|
return nil
|
||||||
},
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("cannot finish connection").Base(err)
|
||||||
|
}
|
||||||
|
return &idleConn{
|
||||||
|
Conn: conn,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
if isHTTPS {
|
||||||
|
return &http.Client{
|
||||||
|
Transport: &http2.Transport{
|
||||||
|
DialTLSContext: func(ctx context.Context, network string, address string, cfg *tls.Config) (net.Conn, error) {
|
||||||
|
conn, err := dial(ctx, network, address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
host, _, _ := net.SplitHostPort(address)
|
||||||
|
tlsConn := utls.UClient(conn, &utls.Config{ServerName: host}, utls.HelloChrome_Auto)
|
||||||
|
handshakeCtx, cancel := context.WithTimeout(ctx, idleTimeout)
|
||||||
|
defer cancel()
|
||||||
|
if err := tlsConn.HandshakeContext(handshakeCtx); err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return tlsConn, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||||
|
if req.URL.Scheme != "https" {
|
||||||
|
return errors.New("redirected to non-https URL: ", req.URL.String())
|
||||||
|
}
|
||||||
|
if len(via) >= 10 {
|
||||||
|
return errors.New("stopped after 10 redirects")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
Proxy: nil,
|
||||||
|
DisableKeepAlives: true,
|
||||||
|
DialContext: dial,
|
||||||
|
ResponseHeaderTimeout: idleTimeout,
|
||||||
|
},
|
||||||
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||||
|
if req.URL.Scheme != "https" {
|
||||||
|
return errors.New("redirected to non-https URL: ", req.URL.String())
|
||||||
|
}
|
||||||
|
if len(via) >= 10 {
|
||||||
|
return errors.New("stopped after 10 redirects")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,7 +196,13 @@ func (d *downloader) fetch(rawURL string, writer io.Writer) error {
|
|||||||
}
|
}
|
||||||
utils.TryDefaultHeadersWith(req.Header, "nav")
|
utils.TryDefaultHeadersWith(req.Header, "nav")
|
||||||
|
|
||||||
resp, err := d.client.Do(req)
|
var client *http.Client
|
||||||
|
if req.URL.Scheme == "https" {
|
||||||
|
client = d.httpsClient
|
||||||
|
} else {
|
||||||
|
client = d.httpClient
|
||||||
|
}
|
||||||
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
+151
-59
@@ -2,15 +2,18 @@ package metrics
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
stderrors "errors"
|
||||||
"expvar"
|
"expvar"
|
||||||
|
stdnet "net"
|
||||||
"net/http"
|
"net/http"
|
||||||
_ "net/http/pprof"
|
"net/http/pprof"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/app/observatory"
|
"github.com/xtls/xray-core/app/observatory"
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/net"
|
xnet "github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/common/signal/done"
|
"github.com/xtls/xray-core/common/signal/done"
|
||||||
"github.com/xtls/xray-core/core"
|
"github.com/xtls/xray-core/core"
|
||||||
"github.com/xtls/xray-core/features/extension"
|
"github.com/xtls/xray-core/features/extension"
|
||||||
@@ -21,15 +24,17 @@ import (
|
|||||||
type MetricsHandler struct {
|
type MetricsHandler struct {
|
||||||
ohm outbound.Manager
|
ohm outbound.Manager
|
||||||
statsManager feature_stats.Manager
|
statsManager feature_stats.Manager
|
||||||
observatory extension.Observatory
|
ctx context.Context
|
||||||
tag string
|
tag string
|
||||||
listen string
|
listen string
|
||||||
tcpListener net.Listener
|
tcpListener xnet.Listener
|
||||||
|
listener *OutboundListener
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMetricsHandler creates a new MetricsHandler based on the given config.
|
// NewMetricsHandler creates a new MetricsHandler based on the given config.
|
||||||
func NewMetricsHandler(ctx context.Context, config *Config) (*MetricsHandler, error) {
|
func NewMetricsHandler(ctx context.Context, config *Config) (*MetricsHandler, error) {
|
||||||
c := &MetricsHandler{
|
c := &MetricsHandler{
|
||||||
|
ctx: ctx,
|
||||||
tag: config.Tag,
|
tag: config.Tag,
|
||||||
listen: config.Listen,
|
listen: config.Listen,
|
||||||
}
|
}
|
||||||
@@ -37,46 +42,6 @@ func NewMetricsHandler(ctx context.Context, config *Config) (*MetricsHandler, er
|
|||||||
c.statsManager = sm
|
c.statsManager = sm
|
||||||
c.ohm = om
|
c.ohm = om
|
||||||
}))
|
}))
|
||||||
expvar.Publish("stats", expvar.Func(func() interface{} {
|
|
||||||
resp := map[string]map[string]map[string]int64{
|
|
||||||
"inbound": {},
|
|
||||||
"outbound": {},
|
|
||||||
"user": {},
|
|
||||||
}
|
|
||||||
c.statsManager.VisitCounters(func(name string, counter feature_stats.Counter) bool {
|
|
||||||
nameSplit := strings.Split(name, ">>>")
|
|
||||||
typeName, tagOrUser, direction := nameSplit[0], nameSplit[1], nameSplit[3]
|
|
||||||
if item, found := resp[typeName][tagOrUser]; found {
|
|
||||||
item[direction] = counter.Value()
|
|
||||||
} else {
|
|
||||||
resp[typeName][tagOrUser] = map[string]int64{
|
|
||||||
direction: counter.Value(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
return resp
|
|
||||||
}))
|
|
||||||
expvar.Publish("observatory", expvar.Func(func() interface{} {
|
|
||||||
if c.observatory == nil {
|
|
||||||
common.Must(core.RequireFeatures(ctx, func(observatory extension.Observatory) error {
|
|
||||||
c.observatory = observatory
|
|
||||||
return nil
|
|
||||||
}))
|
|
||||||
if c.observatory == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resp := map[string]*observatory.OutboundStatus{}
|
|
||||||
if o, err := c.observatory.GetObservation(context.Background()); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
for _, x := range o.(*observatory.ObservationResult).GetStatus() {
|
|
||||||
resp[x.OutboundTag] = x
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return resp
|
|
||||||
}))
|
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,45 +50,172 @@ func (p *MetricsHandler) Type() interface{} {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *MetricsHandler) Start() error {
|
func (p *MetricsHandler) Start() error {
|
||||||
|
handler := p.httpHandler()
|
||||||
|
|
||||||
// direct listen a port if listen is set
|
// direct listen a port if listen is set
|
||||||
if p.listen != "" {
|
if p.listen != "" {
|
||||||
TCPlistener, err := net.Listen("tcp", p.listen)
|
TCPlistener, err := xnet.Listen("tcp", p.listen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
p.tcpListener = TCPlistener
|
p.tcpListener = TCPlistener
|
||||||
errors.LogInfo(context.Background(), "Metrics server listening on ", p.listen)
|
errors.LogInfo(context.Background(), "Metrics server listening on ", p.listen)
|
||||||
|
|
||||||
go func() {
|
go p.serve(TCPlistener, handler)
|
||||||
if err := http.Serve(TCPlistener, http.DefaultServeMux); err != nil {
|
}
|
||||||
errors.LogErrorInner(context.Background(), err, "failed to start metrics server")
|
|
||||||
}
|
if p.tag == "" {
|
||||||
}()
|
if p.tcpListener == nil {
|
||||||
|
return errors.New("metrics must have a tag or listen address")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
listener := &OutboundListener{
|
listener := &OutboundListener{
|
||||||
buffer: make(chan net.Conn, 4),
|
buffer: make(chan xnet.Conn, 4),
|
||||||
done: done.New(),
|
done: done.New(),
|
||||||
}
|
}
|
||||||
|
p.listener = listener
|
||||||
|
|
||||||
go func() {
|
go p.serve(listener, handler)
|
||||||
if err := http.Serve(listener, http.DefaultServeMux); err != nil {
|
|
||||||
errors.LogErrorInner(context.Background(), err, "failed to start metrics server")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err := p.ohm.RemoveHandler(context.Background(), p.tag); err != nil {
|
if err := p.ohm.RemoveHandler(context.Background(), p.tag); err != nil {
|
||||||
errors.LogInfo(context.Background(), "failed to remove existing handler")
|
errors.LogInfo(context.Background(), "failed to remove existing handler")
|
||||||
}
|
}
|
||||||
|
|
||||||
return p.ohm.AddHandler(context.Background(), &Outbound{
|
if err := p.ohm.AddHandler(context.Background(), &Outbound{
|
||||||
tag: p.tag,
|
tag: p.tag,
|
||||||
listener: listener,
|
listener: listener,
|
||||||
})
|
}); err != nil {
|
||||||
|
if closeErr := p.Close(); closeErr != nil {
|
||||||
|
errors.LogErrorInner(context.Background(), closeErr, "failed to close metrics server after start failure")
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *MetricsHandler) Close() error {
|
func (p *MetricsHandler) Close() error {
|
||||||
return nil
|
var errs []error
|
||||||
|
if p.tcpListener != nil {
|
||||||
|
errs = append(errs, p.tcpListener.Close())
|
||||||
|
p.tcpListener = nil
|
||||||
|
}
|
||||||
|
if p.listener != nil {
|
||||||
|
errs = append(errs, p.listener.Close())
|
||||||
|
p.listener = nil
|
||||||
|
}
|
||||||
|
if p.ohm != nil && p.tag != "" {
|
||||||
|
if err := p.ohm.RemoveHandler(context.Background(), p.tag); err != nil {
|
||||||
|
errors.LogInfo(context.Background(), "failed to remove metrics handler")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors.Combine(errs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *MetricsHandler) serve(listener xnet.Listener, handler http.Handler) {
|
||||||
|
if err := http.Serve(listener, handler); err != nil && !isClosedListenerError(err) {
|
||||||
|
errors.LogErrorInner(context.Background(), err, "failed to start metrics server")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isClosedListenerError(err error) bool {
|
||||||
|
if err == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if stderrors.Is(err, stdnet.ErrClosed) || stderrors.Is(err, http.ErrServerClosed) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
errText := err.Error()
|
||||||
|
return strings.Contains(errText, "listen closed") ||
|
||||||
|
strings.Contains(errText, "use of closed network connection")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *MetricsHandler) httpHandler() http.Handler {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.HandleFunc("/debug/vars", p.handleDebugVars)
|
||||||
|
mux.HandleFunc("/debug/pprof/", pprof.Index)
|
||||||
|
mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
|
||||||
|
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
||||||
|
mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
||||||
|
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
|
||||||
|
return mux
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *MetricsHandler) handleDebugVars(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
vars := map[string]json.RawMessage{}
|
||||||
|
expvar.Do(func(kv expvar.KeyValue) {
|
||||||
|
value := json.RawMessage(kv.Value.String())
|
||||||
|
if !json.Valid(value) {
|
||||||
|
value = json.RawMessage("null")
|
||||||
|
}
|
||||||
|
vars[kv.Key] = value
|
||||||
|
})
|
||||||
|
vars["stats"] = marshalJSON(p.stats())
|
||||||
|
vars["observatory"] = marshalJSON(p.observatoryStatus())
|
||||||
|
|
||||||
|
payload, err := json.Marshal(vars)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Write(payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
func marshalJSON(value interface{}) json.RawMessage {
|
||||||
|
data, err := json.Marshal(value)
|
||||||
|
if err != nil {
|
||||||
|
return json.RawMessage("null")
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *MetricsHandler) stats() map[string]map[string]map[string]int64 {
|
||||||
|
resp := map[string]map[string]map[string]int64{
|
||||||
|
"inbound": {},
|
||||||
|
"outbound": {},
|
||||||
|
"user": {},
|
||||||
|
}
|
||||||
|
p.statsManager.VisitCounters(func(name string, counter feature_stats.Counter) bool {
|
||||||
|
nameSplit := strings.Split(name, ">>>")
|
||||||
|
if len(nameSplit) < 4 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
typeName, tagOrUser, direction := nameSplit[0], nameSplit[1], nameSplit[3]
|
||||||
|
items, found := resp[typeName]
|
||||||
|
if !found {
|
||||||
|
items = map[string]map[string]int64{}
|
||||||
|
resp[typeName] = items
|
||||||
|
}
|
||||||
|
if item, found := items[tagOrUser]; found {
|
||||||
|
item[direction] = counter.Value()
|
||||||
|
} else {
|
||||||
|
items[tagOrUser] = map[string]int64{
|
||||||
|
direction: counter.Value(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *MetricsHandler) observatoryStatus() interface{} {
|
||||||
|
feature := core.MustFromContext(p.ctx).GetFeature(extension.ObservatoryType())
|
||||||
|
if feature == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
observatoryFeature := feature.(extension.Observatory)
|
||||||
|
resp := map[string]*observatory.OutboundStatus{}
|
||||||
|
if o, err := observatoryFeature.GetObservation(context.Background()); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
for _, x := range o.(*observatory.ObservationResult).GetStatus() {
|
||||||
|
resp[x.OutboundTag] = x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|||||||
@@ -0,0 +1,161 @@
|
|||||||
|
package metrics
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
stdnet "net"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/app/dispatcher"
|
||||||
|
"github.com/xtls/xray-core/app/proxyman"
|
||||||
|
_ "github.com/xtls/xray-core/app/proxyman/inbound"
|
||||||
|
_ "github.com/xtls/xray-core/app/proxyman/outbound"
|
||||||
|
appstats "github.com/xtls/xray-core/app/stats"
|
||||||
|
"github.com/xtls/xray-core/common/serial"
|
||||||
|
"github.com/xtls/xray-core/core"
|
||||||
|
feature_outbound "github.com/xtls/xray-core/features/outbound"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMetricsCanRestartInSameProcess(t *testing.T) {
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
server := startMetricsTestServer(t)
|
||||||
|
readMetricsVars(t, server)
|
||||||
|
readMetricsPprof(t, server)
|
||||||
|
if err := server.Close(); err != nil {
|
||||||
|
t.Fatalf("failed to close metrics server: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMetricsCanRunMultipleInstancesInSameProcess(t *testing.T) {
|
||||||
|
server1 := startMetricsTestServer(t)
|
||||||
|
t.Cleanup(func() {
|
||||||
|
_ = server1.Close()
|
||||||
|
})
|
||||||
|
server2 := startMetricsTestServer(t)
|
||||||
|
t.Cleanup(func() {
|
||||||
|
_ = server2.Close()
|
||||||
|
})
|
||||||
|
|
||||||
|
readMetricsVars(t, server1)
|
||||||
|
readMetricsVars(t, server2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMetricsListenOnlyWithoutTagDoesNotRegisterOutbound(t *testing.T) {
|
||||||
|
listen := pickMetricsListenAddress(t)
|
||||||
|
server := startMetricsTestServerWithMetricsConfig(t, &Config{
|
||||||
|
Listen: listen,
|
||||||
|
})
|
||||||
|
t.Cleanup(func() {
|
||||||
|
_ = server.Close()
|
||||||
|
})
|
||||||
|
|
||||||
|
response, err := http.Get("http://" + listen + "/debug/vars")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to read listen-only metrics: %v", err)
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
if response.StatusCode != http.StatusOK {
|
||||||
|
t.Fatalf("unexpected listen-only metrics status: %d", response.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
outboundManager := server.GetFeature(feature_outbound.ManagerType()).(feature_outbound.Manager)
|
||||||
|
if handlers := outboundManager.ListHandlers(context.Background()); len(handlers) != 0 {
|
||||||
|
t.Fatalf("listen-only metrics registered outbound handlers: got %d, want 0", len(handlers))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func startMetricsTestServer(t *testing.T) *core.Instance {
|
||||||
|
return startMetricsTestServerWithMetricsConfig(t, &Config{
|
||||||
|
Tag: "metrics_out",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func startMetricsTestServerWithMetricsConfig(t *testing.T, metricsConfig *Config) *core.Instance {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
server, err := core.New(metricsTestConfig(metricsConfig))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create metrics server: %v", err)
|
||||||
|
}
|
||||||
|
if err := server.Start(); err != nil {
|
||||||
|
_ = server.Close()
|
||||||
|
t.Fatalf("failed to start metrics server: %v", err)
|
||||||
|
}
|
||||||
|
return server
|
||||||
|
}
|
||||||
|
|
||||||
|
func metricsTestConfig(metricsConfig *Config) *core.Config {
|
||||||
|
return &core.Config{
|
||||||
|
App: []*serial.TypedMessage{
|
||||||
|
serial.ToTypedMessage(&dispatcher.Config{}),
|
||||||
|
serial.ToTypedMessage(&proxyman.InboundConfig{}),
|
||||||
|
serial.ToTypedMessage(&proxyman.OutboundConfig{}),
|
||||||
|
serial.ToTypedMessage(&appstats.Config{}),
|
||||||
|
serial.ToTypedMessage(metricsConfig),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func pickMetricsListenAddress(t *testing.T) string {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
listener, err := stdnet.Listen("tcp", "127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to pick metrics listen address: %v", err)
|
||||||
|
}
|
||||||
|
defer listener.Close()
|
||||||
|
return listener.Addr().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func readMetricsVars(t *testing.T, server *core.Instance) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
metricsHandler(t, server).httpHandler().ServeHTTP(
|
||||||
|
recorder,
|
||||||
|
httptest.NewRequest(http.MethodGet, "/debug/vars", nil),
|
||||||
|
)
|
||||||
|
|
||||||
|
if recorder.Code != http.StatusOK {
|
||||||
|
t.Fatalf("unexpected metrics vars status: %d", recorder.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload map[string]interface{}
|
||||||
|
if err := json.NewDecoder(recorder.Body).Decode(&payload); err != nil {
|
||||||
|
t.Fatalf("failed to decode metrics vars: %v", err)
|
||||||
|
}
|
||||||
|
if _, found := payload["stats"]; !found {
|
||||||
|
t.Fatal("metrics vars missing stats")
|
||||||
|
}
|
||||||
|
if _, found := payload["observatory"]; !found {
|
||||||
|
t.Fatal("metrics vars missing observatory")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func readMetricsPprof(t *testing.T, server *core.Instance) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
metricsHandler(t, server).httpHandler().ServeHTTP(
|
||||||
|
recorder,
|
||||||
|
httptest.NewRequest(http.MethodGet, "/debug/pprof/goroutine?debug=1", nil),
|
||||||
|
)
|
||||||
|
|
||||||
|
if recorder.Code != http.StatusOK {
|
||||||
|
t.Fatalf("unexpected metrics pprof status: %d", recorder.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func metricsHandler(t *testing.T, server *core.Instance) *MetricsHandler {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
feature := server.GetFeature((*MetricsHandler)(nil))
|
||||||
|
handler, ok := feature.(*MetricsHandler)
|
||||||
|
if !ok || handler == nil {
|
||||||
|
t.Fatal("metrics handler not registered")
|
||||||
|
}
|
||||||
|
return handler
|
||||||
|
}
|
||||||
+1
-1
@@ -20,7 +20,7 @@ import (
|
|||||||
var (
|
var (
|
||||||
Version_x byte = 26
|
Version_x byte = 26
|
||||||
Version_y byte = 6
|
Version_y byte = 6
|
||||||
Version_z byte = 22
|
Version_z byte = 27
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ require (
|
|||||||
github.com/golang/mock v1.7.0-rc.1
|
github.com/golang/mock v1.7.0-rc.1
|
||||||
github.com/google/go-cmp v0.7.0
|
github.com/google/go-cmp v0.7.0
|
||||||
github.com/gorilla/websocket v1.5.3
|
github.com/gorilla/websocket v1.5.3
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0
|
github.com/klauspost/cpuid/v2 v2.4.0
|
||||||
github.com/miekg/dns v1.1.72
|
github.com/miekg/dns v1.1.72
|
||||||
github.com/pelletier/go-toml v1.9.5
|
github.com/pelletier/go-toml v1.9.5
|
||||||
github.com/pion/stun/v3 v3.1.6
|
github.com/pion/stun/v3 v3.1.6
|
||||||
github.com/pires/go-proxyproto v0.12.0
|
github.com/pires/go-proxyproto v0.14.0
|
||||||
github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af
|
github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/sagernet/sing v0.5.1
|
github.com/sagernet/sing v0.5.1
|
||||||
@@ -30,7 +30,7 @@ require (
|
|||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb
|
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb
|
||||||
golang.zx2c4.com/wireguard/windows v1.0.1
|
golang.zx2c4.com/wireguard/windows v1.0.1
|
||||||
google.golang.org/grpc v1.81.1
|
google.golang.org/grpc v1.82.0
|
||||||
google.golang.org/protobuf v1.36.11
|
google.golang.org/protobuf v1.36.11
|
||||||
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0
|
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0
|
||||||
h12.io/socks v1.0.3
|
h12.io/socks v1.0.3
|
||||||
@@ -55,7 +55,7 @@ require (
|
|||||||
golang.org/x/text v0.38.0 // indirect
|
golang.org/x/text v0.38.0 // indirect
|
||||||
golang.org/x/time v0.14.0 // indirect
|
golang.org/x/time v0.14.0 // indirect
|
||||||
golang.org/x/tools v0.45.0 // indirect
|
golang.org/x/tools v0.45.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ github.com/juju/ratelimit v1.0.2 h1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI=
|
|||||||
github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=
|
github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=
|
||||||
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
|
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
|
||||||
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw=
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
github.com/klauspost/cpuid/v2 v2.4.0/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
@@ -53,8 +53,8 @@ github.com/pion/stun/v3 v3.1.6 h1:WnhsD0eHCiwCfKNkVx0VJJwr2Y3eV4Ueih3KJ+dfZy8=
|
|||||||
github.com/pion/stun/v3 v3.1.6/go.mod h1:zRUghXSQU32Lx5orJsz3uYMkIihweXb3mu5gIns02fs=
|
github.com/pion/stun/v3 v3.1.6/go.mod h1:zRUghXSQU32Lx5orJsz3uYMkIihweXb3mu5gIns02fs=
|
||||||
github.com/pion/transport/v4 v4.0.2 h1:ifYlPqNwsy6aKQ9y8yzxXlHae5431ZrH2avkD/Rn6Tk=
|
github.com/pion/transport/v4 v4.0.2 h1:ifYlPqNwsy6aKQ9y8yzxXlHae5431ZrH2avkD/Rn6Tk=
|
||||||
github.com/pion/transport/v4 v4.0.2/go.mod h1:06hFI+jCFcok2X2MekVufNZ/uzNZXivGBPfviSVcjgM=
|
github.com/pion/transport/v4 v4.0.2/go.mod h1:06hFI+jCFcok2X2MekVufNZ/uzNZXivGBPfviSVcjgM=
|
||||||
github.com/pires/go-proxyproto v0.12.0 h1:TTCxD66dU898tahivkqc3hoceZp7P44FnorWyo9d5vM=
|
github.com/pires/go-proxyproto v0.14.0 h1:2vIGIfVG8eVRsKF0xukEoeT5RWhDXxBU0uv6smLOKdI=
|
||||||
github.com/pires/go-proxyproto v0.12.0/go.mod h1:qUvfqUMEoX7T8g0q7TQLDnhMjdTrxnG0hvpMn+7ePNI=
|
github.com/pires/go-proxyproto v0.14.0/go.mod h1:OXsCrKwrK2tXS9YrI5tkHx5xaQlO8FH3lFW76orFh24=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||||
@@ -147,10 +147,10 @@ golang.zx2c4.com/wireguard/windows v1.0.1 h1:eOxiDVbywPC+ZQqvdCK7x+ZwWXKbYv50TtH
|
|||||||
golang.zx2c4.com/wireguard/windows v1.0.1/go.mod h1:+fbT3FFdX4zzYDLwJh5+HPEcNN/3HyNdzhNSVsQM+zs=
|
golang.zx2c4.com/wireguard/windows v1.0.1/go.mod h1:+fbT3FFdX4zzYDLwJh5+HPEcNN/3HyNdzhNSVsQM+zs=
|
||||||
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
||||||
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1:RmoJA1ujG+/lRGNfUnOMfhCy5EipVMyvUE+KNbPbTlw=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||||
google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ=
|
google.golang.org/grpc v1.82.0 h1:vguDnZUPjE26w09A63VoxZPnvPjB5Riyc0mkXPFmAIU=
|
||||||
google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I=
|
google.golang.org/grpc v1.82.0/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA=
|
||||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ func (c *HysteriaClientConfig) Build() (proto.Message, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
config := &hysteria.ClientConfig{}
|
config := &hysteria.ClientConfig{}
|
||||||
config.Version = c.Version
|
|
||||||
config.Server = &protocol.ServerEndpoint{
|
config.Server = &protocol.ServerEndpoint{
|
||||||
Address: c.Address.Build(),
|
Address: c.Address.Build(),
|
||||||
Port: uint32(c.Port),
|
Port: uint32(c.Port),
|
||||||
@@ -44,6 +43,10 @@ type HysteriaServerConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *HysteriaServerConfig) Build() (proto.Message, error) {
|
func (c *HysteriaServerConfig) Build() (proto.Message, error) {
|
||||||
|
if c.Version != 2 {
|
||||||
|
return nil, errors.New("version != 2")
|
||||||
|
}
|
||||||
|
|
||||||
config := new(hysteria.ServerConfig)
|
config := new(hysteria.ServerConfig)
|
||||||
|
|
||||||
if c.Clients != nil {
|
if c.Clients != nil {
|
||||||
|
|||||||
@@ -404,8 +404,8 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
|
|||||||
return nil, errors.New("maxConnections cannot be specified together with maxConcurrency")
|
return nil, errors.New("maxConnections cannot be specified together with maxConcurrency")
|
||||||
}
|
}
|
||||||
if c.Xmux == (XmuxConfig{}) {
|
if c.Xmux == (XmuxConfig{}) {
|
||||||
c.Xmux.MaxConcurrency.From = 1
|
c.Xmux.MaxConnections.From = 6
|
||||||
c.Xmux.MaxConcurrency.To = 1
|
c.Xmux.MaxConnections.To = 6
|
||||||
c.Xmux.HMaxRequestTimes.From = 600
|
c.Xmux.HMaxRequestTimes.From = 600
|
||||||
c.Xmux.HMaxRequestTimes.To = 900
|
c.Xmux.HMaxRequestTimes.To = 900
|
||||||
c.Xmux.HMaxReusableSecs.From = 1800
|
c.Xmux.HMaxReusableSecs.From = 1800
|
||||||
@@ -571,7 +571,6 @@ func (c *HysteriaConfig) Build() (proto.Message, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
config := &hysteria.Config{}
|
config := &hysteria.Config{}
|
||||||
config.Version = c.Version
|
|
||||||
config.Auth = c.Auth
|
config.Auth = c.Auth
|
||||||
config.UdpIdleTimeout = c.UdpIdleTimeout
|
config.UdpIdleTimeout = c.UdpIdleTimeout
|
||||||
config.MasqType = c.Masquerade.Type
|
config.MasqType = c.Masquerade.Type
|
||||||
|
|||||||
+27
-3
@@ -7,6 +7,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/common/protocol"
|
||||||
|
"github.com/xtls/xray-core/common/serial"
|
||||||
|
"github.com/xtls/xray-core/common/task"
|
||||||
"github.com/xtls/xray-core/proxy/wireguard"
|
"github.com/xtls/xray-core/proxy/wireguard"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
@@ -17,9 +20,12 @@ type WireGuardPeerConfig struct {
|
|||||||
Endpoint string `json:"endpoint"`
|
Endpoint string `json:"endpoint"`
|
||||||
KeepAlive uint32 `json:"keepAlive"`
|
KeepAlive uint32 `json:"keepAlive"`
|
||||||
AllowedIPs []string `json:"allowedIPs,omitempty"`
|
AllowedIPs []string `json:"allowedIPs,omitempty"`
|
||||||
|
|
||||||
|
Level uint32 `json:"level"`
|
||||||
|
Email string `json:"email"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *WireGuardPeerConfig) Build() (proto.Message, error) {
|
func (c *WireGuardPeerConfig) Build() (*wireguard.PeerConfig, error) {
|
||||||
var err error
|
var err error
|
||||||
config := new(wireguard.PeerConfig)
|
config := new(wireguard.PeerConfig)
|
||||||
|
|
||||||
@@ -78,14 +84,32 @@ func (c *WireGuardConfig) Build() (proto.Message, error) {
|
|||||||
config.Endpoint = c.Address
|
config.Endpoint = c.Address
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Peers != nil {
|
if c.IsClient {
|
||||||
config.Peers = make([]*wireguard.PeerConfig, len(c.Peers))
|
config.Peers = make([]*wireguard.PeerConfig, len(c.Peers))
|
||||||
for i, p := range c.Peers {
|
for i, p := range c.Peers {
|
||||||
msg, err := p.Build()
|
msg, err := p.Build()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
config.Peers[i] = msg.(*wireguard.PeerConfig)
|
config.Peers[i] = msg
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
config.Users = make([]*protocol.User, len(c.Peers))
|
||||||
|
processUser := func(idx int) error {
|
||||||
|
p := c.Peers[idx]
|
||||||
|
m, err := p.Build()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
config.Users[idx] = &protocol.User{
|
||||||
|
Email: p.Email,
|
||||||
|
Level: p.Level,
|
||||||
|
Account: serial.ToTypedMessage(m),
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := task.ParallelForN(len(c.Peers), processUser); err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
package conf_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
. "github.com/xtls/xray-core/infra/conf"
|
|
||||||
"github.com/xtls/xray-core/proxy/wireguard"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestWireGuardConfig(t *testing.T) {
|
|
||||||
creator := func() Buildable {
|
|
||||||
return new(WireGuardConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
runMultiTestCase(t, []TestCase{
|
|
||||||
{
|
|
||||||
Input: `{
|
|
||||||
"secretKey": "uJv5tZMDltsiYEn+kUwb0Ll/CXWhMkaSCWWhfPEZM3A=",
|
|
||||||
"address": ["10.1.1.1", "fd59:7153:2388:b5fd:0000:0000:1234:0001"],
|
|
||||||
"peers": [
|
|
||||||
{
|
|
||||||
"publicKey": "6e65ce0be17517110c17d77288ad87e7fd5252dcc7d09b95a39d61db03df832a",
|
|
||||||
"endpoint": "127.0.0.1:1234"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"mtu": 1300,
|
|
||||||
"workers": 2,
|
|
||||||
"domainStrategy": "ForceIPv6v4",
|
|
||||||
"noKernelTun": false
|
|
||||||
}`,
|
|
||||||
Parser: loadJSON(creator),
|
|
||||||
Output: &wireguard.DeviceConfig{
|
|
||||||
// key converted into hex form
|
|
||||||
SecretKey: "b89bf9b5930396db226049fe914c1bd0b97f0975a13246920965a17cf1193370",
|
|
||||||
Endpoint: []string{"10.1.1.1", "fd59:7153:2388:b5fd:0000:0000:1234:0001"},
|
|
||||||
Peers: []*wireguard.PeerConfig{
|
|
||||||
{
|
|
||||||
// also can read from hex form directly
|
|
||||||
PublicKey: "6e65ce0be17517110c17d77288ad87e7fd5252dcc7d09b95a39d61db03df832a",
|
|
||||||
Endpoint: "127.0.0.1:1234",
|
|
||||||
AllowedIps: []string{"0.0.0.0/0", "::0/0"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Mtu: 1300,
|
|
||||||
DomainStrategy: wireguard.DeviceConfig_FORCE_IP64,
|
|
||||||
NoKernelTun: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -3,25 +3,32 @@ package account
|
|||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/common/protocol"
|
"github.com/xtls/xray-core/common/protocol"
|
||||||
|
"github.com/xtls/xray-core/common/uuid"
|
||||||
|
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a *Account) AsAccount() (protocol.Account, error) {
|
func (a *Account) AsAccount() (protocol.Account, error) {
|
||||||
|
var VR net.Port
|
||||||
|
if id, err := uuid.ParseString(a.Auth); err == nil {
|
||||||
|
VR = net.PortFromBytes(id[6:8])
|
||||||
|
}
|
||||||
return &MemoryAccount{
|
return &MemoryAccount{
|
||||||
Auth: a.Auth,
|
Auth: a.Auth,
|
||||||
|
VR: VR,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type MemoryAccount struct {
|
type MemoryAccount struct {
|
||||||
Auth string
|
Auth string
|
||||||
|
VR net.Port
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *MemoryAccount) Equals(another protocol.Account) bool {
|
func (a *MemoryAccount) Equals(other protocol.Account) bool {
|
||||||
if account, ok := another.(*MemoryAccount); ok {
|
if b, ok := other.(*MemoryAccount); ok {
|
||||||
return a.Auth == account.Auth
|
return a.Auth == b.Auth
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -33,97 +40,63 @@ func (a *MemoryAccount) ToProto() proto.Message {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Validator struct {
|
type Validator struct {
|
||||||
emails map[string]struct{}
|
users sync.Map
|
||||||
users map[string]*protocol.MemoryUser
|
|
||||||
|
|
||||||
mutex sync.Mutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewValidator() *Validator {
|
func NewValidator() *Validator {
|
||||||
return &Validator{
|
return &Validator{}
|
||||||
emails: make(map[string]struct{}),
|
|
||||||
users: make(map[string]*protocol.MemoryUser),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Validator) Add(u *protocol.MemoryUser) error {
|
func (v *Validator) Add(user *protocol.MemoryUser) error {
|
||||||
v.mutex.Lock()
|
v.users.Store(user.Account.(*MemoryAccount).Auth, user)
|
||||||
defer v.mutex.Unlock()
|
|
||||||
|
|
||||||
if u.Email != "" {
|
|
||||||
if _, ok := v.emails[u.Email]; ok {
|
|
||||||
return errors.New("User ", u.Email, " already exists.")
|
|
||||||
}
|
|
||||||
v.emails[u.Email] = struct{}{}
|
|
||||||
}
|
|
||||||
v.users[u.Account.(*MemoryAccount).Auth] = u
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Validator) Del(email string) error {
|
func (v *Validator) DelByEmail(email string) error {
|
||||||
if email == "" {
|
if user := v.GetByEmail(email); user != nil {
|
||||||
return errors.New("Email must not be empty.")
|
v.users.Delete(user.Account.(*MemoryAccount).Auth)
|
||||||
}
|
}
|
||||||
|
|
||||||
v.mutex.Lock()
|
|
||||||
defer v.mutex.Unlock()
|
|
||||||
|
|
||||||
if _, ok := v.emails[email]; !ok {
|
|
||||||
return errors.New("User ", email, " not found.")
|
|
||||||
}
|
|
||||||
delete(v.emails, email)
|
|
||||||
for key, user := range v.users {
|
|
||||||
if user.Email == email {
|
|
||||||
delete(v.users, key)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Validator) Get(auth string) *protocol.MemoryUser {
|
func (v *Validator) Get(auth string) *protocol.MemoryUser {
|
||||||
v.mutex.Lock()
|
if value, ok := v.users.Load(auth); ok {
|
||||||
defer v.mutex.Unlock()
|
return value.(*protocol.MemoryUser)
|
||||||
|
|
||||||
return v.users[auth]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *Validator) GetByEmail(email string) *protocol.MemoryUser {
|
|
||||||
if email == "" {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
v.mutex.Lock()
|
|
||||||
defer v.mutex.Unlock()
|
|
||||||
|
|
||||||
if _, ok := v.emails[email]; ok {
|
|
||||||
for _, user := range v.users {
|
|
||||||
if user.Email == email {
|
|
||||||
return user
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Validator) GetAll() []*protocol.MemoryUser {
|
func (v *Validator) GetByEmail(email string) (user *protocol.MemoryUser) {
|
||||||
v.mutex.Lock()
|
v.users.Range(func(key, value any) bool {
|
||||||
defer v.mutex.Unlock()
|
if value.(*protocol.MemoryUser).Email == email {
|
||||||
|
user = value.(*protocol.MemoryUser)
|
||||||
users := make([]*protocol.MemoryUser, 0, len(v.users))
|
return false
|
||||||
for _, user := range v.users {
|
}
|
||||||
users = append(users, user)
|
return true
|
||||||
}
|
})
|
||||||
|
return
|
||||||
return users
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Validator) GetCount() int64 {
|
func (v *Validator) GetAll() (users []*protocol.MemoryUser) {
|
||||||
v.mutex.Lock()
|
v.users.Range(func(key, value any) bool {
|
||||||
defer v.mutex.Unlock()
|
users = append(users, value.(*protocol.MemoryUser))
|
||||||
|
return true
|
||||||
return int64(len(v.users))
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Validator) GetCount() (count int64) {
|
||||||
|
v.users.Range(func(key, value any) bool {
|
||||||
|
count++
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Validator) NotEmpty() (not_empty bool) {
|
||||||
|
v.users.Range(func(key, value any) bool {
|
||||||
|
not_empty = true
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,8 +24,7 @@ const (
|
|||||||
|
|
||||||
type ClientConfig struct {
|
type ClientConfig struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Version int32 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"`
|
Server *protocol.ServerEndpoint `protobuf:"bytes,1,opt,name=server,proto3" json:"server,omitempty"`
|
||||||
Server *protocol.ServerEndpoint `protobuf:"bytes,2,opt,name=server,proto3" json:"server,omitempty"`
|
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
@@ -60,13 +59,6 @@ func (*ClientConfig) Descriptor() ([]byte, []int) {
|
|||||||
return file_proxy_hysteria_config_proto_rawDescGZIP(), []int{0}
|
return file_proxy_hysteria_config_proto_rawDescGZIP(), []int{0}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ClientConfig) GetVersion() int32 {
|
|
||||||
if x != nil {
|
|
||||||
return x.Version
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *ClientConfig) GetServer() *protocol.ServerEndpoint {
|
func (x *ClientConfig) GetServer() *protocol.ServerEndpoint {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.Server
|
return x.Server
|
||||||
@@ -122,10 +114,9 @@ var File_proxy_hysteria_config_proto protoreflect.FileDescriptor
|
|||||||
|
|
||||||
const file_proxy_hysteria_config_proto_rawDesc = "" +
|
const file_proxy_hysteria_config_proto_rawDesc = "" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"\x1bproxy/hysteria/config.proto\x12\x13xray.proxy.hysteria\x1a!common/protocol/server_spec.proto\x1a\x1acommon/protocol/user.proto\"f\n" +
|
"\x1bproxy/hysteria/config.proto\x12\x13xray.proxy.hysteria\x1a!common/protocol/server_spec.proto\x1a\x1acommon/protocol/user.proto\"L\n" +
|
||||||
"\fClientConfig\x12\x18\n" +
|
"\fClientConfig\x12<\n" +
|
||||||
"\aversion\x18\x01 \x01(\x05R\aversion\x12<\n" +
|
"\x06server\x18\x01 \x01(\v2$.xray.common.protocol.ServerEndpointR\x06server\"@\n" +
|
||||||
"\x06server\x18\x02 \x01(\v2$.xray.common.protocol.ServerEndpointR\x06server\"@\n" +
|
|
||||||
"\fServerConfig\x120\n" +
|
"\fServerConfig\x120\n" +
|
||||||
"\x05users\x18\x01 \x03(\v2\x1a.xray.common.protocol.UserR\x05usersB[\n" +
|
"\x05users\x18\x01 \x03(\v2\x1a.xray.common.protocol.UserR\x05usersB[\n" +
|
||||||
"\x17com.xray.proxy.hysteriaP\x01Z(github.com/xtls/xray-core/proxy/hysteria\xaa\x02\x13Xray.Proxy.Hysteriab\x06proto3"
|
"\x17com.xray.proxy.hysteriaP\x01Z(github.com/xtls/xray-core/proxy/hysteria\xaa\x02\x13Xray.Proxy.Hysteriab\x06proto3"
|
||||||
|
|||||||
@@ -10,8 +10,7 @@ import "common/protocol/server_spec.proto";
|
|||||||
import "common/protocol/user.proto";
|
import "common/protocol/user.proto";
|
||||||
|
|
||||||
message ClientConfig {
|
message ClientConfig {
|
||||||
int32 version = 1;
|
xray.common.protocol.ServerEndpoint server = 1;
|
||||||
xray.common.protocol.ServerEndpoint server = 2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message ServerConfig {
|
message ServerConfig {
|
||||||
|
|||||||
@@ -59,12 +59,12 @@ func (s *Server) HysteriaInboundValidator() *account.Validator {
|
|||||||
return s.validator
|
return s.validator
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) AddUser(ctx context.Context, u *protocol.MemoryUser) error {
|
func (s *Server) AddUser(ctx context.Context, user *protocol.MemoryUser) error {
|
||||||
return s.validator.Add(u)
|
return s.validator.Add(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) RemoveUser(ctx context.Context, e string) error {
|
func (s *Server) RemoveUser(ctx context.Context, email string) error {
|
||||||
return s.validator.Del(e)
|
return s.validator.DelByEmail(email)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) GetUser(ctx context.Context, email string) *protocol.MemoryUser {
|
func (s *Server) GetUser(ctx context.Context, email string) *protocol.MemoryUser {
|
||||||
@@ -91,9 +91,12 @@ func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Con
|
|||||||
|
|
||||||
iConn := stat.TryUnwrapStatsConn(conn)
|
iConn := stat.TryUnwrapStatsConn(conn)
|
||||||
|
|
||||||
type User interface{ User() *protocol.MemoryUser }
|
if v, ok := iConn.(interface{ User() *protocol.MemoryUser }); ok {
|
||||||
if v, ok := iConn.(User); ok && v.User() != nil {
|
user := v.User()
|
||||||
inbound.User = v.User()
|
if user != nil {
|
||||||
|
inbound.User = user
|
||||||
|
inbound.VlessRoute = user.Account.(*account.MemoryAccount).VR
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := iConn.(*hysteria.InterConn); ok {
|
if _, ok := iConn.(*hysteria.InterConn); ok {
|
||||||
|
|||||||
+7
-81
@@ -3,8 +3,6 @@ package tun
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
@@ -31,95 +29,23 @@ func (updater *InterfaceUpdater) Update() {
|
|||||||
updater.Lock()
|
updater.Lock()
|
||||||
defer updater.Unlock()
|
defer updater.Unlock()
|
||||||
|
|
||||||
if updater.iface != nil {
|
got, err := findOutboundInterface(updater.tunIndex, updater.fixedName)
|
||||||
iface, err := net.InterfaceByIndex(updater.iface.Index)
|
|
||||||
if err == nil && iface.Name == updater.iface.Name {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updater.iface = nil
|
|
||||||
|
|
||||||
interfaces, err := net.Interfaces()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errors.LogInfoInner(context.Background(), err, "[tun] failed to update interface")
|
errors.LogInfoInner(context.Background(), err, "[tun] failed to update interface")
|
||||||
|
updater.iface = nil
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var got *net.Interface
|
|
||||||
if updater.fixedName != "" {
|
|
||||||
for _, iface := range interfaces {
|
|
||||||
if iface.Index == updater.tunIndex {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if iface.Name == updater.fixedName {
|
|
||||||
got = &iface
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var ifs []struct {
|
|
||||||
index int
|
|
||||||
score int
|
|
||||||
}
|
|
||||||
for i, iface := range interfaces {
|
|
||||||
if iface.Index == updater.tunIndex {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.Contains(iface.Name, "vEthernet") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if iface.Flags&net.FlagUp == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if iface.Flags&net.FlagLoopback != 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
addrs, err := iface.Addrs()
|
|
||||||
if err != nil || len(addrs) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
ifs = append(ifs, struct {
|
|
||||||
index int
|
|
||||||
score int
|
|
||||||
}{i, score(&iface, addrs)})
|
|
||||||
}
|
|
||||||
sort.Slice(ifs, func(i, j int) bool {
|
|
||||||
if ifs[i].score != ifs[j].score {
|
|
||||||
return ifs[i].score > ifs[j].score
|
|
||||||
}
|
|
||||||
|
|
||||||
return interfaces[ifs[i].index].Name < interfaces[ifs[j].index].Name
|
|
||||||
})
|
|
||||||
if len(ifs) > 0 {
|
|
||||||
iface := interfaces[ifs[0].index]
|
|
||||||
got = &iface
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if got == nil {
|
if got == nil {
|
||||||
errors.LogInfo(context.Background(), "[tun] failed to update interface > got == nil")
|
errors.LogInfo(context.Background(), "[tun] failed to update interface > got == nil")
|
||||||
|
updater.iface = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if updater.iface != nil && updater.iface.Index == got.Index && updater.iface.Name == got.Name {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
updater.iface = got
|
updater.iface = got
|
||||||
errors.LogInfo(context.Background(), "[tun] update interface ", got.Name, " ", got.Index)
|
errors.LogInfo(context.Background(), "[tun] update interface ", got.Name, " ", got.Index)
|
||||||
}
|
}
|
||||||
|
|
||||||
func score(iface *net.Interface, addrs []net.Addr) int {
|
|
||||||
score := 0
|
|
||||||
|
|
||||||
name := strings.ToLower(iface.Name)
|
|
||||||
if strings.Contains(name, "wlan") || strings.Contains(name, "wi-fi") {
|
|
||||||
score += 2
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, addr := range addrs {
|
|
||||||
if strings.HasPrefix(addr.String(), "192.168.") {
|
|
||||||
score += 1
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return score
|
|
||||||
}
|
|
||||||
|
|||||||
+36
-1
@@ -17,6 +17,7 @@ import (
|
|||||||
"github.com/xtls/xray-core/core"
|
"github.com/xtls/xray-core/core"
|
||||||
"github.com/xtls/xray-core/features/policy"
|
"github.com/xtls/xray-core/features/policy"
|
||||||
"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/transport"
|
"github.com/xtls/xray-core/transport"
|
||||||
"github.com/xtls/xray-core/transport/internet"
|
"github.com/xtls/xray-core/transport/internet"
|
||||||
"github.com/xtls/xray-core/transport/internet/stat"
|
"github.com/xtls/xray-core/transport/internet/stat"
|
||||||
@@ -32,6 +33,8 @@ type Handler struct {
|
|||||||
dispatcher routing.Dispatcher
|
dispatcher routing.Dispatcher
|
||||||
tag string
|
tag string
|
||||||
sniffingRequest session.SniffingRequest
|
sniffingRequest session.SniffingRequest
|
||||||
|
uplinkCounter stats.Counter
|
||||||
|
downlinkCounter stats.Counter
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConnectionHandler interface with the only method that stack is going to push new connections to
|
// ConnectionHandler interface with the only method that stack is going to push new connections to
|
||||||
@@ -59,6 +62,23 @@ func (t *Handler) Init(ctx context.Context, pm policy.Manager, dispatcher routin
|
|||||||
t.policyManager = pm
|
t.policyManager = pm
|
||||||
t.dispatcher = dispatcher
|
t.dispatcher = dispatcher
|
||||||
|
|
||||||
|
if len(t.tag) > 0 && pm.ForSystem().Stats.InboundUplink {
|
||||||
|
statsManager := core.MustFromContext(ctx).GetFeature(stats.ManagerType()).(stats.Manager)
|
||||||
|
name := "inbound>>>" + t.tag + ">>>traffic>>>uplink"
|
||||||
|
c, _ := stats.GetOrRegisterCounter(statsManager, name)
|
||||||
|
if c != nil {
|
||||||
|
t.uplinkCounter = c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(t.tag) > 0 && pm.ForSystem().Stats.InboundDownlink {
|
||||||
|
statsManager := core.MustFromContext(ctx).GetFeature(stats.ManagerType()).(stats.Manager)
|
||||||
|
name := "inbound>>>" + t.tag + ">>>traffic>>>downlink"
|
||||||
|
c, _ := stats.GetOrRegisterCounter(statsManager, name)
|
||||||
|
if c != nil {
|
||||||
|
t.downlinkCounter = c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,7 +163,22 @@ func (t *Handler) HandleConnection(conn net.Conn, destination net.Destination) {
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
ctx = c.ContextWithID(ctx, session.NewID())
|
ctx = c.ContextWithID(ctx, session.NewID())
|
||||||
|
|
||||||
source := net.DestinationFromAddr(conn.RemoteAddr())
|
// if the connection is already closed, conn.RemoteAddr() will be nil
|
||||||
|
// due to gvisor weird behavior
|
||||||
|
remote := conn.RemoteAddr()
|
||||||
|
if remote == nil {
|
||||||
|
errors.LogInfo(t.ctx, "dropped quickly closed connection")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
source := net.DestinationFromAddr(remote)
|
||||||
|
if t.uplinkCounter != nil || t.downlinkCounter != nil {
|
||||||
|
conn = &stat.CounterConnection{
|
||||||
|
Connection: conn,
|
||||||
|
ReadCounter: t.uplinkCounter,
|
||||||
|
WriteCounter: t.downlinkCounter,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
inbound := session.Inbound{
|
inbound := session.Inbound{
|
||||||
Name: "tun",
|
Name: "tun",
|
||||||
Tag: t.tag,
|
Tag: t.tag,
|
||||||
|
|||||||
@@ -0,0 +1,133 @@
|
|||||||
|
package tun
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"sync/atomic"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common/buf"
|
||||||
|
xnet "github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/features/routing"
|
||||||
|
"github.com/xtls/xray-core/transport"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testCounter struct {
|
||||||
|
value int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *testCounter) Value() int64 {
|
||||||
|
return atomic.LoadInt64(&c.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *testCounter) Set(value int64) int64 {
|
||||||
|
return atomic.SwapInt64(&c.value, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *testCounter) Add(value int64) int64 {
|
||||||
|
return atomic.AddInt64(&c.value, value) - value
|
||||||
|
}
|
||||||
|
|
||||||
|
type testConn struct {
|
||||||
|
reader *bytes.Reader
|
||||||
|
writer bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestConn(input []byte) *testConn {
|
||||||
|
return &testConn{reader: bytes.NewReader(input)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *testConn) Read(payload []byte) (int, error) {
|
||||||
|
return c.reader.Read(payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *testConn) Write(payload []byte) (int, error) {
|
||||||
|
return c.writer.Write(payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *testConn) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *testConn) LocalAddr() net.Addr {
|
||||||
|
return &net.TCPAddr{IP: net.IPv4(10, 0, 0, 1), Port: 1080}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *testConn) RemoteAddr() net.Addr {
|
||||||
|
return &net.TCPAddr{IP: net.IPv4(10, 0, 0, 2), Port: 12345}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *testConn) SetDeadline(time.Time) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *testConn) SetReadDeadline(time.Time) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *testConn) SetWriteDeadline(time.Time) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type testDispatcher struct {
|
||||||
|
writePayload []byte
|
||||||
|
readBytes int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *testDispatcher) Type() interface{} {
|
||||||
|
return routing.DispatcherType()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *testDispatcher) Start() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *testDispatcher) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *testDispatcher) Dispatch(context.Context, xnet.Destination) (*transport.Link, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *testDispatcher) DispatchLink(ctx context.Context, dest xnet.Destination, link *transport.Link) error {
|
||||||
|
mb, err := link.Reader.ReadMultiBuffer()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
atomic.StoreInt32(&d.readBytes, mb.Len())
|
||||||
|
buf.ReleaseMulti(mb)
|
||||||
|
|
||||||
|
return link.Writer.WriteMultiBuffer(buf.MultiBuffer{buf.FromBytes(d.writePayload)})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandlerCountsTunConnectionTraffic(t *testing.T) {
|
||||||
|
uplinkCounter := new(testCounter)
|
||||||
|
downlinkCounter := new(testCounter)
|
||||||
|
dispatcher := &testDispatcher{writePayload: []byte("downlink")}
|
||||||
|
conn := newTestConn([]byte("uplink"))
|
||||||
|
|
||||||
|
handler := &Handler{
|
||||||
|
ctx: context.Background(),
|
||||||
|
config: &Config{},
|
||||||
|
dispatcher: dispatcher,
|
||||||
|
uplinkCounter: uplinkCounter,
|
||||||
|
downlinkCounter: downlinkCounter,
|
||||||
|
}
|
||||||
|
handler.HandleConnection(conn, xnet.TCPDestination(xnet.LocalHostIP, 443))
|
||||||
|
|
||||||
|
if got := uplinkCounter.Value(); got != int64(len("uplink")) {
|
||||||
|
t.Fatalf("unexpected uplink counter: got %d, want %d", got, len("uplink"))
|
||||||
|
}
|
||||||
|
if got := downlinkCounter.Value(); got != int64(len("downlink")) {
|
||||||
|
t.Fatalf("unexpected downlink counter: got %d, want %d", got, len("downlink"))
|
||||||
|
}
|
||||||
|
if got := int(atomic.LoadInt32(&dispatcher.readBytes)); got != len("uplink") {
|
||||||
|
t.Fatalf("dispatcher read unexpected bytes: got %d, want %d", got, len("uplink"))
|
||||||
|
}
|
||||||
|
if got := conn.writer.String(); got != "downlink" {
|
||||||
|
t.Fatalf("connection write mismatch: got %q, want %q", got, "downlink")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -81,3 +81,17 @@ func (t *AndroidTun) newEndpoint() (stack.LinkEndpoint, error) {
|
|||||||
func setinterface(network, address string, fd uintptr, iface *net.Interface) error {
|
func setinterface(network, address string, fd uintptr, iface *net.Interface) error {
|
||||||
return unix.BindToDevice(int(fd), iface.Name)
|
return unix.BindToDevice(int(fd), iface.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func findOutboundInterface(tunIndex int, fixedName string) (*net.Interface, error) {
|
||||||
|
if fixedName == "" {
|
||||||
|
return nil, errors.New("automatic outbound interface selection is not supported on this platform")
|
||||||
|
}
|
||||||
|
iface, err := net.InterfaceByName(fixedName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if iface.Index == tunIndex {
|
||||||
|
return nil, errors.New("outbound interface cannot be the TUN interface")
|
||||||
|
}
|
||||||
|
return iface, nil
|
||||||
|
}
|
||||||
|
|||||||
+272
-2
@@ -3,16 +3,20 @@
|
|||||||
package tun
|
package tun
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"sync"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/buf"
|
"github.com/xtls/xray-core/common/buf"
|
||||||
|
xerrors "github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/platform"
|
"github.com/xtls/xray-core/common/platform"
|
||||||
|
"golang.org/x/net/route"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
"gvisor.dev/gvisor/pkg/buffer"
|
"gvisor.dev/gvisor/pkg/buffer"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip"
|
"gvisor.dev/gvisor/pkg/tcpip"
|
||||||
@@ -42,6 +46,10 @@ type DarwinTun struct {
|
|||||||
options *Config
|
options *Config
|
||||||
tunFd int
|
tunFd int
|
||||||
ownsFd bool // true for macOS (we created the fd), false for iOS (fd from system)
|
ownsFd bool // true for macOS (we created the fd), false for iOS (fd from system)
|
||||||
|
|
||||||
|
routeMonitor *os.File
|
||||||
|
routeMonitorOnce sync.Once
|
||||||
|
systemRoutes []netip.Prefix
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -92,15 +100,53 @@ func NewTun(options *Config) (Tun, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *DarwinTun) Start() error {
|
func (t *DarwinTun) Start() error {
|
||||||
|
if !t.ownsFd {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := t.setSystemRoutes(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if updater != nil {
|
||||||
|
fd, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, 0)
|
||||||
|
if err != nil {
|
||||||
|
_ = t.unsetSystemRoutes()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
t.routeMonitor = os.NewFile(uintptr(fd), "xray-route-monitor")
|
||||||
|
go t.monitorRouteChanges()
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *DarwinTun) Close() error {
|
func (t *DarwinTun) Close() error {
|
||||||
|
t.routeMonitorOnce.Do(func() {
|
||||||
|
if t.routeMonitor != nil {
|
||||||
|
_ = t.routeMonitor.Close()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
routeErr := t.unsetSystemRoutes()
|
||||||
if t.ownsFd {
|
if t.ownsFd {
|
||||||
return t.tunFile.Close()
|
return xerrors.Combine(routeErr, t.tunFile.Close())
|
||||||
}
|
}
|
||||||
// iOS: don't close the fd, it's owned by NetworkExtension
|
// iOS: don't close the fd, it's owned by NetworkExtension
|
||||||
return nil
|
return routeErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *DarwinTun) monitorRouteChanges() {
|
||||||
|
buffer := make([]byte, 64*1024)
|
||||||
|
for {
|
||||||
|
if _, err := t.routeMonitor.Read(buffer); err != nil {
|
||||||
|
if !errors.Is(err, os.ErrClosed) {
|
||||||
|
xerrors.LogInfoInner(context.Background(), err, "[tun] failed to monitor route changes")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if updater != nil {
|
||||||
|
updater.Update()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *DarwinTun) Name() (string, error) {
|
func (t *DarwinTun) Name() (string, error) {
|
||||||
@@ -388,3 +434,227 @@ func setinterface(network, address string, fd uintptr, iface *net.Interface) err
|
|||||||
|
|
||||||
return errors.Join(err1, err2)
|
return errors.Join(err1, err2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func findOutboundInterface(tunIndex int, fixedName string) (*net.Interface, error) {
|
||||||
|
if fixedName != "" {
|
||||||
|
iface, err := net.InterfaceByName(fixedName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if iface.Index == tunIndex {
|
||||||
|
return nil, errors.New("outbound interface cannot be the TUN interface")
|
||||||
|
}
|
||||||
|
return iface, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
rib, err := route.FetchRIB(unix.AF_UNSPEC, route.RIBTypeRoute, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
messages, err := route.ParseRIB(route.RIBTypeRoute, rib)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var ipv6Index int
|
||||||
|
for _, message := range messages {
|
||||||
|
routeMessage, ok := message.(*route.RouteMessage)
|
||||||
|
if !ok || routeMessage.Index == tunIndex {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if routeMessage.Flags&unix.RTF_UP == 0 || routeMessage.Flags&unix.RTF_GATEWAY == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
family, ok := defaultRouteFamily(routeMessage)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if family == unix.AF_INET {
|
||||||
|
return usableDarwinInterface(routeMessage.Index)
|
||||||
|
}
|
||||||
|
if family == unix.AF_INET6 && ipv6Index == 0 {
|
||||||
|
ipv6Index = routeMessage.Index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ipv6Index != 0 {
|
||||||
|
return usableDarwinInterface(ipv6Index)
|
||||||
|
}
|
||||||
|
return nil, errors.New("default route not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultRouteFamily(message *route.RouteMessage) (int, bool) {
|
||||||
|
if len(message.Addrs) <= unix.RTAX_NETMASK {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
switch destination := message.Addrs[unix.RTAX_DST].(type) {
|
||||||
|
case *route.Inet4Addr:
|
||||||
|
mask, ok := message.Addrs[unix.RTAX_NETMASK].(*route.Inet4Addr)
|
||||||
|
if !ok || destination.IP != netip.IPv4Unspecified().As4() {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
ones, bits := net.IPMask(mask.IP[:]).Size()
|
||||||
|
return unix.AF_INET, ones == 0 && bits == 32
|
||||||
|
case *route.Inet6Addr:
|
||||||
|
mask, ok := message.Addrs[unix.RTAX_NETMASK].(*route.Inet6Addr)
|
||||||
|
if !ok || destination.IP != netip.IPv6Unspecified().As16() {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
ones, bits := net.IPMask(mask.IP[:]).Size()
|
||||||
|
return unix.AF_INET6, ones == 0 && bits == 128
|
||||||
|
default:
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func usableDarwinInterface(index int) (*net.Interface, error) {
|
||||||
|
iface, err := net.InterfaceByIndex(index)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 {
|
||||||
|
return nil, errors.New("default route interface is not usable")
|
||||||
|
}
|
||||||
|
return iface, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *DarwinTun) setSystemRoutes() error {
|
||||||
|
routes, err := buildDarwinSystemRoutes(t.options.AutoSystemRoutingTable)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(routes) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
tunIndex, err := t.Index()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, destination := range routes {
|
||||||
|
if err := execDarwinRoute(unix.RTM_ADD, tunIndex, destination); err != nil {
|
||||||
|
_ = t.unsetSystemRoutes()
|
||||||
|
return xerrors.New("failed to add system route ", destination).Base(err)
|
||||||
|
}
|
||||||
|
t.systemRoutes = append(t.systemRoutes, destination)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *DarwinTun) unsetSystemRoutes() error {
|
||||||
|
var errs []error
|
||||||
|
tunIndex, indexErr := t.Index()
|
||||||
|
if indexErr != nil && len(t.systemRoutes) > 0 {
|
||||||
|
errs = append(errs, indexErr)
|
||||||
|
}
|
||||||
|
for i := len(t.systemRoutes) - 1; i >= 0; i-- {
|
||||||
|
destination := t.systemRoutes[i]
|
||||||
|
if err := execDarwinRoute(unix.RTM_DELETE, tunIndex, destination); err != nil && !errors.Is(err, unix.ESRCH) {
|
||||||
|
errs = append(errs, xerrors.New("failed to delete system route ", destination).Base(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.systemRoutes = nil
|
||||||
|
return xerrors.Combine(errs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildDarwinSystemRoutes(configured []string) ([]netip.Prefix, error) {
|
||||||
|
routes := make([]netip.Prefix, 0, len(configured))
|
||||||
|
seen := make(map[netip.Prefix]struct{})
|
||||||
|
|
||||||
|
appendRoute := func(prefix netip.Prefix) {
|
||||||
|
prefix = prefix.Masked()
|
||||||
|
if _, found := seen[prefix]; found {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
seen[prefix] = struct{}{}
|
||||||
|
routes = append(routes, prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, value := range configured {
|
||||||
|
prefix, err := netip.ParsePrefix(value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.New("invalid system route ", value).Base(err)
|
||||||
|
}
|
||||||
|
prefix = prefix.Masked()
|
||||||
|
if prefix.Bits() == 0 {
|
||||||
|
for _, protected := range darwinProtectedDefaultRoutes(prefix.Addr().Is4()) {
|
||||||
|
appendRoute(protected)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
appendRoute(prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
return routes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func darwinProtectedDefaultRoutes(ipv4 bool) []netip.Prefix {
|
||||||
|
routes := make([]netip.Prefix, 0, 8)
|
||||||
|
for i := 0; i < 8; i++ {
|
||||||
|
if ipv4 {
|
||||||
|
var address [4]byte
|
||||||
|
address[0] = 1 << i
|
||||||
|
routes = append(routes, netip.PrefixFrom(netip.AddrFrom4(address), 8-i))
|
||||||
|
} else {
|
||||||
|
var address [16]byte
|
||||||
|
address[0] = 1 << i
|
||||||
|
routes = append(routes, netip.PrefixFrom(netip.AddrFrom16(address), 8-i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return routes
|
||||||
|
}
|
||||||
|
|
||||||
|
func execDarwinRoute(messageType int, interfaceIndex int, destination netip.Prefix) error {
|
||||||
|
message := route.RouteMessage{
|
||||||
|
Type: messageType,
|
||||||
|
Version: unix.RTM_VERSION,
|
||||||
|
Flags: unix.RTF_STATIC | unix.RTF_GATEWAY,
|
||||||
|
Seq: 1,
|
||||||
|
}
|
||||||
|
if messageType == unix.RTM_ADD {
|
||||||
|
message.Flags |= unix.RTF_UP
|
||||||
|
}
|
||||||
|
|
||||||
|
if destination.Addr().Is4() {
|
||||||
|
gatewayPrefix := netip.MustParsePrefix(gateway)
|
||||||
|
message.Addrs = []route.Addr{
|
||||||
|
unix.RTAX_DST: &route.Inet4Addr{IP: destination.Addr().As4()},
|
||||||
|
unix.RTAX_NETMASK: &route.Inet4Addr{IP: prefixMask4(destination.Bits())},
|
||||||
|
unix.RTAX_GATEWAY: &route.Inet4Addr{IP: gatewayPrefix.Addr().As4()},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
message.Flags &^= unix.RTF_GATEWAY
|
||||||
|
message.Index = interfaceIndex
|
||||||
|
message.Addrs = []route.Addr{
|
||||||
|
unix.RTAX_DST: &route.Inet6Addr{IP: destination.Addr().As16()},
|
||||||
|
unix.RTAX_NETMASK: &route.Inet6Addr{IP: prefixMask6(destination.Bits())},
|
||||||
|
unix.RTAX_GATEWAY: &route.LinkAddr{Index: interfaceIndex},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
request, err := message.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fd, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer unix.Close(fd)
|
||||||
|
_, err = unix.Write(fd, request)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func prefixMask4(bits int) [4]byte {
|
||||||
|
var mask [4]byte
|
||||||
|
copy(mask[:], net.CIDRMask(bits, 32))
|
||||||
|
return mask
|
||||||
|
}
|
||||||
|
|
||||||
|
func prefixMask6(bits int) [16]byte {
|
||||||
|
var mask [16]byte
|
||||||
|
copy(mask[:], net.CIDRMask(bits, 128))
|
||||||
|
return mask
|
||||||
|
}
|
||||||
|
|||||||
@@ -42,3 +42,17 @@ func (t *DefaultTun) newEndpoint() (stack.LinkEndpoint, error) {
|
|||||||
func setinterface(string, string, uintptr, *net.Interface) error {
|
func setinterface(string, string, uintptr, *net.Interface) error {
|
||||||
return errors.New("Tun is not supported on your platform")
|
return errors.New("Tun is not supported on your platform")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func findOutboundInterface(tunIndex int, fixedName string) (*net.Interface, error) {
|
||||||
|
if fixedName == "" {
|
||||||
|
return nil, errors.New("automatic outbound interface selection is not supported on this platform")
|
||||||
|
}
|
||||||
|
iface, err := net.InterfaceByName(fixedName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if iface.Index == tunIndex {
|
||||||
|
return nil, errors.New("outbound interface cannot be the TUN interface")
|
||||||
|
}
|
||||||
|
return iface, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -147,3 +147,17 @@ func (t *FreeBSDTun) newEndpoint() (stack.LinkEndpoint, error) {
|
|||||||
func setinterface(network, address string, fd uintptr, iface *net.Interface) error {
|
func setinterface(network, address string, fd uintptr, iface *net.Interface) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func findOutboundInterface(tunIndex int, fixedName string) (*net.Interface, error) {
|
||||||
|
if fixedName == "" {
|
||||||
|
return nil, errors.New("automatic outbound interface selection is not supported on this platform")
|
||||||
|
}
|
||||||
|
iface, err := net.InterfaceByName(fixedName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if iface.Index == tunIndex {
|
||||||
|
return nil, errors.New("outbound interface cannot be the TUN interface")
|
||||||
|
}
|
||||||
|
return iface, nil
|
||||||
|
}
|
||||||
|
|||||||
+153
-2
@@ -3,8 +3,11 @@
|
|||||||
package tun
|
package tun
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/vishvananda/netlink"
|
"github.com/vishvananda/netlink"
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
@@ -22,6 +25,10 @@ type LinuxTun struct {
|
|||||||
tunLink netlink.Link
|
tunLink netlink.Link
|
||||||
options *Config
|
options *Config
|
||||||
ownsTun bool
|
ownsTun bool
|
||||||
|
|
||||||
|
systemRoutes []netlink.Route
|
||||||
|
routeMonitorStop chan struct{}
|
||||||
|
routeMonitorOnce sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
// LinuxTun implements Tun
|
// LinuxTun implements Tun
|
||||||
@@ -161,16 +168,32 @@ func (t *LinuxTun) Start() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err := netlink.LinkSetUp(t.tunLink)
|
if err := netlink.LinkSetUp(t.tunLink); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := t.setSystemRoutes(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if updater != nil {
|
||||||
|
t.routeMonitorStop = make(chan struct{})
|
||||||
|
go t.monitorRouteChanges()
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close is called to shut down the tun interface
|
// Close is called to shut down the tun interface
|
||||||
func (t *LinuxTun) Close() error {
|
func (t *LinuxTun) Close() error {
|
||||||
|
t.routeMonitorOnce.Do(func() {
|
||||||
|
if t.routeMonitorStop != nil {
|
||||||
|
close(t.routeMonitorStop)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
_ = t.unsetSystemRoutes()
|
||||||
|
|
||||||
if t.ownsTun {
|
if t.ownsTun {
|
||||||
_ = netlink.LinkSetDown(t.tunLink)
|
_ = netlink.LinkSetDown(t.tunLink)
|
||||||
}
|
}
|
||||||
@@ -199,3 +222,131 @@ func (t *LinuxTun) newEndpoint() (stack.LinkEndpoint, error) {
|
|||||||
func setinterface(network, address string, fd uintptr, iface *net.Interface) error {
|
func setinterface(network, address string, fd uintptr, iface *net.Interface) error {
|
||||||
return unix.BindToDevice(int(fd), iface.Name)
|
return unix.BindToDevice(int(fd), iface.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *LinuxTun) setSystemRoutes() error {
|
||||||
|
if len(t.options.AutoSystemRoutingTable) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
tunIndex := t.tunLink.Attrs().Index
|
||||||
|
for _, cidr := range t.options.AutoSystemRoutingTable {
|
||||||
|
prefix, err := netip.ParsePrefix(cidr)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("invalid system route ", cidr).Base(err)
|
||||||
|
}
|
||||||
|
prefix = prefix.Masked()
|
||||||
|
_, ipNet, _ := net.ParseCIDR(prefix.String())
|
||||||
|
route := netlink.Route{
|
||||||
|
LinkIndex: tunIndex,
|
||||||
|
Dst: ipNet,
|
||||||
|
Priority: 1,
|
||||||
|
}
|
||||||
|
if err := netlink.RouteAdd(&route); err != nil {
|
||||||
|
_ = t.unsetSystemRoutes()
|
||||||
|
return errors.New("failed to add system route ", cidr).Base(err)
|
||||||
|
}
|
||||||
|
t.systemRoutes = append(t.systemRoutes, route)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *LinuxTun) unsetSystemRoutes() error {
|
||||||
|
var errs []error
|
||||||
|
for i := len(t.systemRoutes) - 1; i >= 0; i-- {
|
||||||
|
route := t.systemRoutes[i]
|
||||||
|
if err := netlink.RouteDel(&route); err != nil {
|
||||||
|
errs = append(errs, errors.New("failed to delete system route").Base(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.systemRoutes = nil
|
||||||
|
return errors.Combine(errs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *LinuxTun) monitorRouteChanges() {
|
||||||
|
routeCh := make(chan netlink.RouteUpdate)
|
||||||
|
if err := netlink.RouteSubscribe(routeCh, t.routeMonitorStop); err != nil {
|
||||||
|
errors.LogInfoInner(context.Background(), err, "[tun] failed to subscribe route changes")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
linkCh := make(chan netlink.LinkUpdate)
|
||||||
|
if err := netlink.LinkSubscribe(linkCh, t.routeMonitorStop); err != nil {
|
||||||
|
errors.LogInfoInner(context.Background(), err, "[tun] failed to subscribe link changes")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case _, ok := <-routeCh:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if updater != nil {
|
||||||
|
updater.Update()
|
||||||
|
}
|
||||||
|
case _, ok := <-linkCh:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if updater != nil {
|
||||||
|
updater.Update()
|
||||||
|
}
|
||||||
|
case <-t.routeMonitorStop:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func findOutboundInterface(tunIndex int, fixedName string) (*net.Interface, error) {
|
||||||
|
if fixedName != "" {
|
||||||
|
iface, err := net.InterfaceByName(fixedName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if iface.Index == tunIndex {
|
||||||
|
return nil, errors.New("outbound interface cannot be the TUN interface")
|
||||||
|
}
|
||||||
|
return iface, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
probeIPs := []net.IP{
|
||||||
|
net.ParseIP("8.8.8.8"),
|
||||||
|
net.ParseIP("2001:4860:4860::8888"),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ip := range probeIPs {
|
||||||
|
routes, err := netlink.RouteGet(ip)
|
||||||
|
if err != nil || len(routes) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
route := routes[0]
|
||||||
|
if route.LinkIndex == tunIndex {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
link, err := netlink.LinkByIndex(route.LinkIndex)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
attrs := link.Attrs()
|
||||||
|
|
||||||
|
if attrs.Flags&net.FlagUp == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
operState := attrs.OperState
|
||||||
|
if operState != netlink.OperUp && operState != netlink.OperUnknown {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if route.Src == nil || route.Src.IsLoopback() || route.Src.IsLinkLocalUnicast() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
iface, err := net.InterfaceByIndex(route.LinkIndex)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return iface, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("no usable outbound interface found")
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import (
|
|||||||
go_errors "errors"
|
go_errors "errors"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
@@ -307,3 +309,77 @@ func setinterface(network, address string, fd uintptr, iface *net.Interface) err
|
|||||||
|
|
||||||
return errors.Combine(err1, err2, err3, err4)
|
return errors.Combine(err1, err2, err3, err4)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func findOutboundInterface(tunIndex int, fixedName string) (*net.Interface, error) {
|
||||||
|
interfaces, err := net.Interfaces()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if fixedName != "" {
|
||||||
|
for _, iface := range interfaces {
|
||||||
|
if iface.Index != tunIndex && iface.Name == fixedName {
|
||||||
|
return &iface, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var candidates []struct {
|
||||||
|
index int
|
||||||
|
score int
|
||||||
|
}
|
||||||
|
for i, iface := range interfaces {
|
||||||
|
if iface.Index == tunIndex {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.Contains(iface.Name, "vEthernet") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if iface.Flags&net.FlagUp == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if iface.Flags&net.FlagLoopback != 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
addrs, err := iface.Addrs()
|
||||||
|
if err != nil || len(addrs) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
candidates = append(candidates, struct {
|
||||||
|
index int
|
||||||
|
score int
|
||||||
|
}{i, scoreWindowsInterface(&iface, addrs)})
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(candidates, func(i, j int) bool {
|
||||||
|
if candidates[i].score != candidates[j].score {
|
||||||
|
return candidates[i].score > candidates[j].score
|
||||||
|
}
|
||||||
|
return interfaces[candidates[i].index].Name < interfaces[candidates[j].index].Name
|
||||||
|
})
|
||||||
|
if len(candidates) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
iface := interfaces[candidates[0].index]
|
||||||
|
return &iface, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func scoreWindowsInterface(iface *net.Interface, addrs []net.Addr) int {
|
||||||
|
score := 0
|
||||||
|
|
||||||
|
name := strings.ToLower(iface.Name)
|
||||||
|
if strings.Contains(name, "wlan") || strings.Contains(name, "wi-fi") {
|
||||||
|
score += 2
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, addr := range addrs {
|
||||||
|
if strings.HasPrefix(addr.String(), "192.168.") {
|
||||||
|
score++
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return score
|
||||||
|
}
|
||||||
|
|||||||
@@ -1 +1,60 @@
|
|||||||
package wireguard
|
package wireguard
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common/protocol"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p *PeerConfig) AsAccount() (protocol.Account, error) {
|
||||||
|
pub, err := ParseKey(p.PublicKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
allowedIPs := make([]netip.Prefix, 0, len(p.AllowedIps))
|
||||||
|
for i := range p.AllowedIps {
|
||||||
|
p, err := netip.ParsePrefix(p.AllowedIps[i])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
allowedIPs = append(allowedIPs, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &MemoryAccount{
|
||||||
|
Pub: *pub,
|
||||||
|
AllowedIPs: allowedIPs,
|
||||||
|
PreSharedKey: p.PreSharedKey,
|
||||||
|
KeepAlive: p.KeepAlive,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type MemoryAccount struct {
|
||||||
|
Pub [32]byte
|
||||||
|
AllowedIPs []netip.Prefix
|
||||||
|
PreSharedKey string
|
||||||
|
KeepAlive string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *MemoryAccount) Equals(other protocol.Account) bool {
|
||||||
|
if b, ok := other.(*MemoryAccount); ok {
|
||||||
|
return a.Pub == b.Pub
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *MemoryAccount) ToProto() proto.Message {
|
||||||
|
allowedIPs := make([]string, 0, len(a.AllowedIPs))
|
||||||
|
for i := range a.AllowedIPs {
|
||||||
|
allowedIPs = append(allowedIPs, a.AllowedIPs[i].String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return &PeerConfig{
|
||||||
|
PublicKey: hex.EncodeToString(a.Pub[:]),
|
||||||
|
AllowedIps: allowedIPs,
|
||||||
|
PreSharedKey: a.PreSharedKey,
|
||||||
|
KeepAlive: a.KeepAlive,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
package wireguard
|
package wireguard
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
protocol "github.com/xtls/xray-core/common/protocol"
|
||||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
@@ -157,6 +158,7 @@ type DeviceConfig struct {
|
|||||||
SecretKey string `protobuf:"bytes,1,opt,name=secret_key,json=secretKey,proto3" json:"secret_key,omitempty"`
|
SecretKey string `protobuf:"bytes,1,opt,name=secret_key,json=secretKey,proto3" json:"secret_key,omitempty"`
|
||||||
Endpoint []string `protobuf:"bytes,2,rep,name=endpoint,proto3" json:"endpoint,omitempty"`
|
Endpoint []string `protobuf:"bytes,2,rep,name=endpoint,proto3" json:"endpoint,omitempty"`
|
||||||
Peers []*PeerConfig `protobuf:"bytes,3,rep,name=peers,proto3" json:"peers,omitempty"`
|
Peers []*PeerConfig `protobuf:"bytes,3,rep,name=peers,proto3" json:"peers,omitempty"`
|
||||||
|
Users []*protocol.User `protobuf:"bytes,5,rep,name=users,proto3" json:"users,omitempty"`
|
||||||
Mtu int32 `protobuf:"varint,4,opt,name=mtu,proto3" json:"mtu,omitempty"`
|
Mtu int32 `protobuf:"varint,4,opt,name=mtu,proto3" json:"mtu,omitempty"`
|
||||||
Reserved []byte `protobuf:"bytes,6,opt,name=reserved,proto3" json:"reserved,omitempty"`
|
Reserved []byte `protobuf:"bytes,6,opt,name=reserved,proto3" json:"reserved,omitempty"`
|
||||||
DomainStrategy DeviceConfig_DomainStrategy `protobuf:"varint,7,opt,name=domain_strategy,json=domainStrategy,proto3,enum=xray.proxy.wireguard.DeviceConfig_DomainStrategy" json:"domain_strategy,omitempty"`
|
DomainStrategy DeviceConfig_DomainStrategy `protobuf:"varint,7,opt,name=domain_strategy,json=domainStrategy,proto3,enum=xray.proxy.wireguard.DeviceConfig_DomainStrategy" json:"domain_strategy,omitempty"`
|
||||||
@@ -217,6 +219,13 @@ func (x *DeviceConfig) GetPeers() []*PeerConfig {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *DeviceConfig) GetUsers() []*protocol.User {
|
||||||
|
if x != nil {
|
||||||
|
return x.Users
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (x *DeviceConfig) GetMtu() int32 {
|
func (x *DeviceConfig) GetMtu() int32 {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.Mtu
|
return x.Mtu
|
||||||
@@ -256,7 +265,7 @@ var File_proxy_wireguard_config_proto protoreflect.FileDescriptor
|
|||||||
|
|
||||||
const file_proxy_wireguard_config_proto_rawDesc = "" +
|
const file_proxy_wireguard_config_proto_rawDesc = "" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"\x1cproxy/wireguard/config.proto\x12\x14xray.proxy.wireguard\"\xad\x01\n" +
|
"\x1cproxy/wireguard/config.proto\x12\x14xray.proxy.wireguard\x1a\x1acommon/protocol/user.proto\"\xad\x01\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"PeerConfig\x12\x1d\n" +
|
"PeerConfig\x12\x1d\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
@@ -266,12 +275,13 @@ const file_proxy_wireguard_config_proto_rawDesc = "" +
|
|||||||
"\n" +
|
"\n" +
|
||||||
"keep_alive\x18\x04 \x01(\tR\tkeepAlive\x12\x1f\n" +
|
"keep_alive\x18\x04 \x01(\tR\tkeepAlive\x12\x1f\n" +
|
||||||
"\vallowed_ips\x18\x05 \x03(\tR\n" +
|
"\vallowed_ips\x18\x05 \x03(\tR\n" +
|
||||||
"allowedIps\"\xaa\x03\n" +
|
"allowedIps\"\xdc\x03\n" +
|
||||||
"\fDeviceConfig\x12\x1d\n" +
|
"\fDeviceConfig\x12\x1d\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"secret_key\x18\x01 \x01(\tR\tsecretKey\x12\x1a\n" +
|
"secret_key\x18\x01 \x01(\tR\tsecretKey\x12\x1a\n" +
|
||||||
"\bendpoint\x18\x02 \x03(\tR\bendpoint\x126\n" +
|
"\bendpoint\x18\x02 \x03(\tR\bendpoint\x126\n" +
|
||||||
"\x05peers\x18\x03 \x03(\v2 .xray.proxy.wireguard.PeerConfigR\x05peers\x12\x10\n" +
|
"\x05peers\x18\x03 \x03(\v2 .xray.proxy.wireguard.PeerConfigR\x05peers\x120\n" +
|
||||||
|
"\x05users\x18\x05 \x03(\v2\x1a.xray.common.protocol.UserR\x05users\x12\x10\n" +
|
||||||
"\x03mtu\x18\x04 \x01(\x05R\x03mtu\x12\x1a\n" +
|
"\x03mtu\x18\x04 \x01(\x05R\x03mtu\x12\x1a\n" +
|
||||||
"\breserved\x18\x06 \x01(\fR\breserved\x12Z\n" +
|
"\breserved\x18\x06 \x01(\fR\breserved\x12Z\n" +
|
||||||
"\x0fdomain_strategy\x18\a \x01(\x0e21.xray.proxy.wireguard.DeviceConfig.DomainStrategyR\x0edomainStrategy\x12\x1b\n" +
|
"\x0fdomain_strategy\x18\a \x01(\x0e21.xray.proxy.wireguard.DeviceConfig.DomainStrategyR\x0edomainStrategy\x12\x1b\n" +
|
||||||
@@ -305,15 +315,17 @@ var file_proxy_wireguard_config_proto_goTypes = []any{
|
|||||||
(DeviceConfig_DomainStrategy)(0), // 0: xray.proxy.wireguard.DeviceConfig.DomainStrategy
|
(DeviceConfig_DomainStrategy)(0), // 0: xray.proxy.wireguard.DeviceConfig.DomainStrategy
|
||||||
(*PeerConfig)(nil), // 1: xray.proxy.wireguard.PeerConfig
|
(*PeerConfig)(nil), // 1: xray.proxy.wireguard.PeerConfig
|
||||||
(*DeviceConfig)(nil), // 2: xray.proxy.wireguard.DeviceConfig
|
(*DeviceConfig)(nil), // 2: xray.proxy.wireguard.DeviceConfig
|
||||||
|
(*protocol.User)(nil), // 3: xray.common.protocol.User
|
||||||
}
|
}
|
||||||
var file_proxy_wireguard_config_proto_depIdxs = []int32{
|
var file_proxy_wireguard_config_proto_depIdxs = []int32{
|
||||||
1, // 0: xray.proxy.wireguard.DeviceConfig.peers:type_name -> xray.proxy.wireguard.PeerConfig
|
1, // 0: xray.proxy.wireguard.DeviceConfig.peers:type_name -> xray.proxy.wireguard.PeerConfig
|
||||||
0, // 1: xray.proxy.wireguard.DeviceConfig.domain_strategy:type_name -> xray.proxy.wireguard.DeviceConfig.DomainStrategy
|
3, // 1: xray.proxy.wireguard.DeviceConfig.users:type_name -> xray.common.protocol.User
|
||||||
2, // [2:2] is the sub-list for method output_type
|
0, // 2: xray.proxy.wireguard.DeviceConfig.domain_strategy:type_name -> xray.proxy.wireguard.DeviceConfig.DomainStrategy
|
||||||
2, // [2:2] is the sub-list for method input_type
|
3, // [3:3] is the sub-list for method output_type
|
||||||
2, // [2:2] is the sub-list for extension type_name
|
3, // [3:3] is the sub-list for method input_type
|
||||||
2, // [2:2] is the sub-list for extension extendee
|
3, // [3:3] is the sub-list for extension type_name
|
||||||
0, // [0:2] is the sub-list for field type_name
|
3, // [3:3] is the sub-list for extension extendee
|
||||||
|
0, // [0:3] is the sub-list for field type_name
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { file_proxy_wireguard_config_proto_init() }
|
func init() { file_proxy_wireguard_config_proto_init() }
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ option go_package = "github.com/xtls/xray-core/proxy/wireguard";
|
|||||||
option java_package = "com.xray.proxy.wireguard";
|
option java_package = "com.xray.proxy.wireguard";
|
||||||
option java_multiple_files = true;
|
option java_multiple_files = true;
|
||||||
|
|
||||||
|
import "common/protocol/user.proto";
|
||||||
|
|
||||||
message PeerConfig {
|
message PeerConfig {
|
||||||
string public_key = 1;
|
string public_key = 1;
|
||||||
string pre_shared_key = 2;
|
string pre_shared_key = 2;
|
||||||
@@ -25,6 +27,7 @@ message DeviceConfig {
|
|||||||
string secret_key = 1;
|
string secret_key = 1;
|
||||||
repeated string endpoint = 2;
|
repeated string endpoint = 2;
|
||||||
repeated PeerConfig peers = 3;
|
repeated PeerConfig peers = 3;
|
||||||
|
repeated xray.common.protocol.User users = 5;
|
||||||
int32 mtu = 4;
|
int32 mtu = 4;
|
||||||
|
|
||||||
bytes reserved = 6;
|
bytes reserved = 6;
|
||||||
|
|||||||
+156
-16
@@ -2,16 +2,20 @@ package wireguard
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/common/buf"
|
"github.com/xtls/xray-core/common/buf"
|
||||||
c "github.com/xtls/xray-core/common/ctx"
|
c "github.com/xtls/xray-core/common/ctx"
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/log"
|
"github.com/xtls/xray-core/common/log"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/common/protocol"
|
||||||
"github.com/xtls/xray-core/common/session"
|
"github.com/xtls/xray-core/common/session"
|
||||||
"github.com/xtls/xray-core/core"
|
"github.com/xtls/xray-core/core"
|
||||||
"github.com/xtls/xray-core/features/policy"
|
"github.com/xtls/xray-core/features/policy"
|
||||||
@@ -20,6 +24,7 @@ import (
|
|||||||
"github.com/xtls/xray-core/transport"
|
"github.com/xtls/xray-core/transport"
|
||||||
"github.com/xtls/xray-core/transport/internet"
|
"github.com/xtls/xray-core/transport/internet"
|
||||||
"github.com/xtls/xray-core/transport/internet/stat"
|
"github.com/xtls/xray-core/transport/internet/stat"
|
||||||
|
"golang.org/x/crypto/curve25519"
|
||||||
"golang.zx2c4.com/wireguard/device"
|
"golang.zx2c4.com/wireguard/device"
|
||||||
"golang.zx2c4.com/wireguard/tun"
|
"golang.zx2c4.com/wireguard/tun"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||||
@@ -42,6 +47,9 @@ type Server struct {
|
|||||||
stack *stack.Stack
|
stack *stack.Stack
|
||||||
dev *device.Device
|
dev *device.Device
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
|
|
||||||
|
pub [32]byte
|
||||||
|
users *sync.Map
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer(ctx context.Context, conf *DeviceConfig) (*Server, error) {
|
func NewServer(ctx context.Context, conf *DeviceConfig) (*Server, error) {
|
||||||
@@ -72,15 +80,6 @@ func NewServer(ctx context.Context, conf *DeviceConfig) (*Server, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(conf.Peers) == 0 {
|
|
||||||
return nil, errors.New("empty peers")
|
|
||||||
}
|
|
||||||
for _, peer := range conf.Peers {
|
|
||||||
if peer.PublicKey == "" {
|
|
||||||
return nil, errors.New("peer without publickey")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
localAddresses := make([]netip.Addr, 0, len(conf.Endpoint))
|
localAddresses := make([]netip.Addr, 0, len(conf.Endpoint))
|
||||||
for _, localaddress := range conf.Endpoint {
|
for _, localaddress := range conf.Endpoint {
|
||||||
addr, err := netip.ParseAddr(localaddress)
|
addr, err := netip.ParseAddr(localaddress)
|
||||||
@@ -101,6 +100,19 @@ func NewServer(ctx context.Context, conf *DeviceConfig) (*Server, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pri := common.Must2(ParseKey(conf.SecretKey))
|
||||||
|
var pub [32]byte
|
||||||
|
curve25519.ScalarBaseMult(&pub, pri)
|
||||||
|
|
||||||
|
users := &sync.Map{}
|
||||||
|
for _, u := range conf.Users {
|
||||||
|
user, err := u.ToMemoryUser()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
users.Store(user.Account.(*MemoryAccount).Pub, user)
|
||||||
|
}
|
||||||
|
|
||||||
return &Server{
|
return &Server{
|
||||||
conf: conf,
|
conf: conf,
|
||||||
ctx: core.ToBackgroundDetachedContext(ctx),
|
ctx: core.ToBackgroundDetachedContext(ctx),
|
||||||
@@ -116,9 +128,100 @@ func NewServer(ctx context.Context, conf *DeviceConfig) (*Server, error) {
|
|||||||
|
|
||||||
tun: tun,
|
tun: tun,
|
||||||
stack: stack,
|
stack: stack,
|
||||||
|
|
||||||
|
pub: pub,
|
||||||
|
users: users,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) AddUser(ctx context.Context, user *protocol.MemoryUser) error {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
if s.dev == nil {
|
||||||
|
return errors.New("too early")
|
||||||
|
}
|
||||||
|
peer := user.Account.(*MemoryAccount)
|
||||||
|
if peer.Pub == s.pub {
|
||||||
|
return errors.New("invalid public key")
|
||||||
|
}
|
||||||
|
var sb strings.Builder
|
||||||
|
sb.WriteString("public_key=" + hex.EncodeToString(peer.Pub[:]) + "\n")
|
||||||
|
sb.WriteString("replace_allowed_ips=true\n")
|
||||||
|
for i := range peer.AllowedIPs {
|
||||||
|
sb.WriteString("allowed_ip=" + peer.AllowedIPs[i].String() + "\n")
|
||||||
|
}
|
||||||
|
if peer.PreSharedKey != "" {
|
||||||
|
sb.WriteString("preshared_key=" + peer.PreSharedKey + "\n")
|
||||||
|
}
|
||||||
|
if peer.KeepAlive != "" {
|
||||||
|
sb.WriteString("persistent_keepalive_interval=" + peer.KeepAlive + "\n")
|
||||||
|
}
|
||||||
|
err := s.dev.IpcSet(sb.String())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.users.Store(peer.Pub, user)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) RemoveUser(ctx context.Context, email string) error {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
if s.dev == nil {
|
||||||
|
return errors.New("too early")
|
||||||
|
}
|
||||||
|
if user := s.GetUser(ctx, email); user != nil {
|
||||||
|
peer := user.Account.(*MemoryAccount)
|
||||||
|
err := s.dev.IpcSet("public_key=" + hex.EncodeToString(peer.Pub[:]) + "\nremove=true\n")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.users.Delete(peer.Pub)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) GetUser(ctx context.Context, email string) (user *protocol.MemoryUser) {
|
||||||
|
s.users.Range(func(key, value any) bool {
|
||||||
|
if value.(*protocol.MemoryUser).Email == email {
|
||||||
|
user = value.(*protocol.MemoryUser)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) GetUserByAddr(ctx context.Context, addr netip.Addr) (user *protocol.MemoryUser) {
|
||||||
|
s.users.Range(func(key, value any) bool {
|
||||||
|
peer := value.(*protocol.MemoryUser).Account.(*MemoryAccount)
|
||||||
|
for i := range peer.AllowedIPs {
|
||||||
|
if peer.AllowedIPs[i].Contains(addr) {
|
||||||
|
user = value.(*protocol.MemoryUser)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) GetUsers(ctx context.Context) (users []*protocol.MemoryUser) {
|
||||||
|
s.users.Range(func(key, value interface{}) bool {
|
||||||
|
users = append(users, value.(*protocol.MemoryUser))
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) GetUsersCount(context.Context) (count int64) {
|
||||||
|
s.users.Range(func(key, value interface{}) bool {
|
||||||
|
count++
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Network implements proxy.Inbound.Network.
|
// Network implements proxy.Inbound.Network.
|
||||||
func (*Server) Network() []net.Network {
|
func (*Server) Network() []net.Network {
|
||||||
return []net.Network{}
|
return []net.Network{}
|
||||||
@@ -196,18 +299,20 @@ func (s *Server) Start() error {
|
|||||||
dev := device.NewDevice(s.tun, bind, logger)
|
dev := device.NewDevice(s.tun, bind, logger)
|
||||||
var cfg strings.Builder
|
var cfg strings.Builder
|
||||||
cfg.WriteString("private_key=" + s.conf.SecretKey + "\n")
|
cfg.WriteString("private_key=" + s.conf.SecretKey + "\n")
|
||||||
for _, peer := range s.conf.Peers {
|
s.users.Range(func(key, value any) bool {
|
||||||
cfg.WriteString("public_key=" + peer.PublicKey + "\n")
|
peer := value.(*protocol.MemoryUser).Account.(*MemoryAccount)
|
||||||
|
cfg.WriteString("public_key=" + hex.EncodeToString(peer.Pub[:]) + "\n")
|
||||||
|
for i := range peer.AllowedIPs {
|
||||||
|
cfg.WriteString("allowed_ip=" + peer.AllowedIPs[i].String() + "\n")
|
||||||
|
}
|
||||||
if peer.PreSharedKey != "" {
|
if peer.PreSharedKey != "" {
|
||||||
cfg.WriteString("preshared_key=" + peer.PreSharedKey + "\n")
|
cfg.WriteString("preshared_key=" + peer.PreSharedKey + "\n")
|
||||||
}
|
}
|
||||||
for _, ip := range peer.AllowedIps {
|
|
||||||
cfg.WriteString("allowed_ip=" + ip + "\n")
|
|
||||||
}
|
|
||||||
if peer.KeepAlive != "" {
|
if peer.KeepAlive != "" {
|
||||||
cfg.WriteString("persistent_keepalive_interval=" + peer.KeepAlive + "\n")
|
cfg.WriteString("persistent_keepalive_interval=" + peer.KeepAlive + "\n")
|
||||||
}
|
}
|
||||||
}
|
return true
|
||||||
|
})
|
||||||
err := dev.IpcSet(cfg.String())
|
err := dev.IpcSet(cfg.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -227,12 +332,36 @@ func (s *Server) HandleConnection(conn net.Conn, dest net.Destination) {
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
ctx = c.ContextWithID(ctx, session.NewID())
|
ctx = c.ContextWithID(ctx, session.NewID())
|
||||||
|
|
||||||
source := net.DestinationFromAddr(conn.RemoteAddr())
|
remote := conn.RemoteAddr()
|
||||||
|
if remote == nil {
|
||||||
|
errors.LogError(context.Background(), "nil remote")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var addr netip.Addr
|
||||||
|
switch v := remote.(type) {
|
||||||
|
case *net.TCPAddr:
|
||||||
|
addr, _ = netip.AddrFromSlice(v.IP)
|
||||||
|
case *net.UDPAddr:
|
||||||
|
addr, _ = netip.AddrFromSlice(v.IP)
|
||||||
|
default:
|
||||||
|
errors.LogError(context.Background(), "invalid addr type ", reflect.TypeOf(v))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user := s.GetUserByAddr(context.TODO(), addr)
|
||||||
|
if user == nil {
|
||||||
|
errors.LogError(context.Background(), "nil user form ", remote, " to ", dest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
source := net.DestinationFromAddr(remote)
|
||||||
inbound := session.Inbound{
|
inbound := session.Inbound{
|
||||||
Name: "wireguard",
|
Name: "wireguard",
|
||||||
Tag: s.tag,
|
Tag: s.tag,
|
||||||
CanSpliceCopy: 3,
|
CanSpliceCopy: 3,
|
||||||
Source: source,
|
Source: source,
|
||||||
|
User: user,
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx = session.ContextWithInbound(ctx, &inbound)
|
ctx = session.ContextWithInbound(ctx, &inbound)
|
||||||
@@ -257,3 +386,14 @@ func (s *Server) HandleConnection(conn net.Conn, dest net.Destination) {
|
|||||||
errors.LogError(ctx, errors.New("connection closed").Base(err))
|
errors.LogError(ctx, errors.New("connection closed").Base(err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ParseKey(str string) (*[32]byte, error) {
|
||||||
|
slice, err := hex.DecodeString(str)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(slice) != 32 {
|
||||||
|
return nil, errors.New("len(slice) != 32")
|
||||||
|
}
|
||||||
|
return (*[32]byte)(slice), nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -372,7 +372,6 @@ func runHysteria2Case(t *testing.T, bin string, mode trafficMode, payloadSize in
|
|||||||
{
|
{
|
||||||
ProtocolName: "hysteria",
|
ProtocolName: "hysteria",
|
||||||
Settings: serial.ToTypedMessage(&hytransport.Config{
|
Settings: serial.ToTypedMessage(&hytransport.Config{
|
||||||
Version: 2,
|
|
||||||
Auth: auth,
|
Auth: auth,
|
||||||
UdpIdleTimeout: 60,
|
UdpIdleTimeout: 60,
|
||||||
}),
|
}),
|
||||||
@@ -421,7 +420,6 @@ func runHysteria2Case(t *testing.T, bin string, mode trafficMode, payloadSize in
|
|||||||
Outbound: []*core.OutboundHandlerConfig{
|
Outbound: []*core.OutboundHandlerConfig{
|
||||||
{
|
{
|
||||||
ProxySettings: serial.ToTypedMessage(&hyproxy.ClientConfig{
|
ProxySettings: serial.ToTypedMessage(&hyproxy.ClientConfig{
|
||||||
Version: 2,
|
|
||||||
Server: &protocol.ServerEndpoint{
|
Server: &protocol.ServerEndpoint{
|
||||||
Address: xnet.NewIPOrDomain(xnet.LocalHostIP),
|
Address: xnet.NewIPOrDomain(xnet.LocalHostIP),
|
||||||
Port: uint32(relayPort),
|
Port: uint32(relayPort),
|
||||||
@@ -437,7 +435,6 @@ func runHysteria2Case(t *testing.T, bin string, mode trafficMode, payloadSize in
|
|||||||
{
|
{
|
||||||
ProtocolName: "hysteria",
|
ProtocolName: "hysteria",
|
||||||
Settings: serial.ToTypedMessage(&hytransport.Config{
|
Settings: serial.ToTypedMessage(&hytransport.Config{
|
||||||
Version: 2,
|
|
||||||
Auth: auth,
|
Auth: auth,
|
||||||
UdpIdleTimeout: 60,
|
UdpIdleTimeout: 60,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ const (
|
|||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Version int32 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"`
|
|
||||||
Auth string `protobuf:"bytes,2,opt,name=auth,proto3" json:"auth,omitempty"`
|
Auth string `protobuf:"bytes,2,opt,name=auth,proto3" json:"auth,omitempty"`
|
||||||
UdpIdleTimeout int64 `protobuf:"varint,3,opt,name=udp_idle_timeout,json=udpIdleTimeout,proto3" json:"udp_idle_timeout,omitempty"`
|
UdpIdleTimeout int64 `protobuf:"varint,3,opt,name=udp_idle_timeout,json=udpIdleTimeout,proto3" json:"udp_idle_timeout,omitempty"`
|
||||||
MasqType string `protobuf:"bytes,4,opt,name=masq_type,json=masqType,proto3" json:"masq_type,omitempty"`
|
MasqType string `protobuf:"bytes,4,opt,name=masq_type,json=masqType,proto3" json:"masq_type,omitempty"`
|
||||||
@@ -68,13 +67,6 @@ func (*Config) Descriptor() ([]byte, []int) {
|
|||||||
return file_transport_internet_hysteria_config_proto_rawDescGZIP(), []int{0}
|
return file_transport_internet_hysteria_config_proto_rawDescGZIP(), []int{0}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Config) GetVersion() int32 {
|
|
||||||
if x != nil {
|
|
||||||
return x.Version
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Config) GetAuth() string {
|
func (x *Config) GetAuth() string {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.Auth
|
return x.Auth
|
||||||
@@ -149,9 +141,8 @@ var File_transport_internet_hysteria_config_proto protoreflect.FileDescriptor
|
|||||||
|
|
||||||
const file_transport_internet_hysteria_config_proto_rawDesc = "" +
|
const file_transport_internet_hysteria_config_proto_rawDesc = "" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"(transport/internet/hysteria/config.proto\x12 xray.transport.internet.hysteria\"\xa3\x04\n" +
|
"(transport/internet/hysteria/config.proto\x12 xray.transport.internet.hysteria\"\x8f\x04\n" +
|
||||||
"\x06Config\x12\x18\n" +
|
"\x06Config\x12\x12\n" +
|
||||||
"\aversion\x18\x01 \x01(\x05R\aversion\x12\x12\n" +
|
|
||||||
"\x04auth\x18\x02 \x01(\tR\x04auth\x12(\n" +
|
"\x04auth\x18\x02 \x01(\tR\x04auth\x12(\n" +
|
||||||
"\x10udp_idle_timeout\x18\x03 \x01(\x03R\x0eudpIdleTimeout\x12\x1b\n" +
|
"\x10udp_idle_timeout\x18\x03 \x01(\x03R\x0eudpIdleTimeout\x12\x1b\n" +
|
||||||
"\tmasq_type\x18\x04 \x01(\tR\bmasqType\x12\x1b\n" +
|
"\tmasq_type\x18\x04 \x01(\tR\bmasqType\x12\x1b\n" +
|
||||||
@@ -166,7 +157,7 @@ const file_transport_internet_hysteria_config_proto_rawDesc = "" +
|
|||||||
"\x17masq_string_status_code\x18\v \x01(\x05R\x14masqStringStatusCode\x1aD\n" +
|
"\x17masq_string_status_code\x18\v \x01(\x05R\x14masqStringStatusCode\x1aD\n" +
|
||||||
"\x16MasqStringHeadersEntry\x12\x10\n" +
|
"\x16MasqStringHeadersEntry\x12\x10\n" +
|
||||||
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
||||||
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01B\x82\x01\n" +
|
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01J\x04\b\x01\x10\x02B\x82\x01\n" +
|
||||||
"$com.xray.transport.internet.hysteriaP\x01Z5github.com/xtls/xray-core/transport/internet/hysteria\xaa\x02 Xray.Transport.Internet.Hysteriab\x06proto3"
|
"$com.xray.transport.internet.hysteriaP\x01Z5github.com/xtls/xray-core/transport/internet/hysteria\xaa\x02 Xray.Transport.Internet.Hysteriab\x06proto3"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ option java_package = "com.xray.transport.internet.hysteria";
|
|||||||
option java_multiple_files = true;
|
option java_multiple_files = true;
|
||||||
|
|
||||||
message Config {
|
message Config {
|
||||||
int32 version = 1;
|
reserved 1;
|
||||||
string auth = 2;
|
string auth = 2;
|
||||||
|
|
||||||
int64 udp_idle_timeout = 3;
|
int64 udp_idle_timeout = 3;
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ func (h *httpHandler) AuthHTTP(w http.ResponseWriter, r *http.Request) bool {
|
|||||||
|
|
||||||
var user *protocol.MemoryUser
|
var user *protocol.MemoryUser
|
||||||
var ok bool
|
var ok bool
|
||||||
if h.validator != nil && h.validator.GetCount() > 0 {
|
if h.validator != nil && h.validator.NotEmpty() {
|
||||||
user = h.validator.Get(auth)
|
user = h.validator.Get(auth)
|
||||||
} else if h.config.Auth != "" {
|
} else if h.config.Auth != "" {
|
||||||
ok = auth == h.config.Auth
|
ok = auth == h.config.Auth
|
||||||
|
|||||||
@@ -584,9 +584,7 @@ func (ln *Listener) Addr() net.Addr {
|
|||||||
// Close implements net.Listener.Close().
|
// Close implements net.Listener.Close().
|
||||||
func (ln *Listener) Close() error {
|
func (ln *Listener) Close() error {
|
||||||
if ln.h3server != nil {
|
if ln.h3server != nil {
|
||||||
if err := ln.h3server.Close(); err != nil {
|
return ln.h3server.Close()
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else if ln.listener != nil {
|
} else if ln.listener != nil {
|
||||||
return ln.listener.Close()
|
return ln.listener.Close()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,27 +6,25 @@ package splithttp
|
|||||||
import (
|
import (
|
||||||
"container/heap"
|
"container/heap"
|
||||||
"io"
|
"io"
|
||||||
"runtime"
|
"sync/atomic"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/common/signal/done"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Packet struct {
|
type Packet struct {
|
||||||
Reader io.ReadCloser
|
Reader *httpServerConn
|
||||||
Payload []byte
|
Payload []byte
|
||||||
Seq uint64
|
Seq uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
type uploadQueue struct {
|
type uploadQueue struct {
|
||||||
reader io.ReadCloser
|
reader atomic.Pointer[httpServerConn]
|
||||||
nomore bool
|
pushedPackets chan Packet
|
||||||
pushedPackets chan Packet
|
heap uploadHeap
|
||||||
writeCloseMutex sync.Mutex
|
nextSeq uint64
|
||||||
heap uploadHeap
|
maxPackets int
|
||||||
nextSeq uint64
|
closed *done.Instance
|
||||||
closed bool
|
|
||||||
maxPackets int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUploadQueue(maxPackets int) *uploadQueue {
|
func NewUploadQueue(maxPackets int) *uploadQueue {
|
||||||
@@ -34,73 +32,53 @@ func NewUploadQueue(maxPackets int) *uploadQueue {
|
|||||||
pushedPackets: make(chan Packet, maxPackets),
|
pushedPackets: make(chan Packet, maxPackets),
|
||||||
heap: uploadHeap{},
|
heap: uploadHeap{},
|
||||||
nextSeq: 0,
|
nextSeq: 0,
|
||||||
closed: false,
|
closed: done.New(),
|
||||||
maxPackets: maxPackets,
|
maxPackets: maxPackets,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *uploadQueue) Push(p Packet) error {
|
func (h *uploadQueue) Push(p Packet) error {
|
||||||
h.writeCloseMutex.Lock()
|
if h.reader.Load() != nil || (p.Reader != nil && !h.reader.CompareAndSwap(nil, p.Reader)) {
|
||||||
defer h.writeCloseMutex.Unlock()
|
|
||||||
|
|
||||||
if h.closed {
|
|
||||||
return errors.New("packet queue closed")
|
|
||||||
}
|
|
||||||
if h.nomore {
|
|
||||||
return errors.New("h.reader already exists")
|
return errors.New("h.reader already exists")
|
||||||
}
|
}
|
||||||
if p.Reader != nil {
|
select {
|
||||||
h.nomore = true
|
case h.pushedPackets <- p: // no panic
|
||||||
|
if h.closed.Done() {
|
||||||
|
return errors.New("packet queue closed")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
case <-h.closed.Wait():
|
||||||
|
return errors.New("packet queue closed")
|
||||||
}
|
}
|
||||||
h.pushedPackets <- p
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *uploadQueue) Close() error {
|
func (h *uploadQueue) Close() error {
|
||||||
h.writeCloseMutex.Lock()
|
h.closed.Close()
|
||||||
defer h.writeCloseMutex.Unlock()
|
if reader := h.reader.Load(); reader != nil {
|
||||||
|
return reader.Close()
|
||||||
if !h.closed {
|
|
||||||
h.closed = true
|
|
||||||
runtime.Gosched() // hope Read() gets the packet
|
|
||||||
f:
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case p := <-h.pushedPackets:
|
|
||||||
if p.Reader != nil {
|
|
||||||
h.reader = p.Reader
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break f
|
|
||||||
}
|
|
||||||
}
|
|
||||||
close(h.pushedPackets)
|
|
||||||
}
|
|
||||||
if h.reader != nil {
|
|
||||||
return h.reader.Close()
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *uploadQueue) Read(b []byte) (int, error) {
|
func (h *uploadQueue) Read(b []byte) (int, error) {
|
||||||
if h.reader != nil {
|
if reader := h.reader.Load(); reader != nil {
|
||||||
return h.reader.Read(b)
|
return reader.Read(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
if h.closed {
|
if h.closed.Done() {
|
||||||
return 0, io.EOF
|
return 0, io.EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(h.heap) == 0 {
|
if len(h.heap) == 0 {
|
||||||
packet, more := <-h.pushedPackets
|
select {
|
||||||
if !more {
|
case p := <-h.pushedPackets:
|
||||||
|
if p.Reader != nil {
|
||||||
|
return p.Reader.Read(b)
|
||||||
|
}
|
||||||
|
heap.Push(&h.heap, p)
|
||||||
|
case <-h.closed.Wait():
|
||||||
return 0, io.EOF
|
return 0, io.EOF
|
||||||
}
|
}
|
||||||
if packet.Reader != nil {
|
|
||||||
h.reader = packet.Reader
|
|
||||||
return h.reader.Read(b)
|
|
||||||
}
|
|
||||||
heap.Push(&h.heap, packet)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for len(h.heap) > 0 {
|
for len(h.heap) > 0 {
|
||||||
@@ -131,11 +109,12 @@ func (h *uploadQueue) Read(b []byte) (int, error) {
|
|||||||
return 0, errors.New("packet queue is too large")
|
return 0, errors.New("packet queue is too large")
|
||||||
}
|
}
|
||||||
heap.Push(&h.heap, packet)
|
heap.Push(&h.heap, packet)
|
||||||
packet2, more := <-h.pushedPackets
|
select {
|
||||||
if !more {
|
case p := <-h.pushedPackets:
|
||||||
|
heap.Push(&h.heap, p)
|
||||||
|
case <-h.closed.Wait():
|
||||||
return 0, io.EOF
|
return 0, io.EOF
|
||||||
}
|
}
|
||||||
heap.Push(&h.heap, packet2)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user