github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/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 "sync/atomic" 20 "time" 21 22 "github.com/SagerNet/gvisor/pkg/tcpip" 23 "github.com/SagerNet/gvisor/pkg/tcpip/buffer" 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 // igmpV1PresentDefault is the initial state for igmpV1Present in the 31 // igmpState. As per RFC 2236 Page 9 says "No IGMPv1 Router Present ... is 32 // the initial state." 33 igmpV1PresentDefault = 0 34 35 // v1RouterPresentTimeout from RFC 2236 Section 8.11, Page 18 36 // See note on igmpState.igmpV1Present for more detail. 37 v1RouterPresentTimeout = 400 * time.Second 38 39 // v1MaxRespTime from RFC 2236 Section 4, Page 5. "The IGMPv1 router 40 // will send General Queries with the Max Response Time set to 0. This MUST 41 // be interpreted as a value of 100 (10 seconds)." 42 // 43 // Note that the Max Response Time field is a value in units of deciseconds. 44 v1MaxRespTime = 10 * time.Second 45 46 // UnsolicitedReportIntervalMax is the maximum delay between sending 47 // unsolicited IGMP reports. 48 // 49 // Obtained from RFC 2236 Section 8.10, Page 19. 50 UnsolicitedReportIntervalMax = 10 * time.Second 51 ) 52 53 // IGMPOptions holds options for IGMP. 54 type IGMPOptions struct { 55 // Enabled indicates whether IGMP will be performed. 56 // 57 // When enabled, IGMP may transmit IGMP report and leave messages when 58 // joining and leaving multicast groups respectively, and handle incoming 59 // IGMP packets. 60 // 61 // This field is ignored and is always assumed to be false for interfaces 62 // without neighbouring nodes (e.g. loopback). 63 Enabled bool 64 } 65 66 var _ ip.MulticastGroupProtocol = (*igmpState)(nil) 67 68 // igmpState is the per-interface IGMP state. 69 // 70 // igmpState.init() MUST be called after creating an IGMP state. 71 type igmpState struct { 72 // The IPv4 endpoint this igmpState is for. 73 ep *endpoint 74 75 genericMulticastProtocol ip.GenericMulticastProtocolState 76 77 // igmpV1Present is for maintaining compatibility with IGMPv1 Routers, from 78 // RFC 2236 Section 4 Page 6: "The IGMPv1 router expects Version 1 79 // Membership Reports in response to its Queries, and will not pay 80 // attention to Version 2 Membership Reports. Therefore, a state variable 81 // MUST be kept for each interface, describing whether the multicast 82 // Querier on that interface is running IGMPv1 or IGMPv2. This variable 83 // MUST be based upon whether or not an IGMPv1 query was heard in the last 84 // [Version 1 Router Present Timeout] seconds". 85 // 86 // Must be accessed with atomic operations. Holds a value of 1 when true, 0 87 // when false. 88 igmpV1Present uint32 89 90 // igmpV1Job is scheduled when this interface receives an IGMPv1 style 91 // message, upon expiration the igmpV1Present flag is cleared. 92 // igmpV1Job may not be nil once igmpState is initialized. 93 igmpV1Job *tcpip.Job 94 } 95 96 // Enabled implements ip.MulticastGroupProtocol. 97 func (igmp *igmpState) Enabled() bool { 98 // No need to perform IGMP on loopback interfaces since they don't have 99 // neighbouring nodes. 100 return igmp.ep.protocol.options.IGMP.Enabled && !igmp.ep.nic.IsLoopback() && igmp.ep.Enabled() 101 } 102 103 // SendReport implements ip.MulticastGroupProtocol. 104 // 105 // Precondition: igmp.ep.mu must be read locked. 106 func (igmp *igmpState) SendReport(groupAddress tcpip.Address) (bool, tcpip.Error) { 107 igmpType := header.IGMPv2MembershipReport 108 if igmp.v1Present() { 109 igmpType = header.IGMPv1MembershipReport 110 } 111 return igmp.writePacket(groupAddress, groupAddress, igmpType) 112 } 113 114 // SendLeave implements ip.MulticastGroupProtocol. 115 // 116 // Precondition: igmp.ep.mu must be read locked. 117 func (igmp *igmpState) SendLeave(groupAddress tcpip.Address) tcpip.Error { 118 // As per RFC 2236 Section 6, Page 8: "If the interface state says the 119 // Querier is running IGMPv1, this action SHOULD be skipped. If the flag 120 // saying we were the last host to report is cleared, this action MAY be 121 // skipped." 122 if igmp.v1Present() { 123 return nil 124 } 125 _, err := igmp.writePacket(header.IPv4AllRoutersGroup, groupAddress, header.IGMPLeaveGroup) 126 return err 127 } 128 129 // ShouldPerformProtocol implements ip.MulticastGroupProtocol. 130 func (igmp *igmpState) ShouldPerformProtocol(groupAddress tcpip.Address) bool { 131 // As per RFC 2236 section 6 page 10, 132 // 133 // The all-systems group (address 224.0.0.1) is handled as a special 134 // case. The host starts in Idle Member state for that group on every 135 // interface, never transitions to another state, and never sends a 136 // report for that group. 137 return groupAddress != header.IPv4AllSystems 138 } 139 140 // init sets up an igmpState struct, and is required to be called before using 141 // a new igmpState. 142 // 143 // Must only be called once for the lifetime of igmp. 144 func (igmp *igmpState) init(ep *endpoint) { 145 igmp.ep = ep 146 igmp.genericMulticastProtocol.Init(&ep.mu.RWMutex, ip.GenericMulticastProtocolOptions{ 147 Rand: ep.protocol.stack.Rand(), 148 Clock: ep.protocol.stack.Clock(), 149 Protocol: igmp, 150 MaxUnsolicitedReportDelay: UnsolicitedReportIntervalMax, 151 }) 152 igmp.igmpV1Present = igmpV1PresentDefault 153 igmp.igmpV1Job = ep.protocol.stack.NewJob(&ep.mu, func() { 154 igmp.setV1Present(false) 155 }) 156 } 157 158 // Precondition: igmp.ep.mu must be locked. 159 func (igmp *igmpState) isSourceIPValidLocked(src tcpip.Address, messageType header.IGMPType) bool { 160 if messageType == header.IGMPMembershipQuery { 161 // RFC 2236 does not require the IGMP implementation to check the source IP 162 // for Membership Query messages. 163 return true 164 } 165 166 // As per RFC 2236 section 10, 167 // 168 // Ignore the Report if you cannot identify the source address of the 169 // packet as belonging to a subnet assigned to the interface on which the 170 // packet was received. 171 // 172 // Ignore the Leave message if you cannot identify the source address of 173 // the packet as belonging to a subnet assigned to the interface on which 174 // the packet was received. 175 // 176 // Note: this rule applies to both V1 and V2 Membership Reports. 177 var isSourceIPValid bool 178 igmp.ep.mu.addressableEndpointState.ForEachPrimaryEndpoint(func(addressEndpoint stack.AddressEndpoint) bool { 179 if subnet := addressEndpoint.Subnet(); subnet.Contains(src) { 180 isSourceIPValid = true 181 return false 182 } 183 return true 184 }) 185 186 return isSourceIPValid 187 } 188 189 // Precondition: igmp.ep.mu must be locked. 190 func (igmp *igmpState) isPacketValidLocked(pkt *stack.PacketBuffer, messageType header.IGMPType, hasRouterAlertOption bool) bool { 191 // We can safely assume that the IP header is valid if we got this far. 192 iph := header.IPv4(pkt.NetworkHeader().View()) 193 194 // As per RFC 2236 section 2, 195 // 196 // All IGMP messages described in this document are sent with IP TTL 1, and 197 // contain the IP Router Alert option [RFC 2113] in their IP header. 198 if !hasRouterAlertOption || iph.TTL() != header.IGMPTTL { 199 return false 200 } 201 202 return igmp.isSourceIPValidLocked(iph.SourceAddress(), messageType) 203 } 204 205 // handleIGMP handles an IGMP packet. 206 // 207 // Precondition: igmp.ep.mu must be locked. 208 func (igmp *igmpState) handleIGMP(pkt *stack.PacketBuffer, hasRouterAlertOption bool) { 209 received := igmp.ep.stats.igmp.packetsReceived 210 headerView, ok := pkt.Data().PullUp(header.IGMPMinimumSize) 211 if !ok { 212 received.invalid.Increment() 213 return 214 } 215 h := header.IGMP(headerView) 216 217 // As per RFC 1071 section 1.3, 218 // 219 // To check a checksum, the 1's complement sum is computed over the 220 // same set of octets, including the checksum field. If the result 221 // is all 1 bits (-0 in 1's complement arithmetic), the check 222 // succeeds. 223 if pkt.Data().AsRange().Checksum() != 0xFFFF { 224 received.checksumErrors.Increment() 225 return 226 } 227 228 isValid := func(minimumSize int) bool { 229 return len(headerView) >= minimumSize && igmp.isPacketValidLocked(pkt, h.Type(), hasRouterAlertOption) 230 } 231 232 switch h.Type() { 233 case header.IGMPMembershipQuery: 234 received.membershipQuery.Increment() 235 if !isValid(header.IGMPQueryMinimumSize) { 236 received.invalid.Increment() 237 return 238 } 239 igmp.handleMembershipQuery(h.GroupAddress(), h.MaxRespTime()) 240 case header.IGMPv1MembershipReport: 241 received.v1MembershipReport.Increment() 242 if !isValid(header.IGMPReportMinimumSize) { 243 received.invalid.Increment() 244 return 245 } 246 igmp.handleMembershipReport(h.GroupAddress()) 247 case header.IGMPv2MembershipReport: 248 received.v2MembershipReport.Increment() 249 if !isValid(header.IGMPReportMinimumSize) { 250 received.invalid.Increment() 251 return 252 } 253 igmp.handleMembershipReport(h.GroupAddress()) 254 case header.IGMPLeaveGroup: 255 received.leaveGroup.Increment() 256 if !isValid(header.IGMPLeaveMessageMinimumSize) { 257 received.invalid.Increment() 258 return 259 } 260 // As per RFC 2236 Section 6, Page 7: "IGMP messages other than Query or 261 // Report, are ignored in all states" 262 263 default: 264 // As per RFC 2236 Section 2.1 Page 3: "Unrecognized message types should 265 // be silently ignored. New message types may be used by newer versions of 266 // IGMP, by multicast routing protocols, or other uses." 267 received.unrecognized.Increment() 268 } 269 } 270 271 func (igmp *igmpState) v1Present() bool { 272 return atomic.LoadUint32(&igmp.igmpV1Present) == 1 273 } 274 275 func (igmp *igmpState) setV1Present(v bool) { 276 if v { 277 atomic.StoreUint32(&igmp.igmpV1Present, 1) 278 } else { 279 atomic.StoreUint32(&igmp.igmpV1Present, 0) 280 } 281 } 282 283 func (igmp *igmpState) resetV1Present() { 284 igmp.igmpV1Job.Cancel() 285 igmp.setV1Present(false) 286 } 287 288 // handleMembershipQuery handles a membership query. 289 // 290 // Precondition: igmp.ep.mu must be locked. 291 func (igmp *igmpState) handleMembershipQuery(groupAddress tcpip.Address, maxRespTime time.Duration) { 292 // As per RFC 2236 Section 6, Page 10: If the maximum response time is zero 293 // then change the state to note that an IGMPv1 router is present and 294 // schedule the query received Job. 295 if maxRespTime == 0 && igmp.Enabled() { 296 igmp.igmpV1Job.Cancel() 297 igmp.igmpV1Job.Schedule(v1RouterPresentTimeout) 298 igmp.setV1Present(true) 299 maxRespTime = v1MaxRespTime 300 } 301 302 igmp.genericMulticastProtocol.HandleQueryLocked(groupAddress, maxRespTime) 303 } 304 305 // handleMembershipReport handles a membership report. 306 // 307 // Precondition: igmp.ep.mu must be locked. 308 func (igmp *igmpState) handleMembershipReport(groupAddress tcpip.Address) { 309 igmp.genericMulticastProtocol.HandleReportLocked(groupAddress) 310 } 311 312 // writePacket assembles and sends an IGMP packet. 313 // 314 // Precondition: igmp.ep.mu must be read locked. 315 func (igmp *igmpState) writePacket(destAddress tcpip.Address, groupAddress tcpip.Address, igmpType header.IGMPType) (bool, tcpip.Error) { 316 igmpData := header.IGMP(buffer.NewView(header.IGMPReportMinimumSize)) 317 igmpData.SetType(igmpType) 318 igmpData.SetGroupAddress(groupAddress) 319 igmpData.SetChecksum(header.IGMPCalculateChecksum(igmpData)) 320 321 pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{ 322 ReserveHeaderBytes: int(igmp.ep.MaxHeaderLength()), 323 Data: buffer.View(igmpData).ToVectorisedView(), 324 }) 325 326 addressEndpoint := igmp.ep.acquireOutgoingPrimaryAddressRLocked(destAddress, false /* allowExpired */) 327 if addressEndpoint == nil { 328 return false, nil 329 } 330 localAddr := addressEndpoint.AddressWithPrefix().Address 331 addressEndpoint.DecRef() 332 addressEndpoint = nil 333 if err := igmp.ep.addIPHeader(localAddr, destAddress, pkt, stack.NetworkHeaderParams{ 334 Protocol: header.IGMPProtocolNumber, 335 TTL: header.IGMPTTL, 336 TOS: stack.DefaultTOS, 337 }, header.IPv4OptionsSerializer{ 338 &header.IPv4SerializableRouterAlertOption{}, 339 }); err != nil { 340 panic(fmt.Sprintf("failed to add IP header: %s", err)) 341 } 342 343 sentStats := igmp.ep.stats.igmp.packetsSent 344 if err := igmp.ep.nic.WritePacketToRemote(header.EthernetAddressFromMulticastIPv4Address(destAddress), ProtocolNumber, pkt); err != nil { 345 sentStats.dropped.Increment() 346 return false, err 347 } 348 switch igmpType { 349 case header.IGMPv1MembershipReport: 350 sentStats.v1MembershipReport.Increment() 351 case header.IGMPv2MembershipReport: 352 sentStats.v2MembershipReport.Increment() 353 case header.IGMPLeaveGroup: 354 sentStats.leaveGroup.Increment() 355 default: 356 panic(fmt.Sprintf("unrecognized igmp type = %d", igmpType)) 357 } 358 return true, nil 359 } 360 361 // joinGroup handles adding a new group to the membership map, setting up the 362 // IGMP state for the group, and sending and scheduling the required 363 // messages. 364 // 365 // If the group already exists in the membership map, returns 366 // *tcpip.ErrDuplicateAddress. 367 // 368 // Precondition: igmp.ep.mu must be locked. 369 func (igmp *igmpState) joinGroup(groupAddress tcpip.Address) { 370 igmp.genericMulticastProtocol.JoinGroupLocked(groupAddress) 371 } 372 373 // isInGroup returns true if the specified group has been joined locally. 374 // 375 // Precondition: igmp.ep.mu must be read locked. 376 func (igmp *igmpState) isInGroup(groupAddress tcpip.Address) bool { 377 return igmp.genericMulticastProtocol.IsLocallyJoinedRLocked(groupAddress) 378 } 379 380 // leaveGroup handles removing the group from the membership map, cancels any 381 // delay timers associated with that group, and sends the Leave Group message 382 // if required. 383 // 384 // Precondition: igmp.ep.mu must be locked. 385 func (igmp *igmpState) leaveGroup(groupAddress tcpip.Address) tcpip.Error { 386 // LeaveGroup returns false only if the group was not joined. 387 if igmp.genericMulticastProtocol.LeaveGroupLocked(groupAddress) { 388 return nil 389 } 390 391 return &tcpip.ErrBadLocalAddress{} 392 } 393 394 // softLeaveAll leaves all groups from the perspective of IGMP, but remains 395 // joined locally. 396 // 397 // Precondition: igmp.ep.mu must be locked. 398 func (igmp *igmpState) softLeaveAll() { 399 igmp.genericMulticastProtocol.MakeAllNonMemberLocked() 400 } 401 402 // initializeAll attemps to initialize the IGMP state for each group that has 403 // been joined locally. 404 // 405 // Precondition: igmp.ep.mu must be locked. 406 func (igmp *igmpState) initializeAll() { 407 igmp.genericMulticastProtocol.InitializeGroupsLocked() 408 } 409 410 // sendQueuedReports attempts to send any reports that are queued for sending. 411 // 412 // Precondition: igmp.ep.mu must be locked. 413 func (igmp *igmpState) sendQueuedReports() { 414 igmp.genericMulticastProtocol.SendQueuedReportsLocked() 415 }