// Copyright (c) 2026, Pim van Pelt package vpp import ( "bytes" "fmt" "log/slog" "net" "git.ipng.ch/ipng/vpp-maglev/internal/config" ip_types "git.ipng.ch/ipng/vpp-maglev/internal/vpp/binapi/ip_types" lb "git.ipng.ch/ipng/vpp-maglev/internal/vpp/binapi/lb" ) // SetLBConf sends lb_conf to VPP with the global load-balancer settings from // cfg. Called on VPP connect (startup and reconnect) and after every // successful config reload. Returns nil if VPP is not connected (silently // skipped — the next connect will push the conf). // // The values sent are cached on the Client; if SetLBConf is called twice in // a row with unchanged values, no API call is made and no log is emitted. func (c *Client) SetLBConf(cfg *config.Config) error { if !c.IsConnected() { return nil } req := &lb.LbConf{ IP4SrcAddress: ip_types.IP4Address(ip4Bytes(cfg.VPP.LB.IPv4SrcAddress)), IP6SrcAddress: ip_types.IP6Address(ip6Bytes(cfg.VPP.LB.IPv6SrcAddress)), StickyBucketsPerCore: cfg.VPP.LB.StickyBucketsPerCore, FlowTimeout: uint32(cfg.VPP.LB.FlowTimeout.Seconds()), } // Skip if nothing changed since the last successful push. c.mu.Lock() prev := c.lastLBConf c.mu.Unlock() if prev != nil && bytes.Equal(prev.IP4SrcAddress[:], req.IP4SrcAddress[:]) && bytes.Equal(prev.IP6SrcAddress[:], req.IP6SrcAddress[:]) && prev.StickyBucketsPerCore == req.StickyBucketsPerCore && prev.FlowTimeout == req.FlowTimeout { return nil } ch, err := c.apiChannel() if err != nil { return err } defer ch.Close() reply := &lb.LbConfReply{} if err := ch.SendRequest(req).ReceiveReply(reply); err != nil { return fmt.Errorf("lb_conf: %w", err) } if reply.Retval != 0 { return fmt.Errorf("lb_conf: retval=%d", reply.Retval) } c.mu.Lock() c.lastLBConf = req c.mu.Unlock() slog.Info("vpp-lb-conf-set", "ipv4-src", ipStringFromCfg(cfg.VPP.LB.IPv4SrcAddress), "ipv6-src", ipStringFromCfg(cfg.VPP.LB.IPv6SrcAddress), "sticky-buckets-per-core", req.StickyBucketsPerCore, "flow-timeout", cfg.VPP.LB.FlowTimeout.String()) return nil } // ip4Bytes returns the 4-byte representation of an IPv4 address, or all-zero // if ip is nil/unset. func ip4Bytes(ip net.IP) [4]byte { var out [4]byte if ip == nil { return out } if b := ip.To4(); b != nil { copy(out[:], b) } return out } // ip6Bytes returns the 16-byte representation of an IPv6 address, or all-zero // if ip is nil/unset. func ip6Bytes(ip net.IP) [16]byte { var out [16]byte if ip == nil { return out } copy(out[:], ip.To16()) return out } // ipStringFromCfg renders an IP for logging; returns "unset" if nil. func ipStringFromCfg(ip net.IP) string { if ip == nil { return "unset" } return ip.String() }