snapshot: sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1
Содержимое пина, зафиксированного в go.mod sing-box-lx, одним коммитом без истории. Полная история SagerNet/gvisor — 1.45 ГБ и клонируется в каждой CI-джобе; наша дельта — одна вставка в одну функцию, история для неё не нужна. Module path github.com/sagernet/gvisor сохранён намеренно: на него опирается replace-директива суперпроекта. Патч поверх — отдельным коммитом, чтобы дельта читалась одним git show и переносилась на новый пин копированием. SPECS/TASKS/048-GVISOR_HANDSHAKE_NIL_CRASH
This commit is contained in:
commit
2c4ae3b0a4
712 changed files with 185689 additions and 0 deletions
654
pkg/tcpip/network/ipv4/igmp.go
Normal file
654
pkg/tcpip/network/ipv4/igmp.go
Normal file
|
|
@ -0,0 +1,654 @@
|
|||
// Copyright 2020 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package ipv4
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/buffer"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/header"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/network/internal/ip"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
const (
|
||||
// v1RouterPresentTimeout from RFC 2236 Section 8.11, Page 18
|
||||
// See note on igmpState.igmpV1Present for more detail.
|
||||
v1RouterPresentTimeout = 400 * time.Second
|
||||
|
||||
// v1MaxRespTime from RFC 2236 Section 4, Page 5. "The IGMPv1 router
|
||||
// will send General Queries with the Max Response Time set to 0. This MUST
|
||||
// be interpreted as a value of 100 (10 seconds)."
|
||||
//
|
||||
// Note that the Max Response Time field is a value in units of deciseconds.
|
||||
v1MaxRespTime = 10 * time.Second
|
||||
|
||||
// UnsolicitedReportIntervalMax is the maximum delay between sending
|
||||
// unsolicited IGMP reports.
|
||||
//
|
||||
// Obtained from RFC 2236 Section 8.10, Page 19.
|
||||
UnsolicitedReportIntervalMax = 10 * time.Second
|
||||
)
|
||||
|
||||
type protocolMode int
|
||||
|
||||
const (
|
||||
protocolModeV2OrV3 protocolMode = iota
|
||||
protocolModeV1
|
||||
// protocolModeV1Compatibility is for maintaining compatibility with IGMPv1
|
||||
// Routers.
|
||||
//
|
||||
// Per RFC 2236 Section 4 Page 6: "The IGMPv1 router expects Version 1
|
||||
// Membership Reports in response to its Queries, and will not pay
|
||||
// attention to Version 2 Membership Reports. Therefore, a state variable
|
||||
// MUST be kept for each interface, describing whether the multicast
|
||||
// Querier on that interface is running IGMPv1 or IGMPv2. This variable
|
||||
// MUST be based upon whether or not an IGMPv1 query was heard in the last
|
||||
// [Version 1 Router Present Timeout] seconds".
|
||||
protocolModeV1Compatibility
|
||||
)
|
||||
|
||||
// IGMPVersion is the forced version of IGMP.
|
||||
type IGMPVersion int
|
||||
|
||||
const (
|
||||
_ IGMPVersion = iota
|
||||
// IGMPVersion1 indicates IGMPv1.
|
||||
IGMPVersion1
|
||||
// IGMPVersion2 indicates IGMPv2. Note that IGMP may still fallback to V1
|
||||
// compatibility mode as required by IGMPv2.
|
||||
IGMPVersion2
|
||||
// IGMPVersion3 indicates IGMPv3. Note that IGMP may still fallback to V2
|
||||
// compatibility mode as required by IGMPv3.
|
||||
IGMPVersion3
|
||||
)
|
||||
|
||||
// IGMPEndpoint is a network endpoint that supports IGMP.
|
||||
type IGMPEndpoint interface {
|
||||
// SetIGMPVersion sets the IGMP version.
|
||||
//
|
||||
// Returns the previous IGMP version.
|
||||
SetIGMPVersion(IGMPVersion) IGMPVersion
|
||||
|
||||
// GetIGMPVersion returns the IGMP version.
|
||||
GetIGMPVersion() IGMPVersion
|
||||
}
|
||||
|
||||
// IGMPOptions holds options for IGMP.
|
||||
//
|
||||
// +stateify savable
|
||||
type IGMPOptions struct {
|
||||
// Enabled indicates whether IGMP will be performed.
|
||||
//
|
||||
// When enabled, IGMP may transmit IGMP report and leave messages when
|
||||
// joining and leaving multicast groups respectively, and handle incoming
|
||||
// IGMP packets.
|
||||
//
|
||||
// This field is ignored and is always assumed to be false for interfaces
|
||||
// without neighbouring nodes (e.g. loopback).
|
||||
Enabled bool
|
||||
}
|
||||
|
||||
var _ ip.MulticastGroupProtocol = (*igmpState)(nil)
|
||||
|
||||
// igmpState is the per-interface IGMP state.
|
||||
//
|
||||
// igmpState.init() MUST be called after creating an IGMP state.
|
||||
//
|
||||
// +stateify savable
|
||||
type igmpState struct {
|
||||
// The IPv4 endpoint this igmpState is for.
|
||||
ep *endpoint
|
||||
|
||||
genericMulticastProtocol ip.GenericMulticastProtocolState
|
||||
|
||||
// mode is used to configure the version of IGMP to perform.
|
||||
mode protocolMode
|
||||
|
||||
// igmpV1Job is scheduled when this interface receives an IGMPv1 style
|
||||
// message, upon expiration the igmpV1Present flag is cleared.
|
||||
// igmpV1Job may not be nil once igmpState is initialized.
|
||||
igmpV1Job *tcpip.Job
|
||||
}
|
||||
|
||||
// Enabled implements ip.MulticastGroupProtocol.
|
||||
func (igmp *igmpState) Enabled() bool {
|
||||
// No need to perform IGMP on loopback interfaces since they don't have
|
||||
// neighbouring nodes.
|
||||
return igmp.ep.protocol.options.IGMP.Enabled && !igmp.ep.nic.IsLoopback() && igmp.ep.Enabled()
|
||||
}
|
||||
|
||||
// SendReport implements ip.MulticastGroupProtocol.
|
||||
//
|
||||
// +checklocksread:igmp.ep.mu
|
||||
func (igmp *igmpState) SendReport(groupAddress tcpip.Address) (bool, tcpip.Error) {
|
||||
igmpType := header.IGMPv2MembershipReport
|
||||
switch igmp.mode {
|
||||
case protocolModeV2OrV3:
|
||||
case protocolModeV1, protocolModeV1Compatibility:
|
||||
igmpType = header.IGMPv1MembershipReport
|
||||
default:
|
||||
panic(fmt.Sprintf("unrecognized mode = %d", igmp.mode))
|
||||
}
|
||||
return igmp.writePacket(groupAddress, groupAddress, igmpType)
|
||||
}
|
||||
|
||||
// SendLeave implements ip.MulticastGroupProtocol.
|
||||
//
|
||||
// +checklocksread:igmp.ep.mu
|
||||
func (igmp *igmpState) SendLeave(groupAddress tcpip.Address) tcpip.Error {
|
||||
// As per RFC 2236 Section 6, Page 8: "If the interface state says the
|
||||
// Querier is running IGMPv1, this action SHOULD be skipped. If the flag
|
||||
// saying we were the last host to report is cleared, this action MAY be
|
||||
// skipped."
|
||||
switch igmp.mode {
|
||||
case protocolModeV2OrV3:
|
||||
_, err := igmp.writePacket(header.IPv4AllRoutersGroup, groupAddress, header.IGMPLeaveGroup)
|
||||
return err
|
||||
case protocolModeV1, protocolModeV1Compatibility:
|
||||
return nil
|
||||
default:
|
||||
panic(fmt.Sprintf("unrecognized mode = %d", igmp.mode))
|
||||
}
|
||||
}
|
||||
|
||||
// ShouldPerformProtocol implements ip.MulticastGroupProtocol.
|
||||
func (igmp *igmpState) ShouldPerformProtocol(groupAddress tcpip.Address) bool {
|
||||
// As per RFC 2236 section 6 page 10,
|
||||
//
|
||||
// The all-systems group (address 224.0.0.1) is handled as a special
|
||||
// case. The host starts in Idle Member state for that group on every
|
||||
// interface, never transitions to another state, and never sends a
|
||||
// report for that group.
|
||||
return groupAddress != header.IPv4AllSystems
|
||||
}
|
||||
|
||||
type igmpv3ReportBuilder struct {
|
||||
igmp *igmpState
|
||||
|
||||
records []header.IGMPv3ReportGroupAddressRecordSerializer
|
||||
}
|
||||
|
||||
// AddRecord implements ip.MulticastGroupProtocolV2ReportBuilder.
|
||||
func (b *igmpv3ReportBuilder) AddRecord(genericRecordType ip.MulticastGroupProtocolV2ReportRecordType, groupAddress tcpip.Address) {
|
||||
var recordType header.IGMPv3ReportRecordType
|
||||
switch genericRecordType {
|
||||
case ip.MulticastGroupProtocolV2ReportRecordModeIsInclude:
|
||||
recordType = header.IGMPv3ReportRecordModeIsInclude
|
||||
case ip.MulticastGroupProtocolV2ReportRecordModeIsExclude:
|
||||
recordType = header.IGMPv3ReportRecordModeIsExclude
|
||||
case ip.MulticastGroupProtocolV2ReportRecordChangeToIncludeMode:
|
||||
recordType = header.IGMPv3ReportRecordChangeToIncludeMode
|
||||
case ip.MulticastGroupProtocolV2ReportRecordChangeToExcludeMode:
|
||||
recordType = header.IGMPv3ReportRecordChangeToExcludeMode
|
||||
case ip.MulticastGroupProtocolV2ReportRecordAllowNewSources:
|
||||
recordType = header.IGMPv3ReportRecordAllowNewSources
|
||||
case ip.MulticastGroupProtocolV2ReportRecordBlockOldSources:
|
||||
recordType = header.IGMPv3ReportRecordBlockOldSources
|
||||
default:
|
||||
panic(fmt.Sprintf("unrecognied genericRecordType = %d", genericRecordType))
|
||||
}
|
||||
|
||||
b.records = append(b.records, header.IGMPv3ReportGroupAddressRecordSerializer{
|
||||
RecordType: recordType,
|
||||
GroupAddress: groupAddress,
|
||||
Sources: nil,
|
||||
})
|
||||
}
|
||||
|
||||
// Send implements ip.MulticastGroupProtocolV2ReportBuilder.
|
||||
//
|
||||
// +checklocksread:b.igmp.ep.mu
|
||||
func (b *igmpv3ReportBuilder) Send() (sent bool, err tcpip.Error) {
|
||||
if len(b.records) == 0 {
|
||||
return false, err
|
||||
}
|
||||
|
||||
options := header.IPv4OptionsSerializer{
|
||||
&header.IPv4SerializableRouterAlertOption{},
|
||||
}
|
||||
mtu := int(b.igmp.ep.MTU()) - int(options.Length())
|
||||
|
||||
allSentWithSpecifiedAddress := true
|
||||
var firstErr tcpip.Error
|
||||
for records := b.records; len(records) != 0; {
|
||||
spaceLeft := mtu
|
||||
maxRecords := 0
|
||||
|
||||
for ; maxRecords < len(records); maxRecords++ {
|
||||
tmp := spaceLeft - records[maxRecords].Length()
|
||||
if tmp > 0 {
|
||||
spaceLeft = tmp
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
serializer := header.IGMPv3ReportSerializer{Records: records[:maxRecords]}
|
||||
records = records[maxRecords:]
|
||||
|
||||
icmpView := buffer.NewViewSize(serializer.Length())
|
||||
serializer.SerializeInto(icmpView.AsSlice())
|
||||
if sentWithSpecifiedAddress, err := b.igmp.writePacketInner(
|
||||
icmpView,
|
||||
b.igmp.ep.stats.igmp.packetsSent.v3MembershipReport,
|
||||
options,
|
||||
header.IGMPv3RoutersAddress,
|
||||
); err != nil {
|
||||
if firstErr != nil {
|
||||
firstErr = nil
|
||||
}
|
||||
allSentWithSpecifiedAddress = false
|
||||
} else if !sentWithSpecifiedAddress {
|
||||
allSentWithSpecifiedAddress = false
|
||||
}
|
||||
}
|
||||
|
||||
return allSentWithSpecifiedAddress, firstErr
|
||||
}
|
||||
|
||||
// NewReportV2Builder implements ip.MulticastGroupProtocol.
|
||||
func (igmp *igmpState) NewReportV2Builder() ip.MulticastGroupProtocolV2ReportBuilder {
|
||||
return &igmpv3ReportBuilder{igmp: igmp}
|
||||
}
|
||||
|
||||
// V2QueryMaxRespCodeToV2Delay implements ip.MulticastGroupProtocol.
|
||||
func (*igmpState) V2QueryMaxRespCodeToV2Delay(code uint16) time.Duration {
|
||||
if code > math.MaxUint8 {
|
||||
panic(fmt.Sprintf("got IGMPv3 MaxRespCode = %d, want <= %d", code, math.MaxUint8))
|
||||
}
|
||||
return header.IGMPv3MaximumResponseDelay(uint8(code))
|
||||
}
|
||||
|
||||
// V2QueryMaxRespCodeToV1Delay implements ip.MulticastGroupProtocol.
|
||||
func (*igmpState) V2QueryMaxRespCodeToV1Delay(code uint16) time.Duration {
|
||||
return time.Duration(code) * time.Millisecond
|
||||
}
|
||||
|
||||
// init sets up an igmpState struct, and is required to be called before using
|
||||
// a new igmpState.
|
||||
//
|
||||
// Must only be called once for the lifetime of igmp.
|
||||
func (igmp *igmpState) init(ep *endpoint) {
|
||||
igmp.ep = ep
|
||||
igmp.genericMulticastProtocol.Init(&ep.mu, ip.GenericMulticastProtocolOptions{
|
||||
Rand: ep.protocol.stack.InsecureRNG(),
|
||||
Clock: ep.protocol.stack.Clock(),
|
||||
Protocol: igmp,
|
||||
MaxUnsolicitedReportDelay: UnsolicitedReportIntervalMax,
|
||||
})
|
||||
// As per RFC 2236 Page 9 says "No IGMPv1 Router Present ... is
|
||||
// the initial state.
|
||||
igmp.mode = protocolModeV2OrV3
|
||||
igmp.igmpV1Job = tcpip.NewJob(ep.protocol.stack.Clock(), &ep.mu, func() {
|
||||
igmp.mode = protocolModeV2OrV3
|
||||
})
|
||||
}
|
||||
|
||||
// +checklocks:igmp.ep.mu
|
||||
func (igmp *igmpState) isSourceIPValidLocked(src tcpip.Address, messageType header.IGMPType) bool {
|
||||
if messageType == header.IGMPMembershipQuery {
|
||||
// RFC 2236 does not require the IGMP implementation to check the source IP
|
||||
// for Membership Query messages.
|
||||
return true
|
||||
}
|
||||
|
||||
// As per RFC 2236 section 10,
|
||||
//
|
||||
// Ignore the Report if you cannot identify the source address of the
|
||||
// packet as belonging to a subnet assigned to the interface on which the
|
||||
// packet was received.
|
||||
//
|
||||
// Ignore the Leave message if you cannot identify the source address of
|
||||
// the packet as belonging to a subnet assigned to the interface on which
|
||||
// the packet was received.
|
||||
//
|
||||
// Note: this rule applies to both V1 and V2 Membership Reports.
|
||||
var isSourceIPValid bool
|
||||
igmp.ep.addressableEndpointState.ForEachPrimaryEndpoint(func(addressEndpoint stack.AddressEndpoint) bool {
|
||||
if subnet := addressEndpoint.Subnet(); subnet.Contains(src) {
|
||||
isSourceIPValid = true
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return isSourceIPValid
|
||||
}
|
||||
|
||||
// +checklocks:igmp.ep.mu
|
||||
func (igmp *igmpState) isPacketValidLocked(pkt *stack.PacketBuffer, messageType header.IGMPType, hasRouterAlertOption bool) bool {
|
||||
// We can safely assume that the IP header is valid if we got this far.
|
||||
iph := header.IPv4(pkt.NetworkHeader().Slice())
|
||||
|
||||
// As per RFC 2236 section 2,
|
||||
//
|
||||
// All IGMP messages described in this document are sent with IP TTL 1, and
|
||||
// contain the IP Router Alert option [RFC 2113] in their IP header.
|
||||
if !hasRouterAlertOption || iph.TTL() != header.IGMPTTL {
|
||||
return false
|
||||
}
|
||||
|
||||
return igmp.isSourceIPValidLocked(iph.SourceAddress(), messageType)
|
||||
}
|
||||
|
||||
// handleIGMP handles an IGMP packet.
|
||||
//
|
||||
// +checklocks:igmp.ep.mu
|
||||
func (igmp *igmpState) handleIGMP(pkt *stack.PacketBuffer, hasRouterAlertOption bool) {
|
||||
received := igmp.ep.stats.igmp.packetsReceived
|
||||
hdr, ok := pkt.Data().PullUp(pkt.Data().Size())
|
||||
if !ok {
|
||||
received.invalid.Increment()
|
||||
return
|
||||
}
|
||||
h := header.IGMP(hdr)
|
||||
if len(h) < header.IGMPMinimumSize {
|
||||
received.invalid.Increment()
|
||||
return
|
||||
}
|
||||
|
||||
// As per RFC 1071 section 1.3,
|
||||
//
|
||||
// To check a checksum, the 1's complement sum is computed over the
|
||||
// same set of octets, including the checksum field. If the result
|
||||
// is all 1 bits (-0 in 1's complement arithmetic), the check
|
||||
// succeeds.
|
||||
if pkt.Data().Checksum() != 0xFFFF {
|
||||
received.checksumErrors.Increment()
|
||||
return
|
||||
}
|
||||
|
||||
isValid := func(minimumSize int) bool {
|
||||
return len(hdr) >= minimumSize && igmp.isPacketValidLocked(pkt, h.Type(), hasRouterAlertOption)
|
||||
}
|
||||
|
||||
switch h.Type() {
|
||||
case header.IGMPMembershipQuery:
|
||||
received.membershipQuery.Increment()
|
||||
if len(h) >= header.IGMPv3QueryMinimumSize {
|
||||
if isValid(header.IGMPv3QueryMinimumSize) {
|
||||
igmp.handleMembershipQueryV3(header.IGMPv3Query(h))
|
||||
} else {
|
||||
received.invalid.Increment()
|
||||
}
|
||||
return
|
||||
} else if !isValid(header.IGMPQueryMinimumSize) {
|
||||
received.invalid.Increment()
|
||||
return
|
||||
}
|
||||
igmp.handleMembershipQuery(h.GroupAddress(), h.MaxRespTime())
|
||||
case header.IGMPv1MembershipReport:
|
||||
received.v1MembershipReport.Increment()
|
||||
if !isValid(header.IGMPReportMinimumSize) {
|
||||
received.invalid.Increment()
|
||||
return
|
||||
}
|
||||
igmp.handleMembershipReport(h.GroupAddress())
|
||||
case header.IGMPv2MembershipReport:
|
||||
received.v2MembershipReport.Increment()
|
||||
if !isValid(header.IGMPReportMinimumSize) {
|
||||
received.invalid.Increment()
|
||||
return
|
||||
}
|
||||
igmp.handleMembershipReport(h.GroupAddress())
|
||||
case header.IGMPLeaveGroup:
|
||||
received.leaveGroup.Increment()
|
||||
if !isValid(header.IGMPLeaveMessageMinimumSize) {
|
||||
received.invalid.Increment()
|
||||
return
|
||||
}
|
||||
// As per RFC 2236 Section 6, Page 7: "IGMP messages other than Query or
|
||||
// Report, are ignored in all states"
|
||||
|
||||
default:
|
||||
// As per RFC 2236 Section 2.1 Page 3: "Unrecognized message types should
|
||||
// be silently ignored. New message types may be used by newer versions of
|
||||
// IGMP, by multicast routing protocols, or other uses."
|
||||
received.unrecognized.Increment()
|
||||
}
|
||||
}
|
||||
|
||||
func (igmp *igmpState) resetV1Present() {
|
||||
igmp.igmpV1Job.Cancel()
|
||||
switch igmp.mode {
|
||||
case protocolModeV2OrV3, protocolModeV1:
|
||||
case protocolModeV1Compatibility:
|
||||
igmp.mode = protocolModeV2OrV3
|
||||
default:
|
||||
panic(fmt.Sprintf("unrecognized mode = %d", igmp.mode))
|
||||
}
|
||||
}
|
||||
|
||||
// handleMembershipQuery handles a membership query.
|
||||
//
|
||||
// +checklocks:igmp.ep.mu
|
||||
func (igmp *igmpState) handleMembershipQuery(groupAddress tcpip.Address, maxRespTime time.Duration) {
|
||||
// As per RFC 2236 Section 6, Page 10: If the maximum response time is zero
|
||||
// then change the state to note that an IGMPv1 router is present and
|
||||
// schedule the query received Job.
|
||||
if maxRespTime == 0 && igmp.Enabled() {
|
||||
switch igmp.mode {
|
||||
case protocolModeV2OrV3, protocolModeV1Compatibility:
|
||||
igmp.igmpV1Job.Cancel()
|
||||
igmp.igmpV1Job.Schedule(v1RouterPresentTimeout)
|
||||
igmp.mode = protocolModeV1Compatibility
|
||||
case protocolModeV1:
|
||||
default:
|
||||
panic(fmt.Sprintf("unrecognized mode = %d", igmp.mode))
|
||||
}
|
||||
|
||||
maxRespTime = v1MaxRespTime
|
||||
}
|
||||
|
||||
igmp.genericMulticastProtocol.HandleQueryLocked(groupAddress, maxRespTime)
|
||||
}
|
||||
|
||||
// handleMembershipQueryV3 handles a membership query.
|
||||
//
|
||||
// +checklocks:igmp.ep.mu
|
||||
func (igmp *igmpState) handleMembershipQueryV3(igmpHdr header.IGMPv3Query) {
|
||||
sources, ok := igmpHdr.Sources()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
igmp.genericMulticastProtocol.HandleQueryV2Locked(
|
||||
igmpHdr.GroupAddress(),
|
||||
uint16(igmpHdr.MaximumResponseCode()),
|
||||
sources,
|
||||
igmpHdr.QuerierRobustnessVariable(),
|
||||
igmpHdr.QuerierQueryInterval(),
|
||||
)
|
||||
}
|
||||
|
||||
// handleMembershipReport handles a membership report.
|
||||
//
|
||||
// +checklocks:igmp.ep.mu
|
||||
func (igmp *igmpState) handleMembershipReport(groupAddress tcpip.Address) {
|
||||
igmp.genericMulticastProtocol.HandleReportLocked(groupAddress)
|
||||
}
|
||||
|
||||
// writePacket assembles and sends an IGMP packet.
|
||||
//
|
||||
// +checklocksread:igmp.ep.mu
|
||||
func (igmp *igmpState) writePacket(destAddress tcpip.Address, groupAddress tcpip.Address, igmpType header.IGMPType) (bool, tcpip.Error) {
|
||||
igmpView := buffer.NewViewSize(header.IGMPReportMinimumSize)
|
||||
igmpData := header.IGMP(igmpView.AsSlice())
|
||||
igmpData.SetType(igmpType)
|
||||
igmpData.SetGroupAddress(groupAddress)
|
||||
igmpData.SetChecksum(header.IGMPCalculateChecksum(igmpData))
|
||||
|
||||
var reportType tcpip.MultiCounterStat
|
||||
sentStats := igmp.ep.stats.igmp.packetsSent
|
||||
switch igmpType {
|
||||
case header.IGMPv1MembershipReport:
|
||||
reportType = sentStats.v1MembershipReport
|
||||
case header.IGMPv2MembershipReport:
|
||||
reportType = sentStats.v2MembershipReport
|
||||
case header.IGMPLeaveGroup:
|
||||
reportType = sentStats.leaveGroup
|
||||
default:
|
||||
panic(fmt.Sprintf("unrecognized igmp type = %d", igmpType))
|
||||
}
|
||||
|
||||
return igmp.writePacketInner(
|
||||
igmpView,
|
||||
reportType,
|
||||
header.IPv4OptionsSerializer{
|
||||
&header.IPv4SerializableRouterAlertOption{},
|
||||
},
|
||||
destAddress,
|
||||
)
|
||||
}
|
||||
|
||||
// +checklocksread:igmp.ep.mu
|
||||
func (igmp *igmpState) writePacketInner(buf *buffer.View, reportStat tcpip.MultiCounterStat, options header.IPv4OptionsSerializer, destAddress tcpip.Address) (bool, tcpip.Error) {
|
||||
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||
ReserveHeaderBytes: int(igmp.ep.MaxHeaderLength()),
|
||||
Payload: buffer.MakeWithView(buf),
|
||||
})
|
||||
defer pkt.DecRef()
|
||||
|
||||
addressEndpoint := igmp.ep.acquireOutgoingPrimaryAddressRLocked(destAddress, tcpip.Address{} /* srcHint */, false /* allowExpired */)
|
||||
if addressEndpoint == nil {
|
||||
return false, nil
|
||||
}
|
||||
localAddr := addressEndpoint.AddressWithPrefix().Address
|
||||
addressEndpoint.DecRef()
|
||||
addressEndpoint = nil
|
||||
if err := igmp.ep.addIPHeader(localAddr, destAddress, pkt, stack.NetworkHeaderParams{
|
||||
Protocol: header.IGMPProtocolNumber,
|
||||
TTL: header.IGMPTTL,
|
||||
TOS: stack.DefaultTOS,
|
||||
}, options); err != nil {
|
||||
panic(fmt.Sprintf("failed to add IP header: %s", err))
|
||||
}
|
||||
|
||||
sentStats := igmp.ep.stats.igmp.packetsSent
|
||||
if err := igmp.ep.nic.WritePacketToRemote(header.EthernetAddressFromMulticastIPv4Address(destAddress), pkt); err != nil {
|
||||
sentStats.dropped.Increment()
|
||||
return false, err
|
||||
}
|
||||
reportStat.Increment()
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// joinGroup handles adding a new group to the membership map, setting up the
|
||||
// IGMP state for the group, and sending and scheduling the required
|
||||
// messages.
|
||||
//
|
||||
// If the group already exists in the membership map, returns
|
||||
// *tcpip.ErrDuplicateAddress.
|
||||
//
|
||||
// +checklocks:igmp.ep.mu
|
||||
func (igmp *igmpState) joinGroup(groupAddress tcpip.Address) {
|
||||
igmp.genericMulticastProtocol.JoinGroupLocked(groupAddress)
|
||||
}
|
||||
|
||||
// isInGroup returns true if the specified group has been joined locally.
|
||||
//
|
||||
// +checklocksread:igmp.ep.mu
|
||||
func (igmp *igmpState) isInGroup(groupAddress tcpip.Address) bool {
|
||||
return igmp.genericMulticastProtocol.IsLocallyJoinedRLocked(groupAddress)
|
||||
}
|
||||
|
||||
// leaveGroup handles removing the group from the membership map, cancels any
|
||||
// delay timers associated with that group, and sends the Leave Group message
|
||||
// if required.
|
||||
//
|
||||
// +checklocks:igmp.ep.mu
|
||||
func (igmp *igmpState) leaveGroup(groupAddress tcpip.Address) tcpip.Error {
|
||||
// LeaveGroup returns false only if the group was not joined.
|
||||
if igmp.genericMulticastProtocol.LeaveGroupLocked(groupAddress) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &tcpip.ErrBadLocalAddress{}
|
||||
}
|
||||
|
||||
// softLeaveAll leaves all groups from the perspective of IGMP, but remains
|
||||
// joined locally.
|
||||
//
|
||||
// +checklocks:igmp.ep.mu
|
||||
func (igmp *igmpState) softLeaveAll() {
|
||||
igmp.genericMulticastProtocol.MakeAllNonMemberLocked()
|
||||
}
|
||||
|
||||
// initializeAll attempts to initialize the IGMP state for each group that has
|
||||
// been joined locally.
|
||||
//
|
||||
// +checklocks:igmp.ep.mu
|
||||
func (igmp *igmpState) initializeAll() {
|
||||
igmp.genericMulticastProtocol.InitializeGroupsLocked()
|
||||
}
|
||||
|
||||
// sendQueuedReports attempts to send any reports that are queued for sending.
|
||||
//
|
||||
// +checklocks:igmp.ep.mu
|
||||
func (igmp *igmpState) sendQueuedReports() {
|
||||
igmp.genericMulticastProtocol.SendQueuedReportsLocked()
|
||||
}
|
||||
|
||||
// setVersion sets the IGMP version.
|
||||
//
|
||||
// +checklocks:igmp.ep.mu
|
||||
func (igmp *igmpState) setVersion(v IGMPVersion) IGMPVersion {
|
||||
prev := igmp.mode
|
||||
igmp.igmpV1Job.Cancel()
|
||||
|
||||
var prevGenericModeV1 bool
|
||||
switch v {
|
||||
case IGMPVersion3:
|
||||
prevGenericModeV1 = igmp.genericMulticastProtocol.SetV1ModeLocked(false)
|
||||
igmp.mode = protocolModeV2OrV3
|
||||
case IGMPVersion2:
|
||||
// IGMPv1 and IGMPv2 map to V1 of the generic multicast protocol.
|
||||
prevGenericModeV1 = igmp.genericMulticastProtocol.SetV1ModeLocked(true)
|
||||
igmp.mode = protocolModeV2OrV3
|
||||
case IGMPVersion1:
|
||||
// IGMPv1 and IGMPv2 map to V1 of the generic multicast protocol.
|
||||
prevGenericModeV1 = igmp.genericMulticastProtocol.SetV1ModeLocked(true)
|
||||
igmp.mode = protocolModeV1
|
||||
default:
|
||||
panic(fmt.Sprintf("unrecognized version = %d", v))
|
||||
}
|
||||
|
||||
return toIGMPVersion(prev, prevGenericModeV1)
|
||||
}
|
||||
|
||||
func toIGMPVersion(mode protocolMode, genericV1 bool) IGMPVersion {
|
||||
switch mode {
|
||||
case protocolModeV2OrV3, protocolModeV1Compatibility:
|
||||
if genericV1 {
|
||||
return IGMPVersion2
|
||||
}
|
||||
return IGMPVersion3
|
||||
case protocolModeV1:
|
||||
return IGMPVersion1
|
||||
default:
|
||||
panic(fmt.Sprintf("unrecognized mode = %d", mode))
|
||||
}
|
||||
}
|
||||
|
||||
// getVersion returns the IGMP version.
|
||||
//
|
||||
// +checklocksread:igmp.ep.mu
|
||||
func (igmp *igmpState) getVersion() IGMPVersion {
|
||||
return toIGMPVersion(igmp.mode, igmp.genericMulticastProtocol.GetV1ModeLocked())
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue