inet.af/netstack@v0.0.0-20220214151720-7585b01ddccf/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 "inet.af/netstack/tcpip" 22 "inet.af/netstack/tcpip/buffer" 23 "inet.af/netstack/tcpip/header" 24 "inet.af/netstack/tcpip/network/internal/ip" 25 "inet.af/netstack/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 defer pkt.DecRef() 274 275 if err := addIPHeader(localAddress, destAddress, pkt, stack.NetworkHeaderParams{ 276 Protocol: header.ICMPv6ProtocolNumber, 277 TTL: header.MLDHopLimit, 278 }, extensionHeaders); err != nil { 279 panic(fmt.Sprintf("failed to add IP header: %s", err)) 280 } 281 if err := mld.ep.nic.WritePacketToRemote(header.EthernetAddressFromMulticastIPv6Address(destAddress), ProtocolNumber, pkt); err != nil { 282 sentStats.dropped.Increment() 283 return false, err 284 } 285 mldStat.Increment() 286 return localAddress != header.IPv6Any, nil 287 }