inet.af/netstack@v0.0.0-20220214151720-7585b01ddccf/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  	"sync/atomic"
    20  	"time"
    21  
    22  	"inet.af/netstack/tcpip"
    23  	"inet.af/netstack/tcpip/buffer"
    24  	"inet.af/netstack/tcpip/header"
    25  	"inet.af/netstack/tcpip/network/internal/ip"
    26  	"inet.af/netstack/tcpip/stack"
    27  )
    28  
    29  const (
    30  	// igmpV1PresentDefault is the initial state for igmpV1Present in the
    31  	// igmpState. As per RFC 2236 Page 9 says "No IGMPv1 Router Present ... is
    32  	// the initial state."
    33  	igmpV1PresentDefault = 0
    34  
    35  	// v1RouterPresentTimeout from RFC 2236 Section 8.11, Page 18
    36  	// See note on igmpState.igmpV1Present for more detail.
    37  	v1RouterPresentTimeout = 400 * time.Second
    38  
    39  	// v1MaxRespTime from RFC 2236 Section 4, Page 5. "The IGMPv1 router
    40  	// will send General Queries with the Max Response Time set to 0. This MUST
    41  	// be interpreted as a value of 100 (10 seconds)."
    42  	//
    43  	// Note that the Max Response Time field is a value in units of deciseconds.
    44  	v1MaxRespTime = 10 * time.Second
    45  
    46  	// UnsolicitedReportIntervalMax is the maximum delay between sending
    47  	// unsolicited IGMP reports.
    48  	//
    49  	// Obtained from RFC 2236 Section 8.10, Page 19.
    50  	UnsolicitedReportIntervalMax = 10 * time.Second
    51  )
    52  
    53  // IGMPOptions holds options for IGMP.
    54  type IGMPOptions struct {
    55  	// Enabled indicates whether IGMP will be performed.
    56  	//
    57  	// When enabled, IGMP may transmit IGMP report and leave messages when
    58  	// joining and leaving multicast groups respectively, and handle incoming
    59  	// IGMP packets.
    60  	//
    61  	// This field is ignored and is always assumed to be false for interfaces
    62  	// without neighbouring nodes (e.g. loopback).
    63  	Enabled bool
    64  }
    65  
    66  var _ ip.MulticastGroupProtocol = (*igmpState)(nil)
    67  
    68  // igmpState is the per-interface IGMP state.
    69  //
    70  // igmpState.init() MUST be called after creating an IGMP state.
    71  type igmpState struct {
    72  	// The IPv4 endpoint this igmpState is for.
    73  	ep *endpoint
    74  
    75  	genericMulticastProtocol ip.GenericMulticastProtocolState
    76  
    77  	// igmpV1Present is for maintaining compatibility with IGMPv1 Routers, from
    78  	// RFC 2236 Section 4 Page 6: "The IGMPv1 router expects Version 1
    79  	// Membership Reports in response to its Queries, and will not pay
    80  	// attention to Version 2 Membership Reports.  Therefore, a state variable
    81  	// MUST be kept for each interface, describing whether the multicast
    82  	// Querier on that interface is running IGMPv1 or IGMPv2.  This variable
    83  	// MUST be based upon whether or not an IGMPv1 query was heard in the last
    84  	// [Version 1 Router Present Timeout] seconds".
    85  	//
    86  	// Must be accessed with atomic operations. Holds a value of 1 when true, 0
    87  	// when false.
    88  	igmpV1Present uint32
    89  
    90  	// igmpV1Job is scheduled when this interface receives an IGMPv1 style
    91  	// message, upon expiration the igmpV1Present flag is cleared.
    92  	// igmpV1Job may not be nil once igmpState is initialized.
    93  	igmpV1Job *tcpip.Job
    94  }
    95  
    96  // Enabled implements ip.MulticastGroupProtocol.
    97  func (igmp *igmpState) Enabled() bool {
    98  	// No need to perform IGMP on loopback interfaces since they don't have
    99  	// neighbouring nodes.
   100  	return igmp.ep.protocol.options.IGMP.Enabled && !igmp.ep.nic.IsLoopback() && igmp.ep.Enabled()
   101  }
   102  
   103  // SendReport implements ip.MulticastGroupProtocol.
   104  //
   105  // Precondition: igmp.ep.mu must be read locked.
   106  func (igmp *igmpState) SendReport(groupAddress tcpip.Address) (bool, tcpip.Error) {
   107  	igmpType := header.IGMPv2MembershipReport
   108  	if igmp.v1Present() {
   109  		igmpType = header.IGMPv1MembershipReport
   110  	}
   111  	return igmp.writePacket(groupAddress, groupAddress, igmpType)
   112  }
   113  
   114  // SendLeave implements ip.MulticastGroupProtocol.
   115  //
   116  // Precondition: igmp.ep.mu must be read locked.
   117  func (igmp *igmpState) SendLeave(groupAddress tcpip.Address) tcpip.Error {
   118  	// As per RFC 2236 Section 6, Page 8: "If the interface state says the
   119  	// Querier is running IGMPv1, this action SHOULD be skipped. If the flag
   120  	// saying we were the last host to report is cleared, this action MAY be
   121  	// skipped."
   122  	if igmp.v1Present() {
   123  		return nil
   124  	}
   125  	_, err := igmp.writePacket(header.IPv4AllRoutersGroup, groupAddress, header.IGMPLeaveGroup)
   126  	return err
   127  }
   128  
   129  // ShouldPerformProtocol implements ip.MulticastGroupProtocol.
   130  func (igmp *igmpState) ShouldPerformProtocol(groupAddress tcpip.Address) bool {
   131  	// As per RFC 2236 section 6 page 10,
   132  	//
   133  	//   The all-systems group (address 224.0.0.1) is handled as a special
   134  	//   case. The host starts in Idle Member state for that group on every
   135  	//   interface, never transitions to another state, and never sends a
   136  	//   report for that group.
   137  	return groupAddress != header.IPv4AllSystems
   138  }
   139  
   140  // init sets up an igmpState struct, and is required to be called before using
   141  // a new igmpState.
   142  //
   143  // Must only be called once for the lifetime of igmp.
   144  func (igmp *igmpState) init(ep *endpoint) {
   145  	igmp.ep = ep
   146  	igmp.genericMulticastProtocol.Init(&ep.mu.RWMutex, ip.GenericMulticastProtocolOptions{
   147  		Rand:                      ep.protocol.stack.Rand(),
   148  		Clock:                     ep.protocol.stack.Clock(),
   149  		Protocol:                  igmp,
   150  		MaxUnsolicitedReportDelay: UnsolicitedReportIntervalMax,
   151  	})
   152  	igmp.igmpV1Present = igmpV1PresentDefault
   153  	igmp.igmpV1Job = tcpip.NewJob(ep.protocol.stack.Clock(), &ep.mu, func() {
   154  		igmp.setV1Present(false)
   155  	})
   156  }
   157  
   158  // Precondition: igmp.ep.mu must be locked.
   159  func (igmp *igmpState) isSourceIPValidLocked(src tcpip.Address, messageType header.IGMPType) bool {
   160  	if messageType == header.IGMPMembershipQuery {
   161  		// RFC 2236 does not require the IGMP implementation to check the source IP
   162  		// for Membership Query messages.
   163  		return true
   164  	}
   165  
   166  	// As per RFC 2236 section 10,
   167  	//
   168  	//   Ignore the Report if you cannot identify the source address of the
   169  	//   packet as belonging to a subnet assigned to the interface on which the
   170  	//   packet was received.
   171  	//
   172  	//   Ignore the Leave message if you cannot identify the source address of
   173  	//   the packet as belonging to a subnet assigned to the interface on which
   174  	//   the packet was received.
   175  	//
   176  	// Note: this rule applies to both V1 and V2 Membership Reports.
   177  	var isSourceIPValid bool
   178  	igmp.ep.mu.addressableEndpointState.ForEachPrimaryEndpoint(func(addressEndpoint stack.AddressEndpoint) bool {
   179  		if subnet := addressEndpoint.Subnet(); subnet.Contains(src) {
   180  			isSourceIPValid = true
   181  			return false
   182  		}
   183  		return true
   184  	})
   185  
   186  	return isSourceIPValid
   187  }
   188  
   189  // Precondition: igmp.ep.mu must be locked.
   190  func (igmp *igmpState) isPacketValidLocked(pkt *stack.PacketBuffer, messageType header.IGMPType, hasRouterAlertOption bool) bool {
   191  	// We can safely assume that the IP header is valid if we got this far.
   192  	iph := header.IPv4(pkt.NetworkHeader().View())
   193  
   194  	// As per RFC 2236 section 2,
   195  	//
   196  	//   All IGMP messages described in this document are sent with IP TTL 1, and
   197  	//   contain the IP Router Alert option [RFC 2113] in their IP header.
   198  	if !hasRouterAlertOption || iph.TTL() != header.IGMPTTL {
   199  		return false
   200  	}
   201  
   202  	return igmp.isSourceIPValidLocked(iph.SourceAddress(), messageType)
   203  }
   204  
   205  // handleIGMP handles an IGMP packet.
   206  //
   207  // Precondition: igmp.ep.mu must be locked.
   208  func (igmp *igmpState) handleIGMP(pkt *stack.PacketBuffer, hasRouterAlertOption bool) {
   209  	received := igmp.ep.stats.igmp.packetsReceived
   210  	headerView, ok := pkt.Data().PullUp(header.IGMPMinimumSize)
   211  	if !ok {
   212  		received.invalid.Increment()
   213  		return
   214  	}
   215  	h := header.IGMP(headerView)
   216  
   217  	// As per RFC 1071 section 1.3,
   218  	//
   219  	//   To check a checksum, the 1's complement sum is computed over the
   220  	//   same set of octets, including the checksum field. If the result
   221  	//   is all 1 bits (-0 in 1's complement arithmetic), the check
   222  	//   succeeds.
   223  	if pkt.Data().AsRange().Checksum() != 0xFFFF {
   224  		received.checksumErrors.Increment()
   225  		return
   226  	}
   227  
   228  	isValid := func(minimumSize int) bool {
   229  		return len(headerView) >= minimumSize && igmp.isPacketValidLocked(pkt, h.Type(), hasRouterAlertOption)
   230  	}
   231  
   232  	switch h.Type() {
   233  	case header.IGMPMembershipQuery:
   234  		received.membershipQuery.Increment()
   235  		if !isValid(header.IGMPQueryMinimumSize) {
   236  			received.invalid.Increment()
   237  			return
   238  		}
   239  		igmp.handleMembershipQuery(h.GroupAddress(), h.MaxRespTime())
   240  	case header.IGMPv1MembershipReport:
   241  		received.v1MembershipReport.Increment()
   242  		if !isValid(header.IGMPReportMinimumSize) {
   243  			received.invalid.Increment()
   244  			return
   245  		}
   246  		igmp.handleMembershipReport(h.GroupAddress())
   247  	case header.IGMPv2MembershipReport:
   248  		received.v2MembershipReport.Increment()
   249  		if !isValid(header.IGMPReportMinimumSize) {
   250  			received.invalid.Increment()
   251  			return
   252  		}
   253  		igmp.handleMembershipReport(h.GroupAddress())
   254  	case header.IGMPLeaveGroup:
   255  		received.leaveGroup.Increment()
   256  		if !isValid(header.IGMPLeaveMessageMinimumSize) {
   257  			received.invalid.Increment()
   258  			return
   259  		}
   260  		// As per RFC 2236 Section 6, Page 7: "IGMP messages other than Query or
   261  		// Report, are ignored in all states"
   262  
   263  	default:
   264  		// As per RFC 2236 Section 2.1 Page 3: "Unrecognized message types should
   265  		// be silently ignored. New message types may be used by newer versions of
   266  		// IGMP, by multicast routing protocols, or other uses."
   267  		received.unrecognized.Increment()
   268  	}
   269  }
   270  
   271  func (igmp *igmpState) v1Present() bool {
   272  	return atomic.LoadUint32(&igmp.igmpV1Present) == 1
   273  }
   274  
   275  func (igmp *igmpState) setV1Present(v bool) {
   276  	if v {
   277  		atomic.StoreUint32(&igmp.igmpV1Present, 1)
   278  	} else {
   279  		atomic.StoreUint32(&igmp.igmpV1Present, 0)
   280  	}
   281  }
   282  
   283  func (igmp *igmpState) resetV1Present() {
   284  	igmp.igmpV1Job.Cancel()
   285  	igmp.setV1Present(false)
   286  }
   287  
   288  // handleMembershipQuery handles a membership query.
   289  //
   290  // Precondition: igmp.ep.mu must be locked.
   291  func (igmp *igmpState) handleMembershipQuery(groupAddress tcpip.Address, maxRespTime time.Duration) {
   292  	// As per RFC 2236 Section 6, Page 10: If the maximum response time is zero
   293  	// then change the state to note that an IGMPv1 router is present and
   294  	// schedule the query received Job.
   295  	if maxRespTime == 0 && igmp.Enabled() {
   296  		igmp.igmpV1Job.Cancel()
   297  		igmp.igmpV1Job.Schedule(v1RouterPresentTimeout)
   298  		igmp.setV1Present(true)
   299  		maxRespTime = v1MaxRespTime
   300  	}
   301  
   302  	igmp.genericMulticastProtocol.HandleQueryLocked(groupAddress, maxRespTime)
   303  }
   304  
   305  // handleMembershipReport handles a membership report.
   306  //
   307  // Precondition: igmp.ep.mu must be locked.
   308  func (igmp *igmpState) handleMembershipReport(groupAddress tcpip.Address) {
   309  	igmp.genericMulticastProtocol.HandleReportLocked(groupAddress)
   310  }
   311  
   312  // writePacket assembles and sends an IGMP packet.
   313  //
   314  // Precondition: igmp.ep.mu must be read locked.
   315  func (igmp *igmpState) writePacket(destAddress tcpip.Address, groupAddress tcpip.Address, igmpType header.IGMPType) (bool, tcpip.Error) {
   316  	igmpData := header.IGMP(buffer.NewView(header.IGMPReportMinimumSize))
   317  	igmpData.SetType(igmpType)
   318  	igmpData.SetGroupAddress(groupAddress)
   319  	igmpData.SetChecksum(header.IGMPCalculateChecksum(igmpData))
   320  
   321  	pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
   322  		ReserveHeaderBytes: int(igmp.ep.MaxHeaderLength()),
   323  		Data:               buffer.View(igmpData).ToVectorisedView(),
   324  	})
   325  	defer pkt.DecRef()
   326  
   327  	addressEndpoint := igmp.ep.acquireOutgoingPrimaryAddressRLocked(destAddress, false /* allowExpired */)
   328  	if addressEndpoint == nil {
   329  		return false, nil
   330  	}
   331  	localAddr := addressEndpoint.AddressWithPrefix().Address
   332  	addressEndpoint.DecRef()
   333  	addressEndpoint = nil
   334  	if err := igmp.ep.addIPHeader(localAddr, destAddress, pkt, stack.NetworkHeaderParams{
   335  		Protocol: header.IGMPProtocolNumber,
   336  		TTL:      header.IGMPTTL,
   337  		TOS:      stack.DefaultTOS,
   338  	}, header.IPv4OptionsSerializer{
   339  		&header.IPv4SerializableRouterAlertOption{},
   340  	}); err != nil {
   341  		panic(fmt.Sprintf("failed to add IP header: %s", err))
   342  	}
   343  
   344  	sentStats := igmp.ep.stats.igmp.packetsSent
   345  	if err := igmp.ep.nic.WritePacketToRemote(header.EthernetAddressFromMulticastIPv4Address(destAddress), ProtocolNumber, pkt); err != nil {
   346  		sentStats.dropped.Increment()
   347  		return false, err
   348  	}
   349  	switch igmpType {
   350  	case header.IGMPv1MembershipReport:
   351  		sentStats.v1MembershipReport.Increment()
   352  	case header.IGMPv2MembershipReport:
   353  		sentStats.v2MembershipReport.Increment()
   354  	case header.IGMPLeaveGroup:
   355  		sentStats.leaveGroup.Increment()
   356  	default:
   357  		panic(fmt.Sprintf("unrecognized igmp type = %d", igmpType))
   358  	}
   359  	return true, nil
   360  }
   361  
   362  // joinGroup handles adding a new group to the membership map, setting up the
   363  // IGMP state for the group, and sending and scheduling the required
   364  // messages.
   365  //
   366  // If the group already exists in the membership map, returns
   367  // *tcpip.ErrDuplicateAddress.
   368  //
   369  // Precondition: igmp.ep.mu must be locked.
   370  func (igmp *igmpState) joinGroup(groupAddress tcpip.Address) {
   371  	igmp.genericMulticastProtocol.JoinGroupLocked(groupAddress)
   372  }
   373  
   374  // isInGroup returns true if the specified group has been joined locally.
   375  //
   376  // Precondition: igmp.ep.mu must be read locked.
   377  func (igmp *igmpState) isInGroup(groupAddress tcpip.Address) bool {
   378  	return igmp.genericMulticastProtocol.IsLocallyJoinedRLocked(groupAddress)
   379  }
   380  
   381  // leaveGroup handles removing the group from the membership map, cancels any
   382  // delay timers associated with that group, and sends the Leave Group message
   383  // if required.
   384  //
   385  // Precondition: igmp.ep.mu must be locked.
   386  func (igmp *igmpState) leaveGroup(groupAddress tcpip.Address) tcpip.Error {
   387  	// LeaveGroup returns false only if the group was not joined.
   388  	if igmp.genericMulticastProtocol.LeaveGroupLocked(groupAddress) {
   389  		return nil
   390  	}
   391  
   392  	return &tcpip.ErrBadLocalAddress{}
   393  }
   394  
   395  // softLeaveAll leaves all groups from the perspective of IGMP, but remains
   396  // joined locally.
   397  //
   398  // Precondition: igmp.ep.mu must be locked.
   399  func (igmp *igmpState) softLeaveAll() {
   400  	igmp.genericMulticastProtocol.MakeAllNonMemberLocked()
   401  }
   402  
   403  // initializeAll attemps to initialize the IGMP state for each group that has
   404  // been joined locally.
   405  //
   406  // Precondition: igmp.ep.mu must be locked.
   407  func (igmp *igmpState) initializeAll() {
   408  	igmp.genericMulticastProtocol.InitializeGroupsLocked()
   409  }
   410  
   411  // sendQueuedReports attempts to send any reports that are queued for sending.
   412  //
   413  // Precondition: igmp.ep.mu must be locked.
   414  func (igmp *igmpState) sendQueuedReports() {
   415  	igmp.genericMulticastProtocol.SendQueuedReportsLocked()
   416  }