implement speedtest ui

This commit is contained in:
Nova 2025-05-07 03:24:26 +03:30
parent da859cb26f
commit bd1b1b1635
18 changed files with 409 additions and 147 deletions

View File

@ -914,12 +914,15 @@ func (x *IsPrivilegedResponse) GetHasPrivilege() bool {
}
type SpeedTestRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Config string `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"`
OutboundTags []string `protobuf:"bytes,2,rep,name=outbound_tags,json=outboundTags,proto3" json:"outbound_tags,omitempty"`
TestCurrent bool `protobuf:"varint,3,opt,name=test_current,json=testCurrent,proto3" json:"test_current,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
Config string `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"`
OutboundTags []string `protobuf:"bytes,2,rep,name=outbound_tags,json=outboundTags,proto3" json:"outbound_tags,omitempty"`
TestCurrent bool `protobuf:"varint,3,opt,name=test_current,json=testCurrent,proto3" json:"test_current,omitempty"`
UseDefaultOutbound bool `protobuf:"varint,4,opt,name=use_default_outbound,json=useDefaultOutbound,proto3" json:"use_default_outbound,omitempty"`
TestDownload bool `protobuf:"varint,5,opt,name=test_download,json=testDownload,proto3" json:"test_download,omitempty"`
TestUpload bool `protobuf:"varint,6,opt,name=test_upload,json=testUpload,proto3" json:"test_upload,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *SpeedTestRequest) Reset() {
@ -973,6 +976,27 @@ func (x *SpeedTestRequest) GetTestCurrent() bool {
return false
}
func (x *SpeedTestRequest) GetUseDefaultOutbound() bool {
if x != nil {
return x.UseDefaultOutbound
}
return false
}
func (x *SpeedTestRequest) GetTestDownload() bool {
if x != nil {
return x.TestDownload
}
return false
}
func (x *SpeedTestRequest) GetTestUpload() bool {
if x != nil {
return x.TestUpload
}
return false
}
type SpeedTestResult struct {
state protoimpl.MessageState `protogen:"open.v1"`
DlSpeed string `protobuf:"bytes,1,opt,name=dl_speed,json=dlSpeed,proto3" json:"dl_speed,omitempty"`
@ -1259,105 +1283,113 @@ var file_libcore_proto_rawDesc = string([]byte{
0x14, 0x49, 0x73, 0x50, 0x72, 0x69, 0x76, 0x69, 0x6c, 0x65, 0x67, 0x65, 0x64, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x68, 0x61, 0x73, 0x5f, 0x70, 0x72, 0x69,
0x76, 0x69, 0x6c, 0x65, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x68, 0x61,
0x73, 0x50, 0x72, 0x69, 0x76, 0x69, 0x6c, 0x65, 0x67, 0x65, 0x22, 0x72, 0x0a, 0x10, 0x53, 0x70,
0x65, 0x65, 0x64, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16,
0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,
0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x23, 0x0a, 0x0d, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75,
0x6e, 0x64, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x6f,
0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x61, 0x67, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x74,
0x65, 0x73, 0x74, 0x5f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28,
0x08, 0x52, 0x0b, 0x74, 0x65, 0x73, 0x74, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x22, 0xe2,
0x01, 0x0a, 0x0f, 0x53, 0x70, 0x65, 0x65, 0x64, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x75,
0x6c, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x64, 0x6c, 0x5f, 0x73, 0x70, 0x65, 0x65, 0x64, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x64, 0x6c, 0x53, 0x70, 0x65, 0x65, 0x64, 0x12, 0x19, 0x0a,
0x08, 0x75, 0x6c, 0x5f, 0x73, 0x70, 0x65, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
0x07, 0x75, 0x6c, 0x53, 0x70, 0x65, 0x65, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x6c, 0x61, 0x74, 0x65,
0x6e, 0x63, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x6c, 0x61, 0x74, 0x65, 0x6e,
0x63, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x74,
0x61, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75,
0x6e, 0x64, 0x54, 0x61, 0x67, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x05,
0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x73,
0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09,
0x52, 0x0a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x25, 0x0a, 0x0e,
0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x07,
0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x75, 0x6e,
0x74, 0x72, 0x79, 0x22, 0x47, 0x0a, 0x11, 0x53, 0x70, 0x65, 0x65, 0x64, 0x54, 0x65, 0x73, 0x74,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75,
0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x69, 0x62, 0x63,
0x6f, 0x72, 0x65, 0x2e, 0x53, 0x70, 0x65, 0x65, 0x64, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73,
0x75, 0x6c, 0x74, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x22, 0x69, 0x0a, 0x16,
0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x70, 0x65, 0x65, 0x64, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x30, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74,
0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65,
0x2e, 0x53, 0x70, 0x65, 0x65, 0x64, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74,
0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x73, 0x5f, 0x72,
0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x73,
0x52, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x32, 0xfa, 0x07, 0x0a, 0x0e, 0x4c, 0x69, 0x62, 0x63,
0x6f, 0x72, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x2d, 0x0a, 0x04, 0x45, 0x78,
0x69, 0x74, 0x12, 0x11, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70,
0x74, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x12, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e,
0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x12, 0x33, 0x0a, 0x05, 0x53, 0x74, 0x61,
0x72, 0x74, 0x12, 0x16, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4c, 0x6f, 0x61,
0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x1a, 0x12, 0x2e, 0x6c, 0x69, 0x62,
0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x12, 0x2d,
0x0a, 0x04, 0x53, 0x74, 0x6f, 0x70, 0x12, 0x11, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65,
0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x12, 0x2e, 0x6c, 0x69, 0x62, 0x63,
0x6f, 0x72, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x12, 0x39, 0x0a,
0x0b, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x16, 0x2e, 0x6c,
0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4c, 0x6f, 0x61, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69,
0x67, 0x52, 0x65, 0x71, 0x1a, 0x12, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45,
0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x12, 0x2b, 0x0a, 0x04, 0x54, 0x65, 0x73, 0x74,
0x12, 0x10, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x52,
0x65, 0x71, 0x1a, 0x11, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x54, 0x65, 0x73,
0x74, 0x52, 0x65, 0x73, 0x70, 0x12, 0x31, 0x0a, 0x08, 0x53, 0x74, 0x6f, 0x70, 0x54, 0x65, 0x73,
0x74, 0x12, 0x11, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74,
0x79, 0x52, 0x65, 0x71, 0x1a, 0x12, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45,
0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x12, 0x38, 0x0a, 0x0a, 0x51, 0x75, 0x65, 0x72,
0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x11, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65,
0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x17, 0x2e, 0x6c, 0x69, 0x62, 0x63,
0x6f, 0x72, 0x65, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65,
0x73, 0x70, 0x12, 0x42, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63,
0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x11, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e,
0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x1c, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f,
0x72, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f,
0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x12, 0x46, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x47, 0x65, 0x6f,
0x49, 0x50, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x17, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65,
0x2e, 0x47, 0x65, 0x6f, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x1d, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x47, 0x65, 0x6f,
0x49, 0x50, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a,
0x0a, 0x0e, 0x47, 0x65, 0x74, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x4c, 0x69, 0x73, 0x74,
0x12, 0x17, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x47, 0x65, 0x6f, 0x4c, 0x69,
0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x6c, 0x69, 0x62, 0x63,
0x6f, 0x72, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x4c, 0x69,
0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x11, 0x43, 0x6f,
0x6d, 0x70, 0x69, 0x6c, 0x65, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x54, 0x6f, 0x53, 0x72, 0x73, 0x12,
0x21, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c,
0x65, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x54, 0x6f, 0x53, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70,
0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x12, 0x4e, 0x0a, 0x13, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c,
0x65, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x54, 0x6f, 0x53, 0x72, 0x73, 0x12, 0x23, 0x2e,
0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x47,
0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x54, 0x6f, 0x53, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70,
0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x12, 0x40, 0x0a, 0x0c, 0x53, 0x65, 0x74, 0x53, 0x79, 0x73,
0x74, 0x65, 0x6d, 0x44, 0x4e, 0x53, 0x12, 0x1c, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65,
0x2e, 0x53, 0x65, 0x74, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x44, 0x4e, 0x53, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45,
0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x12, 0x40, 0x0a, 0x0c, 0x49, 0x73, 0x50, 0x72,
0x69, 0x76, 0x69, 0x6c, 0x65, 0x67, 0x65, 0x64, 0x12, 0x11, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f,
0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x1d, 0x2e, 0x6c, 0x69,
0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x49, 0x73, 0x50, 0x72, 0x69, 0x76, 0x69, 0x6c, 0x65, 0x67,
0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a, 0x09, 0x53, 0x70,
0x65, 0x65, 0x64, 0x54, 0x65, 0x73, 0x74, 0x12, 0x19, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72,
0x65, 0x2e, 0x53, 0x70, 0x65, 0x65, 0x64, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x53, 0x70, 0x65,
0x65, 0x64, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44,
0x0a, 0x0e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x70, 0x65, 0x65, 0x64, 0x54, 0x65, 0x73, 0x74,
0x12, 0x11, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,
0x52, 0x65, 0x71, 0x1a, 0x1f, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x51, 0x75,
0x65, 0x72, 0x79, 0x53, 0x70, 0x65, 0x65, 0x64, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x42, 0x11, 0x5a, 0x0f, 0x67, 0x72, 0x70, 0x63, 0x5f, 0x73, 0x65, 0x72,
0x76, 0x65, 0x72, 0x2f, 0x67, 0x65, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x73, 0x50, 0x72, 0x69, 0x76, 0x69, 0x6c, 0x65, 0x67, 0x65, 0x22, 0xea, 0x01, 0x0a, 0x10, 0x53,
0x70, 0x65, 0x65, 0x64, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
0x16, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x23, 0x0a, 0x0d, 0x6f, 0x75, 0x74, 0x62, 0x6f,
0x75, 0x6e, 0x64, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c,
0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x61, 0x67, 0x73, 0x12, 0x21, 0x0a, 0x0c,
0x74, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01,
0x28, 0x08, 0x52, 0x0b, 0x74, 0x65, 0x73, 0x74, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x12,
0x30, 0x0a, 0x14, 0x75, 0x73, 0x65, 0x5f, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x6f,
0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x75,
0x73, 0x65, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e,
0x64, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f,
0x61, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x74, 0x65, 0x73, 0x74, 0x44, 0x6f,
0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x75,
0x70, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x74, 0x65, 0x73,
0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0xe2, 0x01, 0x0a, 0x0f, 0x53, 0x70, 0x65, 0x65,
0x64, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x64,
0x6c, 0x5f, 0x73, 0x70, 0x65, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x64,
0x6c, 0x53, 0x70, 0x65, 0x65, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x75, 0x6c, 0x5f, 0x73, 0x70, 0x65,
0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x75, 0x6c, 0x53, 0x70, 0x65, 0x65,
0x64, 0x12, 0x18, 0x0a, 0x07, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x03, 0x20, 0x01,
0x28, 0x05, 0x52, 0x07, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x6f,
0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x74, 0x61, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28,
0x09, 0x52, 0x0b, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x61, 0x67, 0x12, 0x14,
0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65,
0x72, 0x72, 0x6f, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x6e,
0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x72, 0x76, 0x65,
0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f,
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x73,
0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x22, 0x47, 0x0a, 0x11,
0x53, 0x70, 0x65, 0x65, 0x64, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x32, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03,
0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x53, 0x70, 0x65,
0x65, 0x64, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x07, 0x72, 0x65,
0x73, 0x75, 0x6c, 0x74, 0x73, 0x22, 0x69, 0x0a, 0x16, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x70,
0x65, 0x65, 0x64, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x30, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x18, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x53, 0x70, 0x65, 0x65, 0x64, 0x54,
0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c,
0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x73, 0x5f, 0x72, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x18,
0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x73, 0x52, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67,
0x32, 0xfa, 0x07, 0x0a, 0x0e, 0x4c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x53, 0x65, 0x72, 0x76,
0x69, 0x63, 0x65, 0x12, 0x2d, 0x0a, 0x04, 0x45, 0x78, 0x69, 0x74, 0x12, 0x11, 0x2e, 0x6c, 0x69,
0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x12,
0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65,
0x73, 0x70, 0x12, 0x33, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, 0x16, 0x2e, 0x6c, 0x69,
0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4c, 0x6f, 0x61, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
0x52, 0x65, 0x71, 0x1a, 0x12, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x72,
0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x12, 0x2d, 0x0a, 0x04, 0x53, 0x74, 0x6f, 0x70, 0x12,
0x11, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52,
0x65, 0x71, 0x1a, 0x12, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x72, 0x72,
0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x12, 0x39, 0x0a, 0x0b, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x43,
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x16, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e,
0x4c, 0x6f, 0x61, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x1a, 0x12, 0x2e,
0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73,
0x70, 0x12, 0x2b, 0x0a, 0x04, 0x54, 0x65, 0x73, 0x74, 0x12, 0x10, 0x2e, 0x6c, 0x69, 0x62, 0x63,
0x6f, 0x72, 0x65, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x1a, 0x11, 0x2e, 0x6c, 0x69,
0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x12, 0x31,
0x0a, 0x08, 0x53, 0x74, 0x6f, 0x70, 0x54, 0x65, 0x73, 0x74, 0x12, 0x11, 0x2e, 0x6c, 0x69, 0x62,
0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x12, 0x2e,
0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73,
0x70, 0x12, 0x38, 0x0a, 0x0a, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12,
0x11, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52,
0x65, 0x71, 0x1a, 0x17, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x51, 0x75, 0x65,
0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x12, 0x42, 0x0a, 0x0f, 0x4c,
0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x11,
0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65,
0x71, 0x1a, 0x1c, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74,
0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x12,
0x46, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x4c, 0x69, 0x73, 0x74, 0x12,
0x17, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x47, 0x65, 0x6f, 0x4c, 0x69, 0x73,
0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f,
0x72, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x4c, 0x69, 0x73, 0x74, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x47, 0x65,
0x6f, 0x53, 0x69, 0x74, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x17, 0x2e, 0x6c, 0x69, 0x62, 0x63,
0x6f, 0x72, 0x65, 0x2e, 0x47, 0x65, 0x6f, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x47, 0x65, 0x74,
0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x11, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x47, 0x65,
0x6f, 0x49, 0x50, 0x54, 0x6f, 0x53, 0x72, 0x73, 0x12, 0x21, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f,
0x72, 0x65, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x54,
0x6f, 0x53, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x69,
0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x12,
0x4e, 0x0a, 0x13, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74,
0x65, 0x54, 0x6f, 0x53, 0x72, 0x73, 0x12, 0x23, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65,
0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x54,
0x6f, 0x53, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x69,
0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x12,
0x40, 0x0a, 0x0c, 0x53, 0x65, 0x74, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x44, 0x4e, 0x53, 0x12,
0x1c, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x53, 0x65, 0x74, 0x53, 0x79, 0x73,
0x74, 0x65, 0x6d, 0x44, 0x4e, 0x53, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e,
0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73,
0x70, 0x12, 0x40, 0x0a, 0x0c, 0x49, 0x73, 0x50, 0x72, 0x69, 0x76, 0x69, 0x6c, 0x65, 0x67, 0x65,
0x64, 0x12, 0x11, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74,
0x79, 0x52, 0x65, 0x71, 0x1a, 0x1d, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x49,
0x73, 0x50, 0x72, 0x69, 0x76, 0x69, 0x6c, 0x65, 0x67, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a, 0x09, 0x53, 0x70, 0x65, 0x65, 0x64, 0x54, 0x65, 0x73, 0x74,
0x12, 0x19, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x53, 0x70, 0x65, 0x65, 0x64,
0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x69,
0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x53, 0x70, 0x65, 0x65, 0x64, 0x54, 0x65, 0x73, 0x74, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x0e, 0x51, 0x75, 0x65, 0x72, 0x79,
0x53, 0x70, 0x65, 0x65, 0x64, 0x54, 0x65, 0x73, 0x74, 0x12, 0x11, 0x2e, 0x6c, 0x69, 0x62, 0x63,
0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x1f, 0x2e, 0x6c,
0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x70, 0x65, 0x65,
0x64, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x11, 0x5a,
0x0f, 0x67, 0x72, 0x70, 0x63, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x67, 0x65, 0x6e,
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
})
var (

View File

@ -115,6 +115,9 @@ message SpeedTestRequest {
string config = 1;
repeated string outbound_tags = 2;
bool test_current = 3;
bool use_default_outbound = 4;
bool test_download = 5;
bool test_upload = 6;
}
message SpeedTestResult {

View File

@ -5,6 +5,7 @@ go 1.23.0
toolchain go1.24.0
require (
github.com/Mahdi-zarei/speedtest-go v1.7.12
github.com/dustin/go-humanize v1.0.1
github.com/gofrs/uuid/v5 v5.3.2
github.com/oschwald/maxminddb-golang v1.13.1
@ -23,7 +24,6 @@ replace github.com/sagernet/sing-box => github.com/Mahdi-zarei/sing-box v1.3.5-0
replace github.com/sagernet/sing-dns => github.com/Mahdi-zarei/sing-dns v0.3.0-beta.14.0.20250419091211-cee3ab2d4492
require (
github.com/Mahdi-zarei/speedtest-go v1.7.11 // indirect
github.com/ajg/form v1.5.1 // indirect
github.com/andybalholm/brotli v1.0.6 // indirect
github.com/caddyserver/certmagic v0.20.0 // indirect

View File

@ -2,8 +2,8 @@ github.com/Mahdi-zarei/sing-box v1.3.5-0.20250419091424-318eff0efb34 h1:xhAgBfvZ
github.com/Mahdi-zarei/sing-box v1.3.5-0.20250419091424-318eff0efb34/go.mod h1:wMfYe7SB/9L11IuO7r0A5Rh6eoxGsyjoErxeYwqLgXU=
github.com/Mahdi-zarei/sing-dns v0.3.0-beta.14.0.20250419091211-cee3ab2d4492 h1:fLReuPi1fM99oFRERDU96JzEY1YpLh8PmDGEcfnexCY=
github.com/Mahdi-zarei/sing-dns v0.3.0-beta.14.0.20250419091211-cee3ab2d4492/go.mod h1:dweQs54ng2YGzoJfz+F9dGuDNdP5pJ3PLeggnK5VWc8=
github.com/Mahdi-zarei/speedtest-go v1.7.11 h1:K/c7qBlJNAYGv6MujDaIKvtb4AH+yPRdssZ1NJMGPFU=
github.com/Mahdi-zarei/speedtest-go v1.7.11/go.mod h1:b1H+UBFUnLKH1YquN2xao8d1hokcnDFFhEKDARTzddM=
github.com/Mahdi-zarei/speedtest-go v1.7.12 h1:KktFSpobkhIgrOd25e8IpVzVq2enpCDzQQPVtGxWZV4=
github.com/Mahdi-zarei/speedtest-go v1.7.12/go.mod h1:b1H+UBFUnLKH1YquN2xao8d1hokcnDFFhEKDARTzddM=
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=

View File

@ -311,6 +311,9 @@ func (s *server) IsPrivileged(ctx context.Context, _ *gen.EmptyReq) (*gen.IsPriv
}
func (s *server) SpeedTest(ctx context.Context, in *gen.SpeedTestRequest) (*gen.SpeedTestResponse, error) {
if !in.TestDownload && !in.TestUpload {
return nil, errors.New("cannot run empty test")
}
var testInstance *boxbox.Box
var cancel context.CancelFunc
outboundTags := in.OutboundTags
@ -323,8 +326,6 @@ func (s *server) SpeedTest(ctx context.Context, in *gen.SpeedTestRequest) (*gen.
}}}, nil
}
testInstance = boxInstance
outbound := testInstance.Outbound().Default()
outboundTags = []string{outbound.Tag()}
} else {
testInstance, cancel, err = boxmain.Create([]byte(in.Config))
if err != nil {
@ -334,7 +335,12 @@ func (s *server) SpeedTest(ctx context.Context, in *gen.SpeedTestRequest) (*gen.
defer testInstance.Close()
}
results := BatchSpeedTest(testCtx, testInstance, outboundTags)
if in.UseDefaultOutbound || in.TestCurrent {
outbound := testInstance.Outbound().Default()
outboundTags = []string{outbound.Tag()}
}
results := BatchSpeedTest(testCtx, testInstance, outboundTags, in.TestDownload, in.TestUpload)
res := make([]*gen.SpeedTestResult, 0)
for _, data := range results {

View File

@ -6,6 +6,7 @@ import (
"fmt"
"github.com/Mahdi-zarei/speedtest-go/speedtest"
"github.com/sagernet/sing-box/adapter"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/service"
"nekobox_core/internal/boxbox"
@ -137,15 +138,20 @@ func urlTest(ctx context.Context, client *http.Client, url string) (time.Duratio
func getNetDialer(dialer func(ctx context.Context, network string, destination metadata.Socksaddr) (net.Conn, error)) func(ctx context.Context, network string, address string) (net.Conn, error) {
return func(ctx context.Context, network string, address string) (net.Conn, error) {
return dialer(ctx, network, metadata.Socksaddr{Addr: metadata.ParseAddr(address)})
return dialer(ctx, network, metadata.ParseSocksaddr(address))
}
}
func BatchSpeedTest(ctx context.Context, i *boxbox.Box, outboundTags []string) []*SpeedTestResult {
func BatchSpeedTest(ctx context.Context, i *boxbox.Box, outboundTags []string, testDl, testUl bool) []*SpeedTestResult {
outbounds := service.FromContext[adapter.OutboundManager](i.Context())
results := make([]*SpeedTestResult, 0)
for _, tag := range outboundTags {
select {
case <-ctx.Done():
break
default:
}
outbound, exists := outbounds.Outbound(tag)
if !exists {
panic("no outbound with tag " + tag + " found")
@ -154,19 +160,23 @@ func BatchSpeedTest(ctx context.Context, i *boxbox.Box, outboundTags []string) [
res.Tag = tag
results = append(results, res)
insCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
err := speedTestWithDialer(insCtx, getNetDialer(outbound.DialContext), res)
cancel()
err := speedTestWithDialer(ctx, getNetDialer(outbound.DialContext), res, testDl, testUl)
if err != nil {
res.Error = err
fmt.Println("Failed to speedtest with err:", err)
}
if !testDl {
res.DlSpeed = ""
}
if !testUl {
res.UlSpeed = ""
}
}
return results
}
func speedTestWithDialer(ctx context.Context, dialer func(ctx context.Context, network string, address string) (net.Conn, error), res *SpeedTestResult) error {
func speedTestWithDialer(ctx context.Context, dialer func(ctx context.Context, network string, address string) (net.Conn, error), res *SpeedTestResult, testDl, testUl bool) error {
clt := speedtest.New(speedtest.WithUserConfig(&speedtest.UserConfig{
DialContextFunc: dialer,
PingMode: speedtest.HTTP,
@ -194,15 +204,19 @@ func speedTestWithDialer(ctx context.Context, dialer func(ctx context.Context, n
go func() {
defer func() { close(done) }()
err = srv[0].DownloadTestContext(ctx)
if err != nil {
res.Error = err
return
if testDl {
err = srv[0].DownloadTestContext(ctx)
if err != nil {
res.Error = err
return
}
}
err = srv[0].UploadTestContext(ctx)
if err != nil {
res.Error = err
return
if testUl {
err = srv[0].UploadTestContext(ctx)
if err != nil {
res.Error = err
return
}
}
}()
@ -218,7 +232,7 @@ func speedTestWithDialer(ctx context.Context, dialer func(ctx context.Context, n
SpTQuerier.storeResult(res)
return nil
case <-ctx.Done():
return ctx.Err()
return E.New("test cancelled")
case <-ticker.C:
res.DlSpeed = speedtest.ByteRate(srv[0].Context.GetEWMADownloadRate()).String()
res.UlSpeed = speedtest.ByteRate(srv[0].Context.GetEWMAUploadRate()).String()

View File

@ -34,6 +34,8 @@ namespace NekoGui {
int id = -1;
int gid = 0;
int latency = 0;
QString dl_speed;
QString ul_speed;
std::shared_ptr<NekoGui_fmt::AbstractBean> bean;
std::shared_ptr<NekoGui_traffic::TrafficData> traffic_data = std::make_shared<NekoGui_traffic::TrafficData>("");
@ -41,7 +43,7 @@ namespace NekoGui {
ProxyEntity(NekoGui_fmt::AbstractBean *bean, const QString &type_);
[[nodiscard]] QString DisplayLatency() const;
[[nodiscard]] QString DisplayTestResult() const;
[[nodiscard]] QColor DisplayLatencyColor() const;

View File

@ -33,4 +33,14 @@ namespace GeoAssets {
inline QStringList GeoIPURLs = {"https://github.com/SagerNet/sing-geoip/releases/latest/download/geoip.db", "https://raw.githubusercontent.com/Chocolate4U/Iran-sing-box-rules/release/geoip.db"};
inline QStringList GeoSiteURLs = {"https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite.db", "https://raw.githubusercontent.com/Chocolate4U/Iran-sing-box-rules/release/geosite.db"};
}
namespace TestConfig
{
enum SpeedTestMode
{
FULL,
DL,
UL,
};
}
} // namespace NekoGui

View File

@ -1,5 +1,7 @@
// DO NOT INCLUDE THIS
#include "Const.hpp"
namespace NekoGui {
class Routing : public JsonStore {
@ -80,6 +82,7 @@ namespace NekoGui {
QString splitter_state = "";
bool enable_stats = true;
QString stats_tab = ""; // either connection or log
int speed_test_mode = TestConfig::FULL;
// Subscription
QString user_agent = ""; // set at main.cpp

View File

@ -219,14 +219,18 @@ private:
static void setup_grpc();
void speedtest_current_group(const QList<std::shared_ptr<NekoGui::ProxyEntity>>& profiles);
void urltest_current_group(const QList<std::shared_ptr<NekoGui::ProxyEntity>>& profiles);
void stopSpeedTests();
void stopTests();
void RunSpeedTest(const QString& config, bool useDefault, const QStringList& outboundTags, const QMap<QString, int>& tag2entID, int entID = -1);
void runURLTest(const QString& config, bool useDefault, const QStringList& outboundTags, const QMap<QString, int>& tag2entID, int entID = -1);
void url_test_current();
void speedtest_current_group(const QList<std::shared_ptr<NekoGui::ProxyEntity>>& profiles);
void runSpeedTest(const QString& config, bool useDefault, const QStringList& outboundTags, const QMap<QString, int>& tag2entID, int entID = -1);
static void stop_core_daemon();
bool set_system_dns(bool set, bool save_set = true);

View File

@ -602,6 +602,8 @@
<addaction name="separator"/>
<addaction name="actionUrl_Test_Selected"/>
<addaction name="menu_resolve_selected"/>
<addaction name="actionSpeedtest_Current"/>
<addaction name="actionSpeedtest_Selected"/>
</widget>
<widget class="QMenu" name="menuHidden_menu">
<property name="title">
@ -614,6 +616,7 @@
<addaction name="menu_remove_unavailable"/>
<addaction name="menu_clear_test_result"/>
<addaction name="menu_delete_repeat"/>
<addaction name="actionSpeedtest_Group"/>
</widget>
<widget class="QMenu" name="menuRouting_Menu">
<property name="title">
@ -1006,6 +1009,21 @@
<string>Open Manager</string>
</property>
</action>
<action name="actionSpeedtest_Current">
<property name="text">
<string>Speedtest Current</string>
</property>
</action>
<action name="actionSpeedtest_Selected">
<property name="text">
<string>Speedtest Selected</string>
</property>
</action>
<action name="actionSpeedtest_Group">
<property name="text">
<string>Speedtest Group</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>

View File

@ -179,6 +179,44 @@
</layout>
</widget>
</item>
<item>
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::Shape::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Shadow::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label_12">
<property name="text">
<string>Speedtest mode</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="speedtest_mode">
<item>
<property name="text">
<string>Download + Upload</string>
</property>
</item>
<item>
<property name="text">
<string>Only Download</string>
</property>
</item>
<item>
<property name="text">
<string>Only Upload</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="horizontalWidget_1" native="true">
<property name="sizePolicy">

View File

@ -249,6 +249,8 @@ namespace NekoGui {
_add(new configItem("id", &id, itemType::integer));
_add(new configItem("gid", &gid, itemType::integer));
_add(new configItem("yc", &latency, itemType::integer));
_add(new configItem("dl", &dl_speed, itemType::string));
_add(new configItem("ul", &ul_speed, itemType::string));
_add(new configItem("report", &full_test_report, itemType::string));
// 可以不关联 bean只加载 ProxyEntity 的信息
@ -260,14 +262,16 @@ namespace NekoGui {
}
};
QString ProxyEntity::DisplayLatency() const {
QString ProxyEntity::DisplayTestResult() const {
QString result;
if (latency < 0) {
return QObject::tr("Unavailable");
result = "Unavailable";
} else if (latency > 0) {
return UNICODE_LRO + QString("%1 ms").arg(latency);
} else {
return "";
result = UNICODE_LRO + QString("%1 ms").arg(latency);
}
if (!dl_speed.isEmpty()) result += "" + dl_speed;
if (!ul_speed.isEmpty()) result += "" + ul_speed;
return result;
}
QColor ProxyEntity::DisplayLatencyColor() const {

View File

@ -308,6 +308,7 @@ namespace NekoGui {
_add(new configItem("proxy_scheme", &proxy_scheme, itemType::string));
_add(new configItem("disable_privilege_req", &disable_privilege_req, itemType::boolean));
_add(new configItem("enable_tun_routing", &enable_tun_routing, itemType::boolean));
_add(new configItem("speed_test_mode", &speed_test_mode, itemType::integer));
}
void DataStore::UpdateStartedId(int id) {

View File

@ -421,6 +421,24 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
#endif
connect(ui->menu_server, &QMenu::aboutToShow, this, [=](){
if (running)
{
ui->actionSpeedtest_Current->setEnabled(true);
} else
{
ui->actionSpeedtest_Current->setEnabled(false);
}
if (auto selected = get_now_selected_list(); selected.empty())
{
ui->actionSpeedtest_Selected->setEnabled(false);
ui->actionUrl_Test_Selected->setEnabled(false);
ui->menu_resolve_selected->setEnabled(false);
} else
{
ui->actionSpeedtest_Selected->setEnabled(true);
ui->actionUrl_Test_Selected->setEnabled(true);
ui->menu_resolve_selected->setEnabled(true);
}
if (!speedtestRunning.tryLock()) {
ui->menu_server->addAction(ui->menu_stop_testing);
} else {
@ -452,12 +470,27 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
}
});
connect(ui->actionUrl_Test_Selected, &QAction::triggered, this, [=]() {
speedtest_current_group(get_now_selected_list());
urltest_current_group(get_now_selected_list());
});
connect(ui->actionUrl_Test_Group, &QAction::triggered, this, [=]() {
urltest_current_group(NekoGui::profileManager->CurrentGroup()->Profiles());
});
connect(ui->actionSpeedtest_Current, &QAction::triggered, this, [=]()
{
if (running != nullptr)
{
speedtest_current_group({running});
}
});
connect(ui->actionSpeedtest_Selected, &QAction::triggered, this, [=]()
{
speedtest_current_group(get_now_selected_list());
});
connect(ui->actionSpeedtest_Group, &QAction::triggered, this, [=]()
{
speedtest_current_group(NekoGui::profileManager->CurrentGroup()->Profiles());
});
connect(ui->menu_stop_testing, &QAction::triggered, this, [=]() { stopSpeedTests(); });
connect(ui->menu_stop_testing, &QAction::triggered, this, [=]() { stopTests(); });
//
auto set_selected_or_group = [=](int mode) {
// 0=group 1=select 2=unknown(menu is hide)
@ -561,7 +594,6 @@ void MainWindow::show_group(int gid) {
ui->tabWidget->widget(groupId2TabIndex(gid))->layout()->addWidget(ui->proxyListTable);
// 列宽是否可调
if (group->manually_column_width) {
for (int i = 0; i <= 4; i++) {
ui->proxyListTable->horizontalHeader()->setSectionResizeMode(i, QHeaderView::Interactive);
@ -1376,7 +1408,7 @@ void MainWindow::refresh_table_item(const int row, const std::shared_ptr<NekoGui
if (profile->full_test_report.isEmpty()) {
auto color = profile->DisplayLatencyColor();
if (color.isValid()) f->setForeground(color);
f->setText(profile->DisplayLatency());
f->setText(profile->DisplayTestResult());
} else {
f->setText(profile->full_test_report);
}
@ -1726,6 +1758,8 @@ void MainWindow::on_menu_scan_qr_triggered() {
void MainWindow::on_menu_clear_test_result_triggered() {
for (const auto &profile: get_selected_or_group()) {
profile->latency = 0;
profile->dl_speed.clear();
profile->ul_speed.clear();
profile->full_test_report = "";
profile->Save();
}
@ -2056,6 +2090,7 @@ void MainWindow::on_tabWidget_customContextMenuRequested(const QPoint &p) {
if (NekoGui::profileManager->groups.size() > 1) menu->addAction(deleteAction);
if (!group->Profiles().empty()) {
menu->addAction(ui->actionUrl_Test_Group);
menu->addAction(ui->actionSpeedtest_Group);
menu->addAction(ui->menu_resolve_domain);
menu->addAction(ui->menu_clear_test_result);
menu->addAction(ui->menu_delete_repeat);

View File

@ -29,7 +29,7 @@ void MainWindow::setup_grpc() {
runOnNewThread([=] {NekoGui_traffic::connection_lister->Loop(); });
}
void MainWindow::RunSpeedTest(const QString& config, bool useDefault, const QStringList& outboundTags, const QMap<QString, int>& tag2entID, int entID) {
void MainWindow::runURLTest(const QString& config, bool useDefault, const QStringList& outboundTags, const QMap<QString, int>& tag2entID, int entID) {
if (stopSpeedtest.load()) {
MW_show_log(tr("Profile test aborted"));
return;
@ -78,12 +78,12 @@ void MainWindow::RunSpeedTest(const QString& config, bool useDefault, const QStr
}
}
void MainWindow::speedtest_current_group(const QList<std::shared_ptr<NekoGui::ProxyEntity>>& profiles) {
void MainWindow::urltest_current_group(const QList<std::shared_ptr<NekoGui::ProxyEntity>>& profiles) {
if (profiles.isEmpty()) {
return;
}
if (!speedtestRunning.tryLock()) {
MessageBoxWarning(software_name, tr("The last speed test did not exit completely, please wait. If it persists, please restart the program."));
MessageBoxWarning(software_name, tr("The last url test did not exit completely, please wait. If it persists, please restart the program."));
return;
}
@ -101,7 +101,7 @@ void MainWindow::speedtest_current_group(const QList<std::shared_ptr<NekoGui::Pr
for (const auto &entID: buildObject->fullConfigs.keys()) {
auto configStr = buildObject->fullConfigs[entID];
auto func = [this, &counter, testCount, configStr, entID]() {
MainWindow::RunSpeedTest(configStr, true, {}, {}, entID);
MainWindow::runURLTest(configStr, true, {}, {}, entID);
counter++;
if (counter.load() == testCount) {
speedtestRunning.unlock();
@ -112,7 +112,7 @@ void MainWindow::speedtest_current_group(const QList<std::shared_ptr<NekoGui::Pr
if (!buildObject->outboundTags.empty()) {
auto func = [this, &buildObject, &counter, testCount]() {
MainWindow::RunSpeedTest(QJsonObject2QString(buildObject->coreConfig, false), false, buildObject->outboundTags, buildObject->tag2entID);
MainWindow::runURLTest(QJsonObject2QString(buildObject->coreConfig, false), false, buildObject->outboundTags, buildObject->tag2entID);
counter++;
if (counter.load() == testCount) {
speedtestRunning.unlock();
@ -126,12 +126,12 @@ void MainWindow::speedtest_current_group(const QList<std::shared_ptr<NekoGui::Pr
speedtestRunning.unlock();
runOnUiThread([=]{
refresh_proxy_list();
MW_show_log(tr("Speedtest finished!"));
MW_show_log(tr("URL test finished!"));
});
});
}
void MainWindow::stopSpeedTests() {
void MainWindow::stopTests() {
stopSpeedtest.store(true);
bool ok;
defaultClient->StopTests(&ok);
@ -170,6 +170,96 @@ void MainWindow::url_test_current() {
});
}
void MainWindow::speedtest_current_group(const QList<std::shared_ptr<NekoGui::ProxyEntity>>& profiles)
{
if (profiles.isEmpty()) {
return;
}
if (!speedtestRunning.tryLock()) {
MessageBoxWarning(software_name, tr("The last speed test did not exit completely, please wait. If it persists, please restart the program."));
return;
}
runOnNewThread([this, profiles]() {
auto buildObject = NekoGui::BuildTestConfig(profiles);
if (!buildObject->error.isEmpty()) {
MW_show_log(tr("Failed to build test config: ") + buildObject->error);
speedtestRunning.unlock();
return;
}
stopSpeedtest.store(false);
for (const auto &entID: buildObject->fullConfigs.keys()) {
auto configStr = buildObject->fullConfigs[entID];
runSpeedTest(configStr, true, {}, {}, entID);
}
if (!buildObject->outboundTags.empty()) {
runSpeedTest(QJsonObject2QString(buildObject->coreConfig, false), false, buildObject->outboundTags, buildObject->tag2entID);
}
speedtestRunning.unlock();
runOnUiThread([=]{
refresh_proxy_list();
MW_show_log(tr("Speedtest finished!"));
});
});
}
void MainWindow::runSpeedTest(const QString& config, bool useDefault, const QStringList& outboundTags, const QMap<QString, int>& tag2entID, int entID)
{
if (stopSpeedtest.load()) {
MW_show_log(tr("Profile speed test aborted"));
return;
}
libcore::SpeedTestRequest req;
auto speedtestConf = NekoGui::dataStore->speed_test_mode;
for (const auto &item: outboundTags) {
req.add_outbound_tags(item.toStdString());
}
req.set_config(config.toStdString());
req.set_use_default_outbound(useDefault);
req.set_test_download(speedtestConf == NekoGui::TestConfig::FULL || speedtestConf == NekoGui::TestConfig::DL);
req.set_test_upload(speedtestConf == NekoGui::TestConfig::FULL || speedtestConf == NekoGui::TestConfig::UL);
bool rpcOK;
auto result = defaultClient->SpeedTest(&rpcOK, req);
//
if (!rpcOK) return;
for (const auto &res: result.results()) {
if (!tag2entID.empty()) {
entID = tag2entID.count(QString(res.outbound_tag().c_str())) == 0 ? -1 : tag2entID[QString(res.outbound_tag().c_str())];
}
if (entID == -1) {
MW_show_log(tr("Something is very wrong, the subject ent cannot be found!"));
continue;
}
auto ent = NekoGui::profileManager->GetProfile(entID);
if (ent == nullptr) {
MW_show_log(tr("Profile manager data is corrupted, try again."));
continue;
}
if (res.error().empty()) {
ent->dl_speed = res.dl_speed().c_str();
ent->ul_speed = res.ul_speed().c_str();
if (ent->latency <= 0 && res.latency() > 0) ent->latency = res.latency();
} else {
if (QString(res.error().c_str()).contains("test aborted") ||
QString(res.error().c_str()).contains("context canceled")) ent->dl_speed = "", ent->ul_speed = "";
else {
ent->dl_speed = "N/A";
ent->ul_speed = "N/A";
MW_show_log(tr("[%1] speed test error: %2").arg(ent->bean->DisplayTypeAndName(), res.error().c_str()));
}
}
ent->Save();
}
}
void MainWindow::stop_core_daemon() {
NekoGui_rpc::defaultClient->Exit();
}

View File

@ -23,7 +23,7 @@ void ProxyItem::refresh_data() {
ui->name->setText(ent->bean->DisplayName());
ui->address->setText(ent->bean->DisplayAddress());
ui->traffic->setText(ent->traffic_data->DisplayTraffic());
ui->test_result->setText(ent->DisplayLatency());
ui->test_result->setText(ent->DisplayTestResult());
runOnUiThread(
[=] {

View File

@ -33,6 +33,7 @@ DialogBasicSettings::DialogBasicSettings(QWidget *parent)
D_LOAD_INT(inbound_socks_port)
D_LOAD_INT(test_concurrent)
D_LOAD_STRING(test_latency_url)
ui->speedtest_mode->setCurrentIndex(NekoGui::dataStore->speed_test_mode);
connect(ui->custom_inbound_edit, &QPushButton::clicked, this, [=] {
C_EDIT_JSON_ALLOW_EMPTY(custom_inbound)
@ -167,6 +168,7 @@ void DialogBasicSettings::accept() {
D_SAVE_INT(test_concurrent)
D_SAVE_STRING(test_latency_url)
NekoGui::dataStore->proxy_scheme = ui->proxy_scheme->currentText().toLower();
NekoGui::dataStore->speed_test_mode = ui->speedtest_mode->currentIndex();
// Style