github.com/sagernet/gvisor@v0.0.0-20240428053021-e691de28565f/pkg/tcpip/network/ipv4/igmp.go (about)

     1  // Copyright 2020 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package ipv4
    16  
    17  import (
    18  	"fmt"
    19  	"math"
    20  	"time"
    21  
    22  	"github.com/sagernet/gvisor/pkg/buffer"
    23  	"github.com/sagernet/gvisor/pkg/tcpip"
    24  	"github.com/sagernet/gvisor/pkg/tcpip/header"
    25  	"github.com/sagernet/gvisor/pkg/tcpip/network/internal/ip"
    26  	"github.com/sagernet/gvisor/pkg/tcpip/stack"
    27  )
    28  
    29  const (
    30  	// v1RouterPresentTimeout from RFC 2236 Section 8.11, Page 18
    31  	// See note on igmpState.igmpV1Present for more detail.
    32  	v1RouterPresentTimeout = 400 * time.Second
    33  
    34  	// v1MaxRespTime from RFC 2236 Section 4, Page 5. "The IGMPv1 router
    35  	// will send General Queries with the Max Response Time set to 0. This MUST
    36  	// be interpreted as a value of 100 (10 seconds)."
    37  	//
    38  	// Note that the Max Response Time field is a value in units of deciseconds.
    39  	v1MaxRespTime = 10 * time.Second
    40  
    41  	// UnsolicitedReportIntervalMax is the maximum delay between sending
    42  	// unsolicited IGMP reports.
    43  	//
    44  	// Obtained from RFC 2236 Section 8.10, Page 19.
    45  	UnsolicitedReportIntervalMax = 10 * time.Second
    46  )
    47  
    48  type protocolMode int
    49  
    50  const (
    51  	protocolModeV2OrV3 protocolMode = iota
    52  	protocolModeV1
    53  	// protocolModeV1Compatibility is for maintaining compatibility with IGMPv1
    54  	// Routers.
    55  	//
    56  	// Per RFC 2236 Section 4 Page 6: "The IGMPv1 router expects Version 1
    57  	// Membership Reports in response to its Queries, and will not pay
    58  	// attention to Version 2 Membership Reports.  Therefore, a state variable
    59  	// MUST be kept for each interface, describing whether the multicast
    60  	// Querier on that interface is running IGMPv1 or IGMPv2.  This variable
    61  	// MUST be based upon whether or not an IGMPv1 query was heard in the last
    62  	// [Version 1 Router Present Timeout] seconds".
    63  	protocolModeV1Compatibility
    64  )
    65  
    66  // IGMPVersion is the forced version of IGMP.
    67  type IGMPVersion int
    68  
    69  const (
    70  	_ IGMPVersion = iota
    71  	// IGMPVersion1 indicates IGMPv1.
    72  	IGMPVersion1
    73  	// IGMPVersion2 indicates IGMPv2. Note that IGMP may still fallback to V1
    74  	// compatibility mode as required by IGMPv2.
    75  	IGMPVersion2
    76  	// IGMPVersion3 indicates IGMPv3. Note that IGMP may still fallback to V2
    77  	// compatibility mode as required by IGMPv3.
    78  	IGMPVersion3
    79  )
    80  
    81  // IGMPEndpoint is a network endpoint that supports IGMP.
    82  type IGMPEndpoint interface {
    83  	// SetIGMPVersion sets the IGMP version.
    84  	//
    85  	// Returns the previous IGMP version.
    86  	SetIGMPVersion(IGMPVersion) IGMPVersion
    87  
    88  	// GetIGMPVersion returns the IGMP version.
    89  	GetIGMPVersion() IGMPVersion
    90  }
    91  
    92  // IGMPOptions holds options for IGMP.
    93  type IGMPOptions struct {
    94  	// Enabled indicates whether IGMP will be performed.
    95  	//
    96  	// When enabled, IGMP may transmit IGMP report and leave messages when
    97  	// joining and leaving multicast groups respectively, and handle incoming
    98  	// IGMP packets.
    99  	//
   100  	// This field is ignored and is always assumed to be false for interfaces
   101  	// without neighbouring nodes (e.g. loopback).
   102  	Enabled bool
   103  }
   104  
   105  var _ ip.MulticastGroupProtocol = (*igmpState)(nil)
   106  
   107  // igmpState is the per-interface IGMP state.
   108  //
   109  // igmpState.init() MUST be called after creating an IGMP state.
   110  type igmpState struct {
   111  	// The IPv4 endpoint this igmpState is for.
   112  	ep *endpoint
   113  
   114  	genericMulticastProtocol ip.GenericMulticastProtocolState
   115  
   116  	// mode is used to configure the version of IGMP to perform.
   117  	mode protocolMode
   118  
   119  	// igmpV1Job is scheduled when this interface receives an IGMPv1 style
   120  	// message, upon expiration the igmpV1Present flag is cleared.
   121  	// igmpV1Job may not be nil once igmpState is initialized.
   122  	igmpV1Job *tcpip.Job
   123  }
   124  
   125  // Enabled implements ip.MulticastGroupProtocol.
   126  func (igmp *igmpState) Enabled() bool {
   127  	// No need to perform IGMP on loopback interfaces since they don't have
   128  	// neighbouring nodes.
   129  	return igmp.ep.protocol.options.IGMP.Enabled && !igmp.ep.nic.IsLoopback() && igmp.ep.Enabled()
   130  }
   131  
   132  // SendReport implements ip.MulticastGroupProtocol.
   133  //
   134  // +checklocksread:igmp.ep.mu
   135  func (igmp *igmpState) SendReport(groupAddress tcpip.Address) (bool, tcpip.Error) {
   136  	igmpType := header.IGMPv2MembershipReport
   137  	switch igmp.mode {
   138  	case protocolModeV2OrV3:
   139  	case protocolModeV1, protocolModeV1Compatibility:
   140  		igmpType = header.IGMPv1MembershipReport
   141  	default:
   142  		panic(fmt.Sprintf("unrecognized mode = %d", igmp.mode))
   143  	}
   144  	return igmp.writePacket(groupAddress, groupAddress, igmpType)
   145  }
   146  
   147  // SendLeave implements ip.MulticastGroupProtocol.
   148  //
   149  // +checklocksread:igmp.ep.mu
   150  func (igmp *igmpState) SendLeave(groupAddress tcpip.Address) tcpip.Error {
   151  	// As per RFC 2236 Section 6, Page 8: "If the interface state says the
   152  	// Querier is running IGMPv1, this action SHOULD be skipped. If the flag
   153  	// saying we were the last host to report is cleared, this action MAY be
   154  	// skipped."
   155  	switch igmp.mode {
   156  	case protocolModeV2OrV3:
   157  		_, err := igmp.writePacket(header.IPv4AllRoutersGroup, groupAddress, header.IGMPLeaveGroup)
   158  		return err
   159  	case protocolModeV1, protocolModeV1Compatibility:
   160  		return nil
   161  	default:
   162  		panic(fmt.Sprintf("unrecognized mode = %d", igmp.mode))
   163  	}
   164  }
   165  
   166  // ShouldPerformProtocol implements ip.MulticastGroupProtocol.
   167  func (igmp *igmpState) ShouldPerformProtocol(groupAddress tcpip.Address) bool {
   168  	// As per RFC 2236 section 6 page 10,
   169  	//
   170  	//   The all-systems group (address 224.0.0.1) is handled as a special
   171  	//   case. The host starts in Idle Member state for that group on every
   172  	//   interface, never transitions to another state, and never sends a
   173  	//   report for that group.
   174  	return groupAddress != header.IPv4AllSystems
   175  }
   176  
   177  type igmpv3ReportBuilder struct {
   178  	igmp *igmpState
   179  
   180  	records []header.IGMPv3ReportGroupAddressRecordSerializer
   181  }
   182  
   183  // AddRecord implements ip.MulticastGroupProtocolV2ReportBuilder.
   184  func (b *igmpv3ReportBuilder) AddRecord(genericRecordType ip.MulticastGroupProtocolV2ReportRecordType, groupAddress tcpip.Address) {
   185  	var recordType header.IGMPv3ReportRecordType
   186  	switch genericRecordType {
   187  	case ip.MulticastGroupProtocolV2ReportRecordModeIsInclude:
   188  		recordType = header.IGMPv3ReportRecordModeIsInclude
   189  	case ip.MulticastGroupProtocolV2ReportRecordModeIsExclude:
   190  		recordType = header.IGMPv3ReportRecordModeIsExclude
   191  	case ip.MulticastGroupProtocolV2ReportRecordChangeToIncludeMode:
   192  		recordType = header.IGMPv3ReportRecordChangeToIncludeMode
   193  	case ip.MulticastGroupProtocolV2ReportRecordChangeToExcludeMode:
   194  		recordType = header.IGMPv3ReportRecordChangeToExcludeMode
   195  	case ip.MulticastGroupProtocolV2ReportRecordAllowNewSources:
   196  		recordType = header.IGMPv3ReportRecordAllowNewSources
   197  	case ip.MulticastGroupProtocolV2ReportRecordBlockOldSources:
   198  		recordType = header.IGMPv3ReportRecordBlockOldSources
   199  	default:
   200  		panic(fmt.Sprintf("unrecognied genericRecordType = %d", genericRecordType))
   201  	}
   202  
   203  	b.records = append(b.records, header.IGMPv3ReportGroupAddressRecordSerializer{
   204  		RecordType:   recordType,
   205  		GroupAddress: groupAddress,
   206  		Sources:      nil,
   207  	})
   208  }
   209  
   210  // Send implements ip.MulticastGroupProtocolV2ReportBuilder.
   211  //
   212  // +checklocksread:b.igmp.ep.mu
   213  func (b *igmpv3ReportBuilder) Send() (sent bool, err tcpip.Error) {
   214  	if len(b.records) == 0 {
   215  		return false, err
   216  	}
   217  
   218  	options := header.IPv4OptionsSerializer{
   219  		&header.IPv4SerializableRouterAlertOption{},
   220  	}
   221  	mtu := int(b.igmp.ep.MTU()) - int(options.Length())
   222  
   223  	allSentWithSpecifiedAddress := true
   224  	var firstErr tcpip.Error
   225  	for records := b.records; len(records) != 0; {
   226  		spaceLeft := mtu
   227  		maxRecords := 0
   228  
   229  		for ; maxRecords < len(records); maxRecords++ {
   230  			tmp := spaceLeft - records[maxRecords].Length()
   231  			if tmp > 0 {
   232  				spaceLeft = tmp
   233  			} else {
   234  				break
   235  			}
   236  		}
   237  
   238  		serializer := header.IGMPv3ReportSerializer{Records: records[:maxRecords]}
   239  		records = records[maxRecords:]
   240  
   241  		icmpView := buffer.NewViewSize(serializer.Length())
   242  		serializer.SerializeInto(icmpView.AsSlice())
   243  		if sentWithSpecifiedAddress, err := b.igmp.writePacketInner(
   244  			icmpView,
   245  			b.igmp.ep.stats.igmp.packetsSent.v3MembershipReport,
   246  			options,
   247  			header.IGMPv3RoutersAddress,
   248  		); err != nil {
   249  			if firstErr != nil {
   250  				firstErr = nil
   251  			}
   252  			allSentWithSpecifiedAddress = false
   253  		} else if !sentWithSpecifiedAddress {
   254  			allSentWithSpecifiedAddress = false
   255  		}
   256  	}
   257  
   258  	return allSentWithSpecifiedAddress, firstErr
   259  }
   260  
   261  // NewReportV2Builder implements ip.MulticastGroupProtocol.
   262  func (igmp *igmpState) NewReportV2Builder() ip.MulticastGroupProtocolV2ReportBuilder {
   263  	return &igmpv3ReportBuilder{igmp: igmp}
   264  }
   265  
   266  // V2QueryMaxRespCodeToV2Delay implements ip.MulticastGroupProtocol.
   267  func (*igmpState) V2QueryMaxRespCodeToV2Delay(code uint16) time.Duration {
   268  	if code > math.MaxUint8 {
   269  		panic(fmt.Sprintf("got IGMPv3 MaxRespCode = %d, want <= %d", code, math.MaxUint8))
   270  	}
   271  	return header.IGMPv3MaximumResponseDelay(uint8(code))
   272  }
   273  
   274  // V2QueryMaxRespCodeToV1Delay implements ip.MulticastGroupProtocol.
   275  func (*igmpState) V2QueryMaxRespCodeToV1Delay(code uint16) time.Duration {
   276  	return time.Duration(code) * time.Millisecond
   277  }
   278  
   279  // init sets up an igmpState struct, and is required to be called before using
   280  // a new igmpState.
   281  //
   282  // Must only be called once for the lifetime of igmp.
   283  func (igmp *igmpState) init(ep *endpoint) {
   284  	igmp.ep = ep
   285  	igmp.genericMulticastProtocol.Init(&ep.mu, ip.GenericMulticastProtocolOptions{
   286  		Rand:                      ep.protocol.stack.InsecureRNG(),
   287  		Clock:                     ep.protocol.stack.Clock(),
   288  		Protocol:                  igmp,
   289  		MaxUnsolicitedReportDelay: UnsolicitedReportIntervalMax,
   290  	})
   291  	// As per RFC 2236 Page 9 says "No IGMPv1 Router Present ... is
   292  	// the initial state.
   293  	igmp.mode = protocolModeV2OrV3
   294  	igmp.igmpV1Job = tcpip.NewJob(ep.protocol.stack.Clock(), &ep.mu, func() {
   295  		igmp.mode = protocolModeV2OrV3
   296  	})
   297  }
   298  
   299  // +checklocks:igmp.ep.mu
   300  func (igmp *igmpState) isSourceIPValidLocked(src tcpip.Address, messageType header.IGMPType) bool {
   301  	if messageType == header.IGMPMembershipQuery {
   302  		// RFC 2236 does not require the IGMP implementation to check the source IP
   303  		// for Membership Query messages.
   304  		return true
   305  	}
   306  
   307  	// As per RFC 2236 section 10,
   308  	//
   309  	//   Ignore the Report if you cannot identify the source address of the
   310  	//   packet as belonging to a subnet assigned to the interface on which the
   311  	//   packet was received.
   312  	//
   313  	//   Ignore the Leave message if you cannot identify the source address of
   314  	//   the packet as belonging to a subnet assigned to the interface on which
   315  	//   the packet was received.
   316  	//
   317  	// Note: this rule applies to both V1 and V2 Membership Reports.
   318  	var isSourceIPValid bool
   319  	igmp.ep.addressableEndpointState.ForEachPrimaryEndpoint(func(addressEndpoint stack.AddressEndpoint) bool {
   320  		if subnet := addressEndpoint.Subnet(); subnet.Contains(src) {
   321  			isSourceIPValid = true
   322  			return false
   323  		}
   324  		return true
   325  	})
   326  
   327  	return isSourceIPValid
   328  }
   329  
   330  // +checklocks:igmp.ep.mu
   331  func (igmp *igmpState) isPacketValidLocked(pkt *stack.PacketBuffer, messageType header.IGMPType, hasRouterAlertOption bool) bool {
   332  	// We can safely assume that the IP header is valid if we got this far.
   333  	iph := header.IPv4(pkt.NetworkHeader().Slice())
   334  
   335  	// As per RFC 2236 section 2,
   336  	//
   337  	//   All IGMP messages described in this document are sent with IP TTL 1, and
   338  	//   contain the IP Router Alert option [RFC 2113] in their IP header.
   339  	if !hasRouterAlertOption || iph.TTL() != header.IGMPTTL {
   340  		return false
   341  	}
   342  
   343  	return igmp.isSourceIPValidLocked(iph.SourceAddress(), messageType)
   344  }
   345  
   346  // handleIGMP handles an IGMP packet.
   347  //
   348  // +checklocks:igmp.ep.mu
   349  func (igmp *igmpState) handleIGMP(pkt *stack.PacketBuffer, hasRouterAlertOption bool) {
   350  	received := igmp.ep.stats.igmp.packetsReceived
   351  	hdr, ok := pkt.Data().PullUp(pkt.Data().Size())
   352  	if !ok {
   353  		received.invalid.Increment()
   354  		return
   355  	}
   356  	h := header.IGMP(hdr)
   357  	if len(h) < header.IGMPMinimumSize {
   358  		received.invalid.Increment()
   359  		return
   360  	}
   361  
   362  	// As per RFC 1071 section 1.3,
   363  	//
   364  	//   To check a checksum, the 1's complement sum is computed over the
   365  	//   same set of octets, including the checksum field. If the result
   366  	//   is all 1 bits (-0 in 1's complement arithmetic), the check
   367  	//   succeeds.
   368  	if pkt.Data().Checksum() != 0xFFFF {
   369  		received.checksumErrors.Increment()
   370  		return
   371  	}
   372  
   373  	isValid := func(minimumSize int) bool {
   374  		return len(hdr) >= minimumSize && igmp.isPacketValidLocked(pkt, h.Type(), hasRouterAlertOption)
   375  	}
   376  
   377  	switch h.Type() {
   378  	case header.IGMPMembershipQuery:
   379  		received.membershipQuery.Increment()
   380  		if len(h) >= header.IGMPv3QueryMinimumSize {
   381  			if isValid(header.IGMPv3QueryMinimumSize) {
   382  				igmp.handleMembershipQueryV3(header.IGMPv3Query(h))
   383  			} else {
   384  				received.invalid.Increment()
   385  			}
   386  			return
   387  		} else if !isValid(header.IGMPQueryMinimumSize) {
   388  			received.invalid.Increment()
   389  			return
   390  		}
   391  		igmp.handleMembershipQuery(h.GroupAddress(), h.MaxRespTime())
   392  	case header.IGMPv1MembershipReport:
   393  		received.v1MembershipReport.Increment()
   394  		if !isValid(header.IGMPReportMinimumSize) {
   395  			received.invalid.Increment()
   396  			return
   397  		}
   398  		igmp.handleMembershipReport(h.GroupAddress())
   399  	case header.IGMPv2MembershipReport:
   400  		received.v2MembershipReport.Increment()
   401  		if !isValid(header.IGMPReportMinimumSize) {
   402  			received.invalid.Increment()
   403  			return
   404  		}
   405  		igmp.handleMembershipReport(h.GroupAddress())
   406  	case header.IGMPLeaveGroup:
   407  		received.leaveGroup.Increment()
   408  		if !isValid(header.IGMPLeaveMessageMinimumSize) {
   409  			received.invalid.Increment()
   410  			return
   411  		}
   412  		// As per RFC 2236 Section 6, Page 7: "IGMP messages other than Query or
   413  		// Report, are ignored in all states"
   414  
   415  	default:
   416  		// As per RFC 2236 Section 2.1 Page 3: "Unrecognized message types should
   417  		// be silently ignored. New message types may be used by newer versions of
   418  		// IGMP, by multicast routing protocols, or other uses."
   419  		received.unrecognized.Increment()
   420  	}
   421  }
   422  
   423  func (igmp *igmpState) resetV1Present() {
   424  	igmp.igmpV1Job.Cancel()
   425  	switch igmp.mode {
   426  	case protocolModeV2OrV3, protocolModeV1:
   427  	case protocolModeV1Compatibility:
   428  		igmp.mode = protocolModeV2OrV3
   429  	default:
   430  		panic(fmt.Sprintf("unrecognized mode = %d", igmp.mode))
   431  	}
   432  }
   433  
   434  // handleMembershipQuery handles a membership query.
   435  //
   436  // +checklocks:igmp.ep.mu
   437  func (igmp *igmpState) handleMembershipQuery(groupAddress tcpip.Address, maxRespTime time.Duration) {
   438  	// As per RFC 2236 Section 6, Page 10: If the maximum response time is zero
   439  	// then change the state to note that an IGMPv1 router is present and
   440  	// schedule the query received Job.
   441  	if maxRespTime == 0 && igmp.Enabled() {
   442  		switch igmp.mode {
   443  		case protocolModeV2OrV3, protocolModeV1Compatibility:
   444  			igmp.igmpV1Job.Cancel()
   445  			igmp.igmpV1Job.Schedule(v1RouterPresentTimeout)
   446  			igmp.mode = protocolModeV1Compatibility
   447  		case protocolModeV1:
   448  		default:
   449  			panic(fmt.Sprintf("unrecognized mode = %d", igmp.mode))
   450  		}
   451  
   452  		maxRespTime = v1MaxRespTime
   453  	}
   454  
   455  	igmp.genericMulticastProtocol.HandleQueryLocked(groupAddress, maxRespTime)
   456  }
   457  
   458  // handleMembershipQueryV3 handles a membership query.
   459  //
   460  // +checklocks:igmp.ep.mu
   461  func (igmp *igmpState) handleMembershipQueryV3(igmpHdr header.IGMPv3Query) {
   462  	sources, ok := igmpHdr.Sources()
   463  	if !ok {
   464  		return
   465  	}
   466  
   467  	igmp.genericMulticastProtocol.HandleQueryV2Locked(
   468  		igmpHdr.GroupAddress(),
   469  		uint16(igmpHdr.MaximumResponseCode()),
   470  		sources,
   471  		igmpHdr.QuerierRobustnessVariable(),
   472  		igmpHdr.QuerierQueryInterval(),
   473  	)
   474  }
   475  
   476  // handleMembershipReport handles a membership report.
   477  //
   478  // +checklocks:igmp.ep.mu
   479  func (igmp *igmpState) handleMembershipReport(groupAddress tcpip.Address) {
   480  	igmp.genericMulticastProtocol.HandleReportLocked(groupAddress)
   481  }
   482  
   483  // writePacket assembles and sends an IGMP packet.
   484  //
   485  // +checklocksread:igmp.ep.mu
   486  func (igmp *igmpState) writePacket(destAddress tcpip.Address, groupAddress tcpip.Address, igmpType header.IGMPType) (bool, tcpip.Error) {
   487  	igmpView := buffer.NewViewSize(header.IGMPReportMinimumSize)
   488  	igmpData := header.IGMP(igmpView.AsSlice())
   489  	igmpData.SetType(igmpType)
   490  	igmpData.SetGroupAddress(groupAddress)
   491  	igmpData.SetChecksum(header.IGMPCalculateChecksum(igmpData))
   492  
   493  	var reportType tcpip.MultiCounterStat
   494  	sentStats := igmp.ep.stats.igmp.packetsSent
   495  	switch igmpType {
   496  	case header.IGMPv1MembershipReport:
   497  		reportType = sentStats.v1MembershipReport
   498  	case header.IGMPv2MembershipReport:
   499  		reportType = sentStats.v2MembershipReport
   500  	case header.IGMPLeaveGroup:
   501  		reportType = sentStats.leaveGroup
   502  	default:
   503  		panic(fmt.Sprintf("unrecognized igmp type = %d", igmpType))
   504  	}
   505  
   506  	return igmp.writePacketInner(
   507  		igmpView,
   508  		reportType,
   509  		header.IPv4OptionsSerializer{
   510  			&header.IPv4SerializableRouterAlertOption{},
   511  		},
   512  		destAddress,
   513  	)
   514  }
   515  
   516  // +checklocksread:igmp.ep.mu
   517  func (igmp *igmpState) writePacketInner(buf *buffer.View, reportStat tcpip.MultiCounterStat, options header.IPv4OptionsSerializer, destAddress tcpip.Address) (bool, tcpip.Error) {
   518  	pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
   519  		ReserveHeaderBytes: int(igmp.ep.MaxHeaderLength()),
   520  		Payload:            buffer.MakeWithView(buf),
   521  	})
   522  	defer pkt.DecRef()
   523  
   524  	addressEndpoint := igmp.ep.acquireOutgoingPrimaryAddressRLocked(destAddress, tcpip.Address{} /* srcHint */, false /* allowExpired */)
   525  	if addressEndpoint == nil {
   526  		return false, nil
   527  	}
   528  	localAddr := addressEndpoint.AddressWithPrefix().Address
   529  	addressEndpoint.DecRef()
   530  	addressEndpoint = nil
   531  	if err := igmp.ep.addIPHeader(localAddr, destAddress, pkt, stack.NetworkHeaderParams{
   532  		Protocol: header.IGMPProtocolNumber,
   533  		TTL:      header.IGMPTTL,
   534  		TOS:      stack.DefaultTOS,
   535  	}, options); err != nil {
   536  		panic(fmt.Sprintf("failed to add IP header: %s", err))
   537  	}
   538  
   539  	sentStats := igmp.ep.stats.igmp.packetsSent
   540  	if err := igmp.ep.nic.WritePacketToRemote(header.EthernetAddressFromMulticastIPv4Address(destAddress), pkt); err != nil {
   541  		sentStats.dropped.Increment()
   542  		return false, err
   543  	}
   544  	reportStat.Increment()
   545  	return true, nil
   546  }
   547  
   548  // joinGroup handles adding a new group to the membership map, setting up the
   549  // IGMP state for the group, and sending and scheduling the required
   550  // messages.
   551  //
   552  // If the group already exists in the membership map, returns
   553  // *tcpip.ErrDuplicateAddress.
   554  //
   555  // +checklocks:igmp.ep.mu
   556  func (igmp *igmpState) joinGroup(groupAddress tcpip.Address) {
   557  	igmp.genericMulticastProtocol.JoinGroupLocked(groupAddress)
   558  }
   559  
   560  // isInGroup returns true if the specified group has been joined locally.
   561  //
   562  // +checklocksread:igmp.ep.mu
   563  func (igmp *igmpState) isInGroup(groupAddress tcpip.Address) bool {
   564  	return igmp.genericMulticastProtocol.IsLocallyJoinedRLocked(groupAddress)
   565  }
   566  
   567  // leaveGroup handles removing the group from the membership map, cancels any
   568  // delay timers associated with that group, and sends the Leave Group message
   569  // if required.
   570  //
   571  // +checklocks:igmp.ep.mu
   572  func (igmp *igmpState) leaveGroup(groupAddress tcpip.Address) tcpip.Error {
   573  	// LeaveGroup returns false only if the group was not joined.
   574  	if igmp.genericMulticastProtocol.LeaveGroupLocked(groupAddress) {
   575  		return nil
   576  	}
   577  
   578  	return &tcpip.ErrBadLocalAddress{}
   579  }
   580  
   581  // softLeaveAll leaves all groups from the perspective of IGMP, but remains
   582  // joined locally.
   583  //
   584  // +checklocks:igmp.ep.mu
   585  func (igmp *igmpState) softLeaveAll() {
   586  	igmp.genericMulticastProtocol.MakeAllNonMemberLocked()
   587  }
   588  
   589  // initializeAll attempts to initialize the IGMP state for each group that has
   590  // been joined locally.
   591  //
   592  // +checklocks:igmp.ep.mu
   593  func (igmp *igmpState) initializeAll() {
   594  	igmp.genericMulticastProtocol.InitializeGroupsLocked()
   595  }
   596  
   597  // sendQueuedReports attempts to send any reports that are queued for sending.
   598  //
   599  // +checklocks:igmp.ep.mu
   600  func (igmp *igmpState) sendQueuedReports() {
   601  	igmp.genericMulticastProtocol.SendQueuedReportsLocked()
   602  }
   603  
   604  // setVersion sets the IGMP version.
   605  //
   606  // +checklocks:igmp.ep.mu
   607  func (igmp *igmpState) setVersion(v IGMPVersion) IGMPVersion {
   608  	prev := igmp.mode
   609  	igmp.igmpV1Job.Cancel()
   610  
   611  	var prevGenericModeV1 bool
   612  	switch v {
   613  	case IGMPVersion3:
   614  		prevGenericModeV1 = igmp.genericMulticastProtocol.SetV1ModeLocked(false)
   615  		igmp.mode = protocolModeV2OrV3
   616  	case IGMPVersion2:
   617  		// IGMPv1 and IGMPv2 map to V1 of the generic multicast protocol.
   618  		prevGenericModeV1 = igmp.genericMulticastProtocol.SetV1ModeLocked(true)
   619  		igmp.mode = protocolModeV2OrV3
   620  	case IGMPVersion1:
   621  		// IGMPv1 and IGMPv2 map to V1 of the generic multicast protocol.
   622  		prevGenericModeV1 = igmp.genericMulticastProtocol.SetV1ModeLocked(true)
   623  		igmp.mode = protocolModeV1
   624  	default:
   625  		panic(fmt.Sprintf("unrecognized version = %d", v))
   626  	}
   627  
   628  	return toIGMPVersion(prev, prevGenericModeV1)
   629  }
   630  
   631  func toIGMPVersion(mode protocolMode, genericV1 bool) IGMPVersion {
   632  	switch mode {
   633  	case protocolModeV2OrV3, protocolModeV1Compatibility:
   634  		if genericV1 {
   635  			return IGMPVersion2
   636  		}
   637  		return IGMPVersion3
   638  	case protocolModeV1:
   639  		return IGMPVersion1
   640  	default:
   641  		panic(fmt.Sprintf("unrecognized mode = %d", mode))
   642  	}
   643  }
   644  
   645  // getVersion returns the IGMP version.
   646  //
   647  // +checklocksread:igmp.ep.mu
   648  func (igmp *igmpState) getVersion() IGMPVersion {
   649  	return toIGMPVersion(igmp.mode, igmp.genericMulticastProtocol.GetV1ModeLocked())
   650  }