mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2025-05-11 02:15:48 +02:00
305 lines
7.2 KiB
Go
305 lines
7.2 KiB
Go
package analytic
|
|
|
|
import (
|
|
stdnet "net"
|
|
"strings"
|
|
|
|
"github.com/shirou/gopsutil/v4/net"
|
|
"github.com/uozi-tech/cosy/logger"
|
|
)
|
|
|
|
func GetNetworkStat() (data *net.IOCountersStat, err error) {
|
|
networkStats, err := net.IOCounters(true)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if len(networkStats) == 0 {
|
|
return &net.IOCountersStat{}, nil
|
|
}
|
|
// Get all network interfaces
|
|
interfaces, err := stdnet.Interfaces()
|
|
if err != nil {
|
|
logger.Error(err)
|
|
return
|
|
}
|
|
|
|
var (
|
|
totalBytesRecv uint64
|
|
totalBytesSent uint64
|
|
totalPacketsRecv uint64
|
|
totalPacketsSent uint64
|
|
totalErrIn uint64
|
|
totalErrOut uint64
|
|
totalDropIn uint64
|
|
totalDropOut uint64
|
|
totalFifoIn uint64
|
|
totalFifoOut uint64
|
|
)
|
|
|
|
// Create a map of external interface names
|
|
externalInterfaces := make(map[string]bool)
|
|
|
|
// Identify external interfaces
|
|
for _, iface := range interfaces {
|
|
// Skip down or loopback interfaces
|
|
if iface.Flags&stdnet.FlagUp == 0 ||
|
|
iface.Flags&stdnet.FlagLoopback != 0 {
|
|
continue
|
|
}
|
|
|
|
// Skip common virtual interfaces by name pattern
|
|
if isVirtualInterface(iface.Name) {
|
|
continue
|
|
}
|
|
|
|
// Check if this is a physical network interface
|
|
if isPhysicalInterface(iface.Name) && len(iface.HardwareAddr) > 0 {
|
|
externalInterfaces[iface.Name] = true
|
|
continue
|
|
}
|
|
|
|
// Get addresses for this interface
|
|
addrs, err := iface.Addrs()
|
|
if err != nil {
|
|
logger.Error(err)
|
|
continue
|
|
}
|
|
|
|
// Skip interfaces without addresses
|
|
if len(addrs) == 0 {
|
|
continue
|
|
}
|
|
|
|
// Check for non-private IP addresses
|
|
for _, addr := range addrs {
|
|
ip, ipNet, err := stdnet.ParseCIDR(addr.String())
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
// Skip virtual, local, multicast, and special purpose IPs
|
|
if !isRealExternalIP(ip, ipNet) {
|
|
continue
|
|
}
|
|
|
|
externalInterfaces[iface.Name] = true
|
|
break
|
|
}
|
|
}
|
|
|
|
// Accumulate stats only from external interfaces
|
|
for _, stat := range networkStats {
|
|
if externalInterfaces[stat.Name] {
|
|
totalBytesRecv += stat.BytesRecv
|
|
totalBytesSent += stat.BytesSent
|
|
totalPacketsRecv += stat.PacketsRecv
|
|
totalPacketsSent += stat.PacketsSent
|
|
totalErrIn += stat.Errin
|
|
totalErrOut += stat.Errout
|
|
totalDropIn += stat.Dropin
|
|
totalDropOut += stat.Dropout
|
|
totalFifoIn += stat.Fifoin
|
|
totalFifoOut += stat.Fifoout
|
|
}
|
|
}
|
|
|
|
return &net.IOCountersStat{
|
|
Name: "analytic.network",
|
|
BytesRecv: totalBytesRecv,
|
|
BytesSent: totalBytesSent,
|
|
PacketsRecv: totalPacketsRecv,
|
|
PacketsSent: totalPacketsSent,
|
|
Errin: totalErrIn,
|
|
Errout: totalErrOut,
|
|
Dropin: totalDropIn,
|
|
Dropout: totalDropOut,
|
|
Fifoin: totalFifoIn,
|
|
Fifoout: totalFifoOut,
|
|
}, nil
|
|
}
|
|
|
|
// isVirtualInterface checks if the interface is a virtual one based on name patterns
|
|
func isVirtualInterface(name string) bool {
|
|
// Common virtual interface name patterns
|
|
virtualPatterns := []string{
|
|
"veth", "virbr", "vnet", "vmnet", "vboxnet", "docker",
|
|
"br-", "bridge", "tun", "tap", "bond", "dummy",
|
|
"vpn", "ipsec", "gre", "sit", "vlan", "virt",
|
|
"wg", "vmk", "ib", "vxlan", "geneve", "ovs",
|
|
"hyperv", "hyper-v", "awdl", "llw", "utun",
|
|
"vpn", "zt", "zerotier", "wireguard",
|
|
}
|
|
|
|
for _, pattern := range virtualPatterns {
|
|
if strings.Contains(strings.ToLower(name), pattern) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// isPhysicalInterface checks if the interface is a physical network interface
|
|
// including server, cloud VM, and container physical interfaces
|
|
func isPhysicalInterface(name string) bool {
|
|
// Common prefixes for physical network interfaces across different platforms
|
|
physicalPrefixes := []string{
|
|
"eth", // Common Linux Ethernet interface
|
|
"en", // macOS and some Linux
|
|
"ens", // Predictable network interface names in systemd
|
|
"enp", // Predictable network interface names in systemd (PCI)
|
|
"eno", // Predictable network interface names in systemd (on-board)
|
|
"wlan", // Wireless interfaces
|
|
"wifi", // Some wireless interfaces
|
|
"wl", // Shortened wireless interfaces
|
|
"bond", // Bonded interfaces
|
|
"em", // Some server network interfaces
|
|
"p", // Some specialized network cards
|
|
"lan", // Some network interfaces
|
|
}
|
|
|
|
// Check for exact matches for common primary interfaces
|
|
if name == "eth0" || name == "en0" || name == "em0" {
|
|
return true
|
|
}
|
|
|
|
// Check for common physical interface patterns
|
|
for _, prefix := range physicalPrefixes {
|
|
if strings.HasPrefix(strings.ToLower(name), prefix) {
|
|
// Check if the remaining part is numeric or empty
|
|
suffix := strings.TrimPrefix(strings.ToLower(name), prefix)
|
|
if suffix == "" || isNumericSuffix(suffix) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// isNumericSuffix checks if a string is a numeric suffix or starts with a number
|
|
func isNumericSuffix(s string) bool {
|
|
if len(s) == 0 {
|
|
return false
|
|
}
|
|
|
|
// Check if the first character is a digit
|
|
if s[0] >= '0' && s[0] <= '9' {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// isRealExternalIP checks if an IP is a genuine external (public) IP
|
|
func isRealExternalIP(ip stdnet.IP, ipNet *stdnet.IPNet) bool {
|
|
// Skip if it's not a global unicast address
|
|
if !ip.IsGlobalUnicast() {
|
|
return false
|
|
}
|
|
|
|
// Skip private IPs
|
|
if ip.IsPrivate() {
|
|
return false
|
|
}
|
|
|
|
// Skip link-local addresses
|
|
if ip.IsLinkLocalUnicast() {
|
|
return false
|
|
}
|
|
|
|
// Skip loopback
|
|
if ip.IsLoopback() {
|
|
return false
|
|
}
|
|
|
|
// Skip multicast
|
|
if ip.IsMulticast() {
|
|
return false
|
|
}
|
|
|
|
// Check for special reserved ranges
|
|
if isReservedIP(ip) {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// isReservedIP checks if an IP belongs to special reserved ranges
|
|
func isReservedIP(ip stdnet.IP) bool {
|
|
// Handle IPv4
|
|
if ip4 := ip.To4(); ip4 != nil {
|
|
// TEST-NET-1: 192.0.2.0/24 (RFC 5737)
|
|
if ip4[0] == 192 && ip4[1] == 0 && ip4[2] == 2 {
|
|
return true
|
|
}
|
|
|
|
// TEST-NET-2: 198.51.100.0/24 (RFC 5737)
|
|
if ip4[0] == 198 && ip4[1] == 51 && ip4[2] == 100 {
|
|
return true
|
|
}
|
|
|
|
// TEST-NET-3: 203.0.113.0/24 (RFC 5737)
|
|
if ip4[0] == 203 && ip4[1] == 0 && ip4[2] == 113 {
|
|
return true
|
|
}
|
|
|
|
// Benchmark tests: 198.18.0.0/15 (includes 198.19.0.0/16) (RFC 2544)
|
|
if ip4[0] == 198 && (ip4[1] == 18 || ip4[1] == 19) {
|
|
return true
|
|
}
|
|
|
|
// Documentation: 240.0.0.0/4 (RFC 1112)
|
|
if ip4[0] >= 240 {
|
|
return true
|
|
}
|
|
|
|
// CGNAT: 100.64.0.0/10 (RFC 6598)
|
|
if ip4[0] == 100 && (ip4[1]&0xC0) == 64 {
|
|
return true
|
|
}
|
|
} else if ip.To16() != nil {
|
|
// Documentation prefix (2001:db8::/32) - RFC 3849
|
|
if ip[0] == 0x20 && ip[1] == 0x01 && ip[2] == 0x0d && ip[3] == 0xb8 {
|
|
return true
|
|
}
|
|
|
|
// Unique Local Addresses (fc00::/7) - RFC 4193
|
|
if (ip[0] & 0xfe) == 0xfc {
|
|
return true
|
|
}
|
|
|
|
// 6to4 relay (2002::/16) - RFC 3056
|
|
if ip[0] == 0x20 && ip[1] == 0x02 {
|
|
return true
|
|
}
|
|
|
|
// Teredo tunneling (2001:0::/32) - RFC 4380
|
|
if ip[0] == 0x20 && ip[1] == 0x01 && ip[2] == 0x00 && ip[3] == 0x00 {
|
|
return true
|
|
}
|
|
|
|
// Deprecated site-local addresses (fec0::/10) - RFC 3879
|
|
if (ip[0]&0xff) == 0xfe && (ip[1]&0xc0) == 0xc0 {
|
|
return true
|
|
}
|
|
|
|
// Old 6bone addresses (3ffe::/16) - Deprecated
|
|
if ip[0] == 0x3f && ip[1] == 0xfe {
|
|
return true
|
|
}
|
|
|
|
// ORCHID addresses (2001:10::/28) - RFC 4843
|
|
if ip[0] == 0x20 && ip[1] == 0x01 && ip[2] == 0x00 && (ip[3]&0xf0) == 0x10 {
|
|
return true
|
|
}
|
|
|
|
// ORCHID v2 addresses (2001:20::/28) - RFC 7343
|
|
if ip[0] == 0x20 && ip[1] == 0x01 && ip[2] == 0x00 && (ip[3]&0xf0) == 0x20 {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|