Files
lan-manager/server/services/ping.go

183 lines
4.6 KiB
Go

package services
import (
"context"
"encoding/json"
"fmt"
"lan-manager/server/db"
"lan-manager/server/utils"
"os/exec"
"runtime"
"strings"
"time"
"net"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
)
func PingHost(ip string) bool {
// Try system ping first (no special privileges needed on Linux)
if ok := pingWithSystem(ip); ok {
return true
}
// Fallback to raw ICMP
return pingWithICMP(ip)
}
func pingWithSystem(ip string) bool {
var cmd *exec.Cmd
if runtime.GOOS == "windows" {
cmd = exec.Command("ping", "-n", "1", "-w", "3000", ip)
} else if runtime.GOOS == "darwin" {
// macOS ping -W is in milliseconds
cmd = exec.Command("ping", "-c", "1", "-W", "3000", ip)
} else {
// Linux ping -W is in seconds
cmd = exec.Command("ping", "-c", "1", "-W", "3", ip)
}
err := cmd.Run()
return err == nil
}
func pingWithICMP(ip string) bool {
c, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0")
if err != nil {
return false
}
defer c.Close()
m := &icmp.Message{
Type: ipv4.ICMPTypeEcho,
Code: 0,
Body: &icmp.Echo{ID: 1, Seq: 1, Data: []byte("lan-manager")},
}
wb, err := m.Marshal(nil)
if err != nil {
return false
}
if _, err := c.WriteTo(wb, &net.IPAddr{IP: parseIP(ip)}); err != nil {
return false
}
rb := make([]byte, 1500)
_ = c.SetReadDeadline(time.Now().Add(3 * time.Second))
n, _, err := c.ReadFrom(rb)
if err != nil {
return false
}
rm, err := icmp.ParseMessage(ipv4.ICMPTypeEchoReply.Protocol(), rb[:n])
if err != nil {
return false
}
return rm.Type == ipv4.ICMPTypeEchoReply
}
func parseIP(ip string) []byte {
parts := strings.Split(ip, ".")
if len(parts) != 4 {
return nil
}
res := make([]byte, 4)
for i, p := range parts {
var v byte
fmt.Sscanf(p, "%d", &v)
res[i] = v
}
return res
}
func StartPingService(interval int) {
ticker := time.NewTicker(time.Duration(interval) * time.Second)
go func() {
for range ticker.C {
rows, err := db.DB.Query(`SELECT id, ip, ssh_port, ssh_username, ssh_password FROM machines`)
if err != nil {
fmt.Printf("[Ping] query error: %v\n", err)
continue
}
type machinePing struct {
id int64
ip string
sshPort int
sshUsername string
sshPassword string
}
list := []machinePing{}
for rows.Next() {
var m machinePing
if err := rows.Scan(&m.id, &m.ip, &m.sshPort, &m.sshUsername, &m.sshPassword); err == nil {
list = append(list, m)
} else {
fmt.Printf("[Ping] scan error: %v\n", err)
}
}
rows.Close()
fmt.Printf("[Ping] tick processed %d machines\n", len(list))
for _, m := range list {
online := PingHost(m.ip)
var onlineInt int
if online {
onlineInt = 1
}
_, dbErr := db.DB.Exec(`UPDATE machines SET is_online = ?, last_ping_at = CURRENT_TIMESTAMP WHERE id = ?`, onlineInt, m.id)
if dbErr != nil {
fmt.Printf("[Ping] update status error for %s: %v\n", m.ip, dbErr)
} else {
fmt.Printf("[Ping] %s -> online=%v\n", m.ip, online)
}
// Auto fetch SSH info if credentials are saved
if m.sshUsername != "" && m.sshPassword != "" {
go func(mid int64, mip string, mport int, muser, mpass string) {
plainPass, decryptErr := utils.Decrypt(mpass)
if decryptErr != nil {
fmt.Printf("[SSH] decrypt failed for %s:%d: %v\n", mip, mport, decryptErr)
return
}
result, err := GetSSHInfo(mip, mport, muser, plainPass)
if err != nil {
fmt.Printf("[SSH] auto-fetch failed for %s:%d: %v\n", mip, mport, err)
return
}
portsStr := ""
if len(result.ListenPorts) > 0 {
b, _ := json.Marshal(result.ListenPorts)
portsStr = string(b)
if len(portsStr) > 2 {
portsStr = portsStr[1 : len(portsStr)-1]
}
}
_, dbErr := db.DB.Exec(`UPDATE machines SET cpu_info=?, memory_info=?, disk_info=?, uptime=?, listen_ports=?, ssh_synced_at=CURRENT_TIMESTAMP, updated_at=CURRENT_TIMESTAMP WHERE id=?`,
result.RawCPUInfo, result.RawMemoryInfo, result.RawDiskInfo, result.Uptime, portsStr, mid)
if dbErr != nil {
fmt.Printf("[SSH] auto-fetch db error for %s:%d: %v\n", mip, mport, dbErr)
} else {
fmt.Printf("[SSH] auto-fetch success for %s:%d\n", mip, mport)
}
}(m.id, m.ip, m.sshPort, m.sshUsername, m.sshPassword)
}
}
}
}()
}
func CleanupLogs(ctx context.Context, retentionDays int) {
if retentionDays <= 0 {
return
}
ticker := time.NewTicker(24 * time.Hour)
go func() {
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
_, _ = db.DB.Exec(`DELETE FROM operation_logs WHERE created_at < datetime('now', '-`+fmt.Sprintf("%d", retentionDays)+` days')`)
}
}
}()
}