141 lines
3.2 KiB
Go
141 lines
3.2 KiB
Go
package vrrp
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"syscall"
|
|
|
|
"golang.org/x/net/ipv4"
|
|
)
|
|
|
|
const (
|
|
VRRPMulticastAddr = "224.0.0.18"
|
|
)
|
|
|
|
type Socket struct {
|
|
conn *ipv4.RawConn
|
|
iface *net.Interface
|
|
localIP net.IP
|
|
groupIP net.IP
|
|
}
|
|
|
|
func NewSocket(ifaceName string) (*Socket, error) {
|
|
iface, err := net.InterfaceByName(ifaceName)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get interface %s: %w", ifaceName, err)
|
|
}
|
|
|
|
addrs, err := iface.Addrs()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get addresses for %s: %w", ifaceName, err)
|
|
}
|
|
|
|
var localIP net.IP
|
|
for _, addr := range addrs {
|
|
if ipNet, ok := addr.(*net.IPNet); ok {
|
|
if ipv4 := ipNet.IP.To4(); ipv4 != nil {
|
|
localIP = ipv4
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if localIP == nil {
|
|
return nil, fmt.Errorf("no IPv4 address found on interface %s", ifaceName)
|
|
}
|
|
|
|
fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, VRRPProtocolNumber)
|
|
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")
|
|
defer file.Close()
|
|
|
|
packetConn, err := net.FilePacketConn(file)
|
|
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()
|
|
return nil, fmt.Errorf("invalid multicast address: %s", VRRPMulticastAddr)
|
|
}
|
|
|
|
if err := rawConn.JoinGroup(iface, &net.IPAddr{IP: groupIP}); err != nil {
|
|
rawConn.Close()
|
|
return nil, fmt.Errorf("failed to join multicast group: %w", err)
|
|
}
|
|
|
|
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,
|
|
iface: iface,
|
|
localIP: localIP,
|
|
groupIP: groupIP,
|
|
}, nil
|
|
}
|
|
|
|
func (s *Socket) Send(pkt *VRRPPacket) 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: s.groupIP,
|
|
Src: s.localIP,
|
|
}
|
|
|
|
if err := s.conn.WriteTo(header, data, nil); err != nil {
|
|
return fmt.Errorf("failed to send packet: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Socket) Receive() (*VRRPPacket, net.IP, error) {
|
|
buf := make([]byte, 1500)
|
|
|
|
header, payload, _, err := s.conn.ReadFrom(buf)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("failed to receive packet: %w", err)
|
|
}
|
|
|
|
pkt, err := Unmarshal(payload)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("failed to unmarshal packet: %w", err)
|
|
}
|
|
|
|
return pkt, header.Src, nil
|
|
}
|
|
|
|
func (s *Socket) Close() error {
|
|
if err := s.conn.LeaveGroup(s.iface, &net.IPAddr{IP: s.groupIP}); err != nil {
|
|
return err
|
|
}
|
|
return s.conn.Close()
|
|
} |