From 557243af1ca05a449f5289b9ad12240de2d9b86d Mon Sep 17 00:00:00 2001 From: Kaien Date: Wed, 15 Apr 2026 15:05:14 +0300 Subject: [PATCH] Add PingHosts RPC with async multi-host ping support Co-Authored-By: Claude Sonnet 4.6 --- proto/agent.pb.go | 230 +++++++++++++++++++++++++++++++++++++---- proto/agent.proto | 18 ++++ proto/agent_grpc.pb.go | 38 +++++++ 3 files changed, 267 insertions(+), 19 deletions(-) diff --git a/proto/agent.pb.go b/proto/agent.pb.go index 815a8d9..6b08706 100644 --- a/proto/agent.pb.go +++ b/proto/agent.pb.go @@ -529,6 +529,178 @@ func (x *GetServerStatusResponse) GetPeersCount() int32 { return 0 } +type PingHostsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Hosts []string `protobuf:"bytes,1,rep,name=hosts,proto3" json:"hosts,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PingHostsRequest) Reset() { + *x = PingHostsRequest{} + mi := &file_proto_agent_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PingHostsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PingHostsRequest) ProtoMessage() {} + +func (x *PingHostsRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_agent_proto_msgTypes[9] + 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 PingHostsRequest.ProtoReflect.Descriptor instead. +func (*PingHostsRequest) Descriptor() ([]byte, []int) { + return file_proto_agent_proto_rawDescGZIP(), []int{9} +} + +func (x *PingHostsRequest) GetHosts() []string { + if x != nil { + return x.Hosts + } + return nil +} + +type PingResult struct { + state protoimpl.MessageState `protogen:"open.v1"` + Host string `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"` + LatencyMs float64 `protobuf:"fixed64,2,opt,name=latency_ms,json=latencyMs,proto3" json:"latency_ms,omitempty"` // -1 if unreachable + Reachable bool `protobuf:"varint,3,opt,name=reachable,proto3" json:"reachable,omitempty"` + Error string `protobuf:"bytes,4,opt,name=error,proto3" json:"error,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PingResult) Reset() { + *x = PingResult{} + mi := &file_proto_agent_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PingResult) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PingResult) ProtoMessage() {} + +func (x *PingResult) ProtoReflect() protoreflect.Message { + mi := &file_proto_agent_proto_msgTypes[10] + 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 PingResult.ProtoReflect.Descriptor instead. +func (*PingResult) Descriptor() ([]byte, []int) { + return file_proto_agent_proto_rawDescGZIP(), []int{10} +} + +func (x *PingResult) GetHost() string { + if x != nil { + return x.Host + } + return "" +} + +func (x *PingResult) GetLatencyMs() float64 { + if x != nil { + return x.LatencyMs + } + return 0 +} + +func (x *PingResult) GetReachable() bool { + if x != nil { + return x.Reachable + } + return false +} + +func (x *PingResult) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +type PingHostsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` + Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"` + Results []*PingResult `protobuf:"bytes,3,rep,name=results,proto3" json:"results,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PingHostsResponse) Reset() { + *x = PingHostsResponse{} + mi := &file_proto_agent_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PingHostsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PingHostsResponse) ProtoMessage() {} + +func (x *PingHostsResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_agent_proto_msgTypes[11] + 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 PingHostsResponse.ProtoReflect.Descriptor instead. +func (*PingHostsResponse) Descriptor() ([]byte, []int) { + return file_proto_agent_proto_rawDescGZIP(), []int{11} +} + +func (x *PingHostsResponse) GetSuccess() bool { + if x != nil { + return x.Success + } + return false +} + +func (x *PingHostsResponse) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +func (x *PingHostsResponse) GetResults() []*PingResult { + if x != nil { + return x.Results + } + return nil +} + var File_proto_agent_proto protoreflect.FileDescriptor const file_proto_agent_proto_rawDesc = "" + @@ -573,12 +745,26 @@ const file_proto_agent_proto_rawDesc = "" + "public_key\x18\x04 \x01(\tR\tpublicKey\x12%\n" + "\x0elistening_port\x18\x05 \x01(\tR\rlisteningPort\x12\x1f\n" + "\vpeers_count\x18\x06 \x01(\x05R\n" + - "peersCount2\xdb\x02\n" + + "peersCount\"(\n" + + "\x10PingHostsRequest\x12\x14\n" + + "\x05hosts\x18\x01 \x03(\tR\x05hosts\"s\n" + + "\n" + + "PingResult\x12\x12\n" + + "\x04host\x18\x01 \x01(\tR\x04host\x12\x1d\n" + + "\n" + + "latency_ms\x18\x02 \x01(\x01R\tlatencyMs\x12\x1c\n" + + "\treachable\x18\x03 \x01(\bR\treachable\x12\x14\n" + + "\x05error\x18\x04 \x01(\tR\x05error\"p\n" + + "\x11PingHostsResponse\x12\x18\n" + + "\asuccess\x18\x01 \x01(\bR\asuccess\x12\x14\n" + + "\x05error\x18\x02 \x01(\tR\x05error\x12+\n" + + "\aresults\x18\x03 \x03(\v2\x11.agent.PingResultR\aresults2\x9b\x03\n" + "\x0eWireGuardAgent\x12S\n" + "\x10GetClientConfigs\x12\x1e.agent.GetClientConfigsRequest\x1a\x1f.agent.GetClientConfigsResponse\x12M\n" + "\x0eRegisterClient\x12\x1c.agent.RegisterClientRequest\x1a\x1d.agent.RegisterClientResponse\x12S\n" + "\x10UnregisterClient\x12\x1e.agent.UnregisterClientRequest\x1a\x1f.agent.UnregisterClientResponse\x12P\n" + - "\x0fGetServerStatus\x12\x1d.agent.GetServerStatusRequest\x1a\x1e.agent.GetServerStatusResponseB\x13Z\x11l_vpn_proto/protob\x06proto3" + "\x0fGetServerStatus\x12\x1d.agent.GetServerStatusRequest\x1a\x1e.agent.GetServerStatusResponse\x12>\n" + + "\tPingHosts\x12\x17.agent.PingHostsRequest\x1a\x18.agent.PingHostsResponseB\x13Z\x11l_vpn_proto/protob\x06proto3" var ( file_proto_agent_proto_rawDescOnce sync.Once @@ -592,7 +778,7 @@ func file_proto_agent_proto_rawDescGZIP() []byte { return file_proto_agent_proto_rawDescData } -var file_proto_agent_proto_msgTypes = make([]protoimpl.MessageInfo, 9) +var file_proto_agent_proto_msgTypes = make([]protoimpl.MessageInfo, 12) var file_proto_agent_proto_goTypes = []any{ (*GetClientConfigsRequest)(nil), // 0: agent.GetClientConfigsRequest (*ClientConfig)(nil), // 1: agent.ClientConfig @@ -603,23 +789,29 @@ var file_proto_agent_proto_goTypes = []any{ (*UnregisterClientResponse)(nil), // 6: agent.UnregisterClientResponse (*GetServerStatusRequest)(nil), // 7: agent.GetServerStatusRequest (*GetServerStatusResponse)(nil), // 8: agent.GetServerStatusResponse + (*PingHostsRequest)(nil), // 9: agent.PingHostsRequest + (*PingResult)(nil), // 10: agent.PingResult + (*PingHostsResponse)(nil), // 11: agent.PingHostsResponse } var file_proto_agent_proto_depIdxs = []int32{ - 1, // 0: agent.GetClientConfigsResponse.clients:type_name -> agent.ClientConfig - 1, // 1: agent.RegisterClientResponse.client:type_name -> agent.ClientConfig - 0, // 2: agent.WireGuardAgent.GetClientConfigs:input_type -> agent.GetClientConfigsRequest - 3, // 3: agent.WireGuardAgent.RegisterClient:input_type -> agent.RegisterClientRequest - 5, // 4: agent.WireGuardAgent.UnregisterClient:input_type -> agent.UnregisterClientRequest - 7, // 5: agent.WireGuardAgent.GetServerStatus:input_type -> agent.GetServerStatusRequest - 2, // 6: agent.WireGuardAgent.GetClientConfigs:output_type -> agent.GetClientConfigsResponse - 4, // 7: agent.WireGuardAgent.RegisterClient:output_type -> agent.RegisterClientResponse - 6, // 8: agent.WireGuardAgent.UnregisterClient:output_type -> agent.UnregisterClientResponse - 8, // 9: agent.WireGuardAgent.GetServerStatus:output_type -> agent.GetServerStatusResponse - 6, // [6:10] is the sub-list for method output_type - 2, // [2:6] is the sub-list for method input_type - 2, // [2:2] is the sub-list for extension type_name - 2, // [2:2] is the sub-list for extension extendee - 0, // [0:2] is the sub-list for field type_name + 1, // 0: agent.GetClientConfigsResponse.clients:type_name -> agent.ClientConfig + 1, // 1: agent.RegisterClientResponse.client:type_name -> agent.ClientConfig + 10, // 2: agent.PingHostsResponse.results:type_name -> agent.PingResult + 0, // 3: agent.WireGuardAgent.GetClientConfigs:input_type -> agent.GetClientConfigsRequest + 3, // 4: agent.WireGuardAgent.RegisterClient:input_type -> agent.RegisterClientRequest + 5, // 5: agent.WireGuardAgent.UnregisterClient:input_type -> agent.UnregisterClientRequest + 7, // 6: agent.WireGuardAgent.GetServerStatus:input_type -> agent.GetServerStatusRequest + 9, // 7: agent.WireGuardAgent.PingHosts:input_type -> agent.PingHostsRequest + 2, // 8: agent.WireGuardAgent.GetClientConfigs:output_type -> agent.GetClientConfigsResponse + 4, // 9: agent.WireGuardAgent.RegisterClient:output_type -> agent.RegisterClientResponse + 6, // 10: agent.WireGuardAgent.UnregisterClient:output_type -> agent.UnregisterClientResponse + 8, // 11: agent.WireGuardAgent.GetServerStatus:output_type -> agent.GetServerStatusResponse + 11, // 12: agent.WireGuardAgent.PingHosts:output_type -> agent.PingHostsResponse + 8, // [8:13] is the sub-list for method output_type + 3, // [3:8] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] is the sub-list for field type_name } func init() { file_proto_agent_proto_init() } @@ -633,7 +825,7 @@ func file_proto_agent_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_agent_proto_rawDesc), len(file_proto_agent_proto_rawDesc)), NumEnums: 0, - NumMessages: 9, + NumMessages: 12, NumExtensions: 0, NumServices: 1, }, diff --git a/proto/agent.proto b/proto/agent.proto index 8a7c09b..b720403 100644 --- a/proto/agent.proto +++ b/proto/agent.proto @@ -9,6 +9,7 @@ service WireGuardAgent { rpc RegisterClient(RegisterClientRequest) returns (RegisterClientResponse); rpc UnregisterClient(UnregisterClientRequest) returns (UnregisterClientResponse); rpc GetServerStatus(GetServerStatusRequest) returns (GetServerStatusResponse); + rpc PingHosts(PingHostsRequest) returns (PingHostsResponse); } message GetClientConfigsRequest {} @@ -58,3 +59,20 @@ message GetServerStatusResponse { string listening_port = 5; int32 peers_count = 6; } + +message PingHostsRequest { + repeated string hosts = 1; +} + +message PingResult { + string host = 1; + double latency_ms = 2; // -1 if unreachable + bool reachable = 3; + string error = 4; +} + +message PingHostsResponse { + bool success = 1; + string error = 2; + repeated PingResult results = 3; +} diff --git a/proto/agent_grpc.pb.go b/proto/agent_grpc.pb.go index 1999e4f..c348b85 100644 --- a/proto/agent_grpc.pb.go +++ b/proto/agent_grpc.pb.go @@ -23,6 +23,7 @@ const ( WireGuardAgent_RegisterClient_FullMethodName = "/agent.WireGuardAgent/RegisterClient" WireGuardAgent_UnregisterClient_FullMethodName = "/agent.WireGuardAgent/UnregisterClient" WireGuardAgent_GetServerStatus_FullMethodName = "/agent.WireGuardAgent/GetServerStatus" + WireGuardAgent_PingHosts_FullMethodName = "/agent.WireGuardAgent/PingHosts" ) // WireGuardAgentClient is the client API for WireGuardAgent service. @@ -33,6 +34,7 @@ type WireGuardAgentClient interface { RegisterClient(ctx context.Context, in *RegisterClientRequest, opts ...grpc.CallOption) (*RegisterClientResponse, error) UnregisterClient(ctx context.Context, in *UnregisterClientRequest, opts ...grpc.CallOption) (*UnregisterClientResponse, error) GetServerStatus(ctx context.Context, in *GetServerStatusRequest, opts ...grpc.CallOption) (*GetServerStatusResponse, error) + PingHosts(ctx context.Context, in *PingHostsRequest, opts ...grpc.CallOption) (*PingHostsResponse, error) } type wireGuardAgentClient struct { @@ -83,6 +85,16 @@ func (c *wireGuardAgentClient) GetServerStatus(ctx context.Context, in *GetServe return out, nil } +func (c *wireGuardAgentClient) PingHosts(ctx context.Context, in *PingHostsRequest, opts ...grpc.CallOption) (*PingHostsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(PingHostsResponse) + err := c.cc.Invoke(ctx, WireGuardAgent_PingHosts_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // WireGuardAgentServer is the server API for WireGuardAgent service. // All implementations must embed UnimplementedWireGuardAgentServer // for forward compatibility. @@ -91,6 +103,7 @@ type WireGuardAgentServer interface { RegisterClient(context.Context, *RegisterClientRequest) (*RegisterClientResponse, error) UnregisterClient(context.Context, *UnregisterClientRequest) (*UnregisterClientResponse, error) GetServerStatus(context.Context, *GetServerStatusRequest) (*GetServerStatusResponse, error) + PingHosts(context.Context, *PingHostsRequest) (*PingHostsResponse, error) mustEmbedUnimplementedWireGuardAgentServer() } @@ -113,6 +126,9 @@ func (UnimplementedWireGuardAgentServer) UnregisterClient(context.Context, *Unre func (UnimplementedWireGuardAgentServer) GetServerStatus(context.Context, *GetServerStatusRequest) (*GetServerStatusResponse, error) { return nil, status.Error(codes.Unimplemented, "method GetServerStatus not implemented") } +func (UnimplementedWireGuardAgentServer) PingHosts(context.Context, *PingHostsRequest) (*PingHostsResponse, error) { + return nil, status.Error(codes.Unimplemented, "method PingHosts not implemented") +} func (UnimplementedWireGuardAgentServer) mustEmbedUnimplementedWireGuardAgentServer() {} func (UnimplementedWireGuardAgentServer) testEmbeddedByValue() {} @@ -206,6 +222,24 @@ func _WireGuardAgent_GetServerStatus_Handler(srv interface{}, ctx context.Contex return interceptor(ctx, in, info, handler) } +func _WireGuardAgent_PingHosts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PingHostsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WireGuardAgentServer).PingHosts(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: WireGuardAgent_PingHosts_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WireGuardAgentServer).PingHosts(ctx, req.(*PingHostsRequest)) + } + return interceptor(ctx, in, info, handler) +} + // WireGuardAgent_ServiceDesc is the grpc.ServiceDesc for WireGuardAgent service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -229,6 +263,10 @@ var WireGuardAgent_ServiceDesc = grpc.ServiceDesc{ MethodName: "GetServerStatus", Handler: _WireGuardAgent_GetServerStatus_Handler, }, + { + MethodName: "PingHosts", + Handler: _WireGuardAgent_PingHosts_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "proto/agent.proto",