github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/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/SagerNet/gvisor/pkg/tcpip"
    22  	"github.com/SagerNet/gvisor/pkg/tcpip/buffer"
    23  	"github.com/SagerNet/gvisor/pkg/tcpip/header"
    24  	"github.com/SagerNet/gvisor/pkg/tcpip/network/internal/ip"
    25  	"github.com/SagerNet/gvisor/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  // MLDOptions holds options for MLD.
    37  type MLDOptions struct {
    38  	// Enabled indicates whether MLD will be performed.
    39  	//
    40  	// When enabled, MLD may transmit MLD report and done messages when
    41  	// joining and leaving multicast groups respectively, and handle incoming
    42  	// MLD packets.
    43  	//
    44  	// This field is ignored and is always assumed to be false for interfaces
    45  	// without neighbouring nodes (e.g. loopback).
    46  	Enabled bool
    47  }
    48  
    49  var _ ip.MulticastGroupProtocol = (*mldState)(nil)
    50  
    51  // mldState is the per-interface MLD state.
    52  //
    53  // mldState.init MUST be called to initialize the MLD state.
    54  type mldState struct {
    55  	// The IPv6 endpoint this mldState is for.
    56  	ep *endpoint
    57  
    58  	genericMulticastProtocol ip.GenericMulticastProtocolState
    59  }
    60  
    61  // Enabled implements ip.MulticastGroupProtocol.
    62  func (mld *mldState) Enabled() bool {
    63  	// No need to perform MLD on loopback interfaces since they don't have
    64  	// neighbouring nodes.
    65  	return mld.ep.protocol.options.MLD.Enabled && !mld.ep.nic.IsLoopback() && mld.ep.Enabled()
    66  }
    67  
    68  // SendReport implements ip.MulticastGroupProtocol.
    69  //
    70  // Precondition: mld.ep.mu must be read locked.
    71  func (mld *mldState) SendReport(groupAddress tcpip.Address) (bool, tcpip.Error) {
    72  	return mld.writePacket(groupAddress, groupAddress, header.ICMPv6MulticastListenerReport)
    73  }
    74  
    75  // SendLeave implements ip.MulticastGroupProtocol.
    76  //
    77  // Precondition: mld.ep.mu must be read locked.
    78  func (mld *mldState) SendLeave(groupAddress tcpip.Address) tcpip.Error {
    79  	_, err := mld.writePacket(header.IPv6AllRoutersLinkLocalMulticastAddress, groupAddress, header.ICMPv6MulticastListenerDone)
    80  	return err
    81  }
    82  
    83  // ShouldPerformProtocol implements ip.MulticastGroupProtocol.
    84  func (mld *mldState) ShouldPerformProtocol(groupAddress tcpip.Address) bool {
    85  	// As per RFC 2710 section 5 page 10,
    86  	//
    87  	//   The link-scope all-nodes address (FF02::1) is handled as a special
    88  	//   case. The node starts in Idle Listener state for that address on
    89  	//   every interface, never transitions to another state, and never sends
    90  	//   a Report or Done for that address.
    91  	//
    92  	//   MLD messages are never sent for multicast addresses whose scope is 0
    93  	//   (reserved) or 1 (node-local).
    94  	if groupAddress == header.IPv6AllNodesMulticastAddress {
    95  		return false
    96  	}
    97  
    98  	scope := header.V6MulticastScope(groupAddress)
    99  	return scope != header.IPv6Reserved0MulticastScope && scope != header.IPv6InterfaceLocalMulticastScope
   100  }
   101  
   102  // init sets up an mldState struct, and is required to be called before using
   103  // a new mldState.
   104  //
   105  // Must only be called once for the lifetime of mld.
   106  func (mld *mldState) init(ep *endpoint) {
   107  	mld.ep = ep
   108  	mld.genericMulticastProtocol.Init(&ep.mu.RWMutex, ip.GenericMulticastProtocolOptions{
   109  		Rand:                      ep.protocol.stack.Rand(),
   110  		Clock:                     ep.protocol.stack.Clock(),
   111  		Protocol:                  mld,
   112  		MaxUnsolicitedReportDelay: UnsolicitedReportIntervalMax,
   113  	})
   114  }
   115  
   116  // handleMulticastListenerQuery handles a query message.
   117  //
   118  // Precondition: mld.ep.mu must be locked.
   119  func (mld *mldState) handleMulticastListenerQuery(mldHdr header.MLD) {
   120  	mld.genericMulticastProtocol.HandleQueryLocked(mldHdr.MulticastAddress(), mldHdr.MaximumResponseDelay())
   121  }
   122  
   123  // handleMulticastListenerReport handles a report message.
   124  //
   125  // Precondition: mld.ep.mu must be locked.
   126  func (mld *mldState) handleMulticastListenerReport(mldHdr header.MLD) {
   127  	mld.genericMulticastProtocol.HandleReportLocked(mldHdr.MulticastAddress())
   128  }
   129  
   130  // joinGroup handles joining a new group and sending and scheduling the required
   131  // messages.
   132  //
   133  // If the group is already joined, returns *tcpip.ErrDuplicateAddress.
   134  //
   135  // Precondition: mld.ep.mu must be locked.
   136  func (mld *mldState) joinGroup(groupAddress tcpip.Address) {
   137  	mld.genericMulticastProtocol.JoinGroupLocked(groupAddress)
   138  }
   139  
   140  // isInGroup returns true if the specified group has been joined locally.
   141  //
   142  // Precondition: mld.ep.mu must be read locked.
   143  func (mld *mldState) isInGroup(groupAddress tcpip.Address) bool {
   144  	return mld.genericMulticastProtocol.IsLocallyJoinedRLocked(groupAddress)
   145  }
   146  
   147  // leaveGroup handles removing the group from the membership map, cancels any
   148  // delay timers associated with that group, and sends the Done message, if
   149  // required.
   150  //
   151  // Precondition: mld.ep.mu must be locked.
   152  func (mld *mldState) leaveGroup(groupAddress tcpip.Address) tcpip.Error {
   153  	// LeaveGroup returns false only if the group was not joined.
   154  	if mld.genericMulticastProtocol.LeaveGroupLocked(groupAddress) {
   155  		return nil
   156  	}
   157  
   158  	return &tcpip.ErrBadLocalAddress{}
   159  }
   160  
   161  // softLeaveAll leaves all groups from the perspective of MLD, but remains
   162  // joined locally.
   163  //
   164  // Precondition: mld.ep.mu must be locked.
   165  func (mld *mldState) softLeaveAll() {
   166  	mld.genericMulticastProtocol.MakeAllNonMemberLocked()
   167  }
   168  
   169  // initializeAll attemps to initialize the MLD state for each group that has
   170  // been joined locally.
   171  //
   172  // Precondition: mld.ep.mu must be locked.
   173  func (mld *mldState) initializeAll() {
   174  	mld.genericMulticastProtocol.InitializeGroupsLocked()
   175  }
   176  
   177  // sendQueuedReports attempts to send any reports that are queued for sending.
   178  //
   179  // Precondition: mld.ep.mu must be locked.
   180  func (mld *mldState) sendQueuedReports() {
   181  	mld.genericMulticastProtocol.SendQueuedReportsLocked()
   182  }
   183  
   184  // writePacket assembles and sends an MLD packet.
   185  //
   186  // Precondition: mld.ep.mu must be read locked.
   187  func (mld *mldState) writePacket(destAddress, groupAddress tcpip.Address, mldType header.ICMPv6Type) (bool, tcpip.Error) {
   188  	sentStats := mld.ep.stats.icmp.packetsSent
   189  	var mldStat tcpip.MultiCounterStat
   190  	switch mldType {
   191  	case header.ICMPv6MulticastListenerReport:
   192  		mldStat = sentStats.multicastListenerReport
   193  	case header.ICMPv6MulticastListenerDone:
   194  		mldStat = sentStats.multicastListenerDone
   195  	default:
   196  		panic(fmt.Sprintf("unrecognized mld type = %d", mldType))
   197  	}
   198  
   199  	icmp := header.ICMPv6(buffer.NewView(header.ICMPv6HeaderSize + header.MLDMinimumSize))
   200  	icmp.SetType(mldType)
   201  	header.MLD(icmp.MessageBody()).SetMulticastAddress(groupAddress)
   202  	// As per RFC 2710 section 3,
   203  	//
   204  	//   All MLD messages described in this document are sent with a link-local
   205  	//   IPv6 Source Address, an IPv6 Hop Limit of 1, and an IPv6 Router Alert
   206  	//   option in a Hop-by-Hop Options header.
   207  	//
   208  	// However, this would cause problems with Duplicate Address Detection with
   209  	// the first address as MLD snooping switches may not send multicast traffic
   210  	// that DAD depends on to the node performing DAD without the MLD report, as
   211  	// documented in RFC 4816:
   212  	//
   213  	//   Note that when a node joins a multicast address, it typically sends a
   214  	//   Multicast Listener Discovery (MLD) report message [RFC2710] [RFC3810]
   215  	//   for the multicast address. In the case of Duplicate Address
   216  	//   Detection, the MLD report message is required in order to inform MLD-
   217  	//   snooping switches, rather than routers, to forward multicast packets.
   218  	//   In the above description, the delay for joining the multicast address
   219  	//   thus means delaying transmission of the corresponding MLD report
   220  	//   message. Since the MLD specifications do not request a random delay
   221  	//   to avoid race conditions, just delaying Neighbor Solicitation would
   222  	//   cause congestion by the MLD report messages. The congestion would
   223  	//   then prevent the MLD-snooping switches from working correctly and, as
   224  	//   a result, prevent Duplicate Address Detection from working. The
   225  	//   requirement to include the delay for the MLD report in this case
   226  	//   avoids this scenario. [RFC3590] also talks about some interaction
   227  	//   issues between Duplicate Address Detection and MLD, and specifies
   228  	//   which source address should be used for the MLD report in this case.
   229  	//
   230  	// As per RFC 3590 section 4, we should still send out MLD reports with an
   231  	// unspecified source address if we do not have an assigned link-local
   232  	// address to use as the source address to ensure DAD works as expected on
   233  	// networks with MLD snooping switches:
   234  	//
   235  	//   MLD Report and Done messages are sent with a link-local address as
   236  	//   the IPv6 source address, if a valid address is available on the
   237  	//   interface.  If a valid link-local address is not available (e.g., one
   238  	//   has not been configured), the message is sent with the unspecified
   239  	//   address (::) as the IPv6 source address.
   240  	//
   241  	//   Once a valid link-local address is available, a node SHOULD generate
   242  	//   new MLD Report messages for all multicast addresses joined on the
   243  	//   interface.
   244  	//
   245  	//   Routers receiving an MLD Report or Done message with the unspecified
   246  	//   address as the IPv6 source address MUST silently discard the packet
   247  	//   without taking any action on the packets contents.
   248  	//
   249  	//   Snooping switches MUST manage multicast forwarding state based on MLD
   250  	//   Report and Done messages sent with the unspecified address as the
   251  	//   IPv6 source address.
   252  	localAddress := mld.ep.getLinkLocalAddressRLocked()
   253  	if len(localAddress) == 0 {
   254  		localAddress = header.IPv6Any
   255  	}
   256  
   257  	icmp.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
   258  		Header: icmp,
   259  		Src:    localAddress,
   260  		Dst:    destAddress,
   261  	}))
   262  
   263  	extensionHeaders := header.IPv6ExtHdrSerializer{
   264  		header.IPv6SerializableHopByHopExtHdr{
   265  			&header.IPv6RouterAlertOption{Value: header.IPv6RouterAlertMLD},
   266  		},
   267  	}
   268  
   269  	pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
   270  		ReserveHeaderBytes: int(mld.ep.MaxHeaderLength()) + extensionHeaders.Length(),
   271  		Data:               buffer.View(icmp).ToVectorisedView(),
   272  	})
   273  
   274  	if err := addIPHeader(localAddress, destAddress, pkt, stack.NetworkHeaderParams{
   275  		Protocol: header.ICMPv6ProtocolNumber,
   276  		TTL:      header.MLDHopLimit,
   277  	}, extensionHeaders); err != nil {
   278  		panic(fmt.Sprintf("failed to add IP header: %s", err))
   279  	}
   280  	if err := mld.ep.nic.WritePacketToRemote(header.EthernetAddressFromMulticastIPv6Address(destAddress), ProtocolNumber, pkt); err != nil {
   281  		sentStats.dropped.Increment()
   282  		return false, err
   283  	}
   284  	mldStat.Increment()
   285  	return localAddress != header.IPv6Any, nil
   286  }