diff options
Diffstat (limited to 'pkg/tcpip/network/ip')
-rw-r--r-- | pkg/tcpip/network/ip/BUILD | 29 | ||||
-rw-r--r-- | pkg/tcpip/network/ip/generic_multicast_protocol.go | 696 | ||||
-rw-r--r-- | pkg/tcpip/network/ip/generic_multicast_protocol_test.go | 812 | ||||
-rw-r--r-- | pkg/tcpip/network/ip/stats.go | 100 |
4 files changed, 0 insertions, 1637 deletions
diff --git a/pkg/tcpip/network/ip/BUILD b/pkg/tcpip/network/ip/BUILD deleted file mode 100644 index 411bca25d..000000000 --- a/pkg/tcpip/network/ip/BUILD +++ /dev/null @@ -1,29 +0,0 @@ -load("//tools:defs.bzl", "go_library", "go_test") - -package(licenses = ["notice"]) - -go_library( - name = "ip", - srcs = [ - "generic_multicast_protocol.go", - "stats.go", - ], - visibility = ["//visibility:public"], - deps = [ - "//pkg/sync", - "//pkg/tcpip", - ], -) - -go_test( - name = "ip_test", - size = "small", - srcs = ["generic_multicast_protocol_test.go"], - deps = [ - ":ip", - "//pkg/sync", - "//pkg/tcpip", - "//pkg/tcpip/faketime", - "@com_github_google_go_cmp//cmp:go_default_library", - ], -) diff --git a/pkg/tcpip/network/ip/generic_multicast_protocol.go b/pkg/tcpip/network/ip/generic_multicast_protocol.go deleted file mode 100644 index b9f129728..000000000 --- a/pkg/tcpip/network/ip/generic_multicast_protocol.go +++ /dev/null @@ -1,696 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package ip holds IPv4/IPv6 common utilities. -package ip - -import ( - "fmt" - "math/rand" - "time" - - "gvisor.dev/gvisor/pkg/sync" - "gvisor.dev/gvisor/pkg/tcpip" -) - -// hostState is the state a host may be in for a multicast group. -type hostState int - -// The states below are generic across IGMPv2 (RFC 2236 section 6) and MLDv1 -// (RFC 2710 section 5). Even though the states are generic across both IGMPv2 -// and MLDv1, IGMPv2 terminology will be used. -// -// ______________receive query______________ -// | | -// | _____send or receive report_____ | -// | | | | -// V | V | -// +-------+ +-----------+ +------------+ +-------------------+ +--------+ | -// | Non-M | | Pending-M | | Delaying-M | | Queued Delaying-M | | Idle-M | - -// +-------+ +-----------+ +------------+ +-------------------+ +--------+ -// | ^ | ^ | ^ | ^ -// | | | | | | | | -// ---------- ------- ---------- ------------- -// initialize new send inital fail to send send or receive -// group membership report delayed report report -// -// Not shown in the diagram above, but any state may transition into the non -// member state when a group is left. -const ( - // nonMember is the "'Non-Member' state, when the host does not belong to the - // group on the interface. This is the initial state for all memberships on - // all network interfaces; it requires no storage in the host." - // - // 'Non-Listener' is the MLDv1 term used to describe this state. - // - // This state is used to keep track of groups that have been joined locally, - // but without advertising the membership to the network. - nonMember hostState = iota - - // pendingMember is a newly joined member that is waiting to successfully send - // the initial set of reports. - // - // This is not an RFC defined state; it is an implementation specific state to - // track that the initial report needs to be sent. - // - // MAY NOT transition to the idle member state from this state. - pendingMember - - // delayingMember is the "'Delaying Member' state, when the host belongs to - // the group on the interface and has a report delay timer running for that - // membership." - // - // 'Delaying Listener' is the MLDv1 term used to describe this state. - delayingMember - - // queuedDelayingMember is a delayingMember that failed to send a report after - // its delayed report timer fired. Hosts in this state are waiting to attempt - // retransmission of the delayed report. - // - // This is not an RFC defined state; it is an implementation specific state to - // track that the delayed report needs to be sent. - // - // May transition to idle member if a report is received for a group. - queuedDelayingMember - - // idleMember is the "Idle Member" state, when the host belongs to the group - // on the interface and does not have a report delay timer running for that - // membership. - // - // 'Idle Listener' is the MLDv1 term used to describe this state. - idleMember -) - -func (s hostState) isDelayingMember() bool { - switch s { - case nonMember, pendingMember, idleMember: - return false - case delayingMember, queuedDelayingMember: - return true - default: - panic(fmt.Sprintf("unrecognized host state = %d", s)) - } -} - -// multicastGroupState holds the Generic Multicast Protocol state for a -// multicast group. -type multicastGroupState struct { - // joins is the number of times the group has been joined. - joins uint64 - - // state holds the host's state for the group. - state hostState - - // lastToSendReport is true if we sent the last report for the group. It is - // used to track whether there are other hosts on the subnet that are also - // members of the group. - // - // Defined in RFC 2236 section 6 page 9 for IGMPv2 and RFC 2710 section 5 page - // 8 for MLDv1. - lastToSendReport bool - - // delayedReportJob is used to delay sending responses to membership report - // messages in order to reduce duplicate reports from multiple hosts on the - // interface. - // - // Must not be nil. - 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 -// protocol. -type GenericMulticastProtocolOptions struct { - // Rand is the source of random numbers. - Rand *rand.Rand - - // Clock is the clock used to create timers. - Clock tcpip.Clock - - // Protocol is the implementation of the variant of multicast group protocol - // in use. - Protocol MulticastGroupProtocol - - // MaxUnsolicitedReportDelay is the maximum amount of time to wait between - // transmitting unsolicited reports. - // - // Unsolicited reports are transmitted when a group is newly joined. - MaxUnsolicitedReportDelay time.Duration - - // AllNodesAddress is a multicast address that all nodes on a network should - // be a member of. - // - // This address will not have the generic multicast protocol performed on it; - // it will be left in the non member/listener state, and packets will never - // be sent for it. - AllNodesAddress tcpip.Address -} - -// MulticastGroupProtocol is a multicast group protocol whose core state machine -// can be represented by GenericMulticastProtocolState. -type MulticastGroupProtocol interface { - // Enabled indicates whether the generic multicast protocol will be - // performed. - // - // When enabled, the protocol may transmit report and leave messages when - // joining and leaving multicast groups respectively, and handle incoming - // packets. - // - // When disabled, the protocol will still keep track of locally joined groups, - // it just won't transmit and handle packets, or update groups' state. - Enabled() bool - - // SendReport sends a multicast report for the specified group address. - // - // Returns false if the caller should queue the report to be sent later. Note, - // returning false does not mean that the receiver hit an error. - SendReport(groupAddress tcpip.Address) (sent bool, err tcpip.Error) - - // SendLeave sends a multicast leave for the specified group address. - SendLeave(groupAddress tcpip.Address) tcpip.Error -} - -// GenericMulticastProtocolState is the per interface generic multicast protocol -// state. -// -// There is actually no protocol named "Generic Multicast Protocol". Instead, -// the term used to refer to a generic multicast protocol that applies to both -// IPv4 and IPv6. Specifically, Generic Multicast Protocol is the core state -// machine of IGMPv2 as defined by RFC 2236 and MLDv1 as defined by RFC 2710. -// -// Callers must synchronize accesses to the generic multicast protocol state; -// GenericMulticastProtocolState obtains no locks in any of its methods. The -// only exception to this is GenericMulticastProtocolState's timer/job callbacks -// which will obtain the lock provided to the GenericMulticastProtocolState when -// it is initialized. -// -// GenericMulticastProtocolState.Init MUST be called before calling any of -// the methods on GenericMulticastProtocolState. -// -// GenericMulticastProtocolState.MakeAllNonMemberLocked MUST be called when the -// multicast group protocol is disabled so that leave messages may be sent. -type GenericMulticastProtocolState struct { - // Do not allow overwriting this state. - _ sync.NoCopy - - opts GenericMulticastProtocolOptions - - // memberships holds group addresses and their associated state. - memberships map[tcpip.Address]multicastGroupState - - // protocolMU is the mutex used to protect the protocol. - protocolMU *sync.RWMutex -} - -// Init initializes the Generic Multicast Protocol state. -// -// Must only be called once for the lifetime of g; Init will panic if it is -// called twice. -// -// The GenericMulticastProtocolState will only grab the lock when timers/jobs -// fire. -// -// Note: the methods on opts.Protocol will always be called while protocolMU is -// held. -func (g *GenericMulticastProtocolState) Init(protocolMU *sync.RWMutex, opts GenericMulticastProtocolOptions) { - if g.memberships != nil { - panic("attempted to initialize generic membership protocol state twice") - } - - *g = GenericMulticastProtocolState{ - opts: opts, - memberships: make(map[tcpip.Address]multicastGroupState), - protocolMU: protocolMU, - } -} - -// MakeAllNonMemberLocked transitions all groups to the non-member state. -// -// The groups will still be considered joined locally. -// -// MUST be called when the multicast group protocol is disabled. -// -// Precondition: g.protocolMU must be locked. -func (g *GenericMulticastProtocolState) MakeAllNonMemberLocked() { - if !g.opts.Protocol.Enabled() { - return - } - - for groupAddress, info := range g.memberships { - g.transitionToNonMemberLocked(groupAddress, &info) - g.memberships[groupAddress] = info - } -} - -// InitializeGroupsLocked initializes each group, as if they were newly joined -// but without affecting the groups' join count. -// -// Must only be called after calling MakeAllNonMember as a group should not be -// initialized while it is not in the non-member state. -// -// Precondition: g.protocolMU must be locked. -func (g *GenericMulticastProtocolState) InitializeGroupsLocked() { - if !g.opts.Protocol.Enabled() { - return - } - - for groupAddress, info := range g.memberships { - g.initializeNewMemberLocked(groupAddress, &info) - g.memberships[groupAddress] = info - } -} - -// SendQueuedReportsLocked attempts to send reports for groups that failed to -// send reports during their last attempt. -// -// Precondition: g.protocolMU must be locked. -func (g *GenericMulticastProtocolState) SendQueuedReportsLocked() { - for groupAddress, info := range g.memberships { - switch info.state { - case nonMember, delayingMember, idleMember: - case pendingMember: - // pendingMembers failed to send their initial unsolicited report so try - // to send the report and queue the extra unsolicited reports. - g.maybeSendInitialReportLocked(groupAddress, &info) - case queuedDelayingMember: - // queuedDelayingMembers failed to send their delayed reports so try to - // send the report and transition them to the idle state. - g.maybeSendDelayedReportLocked(groupAddress, &info) - default: - panic(fmt.Sprintf("unrecognized host state = %d", info.state)) - } - g.memberships[groupAddress] = info - } -} - -// JoinGroupLocked handles joining a new group. -// -// Precondition: g.protocolMU must be locked. -func (g *GenericMulticastProtocolState) JoinGroupLocked(groupAddress tcpip.Address) { - if info, ok := g.memberships[groupAddress]; ok { - // The group has already been joined. - info.joins++ - g.memberships[groupAddress] = info - return - } - - info := multicastGroupState{ - // Since we just joined the group, its count is 1. - joins: 1, - // The state will be updated below, if required. - state: nonMember, - lastToSendReport: false, - delayedReportJob: tcpip.NewJob(g.opts.Clock, g.protocolMU, func() { - if !g.opts.Protocol.Enabled() { - panic(fmt.Sprintf("delayed report job fired for group %s while the multicast group protocol is disabled", groupAddress)) - } - - info, ok := g.memberships[groupAddress] - if !ok { - panic(fmt.Sprintf("expected to find group state for group = %s", groupAddress)) - } - - g.maybeSendDelayedReportLocked(groupAddress, &info) - g.memberships[groupAddress] = info - }), - } - - if g.opts.Protocol.Enabled() { - g.initializeNewMemberLocked(groupAddress, &info) - } - - g.memberships[groupAddress] = info -} - -// IsLocallyJoinedRLocked returns true if the group is locally joined. -// -// Precondition: g.protocolMU must be read locked. -func (g *GenericMulticastProtocolState) IsLocallyJoinedRLocked(groupAddress tcpip.Address) bool { - _, ok := g.memberships[groupAddress] - return ok -} - -// LeaveGroupLocked handles leaving the group. -// -// Returns false if the group is not currently joined. -// -// Precondition: g.protocolMU must be locked. -func (g *GenericMulticastProtocolState) LeaveGroupLocked(groupAddress tcpip.Address) bool { - info, ok := g.memberships[groupAddress] - if !ok { - return false - } - - if info.joins == 0 { - panic(fmt.Sprintf("tried to leave group %s with a join count of 0", groupAddress)) - } - info.joins-- - if info.joins != 0 { - // If we still have outstanding joins, then do nothing further. - g.memberships[groupAddress] = info - return true - } - - g.transitionToNonMemberLocked(groupAddress, &info) - delete(g.memberships, groupAddress) - return true -} - -// HandleQueryLocked handles a query message with the specified maximum response -// time. -// -// If the group address is unspecified, then reports will be scheduled for all -// joined groups. -// -// Report(s) will be scheduled to be sent after a random duration between 0 and -// the maximum response time. -// -// Precondition: g.protocolMU must be locked. -func (g *GenericMulticastProtocolState) HandleQueryLocked(groupAddress tcpip.Address, maxResponseTime time.Duration) { - if !g.opts.Protocol.Enabled() { - return - } - - // As per RFC 2236 section 2.4 (for IGMPv2), - // - // In a Membership Query message, the group address field is set to zero - // when sending a General Query, and set to the group address being - // queried when sending a Group-Specific Query. - // - // As per RFC 2710 section 3.6 (for MLDv1), - // - // In a Query message, the Multicast Address field is set to zero when - // sending a General Query, and set to a specific IPv6 multicast address - // when sending a Multicast-Address-Specific Query. - if groupAddress.Unspecified() { - // This is a general query as the group address is unspecified. - for groupAddress, info := range g.memberships { - g.setDelayTimerForAddressRLocked(groupAddress, &info, maxResponseTime) - g.memberships[groupAddress] = info - } - } else if info, ok := g.memberships[groupAddress]; ok { - g.setDelayTimerForAddressRLocked(groupAddress, &info, maxResponseTime) - g.memberships[groupAddress] = info - } -} - -// HandleReportLocked handles a report message. -// -// If the report is for a joined group, any active delayed report will be -// cancelled and the host state for the group transitions to idle. -// -// Precondition: g.protocolMU must be locked. -func (g *GenericMulticastProtocolState) HandleReportLocked(groupAddress tcpip.Address) { - if !g.opts.Protocol.Enabled() { - return - } - - // As per RFC 2236 section 3 pages 3-4 (for IGMPv2), - // - // If the host receives another host's Report (version 1 or 2) while it has - // a timer running, it stops its timer for the specified group and does not - // send a Report - // - // As per RFC 2710 section 4 page 6 (for MLDv1), - // - // If a node receives another node's Report from an interface for a - // multicast address while it has a timer running for that same address - // on that interface, it stops its timer and does not send a Report for - // that address, thus suppressing duplicate reports on the link. - if info, ok := g.memberships[groupAddress]; ok && info.state.isDelayingMember() { - info.cancelDelayedReportJob() - info.lastToSendReport = false - info.state = idleMember - g.memberships[groupAddress] = info - } -} - -// initializeNewMemberLocked initializes a new group membership. -// -// Precondition: g.protocolMU must be locked. -func (g *GenericMulticastProtocolState) initializeNewMemberLocked(groupAddress tcpip.Address, info *multicastGroupState) { - if info.state != nonMember { - panic(fmt.Sprintf("host must be in non-member state to be initialized; group = %s, state = %d", groupAddress, info.state)) - } - - info.lastToSendReport = false - - if groupAddress == g.opts.AllNodesAddress { - // As per RFC 2236 section 6 page 10 (for IGMPv2), - // - // 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. - // - // As per RFC 2710 section 5 page 10 (for MLDv1), - // - // The link-scope all-nodes address (FF02::1) is handled as a special - // case. The node starts in Idle Listener state for that address on - // every interface, never transitions to another state, and never sends - // a Report or Done for that address. - info.state = idleMember - return - } - - info.state = pendingMember - g.maybeSendInitialReportLocked(groupAddress, info) -} - -// maybeSendInitialReportLocked attempts to start transmission of the initial -// set of reports after newly joining a group. -// -// Host must be in pending member state. -// -// Precondition: g.protocolMU must be locked. -func (g *GenericMulticastProtocolState) maybeSendInitialReportLocked(groupAddress tcpip.Address, info *multicastGroupState) { - if info.state != pendingMember { - panic(fmt.Sprintf("host must be in pending member state to send initial reports; group = %s, state = %d", groupAddress, info.state)) - } - - // As per RFC 2236 section 3 page 5 (for IGMPv2), - // - // When a host joins a multicast group, it should immediately transmit an - // unsolicited Version 2 Membership Report for that group" ... "it is - // recommended that it be repeated". - // - // As per RFC 2710 section 4 page 6 (for MLDv1), - // - // When a node starts listening to a multicast address on an interface, - // it should immediately transmit an unsolicited Report for that address - // on that interface, in case it is the first listener on the link. To - // cover the possibility of the initial Report being lost or damaged, it - // is recommended that it be repeated once or twice after short delays - // [Unsolicited Report Interval]. - // - // TODO(gvisor.dev/issue/4901): Support a configurable number of initial - // unsolicited reports. - sent, err := g.opts.Protocol.SendReport(groupAddress) - if err == nil && sent { - info.lastToSendReport = true - g.setDelayTimerForAddressRLocked(groupAddress, info, g.opts.MaxUnsolicitedReportDelay) - } -} - -// maybeSendDelayedReportLocked attempts to send the delayed report. -// -// Host must be in pending, delaying or queued delaying member state. -// -// Precondition: g.protocolMU must be locked. -func (g *GenericMulticastProtocolState) maybeSendDelayedReportLocked(groupAddress tcpip.Address, info *multicastGroupState) { - if !info.state.isDelayingMember() { - panic(fmt.Sprintf("host must be in delaying or queued delaying member state to send delayed reports; group = %s, state = %d", groupAddress, info.state)) - } - - sent, err := g.opts.Protocol.SendReport(groupAddress) - if err == nil && sent { - info.lastToSendReport = true - info.state = idleMember - } else { - info.state = queuedDelayingMember - } -} - -// maybeSendLeave attempts to send a leave message. -func (g *GenericMulticastProtocolState) maybeSendLeave(groupAddress tcpip.Address, lastToSendReport bool) { - if !g.opts.Protocol.Enabled() || !lastToSendReport { - return - } - - if groupAddress == g.opts.AllNodesAddress { - // As per RFC 2236 section 6 page 10 (for IGMPv2), - // - // 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. - // - // As per RFC 2710 section 5 page 10 (for MLDv1), - // - // The link-scope all-nodes address (FF02::1) is handled as a special - // case. The node starts in Idle Listener state for that address on - // every interface, never transitions to another state, and never sends - // a Report or Done for that address. - return - } - - // Okay to ignore the error here as if packet write failed, the multicast - // routers will eventually drop our membership anyways. If the interface is - // being disabled or removed, the generic multicast protocol's should be - // cleared eventually. - // - // As per RFC 2236 section 3 page 5 (for IGMPv2), - // - // When a router receives a Report, it adds the group being reported to - // the list of multicast group memberships on the network on which it - // received the Report and sets the timer for the membership to the - // [Group Membership Interval]. Repeated Reports refresh the timer. If - // no Reports are received for a particular group before this timer has - // expired, the router assumes that the group has no local members and - // that it need not forward remotely-originated multicasts for that - // group onto the attached network. - // - // As per RFC 2710 section 4 page 5 (for MLDv1), - // - // When a router receives a Report from a link, if the reported address - // is not already present in the router's list of multicast address - // having listeners on that link, the reported address is added to the - // list, its timer is set to [Multicast Listener Interval], and its - // appearance is made known to the router's multicast routing component. - // If a Report is received for a multicast address that is already - // present in the router's list, the timer for that address is reset to - // [Multicast Listener Interval]. If an address's timer expires, it is - // assumed that there are no longer any listeners for that address - // present on the link, so it is deleted from the list and its - // disappearance is made known to the multicast routing component. - // - // The requirement to send a leave message is also optional (it MAY be - // skipped): - // - // As per RFC 2236 section 6 page 8 (for IGMPv2), - // - // "send leave" for the group on the interface. If the interface - // state says the Querier is running IGMPv1, this action SHOULD be - // skipped. If the flag saying we were the last host to report is - // cleared, this action MAY be skipped. The Leave Message is sent to - // the ALL-ROUTERS group (224.0.0.2). - // - // As per RFC 2710 section 5 page 8 (for MLDv1), - // - // "send done" for the address on the interface. If the flag saying - // we were the last node to report is cleared, this action MAY be - // skipped. The Done message is sent to the link-scope all-routers - // address (FF02::2). - _ = g.opts.Protocol.SendLeave(groupAddress) -} - -// transitionToNonMemberLocked transitions the given multicast group the the -// non-member/listener state. -// -// Precondition: g.protocolMU must be locked. -func (g *GenericMulticastProtocolState) transitionToNonMemberLocked(groupAddress tcpip.Address, info *multicastGroupState) { - if info.state == nonMember { - return - } - - info.cancelDelayedReportJob() - g.maybeSendLeave(groupAddress, info.lastToSendReport) - info.lastToSendReport = false - info.state = nonMember -} - -// setDelayTimerForAddressRLocked sets timer to send a delay report. -// -// Precondition: g.protocolMU MUST be read locked. -func (g *GenericMulticastProtocolState) setDelayTimerForAddressRLocked(groupAddress tcpip.Address, info *multicastGroupState, maxResponseTime time.Duration) { - if info.state == nonMember { - return - } - - if groupAddress == g.opts.AllNodesAddress { - // As per RFC 2236 section 6 page 10 (for IGMPv2), - // - // 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. - // - // As per RFC 2710 section 5 page 10 (for MLDv1), - // - // The link-scope all-nodes address (FF02::1) is handled as a special - // case. The node starts in Idle Listener state for that address on - // every interface, never transitions to another state, and never sends - // a Report or Done for that address. - return - } - - // As per RFC 2236 section 3 page 3 (for IGMPv2), - // - // If a timer for the group is already unning, it is reset to the random - // value only if the requested Max Response Time is less than the remaining - // value of the running timer. - // - // As per RFC 2710 section 4 page 5 (for MLDv1), - // - // 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 - // the remaining value of the running timer. - now := time.Unix(0 /* seconds */, g.opts.Clock.NowNanoseconds()) - if info.state == delayingMember { - if info.delayedReportJobFiresAt.IsZero() { - 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.cancelDelayedReportJob() - maxResponseTime = g.calculateDelayTimerDuration(maxResponseTime) - info.delayedReportJob.Schedule(maxResponseTime) - info.delayedReportJobFiresAt = now.Add(maxResponseTime) -} - -// calculateDelayTimerDuration returns a random time between (0, maxRespTime]. -func (g *GenericMulticastProtocolState) calculateDelayTimerDuration(maxRespTime time.Duration) time.Duration { - // As per RFC 2236 section 3 page 3 (for IGMPv2), - // - // When a host receives a Group-Specific Query, it sets a delay timer to a - // random value selected from the range (0, Max Response Time]... - // - // As per RFC 2710 section 4 page 6 (for MLDv1), - // - // When a node receives a Multicast-Address-Specific Query, if it is - // listening to the queried Multicast Address on the interface from - // which the Query was received, it sets a delay timer for that address - // to a random value selected from the range [0, Maximum Response Delay], - // as above. - if maxRespTime == 0 { - return 0 - } - return time.Duration(g.opts.Rand.Int63n(int64(maxRespTime))) -} diff --git a/pkg/tcpip/network/ip/generic_multicast_protocol_test.go b/pkg/tcpip/network/ip/generic_multicast_protocol_test.go deleted file mode 100644 index 60eaea37e..000000000 --- a/pkg/tcpip/network/ip/generic_multicast_protocol_test.go +++ /dev/null @@ -1,812 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package ip_test - -import ( - "math/rand" - "testing" - "time" - - "github.com/google/go-cmp/cmp" - "gvisor.dev/gvisor/pkg/sync" - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/pkg/tcpip/faketime" - "gvisor.dev/gvisor/pkg/tcpip/network/ip" -) - -const ( - addr1 = tcpip.Address("\x01") - addr2 = tcpip.Address("\x02") - addr3 = tcpip.Address("\x03") - addr4 = tcpip.Address("\x04") - - maxUnsolicitedReportDelay = time.Second -) - -var _ ip.MulticastGroupProtocol = (*mockMulticastGroupProtocol)(nil) - -type mockMulticastGroupProtocolProtectedFields struct { - sync.RWMutex - - genericMulticastGroup ip.GenericMulticastProtocolState - sendReportGroupAddrCount map[tcpip.Address]int - sendLeaveGroupAddrCount map[tcpip.Address]int - makeQueuePackets bool - disabled bool -} - -type mockMulticastGroupProtocol struct { - t *testing.T - - mu mockMulticastGroupProtocolProtectedFields -} - -func (m *mockMulticastGroupProtocol) init(opts ip.GenericMulticastProtocolOptions) { - m.mu.Lock() - defer m.mu.Unlock() - m.initLocked() - opts.Protocol = m - m.mu.genericMulticastGroup.Init(&m.mu.RWMutex, opts) -} - -func (m *mockMulticastGroupProtocol) initLocked() { - m.mu.sendReportGroupAddrCount = make(map[tcpip.Address]int) - m.mu.sendLeaveGroupAddrCount = make(map[tcpip.Address]int) -} - -func (m *mockMulticastGroupProtocol) setEnabled(v bool) { - m.mu.Lock() - defer m.mu.Unlock() - m.mu.disabled = !v -} - -func (m *mockMulticastGroupProtocol) setQueuePackets(v bool) { - m.mu.Lock() - defer m.mu.Unlock() - m.mu.makeQueuePackets = v -} - -func (m *mockMulticastGroupProtocol) joinGroup(addr tcpip.Address) { - m.mu.Lock() - defer m.mu.Unlock() - m.mu.genericMulticastGroup.JoinGroupLocked(addr) -} - -func (m *mockMulticastGroupProtocol) leaveGroup(addr tcpip.Address) bool { - m.mu.Lock() - defer m.mu.Unlock() - return m.mu.genericMulticastGroup.LeaveGroupLocked(addr) -} - -func (m *mockMulticastGroupProtocol) handleReport(addr tcpip.Address) { - m.mu.Lock() - defer m.mu.Unlock() - m.mu.genericMulticastGroup.HandleReportLocked(addr) -} - -func (m *mockMulticastGroupProtocol) handleQuery(addr tcpip.Address, maxRespTime time.Duration) { - m.mu.Lock() - defer m.mu.Unlock() - m.mu.genericMulticastGroup.HandleQueryLocked(addr, maxRespTime) -} - -func (m *mockMulticastGroupProtocol) isLocallyJoined(addr tcpip.Address) bool { - m.mu.RLock() - defer m.mu.RUnlock() - return m.mu.genericMulticastGroup.IsLocallyJoinedRLocked(addr) -} - -func (m *mockMulticastGroupProtocol) makeAllNonMember() { - m.mu.Lock() - defer m.mu.Unlock() - m.mu.genericMulticastGroup.MakeAllNonMemberLocked() -} - -func (m *mockMulticastGroupProtocol) initializeGroups() { - m.mu.Lock() - defer m.mu.Unlock() - m.mu.genericMulticastGroup.InitializeGroupsLocked() -} - -func (m *mockMulticastGroupProtocol) sendQueuedReports() { - m.mu.Lock() - defer m.mu.Unlock() - m.mu.genericMulticastGroup.SendQueuedReportsLocked() -} - -// Enabled implements ip.MulticastGroupProtocol. -// -// Precondition: m.mu must be read locked. -func (m *mockMulticastGroupProtocol) Enabled() bool { - if m.mu.TryLock() { - m.mu.Unlock() - m.t.Fatal("got write lock, expected to not take the lock; generic multicast protocol must take the read or write lock before calling Enabled") - } - - return !m.mu.disabled -} - -// SendReport implements ip.MulticastGroupProtocol. -// -// Precondition: m.mu must be locked. -func (m *mockMulticastGroupProtocol) SendReport(groupAddress tcpip.Address) (bool, tcpip.Error) { - if m.mu.TryLock() { - m.mu.Unlock() - m.t.Fatalf("got write lock, expected to not take the lock; generic multicast protocol must take the write lock before sending report for %s", groupAddress) - } - if m.mu.TryRLock() { - m.mu.RUnlock() - m.t.Fatalf("got read lock, expected to not take the lock; generic multicast protocol must take the write lock before sending report for %s", groupAddress) - } - - m.mu.sendReportGroupAddrCount[groupAddress]++ - return !m.mu.makeQueuePackets, nil -} - -// SendLeave implements ip.MulticastGroupProtocol. -// -// Precondition: m.mu must be locked. -func (m *mockMulticastGroupProtocol) SendLeave(groupAddress tcpip.Address) tcpip.Error { - if m.mu.TryLock() { - m.mu.Unlock() - m.t.Fatalf("got write lock, expected to not take the lock; generic multicast protocol must take the write lock before sending leave for %s", groupAddress) - } - if m.mu.TryRLock() { - m.mu.RUnlock() - m.t.Fatalf("got read lock, expected to not take the lock; generic multicast protocol must take the write lock before sending leave for %s", groupAddress) - } - - m.mu.sendLeaveGroupAddrCount[groupAddress]++ - return nil -} - -func (m *mockMulticastGroupProtocol) check(sendReportGroupAddresses []tcpip.Address, sendLeaveGroupAddresses []tcpip.Address) string { - m.mu.Lock() - defer m.mu.Unlock() - - sendReportGroupAddrCount := make(map[tcpip.Address]int) - for _, a := range sendReportGroupAddresses { - sendReportGroupAddrCount[a] = 1 - } - - sendLeaveGroupAddrCount := make(map[tcpip.Address]int) - for _, a := range sendLeaveGroupAddresses { - sendLeaveGroupAddrCount[a] = 1 - } - - diff := cmp.Diff( - &mockMulticastGroupProtocol{ - mu: mockMulticastGroupProtocolProtectedFields{ - sendReportGroupAddrCount: sendReportGroupAddrCount, - sendLeaveGroupAddrCount: sendLeaveGroupAddrCount, - }, - }, - m, - cmp.AllowUnexported(mockMulticastGroupProtocol{}), - cmp.AllowUnexported(mockMulticastGroupProtocolProtectedFields{}), - // ignore mockMulticastGroupProtocol.mu and mockMulticastGroupProtocol.t - cmp.FilterPath( - func(p cmp.Path) bool { - switch p.Last().String() { - case ".RWMutex", ".t", ".makeQueuePackets", ".disabled", ".genericMulticastGroup": - return true - } - return false - }, - cmp.Ignore(), - ), - ) - m.initLocked() - return diff -} - -func TestJoinGroup(t *testing.T) { - tests := []struct { - name string - addr tcpip.Address - shouldSendReports bool - }{ - { - name: "Normal group", - addr: addr1, - shouldSendReports: true, - }, - { - name: "All-nodes group", - addr: addr2, - shouldSendReports: false, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - mgp := mockMulticastGroupProtocol{t: t} - clock := faketime.NewManualClock() - - mgp.init(ip.GenericMulticastProtocolOptions{ - Rand: rand.New(rand.NewSource(0)), - Clock: clock, - MaxUnsolicitedReportDelay: maxUnsolicitedReportDelay, - AllNodesAddress: addr2, - }) - - // Joining a group should send a report immediately and another after - // a random interval between 0 and the maximum unsolicited report delay. - mgp.joinGroup(test.addr) - if test.shouldSendReports { - if diff := mgp.check([]tcpip.Address{test.addr} /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" { - t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) - } - - // Generic multicast protocol timers are expected to take the job mutex. - clock.Advance(maxUnsolicitedReportDelay) - if diff := mgp.check([]tcpip.Address{test.addr} /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" { - t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) - } - } - - // Should have no more messages to send. - clock.Advance(time.Hour) - if diff := mgp.check(nil /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" { - t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) - } - }) - } -} - -func TestLeaveGroup(t *testing.T) { - tests := []struct { - name string - addr tcpip.Address - shouldSendMessages bool - }{ - { - name: "Normal group", - addr: addr1, - shouldSendMessages: true, - }, - { - name: "All-nodes group", - addr: addr2, - shouldSendMessages: false, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - mgp := mockMulticastGroupProtocol{t: t} - clock := faketime.NewManualClock() - - mgp.init(ip.GenericMulticastProtocolOptions{ - Rand: rand.New(rand.NewSource(1)), - Clock: clock, - MaxUnsolicitedReportDelay: maxUnsolicitedReportDelay, - AllNodesAddress: addr2, - }) - - mgp.joinGroup(test.addr) - if test.shouldSendMessages { - if diff := mgp.check([]tcpip.Address{test.addr} /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" { - t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) - } - } - - // Leaving a group should send a leave report immediately and cancel any - // delayed reports. - { - - if !mgp.leaveGroup(test.addr) { - t.Fatalf("got mgp.leaveGroup(%s) = false, want = true", test.addr) - } - } - if test.shouldSendMessages { - if diff := mgp.check(nil /* sendReportGroupAddresses */, []tcpip.Address{test.addr} /* sendLeaveGroupAddresses */); diff != "" { - t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) - } - } - - // Should have no more messages to send. - // - // Generic multicast protocol timers are expected to take the job mutex. - clock.Advance(time.Hour) - if diff := mgp.check(nil /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" { - t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) - } - }) - } -} - -func TestHandleReport(t *testing.T) { - tests := []struct { - name string - reportAddr tcpip.Address - expectReportsFor []tcpip.Address - }{ - { - name: "Unpecified empty", - reportAddr: "", - expectReportsFor: []tcpip.Address{addr1, addr2}, - }, - { - name: "Unpecified any", - reportAddr: "\x00", - expectReportsFor: []tcpip.Address{addr1, addr2}, - }, - { - name: "Specified", - reportAddr: addr1, - expectReportsFor: []tcpip.Address{addr2}, - }, - { - name: "Specified all-nodes", - reportAddr: addr3, - expectReportsFor: []tcpip.Address{addr1, addr2}, - }, - { - name: "Specified other", - reportAddr: addr4, - expectReportsFor: []tcpip.Address{addr1, addr2}, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - mgp := mockMulticastGroupProtocol{t: t} - clock := faketime.NewManualClock() - - mgp.init(ip.GenericMulticastProtocolOptions{ - Rand: rand.New(rand.NewSource(2)), - Clock: clock, - MaxUnsolicitedReportDelay: maxUnsolicitedReportDelay, - AllNodesAddress: addr3, - }) - - mgp.joinGroup(addr1) - if diff := mgp.check([]tcpip.Address{addr1} /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" { - t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) - } - mgp.joinGroup(addr2) - if diff := mgp.check([]tcpip.Address{addr2} /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" { - t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) - } - mgp.joinGroup(addr3) - if diff := mgp.check(nil /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" { - t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) - } - - // Receiving a report for a group we have a timer scheduled for should - // cancel our delayed report timer for the group. - mgp.handleReport(test.reportAddr) - if len(test.expectReportsFor) != 0 { - // Generic multicast protocol timers are expected to take the job mutex. - clock.Advance(maxUnsolicitedReportDelay) - 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. - clock.Advance(time.Hour) - if diff := mgp.check(nil /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" { - t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) - } - }) - } -} - -func TestHandleQuery(t *testing.T) { - tests := []struct { - name string - queryAddr tcpip.Address - maxDelay time.Duration - expectQueriedReportsFor []tcpip.Address - expectDelayedReportsFor []tcpip.Address - }{ - { - name: "Unpecified empty", - queryAddr: "", - maxDelay: 0, - expectQueriedReportsFor: []tcpip.Address{addr1, addr2}, - expectDelayedReportsFor: nil, - }, - { - name: "Unpecified any", - queryAddr: "\x00", - maxDelay: 1, - expectQueriedReportsFor: []tcpip.Address{addr1, addr2}, - expectDelayedReportsFor: nil, - }, - { - name: "Specified", - queryAddr: addr1, - maxDelay: 2, - expectQueriedReportsFor: []tcpip.Address{addr1}, - expectDelayedReportsFor: []tcpip.Address{addr2}, - }, - { - name: "Specified all-nodes", - queryAddr: addr3, - maxDelay: 3, - expectQueriedReportsFor: nil, - expectDelayedReportsFor: []tcpip.Address{addr1, addr2}, - }, - { - name: "Specified other", - queryAddr: addr4, - maxDelay: 4, - expectQueriedReportsFor: nil, - expectDelayedReportsFor: []tcpip.Address{addr1, addr2}, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - mgp := mockMulticastGroupProtocol{t: t} - clock := faketime.NewManualClock() - - mgp.init(ip.GenericMulticastProtocolOptions{ - Rand: rand.New(rand.NewSource(3)), - Clock: clock, - MaxUnsolicitedReportDelay: maxUnsolicitedReportDelay, - AllNodesAddress: addr3, - }) - - mgp.joinGroup(addr1) - if diff := mgp.check([]tcpip.Address{addr1} /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" { - t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) - } - mgp.joinGroup(addr2) - if diff := mgp.check([]tcpip.Address{addr2} /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" { - t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) - } - mgp.joinGroup(addr3) - if diff := mgp.check(nil /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" { - t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) - } - - // Receiving a query should make us reschedule our delayed report timer - // to some time within the new max response delay. - 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) - } - - // The groups that were not affected by the query should still send a - // report after the max unsolicited report delay. - clock.Advance(maxUnsolicitedReportDelay) - if diff := mgp.check(test.expectDelayedReportsFor /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" { - t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) - } - - // Should have no more messages to send. - clock.Advance(time.Hour) - if diff := mgp.check(nil /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" { - t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) - } - }) - } -} - -func TestJoinCount(t *testing.T) { - mgp := mockMulticastGroupProtocol{t: t} - clock := faketime.NewManualClock() - - mgp.init(ip.GenericMulticastProtocolOptions{ - Rand: rand.New(rand.NewSource(4)), - Clock: clock, - MaxUnsolicitedReportDelay: time.Second, - }) - - // Set the join count to 2 for a group. - mgp.joinGroup(addr1) - if !mgp.isLocallyJoined(addr1) { - t.Fatalf("got mgp.isLocallyJoined(%s) = false, want = true", addr1) - } - // Only the first join should trigger a report to be sent. - if diff := mgp.check([]tcpip.Address{addr1} /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" { - t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) - } - mgp.joinGroup(addr1) - if !mgp.isLocallyJoined(addr1) { - t.Errorf("got mgp.isLocallyJoined(%s) = false, want = true", addr1) - } - if diff := mgp.check(nil /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" { - t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) - } - if t.Failed() { - t.FailNow() - } - - // Group should still be considered joined after leaving once. - if !mgp.leaveGroup(addr1) { - t.Errorf("got mgp.leaveGroup(%s) = false, want = true", addr1) - } - if !mgp.isLocallyJoined(addr1) { - t.Errorf("got mgp.isLocallyJoined(%s) = false, want = true", addr1) - } - // A leave report should only be sent once the join count reaches 0. - if diff := mgp.check(nil /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" { - t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) - } - if t.Failed() { - t.FailNow() - } - - // Leaving once more should actually remove us from the group. - if !mgp.leaveGroup(addr1) { - t.Errorf("got mgp.leaveGroup(%s) = false, want = true", addr1) - } - if mgp.isLocallyJoined(addr1) { - t.Errorf("got mgp.isLocallyJoined(%s) = true, want = false", addr1) - } - if diff := mgp.check(nil /* sendReportGroupAddresses */, []tcpip.Address{addr1} /* sendLeaveGroupAddresses */); diff != "" { - t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) - } - if t.Failed() { - t.FailNow() - } - - // Group should no longer be joined so we should not have anything to - // leave. - if mgp.leaveGroup(addr1) { - t.Errorf("got mgp.leaveGroup(%s) = true, want = false", addr1) - } - if mgp.isLocallyJoined(addr1) { - t.Errorf("got mgp.isLocallyJoined(%s) = true, want = false", addr1) - } - if diff := mgp.check(nil /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" { - t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) - } - - // Should have no more messages to send. - // - // Generic multicast protocol timers are expected to take the job mutex. - clock.Advance(time.Hour) - if diff := mgp.check(nil /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" { - t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) - } -} - -func TestMakeAllNonMemberAndInitialize(t *testing.T) { - mgp := mockMulticastGroupProtocol{t: t} - clock := faketime.NewManualClock() - - mgp.init(ip.GenericMulticastProtocolOptions{ - Rand: rand.New(rand.NewSource(3)), - Clock: clock, - MaxUnsolicitedReportDelay: maxUnsolicitedReportDelay, - AllNodesAddress: addr3, - }) - - mgp.joinGroup(addr1) - if diff := mgp.check([]tcpip.Address{addr1} /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" { - t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) - } - mgp.joinGroup(addr2) - if diff := mgp.check([]tcpip.Address{addr2} /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" { - t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) - } - mgp.joinGroup(addr3) - if diff := mgp.check(nil /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" { - t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) - } - - // Should send the leave reports for each but still consider them locally - // joined. - mgp.makeAllNonMember() - if diff := mgp.check(nil /* sendReportGroupAddresses */, []tcpip.Address{addr1, addr2} /* sendLeaveGroupAddresses */); diff != "" { - t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) - } - // Generic multicast protocol timers are expected to take the job mutex. - clock.Advance(time.Hour) - if diff := mgp.check(nil /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" { - t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) - } - for _, group := range []tcpip.Address{addr1, addr2, addr3} { - if !mgp.isLocallyJoined(group) { - t.Fatalf("got mgp.isLocallyJoined(%s) = false, want = true", group) - } - } - - // Should send the initial set of unsolcited reports. - mgp.initializeGroups() - if diff := mgp.check([]tcpip.Address{addr1, addr2} /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" { - t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) - } - clock.Advance(maxUnsolicitedReportDelay) - if diff := mgp.check([]tcpip.Address{addr1, addr2} /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" { - t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) - } - - // Should have no more messages to send. - clock.Advance(time.Hour) - if diff := mgp.check(nil /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" { - t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) - } -} - -// TestGroupStateNonMember tests that groups do not send packets when in the -// non-member state, but are still considered locally joined. -func TestGroupStateNonMember(t *testing.T) { - mgp := mockMulticastGroupProtocol{t: t} - clock := faketime.NewManualClock() - - mgp.init(ip.GenericMulticastProtocolOptions{ - Rand: rand.New(rand.NewSource(3)), - Clock: clock, - MaxUnsolicitedReportDelay: maxUnsolicitedReportDelay, - }) - mgp.setEnabled(false) - - // Joining groups should not send any reports. - mgp.joinGroup(addr1) - if !mgp.isLocallyJoined(addr1) { - t.Fatalf("got mgp.isLocallyJoined(%s) = false, want = true", addr1) - } - if diff := mgp.check(nil /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" { - t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) - } - mgp.joinGroup(addr2) - if !mgp.isLocallyJoined(addr1) { - t.Fatalf("got mgp.isLocallyJoined(%s) = false, want = true", addr2) - } - if diff := mgp.check(nil /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" { - t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) - } - - // Receiving a query should not send any reports. - mgp.handleQuery(addr1, time.Nanosecond) - // Generic multicast protocol timers are expected to take the job mutex. - clock.Advance(time.Nanosecond) - if diff := mgp.check(nil /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" { - t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) - } - - // Leaving groups should not send any leave messages. - if !mgp.leaveGroup(addr1) { - t.Errorf("got mgp.leaveGroup(%s) = false, want = true", addr2) - } - if mgp.isLocallyJoined(addr1) { - t.Errorf("got mgp.isLocallyJoined(%s) = true, want = false", addr2) - } - if diff := mgp.check(nil /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" { - t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) - } - - clock.Advance(time.Hour) - if diff := mgp.check(nil /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" { - t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) - } -} - -func TestQueuedPackets(t *testing.T) { - clock := faketime.NewManualClock() - mgp := mockMulticastGroupProtocol{t: t} - mgp.init(ip.GenericMulticastProtocolOptions{ - Rand: rand.New(rand.NewSource(4)), - Clock: clock, - MaxUnsolicitedReportDelay: maxUnsolicitedReportDelay, - }) - - // Joining should trigger a SendReport, but mgp should report that we did not - // send the packet. - mgp.setQueuePackets(true) - mgp.joinGroup(addr1) - if diff := mgp.check([]tcpip.Address{addr1} /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" { - t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) - } - - // The delayed report timer should have been cancelled since we did not send - // the initial report earlier. - clock.Advance(time.Hour) - if diff := mgp.check(nil /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" { - t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) - } - - // Mock being able to successfully send the report. - mgp.setQueuePackets(false) - mgp.sendQueuedReports() - if diff := mgp.check([]tcpip.Address{addr1} /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" { - t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) - } - - // The delayed report (sent after the initial report) should now be sent. - clock.Advance(maxUnsolicitedReportDelay) - if diff := mgp.check([]tcpip.Address{addr1} /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" { - t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) - } - - // Should not have anything else to send (we should be idle). - mgp.sendQueuedReports() - clock.Advance(time.Hour) - if diff := mgp.check(nil /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" { - t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) - } - - // Receive a query but mock being unable to send reports again. - mgp.setQueuePackets(true) - mgp.handleQuery(addr1, time.Nanosecond) - clock.Advance(time.Nanosecond) - if diff := mgp.check([]tcpip.Address{addr1} /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" { - t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) - } - - // Mock being able to send reports again - we should have a packet queued to - // send. - mgp.setQueuePackets(false) - mgp.sendQueuedReports() - if diff := mgp.check([]tcpip.Address{addr1} /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" { - t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) - } - - // Should not have anything else to send. - mgp.sendQueuedReports() - clock.Advance(time.Hour) - if diff := mgp.check(nil /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" { - t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) - } - - // Receive a query again, but mock being unable to send reports. - mgp.setQueuePackets(true) - mgp.handleQuery(addr1, time.Nanosecond) - clock.Advance(time.Nanosecond) - if diff := mgp.check([]tcpip.Address{addr1} /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" { - t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) - } - - // Receiving a report should should transition us into the idle member state, - // even if we had a packet queued. We should no longer have any packets to - // send. - mgp.handleReport(addr1) - mgp.sendQueuedReports() - clock.Advance(time.Hour) - if diff := mgp.check(nil /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" { - t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) - } - - // When we fail to send the initial set of reports, incoming reports should - // not affect a newly joined group's reports from being sent. - mgp.setQueuePackets(true) - mgp.joinGroup(addr2) - if diff := mgp.check([]tcpip.Address{addr2} /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" { - t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) - } - mgp.handleReport(addr2) - // Attempting to send queued reports while still unable to send reports should - // not change the host state. - mgp.sendQueuedReports() - if diff := mgp.check([]tcpip.Address{addr2} /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" { - t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) - } - // Mock being able to successfully send the report. - mgp.setQueuePackets(false) - mgp.sendQueuedReports() - if diff := mgp.check([]tcpip.Address{addr2} /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" { - t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) - } - // The delayed report (sent after the initial report) should now be sent. - clock.Advance(maxUnsolicitedReportDelay) - if diff := mgp.check([]tcpip.Address{addr2} /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" { - t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) - } - - // Should not have anything else to send. - mgp.sendQueuedReports() - clock.Advance(time.Hour) - if diff := mgp.check(nil /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" { - t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff) - } -} diff --git a/pkg/tcpip/network/ip/stats.go b/pkg/tcpip/network/ip/stats.go deleted file mode 100644 index 898f8b356..000000000 --- a/pkg/tcpip/network/ip/stats.go +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package ip - -import "gvisor.dev/gvisor/pkg/tcpip" - -// LINT.IfChange(MultiCounterIPStats) - -// MultiCounterIPStats holds IP statistics, each counter may have several -// versions. -type MultiCounterIPStats struct { - // PacketsReceived is the total number of IP packets received from the link - // layer. - PacketsReceived tcpip.MultiCounterStat - - // DisabledPacketsReceived is the total number of IP packets received from the - // link layer when the IP layer is disabled. - DisabledPacketsReceived tcpip.MultiCounterStat - - // InvalidDestinationAddressesReceived is the total number of IP packets - // received with an unknown or invalid destination address. - InvalidDestinationAddressesReceived tcpip.MultiCounterStat - - // InvalidSourceAddressesReceived is the total number of IP packets received - // with a source address that should never have been received on the wire. - InvalidSourceAddressesReceived tcpip.MultiCounterStat - - // PacketsDelivered is the total number of incoming IP packets that are - // successfully delivered to the transport layer. - PacketsDelivered tcpip.MultiCounterStat - - // PacketsSent is the total number of IP packets sent via WritePacket. - PacketsSent tcpip.MultiCounterStat - - // OutgoingPacketErrors is the total number of IP packets which failed to - // write to a link-layer endpoint. - OutgoingPacketErrors tcpip.MultiCounterStat - - // MalformedPacketsReceived is the total number of IP Packets that were - // dropped due to the IP packet header failing validation checks. - MalformedPacketsReceived tcpip.MultiCounterStat - - // MalformedFragmentsReceived is the total number of IP Fragments that were - // dropped due to the fragment failing validation checks. - MalformedFragmentsReceived tcpip.MultiCounterStat - - // IPTablesPreroutingDropped is the total number of IP packets dropped in the - // Prerouting chain. - IPTablesPreroutingDropped tcpip.MultiCounterStat - - // IPTablesInputDropped is the total number of IP packets dropped in the Input - // chain. - IPTablesInputDropped tcpip.MultiCounterStat - - // IPTablesOutputDropped is the total number of IP packets dropped in the - // Output chain. - IPTablesOutputDropped tcpip.MultiCounterStat - - // OptionTSReceived is the number of Timestamp options seen. - OptionTSReceived tcpip.MultiCounterStat - - // OptionRRReceived is the number of Record Route options seen. - OptionRRReceived tcpip.MultiCounterStat - - // OptionUnknownReceived is the number of unknown IP options seen. - OptionUnknownReceived tcpip.MultiCounterStat -} - -// Init sets internal counters to track a and b counters. -func (m *MultiCounterIPStats) Init(a, b *tcpip.IPStats) { - m.PacketsReceived.Init(a.PacketsReceived, b.PacketsReceived) - m.DisabledPacketsReceived.Init(a.DisabledPacketsReceived, b.DisabledPacketsReceived) - m.InvalidDestinationAddressesReceived.Init(a.InvalidDestinationAddressesReceived, b.InvalidDestinationAddressesReceived) - m.InvalidSourceAddressesReceived.Init(a.InvalidSourceAddressesReceived, b.InvalidSourceAddressesReceived) - m.PacketsDelivered.Init(a.PacketsDelivered, b.PacketsDelivered) - m.PacketsSent.Init(a.PacketsSent, b.PacketsSent) - m.OutgoingPacketErrors.Init(a.OutgoingPacketErrors, b.OutgoingPacketErrors) - m.MalformedPacketsReceived.Init(a.MalformedPacketsReceived, b.MalformedPacketsReceived) - m.MalformedFragmentsReceived.Init(a.MalformedFragmentsReceived, b.MalformedFragmentsReceived) - m.IPTablesPreroutingDropped.Init(a.IPTablesPreroutingDropped, b.IPTablesPreroutingDropped) - m.IPTablesInputDropped.Init(a.IPTablesInputDropped, b.IPTablesInputDropped) - m.IPTablesOutputDropped.Init(a.IPTablesOutputDropped, b.IPTablesOutputDropped) - m.OptionTSReceived.Init(a.OptionTSReceived, b.OptionTSReceived) - m.OptionRRReceived.Init(a.OptionRRReceived, b.OptionRRReceived) - m.OptionUnknownReceived.Init(a.OptionUnknownReceived, b.OptionUnknownReceived) -} - -// LINT.ThenChange(:MultiCounterIPStats, ../../tcpip.go:IPStats) |