diff options
author | Arthur Sfez <asfez@google.com> | 2021-02-24 16:54:11 -0800 |
---|---|---|
committer | gVisor bot <gvisor-bot@google.com> | 2021-02-24 16:56:19 -0800 |
commit | 1d2975ffbe0e32ebcd2fe9307544799b2f9ae632 (patch) | |
tree | ae1db85dec625cc2f38dee9a66acc71f73782de0 /pkg/tcpip | |
parent | f5692f7dcc48a76a5d7b45cdf71b59be876adb42 (diff) |
Validate MLD packets
Fixes #5490
PiperOrigin-RevId: 359401532
Diffstat (limited to 'pkg/tcpip')
-rw-r--r-- | pkg/tcpip/network/ipv6/icmp.go | 29 | ||||
-rw-r--r-- | pkg/tcpip/network/ipv6/icmp_test.go | 104 | ||||
-rw-r--r-- | pkg/tcpip/network/ipv6/ipv6.go | 70 | ||||
-rw-r--r-- | pkg/tcpip/network/ipv6/ipv6_test.go | 67 | ||||
-rw-r--r-- | pkg/tcpip/network/ipv6/mld_test.go | 155 | ||||
-rw-r--r-- | pkg/tcpip/network/multicast_group_test.go | 35 |
6 files changed, 367 insertions, 93 deletions
diff --git a/pkg/tcpip/network/ipv6/icmp.go b/pkg/tcpip/network/ipv6/icmp.go index 2690644d6..5f44ab317 100644 --- a/pkg/tcpip/network/ipv6/icmp.go +++ b/pkg/tcpip/network/ipv6/icmp.go @@ -260,12 +260,31 @@ func getTargetLinkAddr(it header.NDPOptionIterator) (tcpip.LinkAddress, bool) { }) } -func (e *endpoint) handleICMP(pkt *stack.PacketBuffer, hasFragmentHeader bool) { +func isMLDValid(pkt *stack.PacketBuffer, iph header.IPv6, routerAlert *header.IPv6RouterAlertOption) bool { + // As per RFC 2710 section 3: + // All MLD messages described in this document are sent with a link-local + // IPv6 Source Address, an IPv6 Hop Limit of 1, and an IPv6 Router Alert + // option in a Hop-by-Hop Options header. + if routerAlert == nil || routerAlert.Value != header.IPv6RouterAlertMLD { + return false + } + if pkt.Data.Size() < header.ICMPv6HeaderSize+header.MLDMinimumSize { + return false + } + if iph.HopLimit() != header.MLDHopLimit { + return false + } + if !header.IsV6LinkLocalAddress(iph.SourceAddress()) { + return false + } + return true +} + +func (e *endpoint) handleICMP(pkt *stack.PacketBuffer, hasFragmentHeader bool, routerAlert *header.IPv6RouterAlertOption) { sent := e.stats.icmp.packetsSent received := e.stats.icmp.packetsReceived - // TODO(gvisor.dev/issue/170): ICMP packets don't have their - // TransportHeader fields set. See icmp/protocol.go:protocol.Parse for a - // full explanation. + // TODO(gvisor.dev/issue/170): ICMP packets don't have their TransportHeader + // fields set. See icmp/protocol.go:protocol.Parse for a full explanation. v, ok := pkt.Data.PullUp(header.ICMPv6HeaderSize) if !ok { received.invalid.Increment() @@ -823,7 +842,7 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer, hasFragmentHeader bool) { panic(fmt.Sprintf("unrecognized MLD message = %d", icmpType)) } - if pkt.Data.Size()-header.ICMPv6HeaderSize < header.MLDMinimumSize { + if !isMLDValid(pkt, iph, routerAlert) { received.invalid.Increment() return } diff --git a/pkg/tcpip/network/ipv6/icmp_test.go b/pkg/tcpip/network/ipv6/icmp_test.go index 69c1e4bea..c27164344 100644 --- a/pkg/tcpip/network/ipv6/icmp_test.go +++ b/pkg/tcpip/network/ipv6/icmp_test.go @@ -48,6 +48,8 @@ const ( // Extra time to use when waiting for an async event to occur. defaultAsyncPositiveEventTimeout = 30 * time.Second + + arbitraryHopLimit = 42 ) var ( @@ -157,20 +159,29 @@ func (*testInterface) CheckLocalAddress(tcpip.NetworkProtocolNumber, tcpip.Addre return false } -func handleICMPInIPv6(ep stack.NetworkEndpoint, src, dst tcpip.Address, icmp header.ICMPv6) { - ip := buffer.NewView(header.IPv6MinimumSize) +func handleICMPInIPv6(ep stack.NetworkEndpoint, src, dst tcpip.Address, icmp header.ICMPv6, hopLimit uint8, includeRouterAlert bool) { + var extensionHeaders header.IPv6ExtHdrSerializer + if includeRouterAlert { + extensionHeaders = header.IPv6ExtHdrSerializer{ + header.IPv6SerializableHopByHopExtHdr{ + &header.IPv6RouterAlertOption{Value: header.IPv6RouterAlertMLD}, + }, + } + } + ip := buffer.NewView(header.IPv6MinimumSize + extensionHeaders.Length()) header.IPv6(ip).Encode(&header.IPv6Fields{ PayloadLength: uint16(len(icmp)), TransportProtocol: header.ICMPv6ProtocolNumber, - HopLimit: header.NDPHopLimit, + HopLimit: hopLimit, SrcAddr: src, DstAddr: dst, + ExtensionHeaders: extensionHeaders, }) + vv := ip.ToVectorisedView() vv.AppendView(buffer.View(icmp)) ep.HandlePacket(stack.NewPacketBuffer(stack.PacketBufferOptions{ - ReserveHeaderBytes: header.IPv6MinimumSize, - Data: vv, + Data: vv, })) } @@ -223,66 +234,85 @@ func TestICMPCounts(t *testing.T) { }) types := []struct { - typ header.ICMPv6Type - size int - extraData []byte + typ header.ICMPv6Type + hopLimit uint8 + includeRouterAlert bool + size int + extraData []byte }{ { - typ: header.ICMPv6DstUnreachable, - size: header.ICMPv6DstUnreachableMinimumSize, + typ: header.ICMPv6DstUnreachable, + hopLimit: arbitraryHopLimit, + size: header.ICMPv6DstUnreachableMinimumSize, }, { - typ: header.ICMPv6PacketTooBig, - size: header.ICMPv6PacketTooBigMinimumSize, + typ: header.ICMPv6PacketTooBig, + hopLimit: arbitraryHopLimit, + size: header.ICMPv6PacketTooBigMinimumSize, }, { - typ: header.ICMPv6TimeExceeded, - size: header.ICMPv6MinimumSize, + typ: header.ICMPv6TimeExceeded, + hopLimit: arbitraryHopLimit, + size: header.ICMPv6MinimumSize, }, { - typ: header.ICMPv6ParamProblem, - size: header.ICMPv6MinimumSize, + typ: header.ICMPv6ParamProblem, + hopLimit: arbitraryHopLimit, + size: header.ICMPv6MinimumSize, }, { - typ: header.ICMPv6EchoRequest, - size: header.ICMPv6EchoMinimumSize, + typ: header.ICMPv6EchoRequest, + hopLimit: arbitraryHopLimit, + size: header.ICMPv6EchoMinimumSize, }, { - typ: header.ICMPv6EchoReply, - size: header.ICMPv6EchoMinimumSize, + typ: header.ICMPv6EchoReply, + hopLimit: arbitraryHopLimit, + size: header.ICMPv6EchoMinimumSize, }, { - typ: header.ICMPv6RouterSolicit, - size: header.ICMPv6MinimumSize, + typ: header.ICMPv6RouterSolicit, + hopLimit: header.NDPHopLimit, + size: header.ICMPv6MinimumSize, }, { - typ: header.ICMPv6RouterAdvert, - size: header.ICMPv6HeaderSize + header.NDPRAMinimumSize, + typ: header.ICMPv6RouterAdvert, + hopLimit: header.NDPHopLimit, + size: header.ICMPv6HeaderSize + header.NDPRAMinimumSize, }, { - typ: header.ICMPv6NeighborSolicit, - size: header.ICMPv6NeighborSolicitMinimumSize, + typ: header.ICMPv6NeighborSolicit, + hopLimit: header.NDPHopLimit, + size: header.ICMPv6NeighborSolicitMinimumSize, }, { typ: header.ICMPv6NeighborAdvert, + hopLimit: header.NDPHopLimit, size: header.ICMPv6NeighborAdvertMinimumSize, extraData: tllData[:], }, { - typ: header.ICMPv6RedirectMsg, - size: header.ICMPv6MinimumSize, + typ: header.ICMPv6RedirectMsg, + hopLimit: header.NDPHopLimit, + size: header.ICMPv6MinimumSize, }, { - typ: header.ICMPv6MulticastListenerQuery, - size: header.MLDMinimumSize + header.ICMPv6HeaderSize, + typ: header.ICMPv6MulticastListenerQuery, + hopLimit: header.MLDHopLimit, + includeRouterAlert: true, + size: header.MLDMinimumSize + header.ICMPv6HeaderSize, }, { - typ: header.ICMPv6MulticastListenerReport, - size: header.MLDMinimumSize + header.ICMPv6HeaderSize, + typ: header.ICMPv6MulticastListenerReport, + hopLimit: header.MLDHopLimit, + includeRouterAlert: true, + size: header.MLDMinimumSize + header.ICMPv6HeaderSize, }, { - typ: header.ICMPv6MulticastListenerDone, - size: header.MLDMinimumSize + header.ICMPv6HeaderSize, + typ: header.ICMPv6MulticastListenerDone, + hopLimit: header.MLDHopLimit, + includeRouterAlert: true, + size: header.MLDMinimumSize + header.ICMPv6HeaderSize, }, { typ: 255, /* Unrecognized */ @@ -295,12 +325,12 @@ func TestICMPCounts(t *testing.T) { copy(icmp[typ.size:], typ.extraData) icmp.SetType(typ.typ) icmp.SetChecksum(header.ICMPv6Checksum(icmp[:typ.size], lladdr0, lladdr1, buffer.View(typ.extraData).ToVectorisedView())) - handleICMPInIPv6(ep, lladdr1, lladdr0, icmp) + handleICMPInIPv6(ep, lladdr1, lladdr0, icmp, typ.hopLimit, typ.includeRouterAlert) } // Construct an empty ICMP packet so that // Stats().ICMP.ICMPv6ReceivedPacketStats.Invalid is incremented. - handleICMPInIPv6(ep, lladdr1, lladdr0, header.ICMPv6(buffer.NewView(header.IPv6MinimumSize))) + handleICMPInIPv6(ep, lladdr1, lladdr0, header.ICMPv6(buffer.NewView(header.IPv6MinimumSize)), arbitraryHopLimit, false) icmpv6Stats := s.Stats().ICMP.V6.PacketsReceived visitStats(reflect.ValueOf(&icmpv6Stats).Elem(), func(name string, s *tcpip.StatCounter) { @@ -1632,7 +1662,7 @@ func TestCallsToNeighborCache(t *testing.T) { icmp := test.createPacket() icmp.SetChecksum(header.ICMPv6Checksum(icmp, test.source, test.destination, buffer.VectorisedView{})) - handleICMPInIPv6(ep, test.source, test.destination, icmp) + handleICMPInIPv6(ep, test.source, test.destination, icmp, header.NDPHopLimit, false) // Confirm the endpoint calls the correct NUDHandler method. if testInterface.probeCount != test.wantProbeCount { diff --git a/pkg/tcpip/network/ipv6/ipv6.go b/pkg/tcpip/network/ipv6/ipv6.go index b16a2d322..7638ade35 100644 --- a/pkg/tcpip/network/ipv6/ipv6.go +++ b/pkg/tcpip/network/ipv6/ipv6.go @@ -1001,7 +1001,6 @@ func (e *endpoint) handlePacket(pkt *stack.PacketBuffer) { vv.AppendView(pkt.TransportHeader().View()) vv.Append(pkt.Data) it := header.MakeIPv6PayloadIterator(header.IPv6ExtensionHeaderIdentifier(h.NextHeader()), vv) - hasFragmentHeader := false // iptables filtering. All packets that reach here are intended for // this machine and need not be forwarded. @@ -1012,6 +1011,11 @@ func (e *endpoint) handlePacket(pkt *stack.PacketBuffer) { return } + var ( + hasFragmentHeader bool + routerAlert *header.IPv6RouterAlertOption + ) + for { // Keep track of the start of the previous header so we can report the // special case of a Hop by Hop at a location other than at the start. @@ -1049,34 +1053,46 @@ func (e *endpoint) handlePacket(pkt *stack.PacketBuffer) { break } - // We currently do not support any IPv6 Hop By Hop extension header - // options. - switch opt.UnknownAction() { - case header.IPv6OptionUnknownActionSkip: - case header.IPv6OptionUnknownActionDiscard: - return - case header.IPv6OptionUnknownActionDiscardSendICMPNoMulticastDest: - if header.IsV6MulticastAddress(dstAddr) { + switch opt := opt.(type) { + case *header.IPv6RouterAlertOption: + if routerAlert != nil { + // As per RFC 2711 section 3, there should be at most one Router + // Alert option per packet. + // + // There MUST only be one option of this type, regardless of + // value, per Hop-by-Hop header. + stats.MalformedPacketsReceived.Increment() return } - fallthrough - case header.IPv6OptionUnknownActionDiscardSendICMP: - // This case satisfies a requirement of RFC 8200 section 4.2 - // which states that an unknown option starting with bits [10] should: - // - // discard the packet and, regardless of whether or not the - // packet's Destination Address was a multicast address, send an - // ICMP Parameter Problem, Code 2, message to the packet's - // Source Address, pointing to the unrecognized Option Type. - // - _ = e.protocol.returnError(&icmpReasonParameterProblem{ - code: header.ICMPv6UnknownOption, - pointer: it.ParseOffset() + optsIt.OptionOffset(), - respondToMulticast: true, - }, pkt) - return + routerAlert = opt + stats.OptionRouterAlertReceived.Increment() default: - panic(fmt.Sprintf("unrecognized action for an unrecognized Hop By Hop extension header option = %d", opt)) + switch opt.UnknownAction() { + case header.IPv6OptionUnknownActionSkip: + case header.IPv6OptionUnknownActionDiscard: + return + case header.IPv6OptionUnknownActionDiscardSendICMPNoMulticastDest: + if header.IsV6MulticastAddress(dstAddr) { + return + } + fallthrough + case header.IPv6OptionUnknownActionDiscardSendICMP: + // This case satisfies a requirement of RFC 8200 section 4.2 which + // states that an unknown option starting with bits [10] should: + // + // discard the packet and, regardless of whether or not the + // packet's Destination Address was a multicast address, send an + // ICMP Parameter Problem, Code 2, message to the packet's + // Source Address, pointing to the unrecognized Option Type. + _ = e.protocol.returnError(&icmpReasonParameterProblem{ + code: header.ICMPv6UnknownOption, + pointer: it.ParseOffset() + optsIt.OptionOffset(), + respondToMulticast: true, + }, pkt) + return + default: + panic(fmt.Sprintf("unrecognized action for an unrecognized Hop By Hop extension header option = %d", opt)) + } } } @@ -1303,7 +1319,7 @@ func (e *endpoint) handlePacket(pkt *stack.PacketBuffer) { stats.PacketsDelivered.Increment() if p := tcpip.TransportProtocolNumber(extHdr.Identifier); p == header.ICMPv6ProtocolNumber { pkt.TransportProtocolNumber = p - e.handleICMP(pkt, hasFragmentHeader) + e.handleICMP(pkt, hasFragmentHeader, routerAlert) } else { stats.PacketsDelivered.Increment() switch res := e.dispatcher.DeliverTransportPacket(p, pkt); res { diff --git a/pkg/tcpip/network/ipv6/ipv6_test.go b/pkg/tcpip/network/ipv6/ipv6_test.go index 7e714b50e..05c9f4dbf 100644 --- a/pkg/tcpip/network/ipv6/ipv6_test.go +++ b/pkg/tcpip/network/ipv6/ipv6_test.go @@ -382,9 +382,10 @@ func TestAddIpv6Address(t *testing.T) { func TestReceiveIPv6ExtHdrs(t *testing.T) { tests := []struct { - name string - extHdr func(nextHdr uint8) ([]byte, uint8) - shouldAccept bool + name string + extHdr func(nextHdr uint8) ([]byte, uint8) + shouldAccept bool + countersToBeIncremented func(*tcpip.Stats) []*tcpip.StatCounter // Should we expect an ICMP response and if so, with what contents? expectICMP bool ICMPType header.ICMPv6Type @@ -399,6 +400,42 @@ func TestReceiveIPv6ExtHdrs(t *testing.T) { expectICMP: false, }, { + name: "hopbyhop with router alert option", + extHdr: func(nextHdr uint8) ([]byte, uint8) { + return []byte{ + nextHdr, 0, + + // Router Alert option. + 5, 2, 0, 0, 0, 0, + }, hopByHopExtHdrID + }, + shouldAccept: true, + countersToBeIncremented: func(stats *tcpip.Stats) []*tcpip.StatCounter { + return []*tcpip.StatCounter{stats.IP.OptionRouterAlertReceived} + }, + }, + { + name: "hopbyhop with two router alert options", + extHdr: func(nextHdr uint8) ([]byte, uint8) { + return []byte{ + nextHdr, 1, + + // Router Alert option. + 5, 2, 0, 0, 0, 0, + + // Router Alert option. + 5, 2, 0, 0, 0, 0, 0, 0, + }, hopByHopExtHdrID + }, + shouldAccept: false, + countersToBeIncremented: func(stats *tcpip.Stats) []*tcpip.StatCounter { + return []*tcpip.StatCounter{ + stats.IP.OptionRouterAlertReceived, + stats.IP.MalformedPacketsReceived, + } + }, + }, + { name: "hopbyhop with unknown option skippable action", extHdr: func(nextHdr uint8) ([]byte, uint8) { return []byte{ @@ -924,14 +961,32 @@ func TestReceiveIPv6ExtHdrs(t *testing.T) { DstAddr: dstAddr, }) + stats := s.Stats() + var counters []*tcpip.StatCounter + // Make sure that the counters we expect to be incremented are initially + // set to zero. + if fn := test.countersToBeIncremented; fn != nil { + counters = fn(&stats) + } + for i := range counters { + if got := counters[i].Value(); got != 0 { + t.Errorf("before writing packet: got test.countersToBeIncremented(&stats)[%d].Value() = %d, want = 0", i, got) + } + } + e.InjectInbound(ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{ Data: hdr.View().ToVectorisedView(), })) - stats := s.Stats().UDP.PacketsReceived + for i := range counters { + if got := counters[i].Value(); got != 1 { + t.Errorf("after writing packet: got test.countersToBeIncremented(&stats)[%d].Value() = %d, want = 1", i, got) + } + } + udpReceiveStat := stats.UDP.PacketsReceived if !test.shouldAccept { - if got := stats.Value(); got != 0 { + if got := udpReceiveStat.Value(); got != 0 { t.Errorf("got UDP Rx Packets = %d, want = 0", got) } @@ -977,7 +1032,7 @@ func TestReceiveIPv6ExtHdrs(t *testing.T) { } // Expect a UDP packet. - if got := stats.Value(); got != 1 { + if got := udpReceiveStat.Value(); got != 1 { t.Errorf("got UDP Rx Packets = %d, want = 1", got) } var buf bytes.Buffer diff --git a/pkg/tcpip/network/ipv6/mld_test.go b/pkg/tcpip/network/ipv6/mld_test.go index fe39555e0..f1b8d58f2 100644 --- a/pkg/tcpip/network/ipv6/mld_test.go +++ b/pkg/tcpip/network/ipv6/mld_test.go @@ -48,8 +48,7 @@ func validateMLDPacket(t *testing.T, p buffer.View, localAddress, remoteAddress ), checker.SrcAddr(localAddress), checker.DstAddr(remoteAddress), - // Hop Limit for an MLD message must be 1 as per RFC 2710 section 3. - checker.TTL(1), + checker.TTL(header.MLDHopLimit), checker.MLD(mldType, header.MLDMinimumSize, checker.MLDMaxRespDelay(0), checker.MLDMulticastAddress(groupAddress), @@ -195,7 +194,7 @@ func TestSendQueuedMLDReports(t *testing.T) { // Adding a global address should not send reports for the already joined // group since we should only send queued reports when a link-local - // addres sis assigned. + // address is assigned. // // Note, we will still expect to send a report for the global address's // solicited node address from the unspecified address as per RFC 3590 @@ -295,3 +294,153 @@ func TestSendQueuedMLDReports(t *testing.T) { }) } } + +// createAndInjectMLDPacket creates and injects an MLD packet with the +// specified fields. +func createAndInjectMLDPacket(e *channel.Endpoint, mldType header.ICMPv6Type, hopLimit uint8, srcAddress tcpip.Address, withRouterAlertOption bool, routerAlertValue header.IPv6RouterAlertValue) { + var extensionHeaders header.IPv6ExtHdrSerializer + if withRouterAlertOption { + extensionHeaders = header.IPv6ExtHdrSerializer{ + header.IPv6SerializableHopByHopExtHdr{ + &header.IPv6RouterAlertOption{Value: routerAlertValue}, + }, + } + } + + extensionHeadersLength := extensionHeaders.Length() + payloadLength := extensionHeadersLength + header.ICMPv6HeaderSize + header.MLDMinimumSize + buf := buffer.NewView(header.IPv6MinimumSize + payloadLength) + + ip := header.IPv6(buf) + ip.Encode(&header.IPv6Fields{ + PayloadLength: uint16(payloadLength), + HopLimit: hopLimit, + TransportProtocol: header.ICMPv6ProtocolNumber, + SrcAddr: srcAddress, + DstAddr: header.IPv6AllNodesMulticastAddress, + ExtensionHeaders: extensionHeaders, + }) + + icmp := header.ICMPv6(ip.Payload()[extensionHeadersLength:]) + icmp.SetType(mldType) + mld := header.MLD(icmp.MessageBody()) + mld.SetMaximumResponseDelay(0) + mld.SetMulticastAddress(header.IPv6Any) + icmp.SetChecksum(header.ICMPv6Checksum(icmp, srcAddress, header.IPv6AllNodesMulticastAddress, buffer.VectorisedView{})) + + e.InjectInbound(ipv6.ProtocolNumber, &stack.PacketBuffer{ + Data: buf.ToVectorisedView(), + }) +} + +func TestMLDPacketValidation(t *testing.T) { + const ( + nicID = 1 + linkLocalAddr2 = tcpip.Address("\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02") + ) + + tests := []struct { + name string + messageType header.ICMPv6Type + srcAddr tcpip.Address + includeRouterAlertOption bool + routerAlertValue header.IPv6RouterAlertValue + hopLimit uint8 + expectValidMLD bool + getMessageTypeStatValue func(tcpip.Stats) uint64 + }{ + { + name: "valid", + messageType: header.ICMPv6MulticastListenerQuery, + includeRouterAlertOption: true, + routerAlertValue: header.IPv6RouterAlertMLD, + srcAddr: linkLocalAddr2, + hopLimit: header.MLDHopLimit, + expectValidMLD: true, + getMessageTypeStatValue: func(stats tcpip.Stats) uint64 { return stats.ICMP.V6.PacketsReceived.MulticastListenerQuery.Value() }, + }, + { + name: "bad hop limit", + messageType: header.ICMPv6MulticastListenerReport, + includeRouterAlertOption: true, + routerAlertValue: header.IPv6RouterAlertMLD, + srcAddr: linkLocalAddr2, + hopLimit: header.MLDHopLimit + 1, + expectValidMLD: false, + getMessageTypeStatValue: func(stats tcpip.Stats) uint64 { return stats.ICMP.V6.PacketsReceived.MulticastListenerReport.Value() }, + }, + { + name: "src ip not link local", + messageType: header.ICMPv6MulticastListenerReport, + includeRouterAlertOption: true, + routerAlertValue: header.IPv6RouterAlertMLD, + srcAddr: globalAddr, + hopLimit: header.MLDHopLimit, + expectValidMLD: false, + getMessageTypeStatValue: func(stats tcpip.Stats) uint64 { return stats.ICMP.V6.PacketsReceived.MulticastListenerReport.Value() }, + }, + { + name: "missing router alert ip option", + messageType: header.ICMPv6MulticastListenerDone, + includeRouterAlertOption: false, + srcAddr: linkLocalAddr2, + hopLimit: header.MLDHopLimit, + expectValidMLD: false, + getMessageTypeStatValue: func(stats tcpip.Stats) uint64 { return stats.ICMP.V6.PacketsReceived.MulticastListenerDone.Value() }, + }, + { + name: "incorrect router alert value", + messageType: header.ICMPv6MulticastListenerDone, + includeRouterAlertOption: true, + routerAlertValue: header.IPv6RouterAlertRSVP, + srcAddr: linkLocalAddr2, + hopLimit: header.MLDHopLimit, + expectValidMLD: false, + getMessageTypeStatValue: func(stats tcpip.Stats) uint64 { return stats.ICMP.V6.PacketsReceived.MulticastListenerDone.Value() }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + s := stack.New(stack.Options{ + NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ + MLD: ipv6.MLDOptions{ + Enabled: true, + }, + })}, + }) + e := channel.New(nicID, header.IPv6MinimumMTU, "") + if err := s.CreateNIC(nicID, e); err != nil { + t.Fatalf("CreateNIC(%d, _): %s", nicID, err) + } + stats := s.Stats() + // Verify that every relevant stats is zero'd before we send a packet. + if got := test.getMessageTypeStatValue(s.Stats()); got != 0 { + t.Errorf("got test.getMessageTypeStatValue(s.Stats()) = %d, want = 0", got) + } + if got := stats.ICMP.V6.PacketsReceived.Invalid.Value(); got != 0 { + t.Errorf("got stats.ICMP.V6.PacketsReceived.Invalid.Value() = %d, want = 0", got) + } + if got := stats.IP.PacketsDelivered.Value(); got != 0 { + t.Fatalf("got stats.IP.PacketsDelivered.Value() = %d, want = 0", got) + } + createAndInjectMLDPacket(e, test.messageType, test.hopLimit, test.srcAddr, test.includeRouterAlertOption, test.routerAlertValue) + // We always expect the packet to pass IP validation. + if got := stats.IP.PacketsDelivered.Value(); got != 1 { + t.Fatalf("got stats.IP.PacketsDelivered.Value() = %d, want = 1", got) + } + // Even when the MLD-specific validation checks fail, we expect the + // corresponding MLD counter to be incremented. + if got := test.getMessageTypeStatValue(s.Stats()); got != 1 { + t.Errorf("got test.getMessageTypeStatValue(s.Stats()) = %d, want = 1", got) + } + var expectedInvalidCount uint64 + if !test.expectValidMLD { + expectedInvalidCount = 1 + } + if got := stats.ICMP.V6.PacketsReceived.Invalid.Value(); got != expectedInvalidCount { + t.Errorf("got stats.ICMP.V6.PacketsReceived.Invalid.Value() = %d, want = %d", got, expectedInvalidCount) + } + }) + } +} diff --git a/pkg/tcpip/network/multicast_group_test.go b/pkg/tcpip/network/multicast_group_test.go index 03b7ecffb..73913aef8 100644 --- a/pkg/tcpip/network/multicast_group_test.go +++ b/pkg/tcpip/network/multicast_group_test.go @@ -37,7 +37,8 @@ const ( stackIPv4Addr = tcpip.Address("\x0a\x00\x00\x01") defaultIPv4PrefixLength = 24 - ipv6Addr = tcpip.Address("\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01") + linkLocalIPv6Addr1 = tcpip.Address("\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01") + linkLocalIPv6Addr2 = tcpip.Address("\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02") ipv4MulticastAddr1 = tcpip.Address("\xe0\x00\x00\x03") ipv4MulticastAddr2 = tcpip.Address("\xe0\x00\x00\x04") @@ -69,7 +70,7 @@ var ( return uint8(ipv4.UnsolicitedReportIntervalMax / decisecond) }() - ipv6AddrSNMC = header.SolicitedNodeAddr(ipv6Addr) + ipv6AddrSNMC = header.SolicitedNodeAddr(linkLocalIPv6Addr1) ) // validateMLDPacket checks that a passed PacketInfo is an IPv6 MLD packet @@ -82,7 +83,7 @@ func validateMLDPacket(t *testing.T, p channel.PacketInfo, remoteAddress tcpip.A checker.IPv6ExtHdr( checker.IPv6HopByHopExtensionHeader(checker.IPv6RouterAlert(header.IPv6RouterAlertMLD)), ), - checker.SrcAddr(ipv6Addr), + checker.SrcAddr(linkLocalIPv6Addr1), checker.DstAddr(remoteAddress), // Hop Limit for an MLD message must be 1 as per RFC 2710 section 3. checker.TTL(1), @@ -153,8 +154,8 @@ func createStackWithLinkEndpoint(t *testing.T, v4, mgpEnabled bool, e stack.Link if err := s.AddAddressWithPrefix(nicID, ipv4.ProtocolNumber, addr); err != nil { t.Fatalf("AddAddressWithPrefix(%d, %d, %s): %s", nicID, ipv4.ProtocolNumber, addr, err) } - if err := s.AddAddress(nicID, ipv6.ProtocolNumber, ipv6Addr); err != nil { - t.Fatalf("AddAddress(%d, %d, %s): %s", nicID, ipv6.ProtocolNumber, ipv6Addr, err) + if err := s.AddAddress(nicID, ipv6.ProtocolNumber, linkLocalIPv6Addr1); err != nil { + t.Fatalf("AddAddress(%d, %d, %s): %s", nicID, ipv6.ProtocolNumber, linkLocalIPv6Addr1, err) } return s, clock @@ -236,29 +237,33 @@ func createAndInjectIGMPPacket(e *channel.Endpoint, igmpType byte, maxRespTime b // createAndInjectMLDPacket creates and injects an MLD packet with the // specified fields. -// -// Note, the router alert option is not included in this packet. -// -// TODO(b/162198658): set the router alert option. func createAndInjectMLDPacket(e *channel.Endpoint, mldType uint8, maxRespDelay byte, groupAddress tcpip.Address) { - icmpSize := header.ICMPv6HeaderSize + header.MLDMinimumSize - buf := buffer.NewView(header.IPv6MinimumSize + icmpSize) + extensionHeaders := header.IPv6ExtHdrSerializer{ + header.IPv6SerializableHopByHopExtHdr{ + &header.IPv6RouterAlertOption{Value: header.IPv6RouterAlertMLD}, + }, + } + + extensionHeadersLength := extensionHeaders.Length() + payloadLength := extensionHeadersLength + header.ICMPv6HeaderSize + header.MLDMinimumSize + buf := buffer.NewView(header.IPv6MinimumSize + payloadLength) ip := header.IPv6(buf) ip.Encode(&header.IPv6Fields{ - PayloadLength: uint16(icmpSize), + PayloadLength: uint16(payloadLength), HopLimit: header.MLDHopLimit, TransportProtocol: header.ICMPv6ProtocolNumber, - SrcAddr: header.IPv4Any, + SrcAddr: linkLocalIPv6Addr2, DstAddr: header.IPv6AllNodesMulticastAddress, + ExtensionHeaders: extensionHeaders, }) - icmp := header.ICMPv6(buf[header.IPv6MinimumSize:]) + icmp := header.ICMPv6(ip.Payload()[extensionHeadersLength:]) icmp.SetType(header.ICMPv6Type(mldType)) mld := header.MLD(icmp.MessageBody()) mld.SetMaximumResponseDelay(uint16(maxRespDelay)) mld.SetMulticastAddress(groupAddress) - icmp.SetChecksum(header.ICMPv6Checksum(icmp, header.IPv6Any, header.IPv6AllNodesMulticastAddress, buffer.VectorisedView{})) + icmp.SetChecksum(header.ICMPv6Checksum(icmp, linkLocalIPv6Addr2, header.IPv6AllNodesMulticastAddress, buffer.VectorisedView{})) e.InjectInbound(ipv6.ProtocolNumber, &stack.PacketBuffer{ Data: buf.ToVectorisedView(), |