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 }