From 85a2e39a4b689bfc0b9650f55ea778ebaffd603f Mon Sep 17 00:00:00 2001 From: ston1th Date: Thu, 11 Feb 2021 20:17:52 +0100 Subject: [PATCH] added cidr validation and made hook execs to command array with args --- go.mod | 2 + go.sum | 14 ++++ pkg/config/config.go | 23 ++++-- pkg/controller/vip.go | 11 ++- pkg/vip/arp_linux.go | 165 ++++-------------------------------------- vipman.yaml | 6 +- 6 files changed, 55 insertions(+), 166 deletions(-) diff --git a/go.mod b/go.mod index cf5d57d..024bce2 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,8 @@ require ( github.com/google/uuid v1.1.2 // indirect github.com/hashicorp/go-hclog v0.15.0 github.com/hashicorp/raft v1.2.0 + github.com/mdlayher/arp v0.0.0-20191213142603-f72070a231fc + github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7 github.com/vishvananda/netlink v1.1.0 go.etcd.io/etcd v3.3.25+incompatible go.uber.org/zap v1.16.0 diff --git a/go.sum b/go.sum index 7d9fc0c..a04b0cb 100644 --- a/go.sum +++ b/go.sum @@ -82,6 +82,14 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mdlayher/arp v0.0.0-20191213142603-f72070a231fc h1:m7rJJJeXrYCFpsxXYapkDW53wJCDmf9bsIXUg0HoeQY= +github.com/mdlayher/arp v0.0.0-20191213142603-f72070a231fc/go.mod h1:eOj1DDj3NAZ6yv+WafaKzY37MFZ58TdfIhQ+8nQbiis= +github.com/mdlayher/ethernet v0.0.0-20190313224307-5b5fc417d966/go.mod h1:5s5p/sMJ6sNsFl6uCh85lkFGV8kLuIYJCRJLavVJwvg= +github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7 h1:lez6TS6aAau+8wXUP3G9I3TGlmPFEq2CTxBaRqY6AGE= +github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y= +github.com/mdlayher/raw v0.0.0-20190313224157-43dbcdd7739d/go.mod h1:r1fbeITl2xL/zLbVnNHFyOzQJTgr/3fpf1lJX/cjzR8= +github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18 h1:zwOa3e/13D6veNIz6zzuqrd3eZEMF0dzD0AQWKcYSs4= +github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -125,7 +133,10 @@ golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -135,8 +146,11 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190418153312-f0ce4c0180be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190523142557-0e01d883c5c5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606122018-79a91cf218c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0= diff --git a/pkg/config/config.go b/pkg/config/config.go index 6827588..91cdde8 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -7,6 +7,7 @@ import ( "path/filepath" "strings" + "github.com/vishvananda/netlink" "gopkg.in/yaml.v2" ) @@ -41,14 +42,20 @@ func Validate(c *Config) error { if len(c.VirtualIPs) == 0 { return errors.New("missing virtualIPs config") } - if c.LeaderHook != "" { - if !filepath.IsAbs(c.LeaderHook) { - return errors.New("leaderHook path must be absolute") + for _, vip := range c.VirtualIPs { + _, err := netlink.ParseAddr(vip) + if err != nil { + return fmt.Errorf("failed to parse CIDR virtualIP '%s': %w", vip, err) } } - if c.FollowerHook != "" { - if !filepath.IsAbs(c.FollowerHook) { - return errors.New("followerHook path must be absolute") + if len(c.LeaderHook) > 0 { + if !filepath.IsAbs(c.LeaderHook[0]) { + return errors.New("leaderHook executable path must be absolute") + } + } + if len(c.FollowerHook) > 0 { + if !filepath.IsAbs(c.FollowerHook[0]) { + return errors.New("followerHook executable path must be absolute") } } raft := c.Cluster.Raft @@ -116,8 +123,8 @@ type Config struct { VirtualIPs []string `yaml:"virtualIPs"` Interface string `yaml:"interface,omitempty"` Label string `yaml:"label,omitempty"` - LeaderHook string `yaml:"leaderHook,omitempty"` - FollowerHook string `yaml:"followerHook,omitempty"` + LeaderHook []string `yaml:"leaderHook,omitempty"` + FollowerHook []string `yaml:"followerHook,omitempty"` Cluster Cluster `yaml:"cluster"` } diff --git a/pkg/controller/vip.go b/pkg/controller/vip.go index eb296d8..850ea47 100644 --- a/pkg/controller/vip.go +++ b/pkg/controller/vip.go @@ -19,9 +19,9 @@ func NewVIPController(cfg *config.Config) (callbacks cluster.Callbacks, err erro callbacks = cluster.Callbacks{ Leader: func(ctx context.Context, cc cluster.CallbackContext) { t := time.NewTicker(time.Second * 10) - if cfg.LeaderHook != "" { + if len(cfg.LeaderHook) > 0 { go func() { - err := exec.CommandContext(ctx, cfg.LeaderHook).Run() + err := exec.CommandContext(ctx, cfg.LeaderHook[0], cfg.LeaderHook[1:]...).Run() if err != nil { cc.Error(err, "error running hook", "leaderHook", cfg.LeaderHook) } @@ -42,9 +42,9 @@ func NewVIPController(cfg *config.Config) (callbacks cluster.Callbacks, err erro }, Follower: func(ctx context.Context, cc cluster.CallbackContext) { t := time.NewTicker(time.Second * 10) - if cfg.FollowerHook != "" { + if len(cfg.FollowerHook) > 0 { go func() { - err := exec.CommandContext(ctx, cfg.FollowerHook).Run() + err := exec.CommandContext(ctx, cfg.FollowerHook[0], cfg.FollowerHook[1:]...).Run() if err != nil { cc.Error(err, "error running hook", "followerHook", cfg.FollowerHook) } @@ -52,8 +52,7 @@ func NewVIPController(cfg *config.Config) (callbacks cluster.Callbacks, err erro } for { cc.Info("following", "id", cc.ID()) - //TODO - //deleteIPs(cc, n, cfg.VirtualIPs) + deleteIPs(cc, n, cfg.VirtualIPs) select { case <-ctx.Done(): t.Stop() diff --git a/pkg/vip/arp_linux.go b/pkg/vip/arp_linux.go index af14d3a..e993e01 100644 --- a/pkg/vip/arp_linux.go +++ b/pkg/vip/arp_linux.go @@ -1,159 +1,30 @@ -// +build linux - -// These syscalls are only supported on Linux, so this uses a build directive during compilation. Other OS's will use the arp_unsupported.go and receive an error - package vip import ( - "bytes" - "encoding/binary" "fmt" "net" - "syscall" - "unsafe" + + "github.com/mdlayher/arp" + "github.com/mdlayher/ethernet" ) -const ( - opARPRequest = 1 - opARPReply = 2 - hwLen = 6 -) - -var ( - ethernetBroadcast = net.HardwareAddr{0xff, 0xff, 0xff, 0xff, 0xff, 0xff} - // arpRequest is used to flip between garp request or garp reply - arpRequest = true -) - -func htons(p uint16) uint16 { - var b [2]byte - binary.BigEndian.PutUint16(b[:], p) - return *(*uint16)(unsafe.Pointer(&b)) -} - -// arpHeader specifies the header for an ARP message. -type arpHeader struct { - hardwareType uint16 - protocolType uint16 - hardwareAddressLength uint8 - protocolAddressLength uint8 - opcode uint16 -} - -// arpMessage represents an ARP message. -type arpMessage struct { - arpHeader - senderHardwareAddress []byte - senderProtocolAddress []byte - targetHardwareAddress []byte - targetProtocolAddress []byte -} - -// bytes returns the wire representation of the ARP message. -func (m *arpMessage) bytes() ([]byte, error) { - buf := new(bytes.Buffer) - - if err := binary.Write(buf, binary.BigEndian, m.arpHeader); err != nil { - return nil, fmt.Errorf("binary write failed: %v", err) - } - buf.Write(m.senderHardwareAddress) - buf.Write(m.senderProtocolAddress) - buf.Write(m.targetHardwareAddress) - buf.Write(m.targetProtocolAddress) - - return buf.Bytes(), nil -} - -// gratuitousARP return a gARP request or gARP reply alternatively -// because different devices may support either one of them -func gratuitousARP(ip net.IP, mac net.HardwareAddr) (*arpMessage, error) { - if ip.To4() == nil { - return nil, fmt.Errorf("%q is not an IPv4 address", ip) - } - if len(mac) != hwLen { - return nil, fmt.Errorf("%q is not an Ethernet MAC address", mac) - } - - m := &arpMessage{ - arpHeader: arpHeader{ - 1, // Ethernet - 0x0800, // IPv4 - hwLen, // 48-bit MAC Address - net.IPv4len, // 32-bit IPv4 Address - opARPReply, // ARP Reply - }, - } - - // https://tools.ietf.org/html/rfc5944#section-4.6 - // In either case, the ARP Sender Hardware Address is - // set to the link-layer address to which this cache entry should be - // updated. - m.senderHardwareAddress = mac - - // When using an ARP Reply packet, the Target Hardware - // Address is also set to the link-layer address to which this cache - // entry should be updated (this field is not used in an ARP Request - // packet). - m.targetHardwareAddress = mac - - // In either case, the ARP Sender Protocol Address and - // ARP Target Protocol Address are both set to the IP address of the - // cache entry to be updated, - m.senderProtocolAddress = ip.To4() - m.targetProtocolAddress = ip.To4() - - // send arpRequest and arpReply alternatively - arpRequest = !arpRequest - if arpRequest { - m.arpHeader.opcode = opARPRequest - - // this field is not used in an ARP Request packet - m.targetHardwareAddress = ethernetBroadcast - } - - return m, nil -} - -// sendARP sends the given ARP message via the specified interface. -func sendARP(iface *net.Interface, m *arpMessage) error { - fd, err := syscall.Socket(syscall.AF_PACKET, syscall.SOCK_DGRAM, int(htons(syscall.ETH_P_ARP))) +func sendARP(ip net.IP, iface *net.Interface) error { + c, err := arp.Dial(iface) if err != nil { - return fmt.Errorf("failed to get raw socket: %v", err) + return err } - defer syscall.Close(fd) - - if err := syscall.BindToDevice(fd, iface.Name); err != nil { - return fmt.Errorf("failed to bind to device: %v", err) + for _, op := range []arp.Operation{arp.OperationRequest, arp.OperationReply} { + pkt, err := arp.NewPacket(op, iface.HardwareAddr, ip, ethernet.Broadcast, ip) + if err != nil { + return fmt.Errorf("assembling %q gratuitous packet for %q: %s", op, ip, err) + } + if err = c.WriteTo(pkt, ethernet.Broadcast); err != nil { + return fmt.Errorf("writing %q gratuitous packet for %q: %s", op, ip, err) + } } - - ll := syscall.SockaddrLinklayer{ - Protocol: htons(syscall.ETH_P_ARP), - Ifindex: iface.Index, - Pkttype: 0, // syscall.PACKET_HOST - Hatype: m.hardwareType, - Halen: m.hardwareAddressLength, - } - target := ethernetBroadcast - for i := 0; i < len(target); i++ { - ll.Addr[i] = target[i] - } - - b, err := m.bytes() - if err != nil { - return fmt.Errorf("failed to convert ARP message: %v", err) - } - - if err := syscall.Bind(fd, &ll); err != nil { - return fmt.Errorf("failed to bind: %v", err) - } - if err := syscall.Sendto(fd, b, 0, &ll); err != nil { - return fmt.Errorf("failed to send: %v", err) - } - return nil } -// ARPSendGratuitous sends a gratuitous ARP message via the specified interface. func ARPSendGratuitous(cidr, iface string) error { i, err := net.InterfaceByName(iface) if err != nil { @@ -163,11 +34,5 @@ func ARPSendGratuitous(cidr, iface string) error { if err != nil { return fmt.Errorf("failed to parse cidr: %s", cidr) } - - //log.Infof("Broadcasting ARP update for %s (%s) via %s", address, iface.HardwareAddr, iface.Name) - m, err := gratuitousARP(ip, i.HardwareAddr) - if err != nil { - return err - } - return sendARP(i, m) + return sendARP(ip, i) } diff --git a/vipman.yaml b/vipman.yaml index 096f184..fd5565e 100644 --- a/vipman.yaml +++ b/vipman.yaml @@ -2,8 +2,10 @@ virtualIPs: - "" interface: "" label: "" -leaderHook: "" -followerHook: "" +leaderHook: +- "" +followerHook: +- "" cluster: raft: dir: ""