diff options
author | Ghanan Gowripalan <ghanan@google.com> | 2020-12-01 07:50:49 -0800 |
---|---|---|
committer | gVisor bot <gvisor-bot@google.com> | 2020-12-01 07:52:40 -0800 |
commit | 25570ac4f3a0c4b51251b8111d64d5c1dc6b9a67 (patch) | |
tree | eaa642fa6324fe7dfbb1c6a417170a88d16789e7 /pkg/tcpip/network/ipv4 | |
parent | 6b1dbbbdc8ec0b5d04e0961a216bd7323dbc45fb (diff) |
Track join count in multicast group protocol state
Before this change, the join count and the state for IGMP/MLD was held
across different types which required multiple locks to be held when
accessing a multicast group's state.
Bug #4682, #4861
Fixes #4916
PiperOrigin-RevId: 345019091
Diffstat (limited to 'pkg/tcpip/network/ipv4')
-rw-r--r-- | pkg/tcpip/network/ipv4/igmp.go | 82 | ||||
-rw-r--r-- | pkg/tcpip/network/ipv4/ipv4.go | 69 |
2 files changed, 60 insertions, 91 deletions
diff --git a/pkg/tcpip/network/ipv4/igmp.go b/pkg/tcpip/network/ipv4/igmp.go index 37f1822ca..18ccd28c3 100644 --- a/pkg/tcpip/network/ipv4/igmp.go +++ b/pkg/tcpip/network/ipv4/igmp.go @@ -124,7 +124,14 @@ func (igmp *igmpState) init(ep *endpoint, opts IGMPOptions) { defer igmp.mu.Unlock() igmp.ep = ep igmp.opts = opts - igmp.mu.genericMulticastProtocol.Init(ep.protocol.stack.Rand(), ep.protocol.stack.Clock(), igmp, UnsolicitedReportIntervalMax) + igmp.mu.genericMulticastProtocol.Init(ip.GenericMulticastProtocolOptions{ + Enabled: opts.Enabled, + Rand: ep.protocol.stack.Rand(), + Clock: ep.protocol.stack.Clock(), + Protocol: igmp, + MaxUnsolicitedReportDelay: UnsolicitedReportIntervalMax, + AllNodesAddress: header.IPv4AllSystems, + }) igmp.igmpV1Present = igmpV1PresentDefault igmp.mu.igmpV1Job = igmp.ep.protocol.stack.NewJob(&igmp.mu, func() { igmp.setV1Present(false) @@ -201,17 +208,13 @@ func (igmp *igmpState) setV1Present(v bool) { } func (igmp *igmpState) handleMembershipQuery(groupAddress tcpip.Address, maxRespTime time.Duration) { - if !igmp.opts.Enabled { - return - } - igmp.mu.Lock() defer igmp.mu.Unlock() // As per RFC 2236 Section 6, Page 10: If the maximum response time is zero // then change the state to note that an IGMPv1 router is present and // schedule the query received Job. - if maxRespTime == 0 { + if maxRespTime == 0 && igmp.opts.Enabled { igmp.mu.igmpV1Job.Cancel() igmp.mu.igmpV1Job.Schedule(v1RouterPresentTimeout) igmp.setV1Present(true) @@ -222,10 +225,6 @@ func (igmp *igmpState) handleMembershipQuery(groupAddress tcpip.Address, maxResp } func (igmp *igmpState) handleMembershipReport(groupAddress tcpip.Address) { - if !igmp.opts.Enabled { - return - } - igmp.mu.Lock() defer igmp.mu.Unlock() igmp.mu.genericMulticastProtocol.HandleReport(groupAddress) @@ -279,49 +278,46 @@ func (igmp *igmpState) writePacket(destAddress tcpip.Address, groupAddress tcpip // // If the group already exists in the membership map, returns // tcpip.ErrDuplicateAddress. -func (igmp *igmpState) joinGroup(groupAddress tcpip.Address) *tcpip.Error { - if !igmp.opts.Enabled { - return nil - } - - // As per RFC 2236 section 6 page 10, - // - // The all-systems group (address 224.0.0.1) is handled as a special - // case. The host starts in Idle Member state for that group on every - // interface, never transitions to another state, and never sends a - // report for that group. - // - // This is equivalent to not performing IGMP for the all-systems multicast - // address. Simply not performing IGMP when the group is added will prevent - // any work from being done on the all-systems multicast group when leaving - // the group or when query or report messages are received for it since the - // MGP state will not know about it. - if groupAddress == header.IPv4AllSystems { - return nil - } - +func (igmp *igmpState) joinGroup(groupAddress tcpip.Address) { igmp.mu.Lock() defer igmp.mu.Unlock() + igmp.mu.genericMulticastProtocol.JoinGroup(groupAddress, !igmp.ep.Enabled() /* dontInitialize */) +} - // JoinGroup returns false if we have already joined the group. - if !igmp.mu.genericMulticastProtocol.JoinGroup(groupAddress) { - return tcpip.ErrDuplicateAddress - } - return nil +// isInGroup returns true if the specified group has been joined locally. +func (igmp *igmpState) isInGroup(groupAddress tcpip.Address) bool { + igmp.mu.Lock() + defer igmp.mu.Unlock() + return igmp.mu.genericMulticastProtocol.IsLocallyJoined(groupAddress) } // leaveGroup handles removing the group from the membership map, cancels any // delay timers associated with that group, and sends the Leave Group message // if required. -// -// If the group does not exist in the membership map, this function will -// silently return. -func (igmp *igmpState) leaveGroup(groupAddress tcpip.Address) { - if !igmp.opts.Enabled { - return +func (igmp *igmpState) leaveGroup(groupAddress tcpip.Address) *tcpip.Error { + igmp.mu.Lock() + defer igmp.mu.Unlock() + + // LeaveGroup returns false only if the group was not joined. + if igmp.mu.genericMulticastProtocol.LeaveGroup(groupAddress) { + return nil } + return tcpip.ErrBadLocalAddress +} + +// softLeaveAll leaves all groups from the perspective of IGMP, but remains +// joined locally. +func (igmp *igmpState) softLeaveAll() { + igmp.mu.Lock() + defer igmp.mu.Unlock() + igmp.mu.genericMulticastProtocol.MakeAllNonMember() +} + +// initializeAll attemps to initialize the IGMP state for each group that has +// been joined locally. +func (igmp *igmpState) initializeAll() { igmp.mu.Lock() defer igmp.mu.Unlock() - igmp.mu.genericMulticastProtocol.LeaveGroup(groupAddress) + igmp.mu.genericMulticastProtocol.InitializeGroups() } diff --git a/pkg/tcpip/network/ipv4/ipv4.go b/pkg/tcpip/network/ipv4/ipv4.go index ce2087002..354ac1e1d 100644 --- a/pkg/tcpip/network/ipv4/ipv4.go +++ b/pkg/tcpip/network/ipv4/ipv4.go @@ -127,16 +127,18 @@ func (e *endpoint) Enable() *tcpip.Error { // endpoint may have left groups from the perspective of IGMP when the // endpoint was disabled. Either way, we need to let routers know to // send us multicast traffic. - joinedGroups := e.mu.addressableEndpointState.JoinedGroups() - for _, group := range joinedGroups { - e.igmp.joinGroup(group) - } + e.igmp.initializeAll() // As per RFC 1122 section 3.3.7, all hosts should join the all-hosts // multicast group. Note, the IANA calls the all-hosts multicast group the // all-systems multicast group. - _, err = e.joinGroupLocked(header.IPv4AllSystems) - return err + if err := e.joinGroupLocked(header.IPv4AllSystems); err != nil { + // joinGroupLocked only returns an error if the group address is not a valid + // IPv4 multicast address. + panic(fmt.Sprintf("e.joinGroupLocked(%s): %s", header.IPv4AllSystems, err)) + } + + return nil } // Enabled implements stack.NetworkEndpoint. @@ -173,16 +175,13 @@ func (e *endpoint) disableLocked() { } // The endpoint may have already left the multicast group. - if _, err := e.leaveGroupLocked(header.IPv4AllSystems); err != nil && err != tcpip.ErrBadLocalAddress { + if err := e.leaveGroupLocked(header.IPv4AllSystems); err != nil && err != tcpip.ErrBadLocalAddress { panic(fmt.Sprintf("unexpected error when leaving group = %s: %s", header.IPv4AllSystems, err)) } // Leave groups from the perspective of IGMP so that routers know that // we are no longer interested in the group. - joinedGroups := e.mu.addressableEndpointState.JoinedGroups() - for _, group := range joinedGroups { - e.igmp.leaveGroup(group) - } + e.igmp.softLeaveAll() // The address may have already been removed. if err := e.mu.addressableEndpointState.RemovePermanentAddress(ipv4BroadcastAddr.Address); err != nil && err != tcpip.ErrBadLocalAddress { @@ -849,69 +848,43 @@ func (e *endpoint) PermanentAddresses() []tcpip.AddressWithPrefix { } // JoinGroup implements stack.GroupAddressableEndpoint. -func (e *endpoint) JoinGroup(addr tcpip.Address) (bool, *tcpip.Error) { +func (e *endpoint) JoinGroup(addr tcpip.Address) *tcpip.Error { e.mu.Lock() defer e.mu.Unlock() return e.joinGroupLocked(addr) } -// joinGroupLocked is like JoinGroup, but with locking requirements. +// joinGroupLocked is like JoinGroup but with locking requirements. // // Precondition: e.mu must be locked. -func (e *endpoint) joinGroupLocked(addr tcpip.Address) (bool, *tcpip.Error) { +func (e *endpoint) joinGroupLocked(addr tcpip.Address) *tcpip.Error { if !header.IsV4MulticastAddress(addr) { - return false, tcpip.ErrBadAddress - } - // TODO(gvisor.dev/issue/4916): Keep track of join count and IGMP state in a - // single type. - joined, err := e.mu.addressableEndpointState.JoinGroup(addr) - if err != nil || !joined { - return joined, err - } - - // Only join the group from the perspective of IGMP when the endpoint is - // enabled. - // - // If we are not enabled right now, we will join the group from the - // perspective of IGMP when the endpoint is enabled. - if !e.Enabled() { - return true, nil - } - - // joinGroup only returns an error if we try to join a group twice, but we - // checked above to make sure that the group was newly joined. - if err := e.igmp.joinGroup(addr); err != nil { - panic(fmt.Sprintf("e.igmp.joinGroup(%s): %s", addr, err)) + return tcpip.ErrBadAddress } - return true, nil + e.igmp.joinGroup(addr) + return nil } // LeaveGroup implements stack.GroupAddressableEndpoint. -func (e *endpoint) LeaveGroup(addr tcpip.Address) (bool, *tcpip.Error) { +func (e *endpoint) LeaveGroup(addr tcpip.Address) *tcpip.Error { e.mu.Lock() defer e.mu.Unlock() return e.leaveGroupLocked(addr) } -// leaveGroupLocked is like LeaveGroup, but with locking requirements. +// leaveGroupLocked is like LeaveGroup but with locking requirements. // // Precondition: e.mu must be locked. -func (e *endpoint) leaveGroupLocked(addr tcpip.Address) (bool, *tcpip.Error) { - left, err := e.mu.addressableEndpointState.LeaveGroup(addr) - if err != nil || !left { - return left, err - } - - e.igmp.leaveGroup(addr) - return true, nil +func (e *endpoint) leaveGroupLocked(addr tcpip.Address) *tcpip.Error { + return e.igmp.leaveGroup(addr) } // IsInGroup implements stack.GroupAddressableEndpoint. func (e *endpoint) IsInGroup(addr tcpip.Address) bool { e.mu.RLock() defer e.mu.RUnlock() - return e.mu.addressableEndpointState.IsInGroup(addr) + return e.igmp.isInGroup(addr) } var _ stack.ForwardingNetworkProtocol = (*protocol)(nil) |