From 87821dd59c49cb78357a52214a93c682c73a94b4 Mon Sep 17 00:00:00 2001 From: sjmiller609 <7516283+sjmiller609@users.noreply.github.com> Date: Sat, 30 May 2026 19:29:47 +0000 Subject: [PATCH 01/15] Add mailbox resume network handoff --- cmd/api/api/instances.go | 7 +- cmd/api/api/instances_test.go | 6 +- cmd/api/api/snapshots.go | 5 +- cmd/api/api/snapshots_test.go | 61 +++ lib/forkvm/README.md | 18 + lib/guest/client.go | 107 +++++ lib/guest/guest.pb.go | 188 ++++++-- lib/guest/guest.proto | 15 + lib/guest/guest_grpc.pb.go | 52 ++- lib/instances/fork.go | 13 +- lib/instances/fork_test.go | 2 +- lib/instances/guest_resume_network.go | 246 ++++++++++ lib/instances/manager.go | 8 +- lib/instances/restore.go | 126 +++++- lib/instances/restore_egress_test.go | 64 ++- lib/instances/snapshot.go | 8 +- lib/instances/types.go | 8 +- lib/oapi/oapi.go | 424 +++++++++--------- lib/system/guest_agent/main.go | 4 +- lib/system/guest_agent/network.go | 91 ++++ lib/system/guest_agent/resume_network.go | 201 +++++++++ .../guest_agent/resume_network_other.go | 7 + lib/system/versions.go | 14 +- openapi.yaml | 14 + 24 files changed, 1401 insertions(+), 288 deletions(-) create mode 100644 lib/instances/guest_resume_network.go create mode 100644 lib/system/guest_agent/network.go create mode 100644 lib/system/guest_agent/resume_network.go create mode 100644 lib/system/guest_agent/resume_network_other.go diff --git a/cmd/api/api/instances.go b/cmd/api/api/instances.go index 2c85284b..9d59515d 100644 --- a/cmd/api/api/instances.go +++ b/cmd/api/api/instances.go @@ -657,9 +657,10 @@ func (s *ApiService) ForkInstance(ctx context.Context, request oapi.ForkInstance } result, err := s.InstanceManager.ForkInstance(ctx, inst.Id, instances.ForkInstanceRequest{ - Name: request.Body.Name, - FromRunning: request.Body.FromRunning != nil && *request.Body.FromRunning, - TargetState: targetState, + Name: request.Body.Name, + FromRunning: request.Body.FromRunning != nil && *request.Body.FromRunning, + TargetState: targetState, + WaitForNetwork: request.Body.WaitForNetwork, }) if err != nil { switch { diff --git a/cmd/api/api/instances_test.go b/cmd/api/api/instances_test.go index 4e7863aa..896a0632 100644 --- a/cmd/api/api/instances_test.go +++ b/cmd/api/api/instances_test.go @@ -1165,13 +1165,15 @@ func TestForkInstance_Success(t *testing.T) { result: forked, } svc.InstanceManager = mockMgr + waitForNetwork := false resp, err := svc.ForkInstance( mw.WithResolvedInstance(ctx(), source.Id, source), oapi.ForkInstanceRequestObject{ Id: source.Id, Body: &oapi.ForkInstanceRequest{ - Name: "forked-instance", + Name: "forked-instance", + WaitForNetwork: &waitForNetwork, }, }, ) @@ -1185,6 +1187,8 @@ func TestForkInstance_Success(t *testing.T) { assert.Equal(t, "forked-instance", mockMgr.lastReq.Name) assert.False(t, mockMgr.lastReq.FromRunning) assert.Equal(t, instances.State(""), mockMgr.lastReq.TargetState) + require.NotNil(t, mockMgr.lastReq.WaitForNetwork) + assert.False(t, *mockMgr.lastReq.WaitForNetwork) } func TestForkInstance_NotSupported(t *testing.T) { diff --git a/cmd/api/api/snapshots.go b/cmd/api/api/snapshots.go index 620d9574..ffe35474 100644 --- a/cmd/api/api/snapshots.go +++ b/cmd/api/api/snapshots.go @@ -175,7 +175,10 @@ func (s *ApiService) ForkSnapshot(ctx context.Context, request oapi.ForkSnapshot return oapi.ForkSnapshot400JSONResponse{Code: "invalid_request", Message: "request body is required"}, nil } - domainReq := instances.ForkSnapshotRequest{Name: request.Body.Name} + domainReq := instances.ForkSnapshotRequest{ + Name: request.Body.Name, + WaitForNetwork: request.Body.WaitForNetwork, + } if request.Body.TargetState != nil { domainReq.TargetState = instances.State(*request.Body.TargetState) } diff --git a/cmd/api/api/snapshots_test.go b/cmd/api/api/snapshots_test.go index d0730133..f87e9148 100644 --- a/cmd/api/api/snapshots_test.go +++ b/cmd/api/api/snapshots_test.go @@ -1,14 +1,35 @@ package api import ( + "context" "testing" "time" + "github.com/kernel/hypeman/lib/hypervisor" "github.com/kernel/hypeman/lib/instances" + "github.com/kernel/hypeman/lib/oapi" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +type captureForkSnapshotManager struct { + instances.Manager + lastID string + lastReq *instances.ForkSnapshotRequest + result *instances.Instance + err error +} + +func (m *captureForkSnapshotManager) ForkSnapshot(ctx context.Context, snapshotID string, req instances.ForkSnapshotRequest) (*instances.Instance, error) { + reqCopy := req + m.lastID = snapshotID + m.lastReq = &reqCopy + if m.err != nil { + return nil, m.err + } + return m.result, nil +} + func TestSnapshotScheduleToOAPIPreservesZeroMaxCount(t *testing.T) { t.Parallel() @@ -30,3 +51,43 @@ func TestSnapshotScheduleToOAPIPreservesZeroMaxCount(t *testing.T) { require.NotNil(t, out.Retention.MaxAge) assert.Equal(t, "24h0m0s", *out.Retention.MaxAge) } + +func TestForkSnapshotMapsWaitForNetwork(t *testing.T) { + t.Parallel() + svc := newTestService(t) + + forked := &instances.Instance{ + StoredMetadata: instances.StoredMetadata{ + Id: "forked-instance", + Name: "forked-instance", + Image: "docker.io/library/alpine:latest", + CreatedAt: time.Now(), + HypervisorType: hypervisor.TypeFirecracker, + }, + State: instances.StateRunning, + } + mockMgr := &captureForkSnapshotManager{ + Manager: svc.InstanceManager, + result: forked, + } + svc.InstanceManager = mockMgr + waitForNetwork := false + + resp, err := svc.ForkSnapshot(ctx(), oapi.ForkSnapshotRequestObject{ + SnapshotId: "snap-123", + Body: &oapi.ForkSnapshotRequest{ + Name: "forked-instance", + WaitForNetwork: &waitForNetwork, + }, + }) + require.NoError(t, err) + + created, ok := resp.(oapi.ForkSnapshot201JSONResponse) + require.True(t, ok, "expected 201 response") + assert.Equal(t, "forked-instance", created.Name) + assert.Equal(t, "snap-123", mockMgr.lastID) + require.NotNil(t, mockMgr.lastReq) + assert.Equal(t, "forked-instance", mockMgr.lastReq.Name) + require.NotNil(t, mockMgr.lastReq.WaitForNetwork) + assert.False(t, *mockMgr.lastReq.WaitForNetwork) +} diff --git a/lib/forkvm/README.md b/lib/forkvm/README.md index bb6567c7..88474c72 100644 --- a/lib/forkvm/README.md +++ b/lib/forkvm/README.md @@ -14,6 +14,24 @@ to work across implementations. For networked forks, the fork gets a fresh host/guest identity (IP, MAC, TAP) instead of reusing the source identity. +## Resume network handoff + +Networked standby/running forks need a new host-side allocation, but the guest +memory snapshot still contains the source VM's old interface state. On restore, +Hypeman prepares the fork's TAP/IP/MAC before the VM resumes, then hands the new +guest network config to the guest through a small mailbox embedded in snapshot +memory. After resume, VMGenID tells the guest-agent that this is a restored VM; +the guest-agent reads the mailbox and applies the new MAC, address, route, and +neighbor state with netlink. + +For API calls that return a running fork, `wait_for_network` defaults to true. +In that mode Hypeman waits for a guest UDP "applied" ack before returning, so +the fast path still avoids making host-initiated guest RPC/vsock contact as the +first post-resume dependency. If `wait_for_network=false`, the API returns after +resume once the mailbox has been patched and the guest finishes the network +handoff asynchronously. If the mailbox path is unavailable, restore falls back +to the older host-initiated guest network reconfigure path. + ## Fork data copy behavior - Guest directory copy is **sparse-only** for regular files. diff --git a/lib/guest/client.go b/lib/guest/client.go index 20ff8d0b..945f082c 100644 --- a/lib/guest/client.go +++ b/lib/guest/client.go @@ -82,8 +82,12 @@ func GetOrCreateConn(ctx context.Context, dialer hypervisor.VsockDialer) (*grpc. } // Create new connection using the VsockDialer + traceCtx := ctx conn, err := grpc.Dial("passthrough:///vsock", grpc.WithContextDialer(func(ctx context.Context, addr string) (net.Conn, error) { + if span := trace.SpanFromContext(traceCtx); span.SpanContext().IsValid() { + ctx = trace.ContextWithSpan(ctx, span) + } netConn, err := dialer.DialVsock(ctx, vsockGuestPort) if err != nil { return nil, &AgentVSockDialError{Err: err} @@ -146,6 +150,109 @@ type ExecOptions struct { ResizeChan <-chan *WindowSize // Optional: channel to receive resize events (pointer to avoid copying mutex) } +type ReconfigureNetworkOptions struct { + InterfaceName string + MAC string + IPv4 string + Prefix uint32 + Gateway string + WaitForAgent time.Duration +} + +func ReconfigureNetworkInInstance(ctx context.Context, dialer hypervisor.VsockDialer, opts ReconfigureNetworkOptions) error { + if opts.WaitForAgent == 0 { + return reconfigureNetworkOnce(ctx, dialer, opts) + } + + ctx, span := otel.Tracer("hypeman/guest").Start(ctx, "guest.reconfigure_network", trace.WithAttributes( + attribute.Bool("wait_for_agent", true), + attribute.Int64("wait_for_agent_ms", opts.WaitForAgent.Milliseconds()), + )) + defer span.End() + + deadline := time.Now().Add(opts.WaitForAgent) + start := time.Now() + attempts := 0 + retryableAttempts := 0 + firstRetryableErrorType := "" + lastRetryableErrorType := "" + lastRetryInterval := time.Duration(0) + + for { + attempts++ + err := reconfigureNetworkOnce(ctx, dialer, opts) + if err == nil { + recordGuestExecWait(span, start, attempts, retryableAttempts, firstRetryableErrorType, lastRetryableErrorType, lastRetryInterval) + span.SetStatus(otelcodes.Ok, "") + return nil + } + if !isRetryableConnectionError(err) { + recordGuestExecWait(span, start, attempts, retryableAttempts, firstRetryableErrorType, lastRetryableErrorType, lastRetryInterval) + span.RecordError(err) + span.SetStatus(otelcodes.Error, err.Error()) + return err + } + + retryableAttempts++ + errType := retryableConnectionErrorType(err) + if firstRetryableErrorType == "" { + firstRetryableErrorType = errType + } + lastRetryableErrorType = errType + CloseConn(dialer.Key()) + + if time.Now().After(deadline) { + recordGuestExecWait(span, start, attempts, retryableAttempts, firstRetryableErrorType, lastRetryableErrorType, lastRetryInterval) + span.RecordError(err) + span.SetStatus(otelcodes.Error, err.Error()) + return err + } + + retryInterval := guestExecRetryInterval(time.Since(start)) + lastRetryInterval = retryInterval + select { + case <-ctx.Done(): + recordGuestExecWait(span, start, attempts, retryableAttempts, firstRetryableErrorType, lastRetryableErrorType, lastRetryInterval) + span.RecordError(ctx.Err()) + span.SetStatus(otelcodes.Error, ctx.Err().Error()) + return ctx.Err() + case <-time.After(retryInterval): + } + } +} + +func reconfigureNetworkOnce(ctx context.Context, dialer hypervisor.VsockDialer, opts ReconfigureNetworkOptions) error { + grpcConn, err := GetOrCreateConn(ctx, dialer) + if err != nil { + return fmt.Errorf("get grpc connection: %w", err) + } + client := NewGuestServiceClient(grpcConn) + + _, span := otel.Tracer("hypeman/guest").Start(ctx, "guest.reconfigure_network.rpc") + _, err = client.ReconfigureNetwork(ctx, &ReconfigureNetworkRequest{ + InterfaceName: opts.InterfaceName, + Mac: opts.MAC, + Ipv4: opts.IPv4, + Prefix: opts.Prefix, + Gateway: opts.Gateway, + }) + finishGuestNetworkStepSpan(span, err) + if err != nil { + return fmt.Errorf("reconfigure network rpc: %w", err) + } + return nil +} + +func finishGuestNetworkStepSpan(span trace.Span, err error) { + if err != nil { + span.RecordError(err) + span.SetStatus(otelcodes.Error, err.Error()) + } else { + span.SetStatus(otelcodes.Ok, "") + } + span.End() +} + // ExecIntoInstance executes command in instance via vsock using gRPC. // The dialer is a hypervisor-specific VsockDialer that knows how to connect to the guest. // If WaitForAgent is set, it will retry on connection errors until the timeout. diff --git a/lib/guest/guest.pb.go b/lib/guest/guest.pb.go index 0de1628e..a239fc97 100644 --- a/lib/guest/guest.pb.go +++ b/lib/guest/guest.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.10 -// protoc v3.21.12 +// protoc v6.33.4 // source: lib/guest/guest.proto package guest @@ -1266,6 +1266,120 @@ func (*ShutdownResponse) Descriptor() ([]byte, []int) { return file_lib_guest_guest_proto_rawDescGZIP(), []int{16} } +// ReconfigureNetworkRequest updates a guest network interface after snapshot restore +type ReconfigureNetworkRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + InterfaceName string `protobuf:"bytes,1,opt,name=interface_name,json=interfaceName,proto3" json:"interface_name,omitempty"` // Interface to reconfigure, defaults to eth0 + Mac string `protobuf:"bytes,2,opt,name=mac,proto3" json:"mac,omitempty"` // New MAC address + Ipv4 string `protobuf:"bytes,3,opt,name=ipv4,proto3" json:"ipv4,omitempty"` // New IPv4 address without prefix + Prefix uint32 `protobuf:"varint,4,opt,name=prefix,proto3" json:"prefix,omitempty"` // IPv4 prefix length + Gateway string `protobuf:"bytes,5,opt,name=gateway,proto3" json:"gateway,omitempty"` // Default gateway IPv4 address + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ReconfigureNetworkRequest) Reset() { + *x = ReconfigureNetworkRequest{} + mi := &file_lib_guest_guest_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ReconfigureNetworkRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReconfigureNetworkRequest) ProtoMessage() {} + +func (x *ReconfigureNetworkRequest) ProtoReflect() protoreflect.Message { + mi := &file_lib_guest_guest_proto_msgTypes[17] + 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 ReconfigureNetworkRequest.ProtoReflect.Descriptor instead. +func (*ReconfigureNetworkRequest) Descriptor() ([]byte, []int) { + return file_lib_guest_guest_proto_rawDescGZIP(), []int{17} +} + +func (x *ReconfigureNetworkRequest) GetInterfaceName() string { + if x != nil { + return x.InterfaceName + } + return "" +} + +func (x *ReconfigureNetworkRequest) GetMac() string { + if x != nil { + return x.Mac + } + return "" +} + +func (x *ReconfigureNetworkRequest) GetIpv4() string { + if x != nil { + return x.Ipv4 + } + return "" +} + +func (x *ReconfigureNetworkRequest) GetPrefix() uint32 { + if x != nil { + return x.Prefix + } + return 0 +} + +func (x *ReconfigureNetworkRequest) GetGateway() string { + if x != nil { + return x.Gateway + } + return "" +} + +// ReconfigureNetworkResponse acknowledges the network reconfiguration request +type ReconfigureNetworkResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ReconfigureNetworkResponse) Reset() { + *x = ReconfigureNetworkResponse{} + mi := &file_lib_guest_guest_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ReconfigureNetworkResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReconfigureNetworkResponse) ProtoMessage() {} + +func (x *ReconfigureNetworkResponse) ProtoReflect() protoreflect.Message { + mi := &file_lib_guest_guest_proto_msgTypes[18] + 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 ReconfigureNetworkResponse.ProtoReflect.Descriptor instead. +func (*ReconfigureNetworkResponse) Descriptor() ([]byte, []int) { + return file_lib_guest_guest_proto_rawDescGZIP(), []int{18} +} + var File_lib_guest_guest_proto protoreflect.FileDescriptor const file_lib_guest_guest_proto_rawDesc = "" + @@ -1358,13 +1472,21 @@ const file_lib_guest_guest_proto_rawDesc = "" + "\x05error\x18\b \x01(\tR\x05error\")\n" + "\x0fShutdownRequest\x12\x16\n" + "\x06signal\x18\x01 \x01(\x05R\x06signal\"\x12\n" + - "\x10ShutdownResponse2\xd3\x02\n" + + "\x10ShutdownResponse\"\x9a\x01\n" + + "\x19ReconfigureNetworkRequest\x12%\n" + + "\x0einterface_name\x18\x01 \x01(\tR\rinterfaceName\x12\x10\n" + + "\x03mac\x18\x02 \x01(\tR\x03mac\x12\x12\n" + + "\x04ipv4\x18\x03 \x01(\tR\x04ipv4\x12\x16\n" + + "\x06prefix\x18\x04 \x01(\rR\x06prefix\x12\x18\n" + + "\agateway\x18\x05 \x01(\tR\agateway\"\x1c\n" + + "\x1aReconfigureNetworkResponse2\xae\x03\n" + "\fGuestService\x123\n" + "\x04Exec\x12\x12.guest.ExecRequest\x1a\x13.guest.ExecResponse(\x010\x01\x12F\n" + "\vCopyToGuest\x12\x19.guest.CopyToGuestRequest\x1a\x1a.guest.CopyToGuestResponse(\x01\x12L\n" + "\rCopyFromGuest\x12\x1b.guest.CopyFromGuestRequest\x1a\x1c.guest.CopyFromGuestResponse0\x01\x12;\n" + "\bStatPath\x12\x16.guest.StatPathRequest\x1a\x17.guest.StatPathResponse\x12;\n" + - "\bShutdown\x12\x16.guest.ShutdownRequest\x1a\x17.guest.ShutdownResponseB'Z%github.com/onkernel/hypeman/lib/guestb\x06proto3" + "\bShutdown\x12\x16.guest.ShutdownRequest\x1a\x17.guest.ShutdownResponse\x12Y\n" + + "\x12ReconfigureNetwork\x12 .guest.ReconfigureNetworkRequest\x1a!.guest.ReconfigureNetworkResponseB'Z%github.com/onkernel/hypeman/lib/guestb\x06proto3" var ( file_lib_guest_guest_proto_rawDescOnce sync.Once @@ -1378,31 +1500,33 @@ func file_lib_guest_guest_proto_rawDescGZIP() []byte { return file_lib_guest_guest_proto_rawDescData } -var file_lib_guest_guest_proto_msgTypes = make([]protoimpl.MessageInfo, 18) +var file_lib_guest_guest_proto_msgTypes = make([]protoimpl.MessageInfo, 20) var file_lib_guest_guest_proto_goTypes = []any{ - (*ExecRequest)(nil), // 0: guest.ExecRequest - (*ExecStart)(nil), // 1: guest.ExecStart - (*WindowSize)(nil), // 2: guest.WindowSize - (*ExecResponse)(nil), // 3: guest.ExecResponse - (*CopyToGuestRequest)(nil), // 4: guest.CopyToGuestRequest - (*CopyToGuestStart)(nil), // 5: guest.CopyToGuestStart - (*CopyToGuestEnd)(nil), // 6: guest.CopyToGuestEnd - (*CopyToGuestResponse)(nil), // 7: guest.CopyToGuestResponse - (*CopyFromGuestRequest)(nil), // 8: guest.CopyFromGuestRequest - (*CopyFromGuestResponse)(nil), // 9: guest.CopyFromGuestResponse - (*CopyFromGuestHeader)(nil), // 10: guest.CopyFromGuestHeader - (*CopyFromGuestEnd)(nil), // 11: guest.CopyFromGuestEnd - (*CopyFromGuestError)(nil), // 12: guest.CopyFromGuestError - (*StatPathRequest)(nil), // 13: guest.StatPathRequest - (*StatPathResponse)(nil), // 14: guest.StatPathResponse - (*ShutdownRequest)(nil), // 15: guest.ShutdownRequest - (*ShutdownResponse)(nil), // 16: guest.ShutdownResponse - nil, // 17: guest.ExecStart.EnvEntry + (*ExecRequest)(nil), // 0: guest.ExecRequest + (*ExecStart)(nil), // 1: guest.ExecStart + (*WindowSize)(nil), // 2: guest.WindowSize + (*ExecResponse)(nil), // 3: guest.ExecResponse + (*CopyToGuestRequest)(nil), // 4: guest.CopyToGuestRequest + (*CopyToGuestStart)(nil), // 5: guest.CopyToGuestStart + (*CopyToGuestEnd)(nil), // 6: guest.CopyToGuestEnd + (*CopyToGuestResponse)(nil), // 7: guest.CopyToGuestResponse + (*CopyFromGuestRequest)(nil), // 8: guest.CopyFromGuestRequest + (*CopyFromGuestResponse)(nil), // 9: guest.CopyFromGuestResponse + (*CopyFromGuestHeader)(nil), // 10: guest.CopyFromGuestHeader + (*CopyFromGuestEnd)(nil), // 11: guest.CopyFromGuestEnd + (*CopyFromGuestError)(nil), // 12: guest.CopyFromGuestError + (*StatPathRequest)(nil), // 13: guest.StatPathRequest + (*StatPathResponse)(nil), // 14: guest.StatPathResponse + (*ShutdownRequest)(nil), // 15: guest.ShutdownRequest + (*ShutdownResponse)(nil), // 16: guest.ShutdownResponse + (*ReconfigureNetworkRequest)(nil), // 17: guest.ReconfigureNetworkRequest + (*ReconfigureNetworkResponse)(nil), // 18: guest.ReconfigureNetworkResponse + nil, // 19: guest.ExecStart.EnvEntry } var file_lib_guest_guest_proto_depIdxs = []int32{ 1, // 0: guest.ExecRequest.start:type_name -> guest.ExecStart 2, // 1: guest.ExecRequest.resize:type_name -> guest.WindowSize - 17, // 2: guest.ExecStart.env:type_name -> guest.ExecStart.EnvEntry + 19, // 2: guest.ExecStart.env:type_name -> guest.ExecStart.EnvEntry 5, // 3: guest.CopyToGuestRequest.start:type_name -> guest.CopyToGuestStart 6, // 4: guest.CopyToGuestRequest.end:type_name -> guest.CopyToGuestEnd 10, // 5: guest.CopyFromGuestResponse.header:type_name -> guest.CopyFromGuestHeader @@ -1413,13 +1537,15 @@ var file_lib_guest_guest_proto_depIdxs = []int32{ 8, // 10: guest.GuestService.CopyFromGuest:input_type -> guest.CopyFromGuestRequest 13, // 11: guest.GuestService.StatPath:input_type -> guest.StatPathRequest 15, // 12: guest.GuestService.Shutdown:input_type -> guest.ShutdownRequest - 3, // 13: guest.GuestService.Exec:output_type -> guest.ExecResponse - 7, // 14: guest.GuestService.CopyToGuest:output_type -> guest.CopyToGuestResponse - 9, // 15: guest.GuestService.CopyFromGuest:output_type -> guest.CopyFromGuestResponse - 14, // 16: guest.GuestService.StatPath:output_type -> guest.StatPathResponse - 16, // 17: guest.GuestService.Shutdown:output_type -> guest.ShutdownResponse - 13, // [13:18] is the sub-list for method output_type - 8, // [8:13] is the sub-list for method input_type + 17, // 13: guest.GuestService.ReconfigureNetwork:input_type -> guest.ReconfigureNetworkRequest + 3, // 14: guest.GuestService.Exec:output_type -> guest.ExecResponse + 7, // 15: guest.GuestService.CopyToGuest:output_type -> guest.CopyToGuestResponse + 9, // 16: guest.GuestService.CopyFromGuest:output_type -> guest.CopyFromGuestResponse + 14, // 17: guest.GuestService.StatPath:output_type -> guest.StatPathResponse + 16, // 18: guest.GuestService.Shutdown:output_type -> guest.ShutdownResponse + 18, // 19: guest.GuestService.ReconfigureNetwork:output_type -> guest.ReconfigureNetworkResponse + 14, // [14:20] is the sub-list for method output_type + 8, // [8:14] is the sub-list for method input_type 8, // [8:8] is the sub-list for extension type_name 8, // [8:8] is the sub-list for extension extendee 0, // [0:8] is the sub-list for field type_name @@ -1457,7 +1583,7 @@ func file_lib_guest_guest_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_lib_guest_guest_proto_rawDesc), len(file_lib_guest_guest_proto_rawDesc)), NumEnums: 0, - NumMessages: 18, + NumMessages: 20, NumExtensions: 0, NumServices: 1, }, diff --git a/lib/guest/guest.proto b/lib/guest/guest.proto index c42198a9..317c21b3 100644 --- a/lib/guest/guest.proto +++ b/lib/guest/guest.proto @@ -20,6 +20,9 @@ service GuestService { // Shutdown requests graceful VM shutdown by signaling init (PID 1) rpc Shutdown(ShutdownRequest) returns (ShutdownResponse); + + // ReconfigureNetwork updates the guest network identity without spawning shell commands + rpc ReconfigureNetwork(ReconfigureNetworkRequest) returns (ReconfigureNetworkResponse); } // ExecRequest represents messages from client to server @@ -154,3 +157,15 @@ message ShutdownRequest { // ShutdownResponse acknowledges the shutdown request message ShutdownResponse {} + +// ReconfigureNetworkRequest updates a guest network interface after snapshot restore +message ReconfigureNetworkRequest { + string interface_name = 1; // Interface to reconfigure, defaults to eth0 + string mac = 2; // New MAC address + string ipv4 = 3; // New IPv4 address without prefix + uint32 prefix = 4; // IPv4 prefix length + string gateway = 5; // Default gateway IPv4 address +} + +// ReconfigureNetworkResponse acknowledges the network reconfiguration request +message ReconfigureNetworkResponse {} diff --git a/lib/guest/guest_grpc.pb.go b/lib/guest/guest_grpc.pb.go index 71224327..f93631d9 100644 --- a/lib/guest/guest_grpc.pb.go +++ b/lib/guest/guest_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.6.0 -// - protoc v3.21.12 +// - protoc v6.33.4 // source: lib/guest/guest.proto package guest @@ -19,11 +19,12 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( - GuestService_Exec_FullMethodName = "/guest.GuestService/Exec" - GuestService_CopyToGuest_FullMethodName = "/guest.GuestService/CopyToGuest" - GuestService_CopyFromGuest_FullMethodName = "/guest.GuestService/CopyFromGuest" - GuestService_StatPath_FullMethodName = "/guest.GuestService/StatPath" - GuestService_Shutdown_FullMethodName = "/guest.GuestService/Shutdown" + GuestService_Exec_FullMethodName = "/guest.GuestService/Exec" + GuestService_CopyToGuest_FullMethodName = "/guest.GuestService/CopyToGuest" + GuestService_CopyFromGuest_FullMethodName = "/guest.GuestService/CopyFromGuest" + GuestService_StatPath_FullMethodName = "/guest.GuestService/StatPath" + GuestService_Shutdown_FullMethodName = "/guest.GuestService/Shutdown" + GuestService_ReconfigureNetwork_FullMethodName = "/guest.GuestService/ReconfigureNetwork" ) // GuestServiceClient is the client API for GuestService service. @@ -42,6 +43,8 @@ type GuestServiceClient interface { StatPath(ctx context.Context, in *StatPathRequest, opts ...grpc.CallOption) (*StatPathResponse, error) // Shutdown requests graceful VM shutdown by signaling init (PID 1) Shutdown(ctx context.Context, in *ShutdownRequest, opts ...grpc.CallOption) (*ShutdownResponse, error) + // ReconfigureNetwork updates the guest network identity without spawning shell commands + ReconfigureNetwork(ctx context.Context, in *ReconfigureNetworkRequest, opts ...grpc.CallOption) (*ReconfigureNetworkResponse, error) } type guestServiceClient struct { @@ -117,6 +120,16 @@ func (c *guestServiceClient) Shutdown(ctx context.Context, in *ShutdownRequest, return out, nil } +func (c *guestServiceClient) ReconfigureNetwork(ctx context.Context, in *ReconfigureNetworkRequest, opts ...grpc.CallOption) (*ReconfigureNetworkResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ReconfigureNetworkResponse) + err := c.cc.Invoke(ctx, GuestService_ReconfigureNetwork_FullMethodName, in, out, cOpts...) + 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. @@ -133,6 +146,8 @@ type GuestServiceServer interface { StatPath(context.Context, *StatPathRequest) (*StatPathResponse, error) // Shutdown requests graceful VM shutdown by signaling init (PID 1) Shutdown(context.Context, *ShutdownRequest) (*ShutdownResponse, error) + // ReconfigureNetwork updates the guest network identity without spawning shell commands + ReconfigureNetwork(context.Context, *ReconfigureNetworkRequest) (*ReconfigureNetworkResponse, error) mustEmbedUnimplementedGuestServiceServer() } @@ -158,6 +173,9 @@ func (UnimplementedGuestServiceServer) StatPath(context.Context, *StatPathReques func (UnimplementedGuestServiceServer) Shutdown(context.Context, *ShutdownRequest) (*ShutdownResponse, error) { return nil, status.Error(codes.Unimplemented, "method Shutdown not implemented") } +func (UnimplementedGuestServiceServer) ReconfigureNetwork(context.Context, *ReconfigureNetworkRequest) (*ReconfigureNetworkResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ReconfigureNetwork not implemented") +} func (UnimplementedGuestServiceServer) mustEmbedUnimplementedGuestServiceServer() {} func (UnimplementedGuestServiceServer) testEmbeddedByValue() {} @@ -240,6 +258,24 @@ func _GuestService_Shutdown_Handler(srv interface{}, ctx context.Context, dec fu return interceptor(ctx, in, info, handler) } +func _GuestService_ReconfigureNetwork_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ReconfigureNetworkRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GuestServiceServer).ReconfigureNetwork(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: GuestService_ReconfigureNetwork_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GuestServiceServer).ReconfigureNetwork(ctx, req.(*ReconfigureNetworkRequest)) + } + 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) @@ -255,6 +291,10 @@ var GuestService_ServiceDesc = grpc.ServiceDesc{ MethodName: "Shutdown", Handler: _GuestService_Shutdown_Handler, }, + { + MethodName: "ReconfigureNetwork", + Handler: _GuestService_ReconfigureNetwork_Handler, + }, }, Streams: []grpc.StreamDesc{ { diff --git a/lib/instances/fork.go b/lib/instances/fork.go index b4af9434..2c61fa25 100644 --- a/lib/instances/fork.go +++ b/lib/instances/fork.go @@ -81,7 +81,9 @@ func (m *manager) forkInstance(ctx context.Context, id string, req ForkInstanceR // the source data directory. Restore the fork while source remains standby and // under lock, then restore the source. if forkErr == nil && targetState == StateRunning { - restoredFork, err := m.applyForkTargetState(ctx, forked.Id, StateRunning) + restoredFork, err := m.applyForkTargetState(ctx, forked.Id, StateRunning, restoreInstanceOptions{ + WaitForGuestNetwork: req.WaitForNetwork, + }) if err != nil { forkErr = fmt.Errorf("restore forked instance before source restore: %w", err) if cleanupErr := m.cleanupForkInstanceOnError(ctx, forked.Id); cleanupErr != nil { @@ -93,7 +95,7 @@ func (m *manager) forkInstance(ctx context.Context, id string, req ForkInstanceR } log.InfoContext(ctx, "restoring source instance after running fork", "source_instance_id", id) - restoredSource, restoreErr := m.restoreInstance(ctx, id) + restoredSource, restoreErr := m.restoreInstance(ctx, id, restoreInstanceOptions{}) if restoreErr != nil { if forkErr != nil { @@ -401,7 +403,7 @@ func resolveForkTargetState(requested State, sourceState State) (State, error) { return requested, nil } -func (m *manager) applyForkTargetState(ctx context.Context, forkID string, target State) (*Instance, error) { +func (m *manager) applyForkTargetState(ctx context.Context, forkID string, target State, restoreOpts restoreInstanceOptions) (*Instance, error) { lock := m.getInstanceLock(forkID) lock.Lock() defer lock.Unlock() @@ -411,6 +413,9 @@ func (m *manager) applyForkTargetState(ctx context.Context, forkID string, targe return nil, err } if inst != nil && (inst.State == StateRunning || inst.State == StateInitializing) { + if guestInitiatedResumeNetworkMailbox(&inst.StoredMetadata) { + return inst, nil + } if err := ensureGuestAgentReadyForForkPhase(ctx, &inst.StoredMetadata, "before returning running fork instance"); err != nil { return nil, fmt.Errorf("wait for forked guest agent readiness: %w", err) } @@ -440,7 +445,7 @@ func (m *manager) applyForkTargetState(ctx context.Context, forkID string, targe case StateStandby: switch target { case StateRunning: - return returnWithReadiness(m.restoreInstance(ctx, forkID)) + return returnWithReadiness(m.restoreInstance(ctx, forkID, restoreOpts)) case StateStopped: if err := os.RemoveAll(m.paths.InstanceSnapshotLatest(forkID)); err != nil { return nil, fmt.Errorf("remove fork snapshot: %w", err) diff --git a/lib/instances/fork_test.go b/lib/instances/fork_test.go index 606b7671..f158a645 100644 --- a/lib/instances/fork_test.go +++ b/lib/instances/fork_test.go @@ -308,7 +308,7 @@ func TestApplyForkTargetStateStoppedRefreshesSnapshotForkCID(t *testing.T) { meta.StoredMetadata.Phases.Record(phasetracking.PhaseStandby, time.Now()) require.NoError(t, manager.saveMetadata(meta)) - inst, err := manager.applyForkTargetState(ctx, forkID, StateStopped) + inst, err := manager.applyForkTargetState(ctx, forkID, StateStopped, restoreInstanceOptions{}) require.NoError(t, err) require.Equal(t, StateStopped, inst.State) require.Equal(t, generateVsockCID(forkID), inst.VsockCID) diff --git a/lib/instances/guest_resume_network.go b/lib/instances/guest_resume_network.go new file mode 100644 index 00000000..354657b0 --- /dev/null +++ b/lib/instances/guest_resume_network.go @@ -0,0 +1,246 @@ +package instances + +import ( + "bytes" + "context" + "encoding/binary" + "encoding/json" + "fmt" + stdnet "net" + "os" + "path/filepath" + "strings" + "sync" + "time" + + "github.com/kernel/hypeman/lib/hypervisor" + "github.com/kernel/hypeman/lib/logger" + "go.opentelemetry.io/otel/attribute" + "golang.org/x/sys/unix" +) + +const guestResumeNetworkMailboxEnv = "HYPEMAN_RESUME_NETWORK_MAILBOX" +const guestResumeNetworkMailboxTokenEnv = "HYPEMAN_RESUME_NETWORK_MAILBOX_TOKEN" +const firecrackerSnapshotMemoryFile = "memory" + +const guestResumeNetworkMailboxSeqOffset = 64 +const guestResumeNetworkMailboxLengthOffset = 68 +const guestResumeNetworkMailboxPayloadOffset = 72 + +var guestResumeNetworkMailboxMagic = []byte("HYPEMAN_RESUME_NETWORK_MAILBOX_V1\x00") +var guestResumeNetworkMailboxOffsets sync.Map + +type guestResumeNetworkPayload struct { + InterfaceName string `json:"interface_name"` + MAC string `json:"mac"` + IPv4 string `json:"ipv4"` + Prefix uint32 `json:"prefix"` + Gateway string `json:"gateway"` + AckPort uint32 `json:"ack_port,omitempty"` +} + +type guestResumeNetworkUDPAck struct { + received time.Time + text string +} + +type guestResumeNetworkUDPWaiter struct { + conn *stdnet.UDPConn + ch chan guestResumeNetworkUDPAck +} + +func guestInitiatedResumeNetworkMailbox(stored *StoredMetadata) bool { + token := guestInitiatedResumeNetworkMailboxToken(stored) + return stored != nil && + stored.HypervisorType == hypervisor.TypeFirecracker && + strings.TrimSpace(stored.Env[guestResumeNetworkMailboxEnv]) == "1" && + token != "" && + len(token) <= guestResumeNetworkMailboxSeqOffset-len(guestResumeNetworkMailboxMagic) +} + +func guestInitiatedResumeNetworkMailboxToken(stored *StoredMetadata) string { + if stored == nil { + return "" + } + return strings.TrimSpace(stored.Env[guestResumeNetworkMailboxTokenEnv]) +} + +func newGuestResumeNetworkPayload(cfg *guestNetworkConfig) guestResumeNetworkPayload { + return guestResumeNetworkPayload{ + InterfaceName: "eth0", + MAC: cfg.mac, + IPv4: cfg.ip, + Prefix: uint32(cfg.prefix), + Gateway: cfg.gateway, + } +} + +func startGuestResumeNetworkUDPWaiter() (*guestResumeNetworkUDPWaiter, error) { + conn, err := stdnet.ListenUDP("udp4", &stdnet.UDPAddr{IP: stdnet.IPv4zero, Port: 0}) + if err != nil { + return nil, fmt.Errorf("listen for guest resume network UDP ack: %w", err) + } + + w := &guestResumeNetworkUDPWaiter{ + conn: conn, + ch: make(chan guestResumeNetworkUDPAck, 128), + } + go w.readLoop() + return w, nil +} + +func (w *guestResumeNetworkUDPWaiter) Port() uint32 { + if w == nil || w.conn == nil { + return 0 + } + return uint32(w.conn.LocalAddr().(*stdnet.UDPAddr).Port) +} + +func (w *guestResumeNetworkUDPWaiter) Close() { + if w == nil || w.conn == nil { + return + } + _ = w.conn.Close() +} + +func (w *guestResumeNetworkUDPWaiter) readLoop() { + buf := make([]byte, 1024) + for { + n, _, err := w.conn.ReadFromUDP(buf) + if err != nil { + return + } + w.ch <- guestResumeNetworkUDPAck{ + received: time.Now(), + text: strings.TrimSpace(string(buf[:n])), + } + } +} + +func (w *guestResumeNetworkUDPWaiter) WaitApplied(ctx context.Context, mac, ip string) (time.Duration, string, error) { + if w == nil { + return 0, "", fmt.Errorf("guest resume network UDP waiter is nil") + } + + start := time.Now() + wantMAC := "mac=" + strings.ToLower(mac) + wantIP := "ip=" + ip + for { + select { + case ack := <-w.ch: + text := strings.ToLower(ack.text) + if strings.Contains(text, "stage=applied") && strings.Contains(text, wantMAC) && strings.Contains(text, wantIP) { + return ack.received.Sub(start), ack.text, nil + } + case <-ctx.Done(): + return 0, "", ctx.Err() + } + } +} + +func (m *manager) waitForGuestResumeNetworkUDPAck(ctx context.Context, waiter *guestResumeNetworkUDPWaiter, stored *StoredMetadata, cfg *guestNetworkConfig) error { + if waiter == nil || cfg == nil { + return nil + } + + log := logger.FromContext(ctx) + waitCtx, waitSpanEnd := m.startLifecycleStep(ctx, "guest.resume_network.udp_ack_wait", + attribute.String("instance_id", stored.Id), + attribute.String("hypervisor", string(stored.HypervisorType)), + attribute.String("operation", "guest_resume_network_udp_ack_wait"), + ) + waitCtx, cancel := context.WithTimeout(waitCtx, 2*time.Second) + defer cancel() + + elapsed, ack, err := waiter.WaitApplied(waitCtx, cfg.mac, cfg.ip) + waitSpanEnd(err) + if err != nil { + return err + } + log.InfoContext(ctx, "guest resume network UDP ack received", "instance_id", stored.Id, "elapsed", elapsed, "ack", ack) + return nil +} + +func patchGuestResumeNetworkMailbox(snapshotDir, token string, payload *guestResumeNetworkPayload) error { + if token == "" { + return fmt.Errorf("resume network mailbox token is empty") + } + if len(token) > guestResumeNetworkMailboxSeqOffset-len(guestResumeNetworkMailboxMagic) { + return fmt.Errorf("resume network mailbox token is too long") + } + if payload == nil { + return fmt.Errorf("resume network mailbox payload is nil") + } + + payloadBytes, err := json.Marshal(payload) + if err != nil { + return fmt.Errorf("marshal resume network mailbox payload: %w", err) + } + if len(payloadBytes) > 4096-guestResumeNetworkMailboxPayloadOffset { + return fmt.Errorf("resume network mailbox payload too large: %d bytes", len(payloadBytes)) + } + + file, err := os.OpenFile(filepath.Join(snapshotDir, firecrackerSnapshotMemoryFile), os.O_RDWR, 0) + if err != nil { + return fmt.Errorf("open snapshot memory for resume network mailbox: %w", err) + } + defer file.Close() + + info, err := file.Stat() + if err != nil { + return fmt.Errorf("stat snapshot memory for resume network mailbox: %w", err) + } + if info.Size() <= 0 { + return fmt.Errorf("resume network mailbox memory file is empty") + } + + marker := make([]byte, 0, len(guestResumeNetworkMailboxMagic)+len(token)) + marker = append(marker, guestResumeNetworkMailboxMagic...) + marker = append(marker, []byte(token)...) + + idx, err := findGuestResumeNetworkMailbox(file, info.Size(), marker, token) + if err != nil { + return err + } + if idx+int64(guestResumeNetworkMailboxPayloadOffset)+int64(len(payloadBytes)) > info.Size() { + return fmt.Errorf("resume network mailbox marker is too close to end of memory file") + } + + if _, err := file.WriteAt(payloadBytes, idx+int64(guestResumeNetworkMailboxPayloadOffset)); err != nil { + return fmt.Errorf("write resume network mailbox payload: %w", err) + } + var u32 [4]byte + binary.LittleEndian.PutUint32(u32[:], uint32(len(payloadBytes))) + if _, err := file.WriteAt(u32[:], idx+int64(guestResumeNetworkMailboxLengthOffset)); err != nil { + return fmt.Errorf("write resume network mailbox payload length: %w", err) + } + binary.LittleEndian.PutUint32(u32[:], 1) + if _, err := file.WriteAt(u32[:], idx+int64(guestResumeNetworkMailboxSeqOffset)); err != nil { + return fmt.Errorf("write resume network mailbox sequence: %w", err) + } + return nil +} + +func findGuestResumeNetworkMailbox(file *os.File, size int64, marker []byte, token string) (int64, error) { + if cached, ok := guestResumeNetworkMailboxOffsets.Load(token); ok { + if offset, ok := cached.(int64); ok && offset >= 0 && offset+int64(len(marker)) <= size { + buf := make([]byte, len(marker)) + if _, err := file.ReadAt(buf, offset); err == nil && bytes.Equal(buf, marker) { + return offset, nil + } + } + } + + data, err := unix.Mmap(int(file.Fd()), 0, int(size), unix.PROT_READ, unix.MAP_SHARED) + if err != nil { + return 0, fmt.Errorf("mmap snapshot memory for resume network mailbox: %w", err) + } + defer unix.Munmap(data) + + idx := bytes.Index(data, marker) + if idx < 0 { + return 0, fmt.Errorf("resume network mailbox marker not found") + } + guestResumeNetworkMailboxOffsets.Store(token, int64(idx)) + return int64(idx), nil +} diff --git a/lib/instances/manager.go b/lib/instances/manager.go index abdd2165..c55ba93a 100644 --- a/lib/instances/manager.go +++ b/lib/instances/manager.go @@ -389,14 +389,16 @@ func (m *manager) ForkInstance(ctx context.Context, id string, req ForkInstanceR return nil, err } - inst, err := m.applyForkTargetState(ctx, forked.Id, targetState) + inst, err := m.applyForkTargetState(ctx, forked.Id, targetState, restoreInstanceOptions{ + WaitForGuestNetwork: req.WaitForNetwork, + }) if err != nil { if cleanupErr := m.cleanupForkInstanceOnError(ctx, forked.Id); cleanupErr != nil { return nil, fmt.Errorf("apply fork target state: %w; additionally failed to cleanup forked instance %s: %v", err, forked.Id, cleanupErr) } return nil, fmt.Errorf("apply fork target state: %w", err) } - if inst.State == StateRunning { + if inst.State == StateRunning && !guestInitiatedResumeNetworkMailbox(&inst.StoredMetadata) { if err := ensureGuestAgentReadyForForkPhase(ctx, &inst.StoredMetadata, "before returning running fork instance"); err != nil { if cleanupErr := m.cleanupForkInstanceOnError(ctx, forked.Id); cleanupErr != nil { return nil, fmt.Errorf("wait for fork guest agent readiness: %w; additionally failed to cleanup forked instance %s: %v", err, forked.Id, cleanupErr) @@ -449,7 +451,7 @@ func (m *manager) RestoreInstance(ctx context.Context, id string) (*Instance, er if current.State == StateRunning || current.State == StateInitializing { return current, nil } - inst, err := m.restoreInstance(ctx, id) + inst, err := m.restoreInstance(ctx, id, restoreInstanceOptions{}) if err == nil { m.notifyLifecycleEvent(ctx, LifecycleEventRestore, inst) } diff --git a/lib/instances/restore.go b/lib/instances/restore.go index 9f1a3034..714f319e 100644 --- a/lib/instances/restore.go +++ b/lib/instances/restore.go @@ -16,14 +16,24 @@ import ( "github.com/kernel/hypeman/lib/network" snapshotstore "github.com/kernel/hypeman/lib/snapshot" "go.opentelemetry.io/otel/attribute" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) -// RestoreInstance restores an instance from standby -// Multi-hop orchestration: Standby → Paused → Running +type restoreInstanceOptions struct { + WaitForGuestNetwork *bool +} + +func (o restoreInstanceOptions) waitForGuestNetwork() bool { + return o.WaitForGuestNetwork == nil || *o.WaitForGuestNetwork +} + +// RestoreInstance restores an instance from standby. +// Multi-hop orchestration: Standby → Paused → Running. func (m *manager) restoreInstance( ctx context.Context, - id string, + opts restoreInstanceOptions, ) (_ *Instance, retErr error) { start := time.Now() log := logger.FromContext(ctx) @@ -44,6 +54,7 @@ func (m *manager) restoreInstance( inst := m.toInstance(ctx, meta) stored := &meta.StoredMetadata + waitForGuestNetwork := opts.waitForGuestNetwork() ctx = enrichInstancesTrace(ctx, attribute.String("hypervisor", string(stored.HypervisorType))) log.DebugContext(ctx, "loaded instance", "instance_id", id, "state", inst.State, "has_snapshot", inst.HasSnapshot) @@ -227,6 +238,42 @@ func (m *manager) restoreInstance( proxyRegistered = true } + var resumeNetworkAckWaiter *guestResumeNetworkUDPWaiter + var resumeNetworkAckCfg *guestNetworkConfig + resumeNetworkMailboxPatched := false + if allocatedNet != nil && !stored.SkipGuestAgent && guestInitiatedResumeNetworkMailbox(stored) { + resumeNetworkCfg, cfgErr := guestNetworkReconfigureConfig(allocatedNet) + if cfgErr != nil { + log.WarnContext(ctx, "failed to build guest resume network mailbox payload; falling back to host-initiated reconfigure", "instance_id", id, "error", cfgErr) + } else { + payload := newGuestResumeNetworkPayload(resumeNetworkCfg) + if waitForGuestNetwork { + var waitErr error + resumeNetworkAckWaiter, waitErr = startGuestResumeNetworkUDPWaiter() + if waitErr != nil { + log.ErrorContext(ctx, "failed to start guest resume network UDP ack waiter", "instance_id", id, "error", waitErr) + releaseNetwork() + return nil, fmt.Errorf("start guest resume network UDP ack waiter: %w", waitErr) + } + resumeNetworkAckCfg = resumeNetworkCfg + payload.AckPort = resumeNetworkAckWaiter.Port() + } + if patchErr := patchGuestResumeNetworkMailbox(snapshotDir, guestInitiatedResumeNetworkMailboxToken(stored), &payload); patchErr != nil { + if resumeNetworkAckWaiter != nil { + resumeNetworkAckWaiter.Close() + resumeNetworkAckWaiter = nil + resumeNetworkAckCfg = nil + } + log.WarnContext(ctx, "failed to patch guest resume network mailbox; falling back to host-initiated reconfigure", "instance_id", id, "error", patchErr) + } else { + resumeNetworkMailboxPatched = true + } + } + } + if resumeNetworkAckWaiter != nil { + defer resumeNetworkAckWaiter.Close() + } + // 5. Transition: Standby → Paused (start hypervisor + restore) restoreCtx, restoreSpanEnd := m.startLifecycleStep(ctx, "restore_from_snapshot", attribute.String("instance_id", id), @@ -281,15 +328,22 @@ func (m *manager) restoreInstance( attribute.String("hypervisor", string(stored.HypervisorType)), attribute.String("operation", "reconfigure_guest_network"), ) - if err := reconfigureGuestNetwork(reconfigureCtx, stored, allocatedNet); err != nil { - reconfigureSpanEnd(err) - log.ErrorContext(ctx, "failed to configure guest network after restore", "instance_id", id, "error", err) + var reconfigureErr error + if resumeNetworkMailboxPatched && waitForGuestNetwork { + reconfigureErr = m.waitForGuestResumeNetworkUDPAck(reconfigureCtx, resumeNetworkAckWaiter, stored, resumeNetworkAckCfg) + } else if resumeNetworkMailboxPatched { + log.InfoContext(ctx, "guest resume network mailbox patched", "instance_id", id) + } else { + reconfigureErr = reconfigureGuestNetwork(reconfigureCtx, stored, allocatedNet) + } + reconfigureSpanEnd(reconfigureErr) + if reconfigureErr != nil { + log.ErrorContext(ctx, "failed to configure guest network after restore", "instance_id", id, "error", reconfigureErr) _ = hv.Shutdown(ctx) m.rollbackAdmissionAllocationActive(stored) releaseNetwork() - return nil, fmt.Errorf("configure guest network after restore: %w", err) + return nil, fmt.Errorf("configure guest network after restore: %w", reconfigureErr) } - reconfigureSpanEnd(nil) } // 8. Delete snapshot after successful restore unless the hypervisor is keeping it @@ -379,7 +433,7 @@ func (m *manager) restoreFromSnapshot( } func reconfigureGuestNetwork(ctx context.Context, stored *StoredMetadata, alloc *network.Allocation) error { - cmd, err := guestNetworkReconfigureCommand(alloc) + cfg, err := guestNetworkReconfigureConfig(alloc) if err != nil { return err } @@ -389,6 +443,30 @@ func reconfigureGuestNetwork(ctx context.Context, stored *StoredMetadata, alloc return fmt.Errorf("create vsock dialer: %w", err) } + err = guest.ReconfigureNetworkInInstance(ctx, dialer, guest.ReconfigureNetworkOptions{ + InterfaceName: "eth0", + MAC: cfg.mac, + IPv4: cfg.ip, + Prefix: uint32(cfg.prefix), + Gateway: cfg.gateway, + WaitForAgent: 120 * time.Second, + }) + if err != nil { + if status.Code(err) == codes.Unimplemented { + return reconfigureGuestNetworkWithExec(ctx, dialer, alloc) + } + return fmt.Errorf("reconfigure guest network: %w", err) + } + + return nil +} + +func reconfigureGuestNetworkWithExec(ctx context.Context, dialer hypervisor.VsockDialer, alloc *network.Allocation) error { + cmd, err := guestNetworkReconfigureCommand(alloc) + if err != nil { + return err + } + var stdout, stderr bytes.Buffer exit, err := guest.ExecIntoInstance(ctx, dialer, guest.ExecOptions{ Command: []string{"sh", "-c", cmd}, @@ -402,30 +480,44 @@ func reconfigureGuestNetwork(ctx context.Context, stored *StoredMetadata, alloc if exit.Code != 0 { return fmt.Errorf("network reconfiguration command failed (exit=%d, stdout=%q, stderr=%q)", exit.Code, strings.TrimSpace(stdout.String()), strings.TrimSpace(stderr.String())) } - return nil } -func guestNetworkReconfigureCommand(alloc *network.Allocation) (string, error) { +type guestNetworkConfig struct { + ip string + mac string + gateway string + prefix int +} + +func guestNetworkReconfigureConfig(alloc *network.Allocation) (*guestNetworkConfig, error) { if alloc == nil { - return "", fmt.Errorf("missing network allocation") + return nil, fmt.Errorf("missing network allocation") } ip := strings.TrimSpace(alloc.IP) if ip == "" { - return "", fmt.Errorf("missing network allocation IP") + return nil, fmt.Errorf("missing network allocation IP") } mac := strings.ToLower(strings.TrimSpace(alloc.MAC)) if mac == "" { - return "", fmt.Errorf("missing network allocation MAC") + return nil, fmt.Errorf("missing network allocation MAC") } if _, err := net.ParseMAC(mac); err != nil { - return "", fmt.Errorf("invalid network allocation MAC %q: %w", alloc.MAC, err) + return nil, fmt.Errorf("invalid network allocation MAC %q: %w", alloc.MAC, err) } gateway := strings.TrimSpace(alloc.Gateway) if gateway == "" { - return "", fmt.Errorf("missing network allocation gateway") + return nil, fmt.Errorf("missing network allocation gateway") } prefix, err := netmaskToPrefix(alloc.Netmask) + if err != nil { + return nil, err + } + return &guestNetworkConfig{ip: ip, mac: mac, gateway: gateway, prefix: prefix}, nil +} + +func guestNetworkReconfigureCommand(alloc *network.Allocation) (string, error) { + cfg, err := guestNetworkReconfigureConfig(alloc) if err != nil { return "", err } @@ -445,7 +537,7 @@ func guestNetworkReconfigureCommand(alloc *network.Allocation) (string, error) { "ip route replace default via %s dev eth0 && "+ // Drop snapshotted ARP/neighbor entries so peers are rediscovered. "(ip neigh flush dev eth0 || true)", - mac, ip, prefix, gateway, + cfg.mac, cfg.ip, cfg.prefix, cfg.gateway, ), nil } diff --git a/lib/instances/restore_egress_test.go b/lib/instances/restore_egress_test.go index f65b0eb1..b5fcdb4e 100644 --- a/lib/instances/restore_egress_test.go +++ b/lib/instances/restore_egress_test.go @@ -1,6 +1,9 @@ package instances import ( + "encoding/binary" + "encoding/json" + "os" "testing" "github.com/kernel/hypeman/lib/network" @@ -30,7 +33,25 @@ func TestNetworkConfigFromAllocation_PreservesDNS(t *testing.T) { assert.Equal(t, alloc.TAPDevice, cfg.TAPDevice) } -func TestGuestNetworkReconfigureCommand_AppliesAllocatedMAC(t *testing.T) { +func TestGuestNetworkReconfigureConfig_AppliesAllocatedMAC(t *testing.T) { + t.Parallel() + + alloc := &network.Allocation{ + IP: "10.102.146.62", + MAC: "02:00:00:85:17:c8", + Gateway: "10.102.0.1", + Netmask: "255.255.0.0", + } + + cfg, err := guestNetworkReconfigureConfig(alloc) + require.NoError(t, err) + assert.Equal(t, "10.102.146.62", cfg.ip) + assert.Equal(t, "02:00:00:85:17:c8", cfg.mac) + assert.Equal(t, "10.102.0.1", cfg.gateway) + assert.Equal(t, 16, cfg.prefix) +} + +func TestGuestNetworkReconfigureCommand_FallbackPreservesShellBehavior(t *testing.T) { t.Parallel() alloc := &network.Allocation{ @@ -42,18 +63,15 @@ func TestGuestNetworkReconfigureCommand_AppliesAllocatedMAC(t *testing.T) { cmd, err := guestNetworkReconfigureCommand(alloc) require.NoError(t, err) - assert.Contains(t, cmd, "ip link set dev eth0 down") assert.Contains(t, cmd, "ip link set dev eth0 address 02:00:00:85:17:c8") assert.Contains(t, cmd, "ip addr add 10.102.146.62/16 dev eth0") - assert.Contains(t, cmd, "ip route replace default via 10.102.0.1 dev eth0") assert.Contains(t, cmd, "(ip neigh flush dev eth0 || true)") - assert.NotContains(t, cmd, "cat /sys/class/net/eth0/address") } -func TestGuestNetworkReconfigureCommand_RequiresAllocatedMAC(t *testing.T) { +func TestGuestNetworkReconfigureConfig_RequiresAllocatedMAC(t *testing.T) { t.Parallel() - _, err := guestNetworkReconfigureCommand(&network.Allocation{ + _, err := guestNetworkReconfigureConfig(&network.Allocation{ IP: "10.102.146.62", Gateway: "10.102.0.1", Netmask: "255.255.0.0", @@ -62,6 +80,40 @@ func TestGuestNetworkReconfigureCommand_RequiresAllocatedMAC(t *testing.T) { assert.Contains(t, err.Error(), "missing network allocation MAC") } +func TestPatchGuestResumeNetworkMailbox(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + token := "test-token" + mem := make([]byte, 4096) + copy(mem[512:], guestResumeNetworkMailboxMagic) + copy(mem[512+len(guestResumeNetworkMailboxMagic):], token) + require.NoError(t, os.WriteFile(dir+"/"+firecrackerSnapshotMemoryFile, mem, 0644)) + + payload := &guestResumeNetworkPayload{ + InterfaceName: "eth0", + MAC: "02:00:00:85:17:c8", + IPv4: "10.102.146.62", + Prefix: 16, + Gateway: "10.102.0.1", + AckPort: 43210, + } + require.NoError(t, patchGuestResumeNetworkMailbox(dir, token, payload)) + + patched, err := os.ReadFile(dir + "/" + firecrackerSnapshotMemoryFile) + require.NoError(t, err) + + offset := 512 + require.Equal(t, uint32(1), binary.LittleEndian.Uint32(patched[offset+guestResumeNetworkMailboxSeqOffset:])) + payloadLen := binary.LittleEndian.Uint32(patched[offset+guestResumeNetworkMailboxLengthOffset:]) + require.NotZero(t, payloadLen) + + var decoded guestResumeNetworkPayload + err = json.Unmarshal(patched[offset+guestResumeNetworkMailboxPayloadOffset:offset+guestResumeNetworkMailboxPayloadOffset+int(payloadLen)], &decoded) + require.NoError(t, err) + assert.Equal(t, *payload, decoded) +} + func TestRequiresRestoreConfigDiskRefresh(t *testing.T) { t.Parallel() diff --git a/lib/instances/snapshot.go b/lib/instances/snapshot.go index 05d8084e..523917b4 100644 --- a/lib/instances/snapshot.go +++ b/lib/instances/snapshot.go @@ -127,7 +127,7 @@ func (m *manager) createSnapshot(ctx context.Context, id string, req CreateSnaps } if restoreSource { - _, restoreErr := m.restoreInstance(ctx, id) + _, restoreErr := m.restoreInstance(ctx, id, restoreInstanceOptions{}) if restoreErr != nil { if copyErr != nil { return nil, fmt.Errorf("snapshot copy failed: %v; additionally failed to restore source: %w", copyErr, restoreErr) @@ -337,7 +337,7 @@ func (m *manager) restoreSnapshot(ctx context.Context, id string, snapshotID str } return m.getInstance(ctx, id) case StateRunning: - inst, err := m.restoreInstance(ctx, id) + inst, err := m.restoreInstance(ctx, id, restoreInstanceOptions{}) if err != nil { return nil, err } @@ -486,7 +486,9 @@ func (m *manager) forkSnapshot(ctx context.Context, snapshotID string, req ForkS } cu.Release() - inst, err := m.applyForkTargetState(ctx, forkID, targetState) + inst, err := m.applyForkTargetState(ctx, forkID, targetState, restoreInstanceOptions{ + WaitForGuestNetwork: req.WaitForNetwork, + }) if err != nil { if cleanupErr := m.cleanupForkInstanceOnError(ctx, forkID); cleanupErr != nil { return nil, fmt.Errorf("apply snapshot fork target state: %w; additionally failed to cleanup forked instance %s: %v", err, forkID, cleanupErr) diff --git a/lib/instances/types.go b/lib/instances/types.go index 8c031fc9..e38f5499 100644 --- a/lib/instances/types.go +++ b/lib/instances/types.go @@ -268,9 +268,10 @@ type UpdateInstanceRequest struct { // ForkInstanceRequest is the domain request for forking an instance. type ForkInstanceRequest struct { - Name string // Required: name for the new forked instance - FromRunning bool // Optional: allow forking from Running by auto standby/fork/restore - TargetState State // Optional: desired final state of forked instance (Stopped, Standby, Running). Empty means inherit source state. + Name string // Required: name for the new forked instance + FromRunning bool // Optional: allow forking from Running by auto standby/fork/restore + TargetState State // Optional: desired final state of forked instance (Stopped, Standby, Running). Empty means inherit source state. + WaitForNetwork *bool // Optional: wait for guest networking before returning a Running fork. Nil defaults to true. } // SnapshotKind determines how snapshot data is captured and restored. @@ -314,6 +315,7 @@ type ForkSnapshotRequest struct { Name string // Required: name for the new instance TargetState State // Optional TargetHypervisor hypervisor.Type // Optional, allowed only for Stopped snapshots + WaitForNetwork *bool // Optional: wait for guest networking before returning a Running fork. Nil defaults to true. } // SnapshotPolicy defines default snapshot behavior for an instance. diff --git a/lib/oapi/oapi.go b/lib/oapi/oapi.go index 1c053f62..add4df50 100644 --- a/lib/oapi/oapi.go +++ b/lib/oapi/oapi.go @@ -732,6 +732,10 @@ type ForkInstanceRequest struct { // TargetState Target state for the forked instance after fork completes TargetState *ForkTargetState `json:"target_state,omitempty"` + + // WaitForNetwork When the fork result is Running, wait for guest networking to be applied before returning. + // Defaults to true. Set false to return after the VM is resumed while guest networking finishes asynchronously. + WaitForNetwork *bool `json:"wait_for_network,omitempty"` } // ForkSnapshotRequest defines model for ForkSnapshotRequest. @@ -745,6 +749,10 @@ type ForkSnapshotRequest struct { // TargetState Target state when restoring or forking from a snapshot TargetState *SnapshotTargetState `json:"target_state,omitempty"` + + // WaitForNetwork When the fork result is Running, wait for guest networking to be applied before returning. + // Defaults to true. Set false to return after the VM is resumed while guest networking finishes asynchronously. + WaitForNetwork *bool `json:"wait_for_network,omitempty"` } // ForkSnapshotRequestTargetHypervisor Optional hypervisor override. Allowed only when forking from a Stopped snapshot. @@ -15953,213 +15961,215 @@ var swaggerSpec = []string{ "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", + "g+eDztdPX///AAAA//9DeBEen5ABAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/lib/system/guest_agent/main.go b/lib/system/guest_agent/main.go index 46ee3d7a..a968e89a 100644 --- a/lib/system/guest_agent/main.go +++ b/lib/system/guest_agent/main.go @@ -53,8 +53,10 @@ func main() { } // Create gRPC server + guestSvc := &guestServer{} + startResumeNetworkWatcher(guestSvc) grpcServer := grpc.NewServer() - pb.RegisterGuestServiceServer(grpcServer, &guestServer{}) + pb.RegisterGuestServiceServer(grpcServer, guestSvc) // Serve gRPC over vsock if err := grpcServer.Serve(l); err != nil { diff --git a/lib/system/guest_agent/network.go b/lib/system/guest_agent/network.go new file mode 100644 index 00000000..66b71bad --- /dev/null +++ b/lib/system/guest_agent/network.go @@ -0,0 +1,91 @@ +package main + +import ( + "context" + "fmt" + "net" + + pb "github.com/kernel/hypeman/lib/guest" + "github.com/vishvananda/netlink" + "golang.org/x/sys/unix" +) + +func (s *guestServer) ReconfigureNetwork(_ context.Context, req *pb.ReconfigureNetworkRequest) (*pb.ReconfigureNetworkResponse, error) { + iface := req.InterfaceName + if iface == "" { + iface = "eth0" + } + + link, err := netlink.LinkByName(iface) + if err != nil { + return nil, fmt.Errorf("find interface %s: %w", iface, err) + } + + mac, err := net.ParseMAC(req.Mac) + if err != nil { + return nil, fmt.Errorf("parse mac %q: %w", req.Mac, err) + } + ip := net.ParseIP(req.Ipv4).To4() + if ip == nil { + return nil, fmt.Errorf("parse ipv4 %q", req.Ipv4) + } + if req.Prefix > 32 { + return nil, fmt.Errorf("invalid ipv4 prefix %d", req.Prefix) + } + gateway := net.ParseIP(req.Gateway).To4() + if gateway == nil { + return nil, fmt.Errorf("parse gateway %q", req.Gateway) + } + + if err := netlink.LinkSetDown(link); err != nil { + return nil, fmt.Errorf("set %s down: %w", iface, err) + } + if err := netlink.LinkSetHardwareAddr(link, mac); err != nil { + return nil, fmt.Errorf("set %s mac: %w", iface, err) + } + if err := flushIPv4Addrs(link); err != nil { + return nil, err + } + addr := &netlink.Addr{IPNet: &net.IPNet{IP: ip, Mask: net.CIDRMask(int(req.Prefix), 32)}} + if err := netlink.AddrAdd(link, addr); err != nil { + return nil, fmt.Errorf("add ipv4 address: %w", err) + } + if err := netlink.LinkSetUp(link); err != nil { + return nil, fmt.Errorf("set %s up: %w", iface, err) + } + if err := netlink.RouteReplace(&netlink.Route{ + LinkIndex: link.Attrs().Index, + Gw: gateway, + }); err != nil { + return nil, fmt.Errorf("replace default route: %w", err) + } + _ = flushNeighbors(link) + + return &pb.ReconfigureNetworkResponse{}, nil +} + +func flushIPv4Addrs(link netlink.Link) error { + addrs, err := netlink.AddrList(link, unix.AF_INET) + if err != nil { + return fmt.Errorf("list ipv4 addresses: %w", err) + } + for _, addr := range addrs { + if err := netlink.AddrDel(link, &addr); err != nil { + return fmt.Errorf("delete ipv4 address %s: %w", addr.String(), err) + } + } + return nil +} + +func flushNeighbors(link netlink.Link) error { + neighbors, err := netlink.NeighList(link.Attrs().Index, unix.AF_INET) + if err != nil { + return fmt.Errorf("list neighbors: %w", err) + } + for _, neighbor := range neighbors { + if err := netlink.NeighDel(&neighbor); err != nil { + return fmt.Errorf("delete neighbor %s: %w", neighbor.String(), err) + } + } + return nil +} diff --git a/lib/system/guest_agent/resume_network.go b/lib/system/guest_agent/resume_network.go new file mode 100644 index 00000000..060cd687 --- /dev/null +++ b/lib/system/guest_agent/resume_network.go @@ -0,0 +1,201 @@ +//go:build linux + +package main + +import ( + "bufio" + "context" + "encoding/binary" + "encoding/json" + "fmt" + "io" + "log" + "net" + "os" + "strconv" + "strings" + "sync/atomic" + "time" + "unsafe" + + pb "github.com/kernel/hypeman/lib/guest" + "golang.org/x/sys/unix" +) + +const resumeNetworkMailboxEnv = "HYPEMAN_RESUME_NETWORK_MAILBOX" +const resumeNetworkMailboxTokenEnv = "HYPEMAN_RESUME_NETWORK_MAILBOX_TOKEN" +const vmgenIDKmsgSignal = "crng reseeded due to virtual machine fork" +const resumeNetworkMailboxSize = 4096 +const resumeNetworkMailboxSeqOffset = 64 +const resumeNetworkMailboxLengthOffset = 68 +const resumeNetworkMailboxPayloadOffset = 72 + +var resumeNetworkMailboxMagic = []byte("HYPEMAN_RESUME_NETWORK_MAILBOX_V1\x00") + +type resumeNetworkPayload struct { + InterfaceName string `json:"interface_name"` + MAC string `json:"mac"` + IPv4 string `json:"ipv4"` + Prefix uint32 `json:"prefix"` + Gateway string `json:"gateway"` + AckPort uint32 `json:"ack_port,omitempty"` +} + +type vmGenIDResumeWaiter struct { + file *os.File + reader *bufio.Reader +} + +func startResumeNetworkWatcher(s *guestServer) { + if strings.TrimSpace(os.Getenv(resumeNetworkMailboxEnv)) != "1" { + return + } + + mailbox := newResumeNetworkMailbox() + if mailbox == nil { + return + } + + go resumeNetworkMailboxLoop(s, mailbox) +} + +func newResumeNetworkMailbox() []byte { + token := strings.TrimSpace(os.Getenv(resumeNetworkMailboxTokenEnv)) + if token == "" { + log.Printf("[guest-agent] resume network mailbox disabled: missing %s", resumeNetworkMailboxTokenEnv) + return nil + } + if len(token) > resumeNetworkMailboxSeqOffset-len(resumeNetworkMailboxMagic) { + log.Printf("[guest-agent] resume network mailbox disabled: %s is too long", resumeNetworkMailboxTokenEnv) + return nil + } + + buf := make([]byte, resumeNetworkMailboxSize) + copy(buf, resumeNetworkMailboxMagic) + copy(buf[len(resumeNetworkMailboxMagic):resumeNetworkMailboxSeqOffset], token) + if err := unix.Mlock(buf); err != nil { + log.Printf("[guest-agent] resume network mailbox mlock failed: %v", err) + } + log.Printf("[guest-agent] resume network mailbox armed token=%s", token) + return buf +} + +func resumeNetworkMailboxLoop(s *guestServer, mailbox []byte) { + for { + waiter, err := newVMGenIDResumeWaiter() + if err != nil { + log.Printf("[guest-agent] resume network VMGenID prepare failed: %v", err) + time.Sleep(100 * time.Millisecond) + continue + } + + start := time.Now() + if err := waiter.Wait(); err != nil { + waiter.Close() + log.Printf("[guest-agent] resume network VMGenID wait failed: %v", err) + time.Sleep(100 * time.Millisecond) + continue + } + waiter.Close() + + if err := waitAndApplyResumeNetworkMailbox(s, mailbox); err != nil { + log.Printf("[guest-agent] resume network mailbox apply failed: %v", err) + time.Sleep(25 * time.Millisecond) + continue + } + log.Printf("[guest-agent] resume network mailbox applied in %s", time.Since(start)) + } +} + +func waitAndApplyResumeNetworkMailbox(s *guestServer, buf []byte) error { + for { + seq := atomicLoadUint32(buf[resumeNetworkMailboxSeqOffset:]) + if seq == 0 { + time.Sleep(100 * time.Microsecond) + continue + } + + payloadLen := binary.LittleEndian.Uint32(buf[resumeNetworkMailboxLengthOffset:]) + if payloadLen == 0 || int(payloadLen) > len(buf)-resumeNetworkMailboxPayloadOffset { + return fmt.Errorf("invalid mailbox payload length %d", payloadLen) + } + + var payload resumeNetworkPayload + if err := json.Unmarshal(buf[resumeNetworkMailboxPayloadOffset:resumeNetworkMailboxPayloadOffset+int(payloadLen)], &payload); err != nil { + return fmt.Errorf("decode mailbox payload: %w", err) + } + + _, err := s.ReconfigureNetwork(context.Background(), &pb.ReconfigureNetworkRequest{ + InterfaceName: payload.InterfaceName, + Mac: payload.MAC, + Ipv4: payload.IPv4, + Prefix: payload.Prefix, + Gateway: payload.Gateway, + }) + if err != nil { + return err + } + sendResumeNetworkAck(payload, "applied") + atomicStoreUint32(buf[resumeNetworkMailboxSeqOffset:], 0) + return nil + } +} + +func sendResumeNetworkAck(payload resumeNetworkPayload, stage string) { + if payload.AckPort == 0 || payload.Gateway == "" { + return + } + + addr := net.JoinHostPort(payload.Gateway, strconv.FormatUint(uint64(payload.AckPort), 10)) + conn, err := net.DialTimeout("udp4", addr, 100*time.Millisecond) + if err != nil { + log.Printf("[guest-agent] resume network ack dial failed: %v", err) + return + } + defer conn.Close() + + _, _ = fmt.Fprintf(conn, "stage=%s mac=%s ip=%s\n", stage, payload.MAC, payload.IPv4) +} + +func atomicLoadUint32(buf []byte) uint32 { + return atomic.LoadUint32((*uint32)(unsafe.Pointer(&buf[0]))) +} + +func atomicStoreUint32(buf []byte, value uint32) { + atomic.StoreUint32((*uint32)(unsafe.Pointer(&buf[0])), value) +} + +func newVMGenIDResumeWaiter() (*vmGenIDResumeWaiter, error) { + f, err := os.Open("/dev/kmsg") + if err != nil { + return nil, fmt.Errorf("open /dev/kmsg: %w", err) + } + + if _, err := f.Seek(0, io.SeekEnd); err != nil { + log.Printf("[guest-agent] warning: failed to seek /dev/kmsg to end: %v", err) + } + + return &vmGenIDResumeWaiter{ + file: f, + reader: bufio.NewReader(f), + }, nil +} + +func (w *vmGenIDResumeWaiter) Close() { + if w == nil || w.file == nil { + return + } + _ = w.file.Close() +} + +func (w *vmGenIDResumeWaiter) Wait() error { + for { + line, err := w.reader.ReadString('\n') + if err != nil { + return fmt.Errorf("read /dev/kmsg: %w", err) + } + if strings.Contains(line, vmgenIDKmsgSignal) { + return nil + } + } +} diff --git a/lib/system/guest_agent/resume_network_other.go b/lib/system/guest_agent/resume_network_other.go new file mode 100644 index 00000000..dfa74e07 --- /dev/null +++ b/lib/system/guest_agent/resume_network_other.go @@ -0,0 +1,7 @@ +//go:build !linux + +package main + +type resumeNetworkController struct{} + +func startResumeNetworkWatcher(_ *guestServer) {} diff --git a/lib/system/versions.go b/lib/system/versions.go index aa07108d..7743add0 100644 --- a/lib/system/versions.go +++ b/lib/system/versions.go @@ -20,14 +20,18 @@ const ( // Kernel_202603301 is the current kernel version with expanded nftables/raw support for Docker bridge networking Kernel_202603301 KernelVersion = "ch-6.12.8-kernel-1.6-202603301" + + // Kernel_202605291 is the current kernel version with VMGenID support for snapshot resume detection + Kernel_202605291 KernelVersion = "ch-6.12.8-kernel-3.0-202605291" ) var ( // DefaultKernelVersion is the kernel version used for new instances - DefaultKernelVersion = Kernel_202603301 + DefaultKernelVersion = Kernel_202605291 // SupportedKernelVersions lists all supported kernel versions SupportedKernelVersions = []KernelVersion{ + Kernel_202605291, Kernel_202603301, Kernel_202603091, Kernel_202602101, @@ -37,6 +41,10 @@ var ( // KernelDownloadURLs maps kernel versions and architectures to download URLs var KernelDownloadURLs = map[KernelVersion]map[string]string{ + Kernel_202605291: { + "x86_64": "https://github.com/kernel/linux/releases/download/ch-6.12.8-kernel-3.0-202605291/vmlinux-x86_64", + "aarch64": "https://github.com/kernel/linux/releases/download/ch-6.12.8-kernel-3.0-202605291/Image-arm64", + }, Kernel_202603301: { "x86_64": "https://github.com/kernel/linux/releases/download/ch-6.12.8-kernel-1.6-202603301/vmlinux-x86_64", "aarch64": "https://github.com/kernel/linux/releases/download/ch-6.12.8-kernel-1.6-202603301/Image-arm64", @@ -58,6 +66,10 @@ var KernelDownloadURLs = map[KernelVersion]map[string]string{ // KernelHeaderURLs maps kernel versions and architectures to kernel header tarball URLs // These tarballs contain kernel headers needed for DKMS to build out-of-tree modules (e.g., NVIDIA vGPU drivers) var KernelHeaderURLs = map[KernelVersion]map[string]string{ + Kernel_202605291: { + "x86_64": "https://github.com/kernel/linux/releases/download/ch-6.12.8-kernel-3.0-202605291/kernel-headers-x86_64.tar.gz", + "aarch64": "https://github.com/kernel/linux/releases/download/ch-6.12.8-kernel-3.0-202605291/kernel-headers-aarch64.tar.gz", + }, Kernel_202603301: { "x86_64": "https://github.com/kernel/linux/releases/download/ch-6.12.8-kernel-1.6-202603301/kernel-headers-x86_64.tar.gz", "aarch64": "https://github.com/kernel/linux/releases/download/ch-6.12.8-kernel-1.6-202603301/kernel-headers-aarch64.tar.gz", diff --git a/openapi.yaml b/openapi.yaml index c6bce872..e6d03230 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -690,6 +690,13 @@ components: Optional final state for the forked instance. Default is the source instance state at fork time. For example, forking from Running defaults the fork result to Running. + wait_for_network: + type: boolean + description: | + When the fork result is Running, wait for guest networking to be applied before returning. + Defaults to true. Set false to return after the VM is resumed while guest networking finishes asynchronously. + default: true + example: true ForkTargetState: type: string @@ -875,6 +882,13 @@ components: Optional hypervisor override. Allowed only when forking from a Stopped snapshot. Standby snapshots must fork with their original hypervisor. example: cloud-hypervisor + wait_for_network: + type: boolean + description: | + When the fork result is Running, wait for guest networking to be applied before returning. + Defaults to true. Set false to return after the VM is resumed while guest networking finishes asynchronously. + default: true + example: true SnapshotScheduleRetention: type: object From 077e68f17d67562f81b81fdd5ca6dc713ae71f78 Mon Sep 17 00:00:00 2001 From: sjmiller609 <7516283+sjmiller609@users.noreply.github.com> Date: Mon, 1 Jun 2026 12:37:13 +0000 Subject: [PATCH 02/15] Add fork mailbox payload API --- cmd/api/api/fork_mailbox.go | 37 ++ cmd/api/api/instances.go | 8 + cmd/api/api/instances_test.go | 15 + cmd/api/api/snapshots.go | 5 + cmd/api/api/snapshots_test.go | 15 + lib/instances/fork.go | 12 + lib/instances/fork_mailbox.go | 267 +++++++++++ lib/instances/fork_mailbox_test.go | 40 ++ lib/instances/guest_resume_network.go | 20 + lib/instances/manager.go | 1 + lib/instances/restore.go | 16 + lib/instances/snapshot.go | 12 + lib/instances/types.go | 13 + lib/oapi/oapi.go | 628 ++++++++++++++------------ openapi.yaml | 51 +++ 15 files changed, 845 insertions(+), 295 deletions(-) create mode 100644 cmd/api/api/fork_mailbox.go create mode 100644 lib/instances/fork_mailbox.go create mode 100644 lib/instances/fork_mailbox_test.go diff --git a/cmd/api/api/fork_mailbox.go b/cmd/api/api/fork_mailbox.go new file mode 100644 index 00000000..b91560b8 --- /dev/null +++ b/cmd/api/api/fork_mailbox.go @@ -0,0 +1,37 @@ +package api + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/kernel/hypeman/lib/instances" + "github.com/kernel/hypeman/lib/oapi" +) + +func toDomainForkMailboxes(mailboxes *[]oapi.ForkMailboxPayload) ([]instances.ForkMailboxPayload, error) { + if mailboxes == nil || len(*mailboxes) == 0 { + return nil, nil + } + + out := make([]instances.ForkMailboxPayload, 0, len(*mailboxes)) + for _, mailbox := range *mailboxes { + payload, err := json.Marshal(mailbox.Payload) + if err != nil { + return nil, fmt.Errorf("marshal mailbox %q payload: %w", mailbox.Name, err) + } + waitForAck := mailbox.WaitForAck != nil && *mailbox.WaitForAck + var ackTimeout time.Duration + if mailbox.AckTimeoutMs != nil { + ackTimeout = time.Duration(*mailbox.AckTimeoutMs) * time.Millisecond + } + out = append(out, instances.ForkMailboxPayload{ + Name: mailbox.Name, + Token: mailbox.Token, + Payload: payload, + WaitForAck: waitForAck, + AckTimeout: ackTimeout, + }) + } + return out, nil +} diff --git a/cmd/api/api/instances.go b/cmd/api/api/instances.go index 9d59515d..d30b6933 100644 --- a/cmd/api/api/instances.go +++ b/cmd/api/api/instances.go @@ -655,12 +655,20 @@ func (s *ApiService) ForkInstance(ctx context.Context, request oapi.ForkInstance if request.Body.TargetState != nil { targetState = instances.State(*request.Body.TargetState) } + mailboxes, err := toDomainForkMailboxes(request.Body.Mailboxes) + if err != nil { + return oapi.ForkInstance400JSONResponse{ + Code: "invalid_request", + Message: err.Error(), + }, nil + } result, err := s.InstanceManager.ForkInstance(ctx, inst.Id, instances.ForkInstanceRequest{ Name: request.Body.Name, FromRunning: request.Body.FromRunning != nil && *request.Body.FromRunning, TargetState: targetState, WaitForNetwork: request.Body.WaitForNetwork, + Mailboxes: mailboxes, }) if err != nil { switch { diff --git a/cmd/api/api/instances_test.go b/cmd/api/api/instances_test.go index 896a0632..066c3fba 100644 --- a/cmd/api/api/instances_test.go +++ b/cmd/api/api/instances_test.go @@ -1166,6 +1166,8 @@ func TestForkInstance_Success(t *testing.T) { } svc.InstanceManager = mockMgr waitForNetwork := false + waitForAck := true + ackTimeoutMS := 1500 resp, err := svc.ForkInstance( mw.WithResolvedInstance(ctx(), source.Id, source), @@ -1174,6 +1176,13 @@ func TestForkInstance_Success(t *testing.T) { Body: &oapi.ForkInstanceRequest{ Name: "forked-instance", WaitForNetwork: &waitForNetwork, + Mailboxes: &[]oapi.ForkMailboxPayload{{ + Name: "kernel.identity.v1", + Token: "template-token", + Payload: map[string]interface{}{"instance_name": "forked-instance"}, + WaitForAck: &waitForAck, + AckTimeoutMs: &ackTimeoutMS, + }}, }, }, ) @@ -1189,6 +1198,12 @@ func TestForkInstance_Success(t *testing.T) { assert.Equal(t, instances.State(""), mockMgr.lastReq.TargetState) require.NotNil(t, mockMgr.lastReq.WaitForNetwork) assert.False(t, *mockMgr.lastReq.WaitForNetwork) + require.Len(t, mockMgr.lastReq.Mailboxes, 1) + assert.Equal(t, "kernel.identity.v1", mockMgr.lastReq.Mailboxes[0].Name) + assert.Equal(t, "template-token", mockMgr.lastReq.Mailboxes[0].Token) + assert.True(t, mockMgr.lastReq.Mailboxes[0].WaitForAck) + assert.Equal(t, 1500*time.Millisecond, mockMgr.lastReq.Mailboxes[0].AckTimeout) + assert.JSONEq(t, `{"instance_name":"forked-instance"}`, string(mockMgr.lastReq.Mailboxes[0].Payload)) } func TestForkInstance_NotSupported(t *testing.T) { diff --git a/cmd/api/api/snapshots.go b/cmd/api/api/snapshots.go index ffe35474..e0d9b379 100644 --- a/cmd/api/api/snapshots.go +++ b/cmd/api/api/snapshots.go @@ -179,6 +179,11 @@ func (s *ApiService) ForkSnapshot(ctx context.Context, request oapi.ForkSnapshot Name: request.Body.Name, WaitForNetwork: request.Body.WaitForNetwork, } + mailboxes, err := toDomainForkMailboxes(request.Body.Mailboxes) + if err != nil { + return oapi.ForkSnapshot400JSONResponse{Code: "invalid_request", Message: err.Error()}, nil + } + domainReq.Mailboxes = mailboxes if request.Body.TargetState != nil { domainReq.TargetState = instances.State(*request.Body.TargetState) } diff --git a/cmd/api/api/snapshots_test.go b/cmd/api/api/snapshots_test.go index f87e9148..c1289b7d 100644 --- a/cmd/api/api/snapshots_test.go +++ b/cmd/api/api/snapshots_test.go @@ -72,12 +72,21 @@ func TestForkSnapshotMapsWaitForNetwork(t *testing.T) { } svc.InstanceManager = mockMgr waitForNetwork := false + waitForAck := true + ackTimeoutMS := 2500 resp, err := svc.ForkSnapshot(ctx(), oapi.ForkSnapshotRequestObject{ SnapshotId: "snap-123", Body: &oapi.ForkSnapshotRequest{ Name: "forked-instance", WaitForNetwork: &waitForNetwork, + Mailboxes: &[]oapi.ForkMailboxPayload{{ + Name: "kernel.identity.v1", + Token: "template-token", + Payload: map[string]interface{}{"instance_name": "forked-instance"}, + WaitForAck: &waitForAck, + AckTimeoutMs: &ackTimeoutMS, + }}, }, }) require.NoError(t, err) @@ -90,4 +99,10 @@ func TestForkSnapshotMapsWaitForNetwork(t *testing.T) { assert.Equal(t, "forked-instance", mockMgr.lastReq.Name) require.NotNil(t, mockMgr.lastReq.WaitForNetwork) assert.False(t, *mockMgr.lastReq.WaitForNetwork) + require.Len(t, mockMgr.lastReq.Mailboxes, 1) + assert.Equal(t, "kernel.identity.v1", mockMgr.lastReq.Mailboxes[0].Name) + assert.Equal(t, "template-token", mockMgr.lastReq.Mailboxes[0].Token) + assert.True(t, mockMgr.lastReq.Mailboxes[0].WaitForAck) + assert.Equal(t, 2500*time.Millisecond, mockMgr.lastReq.Mailboxes[0].AckTimeout) + assert.JSONEq(t, `{"instance_name":"forked-instance"}`, string(mockMgr.lastReq.Mailboxes[0].Payload)) } diff --git a/lib/instances/fork.go b/lib/instances/fork.go index 2c61fa25..66cea345 100644 --- a/lib/instances/fork.go +++ b/lib/instances/fork.go @@ -47,6 +47,14 @@ func (m *manager) forkInstance(ctx context.Context, id string, req ForkInstanceR if err != nil { return nil, "", err } + if len(req.Mailboxes) > 0 { + if source.State == StateStopped { + return nil, "", fmt.Errorf("%w: mailboxes require a standby snapshot fork", ErrInvalidRequest) + } + if targetState != StateRunning { + return nil, "", fmt.Errorf("%w: mailboxes require target_state %s", ErrInvalidRequest, StateRunning) + } + } switch source.State { case StateRunning: @@ -83,6 +91,7 @@ func (m *manager) forkInstance(ctx context.Context, id string, req ForkInstanceR if forkErr == nil && targetState == StateRunning { restoredFork, err := m.applyForkTargetState(ctx, forked.Id, StateRunning, restoreInstanceOptions{ WaitForGuestNetwork: req.WaitForNetwork, + Mailboxes: req.Mailboxes, }) if err != nil { forkErr = fmt.Errorf("restore forked instance before source restore: %w", err) @@ -379,6 +388,9 @@ func validateForkRequest(req ForkInstanceRequest) error { if req.TargetState != "" && req.TargetState != StateStopped && req.TargetState != StateStandby && req.TargetState != StateRunning { return fmt.Errorf("%w: invalid fork target state %q (must be one of %s, %s, %s)", ErrInvalidRequest, req.TargetState, StateStopped, StateStandby, StateRunning) } + if err := validateForkMailboxes(req.Mailboxes); err != nil { + return err + } return nil } diff --git a/lib/instances/fork_mailbox.go b/lib/instances/fork_mailbox.go new file mode 100644 index 00000000..044d508f --- /dev/null +++ b/lib/instances/fork_mailbox.go @@ -0,0 +1,267 @@ +package instances + +import ( + "bytes" + "context" + "encoding/binary" + "encoding/json" + "fmt" + "os" + "path/filepath" + "regexp" + "strings" + "sync" + "time" + + "github.com/kernel/hypeman/lib/logger" + "go.opentelemetry.io/otel/attribute" + "golang.org/x/sys/unix" +) + +const ( + forkMailboxSeqOffset = 256 + forkMailboxLengthOffset = 260 + forkMailboxPayloadOffset = 264 + forkMailboxPayloadSize = 4096 - forkMailboxPayloadOffset +) + +var ( + forkMailboxMagic = []byte("HYPEMAN_FORK_MAILBOX_V1\x00") + forkMailboxNamePattern = regexp.MustCompile(`^[A-Za-z0-9._:-]{1,64}$`) + forkMailboxOffsetByMark sync.Map +) + +type patchedForkMailbox struct { + name string + waiter *guestResumeNetworkUDPWaiter + timeout time.Duration +} + +func validateForkMailboxes(mailboxes []ForkMailboxPayload) error { + if len(mailboxes) > 16 { + return fmt.Errorf("%w: at most 16 mailboxes can be patched for a fork", ErrInvalidRequest) + } + seen := make(map[string]struct{}, len(mailboxes)) + for _, mailbox := range mailboxes { + name := strings.TrimSpace(mailbox.Name) + if !forkMailboxNamePattern.MatchString(name) { + return fmt.Errorf("%w: invalid mailbox name %q", ErrInvalidRequest, mailbox.Name) + } + if _, ok := seen[name]; ok { + return fmt.Errorf("%w: duplicate mailbox name %q", ErrInvalidRequest, name) + } + seen[name] = struct{}{} + + if strings.TrimSpace(mailbox.Token) == "" { + return fmt.Errorf("%w: mailbox %q token is required", ErrInvalidRequest, name) + } + if len(mailbox.Token) > 128 { + return fmt.Errorf("%w: mailbox %q token is too long", ErrInvalidRequest, name) + } + if len(forkMailboxMarker(name, mailbox.Token)) > forkMailboxSeqOffset { + return fmt.Errorf("%w: mailbox %q marker is too long", ErrInvalidRequest, name) + } + if len(mailbox.Payload) == 0 { + return fmt.Errorf("%w: mailbox %q payload is required", ErrInvalidRequest, name) + } + if len(mailbox.Payload) > forkMailboxPayloadSize { + return fmt.Errorf("%w: mailbox %q payload is too large", ErrInvalidRequest, name) + } + var payload map[string]any + if err := json.Unmarshal(mailbox.Payload, &payload); err != nil || payload == nil { + return fmt.Errorf("%w: mailbox %q payload must be a JSON object", ErrInvalidRequest, name) + } + if mailbox.WaitForAck && mailbox.AckTimeout < 0 { + return fmt.Errorf("%w: mailbox %q ack_timeout_ms must be positive", ErrInvalidRequest, name) + } + if mailbox.AckTimeout > 30*time.Second { + return fmt.Errorf("%w: mailbox %q ack_timeout_ms must be 30000 or less", ErrInvalidRequest, name) + } + } + return nil +} + +func (m *manager) patchForkMailboxes(ctx context.Context, snapshotDir string, mailboxes []ForkMailboxPayload) ([]patchedForkMailbox, error) { + if len(mailboxes) == 0 { + return nil, nil + } + + patched := make([]patchedForkMailbox, 0, len(mailboxes)) + for _, mailbox := range mailboxes { + payload := mailbox.Payload + var waiter *guestResumeNetworkUDPWaiter + if mailbox.WaitForAck { + var err error + waiter, err = startGuestResumeNetworkUDPWaiter() + if err != nil { + closePatchedForkMailboxes(patched) + return nil, fmt.Errorf("start mailbox %q UDP ack waiter: %w", mailbox.Name, err) + } + payload, err = forkMailboxPayloadWithAckPort(payload, waiter.Port()) + if err != nil { + waiter.Close() + closePatchedForkMailboxes(patched) + return nil, err + } + } + + if err := patchForkMailbox(snapshotDir, mailbox.Name, mailbox.Token, payload); err != nil { + if waiter != nil { + waiter.Close() + } + closePatchedForkMailboxes(patched) + return nil, err + } + patched = append(patched, patchedForkMailbox{ + name: mailbox.Name, + waiter: waiter, + timeout: forkMailboxAckTimeout(mailbox.AckTimeout), + }) + } + return patched, nil +} + +func forkMailboxAckTimeout(timeout time.Duration) time.Duration { + if timeout > 0 { + return timeout + } + return 2 * time.Second +} + +func closePatchedForkMailboxes(patched []patchedForkMailbox) { + for _, mailbox := range patched { + if mailbox.waiter != nil { + mailbox.waiter.Close() + } + } +} + +func (m *manager) waitForForkMailboxAcks(ctx context.Context, stored *StoredMetadata, patched []patchedForkMailbox) error { + log := logger.FromContext(ctx) + for _, mailbox := range patched { + if mailbox.waiter == nil { + continue + } + + waitCtx, waitSpanEnd := m.startLifecycleStep(ctx, "guest.fork_mailbox.udp_ack_wait", + attribute.String("instance_id", stored.Id), + attribute.String("mailbox", mailbox.name), + attribute.String("operation", "guest_fork_mailbox_udp_ack_wait"), + ) + waitCtx, cancel := context.WithTimeout(waitCtx, mailbox.timeout) + elapsed, ack, err := mailbox.waiter.WaitMailboxApplied(waitCtx, mailbox.name) + cancel() + waitSpanEnd(err) + if err != nil { + return fmt.Errorf("wait for mailbox %q ack: %w", mailbox.name, err) + } + log.InfoContext(ctx, "guest fork mailbox UDP ack received", "instance_id", stored.Id, "mailbox", mailbox.name, "elapsed", elapsed, "ack", ack) + } + return nil +} + +func forkMailboxPayloadWithAckPort(payload json.RawMessage, port uint32) (json.RawMessage, error) { + var obj map[string]any + if err := json.Unmarshal(payload, &obj); err != nil { + return nil, fmt.Errorf("unmarshal mailbox payload for ack injection: %w", err) + } + if obj == nil { + return nil, fmt.Errorf("%w: mailbox payload must be a JSON object when wait_for_ack is true", ErrInvalidRequest) + } + obj["ack_port"] = port + out, err := json.Marshal(obj) + if err != nil { + return nil, fmt.Errorf("marshal mailbox payload with ack port: %w", err) + } + if len(out) > forkMailboxPayloadSize { + return nil, fmt.Errorf("%w: mailbox payload is too large after ack_port injection", ErrInvalidRequest) + } + return out, nil +} + +func patchForkMailbox(snapshotDir, name, token string, payload []byte) error { + if !forkMailboxNamePattern.MatchString(name) { + return fmt.Errorf("invalid mailbox name %q", name) + } + if token == "" { + return fmt.Errorf("mailbox %q token is empty", name) + } + if len(payload) > forkMailboxPayloadSize { + return fmt.Errorf("mailbox %q payload too large: %d bytes", name, len(payload)) + } + + marker := forkMailboxMarker(name, token) + if len(marker) > forkMailboxSeqOffset { + return fmt.Errorf("mailbox %q marker is too long", name) + } + + file, err := os.OpenFile(filepath.Join(snapshotDir, firecrackerSnapshotMemoryFile), os.O_RDWR, 0) + if err != nil { + return fmt.Errorf("open snapshot memory for mailbox %q: %w", name, err) + } + defer file.Close() + + info, err := file.Stat() + if err != nil { + return fmt.Errorf("stat snapshot memory for mailbox %q: %w", name, err) + } + if info.Size() <= 0 { + return fmt.Errorf("mailbox %q memory file is empty", name) + } + + idx, err := findForkMailbox(file, info.Size(), marker) + if err != nil { + return fmt.Errorf("find mailbox %q marker: %w", name, err) + } + if idx+int64(forkMailboxPayloadOffset)+int64(len(payload)) > info.Size() { + return fmt.Errorf("mailbox %q marker is too close to end of memory file", name) + } + + if _, err := file.WriteAt(payload, idx+int64(forkMailboxPayloadOffset)); err != nil { + return fmt.Errorf("write mailbox %q payload: %w", name, err) + } + var u32 [4]byte + binary.LittleEndian.PutUint32(u32[:], uint32(len(payload))) + if _, err := file.WriteAt(u32[:], idx+int64(forkMailboxLengthOffset)); err != nil { + return fmt.Errorf("write mailbox %q payload length: %w", name, err) + } + binary.LittleEndian.PutUint32(u32[:], 1) + if _, err := file.WriteAt(u32[:], idx+int64(forkMailboxSeqOffset)); err != nil { + return fmt.Errorf("write mailbox %q sequence: %w", name, err) + } + return nil +} + +func forkMailboxMarker(name, token string) []byte { + marker := make([]byte, 0, len(forkMailboxMagic)+len(name)+1+len(token)) + marker = append(marker, forkMailboxMagic...) + marker = append(marker, []byte(name)...) + marker = append(marker, 0) + marker = append(marker, []byte(token)...) + return marker +} + +func findForkMailbox(file *os.File, size int64, marker []byte) (int64, error) { + cacheKey := string(marker) + if cached, ok := forkMailboxOffsetByMark.Load(cacheKey); ok { + if offset, ok := cached.(int64); ok && offset >= 0 && offset+int64(len(marker)) <= size { + buf := make([]byte, len(marker)) + if _, err := file.ReadAt(buf, offset); err == nil && bytes.Equal(buf, marker) { + return offset, nil + } + } + } + + data, err := unix.Mmap(int(file.Fd()), 0, int(size), unix.PROT_READ, unix.MAP_SHARED) + if err != nil { + return 0, fmt.Errorf("mmap snapshot memory: %w", err) + } + defer unix.Munmap(data) + + idx := bytes.Index(data, marker) + if idx < 0 { + return 0, fmt.Errorf("marker not found") + } + forkMailboxOffsetByMark.Store(cacheKey, int64(idx)) + return int64(idx), nil +} diff --git a/lib/instances/fork_mailbox_test.go b/lib/instances/fork_mailbox_test.go new file mode 100644 index 00000000..be75fe74 --- /dev/null +++ b/lib/instances/fork_mailbox_test.go @@ -0,0 +1,40 @@ +package instances + +import ( + "encoding/binary" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestPatchForkMailbox(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + memoryPath := filepath.Join(dir, firecrackerSnapshotMemoryFile) + memory := make([]byte, 8192) + marker := forkMailboxMarker("kernel.identity.v1", "template-token") + const offset = 1024 + copy(memory[offset:], marker) + require.NoError(t, os.WriteFile(memoryPath, memory, 0600)) + + require.NoError(t, patchForkMailbox(dir, "kernel.identity.v1", "template-token", []byte(`{"instance_name":"forked"}`))) + + updated, err := os.ReadFile(memoryPath) + require.NoError(t, err) + assert.Equal(t, uint32(1), binary.LittleEndian.Uint32(updated[offset+forkMailboxSeqOffset:])) + payloadLen := binary.LittleEndian.Uint32(updated[offset+forkMailboxLengthOffset:]) + assert.Equal(t, uint32(len(`{"instance_name":"forked"}`)), payloadLen) + assert.Equal(t, `{"instance_name":"forked"}`, string(updated[offset+forkMailboxPayloadOffset:offset+forkMailboxPayloadOffset+int(payloadLen)])) +} + +func TestForkMailboxPayloadWithAckPort(t *testing.T) { + t.Parallel() + + payload, err := forkMailboxPayloadWithAckPort([]byte(`{"instance_name":"forked"}`), 12345) + require.NoError(t, err) + assert.JSONEq(t, `{"instance_name":"forked","ack_port":12345}`, string(payload)) +} diff --git a/lib/instances/guest_resume_network.go b/lib/instances/guest_resume_network.go index 354657b0..82c23caf 100644 --- a/lib/instances/guest_resume_network.go +++ b/lib/instances/guest_resume_network.go @@ -138,6 +138,26 @@ func (w *guestResumeNetworkUDPWaiter) WaitApplied(ctx context.Context, mac, ip s } } +func (w *guestResumeNetworkUDPWaiter) WaitMailboxApplied(ctx context.Context, name string) (time.Duration, string, error) { + if w == nil { + return 0, "", fmt.Errorf("guest fork mailbox UDP waiter is nil") + } + + start := time.Now() + wantMailbox := "mailbox=" + strings.ToLower(name) + for { + select { + case ack := <-w.ch: + text := strings.ToLower(ack.text) + if strings.Contains(text, "stage=applied") && strings.Contains(text, wantMailbox) { + return ack.received.Sub(start), ack.text, nil + } + case <-ctx.Done(): + return 0, "", ctx.Err() + } + } +} + func (m *manager) waitForGuestResumeNetworkUDPAck(ctx context.Context, waiter *guestResumeNetworkUDPWaiter, stored *StoredMetadata, cfg *guestNetworkConfig) error { if waiter == nil || cfg == nil { return nil diff --git a/lib/instances/manager.go b/lib/instances/manager.go index c55ba93a..bb6cbe1e 100644 --- a/lib/instances/manager.go +++ b/lib/instances/manager.go @@ -391,6 +391,7 @@ func (m *manager) ForkInstance(ctx context.Context, id string, req ForkInstanceR inst, err := m.applyForkTargetState(ctx, forked.Id, targetState, restoreInstanceOptions{ WaitForGuestNetwork: req.WaitForNetwork, + Mailboxes: req.Mailboxes, }) if err != nil { if cleanupErr := m.cleanupForkInstanceOnError(ctx, forked.Id); cleanupErr != nil { diff --git a/lib/instances/restore.go b/lib/instances/restore.go index 714f319e..8bb4c4bc 100644 --- a/lib/instances/restore.go +++ b/lib/instances/restore.go @@ -22,6 +22,7 @@ import ( type restoreInstanceOptions struct { WaitForGuestNetwork *bool + Mailboxes []ForkMailboxPayload } func (o restoreInstanceOptions) waitForGuestNetwork() bool { @@ -274,6 +275,13 @@ func (m *manager) restoreInstance( defer resumeNetworkAckWaiter.Close() } + patchedMailboxes, err := m.patchForkMailboxes(ctx, snapshotDir, opts.Mailboxes) + if err != nil { + releaseNetwork() + return nil, fmt.Errorf("patch fork mailboxes: %w", err) + } + defer closePatchedForkMailboxes(patchedMailboxes) + // 5. Transition: Standby → Paused (start hypervisor + restore) restoreCtx, restoreSpanEnd := m.startLifecycleStep(ctx, "restore_from_snapshot", attribute.String("instance_id", id), @@ -346,6 +354,14 @@ func (m *manager) restoreInstance( } } + if err := m.waitForForkMailboxAcks(ctx, stored, patchedMailboxes); err != nil { + log.ErrorContext(ctx, "failed waiting for fork mailbox acknowledgements", "instance_id", id, "error", err) + _ = hv.Shutdown(ctx) + m.rollbackAdmissionAllocationActive(stored) + releaseNetwork() + return nil, fmt.Errorf("wait for fork mailbox acknowledgements: %w", err) + } + // 8. Delete snapshot after successful restore unless the hypervisor is keeping it // as the base for the next standby snapshot. if m.supportsSnapshotBaseReuse(stored.HypervisorType) { diff --git a/lib/instances/snapshot.go b/lib/instances/snapshot.go index 523917b4..cca52018 100644 --- a/lib/instances/snapshot.go +++ b/lib/instances/snapshot.go @@ -387,6 +387,14 @@ func (m *manager) forkSnapshot(ctx context.Context, snapshotID string, req ForkS if err != nil { return nil, err } + if len(req.Mailboxes) > 0 { + if rec.Snapshot.Kind != SnapshotKindStandby { + return nil, fmt.Errorf("%w: mailboxes require a standby snapshot fork", ErrInvalidRequest) + } + if targetState != StateRunning { + return nil, fmt.Errorf("%w: mailboxes require target_state %s", ErrInvalidRequest, StateRunning) + } + } targetHypervisor, err := m.resolveSnapshotTargetHypervisor(rec, req.TargetHypervisor) if err != nil { return nil, err @@ -488,6 +496,7 @@ func (m *manager) forkSnapshot(ctx context.Context, snapshotID string, req ForkS cu.Release() inst, err := m.applyForkTargetState(ctx, forkID, targetState, restoreInstanceOptions{ WaitForGuestNetwork: req.WaitForNetwork, + Mailboxes: req.Mailboxes, }) if err != nil { if cleanupErr := m.cleanupForkInstanceOnError(ctx, forkID); cleanupErr != nil { @@ -571,6 +580,9 @@ func validateForkSnapshotRequest(req ForkSnapshotRequest) error { if req.TargetState != "" && req.TargetState != StateStopped && req.TargetState != StateStandby && req.TargetState != StateRunning { return fmt.Errorf("%w: invalid target_state %q", ErrInvalidRequest, req.TargetState) } + if err := validateForkMailboxes(req.Mailboxes); err != nil { + return err + } return nil } diff --git a/lib/instances/types.go b/lib/instances/types.go index e38f5499..0b8a05dd 100644 --- a/lib/instances/types.go +++ b/lib/instances/types.go @@ -1,6 +1,7 @@ package instances import ( + "encoding/json" "time" "github.com/kernel/hypeman/lib/autostandby" @@ -272,6 +273,17 @@ type ForkInstanceRequest struct { FromRunning bool // Optional: allow forking from Running by auto standby/fork/restore TargetState State // Optional: desired final state of forked instance (Stopped, Standby, Running). Empty means inherit source state. WaitForNetwork *bool // Optional: wait for guest networking before returning a Running fork. Nil defaults to true. + Mailboxes []ForkMailboxPayload +} + +// ForkMailboxPayload is a caller-provided JSON payload patched into guest memory +// before a forked standby snapshot is resumed. +type ForkMailboxPayload struct { + Name string + Token string + Payload json.RawMessage + WaitForAck bool + AckTimeout time.Duration } // SnapshotKind determines how snapshot data is captured and restored. @@ -316,6 +328,7 @@ type ForkSnapshotRequest struct { TargetState State // Optional TargetHypervisor hypervisor.Type // Optional, allowed only for Stopped snapshots WaitForNetwork *bool // Optional: wait for guest networking before returning a Running fork. Nil defaults to true. + Mailboxes []ForkMailboxPayload } // SnapshotPolicy defines default snapshot behavior for an instance. diff --git a/lib/oapi/oapi.go b/lib/oapi/oapi.go index add4df50..cfa0a7af 100644 --- a/lib/oapi/oapi.go +++ b/lib/oapi/oapi.go @@ -727,6 +727,12 @@ type ForkInstanceRequest struct { // When true and source is Running, the source is put into standby, forked, then restored back to Running. FromRunning *bool `json:"from_running,omitempty"` + // Mailboxes Optional JSON mailbox payloads to patch into a standby snapshot before resuming the fork. + // Each mailbox must correspond to a guest-side mailbox marker that was present when the + // source snapshot was captured. Mailboxes are only supported for forks that restore from a + // standby snapshot into Running state. + Mailboxes *[]ForkMailboxPayload `json:"mailboxes,omitempty"` + // Name Name for the forked instance (lowercase letters, digits, and dashes only; cannot start or end with a dash) Name string `json:"name"` @@ -738,8 +744,33 @@ type ForkInstanceRequest struct { WaitForNetwork *bool `json:"wait_for_network,omitempty"` } +// ForkMailboxPayload defines model for ForkMailboxPayload. +type ForkMailboxPayload struct { + // AckTimeoutMs Timeout for wait_for_ack. Defaults to 2000ms. + AckTimeoutMs *int `json:"ack_timeout_ms,omitempty"` + + // Name Guest mailbox name. + Name string `json:"name"` + + // Payload JSON object patched into the mailbox before the fork resumes. + Payload map[string]interface{} `json:"payload"` + + // Token Per-template mailbox token used to identify the guest memory marker. + Token string `json:"token"` + + // WaitForAck If true, Hypeman injects ack_port into the payload and waits after resume for a UDP + // acknowledgement containing the mailbox name and stage=applied. + WaitForAck *bool `json:"wait_for_ack,omitempty"` +} + // ForkSnapshotRequest defines model for ForkSnapshotRequest. type ForkSnapshotRequest struct { + // Mailboxes Optional JSON mailbox payloads to patch into a standby snapshot before resuming the fork. + // Each mailbox must correspond to a guest-side mailbox marker that was present when the + // source snapshot was captured. Mailboxes are only supported for forks that restore from a + // standby snapshot into Running state. + Mailboxes *[]ForkMailboxPayload `json:"mailboxes,omitempty"` + // Name Name for the new instance (lowercase letters, digits, and dashes only; cannot start or end with a dash) Name string `json:"name"` @@ -15875,301 +15906,308 @@ func (sh *strictHandler) GetVolume(w http.ResponseWriter, r *http.Request, id st // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "g+eDztdPX///AAAA//9DeBEen5ABAA==", + "H4sIAAAAAAAC/+y9/3IbOZI/+CoI3m6MNENSpH7Z1kbH99SS7dZ2y9ZZtud2mj4KrAJJjKqAagBFiXb4", + "332AfcR9kgskgPqJIouyJVvTjt2YllkoFJBIJDITmZ/81Al4nHBGmJKdo08dGcxJjOHPY6VwMH/PozQm", + "b8gfKZFK/5wInhChKIFGMU+ZGidYzfW/QiIDQRNFOescdS6wmqObOREELaAXJOc8jUI0IQjeI2Gn2yG3", + "OE4i0jnq7MRM7YRY4U63o5aJ/kkqQdms87nbEQSHnEVL85kpTiPVOZriSJJu5bPnumuEJdKv9OCdrL8J", + "5xHBrPMZevwjpYKEnaPfi9P4kDXmk3+SQOmPH6eKXyrMwsnygkc0WNYn+xtl6S18DeFU8RgrGiBp3kEJ", + "vIQmWJIQcYZwoOiCIMomPGUhentygQLOGAl0Z3LE+EQSsSAhmgoeIzUnaM6lgjZK4OAaKTyJSH/EOt3K", + "ehCmn4TrqfT3OVFzIjyDpRLZXtCUC6TmVCLK9NOA9IsLpkRK6pTtdmgYkbGiMeGpqhPqF36DIs5mMC3X", + "L4pTqdAcLwj6SARHf6Q4otMlZbNmIk3IlAuCflkmJMYMJREOiERUIcoUd7MxNMp57CD2MRedMS7IOCRS", + "UYZ1/+OEC7MjyqN/DX/gCBXawtCgPVJzrByXM67QNSFJeaL4Bl+Xyfj77m732WAw+NDtUEVis63wLY3T", + "uHN0eHCwd9DtxJSZfw+z0VOmyIwIPXz7CxYCLwvTkTwVARkHNBSrZhJElDCFTs5O39xxAp3hoA//t/O0", + "0+0Mn+32h4dP4d/Dw05xWjXCl0f+efXWu1RYpbIug8xuGltGGReYpD7rV2k8IQLxKQpSIQhT0RLBliJh", + "C6YrTXvgW4qAsymdpcJtQd+WK5FzjiXCzAiNXkVe5J212neBFmIhv2FjQWJMmaZxbRBv3COkdyiym0gP", + "KeBMCR5FWigoReJESbeLulqMM4STJKIBiJ7SptqPB7LT7bA0ivTDygjz1SYRnVFo0Io0VBYWyb2LFEeE", + "KSKyHd6GNCWx2PThnNze1cjlYnspKCkL/NNlVZrHWsILEpjpZidAiSITEvCYIN11eQV2B7uHvcF+b3D4", + "dvjkaLB/NDj4R6fbmXIRY9U56oRYkZ5e8DbLtFp+n+RU0g2RbZgfVR7a9SsyuB27RFiqbFfDJqdqOcae", + "Mb2lMZEKx4ne2HoMBWI2bWvXYXUdHOVXEnj4RQRm5FaNLYW88/HxB7lNSKCPGO62Z3Zi6/66iE4RRpkM", + "0OxqBOPKiTz7ookIgqUesNY79On0eydlMk30WUjCcRJhpfvVSgqwwTimUupXsx9CKs3G7HYck48ZV2OR", + "MmYaMqJuuLgutrS9jGnS6XbmWI4XsyTtdFedA2Wmhk+QCCcS+rMrLsZECC46RtdcjqdcuEXSh1hOwhVd", + "1SgkszPLQ6FOt1MiQCYf3VzcuLNV9Q4OvgK8JIyabvRqmEx94MW+6sPNhrZaUhqxbLRSt8zIvizLEiCk", + "eMa4VDSQreQmnMZ6eWMeekTnadYdoiFhik4pEVZRJUikDI411wnSnSDKUCor+yDTpcdkoY2f8WJ/rIKk", + "TpSKpVBcvMJhnx8xhWMuW/5sp6xh0vLcvZbIAlPYk6dkQc3RUlaG7NKMQ0EXRHjEd3aiGlFo2qEtvde1", + "CGGcke0SpdiChhS3EQchjGlMPdxzcXKGzGN0doq25uS2/JHdJ5OnneYuGY49vPBLGmPW0xtCD8v1D22L", + "ff+279X5eRyn45ngaVLv+ez1+fk7BA8RA5Wx2OPTXZ/qlwR0jMNQECn983cPi2MbDAaDI7x7NBj0B75R", + "LggLuWgkqXnsJ+lwEJIVXbYiqe2/RtJX789Oz47RCRcJF2AErd04RfIU51Vkm/Kq+Pj/55RGYZ3rJ/pn", + "IsbZIeIj2JlTo85OnZ5g30Pvz9GWliEhmaSzGWWz7Tb8HnBNDn3U+Q5xGCqybbSZqJyWcufzNhAEr/mc", + "btHqY/WtlpqVHMeyqXfXREvUmEYRlSTgLJTFb1CmDvebJ1PYMOaEqn3quf4ZxURKPCNoC1wqYH4YYaoV", + "mymmEQm32ymzTZP5J58UjpASewNb9PAkGO7ueWVHjGdkHNKZ9YlVjyj9u2Yx3Y9C0No/ETjM280DPinI", + "tP69FyC64SOCTIkgmse/8HOJ4AvCsLVe/g2+2/m/dnJn4Y71FO4AMS/y5p+7nT9SkpJxwiU1I6xJLvtE", + "sxGQGsEb/jHDo1VrXeAoqbBYvT+gxVfYiblet5Y21m2hVRs8W/vKW92mKjtBNGa6REEKNIrI51qp8WgH", + "nCn7oOK+5DMUUWYsDq3ambUAvWqZkJ8iDiLxK9EhI3998+tx30F4mR8aetPPupkCHvFZkZpzgoWakBIx", + "G44w21E+ukbyX5S2T+WswpKMV0uQC8oYCcFfbDe2aanVWK+ZAbvomqrxggjp3XMwrF+pQrZFY1cRD66n", + "NCLjOZZz62ALQ2qchRelmXi0tZIjHoM97joELQLs18tfjncPDpH9gIeG1nOpG9RnUnhbd2/aIoXFBEeR", + "lzea2W3zM7rOIX4OyJ2VTWdPxoGOMY2k69jVtHZyKufmL5DdelRw9mkxoNkr0n9/8Ez6BISEsRIab2/8", + "OmDmGZ5FXNN0iVJG/0hLCnYfnU3BQawPChqSsIswPAC/g7b/ZoQRoeVU7hkqKMFoi/Rn/S4aab2wp7Xg", + "Ht7tDQa9wahTVmOj/Z4x7xOsFBF6gP/f77j38bj3j0Hv2Yf8z3G/9+Fv/+ZjgLaaudMK7Ty33N7vIjfY", + "orpeHeg6Vf7O0r84fJ/EMUt9puXEpit9clZXHMxcQx5cE9GnfCeiE4HFcofNKLs9irAiUpVnvrrtV6UF", + "zGMFEdhMk2lDMlSMHmDjrYjfEBFoCRwRzXiyq4UwVbKLsLabQXghfUr+Bwow03vBKBdcIMJCdEPVHGFo", + "V6ZWvOzhhPaoGWqn24nx7W+EzdS8c3S4V+NzzeRb9o/eh7+6n7b/j5fVRRoRD5O/4amibIbgcfFaz40h", + "u6JZtSKOumkEal5M2Zl5bVi/g/qyFXYTWbXSxphrXGothDIX2ZqB1O93tbEVe0yH1wsiBA3dsXxyfoq2", + "InpN7H5BImVolA4GewE0gD+J/SXgcYxZaH7b7qPXMVX6OEzzU95c2VZu10gw56CoRBHf5DoNNEUwcHC0", + "8hxfRRovtU+yfuun/i9cql6MGZ4RMEdtQzQR/JrogZo7AUokuiZLreUs0Ux32ltQCTc8hC3QAhuvQ3/E", + "3s65JKaJeyTBt08XBMU8uDZXv3MOlvwCRymRXXQz1yoH+AQJjuzPyFyMjdhcD1IGPCGhNkJMM5gauiJs", + "cYVinMA2x4LAHkcxVkRQHNGP5gofbhlISPUJN2IENgZKsN7zQcBFCDdsHBEczAtU+ItEV0ZhuYLuryjT", + "bH1lNmblsvpT5/W7tz+/fvfqdPz64vmr47Pxr8//S/9sXuoc/f6pY0I1Mk3lZ4IFEejfPsF8Pxv1NiSi", + "c9Q5TtWcC/rReGs+dzuaBlLzF05onyeEYdoPeNzpdv5a/OeHzx+cQmbc2Au9DTwD++xVhsxZ6hFJp84b", + "KJH1MLm7DU0yLaJeXrzb0adzgqVUc8HT2by8MaxqsNGWCKm8HlM+niS+MVF5jc52XiOtuKCI6g2aKSrD", + "weD85x056uh/HLh/bPfRqdm1MHwtg7iw+pOca/bJoj5OLt4hHEU8sD6UadMFr/uUT8ATpsQy4dRnxFWE", + "U960LqN6vfzpBqJoZ0LZjtTL0As2ozvwzZ1NiedsQQVnsTbnFlhQfU7L8l559fr0+fj5q/edI30QhGlg", + "vZIXr9+87Rx19gaDQcfHoJqD1sjAlxfvzK0nbBuCIzUfB3MSXK978RdoewJNYcepJEpnY0k/erSQ44w0", + "KCYxF8b6tu+grXlZSTFbHsG6jjp7L382fDl8CSzp1tNeL2W9mI4rN4Ivf/Yx2nyZELGg0uei+yV75pim", + "HilU2hbmgi3jd9gA/YLpE0Q8DXuFT3Y7UypIAJEZ+l9/kFjbAIuP5Rstz3t+z1kr3XeNUoujhDKyQqv9", + "TrTLGy6uI47D3vArK5f2LtYTVWMelNc3u5RzLFELVptgFt7QUM3HIb9hesgekWyfoKxxJpdv9Uxw9L//", + "/T/vz3MTbfhyklghPdw9+EIhXRHLumuv+yWbSJr4p/Eu8U/i/fn//vf/uJl820kYHeZO+qBd/+emh2q8", + "jQ1DNJ7Uhkvl7ODPYl0Ut7Y4vI4c7629QfbJeL4gIsLLguC1Y+oMByD9KqMSFAIskX1Pi9FrpF9eI4Z1", + "b04/eFn1D+wO/IJWENjZ4ySLLF1F/zemdW6meObkmdLPWtTYY6XNRLJ5DHfP7Z+79Rn5JySvaTIGfX2M", + "Z5m3eVUw6uU1TawRAG8YLogiI0fCFMyGCeeqP2ImNkYvPfAHuSUBiEypsELHF2cS3dAoAt8UyKT6yaRN", + "ikJQFTSXSv+vSFkXTVKl7QSuCLIWG3wkhbFA4wlBKcPuJr6itdsJ1gMbgCzXRDASjY1WLltSxryE7EuN", + "xIGpTrG0wXFCpUmZXqe/nl+irdMlwzEN0K+m13MephFBlyauYbtMve6IJQICJPRHNDtS+10+RTxVPT7t", + "KUGIG2IMnWXePXtNvHh58c4GGsjt/oi9IZqwhIU2xNgdWDb8NOTsL3rDk7DcbfH7FaI3BZNIhhM55203", + "16Vtnu+u9m6MbmcRJGl5SXe7jeGnCypUiiMtqkuKrDe0wITPewwWE51fNJys2MzDdVX5Tritr8f0DLH0", + "3kBdj8vGKFqtXTYFJ0LNeeMs3E/tBrum/zPmBrLSZZUbuV/wrUvTSS1syPzcdTO7A5XOMppUHF1fhzzH", + "suAUaBX2bqK/jEIp0dYVTmjf8nE/4PFVF139tfSD3vvOMtHqyQ0y1AB5wvRPxf6r7pC1joqNAs2Li4Pl", + "3dfjWDbGWKHFECmBmTTRcXOckD76BYQ4UiROtCRjM0QlyoLKEOM3/4G40YncqyOmhyZNhIolR+auknTG", + "KJttaytBH0w4DI1Pa5qqVOh2CypzapZZx/mNavG0ZnTEyGPIzaAsiNKQoCvnW7oqq5V1z1PdorSuqJqB", + "ZEgChhHYimonTpX+vJ5wjFUw13TiqTIha3bq5XDCin9r3VWuHUt2yXeH9b/MxEU1BWfhsZD05Oz1Ejgk", + "C57RJgekVVT8ztFrsoQld45QXHOFFn2gfk+lIJJHC2KP3aIXdQJJRtwoTrkD1bhCrfdTb/9qeo3PL7hu", + "KTS9WpO/bGl4kouk6rnJ5hxjjQcXje6kkJ6c+V5X29WSAPHBcjlCoI5ddY2pRcCBgZhmlgiFVJBA1bqn", + "bDZiEL1yZX/p296u9CbXOspXSdmCDAhQ2otLiwor69Q+6EZPjcdUKRJ2y7rBNSGJXD8prV5bl7nHry/I", + "jaBOkLlw5pbqGWFTLgISWyPhy+zO54XOvFbgZl3Ug0kMfQtjdpkhkBdDQhO5ZNYDHLylhJFq3mRYsdpM", + "8EL5k1c4iq7Qlm20jQT5J+QA2LVinOXM/vbkwrFAduH+/ryrOVJLgau5UslY/48c6118Ve3Mvut2eJ7T", + "9nQA9tX+/p5dVeuzMwOudFt2z3kDMpqXxqnfjXd6mi/0KG2ESxtV/iR/JffhXlMWtu3gV9220bmXKUbO", + "0rhv/14iSC9NZgJDcO/X9O7d+cYWqNkswddkEPsCNPPcxFQqHhcj/bcqwSW0HIZSJtaCR70QKwye0Jbu", + "WjPceshzvDRdGVvM6/egH8l4NvFELNGPkIQwozM8WaryzcXQm0f4pdfnbiy+ZWlKHTAWJAnHiq8OnqZT", + "5Nq2iZU0mQ6KjxdTylcnltjIm1LmnzmOrF2ru+glAbXuBNBxgrmJbTVEAKXx/Xnx1rA/Yj04fo/QafaB", + "rNusSwy6JQ7NxcsWF4VBmBQSNFluI4zen/fR22y0f5FIGywL4nIp5liiCSEMpeC5htOwZ87i4gBSCYem", + "qr5ufScm7WIbLke5fdbPsp3BS5PlbkOQ1oRW5mNyNmGh7G00ZkUvWCuv1aqQ8zdkRqUSlYBztPXmxcne", + "3t6zqvtz96A3GPaGB2+Hg6OB/v9/tI9N//qZJb6+jsuyxYa9FaXPybuz013rLC1/R33cx8+e3t5i9eyQ", + "3shnH+OJmP1zDz9I7olflJ3m8XpoK5VE9JyY1Fzli9IrBMM1ROHdObjunmLl8tDfVW0NJd7qlveRVOML", + "17bBwpunvVQF5tqA78Lk6pb8MgG7M98lBQ3OxlUG1BtBekrl9c+C4GtIFqyf2zGeETk255k/kiKVJryH", + "3FrvhuBcTaW5di17PYf7T/af7h3uPx0MPLkkdYbnAR0H+gRqNYDXJ2cowksiELyDtuC+LESTiE/KjH6w", + "d/j0yeDZcLftOMwNUTs6ZIaXewttWYr8zSGkuCelQe3uPjnc29sbHB7u7rcalfUXtxqU8y2XVJIne0/2", + "h09391tRwafQP3e5PVUF3pfTeWxwBfS/ejIhAZ3SAEF2ENIvoK0YjjCS3VaV9+QEhy7z1X92KEwjuTLg", + "wnzMtjSOtjiNFE0iYp7BgrTyRcPMT6EnLzYHY1mm8WY92YyotQEGbi5ZE1TKbCuR7tykUheUJ0qi8Mjs", + "0LVyDlYzH9iHJj6wc2jJDb9p06kXkQWJikxgji6T0ysIyvjELFppVpQtcETDMWVJ6mWJRlK+SAXooqZT", + "hCc8Veaa0aaG5x+BeGuwPaZaXLezc19wcb02clWfxFkG/Fqv0DE40qfWVQOnOEb2bZccUVD6sutAc2lq", + "n0v0xrxhPET5z0laxtPpwpesJ4khQaTiIEmtw9B201a7jDGNJvyWrLpy+M/L16+QbYgSvLQ3hRwlWEHA", + "gFZsMyyEzA63yCaCyDSmFm5Ij70/Ys9xMM96BO92wIUgMuFGg8bW5yppSPJ2WFyDlYIVusHSOAWZMlq2", + "mpMRs1TLRqBbBThREN+Hzt1U85jS3O0PN7dcXFtXrCWrXc0Rq00OJv3GLbLCyurzrSSUZkI7mgtDTp+g", + "8quU4Md2gT2GFfJj7YGimnpTEwjydZ0fYkYAlEORNvR7C+0vofnnbucGUwWYDKUQqdWBLyyjITBppErb", + "UPcIhDaBzwVvrOJwz2N9jhmbq1TYnVeMEgKnM7okyvqCFbdNEZ4qa3S/Pzf2s0xjcGLSiNS/OqWMwuph", + "uWTBXHDGUxktWzl62+ZweBjTA/Z0nWFvxJXb9oFWV+rIMIBOwwXKFgkH1+VYKv1mDPdJGfDW3gB6Ww28", + "5d8jL4F2Tm5AFHuJmU1QQ9+YlWrZX1Tj9Pbhu+6fwzJrH/f+YVi5Pz5qTHjKqecPuPWxJMhZsxhGtMLW", + "tn4XN5sCXlTGuTEB0tWWU/Fr4sv+JaKn3AWj6xfaGp1UcWdxG2wew4o2dslI4X6ZYMPdp1WK1UhSXPz1", + "h+rZ1IbMOH+LuaiWSLOfltk5ZSytQcDpj0i7swxlgPEwend6MWI4uGb8JiLhzNweBJwph8VVIDGY6zai", + "aUZ+shvdbLQ2O8sRPmeDpr221t/+43h+zMczIzcPcTZDrl9P06KnCXA/B/OqaPSMFfNGYEcLGpI+Av0Y", + "wmIdtkBFV75UPElImK1ef8QuKwsqDRuCxAM6qDmhAnFBZ7T84fIV2X2GtW+isbiN/kNruSetpUjYug8O", + "Hho50Kg7m4lNTRS9gZUp5oZbJu10O5cZ6pelepl13mTIaTWOydNY6irLxbtNg/cTwafUh+UI0Z72qfU9", + "u7D23/YHl73h/2NSVPR+hAOfMhMhGvOwoinZ9u1s65cX7y6axpTBZqHi6GpzymJ6VwGHOorYw8DGXVkf", + "rRMP2nTOPpJ7F5/5tMipwDGZpNMpEePYc334Qj9HpoEJ3qYMnf9c9tjt7rdXUC9KiwMXA1McWNSjdtT3", + "aB2VaXQL1PzgX643xJzJTVgJeqkEcec2NOqjVxlQGXp58U6iPA7bcxdZXt7GXMSL+VLSAEemRwN9Qlnx", + "ChGYs7UP8CJ/0V62ek5wP76d2whoazFLUtiGl296Z6/f78QhWXRLYwKjZs4jose9XZAWC4eYkCdOloTE", + "oukuxzCGbLuBCrTKdnBrIhX2q4c6iiscjWXEfeGob/VDBA/R1vsXJqNdj6CLktJS6t8LVCjx96F3x2iJ", + "1PTZS/hg9VK4tMG93vEy0ri5QCpMr/RR31YxaYR19bwOLsmvywvNr9cDGppOmr974jIdK7qA9ccgkxCJ", + "ICHSRdgh86o5oa0SLUmCBVYkWhrNKzv6IjolwTKIiFWV6xFytyTYINXyuW7+2SClpIKM1VwQOedROcRu", + "r1sHtpWQ5rEgFmDMzKkQWqA42BlwMDp/FEqZoUA5rXJvne9grlSywaR+efv2wtwfKCIWJma+mMYka8Fj", + "pyTCSzQh6oYQ5qaCJcLoJc+Q3Kp5x7IB5EmocUIE5WUadvY83700qSdoJnBAkHnLKXx2SSScmm1Jab/i", + "ASsNAiJlw/oOV62vfXWaRu3W2Des4VpY9mCTBX57cuHAijLcZUfm3TqVL4jomS3nAJhXL+2uXA2b5T7F", + "OCP1jwk+IYCjZVOLi0nQLnQWcML066XE44JwkMWMX/sd2AWGVF2zzz+0Uvaq290XKhhjFvrwq01Ol0F3", + "mKWxXhCwMFKIjqEhKbidjFpeTE0TBIeUESkrqfFBKqJOt9Ob2lkd7exEPMDRnEt1tL83fLqzOkNhZWqK", + "tWfGIV1l/zqrx0R0ugxygyMNky6zxA5OkhZ3fIaOa84HEE/1UHjArNZnW0HDy721tSz/WxwoB/0Hl36l", + "oC5c3LaJZsnSfKDDzH978OxZcX8OvFF2ed0Sx/47Nd7XMzPh8mCFgqesQkfD5B+9GhUXPnw3LpTN4Z0Q", + "F0+fnYcuWt2GjZQ+9nTwtDjLVuUhQNhUtrndd56pmtblvH5SILfdv7YDiPwt6xxuS69Gg9V0WcNTWiLW", + "WGo1QXlC2Eb0PNjf292Mnm0ncuZgBypyyYcqdHJ+anQi6xImAsVEYVsLpyBkwNempYw28ENMYsjEnP7H", + "atHSEKFZhAlqjPE7qQHK3kt8XwMQ4huToRKiGDM61QLZtix+Wc7x7sHhkYFrDcl0/+Cw3+9vCp7yPEdL", + "abUUOwYgooCj0pfzL1uHe8BIaTOXT52L47e/aEGWSmEOrR05oeyo8O/sn/kD+MP8c0KZF1ulFcIvndaQ", + "fcsR79rgN78fFYqxOL2nVcEFv7McEl8A0MkLhKfwTOs2huO+FPHuzpi4OTC7KmDhFvOFW+Di0o+rA+Wc", + "Vwna2G+mTNEohwyuh8jdCfRZrsTFrGFiJoRlSJhRZP4KOFvoXeGDxSydRO7ZF4WXrlS9/l7XuNbvN6d4", + "rWFbv5ctk39t4YAtaJ/nJPrmUv8uIdjlr7+e/ecf/6+8ePLP4R+/vX//X4uX/3n6iv7X++ji9Rfh86zG", + "a/ymoItfDWcR4o5LYIttWekcq8DjjdKGTgOF7RNjW6tg3kcn4DU/GrEe+o0qInB0hEadSgb5qIO2CNgE", + "8JZW7HRXFghjW798Ye4W9cufnML3udpHaBEvhF2QDCdHppOQx5iy7REbMdsXchORoAHrv0J3UaxXD2lL", + "b4kmAurN2buN/ONd9AknyeftEYPrAXKrBIaICqEygFn3BWAKOyqTUmKbk9AhFprrhRHLzqUMsNBcAPYz", + "NRdCN6sJuX6irLZUrI3wdOCDdoSkQL2QEZXKKNsZZ2s2yrIV0dPBdt1yWaNNZzy0gv1gJ3hCFSxTtthL", + "hoHh00Zwj50zbk2opZZNZo8gsJUUh/9eItdRTotsiY2H3Fx8ShuOEMlCcuh2x1sGB1a35YTMDSO8FrUA", + "lXlusoff/naJFBGxw3PYCjQ5pzTQ84PsECplqlmRYnR8cv58u9+ikibQNhv/inV8m82wiv1hbxybLlJz", + "ww7HpIvOTiF72+7QXIGDrKsXXKDICJh8Xx+hd5JUbETIz4ekD7OS0TK/tjQnwKiz7XpMqpLiCL3J9Eac", + "DaVUvbN8E5rvS+jWxuWalLBa791aXTzh7CIr2iABDCJTDIaAPnGbRUF7R4WlOOz5ilm98d4u3iQ3Gs2F", + "tf/acL5fX93Z20zdcaWZkjmWPu6eF69CoNGKeoi04t8Vzff+pe+uLUmYfQ6qLOqjr/i6r5jcQW84fDvc", + "39zm3xSJtQxZVkDLy8BY26Oo3gcaad3+vaVq3JjzgvRjm+HirLz352iOJfuLgocVW2+496RVXR391bbZ", + "IsU8ET41Q8qklMM/y7IcDBLcNY0ikzwk6YzhCD1DW5dnL389++23bdRDr1+fV5di1Ru+9WkByupExcuL", + "d3CdhuXYhWs15xjjPEaP3FKpZB0erlXewpeAwJpX2xVOcZM0feQVVFYjyf5SQnv14v1tf0UIWJdsUiPj", + "Q4C7fsss3u8PWHYlFOyX4rla4+We4FwbhbsPCrUs583PXxeY9V6Gs7YMcfGsdxALd0ZC7XaoJ738WGoR", + "TEJ0dpEXY8mdjK77ypxsTe/hYNAfDtq4XGMcrPj2+fFJ+48Pdo1qcYQnR0F4RKZf4PK1jG2UcRzd4KVE", + "I2cujTrGPisYZoVta02qVtfTdcDZu+HLVhUav5w2ip2795c2L6ZJuWmRP1yFs4vTyEBUFIv+1euYy8Rg", + "t5qqB5kuO2IwwK7FkMtqj+MgEGnuz3BFZY3mmyaW70fMhP5LU2q6j34lS4liCncI2echckiiLMg9HLEt", + "4aKSs9zFBKeShPoHiKbtuqhNPTSqoLqDfmHE5DyFGrnbfXTCmUxjIqyrB00o+KG3kUyNcQfjBWpAxXZJ", + "QyJGTDfzoMN+yhT1o0OTYZTZNJBxNPBx0xciBLvX26kc9vVc1/Dz8DpA4XZIwV8Kz7qq4N9ludRfa4vu", + "C+phtwrGdzpdFoYPb403uSsjKOBpFGozYaIPBuPFIaF1Nkmi8iqKcJa8Y9dMs3Np6jbITXH0R0rEEr0/", + "Py9dsAkytUXiWkwcNlTDOvBko2XYXWNYrx3NHUF7HwKot3qoF5Sprw7LW/Twu4R+w6EtPP25ceUNUabM", + "LI3mkxVzqvhoQ7IYp6lPZ9ePHIzPu3dnpyXmwPhw+HTw9Fnv6WR42NsPB8MeHu4d9nYP8GC6FzzZayjT", + "2j5F4e5ZB14LzVd308Uejl0MpC80rSkCtXJE2qi6G8pCflNyyXjDnIpftyFU6z5fD5BsPQRvWHWEpTLm", + "coOUOOdSAZgpUzaUuFKVscHzdPh2MFzjeVorL2BwDfL3rUhZYPA1QRJnPt24MODiYtXLzG8uTmFALnx5", + "HbWKH29PtMHRwbOjgy8lmgvBXTfGKjs94OI2BRw4kOZKjK/LMyn4LxyoacfqG8bNakOCO91OFrUMf8NB", + "W4mIyx63CsVv2rBdvxhZJb8bMtLOSmoz3MIarMbwSGsBWTLTJFUoSwTV6sVJxNMQFXw/BroOLkbOCir0", + "kUvPs66hYnqeVrUB9BpKLlCmBTFcCOlObPraETJJ9PAIx8a6sIPALKzcheBwae6C9f5ynza6/uohX1o1", + "H97ROj/S/4JpazJYF+HqLozmc4RecXgnMzoYr/oaTXPQ9uvNq37JLZtX5hBW4GNWjTtCLzLVLVP+rLK3", + "JYn9c2wFVg5stF1KTrUr3tHckq9cIa+w2zEU7XQ7jlCQf1jPRHyXc31t/xVZ0RckQXAEeznP9EoVjSyO", + "N8yESkUDaUOA9eI26Re25A0Jx8YwaQp5MulD1njJXnLqy/tztAWInX9D1pGq/7WdhUeVzrrdZ/vPDp/s", + "PjtshcuVD3C92nkCyW31wa3VQYMkHbu6/A1TP7l4Z0zwwBi34Jm3cy8kUSeCa9GjZ54X+s8//qz/rAhH", + "FvJ0EhVueix2IeSsmAXzIu5lsqghxucPGi3odMr++Bhc7/5T0Hh4eyh3J16HZvYhv/fnrHjbW3OVkknP", + "lCnzI0YBQwnZCKr2hkiYAaQtA//0EA7AdMhy0izLOeg1l8bsY6z9vb29p08OdlvxlR1dYeOMwRfkOZTt", + "CApbDFqirTeXl2inwHCmT5eoW4JT8O8zZIudDsoKaX842PNxScPBnXON7XsRN5L8vTXN7KQs0SG1LjPb", + "arvcS+29vcGT/YOnB+22sfVSjsXtagnjAs8NeSxSf3Hlt0CbfHt8gSCta4qDst9kuLu3f3D45OlGo1Ib", + "jQqqTBh0+A0G9vTJ4cH+3u6wHTqgLwrA4l6WNmxZdnk2nYcpPKvhIUVd9HabTgufOmUY7A0JIkzj48BF", + "8FZOH4NtMBamWb4IbQ4G6xivHVwt3m3lOMrcQSbM2qgGXKCUZSgn/fVXgHe70WsW0+Y8WC/G65Z9hJkm", + "lwXBMMXG7kC7RJAF5an8Ch1xZVKdphHnYqN3myyUNwZfA67dqETvz/8CQkQzF5KKJGWjybLfCqiQO05u", + "ow1c4gk/VzcRq9VqtFn6VRPuNmzT7qo86NL2bwRLCrWoStn68LsTHAUpoF/hbD31rAA7AjI5kyRamkDV", + "KOKcoWCO2YxAoWRTzIvNEEZzHoV9b/CgfjKeeq/t+Q2KuMFjuSYksZVnzCD0a1pnoQuCtgqZpMiwUqUA", + "5UFspIqtLVLmxoPYX+oQS1/yQ5bBqOmJFS8AVZtXSj7GiM8kWIEKQnD71foICRYWd4uZSkqL2BiPZWii", + "XX3ae4ZYkd6+I9QcnXxaxiaD/EBDSRwILiUiEZ1B1Z7355W0sxU5FFny2fqQuvJgW7CuuUjzoerBzWHr", + "gmu+A9ETnP4lRyLwMOSgrAhWc97IGLMUatEUGJncJlQY9mgXkDbnUo0zOJENByvVGOqMpILkmExZsmTm", + "AHJtvOeiE213IZeN/LzT2zWu8nfVNMBmmeqlqJ9a3YwHfWxcB1RZieGSg8JUEUA2gfzJC1NQCb3SAtoM", + "2mJclcRSobjCdpvgjAbwyIt3dfPU1tH8bX9w2RaNZzX4zgVW8zM25Z6U7Q2uIZ3r2cYLJkTEFCrtoJAw", + "SkJnPGb3kda3BXl+kSQoTImlnFFIBbYEx8xiK6o5OCDhRcpmFVlf/WAbf7AZw+oyJPBd27BNmI30Z4e9", + "FSnQygTGSYTzPLFWUYZUjv33V/WOBZmlERaoiji1YshyGUeUXbfpXS7jCY9ogPQL1UvmKY8ifjPWj+RP", + "MJftVrPTL4zzNIfKpbEZnE1yMQtS+W4+hZ/0LLcrKXbgetkx7+9Aon+bqCVvrO4LGhELyvSO0dsCo5dx", + "+vd3B03Zlw2dlvIu64Bem0puy7K+He+wto6zsuCeW0oTdVu5Ky07Itfe9EFY96pc07orBm25QChXB6FM", + "10I9glaekHaR5dWQPzeaHUmC8tf3nx48OWxZEOKLfJ0mhf1rezYX8QqPZsNKnbdxmz09ePrs2d7+wbPd", + "jRxULjq0YX2aIkSL61Op/l9xmh1AJNVgo0GZ+FD/kBpiRMsDKlXyv/OAPq/Yuk3BBfnebLrkjIor6e5Z", + "yh7Qdj7GFdrScUnlwpmYQVtkOiVgVI4N3Xr5YCoZgq3GEOAEB1QtPQ4TfGOKEmdNKnBvbbxp5cF6SGr7", + "ttBAWnLJdJInUWy5j6O/Gtd6hReetq4rI9NJkxv/dfWrxomf+4CKV0Qtbmjy0td1d0E2nxssS7Fm+u8A", + "oiddnHg9Zta0WA09VQX8gEtAWz6pEEnhgyysnH/2peLyV5az4PYtKclViq86Qpu34EY2tOdE9pjQwfpM", + "mIp8sAfg3d4aT4oVn1aW1CqVh8pP3c2/2yLZpw4Wmp1gm3+vkPawyYtVYCzgRzsGS/K8726JJRq4qRCm", + "6zFHeER6WZyDjeFFMjX+Vb3nLdaiJw8juObTaRnw6aAZIBCQ+SBk230FK0XiRHURuQUznYQ1dDkU0WuC", + "Rp0DOeogLtCoM4xHnYoT0JsEEePbsf1AOVd5sAqxL6t1WB2kdDOYRDy4NsWMlKBE9tEAxQQziVIGm7/i", + "oxwOVvvaup2ksDYZPh4xN8Q1sQVjmpA5XlCAZbUeqlkpjoXcUiUh3gb6OUIhB/u2XMnRzlA3MykKR/mk", + "4dDBbGk71h3qdpy5gKC8LZhLU6gfyT4Swbs2+U5L7Nevz7vm/gciN8zASuEhbqJmBFpAZp+oYIzmv/vD", + "ryYRGcO4q5iVcZ2OxVQy8FMLIomSFsQuZ4cKE6CAp0xVwSzjdiGc5Yj3+pGUMoiVsLdnANlgv27rP4ck", + "sDX26+6lEqPfgbkrYZeW0r64yz0fC8OmAM+c3/P+xrrXqwMwZRAKRVhNP8WwOONzHUvFLeZ/tqvH5DYg", + "JKzC8fibtA01tG96Qw1/wzbnPavXYVtDuFh9dv37iz2HsTZRuxgSyTjrQbatW1KbGWtQQ2zudZnRSvh7", + "hQzUsQ/8yNegTd4UuV1N61fkVgFIYJhGmrxNrGtFlT2M1lH8zkkXTRuai/XlxO+htoQJ17tTdQlXVeQh", + "CkzYn79+UQnfclwS5dpeWr5prgZegjcueQQLxVWgSfmK0vBOF9kTHQ3j7QrP7c/9bhAL0NUyX4ThmIwT", + "Qab0dgW3mAbGEi5nI+c7p1QxXqKtGN+i/ScomGMhK2NndDZX0bJ8f7nvAQP4olIrgijC1Aa19fPVdC/W", + "gwXschZ792nDl4XUfX/NfxKOV8HYnWTN3HWsK8zU6GN9src/GOztDu6EY+eGtQG5TvJXbB2Ocj9NKQiF", + "9+w9SSmqp9hDlvBVr1d5IyjkjWVkkkoQHB9BoHKCA4IiMgWUl6xO8NrDovbp1YO3GpTNI8/43y2UXTd3", + "hVEGis4+ZSEA3TQ6LgCqDB1QfF4f9goomEzMBDVMGE+Owl5vcPh2uHd0cHg0HN4H8F1eUqohOvbJx+HN", + "k2gXT/ejp8snfwznT2a78Z7X8LqmBg+7Da/+qts2XlLmp2IZgqAk0tCWnUNCRLUQabWAryQRZaQns4jy", + "9WkdK2SBCdJYu/83c+ybGaxUFi7LkyzqDFjlxClx1gPhZNjRr7ydqA7/7HT1sO8Uol0diJ/BqkMxNfFa", + "DQaAWIdfivqZspbnzrtCw9Ynz8q0gXVnjy/LE7a2d5UbKO7j55JgLO2wVSd2/VTzeEdnXFA1j1cfD1mz", + "DEMQ4sw+ShWWcRn66GzGoOxw8ecsrKBoJumXO91O9HG/vGfs7+0ROixmXsaAdqmLakCLa3eoar2aCtAk", + "Ny2EifzT1rge80/D3vAZBL9FH/d/GvSe9dHfC0F4XUOtIvmGrnXp10EbGhYrXTiE9OGzjSLUHD1XcdCv", + "1FenIT+ILZqe5fG8Ipo7K1xGUmmB88e1Na7ACDRqnF+q2tnTbFzUkkISYY+3t+SKrZWTLHSAJmRGmWzj", + "md0bZK7Zg3jU6aNji0EJ1mpe0rvUPZSCLPAJjWMSUq1UGuO+OeJzt6W3rWo8bAZM7N7yqGd9v372bH0O", + "6boA9XXHZP8LEpa+yNxtZ+KuSm8Gz5mzSaEECjTsIjpFmFXq8Ni6+jbTEDJHAGHmyAGq5CxrZYDMFT/n", + "CemiGVcozzFs6VFLWbPnLxs/uQWP6oqkYsMQu18lYzxDL6GrxNfZKUoED9MgT7CJYNB5SrRIK2COK7T6", + "9SFM9+nQgMw1qO+51qHR5MFo54FsWu+K91EzbPNSDwfrl/pevCDdTpqE62WYadROgm2ENbomZcPjkymT", + "vaIJFibzoYVEf1OkYN3INd7iQKtEaeKuUDRP1TnJc6EClwg+2L1TEhF9TNU7QTwK86hSKnMpul6kDg+f", + "zpsuMeHOqT6QXwlJtK0CABHwvRizpXdg1SKraGvgKmdJc6XVM1jlllrlwT1Zq4k1LlX7erUVr7YthF1w", + "cGegm1+3WK190wW2NTqM78MP9y2VtNf2cqECr+bA/zK4Uvd9kzED8IwsqpzX+74LeB9bvLWWcRNkXDVr", + "puhmPu79w7iV0bh/tPPT3/7v3oe/et3LFbtZEtELyRRCia7Jsgd490jb6P0yYBpg9WplemZZheAYnEbB", + "NTFOqhjfFsd7MMiExvIVjmtTgBismLLs32sn9Ld/a45gKpDxHcjJtSz7xVDW91EnSHF3HG3FRMxcJWMX", + "eL/dHzGI5b8mS4kKxQisSuMY9S8ye0Wr6OC0xBG6Mmpgn7DFFZpQqOkiR0xbtTgISKKtCQvKTk0xPg7S", + "RxAcFfuxRRFcopy9cjQRAwS9P6+h7b1+9/bn1+9enY5fXzx/dXw2/vX5f0EQx03PfCHsad7bPzi0JfiK", + "lBx6lvgLgH+/CMXPx24GC8zDX5CUAnUNPQozlZBS6gIKCo3RFokTtXTFhlxuy/Zm2GTHWYfecLavjMI+", + "ePY1is68W1llZsGjntaoG0B0vQ5MQwtvODZ0ZcLcO02O7ZmnDPil9SbO6Ax7fNneCqRfoziMG9Ba0Lja", + "+jeWdvAHx59WEYaNNDCkqiDiVuxSqXrNsfOxVqTGea3HckRGymx6CS0EbJVzSWKmdmwRJ19Ka6hP3tUJ", + "Rfkuc4hFPXhpfZ7MSlW+MLPCSJrX5txprBWdegWBLjRpbuZEkMJCwAs5cuuGJLPJHi0SpU2plYSIPBDS", + "ZYpA7XNBIXskczY4EmQJQXUP7Gpk3nN8m30BvPdY1u64YB45Rv7w5c+jznYfvXGlSunUdQHDqNgTfiDU", + "MhetoonjqvpiFLmqPm/T3rvxrKxaIf2a9laFOfNvlFjTx49/x1S94AIskOa05HvHUwXrJiQCcFmqaKmt", + "oEZpTMJxVrC5af+7Gs0mJzkrh52XcXLWFgYm1kJufakdlzibj6FOaU0OEqSCquUlFHY1EcIECyKOU7Ph", + "XX1Y+3P+YaiK9Pkz+CmnniyEl4QRQQN0fHEG+zHGDJR09P68UMnEFLWpYaiBevn65MxauA6GDywWqoD1", + "XDDf8cVZp9tZEGGsvM6gv9sfwGZOCMMJ7Rx19vrD/qBjKvrCFHegiCL8aRMMM0vpLLR60M+miX5L4Jgo", + "ImTn6HdPoh4EskFj0HfxrGCxJJgKa7IkEaQPGlah+l0A1nVH6ZE5j21B3tYOOqmWNpmCJK/tsn4AdRJ2", + "DUxxdzCwMKPKHryQCmLiz3f+aYMR8++20ueAPB6U2ZpF4XRKS/LP3c7+YLjReFYNA3as77PvGE7VnAv6", + "kcAwDzYkwp0+esZMhhcyWGE20Ka4z4CFijvs9w96vWQax1gsHblyWiVcNinDRCKMGLmxFUH/ySd9ZK8f", + "oGyMnPM00tIEmfQ152hQWPRnHxEWwZwuyIjZczpOI0UTLMCNECN9PhuDqbw1zKfN6mfAAj/zcFmhbtbd", + "ju6u55zOOYGraQmSjAGHeNxU7jd3N1PGtJjEkthaGlndy3o0jxaXYxlwXz7RW8IwUz2ZkIBOaYCgsd69", + "1qPt7bAVNJ8WeLAsRABilvPQ7G77c1Kh6oc/nfs0e4YsecvqBINroCBKw1zncmlSWExwFHmxm2YRn+Bo", + "bOhzTTwq6ktoYYlSLJDilBvGQ2KKXSRLNefM/J1OUqZS8/dE8BtJhFaBbBEzS2sSmrJlhnVvAOszhkJi", + "pkSq/uaOGeLOp2uy/NwfseMwduVvpXkFR5LrU9MWHbR5AWZLG971l2VpiCs5SaXisWWprAJjPkyeqiRV", + "9k5dEmUrr0FzKlGSyjkJR0xx9EmQGZVKLD/vfMq/+BlsF4JDzSeFJmZKO59o+Llp1HKM9ezH0NRj/REg", + "wKijT5dRR/89E1jbLqmcgxNFguNkVlzSrQxPR+uF21UKB5ihhCcGiwiYao41y5X6gHh0HEVIwVZy72pt", + "E1ayYT42vTieNOYWm2TQyjaiDJ3/XNhMg/2n/v0kSSCIz8Hxn5evXyE4qvQamGa5w8pcajN9iqIwBU0e", + "vt4fsec4mCOjNwHe7KhDw1Ensy7CbRhrKm2Edq8HKu5Pemg/mc90afhTv6+7MtrzEfr9k+nlSO+lJB4r", + "fk3YqPO5iwoPZlTN00n27IOfoE0pmpclQYC2jOzfdjWIASoqPwbNuYFZiLiVtdESYZRLoKIfZUIZFisL", + "KHtIbymoTXk8k0VifBqB73bUORo57+2o0x11CFvAb9bFO+p89lPAKtHN4KamhrTTtTMmOhwMttdjJ1j6", + "elToUkO9/T7XtK/dr6Z4WKWrrniYyTlkZr2Cphq4UbceQPP5GYeuvuQPFW+Nimc9FwXlDd4vngOGfSNi", + "DNyKBqbt2chpYCutE8MWUDABLA6HdGIMDuo0uJx5i+ZH1ZyvmxX7TbssgCFGjv/2H4D/4LtZKULz3WcP", + "9V0cAc64A65/ZOwIi+UYseu3iF8S9T1w3OChRKkFRf+W/PtY+OclsXpfTrSKNNshC3ff5MdzgmQTaXsx", + "jbWteglj6l0SptBz+LVv/+ssHsiWvor47OoIGRJGfIYiyuw9YOG2SB+Klpbwksk3yd6z6ScOTHPLnJ//", + "+9//A4OibPa///0/Wps2f8F23zGJkwC+fzUnWKgJwerqCP1KSNLDEV0QNxmAxyYLIpZobwBqZiLgUbGk", + "ktVN5IiN2BuiUsEK96UG11LaDsH0YDAfylIibb6ObkinFnTLOJg9Jrzby4aUD7qju55EZ5hBYQL6VHQ8", + "AFmitvyatb86fu+ZmXPJf1b1ldc8puvliyK3ynBvzwxwQwEDJPbtO3hgJ422Li+fb/cR2BiGKwBYDTTm", + "vBurPPd/yKT1MslIlLJAASob2WSS2Fb7f09tm3YOYNvjn8kDbLE2N3ABG5cH5K67FfhhK7RwB/vp5lzD", + "Pv/sqcvSbHbQ3n2+xU+4OKZWhvDXW2fHe3WamycFkn0LExhtuWh4cCNygS5OzlxN2+1vxvQPcmromdpa", + "fdnRgTgD7LUHM8tOOJtGNFCo58YChZlikplqZQZ5LOLgjR01wm5eVQjj4vm2U0LkazzpMnC+/Mi7/9Oj", + "8tFNjpEcZjnntR8nyTrWOaUy4PrdArf0ApwAIZ36ku3TIhetc0iZ4PrsyFmpLlnxfHbqNuTDuabsp1NW", + "PRseQCieVgTiNxSEleLxBWDyx8TN77JVdIgUKzxX3xdrDh5OC3poL5aPzR+TGyuskE1LQRPU3XiAviTK", + "hHJ37nGh7Rc8E78kwu1qV0cCZp1Ny7xqCquaCcGF9Grb98w0aWf6mv7+TJYvkGcTjcWS/IeK0sLYzWm1", + "ysA9syXL78++hS9sZN5+vXtey2AeIkOwycR5rE3dXSyXLNj+U131PshpZoj9KA+zizSK3I3HggiFXp+c", + "mZ1VPAN2PkFY0nrd3u22lcfBuze/9QgLOMShZTFUfiXKPvnKGr5ZMDOVH2zSxiY0adHUnWdNGs4XrL8J", + "F0QmwrFP+b/vvojoRGCx/PfdFzhKKCP/vnccYUWk2r43Zhk8lGh+aI37ETOfVrhpmWggmhjUe1+noWat", + "Wiqprv2fSk81k95IU83o+kNZbaOsFsm1Ul+1S3GvGqv5xje6ksmYzUdteOTiE/9kmurDevksRzqgaCrL", + "1x62yB4X4OeFR5ShVJJHGEBJM44rHhst3dX5hlx5fDjWPTvtAiGhLgKgNtkEkQdyXrtxPLhya7/78J7r", + "43hCZylPZTH3JMYqmBNpk5UiUhbAj03tzo/nRsX7O+bSwUMeHQ+uV//g+3vS+KsLaoS3uYFap/O7Vm11", + "ftte6/wmhdrmrlloqa6DHdxuCCp0SdRt2biUa14PdvSNy2eLoHfaUMnNBQQWxNGI/R9tf/yuCI4//OSS", + "ZNLBYPcQfids8eEnlyfDzh2rEAYVjwAl9vjVKVz7zSD7HIBk85S86jhM5QlgPQed8y9nIOU3n+0tJMeF", + "PyykVhZSgVyrLSS7FvdrIpXhtx7cRnL85iO4BTH5YSU9hJUk0+mUBpQwldc8rQWJ2ZLJjzC3jNn7oUJw", + "R+mgbW0lZZtyjQKalwV48MCesxwH8aGNI1eB4HHGyPPEQnpbcyQ/DJvtke+NHwYPK5wf3g55zCxmFP46", + "6RKtU/pKaQPGZJxCWUiUI4RA1CcStv6j67GP8grWMk0SLpQ0OJWgABsk+7lWgH2YlmWYSh8uJWAxUiK7", + "IwaVCvRjk8u/c02WBoWScpYBTlbrsfpyr8oooN90G319HcsPcdpKx3rgbWxBq7+djvXNRMeDaFpnpVoA", + "W9nGAINyQrKdzLPkPvqRstn2o4pANcIqm1sBz8ijau1ApT+L67sjs2qyTQdtAdrXlp79Fzxx65P0ae0O", + "i7ZAQBRSPGNcKhoUK+8W4UF/nNCtT+jVlPVy89SWSPcb9C+4uG57xHnqij2Ck644w+/Ql6CHB2hg396l", + "AMa2OQ000zz4KVgrFvctUzBo9VwMojSEqvT2QHSq5FTweGx/NHi1eldYNFBwUQS2128tbPTXH8Bh9Ior", + "ROMkIlqLJyHqGW7Sq2lVfwc3T2WhtOJmwlBvm2JCjAGjk640kRWRcLnmFmwL7tnry+WVmhGfrQfByD7u", + "EB88KBgjZuDwicPOv0KZkIXiXSQigUI3cxrMAREDCnpBRVcAq8BJcpVBYG0foZewU4tIYPDxLUmENoQC", + "ziSPiAG6WMTx1VEdsfX9+Tm8ZMAwDDbr1VFWcj07IKRuVUS4yGoevbK4HVuakwSPIrOiV9pqLMxv22Jf", + "5BBlI+bDwWDkxnZIp+iqAIlx1YCJ4QTqb3z2zbStbjOwpJmL4kgA4QxvEhZ2mi5iaORHwxgOvPVgWiJz", + "mGHcMzBHbTC/8VkGalliZZwkbdnXDhO4eBHHK3gYbRWKs0oV8lT9TaqQCAEvW+5uYm60hQPzD4WvNaPa", + "wkJZeVtgP+91o0GZ85JKC9VCFR3zr0Ucd7odO54COt0G2vsahJNqh/VrMb0yBRiTH3r3JgAlZWFfQCip", + "nBy2/H+zyv3GNPjT+2ctocI/g5elfJ+Vj4KyvACUgArurgrXo0I6gIWs6WKmMJJvj7hZ9mSheGi7661a", + "2dHvwGhdd+uV1ZDMClw+9PVXfQSPOQlG1mYz5aKaHr/uXuy7Z6SvtyS1qbbhkB+8ubl7rhVjJumKWqJQ", + "ClWCnw/qawKuczDnXBbYfkLmeEG5sAjs1uuacSa4LIz1aKPnrjSrXln/7ZVVz4+srwnh4iP7jT68bmPu", + "/G+4R/kbLwrWdibxu06lBhRIiTCaCEqmKMGpJFpbSmOCTIURC+RNcDB31cL7I/Z2TpCtj1lwIGTllKlE", + "V8P4qosmqUIRFjOwdsxDE0knSMDjmLDQ1LwdsTnBC6pNNYEirAgLlj1JoAbyguQFTLTpbm8oTantrMpq", + "F7nivOBguCqU3r1CiSDARMZcZqU6tyMmUvYfBrlSd3vlBnqFiFR4ElE5z2pFBDgkLPDCQl5+32Ls6ztx", + "L4mqV6f9JneWd5Kl3/ISs+jLzOqDfxf3m48sUIsLV1mzhZhfofTKZtOwHPl4mVfk/Rfc0maubo7f6GYm", + "I/GqXfx9XMmUSvL/uJZRdkuGqfkcKZet/9PeteR1pFNWum6xPtm7XrhklRAyMm8k83Y+uT/P7uAj+04k", + "YbfRsG/C3M4n/T2IXEvVO8ncb+QctL6kglfsG4pgO6hvpz5xUZBy34UYNhsuk8ZFmaMEBpuKsx/CuCqM", + "bXjAXYWx87jWLsAL4pmyXhLhJrmcV633C2DrEPgXjX6tzK4gCL+54MtvBB5M2J1l4s0IvAQvI47/7Pcy", + "ARfCJHTacsSPB1Cs4AssXDBtgcetm0mIrssmeX9+vt0kJYRaKSOEesQSolzWNIg91RpfL4gQNHSlI0/O", + "T230KpVIpKyPXscU6jleE5JAoRjKU4kgM7ev5+dSW+tF8Eo5rN0OYUosE06ZWjuKvOn9DObznUrnPbCc", + "tJCKf/rLY/DCPz4hBbJDqyt2AqutSIVVYzCeC06jzNS71NoWnvBU964liyu0O4OzbUojIpdSkdhE5k3T", + "CDYRgO7amkz2PZNR2kVUSaT3Qxcy8BIiYiol5UyOmC3/nhChv61fh+K/eZCR13mvcCY1L4zo+z4C2PRg", + "TMwWVk1UA2gBqAPaOers4CTZgXLR/iApO7wvGNILiEhDchlPeEQDFFF2LdFWRK+N0YEWEkX6j+2VIW1j", + "eO9rV5y6+87SlD5jU+4tymF4NmPmP0cSUlmsuUvERyfWXpLiZnHyBxbaL9bkWrkmCI56isYkS35HqaIR", + "/WhEne6ESkUDk1eTp15CEWabfTli50QJ3QYLggIeRSRQzrmykwge7IzSwWAvSCiglOwRGBwIvObHMXzx", + "5OIdtDOForsjpv8BHb89vjA3sVNsfQSFgTKibri4Rmc7r9cE+V4Cmf6Fo+TMBFfmQHoX/Mf13eaZzY17", + "SDZsUZ6sMoB48qcP47Qa3A9vweP0FgC0RDabrZnAASjFcp6qkN8wv2dgwaM01v8wf5ytAyhROJi/h6bf", + "jbZrhrP2M26Cj2JT2jmFxBQN+iYXFIZgjzW+VBPOTQGUmFLknvcUOFZ/Ru7++k75Ih2/w6tJS1FXkOu7", + "2VsPffLZMTjcrSI9Hss2N5zmZqL4au/TDabN3qefIx5cS5QyRaMSqIG22wAHVP+Y4zbaiz9QEyA70pUS", + "R+Q2oQIQbCrwCIjoGUuEkSIipgxHOzBn0wkgUDovFl5wCknKQUQhTYyGBCU8igBl52ZOGNKzAUeV66Bw", + "TyttBYhim+IVo+JoQgIeE4fKue0z3f6OqXrBRRli83uRi28L9Nfz0VPV81yDKtr8xS9CGT3HtxDWHKb2", + "mtiNaOslz380rqAugrUZdfYGctTpolFnNx519AqcYHChYoUOUExZqojso1Pj34I01MMBkiTgLJQOHNR5", + "8PYGsikp1bBlQ4bjIbz3kGqP5Sog5Rv7EZ940O2Qfh8SbNBWccPZPRl2YdOFiKcKArjdvrKtQqLAPbL9", + "4DewhT3yw7ZvI8n/brdvSUbBKmtxWVh6I9kz+Mi1XjeXVDHnMkedRAFOcEDVsotwFPEg9x6kMrsd6GVD", + "mQiCr7UN1R+xNxlwpU2EQCcX77rOaYZCKq9ND9Yv1kevF0TIdJINDoE0MB48WAwSjpjiKMBRkEaab8l0", + "SgLIYYhoTJVs8KtlQ7nPMoj5RzwL7x5msDWPy5nk5wlYvZwtZIXjdsxS7wgSRJjGRadSlTig+sKVLrh9", + "J7pTro/haWSvtwLBpUS2qx6J6IxOIntZI/vorVY5cExGLIkwY0SgVJq4Iz30XiKIlKlJjNEdQJ1Zw1Fd", + "lAOdJIIr6yaOOBfSeHY1h78/R1KRZAWbvTE9n8Oc7wkm2HRuv/SNDIbKGJqPJdsE6QUxnGIIrvlIH9Pf", + "INjHDOhbwwk/lo3/VtDZjAi9K7ARsuZq1GxrR06z6UuZHo0Y+ZdZq3YY+VmvhWjuQqTzSqCKsWs4BgV6", + "kxtYz8evaSOWiX20WfbFr/qllt8uR/n7B2EffeEs/yylxy4LwdVtkfVzDn9sIPeFkZe2ailBYT0cQeuM", + "hPvMEGiNO/DN4AYeM8oALqUdNMEJfH+MMHjY7LiHhtl+3LxVQgkoFdZpSJVaD9/5XXDg/eB2fuPs0Dvg", + "dn5X+UqAu/jt8ka/q0ylkh/QFQ/50yNz3leCkoHnBBiLpgQlI/VsIMFKQ+m9bdPOTLI9/pk0eHv3vIH+", + "7sj+w+pvYTIUiOV32ZncaIfbQuJELd3lIp9WLgAl/QjJGD7ghyyG4P7wFu5wvf712MPxaePl+o96Wg92", + "f58XHT47ffxFtIp7rnSw7OhTp4dFMKcL0ux0L+9gS6JEkF7CE7hcCQ3BLD3cWaaw6M8+Itu9xaqy/0LU", + "QRyTEIVUkEBFS0SZ4iARzDf+IpHg2hKA51wsfc704s59IXh8bGez5jy0e8o6w/I733jZC7HCvYWTNitc", + "aF9w0+7utrXAQ5Shlz+jLXKrhEHcRVNt+SA6zUhKbgNCQgk8uV0c8HDQ4NmkH8l4NmkzyhXYya8tNjUK", + "Uql47Nb+7BRtQbGFGWF6LbSqPwVNNhF8QUNTiDQn6oJHhqrDBoJu6nfVSkVWKcMZF2Zw30SHaXMgzT7S", + "pCwWTOhC56gzoQzD4NaiFJf3lEmo0t/DFNIa8r3jOKfz4wizlt+WM3Y0J2ojxxFRcW6g8bZ/HHOP+Zgr", + "Bqa6M6102rUrFdkuVrVlCOl9AOZmccwP67Z+//2EV1L5KCMrret8kRmkTW7z74sFBw93Pjy0u/z9Iw7H", + "f0mc8V1wlUMHukcfw/zGAxyhkCxIxBOoImnadrqdVESdo85cqeRoZyfS7eZcqqOng6eDzucPn///AAAA", + "//9KoABRQZcBAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/openapi.yaml b/openapi.yaml index e6d03230..295f6da8 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -697,6 +697,48 @@ components: Defaults to true. Set false to return after the VM is resumed while guest networking finishes asynchronously. default: true example: true + mailboxes: + type: array + description: | + Optional JSON mailbox payloads to patch into a standby snapshot before resuming the fork. + Each mailbox must correspond to a guest-side mailbox marker that was present when the + source snapshot was captured. Mailboxes are only supported for forks that restore from a + standby snapshot into Running state. + items: + $ref: "#/components/schemas/ForkMailboxPayload" + + ForkMailboxPayload: + type: object + required: [name, token, payload] + properties: + name: + type: string + description: Guest mailbox name. + pattern: ^[A-Za-z0-9._:-]+$ + minLength: 1 + maxLength: 64 + example: kernel.identity.v1 + token: + type: string + description: Per-template mailbox token used to identify the guest memory marker. + minLength: 1 + maxLength: 128 + payload: + type: object + description: JSON object patched into the mailbox before the fork resumes. + additionalProperties: true + wait_for_ack: + type: boolean + description: | + If true, Hypeman injects ack_port into the payload and waits after resume for a UDP + acknowledgement containing the mailbox name and stage=applied. + default: false + ack_timeout_ms: + type: integer + description: Timeout for wait_for_ack. Defaults to 2000ms. + minimum: 1 + maximum: 30000 + default: 2000 ForkTargetState: type: string @@ -889,6 +931,15 @@ components: Defaults to true. Set false to return after the VM is resumed while guest networking finishes asynchronously. default: true example: true + mailboxes: + type: array + description: | + Optional JSON mailbox payloads to patch into a standby snapshot before resuming the fork. + Each mailbox must correspond to a guest-side mailbox marker that was present when the + source snapshot was captured. Mailboxes are only supported for forks that restore from a + standby snapshot into Running state. + items: + $ref: "#/components/schemas/ForkMailboxPayload" SnapshotScheduleRetention: type: object From 31072625f9b3c70634bf6a376248073a510ba30b Mon Sep 17 00:00:00 2001 From: sjmiller609 <7516283+sjmiller609@users.noreply.github.com> Date: Mon, 1 Jun 2026 19:49:51 +0000 Subject: [PATCH 03/15] Reject padded fork mailbox names --- lib/instances/fork_mailbox.go | 2 +- lib/instances/fork_mailbox_test.go | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/instances/fork_mailbox.go b/lib/instances/fork_mailbox.go index a1008717..5c81dcb4 100644 --- a/lib/instances/fork_mailbox.go +++ b/lib/instances/fork_mailbox.go @@ -32,7 +32,7 @@ func validateForkMailboxes(mailboxes []ForkMailboxPayload) error { } seen := make(map[string]struct{}, len(mailboxes)) for _, mailbox := range mailboxes { - name := strings.TrimSpace(mailbox.Name) + name := mailbox.Name if !mailboxpkg.ValidForkMailboxName(name) { return fmt.Errorf("%w: invalid mailbox name %q", ErrInvalidRequest, mailbox.Name) } diff --git a/lib/instances/fork_mailbox_test.go b/lib/instances/fork_mailbox_test.go index 68fcb427..1672b7f5 100644 --- a/lib/instances/fork_mailbox_test.go +++ b/lib/instances/fork_mailbox_test.go @@ -40,3 +40,15 @@ func TestForkMailboxPayloadWithAckPort(t *testing.T) { require.NoError(t, err) assert.JSONEq(t, `{"instance_name":"forked","ack_port":12345}`, string(payload)) } + +func TestValidateForkMailboxesRejectsPaddedName(t *testing.T) { + t.Parallel() + + err := validateForkMailboxes([]ForkMailboxPayload{{ + Name: " kernel.identity.v1 ", + Token: "template-token", + Payload: []byte(`{"instance_name":"forked"}`), + }}) + require.Error(t, err) + assert.ErrorIs(t, err, ErrInvalidRequest) +} From 8674804f677ff1bd56838e0ad50d4709adea16a3 Mon Sep 17 00:00:00 2001 From: sjmiller609 <7516283+sjmiller609@users.noreply.github.com> Date: Mon, 1 Jun 2026 19:55:53 +0000 Subject: [PATCH 04/15] Move fork mailbox frame writer into mailbox package --- lib/instances/fork_mailbox.go | 14 ++------------ lib/mailbox/mailbox.go | 21 +++++++++++++++++++++ lib/mailbox/mailbox_test.go | 20 ++++++++++++++++++++ 3 files changed, 43 insertions(+), 12 deletions(-) diff --git a/lib/instances/fork_mailbox.go b/lib/instances/fork_mailbox.go index 5c81dcb4..240e4a23 100644 --- a/lib/instances/fork_mailbox.go +++ b/lib/instances/fork_mailbox.go @@ -3,7 +3,6 @@ package instances import ( "bytes" "context" - "encoding/binary" "encoding/json" "fmt" "os" @@ -206,17 +205,8 @@ func patchForkMailbox(snapshotDir, name, token string, payload []byte) error { return fmt.Errorf("mailbox %q marker is too close to end of memory file", name) } - if _, err := file.WriteAt(payload, idx+int64(mailboxpkg.ForkMailboxPayloadOffset)); err != nil { - return fmt.Errorf("write mailbox %q payload: %w", name, err) - } - var u32 [4]byte - binary.LittleEndian.PutUint32(u32[:], uint32(len(payload))) - if _, err := file.WriteAt(u32[:], idx+int64(mailboxpkg.ForkMailboxLengthOffset)); err != nil { - return fmt.Errorf("write mailbox %q payload length: %w", name, err) - } - binary.LittleEndian.PutUint32(u32[:], 1) - if _, err := file.WriteAt(u32[:], idx+int64(mailboxpkg.ForkMailboxSeqOffset)); err != nil { - return fmt.Errorf("write mailbox %q sequence: %w", name, err) + if err := mailboxpkg.WriteForkMailboxPayloadAt(file, idx, payload); err != nil { + return fmt.Errorf("write mailbox %q frame: %w", name, err) } return nil } diff --git a/lib/mailbox/mailbox.go b/lib/mailbox/mailbox.go index 84b81e4c..40a67246 100644 --- a/lib/mailbox/mailbox.go +++ b/lib/mailbox/mailbox.go @@ -1,8 +1,10 @@ package mailbox import ( + "encoding/binary" "encoding/json" "fmt" + "io" "regexp" ) @@ -75,6 +77,25 @@ func ForkMailboxMarker(name, token string) ([]byte, error) { return marker, nil } +func WriteForkMailboxPayloadAt(w io.WriterAt, offset int64, payload []byte) error { + if len(payload) > ForkMailboxPayloadSize { + return fmt.Errorf("fork mailbox payload too large: %d bytes", len(payload)) + } + if _, err := w.WriteAt(payload, offset+int64(ForkMailboxPayloadOffset)); err != nil { + return fmt.Errorf("write fork mailbox payload: %w", err) + } + var u32 [4]byte + binary.LittleEndian.PutUint32(u32[:], uint32(len(payload))) + if _, err := w.WriteAt(u32[:], offset+int64(ForkMailboxLengthOffset)); err != nil { + return fmt.Errorf("write fork mailbox payload length: %w", err) + } + binary.LittleEndian.PutUint32(u32[:], 1) + if _, err := w.WriteAt(u32[:], offset+int64(ForkMailboxSeqOffset)); err != nil { + return fmt.Errorf("write fork mailbox sequence: %w", err) + } + return nil +} + func MarshalPayload(payload *Payload) ([]byte, error) { if payload == nil { return nil, fmt.Errorf("resume network mailbox payload is nil") diff --git a/lib/mailbox/mailbox_test.go b/lib/mailbox/mailbox_test.go index 75c0732e..735d1a3c 100644 --- a/lib/mailbox/mailbox_test.go +++ b/lib/mailbox/mailbox_test.go @@ -9,6 +9,13 @@ import ( "github.com/stretchr/testify/require" ) +type sliceWriterAt []byte + +func (w sliceWriterAt) WriteAt(p []byte, off int64) (int, error) { + copy(w[off:], p) + return len(p), nil +} + func TestMarker(t *testing.T) { t.Parallel() @@ -59,3 +66,16 @@ func TestForkMailboxMarker(t *testing.T) { _, err = ForkMailboxMarker("kernel.identity.v1", "") require.Error(t, err) } + +func TestWriteForkMailboxPayloadAt(t *testing.T) { + t.Parallel() + + buf := make([]byte, ForkMailboxSize) + payload := []byte(`{"instance_name":"forked"}`) + require.NoError(t, WriteForkMailboxPayloadAt(sliceWriterAt(buf), 512, payload)) + + assert.Equal(t, uint32(1), binary.LittleEndian.Uint32(buf[512+ForkMailboxSeqOffset:])) + payloadLen := binary.LittleEndian.Uint32(buf[512+ForkMailboxLengthOffset:]) + assert.Equal(t, uint32(len(payload)), payloadLen) + assert.Equal(t, string(payload), string(buf[512+ForkMailboxPayloadOffset:512+ForkMailboxPayloadOffset+int(payloadLen)])) +} From 5dcbe36b3d06347ea8e08f41c4f84768f3b37590 Mon Sep 17 00:00:00 2001 From: sjmiller609 <7516283+sjmiller609@users.noreply.github.com> Date: Mon, 1 Jun 2026 19:56:39 +0000 Subject: [PATCH 05/15] Extract fork mailbox restore handoff --- lib/instances/fork_mailbox.go | 43 +++++++++++++++++++++++++++++++++-- lib/instances/restore.go | 8 +++---- 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/lib/instances/fork_mailbox.go b/lib/instances/fork_mailbox.go index 240e4a23..b76c8924 100644 --- a/lib/instances/fork_mailbox.go +++ b/lib/instances/fork_mailbox.go @@ -25,6 +25,38 @@ type patchedForkMailbox struct { timeout time.Duration } +type forkMailboxHandoff struct { + manager *manager + stored *StoredMetadata + patched []patchedForkMailbox +} + +func (m *manager) prepareForkMailboxHandoff(ctx context.Context, stored *StoredMetadata, snapshotDir string, mailboxes []ForkMailboxPayload) (*forkMailboxHandoff, error) { + patched, err := m.patchForkMailboxes(ctx, stored, snapshotDir, mailboxes) + if err != nil { + return nil, err + } + return &forkMailboxHandoff{ + manager: m, + stored: stored, + patched: patched, + }, nil +} + +func (h *forkMailboxHandoff) AfterResume(ctx context.Context) error { + if h == nil { + return nil + } + return h.manager.waitForForkMailboxAcks(ctx, h.stored, h.patched) +} + +func (h *forkMailboxHandoff) Close() { + if h == nil { + return + } + closePatchedForkMailboxes(h.patched) +} + func validateForkMailboxes(mailboxes []ForkMailboxPayload) error { if len(mailboxes) > 16 { return fmt.Errorf("%w: at most 16 mailboxes can be patched for a fork", ErrInvalidRequest) @@ -69,7 +101,7 @@ func validateForkMailboxes(mailboxes []ForkMailboxPayload) error { return nil } -func (m *manager) patchForkMailboxes(ctx context.Context, snapshotDir string, mailboxes []ForkMailboxPayload) ([]patchedForkMailbox, error) { +func (m *manager) patchForkMailboxes(ctx context.Context, stored *StoredMetadata, snapshotDir string, mailboxes []ForkMailboxPayload) ([]patchedForkMailbox, error) { if len(mailboxes) == 0 { return nil, nil } @@ -93,7 +125,14 @@ func (m *manager) patchForkMailboxes(ctx context.Context, snapshotDir string, ma } } - if err := patchForkMailbox(snapshotDir, mailbox.Name, mailbox.Token, payload); err != nil { + _, patchSpanEnd := m.startLifecycleStep(ctx, "guest.fork_mailbox.patch", + attribute.String("instance_id", stored.Id), + attribute.String("mailbox", mailbox.Name), + attribute.String("operation", "guest_fork_mailbox_patch"), + ) + err := patchForkMailbox(snapshotDir, mailbox.Name, mailbox.Token, payload) + patchSpanEnd(err) + if err != nil { if waiter != nil { waiter.Close() } diff --git a/lib/instances/restore.go b/lib/instances/restore.go index 59692c1b..db3d734a 100644 --- a/lib/instances/restore.go +++ b/lib/instances/restore.go @@ -268,12 +268,12 @@ func (m *manager) restoreInstanceWithOptions( } defer resumeNetworkHandoff.Close() - patchedMailboxes, err := m.patchForkMailboxes(ctx, snapshotDir, opts.Mailboxes) + forkMailboxHandoff, err := m.prepareForkMailboxHandoff(ctx, stored, snapshotDir, opts.Mailboxes) if err != nil { releaseNetwork() - return nil, fmt.Errorf("patch fork mailboxes: %w", err) + return nil, fmt.Errorf("prepare fork mailbox handoff: %w", err) } - defer closePatchedForkMailboxes(patchedMailboxes) + defer forkMailboxHandoff.Close() // 5. Transition: Standby → Paused (start hypervisor + restore) restoreCtx, restoreSpanEnd := m.startLifecycleStep(ctx, "restore_from_snapshot", @@ -340,7 +340,7 @@ func (m *manager) restoreInstanceWithOptions( } } - if err := m.waitForForkMailboxAcks(ctx, stored, patchedMailboxes); err != nil { + if err := forkMailboxHandoff.AfterResume(ctx); err != nil { log.ErrorContext(ctx, "failed waiting for fork mailbox acknowledgements", "instance_id", id, "error", err) _ = hv.Shutdown(ctx) m.rollbackAdmissionAllocationActive(stored) From 92fae31fb5947806bdf3315fd97eff1646c5f08e Mon Sep 17 00:00:00 2001 From: sjmiller609 <7516283+sjmiller609@users.noreply.github.com> Date: Mon, 1 Jun 2026 21:04:40 +0000 Subject: [PATCH 06/15] Reject unsupported fork mailbox hypervisors --- lib/instances/fork.go | 3 +++ lib/instances/fork_mailbox.go | 8 ++++++++ lib/instances/fork_mailbox_test.go | 11 +++++++++++ lib/instances/snapshot.go | 5 +++++ 4 files changed, 27 insertions(+) diff --git a/lib/instances/fork.go b/lib/instances/fork.go index 54f53177..bb3023aa 100644 --- a/lib/instances/fork.go +++ b/lib/instances/fork.go @@ -54,6 +54,9 @@ func (m *manager) forkInstance(ctx context.Context, id string, req ForkInstanceR if targetState != StateRunning { return nil, "", fmt.Errorf("%w: mailboxes require target_state %s", ErrInvalidRequest, StateRunning) } + if err := validateForkMailboxHypervisor(source.HypervisorType); err != nil { + return nil, "", err + } } switch source.State { diff --git a/lib/instances/fork_mailbox.go b/lib/instances/fork_mailbox.go index b76c8924..a9894389 100644 --- a/lib/instances/fork_mailbox.go +++ b/lib/instances/fork_mailbox.go @@ -11,6 +11,7 @@ import ( "sync" "time" + "github.com/kernel/hypeman/lib/hypervisor" "github.com/kernel/hypeman/lib/logger" mailboxpkg "github.com/kernel/hypeman/lib/mailbox" "go.opentelemetry.io/otel/attribute" @@ -101,6 +102,13 @@ func validateForkMailboxes(mailboxes []ForkMailboxPayload) error { return nil } +func validateForkMailboxHypervisor(hvType hypervisor.Type) error { + if hvType != hypervisor.TypeFirecracker { + return fmt.Errorf("%w: mailboxes are only supported for %s standby forks", ErrNotSupported, hypervisor.TypeFirecracker) + } + return nil +} + func (m *manager) patchForkMailboxes(ctx context.Context, stored *StoredMetadata, snapshotDir string, mailboxes []ForkMailboxPayload) ([]patchedForkMailbox, error) { if len(mailboxes) == 0 { return nil, nil diff --git a/lib/instances/fork_mailbox_test.go b/lib/instances/fork_mailbox_test.go index 1672b7f5..c433cd58 100644 --- a/lib/instances/fork_mailbox_test.go +++ b/lib/instances/fork_mailbox_test.go @@ -6,6 +6,7 @@ import ( "path/filepath" "testing" + "github.com/kernel/hypeman/lib/hypervisor" "github.com/kernel/hypeman/lib/mailbox" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -52,3 +53,13 @@ func TestValidateForkMailboxesRejectsPaddedName(t *testing.T) { require.Error(t, err) assert.ErrorIs(t, err, ErrInvalidRequest) } + +func TestValidateForkMailboxHypervisor(t *testing.T) { + t.Parallel() + + require.NoError(t, validateForkMailboxHypervisor(hypervisor.TypeFirecracker)) + + err := validateForkMailboxHypervisor(hypervisor.TypeCloudHypervisor) + require.Error(t, err) + assert.ErrorIs(t, err, ErrNotSupported) +} diff --git a/lib/instances/snapshot.go b/lib/instances/snapshot.go index 9041473f..fb3724c0 100644 --- a/lib/instances/snapshot.go +++ b/lib/instances/snapshot.go @@ -399,6 +399,11 @@ func (m *manager) forkSnapshot(ctx context.Context, snapshotID string, req ForkS if err != nil { return nil, err } + if len(req.Mailboxes) > 0 { + if err := validateForkMailboxHypervisor(targetHypervisor); err != nil { + return nil, err + } + } forkID := cuid2.Generate() if _, err := m.loadMetadata(forkID); err == nil { From 882550dc0175227bf8e30ecec873236838df58a0 Mon Sep 17 00:00:00 2001 From: sjmiller609 <7516283+sjmiller609@users.noreply.github.com> Date: Mon, 1 Jun 2026 21:07:16 +0000 Subject: [PATCH 07/15] Share mailbox snapshot patch primitive --- lib/instances/fork_mailbox.go | 126 +++++++++++++------------- lib/instances/fork_mailbox_test.go | 43 ++++++++- lib/instances/guest_resume_network.go | 43 ++------- lib/mailbox/mailbox.go | 89 ++++++++++++++++-- 4 files changed, 192 insertions(+), 109 deletions(-) diff --git a/lib/instances/fork_mailbox.go b/lib/instances/fork_mailbox.go index a9894389..a70f2837 100644 --- a/lib/instances/fork_mailbox.go +++ b/lib/instances/fork_mailbox.go @@ -1,7 +1,6 @@ package instances import ( - "bytes" "context" "encoding/json" "fmt" @@ -15,7 +14,6 @@ import ( "github.com/kernel/hypeman/lib/logger" mailboxpkg "github.com/kernel/hypeman/lib/mailbox" "go.opentelemetry.io/otel/attribute" - "golang.org/x/sys/unix" ) var forkMailboxOffsetByMark sync.Map @@ -26,6 +24,12 @@ type patchedForkMailbox struct { timeout time.Duration } +type forkMailboxPatch struct { + name string + token string + payload []byte +} + type forkMailboxHandoff struct { manager *manager stored *StoredMetadata @@ -115,6 +119,7 @@ func (m *manager) patchForkMailboxes(ctx context.Context, stored *StoredMetadata } patched := make([]patchedForkMailbox, 0, len(mailboxes)) + patches := make([]forkMailboxPatch, 0, len(mailboxes)) for _, mailbox := range mailboxes { payload := mailbox.Payload var waiter *guestResumeNetworkUDPWaiter @@ -133,26 +138,21 @@ func (m *manager) patchForkMailboxes(ctx context.Context, stored *StoredMetadata } } - _, patchSpanEnd := m.startLifecycleStep(ctx, "guest.fork_mailbox.patch", - attribute.String("instance_id", stored.Id), - attribute.String("mailbox", mailbox.Name), - attribute.String("operation", "guest_fork_mailbox_patch"), - ) - err := patchForkMailbox(snapshotDir, mailbox.Name, mailbox.Token, payload) - patchSpanEnd(err) - if err != nil { - if waiter != nil { - waiter.Close() - } - closePatchedForkMailboxes(patched) - return nil, err - } + patches = append(patches, forkMailboxPatch{ + name: mailbox.Name, + token: mailbox.Token, + payload: payload, + }) patched = append(patched, patchedForkMailbox{ name: mailbox.Name, waiter: waiter, timeout: forkMailboxAckTimeout(mailbox.AckTimeout), }) } + if err := m.patchForkMailboxPayloads(ctx, stored, snapshotDir, patches); err != nil { + closePatchedForkMailboxes(patched) + return nil, err + } return patched, nil } @@ -214,71 +214,69 @@ func forkMailboxPayloadWithAckPort(payload json.RawMessage, port uint32) (json.R return out, nil } -func patchForkMailbox(snapshotDir, name, token string, payload []byte) error { - if !mailboxpkg.ValidForkMailboxName(name) { - return fmt.Errorf("invalid mailbox name %q", name) - } - if !mailboxpkg.ValidForkMailboxToken(token) { - return fmt.Errorf("mailbox %q token is empty", name) - } - if len(payload) > mailboxpkg.ForkMailboxPayloadSize { - return fmt.Errorf("mailbox %q payload too large: %d bytes", name, len(payload)) - } - - marker, err := mailboxpkg.ForkMailboxMarker(name, token) - if err != nil { - return fmt.Errorf("build mailbox %q marker: %w", name, err) - } - +func (m *manager) patchForkMailboxPayloads(ctx context.Context, stored *StoredMetadata, snapshotDir string, patches []forkMailboxPatch) error { file, err := os.OpenFile(filepath.Join(snapshotDir, firecrackerSnapshotMemoryFile), os.O_RDWR, 0) if err != nil { - return fmt.Errorf("open snapshot memory for mailbox %q: %w", name, err) + return fmt.Errorf("open snapshot memory for fork mailboxes: %w", err) } defer file.Close() info, err := file.Stat() if err != nil { - return fmt.Errorf("stat snapshot memory for mailbox %q: %w", name, err) + return fmt.Errorf("stat snapshot memory for fork mailboxes: %w", err) } if info.Size() <= 0 { - return fmt.Errorf("mailbox %q memory file is empty", name) + return fmt.Errorf("fork mailbox memory file is empty") } - idx, err := findForkMailbox(file, info.Size(), marker) - if err != nil { - return fmt.Errorf("find mailbox %q marker: %w", name, err) + type preparedForkMailboxPatch struct { + patch forkMailboxPatch + offset int64 + finish func(error) } - if idx+int64(mailboxpkg.ForkMailboxPayloadOffset)+int64(len(payload)) > info.Size() { - return fmt.Errorf("mailbox %q marker is too close to end of memory file", name) + prepared := make([]preparedForkMailboxPatch, 0, len(patches)) + finishPrepared := func(err error) { + for _, item := range prepared { + item.finish(err) + } } - if err := mailboxpkg.WriteForkMailboxPayloadAt(file, idx, payload); err != nil { - return fmt.Errorf("write mailbox %q frame: %w", name, err) - } - return nil -} - -func findForkMailbox(file *os.File, size int64, marker []byte) (int64, error) { - cacheKey := string(marker) - if cached, ok := forkMailboxOffsetByMark.Load(cacheKey); ok { - if offset, ok := cached.(int64); ok && offset >= 0 && offset+int64(len(marker)) <= size { - buf := make([]byte, len(marker)) - if _, err := file.ReadAt(buf, offset); err == nil && bytes.Equal(buf, marker) { - return offset, nil + for _, patch := range patches { + _, patchSpanEnd := m.startLifecycleStep(ctx, "guest.fork_mailbox.patch", + attribute.String("instance_id", stored.Id), + attribute.String("mailbox", patch.name), + attribute.String("operation", "guest_fork_mailbox_patch"), + ) + marker, err := mailboxpkg.ForkMailboxMarker(patch.name, patch.token) + if err == nil { + var offset int64 + offset, err = mailboxpkg.FindMarker(file, info.Size(), marker, &forkMailboxOffsetByMark) + if err == nil { + err = mailboxpkg.EnsurePayloadFits(mailboxpkg.ForkLayout, info.Size(), offset, len(patch.payload)) + } + if err == nil { + prepared = append(prepared, preparedForkMailboxPatch{ + patch: patch, + offset: offset, + finish: patchSpanEnd, + }) + continue } } + patchSpanEnd(err) + finishPrepared(err) + return fmt.Errorf("preflight mailbox %q: %w", patch.name, err) } - data, err := unix.Mmap(int(file.Fd()), 0, int(size), unix.PROT_READ, unix.MAP_SHARED) - if err != nil { - return 0, fmt.Errorf("mmap snapshot memory: %w", err) - } - defer unix.Munmap(data) - - idx := bytes.Index(data, marker) - if idx < 0 { - return 0, fmt.Errorf("marker not found") + for i, item := range prepared { + err := mailboxpkg.WritePayloadAt(file, mailboxpkg.ForkLayout, item.offset, item.patch.payload) + item.finish(err) + if err != nil { + for _, pending := range prepared[i+1:] { + pending.finish(err) + } + return fmt.Errorf("write mailbox %q frame: %w", item.patch.name, err) + } } - forkMailboxOffsetByMark.Store(cacheKey, int64(idx)) - return int64(idx), nil + return nil } diff --git a/lib/instances/fork_mailbox_test.go b/lib/instances/fork_mailbox_test.go index c433cd58..afc42af2 100644 --- a/lib/instances/fork_mailbox_test.go +++ b/lib/instances/fork_mailbox_test.go @@ -1,6 +1,7 @@ package instances import ( + "context" "encoding/binary" "os" "path/filepath" @@ -24,7 +25,13 @@ func TestPatchForkMailbox(t *testing.T) { copy(memory[offset:], marker) require.NoError(t, os.WriteFile(memoryPath, memory, 0600)) - require.NoError(t, patchForkMailbox(dir, "kernel.identity.v1", "template-token", []byte(`{"instance_name":"forked"}`))) + mgr := &manager{} + stored := &StoredMetadata{Id: "forked-instance"} + require.NoError(t, mgr.patchForkMailboxPayloads(context.Background(), stored, dir, []forkMailboxPatch{{ + name: "kernel.identity.v1", + token: "template-token", + payload: []byte(`{"instance_name":"forked"}`), + }})) updated, err := os.ReadFile(memoryPath) require.NoError(t, err) @@ -34,6 +41,40 @@ func TestPatchForkMailbox(t *testing.T) { assert.Equal(t, `{"instance_name":"forked"}`, string(updated[offset+mailbox.ForkMailboxPayloadOffset:offset+mailbox.ForkMailboxPayloadOffset+int(payloadLen)])) } +func TestPatchForkMailboxPreflightsAllMarkers(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + memoryPath := filepath.Join(dir, firecrackerSnapshotMemoryFile) + memory := make([]byte, 8192) + marker, err := mailbox.ForkMailboxMarker("kernel.identity.v1", "template-token") + require.NoError(t, err) + const offset = 1024 + copy(memory[offset:], marker) + require.NoError(t, os.WriteFile(memoryPath, memory, 0600)) + + mgr := &manager{} + stored := &StoredMetadata{Id: "forked-instance"} + err = mgr.patchForkMailboxPayloads(context.Background(), stored, dir, []forkMailboxPatch{ + { + name: "kernel.identity.v1", + token: "template-token", + payload: []byte(`{"instance_name":"forked"}`), + }, + { + name: "kernel.other.v1", + token: "other-token", + payload: []byte(`{"value":true}`), + }, + }) + require.Error(t, err) + + updated, err := os.ReadFile(memoryPath) + require.NoError(t, err) + assert.Equal(t, uint32(0), binary.LittleEndian.Uint32(updated[offset+mailbox.ForkMailboxSeqOffset:])) + assert.Equal(t, uint32(0), binary.LittleEndian.Uint32(updated[offset+mailbox.ForkMailboxLengthOffset:])) +} + func TestForkMailboxPayloadWithAckPort(t *testing.T) { t.Parallel() diff --git a/lib/instances/guest_resume_network.go b/lib/instances/guest_resume_network.go index 3121f80d..3c739ac8 100644 --- a/lib/instances/guest_resume_network.go +++ b/lib/instances/guest_resume_network.go @@ -1,9 +1,7 @@ package instances import ( - "bytes" "context" - "encoding/binary" "fmt" stdnet "net" "os" @@ -17,7 +15,6 @@ import ( "github.com/kernel/hypeman/lib/mailbox" "github.com/nrednav/cuid2" "go.opentelemetry.io/otel/attribute" - "golang.org/x/sys/unix" ) const firecrackerSnapshotMemoryFile = "memory" @@ -211,45 +208,19 @@ func patchGuestResumeNetworkMailbox(snapshotDir, token string, payload *mailbox. if err != nil { return err } - if idx+int64(mailbox.MailboxPayloadOffset)+int64(len(payloadBytes)) > info.Size() { - return fmt.Errorf("resume network mailbox marker is too close to end of memory file") + if err := mailbox.EnsurePayloadFits(mailbox.ResumeNetworkLayout, info.Size(), idx, len(payloadBytes)); err != nil { + return fmt.Errorf("resume network mailbox payload does not fit: %w", err) } - - if _, err := file.WriteAt(payloadBytes, idx+int64(mailbox.MailboxPayloadOffset)); err != nil { - return fmt.Errorf("write resume network mailbox payload: %w", err) - } - var u32 [4]byte - binary.LittleEndian.PutUint32(u32[:], uint32(len(payloadBytes))) - if _, err := file.WriteAt(u32[:], idx+int64(mailbox.MailboxLengthOffset)); err != nil { - return fmt.Errorf("write resume network mailbox payload length: %w", err) - } - binary.LittleEndian.PutUint32(u32[:], 1) - if _, err := file.WriteAt(u32[:], idx+int64(mailbox.MailboxSeqOffset)); err != nil { - return fmt.Errorf("write resume network mailbox sequence: %w", err) + if err := mailbox.WritePayloadAt(file, mailbox.ResumeNetworkLayout, idx, payloadBytes); err != nil { + return fmt.Errorf("write resume network mailbox frame: %w", err) } return nil } func findGuestResumeNetworkMailbox(file *os.File, size int64, marker []byte, token string) (int64, error) { - if cached, ok := guestResumeNetworkMailboxOffsets.Load(token); ok { - if offset, ok := cached.(int64); ok && offset >= 0 && offset+int64(len(marker)) <= size { - buf := make([]byte, len(marker)) - if _, err := file.ReadAt(buf, offset); err == nil && bytes.Equal(buf, marker) { - return offset, nil - } - } - } - - data, err := unix.Mmap(int(file.Fd()), 0, int(size), unix.PROT_READ, unix.MAP_SHARED) + idx, err := mailbox.FindMarker(file, size, marker, &guestResumeNetworkMailboxOffsets) if err != nil { - return 0, fmt.Errorf("mmap snapshot memory for resume network mailbox: %w", err) - } - defer unix.Munmap(data) - - idx := bytes.Index(data, marker) - if idx < 0 { - return 0, fmt.Errorf("resume network mailbox marker not found") + return 0, fmt.Errorf("find resume network mailbox marker for token %q: %w", token, err) } - guestResumeNetworkMailboxOffsets.Store(token, int64(idx)) - return int64(idx), nil + return idx, nil } diff --git a/lib/mailbox/mailbox.go b/lib/mailbox/mailbox.go index 40a67246..a0986913 100644 --- a/lib/mailbox/mailbox.go +++ b/lib/mailbox/mailbox.go @@ -1,11 +1,16 @@ package mailbox import ( + "bytes" "encoding/binary" "encoding/json" "fmt" "io" + "os" "regexp" + "sync" + + "golang.org/x/sys/unix" ) const MailboxEnv = "HYPEMAN_RESUME_NETWORK_MAILBOX" @@ -28,6 +33,31 @@ const ForkMailboxTokenMaxLen = 128 var ForkMailboxNamePattern = regexp.MustCompile(`^[A-Za-z0-9._:-]{1,64}$`) +type Layout struct { + Size int + SeqOffset int + LengthOffset int + PayloadOffset int +} + +var ResumeNetworkLayout = Layout{ + Size: MailboxSize, + SeqOffset: MailboxSeqOffset, + LengthOffset: MailboxLengthOffset, + PayloadOffset: MailboxPayloadOffset, +} + +var ForkLayout = Layout{ + Size: ForkMailboxSize, + SeqOffset: ForkMailboxSeqOffset, + LengthOffset: ForkMailboxLengthOffset, + PayloadOffset: ForkMailboxPayloadOffset, +} + +func (l Layout) PayloadSize() int { + return l.Size - l.PayloadOffset +} + type Payload struct { InterfaceName string `json:"interface_name"` MAC string `json:"mac"` @@ -78,20 +108,63 @@ func ForkMailboxMarker(name, token string) ([]byte, error) { } func WriteForkMailboxPayloadAt(w io.WriterAt, offset int64, payload []byte) error { - if len(payload) > ForkMailboxPayloadSize { - return fmt.Errorf("fork mailbox payload too large: %d bytes", len(payload)) + return WritePayloadAt(w, ForkLayout, offset, payload) +} + +func FindMarker(file *os.File, size int64, marker []byte, cache *sync.Map) (int64, error) { + cacheKey := string(marker) + if cache != nil { + if cached, ok := cache.Load(cacheKey); ok { + if offset, ok := cached.(int64); ok && offset >= 0 && offset+int64(len(marker)) <= size { + buf := make([]byte, len(marker)) + if _, err := file.ReadAt(buf, offset); err == nil && bytes.Equal(buf, marker) { + return offset, nil + } + } + } + } + + data, err := unix.Mmap(int(file.Fd()), 0, int(size), unix.PROT_READ, unix.MAP_SHARED) + if err != nil { + return 0, fmt.Errorf("mmap snapshot memory: %w", err) + } + defer unix.Munmap(data) + + idx := bytes.Index(data, marker) + if idx < 0 { + return 0, fmt.Errorf("marker not found") + } + if cache != nil { + cache.Store(cacheKey, int64(idx)) + } + return int64(idx), nil +} + +func EnsurePayloadFits(layout Layout, memorySize int64, offset int64, payloadLen int) error { + if payloadLen > layout.PayloadSize() { + return fmt.Errorf("mailbox payload too large: %d bytes", payloadLen) + } + if offset+int64(layout.PayloadOffset)+int64(payloadLen) > memorySize { + return fmt.Errorf("mailbox marker is too close to end of memory file") + } + return nil +} + +func WritePayloadAt(w io.WriterAt, layout Layout, offset int64, payload []byte) error { + if len(payload) > layout.PayloadSize() { + return fmt.Errorf("mailbox payload too large: %d bytes", len(payload)) } - if _, err := w.WriteAt(payload, offset+int64(ForkMailboxPayloadOffset)); err != nil { - return fmt.Errorf("write fork mailbox payload: %w", err) + if _, err := w.WriteAt(payload, offset+int64(layout.PayloadOffset)); err != nil { + return fmt.Errorf("write mailbox payload: %w", err) } var u32 [4]byte binary.LittleEndian.PutUint32(u32[:], uint32(len(payload))) - if _, err := w.WriteAt(u32[:], offset+int64(ForkMailboxLengthOffset)); err != nil { - return fmt.Errorf("write fork mailbox payload length: %w", err) + if _, err := w.WriteAt(u32[:], offset+int64(layout.LengthOffset)); err != nil { + return fmt.Errorf("write mailbox payload length: %w", err) } binary.LittleEndian.PutUint32(u32[:], 1) - if _, err := w.WriteAt(u32[:], offset+int64(ForkMailboxSeqOffset)); err != nil { - return fmt.Errorf("write fork mailbox sequence: %w", err) + if _, err := w.WriteAt(u32[:], offset+int64(layout.SeqOffset)); err != nil { + return fmt.Errorf("write mailbox sequence: %w", err) } return nil } From d195d9beb4e8547fa220fc675127387013ea2323 Mon Sep 17 00:00:00 2001 From: sjmiller609 <7516283+sjmiller609@users.noreply.github.com> Date: Mon, 1 Jun 2026 21:08:21 +0000 Subject: [PATCH 08/15] Parse fork mailbox acknowledgements exactly --- lib/instances/fork_mailbox_test.go | 36 +++++++++++++++++++++++++++ lib/instances/guest_resume_network.go | 17 ++++++++++--- 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/lib/instances/fork_mailbox_test.go b/lib/instances/fork_mailbox_test.go index afc42af2..d87e27cf 100644 --- a/lib/instances/fork_mailbox_test.go +++ b/lib/instances/fork_mailbox_test.go @@ -6,6 +6,7 @@ import ( "os" "path/filepath" "testing" + "time" "github.com/kernel/hypeman/lib/hypervisor" "github.com/kernel/hypeman/lib/mailbox" @@ -83,6 +84,41 @@ func TestForkMailboxPayloadWithAckPort(t *testing.T) { assert.JSONEq(t, `{"instance_name":"forked","ack_port":12345}`, string(payload)) } +func TestWaitMailboxAppliedRequiresExactFields(t *testing.T) { + t.Parallel() + + waiter := &guestResumeNetworkUDPWaiter{ch: make(chan guestResumeNetworkUDPAck, 2)} + now := time.Now() + waiter.ch <- guestResumeNetworkUDPAck{ + received: now, + text: "stage=applied mailbox=kernel.identity.v10", + } + waiter.ch <- guestResumeNetworkUDPAck{ + received: now.Add(time.Millisecond), + text: "mailbox=kernel.identity.v1 stage=applied", + } + + _, ack, err := waiter.WaitMailboxApplied(context.Background(), "kernel.identity.v1") + require.NoError(t, err) + assert.Equal(t, "mailbox=kernel.identity.v1 stage=applied", ack) +} + +func TestWaitMailboxAppliedIgnoresMalformedAck(t *testing.T) { + t.Parallel() + + waiter := &guestResumeNetworkUDPWaiter{ch: make(chan guestResumeNetworkUDPAck, 1)} + waiter.ch <- guestResumeNetworkUDPAck{ + received: time.Now(), + text: "stage=applied mailbox=kernel.identity.v1-extra freeform", + } + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond) + defer cancel() + + _, _, err := waiter.WaitMailboxApplied(ctx, "kernel.identity.v1") + require.Error(t, err) + assert.ErrorIs(t, err, context.DeadlineExceeded) +} + func TestValidateForkMailboxesRejectsPaddedName(t *testing.T) { t.Parallel() diff --git a/lib/instances/guest_resume_network.go b/lib/instances/guest_resume_network.go index 3c739ac8..907b29e1 100644 --- a/lib/instances/guest_resume_network.go +++ b/lib/instances/guest_resume_network.go @@ -143,12 +143,11 @@ func (w *guestResumeNetworkUDPWaiter) WaitMailboxApplied(ctx context.Context, na } start := time.Now() - wantMailbox := "mailbox=" + strings.ToLower(name) for { select { case ack := <-w.ch: - text := strings.ToLower(ack.text) - if strings.Contains(text, "stage=applied") && strings.Contains(text, wantMailbox) { + fields := parseUDPAckFields(ack.text) + if strings.EqualFold(fields["stage"], "applied") && fields["mailbox"] == name { return ack.received.Sub(start), ack.text, nil } case <-ctx.Done(): @@ -157,6 +156,18 @@ func (w *guestResumeNetworkUDPWaiter) WaitMailboxApplied(ctx context.Context, na } } +func parseUDPAckFields(text string) map[string]string { + fields := make(map[string]string) + for _, part := range strings.Fields(text) { + key, value, ok := strings.Cut(part, "=") + if !ok || key == "" { + continue + } + fields[strings.ToLower(key)] = value + } + return fields +} + func (m *manager) waitForGuestResumeNetworkUDPAck(ctx context.Context, waiter *guestResumeNetworkUDPWaiter, stored *StoredMetadata, cfg *guestNetworkConfig) error { if waiter == nil || cfg == nil { return nil From 70a922b46a26d725ad90d3b2cc113d327fa8425f Mon Sep 17 00:00:00 2001 From: sjmiller609 <7516283+sjmiller609@users.noreply.github.com> Date: Tue, 2 Jun 2026 15:44:49 +0000 Subject: [PATCH 09/15] Unify restore handoff orchestration --- lib/instances/restore.go | 53 +++++++++++++------------ lib/instances/resume_network_handoff.go | 14 ++++++- 2 files changed, 40 insertions(+), 27 deletions(-) diff --git a/lib/instances/restore.go b/lib/instances/restore.go index db3d734a..3935e66f 100644 --- a/lib/instances/restore.go +++ b/lib/instances/restore.go @@ -24,6 +24,11 @@ type restoreInstanceOptions struct { Mailboxes []ForkMailboxPayload } +type restoreHandoff interface { + AfterResume(ctx context.Context) error + Close() +} + // RestoreInstance restores an instance from standby. // Multi-hop orchestration: Standby → Paused → Running. func (m *manager) restoreInstance( @@ -260,20 +265,23 @@ func (m *manager) restoreInstanceWithOptions( proxyRegistered = true } + var handoffs []restoreHandoff + resumeNetworkHandoff, err := m.prepareResumeNetworkHandoff(ctx, stored, allocatedNet, snapshotDir) if err != nil { log.ErrorContext(ctx, "failed to prepare guest resume network handoff", "instance_id", id, "error", err) releaseNetwork() return nil, fmt.Errorf("prepare guest resume network handoff: %w", err) } - defer resumeNetworkHandoff.Close() + handoffs = append(handoffs, resumeNetworkHandoff) forkMailboxHandoff, err := m.prepareForkMailboxHandoff(ctx, stored, snapshotDir, opts.Mailboxes) if err != nil { releaseNetwork() return nil, fmt.Errorf("prepare fork mailbox handoff: %w", err) } - defer forkMailboxHandoff.Close() + handoffs = append(handoffs, forkMailboxHandoff) + defer closeRestoreHandoffs(handoffs) // 5. Transition: Standby → Paused (start hypervisor + restore) restoreCtx, restoreSpanEnd := m.startLifecycleStep(ctx, "restore_from_snapshot", @@ -320,32 +328,11 @@ func (m *manager) restoreInstanceWithOptions( reservedResources = false } - // Forked standby restores may allocate a fresh identity while the guest memory snapshot - // still has the source VM's old IP configuration. Reconfigure guest networking after - // resume so host ingress to the new private IP works reliably. - if allocatedNet != nil && !stored.SkipGuestAgent { - reconfigureCtx, reconfigureSpanEnd := m.startLifecycleStep(ctx, "reconfigure_guest_network", - attribute.String("instance_id", id), - attribute.String("hypervisor", string(stored.HypervisorType)), - attribute.String("operation", "reconfigure_guest_network"), - ) - reconfigureErr := resumeNetworkHandoff.AfterResume(reconfigureCtx) - reconfigureSpanEnd(reconfigureErr) - if reconfigureErr != nil { - log.ErrorContext(ctx, "failed to configure guest network after restore", "instance_id", id, "error", reconfigureErr) - _ = hv.Shutdown(ctx) - m.rollbackAdmissionAllocationActive(stored) - releaseNetwork() - return nil, fmt.Errorf("configure guest network after restore: %w", reconfigureErr) - } - } - - if err := forkMailboxHandoff.AfterResume(ctx); err != nil { - log.ErrorContext(ctx, "failed waiting for fork mailbox acknowledgements", "instance_id", id, "error", err) + if err := afterRestoreHandoffs(ctx, stored, handoffs); err != nil { _ = hv.Shutdown(ctx) m.rollbackAdmissionAllocationActive(stored) releaseNetwork() - return nil, fmt.Errorf("wait for fork mailbox acknowledgements: %w", err) + return nil, err } // 8. Delete snapshot after successful restore unless the hypervisor is keeping it @@ -385,6 +372,22 @@ func (m *manager) restoreInstanceWithOptions( return &finalInst, nil } +func closeRestoreHandoffs(handoffs []restoreHandoff) { + for _, handoff := range handoffs { + handoff.Close() + } +} + +func afterRestoreHandoffs(ctx context.Context, stored *StoredMetadata, handoffs []restoreHandoff) error { + for _, handoff := range handoffs { + if err := handoff.AfterResume(ctx); err != nil { + logger.FromContext(ctx).ErrorContext(ctx, "failed after restore handoff", "instance_id", stored.Id, "error", err) + return fmt.Errorf("after restore handoff: %w", err) + } + } + return nil +} + func (m *manager) restoreCompressionConfigForMetrics(stored *StoredMetadata, snapshotDir string) *snapshotstore.SnapshotCompressionConfig { _, algorithm, ok := findCompressedSnapshotMemoryFile(snapshotDir) if !ok { diff --git a/lib/instances/resume_network_handoff.go b/lib/instances/resume_network_handoff.go index 073fc92e..9136a35f 100644 --- a/lib/instances/resume_network_handoff.go +++ b/lib/instances/resume_network_handoff.go @@ -71,12 +71,22 @@ func (h *resumeNetworkHandoff) AfterResume(ctx context.Context) error { if h == nil || h.allocatedNet == nil || h.stored.SkipGuestAgent { return nil } + + ctx, spanEnd := h.manager.startLifecycleStep(ctx, "reconfigure_guest_network", + attribute.String("instance_id", h.stored.Id), + attribute.String("hypervisor", string(h.stored.HypervisorType)), + attribute.String("operation", "reconfigure_guest_network"), + ) + var err error + defer func() { spanEnd(err) }() + if h.patched { - err := h.manager.waitForGuestResumeNetworkUDPAck(ctx, h.ackWaiter, h.stored, h.ackCfg) + err = h.manager.waitForGuestResumeNetworkUDPAck(ctx, h.ackWaiter, h.stored, h.ackCfg) if err == nil { return nil } logger.FromContext(ctx).ErrorContext(ctx, "guest resume network UDP ack wait failed; falling back to host-initiated reconfigure", "instance_id", h.stored.Id, "error", err) } - return reconfigureGuestNetwork(ctx, h.stored, h.allocatedNet) + err = reconfigureGuestNetwork(ctx, h.stored, h.allocatedNet) + return err } From 58ecc21e3d9cffbfcb3c8c329fe21621a5f5dd6d Mon Sep 17 00:00:00 2001 From: sjmiller609 <7516283+sjmiller609@users.noreply.github.com> Date: Tue, 2 Jun 2026 15:45:19 +0000 Subject: [PATCH 10/15] Share UDP ack wait matching --- lib/instances/fork_mailbox_test.go | 19 +++++++++++++++ lib/instances/guest_resume_network.go | 34 +++++++++++---------------- 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/lib/instances/fork_mailbox_test.go b/lib/instances/fork_mailbox_test.go index d87e27cf..8ff02f5e 100644 --- a/lib/instances/fork_mailbox_test.go +++ b/lib/instances/fork_mailbox_test.go @@ -119,6 +119,25 @@ func TestWaitMailboxAppliedIgnoresMalformedAck(t *testing.T) { assert.ErrorIs(t, err, context.DeadlineExceeded) } +func TestWaitAppliedRequiresExactFields(t *testing.T) { + t.Parallel() + + waiter := &guestResumeNetworkUDPWaiter{ch: make(chan guestResumeNetworkUDPAck, 2)} + now := time.Now() + waiter.ch <- guestResumeNetworkUDPAck{ + received: now, + text: "stage=applied mac=02:00:00:85:17:c8:ff ip=10.102.146.62", + } + waiter.ch <- guestResumeNetworkUDPAck{ + received: now.Add(time.Millisecond), + text: "ip=10.102.146.62 stage=applied mac=02:00:00:85:17:C8", + } + + _, ack, err := waiter.WaitApplied(context.Background(), "02:00:00:85:17:c8", "10.102.146.62") + require.NoError(t, err) + assert.Equal(t, "ip=10.102.146.62 stage=applied mac=02:00:00:85:17:C8", ack) +} + func TestValidateForkMailboxesRejectsPaddedName(t *testing.T) { t.Parallel() diff --git a/lib/instances/guest_resume_network.go b/lib/instances/guest_resume_network.go index 907b29e1..7468fefa 100644 --- a/lib/instances/guest_resume_network.go +++ b/lib/instances/guest_resume_network.go @@ -117,29 +117,23 @@ func (w *guestResumeNetworkUDPWaiter) readLoop() { } func (w *guestResumeNetworkUDPWaiter) WaitApplied(ctx context.Context, mac, ip string) (time.Duration, string, error) { - if w == nil { - return 0, "", fmt.Errorf("guest resume network UDP waiter is nil") - } - - start := time.Now() - wantMAC := "mac=" + strings.ToLower(mac) - wantIP := "ip=" + ip - for { - select { - case ack := <-w.ch: - text := strings.ToLower(ack.text) - if strings.Contains(text, "stage=applied") && strings.Contains(text, wantMAC) && strings.Contains(text, wantIP) { - return ack.received.Sub(start), ack.text, nil - } - case <-ctx.Done(): - return 0, "", ctx.Err() - } - } + wantMAC := strings.ToLower(mac) + return w.wait(ctx, func(fields map[string]string) bool { + return strings.EqualFold(fields["stage"], "applied") && + strings.EqualFold(fields["mac"], wantMAC) && + fields["ip"] == ip + }) } func (w *guestResumeNetworkUDPWaiter) WaitMailboxApplied(ctx context.Context, name string) (time.Duration, string, error) { + return w.wait(ctx, func(fields map[string]string) bool { + return strings.EqualFold(fields["stage"], "applied") && fields["mailbox"] == name + }) +} + +func (w *guestResumeNetworkUDPWaiter) wait(ctx context.Context, match func(map[string]string) bool) (time.Duration, string, error) { if w == nil { - return 0, "", fmt.Errorf("guest fork mailbox UDP waiter is nil") + return 0, "", fmt.Errorf("guest resume network UDP waiter is nil") } start := time.Now() @@ -147,7 +141,7 @@ func (w *guestResumeNetworkUDPWaiter) WaitMailboxApplied(ctx context.Context, na select { case ack := <-w.ch: fields := parseUDPAckFields(ack.text) - if strings.EqualFold(fields["stage"], "applied") && fields["mailbox"] == name { + if match(fields) { return ack.received.Sub(start), ack.text, nil } case <-ctx.Done(): From 203efbd00c21f8a04edd6d9e8a37c3e3f63547ed Mon Sep 17 00:00:00 2001 From: sjmiller609 <7516283+sjmiller609@users.noreply.github.com> Date: Tue, 2 Jun 2026 15:45:49 +0000 Subject: [PATCH 11/15] Remove fork mailbox writer wrapper --- lib/mailbox/mailbox.go | 4 ---- lib/mailbox/mailbox_test.go | 4 ++-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/mailbox/mailbox.go b/lib/mailbox/mailbox.go index a0986913..b2bb362c 100644 --- a/lib/mailbox/mailbox.go +++ b/lib/mailbox/mailbox.go @@ -107,10 +107,6 @@ func ForkMailboxMarker(name, token string) ([]byte, error) { return marker, nil } -func WriteForkMailboxPayloadAt(w io.WriterAt, offset int64, payload []byte) error { - return WritePayloadAt(w, ForkLayout, offset, payload) -} - func FindMarker(file *os.File, size int64, marker []byte, cache *sync.Map) (int64, error) { cacheKey := string(marker) if cache != nil { diff --git a/lib/mailbox/mailbox_test.go b/lib/mailbox/mailbox_test.go index 735d1a3c..4f0590bb 100644 --- a/lib/mailbox/mailbox_test.go +++ b/lib/mailbox/mailbox_test.go @@ -67,12 +67,12 @@ func TestForkMailboxMarker(t *testing.T) { require.Error(t, err) } -func TestWriteForkMailboxPayloadAt(t *testing.T) { +func TestWriteForkMailboxPayloadFrame(t *testing.T) { t.Parallel() buf := make([]byte, ForkMailboxSize) payload := []byte(`{"instance_name":"forked"}`) - require.NoError(t, WriteForkMailboxPayloadAt(sliceWriterAt(buf), 512, payload)) + require.NoError(t, WritePayloadAt(sliceWriterAt(buf), ForkLayout, 512, payload)) assert.Equal(t, uint32(1), binary.LittleEndian.Uint32(buf[512+ForkMailboxSeqOffset:])) payloadLen := binary.LittleEndian.Uint32(buf[512+ForkMailboxLengthOffset:]) From 7de8b71c09201465889e2668755133d43c1ba82d Mon Sep 17 00:00:00 2001 From: sjmiller609 <7516283+sjmiller609@users.noreply.github.com> Date: Tue, 2 Jun 2026 15:46:14 +0000 Subject: [PATCH 12/15] Tighten fork mailbox ack timeout validation --- lib/instances/fork_mailbox.go | 12 +++++++----- lib/instances/fork_mailbox_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/lib/instances/fork_mailbox.go b/lib/instances/fork_mailbox.go index a70f2837..6d84a8cf 100644 --- a/lib/instances/fork_mailbox.go +++ b/lib/instances/fork_mailbox.go @@ -96,11 +96,13 @@ func validateForkMailboxes(mailboxes []ForkMailboxPayload) error { if err := json.Unmarshal(mailbox.Payload, &payload); err != nil || payload == nil { return fmt.Errorf("%w: mailbox %q payload must be a JSON object", ErrInvalidRequest, name) } - if mailbox.WaitForAck && mailbox.AckTimeout < 0 { - return fmt.Errorf("%w: mailbox %q ack_timeout_ms must be positive", ErrInvalidRequest, name) - } - if mailbox.AckTimeout > 30*time.Second { - return fmt.Errorf("%w: mailbox %q ack_timeout_ms must be 30000 or less", ErrInvalidRequest, name) + if mailbox.WaitForAck { + if mailbox.AckTimeout < 0 { + return fmt.Errorf("%w: mailbox %q ack_timeout_ms must not be negative", ErrInvalidRequest, name) + } + if mailbox.AckTimeout > 30*time.Second { + return fmt.Errorf("%w: mailbox %q ack_timeout_ms must be 30000 or less", ErrInvalidRequest, name) + } } } return nil diff --git a/lib/instances/fork_mailbox_test.go b/lib/instances/fork_mailbox_test.go index 8ff02f5e..609257b9 100644 --- a/lib/instances/fork_mailbox_test.go +++ b/lib/instances/fork_mailbox_test.go @@ -150,6 +150,31 @@ func TestValidateForkMailboxesRejectsPaddedName(t *testing.T) { assert.ErrorIs(t, err, ErrInvalidRequest) } +func TestValidateForkMailboxesAckTimeout(t *testing.T) { + t.Parallel() + + base := ForkMailboxPayload{ + Name: "kernel.identity.v1", + Token: "template-token", + Payload: []byte(`{"instance_name":"forked"}`), + } + + withDefaultTimeout := base + withDefaultTimeout.WaitForAck = true + require.NoError(t, validateForkMailboxes([]ForkMailboxPayload{withDefaultTimeout})) + + withNegativeTimeout := base + withNegativeTimeout.WaitForAck = true + withNegativeTimeout.AckTimeout = -time.Millisecond + err := validateForkMailboxes([]ForkMailboxPayload{withNegativeTimeout}) + require.Error(t, err) + assert.ErrorIs(t, err, ErrInvalidRequest) + + ignoredWithoutAck := base + ignoredWithoutAck.AckTimeout = 31 * time.Second + require.NoError(t, validateForkMailboxes([]ForkMailboxPayload{ignoredWithoutAck})) +} + func TestValidateForkMailboxHypervisor(t *testing.T) { t.Parallel() From 1778dc2958c42331ab2ad8c22482f3d35ac3246b Mon Sep 17 00:00:00 2001 From: sjmiller609 <7516283+sjmiller609@users.noreply.github.com> Date: Tue, 2 Jun 2026 15:46:49 +0000 Subject: [PATCH 13/15] Document fork mailbox ack waiting --- lib/instances/fork_mailbox.go | 2 +- lib/instances/fork_mailbox_test.go | 7 + lib/oapi/oapi.go | 603 +++++++++++++++-------------- openapi.yaml | 9 +- 4 files changed, 315 insertions(+), 306 deletions(-) diff --git a/lib/instances/fork_mailbox.go b/lib/instances/fork_mailbox.go index 6d84a8cf..322fde9e 100644 --- a/lib/instances/fork_mailbox.go +++ b/lib/instances/fork_mailbox.go @@ -162,7 +162,7 @@ func forkMailboxAckTimeout(timeout time.Duration) time.Duration { if timeout > 0 { return timeout } - return 2 * time.Second + return 5 * time.Second } func closePatchedForkMailboxes(patched []patchedForkMailbox) { diff --git a/lib/instances/fork_mailbox_test.go b/lib/instances/fork_mailbox_test.go index 609257b9..d00962ae 100644 --- a/lib/instances/fork_mailbox_test.go +++ b/lib/instances/fork_mailbox_test.go @@ -175,6 +175,13 @@ func TestValidateForkMailboxesAckTimeout(t *testing.T) { require.NoError(t, validateForkMailboxes([]ForkMailboxPayload{ignoredWithoutAck})) } +func TestForkMailboxAckTimeoutDefault(t *testing.T) { + t.Parallel() + + assert.Equal(t, 5*time.Second, forkMailboxAckTimeout(0)) + assert.Equal(t, 1500*time.Millisecond, forkMailboxAckTimeout(1500*time.Millisecond)) +} + func TestValidateForkMailboxHypervisor(t *testing.T) { t.Parallel() diff --git a/lib/oapi/oapi.go b/lib/oapi/oapi.go index e42adf1c..d731ae03 100644 --- a/lib/oapi/oapi.go +++ b/lib/oapi/oapi.go @@ -742,7 +742,7 @@ type ForkInstanceRequest struct { // ForkMailboxPayload defines model for ForkMailboxPayload. type ForkMailboxPayload struct { - // AckTimeoutMs Timeout for wait_for_ack. Defaults to 2000ms. + // AckTimeoutMs Timeout for wait_for_ack. Omit or set 0 to use the default 5000ms. AckTimeoutMs *int `json:"ack_timeout_ms,omitempty"` // Name Guest mailbox name. @@ -755,7 +755,8 @@ type ForkMailboxPayload struct { Token string `json:"token"` // WaitForAck If true, Hypeman injects ack_port into the payload and waits after resume for a UDP - // acknowledgement containing the mailbox name and stage=applied. + // acknowledgement containing the mailbox name and stage=applied. If the acknowledgement + // does not arrive before ack_timeout_ms, the fork restore fails. WaitForAck *bool `json:"wait_for_ack,omitempty"` } @@ -15899,305 +15900,305 @@ func (sh *strictHandler) GetVolume(w http.ResponseWriter, r *http.Request, id st var swaggerSpec = []string{ "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", - "Ol8/ff3/AwAA//9NxtSgAZUBAA==", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/openapi.yaml b/openapi.yaml index 365476ef..4c721e92 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -724,14 +724,15 @@ components: type: boolean description: | If true, Hypeman injects ack_port into the payload and waits after resume for a UDP - acknowledgement containing the mailbox name and stage=applied. + acknowledgement containing the mailbox name and stage=applied. If the acknowledgement + does not arrive before ack_timeout_ms, the fork restore fails. default: false ack_timeout_ms: type: integer - description: Timeout for wait_for_ack. Defaults to 2000ms. - minimum: 1 + description: Timeout for wait_for_ack. Omit or set 0 to use the default 5000ms. + minimum: 0 maximum: 30000 - default: 2000 + default: 5000 ForkTargetState: type: string From 8145ab06c7935ed4fa7d571b96b88801efce3776 Mon Sep 17 00:00:00 2001 From: sjmiller609 <7516283+sjmiller609@users.noreply.github.com> Date: Tue, 2 Jun 2026 21:16:19 +0000 Subject: [PATCH 14/15] Close restore handoffs on prepare errors --- lib/instances/restore.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/instances/restore.go b/lib/instances/restore.go index 3935e66f..565a7b93 100644 --- a/lib/instances/restore.go +++ b/lib/instances/restore.go @@ -266,6 +266,9 @@ func (m *manager) restoreInstanceWithOptions( } var handoffs []restoreHandoff + defer func() { + closeRestoreHandoffs(handoffs) + }() resumeNetworkHandoff, err := m.prepareResumeNetworkHandoff(ctx, stored, allocatedNet, snapshotDir) if err != nil { @@ -281,7 +284,6 @@ func (m *manager) restoreInstanceWithOptions( return nil, fmt.Errorf("prepare fork mailbox handoff: %w", err) } handoffs = append(handoffs, forkMailboxHandoff) - defer closeRestoreHandoffs(handoffs) // 5. Transition: Standby → Paused (start hypervisor + restore) restoreCtx, restoreSpanEnd := m.startLifecycleStep(ctx, "restore_from_snapshot", From f763c6ef1bb8405e5c59aa3142886e4cbcff940c Mon Sep 17 00:00:00 2001 From: sjmiller609 <7516283+sjmiller609@users.noreply.github.com> Date: Tue, 2 Jun 2026 21:16:47 +0000 Subject: [PATCH 15/15] Document fork mailbox token ownership --- lib/mailbox/README.md | 2 +- lib/oapi/oapi.go | 608 +++++++++++++++++++++--------------------- openapi.yaml | 10 +- 3 files changed, 313 insertions(+), 307 deletions(-) diff --git a/lib/mailbox/README.md b/lib/mailbox/README.md index 5830f3b4..00b5bc68 100644 --- a/lib/mailbox/README.md +++ b/lib/mailbox/README.md @@ -4,4 +4,4 @@ The resume network handoff uses a small pre-armed mailbox in guest memory to avo Before restore, the host finds the marker in the snapshot memory file, writes a JSON network payload into the buffer, and flips the sequence field. After resume, VMGenID wakes the guest watcher, which reads the payload, applies the new network identity locally, and sends a UDP applied ack to the host. If the mailbox is missing, cannot be patched, or does not ack in time, restore falls back to the host-initiated network reconfigure path. -Fork payload mailboxes use the same snapshot-memory handoff pattern for caller-provided JSON payloads. A guest-side component places a named marker plus token in memory before the standby snapshot is captured. When a fork is restored from that snapshot, Hypeman finds the named marker, patches the JSON payload, and optionally waits for the guest to send a UDP `stage=applied` acknowledgement for that mailbox name. +Fork payload mailboxes use the same snapshot-memory handoff pattern for caller-provided JSON payloads. A guest-side component places a named marker plus token in memory before the standby snapshot is captured; Hypeman does not create, return, or enumerate those tokens. When a fork is restored from that snapshot, Hypeman finds the named marker, patches the JSON payload, and optionally waits for the guest to send a UDP `stage=applied` acknowledgement for that mailbox name. diff --git a/lib/oapi/oapi.go b/lib/oapi/oapi.go index d731ae03..8b4a6781 100644 --- a/lib/oapi/oapi.go +++ b/lib/oapi/oapi.go @@ -729,8 +729,9 @@ type ForkInstanceRequest struct { // Mailboxes Optional JSON mailbox payloads to patch into a standby snapshot before resuming the fork. // Each mailbox must correspond to a guest-side mailbox marker that was present when the - // source snapshot was captured. Mailboxes are only supported for forks that restore from a - // standby snapshot into Running state. + // source snapshot was captured. Guest software writes the marker before standby capture; + // Hypeman does not create, return, or enumerate mailbox tokens. Mailboxes are only supported + // for forks that restore from a standby snapshot into Running state. Mailboxes *[]ForkMailboxPayload `json:"mailboxes,omitempty"` // Name Name for the forked instance (lowercase letters, digits, and dashes only; cannot start or end with a dash) @@ -764,8 +765,9 @@ type ForkMailboxPayload struct { type ForkSnapshotRequest struct { // Mailboxes Optional JSON mailbox payloads to patch into a standby snapshot before resuming the fork. // Each mailbox must correspond to a guest-side mailbox marker that was present when the - // source snapshot was captured. Mailboxes are only supported for forks that restore from a - // standby snapshot into Running state. + // source snapshot was captured. Guest software writes the marker before standby capture; + // Hypeman does not create, return, or enumerate mailbox tokens. Mailboxes are only supported + // for forks that restore from a standby snapshot into Running state. Mailboxes *[]ForkMailboxPayload `json:"mailboxes,omitempty"` // Name Name for the new instance (lowercase letters, digits, and dashes only; cannot start or end with a dash) @@ -15899,306 +15901,308 @@ func (sh *strictHandler) GetVolume(w http.ResponseWriter, r *http.Request, id st // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "BZVdlgEA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/openapi.yaml b/openapi.yaml index 4c721e92..4747ac99 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -695,8 +695,9 @@ components: description: | Optional JSON mailbox payloads to patch into a standby snapshot before resuming the fork. Each mailbox must correspond to a guest-side mailbox marker that was present when the - source snapshot was captured. Mailboxes are only supported for forks that restore from a - standby snapshot into Running state. + source snapshot was captured. Guest software writes the marker before standby capture; + Hypeman does not create, return, or enumerate mailbox tokens. Mailboxes are only supported + for forks that restore from a standby snapshot into Running state. items: $ref: "#/components/schemas/ForkMailboxPayload" @@ -923,8 +924,9 @@ components: description: | Optional JSON mailbox payloads to patch into a standby snapshot before resuming the fork. Each mailbox must correspond to a guest-side mailbox marker that was present when the - source snapshot was captured. Mailboxes are only supported for forks that restore from a - standby snapshot into Running state. + source snapshot was captured. Guest software writes the marker before standby capture; + Hypeman does not create, return, or enumerate mailbox tokens. Mailboxes are only supported + for forks that restore from a standby snapshot into Running state. items: $ref: "#/components/schemas/ForkMailboxPayload"