Do not rate limit ICMP Echos by default

As per https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt
linux does not limit ICMP Echos by default.

icmp_ratemask - INTEGER
	Mask made of ICMP types for which rates are being limited.
	Significant bits: IHGFEDCBA9876543210
	Default mask:     0000001100000011000 (6168)

	Bit definitions (see include/linux/icmp.h):
		0 Echo Reply
		3 Destination Unreachable *
		4 Source Quench *
		5 Redirect
		8 Echo Request
		B Time Exceeded *
		C Parameter Problem *
		D Timestamp Request
		E Timestamp Reply
		F Info Request
		G Info Reply
		H Address Mask Request
		I Address Mask Reply

	* These are rate limited by default (see default mask above)

Equivalently for ICMPv6.

Lay out foundation for ICMP rate masks, exposing that configuration will be
addressed later when the need arises (#6521).

Fixes #6519

PiperOrigin-RevId: 398337963
This commit is contained in:
Bruno Dal Bo
2021-09-22 15:07:05 -07:00
committed by gVisor bot
parent 4f67756752
commit 586f147cd6
8 changed files with 420 additions and 93 deletions
+38 -51
View File
@@ -240,12 +240,6 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer) {
case header.ICMPv4Echo:
received.echoRequest.Increment()
sent := e.stats.icmp.packetsSent
if !e.protocol.stack.AllowICMPMessage() {
sent.rateLimited.Increment()
return
}
// DeliverTransportPacket will take ownership of pkt so don't use it beyond
// this point. Make a deep copy of the data before pkt gets sent as we will
// be modifying fields.
@@ -281,6 +275,12 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer) {
}
defer r.Release()
sent := e.stats.icmp.packetsSent
if !e.protocol.allowICMPReply(header.ICMPv4EchoReply, header.ICMPv4UnusedCode) {
sent.rateLimited.Increment()
return
}
// TODO(gvisor.dev/issue/3810:) When adding protocol numbers into the
// header information, we may have to change this code to handle the
// ICMP header no longer being in the data buffer.
@@ -562,13 +562,6 @@ func (p *protocol) returnError(reason icmpReason, pkt *stack.PacketBuffer) tcpip
return &tcpip.ErrNotConnected{}
}
sent := netEP.stats.icmp.packetsSent
if !p.stack.AllowICMPMessage() {
sent.rateLimited.Increment()
return nil
}
transportHeader := pkt.TransportHeader().View()
// Don't respond to icmp error packets.
@@ -606,6 +599,35 @@ func (p *protocol) returnError(reason icmpReason, pkt *stack.PacketBuffer) tcpip
}
}
sent := netEP.stats.icmp.packetsSent
icmpType, icmpCode, counter, pointer := func() (header.ICMPv4Type, header.ICMPv4Code, tcpip.MultiCounterStat, byte) {
switch reason := reason.(type) {
case *icmpReasonPortUnreachable:
return header.ICMPv4DstUnreachable, header.ICMPv4PortUnreachable, sent.dstUnreachable, 0
case *icmpReasonProtoUnreachable:
return header.ICMPv4DstUnreachable, header.ICMPv4ProtoUnreachable, sent.dstUnreachable, 0
case *icmpReasonNetworkUnreachable:
return header.ICMPv4DstUnreachable, header.ICMPv4NetUnreachable, sent.dstUnreachable, 0
case *icmpReasonHostUnreachable:
return header.ICMPv4DstUnreachable, header.ICMPv4HostUnreachable, sent.dstUnreachable, 0
case *icmpReasonFragmentationNeeded:
return header.ICMPv4DstUnreachable, header.ICMPv4FragmentationNeeded, sent.dstUnreachable, 0
case *icmpReasonTTLExceeded:
return header.ICMPv4TimeExceeded, header.ICMPv4TTLExceeded, sent.timeExceeded, 0
case *icmpReasonReassemblyTimeout:
return header.ICMPv4TimeExceeded, header.ICMPv4ReassemblyTimeout, sent.timeExceeded, 0
case *icmpReasonParamProblem:
return header.ICMPv4ParamProblem, header.ICMPv4UnusedCode, sent.paramProblem, reason.pointer
default:
panic(fmt.Sprintf("unsupported ICMP type %T", reason))
}
}()
if !p.allowICMPReply(icmpType, icmpCode) {
sent.rateLimited.Increment()
return nil
}
// Now work out how much of the triggering packet we should return.
// As per RFC 1812 Section 4.3.2.3
//
@@ -658,44 +680,9 @@ func (p *protocol) returnError(reason icmpReason, pkt *stack.PacketBuffer) tcpip
icmpPkt.TransportProtocolNumber = header.ICMPv4ProtocolNumber
icmpHdr := header.ICMPv4(icmpPkt.TransportHeader().Push(header.ICMPv4MinimumSize))
var counter tcpip.MultiCounterStat
switch reason := reason.(type) {
case *icmpReasonPortUnreachable:
icmpHdr.SetType(header.ICMPv4DstUnreachable)
icmpHdr.SetCode(header.ICMPv4PortUnreachable)
counter = sent.dstUnreachable
case *icmpReasonProtoUnreachable:
icmpHdr.SetType(header.ICMPv4DstUnreachable)
icmpHdr.SetCode(header.ICMPv4ProtoUnreachable)
counter = sent.dstUnreachable
case *icmpReasonNetworkUnreachable:
icmpHdr.SetType(header.ICMPv4DstUnreachable)
icmpHdr.SetCode(header.ICMPv4NetUnreachable)
counter = sent.dstUnreachable
case *icmpReasonHostUnreachable:
icmpHdr.SetType(header.ICMPv4DstUnreachable)
icmpHdr.SetCode(header.ICMPv4HostUnreachable)
counter = sent.dstUnreachable
case *icmpReasonFragmentationNeeded:
icmpHdr.SetType(header.ICMPv4DstUnreachable)
icmpHdr.SetCode(header.ICMPv4FragmentationNeeded)
counter = sent.dstUnreachable
case *icmpReasonTTLExceeded:
icmpHdr.SetType(header.ICMPv4TimeExceeded)
icmpHdr.SetCode(header.ICMPv4TTLExceeded)
counter = sent.timeExceeded
case *icmpReasonReassemblyTimeout:
icmpHdr.SetType(header.ICMPv4TimeExceeded)
icmpHdr.SetCode(header.ICMPv4ReassemblyTimeout)
counter = sent.timeExceeded
case *icmpReasonParamProblem:
icmpHdr.SetType(header.ICMPv4ParamProblem)
icmpHdr.SetCode(header.ICMPv4UnusedCode)
icmpHdr.SetPointer(reason.pointer)
counter = sent.paramProblem
default:
panic(fmt.Sprintf("unsupported ICMP type %T", reason))
}
icmpHdr.SetCode(icmpCode)
icmpHdr.SetType(icmpType)
icmpHdr.SetPointer(pointer)
icmpHdr.SetChecksum(header.ICMPv4Checksum(icmpHdr, icmpPkt.Data().AsRange().Checksum()))
if err := route.WritePacket(
+28
View File
@@ -1215,6 +1215,9 @@ type protocol struct {
// eps is keyed by NICID to allow protocol methods to retrieve an endpoint
// when handling a packet, by looking at which NIC handled the packet.
eps map[tcpip.NICID]*endpoint
// ICMP types for which the stack's global rate limiting must apply.
icmpRateLimitedTypes map[header.ICMPv4Type]struct{}
}
// defaultTTL is the current default TTL for the protocol. Only the
@@ -1330,6 +1333,23 @@ func (*protocol) Parse(pkt *stack.PacketBuffer) (proto tcpip.TransportProtocolNu
return ipHdr.TransportProtocol(), !ipHdr.More() && ipHdr.FragmentOffset() == 0, true
}
// allowICMPReply reports whether an ICMP reply with provided type and code may
// be sent following the rate mask options and global ICMP rate limiter.
func (p *protocol) allowICMPReply(icmpType header.ICMPv4Type, code header.ICMPv4Code) bool {
// Mimic linux and never rate limit for PMTU discovery.
// https://github.com/torvalds/linux/blob/9e9fb7655ed585da8f468e29221f0ba194a5f613/net/ipv4/icmp.c#L288
if icmpType == header.ICMPv4DstUnreachable && code == header.ICMPv4FragmentationNeeded {
return true
}
p.mu.RLock()
defer p.mu.RUnlock()
if _, ok := p.mu.icmpRateLimitedTypes[icmpType]; ok {
return p.stack.AllowICMPMessage()
}
return true
}
// calculateNetworkMTU calculates the network-layer payload MTU based on the
// link-layer payload mtu.
func calculateNetworkMTU(linkMTU, networkHeaderSize uint32) (uint32, tcpip.Error) {
@@ -1409,6 +1429,14 @@ func NewProtocolWithOptions(opts Options) stack.NetworkProtocolFactory {
}
p.fragmentation = fragmentation.NewFragmentation(fragmentblockSize, fragmentation.HighFragThreshold, fragmentation.LowFragThreshold, ReassembleTimeout, s.Clock(), p)
p.mu.eps = make(map[tcpip.NICID]*endpoint)
// Set ICMP rate limiting to Linux defaults.
// See https://man7.org/linux/man-pages/man7/icmp.7.html.
p.mu.icmpRateLimitedTypes = map[header.ICMPv4Type]struct{}{
header.ICMPv4DstUnreachable: struct{}{},
header.ICMPv4SrcQuench: struct{}{},
header.ICMPv4TimeExceeded: struct{}{},
header.ICMPv4ParamProblem: struct{}{},
}
return p
}
}
+136
View File
@@ -3373,3 +3373,139 @@ func TestCloseLocking(t *testing.T) {
}
}()
}
func TestIcmpRateLimit(t *testing.T) {
var (
host1IPv4Addr = tcpip.ProtocolAddress{
Protocol: ipv4.ProtocolNumber,
AddressWithPrefix: tcpip.AddressWithPrefix{
Address: tcpip.Address(net.ParseIP("192.168.0.1").To4()),
PrefixLen: 24,
},
}
host2IPv4Addr = tcpip.ProtocolAddress{
Protocol: ipv4.ProtocolNumber,
AddressWithPrefix: tcpip.AddressWithPrefix{
Address: tcpip.Address(net.ParseIP("192.168.0.2").To4()),
PrefixLen: 24,
},
}
)
const icmpBurst = 5
e := channel.New(1, defaultMTU, tcpip.LinkAddress(""))
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{arp.NewProtocol, ipv4.NewProtocol},
TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol},
Clock: faketime.NewManualClock(),
})
s.SetICMPBurst(icmpBurst)
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("s.CreateNIC(%d, _): %s", nicID, err)
}
if err := s.AddProtocolAddress(nicID, host1IPv4Addr, stack.AddressProperties{}); err != nil {
t.Fatalf("s.AddProtocolAddress(%d, %+v, {}): %s", nicID, host1IPv4Addr, err)
}
s.SetRouteTable([]tcpip.Route{
{
Destination: host1IPv4Addr.AddressWithPrefix.Subnet(),
NIC: nicID,
},
})
tests := []struct {
name string
createPacket func() buffer.View
check func(*testing.T, *channel.Endpoint, int)
}{
{
name: "echo",
createPacket: func() buffer.View {
totalLength := header.IPv4MinimumSize + header.ICMPv4MinimumSize
hdr := buffer.NewPrependable(totalLength)
icmpH := header.ICMPv4(hdr.Prepend(header.ICMPv4MinimumSize))
icmpH.SetIdent(1)
icmpH.SetSequence(1)
icmpH.SetType(header.ICMPv4Echo)
icmpH.SetCode(header.ICMPv4UnusedCode)
icmpH.SetChecksum(0)
icmpH.SetChecksum(^header.Checksum(icmpH, 0))
ip := header.IPv4(hdr.Prepend(header.IPv4MinimumSize))
ip.Encode(&header.IPv4Fields{
TotalLength: uint16(totalLength),
Protocol: uint8(header.ICMPv4ProtocolNumber),
TTL: 1,
SrcAddr: host2IPv4Addr.AddressWithPrefix.Address,
DstAddr: host1IPv4Addr.AddressWithPrefix.Address,
})
ip.SetChecksum(^ip.CalculateChecksum())
return hdr.View()
},
check: func(t *testing.T, e *channel.Endpoint, round int) {
p, ok := e.Read()
if !ok {
t.Fatalf("expected echo response, no packet read in endpoint in round %d", round)
}
if got, want := p.Proto, header.IPv4ProtocolNumber; got != want {
t.Errorf("got p.Proto = %d, want = %d", got, want)
}
checker.IPv4(t, stack.PayloadSince(p.Pkt.NetworkHeader()),
checker.SrcAddr(host1IPv4Addr.AddressWithPrefix.Address),
checker.DstAddr(host2IPv4Addr.AddressWithPrefix.Address),
checker.ICMPv4(
checker.ICMPv4Type(header.ICMPv4EchoReply),
))
},
},
{
name: "dst unreachable",
createPacket: func() buffer.View {
totalLength := header.IPv4MinimumSize + header.UDPMinimumSize
hdr := buffer.NewPrependable(totalLength)
udpH := header.UDP(hdr.Prepend(header.UDPMinimumSize))
udpH.Encode(&header.UDPFields{
SrcPort: 100,
DstPort: 101,
Length: header.UDPMinimumSize,
})
ip := header.IPv4(hdr.Prepend(header.IPv4MinimumSize))
ip.Encode(&header.IPv4Fields{
TotalLength: uint16(totalLength),
Protocol: uint8(header.UDPProtocolNumber),
TTL: 1,
SrcAddr: host2IPv4Addr.AddressWithPrefix.Address,
DstAddr: host1IPv4Addr.AddressWithPrefix.Address,
})
ip.SetChecksum(^ip.CalculateChecksum())
return hdr.View()
},
check: func(t *testing.T, e *channel.Endpoint, round int) {
p, ok := e.Read()
if round >= icmpBurst {
if ok {
t.Errorf("got packet %x in round %d, expected ICMP rate limit to stop it", p.Pkt.Data().Views(), round)
}
return
}
if !ok {
t.Fatalf("expected unreachable in round %d, no packet read in endpoint", round)
}
checker.IPv4(t, stack.PayloadSince(p.Pkt.NetworkHeader()),
checker.SrcAddr(host1IPv4Addr.AddressWithPrefix.Address),
checker.DstAddr(host2IPv4Addr.AddressWithPrefix.Address),
checker.ICMPv4(
checker.ICMPv4Type(header.ICMPv4DstUnreachable),
))
},
},
}
for _, testCase := range tests {
t.Run(testCase.name, func(t *testing.T) {
for round := 0; round < icmpBurst+1; round++ {
e.InjectInbound(header.IPv4ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
Data: testCase.createPacket().ToVectorisedView(),
}))
testCase.check(t, e, round)
}
})
}
}
+1
View File
@@ -51,6 +51,7 @@ go_test(
"//pkg/tcpip/transport/udp",
"//pkg/waiter",
"@com_github_google_go_cmp//cmp:go_default_library",
"@org_golang_x_time//rate:go_default_library",
],
)
+36 -41
View File
@@ -692,6 +692,11 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer, hasFragmentHeader bool, r
}
defer r.Release()
if !e.protocol.allowICMPReply(header.ICMPv6EchoReply) {
sent.rateLimited.Increment()
return
}
replyPkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
ReserveHeaderBytes: int(r.MaxHeaderLength()) + header.ICMPv6EchoMinimumSize,
Data: pkt.Data().ExtractVV(),
@@ -1174,13 +1179,6 @@ func (p *protocol) returnError(reason icmpReason, pkt *stack.PacketBuffer) tcpip
return &tcpip.ErrNotConnected{}
}
sent := netEP.stats.icmp.packetsSent
if !p.stack.AllowICMPMessage() {
sent.rateLimited.Increment()
return nil
}
if pkt.TransportProtocolNumber == header.ICMPv6ProtocolNumber {
// TODO(gvisor.dev/issues/3810): Sort this out when ICMP headers are stored.
// Unfortunately at this time ICMP Packets do not have a transport
@@ -1198,6 +1196,33 @@ func (p *protocol) returnError(reason icmpReason, pkt *stack.PacketBuffer) tcpip
}
}
sent := netEP.stats.icmp.packetsSent
icmpType, icmpCode, counter, typeSpecific := func() (header.ICMPv6Type, header.ICMPv6Code, tcpip.MultiCounterStat, uint32) {
switch reason := reason.(type) {
case *icmpReasonParameterProblem:
return header.ICMPv6ParamProblem, reason.code, sent.paramProblem, reason.pointer
case *icmpReasonPortUnreachable:
return header.ICMPv6DstUnreachable, header.ICMPv6PortUnreachable, sent.dstUnreachable, 0
case *icmpReasonNetUnreachable:
return header.ICMPv6DstUnreachable, header.ICMPv6NetworkUnreachable, sent.dstUnreachable, 0
case *icmpReasonHostUnreachable:
return header.ICMPv6DstUnreachable, header.ICMPv6AddressUnreachable, sent.dstUnreachable, 0
case *icmpReasonPacketTooBig:
return header.ICMPv6PacketTooBig, header.ICMPv6UnusedCode, sent.packetTooBig, 0
case *icmpReasonHopLimitExceeded:
return header.ICMPv6TimeExceeded, header.ICMPv6HopLimitExceeded, sent.timeExceeded, 0
case *icmpReasonReassemblyTimeout:
return header.ICMPv6TimeExceeded, header.ICMPv6ReassemblyTimeout, sent.timeExceeded, 0
default:
panic(fmt.Sprintf("unsupported ICMP type %T", reason))
}
}()
if !p.allowICMPReply(icmpType) {
sent.rateLimited.Increment()
return nil
}
network, transport := pkt.NetworkHeader().View(), pkt.TransportHeader().View()
// As per RFC 4443 section 2.4
@@ -1232,40 +1257,10 @@ func (p *protocol) returnError(reason icmpReason, pkt *stack.PacketBuffer) tcpip
newPkt.TransportProtocolNumber = header.ICMPv6ProtocolNumber
icmpHdr := header.ICMPv6(newPkt.TransportHeader().Push(header.ICMPv6DstUnreachableMinimumSize))
var counter tcpip.MultiCounterStat
switch reason := reason.(type) {
case *icmpReasonParameterProblem:
icmpHdr.SetType(header.ICMPv6ParamProblem)
icmpHdr.SetCode(reason.code)
icmpHdr.SetTypeSpecific(reason.pointer)
counter = sent.paramProblem
case *icmpReasonPortUnreachable:
icmpHdr.SetType(header.ICMPv6DstUnreachable)
icmpHdr.SetCode(header.ICMPv6PortUnreachable)
counter = sent.dstUnreachable
case *icmpReasonNetUnreachable:
icmpHdr.SetType(header.ICMPv6DstUnreachable)
icmpHdr.SetCode(header.ICMPv6NetworkUnreachable)
counter = sent.dstUnreachable
case *icmpReasonHostUnreachable:
icmpHdr.SetType(header.ICMPv6DstUnreachable)
icmpHdr.SetCode(header.ICMPv6AddressUnreachable)
counter = sent.dstUnreachable
case *icmpReasonPacketTooBig:
icmpHdr.SetType(header.ICMPv6PacketTooBig)
icmpHdr.SetCode(header.ICMPv6UnusedCode)
counter = sent.packetTooBig
case *icmpReasonHopLimitExceeded:
icmpHdr.SetType(header.ICMPv6TimeExceeded)
icmpHdr.SetCode(header.ICMPv6HopLimitExceeded)
counter = sent.timeExceeded
case *icmpReasonReassemblyTimeout:
icmpHdr.SetType(header.ICMPv6TimeExceeded)
icmpHdr.SetCode(header.ICMPv6ReassemblyTimeout)
counter = sent.timeExceeded
default:
panic(fmt.Sprintf("unsupported ICMP type %T", reason))
}
icmpHdr.SetType(icmpType)
icmpHdr.SetCode(icmpCode)
icmpHdr.SetTypeSpecific(typeSpecific)
dataRange := newPkt.Data().AsRange()
icmpHdr.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
Header: icmpHdr,
+3
View File
@@ -22,6 +22,7 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
"golang.org/x/time/rate"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/buffer"
"gvisor.dev/gvisor/pkg/tcpip/checker"
@@ -1435,6 +1436,8 @@ func TestPacketQueing(t *testing.T) {
TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol},
Clock: clock,
})
// Make sure ICMP rate limiting doesn't get in our way.
s.SetICMPLimit(rate.Inf)
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("s.CreateNIC(%d, _): %s", nicID, err)
+32 -1
View File
@@ -1990,6 +1990,9 @@ type protocol struct {
// eps is keyed by NICID to allow protocol methods to retrieve an endpoint
// when handling a packet, by looking at which NIC handled the packet.
eps map[tcpip.NICID]*endpoint
// ICMP types for which the stack's global rate limiting must apply.
icmpRateLimitedTypes map[header.ICMPv6Type]struct{}
}
ids []uint32
@@ -2001,7 +2004,8 @@ type protocol struct {
// Must be accessed using atomic operations.
defaultTTL uint32
fragmentation *fragmentation.Fragmentation
fragmentation *fragmentation.Fragmentation
icmpRateLimiter *stack.ICMPRateLimiter
}
// Number returns the ipv6 protocol number.
@@ -2177,6 +2181,18 @@ func (*protocol) Parse(pkt *stack.PacketBuffer) (proto tcpip.TransportProtocolNu
return proto, !fragMore && fragOffset == 0, true
}
// allowICMPReply reports whether an ICMP reply with provided type may
// be sent following the rate mask options and global ICMP rate limiter.
func (p *protocol) allowICMPReply(icmpType header.ICMPv6Type) bool {
p.mu.RLock()
defer p.mu.RUnlock()
if _, ok := p.mu.icmpRateLimitedTypes[icmpType]; ok {
return p.stack.AllowICMPMessage()
}
return true
}
// calculateNetworkMTU calculates the network-layer payload MTU based on the
// link-layer payload MTU and the length of every IPv6 header.
// Note that this is different than the Payload Length field of the IPv6 header,
@@ -2273,6 +2289,21 @@ func NewProtocolWithOptions(opts Options) stack.NetworkProtocolFactory {
p.fragmentation = fragmentation.NewFragmentation(header.IPv6FragmentExtHdrFragmentOffsetBytesPerUnit, fragmentation.HighFragThreshold, fragmentation.LowFragThreshold, ReassembleTimeout, s.Clock(), p)
p.mu.eps = make(map[tcpip.NICID]*endpoint)
p.SetDefaultTTL(DefaultTTL)
// Set default ICMP rate limiting to Linux defaults.
//
// Default: 0-1,3-127 (rate limit ICMPv6 errors except Packet Too Big)
// See https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt.
defaultIcmpTypes := make(map[header.ICMPv6Type]struct{})
for i := header.ICMPv6Type(0); i < header.ICMPv6EchoRequest; i++ {
switch i {
case header.ICMPv6PacketTooBig:
// Do not rate limit packet too big by default.
default:
defaultIcmpTypes[i] = struct{}{}
}
}
p.mu.icmpRateLimitedTypes = defaultIcmpTypes
return p
}
}
+146
View File
@@ -3522,3 +3522,149 @@ func TestMultiCounterStatsInitialization(t *testing.T) {
t.Error(err)
}
}
func TestIcmpRateLimit(t *testing.T) {
var (
host1IPv6Addr = tcpip.ProtocolAddress{
Protocol: ProtocolNumber,
AddressWithPrefix: tcpip.AddressWithPrefix{
Address: tcpip.Address(net.ParseIP("10::1").To16()),
PrefixLen: 64,
},
}
host2IPv6Addr = tcpip.ProtocolAddress{
Protocol: ProtocolNumber,
AddressWithPrefix: tcpip.AddressWithPrefix{
Address: tcpip.Address(net.ParseIP("10::2").To16()),
PrefixLen: 64,
},
}
)
const icmpBurst = 5
e := channel.New(1, defaultMTU, tcpip.LinkAddress(""))
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocol},
TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol},
Clock: faketime.NewManualClock(),
})
s.SetICMPBurst(icmpBurst)
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("s.CreateNIC(%d, _): %s", nicID, err)
}
if err := s.AddProtocolAddress(nicID, host1IPv6Addr, stack.AddressProperties{}); err != nil {
t.Fatalf("s.AddProtocolAddress(%d, %+v, {}): %s", nicID, host1IPv6Addr, err)
}
s.SetRouteTable([]tcpip.Route{
{
Destination: host1IPv6Addr.AddressWithPrefix.Subnet(),
NIC: nicID,
},
})
tests := []struct {
name string
createPacket func() buffer.View
check func(*testing.T, *channel.Endpoint, int)
}{
{
name: "echo",
createPacket: func() buffer.View {
totalLength := header.IPv6MinimumSize + header.ICMPv6MinimumSize
hdr := buffer.NewPrependable(totalLength)
icmpH := header.ICMPv6(hdr.Prepend(header.ICMPv6MinimumSize))
icmpH.SetIdent(1)
icmpH.SetSequence(1)
icmpH.SetType(header.ICMPv6EchoRequest)
icmpH.SetCode(header.ICMPv6UnusedCode)
icmpH.SetChecksum(0)
icmpH.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
Header: icmpH,
Src: host2IPv6Addr.AddressWithPrefix.Address,
Dst: host1IPv6Addr.AddressWithPrefix.Address,
}))
payloadLength := hdr.UsedLength()
ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
ip.Encode(&header.IPv6Fields{
PayloadLength: uint16(payloadLength),
TransportProtocol: header.ICMPv6ProtocolNumber,
HopLimit: 1,
SrcAddr: host2IPv6Addr.AddressWithPrefix.Address,
DstAddr: host1IPv6Addr.AddressWithPrefix.Address,
})
return hdr.View()
},
check: func(t *testing.T, e *channel.Endpoint, round int) {
p, ok := e.Read()
if !ok {
t.Fatalf("expected echo response, no packet read in endpoint in round %d", round)
}
if got, want := p.Proto, header.IPv6ProtocolNumber; got != want {
t.Errorf("got p.Proto = %d, want = %d", got, want)
}
checker.IPv6(t, stack.PayloadSince(p.Pkt.NetworkHeader()),
checker.SrcAddr(host1IPv6Addr.AddressWithPrefix.Address),
checker.DstAddr(host2IPv6Addr.AddressWithPrefix.Address),
checker.ICMPv6(
checker.ICMPv6Type(header.ICMPv6EchoReply),
))
},
},
{
name: "dst unreachable",
createPacket: func() buffer.View {
totalLength := header.IPv6MinimumSize + header.UDPMinimumSize
hdr := buffer.NewPrependable(totalLength)
udpH := header.UDP(hdr.Prepend(header.UDPMinimumSize))
udpH.Encode(&header.UDPFields{
SrcPort: 100,
DstPort: 101,
Length: header.UDPMinimumSize,
})
// Calculate the UDP checksum and set it.
sum := header.PseudoHeaderChecksum(udp.ProtocolNumber, host2IPv6Addr.AddressWithPrefix.Address, host1IPv6Addr.AddressWithPrefix.Address, header.UDPMinimumSize)
sum = header.Checksum(nil, sum)
udpH.SetChecksum(^udpH.CalculateChecksum(sum))
payloadLength := hdr.UsedLength()
ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
ip.Encode(&header.IPv6Fields{
PayloadLength: uint16(payloadLength),
TransportProtocol: header.UDPProtocolNumber,
HopLimit: 1,
SrcAddr: host2IPv6Addr.AddressWithPrefix.Address,
DstAddr: host1IPv6Addr.AddressWithPrefix.Address,
})
return hdr.View()
},
check: func(t *testing.T, e *channel.Endpoint, round int) {
p, ok := e.Read()
if round >= icmpBurst {
if ok {
t.Errorf("got packet %x in round %d, expected ICMP rate limit to stop it", p.Pkt.Data().Views(), round)
}
return
}
if !ok {
t.Fatalf("expected unreachable in round %d, no packet read in endpoint", round)
}
checker.IPv6(t, stack.PayloadSince(p.Pkt.NetworkHeader()),
checker.SrcAddr(host1IPv6Addr.AddressWithPrefix.Address),
checker.DstAddr(host2IPv6Addr.AddressWithPrefix.Address),
checker.ICMPv6(
checker.ICMPv6Type(header.ICMPv6DstUnreachable),
))
},
},
}
for _, testCase := range tests {
t.Run(testCase.name, func(t *testing.T) {
for round := 0; round < icmpBurst+1; round++ {
e.InjectInbound(header.IPv6ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
Data: testCase.createPacket().ToVectorisedView(),
}))
testCase.check(t, e, round)
}
})
}
}