ethr/plt_linux.go
Pankaj Garg 945d59c33b
Add support for throttling in Bandwidth tests, ToS and improved external mode. (#135)
* Intermediate checkin

* Last check-in before deleting ACK message in Ethr.

* Support for client port, throttling and tos almost working.

* Most functionality working as expected with code all cleaned up.

* Linux/OSX Fixes.

* Fix handshake mechanism.

* Minor cleanup.

* More improvements for external mode.

* Improve admin-mode, root user permission checking.

* Improve detection of IP version for ICMP.

* Update README.md
2020-11-30 18:20:57 -08:00

196 lines
4.6 KiB
Go

// +build linux
//-----------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license.
// See LICENSE.txt file in the project root for full license information.
//-----------------------------------------------------------------------------
package main
import (
"bufio"
"net"
"os"
"strconv"
"strings"
"syscall"
tm "github.com/nsf/termbox-go"
)
type ethrNetDevInfo struct {
bytes uint64
packets uint64
drop uint64
errs uint64
fifo uint64
frame uint64
compressed uint64
multicast uint64
}
type osStats struct {
}
func getNetDevStats(stats *ethrNetStat) {
ifs, err := net.Interfaces()
if err != nil {
ui.printErr("%v", err)
return
}
netStatsFile, err := os.Open("/proc/net/dev")
if err != nil {
ui.printErr("%v", err)
return
}
defer netStatsFile.Close()
reader := bufio.NewReader(netStatsFile)
// Pass the header
// Inter-| Receive | Transmit
// face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed
reader.ReadString('\n')
reader.ReadString('\n')
var line string
for err == nil {
line, err = reader.ReadString('\n')
if line == "" {
continue
}
netDevStat := buildNetDevStat(line)
if isIfUp(netDevStat.interfaceName, ifs) {
stats.netDevStats = append(stats.netDevStats, buildNetDevStat(line))
}
}
}
func getTCPStats(stats *ethrNetStat) {
snmpStatsFile, err := os.Open("/proc/net/snmp")
if err != nil {
ui.printDbg("%v", err)
return
}
defer snmpStatsFile.Close()
reader := bufio.NewReader(snmpStatsFile)
var line string
for err == nil {
// Tcp: RtoAlgorithm RtoMin RtoMax MaxConn ActiveOpens PassiveOpens AttemptFails EstabResets
// CurrEstab InSegs OutSegs RetransSegs InErrs OutRsts InCsumErrors
line, err = reader.ReadString('\n')
if line == "" || !strings.HasPrefix(line, "Tcp") {
continue
}
// Skip the first line starting with Tcp
line, err = reader.ReadString('\n')
if !strings.HasPrefix(line, "Tcp") {
break
}
fields := strings.Fields(line)
stats.tcpStats.segRetrans = toUInt64(fields[12])
}
}
func hideCursor() {
tm.SetCursor(0, 0)
}
func blockWindowResize() {
}
func buildNetDevStat(line string) ethrNetDevStat {
fields := strings.Fields(line)
if len(fields) < 17 {
return ethrNetDevStat{}
}
interfaceName := strings.TrimSuffix(fields[0], ":")
rxInfo := toNetDevInfo(fields[1:9])
txInfo := toNetDevInfo(fields[9:17])
return ethrNetDevStat{
interfaceName: interfaceName,
rxBytes: rxInfo.bytes,
txBytes: txInfo.bytes,
rxPkts: rxInfo.packets,
txPkts: txInfo.packets,
}
}
func toNetDevInfo(fields []string) ethrNetDevInfo {
return ethrNetDevInfo{
bytes: toUInt64(fields[0]),
packets: toUInt64(fields[1]),
errs: toUInt64(fields[2]),
drop: toUInt64(fields[3]),
fifo: toUInt64(fields[4]),
frame: toUInt64(fields[5]),
compressed: toUInt64(fields[6]),
multicast: toUInt64(fields[7]),
}
}
func toUInt64(str string) uint64 {
res, err := strconv.ParseUint(str, 10, 64)
if err != nil {
ui.printDbg("Error in string conversion: %v", err)
return 0
}
return res
}
func isIfUp(ifName string, ifs []net.Interface) bool {
for _, ifi := range ifs {
if ifi.Name == ifName {
if (ifi.Flags & net.FlagUp) != 0 {
return true
}
return false
}
}
return false
}
func setSockOptInt(fd uintptr, level, opt, val int) (err error) {
err = syscall.SetsockoptInt(int(fd), level, opt, val)
if err != nil {
ui.printErr("Failed to set socket option (%v) to value (%v) during Dial. Error: %s", opt, val, err)
}
return
}
func IcmpNewConn(address string) (net.PacketConn, error) {
dialedConn, err := net.Dial(Icmp(), address)
if err != nil {
return nil, err
}
localAddr := dialedConn.LocalAddr()
dialedConn.Close()
conn, err := net.ListenPacket(Icmp(), localAddr.String())
if err != nil {
return nil, err
}
return conn, nil
}
func VerifyPermissionForTest(testID EthrTestID) {
if testID.Protocol == ICMP || (testID.Protocol == TCP &&
(testID.Type == TraceRoute || testID.Type == MyTraceRoute)) {
if !IsAdmin() {
ui.printMsg("Warning: You are not running as administrator. For %s based %s",
protoToString(testID.Protocol), testToString(testID.Type))
ui.printMsg("test, running as administrator is required.\n")
}
}
}
func IsAdmin() bool {
return os.Geteuid() == 0
}
func SetTClass(fd uintptr, tos int) {
setSockOptInt(fd, syscall.IPPROTO_IPV6, syscall.IPV6_TCLASS, tos)
}