From 438de9f02884a7c56d492775ba5cfdfeadb93fb0 Mon Sep 17 00:00:00 2001 From: Songpon Srisawai Date: Sat, 5 Jul 2025 20:48:34 +0700 Subject: [PATCH] Support get IPs for both guestagent and plain mode Signed-off-by: Songpon Srisawai --- pkg/guestagent/api/client/client.go | 8 + pkg/guestagent/api/guestservice.pb.desc | 13 +- pkg/guestagent/api/guestservice.pb.go | 208 ++++++++++++++------- pkg/guestagent/api/guestservice.proto | 7 + pkg/guestagent/api/guestservice_grpc.pb.go | 74 +++++++- pkg/guestagent/api/server/server.go | 8 + pkg/guestagent/guestagent.go | 6 + pkg/guestagent/guestagent_linux.go | 77 ++++++++ pkg/hostagent/api/api.go | 4 +- pkg/hostagent/hostagent.go | 186 +++++++++++++++++- pkg/store/instance.go | 4 + 11 files changed, 517 insertions(+), 78 deletions(-) diff --git a/pkg/guestagent/api/client/client.go b/pkg/guestagent/api/client/client.go index 544123a5fb9..fa08344d165 100644 --- a/pkg/guestagent/api/client/client.go +++ b/pkg/guestagent/api/client/client.go @@ -76,3 +76,11 @@ func (c *GuestAgentClient) Tunnel(ctx context.Context) (api.GuestService_TunnelC } return stream, nil } + +func (c *GuestAgentClient) IPv4(ctx context.Context) (*api.CommandOutput, error) { + return c.cli.GetIPv4(ctx, &emptypb.Empty{}) +} + +func (c *GuestAgentClient) IPv6(ctx context.Context) (*api.CommandOutput, error) { + return c.cli.GetIPv6(ctx, &emptypb.Empty{}) +} diff --git a/pkg/guestagent/api/guestservice.pb.desc b/pkg/guestagent/api/guestservice.pb.desc index 2155145e47d..1f6786083df 100644 --- a/pkg/guestagent/api/guestservice.pb.desc +++ b/pkg/guestagent/api/guestservice.pb.desc @@ -1,5 +1,5 @@ -ô +ž guestservice.protogoogle/protobuf/empty.protogoogle/protobuf/timestamp.proto"0 Info( local_ports ( 2.IPPortR @@ -12,7 +12,10 @@ localPorts" IPPort protocol ( Rprotocol ip ( Rip -port (Rport"X +port (Rport"B + CommandOutput +is_json (RisJson +payload ( Rpayload"X Inotify mount_path ( R mountPath. @@ -22,9 +25,11 @@ mount_path ( R mountPath. protocol ( Rprotocol data ( Rdata guestAddr ( R guestAddr$ - udpTargetAddr ( R udpTargetAddr2È + udpTargetAddr ( R udpTargetAddr2® GuestService( GetInfo.google.protobuf.Empty.Info- GetEvents.google.protobuf.Empty.Event01 PostInotify.Inotify.google.protobuf.Empty(, -Tunnel.TunnelMessage.TunnelMessage(0B!Zgithub.com/lima-vm/lima/pkg/apibproto3 \ No newline at end of file +Tunnel.TunnelMessage.TunnelMessage(01 +GetIPv4.google.protobuf.Empty.CommandOutput1 +GetIPv6.google.protobuf.Empty.CommandOutputB!Zgithub.com/lima-vm/lima/pkg/apibproto3 \ No newline at end of file diff --git a/pkg/guestagent/api/guestservice.pb.go b/pkg/guestagent/api/guestservice.pb.go index 27bb5983dee..08f2a964c61 100644 --- a/pkg/guestagent/api/guestservice.pb.go +++ b/pkg/guestagent/api/guestservice.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.28.1 -// protoc v5.27.1 +// protoc v6.30.2 // source: guestservice.proto package api @@ -203,6 +203,61 @@ func (x *IPPort) GetPort() int32 { return 0 } +type CommandOutput struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + IsJson bool `protobuf:"varint,1,opt,name=is_json,json=isJson,proto3" json:"is_json,omitempty"` + Payload []byte `protobuf:"bytes,2,opt,name=payload,proto3" json:"payload,omitempty"` +} + +func (x *CommandOutput) Reset() { + *x = CommandOutput{} + if protoimpl.UnsafeEnabled { + mi := &file_guestservice_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CommandOutput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CommandOutput) ProtoMessage() {} + +func (x *CommandOutput) ProtoReflect() protoreflect.Message { + mi := &file_guestservice_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CommandOutput.ProtoReflect.Descriptor instead. +func (*CommandOutput) Descriptor() ([]byte, []int) { + return file_guestservice_proto_rawDescGZIP(), []int{3} +} + +func (x *CommandOutput) GetIsJson() bool { + if x != nil { + return x.IsJson + } + return false +} + +func (x *CommandOutput) GetPayload() []byte { + if x != nil { + return x.Payload + } + return nil +} + type Inotify struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -215,7 +270,7 @@ type Inotify struct { func (x *Inotify) Reset() { *x = Inotify{} if protoimpl.UnsafeEnabled { - mi := &file_guestservice_proto_msgTypes[3] + mi := &file_guestservice_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -228,7 +283,7 @@ func (x *Inotify) String() string { func (*Inotify) ProtoMessage() {} func (x *Inotify) ProtoReflect() protoreflect.Message { - mi := &file_guestservice_proto_msgTypes[3] + mi := &file_guestservice_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -241,7 +296,7 @@ func (x *Inotify) ProtoReflect() protoreflect.Message { // Deprecated: Use Inotify.ProtoReflect.Descriptor instead. func (*Inotify) Descriptor() ([]byte, []int) { - return file_guestservice_proto_rawDescGZIP(), []int{3} + return file_guestservice_proto_rawDescGZIP(), []int{4} } func (x *Inotify) GetMountPath() string { @@ -273,7 +328,7 @@ type TunnelMessage struct { func (x *TunnelMessage) Reset() { *x = TunnelMessage{} if protoimpl.UnsafeEnabled { - mi := &file_guestservice_proto_msgTypes[4] + mi := &file_guestservice_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -286,7 +341,7 @@ func (x *TunnelMessage) String() string { func (*TunnelMessage) ProtoMessage() {} func (x *TunnelMessage) ProtoReflect() protoreflect.Message { - mi := &file_guestservice_proto_msgTypes[4] + mi := &file_guestservice_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -299,7 +354,7 @@ func (x *TunnelMessage) ProtoReflect() protoreflect.Message { // Deprecated: Use TunnelMessage.ProtoReflect.Descriptor instead. func (*TunnelMessage) Descriptor() ([]byte, []int) { - return file_guestservice_proto_rawDescGZIP(), []int{4} + return file_guestservice_proto_rawDescGZIP(), []int{5} } func (x *TunnelMessage) GetId() string { @@ -364,38 +419,48 @@ var file_guestservice_proto_rawDesc = []byte{ 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, - 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x22, 0x58, - 0x0a, 0x07, 0x49, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x6f, 0x75, - 0x6e, 0x74, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6d, - 0x6f, 0x75, 0x6e, 0x74, 0x50, 0x61, 0x74, 0x68, 0x12, 0x2e, 0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, - 0x6d, 0x70, 0x52, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x22, 0x93, 0x01, 0x0a, 0x0d, 0x54, 0x75, 0x6e, - 0x6e, 0x65, 0x6c, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1c, 0x0a, 0x09, 0x67, 0x75, - 0x65, 0x73, 0x74, 0x41, 0x64, 0x64, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x67, - 0x75, 0x65, 0x73, 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, 0x24, 0x0a, 0x0d, 0x75, 0x64, 0x70, 0x54, - 0x61, 0x72, 0x67, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0d, 0x75, 0x64, 0x70, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x32, 0xc8, - 0x01, 0x0a, 0x0c, 0x47, 0x75, 0x65, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, - 0x28, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, + 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x22, 0x42, + 0x0a, 0x0d, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, + 0x17, 0x0a, 0x07, 0x69, 0x73, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x06, 0x69, 0x73, 0x4a, 0x73, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, + 0x6f, 0x61, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, + 0x61, 0x64, 0x22, 0x58, 0x0a, 0x07, 0x49, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x12, 0x1d, 0x0a, + 0x0a, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x09, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x50, 0x61, 0x74, 0x68, 0x12, 0x2e, 0x0a, 0x04, + 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x22, 0x93, 0x01, 0x0a, + 0x0d, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x0e, + 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1a, + 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, + 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1c, + 0x0a, 0x09, 0x67, 0x75, 0x65, 0x73, 0x74, 0x41, 0x64, 0x64, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x09, 0x67, 0x75, 0x65, 0x73, 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, 0x24, 0x0a, 0x0d, + 0x75, 0x64, 0x70, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0d, 0x75, 0x64, 0x70, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x41, 0x64, + 0x64, 0x72, 0x32, 0xae, 0x02, 0x0a, 0x0c, 0x47, 0x75, 0x65, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x12, 0x28, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x16, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x05, 0x2e, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x2d, 0x0a, + 0x09, 0x47, 0x65, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, - 0x74, 0x79, 0x1a, 0x05, 0x2e, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x2d, 0x0a, 0x09, 0x47, 0x65, 0x74, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x06, - 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x31, 0x0a, 0x0b, 0x50, 0x6f, 0x73, 0x74, - 0x49, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x12, 0x08, 0x2e, 0x49, 0x6e, 0x6f, 0x74, 0x69, 0x66, - 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x28, 0x01, 0x12, 0x2c, 0x0a, 0x06, 0x54, - 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x0e, 0x2e, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x4d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x0e, 0x2e, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x4d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, 0x21, 0x5a, 0x1f, 0x67, 0x69, 0x74, - 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x6d, 0x61, 0x2d, 0x76, 0x6d, 0x2f, - 0x6c, 0x69, 0x6d, 0x61, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x33, + 0x74, 0x79, 0x1a, 0x06, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x31, 0x0a, 0x0b, + 0x50, 0x6f, 0x73, 0x74, 0x49, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x12, 0x08, 0x2e, 0x49, 0x6e, + 0x6f, 0x74, 0x69, 0x66, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x28, 0x01, 0x12, + 0x2c, 0x0a, 0x06, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x0e, 0x2e, 0x54, 0x75, 0x6e, 0x6e, + 0x65, 0x6c, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x0e, 0x2e, 0x54, 0x75, 0x6e, 0x6e, + 0x65, 0x6c, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x28, 0x01, 0x30, 0x01, 0x12, 0x31, 0x0a, + 0x07, 0x47, 0x65, 0x74, 0x49, 0x50, 0x76, 0x34, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x1a, 0x0e, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, + 0x12, 0x31, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x49, 0x50, 0x76, 0x36, 0x12, 0x16, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, + 0x70, 0x74, 0x79, 0x1a, 0x0e, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x4f, 0x75, 0x74, + 0x70, 0x75, 0x74, 0x42, 0x21, 0x5a, 0x1f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x6c, 0x69, 0x6d, 0x61, 0x2d, 0x76, 0x6d, 0x2f, 0x6c, 0x69, 0x6d, 0x61, 0x2f, 0x70, + 0x6b, 0x67, 0x2f, 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -410,35 +475,40 @@ func file_guestservice_proto_rawDescGZIP() []byte { return file_guestservice_proto_rawDescData } -var file_guestservice_proto_msgTypes = make([]protoimpl.MessageInfo, 5) +var file_guestservice_proto_msgTypes = make([]protoimpl.MessageInfo, 6) var file_guestservice_proto_goTypes = []interface{}{ (*Info)(nil), // 0: Info (*Event)(nil), // 1: Event (*IPPort)(nil), // 2: IPPort - (*Inotify)(nil), // 3: Inotify - (*TunnelMessage)(nil), // 4: TunnelMessage - (*timestamppb.Timestamp)(nil), // 5: google.protobuf.Timestamp - (*emptypb.Empty)(nil), // 6: google.protobuf.Empty + (*CommandOutput)(nil), // 3: CommandOutput + (*Inotify)(nil), // 4: Inotify + (*TunnelMessage)(nil), // 5: TunnelMessage + (*timestamppb.Timestamp)(nil), // 6: google.protobuf.Timestamp + (*emptypb.Empty)(nil), // 7: google.protobuf.Empty } var file_guestservice_proto_depIdxs = []int32{ - 2, // 0: Info.local_ports:type_name -> IPPort - 5, // 1: Event.time:type_name -> google.protobuf.Timestamp - 2, // 2: Event.local_ports_added:type_name -> IPPort - 2, // 3: Event.local_ports_removed:type_name -> IPPort - 5, // 4: Inotify.time:type_name -> google.protobuf.Timestamp - 6, // 5: GuestService.GetInfo:input_type -> google.protobuf.Empty - 6, // 6: GuestService.GetEvents:input_type -> google.protobuf.Empty - 3, // 7: GuestService.PostInotify:input_type -> Inotify - 4, // 8: GuestService.Tunnel:input_type -> TunnelMessage - 0, // 9: GuestService.GetInfo:output_type -> Info - 1, // 10: GuestService.GetEvents:output_type -> Event - 6, // 11: GuestService.PostInotify:output_type -> google.protobuf.Empty - 4, // 12: GuestService.Tunnel:output_type -> TunnelMessage - 9, // [9:13] is the sub-list for method output_type - 5, // [5:9] is the sub-list for method input_type - 5, // [5:5] is the sub-list for extension type_name - 5, // [5:5] is the sub-list for extension extendee - 0, // [0:5] is the sub-list for field type_name + 2, // 0: Info.local_ports:type_name -> IPPort + 6, // 1: Event.time:type_name -> google.protobuf.Timestamp + 2, // 2: Event.local_ports_added:type_name -> IPPort + 2, // 3: Event.local_ports_removed:type_name -> IPPort + 6, // 4: Inotify.time:type_name -> google.protobuf.Timestamp + 7, // 5: GuestService.GetInfo:input_type -> google.protobuf.Empty + 7, // 6: GuestService.GetEvents:input_type -> google.protobuf.Empty + 4, // 7: GuestService.PostInotify:input_type -> Inotify + 5, // 8: GuestService.Tunnel:input_type -> TunnelMessage + 7, // 9: GuestService.GetIPv4:input_type -> google.protobuf.Empty + 7, // 10: GuestService.GetIPv6:input_type -> google.protobuf.Empty + 0, // 11: GuestService.GetInfo:output_type -> Info + 1, // 12: GuestService.GetEvents:output_type -> Event + 7, // 13: GuestService.PostInotify:output_type -> google.protobuf.Empty + 5, // 14: GuestService.Tunnel:output_type -> TunnelMessage + 3, // 15: GuestService.GetIPv4:output_type -> CommandOutput + 3, // 16: GuestService.GetIPv6:output_type -> CommandOutput + 11, // [11:17] is the sub-list for method output_type + 5, // [5:11] is the sub-list for method input_type + 5, // [5:5] is the sub-list for extension type_name + 5, // [5:5] is the sub-list for extension extendee + 0, // [0:5] is the sub-list for field type_name } func init() { file_guestservice_proto_init() } @@ -484,7 +554,7 @@ func file_guestservice_proto_init() { } } file_guestservice_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Inotify); i { + switch v := v.(*CommandOutput); i { case 0: return &v.state case 1: @@ -496,6 +566,18 @@ func file_guestservice_proto_init() { } } file_guestservice_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Inotify); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_guestservice_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*TunnelMessage); i { case 0: return &v.state @@ -514,7 +596,7 @@ func file_guestservice_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_guestservice_proto_rawDesc, NumEnums: 0, - NumMessages: 5, + NumMessages: 6, NumExtensions: 0, NumServices: 1, }, diff --git a/pkg/guestagent/api/guestservice.proto b/pkg/guestagent/api/guestservice.proto index 377cf053ef7..25019e38fa7 100644 --- a/pkg/guestagent/api/guestservice.proto +++ b/pkg/guestagent/api/guestservice.proto @@ -10,6 +10,8 @@ service GuestService { rpc PostInotify(stream Inotify) returns (google.protobuf.Empty); rpc Tunnel(stream TunnelMessage) returns (stream TunnelMessage); + rpc GetIPv4(google.protobuf.Empty) returns (CommandOutput); + rpc GetIPv6(google.protobuf.Empty) returns (CommandOutput); } message Info { @@ -29,6 +31,11 @@ message IPPort { int32 port = 3; } +message CommandOutput { + bool is_json = 1; + bytes payload = 2; +} + message Inotify { string mount_path = 1; google.protobuf.Timestamp time = 2; diff --git a/pkg/guestagent/api/guestservice_grpc.pb.go b/pkg/guestagent/api/guestservice_grpc.pb.go index 9b9fd1f6baa..063b832e4be 100644 --- a/pkg/guestagent/api/guestservice_grpc.pb.go +++ b/pkg/guestagent/api/guestservice_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.2.0 -// - protoc v5.27.1 +// - protoc v6.30.2 // source: guestservice.proto package api @@ -27,6 +27,8 @@ type GuestServiceClient interface { GetEvents(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (GuestService_GetEventsClient, error) PostInotify(ctx context.Context, opts ...grpc.CallOption) (GuestService_PostInotifyClient, error) Tunnel(ctx context.Context, opts ...grpc.CallOption) (GuestService_TunnelClient, error) + GetIPv4(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*CommandOutput, error) + GetIPv6(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*CommandOutput, error) } type guestServiceClient struct { @@ -143,6 +145,24 @@ func (x *guestServiceTunnelClient) Recv() (*TunnelMessage, error) { return m, nil } +func (c *guestServiceClient) GetIPv4(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*CommandOutput, error) { + out := new(CommandOutput) + err := c.cc.Invoke(ctx, "/GuestService/GetIPv4", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *guestServiceClient) GetIPv6(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*CommandOutput, error) { + out := new(CommandOutput) + err := c.cc.Invoke(ctx, "/GuestService/GetIPv6", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // GuestServiceServer is the server API for GuestService service. // All implementations must embed UnimplementedGuestServiceServer // for forward compatibility @@ -151,6 +171,8 @@ type GuestServiceServer interface { GetEvents(*emptypb.Empty, GuestService_GetEventsServer) error PostInotify(GuestService_PostInotifyServer) error Tunnel(GuestService_TunnelServer) error + GetIPv4(context.Context, *emptypb.Empty) (*CommandOutput, error) + GetIPv6(context.Context, *emptypb.Empty) (*CommandOutput, error) mustEmbedUnimplementedGuestServiceServer() } @@ -170,6 +192,12 @@ func (UnimplementedGuestServiceServer) PostInotify(GuestService_PostInotifyServe func (UnimplementedGuestServiceServer) Tunnel(GuestService_TunnelServer) error { return status.Errorf(codes.Unimplemented, "method Tunnel not implemented") } +func (UnimplementedGuestServiceServer) GetIPv4(context.Context, *emptypb.Empty) (*CommandOutput, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetIPv4 not implemented") +} +func (UnimplementedGuestServiceServer) GetIPv6(context.Context, *emptypb.Empty) (*CommandOutput, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetIPv6 not implemented") +} func (UnimplementedGuestServiceServer) mustEmbedUnimplementedGuestServiceServer() {} // UnsafeGuestServiceServer may be embedded to opt out of forward compatibility for this service. @@ -274,6 +302,42 @@ func (x *guestServiceTunnelServer) Recv() (*TunnelMessage, error) { return m, nil } +func _GuestService_GetIPv4_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GuestServiceServer).GetIPv4(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/GuestService/GetIPv4", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GuestServiceServer).GetIPv4(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _GuestService_GetIPv6_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GuestServiceServer).GetIPv6(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/GuestService/GetIPv6", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GuestServiceServer).GetIPv6(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + // GuestService_ServiceDesc is the grpc.ServiceDesc for GuestService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -285,6 +349,14 @@ var GuestService_ServiceDesc = grpc.ServiceDesc{ MethodName: "GetInfo", Handler: _GuestService_GetInfo_Handler, }, + { + MethodName: "GetIPv4", + Handler: _GuestService_GetIPv4_Handler, + }, + { + MethodName: "GetIPv6", + Handler: _GuestService_GetIPv6_Handler, + }, }, Streams: []grpc.StreamDesc{ { diff --git a/pkg/guestagent/api/server/server.go b/pkg/guestagent/api/server/server.go index b8f6e249dde..a6ebb7743f5 100644 --- a/pkg/guestagent/api/server/server.go +++ b/pkg/guestagent/api/server/server.go @@ -56,3 +56,11 @@ func (s *GuestServer) PostInotify(server api.GuestService_PostInotifyServer) err func (s *GuestServer) Tunnel(stream api.GuestService_TunnelServer) error { return s.TunnelS.Start(stream) } + +func (s *GuestServer) GetIPv4(ctx context.Context, _ *emptypb.Empty) (*api.CommandOutput, error) { + return s.Agent.IPv4(ctx) +} + +func (s *GuestServer) GetIPv6(ctx context.Context, _ *emptypb.Empty) (*api.CommandOutput, error) { + return s.Agent.IPv6(ctx) +} diff --git a/pkg/guestagent/guestagent.go b/pkg/guestagent/guestagent.go index b63f6e00b85..2d777cfa981 100644 --- a/pkg/guestagent/guestagent.go +++ b/pkg/guestagent/guestagent.go @@ -14,4 +14,10 @@ type Agent interface { Events(ctx context.Context, ch chan *api.Event) LocalPorts(ctx context.Context) ([]*api.IPPort, error) HandleInotify(event *api.Inotify) + IPv4(ctx context.Context) (*api.CommandOutput, error) + IPv6(ctx context.Context) (*api.CommandOutput, error) +} + +type RouteEntry struct { + Dev string `json:"dev"` } diff --git a/pkg/guestagent/guestagent_linux.go b/pkg/guestagent/guestagent_linux.go index 0bcd49efa49..fd3c51ceeb5 100644 --- a/pkg/guestagent/guestagent_linux.go +++ b/pkg/guestagent/guestagent_linux.go @@ -5,8 +5,10 @@ package guestagent import ( "context" + "encoding/json" "errors" "os" + "os/exec" "reflect" "sync" "syscall" @@ -186,6 +188,7 @@ func (a *agent) collectEvent(ctx context.Context, st eventState) (*api.Event, ev } ev.LocalPortsAdded, ev.LocalPortsRemoved = comparePorts(st.ports, newSt.ports) ev.Time = timestamppb.Now() + return ev, newSt } @@ -368,3 +371,77 @@ func (a *agent) HandleInotify(event *api.Inotify) { } } } + +func (a *agent) IPv4(ctx context.Context) (*api.CommandOutput, error) { + return a.getIPAddress(ctx, "4") +} + +func (a *agent) IPv6(ctx context.Context) (*api.CommandOutput, error) { + return a.getIPAddress(ctx, "6") +} + +// getIPAddress performs two steps: +// (1) Identifies the default interface using "ip route show default". +// (2) Retrieves interface details for IPv4 or IPv6. +// Returns nil if no default interface is found. +func (a *agent) getIPAddress(_ context.Context, ipVersion string) (*api.CommandOutput, error) { + // Get default interface + iface, err := getDefaultInterface(ipVersion) + if err != nil { + logrus.Errorf("Error getting default interface: %v", err) + } + + // Get Interface info + output, err := getInterfaceInfo(ipVersion, iface) + if err != nil { + logrus.Errorf("Error getting interface info for %s: %v", iface, err) + } + + return &api.CommandOutput{ + IsJson: true, + Payload: output, + }, err +} + +// getDefaultInterface fetches the default interface for IPv4 or IPv6. +func getDefaultInterface(ipVersion string) (string, error) { + args := []string{"-j", "route", "show", "default"} + if ipVersion == "6" { + args = append([]string{"-6"}, args...) + } else { + args = append([]string{"-4"}, args...) + } + + output, err := exec.Command("ip", args...).Output() + if err != nil { + return "", errors.New("failed to run `ip route show default`") + } + + var routes []RouteEntry + if err := json.Unmarshal(output, &routes); err != nil { + return "", errors.New("failed to unmarshal route JSON") + } + + // return nil if no default interface + if len(routes) == 0 || routes[0].Dev == "" { + return "", nil + } + + return routes[0].Dev, nil +} + +// getInterfaceInfo fetches the interface information for IPv4 or IPv6. +func getInterfaceInfo(ipVersion, iface string) ([]byte, error) { + // No default route found + if iface == "" { + return nil, nil + } + + args := []string{"-j", "address", "show", "dev", iface} + if ipVersion == "6" { + args = append([]string{"-6"}, args...) + } else { + args = append([]string{"-4"}, args...) + } + return exec.Command("ip", args...).Output() +} diff --git a/pkg/hostagent/api/api.go b/pkg/hostagent/api/api.go index f86de58e5d7..e9ae49a5dc3 100644 --- a/pkg/hostagent/api/api.go +++ b/pkg/hostagent/api/api.go @@ -4,5 +4,7 @@ package api type Info struct { - SSHLocalPort int `json:"sshLocalPort,omitempty"` + SSHLocalPort int `json:"sshLocalPort,omitempty"` + GuestIPv4Address string `json:"guestIPv4Address,omitempty"` + GuestIPv6Address string `json:"guestIPv6Address,omitempty"` } diff --git a/pkg/hostagent/hostagent.go b/pkg/hostagent/hostagent.go index d1528859513..b44847834cb 100644 --- a/pkg/hostagent/hostagent.go +++ b/pkg/hostagent/hostagent.go @@ -29,6 +29,7 @@ import ( "github.com/lima-vm/lima/pkg/driver" "github.com/lima-vm/lima/pkg/driverutil" "github.com/lima-vm/lima/pkg/freeport" + "github.com/lima-vm/lima/pkg/guestagent" guestagentapi "github.com/lima-vm/lima/pkg/guestagent/api" guestagentclient "github.com/lima-vm/lima/pkg/guestagent/api/client" hostagentapi "github.com/lima-vm/lima/pkg/hostagent/api" @@ -72,6 +73,9 @@ type HostAgent struct { guestAgentAliveCh chan struct{} // closed on establishing the connection guestAgentAliveChOnce sync.Once + + guestIPv4Address string + guestIPv6Address string } type options struct { @@ -413,7 +417,9 @@ func (a *HostAgent) startRoutinesAndWait(ctx context.Context, errCh <-chan error func (a *HostAgent) Info(_ context.Context) (*hostagentapi.Info, error) { info := &hostagentapi.Info{ - SSHLocalPort: a.sshLocalPort, + SSHLocalPort: a.sshLocalPort, + GuestIPv4Address: a.guestIPv4Address, + GuestIPv6Address: a.guestIPv6Address, } return info, nil } @@ -514,6 +520,70 @@ sudo chown -R "${USER}" /run/host-services` } return errors.Join(rmErrs...) }) + + // Get instance IP Address + if !*a.instConfig.Plain { + // Collect IP output from guestagent + out, err := a.client.IPv4(ctx) + if err != nil { + logrus.Warnf("Received error from guestagent on collecting IPv4") + } + + // Update to hostagent struct + if err := a.setGuestIPv4Address(out); err != nil { + logrus.Warnf("Received error from parsing IPv4") + } + + out, err = a.client.IPv6(ctx) + if err != nil { + logrus.Warnf("Received error from guestagent on collecting IPv6") + } + + if err := a.setGuestIPv6Address(out); err != nil { + logrus.Warnf("Received error from parsing IPv6") + } + } + + // Plain Mode: Get instance IP Address + if *a.instConfig.Plain { + ifaceIPv4, err := a.getDefaultInterfaceSSH(ctx, "4") + if err != nil { + errs = append(errs, err) + } + outIPv4, err := a.getInterfaceInfoSSH(ctx, "4", ifaceIPv4) + if err != nil { + errs = append(errs, err) + } + + cmOutIPv4 := guestagentapi.CommandOutput{ + IsJson: true, + Payload: outIPv4, + } + + if err := a.setGuestIPv4Address(&cmOutIPv4); err != nil { + logrus.Warnf("Received error from parsing IPv4") + } + + // IPv6 + ifaceIPv6, err := a.getDefaultInterfaceSSH(ctx, "6") + if err != nil { + errs = append(errs, err) + } + outIPv6, err := a.getInterfaceInfoSSH(ctx, "6", ifaceIPv6) + if err != nil { + errs = append(errs, err) + } + + cmOutIPv6 := guestagentapi.CommandOutput{ + IsJson: true, + Payload: outIPv6, + } + + if err := a.setGuestIPv6Address(&cmOutIPv6); err != nil { + logrus.Warnf("Received error from parsing IPv4") + } + } + return errors.Join(errs...) } @@ -669,7 +739,6 @@ func (a *HostAgent) processGuestAgentEvents(ctx context.Context, client *guestag a.grpcPortForwarder.OnEvent(ctx, client, ev) } } - if err := client.Events(ctx, onEvent); err != nil { if status.Code(err) == codes.Canceled { return context.Canceled @@ -684,7 +753,7 @@ const ( verbCancel = "cancel" ) -func executeSSH(ctx context.Context, sshConfig *ssh.SSHConfig, port int, command ...string) error { +func executeSSH(ctx context.Context, sshConfig *ssh.SSHConfig, port int, command ...string) ([]byte, error) { args := sshConfig.Args() args = append(args, "-p", strconv.Itoa(port), @@ -693,10 +762,11 @@ func executeSSH(ctx context.Context, sshConfig *ssh.SSHConfig, port int, command ) args = append(args, command...) cmd := exec.CommandContext(ctx, sshConfig.Binary(), args...) - if out, err := cmd.Output(); err != nil { - return fmt.Errorf("failed to run %v: %q: %w", cmd.Args, string(out), err) + out, err := cmd.Output() + if err != nil { + return out, fmt.Errorf("failed to run %v: %q: %w", cmd.Args, string(out), err) } - return nil + return out, nil } func forwardSSH(ctx context.Context, sshConfig *ssh.SSHConfig, port int, local, remote, verb string, reverse bool) error { @@ -726,7 +796,7 @@ func forwardSSH(ctx context.Context, sshConfig *ssh.SSHConfig, port int, local, case verbForward: if reverse { logrus.Infof("Forwarding %q (host) to %q (guest)", local, remote) - if err := executeSSH(ctx, sshConfig, port, "rm", "-f", remote); err != nil { + if _, err := executeSSH(ctx, sshConfig, port, "rm", "-f", remote); err != nil { logrus.WithError(err).Warnf("Failed to clean up %q (guest) before setting up forwarding", remote) } } else { @@ -741,7 +811,7 @@ func forwardSSH(ctx context.Context, sshConfig *ssh.SSHConfig, port int, local, case verbCancel: if reverse { logrus.Infof("Stopping forwarding %q (host) to %q (guest)", local, remote) - if err := executeSSH(ctx, sshConfig, port, "rm", "-f", remote); err != nil { + if _, err := executeSSH(ctx, sshConfig, port, "rm", "-f", remote); err != nil { logrus.WithError(err).Warnf("Failed to clean up %q (guest) after stopping forwarding", remote) } } else { @@ -762,7 +832,7 @@ func forwardSSH(ctx context.Context, sshConfig *ssh.SSHConfig, port int, local, if verb == verbForward && strings.HasPrefix(local, "/") { if reverse { logrus.WithError(err).Warnf("Failed to set up forward from %q (host) to %q (guest)", local, remote) - if err := executeSSH(ctx, sshConfig, port, "rm", "-f", remote); err != nil { + if _, err := executeSSH(ctx, sshConfig, port, "rm", "-f", remote); err != nil { logrus.WithError(err).Warnf("Failed to clean up %q (guest) after forwarding failed", remote) } } else { @@ -803,3 +873,101 @@ func copyToHost(ctx context.Context, sshConfig *ssh.SSHConfig, port int, local, } return nil } + +// NetworkInterfaceInfo represents a network interface output from `iproute2`. +type NetworkInterfaceInfo struct { + IfName string `json:"ifname"` + AddrInfo []AddrInfo `json:"addr_info"` +} + +type AddrInfo struct { + Family string `json:"family"` + Local string `json:"local"` +} + +// extractIP extracts the IP address from JSON output. +func (a *HostAgent) extractIP(o *guestagentapi.CommandOutput) (string, error) { + if o == nil { + logrus.Warnf("There is no default interfaces") + return "", nil + } + if !o.IsJson { + return "", errors.New("IP Output must be in JSON format") + } + + if len(o.Payload) == 0 { + return "", nil + } + + var ifaceInfo []NetworkInterfaceInfo + if err := json.Unmarshal(o.Payload, &ifaceInfo); err != nil { + logrus.Infof("Err unmashalling ifaceInfo") + return "", err + } + + return ifaceInfo[0].AddrInfo[0].Local, nil +} + +// setGuestIPv4Address sets IPv4 info on the host agent. +func (a *HostAgent) setGuestIPv4Address(o *guestagentapi.CommandOutput) error { + ip, err := a.extractIP(o) + if err != nil { + return err + } + a.guestIPv4Address = ip + return nil +} + +// setGuestIPv6Address sets IPv6 info on the host agent. +func (a *HostAgent) setGuestIPv6Address(o *guestagentapi.CommandOutput) error { + ip, err := a.extractIP(o) + if err != nil { + return err + } + a.guestIPv6Address = ip + return nil +} + +// getDefaultInterfaceSSH fetches the default interface for IPv4 or IPv6 via SSH. +func (a *HostAgent) getDefaultInterfaceSSH(ctx context.Context, ipVersion string) (string, error) { + args := []string{"-j", "route", "show", "default"} + if ipVersion == "6" { + args = append([]string{"-6"}, args...) + } else { + args = append([]string{"-4"}, args...) + } + + cmd := "ip " + strings.Join(args, " ") + output, err := executeSSH(ctx, a.sshConfig, a.sshLocalPort, cmd) + if err != nil { + return "", errors.New("failed to run `ip route show default`") + } + + var routes []guestagent.RouteEntry + if err := json.Unmarshal(output, &routes); err != nil { + return "", errors.New("failed to unmarshal route JSON") + } + + // return nil if no default interface + if len(routes) == 0 || routes[0].Dev == "" { + return "", nil + } + + return routes[0].Dev, nil +} + +// getInterfaceInfoSSH fetches the interface information for IPv4 or IPv6 via SSH. +func (a *HostAgent) getInterfaceInfoSSH(ctx context.Context, ipVersion, iface string) ([]byte, error) { + if iface == "" { + return nil, nil + } + + args := []string{"-j", "address", "show", "dev", iface} + if ipVersion == "6" { + args = append([]string{"-6"}, args...) + } else { + args = append([]string{"-4"}, args...) + } + cmd := "ip " + strings.Join(args, " ") + return executeSSH(ctx, a.sshConfig, a.sshLocalPort, cmd) +} diff --git a/pkg/store/instance.go b/pkg/store/instance.go index 19b4b2270dc..e11d5c372bc 100644 --- a/pkg/store/instance.go +++ b/pkg/store/instance.go @@ -66,6 +66,8 @@ type Instance struct { Protected bool `json:"protected"` LimaVersion string `json:"limaVersion"` Param map[string]string `json:"param,omitempty"` + IPv4Address string `json:"ipv4,omitempty"` + IPv6Address string `json:"ipv6,omitempty"` } // Inspect returns err only when the instance does not exist (os.ErrNotExist). @@ -121,6 +123,8 @@ func Inspect(instName string) (*Instance, error) { inst.Errors = append(inst.Errors, fmt.Errorf("failed to get Info from %q: %w", haSock, err)) } else { inst.SSHLocalPort = info.SSHLocalPort + inst.IPv4Address = info.GuestIPv4Address + inst.IPv6Address = info.GuestIPv6Address } } }