185 lines
3.8 KiB
Go
185 lines
3.8 KiB
Go
package vrrp
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"net"
|
|
)
|
|
|
|
const (
|
|
VRRPVersion = 2
|
|
VRRPProtocolNumber = 112
|
|
)
|
|
|
|
type VRRPPacket struct {
|
|
Version uint8
|
|
Type uint8
|
|
VirtualRtrID uint8
|
|
Priority uint8
|
|
CountIPAddrs uint8
|
|
AuthType uint8
|
|
AdvertInt uint8
|
|
Checksum uint16
|
|
IPAddresses []net.IP
|
|
AuthData [8]byte
|
|
}
|
|
|
|
const (
|
|
VRRPTypeAdvertisement = 1
|
|
)
|
|
|
|
const (
|
|
AuthTypeNone = 0
|
|
AuthTypeSimpleText = 1
|
|
AuthTypeIPAH = 2
|
|
)
|
|
|
|
func NewAdvertisement(vrID uint8, priority uint8, advertInt uint8, ips []net.IP, authType uint8, authPass string) *VRRPPacket {
|
|
pkt := &VRRPPacket{
|
|
Version: VRRPVersion,
|
|
Type: VRRPTypeAdvertisement,
|
|
VirtualRtrID: vrID,
|
|
Priority: priority,
|
|
CountIPAddrs: uint8(len(ips)),
|
|
AuthType: authType,
|
|
AdvertInt: advertInt,
|
|
IPAddresses: ips,
|
|
}
|
|
|
|
if authType == AuthTypeSimpleText && authPass != "" {
|
|
copy(pkt.AuthData[:], authPass)
|
|
}
|
|
|
|
return pkt
|
|
}
|
|
|
|
func (p *VRRPPacket) Marshal() ([]byte, error) {
|
|
buf := new(bytes.Buffer)
|
|
|
|
versionType := (p.Version << 4) | p.Type
|
|
if err := binary.Write(buf, binary.BigEndian, versionType); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := binary.Write(buf, binary.BigEndian, p.VirtualRtrID); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := binary.Write(buf, binary.BigEndian, p.Priority); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := binary.Write(buf, binary.BigEndian, p.CountIPAddrs); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := binary.Write(buf, binary.BigEndian, p.AuthType); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := binary.Write(buf, binary.BigEndian, p.AdvertInt); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := binary.Write(buf, binary.BigEndian, uint16(0)); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, ip := range p.IPAddresses {
|
|
ip4 := ip.To4()
|
|
if ip4 == nil {
|
|
return nil, fmt.Errorf("invalid IPv4 address: %s", ip)
|
|
}
|
|
if err := binary.Write(buf, binary.BigEndian, ip4); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if err := binary.Write(buf, binary.BigEndian, p.AuthData); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
data := buf.Bytes()
|
|
|
|
checksum := calculateChecksum(data)
|
|
binary.BigEndian.PutUint16(data[6:8], checksum)
|
|
|
|
return data, nil
|
|
}
|
|
|
|
func Unmarshal(data []byte) (*VRRPPacket, error) {
|
|
if len(data) < 20 {
|
|
return nil, fmt.Errorf("packet too short: %d bytes", len(data))
|
|
}
|
|
|
|
pkt := &VRRPPacket{}
|
|
|
|
versionType := data[0]
|
|
pkt.Version = versionType >> 4
|
|
pkt.Type = versionType & 0x0F
|
|
pkt.VirtualRtrID = data[1]
|
|
pkt.Priority = data[2]
|
|
pkt.CountIPAddrs = data[3]
|
|
pkt.AuthType = data[4]
|
|
pkt.AdvertInt = data[5]
|
|
pkt.Checksum = binary.BigEndian.Uint16(data[6:8])
|
|
|
|
offset := 8
|
|
pkt.IPAddresses = make([]net.IP, pkt.CountIPAddrs)
|
|
for i := 0; i < int(pkt.CountIPAddrs); i++ {
|
|
if offset+4 > len(data) {
|
|
return nil, fmt.Errorf("packet too short for IP addresses")
|
|
}
|
|
pkt.IPAddresses[i] = net.IPv4(data[offset], data[offset+1], data[offset+2], data[offset+3])
|
|
offset += 4
|
|
}
|
|
|
|
if offset+8 > len(data) {
|
|
return nil, fmt.Errorf("packet too short for auth data")
|
|
}
|
|
copy(pkt.AuthData[:], data[offset:offset+8])
|
|
|
|
return pkt, nil
|
|
}
|
|
|
|
func calculateChecksum(data []byte) uint16 {
|
|
sum := uint32(0)
|
|
|
|
for i := 0; i < len(data)-1; i += 2 {
|
|
sum += uint32(data[i])<<8 | uint32(data[i+1])
|
|
}
|
|
|
|
if len(data)%2 == 1 {
|
|
sum += uint32(data[len(data)-1]) << 8
|
|
}
|
|
|
|
for sum > 0xFFFF {
|
|
sum = (sum & 0xFFFF) + (sum >> 16)
|
|
}
|
|
|
|
return uint16(^sum)
|
|
}
|
|
|
|
func (p *VRRPPacket) Validate(authPass string) error {
|
|
if p.Version != VRRPVersion {
|
|
return fmt.Errorf("unsupported VRRP version: %d", p.Version)
|
|
}
|
|
|
|
if p.Type != VRRPTypeAdvertisement {
|
|
return fmt.Errorf("unsupported VRRP type: %d", p.Type)
|
|
}
|
|
|
|
if p.AuthType == AuthTypeSimpleText {
|
|
if authPass != "" {
|
|
var expectedAuth [8]byte
|
|
copy(expectedAuth[:], authPass)
|
|
if !bytes.Equal(p.AuthData[:], expectedAuth[:]) {
|
|
return fmt.Errorf("authentication failed")
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|