diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7db357434..0d91db0b1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -35,24 +35,7 @@ jobs: if: steps.check-assets.outputs.missing == 'true' run: sleep 90 - check-proto: - runs-on: ubuntu-latest - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name - steps: - - name: Checkout codebase - uses: actions/checkout@v7 - - name: Check Proto Version Header - run: | - head -n 4 core/config.pb.go > ref.txt - find . -name "*.pb.go" ! -name "*_grpc.pb.go" -print0 | while IFS= read -r -d '' file; do - if ! cmp -s ref.txt <(head -n 4 "$file"); then - echo "Error: Header mismatch in $file" - head -n 4 "$file" - exit 1 - fi - done - - check-format: + check-format-and-proto: runs-on: ubuntu-latest if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name permissions: @@ -68,6 +51,8 @@ jobs: cache: false - name: Check Format run: go run ./infra/vformat/main.go -mode check -pwd ./ + - name: Check Protobuf + run: go run ./infra/vprotogen/main.go -mode check -pwd ./ test: needs: check-assets diff --git a/app/observatory/config.pb.go b/app/observatory/config.pb.go index f87440c8d..d4c963b92 100644 --- a/app/observatory/config.pb.go +++ b/app/observatory/config.pb.go @@ -152,23 +152,23 @@ func (x *HealthPingMeasurementResult) GetMin() int64 { type OutboundStatus struct { state protoimpl.MessageState `protogen:"open.v1"` // @Document Whether this outbound is usable - // @Restriction ReadOnlyForUser + //@Restriction ReadOnlyForUser Alive bool `protobuf:"varint,1,opt,name=alive,proto3" json:"alive,omitempty"` // @Document The time for probe request to finish. - // @Type time.ms - // @Restriction ReadOnlyForUser + //@Type time.ms + //@Restriction ReadOnlyForUser Delay int64 `protobuf:"varint,2,opt,name=delay,proto3" json:"delay,omitempty"` // @Document The last error caused this outbound failed to relay probe request - // @Restriction NotMachineReadable + //@Restriction NotMachineReadable LastErrorReason string `protobuf:"bytes,3,opt,name=last_error_reason,json=lastErrorReason,proto3" json:"last_error_reason,omitempty"` // @Document The outbound tag for this Server - // @Type id.outboundTag + //@Type id.outboundTag OutboundTag string `protobuf:"bytes,4,opt,name=outbound_tag,json=outboundTag,proto3" json:"outbound_tag,omitempty"` // @Document The time this outbound is known to be alive - // @Type id.outboundTag + //@Type id.outboundTag LastSeenTime int64 `protobuf:"varint,5,opt,name=last_seen_time,json=lastSeenTime,proto3" json:"last_seen_time,omitempty"` // @Document The time this outbound is tried - // @Type id.outboundTag + //@Type id.outboundTag LastTryTime int64 `protobuf:"varint,6,opt,name=last_try_time,json=lastTryTime,proto3" json:"last_try_time,omitempty"` HealthPing *HealthPingMeasurementResult `protobuf:"bytes,7,opt,name=health_ping,json=healthPing,proto3" json:"health_ping,omitempty"` unknownFields protoimpl.UnknownFields @@ -257,14 +257,14 @@ func (x *OutboundStatus) GetHealthPing() *HealthPingMeasurementResult { type ProbeResult struct { state protoimpl.MessageState `protogen:"open.v1"` // @Document Whether this outbound is usable - // @Restriction ReadOnlyForUser + //@Restriction ReadOnlyForUser Alive bool `protobuf:"varint,1,opt,name=alive,proto3" json:"alive,omitempty"` // @Document The time for probe request to finish. - // @Type time.ms - // @Restriction ReadOnlyForUser + //@Type time.ms + //@Restriction ReadOnlyForUser Delay int64 `protobuf:"varint,2,opt,name=delay,proto3" json:"delay,omitempty"` // @Document The error caused this outbound failed to relay probe request - // @Restriction NotMachineReadable + //@Restriction NotMachineReadable LastErrorReason string `protobuf:"bytes,3,opt,name=last_error_reason,json=lastErrorReason,proto3" json:"last_error_reason,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -324,7 +324,7 @@ func (x *ProbeResult) GetLastErrorReason() string { type Intensity struct { state protoimpl.MessageState `protogen:"open.v1"` // @Document The time interval for a probe request in ms. - // @Type time.ms + //@Type time.ms ProbeInterval uint32 `protobuf:"varint,1,opt,name=probe_interval,json=probeInterval,proto3" json:"probe_interval,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache diff --git a/app/router/command/command.proto b/app/router/command/command.proto index 22ae95b98..c9e604a35 100644 --- a/app/router/command/command.proto +++ b/app/router/command/command.proto @@ -34,18 +34,19 @@ message RoutingContext { // opened by xray-core. // * FieldSelectors selects a subset of fields in routing statistics to return. // Valid selectors: -// - inbound: Selects connection's inbound tag. -// - network: Selects connection's network. -// - ip: Equivalent as "ip_source" and "ip_target", selects both source and -// target IP. -// - port: Equivalent as "port_source" and "port_target", selects both source -// and target port. -// - domain: Selects target domain. -// - protocol: Select connection's protocol. -// - user: Select connection's inbound user email. -// - attributes: Select connection's additional attributes. -// - outbound: Equivalent as "outbound" and "outbound_group", select both -// outbound tag and outbound group tags. +// - inbound: Selects connection's inbound tag. +// - network: Selects connection's network. +// - ip: Equivalent as "ip_source" and "ip_target", selects both source and +// target IP. +// - port: Equivalent as "port_source" and "port_target", selects both source +// and target port. +// - domain: Selects target domain. +// - protocol: Select connection's protocol. +// - user: Select connection's inbound user email. +// - attributes: Select connection's additional attributes. +// - outbound: Equivalent as "outbound" and "outbound_group", select both +// outbound tag and outbound group tags. +// // * If FieldSelectors is left empty, all fields will be returned. message SubscribeRoutingStatsRequest { repeated string FieldSelectors = 1; diff --git a/core/proto.go b/core/proto.go index 030365cc6..8e2d5398c 100644 --- a/core/proto.go +++ b/core/proto.go @@ -1,5 +1,3 @@ package core -//go:generate go install -v google.golang.org/protobuf/cmd/protoc-gen-go@latest -//go:generate go install -v google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest //go:generate go run ../infra/vprotogen/main.go -pwd ./.. diff --git a/go.mod b/go.mod index f677ffc83..edd30203d 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.27 require ( github.com/apernet/quic-go v0.61.1-0.20260806010916-184d081eef3e + github.com/bufbuild/protocompile v0.14.1 github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 github.com/golang/mock v1.7.0-rc.1 github.com/google/go-cmp v0.7.0 @@ -32,7 +33,7 @@ require ( golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb golang.zx2c4.com/wireguard/windows v1.1.1 google.golang.org/grpc v1.84.0 - google.golang.org/protobuf v1.36.12 + google.golang.org/protobuf v1.36.11 h12.io/socks v1.0.3 layeh.com/gopher-luar v1.0.11 lukechampine.com/blake3 v1.4.1 @@ -58,6 +59,12 @@ require ( golang.org/x/text v0.43.0 // indirect golang.org/x/tools v0.51.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800 // indirect + google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0 // indirect ) + +tool ( + google.golang.org/grpc/cmd/protoc-gen-go-grpc + google.golang.org/protobuf/cmd/protoc-gen-go +) diff --git a/go.sum b/go.sum index 006c336d4..56196a63b 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ github.com/andybalholm/brotli v1.2.3 h1:8H1qwOkl2LPfjf3YezB90JnCliZb6SInJ/OJkEbA github.com/andybalholm/brotli v1.2.3/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/apernet/quic-go v0.61.1-0.20260806010916-184d081eef3e h1:5mgtR5gwIgBKMiGI1QdXldZZ+SNor06Nbu1wCBulQBg= github.com/apernet/quic-go v0.61.1-0.20260806010916-184d081eef3e/go.mod h1:x7qxEvX6MCVtDuBKHj3E+88+BtrbEMuAL5qGUKItjW8= +github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw= +github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -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/grpc v1.84.0 h1:soMyaPJ8pAak5PIQ0DGBUir0XRo2fRoMqhNWMLlLxO0= 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/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/infra/vprotogen/main.go b/infra/vprotogen/main.go index 2c1e2298a..54e126ef2 100644 --- a/infra/vprotogen/main.go +++ b/infra/vprotogen/main.go @@ -1,149 +1,87 @@ package main import ( - "errors" + "bytes" + "context" "flag" "fmt" - "go/build" - "io" - "net/http" + "io/fs" "os" "os/exec" "path/filepath" - "regexp" - "runtime" - "strconv" "strings" + + "github.com/bufbuild/protocompile" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protodesc" + "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/types/descriptorpb" + "google.golang.org/protobuf/types/pluginpb" ) -var directory = flag.String("pwd", "", "Working directory of Xray vprotogen.") +var ( + directory = flag.String("pwd", "", "Working directory of Xray vprotogen.") + mode = flag.String("mode", "generate", "Execution mode. 'generate' (default) compiles and saves changes. 'check' reports out-of-date files and exits with non-zero.") +) -// envFile returns the name of the Go environment configuration file. -// Copy from https://github.com/golang/go/blob/c4f2a9788a7be04daf931ac54382fbe2cb754938/src/cmd/go/internal/cfg/cfg.go#L150-L166 -func envFile() (string, error) { - if file := os.Getenv("GOENV"); file != "" { - if file == "off" { - return "", errors.New("GOENV=off") +// topoSort sorts file descriptors so each dependency appears before files importing it. +func topoSort(files []protoreflect.FileDescriptor) []*descriptorpb.FileDescriptorProto { + fileMap := make(map[string]protoreflect.FileDescriptor) + for _, f := range files { + fileMap[f.Path()] = f + } + + visited := make(map[string]bool) + var sorted []*descriptorpb.FileDescriptorProto + + var visit func(fd protoreflect.FileDescriptor) + visit = func(fd protoreflect.FileDescriptor) { + if visited[fd.Path()] { + return } - return file, nil - } - dir, err := os.UserConfigDir() - if err != nil { - return "", err - } - if dir == "" { - return "", errors.New("missing user-config dir") - } - return filepath.Join(dir, "go", "env"), nil -} + visited[fd.Path()] = true -// GetRuntimeEnv returns the value of runtime environment variable, -// that is set by running following command: `go env -w key=value`. -func GetRuntimeEnv(key string) (string, error) { - file, err := envFile() - if err != nil { - return "", err - } - if file == "" { - return "", errors.New("missing runtime env file") - } - var data []byte - var runtimeEnv string - data, readErr := os.ReadFile(file) - if readErr != nil { - return "", readErr - } - envStrings := strings.Split(string(data), "\n") - for _, envItem := range envStrings { - envItem = strings.TrimSuffix(envItem, "\r") - envKeyValue := strings.Split(envItem, "=") - if strings.EqualFold(strings.TrimSpace(envKeyValue[0]), key) { - runtimeEnv = strings.TrimSpace(envKeyValue[1]) + imports := fd.Imports() + for i := 0; i < imports.Len(); i++ { + if dep, ok := fileMap[imports.Get(i).Path()]; ok { + visit(dep) + } } + sorted = append(sorted, protodesc.ToFileDescriptorProto(fd)) } - return runtimeEnv, nil + + for _, f := range files { + visit(f) + } + return sorted } -// GetGOBIN returns GOBIN environment variable as a string. It will NOT be empty. -func GetGOBIN() string { - // The one set by user explicitly by `export GOBIN=/path` or `env GOBIN=/path command` - GOBIN := os.Getenv("GOBIN") - if GOBIN == "" { - var err error - // The one set by user by running `go env -w GOBIN=/path` - GOBIN, err = GetRuntimeEnv("GOBIN") - if err != nil { - // The default one that Golang uses - return filepath.Join(build.Default.GOPATH, "bin") - } - if GOBIN == "" { - return filepath.Join(build.Default.GOPATH, "bin") - } - return GOBIN - } - return GOBIN -} - -func whichProtoc(suffix, targetedVersion string) (string, error) { - protoc := "protoc" + suffix - - path, err := exec.LookPath(protoc) +func runPlugin(pluginName, pwd string, req *pluginpb.CodeGeneratorRequest) (*pluginpb.CodeGeneratorResponse, error) { + reqBytes, err := proto.Marshal(req) if err != nil { - return "", fmt.Errorf(` -Command "%s" not found. -Make sure that %s is in your system path or current path. -Download %s v%s or later from https://github.com/protocolbuffers/protobuf/releases -`, protoc, protoc, protoc, targetedVersion) + return nil, err } - return path, nil -} -func getProjectProtocVersion(url string) (string, error) { - resp, err := http.Get(url) - if err != nil { - return "", errors.New("can not get the version of protobuf used in xray project") - } - defer resp.Body.Close() - body, err := io.ReadAll(resp.Body) - if err != nil { - return "", errors.New("can not read from body") - } - versionRegexp := regexp.MustCompile(`\/\/\s*protoc\s*v\d+\.(\d+\.\d+)`) - matched := versionRegexp.FindStringSubmatch(string(body)) - return matched[1], nil -} + cmd := exec.Command("go", "tool", pluginName) + cmd.Dir = pwd + cmd.Stdin = bytes.NewReader(reqBytes) + cmd.Env = os.Environ() + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr -func getInstalledProtocVersion(protocPath string) (string, error) { - cmd := exec.Command(protocPath, "--version") - cmd.Env = append(cmd.Env, os.Environ()...) - output, cmdErr := cmd.CombinedOutput() - if cmdErr != nil { - return "", cmdErr + if err := cmd.Run(); err != nil { + return nil, fmt.Errorf("%v: %s", err, stderr.String()) } - versionRegexp := regexp.MustCompile(`protoc\s*(\d+\.\d+)`) - matched := versionRegexp.FindStringSubmatch(string(output)) - return matched[1], nil -} -func parseVersion(s string, width int) int64 { - strList := strings.Split(s, ".") - format := fmt.Sprintf("%%s%%0%ds", width) - v := "" - for _, value := range strList { - v = fmt.Sprintf(format, v, value) + var resp pluginpb.CodeGeneratorResponse + if err := proto.Unmarshal(stdout.Bytes(), &resp); err != nil { + return nil, fmt.Errorf("failed to unmarshal response: %w", err) } - var result int64 - var err error - if result, err = strconv.ParseInt(v, 10, 64); err != nil { - return 0 + if resp.GetError() != "" { + return nil, fmt.Errorf("%s", resp.GetError()) } - return result -} - -func needToUpdate(targetedVersion, installedVersion string) bool { - vt := parseVersion(targetedVersion, 4) - vi := parseVersion(installedVersion, 4) - return vt > vi + return &resp, nil } func main() { @@ -154,106 +92,147 @@ func main() { flag.Parse() if !filepath.IsAbs(*directory) { - pwd, wdErr := os.Getwd() - if wdErr != nil { + pwd, err := os.Getwd() + if err != nil { fmt.Println("Can not get current working directory.") os.Exit(1) } *directory = filepath.Join(pwd, *directory) } - pwd := *directory - GOBIN := GetGOBIN() - binPath := os.Getenv("PATH") - pathSlice := []string{pwd, GOBIN, binPath} - binPath = strings.Join(pathSlice, string(os.PathListSeparator)) - os.Setenv("PATH", binPath) - suffix := "" - if runtime.GOOS == "windows" { - suffix = ".exe" - } - - /* - targetedVersion, err := getProjectProtocVersion("https://raw.githubusercontent.com/XTLS/Xray-core/HEAD/core/config.pb.go") - if err != nil { - fmt.Println(err) - os.Exit(1) - } - */ - targetedVersion := "" - - protoc, err := whichProtoc(suffix, targetedVersion) - if err != nil { - fmt.Println(err) + isCheck := false + switch *mode { + case "generate", "": + isCheck = false + case "check": + isCheck = true + default: + fmt.Fprintf(os.Stderr, "Unrecognized 'mode': %s. Use 'generate' or 'check'.\n", *mode) os.Exit(1) } - installedVersion, err := getInstalledProtocVersion(protoc) - if err != nil { - fmt.Println(err) - os.Exit(1) + if isCheck { + fmt.Println("Checking for out-of-date Protobuf generated files...") } - if needToUpdate(targetedVersion, installedVersion) { - fmt.Printf(` -You are using an old protobuf version, please update to v%s or later. -Download it from https://github.com/protocolbuffers/protobuf/releases - - * Protobuf version used in xray project: v%s - * Protobuf version you have installed: v%s - -`, targetedVersion, targetedVersion, installedVersion) - os.Exit(1) - } - - protoFilesMap := make(map[string][]string) - walkErr := filepath.Walk(pwd, func(path string, info os.FileInfo, err error) error { - if err != nil { - fmt.Println(err) + var protoFiles []string + var pbCount int + err := filepath.WalkDir(pwd, func(path string, d fs.DirEntry, err error) error { + if err != nil || d.IsDir() { return err } - - if info.IsDir() { - return nil + if strings.HasSuffix(d.Name(), ".proto") { + rel, _ := filepath.Rel(pwd, path) + protoFiles = append(protoFiles, filepath.ToSlash(rel)) } - - dir := filepath.Dir(path) - filename := filepath.Base(path) - if strings.HasSuffix(filename, ".proto") { - path = path[len(pwd)+1:] - protoFilesMap[dir] = append(protoFilesMap[dir], path) + if strings.HasSuffix(d.Name(), ".pb.go") { + pbCount++ } - return nil }) - if walkErr != nil { - fmt.Println(walkErr) + if err != nil { + fmt.Println("Walk error:", err) + os.Exit(1) + } + if len(protoFiles) == 0 { + if isCheck && pbCount > 0 { + fmt.Fprintf(os.Stderr, "Found %d .pb.go files without any .proto files. Remove these orphaned files.\n", pbCount) + os.Exit(1) + } + 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) } - for _, files := range protoFilesMap { - for _, relProtoFile := range files { - args := []string{ - "--go_out", pwd, - "--go_opt", "paths=source_relative", - "--go-grpc_out", pwd, - "--go-grpc_opt", "paths=source_relative", - "--plugin", "protoc-gen-go=" + filepath.Join(GOBIN, "protoc-gen-go"+suffix), - "--plugin", "protoc-gen-go-grpc=" + filepath.Join(GOBIN, "protoc-gen-go-grpc"+suffix), + var fdList []protoreflect.FileDescriptor + for _, f := range linkedFiles { + fdList = append(fdList, f) + } + + req := &pluginpb.CodeGeneratorRequest{ + FileToGenerate: protoFiles, + Parameter: proto.String("paths=source_relative"), + ProtoFile: topoSort(fdList), + // TODO: replace with unknown in the future + CompilerVersion: &pluginpb.Version{ + Major: proto.Int32(6), + Minor: proto.Int32(33), + Patch: proto.Int32(5), + }, + } + + var diffCount int + generatedFiles := make(map[string]struct{}) + for _, plugin := range []string{"protoc-gen-go", "protoc-gen-go-grpc"} { + resp, err := runPlugin(plugin, pwd, req) + if err != nil { + fmt.Fprintf(os.Stderr, "%s failed: %v\n", plugin, err) + os.Exit(1) + } + + for _, f := range resp.File { + targetPath := filepath.Join(pwd, filepath.FromSlash(f.GetName())) + generatedFiles[targetPath] = struct{}{} + content := []byte(f.GetContent()) + + hasDiff := false + existing, err := os.ReadFile(targetPath) + if err != nil { + hasDiff = true + } else { + oldNorm := bytes.ReplaceAll(existing, []byte("\r\n"), []byte("\n")) + newNorm := bytes.ReplaceAll(content, []byte("\r\n"), []byte("\n")) + if !bytes.Equal(oldNorm, newNorm) { + hasDiff = true + } } - args = append(args, relProtoFile) - cmd := exec.Command(protoc, args...) - cmd.Env = append(cmd.Env, os.Environ()...) - cmd.Dir = pwd - output, cmdErr := cmd.CombinedOutput() - if len(output) > 0 { - fmt.Println(string(output)) - } - if cmdErr != nil { - fmt.Println(cmdErr) - os.Exit(1) + + if hasDiff { + diffCount++ + fmt.Println(targetPath) + + if isCheck { + continue + } + + if err := os.MkdirAll(filepath.Dir(targetPath), 0o755); err != nil { + fmt.Fprintf(os.Stderr, "Error creating directory: %v\n", err) + os.Exit(1) + } + mode := os.FileMode(0o644) + if stat, err := os.Stat(targetPath); err == nil { + mode = stat.Mode().Perm() + } + if err := os.WriteFile(targetPath, content, mode); err != nil { + fmt.Fprintf(os.Stderr, "Error writing %s: %v\n", targetPath, err) + os.Exit(1) + } } } } + + if isCheck { + if pbCount != len(generatedFiles) { + fmt.Printf("Protobuf file count mismatch: found %d, expected %d. Remove orphaned files and regenerate.\n", pbCount, len(generatedFiles)) + diffCount++ + } + 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.") + } } diff --git a/proxy/dokodemo/config.pb.go b/proxy/dokodemo/config.pb.go index 0aca028d9..792aaf91a 100644 --- a/proxy/dokodemo/config.pb.go +++ b/proxy/dokodemo/config.pb.go @@ -23,14 +23,14 @@ const ( ) type Config struct { - state protoimpl.MessageState `protogen:"open.v1"` - RewriteAddress *net.IPOrDomain `protobuf:"bytes,1,opt,name=rewrite_address,json=rewriteAddress,proto3" json:"rewrite_address,omitempty"` - RewritePort uint32 `protobuf:"varint,2,opt,name=rewrite_port,json=rewritePort,proto3" json:"rewrite_port,omitempty"` - PortMap map[string]string `protobuf:"bytes,3,rep,name=port_map,json=portMap,proto3" json:"port_map,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + state protoimpl.MessageState `protogen:"open.v1"` // List of networks that the Dokodemo accepts. - AllowedNetworks []net.Network `protobuf:"varint,7,rep,packed,name=allowed_networks,json=allowedNetworks,proto3,enum=xray.common.net.Network" json:"allowed_networks,omitempty"` - FollowRedirect bool `protobuf:"varint,5,opt,name=follow_redirect,json=followRedirect,proto3" json:"follow_redirect,omitempty"` - UserLevel uint32 `protobuf:"varint,6,opt,name=user_level,json=userLevel,proto3" json:"user_level,omitempty"` + AllowedNetworks []net.Network `protobuf:"varint,7,rep,packed,name=allowed_networks,json=allowedNetworks,proto3,enum=xray.common.net.Network" json:"allowed_networks,omitempty"` + RewriteAddress *net.IPOrDomain `protobuf:"bytes,1,opt,name=rewrite_address,json=rewriteAddress,proto3" json:"rewrite_address,omitempty"` + RewritePort uint32 `protobuf:"varint,2,opt,name=rewrite_port,json=rewritePort,proto3" json:"rewrite_port,omitempty"` + PortMap map[string]string `protobuf:"bytes,3,rep,name=port_map,json=portMap,proto3" json:"port_map,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + FollowRedirect bool `protobuf:"varint,5,opt,name=follow_redirect,json=followRedirect,proto3" json:"follow_redirect,omitempty"` + UserLevel uint32 `protobuf:"varint,6,opt,name=user_level,json=userLevel,proto3" json:"user_level,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -65,6 +65,13 @@ func (*Config) Descriptor() ([]byte, []int) { return file_proxy_dokodemo_config_proto_rawDescGZIP(), []int{0} } +func (x *Config) GetAllowedNetworks() []net.Network { + if x != nil { + return x.AllowedNetworks + } + return nil +} + func (x *Config) GetRewriteAddress() *net.IPOrDomain { if x != nil { return x.RewriteAddress @@ -86,13 +93,6 @@ func (x *Config) GetPortMap() map[string]string { return nil } -func (x *Config) GetAllowedNetworks() []net.Network { - if x != nil { - return x.AllowedNetworks - } - return nil -} - func (x *Config) GetFollowRedirect() bool { if x != nil { return x.FollowRedirect @@ -112,11 +112,11 @@ var File_proxy_dokodemo_config_proto protoreflect.FileDescriptor const file_proxy_dokodemo_config_proto_rawDesc = "" + "\n" + "\x1bproxy/dokodemo/config.proto\x12\x13xray.proxy.dokodemo\x1a\x18common/net/address.proto\x1a\x18common/net/network.proto\"\xff\x02\n" + - "\x06Config\x12D\n" + + "\x06Config\x12C\n" + + "\x10allowed_networks\x18\a \x03(\x0e2\x18.xray.common.net.NetworkR\x0fallowedNetworks\x12D\n" + "\x0frewrite_address\x18\x01 \x01(\v2\x1b.xray.common.net.IPOrDomainR\x0erewriteAddress\x12!\n" + "\frewrite_port\x18\x02 \x01(\rR\vrewritePort\x12C\n" + - "\bport_map\x18\x03 \x03(\v2(.xray.proxy.dokodemo.Config.PortMapEntryR\aportMap\x12C\n" + - "\x10allowed_networks\x18\a \x03(\x0e2\x18.xray.common.net.NetworkR\x0fallowedNetworks\x12'\n" + + "\bport_map\x18\x03 \x03(\v2(.xray.proxy.dokodemo.Config.PortMapEntryR\aportMap\x12'\n" + "\x0ffollow_redirect\x18\x05 \x01(\bR\x0efollowRedirect\x12\x1d\n" + "\n" + "user_level\x18\x06 \x01(\rR\tuserLevel\x1a:\n" + @@ -141,13 +141,13 @@ var file_proxy_dokodemo_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2) var file_proxy_dokodemo_config_proto_goTypes = []any{ (*Config)(nil), // 0: xray.proxy.dokodemo.Config nil, // 1: xray.proxy.dokodemo.Config.PortMapEntry - (*net.IPOrDomain)(nil), // 2: xray.common.net.IPOrDomain - (net.Network)(0), // 3: xray.common.net.Network + (net.Network)(0), // 2: xray.common.net.Network + (*net.IPOrDomain)(nil), // 3: xray.common.net.IPOrDomain } var file_proxy_dokodemo_config_proto_depIdxs = []int32{ - 2, // 0: xray.proxy.dokodemo.Config.rewrite_address:type_name -> xray.common.net.IPOrDomain - 1, // 1: xray.proxy.dokodemo.Config.port_map:type_name -> xray.proxy.dokodemo.Config.PortMapEntry - 3, // 2: xray.proxy.dokodemo.Config.allowed_networks:type_name -> xray.common.net.Network + 2, // 0: xray.proxy.dokodemo.Config.allowed_networks:type_name -> xray.common.net.Network + 3, // 1: xray.proxy.dokodemo.Config.rewrite_address:type_name -> xray.common.net.IPOrDomain + 1, // 2: xray.proxy.dokodemo.Config.port_map:type_name -> xray.proxy.dokodemo.Config.PortMapEntry 3, // [3:3] is the sub-list for method output_type 3, // [3:3] is the sub-list for method input_type 3, // [3:3] is the sub-list for extension type_name diff --git a/proxy/tun/config.pb.go b/proxy/tun/config.pb.go index 6a862a187..9321052e9 100644 --- a/proxy/tun/config.pb.go +++ b/proxy/tun/config.pb.go @@ -7,12 +7,11 @@ package tun import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" unsafe "unsafe" - - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" ) const (