mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-11-06 10:12:40 +00:00
Compare commits
39
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
23f37b571c | ||
|
|
858c44f410 | ||
|
|
fff4acac52 | ||
|
|
ad98ffd42b | ||
|
|
63ca2e0c1d | ||
|
|
ab88e0118f | ||
|
|
556767e40b | ||
|
|
967ade1e03 | ||
|
|
7c8dbcb4bb | ||
|
|
8ab161a2b0 | ||
|
|
a4a3dff5b2 | ||
|
|
6cdd323ad8 | ||
|
|
316e07a538 | ||
|
|
0c3402d778 | ||
|
|
bc7c895278 | ||
|
|
e7a69fa85a | ||
|
|
9b8190f52c | ||
|
|
6ca6d8a693 | ||
|
|
d1140de273 | ||
|
|
6ea0ab5d3a | ||
|
|
0afec66085 | ||
|
|
55eecaed16 | ||
|
|
2dfee4b842 | ||
|
|
b4123b5748 | ||
|
|
ec124ff9e0 | ||
|
|
8a3c0cbf1d | ||
|
|
78b5a6ec5c | ||
|
|
5865e3ac3d | ||
|
|
48f62b801a | ||
|
|
43c29f8eb1 | ||
|
|
613d8147bb | ||
|
|
38f090255c | ||
|
|
35a3121a83 | ||
|
|
bca08ac080 | ||
|
|
6a4274ab75 | ||
|
|
229799a7a3 | ||
|
|
3071fbfa25 | ||
|
|
1576e79563 | ||
|
|
67e7f82ca1 |
@@ -20,7 +20,7 @@ RUN cat <<EOF >/tmp/usr/local/etc/xray/00_log.json
|
||||
{
|
||||
"log": {
|
||||
"error": "/var/log/xray/error.log",
|
||||
"loglevel": "warning",
|
||||
"logLevel": "warning",
|
||||
"access": "none",
|
||||
"dnsLog": false
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ RUN cat <<EOF >/tmp/usr/local/etc/xray/00_log.json
|
||||
{
|
||||
"log": {
|
||||
"error": "/var/log/xray/error.log",
|
||||
"loglevel": "warning",
|
||||
"logLevel": "warning",
|
||||
"access": "none",
|
||||
"dnsLog": false
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ jobs:
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v4.6.0
|
||||
uses: docker/login-action@v4.7.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
|
||||
@@ -35,24 +35,7 @@ jobs:
|
||||
if: steps.check-assets.outputs.missing == 'true'
|
||||
run: sleep 90
|
||||
|
||||
check-proto:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
||||
steps:
|
||||
- name: Checkout codebase
|
||||
uses: actions/checkout@v7
|
||||
- name: Check Proto Version Header
|
||||
run: |
|
||||
head -n 4 core/config.pb.go > ref.txt
|
||||
find . -name "*.pb.go" ! -name "*_grpc.pb.go" -print0 | while IFS= read -r -d '' file; do
|
||||
if ! cmp -s ref.txt <(head -n 4 "$file"); then
|
||||
echo "Error: Header mismatch in $file"
|
||||
head -n 4 "$file"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
check-format:
|
||||
check-format-and-proto:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
||||
permissions:
|
||||
@@ -68,6 +51,8 @@ jobs:
|
||||
cache: false
|
||||
- name: Check Format
|
||||
run: go run ./infra/vformat/main.go -mode check -pwd ./
|
||||
- name: Check Protobuf
|
||||
run: go run ./infra/vprotogen/main.go -mode check -pwd ./
|
||||
|
||||
test:
|
||||
needs: check-assets
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.5
|
||||
// protoc-gen-go v1.36.12
|
||||
// protoc (unknown)
|
||||
// source: app/commander/config.proto
|
||||
|
||||
package commander
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.5
|
||||
// protoc-gen-go v1.36.12
|
||||
// protoc (unknown)
|
||||
// source: app/dispatcher/config.proto
|
||||
|
||||
package dispatcher
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.5
|
||||
// protoc-gen-go v1.36.12
|
||||
// protoc (unknown)
|
||||
// source: app/dns/config.proto
|
||||
|
||||
package dns
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.5
|
||||
// protoc-gen-go v1.36.12
|
||||
// protoc (unknown)
|
||||
// source: app/dns/fakedns/fakedns.proto
|
||||
|
||||
package fakedns
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.5
|
||||
// protoc-gen-go v1.36.12
|
||||
// protoc (unknown)
|
||||
// source: app/geodata/config.proto
|
||||
|
||||
package geodata
|
||||
|
||||
+55
-58
@@ -1,6 +1,8 @@
|
||||
package geodata
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
go_errors "errors"
|
||||
@@ -21,7 +23,10 @@ import (
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
const idleTimeout = 30 * time.Second
|
||||
const (
|
||||
idleTimeout = 30 * time.Second
|
||||
maxDownloadSize int64 = 256 * 1024 * 1024
|
||||
)
|
||||
|
||||
// DO NOT EDIT. Ignored by geodata, rejected by Lua.
|
||||
const luaGuardHeader = "\x12\x01\x00"
|
||||
@@ -33,7 +38,6 @@ type stage struct {
|
||||
|
||||
type downloader struct {
|
||||
ctx context.Context
|
||||
httpClient *http.Client
|
||||
httpsClient *http.Client
|
||||
}
|
||||
|
||||
@@ -61,12 +65,11 @@ func (c *idleConn) Write(b []byte) (int, error) {
|
||||
func newDownloader(ctx context.Context, dispatcher routing.Dispatcher, outbound string) *downloader {
|
||||
return &downloader{
|
||||
ctx: ctx,
|
||||
httpClient: newClient(ctx, dispatcher, outbound, false),
|
||||
httpsClient: newClient(ctx, dispatcher, outbound, true),
|
||||
httpsClient: newClient(ctx, dispatcher, outbound),
|
||||
}
|
||||
}
|
||||
|
||||
func newClient(baseCtx context.Context, dispatcher routing.Dispatcher, outbound string, isHTTPS bool) *http.Client {
|
||||
func newClient(baseCtx context.Context, dispatcher routing.Dispatcher, outbound string) *http.Client {
|
||||
dial := func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
var conn net.Conn
|
||||
err := task.Run(ctx, func() error {
|
||||
@@ -91,53 +94,33 @@ func newClient(baseCtx context.Context, dispatcher routing.Dispatcher, outbound
|
||||
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())
|
||||
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
|
||||
}
|
||||
if len(via) >= 10 {
|
||||
return errors.New("stopped after 10 redirects")
|
||||
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 nil
|
||||
return tlsConn, 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
|
||||
},
|
||||
}
|
||||
},
|
||||
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) >= 20 {
|
||||
return errors.New("stopped after 20 redirects")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,27 +186,41 @@ func (d *downloader) fetch(rawURL string, writer io.Writer) error {
|
||||
}
|
||||
utils.TryDefaultHeadersWith(req.Header, "nav")
|
||||
|
||||
var client *http.Client
|
||||
if req.URL.Scheme == "https" {
|
||||
client = d.httpsClient
|
||||
} else {
|
||||
client = d.httpClient
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
resp, err := d.httpsClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
|
||||
io.Copy(io.Discard, resp.Body)
|
||||
return errors.New("unexpected status code: ", resp.StatusCode)
|
||||
}
|
||||
if resp.ContentLength > maxDownloadSize {
|
||||
return errors.New("geodata download exceeds 256 MiB")
|
||||
}
|
||||
|
||||
n, err := io.Copy(writer, resp.Body)
|
||||
br := bufio.NewReader(resp.Body)
|
||||
header, err := br.Peek(2)
|
||||
if err != nil && err != io.EOF {
|
||||
return err
|
||||
}
|
||||
var body io.Reader = br
|
||||
if len(header) == 2 && header[0] == 0x1f && header[1] == 0x8b {
|
||||
zr, err := gzip.NewReader(br)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer zr.Close()
|
||||
body = zr
|
||||
}
|
||||
|
||||
n, err := io.Copy(writer, io.LimitReader(body, maxDownloadSize+1))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n > maxDownloadSize {
|
||||
return errors.New("geodata download exceeds 256 MiB")
|
||||
}
|
||||
if n == 0 {
|
||||
return errors.New("empty response body")
|
||||
}
|
||||
|
||||
@@ -7,10 +7,13 @@ import (
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
commongeodata "github.com/xtls/xray-core/common/geodata"
|
||||
xlua "github.com/xtls/xray-core/common/lua"
|
||||
"github.com/xtls/xray-core/common/serial"
|
||||
"github.com/xtls/xray-core/core"
|
||||
"github.com/yuin/gopher-lua/parse"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
@@ -31,11 +34,11 @@ func TestDownloadOneLuaGuard(t *testing.T) {
|
||||
if err := os.WriteFile(filepath.Join(dir, "asset.lua"), nil, 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
_, _ = w.Write([]byte(tc.body))
|
||||
}))
|
||||
defer server.Close()
|
||||
d := &downloader{ctx: context.Background(), httpClient: server.Client()}
|
||||
d := &downloader{ctx: context.Background(), httpsClient: server.Client()}
|
||||
staged, err := d.downloadOne(&Asset{Url: server.URL, File: "asset.lua"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -71,3 +74,53 @@ func TestDownloadOneLuaGuard(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProtobufConfigValidation(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
t.Setenv("xray.location.asset", dir)
|
||||
for _, name := range []string{"asset.dat", "asset.DAT"} {
|
||||
if err := os.WriteFile(filepath.Join(dir, name), nil, 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if err := os.Mkdir(filepath.Join(dir, "directory.dat"), 0o700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
server, err := core.New(&core.Config{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() { _ = server.Close() })
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
config *Config
|
||||
wantError string
|
||||
}{
|
||||
{"minutely cron", &Config{Cron: "* * * * *"}, ""},
|
||||
{"minimum interval", &Config{Cron: "@every 1m"}, ""},
|
||||
{"short interval", &Config{Cron: "@every 59s"}, "interval must be at least 1 minute"},
|
||||
{"fractional interval", &Config{Cron: "@every 59.999s"}, "interval must be at least 1 minute"},
|
||||
{"invalid cron", &Config{Cron: "invalid"}, "invalid geodata cron"},
|
||||
{"dat extension", &Config{Cron: "@daily", Assets: []*Asset{{Url: "https://example.com/asset.dat", File: "asset.dat"}}}, ""},
|
||||
{"uppercase extension", &Config{Cron: "@daily", Assets: []*Asset{{Url: "https://example.com/asset.dat", File: "asset.DAT"}}}, ""},
|
||||
{"invalid extension", &Config{Cron: "@daily", Assets: []*Asset{{Url: "https://example.com/asset.dat", File: "asset.lua"}}}, "extension must be .dat"},
|
||||
{"missing extension", &Config{Cron: "@daily", Assets: []*Asset{{Url: "https://example.com/asset.dat", File: "asset"}}}, "extension must be .dat"},
|
||||
{"missing file", &Config{Cron: "@daily", Assets: []*Asset{{Url: "https://example.com/asset.dat", File: "missing.dat"}}}, "invalid geodata asset file"},
|
||||
{"directory", &Config{Cron: "@daily", Assets: []*Asset{{Url: "https://example.com/asset.dat", File: "directory.dat"}}}, "asset is not a regular file"},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
config, err := serial.ToTypedMessage(tc.config).GetInstance()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = core.CreateObject(server, config)
|
||||
if tc.wantError == "" {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
} else if err == nil || !strings.Contains(err.Error(), tc.wantError) {
|
||||
t.Fatalf("CreateObject() error = %v; want %q", err, tc.wantError)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+23
-3
@@ -2,12 +2,16 @@ package geodata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/robfig/cron/v3"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
commongeodata "github.com/xtls/xray-core/common/geodata"
|
||||
"github.com/xtls/xray-core/common/platform/filesystem"
|
||||
"github.com/xtls/xray-core/core"
|
||||
"github.com/xtls/xray-core/features/routing"
|
||||
)
|
||||
@@ -25,6 +29,24 @@ func New(ctx context.Context, config *Config) (*Instance, error) {
|
||||
if config.Cron == "" {
|
||||
return &Instance{}, nil
|
||||
}
|
||||
if len(config.Assets) > 100 {
|
||||
return nil, errors.New("geodata supports at most 100 assets")
|
||||
}
|
||||
for _, asset := range config.Assets {
|
||||
if !strings.EqualFold(filepath.Ext(asset.GetFile()), ".dat") {
|
||||
return nil, errors.New("geodata asset file extension must be .dat")
|
||||
}
|
||||
if _, err := filesystem.ResolveAsset(asset.GetFile()); err != nil {
|
||||
return nil, errors.New("invalid geodata asset file: ", asset.GetFile()).Base(err)
|
||||
}
|
||||
}
|
||||
schedule, err := cron.ParseStandard(config.Cron)
|
||||
if err != nil {
|
||||
return nil, errors.New("invalid geodata cron").Base(err)
|
||||
}
|
||||
if interval, ok := schedule.(cron.ConstantDelaySchedule); ok && interval.Delay < time.Minute {
|
||||
return nil, errors.New("geodata cron interval must be at least 1 minute")
|
||||
}
|
||||
|
||||
g := &Instance{
|
||||
assets: config.Assets,
|
||||
@@ -44,9 +66,7 @@ func New(ctx context.Context, config *Config) (*Instance, error) {
|
||||
cron.WithChain(cron.SkipIfStillRunning(cron.DiscardLogger)),
|
||||
cron.WithLogger(cron.DiscardLogger),
|
||||
)
|
||||
if _, err := g.tasker.AddFunc(config.Cron, g.execute); err != nil {
|
||||
return nil, errors.New("invalid geodata cron").Base(err)
|
||||
}
|
||||
g.tasker.Schedule(schedule, cron.FuncJob(g.execute))
|
||||
errors.LogInfo(ctx, "scheduled geodata reload with cron: ", config.Cron)
|
||||
|
||||
return g, nil
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.5
|
||||
// protoc-gen-go v1.36.12
|
||||
// protoc (unknown)
|
||||
// source: app/log/command/config.proto
|
||||
|
||||
package command
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.6.0
|
||||
// - protoc v6.33.5
|
||||
// - protoc-gen-go-grpc v1.6.2
|
||||
// - protoc (unknown)
|
||||
// source: app/log/command/config.proto
|
||||
|
||||
package command
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.5
|
||||
// protoc-gen-go v1.36.12
|
||||
// protoc (unknown)
|
||||
// source: app/log/config.proto
|
||||
|
||||
package log
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.5
|
||||
// protoc-gen-go v1.36.12
|
||||
// protoc (unknown)
|
||||
// source: app/metrics/config.proto
|
||||
|
||||
package metrics
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.5
|
||||
// protoc-gen-go v1.36.12
|
||||
// protoc (unknown)
|
||||
// source: app/observatory/burst/config.proto
|
||||
|
||||
package burst
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.5
|
||||
// protoc-gen-go v1.36.12
|
||||
// protoc (unknown)
|
||||
// source: app/observatory/command/command.proto
|
||||
|
||||
package command
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.6.0
|
||||
// - protoc v6.33.5
|
||||
// - protoc-gen-go-grpc v1.6.2
|
||||
// - protoc (unknown)
|
||||
// source: app/observatory/command/command.proto
|
||||
|
||||
package command
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.5
|
||||
// protoc-gen-go v1.36.12
|
||||
// protoc (unknown)
|
||||
// source: app/observatory/config.proto
|
||||
|
||||
package observatory
|
||||
@@ -152,23 +152,23 @@ func (x *HealthPingMeasurementResult) GetMin() int64 {
|
||||
type OutboundStatus struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// @Document Whether this outbound is usable
|
||||
// @Restriction ReadOnlyForUser
|
||||
//@Restriction ReadOnlyForUser
|
||||
Alive bool `protobuf:"varint,1,opt,name=alive,proto3" json:"alive,omitempty"`
|
||||
// @Document The time for probe request to finish.
|
||||
// @Type time.ms
|
||||
// @Restriction ReadOnlyForUser
|
||||
//@Type time.ms
|
||||
//@Restriction ReadOnlyForUser
|
||||
Delay int64 `protobuf:"varint,2,opt,name=delay,proto3" json:"delay,omitempty"`
|
||||
// @Document The last error caused this outbound failed to relay probe request
|
||||
// @Restriction NotMachineReadable
|
||||
//@Restriction NotMachineReadable
|
||||
LastErrorReason string `protobuf:"bytes,3,opt,name=last_error_reason,json=lastErrorReason,proto3" json:"last_error_reason,omitempty"`
|
||||
// @Document The outbound tag for this Server
|
||||
// @Type id.outboundTag
|
||||
//@Type id.outboundTag
|
||||
OutboundTag string `protobuf:"bytes,4,opt,name=outbound_tag,json=outboundTag,proto3" json:"outbound_tag,omitempty"`
|
||||
// @Document The time this outbound is known to be alive
|
||||
// @Type id.outboundTag
|
||||
//@Type id.outboundTag
|
||||
LastSeenTime int64 `protobuf:"varint,5,opt,name=last_seen_time,json=lastSeenTime,proto3" json:"last_seen_time,omitempty"`
|
||||
// @Document The time this outbound is tried
|
||||
// @Type id.outboundTag
|
||||
//@Type id.outboundTag
|
||||
LastTryTime int64 `protobuf:"varint,6,opt,name=last_try_time,json=lastTryTime,proto3" json:"last_try_time,omitempty"`
|
||||
HealthPing *HealthPingMeasurementResult `protobuf:"bytes,7,opt,name=health_ping,json=healthPing,proto3" json:"health_ping,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
@@ -257,14 +257,14 @@ func (x *OutboundStatus) GetHealthPing() *HealthPingMeasurementResult {
|
||||
type ProbeResult struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// @Document Whether this outbound is usable
|
||||
// @Restriction ReadOnlyForUser
|
||||
//@Restriction ReadOnlyForUser
|
||||
Alive bool `protobuf:"varint,1,opt,name=alive,proto3" json:"alive,omitempty"`
|
||||
// @Document The time for probe request to finish.
|
||||
// @Type time.ms
|
||||
// @Restriction ReadOnlyForUser
|
||||
//@Type time.ms
|
||||
//@Restriction ReadOnlyForUser
|
||||
Delay int64 `protobuf:"varint,2,opt,name=delay,proto3" json:"delay,omitempty"`
|
||||
// @Document The error caused this outbound failed to relay probe request
|
||||
// @Restriction NotMachineReadable
|
||||
//@Restriction NotMachineReadable
|
||||
LastErrorReason string `protobuf:"bytes,3,opt,name=last_error_reason,json=lastErrorReason,proto3" json:"last_error_reason,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
@@ -324,7 +324,7 @@ func (x *ProbeResult) GetLastErrorReason() string {
|
||||
type Intensity struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// @Document The time interval for a probe request in ms.
|
||||
// @Type time.ms
|
||||
//@Type time.ms
|
||||
ProbeInterval uint32 `protobuf:"varint,1,opt,name=probe_interval,json=probeInterval,proto3" json:"probe_interval,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.5
|
||||
// protoc-gen-go v1.36.12
|
||||
// protoc (unknown)
|
||||
// source: app/policy/config.proto
|
||||
|
||||
package policy
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.5
|
||||
// protoc-gen-go v1.36.12
|
||||
// protoc (unknown)
|
||||
// source: app/proxyman/command/command.proto
|
||||
|
||||
package command
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.6.0
|
||||
// - protoc v6.33.5
|
||||
// - protoc-gen-go-grpc v1.6.2
|
||||
// - protoc (unknown)
|
||||
// source: app/proxyman/command/command.proto
|
||||
|
||||
package command
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.5
|
||||
// protoc-gen-go v1.36.12
|
||||
// protoc (unknown)
|
||||
// source: app/proxyman/config.proto
|
||||
|
||||
package proxyman
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.5
|
||||
// protoc-gen-go v1.36.12
|
||||
// protoc (unknown)
|
||||
// source: app/reverse/config.proto
|
||||
|
||||
package reverse
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.5
|
||||
// protoc-gen-go v1.36.12
|
||||
// protoc (unknown)
|
||||
// source: app/router/command/command.proto
|
||||
|
||||
package command
|
||||
|
||||
@@ -34,18 +34,19 @@ message RoutingContext {
|
||||
// opened by xray-core.
|
||||
// * FieldSelectors selects a subset of fields in routing statistics to return.
|
||||
// Valid selectors:
|
||||
// - inbound: Selects connection's inbound tag.
|
||||
// - network: Selects connection's network.
|
||||
// - ip: Equivalent as "ip_source" and "ip_target", selects both source and
|
||||
// target IP.
|
||||
// - port: Equivalent as "port_source" and "port_target", selects both source
|
||||
// and target port.
|
||||
// - domain: Selects target domain.
|
||||
// - protocol: Select connection's protocol.
|
||||
// - user: Select connection's inbound user email.
|
||||
// - attributes: Select connection's additional attributes.
|
||||
// - outbound: Equivalent as "outbound" and "outbound_group", select both
|
||||
// outbound tag and outbound group tags.
|
||||
// - inbound: Selects connection's inbound tag.
|
||||
// - network: Selects connection's network.
|
||||
// - ip: Equivalent as "ip_source" and "ip_target", selects both source and
|
||||
// target IP.
|
||||
// - port: Equivalent as "port_source" and "port_target", selects both source
|
||||
// and target port.
|
||||
// - domain: Selects target domain.
|
||||
// - protocol: Select connection's protocol.
|
||||
// - user: Select connection's inbound user email.
|
||||
// - attributes: Select connection's additional attributes.
|
||||
// - outbound: Equivalent as "outbound" and "outbound_group", select both
|
||||
// outbound tag and outbound group tags.
|
||||
//
|
||||
// * If FieldSelectors is left empty, all fields will be returned.
|
||||
message SubscribeRoutingStatsRequest {
|
||||
repeated string FieldSelectors = 1;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.6.0
|
||||
// - protoc v6.33.5
|
||||
// - protoc-gen-go-grpc v1.6.2
|
||||
// - protoc (unknown)
|
||||
// source: app/router/command/command.proto
|
||||
|
||||
package command
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.5
|
||||
// protoc-gen-go v1.36.12
|
||||
// protoc (unknown)
|
||||
// source: app/router/config.proto
|
||||
|
||||
package router
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.5
|
||||
// protoc-gen-go v1.36.12
|
||||
// protoc (unknown)
|
||||
// source: app/stats/command/command.proto
|
||||
|
||||
package command
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.6.0
|
||||
// - protoc v6.33.5
|
||||
// - protoc-gen-go-grpc v1.6.2
|
||||
// - protoc (unknown)
|
||||
// source: app/stats/command/command.proto
|
||||
|
||||
package command
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.5
|
||||
// protoc-gen-go v1.36.12
|
||||
// protoc (unknown)
|
||||
// source: app/stats/config.proto
|
||||
|
||||
package stats
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.5
|
||||
// protoc-gen-go v1.36.12
|
||||
// protoc (unknown)
|
||||
// source: app/version/config.proto
|
||||
|
||||
package version
|
||||
|
||||
+59
-17
@@ -8,23 +8,65 @@ import (
|
||||
|
||||
var privateIPMatcher = sync.OnceValue(func() IPMatcher {
|
||||
return common.Must2(IPReg.BuildIPMatcher(common.Must2(ParseIPRules([]string{
|
||||
"0.0.0.0/8",
|
||||
"10.0.0.0/8",
|
||||
"100.64.0.0/10",
|
||||
"127.0.0.0/8",
|
||||
"169.254.0.0/16",
|
||||
"172.16.0.0/12",
|
||||
"192.0.0.0/24",
|
||||
"192.0.2.0/24",
|
||||
"192.88.99.0/24",
|
||||
"192.168.0.0/16",
|
||||
"198.18.0.0/15",
|
||||
"198.51.100.0/24",
|
||||
"203.0.113.0/24",
|
||||
"224.0.0.0/3",
|
||||
"::/127",
|
||||
"fc00::/7",
|
||||
"fe80::/10",
|
||||
// https://www.iana.org/assignments/iana-ipv4-special-registry
|
||||
"0.0.0.0/8", // "This network"
|
||||
"0.0.0.0/32", // "This host on this network"
|
||||
"10.0.0.0/8", // Private-Use
|
||||
"100.64.0.0/10", // Shared Address Space
|
||||
"127.0.0.0/8", // Loopback
|
||||
"169.254.0.0/16", // Link Local
|
||||
"172.16.0.0/12", // Private-Use
|
||||
// "192.0.0.0/24", // IETF Protocol Assignments
|
||||
"192.0.0.0/29", // IPv4 Service Continuity Prefix
|
||||
"192.0.0.8/32", // IPv4 dummy address
|
||||
"192.0.0.9/32", // Port Control Protocol Anycast
|
||||
"192.0.0.10/32", // Traversal Using Relays around NAT Anycast
|
||||
"192.0.0.170/32", // NAT64/DNS64 Discovery
|
||||
"192.0.0.171/32", // NAT64/DNS64 Discovery
|
||||
"192.0.2.0/24", // Documentation (TEST-NET-1)
|
||||
// "192.31.196.0/24", // AS112-v4
|
||||
"192.52.193.0/24", // AMT
|
||||
// "192.88.99.0/24", // Deprecated (6to4 Relay Anycast)
|
||||
"192.88.99.2/32", // 6a44-relay anycast address
|
||||
"192.168.0.0/16", // Private-Use
|
||||
// "192.175.48.0/24", // Direct Delegation AS112 Service
|
||||
"198.18.0.0/15", // Benchmarking
|
||||
"198.51.100.0/24", // Documentation (TEST-NET-2)
|
||||
"203.0.113.0/24", // Documentation (TEST-NET-3)
|
||||
"240.0.0.0/4", // Reserved
|
||||
"255.255.255.255/32", // Limited Broadcast
|
||||
|
||||
// https://www.iana.org/assignments/multicast-addresses
|
||||
"224.0.0.0/4",
|
||||
|
||||
// https://www.iana.org/assignments/iana-ipv6-special-registry
|
||||
"::1/128", // Loopback Address
|
||||
"::/128", // Unspecified Address
|
||||
// "::ffff:0:0/96", // IPv4-mapped Address
|
||||
"64:ff9b::/96", // IPv4-IPv6 Translat.
|
||||
"64:ff9b:1::/48", // IPv4-IPv6 Translat.
|
||||
"100::/64", // Discard-Only Address Block
|
||||
"100:0:0:1::/64", // Dummy IPv6 Prefix
|
||||
// "2001::/23", // IETF Protocol Assignments
|
||||
"2001::/32", // TEREDO
|
||||
"2001:1::1/128", // Port Control Protocol Anycast
|
||||
"2001:1::2/128", // Traversal Using Relays around NAT Anycast
|
||||
"2001:1::3/128", // DNS-SD Service Registration Protocol Anycast
|
||||
"2001:2::/48", // Benchmarking
|
||||
"2001:3::/32", // AMT
|
||||
// "2001:4:112::/48", // AS112-v6
|
||||
// "2001:10::/28", // Deprecated (previously ORCHID)
|
||||
"2001:20::/28", // ORCHIDv2
|
||||
"2001:30::/28", // Drone Remote ID Protocol Entity Tags (DETs) Prefix
|
||||
"2001:db8::/32", // Documentation
|
||||
"2002::/16", // 6to4
|
||||
// "2620:4f:8000::/48", // Direct Delegation AS112 Service
|
||||
"3fff::/20", // Documentation
|
||||
"5f00::/16", // Segment Routing (SRv6) SIDs
|
||||
"fc00::/7", // Unique-Local
|
||||
"fe80::/10", // Link-Local Unicast
|
||||
|
||||
// https://www.iana.org/assignments/ipv6-multicast-addresses
|
||||
"ff00::/8",
|
||||
}))))
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.5
|
||||
// protoc-gen-go v1.36.12
|
||||
// protoc (unknown)
|
||||
// source: common/geodata/geodat.proto
|
||||
|
||||
package geodata
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.5
|
||||
// protoc-gen-go v1.36.12
|
||||
// protoc (unknown)
|
||||
// source: common/log/log.proto
|
||||
|
||||
package log
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
"github.com/xtls/xray-core/common/base64"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/platform"
|
||||
"github.com/xtls/xray-core/common/platform/filesystem"
|
||||
glua "github.com/yuin/gopher-lua"
|
||||
"github.com/yuin/gopher-lua/parse"
|
||||
)
|
||||
@@ -36,7 +36,7 @@ func Compile(script string) (*Program, error) {
|
||||
// TODO: Restore inline script support and re-enable the related tests.
|
||||
return nil, errors.New("currently unavailable")
|
||||
code, err = base64.Decode(script)
|
||||
} else if name, err = platform.ResolveLuaFile(script); err == nil {
|
||||
} else if name, err = filesystem.ResolveLuaFile(script); err == nil {
|
||||
code, err = os.ReadFile(name)
|
||||
// TODO: Restore privileged signature authorization and re-enable the related tests and CLI entries.
|
||||
// if err == nil {
|
||||
|
||||
@@ -187,19 +187,20 @@ func TestCompileInlineDiagnostics(t *testing.T) {
|
||||
*/
|
||||
|
||||
func TestCompileFileExtension(t *testing.T) {
|
||||
for _, name := range []string{"script", "script.dat", "script.lua.txt", "script.LUA"} {
|
||||
for _, name := range []string{"script", "script.dat", "script.lua.txt"} {
|
||||
if program, err := Compile(name); program != nil || err == nil || !strings.Contains(err.Error(), "Lua file extension must be .lua") {
|
||||
t.Fatalf("Compile(%q) = %v, %v; want nil program and .lua extension error", name, program, err)
|
||||
}
|
||||
}
|
||||
for _, name := range []string{"script.lua", "script.LUA", "script.Lua", "script.lUa"} {
|
||||
path := filepath.Join(t.TempDir(), name)
|
||||
if err := os.WriteFile(path, []byte("value = 42"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if program, err := Compile(path); program != nil || err == nil || !strings.Contains(err.Error(), "Lua file extension must be .lua") {
|
||||
t.Fatalf("Compile(%q) = %v, %v; want nil program and .lua extension error", path, program, err)
|
||||
if program, err := Compile(path); program == nil || err != nil {
|
||||
t.Fatalf("Compile(%q) = %v, %v; want successful compilation", path, program, err)
|
||||
}
|
||||
}
|
||||
path := writeTestScript(t, "value = 42") + string(filepath.Separator) + "."
|
||||
if program, err := Compile(path); program == nil || err != nil {
|
||||
t.Fatalf("Compile(%q) = %v, %v; want acceptance of the resolved .lua path", path, program, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompileRejectsReadFailure(t *testing.T) {
|
||||
@@ -234,10 +235,6 @@ func writeTestScript(t *testing.T, code string) string {
|
||||
if err := os.WriteFile(path, []byte(code), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
path, err := filepath.EvalSymlinks(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.5
|
||||
// protoc-gen-go v1.36.12
|
||||
// protoc (unknown)
|
||||
// source: common/net/address.proto
|
||||
|
||||
package net
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.5
|
||||
// protoc-gen-go v1.36.12
|
||||
// protoc (unknown)
|
||||
// source: common/net/destination.proto
|
||||
|
||||
package net
|
||||
|
||||
@@ -4,7 +4,7 @@ package net
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/hex"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
@@ -45,10 +45,7 @@ func FindProcess(network, srcIP string, srcPort uint16, destIP string, destPort
|
||||
panic("Unsupported network type for process lookup.")
|
||||
}
|
||||
|
||||
targetHexAddr, err := formatLittleEndianString(net.ParseIP(srcIP), Port(srcPort))
|
||||
if err != nil {
|
||||
return 0, "", "", errors.New("failed to format address: ", err)
|
||||
}
|
||||
targetHexAddr := formatProcNetAddress(net.ParseIP(srcIP), Port(srcPort))
|
||||
|
||||
portFallback := ""
|
||||
if network == "udp" {
|
||||
@@ -87,24 +84,21 @@ func FindProcess(network, srcIP string, srcPort uint16, destIP string, destPort
|
||||
return pid, procName, absPath, nil
|
||||
}
|
||||
|
||||
func formatLittleEndianString(addr net.IP, port Port) (string, error) {
|
||||
ip := addr
|
||||
func formatProcNetAddress(addr net.IP, port Port) string {
|
||||
var ipBytes []byte
|
||||
if ip.To4() != nil {
|
||||
ipBytes = ip.To4()
|
||||
if ipv4 := addr.To4(); ipv4 != nil {
|
||||
ipBytes = ipv4
|
||||
} else {
|
||||
ipBytes = ip.To16()
|
||||
}
|
||||
if ipBytes == nil {
|
||||
return "", errors.New("invalid IP format for ", addr, ": ", ip)
|
||||
ipBytes = addr.To16()
|
||||
}
|
||||
|
||||
for i, j := 0, len(ipBytes)-1; i < j; i, j = i+1, j-1 {
|
||||
ipBytes[i], ipBytes[j] = ipBytes[j], ipBytes[i]
|
||||
var formatted strings.Builder
|
||||
formatted.Grow(len(ipBytes)*2 + 5)
|
||||
for i := 0; i < len(ipBytes); i += 4 {
|
||||
fmt.Fprintf(&formatted, "%08X", binary.NativeEndian.Uint32(ipBytes[i:i+4]))
|
||||
}
|
||||
portHex := fmt.Sprintf("%04X", uint16(port))
|
||||
ipHex := strings.ToUpper(hex.EncodeToString(ipBytes))
|
||||
return fmt.Sprintf("%s:%s", ipHex, portHex), nil
|
||||
fmt.Fprintf(&formatted, ":%04X", uint16(port))
|
||||
return formatted.String()
|
||||
}
|
||||
|
||||
func findInodeInFiles(filePaths []string, targetHexAddr, portFallback string) (string, error) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.5
|
||||
// protoc-gen-go v1.36.12
|
||||
// protoc (unknown)
|
||||
// source: common/net/network.proto
|
||||
|
||||
package net
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.5
|
||||
// protoc-gen-go v1.36.12
|
||||
// protoc (unknown)
|
||||
// source: common/net/port.proto
|
||||
|
||||
package net
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
"github.com/xtls/xray-core/common/platform"
|
||||
@@ -26,14 +27,6 @@ func ReadFile(path string) ([]byte, error) {
|
||||
return buf.ReadAllToBytes(reader)
|
||||
}
|
||||
|
||||
func ReadAsset(file string) ([]byte, error) {
|
||||
path, _, err := getAssetFileLocation(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ReadFile(path)
|
||||
}
|
||||
|
||||
func OpenAsset(file string) (io.ReadCloser, int64, error) {
|
||||
path, info, err := getAssetFileLocation(file)
|
||||
if err != nil {
|
||||
@@ -43,11 +36,6 @@ func OpenAsset(file string) (io.ReadCloser, int64, error) {
|
||||
return r, info.Size(), err
|
||||
}
|
||||
|
||||
func StatAsset(file string) (os.FileInfo, error) {
|
||||
_, info, err := getAssetFileLocation(file)
|
||||
return info, err
|
||||
}
|
||||
|
||||
func ResolveAsset(file string) (string, error) {
|
||||
path, _, err := getAssetFileLocation(file)
|
||||
return path, err
|
||||
@@ -79,17 +67,29 @@ func ReadCert(file string) ([]byte, error) {
|
||||
return ReadFile(platform.GetCertLocation(file))
|
||||
}
|
||||
|
||||
func CopyFile(dst string, src string) error {
|
||||
bytes, err := ReadFile(src)
|
||||
if err != nil {
|
||||
return err
|
||||
// ResolveLuaFile finds a local .lua script and returns its absolute path.
|
||||
// Relative paths: XRAY_LOCATION_CONFDIR > XRAY_LOCATION_CONFIG > working dir > executable dir.
|
||||
func ResolveLuaFile(path string) (string, error) {
|
||||
if !strings.EqualFold(filepath.Ext(path), ".lua") {
|
||||
return "", errors.New("Lua file extension must be .lua")
|
||||
}
|
||||
f, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY, 0o644)
|
||||
if err != nil {
|
||||
return err
|
||||
if filepath.IsAbs(path) {
|
||||
return path, nil
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = f.Write(bytes)
|
||||
return err
|
||||
for _, dir := range []string{
|
||||
platform.GetConfDirPath(),
|
||||
platform.NewEnvFlag(platform.ConfigLocation).GetValue(func() string { return "" }),
|
||||
".",
|
||||
platform.GetExecutableDir(),
|
||||
} {
|
||||
if dir != "" {
|
||||
resolved := filepath.Join(dir, path)
|
||||
if _, err := os.Stat(resolved); err == nil {
|
||||
return filepath.Abs(resolved)
|
||||
} else if !os.IsNotExist(err) {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", os.ErrNotExist
|
||||
}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
package filesystem_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/platform"
|
||||
. "github.com/xtls/xray-core/common/platform/filesystem"
|
||||
)
|
||||
|
||||
func TestStatAssetRejectsInvalidPath(t *testing.T) {
|
||||
func TestResolveAssetRejectsInvalidPath(t *testing.T) {
|
||||
for _, file := range []string{
|
||||
"",
|
||||
".",
|
||||
@@ -25,8 +29,49 @@ func TestStatAssetRejectsInvalidPath(t *testing.T) {
|
||||
`nested\..\geoip.dat`,
|
||||
filepath.Join(t.TempDir(), "geoip.dat"),
|
||||
} {
|
||||
if _, err := StatAsset(file); err == nil {
|
||||
if _, err := ResolveAsset(file); err == nil {
|
||||
t.Fatalf("expected error for %q", file)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveLuaFile(t *testing.T) {
|
||||
t.Chdir(t.TempDir())
|
||||
workingDir, err := os.Getwd()
|
||||
common.Must(err)
|
||||
executable, err := os.Executable()
|
||||
common.Must(err)
|
||||
file, err := os.CreateTemp(filepath.Dir(executable), "lua-*.lua")
|
||||
common.Must(err)
|
||||
common.Must(file.Close())
|
||||
defer os.Remove(file.Name())
|
||||
|
||||
name := filepath.Base(file.Name())
|
||||
paths := []string{
|
||||
filepath.Join(t.TempDir(), name),
|
||||
filepath.Join(t.TempDir(), name),
|
||||
filepath.Join(workingDir, name),
|
||||
file.Name(),
|
||||
}
|
||||
t.Setenv(platform.ConfdirLocation, filepath.Dir(paths[0]))
|
||||
t.Setenv(platform.ConfigLocation, filepath.Dir(paths[1]))
|
||||
for _, path := range paths[:3] {
|
||||
common.Must(os.WriteFile(path, nil, 0o600))
|
||||
}
|
||||
if got, err := ResolveLuaFile(paths[2]); err != nil || got != paths[2] {
|
||||
t.Fatalf("absolute path = %q, %v; want %q", got, err, paths[2])
|
||||
}
|
||||
for i, want := range paths {
|
||||
if i == 2 {
|
||||
t.Setenv(platform.ConfdirLocation, "")
|
||||
t.Setenv(platform.ConfigLocation, "")
|
||||
}
|
||||
if got, err := ResolveLuaFile(name); err != nil || got != want {
|
||||
t.Fatalf("resolved path = %q, %v; want %q", got, err, want)
|
||||
}
|
||||
common.Must(os.Remove(want))
|
||||
}
|
||||
if _, err := ResolveLuaFile(name); !errors.Is(err, os.ErrNotExist) {
|
||||
t.Fatalf("missing file error = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ func LineSeparator() string {
|
||||
|
||||
// GetAssetLocation searches for `file` in the env dir, the executable dir, and certain locations
|
||||
func GetAssetLocation(file string) string {
|
||||
assetPath := NewEnvFlag(AssetLocation).GetValue(getExecutableDir)
|
||||
assetPath := NewEnvFlag(AssetLocation).GetValue(GetExecutableDir)
|
||||
defPath := filepath.Join(assetPath, file)
|
||||
for _, p := range []string{
|
||||
defPath,
|
||||
@@ -36,6 +36,6 @@ func GetAssetLocation(file string) string {
|
||||
|
||||
// GetCertLocation searches for `file` in the env dir and the executable dir
|
||||
func GetCertLocation(file string) string {
|
||||
certPath := NewEnvFlag(CertLocation).GetValue(getExecutableDir)
|
||||
certPath := NewEnvFlag(CertLocation).GetValue(GetExecutableDir)
|
||||
return filepath.Join(certPath, file)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package platform // import "github.com/xtls/xray-core/common/platform"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
@@ -74,7 +72,7 @@ func NormalizeEnvName(name string) string {
|
||||
return strings.ReplaceAll(strings.ToUpper(strings.TrimSpace(name)), ".", "_")
|
||||
}
|
||||
|
||||
func getExecutableDir() string {
|
||||
func GetExecutableDir() string {
|
||||
exec, err := os.Executable()
|
||||
if err != nil {
|
||||
return ""
|
||||
@@ -83,7 +81,7 @@ func getExecutableDir() string {
|
||||
}
|
||||
|
||||
func GetConfigurationPath() string {
|
||||
configPath := NewEnvFlag(ConfigLocation).GetValue(getExecutableDir)
|
||||
configPath := NewEnvFlag(ConfigLocation).GetValue(GetExecutableDir)
|
||||
return filepath.Join(configPath, "config.json")
|
||||
}
|
||||
|
||||
@@ -92,64 +90,3 @@ func GetConfDirPath() string {
|
||||
configPath := NewEnvFlag(ConfdirLocation).GetValue(func() string { return "" })
|
||||
return configPath
|
||||
}
|
||||
|
||||
// ResolveLuaFile finds a local .lua script and returns its resolved absolute path.
|
||||
// Relative paths: XRAY_LOCATION_CONFDIR > XRAY_LOCATION_CONFIG > working dir > executable dir.
|
||||
func ResolveLuaFile(path string) (string, error) {
|
||||
if path == "" {
|
||||
return "", errors.New("Lua file path is empty")
|
||||
}
|
||||
paths := []string{path}
|
||||
if !filepath.IsAbs(path) {
|
||||
paths = nil
|
||||
for _, dir := range []string{
|
||||
GetConfDirPath(),
|
||||
NewEnvFlag(ConfigLocation).GetValue(func() string { return "" }),
|
||||
".",
|
||||
getExecutableDir(),
|
||||
} {
|
||||
if dir != "" {
|
||||
paths = append(paths, filepath.Join(dir, path))
|
||||
}
|
||||
}
|
||||
}
|
||||
path, err := resolveFile(paths)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return resolveLuaTarget(path)
|
||||
}
|
||||
|
||||
func resolveLuaTarget(path string) (string, error) {
|
||||
path, err := filepath.EvalSymlinks(path)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to resolve Lua file symlinks: %w", err)
|
||||
}
|
||||
if filepath.Ext(path) != ".lua" {
|
||||
return "", errors.New("Lua file extension must be .lua")
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
|
||||
func resolveFile(paths []string) (string, error) {
|
||||
var tried []string
|
||||
for _, path := range paths {
|
||||
path, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to resolve file path: %w", err)
|
||||
}
|
||||
tried = append(tried, path)
|
||||
info, err := os.Stat(path)
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to inspect file %q: %w", path, err)
|
||||
}
|
||||
if !info.Mode().IsRegular() {
|
||||
return "", fmt.Errorf("file is not a regular file: %s", path)
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
return "", fmt.Errorf("file not found; tried %q: %w", tried, os.ErrNotExist)
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
package platform_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
@@ -66,89 +64,3 @@ func TestGetAssetLocation(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveLuaFile(t *testing.T) {
|
||||
workingDir := t.TempDir()
|
||||
t.Chdir(workingDir)
|
||||
executable, err := os.Executable()
|
||||
common.Must(err)
|
||||
file, err := os.CreateTemp(filepath.Dir(executable), "lua-*.lua")
|
||||
common.Must(err)
|
||||
common.Must(file.Close())
|
||||
defer os.Remove(file.Name())
|
||||
|
||||
name := filepath.Base(file.Name())
|
||||
paths := []string{
|
||||
filepath.Join(t.TempDir(), name),
|
||||
filepath.Join(t.TempDir(), name),
|
||||
filepath.Join(workingDir, name),
|
||||
file.Name(),
|
||||
}
|
||||
t.Setenv(ConfdirLocation, filepath.Dir(paths[0]))
|
||||
t.Setenv(ConfigLocation, filepath.Dir(paths[1]))
|
||||
for _, path := range paths[:3] {
|
||||
common.Must(os.WriteFile(path, nil, 0o600))
|
||||
}
|
||||
for i, path := range paths {
|
||||
paths[i], err = filepath.EvalSymlinks(path)
|
||||
common.Must(err)
|
||||
}
|
||||
if got, err := ResolveLuaFile(paths[2]); err != nil || got != paths[2] {
|
||||
t.Fatalf("absolute path = %q, %v; want %q", got, err, paths[2])
|
||||
}
|
||||
for i, want := range paths {
|
||||
if i == 2 {
|
||||
t.Setenv(ConfdirLocation, "")
|
||||
t.Setenv(ConfigLocation, "")
|
||||
}
|
||||
if got, err := ResolveLuaFile(name); err != nil || got != want {
|
||||
t.Fatalf("resolved path = %q, %v; want %q", got, err, want)
|
||||
}
|
||||
common.Must(os.Remove(want))
|
||||
}
|
||||
if _, err := ResolveLuaFile(name); !errors.Is(err, os.ErrNotExist) {
|
||||
t.Fatalf("missing file error = %v", err)
|
||||
}
|
||||
|
||||
t.Setenv(ConfdirLocation, filepath.Dir(paths[0]))
|
||||
t.Setenv(ConfigLocation, filepath.Dir(paths[1]))
|
||||
common.Must(os.Mkdir(paths[0], 0o700))
|
||||
common.Must(os.WriteFile(paths[1], nil, 0o600))
|
||||
for _, path := range []string{"", name, filepath.Join(t.TempDir(), name)} {
|
||||
if _, err := ResolveLuaFile(path); err == nil {
|
||||
t.Fatalf("accepted invalid path %q", path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveLuaFileSymlink(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
link, target string
|
||||
valid bool
|
||||
}{
|
||||
{"alias.lua", "data.dat", false},
|
||||
{"alias.dat", "script.lua", true},
|
||||
} {
|
||||
t.Run(tc.link, func(t *testing.T) {
|
||||
dir, err := filepath.EvalSymlinks(t.TempDir())
|
||||
common.Must(err)
|
||||
target := filepath.Join(dir, tc.target)
|
||||
common.Must(os.WriteFile(target, nil, 0o600))
|
||||
link := filepath.Join(dir, tc.link)
|
||||
if err := os.Symlink(tc.target, link); err != nil {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skipf("cannot create symlinks: %v", err)
|
||||
}
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, err := ResolveLuaFile(link)
|
||||
if tc.valid {
|
||||
if err != nil || got != target {
|
||||
t.Fatalf("ResolveLuaFile(%q) = %q, %v; want %q", link, got, err, target)
|
||||
}
|
||||
} else if err == nil || got != "" || !strings.Contains(err.Error(), "Lua file extension must be .lua") {
|
||||
t.Fatalf("ResolveLuaFile(%q) = %q, %v; want .lua extension error", link, got, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,12 +11,12 @@ func LineSeparator() string {
|
||||
|
||||
// GetAssetLocation searches for `file` in the env dir and the executable dir
|
||||
func GetAssetLocation(file string) string {
|
||||
assetPath := NewEnvFlag(AssetLocation).GetValue(getExecutableDir)
|
||||
assetPath := NewEnvFlag(AssetLocation).GetValue(GetExecutableDir)
|
||||
return filepath.Join(assetPath, file)
|
||||
}
|
||||
|
||||
// GetCertLocation searches for `file` in the env dir and the executable dir
|
||||
func GetCertLocation(file string) string {
|
||||
certPath := NewEnvFlag(CertLocation).GetValue(getExecutableDir)
|
||||
certPath := NewEnvFlag(CertLocation).GetValue(GetExecutableDir)
|
||||
return filepath.Join(certPath, file)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.5
|
||||
// protoc-gen-go v1.36.12
|
||||
// protoc (unknown)
|
||||
// source: common/protocol/headers.proto
|
||||
|
||||
package protocol
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.5
|
||||
// protoc-gen-go v1.36.12
|
||||
// protoc (unknown)
|
||||
// source: common/protocol/server_spec.proto
|
||||
|
||||
package protocol
|
||||
|
||||
@@ -101,6 +101,10 @@ func ReadClientHello(data []byte, h *SniffHeader) error {
|
||||
return errNotClientHello
|
||||
}
|
||||
if nameType == 0 {
|
||||
// RFC 6066 requires a non-empty HostName.
|
||||
if nameLen == 0 {
|
||||
return errNotClientHello
|
||||
}
|
||||
// QUIC separated across packets
|
||||
// May cause the serverName to be incomplete
|
||||
b := byte(0)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.5
|
||||
// protoc-gen-go v1.36.12
|
||||
// protoc (unknown)
|
||||
// source: common/protocol/user.proto
|
||||
|
||||
package protocol
|
||||
|
||||
@@ -134,7 +134,7 @@ func TestMarshalConfigJson(t *testing.T) {
|
||||
func getConfig() string {
|
||||
return `{
|
||||
"log": {
|
||||
"loglevel": "debug"
|
||||
"logLevel": "debug"
|
||||
},
|
||||
"stats": {},
|
||||
"policy": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.5
|
||||
// protoc-gen-go v1.36.12
|
||||
// protoc (unknown)
|
||||
// source: common/serial/typed_message.proto
|
||||
|
||||
package serial
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.5
|
||||
// protoc-gen-go v1.36.12
|
||||
// protoc (unknown)
|
||||
// source: core/config.proto
|
||||
|
||||
package core
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ import (
|
||||
var (
|
||||
Version_x byte = 26
|
||||
Version_y byte = 10
|
||||
Version_z byte = 21
|
||||
Version_z byte = 31
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
package core
|
||||
|
||||
//go:generate go install -v google.golang.org/protobuf/cmd/protoc-gen-go@latest
|
||||
//go:generate go install -v google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
|
||||
//go:generate go run ../infra/vprotogen/main.go -pwd ./..
|
||||
|
||||
@@ -2,6 +2,7 @@ package localdns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
@@ -100,6 +101,15 @@ func New() *Client {
|
||||
if internet.IsSkippedDNSServer(address) {
|
||||
return nil, errors.New("skipped DNS server ", address)
|
||||
}
|
||||
// A socket of the server's IP version only needs that version bound
|
||||
// to the interface, which a dual-stack one would need both of.
|
||||
if ap, err := netip.ParseAddrPort(address); err == nil && (network == "udp" || network == "tcp") {
|
||||
if ap.Addr().Unmap().Is4() {
|
||||
network += "4"
|
||||
} else {
|
||||
network += "6"
|
||||
}
|
||||
}
|
||||
return d.DialContext(ctx, network, address)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -4,17 +4,18 @@ go 1.27
|
||||
|
||||
require (
|
||||
github.com/apernet/quic-go v0.61.1-0.20260806010916-184d081eef3e
|
||||
github.com/bufbuild/protocompile v0.14.1
|
||||
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344
|
||||
github.com/golang/mock v1.7.0-rc.1
|
||||
github.com/google/go-cmp v0.7.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/klauspost/cpuid/v2 v2.4.1
|
||||
github.com/klauspost/cpuid/v2 v2.4.2
|
||||
github.com/libp2p/go-nat v1.0.1-0.20250821073202-01afc089f138
|
||||
github.com/miekg/dns v1.1.73
|
||||
github.com/pelletier/go-toml v1.9.5
|
||||
github.com/pion/stun/v3 v3.1.7
|
||||
github.com/pires/go-proxyproto v0.15.0
|
||||
github.com/pires/go-proxyproto v0.16.0
|
||||
github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/stretchr/testify v1.12.1
|
||||
@@ -25,13 +26,13 @@ require (
|
||||
golang.org/x/crypto v0.58.0
|
||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20261020153306-9f2b28d4331a
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
|
||||
golang.org/x/net v0.61.0
|
||||
golang.org/x/net v0.62.0
|
||||
golang.org/x/sync v0.24.0
|
||||
golang.org/x/sys v0.49.0
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
|
||||
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb
|
||||
golang.zx2c4.com/wireguard/windows v1.1.1
|
||||
google.golang.org/grpc v1.84.0
|
||||
google.golang.org/grpc v1.85.0
|
||||
google.golang.org/protobuf v1.36.12
|
||||
h12.io/socks v1.0.3
|
||||
layeh.com/gopher-luar v1.0.11
|
||||
@@ -57,7 +58,13 @@ require (
|
||||
go.yaml.in/yaml/v3 v3.0.5 // indirect
|
||||
golang.org/x/text v0.43.0 // indirect
|
||||
golang.org/x/tools v0.51.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260817212433-ac3dfec99bb1 // indirect
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0 // indirect
|
||||
)
|
||||
|
||||
tool (
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc
|
||||
google.golang.org/protobuf/cmd/protoc-gen-go
|
||||
)
|
||||
|
||||
@@ -2,6 +2,8 @@ github.com/andybalholm/brotli v1.2.3 h1:8H1qwOkl2LPfjf3YezB90JnCliZb6SInJ/OJkEbA
|
||||
github.com/andybalholm/brotli v1.2.3/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||
github.com/apernet/quic-go v0.61.1-0.20260806010916-184d081eef3e h1:5mgtR5gwIgBKMiGI1QdXldZZ+SNor06Nbu1wCBulQBg=
|
||||
github.com/apernet/quic-go v0.61.1-0.20260806010916-184d081eef3e/go.mod h1:x7qxEvX6MCVtDuBKHj3E+88+BtrbEMuAL5qGUKItjW8=
|
||||
github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw=
|
||||
github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
@@ -33,8 +35,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/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
||||
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
||||
github.com/klauspost/cpuid/v2 v2.4.1 h1:I9KxadAkvo+QjtcEE2f2tyfEvmIla/T0m+14PrZNl0U=
|
||||
github.com/klauspost/cpuid/v2 v2.4.1/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU=
|
||||
github.com/klauspost/cpuid/v2 v2.4.2 h1:xRw7IZEsAgVCp6trS/DgWbTAqpixhmywgVgBjYGg1bQ=
|
||||
github.com/klauspost/cpuid/v2 v2.4.2/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU=
|
||||
github.com/koron/go-ssdp v0.0.4 h1:1IDwrghSKYM7yLf7XCzbByg2sJ/JcNOZRXS2jczTwz0=
|
||||
github.com/koron/go-ssdp v0.0.4/go.mod h1:oDXq+E5IL5q0U8uSBcoAXzTzInwy5lEgC91HoKtbmZk=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
@@ -59,8 +61,8 @@ github.com/pion/stun/v3 v3.1.7 h1:uRXMTlGLf89WgItGNyZ6aR5jMTX0NBbybXADpQCzn+E=
|
||||
github.com/pion/stun/v3 v3.1.7/go.mod h1:Nq77RW4aRrSNrltf2ksUJLjxWeipj4lnlgdsYIxC8g8=
|
||||
github.com/pion/transport/v4 v4.1.0 h1:8S+nF2reM2cJuqC6g78OVy2BBgmbdns+acx3jA97BvQ=
|
||||
github.com/pion/transport/v4 v4.1.0/go.mod h1:06hFI+jCFcok2X2MekVufNZ/uzNZXivGBPfviSVcjgM=
|
||||
github.com/pires/go-proxyproto v0.15.0 h1:dTshmNbFm/D+0+sbrxUuddPOZ5Y0B7c5NhtsBkm6LqI=
|
||||
github.com/pires/go-proxyproto v0.15.0/go.mod h1:OXsCrKwrK2tXS9YrI5tkHx5xaQlO8FH3lFW76orFh24=
|
||||
github.com/pires/go-proxyproto v0.16.0 h1:9KYT/fM3wpjT8bdMEj/7FBcoiUG1KyzEjRYnPu+vz7A=
|
||||
github.com/pires/go-proxyproto v0.16.0/go.mod h1:n7yhg8yIfNl3KgYDCwBzmeUHNpKTVnUhcWpc7rV4RW4=
|
||||
github.com/quic-go/go-ossfuzz-seeds v0.1.0 h1:APacT+iIaNF6fd8AGEiN3bT/Jtkd2jz4v4TzM7MFjy0=
|
||||
github.com/quic-go/go-ossfuzz-seeds v0.1.0/go.mod h1:3IOHRbJIc+L6YKMwfDtJAM9Vj9k0YY4muhuyUYk5tbk=
|
||||
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||
@@ -107,8 +109,8 @@ golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.61.0 h1:RR+6j/BTBCrggOLugxa+66R8FvoN6MHlzDlZIbN9YFw=
|
||||
golang.org/x/net v0.61.0/go.mod h1:WgDCOTH8iwtB66T4/GfI94AHo/hhdb7Hah9NDseQfiI=
|
||||
golang.org/x/net v0.62.0 h1:18ELalfM5+XNRwzM2nYqXsZQ9ByS1uhj2O/U7+5jqRY=
|
||||
golang.org/x/net v0.62.0/go.mod h1:WgDCOTH8iwtB66T4/GfI94AHo/hhdb7Hah9NDseQfiI=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.24.0 h1:iqiDrGoDgyuvAKrhLQthUEzU/R378N78gXbBjxed7oA=
|
||||
@@ -148,10 +150,12 @@ golang.zx2c4.com/wireguard/windows v1.1.1 h1:8/H97U1v1PNDNcBsMZgU3KFuND9MQdTsU2N
|
||||
golang.zx2c4.com/wireguard/windows v1.1.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/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800 h1:qEHAMpSaUhtD0p3NbEEI83HwNGFxEwaSJ1G9PLnCBZE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.84.0 h1:soMyaPJ8pAak5PIQ0DGBUir0XRo2fRoMqhNWMLlLxO0=
|
||||
google.golang.org/grpc v1.84.0/go.mod h1:ljCht0DrxQrXBDRTZp52Qxh3Ffk8CdYm2sj4O2QN2C0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260817212433-ac3dfec99bb1 h1:utmqiUzZAEINbLmRyAt1QjVkMyLQ+lvIwCpLaB1rZfw=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260817212433-ac3dfec99bb1/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.85.0 h1:BPBCfuVRQ1qvBhKDOq9uvQllMvO59rKsYhXpp7Dd8vw=
|
||||
google.golang.org/grpc v1.85.0/go.mod h1:0rQrOD58yjal/0EaVbGBANindixsCZNQPDV3VKtaKuQ=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 h1:rgSNvqscFZ1JgV/4wH5GOsZFSFkR2Eua9As3KIr2LlM=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2/go.mod h1:iMEtFwDlAhjDU9L5mY6U1XLwlIId/G3h+QcBHDIvrJ8=
|
||||
google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
|
||||
google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
+35
-3
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/geodata"
|
||||
"github.com/xtls/xray-core/common/log"
|
||||
xnet "github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/protocol"
|
||||
"github.com/xtls/xray-core/proxy/freedom"
|
||||
@@ -48,6 +49,8 @@ type FreedomFinalRuleConfig struct {
|
||||
Network *NetworkList `json:"network"`
|
||||
Port *PortList `json:"port"`
|
||||
IP *StringList `json:"ip"`
|
||||
Delay *Int32Range `json:"delay"`
|
||||
LogLevel string `json:"logLevel"`
|
||||
BlockDelay *Int32Range `json:"blockDelay"`
|
||||
}
|
||||
|
||||
@@ -91,6 +94,7 @@ func (c *FreedomConfig) Build() (proto.Message, error) {
|
||||
}
|
||||
|
||||
if c.Fragment != nil {
|
||||
errors.PrintDeprecatedFeatureWarning(`"fragment" in Freedom`, `"fragment" in "streamSettings.finalmask.tcp"`)
|
||||
config.Fragment = new(freedom.Fragment)
|
||||
|
||||
switch strings.ToLower(c.Fragment.Packets) {
|
||||
@@ -147,6 +151,7 @@ func (c *FreedomConfig) Build() (proto.Message, error) {
|
||||
}
|
||||
|
||||
if c.Noises != nil {
|
||||
errors.PrintDeprecatedFeatureWarning(`"noises" in Freedom`, `"noise" in "streamSettings.finalmask.udp"`)
|
||||
for _, n := range c.Noises {
|
||||
NConfig, err := ParseNoise(n)
|
||||
if err != nil {
|
||||
@@ -278,10 +283,37 @@ func (c *FreedomFinalRuleConfig) Build() (*freedom.FinalRuleConfig, error) {
|
||||
}
|
||||
|
||||
if c.BlockDelay != nil {
|
||||
rule.BlockDelay = &freedom.Range{
|
||||
Min: uint64(c.BlockDelay.From),
|
||||
Max: uint64(c.BlockDelay.To),
|
||||
errors.PrintDeprecatedFeatureWarning("finalRules.blockDelay", "finalRules.delay")
|
||||
c.Delay = c.BlockDelay
|
||||
}
|
||||
|
||||
if rule.Action != freedom.RuleAction_Block {
|
||||
if c.Delay != nil || c.LogLevel != "" {
|
||||
return nil, errors.New("delay and logLevel are only supported for block finalRules")
|
||||
}
|
||||
return rule, nil
|
||||
}
|
||||
|
||||
if c.Delay != nil {
|
||||
rule.Delay = &freedom.Range{
|
||||
Min: uint64(c.Delay.From),
|
||||
Max: uint64(c.Delay.To),
|
||||
}
|
||||
}
|
||||
|
||||
switch strings.ToLower(c.LogLevel) {
|
||||
case "", "info":
|
||||
rule.LogLevel = log.Severity_Info.Enum()
|
||||
case "none":
|
||||
rule.LogLevel = log.Severity_Unknown.Enum()
|
||||
case "debug":
|
||||
rule.LogLevel = log.Severity_Debug.Enum()
|
||||
case "warning":
|
||||
rule.LogLevel = log.Severity_Warning.Enum()
|
||||
case "error":
|
||||
rule.LogLevel = log.Severity_Error.Enum()
|
||||
default:
|
||||
return nil, errors.New("unknown finalRules.logLevel: ", c.LogLevel)
|
||||
}
|
||||
|
||||
return rule, nil
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/xtls/xray-core/common/geodata"
|
||||
"github.com/xtls/xray-core/common/log"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/protocol"
|
||||
. "github.com/xtls/xray-core/infra/conf"
|
||||
@@ -46,7 +47,8 @@ func TestFreedomConfig(t *testing.T) {
|
||||
"network": "tcp,udp",
|
||||
"port": "53,443",
|
||||
"ip": ["10.0.0.0/8", "2001:db8::/32"],
|
||||
"blockDelay": "30-60"
|
||||
"delay": "30-60",
|
||||
"logLevel": "debug"
|
||||
}, {
|
||||
"action": "allow",
|
||||
"network": ["udp"]
|
||||
@@ -86,10 +88,8 @@ func TestFreedomConfig(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
BlockDelay: &freedom.Range{
|
||||
Min: 30,
|
||||
Max: 60,
|
||||
},
|
||||
Delay: &freedom.Range{Min: 30, Max: 60},
|
||||
LogLevel: log.Severity_Debug.Enum(),
|
||||
},
|
||||
{
|
||||
Action: freedom.RuleAction_Allow,
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"github.com/robfig/cron/v3"
|
||||
"github.com/xtls/xray-core/app/geodata"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/platform/filesystem"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
@@ -19,9 +18,6 @@ func (c *GeodataAssetConfig) Build() (*geodata.Asset, error) {
|
||||
if err := validateHTTPS(c.URL); err != nil {
|
||||
return nil, errors.New("invalid geodata asset url: ", c.URL).Base(err)
|
||||
}
|
||||
if _, err := filesystem.StatAsset(c.File); err != nil {
|
||||
return nil, errors.New("invalid geodata asset file: ", c.File).Base(err)
|
||||
}
|
||||
return &geodata.Asset{
|
||||
Url: c.URL,
|
||||
File: c.File,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package conf_test
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/xtls/xray-core/app/geodata"
|
||||
@@ -9,8 +8,6 @@ import (
|
||||
)
|
||||
|
||||
func TestGeodataConfig(t *testing.T) {
|
||||
t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
|
||||
|
||||
creator := func() Buildable {
|
||||
return new(GeodataConfig)
|
||||
}
|
||||
@@ -38,27 +35,7 @@ func TestGeodataConfig(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestGeodataAssetConfig(t *testing.T) {
|
||||
t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
|
||||
|
||||
if _, err := (&GeodataAssetConfig{
|
||||
URL: "https://example.com/geoip.dat",
|
||||
File: "geoip.dat",
|
||||
}).Build(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := (&GeodataAssetConfig{
|
||||
URL: "https://example.com/geoip.dat",
|
||||
File: "missing.dat",
|
||||
}).Build(); err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeodataAssetConfigInvalidURL(t *testing.T) {
|
||||
t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
|
||||
|
||||
for _, rawURL := range []string{
|
||||
"",
|
||||
"http://example.com/geoip.dat",
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ func DefaultLogConfig() *log.Config {
|
||||
type LogConfig struct {
|
||||
AccessLog string `json:"access"`
|
||||
ErrorLog string `json:"error"`
|
||||
LogLevel string `json:"loglevel"`
|
||||
LogLevel string `json:"logLevel"`
|
||||
DNSLog bool `json:"dnsLog"`
|
||||
MaskAddress string `json:"maskAddress"`
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ func TestLoaderError(t *testing.T) {
|
||||
"log": {
|
||||
// abcd
|
||||
0,
|
||||
"loglevel": "info"
|
||||
"logLevel": "info"
|
||||
}
|
||||
}`,
|
||||
Output: "line 4 char 6",
|
||||
@@ -27,7 +27,7 @@ func TestLoaderError(t *testing.T) {
|
||||
Input: `{
|
||||
"log": {
|
||||
// abcd
|
||||
"loglevel": "info",
|
||||
"logLevel": "info",
|
||||
}
|
||||
}`,
|
||||
Output: "line 5 char 5",
|
||||
|
||||
@@ -293,6 +293,7 @@ type SplitHTTPConfig struct {
|
||||
ServerMaxHeaderBytes int32 `json:"serverMaxHeaderBytes"`
|
||||
Xmux XmuxConfig `json:"xmux"`
|
||||
DownloadSettings *StreamConfig `json:"downloadSettings"`
|
||||
H2Params json.RawMessage `json:"h2Params"`
|
||||
Extra json.RawMessage `json:"extra"`
|
||||
}
|
||||
|
||||
@@ -495,6 +496,7 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
|
||||
ServerMaxHeaderBytes: c.ServerMaxHeaderBytes,
|
||||
SessionIDTable: c.SessionIDTable,
|
||||
SessionIDLength: newRangeConfig(c.SessionIDLength),
|
||||
H2Params: c.H2Params,
|
||||
Xmux: &splithttp.XmuxConfig{
|
||||
MaxConcurrency: newRangeConfig(c.Xmux.MaxConcurrency),
|
||||
MaxConnections: newRangeConfig(c.Xmux.MaxConnections),
|
||||
@@ -997,12 +999,20 @@ type XDriveConfig struct {
|
||||
EagerWindowMs uint32 `json:"eagerWindowMs"`
|
||||
HoleTimeoutMs uint32 `json:"holeTimeoutMs"`
|
||||
Template json.RawMessage `json:"template"`
|
||||
S3 *XDriveS3Config `json:"s3"`
|
||||
Xmux XmuxConfig `json:"xmux"`
|
||||
|
||||
// set by StreamConfig when "alpn" is ["h3"]
|
||||
h3 bool
|
||||
}
|
||||
|
||||
type XDriveS3Config struct {
|
||||
Endpoint string `json:"endpoint"`
|
||||
Region string `json:"region"`
|
||||
Bucket string `json:"bucket"`
|
||||
PathStyle *bool `json:"pathStyle"`
|
||||
}
|
||||
|
||||
// Build implements Buildable.
|
||||
func (c *XDriveConfig) Build() (proto.Message, error) {
|
||||
switch c.Service {
|
||||
@@ -1019,8 +1029,12 @@ func (c *XDriveConfig) Build() (proto.Message, error) {
|
||||
if len(c.Template) == 0 {
|
||||
return nil, errors.New(`service "template" needs a "template" object`)
|
||||
}
|
||||
case "s3":
|
||||
if c.S3 == nil {
|
||||
return nil, errors.New(`service "s3" needs an "s3" object`)
|
||||
}
|
||||
default:
|
||||
return nil, errors.New(`unknown "service": "`, c.Service, `", use "google-drive", "template" or "local"`)
|
||||
return nil, errors.New(`unknown "service": "`, c.Service, `", use "google-drive", "template", "s3" or "local"`)
|
||||
}
|
||||
if c.Xmux.MaxConnections.To > 0 && c.Xmux.MaxConcurrency.To > 0 {
|
||||
return nil, errors.New("maxConnections cannot be specified together with maxConcurrency")
|
||||
@@ -1059,5 +1073,16 @@ func (c *XDriveConfig) Build() (proto.Message, error) {
|
||||
HKeepAlivePeriod: c.Xmux.HKeepAlivePeriod,
|
||||
},
|
||||
}
|
||||
if c.Service == "s3" {
|
||||
config.S3 = &xdrive.S3Config{
|
||||
Endpoint: c.S3.Endpoint,
|
||||
Region: c.S3.Region,
|
||||
Bucket: c.S3.Bucket,
|
||||
PathStyle: c.S3.PathStyle == nil || *c.S3.PathStyle,
|
||||
}
|
||||
if err := xdrive.ValidateS3Config(config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
@@ -324,7 +324,7 @@ func TestXDriveRejectsUnknownService(t *testing.T) {
|
||||
if err := json.Unmarshal([]byte(`{"remoteFolder": "/tmp/xdrive", "service": "Dropbox"}`), config); err != nil {
|
||||
t.Fatalf("Unmarshal: %v", err)
|
||||
}
|
||||
if _, err := config.Build(); err == nil || !strings.Contains(err.Error(), `use "google-drive", "template" or "local"`) {
|
||||
if _, err := config.Build(); err == nil || !strings.Contains(err.Error(), `use "google-drive", "template", "s3" or "local"`) {
|
||||
t.Fatalf("expected an unknown service error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
+10
-15
@@ -6,9 +6,7 @@ import (
|
||||
"math/big"
|
||||
"net"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/proxy/tun"
|
||||
@@ -39,25 +37,22 @@ func (v *TunConfig) Build() (proto.Message, error) {
|
||||
AutoSystemRoutingTable: v.AutoSystemRoutingTable,
|
||||
AutoSystemDnsToGateway: v.AutoSystemDnsToGateway,
|
||||
}
|
||||
for _, leak := range v.AutoSystemWfpBlockLeak {
|
||||
switch leak := strings.ToLower(leak); leak {
|
||||
case "dns", "misconfigtun":
|
||||
config.AutoSystemWfpBlockLeak = append(config.AutoSystemWfpBlockLeak, leak)
|
||||
default:
|
||||
return nil, errors.New("unknown autoSystemWfpBlockLeak value: ", leak)
|
||||
}
|
||||
leaks, err := tun.ExpandWfpBlockLeak(v.AutoSystemWfpBlockLeak)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.AutoSystemWfpBlockLeak = leaks
|
||||
// Each option needs other settings on the system it takes effect on: the
|
||||
// filters go along with the routes of autoSystemRoutingTable, "dns" lets
|
||||
// DNS through the TUN only, and autoSystemDnsToGateway points the system
|
||||
// DNS at the gateway.
|
||||
// filters go along with the routes of autoSystemRoutingTable, keeping the
|
||||
// system's DNS inside the TUN needs DNS servers on it, and
|
||||
// autoSystemDnsToGateway points the system DNS at the gateway.
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
if len(config.AutoSystemWfpBlockLeak) > 0 && len(v.AutoSystemRoutingTable) == 0 {
|
||||
if len(leaks) > 0 && len(v.AutoSystemRoutingTable) == 0 {
|
||||
return nil, errors.New("autoSystemWfpBlockLeak needs autoSystemRoutingTable to be set")
|
||||
}
|
||||
if slices.Contains(config.AutoSystemWfpBlockLeak, "dns") && len(v.DNS) == 0 {
|
||||
return nil, errors.New(`autoSystemWfpBlockLeak "dns" needs dns to be set`)
|
||||
if tun.BlocksSystemDNS(leaks) && len(v.DNS) == 0 {
|
||||
return nil, errors.New(`autoSystemWfpBlockLeak "all", "allexceptlan", "dns", "windnsclient", or TCP or UDP port 53, needs dns to be set`)
|
||||
}
|
||||
case "linux", "freebsd":
|
||||
if v.AutoSystemDnsToGateway && len(v.Gateway) == 0 {
|
||||
|
||||
+60
-6
@@ -29,12 +29,37 @@ func TestTunConfigAutoSystem(t *testing.T) {
|
||||
{
|
||||
Input: `{"name": "xray0", "dns": ["1.1.1.1"], "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["dns", "misconfigtun"]}`,
|
||||
Parser: loadJSON(creator),
|
||||
Output: &tun.Config{Name: "xray0", Desc: "Wintun", MTU: 1500, DNS: []string{"1.1.1.1"}, AutoSystemRoutingTable: []string{"0.0.0.0/0"}, AutoOutboundsInterface: "auto", AutoSystemWfpBlockLeak: []string{"dns", "misconfigtun"}},
|
||||
Output: &tun.Config{Name: "xray0", Desc: "Wintun", MTU: 1500, DNS: []string{"1.1.1.1"}, AutoSystemRoutingTable: []string{"0.0.0.0/0"}, AutoOutboundsInterface: "auto", AutoSystemWfpBlockLeak: []string{"53", "853", "windnsclient", "misconfigtun"}},
|
||||
},
|
||||
{
|
||||
Input: `{"name": "xray0", "dns": ["1.1.1.1"], "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["DNS"]}`,
|
||||
Parser: loadJSON(creator),
|
||||
Output: &tun.Config{Name: "xray0", Desc: "Wintun", MTU: 1500, DNS: []string{"1.1.1.1"}, AutoSystemRoutingTable: []string{"0.0.0.0/0"}, AutoOutboundsInterface: "auto", AutoSystemWfpBlockLeak: []string{"dns"}},
|
||||
Output: &tun.Config{Name: "xray0", Desc: "Wintun", MTU: 1500, DNS: []string{"1.1.1.1"}, AutoSystemRoutingTable: []string{"0.0.0.0/0"}, AutoOutboundsInterface: "auto", AutoSystemWfpBlockLeak: []string{"53", "853", "windnsclient"}},
|
||||
},
|
||||
{
|
||||
Input: `{"name": "xray0", "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["TCP/853", "udp/853", "tcp/0853"]}`,
|
||||
Parser: loadJSON(creator),
|
||||
Output: &tun.Config{Name: "xray0", Desc: "Wintun", MTU: 1500, AutoSystemRoutingTable: []string{"0.0.0.0/0"}, AutoOutboundsInterface: "auto", AutoSystemWfpBlockLeak: []string{"tcp/853", "udp/853"}},
|
||||
},
|
||||
{
|
||||
Input: `{"name": "xray0", "dns": ["1.1.1.1"], "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["All", "UDP", "tcp/8000-9000", "icmp"]}`,
|
||||
Parser: loadJSON(creator),
|
||||
Output: &tun.Config{Name: "xray0", Desc: "Wintun", MTU: 1500, DNS: []string{"1.1.1.1"}, AutoSystemRoutingTable: []string{"0.0.0.0/0"}, AutoOutboundsInterface: "auto", AutoSystemWfpBlockLeak: []string{"all", "udp", "tcp/8000-9000", "icmp"}},
|
||||
},
|
||||
{
|
||||
Input: `{"name": "xray0", "dns": ["1.1.1.1"], "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["AllExceptLAN"]}`,
|
||||
Parser: loadJSON(creator),
|
||||
Output: &tun.Config{Name: "xray0", Desc: "Wintun", MTU: 1500, DNS: []string{"1.1.1.1"}, AutoSystemRoutingTable: []string{"0.0.0.0/0"}, AutoOutboundsInterface: "auto", AutoSystemWfpBlockLeak: []string{"allexceptlan", "53", "853", "windnsclient"}},
|
||||
},
|
||||
{
|
||||
Input: `{"name": "xray0", "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["1.1.1.1/24:TCP/853", "[2001:DB8::/32]:udp/10-20", "[::/0]:ICMP", "853"]}`,
|
||||
Parser: loadJSON(creator),
|
||||
Output: &tun.Config{Name: "xray0", Desc: "Wintun", MTU: 1500, AutoSystemRoutingTable: []string{"0.0.0.0/0"}, AutoOutboundsInterface: "auto", AutoSystemWfpBlockLeak: []string{"1.1.1.0/24:tcp/853", "[2001:db8::/32]:udp/10-20", "[::/0]:icmp", "853"}},
|
||||
},
|
||||
{
|
||||
Input: `{"name": "xray0", "dns": ["1.1.1.1"], "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["0.0.0.0/0:dns", "[::/0]:All", "53"]}`,
|
||||
Parser: loadJSON(creator),
|
||||
Output: &tun.Config{Name: "xray0", Desc: "Wintun", MTU: 1500, DNS: []string{"1.1.1.1"}, AutoSystemRoutingTable: []string{"0.0.0.0/0"}, AutoOutboundsInterface: "auto", AutoSystemWfpBlockLeak: []string{"0.0.0.0/0:53", "0.0.0.0/0:853", "0.0.0.0/0:windnsclient", "[::/0]:all", "53"}},
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -49,6 +74,24 @@ func TestTunConfigAutoSystemNeeds(t *testing.T) {
|
||||
{`{"name": "xray0", "autoSystemWfpBlockLeak": ["misconfigtun"]}`, []string{"windows"}},
|
||||
{`{"name": "xray0", "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["misconfigtun"]}`, nil},
|
||||
{`{"name": "xray0", "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["dns"]}`, []string{"windows"}},
|
||||
{`{"name": "xray0", "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["udp/53"]}`, []string{"windows"}},
|
||||
{`{"name": "xray0", "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["windnsclient"]}`, []string{"windows"}},
|
||||
{`{"name": "xray0", "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["tcp/853", "udp/853"]}`, nil},
|
||||
{`{"name": "xray0", "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["all"]}`, []string{"windows"}},
|
||||
{`{"name": "xray0", "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["udp"]}`, []string{"windows"}},
|
||||
{`{"name": "xray0", "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["tcp/50-60"]}`, []string{"windows"}},
|
||||
{`{"name": "xray0", "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["tcp/54-60", "icmp"]}`, nil},
|
||||
{`{"name": "xray0", "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["53"]}`, []string{"windows"}},
|
||||
{`{"name": "xray0", "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["50-60"]}`, []string{"windows"}},
|
||||
{`{"name": "xray0", "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["[::/0]:udp/53"]}`, []string{"windows"}},
|
||||
{`{"name": "xray0", "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["1.1.1.1/32:dns"]}`, []string{"windows"}},
|
||||
{`{"name": "xray0", "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["[2001:db8::/32]:all"]}`, []string{"windows"}},
|
||||
{`{"name": "xray0", "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["0.0.0.0/0:windnsclient"]}`, []string{"windows"}},
|
||||
{`{"name": "xray0", "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["0.0.0.0/0:tcp/853", "1.1.1.0/24:icmp", "[::/0]:misconfigtun", "54-60"]}`, nil},
|
||||
{`{"name": "xray0", "autoSystemWfpBlockLeak": ["[::/0]:misconfigtun"]}`, []string{"windows"}},
|
||||
{`{"name": "xray0", "autoSystemWfpBlockLeak": ["all"]}`, []string{"windows"}},
|
||||
{`{"name": "xray0", "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["allexceptlan"]}`, []string{"windows"}},
|
||||
{`{"name": "xray0", "dns": ["1.1.1.1"], "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["allexceptlan"]}`, nil},
|
||||
{`{"name": "xray0", "autoSystemDnsToGateway": true}`, []string{"linux", "freebsd"}},
|
||||
} {
|
||||
config := new(TunConfig)
|
||||
@@ -62,11 +105,22 @@ func TestTunConfigAutoSystemNeeds(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestTunConfigAutoSystemWfpBlockLeakUnknown(t *testing.T) {
|
||||
config := new(TunConfig)
|
||||
if err := json.Unmarshal([]byte(`{"name": "xray0", "autoSystemWfpBlockLeak": ["dns", "ip"]}`), config); err != nil {
|
||||
// With the settings the values need on Windows, so that only an unknown
|
||||
// one fails.
|
||||
build := func(value string) error {
|
||||
config := new(TunConfig)
|
||||
if err := json.Unmarshal([]byte(`{"name": "xray0", "dns": ["1.1.1.1"], "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["dns", "`+value+`"]}`), config); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err := config.Build()
|
||||
return err
|
||||
}
|
||||
if err := build("1.1.1.0/24:53"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := config.Build(); err == nil {
|
||||
t.Error("an unknown autoSystemWfpBlockLeak value was accepted")
|
||||
for _, value := range []string{"ip", "tcp/0", "tcp/0-10", "icmp/1", "udp/20-10", "0", "20-10", "53/tcp", "tcpv4", "ipv4:udp", "1.1.1.1:53", "2001:db8::/32:53", "[1.1.1.0/24]:53", "[::ffff:1.1.1.0/120]:53", "1.1.1.0/24"} {
|
||||
if build(value) == nil {
|
||||
t.Errorf("autoSystemWfpBlockLeak value %q was accepted", value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+6
-3
@@ -237,10 +237,11 @@ type OutboundDetourConfig struct {
|
||||
|
||||
func requiresTransportSecurity(address *Address) bool {
|
||||
if address == nil || address.Address == nil {
|
||||
return false
|
||||
return true
|
||||
}
|
||||
if address.Family().IsIP() {
|
||||
return !geodata.GetPrivateIPMatcher().Match(address.IP())
|
||||
ip := address.IP()
|
||||
return !ip.IsPrivate() && !ip.IsLoopback() && !ip.IsLinkLocalUnicast()
|
||||
}
|
||||
domain := strings.TrimSuffix(strings.ToLower(address.Domain()), ".")
|
||||
return !geodata.GetPrivateDomainMatcher().MatchAny(domain)
|
||||
@@ -608,7 +609,6 @@ func (c *Config) Build() (*core.Config, error) {
|
||||
config := &core.Config{
|
||||
App: []*serial.TypedMessage{
|
||||
serial.ToTypedMessage(&dispatcher.Config{}),
|
||||
serial.ToTypedMessage(&proxyman.InboundConfig{}),
|
||||
serial.ToTypedMessage(&proxyman.OutboundConfig{}),
|
||||
},
|
||||
}
|
||||
@@ -718,6 +718,9 @@ func (c *Config) Build() (*core.Config, error) {
|
||||
config.App = append(config.App, serial.ToTypedMessage(r))
|
||||
}
|
||||
|
||||
// Initialize the inbound module last to ensure inbound connections are handled correctly.
|
||||
config.App = append(config.App, serial.ToTypedMessage(&proxyman.InboundConfig{}))
|
||||
|
||||
var inbounds []InboundDetourConfig
|
||||
|
||||
if len(c.InboundConfigs) > 0 {
|
||||
|
||||
@@ -42,7 +42,7 @@ func TestXrayConfig(t *testing.T) {
|
||||
Input: `{
|
||||
"log": {
|
||||
"access": "/var/log/xray/access.log",
|
||||
"loglevel": "error",
|
||||
"logLevel": "error",
|
||||
"error": "/var/log/xray/error.log"
|
||||
},
|
||||
"inbounds": [{
|
||||
@@ -90,7 +90,6 @@ func TestXrayConfig(t *testing.T) {
|
||||
AccessLogPath: "/var/log/xray/access.log",
|
||||
}),
|
||||
serial.ToTypedMessage(&dispatcher.Config{}),
|
||||
serial.ToTypedMessage(&proxyman.InboundConfig{}),
|
||||
serial.ToTypedMessage(&proxyman.OutboundConfig{}),
|
||||
serial.ToTypedMessage(&router.Config{
|
||||
DomainStrategy: router.Config_AsIs,
|
||||
@@ -111,6 +110,7 @@ func TestXrayConfig(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}),
|
||||
serial.ToTypedMessage(&proxyman.InboundConfig{}),
|
||||
},
|
||||
Inbound: []*core.InboundHandlerConfig{
|
||||
{
|
||||
|
||||
+174
-197
@@ -1,149 +1,87 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"bytes"
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"io"
|
||||
"net/http"
|
||||
"io/fs"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/bufbuild/protocompile"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/reflect/protodesc"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
"google.golang.org/protobuf/types/descriptorpb"
|
||||
"google.golang.org/protobuf/types/pluginpb"
|
||||
)
|
||||
|
||||
var directory = flag.String("pwd", "", "Working directory of Xray vprotogen.")
|
||||
var (
|
||||
directory = flag.String("pwd", "", "Working directory of Xray vprotogen.")
|
||||
mode = flag.String("mode", "generate", "Execution mode. 'generate' (default) compiles and saves changes. 'check' reports out-of-date files and exits with non-zero.")
|
||||
)
|
||||
|
||||
// envFile returns the name of the Go environment configuration file.
|
||||
// Copy from https://github.com/golang/go/blob/c4f2a9788a7be04daf931ac54382fbe2cb754938/src/cmd/go/internal/cfg/cfg.go#L150-L166
|
||||
func envFile() (string, error) {
|
||||
if file := os.Getenv("GOENV"); file != "" {
|
||||
if file == "off" {
|
||||
return "", errors.New("GOENV=off")
|
||||
// topoSort sorts file descriptors so each dependency appears before files importing it.
|
||||
func topoSort(files []protoreflect.FileDescriptor) []*descriptorpb.FileDescriptorProto {
|
||||
fileMap := make(map[string]protoreflect.FileDescriptor)
|
||||
for _, f := range files {
|
||||
fileMap[f.Path()] = f
|
||||
}
|
||||
|
||||
visited := make(map[string]bool)
|
||||
var sorted []*descriptorpb.FileDescriptorProto
|
||||
|
||||
var visit func(fd protoreflect.FileDescriptor)
|
||||
visit = func(fd protoreflect.FileDescriptor) {
|
||||
if visited[fd.Path()] {
|
||||
return
|
||||
}
|
||||
return file, nil
|
||||
}
|
||||
dir, err := os.UserConfigDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if dir == "" {
|
||||
return "", errors.New("missing user-config dir")
|
||||
}
|
||||
return filepath.Join(dir, "go", "env"), nil
|
||||
}
|
||||
visited[fd.Path()] = true
|
||||
|
||||
// GetRuntimeEnv returns the value of runtime environment variable,
|
||||
// that is set by running following command: `go env -w key=value`.
|
||||
func GetRuntimeEnv(key string) (string, error) {
|
||||
file, err := envFile()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if file == "" {
|
||||
return "", errors.New("missing runtime env file")
|
||||
}
|
||||
var data []byte
|
||||
var runtimeEnv string
|
||||
data, readErr := os.ReadFile(file)
|
||||
if readErr != nil {
|
||||
return "", readErr
|
||||
}
|
||||
envStrings := strings.Split(string(data), "\n")
|
||||
for _, envItem := range envStrings {
|
||||
envItem = strings.TrimSuffix(envItem, "\r")
|
||||
envKeyValue := strings.Split(envItem, "=")
|
||||
if strings.EqualFold(strings.TrimSpace(envKeyValue[0]), key) {
|
||||
runtimeEnv = strings.TrimSpace(envKeyValue[1])
|
||||
imports := fd.Imports()
|
||||
for i := 0; i < imports.Len(); i++ {
|
||||
if dep, ok := fileMap[imports.Get(i).Path()]; ok {
|
||||
visit(dep)
|
||||
}
|
||||
}
|
||||
sorted = append(sorted, protodesc.ToFileDescriptorProto(fd))
|
||||
}
|
||||
return runtimeEnv, nil
|
||||
|
||||
for _, f := range files {
|
||||
visit(f)
|
||||
}
|
||||
return sorted
|
||||
}
|
||||
|
||||
// GetGOBIN returns GOBIN environment variable as a string. It will NOT be empty.
|
||||
func GetGOBIN() string {
|
||||
// The one set by user explicitly by `export GOBIN=/path` or `env GOBIN=/path command`
|
||||
GOBIN := os.Getenv("GOBIN")
|
||||
if GOBIN == "" {
|
||||
var err error
|
||||
// The one set by user by running `go env -w GOBIN=/path`
|
||||
GOBIN, err = GetRuntimeEnv("GOBIN")
|
||||
if err != nil {
|
||||
// The default one that Golang uses
|
||||
return filepath.Join(build.Default.GOPATH, "bin")
|
||||
}
|
||||
if GOBIN == "" {
|
||||
return filepath.Join(build.Default.GOPATH, "bin")
|
||||
}
|
||||
return GOBIN
|
||||
}
|
||||
return GOBIN
|
||||
}
|
||||
|
||||
func whichProtoc(suffix, targetedVersion string) (string, error) {
|
||||
protoc := "protoc" + suffix
|
||||
|
||||
path, err := exec.LookPath(protoc)
|
||||
func runPlugin(pluginName, pwd string, req *pluginpb.CodeGeneratorRequest) (*pluginpb.CodeGeneratorResponse, error) {
|
||||
reqBytes, err := proto.Marshal(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf(`
|
||||
Command "%s" not found.
|
||||
Make sure that %s is in your system path or current path.
|
||||
Download %s v%s or later from https://github.com/protocolbuffers/protobuf/releases
|
||||
`, protoc, protoc, protoc, targetedVersion)
|
||||
return nil, err
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
|
||||
func getProjectProtocVersion(url string) (string, error) {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return "", errors.New("can not get the version of protobuf used in xray project")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", errors.New("can not read from body")
|
||||
}
|
||||
versionRegexp := regexp.MustCompile(`\/\/\s*protoc\s*v\d+\.(\d+\.\d+)`)
|
||||
matched := versionRegexp.FindStringSubmatch(string(body))
|
||||
return matched[1], nil
|
||||
}
|
||||
cmd := exec.Command("go", "tool", pluginName)
|
||||
cmd.Dir = pwd
|
||||
cmd.Stdin = bytes.NewReader(reqBytes)
|
||||
cmd.Env = os.Environ()
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
func getInstalledProtocVersion(protocPath string) (string, error) {
|
||||
cmd := exec.Command(protocPath, "--version")
|
||||
cmd.Env = append(cmd.Env, os.Environ()...)
|
||||
output, cmdErr := cmd.CombinedOutput()
|
||||
if cmdErr != nil {
|
||||
return "", cmdErr
|
||||
if err := cmd.Run(); err != nil {
|
||||
return nil, fmt.Errorf("%v: %s", err, stderr.String())
|
||||
}
|
||||
versionRegexp := regexp.MustCompile(`protoc\s*(\d+\.\d+)`)
|
||||
matched := versionRegexp.FindStringSubmatch(string(output))
|
||||
return matched[1], nil
|
||||
}
|
||||
|
||||
func parseVersion(s string, width int) int64 {
|
||||
strList := strings.Split(s, ".")
|
||||
format := fmt.Sprintf("%%s%%0%ds", width)
|
||||
v := ""
|
||||
for _, value := range strList {
|
||||
v = fmt.Sprintf(format, v, value)
|
||||
var resp pluginpb.CodeGeneratorResponse
|
||||
if err := proto.Unmarshal(stdout.Bytes(), &resp); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
|
||||
}
|
||||
var result int64
|
||||
var err error
|
||||
if result, err = strconv.ParseInt(v, 10, 64); err != nil {
|
||||
return 0
|
||||
if resp.GetError() != "" {
|
||||
return nil, fmt.Errorf("%s", resp.GetError())
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func needToUpdate(targetedVersion, installedVersion string) bool {
|
||||
vt := parseVersion(targetedVersion, 4)
|
||||
vi := parseVersion(installedVersion, 4)
|
||||
return vt > vi
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -154,106 +92,145 @@ func main() {
|
||||
flag.Parse()
|
||||
|
||||
if !filepath.IsAbs(*directory) {
|
||||
pwd, wdErr := os.Getwd()
|
||||
if wdErr != nil {
|
||||
pwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
fmt.Println("Can not get current working directory.")
|
||||
os.Exit(1)
|
||||
}
|
||||
*directory = filepath.Join(pwd, *directory)
|
||||
}
|
||||
|
||||
pwd := *directory
|
||||
GOBIN := GetGOBIN()
|
||||
binPath := os.Getenv("PATH")
|
||||
pathSlice := []string{pwd, GOBIN, binPath}
|
||||
binPath = strings.Join(pathSlice, string(os.PathListSeparator))
|
||||
os.Setenv("PATH", binPath)
|
||||
|
||||
suffix := ""
|
||||
if runtime.GOOS == "windows" {
|
||||
suffix = ".exe"
|
||||
}
|
||||
|
||||
/*
|
||||
targetedVersion, err := getProjectProtocVersion("https://raw.githubusercontent.com/XTLS/Xray-core/HEAD/core/config.pb.go")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
*/
|
||||
targetedVersion := ""
|
||||
|
||||
protoc, err := whichProtoc(suffix, targetedVersion)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
isCheck := false
|
||||
switch *mode {
|
||||
case "generate", "":
|
||||
isCheck = false
|
||||
case "check":
|
||||
isCheck = true
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "Unrecognized 'mode': %s. Use 'generate' or 'check'.\n", *mode)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
installedVersion, err := getInstalledProtocVersion(protoc)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
if isCheck {
|
||||
fmt.Println("Checking for out-of-date Protobuf generated files...")
|
||||
}
|
||||
|
||||
if needToUpdate(targetedVersion, installedVersion) {
|
||||
fmt.Printf(`
|
||||
You are using an old protobuf version, please update to v%s or later.
|
||||
Download it from https://github.com/protocolbuffers/protobuf/releases
|
||||
|
||||
* Protobuf version used in xray project: v%s
|
||||
* Protobuf version you have installed: v%s
|
||||
|
||||
`, targetedVersion, targetedVersion, installedVersion)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
protoFilesMap := make(map[string][]string)
|
||||
walkErr := filepath.Walk(pwd, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
var protoFiles []string
|
||||
var existingPbFiles []string
|
||||
err := filepath.WalkDir(pwd, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil || d.IsDir() {
|
||||
return err
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
if strings.HasSuffix(d.Name(), ".proto") {
|
||||
rel, _ := filepath.Rel(pwd, path)
|
||||
protoFiles = append(protoFiles, filepath.ToSlash(rel))
|
||||
}
|
||||
|
||||
dir := filepath.Dir(path)
|
||||
filename := filepath.Base(path)
|
||||
if strings.HasSuffix(filename, ".proto") {
|
||||
path = path[len(pwd)+1:]
|
||||
protoFilesMap[dir] = append(protoFilesMap[dir], path)
|
||||
if strings.HasSuffix(d.Name(), ".pb.go") {
|
||||
existingPbFiles = append(existingPbFiles, path)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if walkErr != nil {
|
||||
fmt.Println(walkErr)
|
||||
if err != nil {
|
||||
fmt.Println("Walk error:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
for _, files := range protoFilesMap {
|
||||
for _, relProtoFile := range files {
|
||||
args := []string{
|
||||
"--go_out", pwd,
|
||||
"--go_opt", "paths=source_relative",
|
||||
"--go-grpc_out", pwd,
|
||||
"--go-grpc_opt", "paths=source_relative",
|
||||
"--plugin", "protoc-gen-go=" + filepath.Join(GOBIN, "protoc-gen-go"+suffix),
|
||||
"--plugin", "protoc-gen-go-grpc=" + filepath.Join(GOBIN, "protoc-gen-go-grpc"+suffix),
|
||||
compiler := protocompile.Compiler{
|
||||
Resolver: protocompile.WithStandardImports(&protocompile.SourceResolver{
|
||||
ImportPaths: []string{pwd},
|
||||
}),
|
||||
SourceInfoMode: protocompile.SourceInfoStandard,
|
||||
}
|
||||
|
||||
linkedFiles, err := compiler.Compile(context.Background(), protoFiles...)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Protobuf compilation failed:\n%v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var fdList []protoreflect.FileDescriptor
|
||||
for _, f := range linkedFiles {
|
||||
fdList = append(fdList, f)
|
||||
}
|
||||
|
||||
req := &pluginpb.CodeGeneratorRequest{
|
||||
FileToGenerate: protoFiles,
|
||||
Parameter: new("paths=source_relative"),
|
||||
ProtoFile: topoSort(fdList),
|
||||
}
|
||||
|
||||
var diffCount int
|
||||
generatedFiles := make(map[string]struct{})
|
||||
for _, plugin := range []string{"protoc-gen-go", "protoc-gen-go-grpc"} {
|
||||
resp, err := runPlugin(plugin, pwd, req)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%s failed: %v\n", plugin, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
for _, f := range resp.File {
|
||||
targetPath := filepath.Join(pwd, filepath.FromSlash(f.GetName()))
|
||||
generatedFiles[targetPath] = struct{}{}
|
||||
content := []byte(f.GetContent())
|
||||
|
||||
hasDiff := false
|
||||
existing, err := os.ReadFile(targetPath)
|
||||
if err != nil {
|
||||
hasDiff = true
|
||||
} else {
|
||||
oldNorm := bytes.ReplaceAll(existing, []byte("\r\n"), []byte("\n"))
|
||||
newNorm := bytes.ReplaceAll(content, []byte("\r\n"), []byte("\n"))
|
||||
if !bytes.Equal(oldNorm, newNorm) {
|
||||
hasDiff = true
|
||||
}
|
||||
}
|
||||
args = append(args, relProtoFile)
|
||||
cmd := exec.Command(protoc, args...)
|
||||
cmd.Env = append(cmd.Env, os.Environ()...)
|
||||
cmd.Dir = pwd
|
||||
output, cmdErr := cmd.CombinedOutput()
|
||||
if len(output) > 0 {
|
||||
fmt.Println(string(output))
|
||||
}
|
||||
if cmdErr != nil {
|
||||
fmt.Println(cmdErr)
|
||||
os.Exit(1)
|
||||
|
||||
if hasDiff {
|
||||
diffCount++
|
||||
fmt.Println(targetPath)
|
||||
|
||||
if isCheck {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(targetPath), 0o755); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error creating directory: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
mode := os.FileMode(0o644)
|
||||
if stat, err := os.Stat(targetPath); err == nil {
|
||||
mode = stat.Mode().Perm()
|
||||
}
|
||||
if err := os.WriteFile(targetPath, content, mode); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error writing %s: %v\n", targetPath, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, path := range existingPbFiles {
|
||||
if _, ok := generatedFiles[path]; !ok {
|
||||
diffCount++
|
||||
if isCheck {
|
||||
fmt.Printf("Orphaned Protobuf file: %s\n", path)
|
||||
} else {
|
||||
fmt.Printf("Removing orphaned Protobuf file: %s\n", path)
|
||||
if err := os.Remove(path); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error removing %s: %v\n", path, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if isCheck {
|
||||
if diffCount > 0 {
|
||||
fmt.Println("Protobuf generated file problem(s) found.")
|
||||
fmt.Println("Please run 'go run ./infra/vprotogen' to regenerate them.")
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println("All Protobuf generated file check has been passed.")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,18 +47,19 @@ func executeBalancerInfo(cmd *base.Command, args []string) {
|
||||
conn, ctx, close := dialAPIServer()
|
||||
defer close()
|
||||
client := routerService.NewRoutingServiceClient(conn)
|
||||
r := &routerService.GetBalancerInfoRequest{Tag: unnamedArgs[0]}
|
||||
resp, err := client.GetBalancerInfo(ctx, r)
|
||||
if err != nil {
|
||||
base.Fatalf("failed to get health information: %s", err)
|
||||
}
|
||||
for _, tag := range unnamedArgs {
|
||||
r := &routerService.GetBalancerInfoRequest{Tag: tag}
|
||||
resp, err := client.GetBalancerInfo(ctx, r)
|
||||
if err != nil {
|
||||
base.Fatalf("failed to get health information: %s", err)
|
||||
}
|
||||
|
||||
if apiJSON {
|
||||
showJSONResponse(resp)
|
||||
return
|
||||
if apiJSON {
|
||||
showJSONResponse(resp)
|
||||
} else {
|
||||
showBalancerInfo(resp.Balancer)
|
||||
}
|
||||
}
|
||||
|
||||
showBalancerInfo(resp.Balancer)
|
||||
}
|
||||
|
||||
func showBalancerInfo(b *routerService.BalancerMsg) {
|
||||
|
||||
@@ -92,6 +92,8 @@ func executeAddRules(cmd *base.Command, args []string) {
|
||||
if err != nil {
|
||||
base.Fatalf("failed to perform AddRule: %s", err)
|
||||
}
|
||||
// if multiple files are specified, use append to merge them instead of overriding previous one
|
||||
shouldAppend = true
|
||||
showJSONResponse(resp)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package lua
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
xlua "github.com/xtls/xray-core/common/lua"
|
||||
"github.com/xtls/xray-core/main/commands/base"
|
||||
@@ -32,14 +33,13 @@ func executeSign(cmd *base.Command, args []string) {
|
||||
cmd.Usage()
|
||||
}
|
||||
|
||||
if !strings.EqualFold(filepath.Ext(args[0]), ".lua") {
|
||||
base.Fatalf("Lua file extension must be .lua")
|
||||
}
|
||||
info, err := os.Stat(args[0])
|
||||
if err != nil {
|
||||
base.Fatalf("failed to inspect Lua file: %s", err)
|
||||
}
|
||||
if !info.Mode().IsRegular() {
|
||||
base.Fatalf("Lua input is not a regular file: %s", args[0])
|
||||
}
|
||||
|
||||
code, err := os.ReadFile(args[0])
|
||||
if err != nil {
|
||||
base.Fatalf("failed to read Lua file: %s", err)
|
||||
@@ -67,14 +67,7 @@ func executeSign(cmd *base.Command, args []string) {
|
||||
|
||||
func writeSignedFile(path string, code []byte, mode os.FileMode) error {
|
||||
if info, err := os.Stat(path); err == nil {
|
||||
if !info.Mode().IsRegular() {
|
||||
return &os.PathError{Op: "write", Path: path, Err: os.ErrInvalid}
|
||||
}
|
||||
mode = info.Mode().Perm()
|
||||
path, err = filepath.EvalSymlinks(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
Vendored
+2
-10
@@ -58,18 +58,10 @@ func readConfigFile(file *os.File) ([]byte, error) {
|
||||
return nil, err
|
||||
}
|
||||
if info.Mode().IsRegular() {
|
||||
path, err := filepath.Abs(file.Name())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
path, err = filepath.EvalSymlinks(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch strings.ToLower(filepath.Ext(path)) {
|
||||
switch strings.ToLower(filepath.Ext(file.Name())) {
|
||||
case ".jsonc", ".json", ".yml", ".yaml", ".toml", ".pb", ".protobuf":
|
||||
default:
|
||||
return nil, errors.New("unsupported config file extension: ", path)
|
||||
return nil, errors.New("unsupported config file extension: ", file.Name())
|
||||
}
|
||||
}
|
||||
return io.ReadAll(file)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.5
|
||||
// protoc-gen-go v1.36.12
|
||||
// protoc (unknown)
|
||||
// source: proxy/blackhole/config.proto
|
||||
|
||||
package blackhole
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.5
|
||||
// protoc-gen-go v1.36.12
|
||||
// protoc (unknown)
|
||||
// source: proxy/dns/config.proto
|
||||
|
||||
package dns
|
||||
|
||||
@@ -381,6 +381,13 @@ func (h *Handler) handleIPQuery(id uint16, qType dnsmessage.Type, domain string,
|
||||
errors.LogInfoInner(context.Background(), err, "pack message")
|
||||
b.Release()
|
||||
timer.SetTimeout(0)
|
||||
return
|
||||
}
|
||||
if len(msgBytes) > buf.Size {
|
||||
b.Release()
|
||||
timer.SetTimeout(0)
|
||||
errors.LogError(context.Background(), "DNS response message too large")
|
||||
return
|
||||
}
|
||||
b.Resize(0, int32(len(msgBytes)))
|
||||
|
||||
|
||||
+24
-24
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.5
|
||||
// protoc-gen-go v1.36.12
|
||||
// protoc (unknown)
|
||||
// source: proxy/dokodemo/config.proto
|
||||
|
||||
package dokodemo
|
||||
@@ -23,14 +23,14 @@ const (
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
RewriteAddress *net.IPOrDomain `protobuf:"bytes,1,opt,name=rewrite_address,json=rewriteAddress,proto3" json:"rewrite_address,omitempty"`
|
||||
RewritePort uint32 `protobuf:"varint,2,opt,name=rewrite_port,json=rewritePort,proto3" json:"rewrite_port,omitempty"`
|
||||
PortMap map[string]string `protobuf:"bytes,3,rep,name=port_map,json=portMap,proto3" json:"port_map,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// List of networks that the Dokodemo accepts.
|
||||
AllowedNetworks []net.Network `protobuf:"varint,7,rep,packed,name=allowed_networks,json=allowedNetworks,proto3,enum=xray.common.net.Network" json:"allowed_networks,omitempty"`
|
||||
FollowRedirect bool `protobuf:"varint,5,opt,name=follow_redirect,json=followRedirect,proto3" json:"follow_redirect,omitempty"`
|
||||
UserLevel uint32 `protobuf:"varint,6,opt,name=user_level,json=userLevel,proto3" json:"user_level,omitempty"`
|
||||
AllowedNetworks []net.Network `protobuf:"varint,7,rep,packed,name=allowed_networks,json=allowedNetworks,proto3,enum=xray.common.net.Network" json:"allowed_networks,omitempty"`
|
||||
RewriteAddress *net.IPOrDomain `protobuf:"bytes,1,opt,name=rewrite_address,json=rewriteAddress,proto3" json:"rewrite_address,omitempty"`
|
||||
RewritePort uint32 `protobuf:"varint,2,opt,name=rewrite_port,json=rewritePort,proto3" json:"rewrite_port,omitempty"`
|
||||
PortMap map[string]string `protobuf:"bytes,3,rep,name=port_map,json=portMap,proto3" json:"port_map,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||
FollowRedirect bool `protobuf:"varint,5,opt,name=follow_redirect,json=followRedirect,proto3" json:"follow_redirect,omitempty"`
|
||||
UserLevel uint32 `protobuf:"varint,6,opt,name=user_level,json=userLevel,proto3" json:"user_level,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@@ -65,6 +65,13 @@ func (*Config) Descriptor() ([]byte, []int) {
|
||||
return file_proxy_dokodemo_config_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *Config) GetAllowedNetworks() []net.Network {
|
||||
if x != nil {
|
||||
return x.AllowedNetworks
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Config) GetRewriteAddress() *net.IPOrDomain {
|
||||
if x != nil {
|
||||
return x.RewriteAddress
|
||||
@@ -86,13 +93,6 @@ func (x *Config) GetPortMap() map[string]string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Config) GetAllowedNetworks() []net.Network {
|
||||
if x != nil {
|
||||
return x.AllowedNetworks
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Config) GetFollowRedirect() bool {
|
||||
if x != nil {
|
||||
return x.FollowRedirect
|
||||
@@ -112,11 +112,11 @@ var File_proxy_dokodemo_config_proto protoreflect.FileDescriptor
|
||||
const file_proxy_dokodemo_config_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\x1bproxy/dokodemo/config.proto\x12\x13xray.proxy.dokodemo\x1a\x18common/net/address.proto\x1a\x18common/net/network.proto\"\xff\x02\n" +
|
||||
"\x06Config\x12D\n" +
|
||||
"\x06Config\x12C\n" +
|
||||
"\x10allowed_networks\x18\a \x03(\x0e2\x18.xray.common.net.NetworkR\x0fallowedNetworks\x12D\n" +
|
||||
"\x0frewrite_address\x18\x01 \x01(\v2\x1b.xray.common.net.IPOrDomainR\x0erewriteAddress\x12!\n" +
|
||||
"\frewrite_port\x18\x02 \x01(\rR\vrewritePort\x12C\n" +
|
||||
"\bport_map\x18\x03 \x03(\v2(.xray.proxy.dokodemo.Config.PortMapEntryR\aportMap\x12C\n" +
|
||||
"\x10allowed_networks\x18\a \x03(\x0e2\x18.xray.common.net.NetworkR\x0fallowedNetworks\x12'\n" +
|
||||
"\bport_map\x18\x03 \x03(\v2(.xray.proxy.dokodemo.Config.PortMapEntryR\aportMap\x12'\n" +
|
||||
"\x0ffollow_redirect\x18\x05 \x01(\bR\x0efollowRedirect\x12\x1d\n" +
|
||||
"\n" +
|
||||
"user_level\x18\x06 \x01(\rR\tuserLevel\x1a:\n" +
|
||||
@@ -141,13 +141,13 @@ var file_proxy_dokodemo_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||
var file_proxy_dokodemo_config_proto_goTypes = []any{
|
||||
(*Config)(nil), // 0: xray.proxy.dokodemo.Config
|
||||
nil, // 1: xray.proxy.dokodemo.Config.PortMapEntry
|
||||
(*net.IPOrDomain)(nil), // 2: xray.common.net.IPOrDomain
|
||||
(net.Network)(0), // 3: xray.common.net.Network
|
||||
(net.Network)(0), // 2: xray.common.net.Network
|
||||
(*net.IPOrDomain)(nil), // 3: xray.common.net.IPOrDomain
|
||||
}
|
||||
var file_proxy_dokodemo_config_proto_depIdxs = []int32{
|
||||
2, // 0: xray.proxy.dokodemo.Config.rewrite_address:type_name -> xray.common.net.IPOrDomain
|
||||
1, // 1: xray.proxy.dokodemo.Config.port_map:type_name -> xray.proxy.dokodemo.Config.PortMapEntry
|
||||
3, // 2: xray.proxy.dokodemo.Config.allowed_networks:type_name -> xray.common.net.Network
|
||||
2, // 0: xray.proxy.dokodemo.Config.allowed_networks:type_name -> xray.common.net.Network
|
||||
3, // 1: xray.proxy.dokodemo.Config.rewrite_address:type_name -> xray.common.net.IPOrDomain
|
||||
1, // 2: xray.proxy.dokodemo.Config.port_map:type_name -> xray.proxy.dokodemo.Config.PortMapEntry
|
||||
3, // [3:3] is the sub-list for method output_type
|
||||
3, // [3:3] is the sub-list for method input_type
|
||||
3, // [3:3] is the sub-list for extension type_name
|
||||
|
||||
+36
-22
@@ -1,13 +1,14 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.5
|
||||
// protoc-gen-go v1.36.12
|
||||
// protoc (unknown)
|
||||
// source: proxy/freedom/config.proto
|
||||
|
||||
package freedom
|
||||
|
||||
import (
|
||||
geodata "github.com/xtls/xray-core/common/geodata"
|
||||
log "github.com/xtls/xray-core/common/log"
|
||||
net "github.com/xtls/xray-core/common/net"
|
||||
protocol "github.com/xtls/xray-core/common/protocol"
|
||||
internet "github.com/xtls/xray-core/transport/internet"
|
||||
@@ -357,7 +358,8 @@ type FinalRuleConfig struct {
|
||||
Networks []net.Network `protobuf:"varint,2,rep,packed,name=networks,proto3,enum=xray.common.net.Network" json:"networks,omitempty"`
|
||||
PortList *net.PortList `protobuf:"bytes,3,opt,name=port_list,json=portList,proto3" json:"port_list,omitempty"`
|
||||
Ip []*geodata.IPRule `protobuf:"bytes,4,rep,name=ip,proto3" json:"ip,omitempty"`
|
||||
BlockDelay *Range `protobuf:"bytes,5,opt,name=block_delay,json=blockDelay,proto3" json:"block_delay,omitempty"`
|
||||
Delay *Range `protobuf:"bytes,5,opt,name=delay,proto3" json:"delay,omitempty"`
|
||||
LogLevel *log.Severity `protobuf:"varint,6,opt,name=log_level,json=logLevel,proto3,enum=xray.common.log.Severity,oneof" json:"log_level,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@@ -420,13 +422,20 @@ func (x *FinalRuleConfig) GetIp() []*geodata.IPRule {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *FinalRuleConfig) GetBlockDelay() *Range {
|
||||
func (x *FinalRuleConfig) GetDelay() *Range {
|
||||
if x != nil {
|
||||
return x.BlockDelay
|
||||
return x.Delay
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *FinalRuleConfig) GetLogLevel() log.Severity {
|
||||
if x != nil && x.LogLevel != nil {
|
||||
return *x.LogLevel
|
||||
}
|
||||
return log.Severity(0)
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
DomainStrategy internet.DomainStrategy `protobuf:"varint,1,opt,name=domain_strategy,json=domainStrategy,proto3,enum=xray.transport.internet.DomainStrategy" json:"domain_strategy,omitempty"`
|
||||
@@ -523,7 +532,7 @@ var File_proxy_freedom_config_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_proxy_freedom_config_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\x1aproxy/freedom/config.proto\x12\x12xray.proxy.freedom\x1a!common/protocol/server_spec.proto\x1a\x1ftransport/internet/config.proto\x1a\x15common/net/port.proto\x1a\x18common/net/network.proto\x1a\x1bcommon/geodata/geodat.proto\"S\n" +
|
||||
"\x1aproxy/freedom/config.proto\x12\x12xray.proxy.freedom\x1a!common/protocol/server_spec.proto\x1a\x1ftransport/internet/config.proto\x1a\x15common/net/port.proto\x1a\x18common/net/network.proto\x1a\x1bcommon/geodata/geodat.proto\x1a\x14common/log/log.proto\"S\n" +
|
||||
"\x13DestinationOverride\x12<\n" +
|
||||
"\x06server\x18\x01 \x01(\v2$.xray.common.protocol.ServerEndpointR\x06server\"\x98\x02\n" +
|
||||
"\bFragment\x12!\n" +
|
||||
@@ -549,14 +558,16 @@ const file_proxy_freedom_config_proto_rawDesc = "" +
|
||||
"\bapply_to\x18\x06 \x01(\tR\aapplyTo\"+\n" +
|
||||
"\x05Range\x12\x10\n" +
|
||||
"\x03min\x18\x01 \x01(\x04R\x03min\x12\x10\n" +
|
||||
"\x03max\x18\x02 \x01(\x04R\x03max\"\xa0\x02\n" +
|
||||
"\x03max\x18\x02 \x01(\x04R\x03max\"\xe0\x02\n" +
|
||||
"\x0fFinalRuleConfig\x126\n" +
|
||||
"\x06action\x18\x01 \x01(\x0e2\x1e.xray.proxy.freedom.RuleActionR\x06action\x124\n" +
|
||||
"\bnetworks\x18\x02 \x03(\x0e2\x18.xray.common.net.NetworkR\bnetworks\x126\n" +
|
||||
"\tport_list\x18\x03 \x01(\v2\x19.xray.common.net.PortListR\bportList\x12+\n" +
|
||||
"\x02ip\x18\x04 \x03(\v2\x1b.xray.common.geodata.IPRuleR\x02ip\x12:\n" +
|
||||
"\vblock_delay\x18\x05 \x01(\v2\x19.xray.proxy.freedom.RangeR\n" +
|
||||
"blockDelay\"\xaf\x03\n" +
|
||||
"\x02ip\x18\x04 \x03(\v2\x1b.xray.common.geodata.IPRuleR\x02ip\x12/\n" +
|
||||
"\x05delay\x18\x05 \x01(\v2\x19.xray.proxy.freedom.RangeR\x05delay\x12;\n" +
|
||||
"\tlog_level\x18\x06 \x01(\x0e2\x19.xray.common.log.SeverityH\x00R\blogLevel\x88\x01\x01B\f\n" +
|
||||
"\n" +
|
||||
"_log_level\"\xaf\x03\n" +
|
||||
"\x06Config\x12P\n" +
|
||||
"\x0fdomain_strategy\x18\x01 \x01(\x0e2'.xray.transport.internet.DomainStrategyR\x0edomainStrategy\x12Z\n" +
|
||||
"\x14destination_override\x18\x03 \x01(\v2'.xray.proxy.freedom.DestinationOverrideR\x13destinationOverride\x12\x1d\n" +
|
||||
@@ -599,7 +610,8 @@ var file_proxy_freedom_config_proto_goTypes = []any{
|
||||
(net.Network)(0), // 8: xray.common.net.Network
|
||||
(*net.PortList)(nil), // 9: xray.common.net.PortList
|
||||
(*geodata.IPRule)(nil), // 10: xray.common.geodata.IPRule
|
||||
(internet.DomainStrategy)(0), // 11: xray.transport.internet.DomainStrategy
|
||||
(log.Severity)(0), // 11: xray.common.log.Severity
|
||||
(internet.DomainStrategy)(0), // 12: xray.transport.internet.DomainStrategy
|
||||
}
|
||||
var file_proxy_freedom_config_proto_depIdxs = []int32{
|
||||
7, // 0: xray.proxy.freedom.DestinationOverride.server:type_name -> xray.common.protocol.ServerEndpoint
|
||||
@@ -607,17 +619,18 @@ var file_proxy_freedom_config_proto_depIdxs = []int32{
|
||||
8, // 2: xray.proxy.freedom.FinalRuleConfig.networks:type_name -> xray.common.net.Network
|
||||
9, // 3: xray.proxy.freedom.FinalRuleConfig.port_list:type_name -> xray.common.net.PortList
|
||||
10, // 4: xray.proxy.freedom.FinalRuleConfig.ip:type_name -> xray.common.geodata.IPRule
|
||||
4, // 5: xray.proxy.freedom.FinalRuleConfig.block_delay:type_name -> xray.proxy.freedom.Range
|
||||
11, // 6: xray.proxy.freedom.Config.domain_strategy:type_name -> xray.transport.internet.DomainStrategy
|
||||
1, // 7: xray.proxy.freedom.Config.destination_override:type_name -> xray.proxy.freedom.DestinationOverride
|
||||
2, // 8: xray.proxy.freedom.Config.fragment:type_name -> xray.proxy.freedom.Fragment
|
||||
3, // 9: xray.proxy.freedom.Config.noises:type_name -> xray.proxy.freedom.Noise
|
||||
5, // 10: xray.proxy.freedom.Config.final_rules:type_name -> xray.proxy.freedom.FinalRuleConfig
|
||||
11, // [11:11] is the sub-list for method output_type
|
||||
11, // [11:11] is the sub-list for method input_type
|
||||
11, // [11:11] is the sub-list for extension type_name
|
||||
11, // [11:11] is the sub-list for extension extendee
|
||||
0, // [0:11] is the sub-list for field type_name
|
||||
4, // 5: xray.proxy.freedom.FinalRuleConfig.delay:type_name -> xray.proxy.freedom.Range
|
||||
11, // 6: xray.proxy.freedom.FinalRuleConfig.log_level:type_name -> xray.common.log.Severity
|
||||
12, // 7: xray.proxy.freedom.Config.domain_strategy:type_name -> xray.transport.internet.DomainStrategy
|
||||
1, // 8: xray.proxy.freedom.Config.destination_override:type_name -> xray.proxy.freedom.DestinationOverride
|
||||
2, // 9: xray.proxy.freedom.Config.fragment:type_name -> xray.proxy.freedom.Fragment
|
||||
3, // 10: xray.proxy.freedom.Config.noises:type_name -> xray.proxy.freedom.Noise
|
||||
5, // 11: xray.proxy.freedom.Config.final_rules:type_name -> xray.proxy.freedom.FinalRuleConfig
|
||||
12, // [12:12] is the sub-list for method output_type
|
||||
12, // [12:12] is the sub-list for method input_type
|
||||
12, // [12:12] is the sub-list for extension type_name
|
||||
12, // [12:12] is the sub-list for extension extendee
|
||||
0, // [0:12] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_proxy_freedom_config_proto_init() }
|
||||
@@ -625,6 +638,7 @@ func file_proxy_freedom_config_proto_init() {
|
||||
if File_proxy_freedom_config_proto != nil {
|
||||
return
|
||||
}
|
||||
file_proxy_freedom_config_proto_msgTypes[4].OneofWrappers = []any{}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
|
||||
@@ -11,6 +11,7 @@ import "transport/internet/config.proto";
|
||||
import "common/net/port.proto";
|
||||
import "common/net/network.proto";
|
||||
import "common/geodata/geodat.proto";
|
||||
import "common/log/log.proto";
|
||||
|
||||
message DestinationOverride {
|
||||
xray.common.protocol.ServerEndpoint server = 1;
|
||||
@@ -51,7 +52,8 @@ message FinalRuleConfig {
|
||||
repeated xray.common.net.Network networks = 2;
|
||||
xray.common.net.PortList port_list = 3;
|
||||
repeated xray.common.geodata.IPRule ip = 4;
|
||||
Range block_delay = 5;
|
||||
Range delay = 5;
|
||||
optional xray.common.log.Severity log_level = 6;
|
||||
}
|
||||
|
||||
message Config {
|
||||
|
||||
+52
-25
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/xtls/xray-core/common/dice"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/geodata"
|
||||
"github.com/xtls/xray-core/common/log"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/platform"
|
||||
"github.com/xtls/xray-core/common/retry"
|
||||
@@ -72,23 +73,26 @@ func init() {
|
||||
}
|
||||
|
||||
defaultBlockPrivateRule = &FinalRule{
|
||||
action: RuleAction_Block,
|
||||
network: allNetworks,
|
||||
ip: geodata.GetPrivateIPMatcher(),
|
||||
action: RuleAction_Block,
|
||||
network: allNetworks,
|
||||
ip: geodata.GetPrivateIPMatcher(),
|
||||
logLevel: log.Severity_Warning.Enum(),
|
||||
}
|
||||
|
||||
defaultBlockAllRule = &FinalRule{
|
||||
action: RuleAction_Block,
|
||||
network: allNetworks,
|
||||
action: RuleAction_Block,
|
||||
network: allNetworks,
|
||||
logLevel: log.Severity_Warning.Enum(),
|
||||
}
|
||||
}
|
||||
|
||||
type FinalRule struct {
|
||||
action RuleAction
|
||||
network [8]bool
|
||||
port net.MemoryPortList
|
||||
ip geodata.IPMatcher
|
||||
blockDelay *Range
|
||||
action RuleAction
|
||||
network [8]bool
|
||||
port net.MemoryPortList
|
||||
ip geodata.IPMatcher
|
||||
delay *Range
|
||||
logLevel *log.Severity
|
||||
}
|
||||
|
||||
// Handler handles Freedom connections.
|
||||
@@ -102,8 +106,9 @@ type Handler struct {
|
||||
|
||||
func buildFinalRule(config *FinalRuleConfig) (*FinalRule, error) {
|
||||
rule := &FinalRule{
|
||||
action: config.GetAction(),
|
||||
blockDelay: config.GetBlockDelay(),
|
||||
action: config.GetAction(),
|
||||
delay: config.GetDelay(),
|
||||
logLevel: config.LogLevel,
|
||||
}
|
||||
|
||||
if len(config.Networks) == 0 {
|
||||
@@ -164,7 +169,7 @@ func getDefaultFinalRule(inbound *session.Inbound) *FinalRule {
|
||||
switch inbound.Name {
|
||||
case "vless-reverse":
|
||||
return defaultBlockAllRule
|
||||
case "vless", "vmess", "trojan", "hysteria", "wireguard":
|
||||
case "vless", "vmess", "trojan", "hysteria", "wireguard", "masque":
|
||||
return defaultBlockPrivateRule
|
||||
default:
|
||||
if strings.HasPrefix(inbound.Name, "shadowsocks") {
|
||||
@@ -212,12 +217,12 @@ func (h *Handler) policy() policy.Session {
|
||||
return p
|
||||
}
|
||||
|
||||
func (h *Handler) blockDelay(rule *FinalRule) time.Duration {
|
||||
func (r *FinalRule) blockDuration() time.Duration {
|
||||
min := uint64(30)
|
||||
max := uint64(90)
|
||||
if rule.blockDelay != nil {
|
||||
min = rule.blockDelay.Min
|
||||
max = rule.blockDelay.Max
|
||||
if r.delay != nil {
|
||||
min = r.delay.Min
|
||||
max = r.delay.Max
|
||||
}
|
||||
span := max - min
|
||||
if max < min {
|
||||
@@ -226,17 +231,39 @@ func (h *Handler) blockDelay(rule *FinalRule) time.Duration {
|
||||
return time.Duration(min+uint64(dice.Roll(int(span+1)))) * time.Second
|
||||
}
|
||||
|
||||
func (r *FinalRule) log(ctx context.Context, msg ...interface{}) {
|
||||
level := log.Severity_Info
|
||||
if r.logLevel != nil {
|
||||
level = *r.logLevel
|
||||
}
|
||||
switch level {
|
||||
case log.Severity_Debug:
|
||||
errors.LogDebug(ctx, msg...)
|
||||
case log.Severity_Info:
|
||||
errors.LogInfo(ctx, msg...)
|
||||
case log.Severity_Warning:
|
||||
errors.LogWarning(ctx, msg...)
|
||||
case log.Severity_Error:
|
||||
errors.LogError(ctx, msg...)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) blackhole(ctx context.Context, input buf.Reader, output buf.Writer, rule *FinalRule, dest *net.Destination) error {
|
||||
delay := h.blockDelay(rule)
|
||||
errors.LogInfo(ctx, "blocked target: ", *dest, ", blackholing connection for ", delay)
|
||||
timer := time.AfterFunc(delay, func() {
|
||||
delay := rule.blockDuration()
|
||||
rule.log(ctx, "target blocked by a finalrule: ", *dest, ", blackholing connection for ", delay)
|
||||
defer common.Close(output)
|
||||
done := make(chan struct{})
|
||||
go func() { // a DispatchLink inbound's reader ignores Interrupt, so don't wait for this copy
|
||||
_ = buf.Copy(input, buf.Discard)
|
||||
close(done)
|
||||
}()
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(delay):
|
||||
common.Interrupt(input)
|
||||
common.Interrupt(output)
|
||||
errors.LogInfo(ctx, "closed blackholed connection to blocked target: ", *dest)
|
||||
})
|
||||
defer timer.Stop()
|
||||
defer common.Close(output)
|
||||
_ = buf.Copy(input, buf.Discard)
|
||||
rule.log(ctx, "closed blackholed connection to blocked target: ", *dest)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.5
|
||||
// protoc-gen-go v1.36.12
|
||||
// protoc (unknown)
|
||||
// source: proxy/http/config.proto
|
||||
|
||||
package http
|
||||
|
||||
@@ -312,8 +312,9 @@ func (s *Server) handlePlainHTTP(ctx context.Context, request *http.Request, wri
|
||||
// Sometimes, server might send 1xx response to client
|
||||
// it should not be processed by http proxy handler, just forward it to client
|
||||
func readResponseAndHandle100Continue(r *bufio.Reader, req *http.Request, writer io.Writer) (*http.Response, error) {
|
||||
// have a little look of response
|
||||
peekBytes, err := r.Peek(56)
|
||||
// Wait for the HTTP/1.x status prefix before inspecting the buffered response.
|
||||
r.Peek(12)
|
||||
peekBytes, err := r.Peek(min(56, r.Buffered()))
|
||||
if err == nil || err == bufio.ErrBufferFull {
|
||||
str := string(peekBytes)
|
||||
ResponseLine := strings.Split(str, "\r\n")[0]
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
"testing/iotest"
|
||||
)
|
||||
|
||||
// A malformed upstream response containing a bare '\n' before the real
|
||||
@@ -29,7 +30,7 @@ func TestReadResponseAndHandle100ContinueDoesNotPanicOnEarlyNewline(t *testing.T
|
||||
func TestReadResponseAndHandle100ContinueForwardsAndParsesFinalResponse(t *testing.T) {
|
||||
payload := "HTTP/1.1 100 Continue\r\n\r\n" +
|
||||
"HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nhello"
|
||||
r := bufio.NewReader(bytes.NewReader([]byte(payload)))
|
||||
r := bufio.NewReader(iotest.OneByteReader(bytes.NewReader([]byte(payload))))
|
||||
req, err := http.NewRequest("GET", "http://example.com/", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.5
|
||||
// protoc-gen-go v1.36.12
|
||||
// protoc (unknown)
|
||||
// source: proxy/hysteria/account/config.proto
|
||||
|
||||
package account
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.5
|
||||
// protoc-gen-go v1.36.12
|
||||
// protoc (unknown)
|
||||
// source: proxy/hysteria/config.proto
|
||||
|
||||
package hysteria
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.5
|
||||
// protoc-gen-go v1.36.12
|
||||
// protoc (unknown)
|
||||
// source: proxy/loopback/config.proto
|
||||
|
||||
package loopback
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.5
|
||||
// protoc-gen-go v1.36.12
|
||||
// protoc (unknown)
|
||||
// source: proxy/masque/config.proto
|
||||
|
||||
package masque
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.5
|
||||
// protoc-gen-go v1.36.12
|
||||
// protoc (unknown)
|
||||
// source: proxy/shadowsocks/config.proto
|
||||
|
||||
package shadowsocks
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.5
|
||||
// protoc-gen-go v1.36.12
|
||||
// protoc (unknown)
|
||||
// source: proxy/shadowsocks_2022/config.proto
|
||||
|
||||
package shadowsocks_2022
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.5
|
||||
// protoc-gen-go v1.36.12
|
||||
// protoc (unknown)
|
||||
// source: proxy/socks/config.proto
|
||||
|
||||
package socks
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.5
|
||||
// protoc-gen-go v1.36.12
|
||||
// protoc (unknown)
|
||||
// source: proxy/trojan/config.proto
|
||||
|
||||
package trojan
|
||||
|
||||
+27
-11
@@ -4,7 +4,7 @@ TUN interface support bridges the gap between network layer 3 and layer 7, intro
|
||||
|
||||
This functionality is targeted to assist applications/end devices that don't have proxy support, or can't run external applications (like Smart TV's). Making it possible to run Xray proxy right on network edge devices (routers) with support to route raw network traffic. \
|
||||
Primary targets are Linux based router devices. Like most popular OpenWRT option. \
|
||||
Support for Windows, macOS, Android and iOS is also implemented (see below).
|
||||
Support for Windows, macOS, FreeBSD, Android and iOS is also implemented (see below).
|
||||
|
||||
## PLEASE READ FOLLOWING CAREFULLY
|
||||
|
||||
@@ -23,6 +23,12 @@ For more advanced routing policies or rules, OS level configuration can still ma
|
||||
This keeps complex system level routing and rules in a single place of responsibility - the OS itself. \
|
||||
Examples of how to achieve this on a simple Linux system (Ubuntu with systemd-networkd) can be found at the end of this README.
|
||||
|
||||
Packets are handled by Xray-core's own lightweight TCP/IP stack, which replaced gVisor (XTLS/Xray-core#7130).
|
||||
|
||||
On Linux, as on Android, the TUN can also be a file descriptor handed over through the environment variable `xray.tun.fd` (or `XRAY_TUN_FD`), e.g. for lightweight or unprivileged setups.
|
||||
|
||||
With `autoSystemRoutingTable`, Xray's own connections are kept out of the TUN through `autoOutboundsInterface` (then `"auto"` by default): each of their sockets is bound to another interface, the one named there, or with `"auto"` the one of the default route (Android needs the name). Linux and Android bind with `SO_BINDTODEVICE`, Windows with `IP_UNICAST_IF` and `IPV6_UNICAST_IF`, macOS and iOS with `IP_BOUND_IF` and `IPV6_BOUND_IF`, and FreeBSD switches them to another routing table (see [FreeBSD SUPPORT](#freebsd-support)). Connections to loopback addresses, TCP and UDP, stay unbound. If a socket cannot be bound, the connection fails rather than leave unbound, where it could loop back into the TUN (XTLS/Xray-core#7173).
|
||||
|
||||
### SYSTEM DNS ON LINUX AND FREEBSD (`autoSystemDnsToGateway`)
|
||||
|
||||
On Linux and FreeBSD, setting `autoSystemDnsToGateway` to `true` lets the inbound point the system resolver at the tun interface, so name lookups resolve through Xray instead of going out over the physical link. It is off by default, and it exists only on those two systems. This section describes Linux; the FreeBSD differences follow in [SYSTEM DNS ON FREEBSD](#system-dns-on-freebsd).
|
||||
@@ -215,24 +221,33 @@ All in all systemd-networkd and its derivatives (like netplan or NetworkManager)
|
||||
Windows version of the same functionality is implemented through Wintun library. \
|
||||
To make it start, wintun.dll specific for your Windows/arch must be present next to Xray.exe binary.
|
||||
|
||||
After the start network adapter with the name you chose in the config will be created in the system, and exist while Xray is running.
|
||||
After the start network adapter with the name you chose in the config will be created in the system, and exist while Xray is running. A Wintun adapter that already has that name is reused.
|
||||
|
||||
When `dns` is set, those servers are applied to the adapter. Windows is kept from registering the TUN's addresses in DNS, and its DNS cache is flushed when the TUN starts and stops.
|
||||
With IPv6 disabled system-wide (the `DisabledComponents` registry value), the adapter has no IPv6, and the TUN starts with IPv4 only, unless `gateway`, `dns` or `autoSystemRoutingTable` has IPv6 entries, which then keep it from starting.
|
||||
|
||||
With `autoSystemWfpBlockLeak`, which needs `autoSystemRoutingTable` (the config is rejected otherwise), Xray also adds Windows Filtering Platform filters that keep two kinds of traffic of every program but Xray itself from leaving outside the TUN, each chosen by a value in the list, e.g. `"autoSystemWfpBlockLeak": ["dns", "misconfigtun"]`:
|
||||
- `"dns"` (needs `dns`, the config is rejected otherwise): DNS (port 53) only goes through the TUN. Windows keeps sending name queries to the DNS servers of the other interfaces as well, out through those interfaces whatever the routes say, and other programs reach a resolver on the local network (e.g. `192.168.1.1` handed out by DHCP) through its more specific LAN route instead of the TUN. On Windows 11 and Server 2022 and later, where those queries may also go over HTTPS or TLS, Windows' DNS Client service cannot connect outside the TUN at all, except for name resolution on the local network (LLMNR, mDNS). The `dns` servers therefore have to lie within `gateway` or `autoSystemRoutingTable` (a warning is logged otherwise), and DNS servers that should be reached directly belong in Xray's own `dns` settings.
|
||||
- `"misconfigtun"`: an IP version without routes in `autoSystemRoutingTable`, IPv4 or IPv6, is blocked entirely, in both directions, as it would bypass the TUN. Only loopback and what Windows itself needs on the local link (DHCP, and for IPv6 neighbor and multicast listener discovery) remain allowed. An address of that version in `gateway` is not needed: without one, Windows gives the TUN link-local addresses itself, an IPv6 one at once and an IPv4 one from `169.254.0.0/16` after some seconds (until then, IPv4 routed to the TUN is unreachable), and what is routed to the TUN goes through it with those.
|
||||
When `dns` is set, those servers are applied to the adapter. Windows is kept from registering the TUN's addresses in DNS, and its DNS cache is flushed when the TUN starts and stops. Xray's own lookups through the system's DNS servers, like those of the `localhost` DNS server with `autoOutboundsInterface`, skip them, unless another interface uses them as well, as they would lead back into the TUN.
|
||||
|
||||
With the filters in place, Xray's own connections out also get past Windows Firewall's block rules (other firewalls may still block them), while connections to Xray's inbounds stay subject to them.
|
||||
Setting `autoSystemWfpBlockLeak` to at least `["dns"]` is recommended, to block DNS leaks. It needs `autoSystemRoutingTable` (the config is rejected otherwise), and adds Windows Filtering Platform filters that let traffic of every program but Xray itself leave only through the TUN, each kind chosen by a value:
|
||||
- `"dns"`: stands for `"53"`, `"853"` (DNS over TLS and QUIC) and `"windnsclient"`. Windows also queries the DNS servers of the other interfaces, through those interfaces whatever the routes say, and other programs reach a resolver on the local network (e.g. `192.168.1.1` from DHCP) through its more specific route.
|
||||
- `"windnsclient"`: Windows' DNS Client service, except for name resolution on the local network (LLMNR, mDNS). Since Windows 11 and Server 2022, it may query over HTTPS or TLS, to any port; earlier versions only query port 53, so there it does nothing.
|
||||
- `"tcp"`, `"udp"`: that protocol, to all ports, or with `/<port>` or `/<low>-<high>` (1 to 65535) to some, e.g. `"tcp/853"` or `"udp/10-20"`. The ports alone stand for both, e.g. `"53"` or `"10-20"`.
|
||||
- `"icmp"`: ICMP and ICMPv6, e.g. `ping`.
|
||||
- `"misconfigtun"`: an IP version without routes in `autoSystemRoutingTable`, which would bypass the TUN.
|
||||
- `"all"`: everything, in both directions. Access to the local network then breaks unless it is routed into the TUN as well, and so do incoming connections to other programs, other VPNs, and virtual machines whose NAT runs on the host.
|
||||
- `"allexceptlan"`: like `"all"`, but traffic to and from the local network stays allowed, e.g. for printers or casting to a TV: private, link-local, multicast and broadcast addresses (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `169.254.0.0/16`, `224.0.0.0/4`, `255.255.255.255`, `fc00::/7`, `fe80::/10`, `ff00::/8`), not global IPv6 ones. It includes `"dns"`, and the other values apply there as well. `"all"` overrides it.
|
||||
|
||||
Names that Xray resolves through the system resolver, such as an outbound's server address given as a domain with the default `AsIs` domain strategy, would be looked up by Windows on Xray's behalf, and those queries would then go into the TUN too. While DNS is restricted this way and `autoOutboundsInterface` is in use (the default with `autoSystemRoutingTable`), Xray therefore resolves them itself, with its own queries to the DNS servers of the other interfaces. That bypasses Windows' DNS cache, and its name resolution on the local network (LLMNR, mDNS): a server address given as a domain is looked up again for every connection, and a DNS server that does not answer delays each lookup. Having Xray's own `dns` resolve it, through the outbound's `sockopt.domainStrategy`, avoids that. The `localhost` DNS server queries the same servers whenever `autoOutboundsInterface` is in use. Both skip the TUN's own DNS servers, unless another interface uses them as well: queried from Xray itself, they would lead back into it, or nowhere.
|
||||
A CIDR in front limits a value to its addresses, an IPv6 one in brackets, e.g. `"8.8.8.8/32:all"`, `"1.1.1.0/24:53"` or `"[::/0]:all"` (IPv6 only). It limits what the value stands for or includes as well: `"0.0.0.0/0:allexceptlan"`, for instance, leaves DNS over IPv6 open.
|
||||
|
||||
If the filters cannot be added, Xray does not start. They are removed when Xray exits. Not covered is name resolution on the local network (LLMNR, mDNS, NetBIOS), except over an IP version that is blocked.
|
||||
Loopback, DHCP, DHCPv6 and IPv6 neighbor discovery always stay allowed. Xray's own connections out also get past Windows Firewall's block rules, while connections to its inbounds stay subject to them. If the filters cannot be added, Xray does not start; they are removed when it exits. `"dns"` leaves name resolution on the local network (LLMNR, mDNS, NetBIOS) alone; `"all"` or `"udp"`, for instance, block it as well.
|
||||
|
||||
`autoSystemWfpBlockLeak` (Windows only) is empty by default, as the filters break some setups: with `"dns"`, a local DNS resolver other programs use (e.g. on `127.0.0.1:53`), the DNS of another VPN on its own interface, virtual machines whose NAT resolves names on the host, or signing in to a captive portal; with `"misconfigtun"`, IPv4 or IPv6 on the local network while no route of that version leads to the TUN. Without the filters, DNS may leak as described above. To keep an IP version out of the TUN on purpose while still blocking DNS leaks, use only `["dns"]`.
|
||||
Keeping the system's DNS inside the TUN, with `"all"`, `"allexceptlan"`, `"dns"`, `"windnsclient"`, or anything covering TCP or UDP port 53, even to some addresses only, needs `dns` (the config is rejected otherwise), whose servers have to lie within `gateway` or `autoSystemRoutingTable`, or be loopback addresses like `127.0.0.1` (a warning is logged otherwise); DNS servers to be reached directly belong in Xray's own `dns` settings. With `autoOutboundsInterface`, names that Xray would look up through the system, e.g. an outbound's server address with the default `AsIs` domain strategy, are then resolved by Xray itself, from the DNS servers of the other interfaces, as Windows would send those queries into the TUN. That bypasses Windows' DNS cache, so such a name is looked up for every connection; resolving it with Xray's own `dns`, through the outbound's `sockopt.domainStrategy`, avoids that.
|
||||
|
||||
`autoSystemWfpBlockLeak` is empty by default, as the filters break some setups: with `"dns"`, a local DNS resolver that queries through another interface, the DNS of another VPN, virtual machines whose NAT resolves names on the host, and captive portals; with `"misconfigtun"`, IPv4 or IPv6 on the local network while not routed into the TUN.
|
||||
|
||||
`autoOutboundsInterface` (the default with `autoSystemRoutingTable`) keeps Xray's own connections out of the TUN by binding them to another interface, which Windows only honors while that interface has weak host send and forwarding off for the IP versions routed to the TUN. Otherwise, Windows sends them into the TUN, from that interface's address, and they stall. While the TUN runs, Xray therefore turns weak host send off on that interface, and on again when it stops or another interface takes over. Forwarding cannot be turned off this way, as Mobile Hotspot and Internet Connection Sharing need it, so a warning is logged while it is on. Having the hotspot share the TUN instead of that interface (Settings, Mobile hotspot, Share my internet connection from) moves forwarding to the TUN, where it does no harm, and sends the hotspot's devices through Xray as well.
|
||||
|
||||
An outbound interface with only IPv4 or only IPv6 works as well: a UDP socket opened for both IP versions, which such an interface cannot be bound to, is opened again for the destination's version only, and IPv6 TCP is bound for IPv6 alone, so connections of the missing version fail rather than leave through another interface (XTLS/Xray-core#7214).
|
||||
|
||||
You can give the adapter ip address manually, you can live Windows to give it autogenerated ip address (which take few seconds), it doesn't matter, the traffic going _through_ the interface will be forwarded into the app for proxying. \
|
||||
Minimal configuration that will work for local machine is routing passing the traffic on-link through the interface.
|
||||
You will need the interface id for that, unfortunately it is going to change with every Xray start due to implementation ambiguity between Xray and wintun driver.
|
||||
@@ -307,6 +322,7 @@ sudo route add -inet6 -host 2606:4700:4700::1111 -iface utun10
|
||||
sudo route add -inet6 -host 2606:4700:4700::1001 -iface utun10
|
||||
```
|
||||
Alternatively, configure `autoSystemRoutingTable` and Xray will add and remove those system routes while it is running.
|
||||
Xray's own connections are then kept out of the TUN through `autoOutboundsInterface`: an IPv4 socket is bound with `IP_BOUND_IF`, an IPv6 or dual-stack one with `IPV6_BOUND_IF`, which covers the IPv4 traffic of a dual-stack socket as well, since macOS rejects `IP_BOUND_IF` on IPv6 sockets (XTLS/Xray-core#7230).
|
||||
Important to remember that everything written above about Linux routing concept, also apply to Mac OS X. If you simply route default route through utun interface, that will result network loop and immediate network failure.
|
||||
|
||||
## ANDROID SUPPORT
|
||||
@@ -338,7 +354,7 @@ let tunFd = ((0 ... 1024).first { (_ fd: Int32) -> Bool in var len = socklen_t(b
|
||||
}!
|
||||
```
|
||||
|
||||
Set the environment variable `xray.tun.fd` (or `XRAY_TUN_FD`) to the fd number before starting Xray. This can be done from Swift/Objective-C or by exposing a Go function via gomobile bindings.
|
||||
Set the environment variable `xray.tun.fd` (or `XRAY_TUN_FD`) to the fd number before starting Xray. This can be done from Swift/Objective-C or by exposing a Go function via gomobile bindings. Xray works on a duplicate of the fd and closes only that, so the fd itself stays NetworkExtension's (XTLS/Xray-core#6883).
|
||||
|
||||
Build using gomobile for iOS framework integration:
|
||||
```
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.5
|
||||
// protoc-gen-go v1.36.12
|
||||
// protoc (unknown)
|
||||
// source: proxy/tun/config.proto
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
unsafe "unsafe"
|
||||
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -558,19 +558,16 @@ func ioctlPtr(fd int, req uint, arg unsafe.Pointer) error {
|
||||
}
|
||||
|
||||
func setinterface(network, address string, fd uintptr, iface *net.Interface) error {
|
||||
var err1, err2 error
|
||||
|
||||
switch network {
|
||||
case "tcp6", "udp6", "ip6":
|
||||
err1 = unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_BOUND_IF, iface.Index)
|
||||
fallthrough
|
||||
// IPV6_BOUND_IF binds the IPv4 traffic of a dual-stack socket as
|
||||
// well, and an IPv6 socket rejects IP_BOUND_IF with EINVAL.
|
||||
return unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_BOUND_IF, iface.Index)
|
||||
case "tcp4", "udp4", "ip4":
|
||||
err2 = unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_BOUND_IF, iface.Index)
|
||||
return unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_BOUND_IF, iface.Index)
|
||||
default:
|
||||
panic(network + " " + address)
|
||||
}
|
||||
|
||||
return errors.Join(err1, err2)
|
||||
}
|
||||
|
||||
func findOutboundInterface(tunIndex int, fixedName string) (*net.Interface, error) {
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
package tun
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"sync"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -231,3 +234,40 @@ func TestWaitKqueueReportsPersistentFailure(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestSetinterfaceSocketFamilies binds each kind of socket Xray's dialers
|
||||
// open to lo0, including the dual-stack one Go opens for UDP.
|
||||
func TestSetinterfaceSocketFamilies(t *testing.T) {
|
||||
lo, err := net.InterfaceByName("lo0")
|
||||
if err != nil {
|
||||
t.Skip(err)
|
||||
}
|
||||
lc := net.ListenConfig{Control: func(network, address string, c syscall.RawConn) error {
|
||||
var err error
|
||||
if controlErr := c.Control(func(fd uintptr) {
|
||||
err = setinterface(network, address, fd, lo)
|
||||
}); controlErr != nil {
|
||||
return controlErr
|
||||
}
|
||||
return err
|
||||
}}
|
||||
for _, s := range []struct{ network, address string }{
|
||||
{"udp", "0.0.0.0:0"}, // dual-stack
|
||||
{"udp4", "127.0.0.1:0"},
|
||||
{"udp6", "[::1]:0"},
|
||||
{"tcp4", "127.0.0.1:0"},
|
||||
{"tcp6", "[::1]:0"},
|
||||
} {
|
||||
var closer interface{ Close() error }
|
||||
if s.network[:3] == "udp" {
|
||||
closer, err = lc.ListenPacket(context.Background(), s.network, s.address)
|
||||
} else {
|
||||
closer, err = lc.Listen(context.Background(), s.network, s.address)
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("%s %s: %v", s.network, s.address, err)
|
||||
continue
|
||||
}
|
||||
closer.Close()
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user