@@ -547,6 +547,14 @@ func (d *Daemon) Identity() *crypto.Identity { return d.identity }
547547// TaskQueue returns the daemon's task queue.
548548func (d * Daemon ) TaskQueue () * TaskQueue { return d .taskQueue }
549549
550+ // AddTunnelPeer registers a peer's address in the tunnel manager (for testing/manual setup).
551+ func (d * Daemon ) AddTunnelPeer (nodeID uint32 , addr * net.UDPAddr ) {
552+ d .tunnels .AddPeer (nodeID , addr )
553+ }
554+
555+ // TunnelAddr returns the local UDP address of the tunnel listener.
556+ func (d * Daemon ) TunnelAddr () net.Addr { return d .tunnels .LocalAddr () }
557+
550558func (d * Daemon ) Addr () protocol.Addr {
551559 d .addrMu .RLock ()
552560 defer d .addrMu .RUnlock ()
@@ -728,6 +736,26 @@ func (d *Daemon) handleStreamPacket(pkt *protocol.Packet) {
728736 return
729737 }
730738
739+ // Trust gate: private nodes only accept SYN from trusted or same-network peers
740+ if ! d .config .Public {
741+ srcNode := pkt .Src .Node
742+ trusted := d .handshakes .IsTrusted (srcNode )
743+ if ! trusted && d .regConn != nil {
744+ // Fall back to registry trust check (covers admin-set trust pairs + shared networks)
745+ trusted , _ = d .regConn .CheckTrust (d .NodeID (), srcNode )
746+ }
747+ if ! trusted {
748+ slog .Warn ("SYN rejected: untrusted source" , "src_node" , srcNode , "src_addr" , pkt .Src , "dst_port" , pkt .DstPort )
749+ d .webhook .Emit ("syn.rejected" , map [string ]interface {}{
750+ "src_node_id" : srcNode ,
751+ "src_addr" : pkt .Src .String (),
752+ "dst_port" : pkt .DstPort ,
753+ })
754+ d .sendRST (pkt )
755+ return
756+ }
757+ }
758+
731759 conn := d .ports .NewConnection (pkt .DstPort , pkt .Src , pkt .SrcPort )
732760 conn .Mu .Lock ()
733761 conn .LocalAddr = d .Addr ()
@@ -1520,7 +1548,7 @@ func (d *Daemon) SendDatagram(dstAddr protocol.Addr, dstPort uint16, data []byte
15201548 srcPort := d .ports .AllocEphemeralPort ()
15211549
15221550 if dstAddr .IsBroadcast () {
1523- return fmt . Errorf ( "broadcast is not available — custom networks are WIP" )
1551+ return d . broadcastDatagram ( dstAddr . Network , srcPort , dstPort , data )
15241552 }
15251553
15261554 if err := d .ensureTunnel (dstAddr .Node ); err != nil {
@@ -1541,7 +1569,12 @@ func (d *Daemon) SendDatagram(dstAddr protocol.Addr, dstPort uint16, data []byte
15411569}
15421570
15431571// broadcastDatagram sends a datagram to all members of a network.
1572+ // Only network members are allowed to broadcast. Backbone (network 0) is blocked.
15441573func (d * Daemon ) broadcastDatagram (netID uint16 , srcPort , dstPort uint16 , data []byte ) error {
1574+ if netID == 0 {
1575+ return fmt .Errorf ("broadcast on backbone network is not permitted" )
1576+ }
1577+
15451578 resp , err := d .regConn .ListNodes (netID )
15461579 if err != nil {
15471580 return fmt .Errorf ("list nodes for broadcast: %w" , err )
@@ -1552,6 +1585,22 @@ func (d *Daemon) broadcastDatagram(netID uint16, srcPort, dstPort uint16, data [
15521585 return nil // no nodes
15531586 }
15541587
1588+ // Verify sender is a member of the network
1589+ isMember := false
1590+ for _ , n := range nodesRaw {
1591+ nodeMap , ok := n .(map [string ]interface {})
1592+ if ! ok {
1593+ continue
1594+ }
1595+ if nid , ok := nodeMap ["node_id" ].(float64 ); ok && uint32 (nid ) == d .NodeID () {
1596+ isMember = true
1597+ break
1598+ }
1599+ }
1600+ if ! isMember {
1601+ return fmt .Errorf ("broadcast denied: node %d is not a member of network %d" , d .NodeID (), netID )
1602+ }
1603+
15551604 for _ , n := range nodesRaw {
15561605 nodeMap , ok := n .(map [string ]interface {})
15571606 if ! ok {
0 commit comments