From 78cd513b82f12c3f1361626590d3394a32c6db58 Mon Sep 17 00:00:00 2001 From: "Xiaokang Wang (Shelikhoo)" Date: Wed, 5 Feb 2025 20:36:40 +0000 Subject: [PATCH] Add Persistent Storage Support to V2Ray (#3300) * update protogen to strip unused part * add persistent storage support * fix coding style * update linter setting * update github integration --- .github/linters/.golangci.yml | 6 +- .github/workflows/deb.yml | 2 +- .github/workflows/release.yml | 10 +- app/observatory/config.pb.go | 46 ++-- app/observatory/config.proto | 2 + app/observatory/observer.go | 62 ++++- .../filesystemstorage/config.pb.go | 214 ++++++++++++++++++ .../filesystemstorage/config.proto | 23 ++ app/persistentstorage/filesystemstorage/fs.go | 133 +++++++++++ app/persistentstorage/protostorage/protokv.go | 51 +++++ app/persistentstorage/storage.go | 5 + app/proxyman/outbound/handler_test.go | 24 +- .../defereredPersistentStorage.go | 111 +++++++++ common/environment/envimpl/fs.go | 8 + common/environment/filesystemcap/fscap.go | 2 + common/environment/filesystemimpl/fsimpl.go | 33 +++ common/environment/rootcap_impl.go | 29 ++- common/platform/filesystem/file.go | 9 + common/platform/filesystem/fsifce/ifce.go | 9 +- features/extension/storage/storage.go | 9 + infra/vprotogen/main.go | 11 +- main/distro/all/all.go | 1 + transport/internet/kcp/kcp_test.go | 9 +- transport/internet/tls/ech.go | 1 + v2ray.go | 19 +- 25 files changed, 771 insertions(+), 58 deletions(-) create mode 100644 app/persistentstorage/filesystemstorage/config.pb.go create mode 100644 app/persistentstorage/filesystemstorage/config.proto create mode 100644 app/persistentstorage/filesystemstorage/fs.go create mode 100644 app/persistentstorage/protostorage/protokv.go create mode 100644 app/persistentstorage/storage.go create mode 100644 common/environment/deferredpersistentstorage/defereredPersistentStorage.go create mode 100644 common/environment/filesystemimpl/fsimpl.go diff --git a/.github/linters/.golangci.yml b/.github/linters/.golangci.yml index 4329d531e..a1beafd39 100644 --- a/.github/linters/.golangci.yml +++ b/.github/linters/.golangci.yml @@ -1,8 +1,5 @@ run: timeout: 5m - skip-files: - - generated.* - - .pb.go issues: new: true @@ -13,6 +10,9 @@ issues: - linters: - stylecheck text: "ST1016:" + exclude-files: + - generated.* + - .pb.go linters: enable: diff --git a/.github/workflows/deb.yml b/.github/workflows/deb.yml index 8d6bcde11..5dce67c6d 100644 --- a/.github/workflows/deb.yml +++ b/.github/workflows/deb.yml @@ -53,7 +53,7 @@ jobs: cp ../*.deb ./ - name: Upload artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: v2ray-debian-packages path: ./*.deb diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0033e1461..908e4528a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -188,7 +188,7 @@ jobs: openssl dgst -sha512 $FILE | sed 's/([^)]*)//g' >>$DGST - name: Upload ZIP file to Artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: v2ray-${{ steps.get_filename.outputs.ASSET_NAME }}.zip path: v2ray-${{ steps.get_filename.outputs.ASSET_NAME }}.zip @@ -217,7 +217,7 @@ jobs: with: go-version: ^1.23 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: path: build_artifacts @@ -252,17 +252,17 @@ jobs: openssl dgst -sha256 $FILE | sed 's/([^)]*)//g' >>$DGST openssl dgst -sha512 $FILE | sed 's/([^)]*)//g' >>$DGST - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: Release.unsigned path: build_artifacts/Release.unsigned - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: Release.unsigned.dgst path: build_artifacts/Release.unsigned.dgst - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: v2ray-extra.zip path: build_artifacts/v2ray-extra.zip diff --git a/app/observatory/config.pb.go b/app/observatory/config.pb.go index 8e005d69a..dc0e6985f 100644 --- a/app/observatory/config.pb.go +++ b/app/observatory/config.pb.go @@ -365,11 +365,12 @@ func (x *Intensity) GetProbeInterval() uint32 { type Config struct { state protoimpl.MessageState `protogen:"open.v1"` // @Document The selectors for outbound under observation - SubjectSelector []string `protobuf:"bytes,2,rep,name=subject_selector,json=subjectSelector,proto3" json:"subject_selector,omitempty"` - ProbeUrl string `protobuf:"bytes,3,opt,name=probe_url,json=probeUrl,proto3" json:"probe_url,omitempty"` - ProbeInterval int64 `protobuf:"varint,4,opt,name=probe_interval,json=probeInterval,proto3" json:"probe_interval,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + SubjectSelector []string `protobuf:"bytes,2,rep,name=subject_selector,json=subjectSelector,proto3" json:"subject_selector,omitempty"` + ProbeUrl string `protobuf:"bytes,3,opt,name=probe_url,json=probeUrl,proto3" json:"probe_url,omitempty"` + ProbeInterval int64 `protobuf:"varint,4,opt,name=probe_interval,json=probeInterval,proto3" json:"probe_interval,omitempty"` + PersistentProbeResult bool `protobuf:"varint,5,opt,name=persistent_probe_result,json=persistentProbeResult,proto3" json:"persistent_probe_result,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *Config) Reset() { @@ -423,6 +424,13 @@ func (x *Config) GetProbeInterval() int64 { return 0 } +func (x *Config) GetPersistentProbeResult() bool { + if x != nil { + return x.PersistentProbeResult + } + return false +} + var File_app_observatory_config_proto protoreflect.FileDescriptor var file_app_observatory_config_proto_rawDesc = string([]byte{ @@ -476,24 +484,28 @@ var file_app_observatory_config_proto_rawDesc = string([]byte{ 0x22, 0x32, 0x0a, 0x09, 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x79, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x62, 0x65, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x62, 0x65, 0x49, 0x6e, 0x74, 0x65, - 0x72, 0x76, 0x61, 0x6c, 0x22, 0x9d, 0x01, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, + 0x72, 0x76, 0x61, 0x6c, 0x22, 0xd5, 0x01, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x72, 0x6f, 0x62, 0x65, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x62, 0x65, 0x55, 0x72, 0x6c, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x62, 0x65, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0d, 0x70, 0x72, 0x6f, 0x62, 0x65, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x3a, 0x24, - 0x82, 0xb5, 0x18, 0x20, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x15, 0x62, - 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, - 0x74, 0x6f, 0x72, 0x79, 0x42, 0x6f, 0x0a, 0x1e, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, - 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6f, 0x62, 0x73, 0x65, 0x72, - 0x76, 0x61, 0x74, 0x6f, 0x72, 0x79, 0x50, 0x01, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76, 0x32, 0x66, 0x6c, 0x79, 0x2f, 0x76, 0x32, 0x72, 0x61, 0x79, - 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x35, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x6f, 0x62, 0x73, - 0x65, 0x72, 0x76, 0x61, 0x74, 0x6f, 0x72, 0x79, 0xaa, 0x02, 0x1a, 0x56, 0x32, 0x52, 0x61, 0x79, - 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, - 0x61, 0x74, 0x6f, 0x72, 0x79, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x0d, 0x70, 0x72, 0x6f, 0x62, 0x65, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x36, + 0x0a, 0x17, 0x70, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x70, 0x72, 0x6f, + 0x62, 0x65, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x15, 0x70, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x74, 0x50, 0x72, 0x6f, 0x62, 0x65, + 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x3a, 0x24, 0x82, 0xb5, 0x18, 0x20, 0x0a, 0x07, 0x73, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x15, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, + 0x64, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x6f, 0x72, 0x79, 0x42, 0x6f, 0x0a, 0x1e, + 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, + 0x70, 0x70, 0x2e, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x6f, 0x72, 0x79, 0x50, 0x01, + 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76, 0x32, 0x66, + 0x6c, 0x79, 0x2f, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x35, + 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x6f, 0x72, 0x79, + 0xaa, 0x02, 0x1a, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x70, + 0x70, 0x2e, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x6f, 0x72, 0x79, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, }) var ( diff --git a/app/observatory/config.proto b/app/observatory/config.proto index 1186a290c..f06772876 100644 --- a/app/observatory/config.proto +++ b/app/observatory/config.proto @@ -84,4 +84,6 @@ message Config { string probe_url = 3; int64 probe_interval = 4; + + bool persistent_probe_result = 5; } \ No newline at end of file diff --git a/app/observatory/observer.go b/app/observatory/observer.go index e0912074e..24290c9b0 100644 --- a/app/observatory/observer.go +++ b/app/observatory/observer.go @@ -12,6 +12,11 @@ import ( "sync" "time" + "github.com/v2fly/v2ray-core/v5/app/persistentstorage" + "github.com/v2fly/v2ray-core/v5/app/persistentstorage/protostorage" + "github.com/v2fly/v2ray-core/v5/common/environment" + "github.com/v2fly/v2ray-core/v5/common/environment/envctx" + "github.com/golang/protobuf/proto" core "github.com/v2fly/v2ray-core/v5" @@ -34,7 +39,10 @@ type Observer struct { finished *done.Instance - ohm outbound.Manager + ohm outbound.Manager + persistStorage persistentstorage.ScopedPersistentStorage + + persistOutboundStatusProtoStorage protostorage.ProtoPersistentStorage } func (o *Observer) GetObservation(ctx context.Context) (proto.Message, error) { @@ -47,6 +55,24 @@ func (o *Observer) Type() interface{} { func (o *Observer) Start() error { if o.config != nil && len(o.config.SubjectSelector) != 0 { + if o.config.PersistentProbeResult { + appEnvironment := envctx.EnvironmentFromContext(o.ctx).(environment.AppEnvironment) + o.persistStorage = appEnvironment.PersistentStorage() + + outboundStatusStorage, err := o.persistStorage.NarrowScope(o.ctx, []byte("outbound_status")) + if err != nil { + return newError("failed to get persistent storage for outbound_status").Base(err) + } + o.persistOutboundStatusProtoStorage = outboundStatusStorage.(protostorage.ProtoPersistentStorage) + list, err := outboundStatusStorage.List(o.ctx, []byte("")) + if err != nil { + newError("failed to list persisted outbound status").Base(err).WriteToLog() + } else { + for _, v := range list { + o.loadOutboundStatus(string(v)) + } + } + } o.finished = done.New() go o.background() } @@ -195,6 +221,12 @@ func (o *Observer) updateStatusForResult(outbound string, result *ProbeResult) { status.LastErrorReason = result.LastErrorReason status.Delay = 99999999 } + if o.config.PersistentProbeResult { + err := o.persistOutboundStatusProtoStorage.PutProto(o.ctx, outbound, status) + if err != nil { + newError("failed to persist outbound status").Base(err).WriteToLog() + } + } } func (o *Observer) findStatusLocationLockHolderOnly(outbound string) int { @@ -206,19 +238,33 @@ func (o *Observer) findStatusLocationLockHolderOnly(outbound string) int { return -1 } +func (o *Observer) loadOutboundStatus(name string) { + if o.persistOutboundStatusProtoStorage == nil { + return + } + status := &OutboundStatus{} + err := o.persistOutboundStatusProtoStorage.GetProto(o.ctx, name, status) + if err != nil { + newError("failed to load outbound status").Base(err).WriteToLog() + return + } + o.status = append(o.status, status) +} + func New(ctx context.Context, config *Config) (*Observer, error) { - var outboundManager outbound.Manager + obs := &Observer{ + config: config, + ctx: ctx, + } + err := core.RequireFeatures(ctx, func(om outbound.Manager) { - outboundManager = om + obs.ohm = om }) if err != nil { return nil, newError("Cannot get depended features").Base(err) } - return &Observer{ - config: config, - ctx: ctx, - ohm: outboundManager, - }, nil + + return obs, nil } func init() { diff --git a/app/persistentstorage/filesystemstorage/config.pb.go b/app/persistentstorage/filesystemstorage/config.pb.go new file mode 100644 index 000000000..681ff906a --- /dev/null +++ b/app/persistentstorage/filesystemstorage/config.pb.go @@ -0,0 +1,214 @@ +package filesystemstorage + +import ( + _ "github.com/v2fly/v2ray-core/v5/common/protoext" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type StateStorageRoot int32 + +const ( + StateStorageRoot_WorkDir StateStorageRoot = 0 +) + +// Enum value maps for StateStorageRoot. +var ( + StateStorageRoot_name = map[int32]string{ + 0: "WorkDir", + } + StateStorageRoot_value = map[string]int32{ + "WorkDir": 0, + } +) + +func (x StateStorageRoot) Enum() *StateStorageRoot { + p := new(StateStorageRoot) + *p = x + return p +} + +func (x StateStorageRoot) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (StateStorageRoot) Descriptor() protoreflect.EnumDescriptor { + return file_app_persistentstorage_filesystemstorage_config_proto_enumTypes[0].Descriptor() +} + +func (StateStorageRoot) Type() protoreflect.EnumType { + return &file_app_persistentstorage_filesystemstorage_config_proto_enumTypes[0] +} + +func (x StateStorageRoot) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use StateStorageRoot.Descriptor instead. +func (StateStorageRoot) EnumDescriptor() ([]byte, []int) { + return file_app_persistentstorage_filesystemstorage_config_proto_rawDescGZIP(), []int{0} +} + +type Config struct { + state protoimpl.MessageState `protogen:"open.v1"` + StateStorageRoot StateStorageRoot `protobuf:"varint,1,opt,name=state_storage_root,json=stateStorageRoot,proto3,enum=v2ray.core.app.persistentstorage.filesystemstorage.StateStorageRoot" json:"state_storage_root,omitempty"` + InstanceName string `protobuf:"bytes,4,opt,name=instance_name,json=instanceName,proto3" json:"instance_name,omitempty"` + Protojson bool `protobuf:"varint,5,opt,name=protojson,proto3" json:"protojson,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Config) Reset() { + *x = Config{} + mi := &file_app_persistentstorage_filesystemstorage_config_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Config) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Config) ProtoMessage() {} + +func (x *Config) ProtoReflect() protoreflect.Message { + mi := &file_app_persistentstorage_filesystemstorage_config_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Config.ProtoReflect.Descriptor instead. +func (*Config) Descriptor() ([]byte, []int) { + return file_app_persistentstorage_filesystemstorage_config_proto_rawDescGZIP(), []int{0} +} + +func (x *Config) GetStateStorageRoot() StateStorageRoot { + if x != nil { + return x.StateStorageRoot + } + return StateStorageRoot_WorkDir +} + +func (x *Config) GetInstanceName() string { + if x != nil { + return x.InstanceName + } + return "" +} + +func (x *Config) GetProtojson() bool { + if x != nil { + return x.Protojson + } + return false +} + +var File_app_persistentstorage_filesystemstorage_config_proto protoreflect.FileDescriptor + +var file_app_persistentstorage_filesystemstorage_config_proto_rawDesc = string([]byte{ + 0x0a, 0x34, 0x61, 0x70, 0x70, 0x2f, 0x70, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x74, + 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x79, 0x73, 0x74, + 0x65, 0x6d, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x32, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, + 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, + 0x74, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x79, 0x73, + 0x74, 0x65, 0x6d, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x1a, 0x20, 0x63, 0x6f, 0x6d, 0x6d, + 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x65, 0x78, 0x74, 0x2f, 0x65, 0x78, 0x74, 0x65, + 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xe1, 0x01, 0x0a, + 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x72, 0x0a, 0x12, 0x73, 0x74, 0x61, 0x74, 0x65, + 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x44, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, + 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x74, 0x73, + 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x79, 0x73, 0x74, 0x65, + 0x6d, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x74, + 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x52, 0x10, 0x73, 0x74, 0x61, 0x74, 0x65, + 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x69, + 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0c, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, + 0x12, 0x1c, 0x0a, 0x09, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x6a, 0x73, 0x6f, 0x6e, 0x3a, 0x20, + 0x82, 0xb5, 0x18, 0x1c, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x11, 0x66, + 0x69, 0x6c, 0x65, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, + 0x2a, 0x1f, 0x0a, 0x10, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, + 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x0b, 0x0a, 0x07, 0x57, 0x6f, 0x72, 0x6b, 0x44, 0x69, 0x72, 0x10, + 0x00, 0x42, 0xb3, 0x01, 0x0a, 0x32, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, + 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x74, 0x73, + 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x79, 0x73, 0x74, 0x65, + 0x6d, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x50, 0x01, 0x5a, 0x46, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76, 0x32, 0x66, 0x6c, 0x79, 0x2f, 0x76, 0x32, 0x72, + 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x35, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x70, + 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, + 0x2f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x73, 0x74, 0x6f, 0x72, 0x61, + 0x67, 0x65, 0xaa, 0x02, 0x32, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, + 0x41, 0x70, 0x70, 0x2e, 0x50, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x74, + 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, + 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +}) + +var ( + file_app_persistentstorage_filesystemstorage_config_proto_rawDescOnce sync.Once + file_app_persistentstorage_filesystemstorage_config_proto_rawDescData []byte +) + +func file_app_persistentstorage_filesystemstorage_config_proto_rawDescGZIP() []byte { + file_app_persistentstorage_filesystemstorage_config_proto_rawDescOnce.Do(func() { + file_app_persistentstorage_filesystemstorage_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_app_persistentstorage_filesystemstorage_config_proto_rawDesc), len(file_app_persistentstorage_filesystemstorage_config_proto_rawDesc))) + }) + return file_app_persistentstorage_filesystemstorage_config_proto_rawDescData +} + +var file_app_persistentstorage_filesystemstorage_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_app_persistentstorage_filesystemstorage_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_app_persistentstorage_filesystemstorage_config_proto_goTypes = []any{ + (StateStorageRoot)(0), // 0: v2ray.core.app.persistentstorage.filesystemstorage.StateStorageRoot + (*Config)(nil), // 1: v2ray.core.app.persistentstorage.filesystemstorage.Config +} +var file_app_persistentstorage_filesystemstorage_config_proto_depIdxs = []int32{ + 0, // 0: v2ray.core.app.persistentstorage.filesystemstorage.Config.state_storage_root:type_name -> v2ray.core.app.persistentstorage.filesystemstorage.StateStorageRoot + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_app_persistentstorage_filesystemstorage_config_proto_init() } +func file_app_persistentstorage_filesystemstorage_config_proto_init() { + if File_app_persistentstorage_filesystemstorage_config_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_app_persistentstorage_filesystemstorage_config_proto_rawDesc), len(file_app_persistentstorage_filesystemstorage_config_proto_rawDesc)), + NumEnums: 1, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_app_persistentstorage_filesystemstorage_config_proto_goTypes, + DependencyIndexes: file_app_persistentstorage_filesystemstorage_config_proto_depIdxs, + EnumInfos: file_app_persistentstorage_filesystemstorage_config_proto_enumTypes, + MessageInfos: file_app_persistentstorage_filesystemstorage_config_proto_msgTypes, + }.Build() + File_app_persistentstorage_filesystemstorage_config_proto = out.File + file_app_persistentstorage_filesystemstorage_config_proto_goTypes = nil + file_app_persistentstorage_filesystemstorage_config_proto_depIdxs = nil +} diff --git a/app/persistentstorage/filesystemstorage/config.proto b/app/persistentstorage/filesystemstorage/config.proto new file mode 100644 index 000000000..e9e7e91ef --- /dev/null +++ b/app/persistentstorage/filesystemstorage/config.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; + +package v2ray.core.app.persistentstorage.filesystemstorage; +option csharp_namespace = "V2Ray.Core.App.Persistentstorage.Filesystemstorage"; +option go_package = "github.com/v2fly/v2ray-core/v5/app/persistentstorage/filesystemstorage"; +option java_package = "com.v2ray.core.persistentstorage.filesystemstorage"; +option java_multiple_files = true; + +import "common/protoext/extensions.proto"; + +enum StateStorageRoot { + WorkDir = 0; +} + +message Config { + option (v2ray.core.common.protoext.message_opt).type = "service"; + option (v2ray.core.common.protoext.message_opt).short_name = "filesystemstorage"; + + StateStorageRoot state_storage_root = 1; + string instance_name = 4; + + bool protojson = 5; +} diff --git a/app/persistentstorage/filesystemstorage/fs.go b/app/persistentstorage/filesystemstorage/fs.go new file mode 100644 index 000000000..b2ea8f88c --- /dev/null +++ b/app/persistentstorage/filesystemstorage/fs.go @@ -0,0 +1,133 @@ +package filesystemstorage + +import ( + "bytes" + "context" + "io" + "path/filepath" + "strings" + + "google.golang.org/protobuf/proto" + + "github.com/v2fly/v2ray-core/v5/app/persistentstorage/protostorage" + "github.com/v2fly/v2ray-core/v5/common" + "github.com/v2fly/v2ray-core/v5/common/environment" + "github.com/v2fly/v2ray-core/v5/common/environment/envctx" + "github.com/v2fly/v2ray-core/v5/features/extension/storage" +) + +func newFileSystemStorage(ctx context.Context, config *Config) storage.ScopedPersistentStorageService { + appEnvironment := envctx.EnvironmentFromContext(ctx).(environment.AppEnvironment) + fss := &fileSystemStorage{ + fs: appEnvironment, + pathRoot: config.InstanceName, + currentLocation: nil, + config: config, + } + + protoStorageInst := protostorage.NewProtoStorage(fss, config.Protojson) + fss.proto = protoStorageInst + return fss +} + +type fileSystemStorage struct { + fs environment.FileSystemCapabilitySet + proto protostorage.ProtoPersistentStorage + + pathRoot string + currentLocation []string + config *Config +} + +func (f *fileSystemStorage) Type() interface{} { + return storage.ScopedPersistentStorageServiceType +} + +func (f *fileSystemStorage) Start() error { + return nil +} + +func (f *fileSystemStorage) Close() error { + return nil +} + +func (f *fileSystemStorage) PutProto(ctx context.Context, key string, pb proto.Message) error { + return f.proto.PutProto(ctx, key, pb) +} + +func (f *fileSystemStorage) GetProto(ctx context.Context, key string, pb proto.Message) error { + return f.proto.GetProto(ctx, key, pb) +} + +func (f *fileSystemStorage) ScopedPersistentStorageEngine() { +} + +func (f *fileSystemStorage) Put(ctx context.Context, key []byte, value []byte) error { + finalPath := filepath.Join(f.pathRoot, filepath.Join(f.currentLocation...), string(key)) + if value == nil { + return f.fs.RemoveFile()(finalPath) + } + writer, err := f.fs.OpenFileForWrite()(finalPath) + if err != nil { + return err + } + defer writer.Close() + _, err = io.Copy(writer, io.NopCloser(bytes.NewReader(value))) + return err +} + +func (f *fileSystemStorage) Get(ctx context.Context, key []byte) ([]byte, error) { + finalPath := filepath.Join(f.pathRoot, filepath.Join(f.currentLocation...), string(key)) + reader, err := f.fs.OpenFileForRead()(finalPath) + if err != nil { + return nil, err + } + defer reader.Close() + return io.ReadAll(reader) +} + +func (f *fileSystemStorage) List(ctx context.Context, keyPrefix []byte) ([][]byte, error) { + res, err := f.fs.ReadDir()(filepath.Join(f.pathRoot, filepath.Join(f.currentLocation...))) + if err != nil { + return nil, err + } + var result [][]byte + for _, entry := range res { + if !entry.IsDir() && bytes.HasPrefix([]byte(entry.Name()), keyPrefix) { + result = append(result, []byte(entry.Name())) + } + } + return result, nil +} + +func (f *fileSystemStorage) Clear(ctx context.Context) { + allFile, err := f.List(ctx, []byte{}) + if err != nil { + return + } + for _, file := range allFile { + _ = f.Put(ctx, file, nil) + } +} + +func (f *fileSystemStorage) NarrowScope(ctx context.Context, key []byte) (storage.ScopedPersistentStorage, error) { + escapedKey := strings.ReplaceAll(string(key), "/", "_") + fss := &fileSystemStorage{ + fs: f.fs, + pathRoot: f.pathRoot, + currentLocation: append(f.currentLocation, escapedKey), + config: f.config, + } + fss.proto = protostorage.NewProtoStorage(fss, f.config.Protojson) + return fss, nil +} + +func (f *fileSystemStorage) DropScope(ctx context.Context, key []byte) error { + panic("unimplemented") +} + +func init() { + common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { + return newFileSystemStorage(ctx, config.(*Config)), nil + })) +} diff --git a/app/persistentstorage/protostorage/protokv.go b/app/persistentstorage/protostorage/protokv.go new file mode 100644 index 000000000..350a74a62 --- /dev/null +++ b/app/persistentstorage/protostorage/protokv.go @@ -0,0 +1,51 @@ +package protostorage + +import ( + "context" + + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" + + "github.com/v2fly/v2ray-core/v5/features/extension/storage" +) + +type ProtoPersistentStorage interface { + PutProto(ctx context.Context, key string, pb proto.Message) error + GetProto(ctx context.Context, key string, pb proto.Message) error +} + +type protoStorage struct { + storage storage.ScopedPersistentStorage + textFormat bool +} + +func (p *protoStorage) PutProto(ctx context.Context, key string, pb proto.Message) error { + if !p.textFormat { + data, err := proto.Marshal(pb) + if err != nil { + return err + } + return p.storage.Put(ctx, []byte(key), data) + } else { + protojsonStr := protojson.Format(pb) + return p.storage.Put(ctx, []byte(key), []byte(protojsonStr)) + } +} + +func (p *protoStorage) GetProto(ctx context.Context, key string, pb proto.Message) error { + data, err := p.storage.Get(ctx, []byte(key)) + if err != nil { + return err + } + if !p.textFormat { + return proto.Unmarshal(data, pb) + } + return protojson.Unmarshal(data, pb) +} + +func NewProtoStorage(storage storage.ScopedPersistentStorage, textFormat bool) ProtoPersistentStorage { + return &protoStorage{ + storage: storage, + textFormat: textFormat, + } +} diff --git a/app/persistentstorage/storage.go b/app/persistentstorage/storage.go new file mode 100644 index 000000000..74a86191f --- /dev/null +++ b/app/persistentstorage/storage.go @@ -0,0 +1,5 @@ +package persistentstorage + +import "github.com/v2fly/v2ray-core/v5/features/extension/storage" + +type ScopedPersistentStorage = storage.ScopedPersistentStorage diff --git a/app/proxyman/outbound/handler_test.go b/app/proxyman/outbound/handler_test.go index af5c6ab68..e56559e44 100644 --- a/app/proxyman/outbound/handler_test.go +++ b/app/proxyman/outbound/handler_test.go @@ -5,18 +5,18 @@ import ( "testing" _ "unsafe" - "github.com/v2fly/v2ray-core/v5/common/environment/systemnetworkimpl" - - "github.com/v2fly/v2ray-core/v5/common/environment" - "github.com/v2fly/v2ray-core/v5/common/environment/envctx" - "github.com/v2fly/v2ray-core/v5/common/environment/transientstorageimpl" - "google.golang.org/protobuf/types/known/anypb" core "github.com/v2fly/v2ray-core/v5" "github.com/v2fly/v2ray-core/v5/app/policy" . "github.com/v2fly/v2ray-core/v5/app/proxyman/outbound" "github.com/v2fly/v2ray-core/v5/app/stats" + "github.com/v2fly/v2ray-core/v5/common/environment" + "github.com/v2fly/v2ray-core/v5/common/environment/deferredpersistentstorage" + "github.com/v2fly/v2ray-core/v5/common/environment/envctx" + "github.com/v2fly/v2ray-core/v5/common/environment/filesystemimpl" + "github.com/v2fly/v2ray-core/v5/common/environment/systemnetworkimpl" + "github.com/v2fly/v2ray-core/v5/common/environment/transientstorageimpl" "github.com/v2fly/v2ray-core/v5/common/net" "github.com/v2fly/v2ray-core/v5/common/serial" "github.com/v2fly/v2ray-core/v5/features/outbound" @@ -50,7 +50,11 @@ func TestOutboundWithoutStatCounter(t *testing.T) { v.AddFeature((outbound.Manager)(new(Manager))) ctx := toContext(context.Background(), v) defaultNetworkImpl := systemnetworkimpl.NewSystemNetworkDefault() - rootEnv := environment.NewRootEnvImpl(ctx, transientstorageimpl.NewScopedTransientStorageImpl(), defaultNetworkImpl.Dialer(), defaultNetworkImpl.Listener()) + defaultFilesystemImpl := filesystemimpl.NewDefaultFileSystemDefaultImpl() + deferredPersistentStorageImpl := deferredpersistentstorage.NewDeferredPersistentStorage(ctx) + rootEnv := environment.NewRootEnvImpl(ctx, + transientstorageimpl.NewScopedTransientStorageImpl(), defaultNetworkImpl.Dialer(), defaultNetworkImpl.Listener(), + defaultFilesystemImpl, deferredPersistentStorageImpl) proxyEnvironment := rootEnv.ProxyEnvironment("o") ctx = envctx.ContextWithEnvironment(ctx, proxyEnvironment) h, _ := NewHandler(ctx, &core.OutboundHandlerConfig{ @@ -83,7 +87,11 @@ func TestOutboundWithStatCounter(t *testing.T) { v.AddFeature((outbound.Manager)(new(Manager))) ctx := toContext(context.Background(), v) defaultNetworkImpl := systemnetworkimpl.NewSystemNetworkDefault() - rootEnv := environment.NewRootEnvImpl(ctx, transientstorageimpl.NewScopedTransientStorageImpl(), defaultNetworkImpl.Dialer(), defaultNetworkImpl.Listener()) + defaultFilesystemImpl := filesystemimpl.NewDefaultFileSystemDefaultImpl() + deferredPersistentStorageImpl := deferredpersistentstorage.NewDeferredPersistentStorage(ctx) + rootEnv := environment.NewRootEnvImpl(ctx, + transientstorageimpl.NewScopedTransientStorageImpl(), defaultNetworkImpl.Dialer(), defaultNetworkImpl.Listener(), + defaultFilesystemImpl, deferredPersistentStorageImpl) proxyEnvironment := rootEnv.ProxyEnvironment("o") ctx = envctx.ContextWithEnvironment(ctx, proxyEnvironment) h, _ := NewHandler(ctx, &core.OutboundHandlerConfig{ diff --git a/common/environment/deferredpersistentstorage/defereredPersistentStorage.go b/common/environment/deferredpersistentstorage/defereredPersistentStorage.go new file mode 100644 index 000000000..f3b840b68 --- /dev/null +++ b/common/environment/deferredpersistentstorage/defereredPersistentStorage.go @@ -0,0 +1,111 @@ +package deferredpersistentstorage + +import ( + "context" + + "github.com/v2fly/v2ray-core/v5/app/persistentstorage" + "github.com/v2fly/v2ray-core/v5/common/errors" + "github.com/v2fly/v2ray-core/v5/features/extension/storage" +) + +type DeferredPersistentStorage interface { + storage.ScopedPersistentStorage + ProvideInner(ctx context.Context, inner persistentstorage.ScopedPersistentStorage) +} + +var errNotExist = errors.New("persistent storage does not exist") + +type deferredPersistentStorage struct { + ready context.Context + done context.CancelFunc + inner persistentstorage.ScopedPersistentStorage + + awaitingChildren []*deferredPersistentStorage + + intoScopes []string +} + +func (d *deferredPersistentStorage) ScopedPersistentStorageEngine() { +} + +func (d *deferredPersistentStorage) Put(ctx context.Context, key []byte, value []byte) error { + <-d.ready.Done() + if d.inner == nil { + return errNotExist + } + return d.inner.Put(ctx, key, value) +} + +func (d *deferredPersistentStorage) Get(ctx context.Context, key []byte) ([]byte, error) { + <-d.ready.Done() + if d.inner == nil { + return nil, errNotExist + } + return d.inner.Get(ctx, key) +} + +func (d *deferredPersistentStorage) List(ctx context.Context, keyPrefix []byte) ([][]byte, error) { + <-d.ready.Done() + if d.inner == nil { + return nil, errNotExist + } + return d.inner.List(ctx, keyPrefix) +} + +func (d *deferredPersistentStorage) Clear(ctx context.Context) { + <-d.ready.Done() + if d.inner == nil { + return + } + d.inner.Clear(ctx) +} + +func (d *deferredPersistentStorage) NarrowScope(ctx context.Context, key []byte) (storage.ScopedPersistentStorage, error) { + if d.ready.Err() != nil { + return d.inner.NarrowScope(ctx, key) + } + ready, done := context.WithCancel(ctx) + swallowCopyScopes := d.intoScopes + dps := &deferredPersistentStorage{ + ready: ready, + done: done, + inner: nil, + intoScopes: append(swallowCopyScopes, string(key)), + } + d.awaitingChildren = append(d.awaitingChildren, dps) + return dps, nil +} + +func (d *deferredPersistentStorage) DropScope(ctx context.Context, key []byte) error { + <-d.ready.Done() + if d.inner == nil { + return errNotExist + } + return d.inner.DropScope(ctx, key) +} + +func (d *deferredPersistentStorage) ProvideInner(ctx context.Context, inner persistentstorage.ScopedPersistentStorage) { + d.inner = inner + if inner != nil { + for _, scope := range d.intoScopes { + newScope, err := inner.NarrowScope(ctx, []byte(scope)) + if err != nil { + panic(err) + } + d.inner = newScope + } + } + for _, child := range d.awaitingChildren { + child.ProvideInner(ctx, d.inner) + } + d.done() +} + +func NewDeferredPersistentStorage(ctx context.Context) DeferredPersistentStorage { + ready, done := context.WithCancel(ctx) + return &deferredPersistentStorage{ + ready: ready, + done: done, + inner: nil, + } +} diff --git a/common/environment/envimpl/fs.go b/common/environment/envimpl/fs.go index afb23c10c..d8a98b95a 100644 --- a/common/environment/envimpl/fs.go +++ b/common/environment/envimpl/fs.go @@ -8,6 +8,14 @@ import ( type fileSystemDefaultImpl struct{} +func (f fileSystemDefaultImpl) ReadDir() fsifce.FileReadDirFunc { + return filesystem.NewFileReadDir +} + +func (f fileSystemDefaultImpl) RemoveFile() fsifce.FileRemoveFunc { + return filesystem.NewFileRemover +} + func (f fileSystemDefaultImpl) OpenFileForReadSeek() fsifce.FileSeekerFunc { return filesystem.NewFileSeeker } diff --git a/common/environment/filesystemcap/fscap.go b/common/environment/filesystemcap/fscap.go index e45b25e3e..36a67ae64 100644 --- a/common/environment/filesystemcap/fscap.go +++ b/common/environment/filesystemcap/fscap.go @@ -6,4 +6,6 @@ type FileSystemCapabilitySet interface { OpenFileForReadSeek() fsifce.FileSeekerFunc OpenFileForRead() fsifce.FileReaderFunc OpenFileForWrite() fsifce.FileWriterFunc + ReadDir() fsifce.FileReadDirFunc + RemoveFile() fsifce.FileRemoveFunc } diff --git a/common/environment/filesystemimpl/fsimpl.go b/common/environment/filesystemimpl/fsimpl.go new file mode 100644 index 000000000..2e5d48aae --- /dev/null +++ b/common/environment/filesystemimpl/fsimpl.go @@ -0,0 +1,33 @@ +package filesystemimpl + +import ( + "github.com/v2fly/v2ray-core/v5/common/environment" + "github.com/v2fly/v2ray-core/v5/common/platform/filesystem" + "github.com/v2fly/v2ray-core/v5/common/platform/filesystem/fsifce" +) + +func NewDefaultFileSystemDefaultImpl() environment.FileSystemCapabilitySet { + return fsCapImpl{} +} + +type fsCapImpl struct{} + +func (f fsCapImpl) OpenFileForReadSeek() fsifce.FileSeekerFunc { + return filesystem.NewFileSeeker +} + +func (f fsCapImpl) OpenFileForRead() fsifce.FileReaderFunc { + return filesystem.NewFileReader +} + +func (f fsCapImpl) OpenFileForWrite() fsifce.FileWriterFunc { + return filesystem.NewFileWriter +} + +func (f fsCapImpl) ReadDir() fsifce.FileReadDirFunc { + return filesystem.NewFileReadDir +} + +func (f fsCapImpl) RemoveFile() fsifce.FileRemoveFunc { + return filesystem.NewFileRemover +} diff --git a/common/environment/rootcap_impl.go b/common/environment/rootcap_impl.go index adc5567bf..78d46d7a5 100644 --- a/common/environment/rootcap_impl.go +++ b/common/environment/rootcap_impl.go @@ -11,19 +11,24 @@ import ( func NewRootEnvImpl(ctx context.Context, transientStorage storage.ScopedTransientStorage, systemDialer internet.SystemDialer, systemListener internet.SystemListener, + filesystem FileSystemCapabilitySet, persistStorage storage.ScopedPersistentStorage, ) RootEnvironment { return &rootEnvImpl{ transientStorage: transientStorage, systemListener: systemListener, systemDialer: systemDialer, + filesystem: filesystem, + persistStorage: persistStorage, ctx: ctx, } } type rootEnvImpl struct { + persistStorage storage.ScopedPersistentStorage transientStorage storage.ScopedTransientStorage systemDialer internet.SystemDialer systemListener internet.SystemListener + filesystem FileSystemCapabilitySet ctx context.Context } @@ -37,10 +42,16 @@ func (r *rootEnvImpl) AppEnvironment(tag string) AppEnvironment { if err != nil { return nil } + persistStorage, err := r.persistStorage.NarrowScope(r.ctx, []byte(tag)) + if err != nil { + return nil + } return &appEnvImpl{ transientStorage: transientStorage, + persistStorage: persistStorage, systemListener: r.systemListener, systemDialer: r.systemDialer, + filesystem: r.filesystem, ctx: r.ctx, } } @@ -67,9 +78,11 @@ func (r *rootEnvImpl) DropProxyEnvironment(tag string) error { } type appEnvImpl struct { + persistStorage storage.ScopedPersistentStorage transientStorage storage.ScopedTransientStorage systemDialer internet.SystemDialer systemListener internet.SystemListener + filesystem FileSystemCapabilitySet ctx context.Context } @@ -95,19 +108,27 @@ func (a *appEnvImpl) OutboundDialer() tagged.DialFunc { } func (a *appEnvImpl) OpenFileForReadSeek() fsifce.FileSeekerFunc { - panic("implement me") + return a.filesystem.OpenFileForReadSeek() } func (a *appEnvImpl) OpenFileForRead() fsifce.FileReaderFunc { - panic("implement me") + return a.filesystem.OpenFileForRead() } func (a *appEnvImpl) OpenFileForWrite() fsifce.FileWriterFunc { - panic("implement me") + return a.filesystem.OpenFileForWrite() +} + +func (a *appEnvImpl) ReadDir() fsifce.FileReadDirFunc { + return a.filesystem.ReadDir() +} + +func (a *appEnvImpl) RemoveFile() fsifce.FileRemoveFunc { + return a.filesystem.RemoveFile() } func (a *appEnvImpl) PersistentStorage() storage.ScopedPersistentStorage { - panic("implement me") + return a.persistStorage } func (a *appEnvImpl) TransientStorage() storage.ScopedTransientStorage { diff --git a/common/platform/filesystem/file.go b/common/platform/filesystem/file.go index f8964968d..afce9749c 100644 --- a/common/platform/filesystem/file.go +++ b/common/platform/filesystem/file.go @@ -3,6 +3,7 @@ package filesystem import ( "io" "os" + "path/filepath" "github.com/v2fly/v2ray-core/v5/common/buf" "github.com/v2fly/v2ray-core/v5/common/platform" @@ -18,9 +19,17 @@ var NewFileReader fsifce.FileReaderFunc = func(path string) (io.ReadCloser, erro } var NewFileWriter fsifce.FileWriterFunc = func(path string) (io.WriteCloser, error) { + basePath := filepath.Dir(path) + if err := os.MkdirAll(basePath, 0o700); err != nil { + return nil, err + } return os.Create(path) } +var NewFileRemover fsifce.FileRemoveFunc = os.Remove + +var NewFileReadDir fsifce.FileReadDirFunc = os.ReadDir + func ReadFile(path string) ([]byte, error) { reader, err := NewFileReader(path) if err != nil { diff --git a/common/platform/filesystem/fsifce/ifce.go b/common/platform/filesystem/fsifce/ifce.go index 9aaabcff8..341a87d14 100644 --- a/common/platform/filesystem/fsifce/ifce.go +++ b/common/platform/filesystem/fsifce/ifce.go @@ -1,9 +1,16 @@ package fsifce -import "io" +import ( + "io" + "io/fs" +) type FileSeekerFunc func(path string) (io.ReadSeekCloser, error) type FileReaderFunc func(path string) (io.ReadCloser, error) type FileWriterFunc func(path string) (io.WriteCloser, error) + +type FileReadDirFunc func(path string) ([]fs.DirEntry, error) + +type FileRemoveFunc func(path string) error diff --git a/features/extension/storage/storage.go b/features/extension/storage/storage.go index bcb407c69..03f061c40 100644 --- a/features/extension/storage/storage.go +++ b/features/extension/storage/storage.go @@ -2,6 +2,8 @@ package storage import ( "context" + + "github.com/v2fly/v2ray-core/v5/features" ) type ScopedPersistentStorage interface { @@ -23,3 +25,10 @@ type ScopedTransientStorage interface { NarrowScope(ctx context.Context, key string) (ScopedTransientStorage, error) DropScope(ctx context.Context, key string) error } + +type ScopedPersistentStorageService interface { + ScopedPersistentStorage + features.Feature +} + +var ScopedPersistentStorageServiceType = (*ScopedPersistentStorageService)(nil) diff --git a/infra/vprotogen/main.go b/infra/vprotogen/main.go index 8f878ac5b..eb1d43519 100644 --- a/infra/vprotogen/main.go +++ b/infra/vprotogen/main.go @@ -108,9 +108,9 @@ func getProjectProtocVersion(url string) (string, error) { if err != nil { return "", fmt.Errorf("can not read from body") } - versionRegexp := regexp.MustCompile(`\/\/\s*protoc\s*v(\d+\.\d+\.\d+)`) + versionRegexp := regexp.MustCompile(`\/\/\s*protoc\s*v(\d+\.(\d+\.\d+))`) matched := versionRegexp.FindStringSubmatch(string(body)) - return matched[1], nil + return matched[2], nil } func getInstalledProtocVersion(protocPath string) (string, error) { @@ -126,10 +126,6 @@ func getInstalledProtocVersion(protocPath string) (string, error) { if len(matched) == 0 { return "", errors.New("can not parse protoc version") } - - if len(matched) == 2 { - installedVersion += "4." // in contrast to getProjectProtocVersion() - } installedVersion += matched[1] fmt.Println("Using protoc version: " + installedVersion) return installedVersion, nil @@ -139,9 +135,6 @@ func parseVersion(s string, width int) int64 { strList := strings.Split(s, ".") format := fmt.Sprintf("%%s%%0%ds", width) v := "" - if len(strList) == 2 { - strList = append([]string{"4"}, strList...) - } for _, value := range strList { v = fmt.Sprintf(format, v, value) } diff --git a/main/distro/all/all.go b/main/distro/all/all.go index ead900ced..2d7eea1d2 100644 --- a/main/distro/all/all.go +++ b/main/distro/all/all.go @@ -34,6 +34,7 @@ import ( _ "github.com/v2fly/v2ray-core/v5/app/commander/webcommander" _ "github.com/v2fly/v2ray-core/v5/app/instman" _ "github.com/v2fly/v2ray-core/v5/app/observatory" + _ "github.com/v2fly/v2ray-core/v5/app/persistentstorage/filesystemstorage" _ "github.com/v2fly/v2ray-core/v5/app/tun" // Inbound and outbound proxies. diff --git a/transport/internet/kcp/kcp_test.go b/transport/internet/kcp/kcp_test.go index 5e37b35ea..4f856ec1a 100644 --- a/transport/internet/kcp/kcp_test.go +++ b/transport/internet/kcp/kcp_test.go @@ -7,6 +7,9 @@ import ( "testing" "time" + "github.com/v2fly/v2ray-core/v5/common/environment/deferredpersistentstorage" + "github.com/v2fly/v2ray-core/v5/common/environment/filesystemimpl" + "github.com/v2fly/v2ray-core/v5/common/environment" "github.com/v2fly/v2ray-core/v5/common/environment/envctx" "github.com/v2fly/v2ray-core/v5/common/environment/systemnetworkimpl" @@ -25,7 +28,11 @@ import ( func TestDialAndListen(t *testing.T) { ctx := context.Background() defaultNetworkImpl := systemnetworkimpl.NewSystemNetworkDefault() - rootEnv := environment.NewRootEnvImpl(ctx, transientstorageimpl.NewScopedTransientStorageImpl(), defaultNetworkImpl.Dialer(), defaultNetworkImpl.Listener()) + defaultFilesystemImpl := filesystemimpl.NewDefaultFileSystemDefaultImpl() + deferredPersistentStorageImpl := deferredpersistentstorage.NewDeferredPersistentStorage(ctx) + rootEnv := environment.NewRootEnvImpl(ctx, + transientstorageimpl.NewScopedTransientStorageImpl(), defaultNetworkImpl.Dialer(), defaultNetworkImpl.Listener(), + defaultFilesystemImpl, deferredPersistentStorageImpl) proxyEnvironment := rootEnv.ProxyEnvironment("o") transportEnvironment, err := proxyEnvironment.NarrowScopeToTransport("kcp") if err != nil { diff --git a/transport/internet/tls/ech.go b/transport/internet/tls/ech.go index 41be26d9d..fd0ab8ca8 100644 --- a/transport/internet/tls/ech.go +++ b/transport/internet/tls/ech.go @@ -13,6 +13,7 @@ import ( "time" "github.com/miekg/dns" + "github.com/v2fly/v2ray-core/v5/common/net" "github.com/v2fly/v2ray-core/v5/transport/internet" ) diff --git a/v2ray.go b/v2ray.go index 0828df985..80905d642 100644 --- a/v2ray.go +++ b/v2ray.go @@ -5,6 +5,10 @@ import ( "reflect" sync "sync" + "github.com/v2fly/v2ray-core/v5/common/environment/deferredpersistentstorage" + "github.com/v2fly/v2ray-core/v5/common/environment/filesystemimpl" + "github.com/v2fly/v2ray-core/v5/features/extension/storage" + "github.com/v2fly/v2ray-core/v5/common" "github.com/v2fly/v2ray-core/v5/common/environment" "github.com/v2fly/v2ray-core/v5/common/environment/systemnetworkimpl" @@ -205,7 +209,14 @@ func initInstanceWithConfig(config *Config, server *Instance) (bool, error) { } defaultNetworkImpl := systemnetworkimpl.NewSystemNetworkDefault() - server.env = environment.NewRootEnvImpl(server.ctx, transientstorageimpl.NewScopedTransientStorageImpl(), defaultNetworkImpl.Dialer(), defaultNetworkImpl.Listener()) + defaultFilesystemImpl := filesystemimpl.NewDefaultFileSystemDefaultImpl() + deferredPersistentStorageImpl := deferredpersistentstorage.NewDeferredPersistentStorage(server.ctx) + server.env = environment.NewRootEnvImpl(server.ctx, + transientstorageimpl.NewScopedTransientStorageImpl(), + defaultNetworkImpl.Dialer(), + defaultNetworkImpl.Listener(), + defaultFilesystemImpl, + deferredPersistentStorageImpl) for _, appSettings := range config.App { settings, err := serial.GetInstanceOf(appSettings) @@ -247,6 +258,12 @@ func initInstanceWithConfig(config *Config, server *Instance) (bool, error) { return true, newError("not all dependency are resolved.") } + if persistentStorageService := server.GetFeature(storage.ScopedPersistentStorageServiceType); persistentStorageService != nil { + deferredPersistentStorageImpl.ProvideInner(server.ctx, persistentStorageService.(storage.ScopedPersistentStorage)) + } else { + deferredPersistentStorageImpl.ProvideInner(server.ctx, nil) + } + if err := addInboundHandlers(server, config.Inbound); err != nil { return true, err }