@@ -195,3 +195,153 @@ func TestSYNRejectionWebhook(t *testing.T) {
195195 data , _ := ev .Data .(map [string ]interface {})
196196 t .Logf ("syn.rejected: src_node=%v" , data ["src_node_id" ])
197197}
198+
199+ // TestPublicNodeAcceptsUntrustedSYN verifies that public nodes skip the trust
200+ // gate entirely — any node can connect without trust or shared network.
201+ func TestPublicNodeAcceptsUntrustedSYN (t * testing.T ) {
202+ t .Parallel ()
203+ env := NewTestEnv (t )
204+
205+ server := env .AddDaemon (func (cfg * daemon.Config ) {
206+ cfg .Public = true // public node — trust gate should be skipped
207+ })
208+ client := env .AddDaemon (func (cfg * daemon.Config ) {
209+ cfg .Public = false // private, no trust, no shared network
210+ })
211+
212+ client .Daemon .AddTunnelPeer (server .Daemon .NodeID (), localUDPAddr (server .Daemon ))
213+ server .Daemon .AddTunnelPeer (client .Daemon .NodeID (), localUDPAddr (client .Daemon ))
214+
215+ conn , err := client .Driver .DialAddr (server .Daemon .Addr (), 7 )
216+ if err != nil {
217+ t .Fatalf ("expected dial to public node to succeed without trust: %v" , err )
218+ }
219+ conn .Close ()
220+ t .Log ("dial to public node succeeded without trust" )
221+ }
222+
223+ // TestDatagramRejectedFromUntrustedNode verifies that private nodes drop
224+ // datagrams from untrusted sources (same trust gate as SYN).
225+ func TestDatagramRejectedFromUntrustedNode (t * testing.T ) {
226+ t .Parallel ()
227+ env := NewTestEnv (t )
228+
229+ server := env .AddDaemon (func (cfg * daemon.Config ) {
230+ cfg .Public = false
231+ })
232+ client := env .AddDaemon (func (cfg * daemon.Config ) {
233+ cfg .Public = false
234+ })
235+
236+ // Pre-populate tunnel peers
237+ client .Daemon .AddTunnelPeer (server .Daemon .NodeID (), localUDPAddr (server .Daemon ))
238+ server .Daemon .AddTunnelPeer (client .Daemon .NodeID (), localUDPAddr (client .Daemon ))
239+
240+ // Start listening for datagrams on server
241+ received := make (chan struct {}, 1 )
242+ go func () {
243+ dg , err := server .Driver .RecvFrom ()
244+ if err == nil && dg != nil {
245+ received <- struct {}{}
246+ }
247+ }()
248+
249+ // Send datagram from untrusted client — should be silently dropped
250+ _ = client .Driver .SendTo (server .Daemon .Addr (), 5000 , []byte ("untrusted datagram" ))
251+
252+ select {
253+ case <- received :
254+ t .Fatal ("datagram from untrusted node should have been dropped, but was delivered" )
255+ case <- time .After (500 * time .Millisecond ):
256+ t .Log ("correctly: datagram from untrusted node was dropped" )
257+ }
258+ }
259+
260+ // TestDatagramAllowedFromSameNetwork verifies that datagrams from same-network
261+ // peers are delivered normally (trust via shared network membership).
262+ func TestDatagramAllowedFromSameNetwork (t * testing.T ) {
263+ t .Parallel ()
264+ env := NewTestEnv (t )
265+
266+ server := env .AddDaemon (func (cfg * daemon.Config ) {
267+ cfg .Public = false
268+ })
269+ client := env .AddDaemon (func (cfg * daemon.Config ) {
270+ cfg .Public = false
271+ })
272+
273+ // Join both to the same custom network via registry
274+ rc , err := registry .Dial (env .RegistryAddr )
275+ if err != nil {
276+ t .Fatalf ("dial registry: %v" , err )
277+ }
278+ defer rc .Close ()
279+
280+ resp , err := rc .CreateNetwork (server .Daemon .NodeID (), "dg-trust-net" , "open" , "" , env .AdminToken )
281+ if err != nil {
282+ t .Fatalf ("create network: %v" , err )
283+ }
284+ netID := uint16 (resp ["network_id" ].(float64 ))
285+
286+ if _ , err := rc .JoinNetwork (client .Daemon .NodeID (), netID , "" , 0 , env .AdminToken ); err != nil {
287+ t .Fatalf ("join network: %v" , err )
288+ }
289+
290+ // Pre-populate tunnel peers
291+ client .Daemon .AddTunnelPeer (server .Daemon .NodeID (), localUDPAddr (server .Daemon ))
292+ server .Daemon .AddTunnelPeer (client .Daemon .NodeID (), localUDPAddr (client .Daemon ))
293+
294+ // Start listening for datagrams on server
295+ received := make (chan string , 1 )
296+ go func () {
297+ dg , err := server .Driver .RecvFrom ()
298+ if err == nil && dg != nil {
299+ received <- string (dg .Data )
300+ }
301+ }()
302+
303+ // Send datagram from trusted client — should be delivered
304+ if err := client .Driver .SendTo (server .Daemon .Addr (), 5000 , []byte ("trusted datagram" )); err != nil {
305+ t .Fatalf ("sendto: %v" , err )
306+ }
307+
308+ select {
309+ case msg := <- received :
310+ if msg != "trusted datagram" {
311+ t .Errorf ("expected %q, got %q" , "trusted datagram" , msg )
312+ }
313+ t .Log ("datagram from same-network node delivered correctly" )
314+ case <- time .After (5 * time .Second ):
315+ t .Fatal ("timeout waiting for datagram from same-network node" )
316+ }
317+ }
318+
319+ // TestDatagramRejectionWebhook verifies that a datagram.rejected webhook event
320+ // is emitted when an untrusted node sends a datagram to a private node.
321+ func TestDatagramRejectionWebhook (t * testing.T ) {
322+ t .Parallel ()
323+ collector := newWebhookCollector ()
324+ defer collector .Close ()
325+
326+ env := NewTestEnv (t )
327+
328+ server := env .AddDaemon (func (cfg * daemon.Config ) {
329+ cfg .Public = false
330+ cfg .WebhookURL = collector .URL ()
331+ })
332+ client := env .AddDaemon (func (cfg * daemon.Config ) {
333+ cfg .Public = false
334+ })
335+
336+ client .Daemon .AddTunnelPeer (server .Daemon .NodeID (), localUDPAddr (server .Daemon ))
337+
338+ // Send datagram — should be rejected
339+ _ = client .Driver .SendTo (server .Daemon .Addr (), 5000 , []byte ("should be rejected" ))
340+
341+ ev , ok := collector .WaitFor ("datagram.rejected" , 3 * time .Second )
342+ if ! ok {
343+ t .Fatal ("expected datagram.rejected webhook event, but none received" )
344+ }
345+ data , _ := ev .Data .(map [string ]interface {})
346+ t .Logf ("datagram.rejected: src_node=%v" , data ["src_node_id" ])
347+ }
0 commit comments