blob: cbd8b0b1441aa57d9b25d082b273844859e2d747 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Package stats implements a LinkEndpoint of the netstack,
// with responsibility to keep track of traffic statistics.
// This LinkEndpoint may be inserted into any software layer as desired.
// One feasible insertaion point is between the link layer sniffer and
// the network protocol layer.
package stats
import (
"log"
"time"
nsfidl "fidl/fuchsia/netstack"
"netstack/netiface"
"github.com/google/netstack/tcpip"
"github.com/google/netstack/tcpip/buffer"
"github.com/google/netstack/tcpip/header"
"github.com/google/netstack/tcpip/stack"
)
const debug = false
type StatsEndpoint struct {
dispatcher stack.NetworkDispatcher
lower stack.LinkEndpoint
// Storage is made of FIDL data structure
// enabling conversion-free export and import.
Stats nsfidl.NetInterfaceStats
Nic *netiface.NIC
}
func NewEndpoint(lower tcpip.LinkEndpointID) (tcpip.LinkEndpointID, *StatsEndpoint) {
e := &StatsEndpoint{
lower: stack.FindLinkEndpoint(lower),
Stats: nsfidl.NetInterfaceStats{
UpSince: time.Now().Unix(),
},
}
return stack.RegisterLinkEndpoint(e), e
}
// DeliverNetworkPacket handles incoming packet from the lower layer.
// It performs packet inspection, and extract a rich set of statistics,
// and stores them to a FIDL data structure.
func (e *StatsEndpoint) DeliverNetworkPacket(linkEP stack.LinkEndpoint, dstLinkAddr, remoteLinkAddr tcpip.LinkAddress, protocol tcpip.NetworkProtocolNumber, vv buffer.VectorisedView) {
e.analyzeTrafficStats(&e.Stats.Rx, protocol, vv.First(), vv.Size())
e.dispatcher.DeliverNetworkPacket(e, dstLinkAddr, remoteLinkAddr, protocol, vv)
}
// Attach implements registration of lower endpoint and dispatcher.
func (e *StatsEndpoint) Attach(dispatcher stack.NetworkDispatcher) {
e.dispatcher = dispatcher
e.lower.Attach(e)
}
func (e *StatsEndpoint) Capabilities() stack.LinkEndpointCapabilities {
return e.lower.Capabilities()
}
// MTU direcly directly uses lower layer MTU().
func (e *StatsEndpoint) MTU() uint32 {
return e.lower.MTU()
}
// MaxHeaderLength directly uses lower layer MaxHeaderLength()
func (e *StatsEndpoint) MaxHeaderLength() uint16 {
return e.lower.MaxHeaderLength()
}
// LinkAddress directly uses the lower layer LinkAddress()
func (e *StatsEndpoint) LinkAddress() tcpip.LinkAddress {
return e.lower.LinkAddress()
}
func (e *StatsEndpoint) IsAttached() bool {
return e.lower.IsAttached()
}
// WritePacket handles outgoing packet from the higher layer.
// It performs packet inspection, and extracts a rich set of statistics,
// and stores them to a FIDL data structure.
func (e *StatsEndpoint) WritePacket(r *stack.Route, hdr buffer.Prependable, payload buffer.VectorisedView, protocol tcpip.NetworkProtocolNumber) *tcpip.Error {
e.analyzeTrafficStats(&e.Stats.Tx, protocol, hdr.View(), hdr.UsedLength()+payload.Size())
return e.lower.WritePacket(r, hdr, payload, protocol)
}
func (e *StatsEndpoint) analyzeTrafficStats(ts *nsfidl.NetTrafficStats, protocol tcpip.NetworkProtocolNumber, hdr []byte, packetSize int) {
ts.PktsTotal += 1
ts.BytesTotal += uint64(packetSize)
switch protocol {
case header.IPv4ProtocolNumber:
ipPkt := header.IPv4(hdr)
if ipPkt.IsValid(packetSize) {
e.analyzeIPv4(ts, ipPkt)
}
case header.IPv6ProtocolNumber:
ipPkt := header.IPv6(hdr)
if ipPkt.IsValid(packetSize) {
e.analyzeIPv6(ts, ipPkt)
}
// Add other protocol below.
}
}
func (e *StatsEndpoint) analyzeIPv4(ts *nsfidl.NetTrafficStats, ipPkt header.IPv4) {
mcastToSelf := header.IsV4MulticastAddress(ipPkt.DestinationAddress()) && e.IsV4FromSelf(ipPkt)
// Add conditions not to collect stats in.
if mcastToSelf {
if debug {
log.Printf("[stats] detected multicast-to-self at NICID %v: %v -> %v", e.Nic.ID, ipPkt.SourceAddress(), ipPkt.DestinationAddress())
}
return
}
ipPayload := ipPkt[ipPkt.HeaderLength():]
nextProtocol := ipPkt.Protocol()
switch tcpip.TransportProtocolNumber(nextProtocol) {
case header.ICMPv4ProtocolNumber:
icmpPkt := header.ICMPv4(ipPayload)
switch icmpPkt.Type() {
case header.ICMPv4Echo:
ts.PktsEchoReq += 1
case header.ICMPv4EchoReply:
ts.PktsEchoRep += 1
}
}
}
func (e *StatsEndpoint) analyzeIPv6(ts *nsfidl.NetTrafficStats, ipPkt header.IPv6) {
mcastToSelf := header.IsV6MulticastAddress(ipPkt.DestinationAddress()) && e.IsV6FromSelf(ipPkt)
// Add conditions not to collect stats in.
if mcastToSelf {
if debug {
log.Printf("[stats] detected multicast-to-self at NICID %v: %v -> %v", e.Nic.ID, ipPkt.SourceAddress(), ipPkt.DestinationAddress())
}
return
}
// TODO(porce): Fix this naive assumption by processing
// all optional headers first before accessing ICMPv6 packet.
ipPayload := ipPkt[header.IPv6MinimumSize:]
nextProtocol := ipPkt.NextHeader()
switch tcpip.TransportProtocolNumber(nextProtocol) {
case header.ICMPv6ProtocolNumber:
icmpPkt := header.ICMPv6(ipPayload)
switch icmpPkt.Type() {
case header.ICMPv6EchoRequest:
ts.PktsEchoReqV6 += 1
case header.ICMPv6EchoReply:
ts.PktsEchoRepV6 += 1
}
}
}
func (e *StatsEndpoint) IsV4FromSelf(ipPkt header.IPv4) bool {
return e.Nic.Addr == ipPkt.SourceAddress()
}
func (e *StatsEndpoint) IsV6FromSelf(ipPkt header.IPv6) bool {
addr := ipPkt.SourceAddress()
for _, a := range e.Nic.Ipv6addrs {
if a == addr {
return true
}
}
return false
}