github.com/ttpreport/gvisor-ligolo@v0.0.0-20240123134145-a858404967ba/pkg/tcpip/network/ipv6/mld.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 ipv6
    16  
    17  import (
    18  	"fmt"
    19  	"time"
    20  
    21  	"github.com/ttpreport/gvisor-ligolo/pkg/buffer"
    22  	"github.com/ttpreport/gvisor-ligolo/pkg/tcpip"
    23  	"github.com/ttpreport/gvisor-ligolo/pkg/tcpip/header"
    24  	"github.com/ttpreport/gvisor-ligolo/pkg/tcpip/network/internal/ip"
    25  	"github.com/ttpreport/gvisor-ligolo/pkg/tcpip/stack"
    26  )
    27  
    28  const (
    29  	// UnsolicitedReportIntervalMax is the maximum delay between sending
    30  	// unsolicited MLD reports.
    31  	//
    32  	// Obtained from RFC 2710 Section 7.10.
    33  	UnsolicitedReportIntervalMax = 10 * time.Second
    34  )
    35  
    36  // MLDVersion is the forced version of MLD.
    37  type MLDVersion int
    38  
    39  const (
    40  	_ MLDVersion = iota
    41  	// MLDVersion1 indicates MLDv1.
    42  	MLDVersion1
    43  	// MLDVersion2 indicates MLDv2. Note that MLD may still fallback to V1
    44  	// compatibility mode as required by MLDv2.
    45  	MLDVersion2
    46  )
    47  
    48  // MLDEndpoint is a network endpoint that supports MLD.
    49  type MLDEndpoint interface {
    50  	// SetMLDVersions sets the MLD version.
    51  	//
    52  	// Returns the previous MLD version.
    53  	SetMLDVersion(MLDVersion) MLDVersion
    54  
    55  	// GetMLDVersion returns the MLD version.
    56  	GetMLDVersion() MLDVersion
    57  }
    58  
    59  // MLDOptions holds options for MLD.
    60  type MLDOptions struct {
    61  	// Enabled indicates whether MLD will be performed.
    62  	//
    63  	// When enabled, MLD may transmit MLD report and done messages when
    64  	// joining and leaving multicast groups respectively, and handle incoming
    65  	// MLD packets.
    66  	//
    67  	// This field is ignored and is always assumed to be false for interfaces
    68  	// without neighbouring nodes (e.g. loopback).
    69  	Enabled bool
    70  }
    71  
    72  var _ ip.MulticastGroupProtocol = (*mldState)(nil)
    73  
    74  // mldState is the per-interface MLD state.
    75  //
    76  // mldState.init MUST be called to initialize the MLD state.
    77  type mldState struct {
    78  	// The IPv6 endpoint this mldState is for.
    79  	ep *endpoint
    80  
    81  	genericMulticastProtocol ip.GenericMulticastProtocolState
    82  }
    83  
    84  // Enabled implements ip.MulticastGroupProtocol.
    85  func (mld *mldState) Enabled() bool {
    86  	// No need to perform MLD on loopback interfaces since they don't have
    87  	// neighbouring nodes.
    88  	return mld.ep.protocol.options.MLD.Enabled && !mld.ep.nic.IsLoopback() && mld.ep.Enabled()
    89  }
    90  
    91  // SendReport implements ip.MulticastGroupProtocol.
    92  //
    93  // Precondition: mld.ep.mu must be read locked.
    94  func (mld *mldState) SendReport(groupAddress tcpip.Address) (bool, tcpip.Error) {
    95  	return mld.writePacket(groupAddress, groupAddress, header.ICMPv6MulticastListenerReport)
    96  }
    97  
    98  // SendLeave implements ip.MulticastGroupProtocol.
    99  //
   100  // Precondition: mld.ep.mu must be read locked.
   101  func (mld *mldState) SendLeave(groupAddress tcpip.Address) tcpip.Error {
   102  	_, err := mld.writePacket(header.IPv6AllRoutersLinkLocalMulticastAddress, groupAddress, header.ICMPv6MulticastListenerDone)
   103  	return err
   104  }
   105  
   106  // ShouldPerformProtocol implements ip.MulticastGroupProtocol.
   107  func (mld *mldState) ShouldPerformProtocol(groupAddress tcpip.Address) bool {
   108  	// As per RFC 2710 section 5 page 10,
   109  	//
   110  	//   The link-scope all-nodes address (FF02::1) is handled as a special
   111  	//   case. The node starts in Idle Listener state for that address on
   112  	//   every interface, never transitions to another state, and never sends
   113  	//   a Report or Done for that address.
   114  	//
   115  	//   MLD messages are never sent for multicast addresses whose scope is 0
   116  	//   (reserved) or 1 (node-local).
   117  	if groupAddress == header.IPv6AllNodesMulticastAddress {
   118  		return false
   119  	}
   120  
   121  	scope := header.V6MulticastScope(groupAddress)
   122  	return scope != header.IPv6Reserved0MulticastScope && scope != header.IPv6InterfaceLocalMulticastScope
   123  }
   124  
   125  type mldv2ReportBuilder struct {
   126  	mld *mldState
   127  
   128  	records []header.MLDv2ReportMulticastAddressRecordSerializer
   129  }
   130  
   131  // AddRecord implements ip.MulticastGroupProtocolV2ReportBuilder.
   132  func (b *mldv2ReportBuilder) AddRecord(genericRecordType ip.MulticastGroupProtocolV2ReportRecordType, groupAddress tcpip.Address) {
   133  	var recordType header.MLDv2ReportRecordType
   134  	switch genericRecordType {
   135  	case ip.MulticastGroupProtocolV2ReportRecordModeIsInclude:
   136  		recordType = header.MLDv2ReportRecordModeIsInclude
   137  	case ip.MulticastGroupProtocolV2ReportRecordModeIsExclude:
   138  		recordType = header.MLDv2ReportRecordModeIsExclude
   139  	case ip.MulticastGroupProtocolV2ReportRecordChangeToIncludeMode:
   140  		recordType = header.MLDv2ReportRecordChangeToIncludeMode
   141  	case ip.MulticastGroupProtocolV2ReportRecordChangeToExcludeMode:
   142  		recordType = header.MLDv2ReportRecordChangeToExcludeMode
   143  	case ip.MulticastGroupProtocolV2ReportRecordAllowNewSources:
   144  		recordType = header.MLDv2ReportRecordAllowNewSources
   145  	case ip.MulticastGroupProtocolV2ReportRecordBlockOldSources:
   146  		recordType = header.MLDv2ReportRecordBlockOldSources
   147  	default:
   148  		panic(fmt.Sprintf("unrecognied genericRecordType = %d", genericRecordType))
   149  	}
   150  
   151  	b.records = append(b.records, header.MLDv2ReportMulticastAddressRecordSerializer{
   152  		RecordType:       recordType,
   153  		MulticastAddress: groupAddress,
   154  		Sources:          nil,
   155  	})
   156  }
   157  
   158  // Send implements ip.MulticastGroupProtocolV2ReportBuilder.
   159  func (b *mldv2ReportBuilder) Send() (sent bool, err tcpip.Error) {
   160  	if len(b.records) == 0 {
   161  		return false, err
   162  	}
   163  
   164  	extensionHeaders := header.IPv6ExtHdrSerializer{
   165  		header.IPv6SerializableHopByHopExtHdr{
   166  			&header.IPv6RouterAlertOption{Value: header.IPv6RouterAlertMLD},
   167  		},
   168  	}
   169  	mtu := int(b.mld.ep.MTU()) - extensionHeaders.Length()
   170  
   171  	allSentWithSpecifiedAddress := true
   172  	var firstErr tcpip.Error
   173  	for records := b.records; len(records) != 0; {
   174  		spaceLeft := mtu
   175  		maxRecords := 0
   176  
   177  		for ; maxRecords < len(records); maxRecords++ {
   178  			tmp := spaceLeft - records[maxRecords].Length()
   179  			if tmp > 0 {
   180  				spaceLeft = tmp
   181  			} else {
   182  				break
   183  			}
   184  		}
   185  
   186  		serializer := header.MLDv2ReportSerializer{Records: records[:maxRecords]}
   187  		records = records[maxRecords:]
   188  
   189  		icmpView := buffer.NewViewSize(header.ICMPv6HeaderSize + serializer.Length())
   190  		icmp := header.ICMPv6(icmpView.AsSlice())
   191  		serializer.SerializeInto(icmp.MessageBody())
   192  		if sentWithSpecifiedAddress, err := b.mld.writePacketInner(
   193  			icmpView,
   194  			header.ICMPv6MulticastListenerV2Report,
   195  			b.mld.ep.stats.icmp.packetsSent.multicastListenerReportV2,
   196  			extensionHeaders,
   197  			header.MLDv2RoutersAddress,
   198  		); err != nil {
   199  			if firstErr != nil {
   200  				firstErr = nil
   201  			}
   202  			allSentWithSpecifiedAddress = false
   203  		} else if !sentWithSpecifiedAddress {
   204  			allSentWithSpecifiedAddress = false
   205  		}
   206  	}
   207  
   208  	return allSentWithSpecifiedAddress, firstErr
   209  }
   210  
   211  // NewReportV2Builder implements ip.MulticastGroupProtocol.
   212  func (mld *mldState) NewReportV2Builder() ip.MulticastGroupProtocolV2ReportBuilder {
   213  	return &mldv2ReportBuilder{mld: mld}
   214  }
   215  
   216  // V2QueryMaxRespCodeToV2Delay implements ip.MulticastGroupProtocol.
   217  func (*mldState) V2QueryMaxRespCodeToV2Delay(code uint16) time.Duration {
   218  	return header.MLDv2MaximumResponseDelay(code)
   219  }
   220  
   221  // V2QueryMaxRespCodeToV1Delay implements ip.MulticastGroupProtocol.
   222  func (*mldState) V2QueryMaxRespCodeToV1Delay(code uint16) time.Duration {
   223  	return time.Duration(code) * time.Millisecond
   224  }
   225  
   226  // init sets up an mldState struct, and is required to be called before using
   227  // a new mldState.
   228  //
   229  // Must only be called once for the lifetime of mld.
   230  func (mld *mldState) init(ep *endpoint) {
   231  	mld.ep = ep
   232  	mld.genericMulticastProtocol.Init(&ep.mu.RWMutex, ip.GenericMulticastProtocolOptions{
   233  		Rand:                      ep.protocol.stack.Rand(),
   234  		Clock:                     ep.protocol.stack.Clock(),
   235  		Protocol:                  mld,
   236  		MaxUnsolicitedReportDelay: UnsolicitedReportIntervalMax,
   237  	})
   238  }
   239  
   240  // handleMulticastListenerQuery handles a query message.
   241  //
   242  // Precondition: mld.ep.mu must be locked.
   243  func (mld *mldState) handleMulticastListenerQuery(mldHdr header.MLD) {
   244  	mld.genericMulticastProtocol.HandleQueryLocked(mldHdr.MulticastAddress(), mldHdr.MaximumResponseDelay())
   245  }
   246  
   247  // handleMulticastListenerQueryV2 handles a V2 query message.
   248  //
   249  // Precondition: mld.ep.mu must be locked.
   250  func (mld *mldState) handleMulticastListenerQueryV2(mldHdr header.MLDv2Query) {
   251  	sources, ok := mldHdr.Sources()
   252  	if !ok {
   253  		return
   254  	}
   255  
   256  	mld.genericMulticastProtocol.HandleQueryV2Locked(
   257  		mldHdr.MulticastAddress(),
   258  		mldHdr.MaximumResponseCode(),
   259  		sources,
   260  		mldHdr.QuerierRobustnessVariable(),
   261  		mldHdr.QuerierQueryInterval(),
   262  	)
   263  }
   264  
   265  // handleMulticastListenerReport handles a report message.
   266  //
   267  // Precondition: mld.ep.mu must be locked.
   268  func (mld *mldState) handleMulticastListenerReport(mldHdr header.MLD) {
   269  	mld.genericMulticastProtocol.HandleReportLocked(mldHdr.MulticastAddress())
   270  }
   271  
   272  // joinGroup handles joining a new group and sending and scheduling the required
   273  // messages.
   274  //
   275  // If the group is already joined, returns *tcpip.ErrDuplicateAddress.
   276  //
   277  // Precondition: mld.ep.mu must be locked.
   278  func (mld *mldState) joinGroup(groupAddress tcpip.Address) {
   279  	mld.genericMulticastProtocol.JoinGroupLocked(groupAddress)
   280  }
   281  
   282  // isInGroup returns true if the specified group has been joined locally.
   283  //
   284  // Precondition: mld.ep.mu must be read locked.
   285  func (mld *mldState) isInGroup(groupAddress tcpip.Address) bool {
   286  	return mld.genericMulticastProtocol.IsLocallyJoinedRLocked(groupAddress)
   287  }
   288  
   289  // leaveGroup handles removing the group from the membership map, cancels any
   290  // delay timers associated with that group, and sends the Done message, if
   291  // required.
   292  //
   293  // Precondition: mld.ep.mu must be locked.
   294  func (mld *mldState) leaveGroup(groupAddress tcpip.Address) tcpip.Error {
   295  	// LeaveGroup returns false only if the group was not joined.
   296  	if mld.genericMulticastProtocol.LeaveGroupLocked(groupAddress) {
   297  		return nil
   298  	}
   299  
   300  	return &tcpip.ErrBadLocalAddress{}
   301  }
   302  
   303  // softLeaveAll leaves all groups from the perspective of MLD, but remains
   304  // joined locally.
   305  //
   306  // Precondition: mld.ep.mu must be locked.
   307  func (mld *mldState) softLeaveAll() {
   308  	mld.genericMulticastProtocol.MakeAllNonMemberLocked()
   309  }
   310  
   311  // initializeAll attemps to initialize the MLD state for each group that has
   312  // been joined locally.
   313  //
   314  // Precondition: mld.ep.mu must be locked.
   315  func (mld *mldState) initializeAll() {
   316  	mld.genericMulticastProtocol.InitializeGroupsLocked()
   317  }
   318  
   319  // sendQueuedReports attempts to send any reports that are queued for sending.
   320  //
   321  // Precondition: mld.ep.mu must be locked.
   322  func (mld *mldState) sendQueuedReports() {
   323  	mld.genericMulticastProtocol.SendQueuedReportsLocked()
   324  }
   325  
   326  // setVersion sets the MLD version.
   327  //
   328  // Precondition: mld.ep.mu must be locked.
   329  func (mld *mldState) setVersion(v MLDVersion) MLDVersion {
   330  	var prev bool
   331  	switch v {
   332  	case MLDVersion2:
   333  		prev = mld.genericMulticastProtocol.SetV1ModeLocked(false)
   334  	case MLDVersion1:
   335  		prev = mld.genericMulticastProtocol.SetV1ModeLocked(true)
   336  	default:
   337  		panic(fmt.Sprintf("unrecognized version = %d", v))
   338  	}
   339  
   340  	return toMLDVersion(prev)
   341  }
   342  
   343  func toMLDVersion(v1Generic bool) MLDVersion {
   344  	if v1Generic {
   345  		return MLDVersion1
   346  	}
   347  	return MLDVersion2
   348  }
   349  
   350  // getVersion returns the MLD version.
   351  //
   352  // Precondition: mld.ep.mu must be read locked.
   353  func (mld *mldState) getVersion() MLDVersion {
   354  	return toMLDVersion(mld.genericMulticastProtocol.GetV1ModeLocked())
   355  }
   356  
   357  // writePacket assembles and sends an MLD packet.
   358  //
   359  // Precondition: mld.ep.mu must be read locked.
   360  func (mld *mldState) writePacket(destAddress, groupAddress tcpip.Address, mldType header.ICMPv6Type) (bool, tcpip.Error) {
   361  	sentStats := mld.ep.stats.icmp.packetsSent
   362  	var mldStat tcpip.MultiCounterStat
   363  	switch mldType {
   364  	case header.ICMPv6MulticastListenerReport:
   365  		mldStat = sentStats.multicastListenerReport
   366  	case header.ICMPv6MulticastListenerDone:
   367  		mldStat = sentStats.multicastListenerDone
   368  	default:
   369  		panic(fmt.Sprintf("unrecognized mld type = %d", mldType))
   370  	}
   371  
   372  	icmpView := buffer.NewViewSize(header.ICMPv6HeaderSize + header.MLDMinimumSize)
   373  
   374  	icmp := header.ICMPv6(icmpView.AsSlice())
   375  	header.MLD(icmp.MessageBody()).SetMulticastAddress(groupAddress)
   376  	extensionHeaders := header.IPv6ExtHdrSerializer{
   377  		header.IPv6SerializableHopByHopExtHdr{
   378  			&header.IPv6RouterAlertOption{Value: header.IPv6RouterAlertMLD},
   379  		},
   380  	}
   381  
   382  	return mld.writePacketInner(
   383  		icmpView,
   384  		mldType,
   385  		mldStat,
   386  		extensionHeaders,
   387  		destAddress,
   388  	)
   389  }
   390  
   391  func (mld *mldState) writePacketInner(buf *buffer.View, mldType header.ICMPv6Type, reportStat tcpip.MultiCounterStat, extensionHeaders header.IPv6ExtHdrSerializer, destAddress tcpip.Address) (bool, tcpip.Error) {
   392  	icmp := header.ICMPv6(buf.AsSlice())
   393  	icmp.SetType(mldType)
   394  
   395  	// As per RFC 2710 section 3,
   396  	//
   397  	//   All MLD messages described in this document are sent with a link-local
   398  	//   IPv6 Source Address, an IPv6 Hop Limit of 1, and an IPv6 Router Alert
   399  	//   option in a Hop-by-Hop Options header.
   400  	//
   401  	// However, this would cause problems with Duplicate Address Detection with
   402  	// the first address as MLD snooping switches may not send multicast traffic
   403  	// that DAD depends on to the node performing DAD without the MLD report, as
   404  	// documented in RFC 4816:
   405  	//
   406  	//   Note that when a node joins a multicast address, it typically sends a
   407  	//   Multicast Listener Discovery (MLD) report message [RFC2710] [RFC3810]
   408  	//   for the multicast address. In the case of Duplicate Address
   409  	//   Detection, the MLD report message is required in order to inform MLD-
   410  	//   snooping switches, rather than routers, to forward multicast packets.
   411  	//   In the above description, the delay for joining the multicast address
   412  	//   thus means delaying transmission of the corresponding MLD report
   413  	//   message. Since the MLD specifications do not request a random delay
   414  	//   to avoid race conditions, just delaying Neighbor Solicitation would
   415  	//   cause congestion by the MLD report messages. The congestion would
   416  	//   then prevent the MLD-snooping switches from working correctly and, as
   417  	//   a result, prevent Duplicate Address Detection from working. The
   418  	//   requirement to include the delay for the MLD report in this case
   419  	//   avoids this scenario. [RFC3590] also talks about some interaction
   420  	//   issues between Duplicate Address Detection and MLD, and specifies
   421  	//   which source address should be used for the MLD report in this case.
   422  	//
   423  	// As per RFC 3590 section 4, we should still send out MLD reports with an
   424  	// unspecified source address if we do not have an assigned link-local
   425  	// address to use as the source address to ensure DAD works as expected on
   426  	// networks with MLD snooping switches:
   427  	//
   428  	//   MLD Report and Done messages are sent with a link-local address as
   429  	//   the IPv6 source address, if a valid address is available on the
   430  	//   interface.  If a valid link-local address is not available (e.g., one
   431  	//   has not been configured), the message is sent with the unspecified
   432  	//   address (::) as the IPv6 source address.
   433  	//
   434  	//   Once a valid link-local address is available, a node SHOULD generate
   435  	//   new MLD Report messages for all multicast addresses joined on the
   436  	//   interface.
   437  	//
   438  	//   Routers receiving an MLD Report or Done message with the unspecified
   439  	//   address as the IPv6 source address MUST silently discard the packet
   440  	//   without taking any action on the packets contents.
   441  	//
   442  	//   Snooping switches MUST manage multicast forwarding state based on MLD
   443  	//   Report and Done messages sent with the unspecified address as the
   444  	//   IPv6 source address.
   445  	localAddress := mld.ep.getLinkLocalAddressRLocked()
   446  	if localAddress.BitLen() == 0 {
   447  		localAddress = header.IPv6Any
   448  	}
   449  
   450  	icmp.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
   451  		Header: icmp,
   452  		Src:    localAddress,
   453  		Dst:    destAddress,
   454  	}))
   455  
   456  	pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
   457  		ReserveHeaderBytes: int(mld.ep.MaxHeaderLength()) + extensionHeaders.Length(),
   458  		Payload:            buffer.MakeWithView(buf),
   459  	})
   460  	defer pkt.DecRef()
   461  
   462  	if err := addIPHeader(localAddress, destAddress, pkt, stack.NetworkHeaderParams{
   463  		Protocol: header.ICMPv6ProtocolNumber,
   464  		TTL:      header.MLDHopLimit,
   465  	}, extensionHeaders); err != nil {
   466  		panic(fmt.Sprintf("failed to add IP header: %s", err))
   467  	}
   468  	if err := mld.ep.nic.WritePacketToRemote(header.EthernetAddressFromMulticastIPv6Address(destAddress), pkt); err != nil {
   469  		mld.ep.stats.icmp.packetsSent.dropped.Increment()
   470  		return false, err
   471  	}
   472  	reportStat.Increment()
   473  	return localAddress != header.IPv6Any, nil
   474  }