package netstat import ( "encoding/json" "errors" "net/http" "time" "github.com/shirou/gopsutil/v4/net" "imuslab.com/zoraxy/mod/info/logger" "imuslab.com/zoraxy/mod/utils" ) // Float stat store the change of RX and TX type FlowStat struct { RX int64 TX int64 } // A new type of FloatStat that save the raw value from rx tx type RawFlowStat struct { RX int64 TX int64 } type NetStatBuffers struct { StatRecordCount int //No. of record number to keep PreviousStat *RawFlowStat //The value of the last instance of netstats Stats []*FlowStat //Statistic of the flow StopChan chan bool //Channel to stop the ticker EventTicker *time.Ticker //Ticker for event logging logger *logger.Logger } // Get a new network statistic buffers func NewNetStatBuffer(recordCount int, systemWideLogger *logger.Logger) (*NetStatBuffers, error) { //Flood fill the stats with 0 initialStats := []*FlowStat{} for i := 0; i < recordCount; i++ { initialStats = append(initialStats, &FlowStat{ RX: 0, TX: 0, }) } //Setup a timer to get the value from NIC accumulation stats ticker := time.NewTicker(time.Second) //Setup a stop channel stopCh := make(chan bool) currnetNetSpec := RawFlowStat{ RX: 0, TX: 0, } thisNetBuffer := NetStatBuffers{ StatRecordCount: recordCount, PreviousStat: &currnetNetSpec, Stats: initialStats, StopChan: stopCh, EventTicker: ticker, logger: systemWideLogger, } //Get the initial measurements of netstats rx, tx, err := thisNetBuffer.GetNetworkInterfaceStats() if err != nil { systemWideLogger.PrintAndLog("netstat", "Unable to get NIC stats: ", err) } retryCount := 0 for rx == 0 && tx == 0 && retryCount < 10 { //Strange. Retry systemWideLogger.PrintAndLog("netstat", "NIC stats return all 0. Retrying...", nil) rx, tx, err = thisNetBuffer.GetNetworkInterfaceStats() if err != nil { systemWideLogger.PrintAndLog("netstat", "Unable to get NIC stats: ", err) } retryCount++ } thisNetBuffer.PreviousStat = &RawFlowStat{ RX: rx, TX: tx, } // Update the buffer every second go func(n *NetStatBuffers) { for { select { case <-n.StopChan: systemWideLogger.PrintAndLog("netstat", "Netstats listener stopped", nil) return case <-ticker.C: if n.PreviousStat.RX == 0 && n.PreviousStat.TX == 0 { //Initiation state is still not done. Ignore request systemWideLogger.PrintAndLog("netstat", "No initial states. Waiting", nil) return } // Get the latest network interface stats rx, tx, err := thisNetBuffer.GetNetworkInterfaceStats() if err != nil { // Log the error, but don't stop the buffer systemWideLogger.PrintAndLog("netstat", "Failed to get network interface stats", err) continue } //Calculate the difference between this and last values drx := rx - n.PreviousStat.RX dtx := tx - n.PreviousStat.TX // Push the new stats to the buffer newStat := &FlowStat{ RX: drx, TX: dtx, } //Set current rx tx as the previous rxtx n.PreviousStat = &RawFlowStat{ RX: rx, TX: tx, } newStats := n.Stats[1:] newStats = append(newStats, newStat) n.Stats = newStats } } }(&thisNetBuffer) return &thisNetBuffer, nil } func (n *NetStatBuffers) HandleGetBufferedNetworkInterfaceStats(w http.ResponseWriter, r *http.Request) { arr, _ := utils.GetPara(r, "array") if arr == "true" { //Restructure it into array rx := []int{} tx := []int{} for _, state := range n.Stats { rx = append(rx, int(state.RX)) tx = append(tx, int(state.TX)) } type info struct { Rx []int Tx []int } js, _ := json.Marshal(info{ Rx: rx, Tx: tx, }) utils.SendJSONResponse(w, string(js)) } else { js, _ := json.Marshal(n.Stats) utils.SendJSONResponse(w, string(js)) } } func (n *NetStatBuffers) Close() { //Fixed issue #394 for stopping netstat listener on platforms not supported platforms if n.StopChan != nil { n.StopChan <- true time.Sleep(300 * time.Millisecond) } if n.EventTicker != nil { n.EventTicker.Stop() } } func (n *NetStatBuffers) HandleGetNetworkInterfaceStats(w http.ResponseWriter, r *http.Request) { rx, tx, err := n.GetNetworkInterfaceStats() if err != nil { utils.SendErrorResponse(w, err.Error()) return } currnetNetSpec := struct { RX int64 TX int64 }{ rx, tx, } js, _ := json.Marshal(currnetNetSpec) utils.SendJSONResponse(w, string(js)) } // Get network interface stats, return accumulated rx bits, tx bits and error if any func (n *NetStatBuffers) GetNetworkInterfaceStats() (int64, int64, error) { // Get aggregated network I/O stats for all interfaces counters, err := net.IOCounters(false) if err != nil { return 0, 0, err } if len(counters) == 0 { return 0, 0, errors.New("no network interfaces found") } var totalRx, totalTx uint64 for _, counter := range counters { totalRx += counter.BytesRecv totalTx += counter.BytesSent } // Convert bytes to bits with overflow check const maxInt64 = int64(^uint64(0) >> 1) if totalRx*8 > uint64(maxInt64) || totalTx*8 > uint64(maxInt64) { return 0, 0, errors.New("overflow detected when converting uint64 to int64") } return int64(totalRx * 8), int64(totalTx * 8), nil }