netstat.go 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. package netstat
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "fmt"
  6. "log"
  7. "net/http"
  8. "os"
  9. "os/exec"
  10. "path/filepath"
  11. "runtime"
  12. "strconv"
  13. "strings"
  14. "time"
  15. "imuslab.com/zoraxy/mod/utils"
  16. )
  17. // Float stat store the change of RX and TX
  18. type FlowStat struct {
  19. RX int64
  20. TX int64
  21. }
  22. // A new type of FloatStat that save the raw value from rx tx
  23. type RawFlowStat struct {
  24. RX int64
  25. TX int64
  26. }
  27. type NetStatBuffers struct {
  28. StatRecordCount int //No. of record number to keep
  29. PreviousStat *RawFlowStat //The value of the last instance of netstats
  30. Stats []*FlowStat //Statistic of the flow
  31. StopChan chan bool //Channel to stop the ticker
  32. EventTicker *time.Ticker //Ticker for event logging
  33. }
  34. // Get a new network statistic buffers
  35. func NewNetStatBuffer(recordCount int) (*NetStatBuffers, error) {
  36. //Flood fill the stats with 0
  37. initialStats := []*FlowStat{}
  38. for i := 0; i < recordCount; i++ {
  39. initialStats = append(initialStats, &FlowStat{
  40. RX: 0,
  41. TX: 0,
  42. })
  43. }
  44. //Setup a timer to get the value from NIC accumulation stats
  45. ticker := time.NewTicker(time.Second)
  46. //Setup a stop channel
  47. stopCh := make(chan bool)
  48. currnetNetSpec := RawFlowStat{
  49. RX: 0,
  50. TX: 0,
  51. }
  52. thisNetBuffer := NetStatBuffers{
  53. StatRecordCount: recordCount,
  54. PreviousStat: &currnetNetSpec,
  55. Stats: initialStats,
  56. StopChan: stopCh,
  57. EventTicker: ticker,
  58. }
  59. //Get the initial measurements of netstats
  60. go func(n *NetStatBuffers) {
  61. rx, tx, err := GetNetworkInterfaceStats()
  62. if err != nil {
  63. log.Println("Unable to get NIC stats: ", err.Error())
  64. }
  65. retryCount := 0
  66. for rx == 0 && tx == 0 && retryCount < 10 {
  67. //Strange. Retry
  68. log.Println("NIC stats return all 0. Retrying...")
  69. rx, tx, err = GetNetworkInterfaceStats()
  70. if err != nil {
  71. log.Println("Unable to get NIC stats: ", err.Error())
  72. }
  73. retryCount++
  74. }
  75. n.PreviousStat = &RawFlowStat{
  76. RX: rx,
  77. TX: tx,
  78. }
  79. }(&thisNetBuffer)
  80. // Update the buffer every second
  81. go func(n *NetStatBuffers) {
  82. for {
  83. select {
  84. case <-n.StopChan:
  85. fmt.Println("- Netstats listener stopped")
  86. return
  87. case <-ticker.C:
  88. if n.PreviousStat.RX == 0 && n.PreviousStat.TX == 0 {
  89. //Initiation state is still not done. Ignore request
  90. log.Println("No initial states. Waiting")
  91. return
  92. }
  93. // Get the latest network interface stats
  94. rx, tx, err := GetNetworkInterfaceStats()
  95. if err != nil {
  96. // Log the error, but don't stop the buffer
  97. log.Printf("Failed to get network interface stats: %v", err)
  98. continue
  99. }
  100. //Calculate the difference between this and last values
  101. drx := rx - n.PreviousStat.RX
  102. dtx := tx - n.PreviousStat.TX
  103. // Push the new stats to the buffer
  104. newStat := &FlowStat{
  105. RX: drx,
  106. TX: dtx,
  107. }
  108. //Set current rx tx as the previous rxtx
  109. n.PreviousStat = &RawFlowStat{
  110. RX: rx,
  111. TX: tx,
  112. }
  113. newStats := n.Stats[1:]
  114. newStats = append(newStats, newStat)
  115. n.Stats = newStats
  116. }
  117. }
  118. }(&thisNetBuffer)
  119. return &thisNetBuffer, nil
  120. }
  121. func (n *NetStatBuffers) HandleGetBufferedNetworkInterfaceStats(w http.ResponseWriter, r *http.Request) {
  122. arr, _ := utils.GetPara(r, "array")
  123. if arr == "true" {
  124. //Restructure it into array
  125. rx := []int{}
  126. tx := []int{}
  127. for _, state := range n.Stats {
  128. rx = append(rx, int(state.RX))
  129. tx = append(tx, int(state.TX))
  130. }
  131. type info struct {
  132. Rx []int
  133. Tx []int
  134. }
  135. js, _ := json.Marshal(info{
  136. Rx: rx,
  137. Tx: tx,
  138. })
  139. utils.SendJSONResponse(w, string(js))
  140. } else {
  141. js, _ := json.Marshal(n.Stats)
  142. utils.SendJSONResponse(w, string(js))
  143. }
  144. }
  145. func (n *NetStatBuffers) Close() {
  146. n.StopChan <- true
  147. time.Sleep(300 * time.Millisecond)
  148. n.EventTicker.Stop()
  149. }
  150. func HandleGetNetworkInterfaceStats(w http.ResponseWriter, r *http.Request) {
  151. rx, tx, err := GetNetworkInterfaceStats()
  152. if err != nil {
  153. utils.SendErrorResponse(w, err.Error())
  154. return
  155. }
  156. currnetNetSpec := struct {
  157. RX int64
  158. TX int64
  159. }{
  160. rx,
  161. tx,
  162. }
  163. js, _ := json.Marshal(currnetNetSpec)
  164. utils.SendJSONResponse(w, string(js))
  165. }
  166. // Get network interface stats, return accumulated rx bits, tx bits and error if any
  167. func GetNetworkInterfaceStats() (int64, int64, error) {
  168. if runtime.GOOS == "windows" {
  169. cmd := exec.Command("wmic", "path", "Win32_PerfRawData_Tcpip_NetworkInterface", "Get", "BytesReceivedPersec,BytesSentPersec,BytesTotalPersec")
  170. out, err := cmd.Output()
  171. if err != nil {
  172. return 0, 0, err
  173. }
  174. //Spawn a timer to terminate the cmd process if timeout
  175. var timer *time.Timer
  176. timer = time.AfterFunc(3*time.Second, func() {
  177. timer.Stop()
  178. cmd.Process.Kill()
  179. })
  180. //Filter out the first line
  181. lines := strings.Split(strings.ReplaceAll(string(out), "\r\n", "\n"), "\n")
  182. if len(lines) >= 2 && len(lines[1]) >= 0 {
  183. dataLine := lines[1]
  184. for strings.Contains(dataLine, " ") {
  185. dataLine = strings.ReplaceAll(dataLine, " ", " ")
  186. }
  187. dataLine = strings.TrimSpace(dataLine)
  188. info := strings.Split(dataLine, " ")
  189. if len(info) != 3 {
  190. return 0, 0, errors.New("Invalid wmic results")
  191. }
  192. rxString := info[0]
  193. txString := info[1]
  194. rx := int64(0)
  195. tx := int64(0)
  196. if s, err := strconv.ParseInt(rxString, 10, 64); err == nil {
  197. rx = s
  198. }
  199. if s, err := strconv.ParseInt(txString, 10, 64); err == nil {
  200. tx = s
  201. }
  202. time.Sleep(100 * time.Millisecond)
  203. return rx * 4, tx * 4, nil
  204. } else {
  205. //Invalid data
  206. return 0, 0, errors.New("Invalid wmic results")
  207. }
  208. } else if runtime.GOOS == "linux" {
  209. allIfaceRxByteFiles, err := filepath.Glob("/sys/class/net/*/statistics/rx_bytes")
  210. if err != nil {
  211. //Permission denied
  212. return 0, 0, errors.New("Access denied")
  213. }
  214. if len(allIfaceRxByteFiles) == 0 {
  215. return 0, 0, errors.New("No valid iface found")
  216. }
  217. rxSum := int64(0)
  218. txSum := int64(0)
  219. for _, rxByteFile := range allIfaceRxByteFiles {
  220. rxBytes, err := os.ReadFile(rxByteFile)
  221. if err == nil {
  222. rxBytesInt, err := strconv.Atoi(strings.TrimSpace(string(rxBytes)))
  223. if err == nil {
  224. rxSum += int64(rxBytesInt)
  225. }
  226. }
  227. //Usually the tx_bytes file is nearby it. Read it as well
  228. txByteFile := filepath.Join(filepath.Dir(rxByteFile), "tx_bytes")
  229. txBytes, err := os.ReadFile(txByteFile)
  230. if err == nil {
  231. txBytesInt, err := strconv.Atoi(strings.TrimSpace(string(txBytes)))
  232. if err == nil {
  233. txSum += int64(txBytesInt)
  234. }
  235. }
  236. }
  237. //Return value as bits
  238. return rxSum * 8, txSum * 8, nil
  239. } else if runtime.GOOS == "darwin" {
  240. cmd := exec.Command("netstat", "-ib") //get data from netstat -ib
  241. out, err := cmd.Output()
  242. if err != nil {
  243. return 0, 0, err
  244. }
  245. outStrs := string(out) //byte array to multi-line string
  246. for _, outStr := range strings.Split(strings.TrimSuffix(outStrs, "\n"), "\n") { //foreach multi-line string
  247. if strings.HasPrefix(outStr, "en") { //search for ethernet interface
  248. if strings.Contains(outStr, "<Link#") { //search for the link with <Link#?>
  249. outStrSplit := strings.Fields(outStr) //split by white-space
  250. rxSum, errRX := strconv.Atoi(outStrSplit[6]) //received bytes sum
  251. if errRX != nil {
  252. return 0, 0, errRX
  253. }
  254. txSum, errTX := strconv.Atoi(outStrSplit[9]) //transmitted bytes sum
  255. if errTX != nil {
  256. return 0, 0, errTX
  257. }
  258. return int64(rxSum) * 8, int64(txSum) * 8, nil
  259. }
  260. }
  261. }
  262. return 0, 0, nil //no ethernet adapters with en*/<Link#*>
  263. }
  264. return 0, 0, errors.New("Platform not supported")
  265. }