wip v1.0.0

This commit is contained in:
loveuer
2025-12-08 22:23:45 +08:00
commit bece440c47
35 changed files with 4893 additions and 0 deletions

90
pkg/config/config.go Normal file
View File

@@ -0,0 +1,90 @@
package config
import (
"fmt"
"os"
"time"
"gopkg.in/yaml.v3"
)
type Config struct {
Global Global `yaml:"global"`
VRRP []VRRPInstance `yaml:"vrrp_instances"`
Health []HealthChecker `yaml:"health_checkers"`
}
type Global struct {
RouterID string `yaml:"router_id"`
NotificationMail string `yaml:"notification_email"`
}
type VRRPInstance struct {
Name string `yaml:"name"`
Interface string `yaml:"interface"`
State string `yaml:"state"`
VirtualRouterID int `yaml:"virtual_router_id"`
Priority int `yaml:"priority"`
VirtualIPs []string `yaml:"virtual_ips"`
AdvertInterval int `yaml:"advert_interval"`
AuthType string `yaml:"auth_type"`
AuthPass string `yaml:"auth_pass"`
NotifyMaster string `yaml:"notify_master"`
NotifyBackup string `yaml:"notify_backup"`
NotifyFault string `yaml:"notify_fault"`
TrackScripts []string `yaml:"track_scripts"`
}
type HealthChecker struct {
Name string `yaml:"name"`
Type string `yaml:"type"`
Interval time.Duration `yaml:"interval"`
Timeout time.Duration `yaml:"timeout"`
Rise int `yaml:"rise"`
Fall int `yaml:"fall"`
Config interface{} `yaml:"config"`
}
func Load(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("failed to read config file: %w", err)
}
var cfg Config
if err := yaml.Unmarshal(data, &cfg); err != nil {
return nil, fmt.Errorf("failed to parse config file: %w", err)
}
if err := validate(&cfg); err != nil {
return nil, fmt.Errorf("invalid configuration: %w", err)
}
return &cfg, nil
}
func validate(cfg *Config) error {
if cfg.Global.RouterID == "" {
return fmt.Errorf("global.router_id is required")
}
for i, vrrp := range cfg.VRRP {
if vrrp.Name == "" {
return fmt.Errorf("vrrp_instances[%d].name is required", i)
}
if vrrp.Interface == "" {
return fmt.Errorf("vrrp_instances[%d].interface is required", i)
}
if vrrp.VirtualRouterID < 1 || vrrp.VirtualRouterID > 255 {
return fmt.Errorf("vrrp_instances[%d].virtual_router_id must be between 1 and 255", i)
}
if vrrp.Priority < 1 || vrrp.Priority > 255 {
return fmt.Errorf("vrrp_instances[%d].priority must be between 1 and 255", i)
}
if len(vrrp.VirtualIPs) == 0 {
return fmt.Errorf("vrrp_instances[%d].virtual_ips cannot be empty", i)
}
}
return nil
}

44
pkg/logger/logger.go Normal file
View File

@@ -0,0 +1,44 @@
package logger
import (
"fmt"
"log"
"os"
"time"
)
type Logger struct {
debug bool
logger *log.Logger
}
func New(debug bool) *Logger {
return &Logger{
debug: debug,
logger: log.New(os.Stdout, "", 0),
}
}
func (l *Logger) Info(format string, args ...interface{}) {
l.log("INFO", format, args...)
}
func (l *Logger) Error(format string, args ...interface{}) {
l.log("ERROR", format, args...)
}
func (l *Logger) Debug(format string, args ...interface{}) {
if l.debug {
l.log("DEBUG", format, args...)
}
}
func (l *Logger) Warn(format string, args ...interface{}) {
l.log("WARN", format, args...)
}
func (l *Logger) log(level string, format string, args ...interface{}) {
timestamp := time.Now().Format("2006-01-02 15:04:05")
message := fmt.Sprintf(format, args...)
l.logger.Printf("[%s] %s: %s", timestamp, level, message)
}

81
pkg/netif/interface.go Normal file
View File

@@ -0,0 +1,81 @@
package netif
import (
"fmt"
"net"
"github.com/vishvananda/netlink"
)
type Interface struct {
Name string
Index int
Link netlink.Link
}
func GetInterface(name string) (*Interface, error) {
link, err := netlink.LinkByName(name)
if err != nil {
return nil, fmt.Errorf("failed to find interface %s: %w", name, err)
}
return &Interface{
Name: name,
Index: link.Attrs().Index,
Link: link,
}, nil
}
func (iface *Interface) AddIP(ipCIDR string) error {
addr, err := netlink.ParseAddr(ipCIDR)
if err != nil {
return fmt.Errorf("invalid IP address %s: %w", ipCIDR, err)
}
if err := netlink.AddrAdd(iface.Link, addr); err != nil {
return fmt.Errorf("failed to add IP %s to %s: %w", ipCIDR, iface.Name, err)
}
return nil
}
func (iface *Interface) DeleteIP(ipCIDR string) error {
addr, err := netlink.ParseAddr(ipCIDR)
if err != nil {
return fmt.Errorf("invalid IP address %s: %w", ipCIDR, err)
}
if err := netlink.AddrDel(iface.Link, addr); err != nil {
return fmt.Errorf("failed to delete IP %s from %s: %w", ipCIDR, iface.Name, err)
}
return nil
}
func (iface *Interface) HasIP(ipCIDR string) (bool, error) {
targetAddr, err := netlink.ParseAddr(ipCIDR)
if err != nil {
return false, fmt.Errorf("invalid IP address %s: %w", ipCIDR, err)
}
addrs, err := netlink.AddrList(iface.Link, 0)
if err != nil {
return false, fmt.Errorf("failed to list addresses on %s: %w", iface.Name, err)
}
for _, addr := range addrs {
if addr.IPNet.String() == targetAddr.IPNet.String() {
return true, nil
}
}
return false, nil
}
func (iface *Interface) GetHardwareAddr() (net.HardwareAddr, error) {
return iface.Link.Attrs().HardwareAddr, nil
}
func (iface *Interface) IsUp() bool {
return iface.Link.Attrs().Flags&net.FlagUp != 0
}