Skip to content

[API Proposal]: make SocketAddress more useful  #86872

@wfurt

Description

@wfurt

Background and motivation

The SocketAddress exists for long time but it is pretty useless at this point. It allows to Create and Serialize EndPoint to buffer that is passable to operation system and it allows read/write access to underlying buffer via indexing.

However, as noted in #78757 there is no convenient access via Span or Memory and there is no way how to pin the buffer so it can actually (via handle or fixed) be passable to OS call.

To solve this limitation, Socket code use Internals.SocketAddress and there are extra steps and allocations to convert from EndPoint to buffer and we do sometimes extra processing to covert Internals.SocketAddress to SocketAddress to get the actual EndPoint. That brings unnecessary allocations and work to core Socket functions. It would be really nice to avoid all this and simply use public API on SocketAddress to do everything we need. While #78993 would help in many cases Socket code also needs to deal with dual mode sockets and mapping between IP address families.

Note that SocketAddress lives in System.Net.Primitives and System.Net.Socket code does not have convenient access to internal methods.

To be able to handle UDP processing efficiently, #30797 is proposing to add overloads with SocketAddress and it would be nice to expose some of the functionality that Socket currently has.

Lastly, #86513 noted some issues around using GCHandle. It would be nice to be able to use NativeMemory in some cases to avoid pinning in managed code.

API Proposal

namespace System.Net;

-public class SocketAddress
+public class SocketAddress : IEquatable<SocketAddress>
{
-     public int Size { get; }
+     public int Size { get; set; }
+     public static int GetMaximumAddressSize(AddressFamily addressFamily);
+     public System.Memory<byte> SocketBuffer { get; set; }
}

This is optional part to make some convenience helper functions. For Socket refactoring we can use private attic extension e.g. ability to have efficient access to underlying data and ability to shrink given buffer to actual addresses is sufficient.

SocketAddress already has Equals method e.g. the IEquatable is just to make it more obvious. Currently, it can only find equality with another SocketAddress. We could also make it work for known EndPoint type and use allocating Serialize method to SocketAddress as fallback.

namespace System.Net;

public class SocketAddress
{

+     public bool TryGetAddress(out System.Int128 address, out long scopeid) { throw null; }
+     public bool TryGetPort(out int port) { throw null; }
+     public bool TryWriteAddressBytes(Span<byte> destination, out int bytesWritten) { throw null; }
}

API Usage

UDP server

SocketAddress sa = new SocketAddress(socket.AddressFamily)
byte[] buffer = new byte[MaxBuffer];
while (true)
{
    int len = socket.ReceiveFrom(buffer, SocketFlags.None, sa);
    if (len > 0) 
    {
        ProcessMessage(buffer, sa.SocketBuffer.Span);
    } 
}

unsafe void ProcessMessage(byte[] buffer, readOnlySpan<byte> socketAddressBuffer)
{ 
  fixed (void *ptr = socketAddressBuffer)
  {
       p/invoke to native with ptr
  }
   ... 
   ... 
}

UDP client

   socket.ReceiveFrom(rawReceiveBuffer, buffer, newClientSA);
   newClientSA.TryGetPort(out int port);
   newClientSA. TryGetAddress(out int128 address, out long scope);
   var connectionId = Common.GetId(address, scope, port);

Crucial part is ability to get/set the bytes efficiently and ability to shrink the address. The current assumption is that setting Size would not allow to set it to something bigger than allocated buffer e.g. string the address.

on DualMode Socket we can receive IPv4 or IPv6 address. We need space for either one but at the end we may need less than allocated. It may be OK to pass more data to Create method but it feels it would be nice to have the prices length as the OS functions would typically provide that.

SocketAddress sa = new SocketAddress(AddressFamily. InterNetworkV6)
/// native receive call ....
if (sa.Family ==  AddressFamily.InterNetwork)
{
  sa.Size = GetSocketAddressSize(AddressFamily. InterNetwork);
}

TryGetPort make sense only IP protocol at the moment. TryGetAddress is similar but it may return something in future - like for VSock or Netlink where the address is process ID.

TryWriteAddressBytes follow similar similar method on IPAddress. for UnixDomainSocket it would return the sun_path of sockaddr_un e.g. it can be useful even in cases when address is not in numerical form.

All three method above are for convenience and could be implemented externally if needed. While we already have implementation and IP protocol seems vastly prevalent they can be useful for anybody who wants to avoid allocation of IPEndPoint just to do logging or some trivial functionality.

Alternative Designs

As noted above, #78993 may be good alternative in many cases. However it would not be sufficient for the new UDP methods as that plan is to avoid EndPoint completely as well as it does help with mappings for dual mode.

Risks

The primarily goal is to provide sufficient surface that we already have internally.

Metadata

Metadata

Assignees

Labels

api-approvedAPI was approved in API review, it can be implementedarea-System.Net.SocketsblockingMarks issues that we want to fast track in order to unblock other important work

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions