diff --git a/src/include/ipxe/fragment.h b/src/include/ipxe/fragment.h index 6b47439d..e311ad1e 100644 --- a/src/include/ipxe/fragment.h +++ b/src/include/ipxe/fragment.h @@ -27,6 +27,8 @@ struct fragment { size_t hdrlen; /** Reassembly timer */ struct retry_timer timer; + /** Fragment reassembler */ + struct fragment_reassembler *fragments; }; /** A fragment reassembler */ @@ -59,6 +61,8 @@ struct fragment_reassembler { * @ret more_frags More fragments exist */ int ( * more_fragments ) ( struct io_buffer *iobuf, size_t hdrlen ); + /** Associated IP statistics */ + struct ip_statistics *stats; }; extern struct io_buffer * diff --git a/src/include/ipxe/ipstat.h b/src/include/ipxe/ipstat.h new file mode 100644 index 00000000..c554c185 --- /dev/null +++ b/src/include/ipxe/ipstat.h @@ -0,0 +1,187 @@ +#ifndef _IPXE_IPSTATS_H +#define _IPXE_IPSTATS_H + +/** @file + * + * IP statistics + * + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include + +struct io_buffer; + +/** IP system statistics + * + * Definitions are taken from the RFC4293 section 5 + * "ipSystemStatsEntry" table. + * + * To minimise code size, we use "unsigned long" as the counter + * variable type regardless of whether this type is 32-bit or 64-bit. + * On a 32-bit build (e.g. the standard BIOS build), this means that + * we omit the "high capacity" 64-bit counters (prefixed with "HC"). + * This reduces the code size required to maintain the counter values, + * and avoids the need to support the "%lld" format in vsprintf.c + * (which would require dragging in the 64-bit division library on a + * standard 32-bit build). Since total available memory in a 32-bit + * environment is limited to 4GB, it is unlikely that we will overflow + * even the 32-bit octet counters under normal operation. + * + * Counters relating to packet forwarding are omitted, since iPXE + * includes no functionality for acting as a router. + * + * Counters related to output fragmentation are omitted, since iPXE + * has no support for fragmenting transmitted packets. + * + * The ipSystemStatsInDiscards and ipSystemStatsOutDiscards counters + * are omitted, since they will always be zero. + * + * Separate octet counters for multicast packets are omitted to save + * code size. + */ +struct ip_statistics { + /** ipSystemStatsInReceives + * + * The total number of input IP datagrams received, including + * those received in error. + */ + unsigned long in_receives; + /** ipSystemStatsInOctets + * + * The total number of octets received in input IP datagrams, + * including those received in error. Octets from datagrams + * counted in ipSystemStatsInReceives MUST be counted here. + */ + unsigned long in_octets; + /** ipSystemStatsInHdrErrors + * + * The number of input IP datagrams discarded due to errors in + * their IP headers, including version number mismatch, other + * format errors, hop count exceeded, errors discovered in + * processing their IP options, etc. + */ + unsigned long in_hdr_errors; + /** ipSystemStatsInAddrErrors + * + * The number of input IP datagrams discarded because the IP + * address in their IP header's destination field was not a + * valid address to be received at this entity. This count + * includes invalid addresses (e.g., ::0). For entities that + * are not IP routers and therefore do not forward datagrams, + * this counter includes datagrams discarded because the + * destination address was not a local address. + */ + unsigned long in_addr_errors; + /** ipSystemStatsInUnknownProtos + * + * The number of locally-addressed IP datagrams received + * successfully but discarded because of an unknown or + * unsupported protocol. + */ + unsigned long in_unknown_protos; + /** ipSystemStatsInTruncatedPkts + * + * The number of input IP datagrams discarded because the + * datagram frame didn't carry enough data. + */ + unsigned long in_truncated_pkts; + /** ipSystemStatsReasmReqds + * + * The number of IP fragments received that needed to be + * reassembled at this interface. + */ + unsigned long reasm_reqds; + /** ipSystemStatsReasmOks + * + * The number of IP datagrams successfully reassembled. + */ + unsigned long reasm_oks; + /** ipSystemStatsReasmFails + * + * The number of failures detected by the IP re-assembly + * algorithm (for whatever reason: timed out, errors, etc.). + * Note that this is not necessarily a count of discarded IP + * fragments since some algorithms (notably the algorithm in + * RFC 815) can lose track of the number of fragments by + * combining them as they are received. + */ + unsigned long reasm_fails; + /** ipSystemStatsInDelivers + * + * The total number of datagrams successfully delivered to IP + * user-protocols (including ICMP). + */ + unsigned long in_delivers; + /** ipSystemStatsOutRequests + * + * The total number of IP datagrams that local IP user- + * protocols (including ICMP) supplied to IP in requests for + * transmission. + */ + unsigned long out_requests; + /** ipSystemStatsOutNoRoutes + * + * The number of locally generated IP datagrams discarded + * because no route could be found to transmit them to their + * destination. + */ + unsigned long out_no_routes; + /** ipSystemStatsOutTransmits + * + * The total number of IP datagrams that this entity supplied + * to the lower layers for transmission. This includes + * datagrams generated locally and those forwarded by this + * entity. + */ + unsigned long out_transmits; + /** ipSystemStatsOutOctets + * + * The total number of octets in IP datagrams delivered to the + * lower layers for transmission. Octets from datagrams + * counted in ipSystemStatsOutTransmits MUST be counted here. + */ + unsigned long out_octets; + /** ipSystemStatsInMcastPkts + * + * The number of IP multicast datagrams received. + */ + unsigned long in_mcast_pkts; + /** ipSystemStatsOutMcastPkts + * + * The number of IP multicast datagrams transmitted. + */ + unsigned long out_mcast_pkts; + /** ipSystemStatsInBcastPkts + * + * The number of IP broadcast datagrams received. + */ + unsigned long in_bcast_pkts; + /** ipSystemStatsOutBcastPkts + * + * The number of IP broadcast datagrams transmitted. + */ + unsigned long out_bcast_pkts; +}; + +/** An IP system statistics family */ +struct ip_statistics_family { + /** IP version */ + unsigned int version; + /** Statistics */ + struct ip_statistics *stats; +}; + +/** IP system statistics family table */ +#define IP_STATISTICS_FAMILIES \ + __table ( struct ip_statistics_family, "ip_statistics_families" ) + +/** Declare an IP system statistics family */ +#define __ip_statistics_family( order ) \ + __table_entry ( IP_STATISTICS_FAMILIES, order ) + +#define IP_STATISTICS_IPV4 01 +#define IP_STATISTICS_IPV6 02 + +#endif /* _IPXE_IPSTATS_H */ diff --git a/src/include/ipxe/tcpip.h b/src/include/ipxe/tcpip.h index fdfbae11..f5ef4f04 100644 --- a/src/include/ipxe/tcpip.h +++ b/src/include/ipxe/tcpip.h @@ -17,6 +17,7 @@ FILE_LICENCE ( GPL2_OR_LATER ); struct io_buffer; struct net_device; +struct ip_statistics; /** Empty checksum value * @@ -132,7 +133,8 @@ struct tcpip_net_protocol { extern int tcpip_rx ( struct io_buffer *iobuf, struct net_device *netdev, uint8_t tcpip_proto, struct sockaddr_tcpip *st_src, - struct sockaddr_tcpip *st_dest, uint16_t pshdr_csum ); + struct sockaddr_tcpip *st_dest, uint16_t pshdr_csum, + struct ip_statistics *stats ); extern int tcpip_tx ( struct io_buffer *iobuf, struct tcpip_protocol *tcpip, struct sockaddr_tcpip *st_src, struct sockaddr_tcpip *st_dest, diff --git a/src/net/fragment.c b/src/net/fragment.c index 3e1dfdf7..410915b3 100644 --- a/src/net/fragment.c +++ b/src/net/fragment.c @@ -24,6 +24,7 @@ FILE_LICENCE ( GPL2_OR_LATER ); #include #include #include +#include #include /** @file @@ -45,6 +46,7 @@ static void fragment_expired ( struct retry_timer *timer, int fail __unused ) { DBGC ( fragment, "FRAG %p expired\n", fragment ); free_iob ( fragment->iobuf ); list_del ( &fragment->list ); + fragment->fragments->stats->reasm_fails++; free ( fragment ); } @@ -89,6 +91,9 @@ struct io_buffer * fragment_reassemble ( struct fragment_reassembler *fragments, size_t expected_offset; int more_frags; + /* Update statistics */ + fragments->stats->reasm_reqds++; + /* Find matching fragment reassembly buffer, if any */ fragment = fragment_find ( fragments, iobuf, *hdrlen ); @@ -115,6 +120,7 @@ struct io_buffer * fragment_reassemble ( struct fragment_reassembler *fragments, fragment->iobuf = iobuf; fragment->hdrlen = *hdrlen; timer_init ( &fragment->timer, fragment_expired, NULL ); + fragment->fragments = fragments; DBGC ( fragment, "FRAG %p [0,%zd)\n", fragment, ( iob_len ( iobuf ) - *hdrlen ) ); @@ -157,6 +163,7 @@ struct io_buffer * fragment_reassemble ( struct fragment_reassembler *fragments, *hdrlen = fragment->hdrlen; list_del ( &fragment->list ); free ( fragment ); + fragments->stats->reasm_oks++; return iobuf; } } @@ -167,6 +174,7 @@ struct io_buffer * fragment_reassemble ( struct fragment_reassembler *fragments, return NULL; drop: + fragments->stats->reasm_fails++; free_iob ( iobuf ); return NULL; } diff --git a/src/net/ipv4.c b/src/net/ipv4.c index b57b2f83..d9a54ade 100644 --- a/src/net/ipv4.c +++ b/src/net/ipv4.c @@ -15,6 +15,7 @@ #include #include #include +#include /** @file * @@ -30,6 +31,16 @@ static uint8_t next_ident_high = 0; /** List of IPv4 miniroutes */ struct list_head ipv4_miniroutes = LIST_HEAD_INIT ( ipv4_miniroutes ); +/** IPv4 statistics */ +static struct ip_statistics ipv4_stats; + +/** IPv4 statistics family */ +struct ip_statistics_family +ipv4_stats_family __ip_statistics_family ( IP_STATISTICS_IPV4 ) = { + .version = 4, + .stats = &ipv4_stats, +}; + /** * Add IPv4 minirouting table entry * @@ -178,6 +189,7 @@ static struct fragment_reassembler ipv4_reassembler = { .is_fragment = ipv4_is_fragment, .fragment_offset = ipv4_fragment_offset, .more_fragments = ipv4_more_fragments, + .stats = &ipv4_stats, }; /** @@ -232,6 +244,9 @@ static int ipv4_tx ( struct io_buffer *iobuf, const void *ll_dest; int rc; + /* Update statistics */ + ipv4_stats.out_requests++; + /* Fill up the IP header, except source address */ memset ( iphdr, 0, sizeof ( *iphdr ) ); iphdr->verhdrlen = ( IP_VER | ( sizeof ( *iphdr ) / 4 ) ); @@ -255,6 +270,7 @@ static int ipv4_tx ( struct io_buffer *iobuf, if ( ! netdev ) { DBGC ( sin_dest->sin_addr, "IPv4 has no route to %s\n", inet_ntoa ( iphdr->dest ) ); + ipv4_stats.out_no_routes++; rc = -ENETUNREACH; goto err; } @@ -282,9 +298,11 @@ static int ipv4_tx ( struct io_buffer *iobuf, /* Calculate link-layer destination address, if possible */ if ( ( ( next_hop.s_addr ^ INADDR_BROADCAST ) & ~netmask.s_addr ) == 0){ /* Broadcast address */ + ipv4_stats.out_bcast_pkts++; ll_dest = netdev->ll_broadcast; } else if ( IN_MULTICAST ( ntohl ( next_hop.s_addr ) ) ) { /* Multicast address */ + ipv4_stats.out_mcast_pkts++; if ( ( rc = netdev->ll_protocol->mc_hash ( AF_INET, &next_hop, ll_dest_buf ) ) !=0){ DBGC ( sin_dest->sin_addr, "IPv4 could not hash " @@ -298,6 +316,10 @@ static int ipv4_tx ( struct io_buffer *iobuf, ll_dest = NULL; } + /* Update statistics */ + ipv4_stats.out_transmits++; + ipv4_stats.out_octets += iob_len ( iobuf ); + /* Hand off to link layer (via ARP if applicable) */ if ( ll_dest ) { if ( ( rc = net_tx ( iobuf, netdev, &ipv4_protocol, ll_dest, @@ -389,43 +411,53 @@ static int ipv4_rx ( struct io_buffer *iobuf, uint16_t pshdr_csum; int rc; + /* Update statistics */ + ipv4_stats.in_receives++; + ipv4_stats.in_octets += iob_len ( iobuf ); + if ( flags & LL_BROADCAST ) { + ipv4_stats.in_bcast_pkts++; + } else if ( flags & LL_MULTICAST ) { + ipv4_stats.in_mcast_pkts++; + } + /* Sanity check the IPv4 header */ if ( iob_len ( iobuf ) < sizeof ( *iphdr ) ) { DBGC ( iphdr->src, "IPv4 packet too short at %zd bytes (min " "%zd bytes)\n", iob_len ( iobuf ), sizeof ( *iphdr ) ); - goto err; + goto err_header; } if ( ( iphdr->verhdrlen & IP_MASK_VER ) != IP_VER ) { DBGC ( iphdr->src, "IPv4 version %#02x not supported\n", iphdr->verhdrlen ); - goto err; + goto err_header; } hdrlen = ( ( iphdr->verhdrlen & IP_MASK_HLEN ) * 4 ); if ( hdrlen < sizeof ( *iphdr ) ) { DBGC ( iphdr->src, "IPv4 header too short at %zd bytes (min " "%zd bytes)\n", hdrlen, sizeof ( *iphdr ) ); - goto err; + goto err_header; } if ( hdrlen > iob_len ( iobuf ) ) { DBGC ( iphdr->src, "IPv4 header too long at %zd bytes " "(packet is %zd bytes)\n", hdrlen, iob_len ( iobuf ) ); - goto err; + goto err_header; } if ( ( csum = tcpip_chksum ( iphdr, hdrlen ) ) != 0 ) { DBGC ( iphdr->src, "IPv4 checksum incorrect (is %04x " "including checksum field, should be 0000)\n", csum ); - goto err; + goto err_header; } len = ntohs ( iphdr->len ); if ( len < hdrlen ) { DBGC ( iphdr->src, "IPv4 length too short at %zd bytes " "(header is %zd bytes)\n", len, hdrlen ); - goto err; + goto err_header; } if ( len > iob_len ( iobuf ) ) { DBGC ( iphdr->src, "IPv4 length too long at %zd bytes " "(packet is %zd bytes)\n", len, iob_len ( iobuf ) ); - goto err; + ipv4_stats.in_truncated_pkts++; + goto err_other; } /* Truncate packet to correct length */ @@ -443,7 +475,8 @@ static int ipv4_rx ( struct io_buffer *iobuf, ( ! ipv4_has_addr ( netdev, iphdr->dest ) ) ) { DBGC ( iphdr->src, "IPv4 discarding non-local unicast packet " "for %s\n", inet_ntoa ( iphdr->dest ) ); - goto err; + ipv4_stats.in_addr_errors++; + goto err_other; } /* Perform fragment reassembly if applicable */ @@ -470,7 +503,7 @@ static int ipv4_rx ( struct io_buffer *iobuf, pshdr_csum = ipv4_pshdr_chksum ( iobuf, TCPIP_EMPTY_CSUM ); iob_pull ( iobuf, hdrlen ); if ( ( rc = tcpip_rx ( iobuf, netdev, iphdr->protocol, &src.st, - &dest.st, pshdr_csum ) ) != 0 ) { + &dest.st, pshdr_csum, &ipv4_stats ) ) != 0 ) { DBGC ( src.sin.sin_addr, "IPv4 received packet rejected by " "stack: %s\n", strerror ( rc ) ); return rc; @@ -478,7 +511,9 @@ static int ipv4_rx ( struct io_buffer *iobuf, return 0; - err: + err_header: + ipv4_stats.in_hdr_errors++; + err_other: free_iob ( iobuf ); return -EINVAL; } diff --git a/src/net/ipv6.c b/src/net/ipv6.c index 621b4ff1..2802aef0 100644 --- a/src/net/ipv6.c +++ b/src/net/ipv6.c @@ -31,6 +31,7 @@ FILE_LICENCE ( GPL2_OR_LATER ); #include #include #include +#include #include #include @@ -57,6 +58,16 @@ FILE_LICENCE ( GPL2_OR_LATER ); /** List of IPv6 miniroutes */ struct list_head ipv6_miniroutes = LIST_HEAD_INIT ( ipv6_miniroutes ); +/** IPv6 statistics */ +static struct ip_statistics ipv6_stats; + +/** IPv6 statistics family */ +struct ip_statistics_family +ipv6_statistics_family __ip_statistics_family ( IP_STATISTICS_IPV6 ) = { + .version = 6, + .stats = &ipv6_stats, +}; + /** * Determine debugging colour for IPv6 debug messages * @@ -398,6 +409,7 @@ static struct fragment_reassembler ipv6_reassembler = { .is_fragment = ipv6_is_fragment, .fragment_offset = ipv6_fragment_offset, .more_fragments = ipv6_more_fragments, + .stats = &ipv6_stats, }; /** @@ -455,6 +467,9 @@ static int ipv6_tx ( struct io_buffer *iobuf, size_t len; int rc; + /* Update statistics */ + ipv6_stats.out_requests++; + /* Fill up the IPv6 header, except source address */ len = iob_len ( iobuf ); iphdr = iob_push ( iobuf, sizeof ( *iphdr ) ); @@ -475,6 +490,7 @@ static int ipv6_tx ( struct io_buffer *iobuf, if ( ! netdev ) { DBGC ( ipv6col ( &iphdr->dest ), "IPv6 has no route to %s\n", inet6_ntoa ( &iphdr->dest ) ); + ipv6_stats.out_no_routes++; rc = -ENETUNREACH; goto err; } @@ -498,6 +514,7 @@ static int ipv6_tx ( struct io_buffer *iobuf, /* Calculate link-layer destination address, if possible */ if ( IN6_IS_ADDR_MULTICAST ( next_hop ) ) { /* Multicast address */ + ipv6_stats.out_mcast_pkts++; if ( ( rc = netdev->ll_protocol->mc_hash ( AF_INET6, next_hop, ll_dest_buf ) ) !=0){ DBGC ( ipv6col ( &iphdr->dest ), "IPv6 could not hash " @@ -511,6 +528,10 @@ static int ipv6_tx ( struct io_buffer *iobuf, ll_dest = NULL; } + /* Update statistics */ + ipv6_stats.out_transmits++; + ipv6_stats.out_octets += iob_len ( iobuf ); + /* Hand off to link layer (via NDP if applicable) */ if ( ll_dest ) { if ( ( rc = net_tx ( iobuf, netdev, &ipv6_protocol, ll_dest, @@ -568,20 +589,29 @@ static int ipv6_rx ( struct io_buffer *iobuf, struct net_device *netdev, int next_header; int rc; + /* Update statistics */ + ipv6_stats.in_receives++; + ipv6_stats.in_octets += iob_len ( iobuf ); + if ( flags & LL_BROADCAST ) { + ipv6_stats.in_bcast_pkts++; + } else if ( flags & LL_MULTICAST ) { + ipv6_stats.in_mcast_pkts++; + } + /* Sanity check the IPv6 header */ if ( iob_len ( iobuf ) < sizeof ( *iphdr ) ) { DBGC ( ipv6col ( &iphdr->src ), "IPv6 packet too short at %zd " "bytes (min %zd bytes)\n", iob_len ( iobuf ), sizeof ( *iphdr ) ); rc = -EINVAL_LEN; - goto err; + goto err_header; } if ( ( iphdr->ver_tc_label & htonl ( IPV6_MASK_VER ) ) != htonl ( IPV6_VER ) ) { DBGC ( ipv6col ( &iphdr->src ), "IPv6 version %#08x not " "supported\n", ntohl ( iphdr->ver_tc_label ) ); rc = -ENOTSUP_VER; - goto err; + goto err_header; } /* Truncate packet to specified length */ @@ -589,8 +619,9 @@ static int ipv6_rx ( struct io_buffer *iobuf, struct net_device *netdev, if ( len > iob_len ( iobuf ) ) { DBGC ( ipv6col ( &iphdr->src ), "IPv6 length too long at %zd " "bytes (packet is %zd bytes)\n", len, iob_len ( iobuf )); + ipv6_stats.in_truncated_pkts++; rc = -EINVAL_LEN; - goto err; + goto err_other; } iob_unput ( iobuf, ( iob_len ( iobuf ) - len - sizeof ( *iphdr ) ) ); hdrlen = sizeof ( *iphdr ); @@ -606,8 +637,9 @@ static int ipv6_rx ( struct io_buffer *iobuf, struct net_device *netdev, ( ! ipv6_has_addr ( netdev, &iphdr->dest ) ) ) { DBGC ( ipv6col ( &iphdr->src ), "IPv6 discarding non-local " "unicast packet for %s\n", inet6_ntoa ( &iphdr->dest ) ); + ipv6_stats.in_addr_errors++; rc = -EPIPE; - goto err; + goto err_other; } /* Process any extension headers */ @@ -624,7 +656,7 @@ static int ipv6_rx ( struct io_buffer *iobuf, struct net_device *netdev, "%zd bytes)\n", this_header, ( iob_len ( iobuf ) - hdrlen ), extlen ); rc = -EINVAL_LEN; - goto err; + goto err_header; } /* Determine size of extension header (if applicable) */ @@ -645,7 +677,7 @@ static int ipv6_rx ( struct io_buffer *iobuf, struct net_device *netdev, "%zd bytes)\n", this_header, ( iob_len ( iobuf ) - hdrlen ), extlen ); rc = -EINVAL_LEN; - goto err; + goto err_header; } hdrlen += extlen; next_header = ext->common.next_header; @@ -662,7 +694,7 @@ static int ipv6_rx ( struct io_buffer *iobuf, struct net_device *netdev, /* Check that all options can be ignored */ if ( ( rc = ipv6_check_options ( iphdr, &ext->options, extlen ) ) != 0 ) - goto err; + goto err_header; } else if ( this_header == IPV6_FRAGMENT ) { @@ -692,7 +724,7 @@ static int ipv6_rx ( struct io_buffer *iobuf, struct net_device *netdev, pshdr_csum = ipv6_pshdr_chksum ( iphdr, iob_len ( iobuf ), next_header, TCPIP_EMPTY_CSUM ); if ( ( rc = tcpip_rx ( iobuf, netdev, next_header, &src.st, &dest.st, - pshdr_csum ) ) != 0 ) { + pshdr_csum, &ipv6_stats ) ) != 0 ) { DBGC ( ipv6col ( &src.sin6.sin6_addr ), "IPv6 received packet " "rejected by stack: %s\n", strerror ( rc ) ); return rc; @@ -700,7 +732,9 @@ static int ipv6_rx ( struct io_buffer *iobuf, struct net_device *netdev, return 0; - err: + err_header: + ipv6_stats.in_hdr_errors++; + err_other: free_iob ( iobuf ); return rc; } diff --git a/src/net/tcpip.c b/src/net/tcpip.c index 0e467144..0b2adfd9 100644 --- a/src/net/tcpip.c +++ b/src/net/tcpip.c @@ -5,6 +5,7 @@ #include #include #include +#include #include /** @file @@ -25,6 +26,7 @@ FILE_LICENCE ( GPL2_OR_LATER ); * @v st_src Partially-filled source address * @v st_dest Partially-filled destination address * @v pshdr_csum Pseudo-header checksum + * @v stats IP statistics * @ret rc Return status code * * This function expects a transport-layer segment from the network @@ -35,20 +37,22 @@ FILE_LICENCE ( GPL2_OR_LATER ); */ int tcpip_rx ( struct io_buffer *iobuf, struct net_device *netdev, uint8_t tcpip_proto, struct sockaddr_tcpip *st_src, - struct sockaddr_tcpip *st_dest, - uint16_t pshdr_csum ) { + struct sockaddr_tcpip *st_dest, uint16_t pshdr_csum, + struct ip_statistics *stats ) { struct tcpip_protocol *tcpip; /* Hand off packet to the appropriate transport-layer protocol */ for_each_table_entry ( tcpip, TCPIP_PROTOCOLS ) { if ( tcpip->tcpip_proto == tcpip_proto ) { DBG ( "TCP/IP received %s packet\n", tcpip->name ); + stats->in_delivers++; return tcpip->rx ( iobuf, netdev, st_src, st_dest, pshdr_csum ); } } DBG ( "Unrecognised TCP/IP protocol %d\n", tcpip_proto ); + stats->in_unknown_protos++; free_iob ( iobuf ); return -EPROTONOSUPPORT; }