Compare commits

..
7 Commits
Author SHA1 Message Date
Fangliding a1be7c52f3 Simplify CI 2026-10-28 00:36:54 +08:00
Fangliding f45f546654 fix platform issue 2026-10-28 00:24:04 +08:00
Fangliding 9a77d85656 pure go generator! 2026-10-27 22:49:18 +08:00
Meo597 75c076b744 cache protoc 2026-10-27 00:09:04 +08:00
Meo597 97e53af8c2 regenerate all with go 1.27 2026-10-26 23:14:54 +08:00
Meo597 0a8b761d12 regenerate all 2026-10-26 22:54:32 +08:00
Meo597 e006c2487c CI: Enforce stricter checks on generated protobuf files 2026-10-26 22:51:19 +08:00
20 changed files with 449 additions and 362 deletions
+3 -18
View File
@@ -35,24 +35,7 @@ jobs:
if: steps.check-assets.outputs.missing == 'true' if: steps.check-assets.outputs.missing == 'true'
run: sleep 90 run: sleep 90
check-proto: check-format-and-proto:
runs-on: ubuntu-latest
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
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:
@@ -68,6 +51,8 @@ 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
+12 -12
View File
@@ -152,23 +152,23 @@ func (x *HealthPingMeasurementResult) GetMin() int64 {
type OutboundStatus struct { type OutboundStatus struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
// @Document Whether this outbound is usable // @Document Whether this outbound is usable
// @Restriction ReadOnlyForUser //@Restriction ReadOnlyForUser
Alive bool `protobuf:"varint,1,opt,name=alive,proto3" json:"alive,omitempty"` Alive bool `protobuf:"varint,1,opt,name=alive,proto3" json:"alive,omitempty"`
// @Document The time for probe request to finish. // @Document The time for probe request to finish.
// @Type time.ms //@Type time.ms
// @Restriction ReadOnlyForUser //@Restriction ReadOnlyForUser
Delay int64 `protobuf:"varint,2,opt,name=delay,proto3" json:"delay,omitempty"` Delay int64 `protobuf:"varint,2,opt,name=delay,proto3" json:"delay,omitempty"`
// @Document The last error caused this outbound failed to relay probe request // @Document The last error caused this outbound failed to relay probe request
// @Restriction NotMachineReadable //@Restriction NotMachineReadable
LastErrorReason string `protobuf:"bytes,3,opt,name=last_error_reason,json=lastErrorReason,proto3" json:"last_error_reason,omitempty"` LastErrorReason string `protobuf:"bytes,3,opt,name=last_error_reason,json=lastErrorReason,proto3" json:"last_error_reason,omitempty"`
// @Document The outbound tag for this Server // @Document The outbound tag for this Server
// @Type id.outboundTag //@Type id.outboundTag
OutboundTag string `protobuf:"bytes,4,opt,name=outbound_tag,json=outboundTag,proto3" json:"outbound_tag,omitempty"` OutboundTag string `protobuf:"bytes,4,opt,name=outbound_tag,json=outboundTag,proto3" json:"outbound_tag,omitempty"`
// @Document The time this outbound is known to be alive // @Document The time this outbound is known to be alive
// @Type id.outboundTag //@Type id.outboundTag
LastSeenTime int64 `protobuf:"varint,5,opt,name=last_seen_time,json=lastSeenTime,proto3" json:"last_seen_time,omitempty"` LastSeenTime int64 `protobuf:"varint,5,opt,name=last_seen_time,json=lastSeenTime,proto3" json:"last_seen_time,omitempty"`
// @Document The time this outbound is tried // @Document The time this outbound is tried
// @Type id.outboundTag //@Type id.outboundTag
LastTryTime int64 `protobuf:"varint,6,opt,name=last_try_time,json=lastTryTime,proto3" json:"last_try_time,omitempty"` LastTryTime int64 `protobuf:"varint,6,opt,name=last_try_time,json=lastTryTime,proto3" json:"last_try_time,omitempty"`
HealthPing *HealthPingMeasurementResult `protobuf:"bytes,7,opt,name=health_ping,json=healthPing,proto3" json:"health_ping,omitempty"` HealthPing *HealthPingMeasurementResult `protobuf:"bytes,7,opt,name=health_ping,json=healthPing,proto3" json:"health_ping,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
@@ -257,14 +257,14 @@ func (x *OutboundStatus) GetHealthPing() *HealthPingMeasurementResult {
type ProbeResult struct { type ProbeResult struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
// @Document Whether this outbound is usable // @Document Whether this outbound is usable
// @Restriction ReadOnlyForUser //@Restriction ReadOnlyForUser
Alive bool `protobuf:"varint,1,opt,name=alive,proto3" json:"alive,omitempty"` Alive bool `protobuf:"varint,1,opt,name=alive,proto3" json:"alive,omitempty"`
// @Document The time for probe request to finish. // @Document The time for probe request to finish.
// @Type time.ms //@Type time.ms
// @Restriction ReadOnlyForUser //@Restriction ReadOnlyForUser
Delay int64 `protobuf:"varint,2,opt,name=delay,proto3" json:"delay,omitempty"` Delay int64 `protobuf:"varint,2,opt,name=delay,proto3" json:"delay,omitempty"`
// @Document The error caused this outbound failed to relay probe request // @Document The error caused this outbound failed to relay probe request
// @Restriction NotMachineReadable //@Restriction NotMachineReadable
LastErrorReason string `protobuf:"bytes,3,opt,name=last_error_reason,json=lastErrorReason,proto3" json:"last_error_reason,omitempty"` LastErrorReason string `protobuf:"bytes,3,opt,name=last_error_reason,json=lastErrorReason,proto3" json:"last_error_reason,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
@@ -324,7 +324,7 @@ func (x *ProbeResult) GetLastErrorReason() string {
type Intensity struct { type Intensity struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
// @Document The time interval for a probe request in ms. // @Document The time interval for a probe request in ms.
// @Type time.ms //@Type time.ms
ProbeInterval uint32 `protobuf:"varint,1,opt,name=probe_interval,json=probeInterval,proto3" json:"probe_interval,omitempty"` ProbeInterval uint32 `protobuf:"varint,1,opt,name=probe_interval,json=probeInterval,proto3" json:"probe_interval,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
+1
View File
@@ -46,6 +46,7 @@ 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;
+2 -2
View File
@@ -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 {
+11 -8
View File
@@ -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
} }
+24 -24
View File
@@ -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
} }
+2 -47
View File
@@ -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)
}
}
+2 -2
View File
@@ -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)
} }
+65 -2
View 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)
}
+88
View File
@@ -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)
}
})
}
}
+2 -2
View File
@@ -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)
} }
+2 -2
View File
@@ -1,5 +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/protobuf/cmd/protoc-gen-go@v1.36.11
//go:generate go install -v google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest //go:generate go install -v google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.6.0
//go:generate go run ../infra/vprotogen/main.go -pwd ./.. //go:generate go run ../infra/vprotogen/main.go -pwd ./..
+8 -1
View File
@@ -4,6 +4,7 @@ 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
@@ -32,7 +33,7 @@ require (
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb
golang.zx2c4.com/wireguard/windows v1.1.1 golang.zx2c4.com/wireguard/windows v1.1.1
google.golang.org/grpc v1.84.0 google.golang.org/grpc v1.84.0
google.golang.org/protobuf v1.36.12 google.golang.org/protobuf v1.36.11
h12.io/socks v1.0.3 h12.io/socks v1.0.3
layeh.com/gopher-luar v1.0.11 layeh.com/gopher-luar v1.0.11
lukechampine.com/blake3 v1.4.1 lukechampine.com/blake3 v1.4.1
@@ -58,6 +59,12 @@ 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.0 // 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
)
+6
View File
@@ -2,6 +2,8 @@ github.com/andybalholm/brotli v1.2.3 h1:8H1qwOkl2LPfjf3YezB90JnCliZb6SInJ/OJkEbA
github.com/andybalholm/brotli v1.2.3/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/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=
@@ -152,6 +154,10 @@ 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.0 h1:6Al3kEFFP9VJhRz3DID6quisgPnTeZVr4lep9kkxdPA=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.0/go.mod h1:QLvsjh0OIR0TYBeiu2bkWGTJBUNQ64st52iWj/yA93I=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
google.golang.org/protobuf v1.36.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=
+1 -1
View File
@@ -19,7 +19,7 @@ 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.ResolveAsset(c.File); err != nil { if _, err := filesystem.StatAsset(c.File); err != nil {
return nil, errors.New("invalid geodata asset file: ", c.File).Base(err) return nil, errors.New("invalid geodata asset file: ", c.File).Base(err)
} }
return &geodata.Asset{ return &geodata.Asset{
+165 -200
View File
@@ -1,149 +1,87 @@
package main package main
import ( import (
"errors" "bytes"
"context"
"flag" "flag"
"fmt" "fmt"
"go/build" "io/fs"
"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 directory = flag.String("pwd", "", "Working directory of Xray vprotogen.") var (
directory = flag.String("pwd", "", "Working directory of Xray vprotogen.")
mode = flag.String("mode", "generate", "Execution mode. 'generate' (default) compiles and saves changes. 'check' reports out-of-date files and exits with non-zero.")
)
// envFile returns the name of the Go environment configuration file. // topoSort sorts file descriptors so each dependency appears before files importing it.
// Copy from https://github.com/golang/go/blob/c4f2a9788a7be04daf931ac54382fbe2cb754938/src/cmd/go/internal/cfg/cfg.go#L150-L166 func topoSort(files []protoreflect.FileDescriptor) []*descriptorpb.FileDescriptorProto {
func envFile() (string, error) { fileMap := make(map[string]protoreflect.FileDescriptor)
if file := os.Getenv("GOENV"); file != "" { for _, f := range files {
if file == "off" { fileMap[f.Path()] = f
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
} }
dir, err := os.UserConfigDir() visited[fd.Path()] = true
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 "", err return nil, err
} }
if dir == "" {
return "", errors.New("missing user-config dir")
}
return filepath.Join(dir, "go", "env"), nil
}
// GetRuntimeEnv returns the value of runtime environment variable, cmd := exec.Command("go", "tool", pluginName)
// that is set by running following command: `go env -w key=value`. cmd.Dir = pwd
func GetRuntimeEnv(key string) (string, error) { cmd.Stdin = bytes.NewReader(reqBytes)
file, err := envFile() cmd.Env = os.Environ()
if err != nil { var stdout, stderr bytes.Buffer
return "", err cmd.Stdout = &stdout
} cmd.Stderr = &stderr
if file == "" {
return "", errors.New("missing runtime env file")
}
var data []byte
var runtimeEnv string
data, readErr := os.ReadFile(file)
if readErr != nil {
return "", readErr
}
envStrings := strings.Split(string(data), "\n")
for _, envItem := range envStrings {
envItem = strings.TrimSuffix(envItem, "\r")
envKeyValue := strings.Split(envItem, "=")
if strings.EqualFold(strings.TrimSpace(envKeyValue[0]), key) {
runtimeEnv = strings.TrimSpace(envKeyValue[1])
}
}
return runtimeEnv, nil
}
// GetGOBIN returns GOBIN environment variable as a string. It will NOT be empty. if err := cmd.Run(); err != nil {
func GetGOBIN() string { return nil, fmt.Errorf("%v: %s", err, stderr.String())
// The one set by user explicitly by `export GOBIN=/path` or `env GOBIN=/path command`
GOBIN := os.Getenv("GOBIN")
if GOBIN == "" {
var err error
// The one set by user by running `go env -w GOBIN=/path`
GOBIN, err = GetRuntimeEnv("GOBIN")
if err != nil {
// The default one that Golang uses
return filepath.Join(build.Default.GOPATH, "bin")
} }
if GOBIN == "" {
return filepath.Join(build.Default.GOPATH, "bin")
}
return GOBIN
}
return GOBIN
}
func whichProtoc(suffix, targetedVersion string) (string, error) { var resp pluginpb.CodeGeneratorResponse
protoc := "protoc" + suffix if err := proto.Unmarshal(stdout.Bytes(), &resp); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
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 if resp.GetError() != "" {
} return nil, fmt.Errorf("%s", resp.GetError())
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() return &resp, nil
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() {
@@ -154,106 +92,133 @@ func main() {
flag.Parse() flag.Parse()
if !filepath.IsAbs(*directory) { if !filepath.IsAbs(*directory) {
pwd, wdErr := os.Getwd() pwd, err := os.Getwd()
if wdErr != nil { if err != 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 pwd := *directory
GOBIN := GetGOBIN()
binPath := os.Getenv("PATH")
pathSlice := []string{pwd, GOBIN, binPath}
binPath = strings.Join(pathSlice, string(os.PathListSeparator))
os.Setenv("PATH", binPath)
suffix := "" isCheck := false
if runtime.GOOS == "windows" { switch *mode {
suffix = ".exe" case "generate", "":
} isCheck = false
case "check":
/* isCheck = true
targetedVersion, err := getProjectProtocVersion("https://raw.githubusercontent.com/XTLS/Xray-core/HEAD/core/config.pb.go") default:
if err != nil { fmt.Fprintf(os.Stderr, "Unrecognized 'mode': %s. Use 'generate' or 'check'.\n", *mode)
fmt.Println(err)
os.Exit(1)
}
*/
targetedVersion := ""
protoc, err := whichProtoc(suffix, targetedVersion)
if err != nil {
fmt.Println(err)
os.Exit(1) os.Exit(1)
} }
installedVersion, err := getInstalledProtocVersion(protoc) if isCheck {
if err != nil { fmt.Println("Checking for out-of-date Protobuf generated files...")
fmt.Println(err)
os.Exit(1)
} }
if needToUpdate(targetedVersion, installedVersion) { var protoFiles []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") {
if info.IsDir() { rel, _ := filepath.Rel(pwd, path)
return nil protoFiles = append(protoFiles, filepath.ToSlash(rel))
} }
dir := filepath.Dir(path)
filename := filepath.Base(path)
if strings.HasSuffix(filename, ".proto") {
path = path[len(pwd)+1:]
protoFilesMap[dir] = append(protoFilesMap[dir], path)
}
return nil return nil
}) })
if walkErr != nil { if err != nil {
fmt.Println(walkErr) fmt.Println("Walk error:", err)
os.Exit(1)
}
if len(protoFiles) == 0 {
return
}
compiler := protocompile.Compiler{
Resolver: protocompile.WithStandardImports(&protocompile.SourceResolver{
ImportPaths: []string{pwd},
}),
SourceInfoMode: protocompile.SourceInfoStandard,
}
linkedFiles, err := compiler.Compile(context.Background(), protoFiles...)
if err != nil {
fmt.Fprintf(os.Stderr, "Protobuf compilation failed:\n%v\n", err)
os.Exit(1) os.Exit(1)
} }
for _, files := range protoFilesMap { var fdList []protoreflect.FileDescriptor
for _, relProtoFile := range files { for _, f := range linkedFiles {
args := []string{ fdList = append(fdList, f)
"--go_out", pwd,
"--go_opt", "paths=source_relative",
"--go-grpc_out", pwd,
"--go-grpc_opt", "paths=source_relative",
"--plugin", "protoc-gen-go=" + filepath.Join(GOBIN, "protoc-gen-go"+suffix),
"--plugin", "protoc-gen-go-grpc=" + filepath.Join(GOBIN, "protoc-gen-go-grpc"+suffix),
} }
args = append(args, relProtoFile)
cmd := exec.Command(protoc, args...) req := &pluginpb.CodeGeneratorRequest{
cmd.Env = append(cmd.Env, os.Environ()...) FileToGenerate: protoFiles,
cmd.Dir = pwd Parameter: proto.String("paths=source_relative"),
output, cmdErr := cmd.CombinedOutput() ProtoFile: topoSort(fdList),
if len(output) > 0 { // TODO: replace with unknown in the future
fmt.Println(string(output)) CompilerVersion: &pluginpb.Version{
Major: proto.Int32(6),
Minor: proto.Int32(33),
Patch: proto.Int32(5),
},
} }
if cmdErr != nil {
fmt.Println(cmdErr) var diffCount int
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()))
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)
} }
} }
} }
}
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.")
}
} }
+11 -4
View File
@@ -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
} }
+10 -2
View File
@@ -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)
+17 -17
View File
@@ -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,6 +65,13 @@ 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
@@ -86,13 +93,6 @@ 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\x12D\n" + "\x06Config\x12C\n" +
"\x10allowed_networks\x18\a \x03(\x0e2\x18.xray.common.net.NetworkR\x0fallowedNetworks\x12D\n" +
"\x0frewrite_address\x18\x01 \x01(\v2\x1b.xray.common.net.IPOrDomainR\x0erewriteAddress\x12!\n" + "\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\x12C\n" + "\bport_map\x18\x03 \x03(\v2(.xray.proxy.dokodemo.Config.PortMapEntryR\aportMap\x12'\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.IPOrDomain)(nil), // 2: xray.common.net.IPOrDomain (net.Network)(0), // 2: xray.common.net.Network
(net.Network)(0), // 3: xray.common.net.Network (*net.IPOrDomain)(nil), // 3: xray.common.net.IPOrDomain
} }
var file_proxy_dokodemo_config_proto_depIdxs = []int32{ var file_proxy_dokodemo_config_proto_depIdxs = []int32{
2, // 0: xray.proxy.dokodemo.Config.rewrite_address:type_name -> xray.common.net.IPOrDomain 2, // 0: xray.proxy.dokodemo.Config.allowed_networks:type_name -> xray.common.net.Network
1, // 1: xray.proxy.dokodemo.Config.port_map:type_name -> xray.proxy.dokodemo.Config.PortMapEntry 3, // 1: xray.proxy.dokodemo.Config.rewrite_address:type_name -> xray.common.net.IPOrDomain
3, // 2: xray.proxy.dokodemo.Config.allowed_networks:type_name -> xray.common.net.Network 1, // 2: xray.proxy.dokodemo.Config.port_map:type_name -> xray.proxy.dokodemo.Config.PortMapEntry
3, // [3:3] is the sub-list for method output_type 3, // [3:3] is the sub-list for method 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
+2 -3
View File
@@ -7,12 +7,11 @@
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 (