github.com/sagernet/gvisor@v0.0.0-20240428053021-e691de28565f/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/buffer" 22 "github.com/sagernet/gvisor/pkg/tcpip" 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 // 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.InsecureRNG(), 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 attempts 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 }