mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-10-31 07:27:02 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ec957cc72 |
@@ -82,7 +82,7 @@ jobs:
|
|||||||
uses: docker/setup-buildx-action@v4
|
uses: docker/setup-buildx-action@v4
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@v4.7.0
|
uses: docker/login-action@v4.6.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
|
|||||||
@@ -35,7 +35,24 @@ jobs:
|
|||||||
if: steps.check-assets.outputs.missing == 'true'
|
if: steps.check-assets.outputs.missing == 'true'
|
||||||
run: sleep 90
|
run: sleep 90
|
||||||
|
|
||||||
check-format-and-proto:
|
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:
|
||||||
runs-on: ubuntu-latest
|
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
|
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
||||||
permissions:
|
permissions:
|
||||||
@@ -51,8 +68,6 @@ jobs:
|
|||||||
cache: false
|
cache: false
|
||||||
- name: Check Format
|
- name: Check Format
|
||||||
run: go run ./infra/vformat/main.go -mode check -pwd ./
|
run: go run ./infra/vformat/main.go -mode check -pwd ./
|
||||||
- name: Check Protobuf
|
|
||||||
run: go run ./infra/vprotogen/main.go -mode check -pwd ./
|
|
||||||
|
|
||||||
test:
|
test:
|
||||||
needs: check-assets
|
needs: check-assets
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.12
|
// protoc-gen-go v1.36.11
|
||||||
// protoc (unknown)
|
// protoc v6.33.5
|
||||||
// source: app/commander/config.proto
|
// source: app/commander/config.proto
|
||||||
|
|
||||||
package commander
|
package commander
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.12
|
// protoc-gen-go v1.36.11
|
||||||
// protoc (unknown)
|
// protoc v6.33.5
|
||||||
// source: app/dispatcher/config.proto
|
// source: app/dispatcher/config.proto
|
||||||
|
|
||||||
package dispatcher
|
package dispatcher
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.12
|
// protoc-gen-go v1.36.11
|
||||||
// protoc (unknown)
|
// protoc v6.33.5
|
||||||
// source: app/dns/config.proto
|
// source: app/dns/config.proto
|
||||||
|
|
||||||
package dns
|
package dns
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.12
|
// protoc-gen-go v1.36.11
|
||||||
// protoc (unknown)
|
// protoc v6.33.5
|
||||||
// source: app/dns/fakedns/fakedns.proto
|
// source: app/dns/fakedns/fakedns.proto
|
||||||
|
|
||||||
package fakedns
|
package fakedns
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.12
|
// protoc-gen-go v1.36.11
|
||||||
// protoc (unknown)
|
// protoc v6.33.5
|
||||||
// source: app/geodata/config.proto
|
// source: app/geodata/config.proto
|
||||||
|
|
||||||
package geodata
|
package geodata
|
||||||
|
|||||||
+36
-16
@@ -21,10 +21,7 @@ import (
|
|||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const idleTimeout = 30 * time.Second
|
||||||
idleTimeout = 30 * time.Second
|
|
||||||
maxDownloadSize int64 = 256 * 1024 * 1024
|
|
||||||
)
|
|
||||||
|
|
||||||
// DO NOT EDIT. Ignored by geodata, rejected by Lua.
|
// DO NOT EDIT. Ignored by geodata, rejected by Lua.
|
||||||
const luaGuardHeader = "\x12\x01\x00"
|
const luaGuardHeader = "\x12\x01\x00"
|
||||||
@@ -36,6 +33,7 @@ type stage struct {
|
|||||||
|
|
||||||
type downloader struct {
|
type downloader struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
|
httpClient *http.Client
|
||||||
httpsClient *http.Client
|
httpsClient *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,11 +61,12 @@ func (c *idleConn) Write(b []byte) (int, error) {
|
|||||||
func newDownloader(ctx context.Context, dispatcher routing.Dispatcher, outbound string) *downloader {
|
func newDownloader(ctx context.Context, dispatcher routing.Dispatcher, outbound string) *downloader {
|
||||||
return &downloader{
|
return &downloader{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
httpsClient: newClient(ctx, dispatcher, outbound),
|
httpClient: newClient(ctx, dispatcher, outbound, false),
|
||||||
|
httpsClient: newClient(ctx, dispatcher, outbound, true),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newClient(baseCtx context.Context, dispatcher routing.Dispatcher, outbound string) *http.Client {
|
func newClient(baseCtx context.Context, dispatcher routing.Dispatcher, outbound string, isHTTPS bool) *http.Client {
|
||||||
dial := func(ctx context.Context, network, address string) (net.Conn, error) {
|
dial := func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||||
var conn net.Conn
|
var conn net.Conn
|
||||||
err := task.Run(ctx, func() error {
|
err := task.Run(ctx, func() error {
|
||||||
@@ -92,6 +91,7 @@ func newClient(baseCtx context.Context, dispatcher routing.Dispatcher, outbound
|
|||||||
Conn: conn,
|
Conn: conn,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
if isHTTPS {
|
||||||
return &http.Client{
|
return &http.Client{
|
||||||
Transport: &http2.Transport{
|
Transport: &http2.Transport{
|
||||||
DialTLSContext: func(ctx context.Context, network string, address string, cfg *tls.Config) (net.Conn, error) {
|
DialTLSContext: func(ctx context.Context, network string, address string, cfg *tls.Config) (net.Conn, error) {
|
||||||
@@ -114,12 +114,31 @@ func newClient(baseCtx context.Context, dispatcher routing.Dispatcher, outbound
|
|||||||
if req.URL.Scheme != "https" {
|
if req.URL.Scheme != "https" {
|
||||||
return errors.New("redirected to non-https URL: ", req.URL.String())
|
return errors.New("redirected to non-https URL: ", req.URL.String())
|
||||||
}
|
}
|
||||||
if len(via) >= 20 {
|
if len(via) >= 10 {
|
||||||
return errors.New("stopped after 20 redirects")
|
return errors.New("stopped after 10 redirects")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
Proxy: nil,
|
||||||
|
DisableKeepAlives: true,
|
||||||
|
DialContext: dial,
|
||||||
|
ResponseHeaderTimeout: idleTimeout,
|
||||||
|
},
|
||||||
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||||
|
if req.URL.Scheme != "https" {
|
||||||
|
return errors.New("redirected to non-https URL: ", req.URL.String())
|
||||||
|
}
|
||||||
|
if len(via) >= 10 {
|
||||||
|
return errors.New("stopped after 10 redirects")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *downloader) download(assets []*Asset) ([]stage, error) {
|
func (d *downloader) download(assets []*Asset) ([]stage, error) {
|
||||||
@@ -184,26 +203,27 @@ func (d *downloader) fetch(rawURL string, writer io.Writer) error {
|
|||||||
}
|
}
|
||||||
utils.TryDefaultHeadersWith(req.Header, "nav")
|
utils.TryDefaultHeadersWith(req.Header, "nav")
|
||||||
|
|
||||||
resp, err := d.httpsClient.Do(req)
|
var client *http.Client
|
||||||
|
if req.URL.Scheme == "https" {
|
||||||
|
client = d.httpsClient
|
||||||
|
} else {
|
||||||
|
client = d.httpClient
|
||||||
|
}
|
||||||
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
|
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
|
||||||
|
io.Copy(io.Discard, resp.Body)
|
||||||
return errors.New("unexpected status code: ", resp.StatusCode)
|
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, io.LimitReader(resp.Body, maxDownloadSize+1))
|
n, err := io.Copy(writer, resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if n > maxDownloadSize {
|
|
||||||
return errors.New("geodata download exceeds 256 MiB")
|
|
||||||
}
|
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
return errors.New("empty response body")
|
return errors.New("empty response body")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,13 +7,10 @@ import (
|
|||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
commongeodata "github.com/xtls/xray-core/common/geodata"
|
commongeodata "github.com/xtls/xray-core/common/geodata"
|
||||||
xlua "github.com/xtls/xray-core/common/lua"
|
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"
|
"github.com/yuin/gopher-lua/parse"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
@@ -34,11 +31,11 @@ func TestDownloadOneLuaGuard(t *testing.T) {
|
|||||||
if err := os.WriteFile(filepath.Join(dir, "asset.lua"), nil, 0o600); err != nil {
|
if err := os.WriteFile(filepath.Join(dir, "asset.lua"), nil, 0o600); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||||
_, _ = w.Write([]byte(tc.body))
|
_, _ = w.Write([]byte(tc.body))
|
||||||
}))
|
}))
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
d := &downloader{ctx: context.Background(), httpsClient: server.Client()}
|
d := &downloader{ctx: context.Background(), httpClient: server.Client()}
|
||||||
staged, err := d.downloadOne(&Asset{Url: server.URL, File: "asset.lua"})
|
staged, err := d.downloadOne(&Asset{Url: server.URL, File: "asset.lua"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -74,53 +71,3 @@ 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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+3
-23
@@ -2,16 +2,12 @@ package geodata
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/robfig/cron/v3"
|
"github.com/robfig/cron/v3"
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
commongeodata "github.com/xtls/xray-core/common/geodata"
|
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/core"
|
||||||
"github.com/xtls/xray-core/features/routing"
|
"github.com/xtls/xray-core/features/routing"
|
||||||
)
|
)
|
||||||
@@ -29,24 +25,6 @@ func New(ctx context.Context, config *Config) (*Instance, error) {
|
|||||||
if config.Cron == "" {
|
if config.Cron == "" {
|
||||||
return &Instance{}, nil
|
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{
|
g := &Instance{
|
||||||
assets: config.Assets,
|
assets: config.Assets,
|
||||||
@@ -66,7 +44,9 @@ func New(ctx context.Context, config *Config) (*Instance, error) {
|
|||||||
cron.WithChain(cron.SkipIfStillRunning(cron.DiscardLogger)),
|
cron.WithChain(cron.SkipIfStillRunning(cron.DiscardLogger)),
|
||||||
cron.WithLogger(cron.DiscardLogger),
|
cron.WithLogger(cron.DiscardLogger),
|
||||||
)
|
)
|
||||||
g.tasker.Schedule(schedule, cron.FuncJob(g.execute))
|
if _, err := g.tasker.AddFunc(config.Cron, g.execute); err != nil {
|
||||||
|
return nil, errors.New("invalid geodata cron").Base(err)
|
||||||
|
}
|
||||||
errors.LogInfo(ctx, "scheduled geodata reload with cron: ", config.Cron)
|
errors.LogInfo(ctx, "scheduled geodata reload with cron: ", config.Cron)
|
||||||
|
|
||||||
return g, nil
|
return g, nil
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.12
|
// protoc-gen-go v1.36.11
|
||||||
// protoc (unknown)
|
// protoc v6.33.5
|
||||||
// source: app/log/command/config.proto
|
// source: app/log/command/config.proto
|
||||||
|
|
||||||
package command
|
package command
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// - protoc-gen-go-grpc v1.6.2
|
// - protoc-gen-go-grpc v1.6.0
|
||||||
// - protoc (unknown)
|
// - protoc v6.33.5
|
||||||
// source: app/log/command/config.proto
|
// source: app/log/command/config.proto
|
||||||
|
|
||||||
package command
|
package command
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.12
|
// protoc-gen-go v1.36.11
|
||||||
// protoc (unknown)
|
// protoc v6.33.5
|
||||||
// source: app/log/config.proto
|
// source: app/log/config.proto
|
||||||
|
|
||||||
package log
|
package log
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.12
|
// protoc-gen-go v1.36.11
|
||||||
// protoc (unknown)
|
// protoc v6.33.5
|
||||||
// source: app/metrics/config.proto
|
// source: app/metrics/config.proto
|
||||||
|
|
||||||
package metrics
|
package metrics
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.12
|
// protoc-gen-go v1.36.11
|
||||||
// protoc (unknown)
|
// protoc v6.33.5
|
||||||
// source: app/observatory/burst/config.proto
|
// source: app/observatory/burst/config.proto
|
||||||
|
|
||||||
package burst
|
package burst
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.12
|
// protoc-gen-go v1.36.11
|
||||||
// protoc (unknown)
|
// protoc v6.33.5
|
||||||
// source: app/observatory/command/command.proto
|
// source: app/observatory/command/command.proto
|
||||||
|
|
||||||
package command
|
package command
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// - protoc-gen-go-grpc v1.6.2
|
// - protoc-gen-go-grpc v1.6.0
|
||||||
// - protoc (unknown)
|
// - protoc v6.33.5
|
||||||
// source: app/observatory/command/command.proto
|
// source: app/observatory/command/command.proto
|
||||||
|
|
||||||
package command
|
package command
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.12
|
// protoc-gen-go v1.36.11
|
||||||
// protoc (unknown)
|
// protoc v6.33.5
|
||||||
// source: app/observatory/config.proto
|
// source: app/observatory/config.proto
|
||||||
|
|
||||||
package observatory
|
package observatory
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.12
|
// protoc-gen-go v1.36.11
|
||||||
// protoc (unknown)
|
// protoc v6.33.5
|
||||||
// source: app/policy/config.proto
|
// source: app/policy/config.proto
|
||||||
|
|
||||||
package policy
|
package policy
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.12
|
// protoc-gen-go v1.36.11
|
||||||
// protoc (unknown)
|
// protoc v6.33.5
|
||||||
// source: app/proxyman/command/command.proto
|
// source: app/proxyman/command/command.proto
|
||||||
|
|
||||||
package command
|
package command
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// - protoc-gen-go-grpc v1.6.2
|
// - protoc-gen-go-grpc v1.6.0
|
||||||
// - protoc (unknown)
|
// - protoc v6.33.5
|
||||||
// source: app/proxyman/command/command.proto
|
// source: app/proxyman/command/command.proto
|
||||||
|
|
||||||
package command
|
package command
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.12
|
// protoc-gen-go v1.36.11
|
||||||
// protoc (unknown)
|
// protoc v6.33.5
|
||||||
// source: app/proxyman/config.proto
|
// source: app/proxyman/config.proto
|
||||||
|
|
||||||
package proxyman
|
package proxyman
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.12
|
// protoc-gen-go v1.36.11
|
||||||
// protoc (unknown)
|
// protoc v6.33.5
|
||||||
// source: app/reverse/config.proto
|
// source: app/reverse/config.proto
|
||||||
|
|
||||||
package reverse
|
package reverse
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.12
|
// protoc-gen-go v1.36.11
|
||||||
// protoc (unknown)
|
// protoc v6.33.5
|
||||||
// source: app/router/command/command.proto
|
// source: app/router/command/command.proto
|
||||||
|
|
||||||
package command
|
package command
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ message RoutingContext {
|
|||||||
// - attributes: Select connection's additional attributes.
|
// - attributes: Select connection's additional attributes.
|
||||||
// - outbound: Equivalent as "outbound" and "outbound_group", select both
|
// - outbound: Equivalent as "outbound" and "outbound_group", select both
|
||||||
// outbound tag and outbound group tags.
|
// outbound tag and outbound group tags.
|
||||||
//
|
|
||||||
// * If FieldSelectors is left empty, all fields will be returned.
|
// * If FieldSelectors is left empty, all fields will be returned.
|
||||||
message SubscribeRoutingStatsRequest {
|
message SubscribeRoutingStatsRequest {
|
||||||
repeated string FieldSelectors = 1;
|
repeated string FieldSelectors = 1;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// - protoc-gen-go-grpc v1.6.2
|
// - protoc-gen-go-grpc v1.6.0
|
||||||
// - protoc (unknown)
|
// - protoc v6.33.5
|
||||||
// source: app/router/command/command.proto
|
// source: app/router/command/command.proto
|
||||||
|
|
||||||
package command
|
package command
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.12
|
// protoc-gen-go v1.36.11
|
||||||
// protoc (unknown)
|
// protoc v6.33.5
|
||||||
// source: app/router/config.proto
|
// source: app/router/config.proto
|
||||||
|
|
||||||
package router
|
package router
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.12
|
// protoc-gen-go v1.36.11
|
||||||
// protoc (unknown)
|
// protoc v6.33.5
|
||||||
// source: app/stats/command/command.proto
|
// source: app/stats/command/command.proto
|
||||||
|
|
||||||
package command
|
package command
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// - protoc-gen-go-grpc v1.6.2
|
// - protoc-gen-go-grpc v1.6.0
|
||||||
// - protoc (unknown)
|
// - protoc v6.33.5
|
||||||
// source: app/stats/command/command.proto
|
// source: app/stats/command/command.proto
|
||||||
|
|
||||||
package command
|
package command
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.12
|
// protoc-gen-go v1.36.11
|
||||||
// protoc (unknown)
|
// protoc v6.33.5
|
||||||
// source: app/stats/config.proto
|
// source: app/stats/config.proto
|
||||||
|
|
||||||
package stats
|
package stats
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.12
|
// protoc-gen-go v1.36.11
|
||||||
// protoc (unknown)
|
// protoc v6.33.5
|
||||||
// source: app/version/config.proto
|
// source: app/version/config.proto
|
||||||
|
|
||||||
package version
|
package version
|
||||||
|
|||||||
+17
-59
@@ -8,65 +8,23 @@ import (
|
|||||||
|
|
||||||
var privateIPMatcher = sync.OnceValue(func() IPMatcher {
|
var privateIPMatcher = sync.OnceValue(func() IPMatcher {
|
||||||
return common.Must2(IPReg.BuildIPMatcher(common.Must2(ParseIPRules([]string{
|
return common.Must2(IPReg.BuildIPMatcher(common.Must2(ParseIPRules([]string{
|
||||||
// https://www.iana.org/assignments/iana-ipv4-special-registry
|
"0.0.0.0/8",
|
||||||
"0.0.0.0/8", // "This network"
|
"10.0.0.0/8",
|
||||||
"0.0.0.0/32", // "This host on this network"
|
"100.64.0.0/10",
|
||||||
"10.0.0.0/8", // Private-Use
|
"127.0.0.0/8",
|
||||||
"100.64.0.0/10", // Shared Address Space
|
"169.254.0.0/16",
|
||||||
"127.0.0.0/8", // Loopback
|
"172.16.0.0/12",
|
||||||
"169.254.0.0/16", // Link Local
|
"192.0.0.0/24",
|
||||||
"172.16.0.0/12", // Private-Use
|
"192.0.2.0/24",
|
||||||
// "192.0.0.0/24", // IETF Protocol Assignments
|
"192.88.99.0/24",
|
||||||
"192.0.0.0/29", // IPv4 Service Continuity Prefix
|
"192.168.0.0/16",
|
||||||
"192.0.0.8/32", // IPv4 dummy address
|
"198.18.0.0/15",
|
||||||
"192.0.0.9/32", // Port Control Protocol Anycast
|
"198.51.100.0/24",
|
||||||
"192.0.0.10/32", // Traversal Using Relays around NAT Anycast
|
"203.0.113.0/24",
|
||||||
"192.0.0.170/32", // NAT64/DNS64 Discovery
|
"224.0.0.0/3",
|
||||||
"192.0.0.171/32", // NAT64/DNS64 Discovery
|
"::/127",
|
||||||
"192.0.2.0/24", // Documentation (TEST-NET-1)
|
"fc00::/7",
|
||||||
// "192.31.196.0/24", // AS112-v4
|
"fe80::/10",
|
||||||
"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",
|
"ff00::/8",
|
||||||
}))))
|
}))))
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.12
|
// protoc-gen-go v1.36.11
|
||||||
// protoc (unknown)
|
// protoc v6.33.5
|
||||||
// source: common/geodata/geodat.proto
|
// source: common/geodata/geodat.proto
|
||||||
|
|
||||||
package geodata
|
package geodata
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.12
|
// protoc-gen-go v1.36.11
|
||||||
// protoc (unknown)
|
// protoc v6.33.5
|
||||||
// source: common/log/log.proto
|
// source: common/log/log.proto
|
||||||
|
|
||||||
package log
|
package log
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
|
|
||||||
"github.com/xtls/xray-core/common/base64"
|
"github.com/xtls/xray-core/common/base64"
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/platform/filesystem"
|
"github.com/xtls/xray-core/common/platform"
|
||||||
glua "github.com/yuin/gopher-lua"
|
glua "github.com/yuin/gopher-lua"
|
||||||
"github.com/yuin/gopher-lua/parse"
|
"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.
|
// TODO: Restore inline script support and re-enable the related tests.
|
||||||
return nil, errors.New("currently unavailable")
|
return nil, errors.New("currently unavailable")
|
||||||
code, err = base64.Decode(script)
|
code, err = base64.Decode(script)
|
||||||
} else if name, err = filesystem.ResolveLuaFile(script); err == nil {
|
} else if name, err = platform.ResolveLuaFile(script); err == nil {
|
||||||
code, err = os.ReadFile(name)
|
code, err = os.ReadFile(name)
|
||||||
// TODO: Restore privileged signature authorization and re-enable the related tests and CLI entries.
|
// TODO: Restore privileged signature authorization and re-enable the related tests and CLI entries.
|
||||||
// if err == nil {
|
// if err == nil {
|
||||||
|
|||||||
@@ -187,20 +187,19 @@ func TestCompileInlineDiagnostics(t *testing.T) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
func TestCompileFileExtension(t *testing.T) {
|
func TestCompileFileExtension(t *testing.T) {
|
||||||
for _, name := range []string{"script", "script.dat", "script.lua.txt"} {
|
for _, name := range []string{"script", "script.dat", "script.lua.txt", "script.LUA"} {
|
||||||
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)
|
path := filepath.Join(t.TempDir(), name)
|
||||||
if err := os.WriteFile(path, []byte("value = 42"), 0o600); err != nil {
|
if err := os.WriteFile(path, []byte("value = 42"), 0o600); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if program, err := Compile(path); program == nil || err != nil {
|
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 successful compilation", path, program, err)
|
t.Fatalf("Compile(%q) = %v, %v; want nil program and .lua extension error", 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) {
|
func TestCompileRejectsReadFailure(t *testing.T) {
|
||||||
@@ -235,6 +234,10 @@ func writeTestScript(t *testing.T, code string) string {
|
|||||||
if err := os.WriteFile(path, []byte(code), 0o600); err != nil {
|
if err := os.WriteFile(path, []byte(code), 0o600); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
path, err := filepath.EvalSymlinks(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.12
|
// protoc-gen-go v1.36.11
|
||||||
// protoc (unknown)
|
// protoc v6.33.5
|
||||||
// source: common/net/address.proto
|
// source: common/net/address.proto
|
||||||
|
|
||||||
package net
|
package net
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.12
|
// protoc-gen-go v1.36.11
|
||||||
// protoc (unknown)
|
// protoc v6.33.5
|
||||||
// source: common/net/destination.proto
|
// source: common/net/destination.proto
|
||||||
|
|
||||||
package net
|
package net
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ package net
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"encoding/binary"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
@@ -45,7 +45,10 @@ func FindProcess(network, srcIP string, srcPort uint16, destIP string, destPort
|
|||||||
panic("Unsupported network type for process lookup.")
|
panic("Unsupported network type for process lookup.")
|
||||||
}
|
}
|
||||||
|
|
||||||
targetHexAddr := formatProcNetAddress(net.ParseIP(srcIP), Port(srcPort))
|
targetHexAddr, err := formatLittleEndianString(net.ParseIP(srcIP), Port(srcPort))
|
||||||
|
if err != nil {
|
||||||
|
return 0, "", "", errors.New("failed to format address: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
portFallback := ""
|
portFallback := ""
|
||||||
if network == "udp" {
|
if network == "udp" {
|
||||||
@@ -84,21 +87,24 @@ func FindProcess(network, srcIP string, srcPort uint16, destIP string, destPort
|
|||||||
return pid, procName, absPath, nil
|
return pid, procName, absPath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatProcNetAddress(addr net.IP, port Port) string {
|
func formatLittleEndianString(addr net.IP, port Port) (string, error) {
|
||||||
|
ip := addr
|
||||||
var ipBytes []byte
|
var ipBytes []byte
|
||||||
if ipv4 := addr.To4(); ipv4 != nil {
|
if ip.To4() != nil {
|
||||||
ipBytes = ipv4
|
ipBytes = ip.To4()
|
||||||
} else {
|
} else {
|
||||||
ipBytes = addr.To16()
|
ipBytes = ip.To16()
|
||||||
|
}
|
||||||
|
if ipBytes == nil {
|
||||||
|
return "", errors.New("invalid IP format for ", addr, ": ", ip)
|
||||||
}
|
}
|
||||||
|
|
||||||
var formatted strings.Builder
|
for i, j := 0, len(ipBytes)-1; i < j; i, j = i+1, j-1 {
|
||||||
formatted.Grow(len(ipBytes)*2 + 5)
|
ipBytes[i], ipBytes[j] = ipBytes[j], ipBytes[i]
|
||||||
for i := 0; i < len(ipBytes); i += 4 {
|
|
||||||
fmt.Fprintf(&formatted, "%08X", binary.NativeEndian.Uint32(ipBytes[i:i+4]))
|
|
||||||
}
|
}
|
||||||
fmt.Fprintf(&formatted, ":%04X", uint16(port))
|
portHex := fmt.Sprintf("%04X", uint16(port))
|
||||||
return formatted.String()
|
ipHex := strings.ToUpper(hex.EncodeToString(ipBytes))
|
||||||
|
return fmt.Sprintf("%s:%s", ipHex, portHex), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func findInodeInFiles(filePaths []string, targetHexAddr, portFallback string) (string, error) {
|
func findInodeInFiles(filePaths []string, targetHexAddr, portFallback string) (string, error) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.12
|
// protoc-gen-go v1.36.11
|
||||||
// protoc (unknown)
|
// protoc v6.33.5
|
||||||
// source: common/net/network.proto
|
// source: common/net/network.proto
|
||||||
|
|
||||||
package net
|
package net
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.12
|
// protoc-gen-go v1.36.11
|
||||||
// protoc (unknown)
|
// protoc v6.33.5
|
||||||
// source: common/net/port.proto
|
// source: common/net/port.proto
|
||||||
|
|
||||||
package net
|
package net
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/buf"
|
"github.com/xtls/xray-core/common/buf"
|
||||||
"github.com/xtls/xray-core/common/platform"
|
"github.com/xtls/xray-core/common/platform"
|
||||||
@@ -27,6 +26,14 @@ func ReadFile(path string) ([]byte, error) {
|
|||||||
return buf.ReadAllToBytes(reader)
|
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) {
|
func OpenAsset(file string) (io.ReadCloser, int64, error) {
|
||||||
path, info, err := getAssetFileLocation(file)
|
path, info, err := getAssetFileLocation(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -36,6 +43,11 @@ func OpenAsset(file string) (io.ReadCloser, int64, error) {
|
|||||||
return r, info.Size(), err
|
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) {
|
func ResolveAsset(file string) (string, error) {
|
||||||
path, _, err := getAssetFileLocation(file)
|
path, _, err := getAssetFileLocation(file)
|
||||||
return path, err
|
return path, err
|
||||||
@@ -67,29 +79,17 @@ func ReadCert(file string) ([]byte, error) {
|
|||||||
return ReadFile(platform.GetCertLocation(file))
|
return ReadFile(platform.GetCertLocation(file))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResolveLuaFile finds a local .lua script and returns its absolute path.
|
func CopyFile(dst string, src string) error {
|
||||||
// Relative paths: XRAY_LOCATION_CONFDIR > XRAY_LOCATION_CONFIG > working dir > executable dir.
|
bytes, err := ReadFile(src)
|
||||||
func ResolveLuaFile(path string) (string, error) {
|
if err != nil {
|
||||||
if !strings.EqualFold(filepath.Ext(path), ".lua") {
|
return err
|
||||||
return "", errors.New("Lua file extension must be .lua")
|
|
||||||
}
|
}
|
||||||
if filepath.IsAbs(path) {
|
f, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY, 0o644)
|
||||||
return path, nil
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
for _, dir := range []string{
|
defer f.Close()
|
||||||
platform.GetConfDirPath(),
|
|
||||||
platform.NewEnvFlag(platform.ConfigLocation).GetValue(func() string { return "" }),
|
_, err = f.Write(bytes)
|
||||||
".",
|
return err
|
||||||
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,17 +1,13 @@
|
|||||||
package filesystem_test
|
package filesystem_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
|
||||||
"github.com/xtls/xray-core/common/platform"
|
|
||||||
. "github.com/xtls/xray-core/common/platform/filesystem"
|
. "github.com/xtls/xray-core/common/platform/filesystem"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestResolveAssetRejectsInvalidPath(t *testing.T) {
|
func TestStatAssetRejectsInvalidPath(t *testing.T) {
|
||||||
for _, file := range []string{
|
for _, file := range []string{
|
||||||
"",
|
"",
|
||||||
".",
|
".",
|
||||||
@@ -29,49 +25,8 @@ func TestResolveAssetRejectsInvalidPath(t *testing.T) {
|
|||||||
`nested\..\geoip.dat`,
|
`nested\..\geoip.dat`,
|
||||||
filepath.Join(t.TempDir(), "geoip.dat"),
|
filepath.Join(t.TempDir(), "geoip.dat"),
|
||||||
} {
|
} {
|
||||||
if _, err := ResolveAsset(file); err == nil {
|
if _, err := StatAsset(file); err == nil {
|
||||||
t.Fatalf("expected error for %q", file)
|
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
|
// GetAssetLocation searches for `file` in the env dir, the executable dir, and certain locations
|
||||||
func GetAssetLocation(file string) string {
|
func GetAssetLocation(file string) string {
|
||||||
assetPath := NewEnvFlag(AssetLocation).GetValue(GetExecutableDir)
|
assetPath := NewEnvFlag(AssetLocation).GetValue(getExecutableDir)
|
||||||
defPath := filepath.Join(assetPath, file)
|
defPath := filepath.Join(assetPath, file)
|
||||||
for _, p := range []string{
|
for _, p := range []string{
|
||||||
defPath,
|
defPath,
|
||||||
@@ -36,6 +36,6 @@ func GetAssetLocation(file string) string {
|
|||||||
|
|
||||||
// GetCertLocation searches for `file` in the env dir and the executable dir
|
// GetCertLocation searches for `file` in the env dir and the executable dir
|
||||||
func GetCertLocation(file string) string {
|
func GetCertLocation(file string) string {
|
||||||
certPath := NewEnvFlag(CertLocation).GetValue(GetExecutableDir)
|
certPath := NewEnvFlag(CertLocation).GetValue(getExecutableDir)
|
||||||
return filepath.Join(certPath, file)
|
return filepath.Join(certPath, file)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package platform // import "github.com/xtls/xray-core/common/platform"
|
package platform // import "github.com/xtls/xray-core/common/platform"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -72,7 +74,7 @@ func NormalizeEnvName(name string) string {
|
|||||||
return strings.ReplaceAll(strings.ToUpper(strings.TrimSpace(name)), ".", "_")
|
return strings.ReplaceAll(strings.ToUpper(strings.TrimSpace(name)), ".", "_")
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetExecutableDir() string {
|
func getExecutableDir() string {
|
||||||
exec, err := os.Executable()
|
exec, err := os.Executable()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
@@ -81,7 +83,7 @@ func GetExecutableDir() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetConfigurationPath() string {
|
func GetConfigurationPath() string {
|
||||||
configPath := NewEnvFlag(ConfigLocation).GetValue(GetExecutableDir)
|
configPath := NewEnvFlag(ConfigLocation).GetValue(getExecutableDir)
|
||||||
return filepath.Join(configPath, "config.json")
|
return filepath.Join(configPath, "config.json")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,3 +92,64 @@ func GetConfDirPath() string {
|
|||||||
configPath := NewEnvFlag(ConfdirLocation).GetValue(func() string { return "" })
|
configPath := NewEnvFlag(ConfdirLocation).GetValue(func() string { return "" })
|
||||||
return configPath
|
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,9 +1,11 @@
|
|||||||
package platform_test
|
package platform_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
@@ -64,3 +66,89 @@ 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
|
// GetAssetLocation searches for `file` in the env dir and the executable dir
|
||||||
func GetAssetLocation(file string) string {
|
func GetAssetLocation(file string) string {
|
||||||
assetPath := NewEnvFlag(AssetLocation).GetValue(GetExecutableDir)
|
assetPath := NewEnvFlag(AssetLocation).GetValue(getExecutableDir)
|
||||||
return filepath.Join(assetPath, file)
|
return filepath.Join(assetPath, file)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCertLocation searches for `file` in the env dir and the executable dir
|
// GetCertLocation searches for `file` in the env dir and the executable dir
|
||||||
func GetCertLocation(file string) string {
|
func GetCertLocation(file string) string {
|
||||||
certPath := NewEnvFlag(CertLocation).GetValue(GetExecutableDir)
|
certPath := NewEnvFlag(CertLocation).GetValue(getExecutableDir)
|
||||||
return filepath.Join(certPath, file)
|
return filepath.Join(certPath, file)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.12
|
// protoc-gen-go v1.36.11
|
||||||
// protoc (unknown)
|
// protoc v6.33.5
|
||||||
// source: common/protocol/headers.proto
|
// source: common/protocol/headers.proto
|
||||||
|
|
||||||
package protocol
|
package protocol
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.12
|
// protoc-gen-go v1.36.11
|
||||||
// protoc (unknown)
|
// protoc v6.33.5
|
||||||
// source: common/protocol/server_spec.proto
|
// source: common/protocol/server_spec.proto
|
||||||
|
|
||||||
package protocol
|
package protocol
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.12
|
// protoc-gen-go v1.36.11
|
||||||
// protoc (unknown)
|
// protoc v6.33.5
|
||||||
// source: common/protocol/user.proto
|
// source: common/protocol/user.proto
|
||||||
|
|
||||||
package protocol
|
package protocol
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.12
|
// protoc-gen-go v1.36.11
|
||||||
// protoc (unknown)
|
// protoc v6.33.5
|
||||||
// source: common/serial/typed_message.proto
|
// source: common/serial/typed_message.proto
|
||||||
|
|
||||||
package serial
|
package serial
|
||||||
|
|||||||
+30
-29
@@ -3,11 +3,7 @@ package signal
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
|
||||||
"github.com/xtls/xray-core/common/task"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ActivityUpdater interface {
|
type ActivityUpdater interface {
|
||||||
@@ -17,10 +13,10 @@ type ActivityUpdater interface {
|
|||||||
type ActivityTimer struct {
|
type ActivityTimer struct {
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
updated chan struct{}
|
updated chan struct{}
|
||||||
checkTask *task.Periodic
|
checker *time.Timer
|
||||||
|
timeout time.Duration
|
||||||
onTimeout func()
|
onTimeout func()
|
||||||
consumed atomic.Bool
|
consumed bool
|
||||||
once sync.Once
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *ActivityTimer) Update() {
|
func (t *ActivityTimer) Update() {
|
||||||
@@ -30,30 +26,29 @@ func (t *ActivityTimer) Update() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *ActivityTimer) check() error {
|
func (t *ActivityTimer) check() bool {
|
||||||
select {
|
select {
|
||||||
case <-t.updated:
|
case <-t.updated:
|
||||||
|
return true
|
||||||
default:
|
default:
|
||||||
t.finish()
|
t.finish()
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *ActivityTimer) finish() {
|
func (t *ActivityTimer) finish() {
|
||||||
t.once.Do(func() {
|
|
||||||
t.consumed.Store(true)
|
|
||||||
t.mu.Lock()
|
t.mu.Lock()
|
||||||
defer t.mu.Unlock()
|
if t.consumed {
|
||||||
|
t.mu.Unlock()
|
||||||
common.CloseIfExists(t.checkTask)
|
return
|
||||||
|
}
|
||||||
|
t.checker.Stop()
|
||||||
|
t.consumed = true
|
||||||
|
t.mu.Unlock()
|
||||||
t.onTimeout()
|
t.onTimeout()
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *ActivityTimer) SetTimeout(timeout time.Duration) {
|
func (t *ActivityTimer) SetTimeout(timeout time.Duration) {
|
||||||
if t.consumed.Load() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if timeout == 0 {
|
if timeout == 0 {
|
||||||
t.finish()
|
t.finish()
|
||||||
return
|
return
|
||||||
@@ -61,25 +56,31 @@ func (t *ActivityTimer) SetTimeout(timeout time.Duration) {
|
|||||||
|
|
||||||
t.mu.Lock()
|
t.mu.Lock()
|
||||||
defer t.mu.Unlock()
|
defer t.mu.Unlock()
|
||||||
// double check, just in case
|
if t.consumed {
|
||||||
if t.consumed.Load() {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
newCheckTask := &task.Periodic{
|
t.timeout = timeout
|
||||||
Interval: timeout,
|
t.checker.Reset(timeout)
|
||||||
Execute: t.check,
|
|
||||||
}
|
|
||||||
common.CloseIfExists(t.checkTask)
|
|
||||||
t.checkTask = newCheckTask
|
|
||||||
t.Update()
|
|
||||||
common.Must(newCheckTask.Start())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func CancelAfterInactivity(ctx context.Context, cancel context.CancelFunc, timeout time.Duration) *ActivityTimer {
|
func CancelAfterInactivity(ctx context.Context, cancel context.CancelFunc, timeout time.Duration) *ActivityTimer {
|
||||||
timer := &ActivityTimer{
|
timer := &ActivityTimer{
|
||||||
updated: make(chan struct{}, 1),
|
updated: make(chan struct{}, 1),
|
||||||
onTimeout: cancel,
|
onTimeout: cancel,
|
||||||
|
timeout: timeout,
|
||||||
|
}
|
||||||
|
timer.checker = time.AfterFunc(timeout, func() {
|
||||||
|
if timer.check() {
|
||||||
|
timer.mu.Lock()
|
||||||
|
defer timer.mu.Unlock()
|
||||||
|
if timer.consumed {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
timer.checker.Reset(timer.timeout)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if timeout == 0 {
|
||||||
|
timer.SetTimeout(0)
|
||||||
}
|
}
|
||||||
timer.SetTimeout(timeout)
|
|
||||||
return timer
|
return timer
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.12
|
// protoc-gen-go v1.36.11
|
||||||
// protoc (unknown)
|
// protoc v6.33.5
|
||||||
// source: core/config.proto
|
// source: core/config.proto
|
||||||
|
|
||||||
package core
|
package core
|
||||||
|
|||||||
+1
-1
@@ -20,7 +20,7 @@ import (
|
|||||||
var (
|
var (
|
||||||
Version_x byte = 26
|
Version_x byte = 26
|
||||||
Version_y byte = 10
|
Version_y byte = 10
|
||||||
Version_z byte = 29
|
Version_z byte = 17
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
package core
|
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 ./..
|
//go:generate go run ../infra/vprotogen/main.go -pwd ./..
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package localdns
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/netip"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -101,15 +100,6 @@ func New() *Client {
|
|||||||
if internet.IsSkippedDNSServer(address) {
|
if internet.IsSkippedDNSServer(address) {
|
||||||
return nil, errors.New("skipped DNS server ", 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)
|
return d.DialContext(ctx, network, address)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ go 1.27
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/apernet/quic-go v0.61.1-0.20260806010916-184d081eef3e
|
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/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344
|
||||||
github.com/golang/mock v1.7.0-rc.1
|
github.com/golang/mock v1.7.0-rc.1
|
||||||
github.com/google/go-cmp v0.7.0
|
github.com/google/go-cmp v0.7.0
|
||||||
@@ -15,7 +14,7 @@ require (
|
|||||||
github.com/miekg/dns v1.1.73
|
github.com/miekg/dns v1.1.73
|
||||||
github.com/pelletier/go-toml v1.9.5
|
github.com/pelletier/go-toml v1.9.5
|
||||||
github.com/pion/stun/v3 v3.1.7
|
github.com/pion/stun/v3 v3.1.7
|
||||||
github.com/pires/go-proxyproto v0.16.0
|
github.com/pires/go-proxyproto v0.15.0
|
||||||
github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af
|
github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/stretchr/testify v1.12.1
|
github.com/stretchr/testify v1.12.1
|
||||||
@@ -59,12 +58,6 @@ require (
|
|||||||
golang.org/x/text v0.43.0 // indirect
|
golang.org/x/text v0.43.0 // indirect
|
||||||
golang.org/x/tools v0.51.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-20260706201446-f0a921348800 // indirect
|
||||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 // indirect
|
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0 // 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,8 +2,6 @@ github.com/andybalholm/brotli v1.2.3 h1:8H1qwOkl2LPfjf3YezB90JnCliZb6SInJ/OJkEbA
|
|||||||
github.com/andybalholm/brotli v1.2.3/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
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 h1:5mgtR5gwIgBKMiGI1QdXldZZ+SNor06Nbu1wCBulQBg=
|
||||||
github.com/apernet/quic-go v0.61.1-0.20260806010916-184d081eef3e/go.mod h1:x7qxEvX6MCVtDuBKHj3E+88+BtrbEMuAL5qGUKItjW8=
|
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/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/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
@@ -61,8 +59,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/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 h1:8S+nF2reM2cJuqC6g78OVy2BBgmbdns+acx3jA97BvQ=
|
||||||
github.com/pion/transport/v4 v4.1.0/go.mod h1:06hFI+jCFcok2X2MekVufNZ/uzNZXivGBPfviSVcjgM=
|
github.com/pion/transport/v4 v4.1.0/go.mod h1:06hFI+jCFcok2X2MekVufNZ/uzNZXivGBPfviSVcjgM=
|
||||||
github.com/pires/go-proxyproto v0.16.0 h1:9KYT/fM3wpjT8bdMEj/7FBcoiUG1KyzEjRYnPu+vz7A=
|
github.com/pires/go-proxyproto v0.15.0 h1:dTshmNbFm/D+0+sbrxUuddPOZ5Y0B7c5NhtsBkm6LqI=
|
||||||
github.com/pires/go-proxyproto v0.16.0/go.mod h1:n7yhg8yIfNl3KgYDCwBzmeUHNpKTVnUhcWpc7rV4RW4=
|
github.com/pires/go-proxyproto v0.15.0/go.mod h1:OXsCrKwrK2tXS9YrI5tkHx5xaQlO8FH3lFW76orFh24=
|
||||||
github.com/quic-go/go-ossfuzz-seeds v0.1.0 h1:APacT+iIaNF6fd8AGEiN3bT/Jtkd2jz4v4TzM7MFjy0=
|
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/go-ossfuzz-seeds v0.1.0/go.mod h1:3IOHRbJIc+L6YKMwfDtJAM9Vj9k0YY4muhuyUYk5tbk=
|
||||||
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||||
@@ -154,8 +152,6 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800 h1:
|
|||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
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 h1:soMyaPJ8pAak5PIQ0DGBUir0XRo2fRoMqhNWMLlLxO0=
|
||||||
google.golang.org/grpc v1.84.0/go.mod h1:ljCht0DrxQrXBDRTZp52Qxh3Ffk8CdYm2sj4O2QN2C0=
|
google.golang.org/grpc v1.84.0/go.mod h1:ljCht0DrxQrXBDRTZp52Qxh3Ffk8CdYm2sj4O2QN2C0=
|
||||||
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 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
|
||||||
google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
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=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
|||||||
@@ -91,7 +91,6 @@ func (c *FreedomConfig) Build() (proto.Message, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if c.Fragment != nil {
|
if c.Fragment != nil {
|
||||||
errors.PrintDeprecatedFeatureWarning(`"fragment" in Freedom`, `"fragment" in "streamSettings.finalmask.tcp"`)
|
|
||||||
config.Fragment = new(freedom.Fragment)
|
config.Fragment = new(freedom.Fragment)
|
||||||
|
|
||||||
switch strings.ToLower(c.Fragment.Packets) {
|
switch strings.ToLower(c.Fragment.Packets) {
|
||||||
@@ -148,7 +147,6 @@ func (c *FreedomConfig) Build() (proto.Message, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if c.Noises != nil {
|
if c.Noises != nil {
|
||||||
errors.PrintDeprecatedFeatureWarning(`"noises" in Freedom`, `"noise" in "streamSettings.finalmask.udp"`)
|
|
||||||
for _, n := range c.Noises {
|
for _, n := range c.Noises {
|
||||||
NConfig, err := ParseNoise(n)
|
NConfig, err := ParseNoise(n)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"github.com/robfig/cron/v3"
|
"github.com/robfig/cron/v3"
|
||||||
"github.com/xtls/xray-core/app/geodata"
|
"github.com/xtls/xray-core/app/geodata"
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/common/platform/filesystem"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -18,6 +19,9 @@ func (c *GeodataAssetConfig) Build() (*geodata.Asset, error) {
|
|||||||
if err := validateHTTPS(c.URL); err != nil {
|
if err := validateHTTPS(c.URL); err != nil {
|
||||||
return nil, errors.New("invalid geodata asset url: ", c.URL).Base(err)
|
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{
|
return &geodata.Asset{
|
||||||
Url: c.URL,
|
Url: c.URL,
|
||||||
File: c.File,
|
File: c.File,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package conf_test
|
package conf_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/app/geodata"
|
"github.com/xtls/xray-core/app/geodata"
|
||||||
@@ -8,6 +9,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestGeodataConfig(t *testing.T) {
|
func TestGeodataConfig(t *testing.T) {
|
||||||
|
t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
|
||||||
|
|
||||||
creator := func() Buildable {
|
creator := func() Buildable {
|
||||||
return new(GeodataConfig)
|
return new(GeodataConfig)
|
||||||
}
|
}
|
||||||
@@ -35,7 +38,27 @@ 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) {
|
func TestGeodataAssetConfigInvalidURL(t *testing.T) {
|
||||||
|
t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
|
||||||
|
|
||||||
for _, rawURL := range []string{
|
for _, rawURL := range []string{
|
||||||
"",
|
"",
|
||||||
"http://example.com/geoip.dat",
|
"http://example.com/geoip.dat",
|
||||||
|
|||||||
@@ -15,11 +15,9 @@ import (
|
|||||||
|
|
||||||
googleuuid "github.com/google/uuid"
|
googleuuid "github.com/google/uuid"
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/geodata"
|
|
||||||
xlua "github.com/xtls/xray-core/common/lua"
|
xlua "github.com/xtls/xray-core/common/lua"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/transport/internet"
|
"github.com/xtls/xray-core/transport/internet"
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask/filter"
|
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask/fragment"
|
"github.com/xtls/xray-core/transport/internet/finalmask/fragment"
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask/header/custom"
|
"github.com/xtls/xray-core/transport/internet/finalmask/header/custom"
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask/mkcp/aes128gcm"
|
"github.com/xtls/xray-core/transport/internet/finalmask/mkcp/aes128gcm"
|
||||||
@@ -76,7 +74,6 @@ var (
|
|||||||
customVarNamePattern = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`)
|
customVarNamePattern = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`)
|
||||||
|
|
||||||
tcpmaskLoader = NewJSONConfigLoader(ConfigCreatorCache{
|
tcpmaskLoader = NewJSONConfigLoader(ConfigCreatorCache{
|
||||||
"filter": func() interface{} { return new(Filter) },
|
|
||||||
"header-custom": func() interface{} { return new(HeaderCustomTCP) },
|
"header-custom": func() interface{} { return new(HeaderCustomTCP) },
|
||||||
"fragment": func() interface{} { return new(FragmentMask) },
|
"fragment": func() interface{} { return new(FragmentMask) },
|
||||||
"sudoku": func() interface{} { return new(Sudoku) },
|
"sudoku": func() interface{} { return new(Sudoku) },
|
||||||
@@ -84,7 +81,6 @@ var (
|
|||||||
}, "type", "settings")
|
}, "type", "settings")
|
||||||
|
|
||||||
udpmaskLoader = NewJSONConfigLoader(ConfigCreatorCache{
|
udpmaskLoader = NewJSONConfigLoader(ConfigCreatorCache{
|
||||||
"filter": func() interface{} { return new(Filter) },
|
|
||||||
"header-custom": func() interface{} { return new(HeaderCustomUDP) },
|
"header-custom": func() interface{} { return new(HeaderCustomUDP) },
|
||||||
"mkcp-legacy": func() interface{} { return new(MkcpLegacy) },
|
"mkcp-legacy": func() interface{} { return new(MkcpLegacy) },
|
||||||
"noise": func() interface{} { return new(NoiseMask) },
|
"noise": func() interface{} { return new(NoiseMask) },
|
||||||
@@ -97,23 +93,6 @@ var (
|
|||||||
}, "type", "settings")
|
}, "type", "settings")
|
||||||
)
|
)
|
||||||
|
|
||||||
type Filter struct {
|
|
||||||
BlockedSourceIPs []string `json:"blockedSourceIPs"`
|
|
||||||
AllowedSourceIPs []string `json:"allowedSourceIPs"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Filter) Build() (proto.Message, error) {
|
|
||||||
rules, err := geodata.ParseIPRules(c.BlockedSourceIPs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New(`invalid "blockedSourceIPs"`).Base(err)
|
|
||||||
}
|
|
||||||
allowed, err := geodata.ParseIPRules(c.AllowedSourceIPs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New(`invalid "allowedSourceIPs"`).Base(err)
|
|
||||||
}
|
|
||||||
return &filter.Config{BlockedSourceIps: rules, AllowedSourceIps: allowed}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type TCPItem struct {
|
type TCPItem struct {
|
||||||
Delay Int32Range `json:"delay"`
|
Delay Int32Range `json:"delay"`
|
||||||
Rand int32 `json:"rand"`
|
Rand int32 `json:"rand"`
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
package conf_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/geodata"
|
|
||||||
. "github.com/xtls/xray-core/infra/conf"
|
|
||||||
"github.com/xtls/xray-core/transport/internet"
|
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask/filter"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestFinalMaskFilter(t *testing.T) {
|
|
||||||
for _, network := range []string{"tcp", "udp"} {
|
|
||||||
for _, test := range []struct {
|
|
||||||
name, settings string
|
|
||||||
blocked, allowed int
|
|
||||||
}{
|
|
||||||
{"blacklist", `{"blockedSourceIPs":["192.0.2.0/24"]}`, 1, 0},
|
|
||||||
{"whitelist", `{"allowedSourceIPs":["192.0.2.0/24"]}`, 0, 1},
|
|
||||||
{"both", `{"blockedSourceIPs":["192.0.2.0/24"],"allowedSourceIPs":["192.0.2.0/24"]}`, 1, 1},
|
|
||||||
{"empty_whitelist", `{"allowedSourceIPs":[]}`, 0, 0},
|
|
||||||
{"empty_blacklist", `{"blockedSourceIPs":[]}`, 0, 0},
|
|
||||||
} {
|
|
||||||
t.Run(network+"/"+test.name, func(t *testing.T) {
|
|
||||||
input := fmt.Sprintf(`{"finalmask":{"%s":[{"type":"filter","settings":%s}]}}`, network, test.settings)
|
|
||||||
var config StreamConfig
|
|
||||||
if err := json.Unmarshal([]byte(input), &config); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
built, err := config.Build()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
masks := built.Tcpmasks
|
|
||||||
if network == "udp" {
|
|
||||||
masks = built.Udpmasks
|
|
||||||
}
|
|
||||||
if len(masks) != 1 {
|
|
||||||
t.Fatalf("expected one mask, got %d", len(masks))
|
|
||||||
}
|
|
||||||
instance, err := masks[0].GetInstance()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
filter, ok := instance.(*filter.Config)
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("unexpected mask type: %T", instance)
|
|
||||||
}
|
|
||||||
if len(filter.BlockedSourceIps) != test.blocked || len(filter.AllowedSourceIps) != test.allowed {
|
|
||||||
t.Fatalf("unexpected filter policy: %v", filter)
|
|
||||||
}
|
|
||||||
for _, rules := range [][]*geodata.IPRule{filter.BlockedSourceIps, filter.AllowedSourceIps} {
|
|
||||||
for _, rule := range rules {
|
|
||||||
cidr := rule.GetCustom().GetCidr()
|
|
||||||
if cidr.GetPrefix() != 24 || !net.IP(cidr.GetIp()).Equal(net.IPv4(192, 0, 2, 0)) {
|
|
||||||
t.Fatalf("unexpected IP rule: %v", cidr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if _, err := internet.ToMemoryStreamConfig(built); err != nil {
|
|
||||||
t.Fatalf("failed to initialize runtime finalmask: %v", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, field := range []string{"blockedSourceIPs", "allowedSourceIPs"} {
|
|
||||||
var config Filter
|
|
||||||
if err := json.Unmarshal([]byte(fmt.Sprintf(`{"%s":["192.0.2.0/33"]}`, field)), &config); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if _, err := config.Build(); err == nil || !strings.Contains(err.Error(), field) {
|
|
||||||
t.Errorf("expected invalid %s, got %v", field, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -90,7 +90,6 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
|
|||||||
config.ProtocolName = protocol
|
config.ProtocolName = protocol
|
||||||
}
|
}
|
||||||
|
|
||||||
var xdriveH3 bool
|
|
||||||
switch strings.ToLower(c.Security) {
|
switch strings.ToLower(c.Security) {
|
||||||
case "", "none":
|
case "", "none":
|
||||||
case "tls":
|
case "tls":
|
||||||
@@ -105,7 +104,6 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
|
|||||||
tm := serial.ToTypedMessage(ts)
|
tm := serial.ToTypedMessage(ts)
|
||||||
config.SecuritySettings = append(config.SecuritySettings, tm)
|
config.SecuritySettings = append(config.SecuritySettings, tm)
|
||||||
config.SecurityType = tm.Type
|
config.SecurityType = tm.Type
|
||||||
xdriveH3 = config.ProtocolName == "xdrive" && tlsSettings.ALPN != nil && slices.Equal(*tlsSettings.ALPN, StringList{"h3"})
|
|
||||||
if config.ProtocolName == "xdrive" && tlsSettings.ALPN != nil && slices.Equal(*tlsSettings.ALPN, StringList{"http/1.1"}) && tlsSettings.ECHConfigList == "" {
|
if config.ProtocolName == "xdrive" && tlsSettings.ALPN != nil && slices.Equal(*tlsSettings.ALPN, StringList{"http/1.1"}) && tlsSettings.ECHConfigList == "" {
|
||||||
// a fingerprint puts its own ALPN into the hello, except Go TLS and the
|
// a fingerprint puts its own ALPN into the hello, except Go TLS and the
|
||||||
// randomized ones, which take it from the config or send none
|
// randomized ones, which take it from the config or send none
|
||||||
@@ -221,7 +219,6 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
if c.XDRIVESettings != nil {
|
if c.XDRIVESettings != nil {
|
||||||
c.XDRIVESettings.h3 = xdriveH3
|
|
||||||
xs, err := c.XDRIVESettings.Build()
|
xs, err := c.XDRIVESettings.Build()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("Failed to build XDRIVE config.").Base(err)
|
return nil, errors.New("Failed to build XDRIVE config.").Base(err)
|
||||||
@@ -263,20 +260,5 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if xdriveH3 && c.FinalMask != nil {
|
|
||||||
if c.XDRIVESettings != nil && c.XDRIVESettings.Service == "google-drive" {
|
|
||||||
// noise only sends datagrams of its own ahead of the QUIC ones, the
|
|
||||||
// other masks change those or need a peer that undoes them
|
|
||||||
for _, mask := range c.FinalMask.Udp {
|
|
||||||
if strings.ToLower(mask.Type) != "noise" {
|
|
||||||
return nil, errors.New(`XDRIVE: over h3 to Google Drive the only "udp" mask that works is "noise", not "`, mask.Type, `"`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if config.QuicParams != nil && (config.QuicParams.Congestion == "brutal" || config.QuicParams.Congestion == "force-brutal") {
|
|
||||||
return nil, errors.New(`XDRIVE: brutal is for Hysteria servers, over h3 "congestion" is "bbr" or "reno", not "`, config.QuicParams.Congestion, `"`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -997,18 +997,6 @@ type XDriveConfig struct {
|
|||||||
EagerWindowMs uint32 `json:"eagerWindowMs"`
|
EagerWindowMs uint32 `json:"eagerWindowMs"`
|
||||||
HoleTimeoutMs uint32 `json:"holeTimeoutMs"`
|
HoleTimeoutMs uint32 `json:"holeTimeoutMs"`
|
||||||
Template json.RawMessage `json:"template"`
|
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.
|
// Build implements Buildable.
|
||||||
@@ -1027,27 +1015,8 @@ func (c *XDriveConfig) Build() (proto.Message, error) {
|
|||||||
if len(c.Template) == 0 {
|
if len(c.Template) == 0 {
|
||||||
return nil, errors.New(`service "template" needs a "template" object`)
|
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:
|
default:
|
||||||
return nil, errors.New(`unknown "service": "`, c.Service, `", use "google-drive", "template", "s3" or "local"`)
|
return nil, errors.New(`unknown "service": "`, c.Service, `", use "google-drive", "template" or "local"`)
|
||||||
}
|
|
||||||
if c.Xmux.MaxConnections.To > 0 && c.Xmux.MaxConcurrency.To > 0 {
|
|
||||||
return nil, errors.New("maxConnections cannot be specified together with maxConcurrency")
|
|
||||||
}
|
|
||||||
if c.Xmux == (XmuxConfig{}) {
|
|
||||||
c.Xmux.MaxConnections.From = 1
|
|
||||||
c.Xmux.MaxConnections.To = 1
|
|
||||||
if c.Service == "google-drive" && !c.h3 {
|
|
||||||
// Google gives an h2 connection 1 MiB of window, which a few sessions on
|
|
||||||
// a long rtt would share, an h3 connection has no such limit
|
|
||||||
c.Xmux.MaxConnections.From = 4
|
|
||||||
c.Xmux.MaxConnections.To = 4
|
|
||||||
}
|
|
||||||
c.Xmux.HMaxReusableSecs.From = 1800
|
|
||||||
c.Xmux.HMaxReusableSecs.To = 3000
|
|
||||||
}
|
}
|
||||||
config := &xdrive.Config{
|
config := &xdrive.Config{
|
||||||
RemoteFolder: c.RemoteFolder,
|
RemoteFolder: c.RemoteFolder,
|
||||||
@@ -1062,25 +1031,6 @@ func (c *XDriveConfig) Build() (proto.Message, error) {
|
|||||||
EagerWindowMs: c.EagerWindowMs,
|
EagerWindowMs: c.EagerWindowMs,
|
||||||
HoleTimeoutMs: c.HoleTimeoutMs,
|
HoleTimeoutMs: c.HoleTimeoutMs,
|
||||||
Template: string(c.Template),
|
Template: string(c.Template),
|
||||||
Xmux: &splithttp.XmuxConfig{
|
|
||||||
MaxConcurrency: newRangeConfig(c.Xmux.MaxConcurrency),
|
|
||||||
MaxConnections: newRangeConfig(c.Xmux.MaxConnections),
|
|
||||||
CMaxReuseTimes: newRangeConfig(c.Xmux.CMaxReuseTimes),
|
|
||||||
HMaxRequestTimes: newRangeConfig(c.Xmux.HMaxRequestTimes),
|
|
||||||
HMaxReusableSecs: newRangeConfig(c.Xmux.HMaxReusableSecs),
|
|
||||||
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
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
. "github.com/xtls/xray-core/infra/conf"
|
. "github.com/xtls/xray-core/infra/conf"
|
||||||
"github.com/xtls/xray-core/transport/internet"
|
"github.com/xtls/xray-core/transport/internet"
|
||||||
finalmaskcustom "github.com/xtls/xray-core/transport/internet/finalmask/header/custom"
|
finalmaskcustom "github.com/xtls/xray-core/transport/internet/finalmask/header/custom"
|
||||||
"github.com/xtls/xray-core/transport/internet/splithttp"
|
|
||||||
"github.com/xtls/xray-core/transport/internet/xdrive"
|
"github.com/xtls/xray-core/transport/internet/xdrive"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
@@ -324,7 +323,7 @@ func TestXDriveRejectsUnknownService(t *testing.T) {
|
|||||||
if err := json.Unmarshal([]byte(`{"remoteFolder": "/tmp/xdrive", "service": "Dropbox"}`), config); err != nil {
|
if err := json.Unmarshal([]byte(`{"remoteFolder": "/tmp/xdrive", "service": "Dropbox"}`), config); err != nil {
|
||||||
t.Fatalf("Unmarshal: %v", err)
|
t.Fatalf("Unmarshal: %v", err)
|
||||||
}
|
}
|
||||||
if _, err := config.Build(); err == nil || !strings.Contains(err.Error(), `use "google-drive", "template", "s3" or "local"`) {
|
if _, err := config.Build(); err == nil || !strings.Contains(err.Error(), `use "google-drive", "template" or "local"`) {
|
||||||
t.Fatalf("expected an unknown service error, got %v", err)
|
t.Fatalf("expected an unknown service error, got %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -425,81 +424,6 @@ func TestXDriveHTTP1ALPNWarning(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// over h3 Google Drive takes only the noise mask, and no service takes brutal
|
|
||||||
func TestXDriveH3Checks(t *testing.T) {
|
|
||||||
const (
|
|
||||||
drive = `"method": "xdrive", "xdriveSettings": {"remoteFolder": "f", "service": "google-drive", "secrets": ["a", "b", "c"]}`
|
|
||||||
local = `"method": "xdrive", "xdriveSettings": {"remoteFolder": "/tmp/xdrive", "service": "local"}`
|
|
||||||
noise = `{"type": "Noise", "settings": {"noise": [{"rand": "50-100"}]}}`
|
|
||||||
salamander = `{"type": "salamander", "settings": {"password": "p"}}`
|
|
||||||
forceBrutal = `{"quicParams": {"congestion": "force-brutal", "brutalUp": "10 mbps"}}`
|
|
||||||
)
|
|
||||||
for _, c := range []struct {
|
|
||||||
alpn, transport, finalmask string
|
|
||||||
ok bool
|
|
||||||
}{
|
|
||||||
{`["h3"]`, drive, `{"udp": [` + noise + `]}`, true},
|
|
||||||
{`["h3"]`, drive, `{"udp": [` + salamander + `]}`, false},
|
|
||||||
{`["h3"]`, drive, `{"udp": [` + noise + `, ` + salamander + `]}`, false},
|
|
||||||
{`["h3"]`, local, `{"udp": [` + salamander + `]}`, true},
|
|
||||||
{`["h3"]`, `"method": "xdrive"`, `{"udp": [` + salamander + `]}`, true},
|
|
||||||
{`["h2"]`, drive, `{"udp": [` + salamander + `]}`, true},
|
|
||||||
{`null`, drive, `{"udp": [` + salamander + `]}`, true},
|
|
||||||
{`["h3"]`, drive, `null`, true},
|
|
||||||
{`["h3"]`, drive, `{"quicParams": {"congestion": "bbr"}}`, true},
|
|
||||||
{`["h3"]`, drive, `{"quicParams": {"congestion": "brutal"}}`, false},
|
|
||||||
{`["h3"]`, local, forceBrutal, false},
|
|
||||||
{`["h3", "h2"]`, drive, `{"quicParams": {"congestion": "brutal"}}`, true},
|
|
||||||
{`["h3"]`, `"method": "xhttp"`, forceBrutal, true},
|
|
||||||
} {
|
|
||||||
config := new(StreamConfig)
|
|
||||||
if err := json.Unmarshal([]byte(`{
|
|
||||||
`+c.transport+`,
|
|
||||||
"security": "tls",
|
|
||||||
"tlsSettings": {"alpn": `+c.alpn+`},
|
|
||||||
"finalmask": `+c.finalmask+`
|
|
||||||
}`), config); err != nil {
|
|
||||||
t.Fatalf("Unmarshal: %v", err)
|
|
||||||
}
|
|
||||||
if _, err := config.Build(); (err == nil) != c.ok {
|
|
||||||
t.Errorf("alpn %s, %s, finalmask %s: %v", c.alpn, c.transport, c.finalmask, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// one h3 connection is not held back by a window like h2, so h3 takes one by default
|
|
||||||
func TestXDriveH3Connections(t *testing.T) {
|
|
||||||
for _, c := range []struct {
|
|
||||||
alpn string
|
|
||||||
want int32
|
|
||||||
}{
|
|
||||||
{`["h3"]`, 1},
|
|
||||||
{`["h2"]`, 4},
|
|
||||||
{`null`, 4},
|
|
||||||
} {
|
|
||||||
config := new(StreamConfig)
|
|
||||||
if err := json.Unmarshal([]byte(`{
|
|
||||||
"method": "xdrive",
|
|
||||||
"xdriveSettings": {"remoteFolder": "f", "service": "google-drive", "secrets": ["a", "b", "c"]},
|
|
||||||
"security": "tls",
|
|
||||||
"tlsSettings": {"alpn": `+c.alpn+`}
|
|
||||||
}`), config); err != nil {
|
|
||||||
t.Fatalf("Unmarshal: %v", err)
|
|
||||||
}
|
|
||||||
built, err := config.Build()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("alpn %s: %v", c.alpn, err)
|
|
||||||
}
|
|
||||||
xs, err := built.TransportSettings[0].Settings.GetInstance()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("alpn %s: %v", c.alpn, err)
|
|
||||||
}
|
|
||||||
if got := xs.(*xdrive.Config).Xmux.MaxConnections; got.From != c.want || got.To != c.want {
|
|
||||||
t.Errorf("alpn %s: maxConnections is %d-%d, want %d", c.alpn, got.From, got.To, c.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestXDriveTemplateNeedsTemplate(t *testing.T) {
|
func TestXDriveTemplateNeedsTemplate(t *testing.T) {
|
||||||
config := new(XDriveConfig)
|
config := new(XDriveConfig)
|
||||||
if err := json.Unmarshal([]byte(`{"remoteFolder": "f", "service": "template"}`), config); err != nil {
|
if err := json.Unmarshal([]byte(`{"remoteFolder": "f", "service": "template"}`), config); err != nil {
|
||||||
@@ -589,50 +513,3 @@ func TestXDriveInboundConfig(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestXDriveXmux(t *testing.T) {
|
|
||||||
defaults := func(connections int32) *splithttp.XmuxConfig {
|
|
||||||
return &splithttp.XmuxConfig{
|
|
||||||
MaxConcurrency: &splithttp.RangeConfig{},
|
|
||||||
MaxConnections: &splithttp.RangeConfig{From: connections, To: connections},
|
|
||||||
CMaxReuseTimes: &splithttp.RangeConfig{},
|
|
||||||
HMaxRequestTimes: &splithttp.RangeConfig{},
|
|
||||||
HMaxReusableSecs: &splithttp.RangeConfig{From: 1800, To: 3000},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, c := range []struct {
|
|
||||||
input string
|
|
||||||
want *splithttp.XmuxConfig
|
|
||||||
}{
|
|
||||||
{`{"service": "google-drive", "secrets": ["a", "b", "c"]}`, defaults(4)},
|
|
||||||
{`{"service": "template", "template": {}}`, defaults(1)},
|
|
||||||
{`{"service": "google-drive", "secrets": ["a", "b", "c"], "xmux": {"maxConcurrency": "1-2", "hMaxRequestTimes": 100, "hKeepAlivePeriod": -1}}`, &splithttp.XmuxConfig{
|
|
||||||
MaxConcurrency: &splithttp.RangeConfig{From: 1, To: 2},
|
|
||||||
MaxConnections: &splithttp.RangeConfig{},
|
|
||||||
CMaxReuseTimes: &splithttp.RangeConfig{},
|
|
||||||
HMaxRequestTimes: &splithttp.RangeConfig{From: 100, To: 100},
|
|
||||||
HMaxReusableSecs: &splithttp.RangeConfig{},
|
|
||||||
HKeepAlivePeriod: -1,
|
|
||||||
}},
|
|
||||||
} {
|
|
||||||
config := new(XDriveConfig)
|
|
||||||
if err := json.Unmarshal([]byte(c.input), config); err != nil {
|
|
||||||
t.Fatalf("%s: Unmarshal: %v", c.input, err)
|
|
||||||
}
|
|
||||||
built, err := config.Build()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%s: Build: %v", c.input, err)
|
|
||||||
}
|
|
||||||
if got := built.(*xdrive.Config).Xmux; !proto.Equal(got, c.want) {
|
|
||||||
t.Errorf("%s: xmux is %v, want %v", c.input, got, c.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
config := new(XDriveConfig)
|
|
||||||
if err := json.Unmarshal([]byte(`{"service": "template", "template": {}, "xmux": {"maxConnections": 2, "maxConcurrency": 8}}`), config); err != nil {
|
|
||||||
t.Fatalf("Unmarshal: %v", err)
|
|
||||||
}
|
|
||||||
if _, err := config.Build(); err == nil {
|
|
||||||
t.Error("Build accepted maxConnections with maxConcurrency")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+3
-6
@@ -237,11 +237,10 @@ type OutboundDetourConfig struct {
|
|||||||
|
|
||||||
func requiresTransportSecurity(address *Address) bool {
|
func requiresTransportSecurity(address *Address) bool {
|
||||||
if address == nil || address.Address == nil {
|
if address == nil || address.Address == nil {
|
||||||
return true
|
return false
|
||||||
}
|
}
|
||||||
if address.Family().IsIP() {
|
if address.Family().IsIP() {
|
||||||
ip := address.IP()
|
return !geodata.GetPrivateIPMatcher().Match(address.IP())
|
||||||
return !ip.IsPrivate() && !ip.IsLoopback() && !ip.IsLinkLocalUnicast()
|
|
||||||
}
|
}
|
||||||
domain := strings.TrimSuffix(strings.ToLower(address.Domain()), ".")
|
domain := strings.TrimSuffix(strings.ToLower(address.Domain()), ".")
|
||||||
return !geodata.GetPrivateDomainMatcher().MatchAny(domain)
|
return !geodata.GetPrivateDomainMatcher().MatchAny(domain)
|
||||||
@@ -609,6 +608,7 @@ func (c *Config) Build() (*core.Config, error) {
|
|||||||
config := &core.Config{
|
config := &core.Config{
|
||||||
App: []*serial.TypedMessage{
|
App: []*serial.TypedMessage{
|
||||||
serial.ToTypedMessage(&dispatcher.Config{}),
|
serial.ToTypedMessage(&dispatcher.Config{}),
|
||||||
|
serial.ToTypedMessage(&proxyman.InboundConfig{}),
|
||||||
serial.ToTypedMessage(&proxyman.OutboundConfig{}),
|
serial.ToTypedMessage(&proxyman.OutboundConfig{}),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -718,9 +718,6 @@ func (c *Config) Build() (*core.Config, error) {
|
|||||||
config.App = append(config.App, serial.ToTypedMessage(r))
|
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
|
var inbounds []InboundDetourConfig
|
||||||
|
|
||||||
if len(c.InboundConfigs) > 0 {
|
if len(c.InboundConfigs) > 0 {
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ func TestXrayConfig(t *testing.T) {
|
|||||||
AccessLogPath: "/var/log/xray/access.log",
|
AccessLogPath: "/var/log/xray/access.log",
|
||||||
}),
|
}),
|
||||||
serial.ToTypedMessage(&dispatcher.Config{}),
|
serial.ToTypedMessage(&dispatcher.Config{}),
|
||||||
|
serial.ToTypedMessage(&proxyman.InboundConfig{}),
|
||||||
serial.ToTypedMessage(&proxyman.OutboundConfig{}),
|
serial.ToTypedMessage(&proxyman.OutboundConfig{}),
|
||||||
serial.ToTypedMessage(&router.Config{
|
serial.ToTypedMessage(&router.Config{
|
||||||
DomainStrategy: router.Config_AsIs,
|
DomainStrategy: router.Config_AsIs,
|
||||||
@@ -110,7 +111,6 @@ func TestXrayConfig(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
serial.ToTypedMessage(&proxyman.InboundConfig{}),
|
|
||||||
},
|
},
|
||||||
Inbound: []*core.InboundHandlerConfig{
|
Inbound: []*core.InboundHandlerConfig{
|
||||||
{
|
{
|
||||||
|
|||||||
+201
-178
@@ -1,87 +1,149 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"errors"
|
||||||
"context"
|
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"go/build"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"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 (
|
var directory = flag.String("pwd", "", "Working directory of Xray vprotogen.")
|
||||||
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.")
|
|
||||||
)
|
|
||||||
|
|
||||||
// topoSort sorts file descriptors so each dependency appears before files importing it.
|
// envFile returns the name of the Go environment configuration file.
|
||||||
func topoSort(files []protoreflect.FileDescriptor) []*descriptorpb.FileDescriptorProto {
|
// Copy from https://github.com/golang/go/blob/c4f2a9788a7be04daf931ac54382fbe2cb754938/src/cmd/go/internal/cfg/cfg.go#L150-L166
|
||||||
fileMap := make(map[string]protoreflect.FileDescriptor)
|
func envFile() (string, error) {
|
||||||
for _, f := range files {
|
if file := os.Getenv("GOENV"); file != "" {
|
||||||
fileMap[f.Path()] = f
|
if file == "off" {
|
||||||
|
return "", errors.New("GOENV=off")
|
||||||
}
|
}
|
||||||
|
return file, nil
|
||||||
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
|
|
||||||
}
|
}
|
||||||
visited[fd.Path()] = true
|
dir, err := os.UserConfigDir()
|
||||||
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, f := range files {
|
|
||||||
visit(f)
|
|
||||||
}
|
|
||||||
return sorted
|
|
||||||
}
|
|
||||||
|
|
||||||
func runPlugin(pluginName, pwd string, req *pluginpb.CodeGeneratorRequest) (*pluginpb.CodeGeneratorResponse, error) {
|
|
||||||
reqBytes, err := proto.Marshal(req)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return "", err
|
||||||
|
}
|
||||||
|
if dir == "" {
|
||||||
|
return "", errors.New("missing user-config dir")
|
||||||
|
}
|
||||||
|
return filepath.Join(dir, "go", "env"), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command("go", "tool", pluginName)
|
// GetRuntimeEnv returns the value of runtime environment variable,
|
||||||
cmd.Dir = pwd
|
// that is set by running following command: `go env -w key=value`.
|
||||||
cmd.Stdin = bytes.NewReader(reqBytes)
|
func GetRuntimeEnv(key string) (string, error) {
|
||||||
cmd.Env = os.Environ()
|
file, err := envFile()
|
||||||
var stdout, stderr bytes.Buffer
|
if err != nil {
|
||||||
cmd.Stdout = &stdout
|
return "", err
|
||||||
cmd.Stderr = &stderr
|
}
|
||||||
|
if file == "" {
|
||||||
if err := cmd.Run(); err != nil {
|
return "", errors.New("missing runtime env file")
|
||||||
return nil, fmt.Errorf("%v: %s", err, stderr.String())
|
}
|
||||||
|
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])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return runtimeEnv, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var resp pluginpb.CodeGeneratorResponse
|
// GetGOBIN returns GOBIN environment variable as a string. It will NOT be empty.
|
||||||
if err := proto.Unmarshal(stdout.Bytes(), &resp); err != nil {
|
func GetGOBIN() string {
|
||||||
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
|
// 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 resp.GetError() != "" {
|
if GOBIN == "" {
|
||||||
return nil, fmt.Errorf("%s", resp.GetError())
|
return filepath.Join(build.Default.GOPATH, "bin")
|
||||||
}
|
}
|
||||||
return &resp, nil
|
return GOBIN
|
||||||
|
}
|
||||||
|
return GOBIN
|
||||||
|
}
|
||||||
|
|
||||||
|
func whichProtoc(suffix, targetedVersion string) (string, error) {
|
||||||
|
protoc := "protoc" + suffix
|
||||||
|
|
||||||
|
path, err := exec.LookPath(protoc)
|
||||||
|
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 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
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
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 result int64
|
||||||
|
var err error
|
||||||
|
if result, err = strconv.ParseInt(v, 10, 64); err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func needToUpdate(targetedVersion, installedVersion string) bool {
|
||||||
|
vt := parseVersion(targetedVersion, 4)
|
||||||
|
vi := parseVersion(installedVersion, 4)
|
||||||
|
return vt > vi
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -92,145 +154,106 @@ func main() {
|
|||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if !filepath.IsAbs(*directory) {
|
if !filepath.IsAbs(*directory) {
|
||||||
pwd, err := os.Getwd()
|
pwd, wdErr := os.Getwd()
|
||||||
if err != nil {
|
if wdErr != nil {
|
||||||
fmt.Println("Can not get current working directory.")
|
fmt.Println("Can not get current working directory.")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
*directory = filepath.Join(pwd, *directory)
|
*directory = filepath.Join(pwd, *directory)
|
||||||
}
|
}
|
||||||
pwd := *directory
|
|
||||||
|
|
||||||
isCheck := false
|
pwd := *directory
|
||||||
switch *mode {
|
GOBIN := GetGOBIN()
|
||||||
case "generate", "":
|
binPath := os.Getenv("PATH")
|
||||||
isCheck = false
|
pathSlice := []string{pwd, GOBIN, binPath}
|
||||||
case "check":
|
binPath = strings.Join(pathSlice, string(os.PathListSeparator))
|
||||||
isCheck = true
|
os.Setenv("PATH", binPath)
|
||||||
default:
|
|
||||||
fmt.Fprintf(os.Stderr, "Unrecognized 'mode': %s. Use 'generate' or 'check'.\n", *mode)
|
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)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if isCheck {
|
installedVersion, err := getInstalledProtocVersion(protoc)
|
||||||
fmt.Println("Checking for out-of-date Protobuf generated files...")
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
var protoFiles []string
|
if needToUpdate(targetedVersion, installedVersion) {
|
||||||
var existingPbFiles []string
|
fmt.Printf(`
|
||||||
err := filepath.WalkDir(pwd, func(path string, d fs.DirEntry, err error) error {
|
You are using an old protobuf version, please update to v%s or later.
|
||||||
if err != nil || d.IsDir() {
|
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)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if strings.HasSuffix(d.Name(), ".proto") {
|
|
||||||
rel, _ := filepath.Rel(pwd, path)
|
if info.IsDir() {
|
||||||
protoFiles = append(protoFiles, filepath.ToSlash(rel))
|
return nil
|
||||||
}
|
}
|
||||||
if strings.HasSuffix(d.Name(), ".pb.go") {
|
|
||||||
existingPbFiles = append(existingPbFiles, path)
|
dir := filepath.Dir(path)
|
||||||
|
filename := filepath.Base(path)
|
||||||
|
if strings.HasSuffix(filename, ".proto") {
|
||||||
|
path = path[len(pwd)+1:]
|
||||||
|
protoFilesMap[dir] = append(protoFilesMap[dir], path)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if walkErr != nil {
|
||||||
fmt.Println("Walk error:", err)
|
fmt.Println(walkErr)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
compiler := protocompile.Compiler{
|
for _, files := range protoFilesMap {
|
||||||
Resolver: protocompile.WithStandardImports(&protocompile.SourceResolver{
|
for _, relProtoFile := range files {
|
||||||
ImportPaths: []string{pwd},
|
args := []string{
|
||||||
}),
|
"--go_out", pwd,
|
||||||
SourceInfoMode: protocompile.SourceInfoStandard,
|
"--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),
|
||||||
}
|
}
|
||||||
|
args = append(args, relProtoFile)
|
||||||
linkedFiles, err := compiler.Compile(context.Background(), protoFiles...)
|
cmd := exec.Command(protoc, args...)
|
||||||
if err != nil {
|
cmd.Env = append(cmd.Env, os.Environ()...)
|
||||||
fmt.Fprintf(os.Stderr, "Protobuf compilation failed:\n%v\n", err)
|
cmd.Dir = pwd
|
||||||
os.Exit(1)
|
output, cmdErr := cmd.CombinedOutput()
|
||||||
|
if len(output) > 0 {
|
||||||
|
fmt.Println(string(output))
|
||||||
}
|
}
|
||||||
|
if cmdErr != nil {
|
||||||
var fdList []protoreflect.FileDescriptor
|
fmt.Println(cmdErr)
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
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,8 +47,7 @@ func executeBalancerInfo(cmd *base.Command, args []string) {
|
|||||||
conn, ctx, close := dialAPIServer()
|
conn, ctx, close := dialAPIServer()
|
||||||
defer close()
|
defer close()
|
||||||
client := routerService.NewRoutingServiceClient(conn)
|
client := routerService.NewRoutingServiceClient(conn)
|
||||||
for _, tag := range unnamedArgs {
|
r := &routerService.GetBalancerInfoRequest{Tag: unnamedArgs[0]}
|
||||||
r := &routerService.GetBalancerInfoRequest{Tag: tag}
|
|
||||||
resp, err := client.GetBalancerInfo(ctx, r)
|
resp, err := client.GetBalancerInfo(ctx, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Fatalf("failed to get health information: %s", err)
|
base.Fatalf("failed to get health information: %s", err)
|
||||||
@@ -56,11 +55,11 @@ func executeBalancerInfo(cmd *base.Command, args []string) {
|
|||||||
|
|
||||||
if apiJSON {
|
if apiJSON {
|
||||||
showJSONResponse(resp)
|
showJSONResponse(resp)
|
||||||
} else {
|
return
|
||||||
|
}
|
||||||
|
|
||||||
showBalancerInfo(resp.Balancer)
|
showBalancerInfo(resp.Balancer)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func showBalancerInfo(b *routerService.BalancerMsg) {
|
func showBalancerInfo(b *routerService.BalancerMsg) {
|
||||||
const tableIndent = 4
|
const tableIndent = 4
|
||||||
|
|||||||
@@ -92,8 +92,6 @@ func executeAddRules(cmd *base.Command, args []string) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
base.Fatalf("failed to perform AddRule: %s", err)
|
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)
|
showJSONResponse(resp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package lua
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
|
|
||||||
xlua "github.com/xtls/xray-core/common/lua"
|
xlua "github.com/xtls/xray-core/common/lua"
|
||||||
"github.com/xtls/xray-core/main/commands/base"
|
"github.com/xtls/xray-core/main/commands/base"
|
||||||
@@ -33,13 +32,14 @@ func executeSign(cmd *base.Command, args []string) {
|
|||||||
cmd.Usage()
|
cmd.Usage()
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.EqualFold(filepath.Ext(args[0]), ".lua") {
|
|
||||||
base.Fatalf("Lua file extension must be .lua")
|
|
||||||
}
|
|
||||||
info, err := os.Stat(args[0])
|
info, err := os.Stat(args[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Fatalf("failed to inspect Lua file: %s", err)
|
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])
|
code, err := os.ReadFile(args[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Fatalf("failed to read Lua file: %s", err)
|
base.Fatalf("failed to read Lua file: %s", err)
|
||||||
@@ -67,7 +67,14 @@ func executeSign(cmd *base.Command, args []string) {
|
|||||||
|
|
||||||
func writeSignedFile(path string, code []byte, mode os.FileMode) error {
|
func writeSignedFile(path string, code []byte, mode os.FileMode) error {
|
||||||
if info, err := os.Stat(path); err == nil {
|
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()
|
mode = info.Mode().Perm()
|
||||||
|
path, err = filepath.EvalSymlinks(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
} else if !os.IsNotExist(err) {
|
} else if !os.IsNotExist(err) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+10
-2
@@ -58,10 +58,18 @@ func readConfigFile(file *os.File) ([]byte, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if info.Mode().IsRegular() {
|
if info.Mode().IsRegular() {
|
||||||
switch strings.ToLower(filepath.Ext(file.Name())) {
|
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)) {
|
||||||
case ".jsonc", ".json", ".yml", ".yaml", ".toml", ".pb", ".protobuf":
|
case ".jsonc", ".json", ".yml", ".yaml", ".toml", ".pb", ".protobuf":
|
||||||
default:
|
default:
|
||||||
return nil, errors.New("unsupported config file extension: ", file.Name())
|
return nil, errors.New("unsupported config file extension: ", path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return io.ReadAll(file)
|
return io.ReadAll(file)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.12
|
// protoc-gen-go v1.36.11
|
||||||
// protoc (unknown)
|
// protoc v6.33.5
|
||||||
// source: proxy/blackhole/config.proto
|
// source: proxy/blackhole/config.proto
|
||||||
|
|
||||||
package blackhole
|
package blackhole
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.12
|
// protoc-gen-go v1.36.11
|
||||||
// protoc (unknown)
|
// protoc v6.33.5
|
||||||
// source: proxy/dns/config.proto
|
// source: proxy/dns/config.proto
|
||||||
|
|
||||||
package dns
|
package dns
|
||||||
|
|||||||
@@ -381,13 +381,6 @@ func (h *Handler) handleIPQuery(id uint16, qType dnsmessage.Type, domain string,
|
|||||||
errors.LogInfoInner(context.Background(), err, "pack message")
|
errors.LogInfoInner(context.Background(), err, "pack message")
|
||||||
b.Release()
|
b.Release()
|
||||||
timer.SetTimeout(0)
|
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)))
|
b.Resize(0, int32(len(msgBytes)))
|
||||||
|
|
||||||
|
|||||||
+19
-19
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.12
|
// protoc-gen-go v1.36.11
|
||||||
// protoc (unknown)
|
// protoc v6.33.5
|
||||||
// source: proxy/dokodemo/config.proto
|
// source: proxy/dokodemo/config.proto
|
||||||
|
|
||||||
package dokodemo
|
package dokodemo
|
||||||
@@ -24,11 +24,11 @@ const (
|
|||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
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"`
|
|
||||||
RewriteAddress *net.IPOrDomain `protobuf:"bytes,1,opt,name=rewrite_address,json=rewriteAddress,proto3" json:"rewrite_address,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"`
|
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"`
|
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"`
|
||||||
|
// 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"`
|
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"`
|
UserLevel uint32 `protobuf:"varint,6,opt,name=user_level,json=userLevel,proto3" json:"user_level,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
@@ -65,13 +65,6 @@ func (*Config) Descriptor() ([]byte, []int) {
|
|||||||
return file_proxy_dokodemo_config_proto_rawDescGZIP(), []int{0}
|
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 {
|
func (x *Config) GetRewriteAddress() *net.IPOrDomain {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.RewriteAddress
|
return x.RewriteAddress
|
||||||
@@ -93,6 +86,13 @@ func (x *Config) GetPortMap() map[string]string {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetAllowedNetworks() []net.Network {
|
||||||
|
if x != nil {
|
||||||
|
return x.AllowedNetworks
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (x *Config) GetFollowRedirect() bool {
|
func (x *Config) GetFollowRedirect() bool {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.FollowRedirect
|
return x.FollowRedirect
|
||||||
@@ -112,11 +112,11 @@ var File_proxy_dokodemo_config_proto protoreflect.FileDescriptor
|
|||||||
const file_proxy_dokodemo_config_proto_rawDesc = "" +
|
const file_proxy_dokodemo_config_proto_rawDesc = "" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"\x1bproxy/dokodemo/config.proto\x12\x13xray.proxy.dokodemo\x1a\x18common/net/address.proto\x1a\x18common/net/network.proto\"\xff\x02\n" +
|
"\x1bproxy/dokodemo/config.proto\x12\x13xray.proxy.dokodemo\x1a\x18common/net/address.proto\x1a\x18common/net/network.proto\"\xff\x02\n" +
|
||||||
"\x06Config\x12C\n" +
|
"\x06Config\x12D\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" +
|
"\x0frewrite_address\x18\x01 \x01(\v2\x1b.xray.common.net.IPOrDomainR\x0erewriteAddress\x12!\n" +
|
||||||
"\frewrite_port\x18\x02 \x01(\rR\vrewritePort\x12C\n" +
|
"\frewrite_port\x18\x02 \x01(\rR\vrewritePort\x12C\n" +
|
||||||
"\bport_map\x18\x03 \x03(\v2(.xray.proxy.dokodemo.Config.PortMapEntryR\aportMap\x12'\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" +
|
||||||
"\x0ffollow_redirect\x18\x05 \x01(\bR\x0efollowRedirect\x12\x1d\n" +
|
"\x0ffollow_redirect\x18\x05 \x01(\bR\x0efollowRedirect\x12\x1d\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"user_level\x18\x06 \x01(\rR\tuserLevel\x1a:\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{
|
var file_proxy_dokodemo_config_proto_goTypes = []any{
|
||||||
(*Config)(nil), // 0: xray.proxy.dokodemo.Config
|
(*Config)(nil), // 0: xray.proxy.dokodemo.Config
|
||||||
nil, // 1: xray.proxy.dokodemo.Config.PortMapEntry
|
nil, // 1: xray.proxy.dokodemo.Config.PortMapEntry
|
||||||
(net.Network)(0), // 2: xray.common.net.Network
|
(*net.IPOrDomain)(nil), // 2: xray.common.net.IPOrDomain
|
||||||
(*net.IPOrDomain)(nil), // 3: xray.common.net.IPOrDomain
|
(net.Network)(0), // 3: xray.common.net.Network
|
||||||
}
|
}
|
||||||
var file_proxy_dokodemo_config_proto_depIdxs = []int32{
|
var file_proxy_dokodemo_config_proto_depIdxs = []int32{
|
||||||
2, // 0: xray.proxy.dokodemo.Config.allowed_networks:type_name -> xray.common.net.Network
|
2, // 0: xray.proxy.dokodemo.Config.rewrite_address:type_name -> xray.common.net.IPOrDomain
|
||||||
3, // 1: 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
|
||||||
1, // 2: 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
|
||||||
3, // [3:3] is the sub-list for method output_type
|
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 method input_type
|
||||||
3, // [3:3] is the sub-list for extension type_name
|
3, // [3:3] is the sub-list for extension type_name
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.12
|
// protoc-gen-go v1.36.11
|
||||||
// protoc (unknown)
|
// protoc v6.33.5
|
||||||
// source: proxy/freedom/config.proto
|
// source: proxy/freedom/config.proto
|
||||||
|
|
||||||
package freedom
|
package freedom
|
||||||
|
|||||||
@@ -229,19 +229,14 @@ func (h *Handler) blockDelay(rule *FinalRule) time.Duration {
|
|||||||
func (h *Handler) blackhole(ctx context.Context, input buf.Reader, output buf.Writer, rule *FinalRule, dest *net.Destination) error {
|
func (h *Handler) blackhole(ctx context.Context, input buf.Reader, output buf.Writer, rule *FinalRule, dest *net.Destination) error {
|
||||||
delay := h.blockDelay(rule)
|
delay := h.blockDelay(rule)
|
||||||
errors.LogInfo(ctx, "blocked target: ", *dest, ", blackholing connection for ", delay)
|
errors.LogInfo(ctx, "blocked target: ", *dest, ", blackholing connection for ", delay)
|
||||||
defer common.Close(output)
|
timer := time.AfterFunc(delay, func() {
|
||||||
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(input)
|
||||||
common.Interrupt(output)
|
common.Interrupt(output)
|
||||||
errors.LogInfo(ctx, "closed blackholed connection to blocked target: ", *dest)
|
errors.LogInfo(ctx, "closed blackholed connection to blocked target: ", *dest)
|
||||||
}
|
})
|
||||||
|
defer timer.Stop()
|
||||||
|
defer common.Close(output)
|
||||||
|
_ = buf.Copy(input, buf.Discard)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-30
@@ -10,7 +10,6 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"sync"
|
"sync"
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/common/buf"
|
"github.com/xtls/xray-core/common/buf"
|
||||||
@@ -87,13 +86,7 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter
|
|||||||
user := server.User
|
user := server.User
|
||||||
var conn stat.Connection
|
var conn stat.Connection
|
||||||
|
|
||||||
var mbuf buf.MultiBuffer
|
mbuf, _ := link.Reader.ReadMultiBuffer()
|
||||||
if reader, ok := link.Reader.(buf.TimeoutReader); ok {
|
|
||||||
// Don't wait forever: server-first protocols (SMTP, FTP, MySQL...) send nothing until the server greets.
|
|
||||||
mbuf, _ = reader.ReadMultiBufferTimeout(time.Millisecond * 500)
|
|
||||||
} else {
|
|
||||||
mbuf, _ = link.Reader.ReadMultiBuffer()
|
|
||||||
}
|
|
||||||
len := mbuf.Len()
|
len := mbuf.Len()
|
||||||
firstPayload := bytespool.Alloc(len)
|
firstPayload := bytespool.Alloc(len)
|
||||||
mbuf, _ = buf.SplitBytes(mbuf, firstPayload)
|
mbuf, _ = buf.SplitBytes(mbuf, firstPayload)
|
||||||
@@ -241,8 +234,7 @@ func setUpHTTPTunnel(ctx context.Context, dest net.Destination, target string, u
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
reader := bufio.NewReader(rawConn)
|
resp, err := http.ReadResponse(bufio.NewReader(rawConn), req)
|
||||||
resp, err := http.ReadResponse(reader, req)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rawConn.Close()
|
rawConn.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -253,11 +245,6 @@ func setUpHTTPTunnel(ctx context.Context, dest net.Destination, target string, u
|
|||||||
rawConn.Close()
|
rawConn.Close()
|
||||||
return nil, errors.New("Proxy responded with non 200 code: " + resp.Status)
|
return nil, errors.New("Proxy responded with non 200 code: " + resp.Status)
|
||||||
}
|
}
|
||||||
// Tunneled bytes (e.g. a server-first greeting) may arrive in the same read as the 200 response.
|
|
||||||
if n := reader.Buffered(); n > 0 {
|
|
||||||
early, _ := reader.Peek(n)
|
|
||||||
return &earlyDataConn{Conn: rawConn, early: early}, nil
|
|
||||||
}
|
|
||||||
return rawConn, nil
|
return rawConn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -388,21 +375,6 @@ func (h *http2Conn) Close() error {
|
|||||||
return h.out.Close()
|
return h.out.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// earlyDataConn returns the bytes read ahead together with the CONNECT response before reading from Conn.
|
|
||||||
type earlyDataConn struct {
|
|
||||||
net.Conn
|
|
||||||
early []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *earlyDataConn) Read(p []byte) (int, error) {
|
|
||||||
if len(c.early) > 0 {
|
|
||||||
n := copy(p, c.early)
|
|
||||||
c.early = c.early[n:]
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
return c.Conn.Read(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
common.Must(common.RegisterConfig((*ClientConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
|
common.Must(common.RegisterConfig((*ClientConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
|
||||||
return NewClient(ctx, config.(*ClientConfig))
|
return NewClient(ctx, config.(*ClientConfig))
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.12
|
// protoc-gen-go v1.36.11
|
||||||
// protoc (unknown)
|
// protoc v6.33.5
|
||||||
// source: proxy/http/config.proto
|
// source: proxy/http/config.proto
|
||||||
|
|
||||||
package http
|
package http
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.12
|
// protoc-gen-go v1.36.11
|
||||||
// protoc (unknown)
|
// protoc v6.33.5
|
||||||
// source: proxy/hysteria/account/config.proto
|
// source: proxy/hysteria/account/config.proto
|
||||||
|
|
||||||
package account
|
package account
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.12
|
// protoc-gen-go v1.36.11
|
||||||
// protoc (unknown)
|
// protoc v6.33.5
|
||||||
// source: proxy/hysteria/config.proto
|
// source: proxy/hysteria/config.proto
|
||||||
|
|
||||||
package hysteria
|
package hysteria
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.12
|
// protoc-gen-go v1.36.11
|
||||||
// protoc (unknown)
|
// protoc v6.33.5
|
||||||
// source: proxy/loopback/config.proto
|
// source: proxy/loopback/config.proto
|
||||||
|
|
||||||
package loopback
|
package loopback
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.12
|
// protoc-gen-go v1.36.11
|
||||||
// protoc (unknown)
|
// protoc v6.33.5
|
||||||
// source: proxy/masque/config.proto
|
// source: proxy/masque/config.proto
|
||||||
|
|
||||||
package masque
|
package masque
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.12
|
// protoc-gen-go v1.36.11
|
||||||
// protoc (unknown)
|
// protoc v6.33.5
|
||||||
// source: proxy/shadowsocks/config.proto
|
// source: proxy/shadowsocks/config.proto
|
||||||
|
|
||||||
package shadowsocks
|
package shadowsocks
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.12
|
// protoc-gen-go v1.36.11
|
||||||
// protoc (unknown)
|
// protoc v6.33.5
|
||||||
// source: proxy/shadowsocks_2022/config.proto
|
// source: proxy/shadowsocks_2022/config.proto
|
||||||
|
|
||||||
package shadowsocks_2022
|
package shadowsocks_2022
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.12
|
// protoc-gen-go v1.36.11
|
||||||
// protoc (unknown)
|
// protoc v6.33.5
|
||||||
// source: proxy/socks/config.proto
|
// source: proxy/socks/config.proto
|
||||||
|
|
||||||
package socks
|
package socks
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ package socks
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"net"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/net"
|
|
||||||
"github.com/xtls/xray-core/common/signal"
|
"github.com/xtls/xray-core/common/signal"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -14,9 +14,6 @@ func NewTempUDPConn(udpConn net.PacketConn, tcpConn net.Conn, expectedRemote *ne
|
|||||||
PacketConn: udpConn,
|
PacketConn: udpConn,
|
||||||
AssociatedTCPConn: tcpConn,
|
AssociatedTCPConn: tcpConn,
|
||||||
}
|
}
|
||||||
t.Timer = signal.CancelAfterInactivity(context.Background(), func() {
|
|
||||||
t.Close()
|
|
||||||
}, net.ConnIdleTimeout)
|
|
||||||
t.ExpectedRemote.Store(expectedRemote)
|
t.ExpectedRemote.Store(expectedRemote)
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
@@ -63,7 +60,9 @@ func (c *TempUDPConn) RemoteAddr() net.Addr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *TempUDPConn) SetTimeout(d time.Duration) {
|
func (c *TempUDPConn) SetTimeout(d time.Duration) {
|
||||||
c.Timer.SetTimeout(d)
|
c.Timer = signal.CancelAfterInactivity(context.Background(), func() {
|
||||||
|
c.Close()
|
||||||
|
}, d)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *TempUDPConn) Close() error {
|
func (c *TempUDPConn) Close() error {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.12
|
// protoc-gen-go v1.36.11
|
||||||
// protoc (unknown)
|
// protoc v6.33.5
|
||||||
// source: proxy/trojan/config.proto
|
// source: proxy/trojan/config.proto
|
||||||
|
|
||||||
package trojan
|
package trojan
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.12
|
// protoc-gen-go v1.36.11
|
||||||
// protoc (unknown)
|
// protoc v6.33.5
|
||||||
// source: proxy/tun/config.proto
|
// source: proxy/tun/config.proto
|
||||||
|
|
||||||
package tun
|
package tun
|
||||||
|
|
||||||
import (
|
import (
|
||||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
|
||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
sync "sync"
|
sync "sync"
|
||||||
unsafe "unsafe"
|
unsafe "unsafe"
|
||||||
|
|
||||||
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
+7
-8
@@ -41,9 +41,8 @@ const (
|
|||||||
type packet struct {
|
type packet struct {
|
||||||
proto byte
|
proto byte
|
||||||
src, dst netip.Addr
|
src, dst netip.Addr
|
||||||
// payload is what follows the IP header, without the last rest bytes of it when they are in other buffers
|
// payload is what follows the IP header
|
||||||
payload []byte
|
payload []byte
|
||||||
rest int
|
|
||||||
// fragment is set if payload is a part of what follows the IP header: the one at offset of the packet
|
// fragment is set if payload is a part of what follows the IP header: the one at offset of the packet
|
||||||
// with the number id, which has more after it or not
|
// with the number id, which has more after it or not
|
||||||
fragment bool
|
fragment bool
|
||||||
@@ -52,8 +51,8 @@ type packet struct {
|
|||||||
id uint32
|
id uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
// parsePacket reads the IP header of a packet that is b and rest bytes after it
|
// parsePacket reads the IP header of b
|
||||||
func parsePacket(b []byte, rest int) (p packet, ok bool) {
|
func parsePacket(b []byte) (p packet, ok bool) {
|
||||||
if len(b) < ipv4HeaderSize {
|
if len(b) < ipv4HeaderSize {
|
||||||
return p, false
|
return p, false
|
||||||
}
|
}
|
||||||
@@ -61,7 +60,7 @@ func parsePacket(b []byte, rest int) (p packet, ok bool) {
|
|||||||
case 4:
|
case 4:
|
||||||
size := int(b[0]&0xf) * 4
|
size := int(b[0]&0xf) * 4
|
||||||
total := int(binary.BigEndian.Uint16(b[2:]))
|
total := int(binary.BigEndian.Uint16(b[2:]))
|
||||||
if size < ipv4HeaderSize || total < size || total > len(b)+rest {
|
if size < ipv4HeaderSize || total < size || total > len(b) {
|
||||||
return p, false
|
return p, false
|
||||||
}
|
}
|
||||||
if f := binary.BigEndian.Uint16(b[6:]); f&0x3fff != 0 {
|
if f := binary.BigEndian.Uint16(b[6:]); f&0x3fff != 0 {
|
||||||
@@ -70,19 +69,19 @@ func parsePacket(b []byte, rest int) (p packet, ok bool) {
|
|||||||
p.proto = b[9]
|
p.proto = b[9]
|
||||||
p.src = netip.AddrFrom4([4]byte(b[12:16]))
|
p.src = netip.AddrFrom4([4]byte(b[12:16]))
|
||||||
p.dst = netip.AddrFrom4([4]byte(b[16:20]))
|
p.dst = netip.AddrFrom4([4]byte(b[16:20]))
|
||||||
p.payload, p.rest = b[size:min(total, len(b))], max(total-len(b), 0)
|
p.payload = b[size:total]
|
||||||
case 6:
|
case 6:
|
||||||
if len(b) < ipv6HeaderSize {
|
if len(b) < ipv6HeaderSize {
|
||||||
return p, false
|
return p, false
|
||||||
}
|
}
|
||||||
total := ipv6HeaderSize + int(binary.BigEndian.Uint16(b[4:]))
|
total := ipv6HeaderSize + int(binary.BigEndian.Uint16(b[4:]))
|
||||||
if total > len(b)+rest {
|
if total > len(b) {
|
||||||
return p, false
|
return p, false
|
||||||
}
|
}
|
||||||
p.proto = b[6]
|
p.proto = b[6]
|
||||||
p.src = netip.AddrFrom16([16]byte(b[8:24]))
|
p.src = netip.AddrFrom16([16]byte(b[8:24]))
|
||||||
p.dst = netip.AddrFrom16([16]byte(b[24:40]))
|
p.dst = netip.AddrFrom16([16]byte(b[24:40]))
|
||||||
p.payload, p.rest = b[ipv6HeaderSize:min(total, len(b))], max(total-len(b), 0)
|
p.payload = b[ipv6HeaderSize:total]
|
||||||
// hop-by-hop options, routing, destination options
|
// hop-by-hop options, routing, destination options
|
||||||
for p.proto == 0 || p.proto == 43 || p.proto == 60 {
|
for p.proto == 0 || p.proto == 43 || p.proto == 60 {
|
||||||
if len(p.payload) < 8 {
|
if len(p.payload) < 8 {
|
||||||
|
|||||||
+8
-88
@@ -71,10 +71,6 @@ type stack struct {
|
|||||||
udp *udpConnectionHandler
|
udp *udpConnectionHandler
|
||||||
// addresses are what connections are opened from
|
// addresses are what connections are opened from
|
||||||
addresses []netip.Addr
|
addresses []netip.Addr
|
||||||
// bufs is what read reads a packet into if the tun is a segmenter, one after the other, the headers in the first.
|
|
||||||
// One that keep took is nil. joined is for a packet that is in more than one and is needed in one piece
|
|
||||||
bufs buf.MultiBuffer
|
|
||||||
joined []byte
|
|
||||||
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
conns map[flow]*tcpConn
|
conns map[flow]*tcpConn
|
||||||
@@ -153,66 +149,26 @@ func (s *stack) Close() error {
|
|||||||
|
|
||||||
// read takes the packets of the tun until it is closed
|
// read takes the packets of the tun until it is closed
|
||||||
func (s *stack) read() {
|
func (s *stack) read() {
|
||||||
var err error
|
// a tun that does not split segments gives them as large as a packet can be
|
||||||
if s.segmenter != nil {
|
|
||||||
err = s.readSegments()
|
|
||||||
} else {
|
|
||||||
// as large as a packet can be, whatever the MTU is
|
|
||||||
b := make([]byte, packetRoom+1<<16)
|
b := make([]byte, packetRoom+1<<16)
|
||||||
for err == nil {
|
for {
|
||||||
var p []byte
|
b, err := s.tun.ReadPacket(b)
|
||||||
if p, err = s.tun.ReadPacket(b); err == nil {
|
if err != nil {
|
||||||
s.Input(p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
closed := s.closed
|
closed := s.closed
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
if !closed {
|
if !closed {
|
||||||
errors.LogErrorInner(s.ctx, err, "[tun] failed to read a packet, no more are read")
|
errors.LogErrorInner(s.ctx, err, "[tun] failed to read a packet, no more are read")
|
||||||
}
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
s.Input(b)
|
||||||
// readSegments is read for a tun that does not split segments and gives them as large as a packet can be.
|
|
||||||
// They are read into buffers for a connection to keep what is large as it is, see keep
|
|
||||||
func (s *stack) readSegments() error {
|
|
||||||
s.bufs = make(buf.MultiBuffer, (1<<16)/buf.Size)
|
|
||||||
defer buf.ReleaseMulti(s.bufs)
|
|
||||||
parts := make([][]byte, len(s.bufs))
|
|
||||||
var r room
|
|
||||||
for {
|
|
||||||
for i, b := range s.bufs {
|
|
||||||
if b == nil {
|
|
||||||
s.bufs[i] = buf.New()
|
|
||||||
_, _ = s.bufs[i].ReadFrom(&r)
|
|
||||||
parts[i] = r.b
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
n, err := s.segmenter.ReadParts(parts)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.input(parts[0][:min(n, buf.Size)], max(n-buf.Size, 0))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// room takes what it is given to read into as read: that is the room of a Buffer as it is, not cleared
|
|
||||||
type room struct{ b []byte }
|
|
||||||
|
|
||||||
func (r *room) Read(b []byte) (int, error) {
|
|
||||||
r.b = b
|
|
||||||
return len(b), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Input implements Stack. Several tunnels may hand over their packets at once
|
// Input implements Stack. Several tunnels may hand over their packets at once
|
||||||
func (s *stack) Input(b []byte) {
|
func (s *stack) Input(b []byte) {
|
||||||
s.input(b, 0)
|
p, ok := parsePacket(b)
|
||||||
}
|
|
||||||
|
|
||||||
// input takes a packet that is b and rest bytes after it, in the bufs that follow the one that b is in
|
|
||||||
func (s *stack) input(b []byte, rest int) {
|
|
||||||
p, ok := parsePacket(b, rest)
|
|
||||||
if !ok || p.src.Is4In6() || p.dst.Is4In6() || p.src.IsLoopback() || p.dst.IsLoopback() || p.dst.IsUnspecified() || p.src.IsMulticast() {
|
if !ok || p.src.Is4In6() || p.dst.Is4In6() || p.src.IsLoopback() || p.dst.IsLoopback() || p.dst.IsUnspecified() || p.src.IsMulticast() {
|
||||||
// nothing comes from many at once, and an address that stands for this machine itself is not one
|
// nothing comes from many at once, and an address that stands for this machine itself is not one
|
||||||
// that the other side of a tun can ask for. Nor is an IPv6 address that stands for an IPv4 one, which
|
// that the other side of a tun can ask for. Nor is an IPv6 address that stands for an IPv4 one, which
|
||||||
@@ -224,10 +180,6 @@ func (s *stack) input(b []byte, rest int) {
|
|||||||
}
|
}
|
||||||
// what was put together may be of two packets with one number, its checksum tells
|
// what was put together may be of two packets with one number, its checksum tells
|
||||||
verify := s.verify || p.fragment
|
verify := s.verify || p.fragment
|
||||||
if p.rest != 0 && (verify || p.proto != protoTCP) {
|
|
||||||
// only a segment of TCP that is not looked at can stay in pieces
|
|
||||||
p.payload, p.rest = s.whole(p.payload, p.rest), 0
|
|
||||||
}
|
|
||||||
if p.fragment {
|
if p.fragment {
|
||||||
p, ok = s.fragments.add(p)
|
p, ok = s.fragments.add(p)
|
||||||
}
|
}
|
||||||
@@ -244,37 +196,6 @@ func (s *stack) input(b []byte, rest int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// whole returns in one piece what is b and rest bytes after it, in the bufs that follow the one that b is in
|
|
||||||
func (s *stack) whole(b []byte, rest int) []byte {
|
|
||||||
if rest == 0 {
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
s.joined = append(s.joined[:0], b...)
|
|
||||||
for i := 1; rest > 0; i++ {
|
|
||||||
n := min(rest, buf.Size)
|
|
||||||
s.joined = append(s.joined, s.bufs[i].BytesTo(int32(n))...)
|
|
||||||
rest -= n
|
|
||||||
}
|
|
||||||
return s.joined
|
|
||||||
}
|
|
||||||
|
|
||||||
// keep adds to mb what is b and rest bytes after it, in the bufs that follow the one that b is in: nothing is copied,
|
|
||||||
// the buffers are taken for it and no longer read into
|
|
||||||
func (s *stack) keep(mb buf.MultiBuffer, b []byte, rest int) buf.MultiBuffer {
|
|
||||||
// b ends where what was read into the first one does, or before it
|
|
||||||
first := s.bufs[0]
|
|
||||||
from := first.Cap() - int32(cap(b))
|
|
||||||
first.Resize(from, from+int32(len(b)))
|
|
||||||
mb, s.bufs[0] = append(mb, first), nil
|
|
||||||
for i := 1; rest > 0; i++ {
|
|
||||||
n := min(rest, buf.Size)
|
|
||||||
s.bufs[i].Resize(0, int32(n))
|
|
||||||
mb, s.bufs[i] = append(mb, s.bufs[i]), nil
|
|
||||||
rest -= n
|
|
||||||
}
|
|
||||||
return mb
|
|
||||||
}
|
|
||||||
|
|
||||||
// local returns the address that packets to remote are sent from
|
// local returns the address that packets to remote are sent from
|
||||||
func (s *stack) local(remote netip.Addr) (netip.Addr, error) {
|
func (s *stack) local(remote netip.Addr) (netip.Addr, error) {
|
||||||
for _, a := range s.addresses {
|
for _, a := range s.addresses {
|
||||||
@@ -316,7 +237,6 @@ func (s *stack) handleTCP(p packet) {
|
|||||||
window: binary.BigEndian.Uint16(t[14:]),
|
window: binary.BigEndian.Uint16(t[14:]),
|
||||||
options: t[tcpHeaderSize:size],
|
options: t[tcpHeaderSize:size],
|
||||||
payload: t[size:],
|
payload: t[size:],
|
||||||
rest: p.rest,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
@@ -363,7 +283,7 @@ func (s *stack) refuse(f flow, seg segment) {
|
|||||||
binary.BigEndian.PutUint32(t[4:], seg.ack)
|
binary.BigEndian.PutUint32(t[4:], seg.ack)
|
||||||
t[13] = tcpRst
|
t[13] = tcpRst
|
||||||
} else {
|
} else {
|
||||||
ack := seg.seq + uint32(len(seg.payload)+seg.rest)
|
ack := seg.seq + uint32(len(seg.payload))
|
||||||
if seg.flags&(tcpSyn|tcpFin) != 0 {
|
if seg.flags&(tcpSyn|tcpFin) != 0 {
|
||||||
ack++
|
ack++
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-111
@@ -65,20 +65,6 @@ func (l *link) Segments() bool {
|
|||||||
return l.segments
|
return l.segments
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadParts implements segmenter
|
|
||||||
func (l *link) ReadParts(parts [][]byte) (int, error) {
|
|
||||||
select {
|
|
||||||
case packet := <-l.in:
|
|
||||||
n := 0
|
|
||||||
for _, p := range parts {
|
|
||||||
n += copy(p, packet[n:])
|
|
||||||
}
|
|
||||||
return n, nil
|
|
||||||
case <-l.done:
|
|
||||||
return 0, io.EOF
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteSegment implements segmenter as the system does when it sends a segment on: a packet for every mss bytes
|
// WriteSegment implements segmenter as the system does when it sends a segment on: a packet for every mss bytes
|
||||||
func (l *link) WriteSegment(header []byte, payload [][]byte, mss int) error {
|
func (l *link) WriteSegment(header []byte, payload [][]byte, mss int) error {
|
||||||
data := bytes.Join(payload, nil)
|
data := bytes.Join(payload, nil)
|
||||||
@@ -220,7 +206,7 @@ func (p *peer) receive() segment {
|
|||||||
func (p *peer) dialed() (segment, []byte) {
|
func (p *peer) dialed() (segment, []byte) {
|
||||||
p.t.Helper()
|
p.t.Helper()
|
||||||
b := p.packet()
|
b := p.packet()
|
||||||
packet, ok := parsePacket(b, 0)
|
packet, ok := parsePacket(b)
|
||||||
if !ok || packet.proto != protoTCP || len(packet.payload) < tcpHeaderSize {
|
if !ok || packet.proto != protoTCP || len(packet.payload) < tcpHeaderSize {
|
||||||
p.t.Fatal("not a segment: ", b)
|
p.t.Fatal("not a segment: ", b)
|
||||||
}
|
}
|
||||||
@@ -237,7 +223,7 @@ func (p *peer) segment(b []byte) segment {
|
|||||||
} else if checksum(sum(b[:size], 0)) != 0 {
|
} else if checksum(sum(b[:size], 0)) != 0 {
|
||||||
p.t.Fatal("wrong checksum of the ip header")
|
p.t.Fatal("wrong checksum of the ip header")
|
||||||
}
|
}
|
||||||
packet, ok := parsePacket(b, 0)
|
packet, ok := parsePacket(b)
|
||||||
if !ok || packet.proto != protoTCP || packet.src != p.dst.Addr() || packet.dst != p.src.Addr() {
|
if !ok || packet.proto != protoTCP || packet.src != p.dst.Addr() || packet.dst != p.src.Addr() {
|
||||||
p.t.Fatal("not a segment of the connection: ", b)
|
p.t.Fatal("not a segment of the connection: ", b)
|
||||||
}
|
}
|
||||||
@@ -478,90 +464,30 @@ func TestTCPOutOfOrder(t *testing.T) {
|
|||||||
read(t, a.conn, "jklmno")
|
read(t, a.conn, "jklmno")
|
||||||
}
|
}
|
||||||
|
|
||||||
// A segment is as large as the system makes it when the tun does not split what it reads, and one that is large,
|
// A segment is as large as the system makes it when the tun does not split what it reads.
|
||||||
// the next and all taken stays in the buffers that such a tun reads it into.
|
|
||||||
func TestTCPLargeSegment(t *testing.T) {
|
func TestTCPLargeSegment(t *testing.T) {
|
||||||
for _, segments := range []bool{false, true} {
|
|
||||||
conns := make(chan accepted, 1)
|
conns := make(chan accepted, 1)
|
||||||
p, l := newPeer(t, "10.0.0.1:1234", "10.0.0.2:80")
|
p, l := newPeer(t, "10.0.0.1:1234", "10.0.0.2:80")
|
||||||
l.segments = segments
|
newTestStack(t, l, accept(conns))
|
||||||
s := newTestStack(t, l, accept(conns))
|
|
||||||
sent := make([]byte, 200000)
|
|
||||||
rand.Read(sent)
|
|
||||||
// one of no connection is refused as a whole
|
|
||||||
p.send(p.seq, tcpPsh, nil, sent[:20000])
|
|
||||||
if r := p.receive(); r.flags != tcpRst|tcpAck || r.ack != p.seq+20000 {
|
|
||||||
t.Fatal("segments: ", segments, ", not refused: flags ", r.flags, ", ack ", r.ack-p.seq, " after its seq")
|
|
||||||
}
|
|
||||||
p.connect(nil)
|
p.connect(nil)
|
||||||
a := take(t, conns)
|
a := take(t, conns)
|
||||||
|
|
||||||
// one that waits for the one before it, that one, one with a part that came already, small ones around
|
sent := make([]byte, 60000)
|
||||||
// one that fits a buffer, and the rest
|
rand.Read(sent)
|
||||||
for _, part := range [][2]int{{60000, 90000}, {0, 60000}, {80000, 110000}, {110000, 110100}, {110100, 115100}, {115100, 118100}, {118100, 170000}, {170000, 200000}} {
|
p.send(p.seq, tcpAck|tcpPsh, nil, sent)
|
||||||
p.send(p.seq+uint32(part[0]), tcpAck|tcpPsh, nil, sent[part[0]:part[1]])
|
|
||||||
}
|
|
||||||
// all of it is taken before anything is read, for the buffers to be as the stack put it in them
|
|
||||||
for r := p.receive(); r.ack != p.seq+uint32(len(sent)); r = p.receive() {
|
|
||||||
}
|
|
||||||
var received []byte
|
var received []byte
|
||||||
for len(received) < len(sent) {
|
for len(received) < len(sent) {
|
||||||
mb, err := a.conn.(buf.Reader).ReadMultiBuffer()
|
mb, err := a.conn.(buf.Reader).ReadMultiBuffer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
// a buffer that was read into starts with the headers, one that was copied to is full
|
|
||||||
if kept := mb[0].Len() == buf.Size-ipv4HeaderSize-tcpHeaderSize; len(received) == 0 && kept != segments {
|
|
||||||
t.Fatal("segments: ", segments, ", the first buffer has ", mb[0].Len(), " bytes")
|
|
||||||
}
|
|
||||||
for _, b := range mb {
|
for _, b := range mb {
|
||||||
// and a small one is copied to where the one before it ends
|
|
||||||
if b.Len() < 1000 {
|
|
||||||
t.Fatal("segments: ", segments, ", a buffer of ", b.Len(), " bytes")
|
|
||||||
}
|
|
||||||
received = append(received, b.Bytes()...)
|
received = append(received, b.Bytes()...)
|
||||||
}
|
}
|
||||||
buf.ReleaseMulti(mb)
|
buf.ReleaseMulti(mb)
|
||||||
}
|
}
|
||||||
if !bytes.Equal(received, sent) {
|
if !bytes.Equal(received, sent) {
|
||||||
t.Fatal("segments: ", segments, ", read something else than what was sent")
|
t.Fatal("read something else than what was sent")
|
||||||
}
|
|
||||||
|
|
||||||
// no more is taken than the window while nobody reads, of the last one a part,
|
|
||||||
// and what is taken is acknowledged when a quarter of the window is
|
|
||||||
p.seq += uint32(len(sent))
|
|
||||||
sent = make([]byte, (int(s.window)/50000+1)*50000)
|
|
||||||
rand.Read(sent)
|
|
||||||
for i := 0; i < len(sent); i += 50000 {
|
|
||||||
p.send(p.seq+uint32(i), tcpAck|tcpPsh, nil, sent[i:i+50000])
|
|
||||||
}
|
|
||||||
for acked := p.seq; ; {
|
|
||||||
r := p.receive()
|
|
||||||
if taken := int32(r.ack - p.seq); taken > s.window {
|
|
||||||
t.Fatal("segments: ", segments, ", ", taken, " bytes taken with a window of ", s.window)
|
|
||||||
} else if taken == s.window {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// one that acknowledges nothing more tells that the window opened for what was read before
|
|
||||||
if r.ack != acked && int32(r.ack-acked) < s.window/4 {
|
|
||||||
t.Fatal("segments: ", segments, ", ", r.ack-acked, " bytes acknowledged with a window of ", s.window)
|
|
||||||
}
|
|
||||||
acked = r.ack
|
|
||||||
}
|
|
||||||
received = received[:0]
|
|
||||||
for len(received) < int(s.window) {
|
|
||||||
mb, err := a.conn.(buf.Reader).ReadMultiBuffer()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
for _, b := range mb {
|
|
||||||
received = append(received, b.Bytes()...)
|
|
||||||
}
|
|
||||||
buf.ReleaseMulti(mb)
|
|
||||||
}
|
|
||||||
if !bytes.Equal(received, sent[:s.window]) {
|
|
||||||
t.Fatal("segments: ", segments, ", read something else than what the window took")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1156,20 +1082,13 @@ func TestTCPSynAfterClose(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// A UDP packet starts a connection, which answers from any address, and an echo request is answered.
|
// A UDP packet starts a connection, which answers from any address, and an echo request is answered.
|
||||||
// Both can be larger than a buffer of a tun that reads into more than one.
|
|
||||||
func TestUDPAndICMP(t *testing.T) {
|
func TestUDPAndICMP(t *testing.T) {
|
||||||
large := make([]byte, 20000)
|
for _, test := range []struct{ src, dst, other string }{
|
||||||
rand.Read(large)
|
{"10.0.0.1:1234", "10.0.0.2:53", "10.0.0.3:5353"},
|
||||||
for _, test := range []struct {
|
{"[fd00::1]:1234", "[fd00::2]:53", "[fd00::3]:5353"},
|
||||||
src, dst, other string
|
|
||||||
segments bool
|
|
||||||
}{
|
|
||||||
{"10.0.0.1:1234", "10.0.0.2:53", "10.0.0.3:5353", false},
|
|
||||||
{"[fd00::1]:1234", "[fd00::2]:53", "[fd00::3]:5353", true},
|
|
||||||
} {
|
} {
|
||||||
conns := make(chan accepted, 1)
|
conns := make(chan accepted, 1)
|
||||||
p, l := newPeer(t, test.src, test.dst)
|
p, l := newPeer(t, test.src, test.dst)
|
||||||
l.segments = test.segments
|
|
||||||
newTestStack(t, l, accept(conns))
|
newTestStack(t, l, accept(conns))
|
||||||
|
|
||||||
var b [ipv6HeaderSize + udpHeaderSize]byte
|
var b [ipv6HeaderSize + udpHeaderSize]byte
|
||||||
@@ -1186,13 +1105,6 @@ func TestUDPAndICMP(t *testing.T) {
|
|||||||
t.Fatal("read ", mb.String(), " from ", a.conn.RemoteAddr(), " to ", a.dest, ", error: ", err)
|
t.Fatal("read ", mb.String(), " from ", a.conn.RemoteAddr(), " to ", a.dest, ", error: ", err)
|
||||||
}
|
}
|
||||||
buf.ReleaseMulti(mb)
|
buf.ReleaseMulti(mb)
|
||||||
binary.BigEndian.PutUint16(u[4:], uint16(udpHeaderSize+len(large)))
|
|
||||||
setIPLength(b[:n], n+udpHeaderSize+len(large))
|
|
||||||
l.in <- append(b[:n+udpHeaderSize:n+udpHeaderSize], large...)
|
|
||||||
if mb, err = a.conn.(buf.Reader).ReadMultiBuffer(); err != nil || len(mb) != 1 || !bytes.Equal(mb[0].Bytes(), large) {
|
|
||||||
t.Fatal("read ", mb.Len(), " bytes of a large packet, error: ", err)
|
|
||||||
}
|
|
||||||
buf.ReleaseMulti(mb)
|
|
||||||
|
|
||||||
for _, from := range []string{test.dst, test.other} {
|
for _, from := range []string{test.dst, test.other} {
|
||||||
answer := buf.FromBytes([]byte("defg"))
|
answer := buf.FromBytes([]byte("defg"))
|
||||||
@@ -1202,7 +1114,7 @@ func TestUDPAndICMP(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
raw := p.packet()
|
raw := p.packet()
|
||||||
packet, ok := parsePacket(raw, 0)
|
packet, ok := parsePacket(raw)
|
||||||
u := packet.payload
|
u := packet.payload
|
||||||
// the system may have to split it on the way to where it goes
|
// the system may have to split it on the way to where it goes
|
||||||
if !ok || raw[0]>>4 == 4 && raw[6] != 0 || packet.proto != protoUDP || netip.AddrPortFrom(packet.src, binary.BigEndian.Uint16(u)).String() != from ||
|
if !ok || raw[0]>>4 == 4 && raw[6] != 0 || packet.proto != protoUDP || netip.AddrPortFrom(packet.src, binary.BigEndian.Uint16(u)).String() != from ||
|
||||||
@@ -1221,12 +1133,12 @@ func TestUDPAndICMP(t *testing.T) {
|
|||||||
proto, request, reply = protoICMPv6, 128, 129
|
proto, request, reply = protoICMPv6, 128, 129
|
||||||
}
|
}
|
||||||
n = putIPHeader(b[:], proto, p.src.Addr(), p.dst.Addr())
|
n = putIPHeader(b[:], proto, p.src.Addr(), p.dst.Addr())
|
||||||
setIPLength(b[:n], n+8+len(large))
|
setIPLength(b[:n], n+12)
|
||||||
l.in <- append(append(b[:n:n], request, 0, 0, 0, 1, 2, 3, 4), large...)
|
l.in <- append(b[:n:n], request, 0, 0, 0, 1, 2, 3, 4, 'a', 'b', 'c', 'd')
|
||||||
packet, ok := parsePacket(p.joined(), 0)
|
packet, ok := parsePacket(p.packet())
|
||||||
m := packet.payload
|
m := packet.payload
|
||||||
if !ok || packet.proto != proto || packet.src != p.dst.Addr() || packet.dst != p.src.Addr() || m[0] != reply || string(m[4:8]) != "\x01\x02\x03\x04" || !bytes.Equal(m[8:], large) {
|
if !ok || packet.proto != proto || packet.src != p.dst.Addr() || packet.dst != p.src.Addr() || m[0] != reply || string(m[4:]) != "\x01\x02\x03\x04abcd" {
|
||||||
t.Fatal("wrong echo reply of ", len(m), " bytes from ", packet.src, " to ", packet.dst)
|
t.Fatal("wrong echo reply: ", packet)
|
||||||
}
|
}
|
||||||
var ip [ipv6HeaderSize]byte
|
var ip [ipv6HeaderSize]byte
|
||||||
size := putIPHeader(ip[:], proto, packet.src, packet.dst)
|
size := putIPHeader(ip[:], proto, packet.src, packet.dst)
|
||||||
@@ -1461,7 +1373,7 @@ func TestUDPDial(t *testing.T) {
|
|||||||
t.Fatal("wrong header of the packet to ", to, ": ", raw[:ipv4HeaderSize])
|
t.Fatal("wrong header of the packet to ", to, ": ", raw[:ipv4HeaderSize])
|
||||||
}
|
}
|
||||||
numbers[to][binary.BigEndian.Uint16(raw[4:])] = true
|
numbers[to][binary.BigEndian.Uint16(raw[4:])] = true
|
||||||
packet, ok := parsePacket(raw, 0)
|
packet, ok := parsePacket(raw)
|
||||||
u := packet.payload
|
u := packet.payload
|
||||||
if !ok || packet.proto != protoUDP || netip.AddrPortFrom(packet.src, binary.BigEndian.Uint16(u)) != local ||
|
if !ok || packet.proto != protoUDP || netip.AddrPortFrom(packet.src, binary.BigEndian.Uint16(u)) != local ||
|
||||||
netip.AddrPortFrom(packet.dst, binary.BigEndian.Uint16(u[2:])) != to || string(u[udpHeaderSize:]) != "abc" {
|
netip.AddrPortFrom(packet.dst, binary.BigEndian.Uint16(u[2:])) != to || string(u[udpHeaderSize:]) != "abc" {
|
||||||
@@ -1483,7 +1395,7 @@ func TestUDPDial(t *testing.T) {
|
|||||||
if _, err := conn.WriteTo([]byte("abc"), &net.UDPAddr{IP: across.Addr().AsSlice(), Port: int(across.Port())}); err != nil {
|
if _, err := conn.WriteTo([]byte("abc"), &net.UDPAddr{IP: across.Addr().AsSlice(), Port: int(across.Port())}); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if packet, ok := parsePacket(p.packet(), 0); !ok || packet.proto != protoUDP || packet.dst != across.Addr() ||
|
if packet, ok := parsePacket(p.packet()); !ok || packet.proto != protoUDP || packet.dst != across.Addr() ||
|
||||||
netip.AddrPortFrom(packet.src, binary.BigEndian.Uint16(packet.payload)) != acrossLocal {
|
netip.AddrPortFrom(packet.src, binary.BigEndian.Uint16(packet.payload)) != acrossLocal {
|
||||||
t.Fatal("wrong packet to ", across, ": ", packet)
|
t.Fatal("wrong packet to ", across, ": ", packet)
|
||||||
}
|
}
|
||||||
@@ -1495,7 +1407,7 @@ func TestUDPDial(t *testing.T) {
|
|||||||
// a stack that takes no connections is one host: it answers a ping of its address and of no other
|
// a stack that takes no connections is one host: it answers a ping of its address and of no other
|
||||||
l.in <- ping(p.src.Addr(), netip.MustParseAddrPort(test.other).Addr())
|
l.in <- ping(p.src.Addr(), netip.MustParseAddrPort(test.other).Addr())
|
||||||
l.in <- ping(p.src.Addr(), local.Addr())
|
l.in <- ping(p.src.Addr(), local.Addr())
|
||||||
if packet, ok := parsePacket(p.packet(), 0); !ok || packet.src != local.Addr() || packet.dst != p.src.Addr() || packet.payload[0]&^129 != 0 {
|
if packet, ok := parsePacket(p.packet()); !ok || packet.src != local.Addr() || packet.dst != p.src.Addr() || packet.payload[0]&^129 != 0 {
|
||||||
t.Fatal("wrong echo reply: ", packet)
|
t.Fatal("wrong echo reply: ", packet)
|
||||||
}
|
}
|
||||||
p.silent(20 * time.Millisecond)
|
p.silent(20 * time.Millisecond)
|
||||||
@@ -1961,7 +1873,7 @@ func TestFragments(t *testing.T) {
|
|||||||
if err := a.conn.(buf.Writer).WriteMultiBuffer(buf.MultiBuffer{buf.FromBytes(sent)}); err != nil {
|
if err := a.conn.(buf.Writer).WriteMultiBuffer(buf.MultiBuffer{buf.FromBytes(sent)}); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
packet, ok := parsePacket(p.joined(), 0)
|
packet, ok := parsePacket(p.joined())
|
||||||
u := packet.payload
|
u := packet.payload
|
||||||
if !ok || packet.fragment || packet.proto != protoUDP || netip.AddrPortFrom(packet.src, binary.BigEndian.Uint16(u)) != p.dst ||
|
if !ok || packet.fragment || packet.proto != protoUDP || netip.AddrPortFrom(packet.src, binary.BigEndian.Uint16(u)) != p.dst ||
|
||||||
netip.AddrPortFrom(packet.dst, binary.BigEndian.Uint16(u[2:])) != p.src || int(binary.BigEndian.Uint16(u[4:])) != len(u) || !bytes.Equal(u[udpHeaderSize:], sent) {
|
netip.AddrPortFrom(packet.dst, binary.BigEndian.Uint16(u[2:])) != p.src || int(binary.BigEndian.Uint16(u[4:])) != len(u) || !bytes.Equal(u[udpHeaderSize:], sent) {
|
||||||
@@ -1986,7 +1898,7 @@ func TestFragments(t *testing.T) {
|
|||||||
mixed := split(summed(udpPacket(p.src, p.dst, bytes.Repeat([]byte{1}, 2000))), 1024, 11)
|
mixed := split(summed(udpPacket(p.src, p.dst, bytes.Repeat([]byte{1}, 2000))), 1024, 11)
|
||||||
l.in <- mixed[0]
|
l.in <- mixed[0]
|
||||||
l.in <- split(summed(udpPacket(p.src, p.dst, bytes.Repeat([]byte{2}, 2000))), 1024, 11)[1]
|
l.in <- split(summed(udpPacket(p.src, p.dst, bytes.Repeat([]byte{2}, 2000))), 1024, 11)[1]
|
||||||
packet, ok = parsePacket(p.joined(), 0)
|
packet, ok = parsePacket(p.joined())
|
||||||
m = packet.payload
|
m = packet.payload
|
||||||
if !ok || packet.proto != proto || packet.src != p.dst.Addr() || packet.dst != p.src.Addr() || m[0] != reply || !bytes.Equal(m[8:], sent) {
|
if !ok || packet.proto != proto || packet.src != p.dst.Addr() || packet.dst != p.src.Addr() || m[0] != reply || !bytes.Equal(m[8:], sent) {
|
||||||
t.Fatal("wrong echo reply: ", packet.src, packet.dst, packet.proto, len(m))
|
t.Fatal("wrong echo reply: ", packet.src, packet.dst, packet.proto, len(m))
|
||||||
@@ -2128,7 +2040,7 @@ func TestChecksumsAndLoopback(t *testing.T) {
|
|||||||
l.in <- damaged(echo)
|
l.in <- damaged(echo)
|
||||||
p.silent(50 * time.Millisecond)
|
p.silent(50 * time.Millisecond)
|
||||||
l.in <- echo
|
l.in <- echo
|
||||||
if packet, ok := parsePacket(p.packet(), 0); !ok || packet.proto != proto || string(packet.payload[4:]) != "\x01\x02\x03\x04abcd" {
|
if packet, ok := parsePacket(p.packet()); !ok || packet.proto != proto || string(packet.payload[4:]) != "\x01\x02\x03\x04abcd" {
|
||||||
t.Fatal("wrong echo reply: ", packet)
|
t.Fatal("wrong echo reply: ", packet)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,9 +49,6 @@ const (
|
|||||||
// tcpParts of how many parts of what was written it is made at most
|
// tcpParts of how many parts of what was written it is made at most
|
||||||
tcpSegment = math.MaxUint16 - ipv6HeaderSize - tcpHeaderSize
|
tcpSegment = math.MaxUint16 - ipv6HeaderSize - tcpHeaderSize
|
||||||
tcpParts = 7
|
tcpParts = 7
|
||||||
// tcpKept is how large a payload is at least for the buffers that it was read into to be kept. A smaller one
|
|
||||||
// is copied to where the one before it ends, for a reader to get buffers that are full
|
|
||||||
tcpKept = buf.Size / 2
|
|
||||||
|
|
||||||
// tcpTick is how often a connection that waits for something looks at what it waits for
|
// tcpTick is how often a connection that waits for something looks at what it waits for
|
||||||
tcpTick = 100 * time.Millisecond
|
tcpTick = 100 * time.Millisecond
|
||||||
@@ -90,9 +87,7 @@ type segment struct {
|
|||||||
flags byte
|
flags byte
|
||||||
window uint16
|
window uint16
|
||||||
options []byte
|
options []byte
|
||||||
// payload is without its last rest bytes when those are in other buffers of the stack, see stack.whole
|
|
||||||
payload []byte
|
payload []byte
|
||||||
rest int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// nextOption returns the first option of o and what follows it, kind is zero when there is none
|
// nextOption returns the first option of o and what follows it, kind is zero when there is none
|
||||||
@@ -754,12 +749,6 @@ func (c *tcpConn) space() uint32 {
|
|||||||
|
|
||||||
// receive takes the payload of a segment. c.mu is held
|
// receive takes the payload of a segment. c.mu is held
|
||||||
func (c *tcpConn) receive(s segment) {
|
func (c *tcpConn) receive(s segment) {
|
||||||
// what is large, the next and all taken stays in the buffers that it was read into
|
|
||||||
size := len(s.payload) + s.rest
|
|
||||||
keep := c.stack.bufs != nil && size >= tcpKept && s.seq == c.rcvNxt && size <= int(c.window-c.rcvLen)
|
|
||||||
if !keep {
|
|
||||||
s.payload = c.stack.whole(s.payload, s.rest)
|
|
||||||
}
|
|
||||||
seq, payload, fin := s.seq, s.payload, s.flags&tcpFin != 0
|
seq, payload, fin := s.seq, s.payload, s.flags&tcpFin != 0
|
||||||
if len(payload) == 0 && !fin {
|
if len(payload) == 0 && !fin {
|
||||||
if int32(c.rcvNxt-seq) > 0 {
|
if int32(c.rcvNxt-seq) > 0 {
|
||||||
@@ -800,14 +789,9 @@ func (c *tcpConn) receive(s segment) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if keep {
|
|
||||||
c.rcvQueue = c.stack.keep(c.rcvQueue, payload, s.rest)
|
|
||||||
} else {
|
|
||||||
size = len(payload)
|
|
||||||
c.rcvQueue = buf.MergeBytes(c.rcvQueue, payload)
|
c.rcvQueue = buf.MergeBytes(c.rcvQueue, payload)
|
||||||
}
|
c.rcvLen += int32(len(payload))
|
||||||
c.rcvLen += int32(size)
|
c.rcvNxt += uint32(len(payload))
|
||||||
c.rcvNxt += uint32(size)
|
|
||||||
for !fin && len(c.rcvHeld) > 0 && int32(c.rcvNxt-c.rcvHeld[0].seq) >= 0 {
|
for !fin && len(c.rcvHeld) > 0 && int32(c.rcvNxt-c.rcvHeld[0].seq) >= 0 {
|
||||||
h := c.rcvHeld[0]
|
h := c.rcvHeld[0]
|
||||||
if before := int32(c.rcvNxt - h.seq); before < h.data.Len() {
|
if before := int32(c.rcvNxt - h.seq); before < h.data.Len() {
|
||||||
@@ -825,7 +809,7 @@ func (c *tcpConn) receive(s segment) {
|
|||||||
c.readable.Signal()
|
c.readable.Signal()
|
||||||
// the reader acknowledges what it takes, with one segment for all that came by then.
|
// the reader acknowledges what it takes, with one segment for all that came by then.
|
||||||
// It is not waited for when it does not read for long
|
// It is not waited for when it does not read for long
|
||||||
if fin || now || c.rcvNxt-c.rcvAcked >= uint32(c.window/4) {
|
if fin || now || c.rcvNxt-c.rcvAcked >= tcpWindow/4 {
|
||||||
c.sendAck()
|
c.sendAck()
|
||||||
} else {
|
} else {
|
||||||
c.arm(tcpTick)
|
c.arm(tcpTick)
|
||||||
|
|||||||
+2
-5
@@ -16,13 +16,10 @@ type Tun interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// segmenter is a tun whose other side takes TCP segments that are larger than the MTU,
|
// segmenter is a tun whose other side takes TCP segments that are larger than the MTU,
|
||||||
// splits them only if it has to send them on, and completes their checksum then.
|
// splits them only if it has to send them on, and completes their checksum then
|
||||||
// What it sends comes as it is too, not split to the MTU
|
|
||||||
type segmenter interface {
|
type segmenter interface {
|
||||||
// Segments reports whether ReadParts and WriteSegment can be used
|
// Segments reports whether WriteSegment can be used
|
||||||
Segments() bool
|
Segments() bool
|
||||||
// ReadParts waits for a packet, reads it into parts, one after the other, and returns its size
|
|
||||||
ReadParts(parts [][]byte) (int, error)
|
|
||||||
// WriteSegment writes a packet with a TCP segment whose payload is in parts, to be sent on with mss bytes
|
// WriteSegment writes a packet with a TCP segment whose payload is in parts, to be sent on with mss bytes
|
||||||
// of it at most in one. Its checksum is that of the pseudo-header only, not inverted
|
// of it at most in one. Its checksum is that of the pseudo-header only, not inverted
|
||||||
WriteSegment(header []byte, payload [][]byte, mss int) error
|
WriteSegment(header []byte, payload [][]byte, mss int) error
|
||||||
|
|||||||
@@ -558,16 +558,19 @@ func ioctlPtr(fd int, req uint, arg unsafe.Pointer) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func setinterface(network, address string, fd uintptr, iface *net.Interface) error {
|
func setinterface(network, address string, fd uintptr, iface *net.Interface) error {
|
||||||
|
var err1, err2 error
|
||||||
|
|
||||||
switch network {
|
switch network {
|
||||||
case "tcp6", "udp6", "ip6":
|
case "tcp6", "udp6", "ip6":
|
||||||
// IPV6_BOUND_IF binds the IPv4 traffic of a dual-stack socket as
|
err1 = unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_BOUND_IF, iface.Index)
|
||||||
// well, and an IPv6 socket rejects IP_BOUND_IF with EINVAL.
|
fallthrough
|
||||||
return unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_BOUND_IF, iface.Index)
|
|
||||||
case "tcp4", "udp4", "ip4":
|
case "tcp4", "udp4", "ip4":
|
||||||
return unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_BOUND_IF, iface.Index)
|
err2 = unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_BOUND_IF, iface.Index)
|
||||||
default:
|
default:
|
||||||
panic(network + " " + address)
|
panic(network + " " + address)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return errors.Join(err1, err2)
|
||||||
}
|
}
|
||||||
|
|
||||||
func findOutboundInterface(tunIndex int, fixedName string) (*net.Interface, error) {
|
func findOutboundInterface(tunIndex int, fixedName string) (*net.Interface, error) {
|
||||||
|
|||||||
@@ -3,10 +3,7 @@
|
|||||||
package tun
|
package tun
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -234,40 +231,3 @@ 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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+1
-43
@@ -6,8 +6,6 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
@@ -24,14 +22,6 @@ type fdTun struct {
|
|||||||
// vnet is the size of the virtio_net_hdr in front of every packet, zero if the tun has none.
|
// vnet is the size of the virtio_net_hdr in front of every packet, zero if the tun has none.
|
||||||
// With it the system does not split what it sends to the tun and takes what is not split from it
|
// With it the system does not split what it sends to the tun and takes what is not split from it
|
||||||
vnet int
|
vnet int
|
||||||
// raw is file for ReadParts, readv what it calls when there may be a packet, iovecs where that reads to:
|
|
||||||
// header, which takes the virtio_net_hdr, and the parts. read and readErr are what came of it
|
|
||||||
raw syscall.RawConn
|
|
||||||
readv func(fd uintptr) bool
|
|
||||||
header [packetRoom]byte
|
|
||||||
iovecs []unix.Iovec
|
|
||||||
read int
|
|
||||||
readErr error
|
|
||||||
|
|
||||||
writeLock sync.RWMutex
|
writeLock sync.RWMutex
|
||||||
closed bool
|
closed bool
|
||||||
@@ -39,25 +29,7 @@ type fdTun struct {
|
|||||||
|
|
||||||
// newFdTun takes a file descriptor that does not block
|
// newFdTun takes a file descriptor that does not block
|
||||||
func newFdTun(fd, vnet int) *fdTun {
|
func newFdTun(fd, vnet int) *fdTun {
|
||||||
t := &fdTun{fd: fd, file: os.NewFile(uintptr(fd), "/dev/net/tun"), vnet: vnet}
|
return &fdTun{fd: fd, file: os.NewFile(uintptr(fd), "/dev/net/tun"), vnet: vnet}
|
||||||
t.raw, _ = t.file.SyscallConn()
|
|
||||||
t.readv = func(fd uintptr) bool {
|
|
||||||
for {
|
|
||||||
n, _, errno := unix.Syscall(unix.SYS_READV, fd, uintptr(unsafe.Pointer(&t.iovecs[0])), uintptr(len(t.iovecs)))
|
|
||||||
switch errno {
|
|
||||||
case 0:
|
|
||||||
t.read, t.readErr = int(n), nil
|
|
||||||
case unix.EINTR:
|
|
||||||
continue
|
|
||||||
case unix.EAGAIN:
|
|
||||||
return false
|
|
||||||
default:
|
|
||||||
t.read, t.readErr = 0, errno
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return t
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *fdTun) ReadPacket(b []byte) ([]byte, error) {
|
func (t *fdTun) ReadPacket(b []byte) ([]byte, error) {
|
||||||
@@ -66,20 +38,6 @@ func (t *fdTun) ReadPacket(b []byte) ([]byte, error) {
|
|||||||
return b[min(t.vnet, n):n], err
|
return b[min(t.vnet, n):n], err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadParts implements segmenter
|
|
||||||
func (t *fdTun) ReadParts(parts [][]byte) (int, error) {
|
|
||||||
t.iovecs = append(t.iovecs[:0], unix.Iovec{Base: &t.header[0]})
|
|
||||||
t.iovecs[0].SetLen(t.vnet)
|
|
||||||
for i, p := range parts {
|
|
||||||
t.iovecs = append(t.iovecs, unix.Iovec{Base: &p[0]})
|
|
||||||
t.iovecs[i+1].SetLen(len(p))
|
|
||||||
}
|
|
||||||
if err := t.raw.Read(t.readv); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return max(t.read-t.vnet, 0), t.readErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *fdTun) WritePacket(header, payload []byte) error {
|
func (t *fdTun) WritePacket(header, payload []byte) error {
|
||||||
// a virtio_net_hdr of zeros leaves nothing to the system
|
// a virtio_net_hdr of zeros leaves nothing to the system
|
||||||
var vnet [packetRoom]byte
|
var vnet [packetRoom]byte
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
//go:build linux
|
|
||||||
|
|
||||||
package tun
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/rand"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A tun with a virtio_net_hdr reads one packet at a time into parts, without the header, waits for the next one,
|
|
||||||
// and stops waiting when it is closed.
|
|
||||||
func TestReadParts(t *testing.T) {
|
|
||||||
// what a tun is like: one packet for every read, and a file descriptor that does not block
|
|
||||||
fds, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_SEQPACKET|unix.SOCK_NONBLOCK|unix.SOCK_CLOEXEC, 0)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer unix.Close(fds[1])
|
|
||||||
tun := newFdTun(fds[0], vnetSize)
|
|
||||||
defer tun.closeFile()
|
|
||||||
|
|
||||||
large := make([]byte, 20000)
|
|
||||||
rand.Read(large)
|
|
||||||
parts := [][]byte{make([]byte, 8192), make([]byte, 8192), make([]byte, 8192)}
|
|
||||||
send := func(packet []byte) {
|
|
||||||
if _, err := unix.Write(fds[1], append(bytes.Repeat([]byte{0xee}, vnetSize), packet...)); err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
send(large)
|
|
||||||
send([]byte("small"))
|
|
||||||
for _, packet := range [][]byte{large, []byte("small")} {
|
|
||||||
n, err := tun.ReadParts(parts)
|
|
||||||
if err != nil || n != len(packet) || !bytes.Equal(bytes.Join(parts, nil)[:n], packet) {
|
|
||||||
t.Fatal("read ", n, " bytes of ", len(packet), ", error: ", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
time.Sleep(50 * time.Millisecond)
|
|
||||||
send([]byte("later"))
|
|
||||||
}()
|
|
||||||
if n, err := tun.ReadParts(parts); err != nil || string(parts[0][:n]) != "later" {
|
|
||||||
t.Fatal("read ", parts[0][:n], " after waiting, error: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
read := make(chan error)
|
|
||||||
go func() {
|
|
||||||
_, err := tun.ReadParts(parts)
|
|
||||||
read <- err
|
|
||||||
}()
|
|
||||||
time.Sleep(50 * time.Millisecond)
|
|
||||||
tun.closeFile()
|
|
||||||
select {
|
|
||||||
case err := <-read:
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("read after the tun was closed")
|
|
||||||
}
|
|
||||||
case <-time.After(5 * time.Second):
|
|
||||||
t.Fatal("still waits after the tun was closed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user