Refresh delayed report timers on query messages

...as per As per RFC 2236 section 3 page 3 (for IGMPv2) and
RFC 2710 section 4 page 5 (for MLDv1).

See comments in code for more details.

Test: ip_test.TestHandleQuery
PiperOrigin-RevId: 354603068
This commit is contained in:
Ghanan Gowripalan 2021-01-29 13:47:58 -08:00 committed by gVisor bot
parent 0a52b64794
commit 5e2edfb872
2 changed files with 68 additions and 42 deletions

View File

@ -126,6 +126,16 @@ type multicastGroupState struct {
// //
// Must not be nil. // Must not be nil.
delayedReportJob *tcpip.Job delayedReportJob *tcpip.Job
// delyedReportJobFiresAt is the time when the delayed report job will fire.
//
// A zero value indicates that the job is not scheduled.
delayedReportJobFiresAt time.Time
}
func (m *multicastGroupState) cancelDelayedReportJob() {
m.delayedReportJob.Cancel()
m.delayedReportJobFiresAt = time.Time{}
} }
// GenericMulticastProtocolOptions holds options for the generic multicast // GenericMulticastProtocolOptions holds options for the generic multicast
@ -428,7 +438,7 @@ func (g *GenericMulticastProtocolState) HandleReportLocked(groupAddress tcpip.Ad
// on that interface, it stops its timer and does not send a Report for // on that interface, it stops its timer and does not send a Report for
// that address, thus suppressing duplicate reports on the link. // that address, thus suppressing duplicate reports on the link.
if info, ok := g.memberships[groupAddress]; ok && info.state.isDelayingMember() { if info, ok := g.memberships[groupAddress]; ok && info.state.isDelayingMember() {
info.delayedReportJob.Cancel() info.cancelDelayedReportJob()
info.lastToSendReport = false info.lastToSendReport = false
info.state = idleMember info.state = idleMember
g.memberships[groupAddress] = info g.memberships[groupAddress] = info
@ -603,7 +613,7 @@ func (g *GenericMulticastProtocolState) transitionToNonMemberLocked(groupAddress
return return
} }
info.delayedReportJob.Cancel() info.cancelDelayedReportJob()
g.maybeSendLeave(groupAddress, info.lastToSendReport) g.maybeSendLeave(groupAddress, info.lastToSendReport)
info.lastToSendReport = false info.lastToSendReport = false
info.state = nonMember info.state = nonMember
@ -645,14 +655,24 @@ func (g *GenericMulticastProtocolState) setDelayTimerForAddressRLocked(groupAddr
// If a timer for any address is already running, it is reset to the new // If a timer for any address is already running, it is reset to the new
// random value only if the requested Maximum Response Delay is less than // random value only if the requested Maximum Response Delay is less than
// the remaining value of the running timer. // the remaining value of the running timer.
now := time.Unix(0 /* seconds */, g.opts.Clock.NowNanoseconds())
if info.state == delayingMember { if info.state == delayingMember {
// TODO: Reset the timer if time remaining is greater than maxResponseTime. if info.delayedReportJobFiresAt.IsZero() {
return panic(fmt.Sprintf("delayed report unscheduled while in the delaying member state; group = %s", groupAddress))
}
if info.delayedReportJobFiresAt.Sub(now) <= maxResponseTime {
// The timer is scheduled to fire before the maximum response time so we
// leave our timer as is.
return
}
} }
info.state = delayingMember info.state = delayingMember
info.delayedReportJob.Cancel() info.cancelDelayedReportJob()
info.delayedReportJob.Schedule(g.calculateDelayTimerDuration(maxResponseTime)) maxResponseTime = g.calculateDelayTimerDuration(maxResponseTime)
info.delayedReportJob.Schedule(maxResponseTime)
info.delayedReportJobFiresAt = now.Add(maxResponseTime)
} }
// calculateDelayTimerDuration returns a random time between (0, maxRespTime]. // calculateDelayTimerDuration returns a random time between (0, maxRespTime].

View File

@ -408,40 +408,46 @@ func TestHandleReport(t *testing.T) {
func TestHandleQuery(t *testing.T) { func TestHandleQuery(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
queryAddr tcpip.Address queryAddr tcpip.Address
maxDelay time.Duration maxDelay time.Duration
expectReportsFor []tcpip.Address expectQueriedReportsFor []tcpip.Address
expectDelayedReportsFor []tcpip.Address
}{ }{
{ {
name: "Unpecified empty", name: "Unpecified empty",
queryAddr: "", queryAddr: "",
maxDelay: 0, maxDelay: 0,
expectReportsFor: []tcpip.Address{addr1, addr2}, expectQueriedReportsFor: []tcpip.Address{addr1, addr2},
expectDelayedReportsFor: nil,
}, },
{ {
name: "Unpecified any", name: "Unpecified any",
queryAddr: "\x00", queryAddr: "\x00",
maxDelay: 1, maxDelay: 1,
expectReportsFor: []tcpip.Address{addr1, addr2}, expectQueriedReportsFor: []tcpip.Address{addr1, addr2},
expectDelayedReportsFor: nil,
}, },
{ {
name: "Specified", name: "Specified",
queryAddr: addr1, queryAddr: addr1,
maxDelay: 2, maxDelay: 2,
expectReportsFor: []tcpip.Address{addr1}, expectQueriedReportsFor: []tcpip.Address{addr1},
expectDelayedReportsFor: []tcpip.Address{addr2},
}, },
{ {
name: "Specified all-nodes", name: "Specified all-nodes",
queryAddr: addr3, queryAddr: addr3,
maxDelay: 3, maxDelay: 3,
expectReportsFor: nil, expectQueriedReportsFor: nil,
expectDelayedReportsFor: []tcpip.Address{addr1, addr2},
}, },
{ {
name: "Specified other", name: "Specified other",
queryAddr: addr4, queryAddr: addr4,
maxDelay: 4, maxDelay: 4,
expectReportsFor: nil, expectQueriedReportsFor: nil,
expectDelayedReportsFor: []tcpip.Address{addr1, addr2},
}, },
} }
@ -469,20 +475,20 @@ func TestHandleQuery(t *testing.T) {
if diff := mgp.check(nil /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" { if diff := mgp.check(nil /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
} }
// Generic multicast protocol timers are expected to take the job mutex.
clock.Advance(maxUnsolicitedReportDelay) // Receiving a query should make us reschedule our delayed report timer
if diff := mgp.check([]tcpip.Address{addr1, addr2} /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" { // to some time within the new max response delay.
t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) mgp.handleQuery(test.queryAddr, test.maxDelay)
clock.Advance(test.maxDelay)
if diff := mgp.check(test.expectQueriedReportsFor /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
} }
// Receiving a query should make us schedule a new delayed report if it // The groups that were not affected by the query should still send a
// is a query directed at us or a general query. // report after the max unsolicited report delay.
mgp.handleQuery(test.queryAddr, test.maxDelay) clock.Advance(maxUnsolicitedReportDelay)
if len(test.expectReportsFor) != 0 { if diff := mgp.check(test.expectDelayedReportsFor /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
clock.Advance(test.maxDelay) t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
if diff := mgp.check(test.expectReportsFor /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
}
} }
// Should have no more messages to send. // Should have no more messages to send.