@@ -110,6 +110,41 @@ func TestAddMappingInvalidPort(t *testing.T) {
110
110
require .False (t , found , "didn't expect a port mapping for invalid nat-ed port" )
111
111
}
112
112
113
+ // TestAddMappingDeduplication tests that duplicate AddMapping calls don't trigger duplicate NAT operations.
114
+ // This is a regression test for a bug where multiple libp2p listeners sharing the same port
115
+ // (e.g., TCP, QUIC, WebTransport, WebRTC-direct all on the same port) would cause duplicate NAT
116
+ // port mapping requests - resulting in 5+ duplicate mapping attempts for the same protocol/port combination.
117
+ func TestAddMappingDeduplication (t * testing.T ) {
118
+ mockNAT , reset := setupMockNAT (t )
119
+ defer reset ()
120
+
121
+ mockNAT .EXPECT ().GetExternalAddress ().Return (net .IPv4 (1 , 2 , 3 , 4 ), nil )
122
+ nat , err := DiscoverNAT (context .Background ())
123
+ require .NoError (t , err )
124
+
125
+ // Expect only ONE call to AddPortMapping despite multiple AddMapping calls
126
+ expectPortMappingSuccess (mockNAT , "tcp" , 10000 , 1234 )
127
+
128
+ // First call should trigger NAT operation
129
+ require .NoError (t , nat .AddMapping (context .Background (), "tcp" , 10000 ))
130
+
131
+ // Verify mapping was created
132
+ mapped , found := nat .GetMapping ("tcp" , 10000 )
133
+ require .True (t , found , "expected port mapping" )
134
+ addr , _ := netip .AddrFromSlice (net .IPv4 (1 , 2 , 3 , 4 ))
135
+ require .Equal (t , netip .AddrPortFrom (addr , 1234 ), mapped )
136
+
137
+ // Second and third calls should NOT trigger NAT operations (no additional expectations)
138
+ // This simulates what happens when multiple transports use the same port
139
+ require .NoError (t , nat .AddMapping (context .Background (), "tcp" , 10000 ))
140
+ require .NoError (t , nat .AddMapping (context .Background (), "tcp" , 10000 ))
141
+
142
+ // Mapping should still exist
143
+ mapped , found = nat .GetMapping ("tcp" , 10000 )
144
+ require .True (t , found , "expected port mapping" )
145
+ require .Equal (t , netip .AddrPortFrom (addr , 1234 ), mapped )
146
+ }
147
+
113
148
// TestNATRediscoveryOnConnectionError tests automatic NAT rediscovery after router restart
114
149
// to ensure mappings are restored when router's NAT service (e.g. miniupnpd) changes its listening port
115
150
// (a regression test for https://github.com/libp2p/go-libp2p/issues/3224#issuecomment-2866844723).
0 commit comments