diff options
author | Bert Muthalaly <stijlist@google.com> | 2019-03-28 14:08:11 -0700 |
---|---|---|
committer | Shentubot <shentubot@google.com> | 2019-03-28 14:09:20 -0700 |
commit | f2e5dcf21c270d5d56da63e03ed204845e192e56 (patch) | |
tree | 86d0e4ad18a31519eb9bf2cd06db04abea2e5a40 | |
parent | e373d3642e95768229f0414fe164beeb13170817 (diff) |
Add ICMP stats
PiperOrigin-RevId: 240848882
Change-Id: I23dd4599f073263437aeab357c3f767e1a432b82
-rw-r--r-- | pkg/sentry/socket/epsocket/epsocket.go | 66 | ||||
-rw-r--r-- | pkg/tcpip/network/ip_test.go | 15 | ||||
-rw-r--r-- | pkg/tcpip/network/ipv4/icmp.go | 45 | ||||
-rw-r--r-- | pkg/tcpip/network/ipv6/icmp.go | 58 | ||||
-rw-r--r-- | pkg/tcpip/network/ipv6/icmp_test.go | 251 | ||||
-rw-r--r-- | pkg/tcpip/tcpip.go | 169 |
6 files changed, 519 insertions, 85 deletions
diff --git a/pkg/sentry/socket/epsocket/epsocket.go b/pkg/sentry/socket/epsocket/epsocket.go index e74bd1bdd..e170da169 100644 --- a/pkg/sentry/socket/epsocket/epsocket.go +++ b/pkg/sentry/socket/epsocket/epsocket.go @@ -66,6 +66,72 @@ var Metrics = tcpip.Stats{ UnknownProtocolRcvdPackets: mustCreateMetric("/netstack/unknown_protocol_received_packets", "Number of packets received by netstack that were for an unknown or unsupported protocol."), MalformedRcvdPackets: mustCreateMetric("/netstack/malformed_received_packets", "Number of packets received by netstack that were deemed malformed."), DroppedPackets: mustCreateMetric("/netstack/dropped_packets", "Number of packets dropped by netstack due to full queues."), + ICMP: tcpip.ICMPStats{ + V4PacketsSent: tcpip.ICMPv4SentPacketStats{ + ICMPv4PacketStats: tcpip.ICMPv4PacketStats{ + Echo: mustCreateMetric("/netstack/icmp/v4/packets_sent/echo", "Total number of ICMPv4 echo packets sent by netstack."), + EchoReply: mustCreateMetric("/netstack/icmp/v4/packets_sent/echo_reply", "Total number of ICMPv4 echo reply packets sent by netstack."), + DstUnreachable: mustCreateMetric("/netstack/icmp/v4/packets_sent/dst_unreachable", "Total number of ICMPv4 destination unreachable packets sent by netstack."), + SrcQuench: mustCreateMetric("/netstack/icmp/v4/packets_sent/src_quench", "Total number of ICMPv4 source quench packets sent by netstack."), + Redirect: mustCreateMetric("/netstack/icmp/v4/packets_sent/redirect", "Total number of ICMPv4 redirect packets sent by netstack."), + TimeExceeded: mustCreateMetric("/netstack/icmp/v4/packets_sent/time_exceeded", "Total number of ICMPv4 time exceeded packets sent by netstack."), + ParamProblem: mustCreateMetric("/netstack/icmp/v4/packets_sent/param_problem", "Total number of ICMPv4 parameter problem packets sent by netstack."), + Timestamp: mustCreateMetric("/netstack/icmp/v4/packets_sent/timestamp", "Total number of ICMPv4 timestamp packets sent by netstack."), + TimestampReply: mustCreateMetric("/netstack/icmp/v4/packets_sent/timestamp_reply", "Total number of ICMPv4 timestamp reply packets sent by netstack."), + InfoRequest: mustCreateMetric("/netstack/icmp/v4/packets_sent/info_request", "Total number of ICMPv4 information request packets sent by netstack."), + InfoReply: mustCreateMetric("/netstack/icmp/v4/packets_sent/info_reply", "Total number of ICMPv4 information reply packets sent by netstack."), + }, + Dropped: mustCreateMetric("/netstack/icmp/v4/packets_sent/dropped", "Total number of ICMPv4 packets dropped by netstack due to link layer errors."), + }, + V4PacketsReceived: tcpip.ICMPv4ReceivedPacketStats{ + ICMPv4PacketStats: tcpip.ICMPv4PacketStats{ + Echo: mustCreateMetric("/netstack/icmp/v4/packets_received/echo", "Total number of ICMPv4 echo packets received by netstack."), + EchoReply: mustCreateMetric("/netstack/icmp/v4/packets_received/echo_reply", "Total number of ICMPv4 echo reply packets received by netstack."), + DstUnreachable: mustCreateMetric("/netstack/icmp/v4/packets_received/dst_unreachable", "Total number of ICMPv4 destination unreachable packets received by netstack."), + SrcQuench: mustCreateMetric("/netstack/icmp/v4/packets_received/src_quench", "Total number of ICMPv4 source quench packets received by netstack."), + Redirect: mustCreateMetric("/netstack/icmp/v4/packets_received/redirect", "Total number of ICMPv4 redirect packets received by netstack."), + TimeExceeded: mustCreateMetric("/netstack/icmp/v4/packets_received/time_exceeded", "Total number of ICMPv4 time exceeded packets received by netstack."), + ParamProblem: mustCreateMetric("/netstack/icmp/v4/packets_received/param_problem", "Total number of ICMPv4 parameter problem packets received by netstack."), + Timestamp: mustCreateMetric("/netstack/icmp/v4/packets_received/timestamp", "Total number of ICMPv4 timestamp packets received by netstack."), + TimestampReply: mustCreateMetric("/netstack/icmp/v4/packets_received/timestamp_reply", "Total number of ICMPv4 timestamp reply packets received by netstack."), + InfoRequest: mustCreateMetric("/netstack/icmp/v4/packets_received/info_request", "Total number of ICMPv4 information request packets received by netstack."), + InfoReply: mustCreateMetric("/netstack/icmp/v4/packets_received/info_reply", "Total number of ICMPv4 information reply packets received by netstack."), + }, + Invalid: mustCreateMetric("/netstack/icmp/v4/packets_received/invalid", "Total number of ICMPv4 packets received that the transport layer could not parse."), + }, + V6PacketsSent: tcpip.ICMPv6SentPacketStats{ + ICMPv6PacketStats: tcpip.ICMPv6PacketStats{ + EchoRequest: mustCreateMetric("/netstack/icmp/v6/packets_sent/echo_request", "Total number of ICMPv6 echo request packets sent by netstack."), + EchoReply: mustCreateMetric("/netstack/icmp/v6/packets_sent/echo_reply", "Total number of ICMPv6 echo reply packets sent by netstack."), + DstUnreachable: mustCreateMetric("/netstack/icmp/v6/packets_sent/dst_unreachable", "Total number of ICMPv6 destination unreachable packets sent by netstack."), + PacketTooBig: mustCreateMetric("/netstack/icmp/v6/packets_sent/packet_too_big", "Total number of ICMPv6 packet too big packets sent by netstack."), + TimeExceeded: mustCreateMetric("/netstack/icmp/v6/packets_sent/time_exceeded", "Total number of ICMPv6 time exceeded packets sent by netstack."), + ParamProblem: mustCreateMetric("/netstack/icmp/v6/packets_sent/param_problem", "Total number of ICMPv6 parameter problem packets sent by netstack."), + RouterSolicit: mustCreateMetric("/netstack/icmp/v6/packets_sent/router_solicit", "Total number of ICMPv6 router solicit packets sent by netstack."), + RouterAdvert: mustCreateMetric("/netstack/icmp/v6/packets_sent/router_advert", "Total number of ICMPv6 router advert packets sent by netstack."), + NeighborSolicit: mustCreateMetric("/netstack/icmp/v6/packets_sent/neighbor_solicit", "Total number of ICMPv6 neighbor solicit packets sent by netstack."), + NeighborAdvert: mustCreateMetric("/netstack/icmp/v6/packets_sent/neighbor_advert", "Total number of ICMPv6 neighbor advert packets sent by netstack."), + RedirectMsg: mustCreateMetric("/netstack/icmp/v6/packets_sent/redirect_msg", "Total number of ICMPv6 redirect message packets sent by netstack."), + }, + Dropped: mustCreateMetric("/netstack/icmp/v6/packets_sent/dropped", "Total number of ICMPv6 packets dropped by netstack due to link layer errors."), + }, + V6PacketsReceived: tcpip.ICMPv6ReceivedPacketStats{ + ICMPv6PacketStats: tcpip.ICMPv6PacketStats{ + EchoRequest: mustCreateMetric("/netstack/icmp/v6/packets_received/echo_request", "Total number of ICMPv6 echo request packets received by netstack."), + EchoReply: mustCreateMetric("/netstack/icmp/v6/packets_received/echo_reply", "Total number of ICMPv6 echo reply packets received by netstack."), + DstUnreachable: mustCreateMetric("/netstack/icmp/v6/packets_received/dst_unreachable", "Total number of ICMPv6 destination unreachable packets received by netstack."), + PacketTooBig: mustCreateMetric("/netstack/icmp/v6/packets_received/packet_too_big", "Total number of ICMPv6 packet too big packets received by netstack."), + TimeExceeded: mustCreateMetric("/netstack/icmp/v6/packets_received/time_exceeded", "Total number of ICMPv6 time exceeded packets received by netstack."), + ParamProblem: mustCreateMetric("/netstack/icmp/v6/packets_received/param_problem", "Total number of ICMPv6 parameter problem packets received by netstack."), + RouterSolicit: mustCreateMetric("/netstack/icmp/v6/packets_received/router_solicit", "Total number of ICMPv6 router solicit packets received by netstack."), + RouterAdvert: mustCreateMetric("/netstack/icmp/v6/packets_received/router_advert", "Total number of ICMPv6 router advert packets received by netstack."), + NeighborSolicit: mustCreateMetric("/netstack/icmp/v6/packets_received/neighbor_solicit", "Total number of ICMPv6 neighbor solicit packets received by netstack."), + NeighborAdvert: mustCreateMetric("/netstack/icmp/v6/packets_received/neighbor_advert", "Total number of ICMPv6 neighbor advert packets received by netstack."), + RedirectMsg: mustCreateMetric("/netstack/icmp/v6/packets_received/redirect_msg", "Total number of ICMPv6 redirect message packets received by netstack."), + }, + Invalid: mustCreateMetric("/netstack/icmp/v6/packets_received/invalid", "Total number of ICMPv6 packets received that the transport layer could not parse."), + }, + }, IP: tcpip.IPStats{ PacketsReceived: mustCreateMetric("/netstack/ip/packets_received", "Total number of IP packets received from the link layer in nic.DeliverNetworkPacket."), InvalidAddressesReceived: mustCreateMetric("/netstack/ip/invalid_addresses_received", "Total number of IP packets received with an unknown or invalid destination address."), diff --git a/pkg/tcpip/network/ip_test.go b/pkg/tcpip/network/ip_test.go index d79eba4b0..522009fac 100644 --- a/pkg/tcpip/network/ip_test.go +++ b/pkg/tcpip/network/ip_test.go @@ -287,9 +287,9 @@ func TestIPv4ReceiveControl(t *testing.T) { {"Non-zero fragment offset", 0, 100, header.ICMPv4PortUnreachable, stack.ControlPortUnreachable, 0, 0}, {"Zero-length packet", 0, 0, header.ICMPv4PortUnreachable, stack.ControlPortUnreachable, 0, 2*header.IPv4MinimumSize + header.ICMPv4DstUnreachableMinimumSize + 8}, } - r := stack.Route{ - LocalAddress: localIpv4Addr, - RemoteAddress: "\x0a\x00\x00\xbb", + r, err := buildIPv4Route(localIpv4Addr, "\x0a\x00\x00\xbb") + if err != nil { + t.Fatal(err) } for _, c := range cases { t.Run(c.name, func(t *testing.T) { @@ -521,9 +521,12 @@ func TestIPv6ReceiveControl(t *testing.T) { {"Non-zero fragment offset", 0, newUint16(100), header.ICMPv6DstUnreachable, header.ICMPv6PortUnreachable, stack.ControlPortUnreachable, 0, 0}, {"Zero-length packet", 0, nil, header.ICMPv6DstUnreachable, header.ICMPv6PortUnreachable, stack.ControlPortUnreachable, 0, 2*header.IPv6MinimumSize + header.ICMPv6DstUnreachableMinimumSize + 8}, } - r := stack.Route{ - LocalAddress: localIpv6Addr, - RemoteAddress: "\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xaa", + r, err := buildIPv6Route( + localIpv6Addr, + "\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xaa", + ) + if err != nil { + t.Fatal(err) } for _, c := range cases { t.Run(c.name, func(t *testing.T) { diff --git a/pkg/tcpip/network/ipv4/icmp.go b/pkg/tcpip/network/ipv4/icmp.go index a9650de03..ed9a4eee5 100644 --- a/pkg/tcpip/network/ipv4/icmp.go +++ b/pkg/tcpip/network/ipv4/icmp.go @@ -55,15 +55,21 @@ func (e *endpoint) handleControl(typ stack.ControlType, extra uint32, vv buffer. } func (e *endpoint) handleICMP(r *stack.Route, netHeader buffer.View, vv buffer.VectorisedView) { + stats := r.Stats() + received := stats.ICMP.V4PacketsReceived v := vv.First() if len(v) < header.ICMPv4MinimumSize { + received.Invalid.Increment() return } h := header.ICMPv4(v) + // TODO: Meaningfully handle all ICMP types. switch h.Type() { case header.ICMPv4Echo: + received.Echo.Increment() if len(v) < header.ICMPv4EchoMinimumSize { + received.Invalid.Increment() return } // It's possible that a raw socket expects to receive this. @@ -76,16 +82,25 @@ func (e *endpoint) handleICMP(r *stack.Route, netHeader buffer.View, vv buffer.V copy(pkt, h) pkt.SetType(header.ICMPv4EchoReply) pkt.SetChecksum(^header.Checksum(pkt, header.ChecksumVV(vv, 0))) - r.WritePacket(nil /* gso */, hdr, vv, header.ICMPv4ProtocolNumber, r.DefaultTTL()) + sent := stats.ICMP.V4PacketsSent + if err := r.WritePacket(nil /* gso */, hdr, vv, header.ICMPv4ProtocolNumber, r.DefaultTTL()); err != nil { + sent.Dropped.Increment() + return + } + sent.EchoReply.Increment() case header.ICMPv4EchoReply: + received.EchoReply.Increment() if len(v) < header.ICMPv4EchoMinimumSize { + received.Invalid.Increment() return } e.dispatcher.DeliverTransportPacket(r, header.ICMPv4ProtocolNumber, netHeader, vv) case header.ICMPv4DstUnreachable: + received.DstUnreachable.Increment() if len(v) < header.ICMPv4DstUnreachableMinimumSize { + received.Invalid.Increment() return } vv.TrimFront(header.ICMPv4DstUnreachableMinimumSize) @@ -97,6 +112,32 @@ func (e *endpoint) handleICMP(r *stack.Route, netHeader buffer.View, vv buffer.V mtu := uint32(binary.BigEndian.Uint16(v[header.ICMPv4DstUnreachableMinimumSize-2:])) e.handleControl(stack.ControlPacketTooBig, calculateMTU(mtu), vv) } + + case header.ICMPv4SrcQuench: + received.SrcQuench.Increment() + + case header.ICMPv4Redirect: + received.Redirect.Increment() + + case header.ICMPv4TimeExceeded: + received.TimeExceeded.Increment() + + case header.ICMPv4ParamProblem: + received.ParamProblem.Increment() + + case header.ICMPv4Timestamp: + received.Timestamp.Increment() + + case header.ICMPv4TimestampReply: + received.TimestampReply.Increment() + + case header.ICMPv4InfoRequest: + received.InfoRequest.Increment() + + case header.ICMPv4InfoReply: + received.InfoReply.Increment() + + default: + received.Invalid.Increment() } - // TODO: Handle other ICMP types. } diff --git a/pkg/tcpip/network/ipv6/icmp.go b/pkg/tcpip/network/ipv6/icmp.go index 36d98caef..3210e6fc7 100644 --- a/pkg/tcpip/network/ipv6/icmp.go +++ b/pkg/tcpip/network/ipv6/icmp.go @@ -63,15 +63,22 @@ func (e *endpoint) handleControl(typ stack.ControlType, extra uint32, vv buffer. } func (e *endpoint) handleICMP(r *stack.Route, netHeader buffer.View, vv buffer.VectorisedView) { + stats := r.Stats().ICMP + sent := stats.V6PacketsSent + received := stats.V6PacketsReceived v := vv.First() if len(v) < header.ICMPv6MinimumSize { + received.Invalid.Increment() return } h := header.ICMPv6(v) + // TODO: Meaningfully handle all ICMP types. switch h.Type() { case header.ICMPv6PacketTooBig: + received.PacketTooBig.Increment() if len(v) < header.ICMPv6PacketTooBigMinimumSize { + received.Invalid.Increment() return } vv.TrimFront(header.ICMPv6PacketTooBigMinimumSize) @@ -79,7 +86,9 @@ func (e *endpoint) handleICMP(r *stack.Route, netHeader buffer.View, vv buffer.V e.handleControl(stack.ControlPacketTooBig, calculateMTU(mtu), vv) case header.ICMPv6DstUnreachable: + received.DstUnreachable.Increment() if len(v) < header.ICMPv6DstUnreachableMinimumSize { + received.Invalid.Increment() return } vv.TrimFront(header.ICMPv6DstUnreachableMinimumSize) @@ -89,15 +98,21 @@ func (e *endpoint) handleICMP(r *stack.Route, netHeader buffer.View, vv buffer.V } case header.ICMPv6NeighborSolicit: + received.NeighborSolicit.Increment() + + e.linkAddrCache.AddLinkAddress(e.nicid, r.RemoteAddress, r.RemoteLinkAddress) + if len(v) < header.ICMPv6NeighborSolicitMinimumSize { + received.Invalid.Increment() return } - targetAddr := tcpip.Address(v[8 : 8+16]) + targetAddr := tcpip.Address(v[8:][:16]) if e.linkAddrCache.CheckLocalAddress(e.nicid, ProtocolNumber, targetAddr) == 0 { // We don't have a useful answer; the best we can do is ignore the request. return } - hdr := buffer.NewPrependable(int(r.MaxHeaderLength()) + header.IPv6MinimumSize + header.ICMPv6NeighborAdvertSize) + + hdr := buffer.NewPrependable(int(r.MaxHeaderLength()) + header.ICMPv6NeighborAdvertSize) pkt := header.ICMPv6(hdr.Prepend(header.ICMPv6NeighborAdvertSize)) pkt.SetType(header.ICMPv6NeighborAdvert) pkt[icmpV6FlagOffset] = ndpSolicitedFlag | ndpOverrideFlag @@ -118,22 +133,29 @@ func (e *endpoint) handleICMP(r *stack.Route, netHeader buffer.View, vv buffer.V defer r.Release() r.LocalAddress = targetAddr pkt.SetChecksum(icmpChecksum(pkt, r.LocalAddress, r.RemoteAddress, buffer.VectorisedView{})) - r.WritePacket(nil /* gso */, hdr, buffer.VectorisedView{}, header.ICMPv6ProtocolNumber, r.DefaultTTL()) - e.linkAddrCache.AddLinkAddress(e.nicid, r.RemoteAddress, r.RemoteLinkAddress) + if err := r.WritePacket(nil /* gso */, hdr, buffer.VectorisedView{}, header.ICMPv6ProtocolNumber, r.DefaultTTL()); err != nil { + sent.Dropped.Increment() + return + } + sent.NeighborAdvert.Increment() case header.ICMPv6NeighborAdvert: + received.NeighborAdvert.Increment() if len(v) < header.ICMPv6NeighborAdvertSize { + received.Invalid.Increment() return } - targetAddr := tcpip.Address(v[8 : 8+16]) + targetAddr := tcpip.Address(v[8:][:16]) e.linkAddrCache.AddLinkAddress(e.nicid, targetAddr, r.RemoteLinkAddress) if targetAddr != r.RemoteAddress { e.linkAddrCache.AddLinkAddress(e.nicid, r.RemoteAddress, r.RemoteLinkAddress) } case header.ICMPv6EchoRequest: + received.EchoRequest.Increment() if len(v) < header.ICMPv6EchoMinimumSize { + received.Invalid.Increment() return } @@ -143,14 +165,37 @@ func (e *endpoint) handleICMP(r *stack.Route, netHeader buffer.View, vv buffer.V copy(pkt, h) pkt.SetType(header.ICMPv6EchoReply) pkt.SetChecksum(icmpChecksum(pkt, r.LocalAddress, r.RemoteAddress, vv)) - r.WritePacket(nil /* gso */, hdr, vv, header.ICMPv6ProtocolNumber, r.DefaultTTL()) + if err := r.WritePacket(nil /* gso */, hdr, vv, header.ICMPv6ProtocolNumber, r.DefaultTTL()); err != nil { + sent.Dropped.Increment() + return + } + sent.EchoReply.Increment() case header.ICMPv6EchoReply: + received.EchoReply.Increment() if len(v) < header.ICMPv6EchoMinimumSize { + received.Invalid.Increment() return } e.dispatcher.DeliverTransportPacket(r, header.ICMPv6ProtocolNumber, netHeader, vv) + case header.ICMPv6TimeExceeded: + received.TimeExceeded.Increment() + + case header.ICMPv6ParamProblem: + received.ParamProblem.Increment() + + case header.ICMPv6RouterSolicit: + received.RouterSolicit.Increment() + + case header.ICMPv6RouterAdvert: + received.RouterAdvert.Increment() + + case header.ICMPv6RedirectMsg: + received.RedirectMsg.Increment() + + default: + received.Invalid.Increment() } } @@ -202,6 +247,7 @@ func (*protocol) LinkAddressRequest(addr, localAddr tcpip.Address, linkEP stack. DstAddr: r.RemoteAddress, }) + // TODO: count this in ICMP stats. return linkEP.WritePacket(r, nil /* gso */, hdr, buffer.VectorisedView{}, ProtocolNumber) } diff --git a/pkg/tcpip/network/ipv6/icmp_test.go b/pkg/tcpip/network/ipv6/icmp_test.go index eee09f3af..8b57a0641 100644 --- a/pkg/tcpip/network/ipv6/icmp_test.go +++ b/pkg/tcpip/network/ipv6/icmp_test.go @@ -15,9 +15,10 @@ package ipv6 import ( + "fmt" + "reflect" "strings" "testing" - "time" "gvisor.googlesource.com/gvisor/pkg/tcpip" "gvisor.googlesource.com/gvisor/pkg/tcpip/buffer" @@ -39,20 +40,151 @@ var ( lladdr1 = header.LinkLocalAddr(linkAddr1) ) -type icmpInfo struct { - typ header.ICMPv6Type - src tcpip.Address +type stubLinkEndpoint struct { + stack.LinkEndpoint +} + +func (*stubLinkEndpoint) Capabilities() stack.LinkEndpointCapabilities { + return 0 +} + +func (*stubLinkEndpoint) MaxHeaderLength() uint16 { + return 0 +} + +func (*stubLinkEndpoint) LinkAddress() tcpip.LinkAddress { + return "" +} + +func (*stubLinkEndpoint) WritePacket(*stack.Route, *stack.GSO, buffer.Prependable, buffer.VectorisedView, tcpip.NetworkProtocolNumber) *tcpip.Error { + return nil +} + +func (*stubLinkEndpoint) Attach(stack.NetworkDispatcher) {} + +type stubDispatcher struct { + stack.TransportDispatcher +} + +func (*stubDispatcher) DeliverTransportPacket(*stack.Route, tcpip.TransportProtocolNumber, buffer.View, buffer.VectorisedView) { +} + +type stubLinkAddressCache struct { + stack.LinkAddressCache +} + +func (*stubLinkAddressCache) CheckLocalAddress(tcpip.NICID, tcpip.NetworkProtocolNumber, tcpip.Address) tcpip.NICID { + return 0 +} + +func (*stubLinkAddressCache) AddLinkAddress(tcpip.NICID, tcpip.Address, tcpip.LinkAddress) { +} + +func TestICMPCounts(t *testing.T) { + s := stack.New([]string{ProtocolName}, []string{icmp.ProtocolName6}, stack.Options{}) + { + id := stack.RegisterLinkEndpoint(&stubLinkEndpoint{}) + if err := s.CreateNIC(1, id); err != nil { + t.Fatalf("CreateNIC(_) = %s", err) + } + if err := s.AddAddress(1, ProtocolNumber, lladdr0); err != nil { + t.Fatalf("AddAddress(_, %d, %s) = %s", ProtocolNumber, lladdr0, err) + } + } + s.SetRouteTable( + []tcpip.Route{{ + Destination: lladdr1, + Mask: tcpip.AddressMask(strings.Repeat("\xff", 16)), + NIC: 1, + }}, + ) + + ep, err := s.NetworkProtocolInstance(ProtocolNumber).NewEndpoint(0, lladdr1, &stubLinkAddressCache{}, &stubDispatcher{}, nil) + if err != nil { + t.Fatalf("NewEndpoint(_) = _, %s, want = _, nil", err) + } + + r, err := s.FindRoute(1, lladdr0, lladdr1, ProtocolNumber, false /* multicastLoop */) + if err != nil { + t.Fatalf("FindRoute(_) = _, %s, want = _, nil", err) + } + defer r.Release() + + types := []struct { + typ header.ICMPv6Type + size int + }{ + {header.ICMPv6DstUnreachable, header.ICMPv6DstUnreachableMinimumSize}, + {header.ICMPv6PacketTooBig, header.ICMPv6PacketTooBigMinimumSize}, + {header.ICMPv6TimeExceeded, header.ICMPv6MinimumSize}, + {header.ICMPv6ParamProblem, header.ICMPv6MinimumSize}, + {header.ICMPv6EchoRequest, header.ICMPv6EchoMinimumSize}, + {header.ICMPv6EchoReply, header.ICMPv6EchoMinimumSize}, + {header.ICMPv6RouterSolicit, header.ICMPv6MinimumSize}, + {header.ICMPv6RouterAdvert, header.ICMPv6MinimumSize}, + {header.ICMPv6NeighborSolicit, header.ICMPv6NeighborSolicitMinimumSize}, + {header.ICMPv6NeighborAdvert, header.ICMPv6NeighborAdvertSize}, + {header.ICMPv6RedirectMsg, header.ICMPv6MinimumSize}, + } + + handleIPv6Payload := func(hdr buffer.Prependable) { + payloadLength := hdr.UsedLength() + ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize)) + ip.Encode(&header.IPv6Fields{ + PayloadLength: uint16(payloadLength), + NextHeader: uint8(header.ICMPv6ProtocolNumber), + HopLimit: r.DefaultTTL(), + SrcAddr: r.LocalAddress, + DstAddr: r.RemoteAddress, + }) + ep.HandlePacket(&r, hdr.View().ToVectorisedView()) + } + + for _, typ := range types { + hdr := buffer.NewPrependable(header.IPv6MinimumSize + typ.size) + pkt := header.ICMPv6(hdr.Prepend(typ.size)) + pkt.SetType(typ.typ) + pkt.SetChecksum(icmpChecksum(pkt, r.LocalAddress, r.RemoteAddress, buffer.VectorisedView{})) + + handleIPv6Payload(hdr) + } + + // Construct an empty ICMP packet so that + // Stats().ICMP.ICMPv6ReceivedPacketStats.Invalid is incremented. + handleIPv6Payload(buffer.NewPrependable(header.IPv6MinimumSize)) + + icmpv6Stats := s.Stats().ICMP.V6PacketsReceived + visitStats(reflect.ValueOf(&icmpv6Stats).Elem(), func(name string, s *tcpip.StatCounter) { + if got, want := s.Value(), uint64(1); got != want { + t.Errorf("got %s = %d, want = %d", name, got, want) + } + }) + if t.Failed() { + t.Logf("stats:\n%+v", s.Stats()) + } +} + +func visitStats(v reflect.Value, f func(string, *tcpip.StatCounter)) { + t := v.Type() + for i := 0; i < v.NumField(); i++ { + v := v.Field(i) + switch v.Kind() { + case reflect.Ptr: + f(t.Field(i).Name, v.Interface().(*tcpip.StatCounter)) + case reflect.Struct: + visitStats(v, f) + default: + panic(fmt.Sprintf("unexpected type %s", v.Type())) + } + } } type testContext struct { - t *testing.T s0 *stack.Stack s1 *stack.Stack linkEP0 *channel.Endpoint linkEP1 *channel.Endpoint - - icmpCh chan icmpInfo } type endpointWithResolutionCapability struct { @@ -65,10 +197,8 @@ func (e endpointWithResolutionCapability) Capabilities() stack.LinkEndpointCapab func newTestContext(t *testing.T) *testContext { c := &testContext{ - t: t, - s0: stack.New([]string{ProtocolName}, []string{icmp.ProtocolName6}, stack.Options{}), - s1: stack.New([]string{ProtocolName}, []string{icmp.ProtocolName6}, stack.Options{}), - icmpCh: make(chan icmpInfo, 10), + s0: stack.New([]string{ProtocolName}, []string{icmp.ProtocolName6}, stack.Options{}), + s1: stack.New([]string{ProtocolName}, []string{icmp.ProtocolName6}, stack.Options{}), } const defaultMTU = 65536 @@ -118,50 +248,58 @@ func newTestContext(t *testing.T) *testContext { }}, ) - go c.routePackets(linkEP0.C, linkEP1) - go c.routePackets(linkEP1.C, linkEP0) - return c } -func (c *testContext) countPacket(pkt channel.PacketInfo) { +func (c *testContext) cleanup() { + close(c.linkEP0.C) + close(c.linkEP1.C) +} + +type routeArgs struct { + src, dst *channel.Endpoint + typ header.ICMPv6Type +} + +func routeICMPv6Packet(t *testing.T, args routeArgs, fn func(*testing.T, header.ICMPv6)) { + t.Helper() + + pkt := <-args.src.C + + { + views := []buffer.View{pkt.Header, pkt.Payload} + size := len(pkt.Header) + len(pkt.Payload) + vv := buffer.NewVectorisedView(size, views) + args.dst.InjectLinkAddr(pkt.Proto, args.dst.LinkAddress(), vv) + } + if pkt.Proto != ProtocolNumber { + t.Errorf("unexpected protocol number %d", pkt.Proto) return } ipv6 := header.IPv6(pkt.Header) transProto := tcpip.TransportProtocolNumber(ipv6.NextHeader()) if transProto != header.ICMPv6ProtocolNumber { + t.Errorf("unexpected transport protocol number %d", transProto) return } - b := pkt.Header[header.IPv6MinimumSize:] - icmp := header.ICMPv6(b) - c.icmpCh <- icmpInfo{ - typ: icmp.Type(), - src: ipv6.SourceAddress(), + icmpv6 := header.ICMPv6(ipv6.Payload()) + if got, want := icmpv6.Type(), args.typ; got != want { + t.Errorf("got ICMPv6 type = %d, want = %d", got, want) + return } -} - -func (c *testContext) routePackets(ch <-chan channel.PacketInfo, ep *channel.Endpoint) { - for pkt := range ch { - c.countPacket(pkt) - views := []buffer.View{pkt.Header, pkt.Payload} - size := len(pkt.Header) + len(pkt.Payload) - vv := buffer.NewVectorisedView(size, views) - ep.InjectLinkAddr(pkt.Proto, ep.LinkAddress(), vv) + if fn != nil { + fn(t, icmpv6) } } -func (c *testContext) cleanup() { - close(c.linkEP0.C) - close(c.linkEP1.C) -} - func TestLinkResolution(t *testing.T) { c := newTestContext(t) defer c.cleanup() + r, err := c.s0.FindRoute(1, lladdr0, lladdr1, ProtocolNumber, false /* multicastLoop */) if err != nil { - t.Fatal(err) + t.Fatalf("FindRoute(_) = _, %s, want = _, nil", err) } defer r.Release() @@ -176,14 +314,24 @@ func TestLinkResolution(t *testing.T) { var wq waiter.Queue ep, err := c.s0.NewEndpoint(header.ICMPv6ProtocolNumber, ProtocolNumber, &wq) if err != nil { - t.Fatal(err) + t.Fatalf("NewEndpoint(_) = _, %s, want = _, nil", err) } for { _, resCh, err := ep.Write(payload, tcpip.WriteOptions{To: &tcpip.FullAddress{NIC: 1, Addr: lladdr1}}) if resCh != nil { if err != tcpip.ErrNoLinkAddress { - t.Fatalf("ep.Write(_) = _, <non-nil>, %s want _, <non-nil>, tcpip.ErrNoLinkAddress", err) + t.Fatalf("ep.Write(_) = _, <non-nil>, %s, want = _, <non-nil>, tcpip.ErrNoLinkAddress", err) + } + for _, args := range []routeArgs{ + {src: c.linkEP0, dst: c.linkEP1, typ: header.ICMPv6NeighborSolicit}, + {src: c.linkEP1, dst: c.linkEP0, typ: header.ICMPv6NeighborAdvert}, + } { + routeICMPv6Packet(t, args, func(t *testing.T, icmpv6 header.ICMPv6) { + if got, want := tcpip.Address(icmpv6[8:][:16]), lladdr1; got != want { + t.Errorf("%d: got target = %s, want = %s", icmpv6.Type(), got, want) + } + }) } <-resCh continue @@ -194,29 +342,10 @@ func TestLinkResolution(t *testing.T) { break } - stats := make(map[header.ICMPv6Type]int) - for { - // This actually takes about 10 milliseconds, so no need to wait for - // a multi-minute go test timeout if something is broken. - select { - case <-time.After(2 * time.Second): - t.Errorf("timeout waiting for ICMP, got: %#+v", stats) - return - case icmpInfo := <-c.icmpCh: - switch icmpInfo.typ { - case header.ICMPv6NeighborAdvert: - if got, want := icmpInfo.src, lladdr1; got != want { - t.Errorf("got ICMPv6NeighborAdvert.sourceAddress = %v, want = %v", got, want) - } - } - stats[icmpInfo.typ]++ - - if stats[header.ICMPv6NeighborSolicit] > 0 && - stats[header.ICMPv6NeighborAdvert] > 0 && - stats[header.ICMPv6EchoRequest] > 0 && - stats[header.ICMPv6EchoReply] > 0 { - return - } - } + for _, args := range []routeArgs{ + {src: c.linkEP0, dst: c.linkEP1, typ: header.ICMPv6EchoRequest}, + {src: c.linkEP1, dst: c.linkEP0, typ: header.ICMPv6EchoReply}, + } { + routeICMPv6Packet(t, args, nil) } } diff --git a/pkg/tcpip/tcpip.go b/pkg/tcpip/tcpip.go index 825854148..e9f73635f 100644 --- a/pkg/tcpip/tcpip.go +++ b/pkg/tcpip/tcpip.go @@ -581,10 +581,156 @@ func (s *StatCounter) String() string { return strconv.FormatUint(s.Value(), 10) } +// ICMPv4PacketStats enumerates counts for all ICMPv4 packet types. +type ICMPv4PacketStats struct { + // Echo is the total number of ICMPv4 echo packets counted. + Echo *StatCounter + + // EchoReply is the total number of ICMPv4 echo reply packets counted. + EchoReply *StatCounter + + // DstUnreachable is the total number of ICMPv4 destination unreachable + // packets counted. + DstUnreachable *StatCounter + + // SrcQuench is the total number of ICMPv4 source quench packets + // counted. + SrcQuench *StatCounter + + // Redirect is the total number of ICMPv4 redirect packets counted. + Redirect *StatCounter + + // TimeExceeded is the total number of ICMPv4 time exceeded packets + // counted. + TimeExceeded *StatCounter + + // ParamProblem is the total number of ICMPv4 parameter problem packets + // counted. + ParamProblem *StatCounter + + // Timestamp is the total number of ICMPv4 timestamp packets counted. + Timestamp *StatCounter + + // TimestampReply is the total number of ICMPv4 timestamp reply packets + // counted. + TimestampReply *StatCounter + + // InfoRequest is the total number of ICMPv4 information request + // packets counted. + InfoRequest *StatCounter + + // InfoReply is the total number of ICMPv4 information reply packets + // counted. + InfoReply *StatCounter +} + +// ICMPv6PacketStats enumerates counts for all ICMPv6 packet types. +type ICMPv6PacketStats struct { + // EchoRequest is the total number of ICMPv6 echo request packets + // counted. + EchoRequest *StatCounter + + // EchoReply is the total number of ICMPv6 echo reply packets counted. + EchoReply *StatCounter + + // DstUnreachable is the total number of ICMPv6 destination unreachable + // packets counted. + DstUnreachable *StatCounter + + // PacketTooBig is the total number of ICMPv6 packet too big packets + // counted. + PacketTooBig *StatCounter + + // TimeExceeded is the total number of ICMPv6 time exceeded packets + // counted. + TimeExceeded *StatCounter + + // ParamProblem is the total number of ICMPv6 parameter problem packets + // counted. + ParamProblem *StatCounter + + // RouterSolicit is the total number of ICMPv6 router solicit packets + // counted. + RouterSolicit *StatCounter + + // RouterAdvert is the total number of ICMPv6 router advert packets + // counted. + RouterAdvert *StatCounter + + // NeighborSolicit is the total number of ICMPv6 neighbor solicit + // packets counted. + NeighborSolicit *StatCounter + + // NeighborAdvert is the total number of ICMPv6 neighbor advert packets + // counted. + NeighborAdvert *StatCounter + + // RedirectMsg is the total number of ICMPv6 redirect message packets + // counted. + RedirectMsg *StatCounter +} + +// ICMPv4SentPacketStats collects outbound ICMPv4-specific stats. +type ICMPv4SentPacketStats struct { + ICMPv4PacketStats + + // Dropped is the total number of ICMPv4 packets dropped due to link + // layer errors. + Dropped *StatCounter +} + +// ICMPv4ReceivedPacketStats collects inbound ICMPv4-specific stats. +type ICMPv4ReceivedPacketStats struct { + ICMPv4PacketStats + + // Invalid is the total number of ICMPv4 packets received that the + // transport layer could not parse. + Invalid *StatCounter +} + +// ICMPv6SentPacketStats collects outbound ICMPv6-specific stats. +type ICMPv6SentPacketStats struct { + ICMPv6PacketStats + + // Dropped is the total number of ICMPv6 packets dropped due to link + // layer errors. + Dropped *StatCounter +} + +// ICMPv6ReceivedPacketStats collects inbound ICMPv6-specific stats. +type ICMPv6ReceivedPacketStats struct { + ICMPv6PacketStats + + // Invalid is the total number of ICMPv6 packets received that the + // transport layer could not parse. + Invalid *StatCounter +} + +// ICMPStats collects ICMP-specific stats (both v4 and v6). +type ICMPStats struct { + // ICMPv4SentPacketStats contains counts of sent packets by ICMPv4 packet type + // and a single count of packets which failed to write to the link + // layer. + V4PacketsSent ICMPv4SentPacketStats + + // ICMPv4ReceivedPacketStats contains counts of received packets by ICMPv4 + // packet type and a single count of invalid packets received. + V4PacketsReceived ICMPv4ReceivedPacketStats + + // ICMPv6SentPacketStats contains counts of sent packets by ICMPv6 packet type + // and a single count of packets which failed to write to the link + // layer. + V6PacketsSent ICMPv6SentPacketStats + + // ICMPv6ReceivedPacketStats contains counts of received packets by ICMPv6 + // packet type and a single count of invalid packets received. + V6PacketsReceived ICMPv6ReceivedPacketStats +} + // IPStats collects IP-specific stats (both v4 and v6). type IPStats struct { - // PacketsReceived is the total number of IP packets received from the link - // layer in nic.DeliverNetworkPacket. + // PacketsReceived is the total number of IP packets received from the + // link layer in nic.DeliverNetworkPacket. PacketsReceived *StatCounter // InvalidAddressesReceived is the total number of IP packets received @@ -605,8 +751,8 @@ type IPStats struct { // TCPStats collects TCP-specific stats. type TCPStats struct { - // ActiveConnectionOpenings is the number of connections opened successfully - // via Connect. + // ActiveConnectionOpenings is the number of connections opened + // successfully via Connect. ActiveConnectionOpenings *StatCounter // PassiveConnectionOpenings is the number of connections opened @@ -617,8 +763,8 @@ type TCPStats struct { // (active and passive openings, respectively) that end in an error. FailedConnectionAttempts *StatCounter - // ValidSegmentsReceived is the number of TCP segments received that the - // transport layer successfully parsed. + // ValidSegmentsReceived is the number of TCP segments received that + // the transport layer successfully parsed. ValidSegmentsReceived *StatCounter // InvalidSegmentsReceived is the number of TCP segments received that @@ -694,6 +840,9 @@ type Stats struct { // DroppedPackets is the number of packets dropped due to full queues. DroppedPackets *StatCounter + // ICMP breaks out ICMP-specific stats (both v4 and v6). + ICMP ICMPStats + // IP breaks out IP-specific stats (both v4 and v6). IP IPStats @@ -709,13 +858,13 @@ func fillIn(v reflect.Value) { v := v.Field(i) switch v.Kind() { case reflect.Ptr: - if s, ok := v.Addr().Interface().(**StatCounter); ok { - if *s == nil { - *s = &StatCounter{} - } + if s := v.Addr().Interface().(**StatCounter); *s == nil { + *s = &StatCounter{} } case reflect.Struct: fillIn(v) + default: + panic(fmt.Sprintf("unexpected type %s", v.Type())) } } } |