Skip to content

Commit e04ad64

Browse files
authored
Add cache for Socket.LocalEndPoint (#39313)
Added _localEndPoint field to cache LocalEndPoint value. Cached value is cleared on error and disconnect. If the value is a wildcard address, it is also cleared on connect and accept. Fix #1482
1 parent 493791a commit e04ad64

4 files changed

Lines changed: 357 additions & 11 deletions

File tree

src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Windows.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ partial void WildcardBindForConnectIfNecessary(AddressFamily addressFamily)
232232
switch (addressFamily)
233233
{
234234
case AddressFamily.InterNetwork:
235-
address = IsDualMode ? IPAddress.Any.MapToIPv6() : IPAddress.Any;
235+
address = IsDualMode ? s_IPAddressAnyMapToIPv6 : IPAddress.Any;
236236
break;
237237

238238
case AddressFamily.InterNetworkV6:

src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs

Lines changed: 71 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,19 @@ public partial class Socket : IDisposable
2222
{
2323
internal const int DefaultCloseTimeout = -1; // NOTE: changing this default is a breaking change.
2424

25+
private static readonly IPAddress s_IPAddressAnyMapToIPv6 = IPAddress.Any.MapToIPv6();
26+
2527
private SafeSocketHandle _handle;
2628

2729
// _rightEndPoint is null if the socket has not been bound. Otherwise, it is any EndPoint of the
2830
// correct type (IPEndPoint, etc).
2931
internal EndPoint? _rightEndPoint;
3032
internal EndPoint? _remoteEndPoint;
3133

34+
// Cached LocalEndPoint value. Cleared on disconnect and error. Cached wildcard addresses are
35+
// also cleared on connect and accept.
36+
private EndPoint? _localEndPoint;
37+
3238
// These flags monitor if the socket was ever connected at any time and if it still is.
3339
private bool _isConnected;
3440
private bool _isDisconnected;
@@ -317,6 +323,7 @@ public EndPoint? LocalEndPoint
317323
// Update the state if we've become connected after a non-blocking connect.
318324
_isConnected = true;
319325
_rightEndPoint = _nonBlockingConnectRightEndPoint;
326+
UpdateLocalEndPointOnConnect();
320327
_nonBlockingConnectInProgress = false;
321328
}
322329

@@ -325,23 +332,27 @@ public EndPoint? LocalEndPoint
325332
return null;
326333
}
327334

328-
Internals.SocketAddress socketAddress = IPEndPointExtensions.Serialize(_rightEndPoint);
329-
330-
unsafe
335+
if (_localEndPoint == null)
331336
{
332-
fixed (byte* buffer = socketAddress.Buffer)
333-
fixed (int* bufferSize = &socketAddress.InternalSize)
337+
Internals.SocketAddress socketAddress = IPEndPointExtensions.Serialize(_rightEndPoint);
338+
339+
unsafe
334340
{
335-
// This may throw ObjectDisposedException.
336-
SocketError errorCode = SocketPal.GetSockName(_handle, buffer, bufferSize);
337-
if (errorCode != SocketError.Success)
341+
fixed (byte* buffer = socketAddress.Buffer)
342+
fixed (int* bufferSize = &socketAddress.InternalSize)
338343
{
339-
UpdateStatusAfterSocketErrorAndThrowException(errorCode);
344+
// This may throw ObjectDisposedException.
345+
SocketError errorCode = SocketPal.GetSockName(_handle, buffer, bufferSize);
346+
if (errorCode != SocketError.Success)
347+
{
348+
UpdateStatusAfterSocketErrorAndThrowException(errorCode);
349+
}
340350
}
341351
}
352+
_localEndPoint = _rightEndPoint.Create(socketAddress);
342353
}
343354

344-
return _rightEndPoint.Create(socketAddress);
355+
return _localEndPoint;
345356
}
346357
}
347358

@@ -359,6 +370,7 @@ public EndPoint? RemoteEndPoint
359370
// Update the state if we've become connected after a non-blocking connect.
360371
_isConnected = true;
361372
_rightEndPoint = _nonBlockingConnectRightEndPoint;
373+
UpdateLocalEndPointOnConnect();
362374
_nonBlockingConnectInProgress = false;
363375
}
364376

@@ -470,6 +482,7 @@ public bool Connected
470482
// Update the state if we've become connected after a non-blocking connect.
471483
_isConnected = true;
472484
_rightEndPoint = _nonBlockingConnectRightEndPoint;
485+
UpdateLocalEndPointOnConnect();
473486
_nonBlockingConnectInProgress = false;
474487
}
475488

@@ -2303,6 +2316,7 @@ private void DoBeginDisconnect(bool reuseSocket, DisconnectOverlappedAsyncResult
23032316
{
23042317
SetToDisconnected();
23052318
_remoteEndPoint = null;
2319+
_localEndPoint = null;
23062320
}
23072321

23082322
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"UnsafeNclNativeMethods.OSSOCK.DisConnectEx returns:{errorCode}");
@@ -2332,6 +2346,7 @@ public void Disconnect(bool reuseSocket)
23322346

23332347
SetToDisconnected();
23342348
_remoteEndPoint = null;
2349+
_localEndPoint = null;
23352350
}
23362351

23372352
// Routine Description:
@@ -2760,6 +2775,7 @@ private void DoBeginSendTo(byte[] buffer, int offset, int size, SocketFlags sock
27602775
catch (ObjectDisposedException)
27612776
{
27622777
_rightEndPoint = oldEndPoint;
2778+
_localEndPoint = null;
27632779
throw;
27642780
}
27652781

@@ -2769,6 +2785,7 @@ private void DoBeginSendTo(byte[] buffer, int offset, int size, SocketFlags sock
27692785
UpdateSendSocketErrorForDisposed(ref errorCode);
27702786
// Update the internal state of this socket according to the error before throwing.
27712787
_rightEndPoint = oldEndPoint;
2788+
_localEndPoint = null;
27722789

27732790
throw new SocketException((int)errorCode);
27742791
}
@@ -3148,6 +3165,7 @@ public IAsyncResult BeginReceiveMessageFrom(byte[] buffer, int offset, int size,
31483165
catch (ObjectDisposedException)
31493166
{
31503167
_rightEndPoint = oldEndPoint;
3168+
_localEndPoint = null;
31513169
throw;
31523170
}
31533171

@@ -3157,6 +3175,7 @@ public IAsyncResult BeginReceiveMessageFrom(byte[] buffer, int offset, int size,
31573175
{
31583176
// Update the internal state of this socket according to the error before throwing.
31593177
_rightEndPoint = oldEndPoint;
3178+
_localEndPoint = null;
31603179

31613180
throw new SocketException((int)errorCode);
31623181
}
@@ -3357,6 +3376,7 @@ private void DoBeginReceiveFrom(byte[] buffer, int offset, int size, SocketFlags
33573376
catch (ObjectDisposedException)
33583377
{
33593378
_rightEndPoint = oldEndPoint;
3379+
_localEndPoint = null;
33603380
throw;
33613381
}
33623382

@@ -3366,6 +3386,7 @@ private void DoBeginReceiveFrom(byte[] buffer, int offset, int size, SocketFlags
33663386
{
33673387
// Update the internal state of this socket according to the error before throwing.
33683388
_rightEndPoint = oldEndPoint;
3389+
_localEndPoint = null;
33693390

33703391
throw new SocketException((int)errorCode);
33713392
}
@@ -3762,6 +3783,7 @@ private bool ConnectAsync(SocketAsyncEventArgs e, bool userSocket)
37623783
catch
37633784
{
37643785
_rightEndPoint = oldEndPoint;
3786+
_localEndPoint = null;
37653787

37663788
// Clear in-use flag on event args object.
37673789
e.Complete();
@@ -4091,6 +4113,7 @@ public bool SendToAsync(SocketAsyncEventArgs e)
40914113
catch
40924114
{
40934115
_rightEndPoint = null;
4116+
_localEndPoint = null;
40944117
// Clear in-use flag on event args object.
40954118
e.Complete();
40964119
throw;
@@ -4099,6 +4122,7 @@ public bool SendToAsync(SocketAsyncEventArgs e)
40994122
if (!CheckErrorAndUpdateStatus(socketError))
41004123
{
41014124
_rightEndPoint = oldEndPoint;
4125+
_localEndPoint = null;
41024126
}
41034127

41044128
return socketError == SocketError.IOPending;
@@ -4619,6 +4643,7 @@ private IAsyncResult BeginConnectEx(EndPoint remoteEP, bool flowContext, AsyncCa
46194643
{
46204644
// _rightEndPoint will always equal oldEndPoint.
46214645
_rightEndPoint = oldEndPoint;
4646+
_localEndPoint = null;
46224647
throw;
46234648
}
46244649

@@ -4635,6 +4660,7 @@ private IAsyncResult BeginConnectEx(EndPoint remoteEP, bool flowContext, AsyncCa
46354660
UpdateConnectSocketErrorForDisposed(ref errorCode);
46364661
// Update the internal state of this socket according to the error before throwing.
46374662
_rightEndPoint = oldEndPoint;
4663+
_localEndPoint = null;
46384664

46394665
throw new SocketException((int)errorCode);
46404666
}
@@ -4858,6 +4884,12 @@ internal Socket UpdateAcceptSocket(Socket socket, EndPoint remoteEP)
48584884
socket._rightEndPoint = _rightEndPoint;
48594885
socket._remoteEndPoint = remoteEP;
48604886

4887+
// If the listener socket was bound to a wildcard address, then the `accept` system call
4888+
// will assign a specific address to the accept socket's local endpoint instead of a
4889+
// wildcard address. In that case we should not copy listener's wildcard local endpoint.
4890+
4891+
socket._localEndPoint = !IsWildcardEndPoint(_localEndPoint) ? _localEndPoint : null;
4892+
48614893
// The socket is connected.
48624894
socket.SetToConnected();
48634895

@@ -4889,9 +4921,38 @@ internal void SetToConnected()
48894921
// some point in time update the perf counter as well.
48904922
_isConnected = true;
48914923
_isDisconnected = false;
4924+
UpdateLocalEndPointOnConnect();
48924925
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, "now connected");
48934926
}
48944927

4928+
private void UpdateLocalEndPointOnConnect()
4929+
{
4930+
// If the client socket was bound to a wildcard address, then the `connect` system call
4931+
// will assign a specific address to the client socket's local endpoint instead of a
4932+
// wildcard address. In that case we should clear the cached wildcard local endpoint.
4933+
4934+
if (IsWildcardEndPoint(_localEndPoint))
4935+
{
4936+
_localEndPoint = null;
4937+
}
4938+
}
4939+
4940+
private bool IsWildcardEndPoint(EndPoint? endPoint)
4941+
{
4942+
if (endPoint == null)
4943+
{
4944+
return false;
4945+
}
4946+
4947+
if (endPoint is IPEndPoint ipEndpoint)
4948+
{
4949+
IPAddress address = ipEndpoint.Address;
4950+
return IPAddress.Any.Equals(address) || IPAddress.IPv6Any.Equals(address) || s_IPAddressAnyMapToIPv6.Equals(address);
4951+
}
4952+
4953+
return false;
4954+
}
4955+
48954956
internal void SetToDisconnected()
48964957
{
48974958
if (!_isConnected)

0 commit comments

Comments
 (0)