diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..5191f0b --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,7 @@ +package main + +import "github.com/oneclickvirt/basics/system" + +func main() { + system.GetSystemInfo() +} diff --git a/defaultset/defaultset.go b/defaultset/defaultset.go new file mode 100644 index 0000000..d75a8fb --- /dev/null +++ b/defaultset/defaultset.go @@ -0,0 +1,35 @@ +package defaultset + +import "fmt" + +func Red(text string) string { + return fmt.Sprintf("\033[31m\033[01m%s\033[0m", text) +} + +func Green(text string) string { + return fmt.Sprintf("\033[32m\033[01m%s\033[0m", text) +} + +func DarkGreen(text string) string { + return fmt.Sprintf("\033[32m\033[02m%s\033[0m", text) +} + +func Yellow(text string) string { + return fmt.Sprintf("\033[33m\033[01m%s\033[0m", text) +} + +func Blue(text string) string { + return fmt.Sprintf("\033[36m\033[01m%s\033[0m", text) +} + +func Purple(text string) string { + return fmt.Sprintf("\033[35m\033[01m%s\033[0m", text) +} + +func Cyan(text string) string { + return fmt.Sprintf("\033[36m\033[01m%s\033[0m", text) +} + +func White(text string) string { + return fmt.Sprintf("\033[37m\033[01m%s\033[0m", text) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..f7478fa --- /dev/null +++ b/go.mod @@ -0,0 +1,24 @@ +module github.com/oneclickvirt/basics + +go 1.21.5 + +require ( + github.com/libp2p/go-nat v0.2.0 + github.com/shirou/gopsutil v3.21.11+incompatible +) + +require ( + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/google/gopacket v1.1.19 // indirect + github.com/huin/goupnp v1.2.0 // indirect + github.com/jackpal/go-nat-pmp v1.0.2 // indirect + github.com/koron/go-ssdp v0.0.4 // indirect + github.com/libp2p/go-netroute v0.2.1 // indirect + github.com/stretchr/testify v1.9.0 // indirect + github.com/tklauser/go-sysconf v0.3.14 // indirect + github.com/tklauser/numcpus v0.8.0 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect + golang.org/x/net v0.10.0 // indirect + golang.org/x/sync v0.2.0 // indirect + golang.org/x/sys v0.19.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..91edbb3 --- /dev/null +++ b/go.sum @@ -0,0 +1,50 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= +github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= +github.com/huin/goupnp v1.2.0 h1:uOKW26NG1hsSSbXIZ1IR7XP9Gjd1U8pnLaCMgntmkmY= +github.com/huin/goupnp v1.2.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= +github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= +github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= +github.com/koron/go-ssdp v0.0.4 h1:1IDwrghSKYM7yLf7XCzbByg2sJ/JcNOZRXS2jczTwz0= +github.com/koron/go-ssdp v0.0.4/go.mod h1:oDXq+E5IL5q0U8uSBcoAXzTzInwy5lEgC91HoKtbmZk= +github.com/libp2p/go-nat v0.2.0 h1:Tyz+bUFAYqGyJ/ppPPymMGbIgNRH+WqC5QrT5fKrrGk= +github.com/libp2p/go-nat v0.2.0/go.mod h1:3MJr+GRpRkyT65EpVPBstXLvOlAPzUVlG6Pwg9ohLJk= +github.com/libp2p/go-netroute v0.2.1 h1:V8kVrpD8GK0Riv15/7VN6RbUQ3URNZVosw7H2v9tksU= +github.com/libp2p/go-netroute v0.2.1/go.mod h1:hraioZr0fhBjG0ZRXJJ6Zj2IVEVNx6tDTFQfSmcq7mQ= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= +github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU= +github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY= +github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY= +github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= +golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/network/baseinfo/baseinfo.go b/network/baseinfo/baseinfo.go new file mode 100644 index 0000000..42d9229 --- /dev/null +++ b/network/baseinfo/baseinfo.go @@ -0,0 +1,254 @@ +package baseinfo + +import ( + "fmt" + "github.com/oneclickvirt/basics/network/model" + "github.com/oneclickvirt/basics/network/utils" + "strconv" + "strings" + "sync" +) + +// FetchIPInfoIo 从 ipinfo.io 获取 IP 信息 +func FetchIPInfoIo(netType string) (*model.IpInfo, *model.SecurityInfo, error) { + data, err := utils.FetchJsonFromURL("http://ipinfo.io", netType, false, "") + if err == nil { + res := &model.IpInfo{} + if ip, ok := data["ip"].(string); ok && ip != "" { + res.Ip = ip + } + if city, ok := data["city"].(string); ok && city != "" { + res.City = city + } + if region, ok := data["region"].(string); ok && region != "" { + res.Region = region + } + if country, ok := data["country"].(string); ok && country != "" { + res.Country = country + } + if org, ok := data["org"].(string); ok && org != "" { + parts := strings.Split(org, " ") + if len(parts) > 0 { + res.ASN = parts[0] + res.Org = strings.Join(parts[1:], " ") + } else { + res.ASN = org + } + } + return res, nil, nil + } else { + return nil, nil, err + } +} + +// FetchCloudFlare 从 speed.cloudflare.com 获取 IP 信息 +func FetchCloudFlare(netType string) (*model.IpInfo, *model.SecurityInfo, error) { + data, err := utils.FetchJsonFromURL("https://speed.cloudflare.com/meta", netType, false, "") + if err == nil { + res := &model.IpInfo{} + if ip, ok := data["clientIp"].(string); ok && ip != "" { + res.Ip = ip + } + if city, ok := data["city"].(string); ok && city != "" { + res.City = city + } + if region, ok := data["region"].(string); ok && region != "" { + res.Region = region + } + if country, ok := data["country"].(string); ok && country != "" { + res.Country = country + } + if asnFloat, ok := data["asn"].(float64); ok { + res.ASN = strconv.FormatInt(int64(asnFloat), 10) + } else if asnStr, ok := data["asn"].(string); ok && asnStr != "" { + res.ASN = asnStr + } + if org, ok := data["asOrganization"].(string); ok && org != "" { + res.Org = org + } + return res, nil, nil + } else { + return nil, nil, err + } +} + +// FetchIpSb 从 api.ip.sb 获取 IP 信息 +func FetchIpSb(netType string) (*model.IpInfo, *model.SecurityInfo, error) { + data, err := utils.FetchJsonFromURL("https://api.ip.sb/geoip", netType, true, "") + if err == nil { + res := &model.IpInfo{} + if ip, ok := data["ip"].(string); ok && ip != "" { + res.Ip = ip + } + if city, ok := data["city"].(string); ok && city != "" { + res.City = city + } + if region, ok := data["region"].(string); ok && region != "" { + res.Region = region + } + if country, ok := data["country"].(string); ok && country != "" { + res.Country = country + } + if asnFloat, ok := data["asn"].(float64); ok { + res.ASN = strconv.FormatInt(int64(asnFloat), 10) + } else if asnStr, ok := data["asn"].(string); ok && asnStr != "" { + res.ASN = asnStr + } + if org, ok := data["asn_organization"].(string); ok && org != "" { + res.Org = org + } + return res, nil, nil + } else { + return nil, nil, err + } +} + +// FetchIpDataCheerVision 从 ipdata.cheervision.co 获取 IP 信息 +func FetchIpDataCheerVision(netType string) (*model.IpInfo, *model.SecurityInfo, error) { + data, err := utils.FetchJsonFromURL("https://ipdata.cheervision.co", netType, true, "") + if err == nil { + ipInfo := utils.ParseIpInfo(data) + securityInfo := utils.ParseSecurityInfo(data) + return ipInfo, securityInfo, nil + } else { + return nil, nil, err + } +} + +// executeFunctions 并发执行函数 +// 仅区分IPV4或IPV6,BOTH的情况需要两次执行本函数分别指定 +func executeFunctions(checkType string, fetchFunc func(string) (*model.IpInfo, *model.SecurityInfo, error), ipInfoChan chan *model.IpInfo, securityInfoChan chan *model.SecurityInfo, wg *sync.WaitGroup) { + defer wg.Done() + ipFetcher := func(ipType string) { + ipInfo, securityInfo, err := fetchFunc(ipType) + if err == nil { + select { + case ipInfoChan <- ipInfo: + default: + } + select { + case securityInfoChan <- securityInfo: + default: + } + } else { + select { + case ipInfoChan <- nil: + default: + } + select { + case securityInfoChan <- nil: + default: + } + } + } + if checkType == "ipv4" { + wg.Add(1) + go func() { + defer wg.Done() + ipFetcher("tcp4") + }() + } + if checkType == "ipv6" { + wg.Add(1) + go func() { + defer wg.Done() + ipFetcher("tcp6") + }() + } +} + +// RunIpCheck 并发请求获取信息 +func RunIpCheck(checkType string) (*model.IpInfo, *model.SecurityInfo, *model.IpInfo, *model.SecurityInfo, error) { + // 定义函数名数组 + functions := []func(string) (*model.IpInfo, *model.SecurityInfo, error){ + FetchIPInfoIo, + FetchCloudFlare, + FetchIpSb, + FetchIpDataCheerVision, + } + // 定义通道 + ipInfoIPv4 := make(chan *model.IpInfo, len(functions)) + securityInfoIPv4 := make(chan *model.SecurityInfo, len(functions)) + ipInfoIPv6 := make(chan *model.IpInfo, len(functions)) + securityInfoIPv6 := make(chan *model.SecurityInfo, len(functions)) + var wg sync.WaitGroup + if checkType == "both" { + wg.Add(len(functions) * 2) // 每个函数都会产生一个 IPv4 和一个 IPv6 结果 + // 启动协程执行函数 + for _, f := range functions { + go executeFunctions("ipv4", f, ipInfoIPv4, securityInfoIPv4, &wg) + go executeFunctions("ipv6", f, ipInfoIPv6, securityInfoIPv6, &wg) + } + } else if checkType == "ipv4" { + wg.Add(len(functions)) // 每个函数都会产生一个 IPv4 结果 + // 启动协程执行函数 + for _, f := range functions { + go executeFunctions("ipv4", f, ipInfoIPv4, securityInfoIPv4, &wg) + } + } else if checkType == "ipv6" { + wg.Add(len(functions)) // 每个函数都会产生一个 IPv6 结果 + // 启动协程执行函数 + for _, f := range functions { + go executeFunctions("ipv6", f, ipInfoIPv6, securityInfoIPv6, &wg) + } + } else { + return nil, nil, nil, nil, fmt.Errorf("wrong checkType") + } + go func() { + wg.Wait() + close(ipInfoIPv4) + close(securityInfoIPv4) + close(ipInfoIPv6) + close(securityInfoIPv6) + }() + // 读取结果并处理 + var ipInfoV4Result *model.IpInfo + var ipInfoV6Result *model.IpInfo + var securityInfoV4Result *model.SecurityInfo + var securityInfoV6Result *model.SecurityInfo + for ipInfo := range ipInfoIPv4 { + if ipInfo != nil { + if ipInfoV4Result == nil { + ipInfoV4Result = &model.IpInfo{} + } + ipInfoV4TempResult, err := utils.CompareAndMergeIpInfo(ipInfoV4Result, ipInfo) + if err == nil { + ipInfoV4Result = ipInfoV4TempResult + } + } + } + for ipInfo := range ipInfoIPv6 { + if ipInfo != nil { + if ipInfoV6Result == nil { + ipInfoV6Result = &model.IpInfo{} + } + ipInfoV6TempResult, err := utils.CompareAndMergeIpInfo(ipInfoV6Result, ipInfo) + if err == nil { + ipInfoV6Result = ipInfoV6TempResult + } + } + } + for securityInfo := range securityInfoIPv4 { + if securityInfo != nil { + if securityInfoV4Result == nil { + securityInfoV4Result = &model.SecurityInfo{} + } + securityInfoV4TempResult, err := utils.CompareAndMergeSecurityInfo(securityInfoV4Result, securityInfo) + if err == nil { + securityInfoV4Result = securityInfoV4TempResult + } + } + } + for securityInfo := range securityInfoIPv6 { + if securityInfo != nil { + if securityInfoV6Result == nil { + securityInfoV6Result = &model.SecurityInfo{} + } + securityInfoV6TempResult, err := utils.CompareAndMergeSecurityInfo(securityInfoV6Result, securityInfo) + if err == nil { + securityInfoV6Result = securityInfoV6TempResult + } + } + } + return ipInfoV4Result, securityInfoV4Result, ipInfoV6Result, securityInfoV6Result, nil +} diff --git a/network/baseinfo/baseinfo_test.go b/network/baseinfo/baseinfo_test.go new file mode 100644 index 0000000..b4f933e --- /dev/null +++ b/network/baseinfo/baseinfo_test.go @@ -0,0 +1,82 @@ +package baseinfo + +import ( + "fmt" + networkModel "github.com/oneclickvirt/basics/network/model" + "testing" + "time" +) + +// printIPInfo 重构输出函数 +func printIPInfo(ipInfo *networkModel.IpInfo, securityInfo *networkModel.SecurityInfo, err error) { + if err != nil { + fmt.Println("获取 IP 信息时出错:", err) + return + } + if ipInfo != nil { + fmt.Println("IPInfo:") + fmt.Println("IP:", ipInfo.Ip) + fmt.Println("ASN:", ipInfo.ASN) + fmt.Println("Org:", ipInfo.Org) + fmt.Println("Country:", ipInfo.Country) + fmt.Println("Region:", ipInfo.Region) + fmt.Println("City:", ipInfo.City) + fmt.Println("---------------------------------") + } + if securityInfo != nil { + fmt.Println("Security Info:") + fmt.Println("IsAbuser:", securityInfo.IsAbuser) + fmt.Println("IsAttacker:", securityInfo.IsAttacker) + fmt.Println("IsBogon:", securityInfo.IsBogon) + fmt.Println("IsCloudProvider:", securityInfo.IsCloudProvider) + fmt.Println("IsProxy:", securityInfo.IsProxy) + fmt.Println("IsRelay:", securityInfo.IsRelay) + fmt.Println("IsTor:", securityInfo.IsTor) + fmt.Println("IsTorExit:", securityInfo.IsTorExit) + fmt.Println("IsVpn:", securityInfo.IsVpn) + fmt.Println("IsAnonymous:", securityInfo.IsAnonymous) + fmt.Println("IsThreat:", securityInfo.IsThreat) + fmt.Println("---------------------------------") + } +} + +func TestIPInfo(t *testing.T) { + // Test for IPv4 + fmt.Println("IPv4 Testing:") + startV4 := time.Now() + ipInfoV4Result, securityInfoV4Result, _, _, err := RunIpCheck("ipv4") + elapsedV4 := time.Since(startV4) + if err == nil { + fmt.Println("IPv4:") + fmt.Println("------") + printIPInfo(ipInfoV4Result, securityInfoV4Result, nil) + } + fmt.Println("---***********************************---") + // Test for IPv6 + fmt.Println("IPv6 Testing:") + startV6 := time.Now() + ipInfoV6Result, securityInfoV6Result, _, _, err := RunIpCheck("ipv6") + elapsedV6 := time.Since(startV6) + if err == nil { + fmt.Println("IPv6:") + fmt.Println("------") + printIPInfo(ipInfoV6Result, securityInfoV6Result, nil) + } + fmt.Println("---***********************************---") + // Test for both IPv4 and IPv6 + fmt.Println("Both Testing:") + startBoth := time.Now() + ipInfoV4Result, securityInfoV4Result, ipInfoV6Result, securityInfoV6Result, err = RunIpCheck("both") + elapsedBoth := time.Since(startBoth) + if err == nil { + fmt.Println("IPv4:") + fmt.Println("------") + printIPInfo(ipInfoV4Result, securityInfoV4Result, nil) + fmt.Println("IPv6:") + fmt.Println("------") + printIPInfo(ipInfoV6Result, securityInfoV6Result, nil) + } + fmt.Printf("IPv4 test took %s\n", elapsedV4) + fmt.Printf("IPv6 test took %s\n", elapsedV6) + fmt.Printf("Both test took %s\n", elapsedBoth) +} diff --git a/network/baseinfo/ecs.log b/network/baseinfo/ecs.log new file mode 100644 index 0000000..e69de29 diff --git a/network/model/model.go b/network/model/model.go new file mode 100644 index 0000000..3d03913 --- /dev/null +++ b/network/model/model.go @@ -0,0 +1,90 @@ +package model + +type IpInfo struct { + Ip string + ASN string + Org string + Country string + Region string + City string +} + +type SecurityScore struct { + Tag string + Reputation *int + TrustScore *int + VpnScore *int + ProxyScore *int + CommunityVoteHarmless *int + CommunityVoteMalicious *int + CloudFlareRisk *int // 还没有加入 + ThreatScore *int + FraudScore *int + AbuseScore *int + HarmlessnessRecords *int + MaliciousRecords *int + SuspiciousRecords *int + NoRecords *int +} + +type SecurityInfo struct { + Tag string + ASNAbuseScore string // 这三个实际是得分类型,但由于是字符串所以还在这解析 + CompannyAbuseScore string + ThreatLevel string + UsageType string // connection_type、usage_type、asn_type + CompanyType string // company type + IsCloudProvider string + IsDatacenter string // datacenter、server、hosting + IsMobile string + IsProxy string // Public Proxy、Web Proxy + IsVpn string + IsTor string + IsTorExit string + IsCrawler string + IsAnonymous string + IsAttacker string + IsAbuser string + IsThreat string + IsRelay string // icloud_relay、is_relay + IsBogon string + IsBot string // Search Engine Robot +} + +// TranslationMap 定义英文到中文的映射表 +var TranslationMap = map[string]string{ + "Reputation": "声誉(越高越好)", + "TrustScore": "信任得分(越高越好)", + "VpnScore": "VPN得分(越低越好)", + "ProxyScore": "代理得分(越低越好)", + "CommunityVoteHarmless": "社区投票-无害", + "CommunityVoteMalicious": "社区投票-恶意", + "CloudFlareRisk": "CloudFlare风险(越低越好)", + "ThreatScore": "威胁得分(越低越好)", + "FraudScore": "欺诈得分(越低越好)", + "AbuseScore": "滥用得分(越低越好)", + "HarmlessnessRecords": "无害记录数", + "MaliciousRecords": "恶意记录数", + "SuspiciousRecords": "可疑记录数", + "NoRecords": "无记录数", + "ASNAbuseScore": "ASN滥用得分(越低越好)", + "CompannyAbuseScore": "公司滥用得分(越低越好)", + "ThreatLevel": "威胁级别", + "UsageType": "使用类型", + "CompanyType": "公司类型", + "IsCloudProvider": "是否云提供商", + "IsDatacenter": "是否数据中心", + "IsMobile": "是否移动设备", + "IsProxy": "是否代理", + "IsVpn": "是否VPN", + "IsTor": "是否Tor", + "IsTorExit": "是否Tor出口", + "IsCrawler": "是否网络爬虫", + "IsAnonymous": "是否匿名", + "IsAttacker": "是否攻击者", + "IsAbuser": "是否滥用者", + "IsThreat": "是否威胁", + "IsRelay": "是否中继", + "IsBogon": "是否Bogon", + "IsBot": "是否机器人", +} diff --git a/network/network.go b/network/network.go new file mode 100644 index 0000000..35db1cc --- /dev/null +++ b/network/network.go @@ -0,0 +1,97 @@ +package network + +import ( + "fmt" + "strings" + + "github.com/oneclickvirt/basics/network/baseinfo" + "github.com/oneclickvirt/basics/network/model" +) + +// sortAndTranslateText 对原始文本进行排序和翻译 +func sortAndTranslateText(orginList []string, language string, fields []string) string { + var result string + for _, key := range fields { + var displayKey string + if language == "zh" { + displayKey = model.TranslationMap[key] + if displayKey == "" { + displayKey = key + } + } else { + displayKey = key + } + for _, line := range orginList { + if strings.Contains(line, key) { + if displayKey == key { + result = result + line + "\n" + } else { + result = result + strings.ReplaceAll(line, key, displayKey) + "\n" + } + break + } + } + } + return result +} + +// processPrintIPInfo 处理IP信息 +func processPrintIPInfo(headASNString string, headLocationString string, ipResult *model.IpInfo) string { + var info string + // 处理ASN信息 + if ipResult.ASN != "" || ipResult.Org != "" { + info += headASNString + if ipResult.ASN != "" { + info += "AS" + ipResult.ASN + if ipResult.Org != "" { + info += " " + } + } + info += ipResult.Org + "\n" + } + // 处理位置信息 + if ipResult.City != "" || ipResult.Region != "" || ipResult.Country != "" { + info += headLocationString + if ipResult.City != "" { + info += ipResult.City + " / " + } + if ipResult.Region != "" { + info += ipResult.Region + " / " + } + if ipResult.Country != "" { + info += ipResult.Country + } + info += "\n" + } + return info +} + +// NetworkCheck 查询网络信息 +// checkType 可选 both ipv4 ipv6 +// language 暂时仅支持 en 或 zh +func NetworkCheck(checkType string, enableSecurityCheck bool, language string) (string, string, error) { + var ipInfo string + if checkType == "both" { + ipInfoV4Result, _, ipInfoV6Result, _, _ := baseinfo.RunIpCheck("both") + if ipInfoV4Result != nil { + ipInfo += processPrintIPInfo(" IPV4 ASN: ", " IPV4 Location: ", ipInfoV4Result) + } + if ipInfoV6Result != nil { + ipInfo += processPrintIPInfo(" IPV6 ASN: ", " IPV6 Location: ", ipInfoV6Result) + } + return ipInfo, "", nil + } else if checkType == "ipv4" { + ipInfoV4Result, _, _, _, _ := baseinfo.RunIpCheck("ipv4") + if ipInfoV4Result != nil { + ipInfo += processPrintIPInfo(" IPV4 ASN: ", " IPV4 Location: ", ipInfoV4Result) + } + return ipInfo, "", nil + } else if checkType == "ipv6" { + _, _, ipInfoV6Result, _, _ := baseinfo.RunIpCheck("ipv6") + if ipInfoV6Result != nil { + ipInfo += processPrintIPInfo(" IPV6 ASN: ", " IPV6 Location: ", ipInfoV6Result) + } + return ipInfo, "", nil + } + return "", "", fmt.Errorf("wrong in NetworkCheck") +} diff --git a/network/network_test.go b/network/network_test.go new file mode 100644 index 0000000..1f26175 --- /dev/null +++ b/network/network_test.go @@ -0,0 +1,14 @@ +package network + +import ( + "fmt" + "testing" +) + +func TestIpv4SecurityCheck(t *testing.T) { + // 全项测试 + ipInfo, _, _ := NetworkCheck("both", false, "zh") + fmt.Println("--------------------------------------------------") + fmt.Printf(ipInfo) + fmt.Println("--------------------------------------------------") +} diff --git a/network/utils/merge.go b/network/utils/merge.go new file mode 100644 index 0000000..58313bf --- /dev/null +++ b/network/utils/merge.go @@ -0,0 +1,54 @@ +package utils + +import ( + "fmt" + + networkModel "github.com/oneclickvirt/basics/network/model" +) + +// chooseString 用于选择非空字符串 +func chooseString(src, dst string) string { + if src != "" { + return src + } + return dst +} + +// CompareAndMergeIpInfo 用于比较和合并两个 IpInfo 结构体 +func CompareAndMergeIpInfo(dst, src *networkModel.IpInfo) (res *networkModel.IpInfo, err error) { + if src == nil { + return nil, fmt.Errorf("Error merge IpInfo") + } + if dst == nil { + dst = &networkModel.IpInfo{} + } + dst.Ip = chooseString(src.Ip, dst.Ip) + dst.ASN = chooseString(src.ASN, dst.ASN) + dst.Org = chooseString(src.Org, dst.Org) + dst.Country = chooseString(src.Country, dst.Country) + dst.Region = chooseString(src.Region, dst.Region) + dst.City = chooseString(src.City, dst.City) + return dst, nil +} + +// CompareAndMergeSecurityInfo 用于比较和合并两个 SecurityInfo 结构体 +func CompareAndMergeSecurityInfo(dst, src *networkModel.SecurityInfo) (res *networkModel.SecurityInfo, err error) { + if src == nil { + return nil, fmt.Errorf("Error merge SecurityInfo") + } + if dst == nil { + dst = &networkModel.SecurityInfo{} + } + dst.IsAbuser = chooseString(src.IsAbuser, dst.IsAbuser) + dst.IsAttacker = chooseString(src.IsAttacker, dst.IsAttacker) + dst.IsBogon = chooseString(src.IsBogon, dst.IsBogon) + dst.IsCloudProvider = chooseString(src.IsCloudProvider, dst.IsCloudProvider) + dst.IsProxy = chooseString(src.IsProxy, dst.IsProxy) + dst.IsRelay = chooseString(src.IsRelay, dst.IsRelay) + dst.IsTor = chooseString(src.IsTor, dst.IsTor) + dst.IsTorExit = chooseString(src.IsTorExit, dst.IsTorExit) + dst.IsVpn = chooseString(src.IsVpn, dst.IsVpn) + dst.IsAnonymous = chooseString(src.IsAnonymous, dst.IsAnonymous) + dst.IsThreat = chooseString(src.IsThreat, dst.IsThreat) + return dst, nil +} diff --git a/network/utils/parse.go b/network/utils/parse.go new file mode 100644 index 0000000..934101e --- /dev/null +++ b/network/utils/parse.go @@ -0,0 +1,86 @@ +package utils + +import ( + networkModel "github.com/oneclickvirt/basics/network/model" + "strconv" + "strings" +) + +func ParseIpInfo(data map[string]interface{}) *networkModel.IpInfo { + ipInfo := &networkModel.IpInfo{} + if ip, ok := data["ip"].(string); ok { + ipInfo.Ip = ip + } + if location, ok := data["location"].(map[string]interface{}); ok { + if city, ok := location["city"].(string); ok { + ipInfo.City = city + } + if region, ok := location["region"].(map[string]interface{}); ok { + if name, ok := region["name"].(string); ok { + ipInfo.Region = name + } + } + if country, ok := location["country"].(map[string]interface{}); ok { + if name, ok := country["name"].(string); ok { + ipInfo.Country = name + } + } + } + if connection, ok := data["connection"].(map[string]interface{}); ok { + if asn, ok := connection["asn"].(float64); ok { + ipInfo.ASN = strconv.Itoa(int(asn)) + } + if org, ok := connection["organization"].(string); ok { + ipInfo.Org = org + } + } + return ipInfo +} + +func ParseSecurityInfo(data map[string]interface{}) *networkModel.SecurityInfo { + securityInfo := &networkModel.SecurityInfo{} + if security, ok := data["security"].(map[string]interface{}); ok { + if isAbuser, ok := security["is_abuser"].(bool); ok { + securityInfo.IsAbuser = strconv.FormatBool(isAbuser) + } + if isAttacker, ok := security["is_attacker"].(bool); ok { + securityInfo.IsAttacker = strconv.FormatBool(isAttacker) + } + if isBogon, ok := security["is_bogon"].(bool); ok { + securityInfo.IsBogon = strconv.FormatBool(isBogon) + } + if isCloudProvider, ok := security["is_cloud_provider"].(bool); ok { + securityInfo.IsCloudProvider = strconv.FormatBool(isCloudProvider) + } + if isProxy, ok := security["is_proxy"].(bool); ok { + securityInfo.IsProxy = strconv.FormatBool(isProxy) + } + if isRelay, ok := security["is_relay"].(bool); ok { + securityInfo.IsRelay = strconv.FormatBool(isRelay) + } + if isTor, ok := security["is_tor"].(bool); ok { + securityInfo.IsTor = strconv.FormatBool(isTor) + } + if isTorExit, ok := security["is_tor_exit"].(bool); ok { + securityInfo.IsTorExit = strconv.FormatBool(isTorExit) + } + if isVpn, ok := security["is_vpn"].(bool); ok { + securityInfo.IsVpn = strconv.FormatBool(isVpn) + } + if isAnonymous, ok := security["is_anonymous"].(bool); ok { + securityInfo.IsAnonymous = strconv.FormatBool(isAnonymous) + } + if isThreat, ok := security["is_threat"].(bool); ok { + securityInfo.IsThreat = strconv.FormatBool(isThreat) + } + } + return securityInfo +} + +// ParseYesNo 检测文本内容含No则返回No,否则返回Yes +func ParseYesNo(text string) string { + if strings.Contains(strings.ToLower(text), "no") { + return "No" + } + return "Yes" +} diff --git a/network/utils/utils.go b/network/utils/utils.go new file mode 100644 index 0000000..bfcf37d --- /dev/null +++ b/network/utils/utils.go @@ -0,0 +1,93 @@ +package utils + +import ( + "context" + "encoding/json" + "fmt" + "net" + "net/http" + "reflect" + "strings" + "time" +) + +// FetchJsonFromURL 函数用于从指定的 URL 获取信息 +// url 参数表示要获取信息的 URL +// netType 参数表示网络类型,只能为 "tcp4" 或 "tcp6"。 +// enableHeader 参数表示是否启用请求头信息。 +// additionalHeader 参数表示传入的额外的请求头信息(用于传输api的key)。 +// 返回一个解析 json 得到的 map 和 一个可能发生的错误 。 +func FetchJsonFromURL(url, netType string, enableHeader bool, additionalHeader string) (map[string]interface{}, error) { + // 检查网络类型是否有效 + if netType != "tcp4" && netType != "tcp6" { + return nil, fmt.Errorf("Invalid netType: %s. Expected 'tcp4' or 'tcp6'.", netType) + } + // 创建 HTTP 客户端 + client := &http.Client{ + Timeout: 6 * time.Second, + Transport: &http.Transport{ + DialContext: func(ctx context.Context, network string, addr string) (net.Conn, error) { + return (&net.Dialer{}).DialContext(ctx, netType, addr) + }, + TLSHandshakeTimeout: 3 * time.Second, + ResponseHeaderTimeout: 3 * time.Second, + ExpectContinueTimeout: 3 * time.Second, + }, + } + // 创建 HTTP 请求 + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, fmt.Errorf("Error creating request: %v", err) + } + // 如果启用请求头,则设置请求头信息 + if enableHeader { + req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36") + req.Header.Set("Accept-Language", "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2") + if additionalHeader != "" { + tempList := strings.Split(additionalHeader, ":") + if len(tempList) == 2 { + req.Header.Set(tempList[0], tempList[1]) + } else if len(tempList) > 2 { + req.Header.Set(tempList[0], strings.Join(tempList[1:], ":")) + } + } + } + // 执行 HTTP 请求 + resp, err := client.Do(req) + if err != nil { + //fmt.Printf("Error fetching %s info: %v \n", url, err) + return nil, fmt.Errorf("Error fetching %s info: %v", url, err) + } + defer resp.Body.Close() + // 解析 JSON 响应体 + var data map[string]interface{} + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + //fmt.Printf("Error decoding %s info: %v \n", url, err) + return nil, fmt.Errorf("Error decoding %s info: %v ", url, err) + } + // 返回解析后的数据和 nil 错误 + return data, nil +} + +// BoolToString 将布尔值转换为对应的字符串表示,true 则返回 "Yes",false 则返回 "No" +func BoolToString(value bool) string { + if value { + return "Yes" + } + return "No" +} + +// ExtractFieldNames 获取结构体的属性名字 +func ExtractFieldNames(data interface{}) []string { + var fields []string + val := reflect.ValueOf(data).Elem() + for i := 0; i < val.NumField(); i++ { + field := val.Type().Field(i) + name := field.Name + if name != "Tag" { + fields = append(fields, name) + } + } + return fields +} diff --git a/system/cpu_linux.go b/system/cpu_linux.go new file mode 100644 index 0000000..f1b74d5 --- /dev/null +++ b/system/cpu_linux.go @@ -0,0 +1,69 @@ +package system + +import ( + "fmt" + "os" + "runtime" + "strconv" + "strings" + + "github.com/oneclickvirt/basics/system/model" + "github.com/shirou/gopsutil/cpu" +) + +func checkCPUFeatureLinux(filename string, feature string) (string, bool) { + content, err := os.ReadFile(filename) + if err != nil { + return "Error reading file", false + } + lines := strings.Split(string(content), "\n") + for _, line := range lines { + if strings.Contains(line, feature) { + return "✔️ Enabled", true + } + } + return "❌ Disabled", false +} + +func checkCPUFeature(filename string, feature string) (string, bool) { + if runtime.GOOS == "linux" { + return checkCPUFeatureLinux(filename, feature) + } + return "Unsupported OS", false +} + +func getCpuInfo(ret *model.SystemInfo, cpuType string) (*model.SystemInfo, error) { + var aesFeature, virtFeature, hypervFeature string + var st bool + ci, err := cpu.Info() + if err != nil { + return nil, fmt.Errorf("cpu.Info error: %v", err.Error()) + } else { + ret.CpuModel = "" + for i := 0; i < len(ci); i++ { + if len(ret.CpuModel) < len(ci[i].ModelName) { + ret.CpuModel = ci[i].ModelName + fmt.Sprintf("%d %s Core", len(ci), cpuType) + " @ " + + strconv.FormatFloat(ci[i].Mhz, 'f', 2, 64) + " MHz" + ret.CpuCores = fmt.Sprintf("%d vCPU(s)", int(ci[i].Cores)) + if ci[i].CacheSize != 0 { // Windows查不到CPU的三缓 + ret.CpuCache = string(ci[i].CacheSize) + } + } + } + } + if runtime.GOOS == "windows" { + aesFeature = `HARDWARE\DESCRIPTION\System\CentralProcessor\0` + virtFeature = `HARDWARE\DESCRIPTION\System\CentralProcessor\0` + hypervFeature = `SYSTEM\CurrentControlSet\Control\Hypervisor\0` + } else if runtime.GOOS == "linux" { + aesFeature = "/proc/cpuinfo" + virtFeature = "/proc/cpuinfo" + hypervFeature = "/proc/cpuinfo" + } + ret.CpuAesNi, _ = checkCPUFeature(aesFeature, "aes") + ret.CpuVAH, st = checkCPUFeature(virtFeature, "vmx") + if !st { + ret.CpuVAH, _ = checkCPUFeature(hypervFeature, "hypervisor") + } + return ret, nil +} diff --git a/system/disk.go b/system/disk.go new file mode 100644 index 0000000..b10cb4e --- /dev/null +++ b/system/disk.go @@ -0,0 +1,96 @@ +package system + +import ( + "github.com/shirou/gopsutil/disk" + "os/exec" + "runtime" + "strconv" + "strings" +) + +func getDiskInfo() (string, string, string, error) { + var diskTotalStr, diskUsageStr, bootPath string + tempDiskTotal, tempDiskUsage := getDiskTotalAndUsed() + diskTotalGB := float64(tempDiskTotal) / (1024 * 1024 * 1024) + diskUsageGB := float64(tempDiskUsage) / (1024 * 1024 * 1024) + // 字节为单位 进行单位转换 + if diskTotalGB < 1 { + diskTotalStr = strconv.FormatFloat(diskTotalGB*1024, 'f', 2, 64) + " MB" + } else { + diskTotalStr = strconv.FormatFloat(diskTotalGB, 'f', 2, 64) + " GB" + } + if diskUsageGB < 1 { + diskUsageStr = strconv.FormatFloat(diskUsageGB*1024, 'f', 2, 64) + " MB" + } else { + diskUsageStr = strconv.FormatFloat(diskUsageGB, 'f', 2, 64) + " GB" + } + parts, err := disk.Partitions(true) + if err != nil { + bootPath = "" + } else { + for _, part := range parts { + if part.Fstype == "tmpfs" { + continue + } + usageStat, err := disk.Usage(part.Mountpoint) + if err != nil { + continue + } + if usageStat.Total > 0 { + bootPath = part.Mountpoint + break + } + } + } + return diskTotalStr, diskUsageStr, bootPath, nil +} + +func getDiskTotalAndUsed() (total uint64, used uint64) { + devices := make(map[string]string) + // 使用默认过滤规则 + diskList, _ := disk.Partitions(false) + for _, d := range diskList { + fsType := strings.ToLower(d.Fstype) + // 不统计 K8s 的虚拟挂载点:https://github.com/shirou/gopsutil/issues/1007 + if devices[d.Device] == "" && isListContainsStr(expectDiskFsTypes, fsType) && !strings.Contains(d.Mountpoint, "/var/lib/kubelet") { + devices[d.Device] = d.Mountpoint + } + } + for _, mountPath := range devices { + diskUsageOf, err := disk.Usage(mountPath) + if err == nil { + total += diskUsageOf.Total + used += diskUsageOf.Used + } + } + // Fallback 到这个方法,仅统计根路径,适用于OpenVZ之类的. + if runtime.GOOS == "linux" && total == 0 && used == 0 { + cmd := exec.Command("df") + out, err := cmd.CombinedOutput() + if err == nil { + s := strings.Split(string(out), "\n") + for _, c := range s { + info := strings.Fields(c) + if len(info) == 6 { + if info[5] == "/" { + total, _ = strconv.ParseUint(info[1], 0, 64) + used, _ = strconv.ParseUint(info[2], 0, 64) + // 默认获取的是1K块为单位的. + total = total * 1024 + used = used * 1024 + } + } + } + } + } + return +} + +func isListContainsStr(list []string, str string) bool { + for i := 0; i < len(list); i++ { + if strings.Contains(str, list[i]) { + return true + } + } + return false +} diff --git a/system/host_linux.go b/system/host_linux.go new file mode 100644 index 0000000..86a6fd8 --- /dev/null +++ b/system/host_linux.go @@ -0,0 +1,52 @@ +package system + +import ( + "context" + "fmt" + "runtime" + "time" + + "github.com/libp2p/go-nat" + "github.com/shirou/gopsutil/host" +) + +func getHostInfo() (string, string, string, string, string, string, string, error) { + var Platform, Kernal, Arch, VmType, NatType string + var cachedBootTime time.Time + hi, err := host.Info() + if err != nil { + println("host.Info error:", err) + } else { + if hi.VirtualizationRole == "guest" { + cpuType = "Virtual" + } else { + cpuType = "Physical" + } + if runtime.GOOS == "linux" { + Platform = hi.Platform + Kernal = hi.PlatformVersion + } else { + Platform = hi.Platform + " " + hi.PlatformVersion + } + Arch = hi.KernelArch + // 查询虚拟化类型 + VmType = hi.VirtualizationSystem + // 系统运行时长查询 + cachedBootTime = time.Unix(int64(hi.BootTime), 0) + } + uptimeDuration := time.Since(cachedBootTime) + days := int(uptimeDuration.Hours() / 24) + uptimeDuration -= time.Duration(days*24) * time.Hour + hours := int(uptimeDuration.Hours()) + uptimeDuration -= time.Duration(hours) * time.Hour + minutes := int(uptimeDuration.Minutes()) + uptimeFormatted := fmt.Sprintf("%d days, %02d hours, %02d minutes", days, hours, minutes) + // 查询NAT类型 + ctx := context.Background() + gateway, err := nat.DiscoverGateway(ctx) + if err == nil { + natType := gateway.Type() + NatType = natType + } + return cpuType, uptimeFormatted, Platform, Kernal, Arch, VmType, NatType, nil +} diff --git a/system/load_linux.go b/system/load_linux.go new file mode 100644 index 0000000..c66a9c6 --- /dev/null +++ b/system/load_linux.go @@ -0,0 +1,56 @@ +package system + +import ( + "bufio" + "os" + "runtime" + "strconv" + "strings" + + "github.com/shirou/gopsutil/load" +) + +// 获取系统负载信息 +func getSystemLoad() (float64, float64, float64, error) { + var load1, load5, load15 float64 + var err error + if runtime.GOOS == "linux" { + // 尝试从 /proc/loadavg 文件获取负载信息 + load1, load5, load15, err = getLoadFromProc() + if err != nil { + load1, load5, load15 = 0, 0, 0 + } + } + // 使用 gopsutil 获取负载 + avg, err := load.Avg() + if err != nil { + load1, load5, load15 = 0, 0, 0 + } else { + if avg.Load1 != 0 && avg.Load5 != 0 && avg.Load15 != 0 { + load1, load5, load15 = avg.Load1, avg.Load5, avg.Load15 + } + } + return load1, load5, load15, nil +} + +// getLoadFromProc 从 /proc/loadavg 文件中获取负载信息 +func getLoadFromProc() (float64, float64, float64, error) { + file, err := os.Open("/proc/loadavg") + if err != nil { + return 0, 0, 0, err + } + defer file.Close() + scanner := bufio.NewScanner(file) + for scanner.Scan() { + fields := strings.Fields(scanner.Text()) + // 解析负载信息并转换为 float64 类型 + load1, _ := strconv.ParseFloat(fields[0], 64) + load5, _ := strconv.ParseFloat(fields[1], 64) + load15, _ := strconv.ParseFloat(fields[2], 64) + return load1, load5, load15, nil + } + if err := scanner.Err(); err != nil { + return 0, 0, 0, err + } + return 0, 0, 0, nil +} diff --git a/system/memory.go b/system/memory.go new file mode 100644 index 0000000..cf0440b --- /dev/null +++ b/system/memory.go @@ -0,0 +1,89 @@ +package system + +import ( + "github.com/shirou/gopsutil/mem" + "os" + "runtime" + "strconv" + "strings" +) + +func getMemoryInfo() (string, string, string, string, string, string) { + var memoryTotalStr, memoryUsageStr, swapTotalStr, swapUsageStr, virtioBalloonStatus, KernelSamepageMerging string + mv, err := mem.VirtualMemory() + if err != nil { + println("mem.VirtualMemory error:", err) + } else { + memoryTotal := float64(mv.Total) + memoryUsage := float64(mv.Total - mv.Available) + if memoryTotal < 1024*1024*1024 { + memoryTotalStr = strconv.FormatFloat(memoryTotal/(1024*1024), 'f', 2, 64) + " MB" + } else { + memoryTotalStr = strconv.FormatFloat(memoryTotal/(1024*1024*1024), 'f', 2, 64) + " GB" + } + if memoryUsage < 1024*1024*1024 { + memoryUsageStr = strconv.FormatFloat(memoryUsage/(1024*1024), 'f', 2, 64) + " MB" + } else { + memoryUsageStr = strconv.FormatFloat(memoryUsage/(1024*1024*1024), 'f', 2, 64) + " GB" + } + if runtime.GOOS != "windows" { + swapTotal := float64(mv.SwapTotal) + swapUsage := float64(mv.SwapTotal - mv.SwapFree) + if swapTotal != 0 { + if swapTotal < 1024*1024*1024 { + swapTotalStr = strconv.FormatFloat(swapTotal/(1024*1024), 'f', 2, 64) + " MB" + } else { + swapTotalStr = strconv.FormatFloat(swapTotal/(1024*1024*1024), 'f', 2, 64) + " GB" + } + if swapUsage < 1024*1024*1024 { + swapUsageStr = strconv.FormatFloat(swapUsage/(1024*1024), 'f', 2, 64) + " MB" + } else { + swapUsageStr = strconv.FormatFloat(swapUsage/(1024*1024*1024), 'f', 2, 64) + " GB" + } + } + } + } + if runtime.GOOS == "windows" { + // gopsutil 在 Windows 下不能正确取 swap + ms, err := mem.SwapMemory() + if err != nil { + println("mem.SwapMemory error:", err) + } else { + swapTotal := float64(ms.Total) + swapUsage := float64(ms.Used) + if swapTotal != 0 { + if swapTotal < 1024*1024*1024 { + swapTotalStr = strconv.FormatFloat(swapTotal/(1024*1024), 'f', 2, 64) + " MB" + } else { + swapTotalStr = strconv.FormatFloat(swapTotal/(1024*1024*1024), 'f', 2, 64) + " GB" + } + if swapUsage < 1024*1024*1024 { + swapUsageStr = strconv.FormatFloat(swapUsage/(1024*1024), 'f', 2, 64) + " MB" + } else { + swapUsageStr = strconv.FormatFloat(swapUsage/(1024*1024*1024), 'f', 2, 64) + " GB" + } + } + } + } + virtioBalloon, err := os.ReadFile("/proc/modules") + if err == nil { + if strings.Contains(string(virtioBalloon), "virtio_balloon") { + virtioBalloonStatus = "✔️ Enabled" + } else { + virtioBalloonStatus = "" + } + } else { + virtioBalloonStatus = "" + } + ksmStatus, err := os.ReadFile("/sys/kernel/mm/ksm/run") + if err == nil { + if strings.Contains(string(ksmStatus), "1") { + KernelSamepageMerging = "✔️ Enabled" + } else { + KernelSamepageMerging = "" + } + } else { + KernelSamepageMerging = "" + } + return memoryTotalStr, memoryUsageStr, swapTotalStr, swapUsageStr, virtioBalloonStatus, KernelSamepageMerging +} diff --git a/system/model/model.go b/system/model/model.go new file mode 100644 index 0000000..b2da361 --- /dev/null +++ b/system/model/model.go @@ -0,0 +1,52 @@ +package model + +type CpuInfo struct { + CpuModel string + CpuCores string + CpuCache string + CpuAesNi string + CpuVAH string +} + +type MemoryInfo struct { + MemoryUsage string + MemoryTotal string + SwapUsage string + SwapTotal string +} + +type DiskInfo struct { + DiskUsage string + DiskTotal string + BootPath string +} + +type SystemInfo struct { + CpuInfo + MemoryInfo + DiskInfo + Platform string // 系统名字 Distro1 + PlatformVersion string // 系统版本 Distro2 + Kernel string // 系统内核 + Arch string // + Uptime string // 正常运行时间 + VmType string // 虚拟化架构 + Load string // load1 load2 load3 + NatType string // stun + VirtioBalloon string // 气球驱动 + KSM string // 内存合并 + TcpAccelerationMethod string // TCP拥塞控制 +} + +type Win32_Processor struct { + L2CacheSize uint32 + L3CacheSize uint32 +} + +type Win32_ComputerSystem struct { + SystemType string +} + +type Win32_OperatingSystem struct { + BuildType string +} diff --git a/system/system.go b/system/system.go new file mode 100644 index 0000000..69b4eb5 --- /dev/null +++ b/system/system.go @@ -0,0 +1,75 @@ +package system + +import ( + "fmt" + "github.com/oneclickvirt/basics/system/model" + "strconv" +) + +var ( + expectDiskFsTypes = []string{ + "apfs", "ext4", "ext3", "ext2", "f2fs", "reiserfs", "jfs", "btrfs", + "fuseblk", "zfs", "simfs", "ntfs", "fat32", "exfat", "xfs", "fuse.rclone", + } + cpuType string +) + +// GetHost 获取主机硬件信息 +func GetHost() *model.SystemInfo { + var ret = &model.SystemInfo{} + // 系统信息查询 + cpuType, ret.Uptime, ret.Platform, ret.Kernel, ret.Arch, ret.VmType, ret.NatType, _ = getHostInfo() + // CPU信息查询 + ret, _ = getCpuInfo(ret, cpuType) + // 硬盘信息查询 + ret.DiskTotal, ret.DiskUsage, ret.BootPath, _ = getDiskInfo() + // 内存信息查询 + ret.MemoryTotal, ret.MemoryUsage, ret.SwapTotal, ret.SwapUsage, ret.VirtioBalloon, ret.KSM = getMemoryInfo() + // 获取负载信息 + load1, load5, load15, err := getSystemLoad() + if err != nil { + load1, load5, load15 = 0, 0, 0 + } + ret.Load = strconv.FormatFloat(load1, 'f', 2, 64) + " / " + + strconv.FormatFloat(load5, 'f', 2, 64) + " / " + + strconv.FormatFloat(load15, 'f', 2, 64) + // 获取TCP控制算法 + ret.TcpAccelerationMethod = getTCPAccelerateStatus() + return ret +} + +func GetSystemInfo() { + ret := GetHost() + fmt.Println("Cpu Model :", ret.CpuModel) + fmt.Println("Cpu Cores :", ret.CpuCores) + if ret.CpuCache != "" { + fmt.Println("Cpu Cache :", ret.CpuCache) + } + fmt.Println("AES-NI :", ret.CpuAesNi) + fmt.Println("VM-x/AMD-V/Hyper-V :", ret.CpuVAH) + fmt.Println("RAM :", ret.MemoryUsage+" / "+ret.MemoryTotal) + if ret.VirtioBalloon != "" { + fmt.Println("Virtio Balloon :", ret.VirtioBalloon) + } + if ret.KSM != "" { + fmt.Println("KSM :", ret.KSM) + } + if ret.SwapTotal == "" && ret.SwapUsage == "" { + fmt.Println("Swap : [ no swap partition or swap file detected ]") + } else if ret.SwapTotal != "" && ret.SwapUsage != "" { + fmt.Println("Swap :", ret.SwapUsage+" / "+ret.SwapTotal) + } + fmt.Println("Disk :", ret.DiskUsage+" / "+ret.DiskTotal) + fmt.Println("Boot Path :", ret.BootPath) + fmt.Println("OS Release :", ret.Platform+" ["+ret.Arch+"] ") + if ret.Kernel != "" { + fmt.Println("Kernel :", ret.Kernel) + } + fmt.Println("Uptime :", ret.Uptime) + fmt.Println("Load :", ret.Load) + fmt.Println("VM Type :", ret.VmType) + fmt.Println("NAT Type :", ret.NatType) + if ret.TcpAccelerationMethod != "" { + fmt.Println("Tcp Accelerate :", ret.TcpAccelerationMethod) + } +} diff --git a/system/system_test.go b/system/system_test.go new file mode 100644 index 0000000..14c61dd --- /dev/null +++ b/system/system_test.go @@ -0,0 +1,9 @@ +package system + +import ( + "testing" +) + +func TestGetSystemInfo(t *testing.T) { + GetSystemInfo() +} diff --git a/system/tcpcontrol_linux.go b/system/tcpcontrol_linux.go new file mode 100644 index 0000000..4c2e45d --- /dev/null +++ b/system/tcpcontrol_linux.go @@ -0,0 +1,19 @@ +package system + +import ( + "bytes" + "os/exec" +) + +// getTCPAccelerateStatus 查询TCP控制算法 +func getTCPAccelerateStatus() string { + cmd := exec.Command("sysctl", "-n", "net.ipv4.tcp_congestion_control") + var out bytes.Buffer + cmd.Stdout = &out + err := cmd.Run() + if err != nil { + return "" + } else { + return out.String() + } +} diff --git a/system/tcpcontrol_windows.go b/system/tcpcontrol_windows.go new file mode 100644 index 0000000..f7ed9f4 --- /dev/null +++ b/system/tcpcontrol_windows.go @@ -0,0 +1,6 @@ +package system + +// getTCPAccelerateStatus 查询TCP控制算法 +func getTCPAccelerateStatus() string { + return "" +}