diff --git a/pkg/sentry/inet/inet.go b/pkg/sentry/inet/inet.go index 5f12113c45..8b4ef8848e 100644 --- a/pkg/sentry/inet/inet.go +++ b/pkg/sentry/inet/inet.go @@ -137,6 +137,9 @@ type Stack interface { // IsSaveRestoreEnabled returns true when netstack s/r is enabled. IsSaveRestoreEnabled() bool + + // Stats returns the network stats. + Stats() tcpip.Stats } // Interface contains information about a network interface. diff --git a/pkg/sentry/inet/test_stack.go b/pkg/sentry/inet/test_stack.go index 119e6c78b6..26d3f61651 100644 --- a/pkg/sentry/inet/test_stack.go +++ b/pkg/sentry/inet/test_stack.go @@ -235,3 +235,9 @@ func (*TestStack) IsSaveRestoreEnabled() bool { // No-op. return false } + +// Stats implements Stack. +func (*TestStack) Stats() tcpip.Stats { + // No-op. + return tcpip.Stats{} +} diff --git a/pkg/sentry/kernel/timekeeper_state.go b/pkg/sentry/kernel/timekeeper_state.go index 38ad158f5e..9845312dc1 100644 --- a/pkg/sentry/kernel/timekeeper_state.go +++ b/pkg/sentry/kernel/timekeeper_state.go @@ -26,6 +26,11 @@ func (t *Timekeeper) beforeSave() { panic("pauseUpdates must be called before Save") } + if t.clocks == nil { + t.restored = nil + return + } + // N.B. we want the *offset* monotonic time. var err error if t.saveMonotonic, err = t.GetTime(time.Monotonic); err != nil { diff --git a/pkg/sentry/socket/hostinet/stack.go b/pkg/sentry/socket/hostinet/stack.go index 32fa99a047..4d1facb0af 100644 --- a/pkg/sentry/socket/hostinet/stack.go +++ b/pkg/sentry/socket/hostinet/stack.go @@ -438,3 +438,8 @@ func (*Stack) EnableSaveRestore() error { func (s *Stack) IsSaveRestoreEnabled() bool { return false } + +// Stats implements inet.Stack.Stats. +func (s *Stack) Stats() tcpip.Stats { + return tcpip.Stats{} +} diff --git a/pkg/sentry/socket/netstack/stack.go b/pkg/sentry/socket/netstack/stack.go index 7e4502a0c7..25edbaa16d 100644 --- a/pkg/sentry/socket/netstack/stack.go +++ b/pkg/sentry/socket/netstack/stack.go @@ -592,6 +592,7 @@ func (s *Stack) SetTCPRecovery(recovery inet.TCPLossRecovery) error { // Statistics implements inet.Stack.Statistics. func (s *Stack) Statistics(stat any, arg string) error { + netStats := s.Stats() switch stats := stat.(type) { case *inet.StatDev: for _, ni := range s.Stack.NICInfo() { @@ -622,7 +623,7 @@ func (s *Stack) Statistics(stat any, arg string) error { break } case *inet.StatSNMPIP: - ip := Metrics.IP + ip := netStats.IP // TODO(gvisor.dev/issue/969) Support stubbed stats. *stats = inet.StatSNMPIP{ 0, // Ip/Forwarding. @@ -646,8 +647,8 @@ func (s *Stack) Statistics(stat any, arg string) error { 0, // Support Ip/FragCreates. } case *inet.StatSNMPICMP: - in := Metrics.ICMP.V4.PacketsReceived.ICMPv4PacketStats - out := Metrics.ICMP.V4.PacketsSent.ICMPv4PacketStats + in := netStats.ICMP.V4.PacketsReceived.ICMPv4PacketStats + out := netStats.ICMP.V4.PacketsSent.ICMPv4PacketStats // TODO(gvisor.dev/issue/969) Support stubbed stats. *stats = inet.StatSNMPICMP{ 0, // Icmp/InMsgs. @@ -679,7 +680,7 @@ func (s *Stack) Statistics(stat any, arg string) error { out.InfoReply.Value(), // OutAddrMaskReps. } case *inet.StatSNMPTCP: - tcp := Metrics.TCP + tcp := netStats.TCP // RFC 2012 (updates 1213): SNMPv2-MIB-TCP. *stats = inet.StatSNMPTCP{ 1, // RtoAlgorithm. @@ -699,7 +700,7 @@ func (s *Stack) Statistics(stat any, arg string) error { tcp.ChecksumErrors.Value(), // InCsumErrors. } case *inet.StatSNMPUDP: - udp := Metrics.UDP + udp := netStats.UDP // TODO(gvisor.dev/issue/969) Support stubbed stats. *stats = inet.StatSNMPUDP{ udp.PacketsReceived.Value(), // InDatagrams. @@ -717,6 +718,11 @@ func (s *Stack) Statistics(stat any, arg string) error { return nil } +// Stats implements inet.Stack.Stats. +func (s *Stack) Stats() tcpip.Stats { + return s.Stack.Stats() +} + // RouteTable implements inet.Stack.RouteTable. func (s *Stack) RouteTable() []inet.Route { var routeTable []inet.Route diff --git a/pkg/tcpip/stack/addressable_endpoint_state.go b/pkg/tcpip/stack/addressable_endpoint_state.go index bb2e0faf0f..e08909c816 100644 --- a/pkg/tcpip/stack/addressable_endpoint_state.go +++ b/pkg/tcpip/stack/addressable_endpoint_state.go @@ -738,8 +738,6 @@ func (a *AddressableEndpointState) Cleanup() { var _ AddressEndpoint = (*addressState)(nil) // addressState holds state for an address. -// -// +stateify savable type addressState struct { addressableEndpointState *AddressableEndpointState addr tcpip.AddressWithPrefix @@ -750,7 +748,7 @@ type addressState struct { // // AddressableEndpointState.mu // addressState.mu - mu addressStateRWMutex `state:"nosave"` + mu addressStateRWMutex refs addressStateRefs // checklocks:mu kind AddressKind diff --git a/pkg/tcpip/stack/stack.go b/pkg/tcpip/stack/stack.go index 4b00f8548c..40a66d9ab0 100644 --- a/pkg/tcpip/stack/stack.go +++ b/pkg/tcpip/stack/stack.go @@ -1988,6 +1988,7 @@ func (s *Stack) ReplaceConfig(st *Stack) { s.nics[id] = nic _ = s.NextNICID() } + s.tables = st.tables } // Restore restarts the stack after a restore. This must be called after the diff --git a/pkg/tcpip/transport/icmp/endpoint.go b/pkg/tcpip/transport/icmp/endpoint.go index 988604fcd4..c7924d534f 100644 --- a/pkg/tcpip/transport/icmp/endpoint.go +++ b/pkg/tcpip/transport/icmp/endpoint.go @@ -57,7 +57,7 @@ type endpoint struct { // The following fields are initialized at creation time and are // immutable. - stack *stack.Stack `state:"manual"` + stack *stack.Stack transProto tcpip.TransportProtocolNumber waiterQueue *waiter.Queue net network.Endpoint diff --git a/pkg/tcpip/transport/icmp/endpoint_state.go b/pkg/tcpip/transport/icmp/endpoint_state.go index 134797e8b0..3684d48e5e 100644 --- a/pkg/tcpip/transport/icmp/endpoint_state.go +++ b/pkg/tcpip/transport/icmp/endpoint_state.go @@ -36,7 +36,11 @@ func (p *icmpPacket) loadReceivedAt(_ context.Context, nsec int64) { // afterLoad is invoked by stateify. func (e *endpoint) afterLoad(ctx context.Context) { - stack.RestoreStackFromContext(ctx).RegisterRestoredEndpoint(e) + if e.stack.IsSaveRestoreEnabled() { + e.stack.RegisterRestoredEndpoint(e) + } else { + stack.RestoreStackFromContext(ctx).RegisterRestoredEndpoint(e) + } } // beforeSave is invoked by stateify. @@ -49,24 +53,28 @@ func (e *endpoint) beforeSave() { func (e *endpoint) Restore(s *stack.Stack) { e.thaw() + saveRestoreEnabled := e.stack.IsSaveRestoreEnabled() e.net.Resume(s) + if saveRestoreEnabled { + e.ops.InitHandler(e, e.stack, tcpip.GetStackSendBufferLimits, tcpip.GetStackReceiveBufferLimits) + } else { + e.stack = s + e.ops.InitHandler(e, e.stack, tcpip.GetStackSendBufferLimits, tcpip.GetStackReceiveBufferLimits) - e.stack = s - e.ops.InitHandler(e, e.stack, tcpip.GetStackSendBufferLimits, tcpip.GetStackReceiveBufferLimits) - - switch state := e.net.State(); state { - case transport.DatagramEndpointStateInitial, transport.DatagramEndpointStateClosed: - case transport.DatagramEndpointStateBound, transport.DatagramEndpointStateConnected: - var err tcpip.Error - info := e.net.Info() - info.ID.LocalPort = e.ident - info.ID, err = e.registerWithStack(info.NetProto, info.ID) - if err != nil { - panic(fmt.Sprintf("e.registerWithStack(%d, %#v): %s", info.NetProto, info.ID, err)) + switch state := e.net.State(); state { + case transport.DatagramEndpointStateInitial, transport.DatagramEndpointStateClosed: + case transport.DatagramEndpointStateBound, transport.DatagramEndpointStateConnected: + var err tcpip.Error + info := e.net.Info() + info.ID.LocalPort = e.ident + info.ID, err = e.registerWithStack(info.NetProto, info.ID) + if err != nil { + panic(fmt.Sprintf("e.registerWithStack(%d, %#v): %s", info.NetProto, info.ID, err)) + } + e.ident = info.ID.LocalPort + default: + panic(fmt.Sprintf("unhandled state = %s", state)) } - e.ident = info.ID.LocalPort - default: - panic(fmt.Sprintf("unhandled state = %s", state)) } } diff --git a/pkg/tcpip/transport/internal/network/endpoint.go b/pkg/tcpip/transport/internal/network/endpoint.go index 6a0523963c..8eefeb7b10 100644 --- a/pkg/tcpip/transport/internal/network/endpoint.go +++ b/pkg/tcpip/transport/internal/network/endpoint.go @@ -35,7 +35,7 @@ import ( // +stateify savable type Endpoint struct { // The following fields must only be set once then never changed. - stack *stack.Stack `state:"manual"` + stack *stack.Stack ops *tcpip.SocketOptions netProto tcpip.NetworkProtocolNumber transProto tcpip.TransportProtocolNumber @@ -53,7 +53,7 @@ type Endpoint struct { // +checklocks:mu effectiveNetProto tcpip.NetworkProtocolNumber // +checklocks:mu - connectedRoute *stack.Route `state:"manual"` + connectedRoute *stack.Route `state:"nosave"` // +checklocks:mu multicastMemberships map[multicastMembership]struct{} // +checklocks:mu diff --git a/pkg/tcpip/transport/packet/endpoint.go b/pkg/tcpip/transport/packet/endpoint.go index 9166bca6cc..0cf7c01589 100644 --- a/pkg/tcpip/transport/packet/endpoint.go +++ b/pkg/tcpip/transport/packet/endpoint.go @@ -63,7 +63,7 @@ type endpoint struct { // The following fields are initialized at creation time and are // immutable. - stack *stack.Stack `state:"manual"` + stack *stack.Stack waiterQueue *waiter.Queue cooked bool ops tcpip.SocketOptions diff --git a/pkg/tcpip/transport/packet/endpoint_state.go b/pkg/tcpip/transport/packet/endpoint_state.go index 16be7d6b3a..355381ff3c 100644 --- a/pkg/tcpip/transport/packet/endpoint_state.go +++ b/pkg/tcpip/transport/packet/endpoint_state.go @@ -43,12 +43,20 @@ func (ep *endpoint) beforeSave() { // afterLoad is invoked by stateify. func (ep *endpoint) afterLoad(ctx context.Context) { + if !ep.stack.IsSaveRestoreEnabled() { + ep.mu.Lock() + ep.stack = stack.RestoreStackFromContext(ctx) + ep.mu.Unlock() + } + ep.stack.RegisterRestoredEndpoint(ep) +} + +// Restore implements tcpip.RestoredEndpoint.Restore. +func (ep *endpoint) Restore(_ *stack.Stack) { ep.mu.Lock() defer ep.mu.Unlock() - ep.stack = stack.RestoreStackFromContext(ctx) ep.ops.InitHandler(ep, ep.stack, tcpip.GetStackSendBufferLimits, tcpip.GetStackReceiveBufferLimits) - if err := ep.stack.RegisterPacketEndpoint(ep.boundNIC, ep.boundNetProto, ep); err != nil { panic(fmt.Sprintf("RegisterPacketEndpoint(%d, %d, _): %s", ep.boundNIC, ep.boundNetProto, err)) } diff --git a/pkg/tcpip/transport/raw/endpoint.go b/pkg/tcpip/transport/raw/endpoint.go index 1eaedc1979..5203c4dd9b 100644 --- a/pkg/tcpip/transport/raw/endpoint.go +++ b/pkg/tcpip/transport/raw/endpoint.go @@ -73,7 +73,7 @@ type endpoint struct { // The following fields are initialized at creation time and are // immutable. - stack *stack.Stack `state:"manual"` + stack *stack.Stack transProto tcpip.TransportProtocolNumber waiterQueue *waiter.Queue associated bool diff --git a/pkg/tcpip/transport/raw/endpoint_state.go b/pkg/tcpip/transport/raw/endpoint_state.go index d915ade2e1..4206514641 100644 --- a/pkg/tcpip/transport/raw/endpoint_state.go +++ b/pkg/tcpip/transport/raw/endpoint_state.go @@ -16,7 +16,6 @@ package raw import ( "context" - "fmt" "time" "gvisor.dev/gvisor/pkg/tcpip" @@ -35,7 +34,11 @@ func (p *rawPacket) loadReceivedAt(_ context.Context, nsec int64) { // afterLoad is invoked by stateify. func (e *endpoint) afterLoad(ctx context.Context) { - stack.RestoreStackFromContext(ctx).RegisterRestoredEndpoint(e) + if e.stack.IsSaveRestoreEnabled() { + e.stack.RegisterRestoredEndpoint(e) + } else { + stack.RestoreStackFromContext(ctx).RegisterRestoredEndpoint(e) + } } // beforeSave is invoked by stateify. @@ -46,16 +49,20 @@ func (e *endpoint) beforeSave() { // Restore implements tcpip.RestoredEndpoint.Restore. func (e *endpoint) Restore(s *stack.Stack) { - e.net.Resume(s) - e.setReceiveDisabled(false) - e.stack = s - e.ops.InitHandler(e, e.stack, tcpip.GetStackSendBufferLimits, tcpip.GetStackReceiveBufferLimits) + if saveRestoredEnabled := e.stack.IsSaveRestoreEnabled(); saveRestoredEnabled { + e.net.Resume(s) + e.ops.InitHandler(e, e.stack, tcpip.GetStackSendBufferLimits, tcpip.GetStackReceiveBufferLimits) + } else { + e.net.Resume(s) + e.stack = s + e.ops.InitHandler(e, e.stack, tcpip.GetStackSendBufferLimits, tcpip.GetStackReceiveBufferLimits) - if e.associated { - netProto := e.net.NetProto() - if err := e.stack.RegisterRawTransportEndpoint(netProto, e.transProto, e); err != nil { - panic(fmt.Sprintf("e.stack.RegisterRawTransportEndpoint(%d, %d, _): %s", netProto, e.transProto, err)) + if e.associated { + netProto := e.net.NetProto() + if err := e.stack.RegisterRawTransportEndpoint(netProto, e.transProto, e); err != nil { + panic("RegisterRawTransportEndpoint failed during restore") + } } } } diff --git a/pkg/tcpip/transport/tcp/endpoint.go b/pkg/tcpip/transport/tcp/endpoint.go index f92ba1e904..a2b39f1177 100644 --- a/pkg/tcpip/transport/tcp/endpoint.go +++ b/pkg/tcpip/transport/tcp/endpoint.go @@ -415,7 +415,7 @@ type Endpoint struct { isPortReserved bool isRegistered bool boundNICID tcpip.NICID - route *stack.Route `state:"manual"` + route *stack.Route `state:"nosave"` ipv4TTL uint8 ipv6HopLimit int16 isConnectNotified bool diff --git a/pkg/tcpip/transport/tcp/endpoint_state.go b/pkg/tcpip/transport/tcp/endpoint_state.go index 98f9ca6526..07b51d381a 100644 --- a/pkg/tcpip/transport/tcp/endpoint_state.go +++ b/pkg/tcpip/transport/tcp/endpoint_state.go @@ -159,21 +159,23 @@ func (e *Endpoint) Restore(s *stack.Stack) { bind := func() { e.mu.Lock() defer e.mu.Unlock() - addr, _, err := e.checkV4MappedLocked(tcpip.FullAddress{Addr: e.BindAddr, Port: e.TransportEndpointInfo.ID.LocalPort}, true /* bind */) - if err != nil { - panic("unable to parse BindAddr: " + err.String()) - } - portRes := ports.Reservation{ - Networks: e.effectiveNetProtos, - Transport: ProtocolNumber, - Addr: addr.Addr, - Port: addr.Port, - Flags: e.boundPortFlags, - BindToDevice: e.boundBindToDevice, - Dest: e.boundDest, - } - if ok := e.stack.ReserveTuple(portRes); !ok { - panic(fmt.Sprintf("unable to re-reserve tuple (%v, %q, %d, %+v, %d, %v)", e.effectiveNetProtos, addr.Addr, addr.Port, e.boundPortFlags, e.boundBindToDevice, e.boundDest)) + if !saveRestoreEnabled { + addr, _, err := e.checkV4MappedLocked(tcpip.FullAddress{Addr: e.BindAddr, Port: e.TransportEndpointInfo.ID.LocalPort}, true /* bind */) + if err != nil { + panic("unable to parse BindAddr: " + err.String()) + } + portRes := ports.Reservation{ + Networks: e.effectiveNetProtos, + Transport: ProtocolNumber, + Addr: addr.Addr, + Port: addr.Port, + Flags: e.boundPortFlags, + BindToDevice: e.boundBindToDevice, + Dest: e.boundDest, + } + if ok := e.stack.ReserveTuple(portRes); !ok { + panic(fmt.Sprintf("unable to re-reserve tuple (%v, %q, %d, %+v, %d, %v)", e.effectiveNetProtos, addr.Addr, addr.Port, e.boundPortFlags, e.boundBindToDevice, e.boundDest)) + } } e.isPortReserved = true @@ -183,7 +185,7 @@ func (e *Endpoint) Restore(s *stack.Stack) { epState := EndpointState(e.origEndpointState) switch { - case epState.connected(): + case epState.connected() || epState == StateTimeWait: bind() if e.connectingAddress.BitLen() == 0 { e.connectingAddress = e.TransportEndpointInfo.ID.RemoteAddress @@ -201,6 +203,10 @@ func (e *Endpoint) Restore(s *stack.Stack) { // Reset the scoreboard to reinitialize the sack information as // we do not restore SACK information. e.scoreboard.Reset() + if saveRestoreEnabled { + // Unregister the endpoint before registering again during Connect. + e.stack.UnregisterTransportEndpoint(e.effectiveNetProtos, header.TCPProtocolNumber, e.TransportEndpointInfo.ID, e, e.boundPortFlags, e.boundBindToDevice) + } e.mu.Lock() err := e.connect(tcpip.FullAddress{NIC: e.boundNICID, Addr: e.connectingAddress, Port: e.TransportEndpointInfo.ID.RemotePort}, false /* handshake */) if _, ok := err.(*tcpip.ErrConnectStarted); !ok { @@ -224,8 +230,8 @@ func (e *Endpoint) Restore(s *stack.Stack) { e.mu.Unlock() connectedLoading.Done() case epState == StateListen: + tcpip.AsyncLoading.Add(1) if !saveRestoreEnabled { - tcpip.AsyncLoading.Add(1) go func() { connectedLoading.Wait() bind() @@ -244,14 +250,19 @@ func (e *Endpoint) Restore(s *stack.Stack) { tcpip.AsyncLoading.Done() }() } else { - e.LockUser() - // All endpoints will be moved to initial state after - // restore. Set endpoint to its originial listen state. - e.setEndpointState(StateListen) - // Initialize the listening context. - rcvWnd := seqnum.Size(e.receiveBufferAvailable()) - e.listenCtx = newListenContext(e.stack, e.protocol, e, rcvWnd, e.ops.GetV6Only(), e.NetProto) - e.UnlockUser() + go func() { + connectedLoading.Wait() + e.LockUser() + // All endpoints will be moved to initial state after + // restore. Set endpoint to its originial listen state. + e.setEndpointState(StateListen) + // Initialize the listening context. + rcvWnd := seqnum.Size(e.receiveBufferAvailable()) + e.listenCtx = newListenContext(e.stack, e.protocol, e, rcvWnd, e.ops.GetV6Only(), e.NetProto) + e.UnlockUser() + listenLoading.Done() + tcpip.AsyncLoading.Done() + }() } case epState == StateConnecting: // Initial SYN hasn't been sent yet so initiate a connect. diff --git a/pkg/tcpip/transport/udp/endpoint.go b/pkg/tcpip/transport/udp/endpoint.go index 0bd7db350a..b5ad77a623 100644 --- a/pkg/tcpip/transport/udp/endpoint.go +++ b/pkg/tcpip/transport/udp/endpoint.go @@ -61,7 +61,7 @@ type endpoint struct { // The following fields are initialized at creation time and do not // change throughout the lifetime of the endpoint. - stack *stack.Stack `state:"manual"` + stack *stack.Stack waiterQueue *waiter.Queue net network.Endpoint stats tcpip.TransportEndpointStats diff --git a/pkg/tcpip/transport/udp/endpoint_state.go b/pkg/tcpip/transport/udp/endpoint_state.go index 488e46600d..2e2a7144fe 100644 --- a/pkg/tcpip/transport/udp/endpoint_state.go +++ b/pkg/tcpip/transport/udp/endpoint_state.go @@ -16,7 +16,6 @@ package udp import ( "context" - "fmt" "time" "gvisor.dev/gvisor/pkg/tcpip" @@ -36,7 +35,11 @@ func (p *udpPacket) loadReceivedAt(_ context.Context, nsec int64) { // afterLoad is invoked by stateify. func (e *endpoint) afterLoad(ctx context.Context) { - stack.RestoreStackFromContext(ctx).RegisterRestoredEndpoint(e) + if e.stack.IsSaveRestoreEnabled() { + e.stack.RegisterRestoredEndpoint(e) + } else { + stack.RestoreStackFromContext(ctx).RegisterRestoredEndpoint(e) + } } // beforeSave is invoked by stateify. @@ -47,34 +50,39 @@ func (e *endpoint) beforeSave() { // Restore implements tcpip.RestoredEndpoint.Restore. func (e *endpoint) Restore(s *stack.Stack) { + saveRestoreEnabled := e.stack.IsSaveRestoreEnabled() e.thaw() e.mu.Lock() defer e.mu.Unlock() - e.net.Resume(s) - - e.stack = s - e.ops.InitHandler(e, e.stack, tcpip.GetStackSendBufferLimits, tcpip.GetStackReceiveBufferLimits) + if saveRestoreEnabled { + e.net.Resume(s) + e.ops.InitHandler(e, e.stack, tcpip.GetStackSendBufferLimits, tcpip.GetStackReceiveBufferLimits) + } else { + e.net.Resume(s) + e.stack = s + e.ops.InitHandler(e, e.stack, tcpip.GetStackSendBufferLimits, tcpip.GetStackReceiveBufferLimits) - switch state := e.net.State(); state { - case transport.DatagramEndpointStateInitial, transport.DatagramEndpointStateClosed: - case transport.DatagramEndpointStateBound, transport.DatagramEndpointStateConnected: - // Our saved state had a port, but we don't actually have a - // reservation. We need to remove the port from our state, but still - // pass it to the reservation machinery. - var err tcpip.Error - id := e.net.Info().ID - id.LocalPort = e.localPort - id.RemotePort = e.remotePort - id, e.boundBindToDevice, err = e.registerWithStack(e.effectiveNetProtos, id) - if err != nil { - panic(err) + switch state := e.net.State(); state { + case transport.DatagramEndpointStateInitial, transport.DatagramEndpointStateClosed: + case transport.DatagramEndpointStateBound, transport.DatagramEndpointStateConnected: + // Our saved state had a port, but we don't actually have a + // reservation. We need to remove the port from our state, but still + // pass it to the reservation machinery. + var err tcpip.Error + id := e.net.Info().ID + id.LocalPort = e.localPort + id.RemotePort = e.remotePort + id, e.boundBindToDevice, err = e.registerWithStack(e.effectiveNetProtos, id) + if err != nil { + panic("registering udp endpoint with the stack failed during restore") + } + e.localPort = id.LocalPort + e.remotePort = id.RemotePort + default: + panic("unhandled state") } - e.localPort = id.LocalPort - e.remotePort = id.RemotePort - default: - panic(fmt.Sprintf("unhandled state = %s", state)) } } diff --git a/test/runner/main.go b/test/runner/main.go index 56057b2264..1341cc3b0e 100644 --- a/test/runner/main.go +++ b/test/runner/main.go @@ -280,6 +280,7 @@ func prepareSave(args []string, undeclaredOutputsDir string, dirs []string, inde // Pass the directory path of the state file to the sandbox. args = append(args, "-TESTONLY-autosave-image-path", dir) dirs = append(dirs, dir) + args = append(args, "-TESTONLY-save-restore-netstack=true") return args, dirs, nil }