Files
attacker b25ad8705f feat: K8s 集群实时代理 — 直接调用 K8s API 不存储 MySQL
- 新增 pkg/k8sclient 轻量级 K8s REST 客户端(解析 kubeconfig TLS 证书)
- ListNodes/Pods/Deployments/Services/Ingresses 改为实时代理 K8s API
- 集群同步端点 POST /:id/sync(拉取后仍存 MySQL 用于离线缓存储备)
- 前端 k8sApi 新增 syncCluster 方法
- 修复 SNMP MaxRepetitions uint8→uint32 类型错误
- 修复 ServicePort.TargetPort 兼容 int/string 双类型
- 创建 k8s_nodes/pods/deployments/services/ingresses 五张表
2026-06-19 06:32:18 +00:00

622 lines
17 KiB
Go

package snmp
import (
"context"
"fmt"
"strconv"
"strings"
"sync"
"time"
"github.com/gosnmp/gosnmp"
)
// ============================================
// SNMP 采集器
// ============================================
// Collector SNMP采集器
// 封装 gosnmp 库,提供SNMP设备的OID采集功能
// 支持 SNMP v1/v2c/v3,支持 Get/Walk/BulkWalk 操作
type Collector struct {
config *gosnmp.GoSNMP
mu sync.Mutex
}
// CollectorConfig 采集器配置
type CollectorConfig struct {
IP string // 目标IP地址
Port uint16 // SNMP端口,默认161
Version gosnmp.SnmpVersion // SNMP版本
Community string // 共同体字符串(v1/v2c)
Timeout time.Duration // 超时时间
Retries int // 重试次数
MaxRepetitions uint8 // BulkWalk最大重复数
// SNMP v3 参数
SecurityModel gosnmp.SnmpV3SecurityModel
MsgFlags gosnmp.SnmpV3MsgFlags
SecurityParameters *gosnmp.UsmSecurityParameters
}
// WalkResult SNMP Walk结果
type WalkResult struct {
OID string `json:"oid"` // OID
Type gosnmp.Asn1BER `json:"type"` // SNMP数据类型
TypeName string `json:"type_name"` // 类型名称
Value interface{} `json:"value"` // 值
Index string `json:"index"` // 索引后缀
Timestamp time.Time `json:"timestamp"` // 采集时间
}
// CollectResult 采集结果
type CollectResult struct {
DeviceID uint `json:"device_id"`
DeviceIP string `json:"device_ip"`
Timestamp int64 `json:"timestamp"`
Success bool `json:"success"`
Error string `json:"error,omitempty"`
Metrics []SNMPMetricValue `json:"metrics"`
Duration float64 `json:"duration_ms"` // 采集耗时(ms)
}
// SNMPMetricValue SNMP采集指标值
type SNMPMetricValue struct {
Name string `json:"name"`
OID string `json:"oid"`
Index string `json:"index,omitempty"`
Value float64 `json:"value"`
RawValue string `json:"raw_value,omitempty"`
Unit string `json:"unit,omitempty"`
DataType string `json:"data_type"`
}
// NewCollector 创建SNMP采集器
// config: 采集器配置
// 返回配置好的SNMP采集器实例和错误
func NewCollector(config *CollectorConfig) (*Collector, error) {
if config.IP == "" {
return nil, fmt.Errorf("target IP is required")
}
g := &gosnmp.GoSNMP{
Target: config.IP,
Port: config.Port,
Version: config.Version,
Timeout: config.Timeout,
Retries: config.Retries,
MaxRepetitions: uint32(config.MaxRepetitions),
ExponentialTimeout: true,
}
// 设置默认值
if g.Port == 0 {
g.Port = 161
}
if g.Timeout == 0 {
g.Timeout = 10 * time.Second
}
if g.Retries == 0 {
g.Retries = 3
}
if g.MaxRepetitions == 0 {
g.MaxRepetitions = 25
}
// 根据版本设置认证参数
switch config.Version {
case gosnmp.Version1:
g.Community = config.Community
if g.Community == "" {
g.Community = "public"
}
case gosnmp.Version2c:
g.Community = config.Community
if g.Community == "" {
g.Community = "public"
}
case gosnmp.Version3:
g.SecurityModel = config.SecurityModel
if g.SecurityModel == 0 {
g.SecurityModel = gosnmp.UserSecurityModel
}
g.MsgFlags = config.MsgFlags
if config.SecurityParameters != nil {
g.SecurityParameters = config.SecurityParameters
}
default:
return nil, fmt.Errorf("unsupported SNMP version: %v", config.Version)
}
return &Collector{
config: g,
}, nil
}
// NewCollectorV2c 快速创建SNMPv2c采集器
// ip: 目标IP
// community: 共同体字符串
func NewCollectorV2c(ip, community string) (*Collector, error) {
if community == "" {
community = "public"
}
return NewCollector(&CollectorConfig{
IP: ip,
Port: 161,
Version: gosnmp.Version2c,
Community: community,
Timeout: 10 * time.Second,
Retries: 3,
})
}
// NewCollectorV3 快速创建SNMPv3采集器
// ip: 目标IP
// username: 用户名
// authProtocol: 认证协议
// authPassword: 认证密码
// privProtocol: 加密协议
// privPassword: 加密密码
// securityLevel: 安全级别
func NewCollectorV3(ip, username string, authProtocol gosnmp.SnmpV3AuthProtocol, authPassword string,
privProtocol gosnmp.SnmpV3PrivProtocol, privPassword string,
securityLevel gosnmp.SnmpV3MsgFlags) (*Collector, error) {
securityParams := &gosnmp.UsmSecurityParameters{
UserName: username,
AuthenticationProtocol: authProtocol,
AuthenticationPassphrase: authPassword,
PrivacyProtocol: privProtocol,
PrivacyPassphrase: privPassword,
}
return NewCollector(&CollectorConfig{
IP: ip,
Port: 161,
Version: gosnmp.Version3,
Timeout: 10 * time.Second,
Retries: 3,
MsgFlags: securityLevel,
SecurityParameters: securityParams,
})
}
// Connect 建立SNMP连接
func (c *Collector) Connect() error {
c.mu.Lock()
defer c.mu.Unlock()
if err := c.config.Connect(); err != nil {
return fmt.Errorf("SNMP connect to %s failed: %w", c.config.Target, err)
}
return nil
}
// Close 关闭SNMP连接
func (c *Collector) Close() error {
c.mu.Lock()
defer c.mu.Unlock()
return c.config.Conn.Close()
}
// ============================================
// SNMP Get 操作
// ============================================
// Get 执行SNMP Get操作,获取指定OID的值
// oids: OID字符串列表
// 返回OID到值的映射和错误
func (c *Collector) Get(ctx context.Context, oids []string) (map[string]*WalkResult, error) {
if len(oids) == 0 {
return nil, fmt.Errorf("oids are required")
}
// 解析OID
parsedOIDs := make([]string, 0, len(oids))
for _, oid := range oids {
parsedOIDs = append(parsedOIDs, oid)
}
c.mu.Lock()
defer c.mu.Unlock()
// 确保连接
if c.config.Conn == nil {
if err := c.config.Connect(); err != nil {
return nil, fmt.Errorf("connect failed: %w", err)
}
}
result, err := c.config.Get(parsedOIDs)
if err != nil {
return nil, fmt.Errorf("SNMP Get failed: %w", err)
}
results := make(map[string]*WalkResult)
for _, v := range result.Variables {
r := &WalkResult{
OID: v.Name,
Type: v.Type,
TypeName: getSNMPTypeName(v.Type),
Value: convertSNMPValue(v),
Timestamp: time.Now(),
}
results[v.Name] = r
}
return results, nil
}
// GetSingle 获取单个OID的值
func (c *Collector) GetSingle(ctx context.Context, oid string) (*WalkResult, error) {
results, err := c.Get(ctx, []string{oid})
if err != nil {
return nil, err
}
result, ok := results[oid]
if !ok {
return nil, fmt.Errorf("OID not found in response: %s", oid)
}
return result, nil
}
// ============================================
// SNMP Walk 操作
// ============================================
// Walk 执行SNMP Walk操作,遍历指定OID下的所有子节点
// rootOID: 根OID
// 返回遍历结果列表和错误
func (c *Collector) Walk(ctx context.Context, rootOID string) ([]*WalkResult, error) {
if rootOID == "" {
return nil, fmt.Errorf("root OID is required")
}
c.mu.Lock()
defer c.mu.Unlock()
// 确保连接
if c.config.Conn == nil {
if err := c.config.Connect(); err != nil {
return nil, fmt.Errorf("connect failed: %w", err)
}
}
var results []*WalkResult
walkFn := func(data gosnmp.SnmpPDU) error {
index := ""
if strings.HasPrefix(data.Name, rootOID) && len(data.Name) > len(rootOID) {
suffix := strings.TrimPrefix(data.Name, rootOID)
index = strings.TrimPrefix(suffix, ".")
}
r := &WalkResult{
OID: data.Name,
Type: data.Type,
TypeName: getSNMPTypeName(data.Type),
Value: convertSNMPValue(data),
Index: index,
Timestamp: time.Now(),
}
results = append(results, r)
return nil
}
if err := c.config.Walk(rootOID, walkFn); err != nil {
return nil, fmt.Errorf("SNMP Walk failed for OID %s: %w", rootOID, err)
}
return results, nil
}
// ============================================
// SNMP BulkWalk 操作
// ============================================
// BulkWalk 执行SNMP BulkWalk操作,高效遍历指定OID下的所有子节点
// 相比Walk使用GetBulk请求,效率更高,适合遍历大量数据
// rootOID: 根OID
// 返回遍历结果列表和错误
func (c *Collector) BulkWalk(ctx context.Context, rootOID string) ([]*WalkResult, error) {
if rootOID == "" {
return nil, fmt.Errorf("root OID is required")
}
c.mu.Lock()
defer c.mu.Unlock()
// 确保连接
if c.config.Conn == nil {
if err := c.config.Connect(); err != nil {
return nil, fmt.Errorf("connect failed: %w", err)
}
}
var results []*WalkResult
walkFn := func(data gosnmp.SnmpPDU) error {
index := ""
if strings.HasPrefix(data.Name, rootOID) && len(data.Name) > len(rootOID) {
suffix := strings.TrimPrefix(data.Name, rootOID)
index = strings.TrimPrefix(suffix, ".")
}
r := &WalkResult{
OID: data.Name,
Type: data.Type,
TypeName: getSNMPTypeName(data.Type),
Value: convertSNMPValue(data),
Index: index,
Timestamp: time.Now(),
}
results = append(results, r)
return nil
}
if err := c.config.BulkWalk(rootOID, walkFn); err != nil {
return nil, fmt.Errorf("SNMP BulkWalk failed for OID %s: %w", rootOID, err)
}
return results, nil
}
// ============================================
// 批量采集
// ============================================
// OIDDefinition OID定义
type OIDDefinition struct {
Name string `json:"name"` // 指标名称
OID string `json:"oid"` // OID
Unit string `json:"unit"` // 单位
DataType string `json:"data_type"` // 数据类型
Conversion string `json:"conversion"` // 转换公式
IsTable bool `json:"is_table"` // 是否表OID(使用Walk)
}
// Collect 执行批量采集
// oids: OID定义列表
// 返回采集结果
func (c *Collector) Collect(ctx context.Context, oids []OIDDefinition) *CollectResult {
start := time.Now()
result := &CollectResult{
DeviceIP: c.config.Target,
Timestamp: time.Now().Unix(),
Metrics: make([]SNMPMetricValue, 0),
}
// 建立连接
if err := c.Connect(); err != nil {
result.Success = false
result.Error = err.Error()
result.Duration = float64(time.Since(start).Milliseconds())
return result
}
defer c.Close()
for _, def := range oids {
if def.IsTable {
// 表OID使用Walk/BulkWalk
walkResults, err := c.BulkWalk(ctx, def.OID)
if err != nil {
result.Error += fmt.Sprintf("[%s walk failed: %v]", def.Name, err)
continue
}
for _, wr := range walkResults {
mv := SNMPMetricValue{
Name: def.Name,
OID: wr.OID,
Index: wr.Index,
Unit: def.Unit,
}
if val, ok := wr.Value.(float64); ok {
mv.Value = applyConversion(val, def.Conversion)
mv.RawValue = fmt.Sprintf("%v", wr.Value)
} else {
mv.RawValue = fmt.Sprintf("%v", wr.Value)
if v, err := strconv.ParseFloat(mv.RawValue, 64); err == nil {
mv.Value = applyConversion(v, def.Conversion)
}
}
mv.DataType = def.DataType
if mv.DataType == "" {
mv.DataType = wr.TypeName
}
result.Metrics = append(result.Metrics, mv)
}
} else {
// 标量OID使用Get
wr, err := c.GetSingle(ctx, def.OID)
if err != nil {
result.Error += fmt.Sprintf("[%s get failed: %v]", def.Name, err)
continue
}
mv := SNMPMetricValue{
Name: def.Name,
OID: wr.OID,
Unit: def.Unit,
}
if val, ok := wr.Value.(float64); ok {
mv.Value = applyConversion(val, def.Conversion)
mv.RawValue = fmt.Sprintf("%v", wr.Value)
} else {
mv.RawValue = fmt.Sprintf("%v", wr.Value)
if v, err := strconv.ParseFloat(mv.RawValue, 64); err == nil {
mv.Value = applyConversion(v, def.Conversion)
}
}
mv.DataType = def.DataType
if mv.DataType == "" {
mv.DataType = wr.TypeName
}
result.Metrics = append(result.Metrics, mv)
}
}
result.Success = len(result.Metrics) > 0
result.Duration = float64(time.Since(start).Milliseconds())
return result
}
// ============================================
// 工具方法
// ============================================
// getSNMPTypeName 获取SNMP数据类型名称
func getSNMPTypeName(t gosnmp.Asn1BER) string {
switch t {
case gosnmp.Integer:
return "integer"
case gosnmp.OctetString:
return "string"
case gosnmp.ObjectIdentifier:
return "oid"
case gosnmp.IPAddress:
return "ipaddress"
case gosnmp.Counter32:
return "counter32"
case gosnmp.Gauge32:
return "gauge32"
case gosnmp.TimeTicks:
return "timeticks"
case gosnmp.Opaque:
return "opaque"
case gosnmp.NsapAddress:
return "nsapaddress"
case gosnmp.Counter64:
return "counter64"
case gosnmp.Uinteger32:
return "uinteger32"
case gosnmp.NoSuchObject:
return "nosuchobject"
case gosnmp.NoSuchInstance:
return "nosuchinstance"
default:
return fmt.Sprintf("unknown(%d)", t)
}
}
// convertSNMPValue 转换SNMP值为Go类型
func convertSNMPValue(pdu gosnmp.SnmpPDU) interface{} {
switch pdu.Type {
case gosnmp.Integer, gosnmp.Counter32, gosnmp.Gauge32, gosnmp.TimeTicks, gosnmp.Counter64, gosnmp.Uinteger32:
if val, ok := pdu.Value.(int); ok {
return float64(val)
}
if val, ok := pdu.Value.(uint); ok {
return float64(val)
}
if val, ok := pdu.Value.(uint32); ok {
return float64(val)
}
if val, ok := pdu.Value.(uint64); ok {
return float64(val)
}
if val, ok := pdu.Value.(int64); ok {
return float64(val)
}
return pdu.Value
case gosnmp.OctetString:
if val, ok := pdu.Value.([]byte); ok {
return string(val)
}
return pdu.Value
case gosnmp.OpaqueFloat:
if val, ok := pdu.Value.(float32); ok {
return float64(val)
}
return pdu.Value
case gosnmp.OpaqueDouble:
if val, ok := pdu.Value.(float64); ok {
return val
}
return pdu.Value
default:
return fmt.Sprintf("%v", pdu.Value)
}
}
// applyConversion 应用转换公式
// 支持简单的数学运算: *N, /N, +N, -N
func applyConversion(value float64, conversion string) float64 {
if conversion == "" {
return value
}
conversion = strings.TrimSpace(conversion)
if strings.HasPrefix(conversion, "*") {
if f, err := strconv.ParseFloat(strings.TrimPrefix(conversion, "*"), 64); err == nil {
return value * f
}
}
if strings.HasPrefix(conversion, "/") {
if f, err := strconv.ParseFloat(strings.TrimPrefix(conversion, "/"), 64); err == nil && f != 0 {
return value / f
}
}
if strings.HasPrefix(conversion, "+") {
if f, err := strconv.ParseFloat(strings.TrimPrefix(conversion, "+"), 64); err == nil {
return value + f
}
}
if strings.HasPrefix(conversion, "-") && len(conversion) > 1 {
if f, err := strconv.ParseFloat(conversion, 64); err == nil {
return value + f // f is negative
}
}
return value
}
// GetTargetIP 获取目标IP
func (c *Collector) GetTargetIP() string {
return c.config.Target
}
// GetVersion 获取SNMP版本
func (c *Collector) GetVersion() gosnmp.SnmpVersion {
return c.config.Version
}
// ============================================
// 预定义OID常量 (RFC1213-MIB, IF-MIB)
// ============================================
// 系统信息 OID
const (
OIDSysDescr = ".1.3.6.1.2.1.1.1.0" // 系统描述
OIDSysObjectID = ".1.3.6.1.2.1.1.2.0" // 系统对象ID
OIDSysUptime = ".1.3.6.1.2.1.1.3.0" // 系统运行时间
OIDSysContact = ".1.3.6.1.2.1.1.4.0" // 系统联系人
OIDSysName = ".1.3.6.1.2.1.1.5.0" // 系统名称
OIDSysLocation = ".1.3.6.1.2.1.1.6.0" // 系统位置
)
// 接口信息 OID (IF-MIB)
const (
OIDIfNumber = ".1.3.6.1.2.1.2.1.0" // 接口数量
OIDIfTable = ".1.3.6.1.2.1.2.2" // 接口表
OIDIfDescr = ".1.3.6.1.2.1.2.2.1.2" // 接口描述
OIDIfType = ".1.3.6.1.2.1.2.2.1.3" // 接口类型
OIDIfSpeed = ".1.3.6.1.2.1.2.2.1.5" // 接口速率
OIDIfPhysAddr = ".1.3.6.1.2.1.2.2.1.6" // 物理地址
OIDIfAdminStatus = ".1.3.6.1.2.1.2.2.1.7" // 管理状态
OIDIfOperStatus = ".1.3.6.1.2.1.2.2.1.8" // 操作状态
OIDIfInOctets = ".1.3.6.1.2.1.2.2.1.10" // 接收字节数
OIDIfInUcastPkts = ".1.3.6.1.2.1.2.2.1.11" // 接收单播包数
OIDIfInErrors = ".1.3.6.1.2.1.2.2.1.14" // 接收错误数
OIDIfOutOctets = ".1.3.6.1.2.1.2.2.1.16" // 发送字节数
OIDIfOutUcastPkts = ".1.3.6.1.2.1.2.2.1.17" // 发送单播包数
OIDIfOutErrors = ".1.3.6.1.2.1.2.2.1.20" // 发送错误数
)
// HostResource MIB OID
const (
OIDHrSystemUptime = ".1.3.6.1.2.1.25.1.1.0" // 系统运行时间
OIDHrProcessorLoad = ".1.3.6.1.2.1.25.3.3.1.2" // CPU负载
OIDHrStorageTable = ".1.3.6.1.2.1.25.2.3" // 存储表
OIDHrStorageDescr = ".1.3.6.1.2.1.25.2.3.1.3" // 存储描述
OIDHrStorageSize = ".1.3.6.1.2.1.25.2.3.1.5" // 存储大小
OIDHrStorageUsed = ".1.3.6.1.2.1.25.2.3.1.6" // 已用存储
)