9
9
using System . Threading . Tasks ;
10
10
using Microsoft . AspNetCore . Builder ;
11
11
using Microsoft . AspNetCore . Connections ;
12
+ using Microsoft . AspNetCore . Hosting ;
12
13
using Microsoft . AspNetCore . Hosting . Server ;
13
14
using Microsoft . AspNetCore . Hosting . Server . Features ;
15
+ using Microsoft . AspNetCore . Http . Features ;
14
16
using Microsoft . AspNetCore . Server . Kestrel . Core . Internal . Http ;
15
17
using Microsoft . AspNetCore . Server . Kestrel . Core . Internal . Infrastructure ;
16
18
using Microsoft . AspNetCore . Server . Kestrel . Https . Internal ;
@@ -247,6 +249,78 @@ public void StartWithMultipleTransportFactoriesDoesNotThrow()
247
249
StartDummyApplication ( server ) ;
248
250
}
249
251
252
+ [ Fact ]
253
+ public async Task ListenIPWithStaticPort_TransportsGetIPv6Any ( )
254
+ {
255
+ var options = new KestrelServerOptions ( ) ;
256
+ options . ApplicationServices = new ServiceCollection ( )
257
+ . AddLogging ( )
258
+ . BuildServiceProvider ( ) ;
259
+ options . ListenAnyIP ( 5000 , options =>
260
+ {
261
+ options . UseHttps ( TestResources . GetTestCertificate ( ) ) ;
262
+ options . Protocols = HttpProtocols . Http1AndHttp2AndHttp3 ;
263
+ } ) ;
264
+
265
+ var mockTransportFactory = new MockTransportFactory ( ) ;
266
+ var mockMultiplexedTransportFactory = new MockMultiplexedTransportFactory ( ) ;
267
+
268
+ using var server = new KestrelServerImpl (
269
+ Options . Create ( options ) ,
270
+ new List < IConnectionListenerFactory > ( ) { mockTransportFactory } ,
271
+ new List < IMultiplexedConnectionListenerFactory > ( ) { mockMultiplexedTransportFactory } ,
272
+ new LoggerFactory ( new [ ] { new KestrelTestLoggerProvider ( ) } ) ) ;
273
+
274
+ await server . StartAsync ( new DummyApplication ( context => Task . CompletedTask ) , CancellationToken . None ) ;
275
+
276
+ var transportEndPoint = Assert . Single ( mockTransportFactory . BoundEndPoints ) ;
277
+ var multiplexedTransportEndPoint = Assert . Single ( mockMultiplexedTransportFactory . BoundEndPoints ) ;
278
+
279
+ // Both transports should get the IPv6Any
280
+ Assert . Equal ( IPAddress . IPv6Any , ( ( IPEndPoint ) transportEndPoint . OriginalEndPoint ) . Address ) ;
281
+ Assert . Equal ( IPAddress . IPv6Any , ( ( IPEndPoint ) multiplexedTransportEndPoint . OriginalEndPoint ) . Address ) ;
282
+
283
+ Assert . Equal ( 5000 , ( ( IPEndPoint ) transportEndPoint . OriginalEndPoint ) . Port ) ;
284
+ Assert . Equal ( 5000 , ( ( IPEndPoint ) multiplexedTransportEndPoint . OriginalEndPoint ) . Port ) ;
285
+ }
286
+
287
+ [ Fact ]
288
+ public async Task ListenIPWithEphemeralPort_TransportsGetIPv6Any ( )
289
+ {
290
+ var options = new KestrelServerOptions ( ) ;
291
+ options . ApplicationServices = new ServiceCollection ( )
292
+ . AddLogging ( )
293
+ . BuildServiceProvider ( ) ;
294
+ options . ListenAnyIP ( 0 , options =>
295
+ {
296
+ options . UseHttps ( TestResources . GetTestCertificate ( ) ) ;
297
+ options . Protocols = HttpProtocols . Http1AndHttp2AndHttp3 ;
298
+ } ) ;
299
+
300
+ var mockTransportFactory = new MockTransportFactory ( ) ;
301
+ var mockMultiplexedTransportFactory = new MockMultiplexedTransportFactory ( ) ;
302
+
303
+ using var server = new KestrelServerImpl (
304
+ Options . Create ( options ) ,
305
+ new List < IConnectionListenerFactory > ( ) { mockTransportFactory } ,
306
+ new List < IMultiplexedConnectionListenerFactory > ( ) { mockMultiplexedTransportFactory } ,
307
+ new LoggerFactory ( new [ ] { new KestrelTestLoggerProvider ( ) } ) ) ;
308
+
309
+ await server . StartAsync ( new DummyApplication ( context => Task . CompletedTask ) , CancellationToken . None ) ;
310
+
311
+ var transportEndPoint = Assert . Single ( mockTransportFactory . BoundEndPoints ) ;
312
+ var multiplexedTransportEndPoint = Assert . Single ( mockMultiplexedTransportFactory . BoundEndPoints ) ;
313
+
314
+ Assert . Equal ( IPAddress . IPv6Any , ( ( IPEndPoint ) transportEndPoint . OriginalEndPoint ) . Address ) ;
315
+ Assert . Equal ( IPAddress . IPv6Any , ( ( IPEndPoint ) multiplexedTransportEndPoint . OriginalEndPoint ) . Address ) ;
316
+
317
+ // Should have been assigned a random value.
318
+ Assert . NotEqual ( 0 , ( ( IPEndPoint ) transportEndPoint . BoundEndPoint ) . Port ) ;
319
+
320
+ // Same random value should be used for both transports.
321
+ Assert . Equal ( ( ( IPEndPoint ) transportEndPoint . BoundEndPoint ) . Port , ( ( IPEndPoint ) multiplexedTransportEndPoint . BoundEndPoint ) . Port ) ;
322
+ }
323
+
250
324
[ Fact ]
251
325
public async Task StopAsyncCallsCompleteWhenFirstCallCompletes ( )
252
326
{
@@ -692,10 +766,24 @@ private static void StartDummyApplication(IServer server)
692
766
693
767
private class MockTransportFactory : IConnectionListenerFactory
694
768
{
769
+ public List < BindDetail > BoundEndPoints { get ; } = new List < BindDetail > ( ) ;
770
+
695
771
public ValueTask < IConnectionListener > BindAsync ( EndPoint endpoint , CancellationToken cancellationToken = default )
696
772
{
773
+ EndPoint resolvedEndPoint = endpoint ;
774
+ if ( resolvedEndPoint is IPEndPoint ipEndPoint )
775
+ {
776
+ var port = ipEndPoint . Port == 0
777
+ ? Random . Shared . Next ( IPEndPoint . MinPort , IPEndPoint . MaxPort )
778
+ : ipEndPoint . Port ;
779
+
780
+ resolvedEndPoint = new IPEndPoint ( new IPAddress ( ipEndPoint . Address . GetAddressBytes ( ) ) , port ) ;
781
+ }
782
+
783
+ BoundEndPoints . Add ( new BindDetail ( endpoint , resolvedEndPoint ) ) ;
784
+
697
785
var mock = new Mock < IConnectionListener > ( ) ;
698
- mock . Setup ( m => m . EndPoint ) . Returns ( endpoint ) ;
786
+ mock . Setup ( m => m . EndPoint ) . Returns ( resolvedEndPoint ) ;
699
787
return new ValueTask < IConnectionListener > ( mock . Object ) ;
700
788
}
701
789
}
@@ -707,5 +795,31 @@ public ValueTask<IConnectionListener> BindAsync(EndPoint endpoint, CancellationT
707
795
throw new InvalidOperationException ( ) ;
708
796
}
709
797
}
798
+
799
+ private class MockMultiplexedTransportFactory : IMultiplexedConnectionListenerFactory
800
+ {
801
+ public List < BindDetail > BoundEndPoints { get ; } = new List < BindDetail > ( ) ;
802
+
803
+ public ValueTask < IMultiplexedConnectionListener > BindAsync ( EndPoint endpoint , IFeatureCollection features = null , CancellationToken cancellationToken = default )
804
+ {
805
+ EndPoint resolvedEndPoint = endpoint ;
806
+ if ( resolvedEndPoint is IPEndPoint ipEndPoint )
807
+ {
808
+ var port = ipEndPoint . Port == 0
809
+ ? Random . Shared . Next ( IPEndPoint . MinPort , IPEndPoint . MaxPort )
810
+ : ipEndPoint . Port ;
811
+
812
+ resolvedEndPoint = new IPEndPoint ( new IPAddress ( ipEndPoint . Address . GetAddressBytes ( ) ) , port ) ;
813
+ }
814
+
815
+ BoundEndPoints . Add ( new BindDetail ( endpoint , resolvedEndPoint ) ) ;
816
+
817
+ var mock = new Mock < IMultiplexedConnectionListener > ( ) ;
818
+ mock . Setup ( m => m . EndPoint ) . Returns ( resolvedEndPoint ) ;
819
+ return new ValueTask < IMultiplexedConnectionListener > ( mock . Object ) ;
820
+ }
821
+ }
822
+
823
+ private record BindDetail ( EndPoint OriginalEndPoint , EndPoint BoundEndPoint ) ;
710
824
}
711
825
}
0 commit comments