diff --git a/pkg/sentry/socket/netstack/stack.go b/pkg/sentry/socket/netstack/stack.go index 22f69af67..e0c20dd13 100644 --- a/pkg/sentry/socket/netstack/stack.go +++ b/pkg/sentry/socket/netstack/stack.go @@ -136,6 +136,7 @@ func (s *Stack) SetInterface(ctx context.Context, msg *nlmsg.Message) *syserr.Er case linux.IFLA_LINKINFO: case linux.IFLA_ADDRESS: case linux.IFLA_MTU: + case linux.IFLA_NET_NS_FD: default: ctx.Warningf("unexpected attribute: %x", attr) return syserr.ErrNotSupported @@ -164,10 +165,34 @@ func (s *Stack) SetInterface(ctx context.Context, msg *nlmsg.Message) *syserr.Er // Netstack interfaces are always up. } - return s.setLink(tcpip.NICID(ifinfomsg.Index), attrs) + return s.setLink(ctx, tcpip.NICID(ifinfomsg.Index), attrs) } -func (s *Stack) setLink(id tcpip.NICID, linkAttrs map[uint16]nlmsg.BytesView) *syserr.Error { +func (s *Stack) setLink(ctx context.Context, id tcpip.NICID, linkAttrs map[uint16]nlmsg.BytesView) *syserr.Error { + // IFLA_NET_NS_FD has to be handled first, because other parameters may be reseted. + if v, ok := linkAttrs[linux.IFLA_NET_NS_FD]; ok { + fd, ok := v.Uint32() + if !ok { + return syserr.ErrInvalidArgument + } + f := inet.NamespaceByFDFromContext(ctx) + if f == nil { + return syserr.ErrInvalidArgument + } + ns, err := f(int32(fd)) + if err != nil { + return syserr.FromError(err) + } + defer ns.DecRef(ctx) + peer := ns.Stack().(*Stack) + if peer.Stack != s.Stack { + var err tcpip.Error + id, err = s.Stack.SetNICStack(id, peer.Stack) + if err != nil { + return syserr.TranslateNetstackError(err) + } + } + } for t, v := range linkAttrs { switch t { case linux.IFLA_MASTER: @@ -268,8 +293,7 @@ func (s *Stack) newVeth(ctx context.Context, linkAttrs map[uint16]nlmsg.BytesVie if err != nil { return syserr.TranslateNetstackError(err) } - ep.SetStack(s.Stack, id) - if err := s.setLink(id, linkAttrs); err != nil { + if err := s.setLink(ctx, id, linkAttrs); err != nil { peerEP.Close() return err } @@ -284,9 +308,8 @@ func (s *Stack) newVeth(ctx context.Context, linkAttrs map[uint16]nlmsg.BytesVie peerEP.Close() return syserr.TranslateNetstackError(err) } - peerEP.SetStack(peerStack.Stack, peerID) if peerLinkAttrs != nil { - if err := peerStack.setLink(peerID, peerLinkAttrs); err != nil { + if err := peerStack.setLink(ctx, peerID, peerLinkAttrs); err != nil { peerStack.Stack.RemoveNIC(peerID) peerEP.Close() return err @@ -310,7 +333,7 @@ func (s *Stack) newBridge(ctx context.Context, linkAttrs map[uint16]nlmsg.BytesV if err != nil { return syserr.TranslateNetstackError(err) } - if err := s.setLink(id, linkAttrs); err != nil { + if err := s.setLink(ctx, id, linkAttrs); err != nil { return err } diff --git a/pkg/tcpip/link/channel/channel.go b/pkg/tcpip/link/channel/channel.go index 39529cab9..679631827 100644 --- a/pkg/tcpip/link/channel/channel.go +++ b/pkg/tcpip/link/channel/channel.go @@ -314,3 +314,6 @@ func (*Endpoint) AddHeader(*stack.PacketBuffer) {} // ParseHeader implements stack.LinkEndpoint.ParseHeader. func (*Endpoint) ParseHeader(*stack.PacketBuffer) bool { return true } + +// SetOnCloseAction implements stack.LinkEndpoint. +func (*Endpoint) SetOnCloseAction(func()) {} diff --git a/pkg/tcpip/link/fdbased/endpoint.go b/pkg/tcpip/link/fdbased/endpoint.go index 18cc40009..abab35959 100644 --- a/pkg/tcpip/link/fdbased/endpoint.go +++ b/pkg/tcpip/link/fdbased/endpoint.go @@ -831,6 +831,9 @@ func (e *endpoint) ARPHardwareType() header.ARPHardwareType { // Close implements stack.LinkEndpoint. func (e *endpoint) Close() {} +// SetOnCloseAction implements stack.LinkEndpoint. +func (*endpoint) SetOnCloseAction(func()) {} + // InjectableEndpoint is an injectable fd-based endpoint. The endpoint writes // to the FD, but does not read from it. All reads come from injected packets. // diff --git a/pkg/tcpip/link/loopback/loopback.go b/pkg/tcpip/link/loopback/loopback.go index 4734a235c..72329998b 100644 --- a/pkg/tcpip/link/loopback/loopback.go +++ b/pkg/tcpip/link/loopback/loopback.go @@ -135,3 +135,6 @@ func (*endpoint) ParseHeader(*stack.PacketBuffer) bool { return true } // Close implements stack.LinkEndpoint. func (*endpoint) Close() {} + +// SetOnCloseAction implements stack.LinkEndpoint. +func (*endpoint) SetOnCloseAction(func()) {} diff --git a/pkg/tcpip/link/muxed/injectable.go b/pkg/tcpip/link/muxed/injectable.go index aafc3ac02..224851775 100644 --- a/pkg/tcpip/link/muxed/injectable.go +++ b/pkg/tcpip/link/muxed/injectable.go @@ -165,6 +165,9 @@ func (*InjectableEndpoint) ParseHeader(*stack.PacketBuffer) bool { return true } // Close implements stack.LinkEndpoint. func (*InjectableEndpoint) Close() {} +// SetOnCloseAction implements stack.LinkEndpoint.SetOnCloseAction. +func (*InjectableEndpoint) SetOnCloseAction(func()) {} + // NewInjectableEndpoint creates a new multi-endpoint injectable endpoint. func NewInjectableEndpoint(routes map[tcpip.Address]stack.InjectableLinkEndpoint) *InjectableEndpoint { return &InjectableEndpoint{ diff --git a/pkg/tcpip/link/nested/nested.go b/pkg/tcpip/link/nested/nested.go index 78f5a0305..66c95689a 100644 --- a/pkg/tcpip/link/nested/nested.go +++ b/pkg/tcpip/link/nested/nested.go @@ -171,3 +171,8 @@ func (e *Endpoint) ParseHeader(pkt *stack.PacketBuffer) bool { func (e *Endpoint) Close() { e.child.Close() } + +// SetOnCloseAction implement stack.LinkEndpoints. +func (e *Endpoint) SetOnCloseAction(action func()) { + e.child.SetOnCloseAction(action) +} diff --git a/pkg/tcpip/link/packetsocket/packetsocket_test.go b/pkg/tcpip/link/packetsocket/packetsocket_test.go index ba1462d64..a5d99df83 100644 --- a/pkg/tcpip/link/packetsocket/packetsocket_test.go +++ b/pkg/tcpip/link/packetsocket/packetsocket_test.go @@ -57,6 +57,7 @@ func (*nullEndpoint) ARPHardwareType() header.ARPHardwareType { return header.AR func (*nullEndpoint) AddHeader(*stack.PacketBuffer) {} func (*nullEndpoint) ParseHeader(*stack.PacketBuffer) bool { return true } func (*nullEndpoint) Close() {} +func (*nullEndpoint) SetOnCloseAction(func()) {} var _ stack.NetworkDispatcher = (*testNetworkDispatcher)(nil) diff --git a/pkg/tcpip/link/pipe/pipe.go b/pkg/tcpip/link/pipe/pipe.go index 5152803d0..ba1c85103 100644 --- a/pkg/tcpip/link/pipe/pipe.go +++ b/pkg/tcpip/link/pipe/pipe.go @@ -151,3 +151,6 @@ func (*Endpoint) ParseHeader(*stack.PacketBuffer) bool { return true } // Close implements stack.LinkEndpoint. func (e *Endpoint) Close() {} + +// SetOnCloseAction implements stack.LinkEndpoint.SetOnCloseAction. +func (*Endpoint) SetOnCloseAction(func()) {} diff --git a/pkg/tcpip/link/sharedmem/sharedmem.go b/pkg/tcpip/link/sharedmem/sharedmem.go index 751aa7ca2..68bb3bd39 100644 --- a/pkg/tcpip/link/sharedmem/sharedmem.go +++ b/pkg/tcpip/link/sharedmem/sharedmem.go @@ -253,6 +253,9 @@ func New(opts Options) (stack.LinkEndpoint, error) { return e, nil } +// SetOnCloseAction implements stack.LinkEndpoint.SetOnCloseAction. +func (e *endpoint) SetOnCloseAction(func()) {} + // Close frees most resources associated with the endpoint. Wait() must be // called after Close() in order to free the rest. func (e *endpoint) Close() { diff --git a/pkg/tcpip/link/sharedmem/sharedmem_server.go b/pkg/tcpip/link/sharedmem/sharedmem_server.go index 53bfcfbaf..2321425c8 100644 --- a/pkg/tcpip/link/sharedmem/sharedmem_server.go +++ b/pkg/tcpip/link/sharedmem/sharedmem_server.go @@ -118,6 +118,9 @@ func NewServerEndpoint(opts Options) (stack.LinkEndpoint, error) { return e, nil } +// SetOnCloseAction implements stack.LinkEndpoint.SetOnCloseAction. +func (*serverEndpoint) SetOnCloseAction(func()) {} + // Close frees all resources associated with the endpoint. func (e *serverEndpoint) Close() { // Tell dispatch goroutine to stop, then write to the eventfd so that it wakes diff --git a/pkg/tcpip/link/veth/veth.go b/pkg/tcpip/link/veth/veth.go index fca5391b2..2e382d344 100644 --- a/pkg/tcpip/link/veth/veth.go +++ b/pkg/tcpip/link/veth/veth.go @@ -25,6 +25,36 @@ import ( var _ stack.LinkEndpoint = (*Endpoint)(nil) var _ stack.GSOEndpoint = (*Endpoint)(nil) +type veth struct { + mu sync.RWMutex + closed bool + backlogQueue chan vethPacket + mtu uint32 + endpoints [2]Endpoint +} + +func (v *veth) close() { + v.mu.Lock() + closed := v.closed + v.closed = true + v.mu.Unlock() + if closed { + return + } + + for i := range v.endpoints { + e := &v.endpoints[i] + e.mu.Lock() + action := e.onCloseAction + e.onCloseAction = nil + e.mu.Unlock() + if action != nil { + action() + } + } + close(v.backlogQueue) +} + // +stateify savable type vethPacket struct { e *Endpoint @@ -38,84 +68,55 @@ const backlogQueueSize = 64 // // +stateify savable type Endpoint struct { - pair *Endpoint + peer *Endpoint - backlogQueue *chan vethPacket + veth *veth mu sync.RWMutex `state:"nosave"` // +checklocks:mu dispatcher stack.NetworkDispatcher - - // +checklocks:mu - stack *stack.Stack - // +checklocks:mu - idx tcpip.NICID // linkAddr is the local address of this endpoint. // // +checklocks:mu linkAddr tcpip.LinkAddress // +checklocks:mu - mtu uint32 + onCloseAction func() } // NewPair creates a new veth pair. func NewPair(mtu uint32) (*Endpoint, *Endpoint) { - backlogQueue := make(chan vethPacket, backlogQueueSize) - a := &Endpoint{ + veth := veth{ + backlogQueue: make(chan vethPacket, backlogQueueSize), mtu: mtu, - linkAddr: tcpip.GetRandMacAddr(), - backlogQueue: &backlogQueue, + endpoints: [2]Endpoint{ + Endpoint{ + linkAddr: tcpip.GetRandMacAddr(), + }, + Endpoint{ + linkAddr: tcpip.GetRandMacAddr(), + }, + }, } - b := &Endpoint{ - mtu: mtu, - pair: a, - linkAddr: tcpip.GetRandMacAddr(), - backlogQueue: &backlogQueue, - } - a.pair = b + a := &veth.endpoints[0] + b := &veth.endpoints[1] + a.peer = b + b.peer = a + a.veth = &veth + b.veth = &veth go func() { - for t := range backlogQueue { + for t := range veth.backlogQueue { t.e.InjectInbound(t.protocol, t.pkt) t.pkt.DecRef() } + }() return a, b } -// SetStack stores the stack and the device index. -func (e *Endpoint) SetStack(s *stack.Stack, idx tcpip.NICID) { - e.mu.Lock() - defer e.mu.Unlock() - e.stack = s - e.idx = idx -} - // Close closes e. Further packet injections will return an error, and all pending // packets are discarded. Close may be called concurrently with WritePackets. func (e *Endpoint) Close() { - e.mu.Lock() - stack := e.stack - e.stack = nil - e.mu.Unlock() - if stack == nil { - return - } - - e = e.pair - e.mu.Lock() - stack = e.stack - idx := e.idx - e.stack = nil - e.mu.Unlock() - if stack != nil { - // The pair endpoint can live in the current stack or another one. - // RemoveNIC will take the stack lock, so let's run it in another - // goroutine to avoid lock conflicts. - go func() { - stack.RemoveNIC(idx) - }() - } - close(*e.backlogQueue) + e.veth.close() } // InjectInbound injects an inbound packet. If the endpoint is not attached, the @@ -146,16 +147,16 @@ func (e *Endpoint) IsAttached() bool { // MTU implements stack.LinkEndpoint.MTU. func (e *Endpoint) MTU() uint32 { - e.mu.RLock() - defer e.mu.RUnlock() - return e.mtu + e.veth.mu.RLock() + defer e.veth.mu.RUnlock() + return e.veth.mtu } // SetMTU implements stack.LinkEndpoint.SetMTU. func (e *Endpoint) SetMTU(mtu uint32) { - e.mu.Lock() - defer e.mu.Unlock() - e.mtu = mtu + e.veth.mu.Lock() + defer e.veth.mu.Unlock() + e.veth.mtu = mtu } // Capabilities implements stack.LinkEndpoint.Capabilities. @@ -204,8 +205,8 @@ func (e *Endpoint) WritePackets(pkts stack.PacketBufferList) (int, tcpip.Error) newPkt := stack.NewPacketBuffer(stack.PacketBufferOptions{ Payload: pkt.ToBuffer(), }) - (*e.backlogQueue) <- vethPacket{ - e: e.pair, + (e.veth.backlogQueue) <- vethPacket{ + e: e.peer, protocol: pkt.NetworkProtocolNumber, pkt: newPkt, } @@ -228,3 +229,10 @@ func (e *Endpoint) AddHeader(pkt *stack.PacketBuffer) {} // ParseHeader implements stack.LinkEndpoint.ParseHeader. func (e *Endpoint) ParseHeader(pkt *stack.PacketBuffer) bool { return true } + +// SetOnCloseAction implements stack.LinkEndpoint. +func (e *Endpoint) SetOnCloseAction(action func()) { + e.mu.Lock() + defer e.mu.Unlock() + e.onCloseAction = action +} diff --git a/pkg/tcpip/link/veth/veth_test.go b/pkg/tcpip/link/veth/veth_test.go index b50d994f8..e32502be5 100644 --- a/pkg/tcpip/link/veth/veth_test.go +++ b/pkg/tcpip/link/veth/veth_test.go @@ -110,13 +110,11 @@ func TestDestroyDevices(t *testing.T) { if err := s1.CreateNIC(vethFirstID, ethernet.New(veth1)); err != nil { t.Fatalf("s.CreateNIC(%d, _): %s", vethFirstID, err) } - veth1.SetStack(s1, vethFirstID) s2 := stack.New(stack.Options{}) if err := s2.CreateNIC(vethSecondID, ethernet.New(veth2)); err != nil { t.Fatalf("s.CreateNIC(%d, _): %s", vethSecondID, err) } - veth2.SetStack(s2, vethSecondID) s1.RemoveNIC(vethFirstID) timeout := time.Millisecond diff --git a/pkg/tcpip/link/waitable/waitable.go b/pkg/tcpip/link/waitable/waitable.go index b78ccbf25..f74a147d2 100644 --- a/pkg/tcpip/link/waitable/waitable.go +++ b/pkg/tcpip/link/waitable/waitable.go @@ -183,6 +183,11 @@ func (e *Endpoint) ParseHeader(pkt *stack.PacketBuffer) bool { return e.lower.ParseHeader(pkt) } +// SetOnCloseAction implements stack.LinkEndpoint.SetOnCloseAction. +func (e *Endpoint) SetOnCloseAction(action func()) { + e.lower.SetOnCloseAction(action) +} + // Close implements stack.LinkEndpoint. func (e *Endpoint) Close() { e.lower.Close() diff --git a/pkg/tcpip/link/waitable/waitable_test.go b/pkg/tcpip/link/waitable/waitable_test.go index 36a96e1b6..d2bcbb5d7 100644 --- a/pkg/tcpip/link/waitable/waitable_test.go +++ b/pkg/tcpip/link/waitable/waitable_test.go @@ -108,6 +108,9 @@ func (*countedEndpoint) ParseHeader(*stack.PacketBuffer) bool { // Close implements stack.LinkEndpoint. func (*countedEndpoint) Close() {} +// SetOnCloseAction implements stack.LinkEndpoint.SetOnCloseAction. +func (*countedEndpoint) SetOnCloseAction(func()) {} + func TestWaitWrite(t *testing.T) { ep := &countedEndpoint{} wep := New(ep) diff --git a/pkg/tcpip/link/xdp/endpoint.go b/pkg/tcpip/link/xdp/endpoint.go index df47588f8..57bd03734 100644 --- a/pkg/tcpip/link/xdp/endpoint.go +++ b/pkg/tcpip/link/xdp/endpoint.go @@ -416,3 +416,6 @@ func (ep *endpoint) dispatch() (bool, tcpip.Error) { // Close implements stack.LinkEndpoint. func (*endpoint) Close() {} + +// SetOnCloseAction implements stack.LinkEndpoint. +func (*endpoint) SetOnCloseAction(func()) {} diff --git a/pkg/tcpip/network/internal/testutil/testutil.go b/pkg/tcpip/network/internal/testutil/testutil.go index 067ed4a7e..1cfd1da44 100644 --- a/pkg/tcpip/network/internal/testutil/testutil.go +++ b/pkg/tcpip/network/internal/testutil/testutil.go @@ -111,6 +111,9 @@ func (ep *MockLinkEndpoint) Close() { ep.WrittenPackets = nil } +// SetOnCloseAction implements stack.LinkEndpoint.SetOnCloseAction. +func (*MockLinkEndpoint) SetOnCloseAction(func()) {} + // MakeRandPkt generates a randomized packet. transportHeaderLength indicates // how many random bytes will be copied in the Transport Header. // extraHeaderReserveLength indicates how much extra space will be reserved for diff --git a/pkg/tcpip/network/ip_test.go b/pkg/tcpip/network/ip_test.go index c8e17457d..96b331c58 100644 --- a/pkg/tcpip/network/ip_test.go +++ b/pkg/tcpip/network/ip_test.go @@ -387,6 +387,9 @@ func (*testInterface) CheckLocalAddress(tcpip.NetworkProtocolNumber, tcpip.Addre // Close implements stack.LinkEndpoint. func (*testInterface) Close() {} +// SetOnCloseAction implements stack.LinkEndpoint.SetOnCloseAction. +func (*testInterface) SetOnCloseAction(func()) {} + func TestSourceAddressValidation(t *testing.T) { rxIPv4ICMP := func(e *channel.Endpoint, src tcpip.Address) { totalLen := header.IPv4MinimumSize + header.ICMPv4MinimumSize diff --git a/pkg/tcpip/network/ipv6/icmp_test.go b/pkg/tcpip/network/ipv6/icmp_test.go index a2a2e2caf..dc4fc3b1f 100644 --- a/pkg/tcpip/network/ipv6/icmp_test.go +++ b/pkg/tcpip/network/ipv6/icmp_test.go @@ -63,6 +63,8 @@ type stubLinkEndpoint struct { func (*stubLinkEndpoint) Close() {} +func (*stubLinkEndpoint) SetOnCloseAction(func()) {} + func (*stubLinkEndpoint) MTU() uint32 { return defaultMTU } diff --git a/pkg/tcpip/stack/bridge.go b/pkg/tcpip/stack/bridge.go index 0f03ee8ce..9fe9e9138 100644 --- a/pkg/tcpip/stack/bridge.go +++ b/pkg/tcpip/stack/bridge.go @@ -224,3 +224,6 @@ func (b *BridgeEndpoint) ParseHeader(*PacketBuffer) bool { // Close implements stack.LinkEndpoint.Close. func (b *BridgeEndpoint) Close() {} + +// SetOnCloseAction implements stack.LinkEndpoint.Close. +func (b *BridgeEndpoint) SetOnCloseAction(func()) {} diff --git a/pkg/tcpip/stack/bridge_test.go b/pkg/tcpip/stack/bridge_test.go index 1aa70410f..913718ad0 100644 --- a/pkg/tcpip/stack/bridge_test.go +++ b/pkg/tcpip/stack/bridge_test.go @@ -100,7 +100,6 @@ func TestWritePacketBetweenDevices(t *testing.T) { if err := secondStack.CreateNIC(vethID, ethernet.New(veth2)); err != nil { t.Fatalf("s.CreateNIC(%d, _): %s", vethID, err) } - veth2.SetStack(secondStack, vethID) veth2.SetLinkAddress(localLinkAddr) s := stack.New(stack.Options{}) @@ -122,7 +121,6 @@ func TestWritePacketBetweenDevices(t *testing.T) { if err := s.CreateNIC(vethID, ethernet.New(veth1)); err != nil { t.Fatalf("s.CreateNIC(%d, _): %s", vethID, err) } - veth1.SetStack(s, vethID) if err := s.SetNICCoordinator(vethID, bridgeID); err != nil { t.Fatalf("s.SetNICCoordinator") } diff --git a/pkg/tcpip/stack/forwarding_test.go b/pkg/tcpip/stack/forwarding_test.go index 64159eaea..1d05197c6 100644 --- a/pkg/tcpip/stack/forwarding_test.go +++ b/pkg/tcpip/stack/forwarding_test.go @@ -345,6 +345,9 @@ func (*fwdTestLinkEndpoint) ParseHeader(*PacketBuffer) bool { return true } func (*fwdTestLinkEndpoint) Close() {} +// SetOnCloseAction implements stack.LinkEndpoint.SetOnCloseAction. +func (*fwdTestLinkEndpoint) SetOnCloseAction(func()) {} + func fwdTestNetFactory(t *testing.T, proto *fwdTestNetworkProtocol) (*faketime.ManualClock, *fwdTestLinkEndpoint, *fwdTestLinkEndpoint) { clock := faketime.NewManualClock() // Create a stack with the network protocol and two NICs. diff --git a/pkg/tcpip/stack/nic.go b/pkg/tcpip/stack/nic.go index e3ffae5dd..a2520d663 100644 --- a/pkg/tcpip/stack/nic.go +++ b/pkg/tcpip/stack/nic.go @@ -311,7 +311,7 @@ func (n *nic) enable() tcpip.Error { // remove detaches NIC from the link endpoint and releases network endpoint // resources. This guarantees no packets between this NIC and the network // stack. -func (n *nic) remove() tcpip.Error { +func (n *nic) remove(closeLinkEndpoint bool) tcpip.Error { n.enableDisableMu.Lock() n.disableLocked() @@ -329,7 +329,16 @@ func (n *nic) remove() tcpip.Error { // Prevent packets from going down to the link before shutting the link down. n.qDisc.Close() n.NetworkLinkEndpoint.Attach(nil) - n.NetworkLinkEndpoint.Close() + if closeLinkEndpoint { + ep := n.NetworkLinkEndpoint + ep.SetOnCloseAction(nil) + // The link endpoint has to be closed without holding a + // netstack lock, because it can trigger other netstack + // operations. + go func() { + ep.Close() + }() + } return nil } diff --git a/pkg/tcpip/stack/registration.go b/pkg/tcpip/stack/registration.go index bd3a66fac..24f0391b6 100644 --- a/pkg/tcpip/stack/registration.go +++ b/pkg/tcpip/stack/registration.go @@ -1141,6 +1141,12 @@ type NetworkLinkEndpoint interface { // Close is called when the endpoint is removed from a stack. Close() + + // SetOnCloseAction sets the action that will be exected before closing the + // endpoint. It is used to destroy a network device when its endpoint + // is closed. Endpoints that are closed only after destroying their + // network devices can implement this method as no-op. + SetOnCloseAction(func()) } // QueueingDiscipline provides a queueing strategy for outgoing packets (e.g diff --git a/pkg/tcpip/stack/stack.go b/pkg/tcpip/stack/stack.go index 70278ec57..c3a6faff5 100644 --- a/pkg/tcpip/stack/stack.go +++ b/pkg/tcpip/stack/stack.go @@ -910,6 +910,9 @@ func (s *Stack) CreateNICWithOptions(id tcpip.NICID, ep LinkEndpoint, opts NICOp } } s.nics[id] = n + ep.SetOnCloseAction(func() { + s.RemoveNIC(id) + }) if !opts.Disabled { return n.enable() } @@ -1016,7 +1019,7 @@ func (s *Stack) removeNICLocked(id tcpip.NICID) tcpip.Error { } s.routeMu.Unlock() - return nic.remove() + return nic.remove(true /* closeLinkEndpoint */) } // SetNICCoordinator sets a coordinator device. @@ -2340,3 +2343,27 @@ func (s *Stack) IsSubnetBroadcast(nicID tcpip.NICID, protocol tcpip.NetworkProto func (s *Stack) PacketEndpointWriteSupported() bool { return s.packetEndpointWriteSupported } + +// SetNICStack moves the network device to the specified network namespace. +func (s *Stack) SetNICStack(id tcpip.NICID, peer *Stack) (tcpip.NICID, tcpip.Error) { + s.mu.Lock() + nic, ok := s.nics[id] + if !ok { + s.mu.Unlock() + return 0, &tcpip.ErrUnknownNICID{} + } + if s == peer { + s.mu.Unlock() + return id, nil + } + delete(s.nics, id) + + // Remove routes in-place. n tracks the number of routes written. + s.RemoveRoutes(func(r tcpip.Route) bool { return r.NIC == id }) + ne := nic.NetworkLinkEndpoint.(LinkEndpoint) + nic.remove(false /* closeLinkEndpoint */) + s.mu.Unlock() + + id = tcpip.NICID(peer.NextNICID()) + return id, peer.CreateNICWithOptions(id, ne, NICOptions{Name: nic.Name()}) +} diff --git a/pkg/tcpip/transport/datagram_test.go b/pkg/tcpip/transport/datagram_test.go index cdbc89fdf..7c4168fda 100644 --- a/pkg/tcpip/transport/datagram_test.go +++ b/pkg/tcpip/transport/datagram_test.go @@ -187,6 +187,9 @@ func (e *mockEndpoint) pktsSize() int { // Close implements stack.LinkEndpoint. func (*mockEndpoint) Close() {} +// SetOnCloseAction implements stack.LinkEndpoint.SetOnCloseAction. +func (*mockEndpoint) SetOnCloseAction(func()) {} + func TestSndBuf(t *testing.T) { const nicID = 1 diff --git a/test/rtnetlink/linux/veth_test.sh b/test/rtnetlink/linux/veth_test.sh index d1c10fffa..94d98e730 100755 --- a/test/rtnetlink/linux/veth_test.sh +++ b/test/rtnetlink/linux/veth_test.sh @@ -30,6 +30,7 @@ if ! wait_for ! ip link show test_veth02; then exit 1 fi +# Create new veth pair where devices are in two namespaces. ip netns add test ip link add test_veth01 type veth peer name test_veth02 netns test ip link show test_veth01 @@ -39,3 +40,15 @@ if ! wait_for ! ip link show test_veth01; then fail "test_veth01 hasn't been destroyed" fi +# Create new veth pair and move one end in another namespace. +ip netns add test +ip link add test_veth01 type veth peer name test_veth02 +ip link set dev test_veth02 netns test +ip link show test_veth01 +ip netns exec test ip link show test_veth02 +# Check that test_veth02 will be destroyed after changing netns. +ip link del test_veth01 +if ! wait_for ! ip netns exec test ip link show test_veth02; then + fail "test_veth02 hasn't been destroyed" +fi +ip netns del test