@@ -54,6 +54,7 @@ pub struct Discovery {
5454 node_id : String ,
5555 bind_address : String ,
5656 role : DiscoveryRole ,
57+ configured_peers : Vec < String > ,
5758 peers : Arc < RwLock < HashMap < String , DiscoveredPeer > > > ,
5859 is_leader : Arc < RwLock < bool > > ,
5960 running : Arc < RwLock < bool > > ,
@@ -66,12 +67,19 @@ impl Discovery {
6667 node_id,
6768 bind_address,
6869 role : role. into ( ) ,
70+ configured_peers : Vec :: new ( ) ,
6971 peers : Arc :: new ( RwLock :: new ( HashMap :: new ( ) ) ) ,
7072 is_leader : Arc :: new ( RwLock :: new ( false ) ) ,
7173 running : Arc :: new ( RwLock :: new ( false ) ) ,
7274 }
7375 }
7476
77+ /// Create with configured peers for direct unicast discovery
78+ pub fn with_peers ( mut self , peers : Vec < String > ) -> Self {
79+ self . configured_peers = peers;
80+ self
81+ }
82+
7583 /// Set leader status
7684 pub fn set_leader ( & self , is_leader : bool ) {
7785 * self . is_leader . write ( ) . unwrap ( ) = is_leader;
@@ -100,9 +108,10 @@ impl Discovery {
100108 let role = self . role ;
101109 let is_leader = Arc :: clone ( & self . is_leader ) ;
102110 let running = Arc :: clone ( & self . running ) ;
111+ let configured_peers = self . configured_peers . clone ( ) ;
103112
104113 thread:: spawn ( move || {
105- if let Err ( e) = run_broadcaster ( node_id, bind_address, role, is_leader, running) {
114+ if let Err ( e) = run_broadcaster ( node_id, bind_address, role, is_leader, running, configured_peers ) {
106115 warn ! ( "Discovery broadcaster error: {}" , e) ;
107116 }
108117 } ) ;
@@ -177,16 +186,17 @@ fn run_broadcaster(
177186 role : DiscoveryRole ,
178187 is_leader : Arc < RwLock < bool > > ,
179188 running : Arc < RwLock < bool > > ,
189+ configured_peers : Vec < String > ,
180190) -> std:: io:: Result < ( ) > {
181191 // Create UDP socket for broadcasting
182192 let socket = UdpSocket :: bind ( "0.0.0.0:0" ) ?;
183193 socket. set_broadcast ( true ) ?;
184-
194+
185195 // Broadcast destinations: 255.255.255.255 for LAN, plus subnet broadcast for WolfNet
186196 let mut broadcast_addrs: Vec < SocketAddr > = vec ! [
187197 format!( "255.255.255.255:{}" , DISCOVERY_PORT ) . parse( ) . unwrap( ) ,
188198 ] ;
189-
199+
190200 // If bind address is on a specific IP (not 0.0.0.0), also broadcast to its /24 subnet
191201 // e.g. bind 10.10.10.3:8550 → broadcast to 10.10.10.255:8551
192202 if let Ok ( bind_sock) = bind_address. parse :: < SocketAddr > ( ) {
@@ -202,7 +212,22 @@ fn run_broadcaster(
202212 }
203213 }
204214
205- info ! ( "Discovery broadcaster started for node {}" , node_id) ;
215+ // Also send discovery directly to configured peers (unicast) — critical for
216+ // cross-subnet and WolfNet connectivity where broadcast doesn't work
217+ for peer in & configured_peers {
218+ // Peer address is host:port for the data port — discovery uses DISCOVERY_PORT
219+ if let Some ( host) = peer. rsplit_once ( ':' ) . map ( |( h, _) | h) {
220+ let discovery_addr = format ! ( "{}:{}" , host, DISCOVERY_PORT ) ;
221+ if let Ok ( addr) = discovery_addr. parse :: < SocketAddr > ( ) {
222+ if !broadcast_addrs. contains ( & addr) {
223+ broadcast_addrs. push ( addr) ;
224+ info ! ( "Added direct peer discovery target: {}" , addr) ;
225+ }
226+ }
227+ }
228+ }
229+
230+ info ! ( "Discovery broadcaster started for node {} ({} targets)" , node_id, broadcast_addrs. len( ) ) ;
206231
207232 while * running. read ( ) . unwrap ( ) {
208233 let is_server = matches ! ( role, DiscoveryRole :: Server ) ;
0 commit comments