11//
22// (C) Copyright 2021-2024 Intel Corporation.
3- // (C) Copyright 2025 Hewlett Packard Enterprise Development LP
3+ // (C) Copyright 2025-2026 Hewlett Packard Enterprise Development LP
44//
55// SPDX-License-Identifier: BSD-2-Clause-Patent
66//
@@ -150,11 +150,30 @@ type ctlAddrParams struct {
150150 port int
151151 replicaAddrSrc replicaAddrGetter
152152 lookupHost ipLookupFn
153+ ctlIface netInterface // optional: if set, use this interface for bind address
153154}
154155
155156func getControlAddr (params ctlAddrParams ) (* net.TCPAddr , error ) {
156- ipStr := "0.0.0.0"
157+ // If a control interface is configured, use its first IPv4 address.
158+ if params .ctlIface != nil {
159+ ip , err := getFirstIPv4Addr (params .ctlIface )
160+ if err != nil {
161+ return nil , errors .Wrap (err , "getting control interface address" )
162+ }
163+
164+ // If this node is a replica, verify the control interface address matches
165+ // the configured replica address. A mismatch would break raft connectivity.
166+ if repAddr , err := params .replicaAddrSrc .ReplicaAddr (); err == nil {
167+ if ! repAddr .IP .Equal (ip ) {
168+ return nil , config .FaultConfigControlInterfaceMismatch (ip .String (), repAddr .IP .String ())
169+ }
170+ }
157171
172+ return & net.TCPAddr {IP : ip , Port : params .port }, nil
173+ }
174+
175+ // Fall back to legacy behavior: use replica address if available, otherwise 0.0.0.0.
176+ ipStr := "0.0.0.0"
158177 if repAddr , err := params .replicaAddrSrc .ReplicaAddr (); err == nil {
159178 ipStr = repAddr .IP .String ()
160179 }
@@ -167,11 +186,17 @@ func getControlAddr(params ctlAddrParams) (*net.TCPAddr, error) {
167186 return ctlAddr , nil
168187}
169188
170- func createListener (ctlAddr * net.TCPAddr , listen netListenFn ) (net.Listener , error ) {
189+ func createListener (ctlAddr * net.TCPAddr , listen netListenFn , bindToCtlAddr bool ) (net.Listener , error ) {
171190 // Create and start listener on management network.
172- lis , err := listen ("tcp4" , fmt .Sprintf ("0.0.0.0:%d" , ctlAddr .Port ))
191+ // Only bind to ctlAddr.IP if explicitly requested (i.e., control_iface is set),
192+ // otherwise bind to all interfaces (0.0.0.0) for backwards compatibility.
193+ bindAddr := fmt .Sprintf ("0.0.0.0:%d" , ctlAddr .Port )
194+ if bindToCtlAddr {
195+ bindAddr = ctlAddr .String ()
196+ }
197+ lis , err := listen ("tcp4" , bindAddr )
173198 if err != nil {
174- return nil , errors .Wrap (err , "unable to listen on management interface" )
199+ return nil , errors .Wrapf (err , "unable to listen on %s" , bindAddr )
175200 }
176201
177202 return lis , nil
@@ -730,9 +755,12 @@ func registerTelemetryCallbacks(ctx context.Context, srv *server) {
730755 return
731756 }
732757
758+ // Use the same bind address as the control plane listener.
759+ bindAddr := srv .ctlAddr .IP .String ()
760+
733761 srv .OnEnginesStarted (func (ctxIn context.Context ) error {
734762 srv .log .Debug ("starting Prometheus exporter" )
735- cleanup , err := startPrometheusExporter (ctxIn , srv .log , telemPort , srv .harness .Instances ())
763+ cleanup , err := startPrometheusExporter (ctxIn , srv .log , telemPort , bindAddr , srv .harness .Instances ())
736764 if err != nil {
737765 return err
738766 }
@@ -875,6 +903,35 @@ type netInterface interface {
875903 Addrs () ([]net.Addr , error )
876904}
877905
906+ // getFirstIPv4Addr returns the first (lowest) IPv4 address from the interface.
907+ // If multiple IPv4 addresses exist, the lowest one is returned for determinism.
908+ func getFirstIPv4Addr (iface netInterface ) (net.IP , error ) {
909+ addrs , err := iface .Addrs ()
910+ if err != nil {
911+ return nil , errors .Wrap (err , "failed to get interface addresses" )
912+ }
913+
914+ var ipv4s []net.IP
915+ for _ , a := range addrs {
916+ if ipNet , ok := a .(* net.IPNet ); ok && ipNet .IP != nil {
917+ if v4 := ipNet .IP .To4 (); v4 != nil {
918+ ipv4s = append (ipv4s , v4 )
919+ }
920+ }
921+ }
922+
923+ if len (ipv4s ) == 0 {
924+ return nil , errors .New ("no IPv4 addresses on interface" )
925+ }
926+
927+ // Sort for deterministic selection (lowest address first).
928+ sort .Slice (ipv4s , func (i , j int ) bool {
929+ return bytes .Compare (ipv4s [i ], ipv4s [j ]) < 0
930+ })
931+
932+ return ipv4s [0 ], nil
933+ }
934+
878935func getSrxSetting (cfg * config.Server ) (int32 , error ) {
879936 if len (cfg .Engines ) == 0 {
880937 return - 1 , nil
0 commit comments