Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 00eba85e39 | |||
| 3fc3c860bc |
@@ -4,3 +4,4 @@
|
||||
|
||||
dist
|
||||
x-*
|
||||
go-alived
|
||||
|
||||
@@ -7,6 +7,7 @@ A lightweight VRRP (Virtual Router Redundancy Protocol) implementation in Go, de
|
||||
- **VRRP Protocol**: RFC 3768/5798 compliant implementation
|
||||
- **High Availability**: Automatic failover with priority-based master election
|
||||
- **Health Checking**: TCP, HTTP/HTTPS, ICMP ping, and script-based checks
|
||||
- **Unicast Support**: Direct peer communication for restricted network environments (macvlan, cloud)
|
||||
- **Easy Deployment**: Built-in install command with systemd/init.d support
|
||||
- **Hot Reload**: Configuration reload via SIGHUP without service restart
|
||||
- **Zero Dependencies**: Single static binary, no runtime dependencies
|
||||
@@ -145,6 +146,54 @@ vrrp_instances:
|
||||
- "192.168.1.100/24" # Must match
|
||||
```
|
||||
|
||||
### Unicast Configuration (for macvlan / cloud environments)
|
||||
|
||||
In environments where multicast is not available (macvlan networks, public clouds, etc.), use unicast to send VRRP advertisements directly to peer IPs:
|
||||
|
||||
**Node 1 (MASTER)**:
|
||||
```yaml
|
||||
global:
|
||||
router_id: "node1"
|
||||
|
||||
vrrp_instances:
|
||||
- name: "VI_1"
|
||||
interface: "eth0"
|
||||
state: "MASTER"
|
||||
virtual_router_id: 51
|
||||
priority: 100
|
||||
advert_interval: 1
|
||||
auth_type: "PASS"
|
||||
auth_pass: "secret123"
|
||||
virtual_ips:
|
||||
- "192.168.1.100/24"
|
||||
unicast_src_ip: "192.168.1.10"
|
||||
unicast_peers:
|
||||
- "192.168.1.11"
|
||||
```
|
||||
|
||||
**Node 2 (BACKUP)**:
|
||||
```yaml
|
||||
global:
|
||||
router_id: "node2"
|
||||
|
||||
vrrp_instances:
|
||||
- name: "VI_1"
|
||||
interface: "eth0"
|
||||
state: "BACKUP"
|
||||
virtual_router_id: 51
|
||||
priority: 50
|
||||
advert_interval: 1
|
||||
auth_type: "PASS"
|
||||
auth_pass: "secret123"
|
||||
virtual_ips:
|
||||
- "192.168.1.100/24"
|
||||
unicast_src_ip: "192.168.1.11"
|
||||
unicast_peers:
|
||||
- "192.168.1.10"
|
||||
```
|
||||
|
||||
See `etc/config.unicast.yaml` for a complete unicast example.
|
||||
|
||||
### Health Checking
|
||||
|
||||
```yaml
|
||||
@@ -245,9 +294,17 @@ sudo kill -HUP $(pgrep go-alived)
|
||||
| VirtualBox | Full | Bridged network + promiscuous mode |
|
||||
| Docker | Limited | Requires `--privileged --net=host` |
|
||||
| OpenWrt/iStoreOS | Full | Use `--method service` for install |
|
||||
| AWS/Aliyun/Azure | None | Multicast disabled |
|
||||
| macvlan networks | Full | Use unicast mode |
|
||||
| AWS/Aliyun/Azure/GCP | Limited | Use unicast mode |
|
||||
|
||||
> **Note**: VRRP requires multicast support (224.0.0.18). Most public clouds disable multicast at the network layer. Use cloud-native HA solutions instead.
|
||||
> **Note**: VRRP defaults to multicast (224.0.0.18). In environments where multicast is disabled or unavailable (public clouds, macvlan networks), use **unicast mode** by configuring `unicast_src_ip` and `unicast_peers`.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Go 1.21+ (for building)
|
||||
- Linux/macOS with root privileges (for raw sockets and interface management)
|
||||
- Network interface with IPv4 address
|
||||
- Multicast support (default mode) **or** unicast configuration (for restricted environments)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
@@ -266,7 +323,8 @@ sudo go-alived run -c /etc/go-alived/config.yaml
|
||||
**3. Both nodes become MASTER (split-brain)**
|
||||
- Check network connectivity between nodes
|
||||
- Verify `virtual_router_id` matches
|
||||
- Ensure multicast traffic is allowed
|
||||
- In multicast mode: ensure multicast traffic is allowed
|
||||
- In unicast mode: verify `unicast_peers` and `unicast_src_ip` are correct
|
||||
|
||||
**4. VIP not pingable after failover**
|
||||
- Gratuitous ARP may be blocked
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
global:
|
||||
router_id: "node1"
|
||||
|
||||
vrrp_instances:
|
||||
# Example: VRRP with unicast peers for macvlan environments
|
||||
# In macvlan networks, multicast between sub-interfaces of the same
|
||||
# parent interface does not work. Use unicast to send VRRP advertisements
|
||||
# directly to peer IP addresses.
|
||||
- name: "VI_1"
|
||||
interface: "eth0"
|
||||
state: "BACKUP"
|
||||
virtual_router_id: 51
|
||||
priority: 100
|
||||
advert_interval: 1
|
||||
auth_type: "PASS"
|
||||
auth_pass: "secret123"
|
||||
virtual_ips:
|
||||
- "192.168.1.100/24"
|
||||
|
||||
# Unicast source IP: the local IP address to use for sending VRRP packets.
|
||||
# If omitted, the interface's primary IPv4 address is used automatically.
|
||||
unicast_src_ip: "192.168.1.10"
|
||||
|
||||
# Unicast peer list: IP addresses of the other VRRP nodes.
|
||||
# When configured, VRRP advertisements are sent directly to these IPs
|
||||
# instead of using multicast (224.0.0.18).
|
||||
# On each node, list the OTHER nodes' IPs here (not your own).
|
||||
unicast_peers:
|
||||
- "192.168.1.11"
|
||||
# Add more peers for setups with more than two nodes:
|
||||
# - "192.168.1.12"
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
// Version can be set at build time via ldflags
|
||||
var Version = "1.2.1"
|
||||
var Version = "1.3.0"
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "go-alived",
|
||||
|
||||
@@ -58,6 +58,7 @@ func runService(cmd *cobra.Command, args []string) {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
setupNotifyScripts(vrrpMgr, cfg, log)
|
||||
setupHealthTracking(vrrpMgr, healthMgr, log)
|
||||
|
||||
healthMgr.StartAll()
|
||||
@@ -100,6 +101,27 @@ func cleanup(log *logger.Logger, vrrpMgr *vrrp.Manager, healthMgr *health.Manage
|
||||
vrrpMgr.StopAll()
|
||||
}
|
||||
|
||||
func setupNotifyScripts(vrrpMgr *vrrp.Manager, cfg *config.Config, log *logger.Logger) {
|
||||
for _, vrrpCfg := range cfg.VRRP {
|
||||
if vrrpCfg.NotifyMaster == "" && vrrpCfg.NotifyBackup == "" && vrrpCfg.NotifyFault == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
inst, ok := vrrpMgr.GetInstance(vrrpCfg.Name)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
vrrp.SetupNotify(inst, &vrrp.NotifyConfig{
|
||||
Name: vrrpCfg.Name,
|
||||
NotifyMaster: vrrpCfg.NotifyMaster,
|
||||
NotifyBackup: vrrpCfg.NotifyBackup,
|
||||
NotifyFault: vrrpCfg.NotifyFault,
|
||||
Log: log,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func setupHealthTracking(vrrpMgr *vrrp.Manager, healthMgr *health.Manager, log *logger.Logger) {
|
||||
instances := vrrpMgr.GetAllInstances()
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@ type Instance struct {
|
||||
AuthType uint8
|
||||
AuthPass string
|
||||
TrackScripts []string
|
||||
UnicastSrcIP net.IP
|
||||
UnicastPeers []net.IP
|
||||
|
||||
state *StateMachine
|
||||
priorityCalc *PriorityCalculator
|
||||
@@ -54,6 +56,8 @@ func NewInstance(
|
||||
authType string,
|
||||
authPass string,
|
||||
trackScripts []string,
|
||||
unicastSrcIP string,
|
||||
unicastPeers []string,
|
||||
log *logger.Logger,
|
||||
) (*Instance, error) {
|
||||
if vrID < 1 || vrID > 255 {
|
||||
@@ -90,6 +94,28 @@ func NewInstance(
|
||||
return nil, fmt.Errorf("failed to get interface: %w", err)
|
||||
}
|
||||
|
||||
var parsedSrcIP net.IP
|
||||
var parsedPeers []net.IP
|
||||
|
||||
if len(unicastPeers) > 0 {
|
||||
parsedPeers = make([]net.IP, 0, len(unicastPeers))
|
||||
for _, peer := range unicastPeers {
|
||||
ip := net.ParseIP(peer)
|
||||
if ip == nil {
|
||||
return nil, fmt.Errorf("invalid unicast peer IP: %s", peer)
|
||||
}
|
||||
parsedPeers = append(parsedPeers, ip.To4())
|
||||
}
|
||||
|
||||
if unicastSrcIP != "" {
|
||||
parsedSrcIP = net.ParseIP(unicastSrcIP)
|
||||
if parsedSrcIP == nil {
|
||||
return nil, fmt.Errorf("invalid unicast src IP: %s", unicastSrcIP)
|
||||
}
|
||||
parsedSrcIP = parsedSrcIP.To4()
|
||||
}
|
||||
}
|
||||
|
||||
inst := &Instance{
|
||||
Name: name,
|
||||
VirtualRouterID: vrID,
|
||||
@@ -101,6 +127,8 @@ func NewInstance(
|
||||
AuthType: authTypeNum,
|
||||
AuthPass: authPass,
|
||||
TrackScripts: trackScripts,
|
||||
UnicastSrcIP: parsedSrcIP,
|
||||
UnicastPeers: parsedPeers,
|
||||
state: NewStateMachine(StateInit),
|
||||
priorityCalc: NewPriorityCalculator(priority),
|
||||
history: NewStateHistory(100),
|
||||
@@ -131,7 +159,15 @@ func (inst *Instance) Start() error {
|
||||
inst.mu.Unlock()
|
||||
|
||||
var err error
|
||||
inst.socket, err = NewSocket(inst.Interface)
|
||||
if len(inst.UnicastPeers) > 0 {
|
||||
if inst.UnicastSrcIP != nil {
|
||||
inst.socket, err = NewUnicastSocket(inst.Interface, inst.UnicastSrcIP)
|
||||
} else {
|
||||
inst.socket, err = NewSocket(inst.Interface)
|
||||
}
|
||||
} else {
|
||||
inst.socket, err = NewSocket(inst.Interface)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create socket: %w", err)
|
||||
}
|
||||
@@ -142,8 +178,13 @@ func (inst *Instance) Start() error {
|
||||
return fmt.Errorf("failed to create ARP sender: %w", err)
|
||||
}
|
||||
|
||||
inst.log.Info("[%s] starting VRRP instance (VRID=%d, Priority=%d, Interface=%s)",
|
||||
inst.Name, inst.VirtualRouterID, inst.Priority, inst.Interface)
|
||||
if len(inst.UnicastPeers) > 0 {
|
||||
inst.log.Info("[%s] starting VRRP instance (VRID=%d, Priority=%d, Interface=%s, Mode=unicast, SrcIP=%s, Peers=%v)",
|
||||
inst.Name, inst.VirtualRouterID, inst.Priority, inst.Interface, inst.socket.localIP, inst.UnicastPeers)
|
||||
} else {
|
||||
inst.log.Info("[%s] starting VRRP instance (VRID=%d, Priority=%d, Interface=%s, Mode=multicast)",
|
||||
inst.Name, inst.VirtualRouterID, inst.Priority, inst.Interface)
|
||||
}
|
||||
|
||||
inst.state.SetState(StateBackup)
|
||||
inst.masterDownTimer.Start()
|
||||
@@ -213,6 +254,13 @@ func (inst *Instance) receiveLoop() {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(inst.UnicastPeers) > 0 {
|
||||
if !inst.isUnicastPeer(srcIP) {
|
||||
inst.log.Debug("[%s] ignoring packet from non-peer %s", inst.Name, srcIP)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if err := pkt.Validate(inst.AuthPass); err != nil {
|
||||
inst.log.Warn("[%s] packet validation failed: %v", inst.Name, err)
|
||||
continue
|
||||
@@ -222,6 +270,15 @@ func (inst *Instance) receiveLoop() {
|
||||
}
|
||||
}
|
||||
|
||||
func (inst *Instance) isUnicastPeer(ip net.IP) bool {
|
||||
for _, peer := range inst.UnicastPeers {
|
||||
if peer.Equal(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (inst *Instance) handleAdvertisement(pkt *VRRPPacket, srcIP net.IP) {
|
||||
currentState := inst.state.GetState()
|
||||
localPriority := inst.priorityCalc.GetPriority()
|
||||
@@ -272,12 +329,22 @@ func (inst *Instance) sendAdvertisement() error {
|
||||
inst.AuthPass,
|
||||
)
|
||||
|
||||
if err := inst.socket.Send(pkt); err != nil {
|
||||
inst.log.Error("[%s] failed to send advertisement: %v", inst.Name, err)
|
||||
return err
|
||||
if len(inst.UnicastPeers) > 0 {
|
||||
for _, peer := range inst.UnicastPeers {
|
||||
if err := inst.socket.SendTo(pkt, peer); err != nil {
|
||||
inst.log.Error("[%s] failed to send unicast advertisement to %s: %v", inst.Name, peer, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
inst.log.Debug("[%s] sent unicast advertisement (priority=%d, peers=%d)", inst.Name, priority, len(inst.UnicastPeers))
|
||||
} else {
|
||||
if err := inst.socket.Send(pkt); err != nil {
|
||||
inst.log.Error("[%s] failed to send advertisement: %v", inst.Name, err)
|
||||
return err
|
||||
}
|
||||
inst.log.Debug("[%s] sent multicast advertisement (priority=%d)", inst.Name, priority)
|
||||
}
|
||||
|
||||
inst.log.Debug("[%s] sent advertisement (priority=%d)", inst.Name, priority)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,8 @@ func (m *Manager) LoadFromConfig(cfg *config.Config) error {
|
||||
vrrpCfg.AuthType,
|
||||
vrrpCfg.AuthPass,
|
||||
vrrpCfg.TrackScripts,
|
||||
vrrpCfg.UnicastSrcIP,
|
||||
vrrpCfg.UnicastPeers,
|
||||
m.log,
|
||||
)
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
package vrrp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/loveuer/go-alived/pkg/logger"
|
||||
)
|
||||
|
||||
const notifyTimeout = 60 * time.Second
|
||||
|
||||
// NotifyConfig holds the notify script configuration for a VRRP instance.
|
||||
type NotifyConfig struct {
|
||||
Name string
|
||||
NotifyMaster string
|
||||
NotifyBackup string
|
||||
NotifyFault string
|
||||
Log *logger.Logger
|
||||
}
|
||||
|
||||
// SetupNotify registers notify scripts as state change callbacks on the instance.
|
||||
func SetupNotify(inst *Instance, cfg *NotifyConfig) {
|
||||
if cfg.NotifyMaster != "" {
|
||||
script := cfg.NotifyMaster
|
||||
inst.OnMaster(func() {
|
||||
cfg.Log.Info("[%s] executing notify_master script", cfg.Name)
|
||||
go runNotifyScript(cfg.Log, cfg.Name, "notify_master", script)
|
||||
})
|
||||
cfg.Log.Info("[%s] registered notify_master script", cfg.Name)
|
||||
}
|
||||
|
||||
if cfg.NotifyBackup != "" {
|
||||
script := cfg.NotifyBackup
|
||||
inst.OnBackup(func() {
|
||||
cfg.Log.Info("[%s] executing notify_backup script", cfg.Name)
|
||||
go runNotifyScript(cfg.Log, cfg.Name, "notify_backup", script)
|
||||
})
|
||||
cfg.Log.Info("[%s] registered notify_backup script", cfg.Name)
|
||||
}
|
||||
|
||||
if cfg.NotifyFault != "" {
|
||||
script := cfg.NotifyFault
|
||||
inst.OnFault(func() {
|
||||
cfg.Log.Info("[%s] executing notify_fault script", cfg.Name)
|
||||
go runNotifyScript(cfg.Log, cfg.Name, "notify_fault", script)
|
||||
})
|
||||
cfg.Log.Info("[%s] registered notify_fault script", cfg.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func runNotifyScript(log *logger.Logger, instName, event, script string) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), notifyTimeout)
|
||||
defer cancel()
|
||||
|
||||
cmd := buildCommand(ctx, script)
|
||||
cmd.Env = append(os.Environ(),
|
||||
fmt.Sprintf("GO_ALIVED_INSTANCE=%s", instName),
|
||||
fmt.Sprintf("GO_ALIVED_EVENT=%s", event),
|
||||
)
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
log.Error("[%s] %s script failed: %v (output: %s)",
|
||||
instName, event, err, strings.TrimSpace(string(output)))
|
||||
return
|
||||
}
|
||||
|
||||
if len(output) > 0 {
|
||||
log.Info("[%s] %s script output: %s",
|
||||
instName, event, strings.TrimSpace(string(output)))
|
||||
}
|
||||
log.Info("[%s] %s script completed successfully", instName, event)
|
||||
}
|
||||
|
||||
func buildCommand(ctx context.Context, script string) *exec.Cmd {
|
||||
script = strings.TrimSpace(script)
|
||||
|
||||
// If the script is a path to an existing executable file, run it directly
|
||||
if info, err := os.Stat(script); err == nil && !info.IsDir() {
|
||||
return exec.CommandContext(ctx, script)
|
||||
}
|
||||
|
||||
// Otherwise treat as inline shell script
|
||||
return exec.CommandContext(ctx, "sh", "-c", script)
|
||||
}
|
||||
+95
-22
@@ -47,29 +47,11 @@ func NewSocket(ifaceName string) (*Socket, error) {
|
||||
return nil, fmt.Errorf("no IPv4 address found on interface %s", ifaceName)
|
||||
}
|
||||
|
||||
fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, VRRPProtocolNumber)
|
||||
rawConn, packetConn, err := createRawConn()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create raw socket: %w", err)
|
||||
}
|
||||
|
||||
if err := syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil {
|
||||
syscall.Close(fd)
|
||||
return nil, fmt.Errorf("failed to set SO_REUSEADDR: %w", err)
|
||||
}
|
||||
|
||||
file := os.NewFile(uintptr(fd), "vrrp-socket")
|
||||
packetConn, err := net.FilePacketConn(file)
|
||||
file.Close()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create packet connection: %w", err)
|
||||
}
|
||||
|
||||
rawConn, err := ipv4.NewRawConn(packetConn)
|
||||
if err != nil {
|
||||
packetConn.Close()
|
||||
return nil, fmt.Errorf("failed to create raw connection: %w", err)
|
||||
}
|
||||
|
||||
groupIP := net.ParseIP(VRRPMulticastAddr).To4()
|
||||
if groupIP == nil {
|
||||
rawConn.Close()
|
||||
@@ -95,6 +77,71 @@ func NewSocket(ifaceName string) (*Socket, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewUnicastSocket(ifaceName string, srcIP net.IP) (*Socket, error) {
|
||||
iface, err := net.InterfaceByName(ifaceName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get interface %s: %w", ifaceName, err)
|
||||
}
|
||||
|
||||
rawConn, packetConn, err := createRawConn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Bind to the specific interface and source IP so we receive unicast VRRP packets
|
||||
bindAddr := &syscall.SockaddrInet4{}
|
||||
copy(bindAddr.Addr[:], srcIP.To4())
|
||||
fd, _ := rawConn.SyscallConn()
|
||||
var bindErr error
|
||||
fd.Control(func(fd uintptr) {
|
||||
bindErr = syscall.Bind(int(fd), bindAddr)
|
||||
})
|
||||
if bindErr != nil {
|
||||
rawConn.Close()
|
||||
return nil, fmt.Errorf("failed to bind to %s: %w", srcIP, bindErr)
|
||||
}
|
||||
|
||||
if err := rawConn.SetControlMessage(ipv4.FlagTTL|ipv4.FlagSrc|ipv4.FlagDst|ipv4.FlagInterface, true); err != nil {
|
||||
rawConn.Close()
|
||||
return nil, fmt.Errorf("failed to set control message: %w", err)
|
||||
}
|
||||
|
||||
return &Socket{
|
||||
conn: rawConn,
|
||||
packetConn: packetConn,
|
||||
iface: iface,
|
||||
localIP: srcIP,
|
||||
groupIP: nil,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func createRawConn() (*ipv4.RawConn, net.PacketConn, error) {
|
||||
fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, VRRPProtocolNumber)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to create raw socket: %w", err)
|
||||
}
|
||||
|
||||
if err := syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil {
|
||||
syscall.Close(fd)
|
||||
return nil, nil, fmt.Errorf("failed to set SO_REUSEADDR: %w", err)
|
||||
}
|
||||
|
||||
file := os.NewFile(uintptr(fd), "vrrp-socket")
|
||||
packetConn, err := net.FilePacketConn(file)
|
||||
file.Close()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to create packet connection: %w", err)
|
||||
}
|
||||
|
||||
rawConn, err := ipv4.NewRawConn(packetConn)
|
||||
if err != nil {
|
||||
packetConn.Close()
|
||||
return nil, nil, fmt.Errorf("failed to create raw connection: %w", err)
|
||||
}
|
||||
|
||||
return rawConn, packetConn, nil
|
||||
}
|
||||
|
||||
func (s *Socket) Send(pkt *VRRPPacket) error {
|
||||
data, err := pkt.Marshal()
|
||||
if err != nil {
|
||||
@@ -119,6 +166,30 @@ func (s *Socket) Send(pkt *VRRPPacket) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Socket) SendTo(pkt *VRRPPacket, dst net.IP) error {
|
||||
data, err := pkt.Marshal()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal packet: %w", err)
|
||||
}
|
||||
|
||||
header := &ipv4.Header{
|
||||
Version: ipv4.Version,
|
||||
Len: ipv4.HeaderLen,
|
||||
TOS: 0xC0,
|
||||
TotalLen: ipv4.HeaderLen + len(data),
|
||||
TTL: 255,
|
||||
Protocol: VRRPProtocolNumber,
|
||||
Dst: dst,
|
||||
Src: s.localIP,
|
||||
}
|
||||
|
||||
if err := s.conn.WriteTo(header, data, nil); err != nil {
|
||||
return fmt.Errorf("failed to send packet to %s: %w", dst, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Socket) Receive() (*VRRPPacket, net.IP, error) {
|
||||
buf := make([]byte, 1500)
|
||||
|
||||
@@ -140,8 +211,10 @@ func (s *Socket) SetReadDeadline(t time.Time) error {
|
||||
}
|
||||
|
||||
func (s *Socket) Close() error {
|
||||
if err := s.conn.LeaveGroup(s.iface, &net.IPAddr{IP: s.groupIP}); err != nil {
|
||||
return err
|
||||
if s.groupIP != nil {
|
||||
if err := s.conn.LeaveGroup(s.iface, &net.IPAddr{IP: s.groupIP}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return s.conn.Close()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
@@ -33,6 +34,8 @@ type VRRPInstance struct {
|
||||
NotifyBackup string `yaml:"notify_backup"`
|
||||
NotifyFault string `yaml:"notify_fault"`
|
||||
TrackScripts []string `yaml:"track_scripts"`
|
||||
UnicastSrcIP string `yaml:"unicast_src_ip"`
|
||||
UnicastPeers []string `yaml:"unicast_peers"`
|
||||
}
|
||||
|
||||
type HealthChecker struct {
|
||||
@@ -84,6 +87,20 @@ func validate(cfg *Config) error {
|
||||
if len(vrrp.VirtualIPs) == 0 {
|
||||
return fmt.Errorf("vrrp_instances[%d].virtual_ips cannot be empty", i)
|
||||
}
|
||||
|
||||
if len(vrrp.UnicastPeers) > 0 {
|
||||
for j, peer := range vrrp.UnicastPeers {
|
||||
if ip := net.ParseIP(peer); ip == nil || ip.To4() == nil {
|
||||
return fmt.Errorf("vrrp_instances[%d].unicast_peers[%d] is not a valid IPv4 address: %s", i, j, peer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if vrrp.UnicastSrcIP != "" {
|
||||
if ip := net.ParseIP(vrrp.UnicastSrcIP); ip == nil || ip.To4() == nil {
|
||||
return fmt.Errorf("vrrp_instances[%d].unicast_src_ip is not a valid IPv4 address: %s", i, vrrp.UnicastSrcIP)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user