github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/tcpip/network/ipv6/mld_test.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_test 16 17 import ( 18 "bytes" 19 "math/rand" 20 "testing" 21 "time" 22 23 "github.com/SagerNet/gvisor/pkg/tcpip" 24 "github.com/SagerNet/gvisor/pkg/tcpip/buffer" 25 "github.com/SagerNet/gvisor/pkg/tcpip/checker" 26 "github.com/SagerNet/gvisor/pkg/tcpip/faketime" 27 "github.com/SagerNet/gvisor/pkg/tcpip/header" 28 "github.com/SagerNet/gvisor/pkg/tcpip/link/channel" 29 "github.com/SagerNet/gvisor/pkg/tcpip/network/ipv6" 30 "github.com/SagerNet/gvisor/pkg/tcpip/stack" 31 "github.com/SagerNet/gvisor/pkg/tcpip/testutil" 32 ) 33 34 var ( 35 linkLocalAddr = testutil.MustParse6("fe80::1") 36 globalAddr = testutil.MustParse6("a80::1") 37 globalMulticastAddr = testutil.MustParse6("ff05:100::2") 38 39 linkLocalAddrSNMC = header.SolicitedNodeAddr(linkLocalAddr) 40 globalAddrSNMC = header.SolicitedNodeAddr(globalAddr) 41 ) 42 43 func validateMLDPacket(t *testing.T, p buffer.View, localAddress, remoteAddress tcpip.Address, mldType header.ICMPv6Type, groupAddress tcpip.Address) { 44 t.Helper() 45 46 checker.IPv6WithExtHdr(t, p, 47 checker.IPv6ExtHdr( 48 checker.IPv6HopByHopExtensionHeader(checker.IPv6RouterAlert(header.IPv6RouterAlertMLD)), 49 ), 50 checker.SrcAddr(localAddress), 51 checker.DstAddr(remoteAddress), 52 checker.TTL(header.MLDHopLimit), 53 checker.MLD(mldType, header.MLDMinimumSize, 54 checker.MLDMaxRespDelay(0), 55 checker.MLDMulticastAddress(groupAddress), 56 ), 57 ) 58 } 59 60 func TestIPv6JoinLeaveSolicitedNodeAddressPerformsMLD(t *testing.T) { 61 const nicID = 1 62 63 s := stack.New(stack.Options{ 64 NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ 65 MLD: ipv6.MLDOptions{ 66 Enabled: true, 67 }, 68 })}, 69 }) 70 e := channel.New(1, header.IPv6MinimumMTU, "") 71 if err := s.CreateNIC(nicID, e); err != nil { 72 t.Fatalf("CreateNIC(%d, _): %s", nicID, err) 73 } 74 75 // The stack will join an address's solicited node multicast address when 76 // an address is added. An MLD report message should be sent for the 77 // solicited-node group. 78 if err := s.AddAddress(nicID, ipv6.ProtocolNumber, linkLocalAddr); err != nil { 79 t.Fatalf("AddAddress(%d, %d, %s) = %s", nicID, ipv6.ProtocolNumber, linkLocalAddr, err) 80 } 81 if p, ok := e.Read(); !ok { 82 t.Fatal("expected a report message to be sent") 83 } else { 84 validateMLDPacket(t, stack.PayloadSince(p.Pkt.NetworkHeader()), linkLocalAddr, linkLocalAddrSNMC, header.ICMPv6MulticastListenerReport, linkLocalAddrSNMC) 85 } 86 87 // The stack will leave an address's solicited node multicast address when 88 // an address is removed. An MLD done message should be sent for the 89 // solicited-node group. 90 if err := s.RemoveAddress(nicID, linkLocalAddr); err != nil { 91 t.Fatalf("RemoveAddress(%d, %s) = %s", nicID, linkLocalAddr, err) 92 } 93 if p, ok := e.Read(); !ok { 94 t.Fatal("expected a done message to be sent") 95 } else { 96 validateMLDPacket(t, stack.PayloadSince(p.Pkt.NetworkHeader()), header.IPv6Any, header.IPv6AllRoutersLinkLocalMulticastAddress, header.ICMPv6MulticastListenerDone, linkLocalAddrSNMC) 97 } 98 } 99 100 func TestSendQueuedMLDReports(t *testing.T) { 101 const ( 102 nicID = 1 103 maxReports = 2 104 ) 105 106 tests := []struct { 107 name string 108 dadTransmits uint8 109 retransmitTimer time.Duration 110 }{ 111 { 112 name: "DAD Disabled", 113 dadTransmits: 0, 114 retransmitTimer: 0, 115 }, 116 { 117 name: "DAD Enabled", 118 dadTransmits: 1, 119 retransmitTimer: time.Second, 120 }, 121 } 122 123 nonce := [...]byte{ 124 1, 2, 3, 4, 5, 6, 125 } 126 127 const maxNSMessages = 2 128 secureRNGBytes := make([]byte, len(nonce)*maxNSMessages) 129 for b := secureRNGBytes[:]; len(b) > 0; b = b[len(nonce):] { 130 if n := copy(b, nonce[:]); n != len(nonce) { 131 t.Fatalf("got copy(...) = %d, want = %d", n, len(nonce)) 132 } 133 } 134 135 for _, test := range tests { 136 t.Run(test.name, func(t *testing.T) { 137 dadResolutionTime := test.retransmitTimer * time.Duration(test.dadTransmits) 138 clock := faketime.NewManualClock() 139 var secureRNG bytes.Reader 140 secureRNG.Reset(secureRNGBytes[:]) 141 s := stack.New(stack.Options{ 142 SecureRNG: &secureRNG, 143 RandSource: rand.NewSource(time.Now().UnixNano()), 144 NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ 145 DADConfigs: stack.DADConfigurations{ 146 DupAddrDetectTransmits: test.dadTransmits, 147 RetransmitTimer: test.retransmitTimer, 148 }, 149 MLD: ipv6.MLDOptions{ 150 Enabled: true, 151 }, 152 })}, 153 Clock: clock, 154 }) 155 156 // Allow space for an extra packet so we can observe packets that were 157 // unexpectedly sent. 158 e := channel.New(maxReports+int(test.dadTransmits)+1 /* extra */, header.IPv6MinimumMTU, "") 159 if err := s.CreateNIC(nicID, e); err != nil { 160 t.Fatalf("CreateNIC(%d, _): %s", nicID, err) 161 } 162 163 resolveDAD := func(addr, snmc tcpip.Address) { 164 clock.Advance(dadResolutionTime) 165 if p, ok := e.Read(); !ok { 166 t.Fatal("expected DAD packet") 167 } else { 168 checker.IPv6(t, stack.PayloadSince(p.Pkt.NetworkHeader()), 169 checker.SrcAddr(header.IPv6Any), 170 checker.DstAddr(snmc), 171 checker.TTL(header.NDPHopLimit), 172 checker.NDPNS( 173 checker.NDPNSTargetAddress(addr), 174 checker.NDPNSOptions([]header.NDPOption{header.NDPNonceOption(nonce[:])}), 175 )) 176 } 177 } 178 179 var reportCounter uint64 180 reportStat := s.Stats().ICMP.V6.PacketsSent.MulticastListenerReport 181 if got := reportStat.Value(); got != reportCounter { 182 t.Errorf("got reportStat.Value() = %d, want = %d", got, reportCounter) 183 } 184 var doneCounter uint64 185 doneStat := s.Stats().ICMP.V6.PacketsSent.MulticastListenerDone 186 if got := doneStat.Value(); got != doneCounter { 187 t.Errorf("got doneStat.Value() = %d, want = %d", got, doneCounter) 188 } 189 190 // Joining a group without an assigned address should send an MLD report 191 // with the unspecified address. 192 if err := s.JoinGroup(ipv6.ProtocolNumber, nicID, globalMulticastAddr); err != nil { 193 t.Fatalf("JoinGroup(%d, %d, %s): %s", ipv6.ProtocolNumber, nicID, globalMulticastAddr, err) 194 } 195 reportCounter++ 196 if got := reportStat.Value(); got != reportCounter { 197 t.Errorf("got reportStat.Value() = %d, want = %d", got, reportCounter) 198 } 199 if p, ok := e.Read(); !ok { 200 t.Errorf("expected MLD report for %s", globalMulticastAddr) 201 } else { 202 validateMLDPacket(t, stack.PayloadSince(p.Pkt.NetworkHeader()), header.IPv6Any, globalMulticastAddr, header.ICMPv6MulticastListenerReport, globalMulticastAddr) 203 } 204 clock.Advance(time.Hour) 205 if p, ok := e.Read(); ok { 206 t.Errorf("got unexpected packet = %#v", p) 207 } 208 if t.Failed() { 209 t.FailNow() 210 } 211 212 // Adding a global address should not send reports for the already joined 213 // group since we should only send queued reports when a link-local 214 // address is assigned. 215 // 216 // Note, we will still expect to send a report for the global address's 217 // solicited node address from the unspecified address as per RFC 3590 218 // section 4. 219 if err := s.AddAddressWithOptions(nicID, ipv6.ProtocolNumber, globalAddr, stack.FirstPrimaryEndpoint); err != nil { 220 t.Fatalf("AddAddressWithOptions(%d, %d, %s, %d): %s", nicID, ipv6.ProtocolNumber, globalAddr, stack.FirstPrimaryEndpoint, err) 221 } 222 reportCounter++ 223 if got := reportStat.Value(); got != reportCounter { 224 t.Errorf("got reportStat.Value() = %d, want = %d", got, reportCounter) 225 } 226 if p, ok := e.Read(); !ok { 227 t.Errorf("expected MLD report for %s", globalAddrSNMC) 228 } else { 229 validateMLDPacket(t, stack.PayloadSince(p.Pkt.NetworkHeader()), header.IPv6Any, globalAddrSNMC, header.ICMPv6MulticastListenerReport, globalAddrSNMC) 230 } 231 if dadResolutionTime != 0 { 232 // Reports should not be sent when the address resolves. 233 resolveDAD(globalAddr, globalAddrSNMC) 234 if got := reportStat.Value(); got != reportCounter { 235 t.Errorf("got reportStat.Value() = %d, want = %d", got, reportCounter) 236 } 237 } 238 // Leave the group since we don't care about the global address's 239 // solicited node multicast group membership. 240 if err := s.LeaveGroup(ipv6.ProtocolNumber, nicID, globalAddrSNMC); err != nil { 241 t.Fatalf("LeaveGroup(%d, %d, %s): %s", ipv6.ProtocolNumber, nicID, globalAddrSNMC, err) 242 } 243 if got := doneStat.Value(); got != doneCounter { 244 t.Errorf("got doneStat.Value() = %d, want = %d", got, doneCounter) 245 } 246 if p, ok := e.Read(); ok { 247 t.Errorf("got unexpected packet = %#v", p) 248 } 249 if t.Failed() { 250 t.FailNow() 251 } 252 253 // Adding a link-local address should send a report for its solicited node 254 // address and globalMulticastAddr. 255 if err := s.AddAddressWithOptions(nicID, ipv6.ProtocolNumber, linkLocalAddr, stack.CanBePrimaryEndpoint); err != nil { 256 t.Fatalf("AddAddressWithOptions(%d, %d, %s, %d): %s", nicID, ipv6.ProtocolNumber, linkLocalAddr, stack.CanBePrimaryEndpoint, err) 257 } 258 if dadResolutionTime != 0 { 259 reportCounter++ 260 if got := reportStat.Value(); got != reportCounter { 261 t.Errorf("got reportStat.Value() = %d, want = %d", got, reportCounter) 262 } 263 if p, ok := e.Read(); !ok { 264 t.Errorf("expected MLD report for %s", linkLocalAddrSNMC) 265 } else { 266 validateMLDPacket(t, stack.PayloadSince(p.Pkt.NetworkHeader()), header.IPv6Any, linkLocalAddrSNMC, header.ICMPv6MulticastListenerReport, linkLocalAddrSNMC) 267 } 268 resolveDAD(linkLocalAddr, linkLocalAddrSNMC) 269 } 270 271 // We expect two batches of reports to be sent (1 batch when the 272 // link-local address is assigned, and another after the maximum 273 // unsolicited report interval. 274 for i := 0; i < 2; i++ { 275 // We expect reports to be sent (one for globalMulticastAddr and another 276 // for linkLocalAddrSNMC). 277 reportCounter += maxReports 278 if got := reportStat.Value(); got != reportCounter { 279 t.Errorf("got reportStat.Value() = %d, want = %d", got, reportCounter) 280 } 281 282 addrs := map[tcpip.Address]bool{ 283 globalMulticastAddr: false, 284 linkLocalAddrSNMC: false, 285 } 286 for range addrs { 287 p, ok := e.Read() 288 if !ok { 289 t.Fatalf("expected MLD report for %s and %s; addrs = %#v", globalMulticastAddr, linkLocalAddrSNMC, addrs) 290 } 291 292 addr := header.IPv6(stack.PayloadSince(p.Pkt.NetworkHeader())).DestinationAddress() 293 if seen, ok := addrs[addr]; !ok { 294 t.Fatalf("got unexpected packet destined to %s", addr) 295 } else if seen { 296 t.Fatalf("got another packet destined to %s", addr) 297 } 298 299 addrs[addr] = true 300 validateMLDPacket(t, stack.PayloadSince(p.Pkt.NetworkHeader()), linkLocalAddr, addr, header.ICMPv6MulticastListenerReport, addr) 301 302 clock.Advance(ipv6.UnsolicitedReportIntervalMax) 303 } 304 } 305 306 // Should not send any more reports. 307 clock.Advance(time.Hour) 308 if p, ok := e.Read(); ok { 309 t.Errorf("got unexpected packet = %#v", p) 310 } 311 }) 312 } 313 } 314 315 // createAndInjectMLDPacket creates and injects an MLD packet with the 316 // specified fields. 317 func createAndInjectMLDPacket(e *channel.Endpoint, mldType header.ICMPv6Type, hopLimit uint8, srcAddress tcpip.Address, withRouterAlertOption bool, routerAlertValue header.IPv6RouterAlertValue) { 318 var extensionHeaders header.IPv6ExtHdrSerializer 319 if withRouterAlertOption { 320 extensionHeaders = header.IPv6ExtHdrSerializer{ 321 header.IPv6SerializableHopByHopExtHdr{ 322 &header.IPv6RouterAlertOption{Value: routerAlertValue}, 323 }, 324 } 325 } 326 327 extensionHeadersLength := extensionHeaders.Length() 328 payloadLength := extensionHeadersLength + header.ICMPv6HeaderSize + header.MLDMinimumSize 329 buf := buffer.NewView(header.IPv6MinimumSize + payloadLength) 330 331 ip := header.IPv6(buf) 332 ip.Encode(&header.IPv6Fields{ 333 PayloadLength: uint16(payloadLength), 334 HopLimit: hopLimit, 335 TransportProtocol: header.ICMPv6ProtocolNumber, 336 SrcAddr: srcAddress, 337 DstAddr: header.IPv6AllNodesMulticastAddress, 338 ExtensionHeaders: extensionHeaders, 339 }) 340 341 icmp := header.ICMPv6(ip.Payload()[extensionHeadersLength:]) 342 icmp.SetType(mldType) 343 mld := header.MLD(icmp.MessageBody()) 344 mld.SetMaximumResponseDelay(0) 345 mld.SetMulticastAddress(header.IPv6Any) 346 icmp.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{ 347 Header: icmp, 348 Src: srcAddress, 349 Dst: header.IPv6AllNodesMulticastAddress, 350 })) 351 352 e.InjectInbound(ipv6.ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{ 353 Data: buf.ToVectorisedView(), 354 })) 355 } 356 357 func TestMLDPacketValidation(t *testing.T) { 358 const nicID = 1 359 linkLocalAddr2 := testutil.MustParse6("fe80::2") 360 361 tests := []struct { 362 name string 363 messageType header.ICMPv6Type 364 srcAddr tcpip.Address 365 includeRouterAlertOption bool 366 routerAlertValue header.IPv6RouterAlertValue 367 hopLimit uint8 368 expectValidMLD bool 369 getMessageTypeStatValue func(tcpip.Stats) uint64 370 }{ 371 { 372 name: "valid", 373 messageType: header.ICMPv6MulticastListenerQuery, 374 includeRouterAlertOption: true, 375 routerAlertValue: header.IPv6RouterAlertMLD, 376 srcAddr: linkLocalAddr2, 377 hopLimit: header.MLDHopLimit, 378 expectValidMLD: true, 379 getMessageTypeStatValue: func(stats tcpip.Stats) uint64 { return stats.ICMP.V6.PacketsReceived.MulticastListenerQuery.Value() }, 380 }, 381 { 382 name: "bad hop limit", 383 messageType: header.ICMPv6MulticastListenerReport, 384 includeRouterAlertOption: true, 385 routerAlertValue: header.IPv6RouterAlertMLD, 386 srcAddr: linkLocalAddr2, 387 hopLimit: header.MLDHopLimit + 1, 388 expectValidMLD: false, 389 getMessageTypeStatValue: func(stats tcpip.Stats) uint64 { return stats.ICMP.V6.PacketsReceived.MulticastListenerReport.Value() }, 390 }, 391 { 392 name: "src ip not link local", 393 messageType: header.ICMPv6MulticastListenerReport, 394 includeRouterAlertOption: true, 395 routerAlertValue: header.IPv6RouterAlertMLD, 396 srcAddr: globalAddr, 397 hopLimit: header.MLDHopLimit, 398 expectValidMLD: false, 399 getMessageTypeStatValue: func(stats tcpip.Stats) uint64 { return stats.ICMP.V6.PacketsReceived.MulticastListenerReport.Value() }, 400 }, 401 { 402 name: "missing router alert ip option", 403 messageType: header.ICMPv6MulticastListenerDone, 404 includeRouterAlertOption: false, 405 srcAddr: linkLocalAddr2, 406 hopLimit: header.MLDHopLimit, 407 expectValidMLD: false, 408 getMessageTypeStatValue: func(stats tcpip.Stats) uint64 { return stats.ICMP.V6.PacketsReceived.MulticastListenerDone.Value() }, 409 }, 410 { 411 name: "incorrect router alert value", 412 messageType: header.ICMPv6MulticastListenerDone, 413 includeRouterAlertOption: true, 414 routerAlertValue: header.IPv6RouterAlertRSVP, 415 srcAddr: linkLocalAddr2, 416 hopLimit: header.MLDHopLimit, 417 expectValidMLD: false, 418 getMessageTypeStatValue: func(stats tcpip.Stats) uint64 { return stats.ICMP.V6.PacketsReceived.MulticastListenerDone.Value() }, 419 }, 420 } 421 422 for _, test := range tests { 423 t.Run(test.name, func(t *testing.T) { 424 s := stack.New(stack.Options{ 425 NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ 426 MLD: ipv6.MLDOptions{ 427 Enabled: true, 428 }, 429 })}, 430 }) 431 e := channel.New(nicID, header.IPv6MinimumMTU, "") 432 if err := s.CreateNIC(nicID, e); err != nil { 433 t.Fatalf("CreateNIC(%d, _): %s", nicID, err) 434 } 435 stats := s.Stats() 436 // Verify that every relevant stats is zero'd before we send a packet. 437 if got := test.getMessageTypeStatValue(s.Stats()); got != 0 { 438 t.Errorf("got test.getMessageTypeStatValue(s.Stats()) = %d, want = 0", got) 439 } 440 if got := stats.ICMP.V6.PacketsReceived.Invalid.Value(); got != 0 { 441 t.Errorf("got stats.ICMP.V6.PacketsReceived.Invalid.Value() = %d, want = 0", got) 442 } 443 if got := stats.IP.PacketsDelivered.Value(); got != 0 { 444 t.Fatalf("got stats.IP.PacketsDelivered.Value() = %d, want = 0", got) 445 } 446 createAndInjectMLDPacket(e, test.messageType, test.hopLimit, test.srcAddr, test.includeRouterAlertOption, test.routerAlertValue) 447 // We always expect the packet to pass IP validation. 448 if got := stats.IP.PacketsDelivered.Value(); got != 1 { 449 t.Fatalf("got stats.IP.PacketsDelivered.Value() = %d, want = 1", got) 450 } 451 // Even when the MLD-specific validation checks fail, we expect the 452 // corresponding MLD counter to be incremented. 453 if got := test.getMessageTypeStatValue(s.Stats()); got != 1 { 454 t.Errorf("got test.getMessageTypeStatValue(s.Stats()) = %d, want = 1", got) 455 } 456 var expectedInvalidCount uint64 457 if !test.expectValidMLD { 458 expectedInvalidCount = 1 459 } 460 if got := stats.ICMP.V6.PacketsReceived.Invalid.Value(); got != expectedInvalidCount { 461 t.Errorf("got stats.ICMP.V6.PacketsReceived.Invalid.Value() = %d, want = %d", got, expectedInvalidCount) 462 } 463 }) 464 } 465 } 466 467 func TestMLDSkipProtocol(t *testing.T) { 468 const nicID = 1 469 470 tests := []struct { 471 name string 472 group tcpip.Address 473 expectReport bool 474 }{ 475 { 476 name: "Reserverd0", 477 group: "\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11", 478 expectReport: false, 479 }, 480 { 481 name: "Interface Local", 482 group: "\xff\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11", 483 expectReport: false, 484 }, 485 { 486 name: "Link Local", 487 group: "\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11", 488 expectReport: true, 489 }, 490 { 491 name: "Realm Local", 492 group: "\xff\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11", 493 expectReport: true, 494 }, 495 { 496 name: "Admin Local", 497 group: "\xff\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11", 498 expectReport: true, 499 }, 500 { 501 name: "Site Local", 502 group: "\xff\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11", 503 expectReport: true, 504 }, 505 { 506 name: "Unassigned(6)", 507 group: "\xff\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11", 508 expectReport: true, 509 }, 510 { 511 name: "Unassigned(7)", 512 group: "\xff\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11", 513 expectReport: true, 514 }, 515 { 516 name: "Organization Local", 517 group: "\xff\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11", 518 expectReport: true, 519 }, 520 { 521 name: "Unassigned(9)", 522 group: "\xff\x09\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11", 523 expectReport: true, 524 }, 525 { 526 name: "Unassigned(A)", 527 group: "\xff\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11", 528 expectReport: true, 529 }, 530 { 531 name: "Unassigned(B)", 532 group: "\xff\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11", 533 expectReport: true, 534 }, 535 { 536 name: "Unassigned(C)", 537 group: "\xff\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11", 538 expectReport: true, 539 }, 540 { 541 name: "Unassigned(D)", 542 group: "\xff\x0d\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11", 543 expectReport: true, 544 }, 545 { 546 name: "Global", 547 group: "\xff\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11", 548 expectReport: true, 549 }, 550 { 551 name: "ReservedF", 552 group: "\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11", 553 expectReport: true, 554 }, 555 } 556 557 for _, test := range tests { 558 t.Run(test.name, func(t *testing.T) { 559 s := stack.New(stack.Options{ 560 NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ 561 MLD: ipv6.MLDOptions{ 562 Enabled: true, 563 }, 564 })}, 565 }) 566 e := channel.New(1, header.IPv6MinimumMTU, "") 567 if err := s.CreateNIC(nicID, e); err != nil { 568 t.Fatalf("CreateNIC(%d, _): %s", nicID, err) 569 } 570 if err := s.AddAddress(nicID, ipv6.ProtocolNumber, linkLocalAddr); err != nil { 571 t.Fatalf("AddAddress(%d, %d, %s) = %s", nicID, ipv6.ProtocolNumber, linkLocalAddr, err) 572 } 573 if p, ok := e.Read(); !ok { 574 t.Fatal("expected a report message to be sent") 575 } else { 576 validateMLDPacket(t, stack.PayloadSince(p.Pkt.NetworkHeader()), linkLocalAddr, linkLocalAddrSNMC, header.ICMPv6MulticastListenerReport, linkLocalAddrSNMC) 577 } 578 579 if err := s.JoinGroup(ipv6.ProtocolNumber, nicID, test.group); err != nil { 580 t.Fatalf("s.JoinGroup(%d, %d, %s): %s", ipv6.ProtocolNumber, nicID, test.group, err) 581 } 582 if isInGroup, err := s.IsInGroup(nicID, test.group); err != nil { 583 t.Fatalf("IsInGroup(%d, %s): %s", nicID, test.group, err) 584 } else if !isInGroup { 585 t.Fatalf("got IsInGroup(%d, %s) = false, want = true", nicID, test.group) 586 } 587 588 if !test.expectReport { 589 if p, ok := e.Read(); ok { 590 t.Fatalf("got e.Read() = (%#v, true), want = (_, false)", p) 591 } 592 593 return 594 } 595 596 if p, ok := e.Read(); !ok { 597 t.Fatal("expected a report message to be sent") 598 } else { 599 validateMLDPacket(t, stack.PayloadSince(p.Pkt.NetworkHeader()), linkLocalAddr, test.group, header.ICMPv6MulticastListenerReport, test.group) 600 } 601 }) 602 } 603 }